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.
5 // +build darwin,!kqueue
15 // TODO(rjeczalik): get rid of calls to canonical, it's tree responsibility
18 failure = uint32(FSEventsMustScanSubDirs | FSEventsUserDropped | FSEventsKernelDropped)
19 filter = uint32(FSEventsCreated | FSEventsRemoved | FSEventsRenamed |
20 FSEventsModified | FSEventsInodeMetaMod)
23 // FSEvent represents single file event. It is created out of values passed by
24 // FSEvents to FSEventStreamCallback function.
26 Path string // real path of the file or directory
27 ID uint64 // ID of the event (FSEventStreamEventId)
28 Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
31 // splitflags separates event flags from single set into slice of flags.
32 func splitflags(set uint32) (e []uint32) {
33 for i := uint32(1); set != 0; i, set = i<<1, set>>1 {
41 // watch represents a filesystem watchpoint. It is a higher level abstraction
42 // over FSEvents' stream, which implements filtering of file events based
43 // on path and event set. It emulates non-recursive watch-point by filtering out
44 // events which paths are more than 1 level deeper than the watched path.
46 // prev stores last event set per path in order to filter out old flags
47 // for new events, which appratenly FSEvents likes to retain. It's a disgusting
48 // hack, it should be researched how to get rid of it.
49 prev map[string]uint32
60 // ~ $ (trigger command) # (event set) -> (effective event set)
64 // 1. Create event is removed when it was present in previous event set.
67 // ~ $ echo > file # Create|Write -> Create|Write
68 // ~ $ echo > file # Create|Write|InodeMetaMod -> Write|InodeMetaMod
70 // 2. Remove event is removed if it was present in previouse event set.
73 // ~ $ touch file # Create -> Create
74 // ~ $ rm file # Create|Remove -> Remove
75 // ~ $ touch file # Create|Remove -> Create
77 // 3. Write event is removed if not followed by InodeMetaMod on existing
80 // ~ $ echo > file # Create|Write -> Create|Write
81 // ~ $ chmod +x file # Create|Write|ChangeOwner -> ChangeOwner
83 // 4. Write&InodeMetaMod is removed when effective event set contain Remove event.
86 // ~ $ echo > file # Write|InodeMetaMod -> Write|InodeMetaMod
87 // ~ $ rm file # Remove|Write|InodeMetaMod -> Remove
89 func (w *watch) strip(base string, set uint32) uint32 {
91 write = FSEventsModified | FSEventsInodeMetaMod
92 both = FSEventsCreated | FSEventsRemoved
96 set &^= FSEventsCreated
97 if set&FSEventsRemoved != 0 {
98 w.prev[base] = FSEventsRemoved
101 case FSEventsRemoved:
102 set &^= FSEventsRemoved
103 if set&FSEventsCreated != 0 {
104 w.prev[base] = FSEventsCreated
108 case FSEventsCreated:
109 w.prev[base] = FSEventsCreated
110 case FSEventsRemoved:
111 w.prev[base] = FSEventsRemoved
115 dbgprintf("split()=%v\n", Event(set))
119 // Dispatch is a stream function which forwards given file events for the watched
120 // path to underlying FileInfo channel.
121 func (w *watch) Dispatch(ev []FSEvent) {
122 events := atomic.LoadUint32(&w.events)
123 isrec := (atomic.LoadInt32(&w.isrec) == 1)
125 if ev[i].Flags&FSEventsHistoryDone != 0 {
132 dbgprintf("%v (0x%x) (%s, i=%d, ID=%d, len=%d)\n", Event(ev[i].Flags),
133 ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev))
134 if ev[i].Flags&failure != 0 {
135 // TODO(rjeczalik): missing error handling
138 if !strings.HasPrefix(ev[i].Path, w.path) {
143 if len(ev[i].Path) > n {
144 if ev[i].Path[n] != '/' {
147 base = ev[i].Path[n+1:]
148 if !isrec && strings.IndexByte(base, '/') != -1 {
152 // TODO(rjeczalik): get diff only from filtered events?
153 e := w.strip(string(base), ev[i].Flags) & events
157 for _, e := range splitflags(e) {
158 dbgprintf("%d: single event: %v", ev[i].ID, Event(e))
167 // Stop closes underlying FSEvents stream and stops dispatching events.
168 func (w *watch) Stop() {
170 // TODO(rjeczalik): make (*stream).Stop flush synchronously undelivered events,
171 // so the following hack can be removed. It should flush all the streams
172 // concurrently as we care not to block too much here.
173 atomic.StoreUint32(&w.events, 0)
174 atomic.StoreInt32(&w.isrec, 0)
177 // fsevents implements Watcher and RecursiveWatcher interfaces backed by FSEvents
179 type fsevents struct {
180 watches map[string]*watch
184 func newWatcher(c chan<- EventInfo) watcher {
186 watches: make(map[string]*watch),
191 func (fse *fsevents) watch(path string, event Event, isrec int32) (err error) {
192 if path, err = canonical(path); err != nil {
195 if _, ok := fse.watches[path]; ok {
196 return errAlreadyWatched
199 prev: make(map[string]uint32),
202 events: uint32(event),
205 w.stream = newStream(path, w.Dispatch)
206 if err = w.stream.Start(); err != nil {
209 fse.watches[path] = w
213 func (fse *fsevents) unwatch(path string) (err error) {
214 if path, err = canonical(path); err != nil {
217 w, ok := fse.watches[path]
222 delete(fse.watches, path)
226 // Watch implements Watcher interface. It fails with non-nil error when setting
227 // the watch-point by FSEvents fails or with errAlreadyWatched error when
228 // the given path is already watched.
229 func (fse *fsevents) Watch(path string, event Event) error {
230 return fse.watch(path, event, 0)
233 // Unwatch implements Watcher interface. It fails with errNotWatched when
234 // the given path is not being watched.
235 func (fse *fsevents) Unwatch(path string) error {
236 return fse.unwatch(path)
239 // Rewatch implements Watcher interface. It fails with errNotWatched when
240 // the given path is not being watched or with errInvalidEventSet when oldevent
241 // does not match event set the watch-point currently holds.
242 func (fse *fsevents) Rewatch(path string, oldevent, newevent Event) error {
243 w, ok := fse.watches[path]
247 if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
248 return errInvalidEventSet
250 atomic.StoreInt32(&w.isrec, 0)
254 // RecursiveWatch implements RecursiveWatcher interface. It fails with non-nil
255 // error when setting the watch-point by FSEvents fails or with errAlreadyWatched
256 // error when the given path is already watched.
257 func (fse *fsevents) RecursiveWatch(path string, event Event) error {
258 return fse.watch(path, event, 1)
261 // RecursiveUnwatch implements RecursiveWatcher interface. It fails with
262 // errNotWatched when the given path is not being watched.
264 // TODO(rjeczalik): fail if w.isrec == 0?
265 func (fse *fsevents) RecursiveUnwatch(path string) error {
266 return fse.unwatch(path)
269 // RecrusiveRewatch implements RecursiveWatcher interface. It fails:
271 // * with errNotWatched when the given path is not being watched
272 // * with errInvalidEventSet when oldevent does not match the current event set
273 // * with errAlreadyWatched when watch-point given by the oldpath was meant to
274 // be relocated to newpath, but the newpath is already watched
275 // * a non-nil error when setting the watch-point with FSEvents fails
277 // TODO(rjeczalik): Improve handling of watch-point relocation? See two TODOs
279 func (fse *fsevents) RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error {
280 switch [2]bool{oldpath == newpath, oldevent == newevent} {
281 case [2]bool{true, true}:
282 w, ok := fse.watches[oldpath]
286 atomic.StoreInt32(&w.isrec, 1)
288 case [2]bool{true, false}:
289 w, ok := fse.watches[oldpath]
293 if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
294 return errors.New("invalid event state diff")
296 atomic.StoreInt32(&w.isrec, 1)
299 // TODO(rjeczalik): rewatch newpath only if exists?
300 // TODO(rjeczalik): migrate w.prev to new watch?
301 if _, ok := fse.watches[newpath]; ok {
302 return errAlreadyWatched
304 if err := fse.Unwatch(oldpath); err != nil {
307 // TODO(rjeczalik): revert unwatch if watch fails?
308 return fse.watch(newpath, newevent, 1)
312 // Close unwatches all watch-points.
313 func (fse *fsevents) Close() error {
314 for _, w := range fse.watches {