| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- // Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
- // Use of this source code is governed by the MIT license that can be
- // found in the LICENSE file.
- // +build darwin,!kqueue
- package notify
- import (
- "errors"
- "strings"
- "sync/atomic"
- )
- const (
- failure = uint32(FSEventsMustScanSubDirs | FSEventsUserDropped | FSEventsKernelDropped)
- filter = uint32(FSEventsCreated | FSEventsRemoved | FSEventsRenamed |
- FSEventsModified | FSEventsInodeMetaMod)
- )
- // FSEvent represents single file event. It is created out of values passed by
- // FSEvents to FSEventStreamCallback function.
- type FSEvent struct {
- Path string // real path of the file or directory
- ID uint64 // ID of the event (FSEventStreamEventId)
- Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
- }
- // splitflags separates event flags from single set into slice of flags.
- func splitflags(set uint32) (e []uint32) {
- for i := uint32(1); set != 0; i, set = i<<1, set>>1 {
- if (set & 1) != 0 {
- e = append(e, i)
- }
- }
- return
- }
- // watch represents a filesystem watchpoint. It is a higher level abstraction
- // over FSEvents' stream, which implements filtering of file events based
- // on path and event set. It emulates non-recursive watch-point by filtering out
- // events which paths are more than 1 level deeper than the watched path.
- type watch struct {
- // prev stores last event set per path in order to filter out old flags
- // for new events, which appratenly FSEvents likes to retain. It's a disgusting
- // hack, it should be researched how to get rid of it.
- prev map[string]uint32
- c chan<- EventInfo
- stream *stream
- path string
- events uint32
- isrec int32
- flushed bool
- }
- // Example format:
- //
- // ~ $ (trigger command) # (event set) -> (effective event set)
- //
- // Heuristics:
- //
- // 1. Create event is removed when it was present in previous event set.
- // Example:
- //
- // ~ $ echo > file # Create|Write -> Create|Write
- // ~ $ echo > file # Create|Write|InodeMetaMod -> Write|InodeMetaMod
- //
- // 2. Remove event is removed if it was present in previouse event set.
- // Example:
- //
- // ~ $ touch file # Create -> Create
- // ~ $ rm file # Create|Remove -> Remove
- // ~ $ touch file # Create|Remove -> Create
- //
- // 3. Write event is removed if not followed by InodeMetaMod on existing
- // file. Example:
- //
- // ~ $ echo > file # Create|Write -> Create|Write
- // ~ $ chmod +x file # Create|Write|ChangeOwner -> ChangeOwner
- //
- // 4. Write&InodeMetaMod is removed when effective event set contain Remove event.
- // Example:
- //
- // ~ $ echo > file # Write|InodeMetaMod -> Write|InodeMetaMod
- // ~ $ rm file # Remove|Write|InodeMetaMod -> Remove
- //
- func (w *watch) strip(base string, set uint32) uint32 {
- const (
- write = FSEventsModified | FSEventsInodeMetaMod
- both = FSEventsCreated | FSEventsRemoved
- )
- switch w.prev[base] {
- case FSEventsCreated:
- set &^= FSEventsCreated
- if set&FSEventsRemoved != 0 {
- w.prev[base] = FSEventsRemoved
- set &^= write
- }
- case FSEventsRemoved:
- set &^= FSEventsRemoved
- if set&FSEventsCreated != 0 {
- w.prev[base] = FSEventsCreated
- }
- default:
- switch set & both {
- case FSEventsCreated:
- w.prev[base] = FSEventsCreated
- case FSEventsRemoved:
- w.prev[base] = FSEventsRemoved
- set &^= write
- }
- }
- dbgprintf("split()=%v\n", Event(set))
- return set
- }
- // Dispatch is a stream function which forwards given file events for the watched
- // path to underlying FileInfo channel.
- func (w *watch) Dispatch(ev []FSEvent) {
- events := atomic.LoadUint32(&w.events)
- isrec := (atomic.LoadInt32(&w.isrec) == 1)
- for i := range ev {
- if ev[i].Flags&FSEventsHistoryDone != 0 {
- w.flushed = true
- continue
- }
- if !w.flushed {
- continue
- }
- dbgprintf("%v (0x%x) (%s, i=%d, ID=%d, len=%d)\n", Event(ev[i].Flags),
- ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev))
- if ev[i].Flags&failure != 0 {
- // TODO(rjeczalik): missing error handling
- continue
- }
- if !strings.HasPrefix(ev[i].Path, w.path) {
- continue
- }
- n := len(w.path)
- base := ""
- if len(ev[i].Path) > n {
- if ev[i].Path[n] != '/' {
- continue
- }
- base = ev[i].Path[n+1:]
- if !isrec && strings.IndexByte(base, '/') != -1 {
- continue
- }
- }
- // TODO(rjeczalik): get diff only from filtered events?
- e := w.strip(string(base), ev[i].Flags) & events
- if e == 0 {
- continue
- }
- for _, e := range splitflags(e) {
- dbgprintf("%d: single event: %v", ev[i].ID, Event(e))
- w.c <- &event{
- fse: ev[i],
- event: Event(e),
- }
- }
- }
- }
- // Stop closes underlying FSEvents stream and stops dispatching events.
- func (w *watch) Stop() {
- w.stream.Stop()
- // TODO(rjeczalik): make (*stream).Stop flush synchronously undelivered events,
- // so the following hack can be removed. It should flush all the streams
- // concurrently as we care not to block too much here.
- atomic.StoreUint32(&w.events, 0)
- atomic.StoreInt32(&w.isrec, 0)
- }
- // fsevents implements Watcher and RecursiveWatcher interfaces backed by FSEvents
- // framework.
- type fsevents struct {
- watches map[string]*watch
- c chan<- EventInfo
- }
- func newWatcher(c chan<- EventInfo) watcher {
- return &fsevents{
- watches: make(map[string]*watch),
- c: c,
- }
- }
- func (fse *fsevents) watch(path string, event Event, isrec int32) (err error) {
- if _, ok := fse.watches[path]; ok {
- return errAlreadyWatched
- }
- w := &watch{
- prev: make(map[string]uint32),
- c: fse.c,
- path: path,
- events: uint32(event),
- isrec: isrec,
- }
- w.stream = newStream(path, w.Dispatch)
- if err = w.stream.Start(); err != nil {
- return err
- }
- fse.watches[path] = w
- return nil
- }
- func (fse *fsevents) unwatch(path string) (err error) {
- w, ok := fse.watches[path]
- if !ok {
- return errNotWatched
- }
- w.stream.Stop()
- delete(fse.watches, path)
- return nil
- }
- // Watch implements Watcher interface. It fails with non-nil error when setting
- // the watch-point by FSEvents fails or with errAlreadyWatched error when
- // the given path is already watched.
- func (fse *fsevents) Watch(path string, event Event) error {
- return fse.watch(path, event, 0)
- }
- // Unwatch implements Watcher interface. It fails with errNotWatched when
- // the given path is not being watched.
- func (fse *fsevents) Unwatch(path string) error {
- return fse.unwatch(path)
- }
- // Rewatch implements Watcher interface. It fails with errNotWatched when
- // the given path is not being watched or with errInvalidEventSet when oldevent
- // does not match event set the watch-point currently holds.
- func (fse *fsevents) Rewatch(path string, oldevent, newevent Event) error {
- w, ok := fse.watches[path]
- if !ok {
- return errNotWatched
- }
- if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
- return errInvalidEventSet
- }
- atomic.StoreInt32(&w.isrec, 0)
- return nil
- }
- // RecursiveWatch implements RecursiveWatcher interface. It fails with non-nil
- // error when setting the watch-point by FSEvents fails or with errAlreadyWatched
- // error when the given path is already watched.
- func (fse *fsevents) RecursiveWatch(path string, event Event) error {
- return fse.watch(path, event, 1)
- }
- // RecursiveUnwatch implements RecursiveWatcher interface. It fails with
- // errNotWatched when the given path is not being watched.
- //
- // TODO(rjeczalik): fail if w.isrec == 0?
- func (fse *fsevents) RecursiveUnwatch(path string) error {
- return fse.unwatch(path)
- }
- // RecrusiveRewatch implements RecursiveWatcher interface. It fails:
- //
- // * with errNotWatched when the given path is not being watched
- // * with errInvalidEventSet when oldevent does not match the current event set
- // * with errAlreadyWatched when watch-point given by the oldpath was meant to
- // be relocated to newpath, but the newpath is already watched
- // * a non-nil error when setting the watch-point with FSEvents fails
- //
- // TODO(rjeczalik): Improve handling of watch-point relocation? See two TODOs
- // that follows.
- func (fse *fsevents) RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error {
- switch [2]bool{oldpath == newpath, oldevent == newevent} {
- case [2]bool{true, true}:
- w, ok := fse.watches[oldpath]
- if !ok {
- return errNotWatched
- }
- atomic.StoreInt32(&w.isrec, 1)
- return nil
- case [2]bool{true, false}:
- w, ok := fse.watches[oldpath]
- if !ok {
- return errNotWatched
- }
- if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
- return errors.New("invalid event state diff")
- }
- atomic.StoreInt32(&w.isrec, 1)
- return nil
- default:
- // TODO(rjeczalik): rewatch newpath only if exists?
- // TODO(rjeczalik): migrate w.prev to new watch?
- if _, ok := fse.watches[newpath]; ok {
- return errAlreadyWatched
- }
- if err := fse.Unwatch(oldpath); err != nil {
- return err
- }
- // TODO(rjeczalik): revert unwatch if watch fails?
- return fse.watch(newpath, newevent, 1)
- }
- }
- // Close unwatches all watch-points.
- func (fse *fsevents) Close() error {
- for _, w := range fse.watches {
- w.Stop()
- }
- fse.watches = nil
- return nil
- }
|