diff --git a/pkg/watcher/linux.go b/pkg/watcher/linux.go new file mode 100644 index 0000000..864a7cf --- /dev/null +++ b/pkg/watcher/linux.go @@ -0,0 +1,85 @@ +package watcher + +import ( + "syscall" + "unsafe" + + "github.com/pkg/errors" +) + +const ( + CrDelMask uint32 = syscall.IN_CREATE | syscall.IN_DELETE + ModMask uint32 = syscall.IN_MODIFY +) + +const inotifyCount = 16 + +type InotifyWatcher struct { + fd int + wds []int + closed bool +} + +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 +} + +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 checking for events from mask and returns inotify mask to channel. +func (w *InotifyWatcher) WatchForMask(fired chan uint32, mask uint32) { + go func() { + 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 + } + } + } + }() +} + +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 +}