package inotify import ( "fmt" "os" "path/filepath" "strings" "sync" "syscall" "unsafe" "golang.org/x/sys/unix" ) var ErrEventOverflow = fmt.Errorf("inotify event queue overflowed") type Event struct { Name string Path string IsDir bool } type Watcher struct { Events chan Event Errors chan error fd int poller *poller pW map[int32]watch wP map[string]int wPLock *sync.Mutex pWLock *sync.Mutex done chan bool isClosed bool } func (w *Watcher) Path() []string { paths := make([]string, 0) if w.isClosed { return paths } w.wPLock.Lock() defer w.wPLock.Unlock() for path := range w.wP { paths = append(paths, path) } return paths } func NewWatcher() (*Watcher, error) { fd, err := unix.InotifyInit1(unix.IN_NONBLOCK) if err != nil { return nil, err } poller, err := newPoller(fd) if err != nil { return nil, err } w := &Watcher{ fd: fd, poller: poller, Events: make(chan Event), Errors: make(chan error), pW: make(map[int32]watch), pWLock: new(sync.Mutex), wP: make(map[string]int), wPLock: new(sync.Mutex), done: make(chan bool), } go w.eventLoop() return w, nil } const bufsize = unix.SizeofInotifyEvent + syscall.NAME_MAX + 1 type eventBuf [bufsize]byte func (w *Watcher) readEvent(buf eventBuf, n int) { var offset uint32 for offset <= uint32(n-unix.SizeofInotifyEvent) { event := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) if event.Mask&unix.IN_Q_OVERFLOW == unix.IN_Q_OVERFLOW { w.Errors <- ErrEventOverflow break } var name string w.pWLock.Lock() path := w.pW[event.Wd] w.pWLock.Unlock() if event.Len > 0 { rawName := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:event.Len:event.Len] name = strings.TrimRight(string(rawName[0:event.Len]), "\000") } var isDir bool if isDeleteSelf(event.Mask) { isDir = path.isDir w.remove(event.Wd) } else { isDir = isDirectory(event.Mask) } if !isIgnore(event.Mask) { w.Events <- Event{ Name: maskString(event.Mask), Path: filepath.Join(path.name, name), IsDir: isDir, } } offset += event.Len + unix.SizeofInotifyEvent } } func (w *Watcher) Close() { if w.isClosed { return } w.poller.close() w.done <- true close(w.Errors) close(w.Events) close(w.done) } func (w *Watcher) readEventLoop() { var ( n int err error buf eventBuf ) for { select { case <-w.done: return default: } n, err = unix.Read(w.fd, buf[:]) if err != nil { if err == unix.EINTR { break } if err == unix.EAGAIN { break } w.Errors <- err continue } if n < unix.SizeofInotifyEvent { w.Errors <- fmt.Errorf("event is too short") break } w.readEvent(buf, n) } } func (w *Watcher) eventLoop() { var ( ok bool err error ) for { ok, err = w.poller.wait() if err != nil { w.Errors <- err continue } if !ok { continue } w.readEventLoop() } } func (w *Watcher) remove(wd int32) { w.pWLock.Lock() defer w.pWLock.Unlock() name, ok := w.pW[wd] if !ok { return } delete(w.pW, wd) w.wPLock.Lock() defer w.wPLock.Unlock() delete(w.wP, name.name) } type watch struct { name string isDir bool } func (w *Watcher) AddWatch(name string) error { name = filepath.Clean(name) wd, err := unix.InotifyAddWatch(w.fd, name, unix.IN_ALL_EVENTS) if err != nil { return err } w.pWLock.Lock() w.pW[int32(wd)] = watch{name: name, isDir: !isExistFile(name)} w.pWLock.Unlock() w.wPLock.Lock() w.wP[name] = wd w.wPLock.Unlock() return nil } func (w *Watcher) RemoveWatch(name string) error { name = filepath.Clean(name) w.wPLock.Lock() defer w.wPLock.Unlock() wd, ok := w.wP[name] if !ok { return fmt.Errorf("can't remove non-existent inotify watch for: %s", name) } delete(w.wP, name) w.pWLock.Lock() defer w.pWLock.Unlock() delete(w.pW, int32(wd)) _, err := unix.InotifyRmWatch(w.fd, uint32(wd)) return err } func maskString(mask uint32) string { for key, value := range eventName { if key&mask != 0 { return value } } return EventUnknown } const ( EventAccess = "IN_ACCESS" EventAttrib = "IN_ATTRIB" EventCloseWrite = "IN_CLOSE_WRITE" EventCloseNoWrite = "IN_CLOSE_NOWRITE" EventCreate = "IN_CREATE" EventDelete = "IN_DELETE" EventDeleteSelf = "IN_DELETE_SELF" EventModify = "IN_MODIFY" EventMoveSelf = "IN_MOVE_SELF" EventMovedFrom = "IN_MOVED_FROM" EventMovedTo = "IN_MOVED_TO" EventOpen = "IN_OPEN" EventIgnored = "IN_IGNORED" EventUnmount = "IN_UNMOUNT" EventUnknown = "UNKNOWN" ) var eventName = map[uint32]string{ unix.IN_ACCESS: EventAccess, unix.IN_ATTRIB: EventAttrib, unix.IN_CLOSE_WRITE: EventCloseWrite, unix.IN_CLOSE_NOWRITE: EventCloseNoWrite, unix.IN_CREATE: EventCreate, unix.IN_DELETE: EventDelete, unix.IN_DELETE_SELF: EventDeleteSelf, unix.IN_MODIFY: EventModify, unix.IN_MOVE_SELF: EventMoveSelf, unix.IN_MOVED_FROM: EventMovedFrom, unix.IN_MOVED_TO: EventMovedTo, unix.IN_OPEN: EventOpen, unix.IN_IGNORED: EventIgnored, unix.IN_UNMOUNT: EventUnmount, } func isDirectory(mask uint32) bool { return mask&unix.IN_ISDIR == unix.IN_ISDIR } func isIgnore(mask uint32) bool { return mask&unix.IN_IGNORED == unix.IN_IGNORED } func isDeleteSelf(mask uint32) bool { return mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF } func isExistFile(name string) bool { f, err := os.Stat(name) return err == nil && !f.IsDir() }