package watcher import ( "syscall" "unsafe" "github.com/pkg/errors" ) // CrDelMask predefined constant with create and delete events. const CrDelMask uint32 = syscall.IN_CREATE | syscall.IN_DELETE // inotifyCount is amount of InotifyEvent structures // we read in WatchForMask(). 16 was chosen experimentally. // Use of sparse files (with preoccupied space using Seek()) // allows us to have a little buffer. With smaller number // events are often missing. const inotifyCount = 16 type InotifyWatcher struct { fd int wds []int closed bool } // NewInotifyWatcher initialises inotify mechanism and returns // an instance of InotifyWatcher. func NewInotifyWatcher() (w *InotifyWatcher, err error) { w = &InotifyWatcher{closed: false} w.fd, err = syscall.InotifyInit() if err != nil { return nil, errors.Wrap(err, "failed to initialise inotify watcher") } w.wds = make([]int, 0) return w, nil } // AddWatch sets on watch file or directory specified in `path` // for syscall.IN_XXXX events specified in `mask`. func (w *InotifyWatcher) AddWatch(path string, mask uint32) error { wd, err := syscall.InotifyAddWatch(w.fd, path, mask) if err != nil { return errors.Wrapf(err, "failed to set %s on watch", path) } w.wds = append(w.wds, wd) return nil } // WatchForMask checks for events specified in `mask` and sends them // to channel `fired`. See `man inotify` for events. func (w *InotifyWatcher) WatchForMask(fired chan uint32, mask uint32) { for !w.closed { buffer := make([]byte, syscall.SizeofInotifyEvent*inotifyCount) n, err := syscall.Read(w.fd, buffer) if err != nil { break } if n < syscall.SizeofInotifyEvent { continue } for offset := 0; offset < len(buffer); offset += syscall.SizeofInotifyEvent { event := (*syscall.InotifyEvent)(unsafe.Pointer(&buffer[offset])) if event.Mask&mask > 0 { fired <- event.Mask } } } } // Close removes all watchers, closes inotify descriptor and stops all // WatchForMask() event loops. func (w *InotifyWatcher) Close() error { for _, wd := range w.wds { if _, err := syscall.InotifyRmWatch(w.fd, uint32(wd)); err != nil { return err } } if err := syscall.Close(w.fd); err != nil { return err } w.closed = true return nil }