301 lines
5.5 KiB
Go
301 lines
5.5 KiB
Go
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()
|
|
}
|