|
@@ -14,46 +14,48 @@ import (
|
|
|
"path/filepath"
|
|
|
"sync"
|
|
|
"syscall"
|
|
|
+ "time"
|
|
|
)
|
|
|
|
|
|
// Watcher watches a set of files, delivering events to a channel.
|
|
|
type Watcher struct {
|
|
|
- Events chan Event
|
|
|
- Errors chan error
|
|
|
- mu sync.Mutex // Mutex for the Watcher itself.
|
|
|
- kq int // File descriptor (as returned by the kqueue() syscall).
|
|
|
- watches map[string]int // Map of watched file descriptors (key: path).
|
|
|
- wmut sync.Mutex // Protects access to watches.
|
|
|
- enFlags map[string]uint32 // Map of watched files to evfilt note flags used in kqueue.
|
|
|
- enmut sync.Mutex // Protects access to enFlags.
|
|
|
- paths map[int]string // Map of watched paths (key: watch descriptor).
|
|
|
- finfo map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor).
|
|
|
- pmut sync.Mutex // Protects access to paths and finfo.
|
|
|
- fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
|
|
|
- femut sync.Mutex // Protects access to fileExists.
|
|
|
- externalWatches map[string]bool // Map of watches added by user of the library.
|
|
|
- ewmut sync.Mutex // Protects access to externalWatches.
|
|
|
- done chan bool // Channel for sending a "quit message" to the reader goroutine
|
|
|
- isClosed bool // Set to true when Close() is first called
|
|
|
+ Events chan Event
|
|
|
+ Errors chan error
|
|
|
+ done chan bool // Channel for sending a "quit message" to the reader goroutine
|
|
|
+
|
|
|
+ kq int // File descriptor (as returned by the kqueue() syscall).
|
|
|
+
|
|
|
+ mu sync.Mutex // Protects access to watcher data
|
|
|
+ watches map[string]int // Map of watched file descriptors (key: path).
|
|
|
+ externalWatches map[string]bool // Map of watches added by user of the library.
|
|
|
+ dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
|
|
|
+ paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
|
|
|
+ fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
|
|
|
+ isClosed bool // Set to true when Close() is first called
|
|
|
+}
|
|
|
+
|
|
|
+type pathInfo struct {
|
|
|
+ name string
|
|
|
+ isDir bool
|
|
|
}
|
|
|
|
|
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
|
|
func NewWatcher() (*Watcher, error) {
|
|
|
- fd, errno := syscall.Kqueue()
|
|
|
- if fd == -1 {
|
|
|
- return nil, os.NewSyscallError("kqueue", errno)
|
|
|
+ kq, err := kqueue()
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
}
|
|
|
+
|
|
|
w := &Watcher{
|
|
|
- kq: fd,
|
|
|
+ kq: kq,
|
|
|
watches: make(map[string]int),
|
|
|
- enFlags: make(map[string]uint32),
|
|
|
- paths: make(map[int]string),
|
|
|
- finfo: make(map[int]os.FileInfo),
|
|
|
+ dirFlags: make(map[string]uint32),
|
|
|
+ paths: make(map[int]pathInfo),
|
|
|
fileExists: make(map[string]bool),
|
|
|
externalWatches: make(map[string]bool),
|
|
|
Events: make(chan Event),
|
|
|
Errors: make(chan error),
|
|
|
- done: make(chan bool, 1),
|
|
|
+ done: make(chan bool),
|
|
|
}
|
|
|
|
|
|
go w.readEvents()
|
|
@@ -70,73 +72,68 @@ func (w *Watcher) Close() error {
|
|
|
w.isClosed = true
|
|
|
w.mu.Unlock()
|
|
|
|
|
|
- // Send "quit" message to the reader goroutine:
|
|
|
- w.done <- true
|
|
|
- w.wmut.Lock()
|
|
|
+ w.mu.Lock()
|
|
|
ws := w.watches
|
|
|
- w.wmut.Unlock()
|
|
|
+ w.mu.Unlock()
|
|
|
+
|
|
|
+ var err error
|
|
|
for name := range ws {
|
|
|
- w.Remove(name)
|
|
|
+ if e := w.Remove(name); e != nil && err == nil {
|
|
|
+ err = e
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ // Send "quit" message to the reader goroutine:
|
|
|
+ w.done <- true
|
|
|
+
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
// Add starts watching the named file or directory (non-recursively).
|
|
|
func (w *Watcher) Add(name string) error {
|
|
|
- w.ewmut.Lock()
|
|
|
+ w.mu.Lock()
|
|
|
w.externalWatches[name] = true
|
|
|
- w.ewmut.Unlock()
|
|
|
+ w.mu.Unlock()
|
|
|
return w.addWatch(name, noteAllEvents)
|
|
|
}
|
|
|
|
|
|
// Remove stops watching the the named file or directory (non-recursively).
|
|
|
func (w *Watcher) Remove(name string) error {
|
|
|
name = filepath.Clean(name)
|
|
|
- w.wmut.Lock()
|
|
|
+ w.mu.Lock()
|
|
|
watchfd, ok := w.watches[name]
|
|
|
- w.wmut.Unlock()
|
|
|
+ w.mu.Unlock()
|
|
|
if !ok {
|
|
|
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
|
|
|
}
|
|
|
- var kbuf [1]syscall.Kevent_t
|
|
|
- watchEntry := &kbuf[0]
|
|
|
- syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_DELETE)
|
|
|
- entryFlags := watchEntry.Flags
|
|
|
- success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil)
|
|
|
- if success == -1 {
|
|
|
- return os.NewSyscallError("kevent_rm_watch", errno)
|
|
|
- } else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR {
|
|
|
- return errors.New("kevent rm error")
|
|
|
+
|
|
|
+ const registerRemove = syscall.EV_DELETE
|
|
|
+ if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
|
|
|
+ return err
|
|
|
}
|
|
|
+
|
|
|
syscall.Close(watchfd)
|
|
|
- w.wmut.Lock()
|
|
|
+
|
|
|
+ w.mu.Lock()
|
|
|
+ isDir := w.paths[watchfd].isDir
|
|
|
delete(w.watches, name)
|
|
|
- w.wmut.Unlock()
|
|
|
- w.enmut.Lock()
|
|
|
- delete(w.enFlags, name)
|
|
|
- w.enmut.Unlock()
|
|
|
- w.pmut.Lock()
|
|
|
delete(w.paths, watchfd)
|
|
|
- fInfo := w.finfo[watchfd]
|
|
|
- delete(w.finfo, watchfd)
|
|
|
- w.pmut.Unlock()
|
|
|
+ delete(w.dirFlags, name)
|
|
|
+ w.mu.Unlock()
|
|
|
|
|
|
// Find all watched paths that are in this directory that are not external.
|
|
|
- if fInfo.IsDir() {
|
|
|
+ if isDir {
|
|
|
var pathsToRemove []string
|
|
|
- w.pmut.Lock()
|
|
|
- for _, wpath := range w.paths {
|
|
|
- wdir, _ := filepath.Split(wpath)
|
|
|
- if filepath.Clean(wdir) == filepath.Clean(name) {
|
|
|
- w.ewmut.Lock()
|
|
|
- if !w.externalWatches[wpath] {
|
|
|
- pathsToRemove = append(pathsToRemove, wpath)
|
|
|
+ w.mu.Lock()
|
|
|
+ for _, path := range w.paths {
|
|
|
+ wdir, _ := filepath.Split(path.name)
|
|
|
+ if filepath.Clean(wdir) == name {
|
|
|
+ if !w.externalWatches[path.name] {
|
|
|
+ pathsToRemove = append(pathsToRemove, path.name)
|
|
|
}
|
|
|
- w.ewmut.Unlock()
|
|
|
}
|
|
|
}
|
|
|
- w.pmut.Unlock()
|
|
|
+ w.mu.Unlock()
|
|
|
for _, name := range pathsToRemove {
|
|
|
// Since these are internal, not much sense in propagating error
|
|
|
// to the user, as that will just confuse them with an error about
|
|
@@ -148,37 +145,38 @@ func (w *Watcher) Remove(name string) error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-const (
|
|
|
- // Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
|
|
- noteAllEvents = syscall.NOTE_DELETE | syscall.NOTE_WRITE | syscall.NOTE_ATTRIB | syscall.NOTE_RENAME
|
|
|
+// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
|
|
+const noteAllEvents = syscall.NOTE_DELETE | syscall.NOTE_WRITE | syscall.NOTE_ATTRIB | syscall.NOTE_RENAME
|
|
|
|
|
|
- // Block for 100 ms on each call to kevent
|
|
|
- keventWaitTime = 100e6
|
|
|
-)
|
|
|
+// keventWaitTime to block on each read from kevent
|
|
|
+var keventWaitTime = durationToTimespec(100 * time.Millisecond)
|
|
|
|
|
|
-// addWatch adds path to the watched file set.
|
|
|
+// addWatch adds name to the watched file set.
|
|
|
// The flags are interpreted as described in kevent(2).
|
|
|
-func (w *Watcher) addWatch(path string, flags uint32) error {
|
|
|
- path = filepath.Clean(path)
|
|
|
+func (w *Watcher) addWatch(name string, flags uint32) error {
|
|
|
+ var isDir bool
|
|
|
+ // Make ./name and name equivalent
|
|
|
+ name = filepath.Clean(name)
|
|
|
+
|
|
|
w.mu.Lock()
|
|
|
if w.isClosed {
|
|
|
w.mu.Unlock()
|
|
|
return errors.New("kevent instance already closed")
|
|
|
}
|
|
|
+ watchfd, alreadyWatching := w.watches[name]
|
|
|
+ // We already have a watch, but we can still override flags.
|
|
|
+ if alreadyWatching {
|
|
|
+ isDir = w.paths[watchfd].isDir
|
|
|
+ }
|
|
|
w.mu.Unlock()
|
|
|
|
|
|
- watchDir := false
|
|
|
-
|
|
|
- w.wmut.Lock()
|
|
|
- watchfd, found := w.watches[path]
|
|
|
- w.wmut.Unlock()
|
|
|
- if !found {
|
|
|
- fi, errstat := os.Lstat(path)
|
|
|
- if errstat != nil {
|
|
|
- return errstat
|
|
|
+ if !alreadyWatching {
|
|
|
+ fi, err := os.Lstat(name)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
}
|
|
|
|
|
|
- // don't watch socket
|
|
|
+ // Don't watch sockets.
|
|
|
if fi.Mode()&os.ModeSocket == os.ModeSocket {
|
|
|
return nil
|
|
|
}
|
|
@@ -190,131 +188,96 @@ func (w *Watcher) addWatch(path string, flags uint32) error {
|
|
|
// be no file events for broken symlinks.
|
|
|
// Hence the returns of nil on errors.
|
|
|
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
|
|
- path, err := filepath.EvalSymlinks(path)
|
|
|
+ name, err = filepath.EvalSymlinks(name)
|
|
|
if err != nil {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
- fi, errstat = os.Lstat(path)
|
|
|
- if errstat != nil {
|
|
|
+ fi, err = os.Lstat(name)
|
|
|
+ if err != nil {
|
|
|
return nil
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- fd, errno := syscall.Open(path, openMode, 0700)
|
|
|
- if fd == -1 {
|
|
|
- return os.NewSyscallError("Open", errno)
|
|
|
+ watchfd, err = syscall.Open(name, openMode, 0700)
|
|
|
+ if watchfd == -1 {
|
|
|
+ return err
|
|
|
}
|
|
|
- watchfd = fd
|
|
|
|
|
|
- w.wmut.Lock()
|
|
|
- w.watches[path] = watchfd
|
|
|
- w.wmut.Unlock()
|
|
|
-
|
|
|
- w.pmut.Lock()
|
|
|
- w.paths[watchfd] = path
|
|
|
- w.finfo[watchfd] = fi
|
|
|
- w.pmut.Unlock()
|
|
|
+ isDir = fi.IsDir()
|
|
|
}
|
|
|
- // Watch the directory if it has not been watched before.
|
|
|
- w.pmut.Lock()
|
|
|
- w.enmut.Lock()
|
|
|
- if w.finfo[watchfd].IsDir() &&
|
|
|
- (flags&syscall.NOTE_WRITE) == syscall.NOTE_WRITE &&
|
|
|
- (!found || (w.enFlags[path]&syscall.NOTE_WRITE) != syscall.NOTE_WRITE) {
|
|
|
- watchDir = true
|
|
|
+
|
|
|
+ const registerAdd = syscall.EV_ADD | syscall.EV_CLEAR | syscall.EV_ENABLE
|
|
|
+ if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
|
|
|
+ syscall.Close(watchfd)
|
|
|
+ return err
|
|
|
}
|
|
|
- w.enmut.Unlock()
|
|
|
- w.pmut.Unlock()
|
|
|
-
|
|
|
- w.enmut.Lock()
|
|
|
- w.enFlags[path] = flags
|
|
|
- w.enmut.Unlock()
|
|
|
-
|
|
|
- var kbuf [1]syscall.Kevent_t
|
|
|
- watchEntry := &kbuf[0]
|
|
|
- watchEntry.Fflags = flags
|
|
|
- syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_ADD|syscall.EV_CLEAR)
|
|
|
- entryFlags := watchEntry.Flags
|
|
|
- success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil)
|
|
|
- if success == -1 {
|
|
|
- return errno
|
|
|
- } else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR {
|
|
|
- return errors.New("kevent add error")
|
|
|
+
|
|
|
+ if !alreadyWatching {
|
|
|
+ w.mu.Lock()
|
|
|
+ w.watches[name] = watchfd
|
|
|
+ w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
|
|
|
+ w.mu.Unlock()
|
|
|
}
|
|
|
|
|
|
- if watchDir {
|
|
|
- errdir := w.watchDirectoryFiles(path)
|
|
|
- if errdir != nil {
|
|
|
- return errdir
|
|
|
+ if isDir {
|
|
|
+ // Watch the directory if it has not been watched before,
|
|
|
+ // or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
|
|
+ w.mu.Lock()
|
|
|
+ watchDir := (flags&syscall.NOTE_WRITE) == syscall.NOTE_WRITE &&
|
|
|
+ (!alreadyWatching || (w.dirFlags[name]&syscall.NOTE_WRITE) != syscall.NOTE_WRITE)
|
|
|
+ // Store flags so this watch can be updated later
|
|
|
+ w.dirFlags[name] = flags
|
|
|
+ w.mu.Unlock()
|
|
|
+
|
|
|
+ if watchDir {
|
|
|
+ if err := w.watchDirectoryFiles(name); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// readEvents reads from the kqueue file descriptor, converts the
|
|
|
-// received events into Event objects and sends them via the Events channel
|
|
|
+// readEvents reads from kqueue and converts the received kevents into
|
|
|
+// Event values that it sends down the Events channel.
|
|
|
func (w *Watcher) readEvents() {
|
|
|
- var (
|
|
|
- keventbuf [10]syscall.Kevent_t // Event buffer
|
|
|
- kevents []syscall.Kevent_t // Received events
|
|
|
- twait *syscall.Timespec // Time to block waiting for events
|
|
|
- n int // Number of events returned from kevent
|
|
|
- errno error // Syscall errno
|
|
|
- )
|
|
|
- kevents = keventbuf[0:0]
|
|
|
- twait = new(syscall.Timespec)
|
|
|
- *twait = syscall.NsecToTimespec(keventWaitTime)
|
|
|
+ eventBuffer := make([]syscall.Kevent_t, 10)
|
|
|
|
|
|
for {
|
|
|
// See if there is a message on the "done" channel
|
|
|
- var done bool
|
|
|
select {
|
|
|
- case done = <-w.done:
|
|
|
- default:
|
|
|
- }
|
|
|
-
|
|
|
- // If "done" message is received
|
|
|
- if done {
|
|
|
- errno := syscall.Close(w.kq)
|
|
|
- if errno != nil {
|
|
|
- w.Errors <- os.NewSyscallError("close", errno)
|
|
|
+ case <-w.done:
|
|
|
+ err := syscall.Close(w.kq)
|
|
|
+ if err != nil {
|
|
|
+ w.Errors <- err
|
|
|
}
|
|
|
close(w.Events)
|
|
|
close(w.Errors)
|
|
|
return
|
|
|
+ default:
|
|
|
}
|
|
|
|
|
|
// Get new events
|
|
|
- if len(kevents) == 0 {
|
|
|
- n, errno = syscall.Kevent(w.kq, nil, keventbuf[:], twait)
|
|
|
-
|
|
|
- // EINTR is okay, basically the syscall was interrupted before
|
|
|
- // timeout expired.
|
|
|
- if errno != nil && errno != syscall.EINTR {
|
|
|
- w.Errors <- os.NewSyscallError("kevent", errno)
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- // Received some events
|
|
|
- if n > 0 {
|
|
|
- kevents = keventbuf[0:n]
|
|
|
- }
|
|
|
+ kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
|
|
|
+ // EINTR is okay, the syscall was interrupted before timeout expired.
|
|
|
+ if err != nil && err != syscall.EINTR {
|
|
|
+ w.Errors <- err
|
|
|
+ continue
|
|
|
}
|
|
|
|
|
|
// Flush the events we received to the Events channel
|
|
|
for len(kevents) > 0 {
|
|
|
- watchEvent := &kevents[0]
|
|
|
- mask := uint32(watchEvent.Fflags)
|
|
|
- w.pmut.Lock()
|
|
|
- name := w.paths[int(watchEvent.Ident)]
|
|
|
- fileInfo := w.finfo[int(watchEvent.Ident)]
|
|
|
- w.pmut.Unlock()
|
|
|
-
|
|
|
- event := newEvent(name, mask, false)
|
|
|
-
|
|
|
- if fileInfo != nil && fileInfo.IsDir() && !(event.Op&Remove == Remove) {
|
|
|
- // Double check to make sure the directory exist. This can happen when
|
|
|
+ kevent := &kevents[0]
|
|
|
+ watchfd := int(kevent.Ident)
|
|
|
+ mask := uint32(kevent.Fflags)
|
|
|
+ w.mu.Lock()
|
|
|
+ path := w.paths[watchfd]
|
|
|
+ w.mu.Unlock()
|
|
|
+ event := newEvent(path.name, mask)
|
|
|
+
|
|
|
+ if path.isDir && !(event.Op&Remove == Remove) {
|
|
|
+ // Double check to make sure the directory exists. This can happen when
|
|
|
// we do a rm -fr on a recursively watched folders and we receive a
|
|
|
// modification event first but the folder has been deleted and later
|
|
|
// receive the delete event
|
|
@@ -324,55 +287,49 @@ func (w *Watcher) readEvents() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if fileInfo != nil && fileInfo.IsDir() && event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
|
|
+ if event.Op&Rename == Rename || event.Op&Remove == Remove {
|
|
|
+ w.Remove(event.Name)
|
|
|
+ w.mu.Lock()
|
|
|
+ delete(w.fileExists, event.Name)
|
|
|
+ w.mu.Unlock()
|
|
|
+ }
|
|
|
+
|
|
|
+ if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
|
|
w.sendDirectoryChangeEvents(event.Name)
|
|
|
} else {
|
|
|
// Send the event on the Events channel
|
|
|
w.Events <- event
|
|
|
}
|
|
|
|
|
|
- // Move to next event
|
|
|
- kevents = kevents[1:]
|
|
|
-
|
|
|
- if event.Op&Rename == Rename {
|
|
|
- w.Remove(event.Name)
|
|
|
- w.femut.Lock()
|
|
|
- delete(w.fileExists, event.Name)
|
|
|
- w.femut.Unlock()
|
|
|
- }
|
|
|
if event.Op&Remove == Remove {
|
|
|
- w.Remove(event.Name)
|
|
|
- w.femut.Lock()
|
|
|
- delete(w.fileExists, event.Name)
|
|
|
- w.femut.Unlock()
|
|
|
-
|
|
|
- // Look for a file that may have overwritten this
|
|
|
- // (ie mv f1 f2 will delete f2 then create f2)
|
|
|
+ // Look for a file that may have overwritten this.
|
|
|
+ // For example, mv f1 f2 will delete f2, then create f2.
|
|
|
fileDir, _ := filepath.Split(event.Name)
|
|
|
fileDir = filepath.Clean(fileDir)
|
|
|
- w.wmut.Lock()
|
|
|
+ w.mu.Lock()
|
|
|
_, found := w.watches[fileDir]
|
|
|
- w.wmut.Unlock()
|
|
|
+ w.mu.Unlock()
|
|
|
if found {
|
|
|
- // make sure the directory exist before we watch for changes. When we
|
|
|
+ // make sure the directory exists before we watch for changes. When we
|
|
|
// do a recursive watch and perform rm -fr, the parent directory might
|
|
|
// have gone missing, ignore the missing directory and let the
|
|
|
- // upcoming delete event remove the watch form the parent folder
|
|
|
- if _, err := os.Lstat(fileDir); !os.IsNotExist(err) {
|
|
|
+ // upcoming delete event remove the watch from the parent directory.
|
|
|
+ if _, err := os.Lstat(fileDir); os.IsExist(err) {
|
|
|
w.sendDirectoryChangeEvents(fileDir)
|
|
|
+ // FIXME: should this be for events on files or just isDir?
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // Move to next event
|
|
|
+ kevents = kevents[1:]
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
|
|
-func newEvent(name string, mask uint32, create bool) Event {
|
|
|
+func newEvent(name string, mask uint32) Event {
|
|
|
e := Event{Name: name}
|
|
|
- if create {
|
|
|
- e.Op |= Create
|
|
|
- }
|
|
|
if mask&syscall.NOTE_DELETE == syscall.NOTE_DELETE {
|
|
|
e.Op |= Remove
|
|
|
}
|
|
@@ -388,6 +345,11 @@ func newEvent(name string, mask uint32, create bool) Event {
|
|
|
return e
|
|
|
}
|
|
|
|
|
|
+func newCreateEvent(name string) Event {
|
|
|
+ return Event{Name: name, Op: Create}
|
|
|
+}
|
|
|
+
|
|
|
+// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
|
|
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
|
|
// Get all files
|
|
|
files, err := ioutil.ReadDir(dirPath)
|
|
@@ -395,36 +357,15 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- // Search for new files
|
|
|
for _, fileInfo := range files {
|
|
|
filePath := filepath.Join(dirPath, fileInfo.Name())
|
|
|
-
|
|
|
- if fileInfo.IsDir() == false {
|
|
|
- // Watch file to mimic linux fsnotify
|
|
|
- e := w.addWatch(filePath, noteAllEvents)
|
|
|
- if e != nil {
|
|
|
- return e
|
|
|
- }
|
|
|
- } else {
|
|
|
- // If the user is currently watching directory
|
|
|
- // we want to preserve the flags used
|
|
|
- w.enmut.Lock()
|
|
|
- currFlags, found := w.enFlags[filePath]
|
|
|
- w.enmut.Unlock()
|
|
|
- var newFlags uint32 = syscall.NOTE_DELETE
|
|
|
- if found {
|
|
|
- newFlags |= currFlags
|
|
|
- }
|
|
|
-
|
|
|
- // Linux gives deletes if not explicitly watching
|
|
|
- e := w.addWatch(filePath, newFlags)
|
|
|
- if e != nil {
|
|
|
- return e
|
|
|
- }
|
|
|
+ if err := w.internalWatch(filePath, fileInfo); err != nil {
|
|
|
+ return err
|
|
|
}
|
|
|
- w.femut.Lock()
|
|
|
+
|
|
|
+ w.mu.Lock()
|
|
|
w.fileExists[filePath] = true
|
|
|
- w.femut.Unlock()
|
|
|
+ w.mu.Unlock()
|
|
|
}
|
|
|
|
|
|
return nil
|
|
@@ -432,7 +373,7 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
|
|
|
|
|
// sendDirectoryEvents searches the directory for newly created files
|
|
|
// and sends them over the event channel. This functionality is to have
|
|
|
-// the BSD version of fsnotify match linux fsnotify which provides a
|
|
|
+// the BSD version of fsnotify match Linux inotify which provides a
|
|
|
// create event for files created in a watched directory.
|
|
|
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
|
|
// Get all files
|
|
@@ -444,36 +385,79 @@ func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
|
|
// Search for new files
|
|
|
for _, fileInfo := range files {
|
|
|
filePath := filepath.Join(dirPath, fileInfo.Name())
|
|
|
- w.femut.Lock()
|
|
|
+ w.mu.Lock()
|
|
|
_, doesExist := w.fileExists[filePath]
|
|
|
- w.femut.Unlock()
|
|
|
+ w.mu.Unlock()
|
|
|
if !doesExist {
|
|
|
- // Send create event (mask=0)
|
|
|
- event := newEvent(filePath, 0, true)
|
|
|
- w.Events <- event
|
|
|
+ // Send create event
|
|
|
+ w.Events <- newCreateEvent(filePath)
|
|
|
}
|
|
|
|
|
|
- // watchDirectoryFiles (but without doing another ReadDir)
|
|
|
- if fileInfo.IsDir() == false {
|
|
|
- // Watch file to mimic linux fsnotify
|
|
|
- w.addWatch(filePath, noteAllEvents)
|
|
|
- } else {
|
|
|
- // If the user is currently watching directory
|
|
|
- // we want to preserve the flags used
|
|
|
- w.enmut.Lock()
|
|
|
- currFlags, found := w.enFlags[filePath]
|
|
|
- w.enmut.Unlock()
|
|
|
- var newFlags uint32 = syscall.NOTE_DELETE
|
|
|
- if found {
|
|
|
- newFlags |= currFlags
|
|
|
- }
|
|
|
-
|
|
|
- // Linux gives deletes if not explicitly watching
|
|
|
- w.addWatch(filePath, newFlags)
|
|
|
+ // like watchDirectoryFiles (but without doing another ReadDir)
|
|
|
+ if err := w.internalWatch(filePath, fileInfo); err != nil {
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
- w.femut.Lock()
|
|
|
+ w.mu.Lock()
|
|
|
w.fileExists[filePath] = true
|
|
|
- w.femut.Unlock()
|
|
|
+ w.mu.Unlock()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) error {
|
|
|
+ if fileInfo.IsDir() {
|
|
|
+ // mimic Linux providing delete events for subdirectories
|
|
|
+ // but preserve the flags used if currently watching subdirectory
|
|
|
+ w.mu.Lock()
|
|
|
+ flags := w.dirFlags[name]
|
|
|
+ w.mu.Unlock()
|
|
|
+
|
|
|
+ flags |= syscall.NOTE_DELETE
|
|
|
+ return w.addWatch(name, flags)
|
|
|
+ }
|
|
|
+
|
|
|
+ // watch file to mimic Linux inotify
|
|
|
+ return w.addWatch(name, noteAllEvents)
|
|
|
+}
|
|
|
+
|
|
|
+// kqueue creates a new kernel event queue and returns a descriptor.
|
|
|
+func kqueue() (kq int, err error) {
|
|
|
+ kq, err = syscall.Kqueue()
|
|
|
+ if kq == -1 {
|
|
|
+ return kq, err
|
|
|
+ }
|
|
|
+ return kq, nil
|
|
|
+}
|
|
|
+
|
|
|
+// register events with the queue
|
|
|
+func register(kq int, fds []int, flags int, fflags uint32) error {
|
|
|
+ changes := make([]syscall.Kevent_t, len(fds))
|
|
|
+
|
|
|
+ for i, fd := range fds {
|
|
|
+ // SetKevent converts int to the platform-specific types:
|
|
|
+ syscall.SetKevent(&changes[i], fd, syscall.EVFILT_VNODE, flags)
|
|
|
+ changes[i].Fflags = fflags
|
|
|
}
|
|
|
+
|
|
|
+ // register the events
|
|
|
+ success, err := syscall.Kevent(kq, changes, nil, nil)
|
|
|
+ if success == -1 {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// read retrieves pending events, or waits until an event occurs.
|
|
|
+// A timeout of nil blocks indefinitely, while 0 polls the queue.
|
|
|
+func read(kq int, events []syscall.Kevent_t, timeout *syscall.Timespec) ([]syscall.Kevent_t, error) {
|
|
|
+ n, err := syscall.Kevent(kq, nil, events, timeout)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return events[0:n], nil
|
|
|
+}
|
|
|
+
|
|
|
+// durationToTimespec prepares a timeout value
|
|
|
+func durationToTimespec(d time.Duration) syscall.Timespec {
|
|
|
+ return syscall.NsecToTimespec(d.Nanoseconds())
|
|
|
}
|