1
0
Fork 0
dwelling-upload/pkg/watcher/linux.go

94 lines
2.2 KiB
Go

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
}