github.com/dgallion1/notify@v0.9.3-0.20201128171805-931189d936e0/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 // +build darwin,!kqueue,cgo 6 7 package notify 8 9 import ( 10 "errors" 11 "strings" 12 "sync/atomic" 13 ) 14 15 const ( 16 failure = uint32(FSEventsMustScanSubDirs | FSEventsUserDropped | FSEventsKernelDropped) 17 filter = uint32(FSEventsCreated | FSEventsRemoved | FSEventsRenamed | 18 FSEventsModified | FSEventsInodeMetaMod) 19 ) 20 21 // FSEvent represents single file event. It is created out of values passed by 22 // FSEvents to FSEventStreamCallback function. 23 type FSEvent struct { 24 Path string // real path of the file or directory 25 ID uint64 // ID of the event (FSEventStreamEventId) 26 Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags) 27 } 28 29 // splitflags separates event flags from single set into slice of flags. 30 func splitflags(set uint32) (e []uint32) { 31 for i := uint32(1); set != 0; i, set = i<<1, set>>1 { 32 if (set & 1) != 0 { 33 e = append(e, i) 34 } 35 } 36 return 37 } 38 39 // watch represents a filesystem watchpoint. It is a higher level abstraction 40 // over FSEvents' stream, which implements filtering of file events based 41 // on path and event set. It emulates non-recursive watch-point by filtering out 42 // events which paths are more than 1 level deeper than the watched path. 43 type watch struct { 44 // prev stores last event set per path in order to filter out old flags 45 // for new events, which appratenly FSEvents likes to retain. It's a disgusting 46 // hack, it should be researched how to get rid of it. 47 prev map[string]uint32 48 c chan<- EventInfo 49 stream *stream 50 path string 51 events uint32 52 isrec int32 53 flushed bool 54 } 55 56 // Example format: 57 // 58 // ~ $ (trigger command) # (event set) -> (effective event set) 59 // 60 // Heuristics: 61 // 62 // 1. Create event is removed when it was present in previous event set. 63 // Example: 64 // 65 // ~ $ echo > file # Create|Write -> Create|Write 66 // ~ $ echo > file # Create|Write|InodeMetaMod -> Write|InodeMetaMod 67 // 68 // 2. Remove event is removed if it was present in previouse event set. 69 // Example: 70 // 71 // ~ $ touch file # Create -> Create 72 // ~ $ rm file # Create|Remove -> Remove 73 // ~ $ touch file # Create|Remove -> Create 74 // 75 // 3. Write event is removed if not followed by InodeMetaMod on existing 76 // file. Example: 77 // 78 // ~ $ echo > file # Create|Write -> Create|Write 79 // ~ $ chmod +x file # Create|Write|ChangeOwner -> ChangeOwner 80 // 81 // 4. Write&InodeMetaMod is removed when effective event set contain Remove event. 82 // Example: 83 // 84 // ~ $ echo > file # Write|InodeMetaMod -> Write|InodeMetaMod 85 // ~ $ rm file # Remove|Write|InodeMetaMod -> Remove 86 // 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 { 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 }