| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 |
- // Copyright 2010 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- // +build freebsd openbsd netbsd dragonfly darwin
- package fsnotify
- import (
- "errors"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "sync"
- "syscall"
- )
- const (
- // Flags (from <sys/event.h>)
- sys_NOTE_DELETE = 0x0001 /* vnode was removed */
- sys_NOTE_WRITE = 0x0002 /* data contents changed */
- sys_NOTE_EXTEND = 0x0004 /* size increased */
- sys_NOTE_ATTRIB = 0x0008 /* attributes changed */
- sys_NOTE_LINK = 0x0010 /* link count changed */
- sys_NOTE_RENAME = 0x0020 /* vnode was renamed */
- sys_NOTE_REVOKE = 0x0040 /* vnode access was revoked */
- // Watch all events
- sys_NOTE_ALLEVENTS = sys_NOTE_DELETE | sys_NOTE_WRITE | sys_NOTE_ATTRIB | sys_NOTE_RENAME
- // Block for 100 ms on each call to kevent
- keventWaitTime = 100e6
- )
- type FileEvent struct {
- mask uint32 // Mask of events
- Name string // File name (optional)
- create bool // set by fsnotify package if found new file
- }
- // IsCreate reports whether the FileEvent was triggered by a creation
- func (e *FileEvent) IsCreate() bool { return e.create }
- // IsDelete reports whether the FileEvent was triggered by a delete
- func (e *FileEvent) IsDelete() bool { return (e.mask & sys_NOTE_DELETE) == sys_NOTE_DELETE }
- // IsModify reports whether the FileEvent was triggered by a file modification
- func (e *FileEvent) IsModify() bool {
- return ((e.mask&sys_NOTE_WRITE) == sys_NOTE_WRITE || (e.mask&sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB)
- }
- // IsRename reports whether the FileEvent was triggered by a change name
- func (e *FileEvent) IsRename() bool { return (e.mask & sys_NOTE_RENAME) == sys_NOTE_RENAME }
- // IsAttrib reports whether the FileEvent was triggered by a change in the file metadata.
- func (e *FileEvent) IsAttrib() bool {
- return (e.mask & sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB
- }
- type Watcher struct {
- 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.
- fsnFlags map[string]uint32 // Map of watched files to flags used for filter
- fsnmut sync.Mutex // Protects access to fsnFlags.
- 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.
- Error chan error // Errors are sent on this channel
- internalEvent chan *FileEvent // Events are queued on this channel
- Event chan *FileEvent // Events are returned on this channel
- done chan bool // Channel for sending a "quit message" to the reader goroutine
- isClosed bool // Set to true when Close() is first called
- }
- // NewWatcher creates and returns a new kevent instance using kqueue(2)
- func NewWatcher() (*Watcher, error) {
- fd, errno := syscall.Kqueue()
- if fd == -1 {
- return nil, os.NewSyscallError("kqueue", errno)
- }
- w := &Watcher{
- kq: fd,
- watches: make(map[string]int),
- fsnFlags: make(map[string]uint32),
- enFlags: make(map[string]uint32),
- paths: make(map[int]string),
- finfo: make(map[int]os.FileInfo),
- fileExists: make(map[string]bool),
- externalWatches: make(map[string]bool),
- internalEvent: make(chan *FileEvent),
- Event: make(chan *FileEvent),
- Error: make(chan error),
- done: make(chan bool, 1),
- }
- go w.readEvents()
- go w.purgeEvents()
- return w, nil
- }
- // Close closes a kevent watcher instance
- // It sends a message to the reader goroutine to quit and removes all watches
- // associated with the kevent instance
- func (w *Watcher) Close() error {
- w.mu.Lock()
- if w.isClosed {
- w.mu.Unlock()
- return nil
- }
- w.isClosed = true
- w.mu.Unlock()
- // Send "quit" message to the reader goroutine
- w.done <- true
- w.wmut.Lock()
- ws := w.watches
- w.wmut.Unlock()
- for path := range ws {
- w.removeWatch(path)
- }
- return nil
- }
- // AddWatch adds path to the watched file set.
- // The flags are interpreted as described in kevent(2).
- func (w *Watcher) addWatch(path string, flags uint32) error {
- w.mu.Lock()
- if w.isClosed {
- w.mu.Unlock()
- return errors.New("kevent instance already closed")
- }
- 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
- }
- // don't watch socket
- if fi.Mode()&os.ModeSocket == os.ModeSocket {
- return nil
- }
- // Follow Symlinks
- // Unfortunately, Linux can add bogus symlinks to watch list without
- // issue, and Windows can't do symlinks period (AFAIK). To maintain
- // consistency, we will act like everything is fine. There will simply
- // 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)
- if err != nil {
- return nil
- }
- fi, errstat = os.Lstat(path)
- if errstat != nil {
- return nil
- }
- }
- fd, errno := syscall.Open(path, open_FLAGS, 0700)
- if fd == -1 {
- return errno
- }
- 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()
- }
- // Watch the directory if it has not been watched before.
- w.pmut.Lock()
- w.enmut.Lock()
- if w.finfo[watchfd].IsDir() &&
- (flags&sys_NOTE_WRITE) == sys_NOTE_WRITE &&
- (!found || (w.enFlags[path]&sys_NOTE_WRITE) != sys_NOTE_WRITE) {
- watchDir = true
- }
- 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 watchDir {
- errdir := w.watchDirectoryFiles(path)
- if errdir != nil {
- return errdir
- }
- }
- return nil
- }
- // Watch adds path to the watched file set, watching all events.
- func (w *Watcher) watch(path string) error {
- w.ewmut.Lock()
- w.externalWatches[path] = true
- w.ewmut.Unlock()
- return w.addWatch(path, sys_NOTE_ALLEVENTS)
- }
- // RemoveWatch removes path from the watched file set.
- func (w *Watcher) removeWatch(path string) error {
- w.wmut.Lock()
- watchfd, ok := w.watches[path]
- w.wmut.Unlock()
- if !ok {
- return errors.New(fmt.Sprintf("can't remove non-existent kevent watch for: %s", path))
- }
- 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")
- }
- syscall.Close(watchfd)
- w.wmut.Lock()
- delete(w.watches, path)
- w.wmut.Unlock()
- w.enmut.Lock()
- delete(w.enFlags, path)
- w.enmut.Unlock()
- w.pmut.Lock()
- delete(w.paths, watchfd)
- fInfo := w.finfo[watchfd]
- delete(w.finfo, watchfd)
- w.pmut.Unlock()
- // Find all watched paths that are in this directory that are not external.
- if fInfo.IsDir() {
- var pathsToRemove []string
- w.pmut.Lock()
- for _, wpath := range w.paths {
- wdir, _ := filepath.Split(wpath)
- if filepath.Clean(wdir) == filepath.Clean(path) {
- w.ewmut.Lock()
- if !w.externalWatches[wpath] {
- pathsToRemove = append(pathsToRemove, wpath)
- }
- w.ewmut.Unlock()
- }
- }
- w.pmut.Unlock()
- for _, p := 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
- // a path they did not explicitly watch themselves.
- w.removeWatch(p)
- }
- }
- return nil
- }
- // readEvents reads from the kqueue file descriptor, converts the
- // received events into Event objects and sends them via the Event channel
- func (w *Watcher) readEvents() {
- var (
- eventbuf [10]syscall.Kevent_t // Event buffer
- events []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
- )
- events = eventbuf[0:0]
- twait = new(syscall.Timespec)
- *twait = syscall.NsecToTimespec(keventWaitTime)
- 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.Error <- os.NewSyscallError("close", errno)
- }
- close(w.internalEvent)
- close(w.Error)
- return
- }
- // Get new events
- if len(events) == 0 {
- n, errno = syscall.Kevent(w.kq, nil, eventbuf[:], twait)
- // EINTR is okay, basically the syscall was interrupted before
- // timeout expired.
- if errno != nil && errno != syscall.EINTR {
- w.Error <- os.NewSyscallError("kevent", errno)
- continue
- }
- // Received some events
- if n > 0 {
- events = eventbuf[0:n]
- }
- }
- // Flush the events we received to the events channel
- for len(events) > 0 {
- fileEvent := new(FileEvent)
- watchEvent := &events[0]
- fileEvent.mask = uint32(watchEvent.Fflags)
- w.pmut.Lock()
- fileEvent.Name = w.paths[int(watchEvent.Ident)]
- fileInfo := w.finfo[int(watchEvent.Ident)]
- w.pmut.Unlock()
- if fileInfo != nil && fileInfo.IsDir() && !fileEvent.IsDelete() {
- // Double check to make sure the directory exist. 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
- if _, err := os.Lstat(fileEvent.Name); os.IsNotExist(err) {
- // mark is as delete event
- fileEvent.mask |= sys_NOTE_DELETE
- }
- }
- if fileInfo != nil && fileInfo.IsDir() && fileEvent.IsModify() && !fileEvent.IsDelete() {
- w.sendDirectoryChangeEvents(fileEvent.Name)
- } else {
- // Send the event on the events channel
- w.internalEvent <- fileEvent
- }
- // Move to next event
- events = events[1:]
- if fileEvent.IsRename() {
- w.removeWatch(fileEvent.Name)
- w.femut.Lock()
- delete(w.fileExists, fileEvent.Name)
- w.femut.Unlock()
- }
- if fileEvent.IsDelete() {
- w.removeWatch(fileEvent.Name)
- w.femut.Lock()
- delete(w.fileExists, fileEvent.Name)
- w.femut.Unlock()
- // Look for a file that may have overwritten this
- // (ie mv f1 f2 will delete f2 then create f2)
- fileDir, _ := filepath.Split(fileEvent.Name)
- fileDir = filepath.Clean(fileDir)
- w.wmut.Lock()
- _, found := w.watches[fileDir]
- w.wmut.Unlock()
- if found {
- // make sure the directory exist 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) {
- w.sendDirectoryChangeEvents(fileDir)
- }
- }
- }
- }
- }
- }
- func (w *Watcher) watchDirectoryFiles(dirPath string) error {
- // Get all files
- files, err := ioutil.ReadDir(dirPath)
- if err != nil {
- return err
- }
- // Search for new files
- for _, fileInfo := range files {
- filePath := filepath.Join(dirPath, fileInfo.Name())
- // Inherit fsnFlags from parent directory
- w.fsnmut.Lock()
- if flags, found := w.fsnFlags[dirPath]; found {
- w.fsnFlags[filePath] = flags
- } else {
- w.fsnFlags[filePath] = FSN_ALL
- }
- w.fsnmut.Unlock()
- if fileInfo.IsDir() == false {
- // Watch file to mimic linux fsnotify
- e := w.addWatch(filePath, sys_NOTE_ALLEVENTS)
- 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 = sys_NOTE_DELETE
- if found {
- newFlags |= currFlags
- }
- // Linux gives deletes if not explicitly watching
- e := w.addWatch(filePath, newFlags)
- if e != nil {
- return e
- }
- }
- w.femut.Lock()
- w.fileExists[filePath] = true
- w.femut.Unlock()
- }
- return nil
- }
- // 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
- // create event for files created in a watched directory.
- func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
- // Get all files
- files, err := ioutil.ReadDir(dirPath)
- if err != nil {
- w.Error <- err
- }
- // Search for new files
- for _, fileInfo := range files {
- filePath := filepath.Join(dirPath, fileInfo.Name())
- w.femut.Lock()
- _, doesExist := w.fileExists[filePath]
- w.femut.Unlock()
- if !doesExist {
- // Inherit fsnFlags from parent directory
- w.fsnmut.Lock()
- if flags, found := w.fsnFlags[dirPath]; found {
- w.fsnFlags[filePath] = flags
- } else {
- w.fsnFlags[filePath] = FSN_ALL
- }
- w.fsnmut.Unlock()
- // Send create event
- fileEvent := new(FileEvent)
- fileEvent.Name = filePath
- fileEvent.create = true
- w.internalEvent <- fileEvent
- }
- w.femut.Lock()
- w.fileExists[filePath] = true
- w.femut.Unlock()
- }
- w.watchDirectoryFiles(dirPath)
- }
|