inotify/inotify.go

301 lines
5.5 KiB
Go
Raw Normal View History

2020-05-26 10:47:00 +08:00
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()
}