// Copyright 2015-2017 Luke Shumaker . // // This is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 2.1 of the // License, or (at your option) any later version. // // This software is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this manual; if not, see // . // Package inotify provides an interface to the Linux inotify system. // The inotify system is a mechanism for monitoring filesystem events. package inotify import ( "os" "sync" "unsafe" "golang.org/x/sys/unix" ) // Most of the complexity here is that we syncronize around access to // the file descriptor. Why? Because we're worried that it could get // closed and re-used between the time the fd is read from the os.File // and the time the system call is actually dispatched. // // A B // in.method() in.Close(); unix.Open() // ----------------------------------------- // fd = in.fd // unix.Close() // unix.Open() // unix.Syscall(fd) // // And you're wondering "should we really protoect against that; after // all: share by communicating, don't communicate by sharing" and "why // do we have to deal with this condition if os.File doesn't?" // Because an inotify instance is more like a net.Listener. If you // notice, net.netFD has do deal with a very similar situtation. At // some level, you may simply observe that with inotify it makes sense // to have one goroutine add/remove watches, and have another read // them; where this kind of communication doesn't make sense with // ordinary files. // // So, how does net.netFD deal with it? Well... it is tightly coupled // with some private routines in runtime/sema.go. That is, we can't // look at it for too much guidance. type Inotify struct { fd inFd fdLock sync.RWMutex fdBlock bool // The bare binimum size of the buffer is // // unix.SizeofInotifyEvent + unix.NAME_MAX + 1 // // But we don't want the bare minimum. 4KiB is a page size. buffFull [4096]byte buff []byte buffLock sync.Mutex buffErr error ch chan chev } // Event is a file system event from inotify. // // An Event is invalid if Wd is < 0. type Event struct { Wd Wd // Watch descriptor Mask Mask // Mask describing event Cookie uint32 // Unique cookie associating related events (for rename(2)) Name *string // Optional name } type chev struct { Ev Event Err error } func newInotify(fd inFd, blocking bool) *Inotify { if fd < 0 { return nil } in := &Inotify{ fd: fd, fdBlock: blocking, ch: make(chan chev), } in.buff = in.buffFull[:0] go in.worker() return in } // NewInotify creates an Inotify object that wraps an inotify instance // on an already open file descriptor, much like net.FileListener. // // Closing file does not affect the Inotify, and closing Inotify does // not affect file. func NewInotify(file *os.File) (*Inotify, error) { dup, err := sys_dupfd_cloexec(inFd(file.Fd())) if err != nil { return nil, err } nonblock, err := sys_getnonblock(dup) if err != nil { sys_close(dup) return nil, err } err = sys_setnonblock(dup, false) if err != nil { sys_close(dup) return nil, err } return newInotify(dup, !nonblock), nil } // InotifyInit creates an inotify instance. The variant // InotifyInit1() allows flags to access extra functionality. func InotifyInit() (*Inotify, error) { fd, err := sys_inotify_init() return newInotify(fd, true), err } // InotifyInit1 create an inotify instance, with flags specifying // extra functionality. func InotifyInit1(flags int) (*Inotify, error) { fd, err := sys_inotify_init1(flags &^ IN_NONBLOCK) return newInotify(fd, flags&IN_NONBLOCK == 0), err } // AddWatch adds a watch to the inotify instance, or modifies an // existing watch item. func (in *Inotify) AddWatch(path string, mask Mask) (Wd, error) { in.fdLock.RLock() defer in.fdLock.RUnlock() return sys_inotify_add_watch(in.fd, path, mask) } // RmWatch removes a watch from the inotify instance. func (in *Inotify) RmWatch(wd Wd) error { in.fdLock.RLock() defer in.fdLock.RUnlock() return sys_inotify_rm_watch(in.fd, wd) } // Close closes the inotify instance; further calls to this object // will error. // // In the event of a double-close condition, in go <= 1.7, this // returns an os.SyscallError wrapping unix.EBADF; on go >= 1.8, it // returns os.ErrClosed. func (in *Inotify) Close() (err error) { defer func() { if r := recover(); r != nil { err = errClosed } }() close(in.ch) // will panic if already closed; hence above // I would love to be able to return the error from sys_close; // but it might block forever. go func() { in.fdLock.Lock() in.fdLock.Unlock() sys_close(in.fd) }() return } // read is a low-level read an event from the inotify instance. // // It's low-level/private because it can deadlock between it and the // `go` block in Close. Instead, a worker calls this in a loop, // writes the result to a channel, and a public reader can safely get // it from the channel. No deadlock. func (in *Inotify) read() (Event, error) { in.buffLock.Lock() defer in.buffLock.Unlock() if len(in.buff) == 0 { if in.buffErr != nil { return Event{Wd: -1}, in.buffErr } var n int in.fdLock.RLock() n, in.buffErr = sys_read(in.fd, in.buffFull[:]) in.fdLock.RUnlock() in.buff = in.buffFull[0:n] } if len(in.buff) < unix.SizeofInotifyEvent { // Either Linux screwed up (and we have no chance of // handling that sanely), or this Inotify came from an // existing FD that wasn't really an inotify instance. in.buffErr = unix.EBADF return Event{Wd: -1}, in.buffErr } raw := (*unix.InotifyEvent)(unsafe.Pointer(&in.buff[0])) ret := Event{ Wd: Wd(raw.Wd), Mask: Mask(raw.Mask), Cookie: raw.Cookie, Name: nil, } if int64(len(in.buff)) < unix.SizeofInotifyEvent+int64(raw.Len) { // Same as above. in.buffErr = unix.EBADF return Event{Wd: -1}, in.buffErr } if raw.Len > 0 { bytes := (*[unix.NAME_MAX]byte)(unsafe.Pointer(&in.buff[unix.SizeofInotifyEvent])) name := string(bytes[:raw.Len-1]) ret.Name = &name } in.buff = in.buff[0 : unix.SizeofInotifyEvent+raw.Len] return ret, nil } func (in *Inotify) worker() { defer recover() for { ev, err := in.read() in.ch <- chev{ev, err} // will panic on .Close() } } // Read calls either ReadBlock or ReadNonblock, depending on whether // the Inotify instance has the IN_NONBLOCK flag set. func (in *Inotify) Read() (Event, error) { if in.fdBlock { return in.ReadBlock() } else { return in.ReadNonblock() } } // ReadBlock reads exactly one Event or an error from the inotify // instance. If an Event or error is not available for reading, it // blocks until one is. // // If err is non-nil, then the Event is invalid (ev.Wd < 0); and if // err is nil, then then the Event is valid. func (in *Inotify) ReadBlock() (Event, error) { e, ok := <-in.ch if !ok { return Event{Wd: -1}, os.NewSyscallError("read", unix.EBADF) } return e.Ev, e.Err } // ReadNonblock reads either an Event or an error from the inotify // instance, but doesn't block if an event isn't ready. // // Unlike Read, it may be the case that both the Event is invalid and // the error is nil. This indicates that the read simply returned // instead of blocking. // // ev, err := in.ReadNonblock() // haveRead = ev.Wd >= 0 || err != nil func (in *Inotify) ReadNonblock() (Event, error) { select { case e, ok := <-in.ch: if !ok { return Event{Wd: -1}, os.NewSyscallError("read", unix.EBADF) } return e.Ev, e.Err default: return Event{Wd: -1}, nil } }