github.com/misseven0/notify@v0.0.0-20230519123055-c1422e46da05/watcher_fsevents.go (about)

     1  // Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
     2  // Use of this source code is governed by the MIT license that can be
     3  // found in the LICENSE file.
     4  
     5  //go:build darwin && !kqueue && cgo
     6  // +build darwin,!kqueue,cgo
     7  
     8  package notify
     9  
    10  import (
    11  	"errors"
    12  	"strings"
    13  	"sync/atomic"
    14  )
    15  
    16  const (
    17  	failure = uint32(FSEventsMustScanSubDirs | FSEventsUserDropped | FSEventsKernelDropped)
    18  	filter  = uint32(FSEventsCreated | FSEventsRemoved | FSEventsRenamed |
    19  		FSEventsModified | FSEventsInodeMetaMod)
    20  )
    21  
    22  // FSEvent represents single file event. It is created out of values passed by
    23  // FSEvents to FSEventStreamCallback function.
    24  type FSEvent struct {
    25  	Path  string // real path of the file or directory
    26  	ID    uint64 // ID of the event (FSEventStreamEventId)
    27  	Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
    28  }
    29  
    30  // splitflags separates event flags from single set into slice of flags.
    31  func splitflags(set uint32) (e []uint32) {
    32  	for i := uint32(1); set != 0; i, set = i<<1, set>>1 {
    33  		if (set & 1) != 0 {
    34  			e = append(e, i)
    35  		}
    36  	}
    37  	return
    38  }
    39  
    40  // watch represents a filesystem watchpoint. It is a higher level abstraction
    41  // over FSEvents' stream, which implements filtering of file events based
    42  // on path and event set. It emulates non-recursive watch-point by filtering out
    43  // events which paths are more than 1 level deeper than the watched path.
    44  type watch struct {
    45  	// prev stores last event set  per path in order to filter out old flags
    46  	// for new events, which appratenly FSEvents likes to retain. It's a disgusting
    47  	// hack, it should be researched how to get rid of it.
    48  	prev    map[string]uint32
    49  	c       chan<- EventInfo
    50  	stream  *stream
    51  	path    string
    52  	events  uint32
    53  	isrec   int32
    54  	flushed bool
    55  }
    56  
    57  // Example format:
    58  //
    59  //	~ $ (trigger command) # (event set) -> (effective event set)
    60  //
    61  // Heuristics:
    62  //
    63  // 1. Create event is removed when it was present in previous event set.
    64  // Example:
    65  //
    66  //	~ $ echo > file # Create|Write -> Create|Write
    67  //	~ $ echo > file # Create|Write|InodeMetaMod -> Write|InodeMetaMod
    68  //
    69  // 2. Remove event is removed if it was present in previouse event set.
    70  // Example:
    71  //
    72  //	~ $ touch file # Create -> Create
    73  //	~ $ rm file    # Create|Remove -> Remove
    74  //	~ $ touch file # Create|Remove -> Create
    75  //
    76  // 3. Write event is removed if not followed by InodeMetaMod on existing
    77  // file. Example:
    78  //
    79  //	~ $ echo > file   # Create|Write -> Create|Write
    80  //	~ $ chmod +x file # Create|Write|ChangeOwner -> ChangeOwner
    81  //
    82  // 4. Write&InodeMetaMod is removed when effective event set contain Remove event.
    83  // Example:
    84  //
    85  //	~ $ echo > file # Write|InodeMetaMod -> Write|InodeMetaMod
    86  //	~ $ rm file     # Remove|Write|InodeMetaMod -> Remove
    87  func (w *watch) strip(base string, set uint32) uint32 {
    88  	const (
    89  		write = FSEventsModified | FSEventsInodeMetaMod
    90  		both  = FSEventsCreated | FSEventsRemoved
    91  	)
    92  	switch w.prev[base] {
    93  	case FSEventsCreated:
    94  		set &^= FSEventsCreated
    95  		if set&FSEventsRemoved != 0 {
    96  			w.prev[base] = FSEventsRemoved
    97  			set &^= write
    98  		}
    99  	case FSEventsRemoved:
   100  		set &^= FSEventsRemoved
   101  		if set&FSEventsCreated != 0 {
   102  			w.prev[base] = FSEventsCreated
   103  		}
   104  	default:
   105  		switch set & both {
   106  		case FSEventsCreated:
   107  			w.prev[base] = FSEventsCreated
   108  		case FSEventsRemoved:
   109  			w.prev[base] = FSEventsRemoved
   110  			set &^= write
   111  		}
   112  	}
   113  	dbgprintf("split()=%v\n", Event(set))
   114  	return set
   115  }
   116  
   117  // Dispatch is a stream function which forwards given file events for the watched
   118  // path to underlying FileInfo channel.
   119  func (w *watch) Dispatch(ev []FSEvent) {
   120  	events := atomic.LoadUint32(&w.events)
   121  	isrec := (atomic.LoadInt32(&w.isrec) == 1)
   122  	for i := range ev {
   123  		if ev[i].Flags&FSEventsHistoryDone != 0 {
   124  			w.flushed = true
   125  			continue
   126  		}
   127  		if !w.flushed {
   128  			continue
   129  		}
   130  		dbgprintf("%v (0x%x) (%s, i=%d, ID=%d, len=%d)\n", Event(ev[i].Flags),
   131  			ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev))
   132  		if ev[i].Flags&failure != 0 && failure&events == 0 {
   133  			// TODO(rjeczalik): missing error handling
   134  			continue
   135  		}
   136  		if !strings.HasPrefix(ev[i].Path, w.path) {
   137  			continue
   138  		}
   139  		n := len(w.path)
   140  		base := ""
   141  		if len(ev[i].Path) > n {
   142  			if ev[i].Path[n] != '/' {
   143  				continue
   144  			}
   145  			base = ev[i].Path[n+1:]
   146  			if !isrec && strings.IndexByte(base, '/') != -1 {
   147  				continue
   148  			}
   149  		}
   150  		// TODO(rjeczalik): get diff only from filtered events?
   151  		e := w.strip(string(base), ev[i].Flags) & events
   152  		if e == 0 {
   153  			continue
   154  		}
   155  		for _, e := range splitflags(e) {
   156  			dbgprintf("%d: single event: %v", ev[i].ID, Event(e))
   157  			w.c <- &event{
   158  				fse:   ev[i],
   159  				event: Event(e),
   160  			}
   161  		}
   162  	}
   163  }
   164  
   165  // Stop closes underlying FSEvents stream and stops dispatching events.
   166  func (w *watch) Stop() {
   167  	w.stream.Stop()
   168  	// TODO(rjeczalik): make (*stream).Stop flush synchronously undelivered events,
   169  	// so the following hack can be removed. It should flush all the streams
   170  	// concurrently as we care not to block too much here.
   171  	atomic.StoreUint32(&w.events, 0)
   172  	atomic.StoreInt32(&w.isrec, 0)
   173  }
   174  
   175  // fsevents implements Watcher and RecursiveWatcher interfaces backed by FSEvents
   176  // framework.
   177  type fsevents struct {
   178  	watches map[string]*watch
   179  	c       chan<- EventInfo
   180  }
   181  
   182  func newWatcher(c chan<- EventInfo) watcher {
   183  	return &fsevents{
   184  		watches: make(map[string]*watch),
   185  		c:       c,
   186  	}
   187  }
   188  
   189  func (fse *fsevents) watch(path string, event Event, isrec int32) (err error) {
   190  	if _, ok := fse.watches[path]; ok {
   191  		return errAlreadyWatched
   192  	}
   193  	w := &watch{
   194  		prev:   make(map[string]uint32),
   195  		c:      fse.c,
   196  		path:   path,
   197  		events: uint32(event),
   198  		isrec:  isrec,
   199  	}
   200  	w.stream = newStream(path, w.Dispatch)
   201  	if err = w.stream.Start(); err != nil {
   202  		return err
   203  	}
   204  	fse.watches[path] = w
   205  	return nil
   206  }
   207  
   208  func (fse *fsevents) unwatch(path string) (err error) {
   209  	w, ok := fse.watches[path]
   210  	if !ok {
   211  		return errNotWatched
   212  	}
   213  	w.stream.Stop()
   214  	delete(fse.watches, path)
   215  	return nil
   216  }
   217  
   218  // Watch implements Watcher interface. It fails with non-nil error when setting
   219  // the watch-point by FSEvents fails or with errAlreadyWatched error when
   220  // the given path is already watched.
   221  func (fse *fsevents) Watch(path string, event Event) error {
   222  	return fse.watch(path, event, 0)
   223  }
   224  
   225  // Unwatch implements Watcher interface. It fails with errNotWatched when
   226  // the given path is not being watched.
   227  func (fse *fsevents) Unwatch(path string) error {
   228  	return fse.unwatch(path)
   229  }
   230  
   231  // Rewatch implements Watcher interface. It fails with errNotWatched when
   232  // the given path is not being watched or with errInvalidEventSet when oldevent
   233  // does not match event set the watch-point currently holds.
   234  func (fse *fsevents) Rewatch(path string, oldevent, newevent Event) error {
   235  	w, ok := fse.watches[path]
   236  	if !ok {
   237  		return errNotWatched
   238  	}
   239  	if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
   240  		return errInvalidEventSet
   241  	}
   242  	atomic.StoreInt32(&w.isrec, 0)
   243  	return nil
   244  }
   245  
   246  // RecursiveWatch implements RecursiveWatcher interface. It fails with non-nil
   247  // error when setting the watch-point by FSEvents fails or with errAlreadyWatched
   248  // error when the given path is already watched.
   249  func (fse *fsevents) RecursiveWatch(path string, event Event) error {
   250  	return fse.watch(path, event, 1)
   251  }
   252  
   253  // RecursiveUnwatch implements RecursiveWatcher interface. It fails with
   254  // errNotWatched when the given path is not being watched.
   255  //
   256  // TODO(rjeczalik): fail if w.isrec == 0?
   257  func (fse *fsevents) RecursiveUnwatch(path string) error {
   258  	return fse.unwatch(path)
   259  }
   260  
   261  // RecursiveRewatch implements RecursiveWatcher interface. It fails:
   262  //
   263  //   - with errNotWatched when the given path is not being watched
   264  //   - with errInvalidEventSet when oldevent does not match the current event set
   265  //   - with errAlreadyWatched when watch-point given by the oldpath was meant to
   266  //     be relocated to newpath, but the newpath is already watched
   267  //   - a non-nil error when setting the watch-point with FSEvents fails
   268  //
   269  // TODO(rjeczalik): Improve handling of watch-point relocation? See two TODOs
   270  // that follows.
   271  func (fse *fsevents) RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error {
   272  	switch [2]bool{oldpath == newpath, oldevent == newevent} {
   273  	case [2]bool{true, true}:
   274  		w, ok := fse.watches[oldpath]
   275  		if !ok {
   276  			return errNotWatched
   277  		}
   278  		atomic.StoreInt32(&w.isrec, 1)
   279  		return nil
   280  	case [2]bool{true, false}:
   281  		w, ok := fse.watches[oldpath]
   282  		if !ok {
   283  			return errNotWatched
   284  		}
   285  		if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
   286  			return errors.New("invalid event state diff")
   287  		}
   288  		atomic.StoreInt32(&w.isrec, 1)
   289  		return nil
   290  	default:
   291  		// TODO(rjeczalik): rewatch newpath only if exists?
   292  		// TODO(rjeczalik): migrate w.prev to new watch?
   293  		if _, ok := fse.watches[newpath]; ok {
   294  			return errAlreadyWatched
   295  		}
   296  		if err := fse.Unwatch(oldpath); err != nil {
   297  			return err
   298  		}
   299  		// TODO(rjeczalik): revert unwatch if watch fails?
   300  		return fse.watch(newpath, newevent, 1)
   301  	}
   302  }
   303  
   304  // Close unwatches all watch-points.
   305  func (fse *fsevents) Close() error {
   306  	for _, w := range fse.watches {
   307  		w.Stop()
   308  	}
   309  	fse.watches = nil
   310  	return nil
   311  }