github.com/mattolenik/notify@v0.9.1/watcher_trigger.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 dragonfly freebsd netbsd openbsd solaris 6 7 // watcher_trigger is used for FEN and kqueue which behave similarly: 8 // only files and dirs can be watched directly, but not files inside dirs. 9 // As a result Create events have to be generated by implementation when 10 // after Write event is returned for watched dir, it is rescanned and Create 11 // event is returned for new files and these are automatically added 12 // to watchlist. In case of removal of watched directory, native system returns 13 // events for all files, but for Rename, they also need to be generated. 14 // As a result native system works as something like trigger for rescan, 15 // but contains additional data about dir in which changes occurred. For files 16 // detailed data is returned. 17 // Usage of watcher_trigger requires: 18 // - trigger implementation, 19 // - encode func, 20 // - not2nat, nat2not maps. 21 // Required manual operations on filesystem can lead to loss of precision. 22 23 package notify 24 25 import ( 26 "fmt" 27 "os" 28 "path/filepath" 29 "strings" 30 "sync" 31 "syscall" 32 ) 33 34 // trigger is to be implemented by platform implementation like FEN or kqueue. 35 type trigger interface { 36 // Close closes watcher's main native file descriptor. 37 Close() error 38 // Stop waiting for new events. 39 Stop() error 40 // Create new instance of watched. 41 NewWatched(string, os.FileInfo) (*watched, error) 42 // Record internally new *watched instance. 43 Record(*watched) 44 // Del removes internal copy of *watched instance. 45 Del(*watched) 46 // Watched returns *watched instance and native events for native type. 47 Watched(interface{}) (*watched, int64, error) 48 // Init initializes native watcher call. 49 Init() error 50 // Watch starts watching provided file/dir. 51 Watch(os.FileInfo, *watched, int64) error 52 // Unwatch stops watching provided file/dir. 53 Unwatch(*watched) error 54 // Wait for new events. 55 Wait() (interface{}, error) 56 // IsStop checks if Wait finished because of request watcher's stop. 57 IsStop(n interface{}, err error) bool 58 } 59 60 // trgWatched is a the base data structure representing watched file/directory. 61 // The platform specific full data structure (watched) must embed this type. 62 type trgWatched struct { 63 // p is a path to watched file/directory. 64 p string 65 // fi provides information about watched file/dir. 66 fi os.FileInfo 67 // eDir represents events watched directly. 68 eDir Event 69 // eNonDir represents events watched indirectly. 70 eNonDir Event 71 } 72 73 // encode Event to native representation. Implementation is to be provided by 74 // platform specific implementation. 75 var encode func(Event, bool) int64 76 77 var ( 78 // nat2not matches native events to notify's ones. To be initialized by 79 // platform dependent implementation. 80 nat2not map[Event]Event 81 // not2nat matches notify's events to native ones. To be initialized by 82 // platform dependent implementation. 83 not2nat map[Event]Event 84 ) 85 86 // trg is a main structure implementing watcher. 87 type trg struct { 88 sync.Mutex 89 // s is a channel used to stop monitoring. 90 s chan struct{} 91 // c is a channel used to pass events further. 92 c chan<- EventInfo 93 // pthLkp is a data structure mapping file names with data about watching 94 // represented by them files/directories. 95 pthLkp map[string]*watched 96 // t is a platform dependent implementation of trigger. 97 t trigger 98 } 99 100 // newWatcher returns new watcher's implementation. 101 func newWatcher(c chan<- EventInfo) watcher { 102 t := &trg{ 103 s: make(chan struct{}, 1), 104 pthLkp: make(map[string]*watched, 0), 105 c: c, 106 } 107 t.t = newTrigger(t.pthLkp) 108 if err := t.t.Init(); err != nil { 109 t.Close() 110 return watcherStub{fmt.Errorf("failed setting up watcher: %v", err)} 111 } 112 go t.monitor() 113 return t 114 } 115 116 // Close implements watcher. 117 func (t *trg) Close() (err error) { 118 t.Lock() 119 if err = t.t.Stop(); err != nil { 120 t.Unlock() 121 return 122 } 123 <-t.s 124 var e error 125 for _, w := range t.pthLkp { 126 if e = t.unwatch(w.p, w.fi); e != nil { 127 dbgprintf("trg: unwatch %q failed: %q\n", w.p, e) 128 err = nonil(err, e) 129 } 130 } 131 if e = t.t.Close(); e != nil { 132 dbgprintf("trg: closing native watch failed: %q\n", e) 133 err = nonil(err, e) 134 } 135 if remaining := len(t.pthLkp); remaining != 0 { 136 err = nonil(err, fmt.Errorf("Not all watches were removed: len(t.pthLkp) == %v", len(t.pthLkp))) 137 } 138 t.Unlock() 139 return 140 } 141 142 // send reported events one by one through chan. 143 func (t *trg) send(evn []event) { 144 for i := range evn { 145 t.c <- &evn[i] 146 } 147 } 148 149 // singlewatch starts to watch given p file/directory. 150 func (t *trg) singlewatch(p string, e Event, direct mode, fi os.FileInfo) (err error) { 151 w, ok := t.pthLkp[p] 152 if !ok { 153 if w, err = t.t.NewWatched(p, fi); err != nil { 154 return 155 } 156 } 157 switch direct { 158 case dir: 159 w.eDir |= e 160 case ndir: 161 w.eNonDir |= e 162 case both: 163 w.eDir |= e 164 w.eNonDir |= e 165 } 166 if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil { 167 return 168 } 169 if !ok { 170 t.t.Record(w) 171 return nil 172 } 173 return errAlreadyWatched 174 } 175 176 // decode converts event received from native to notify.Event 177 // representation taking into account requested events (w). 178 func decode(o int64, w Event) (e Event) { 179 for f, n := range nat2not { 180 if o&int64(f) != 0 { 181 if w&f != 0 { 182 e |= f 183 } 184 if w&n != 0 { 185 e |= n 186 } 187 } 188 } 189 190 return 191 } 192 193 func (t *trg) watch(p string, e Event, fi os.FileInfo) error { 194 if err := t.singlewatch(p, e, dir, fi); err != nil { 195 if err != errAlreadyWatched { 196 return err 197 } 198 } 199 if fi.IsDir() { 200 err := t.walk(p, func(fi os.FileInfo) (err error) { 201 if err = t.singlewatch(filepath.Join(p, fi.Name()), e, ndir, 202 fi); err != nil { 203 if err != errAlreadyWatched { 204 return 205 } 206 } 207 return nil 208 }) 209 if err != nil { 210 return err 211 } 212 } 213 return nil 214 } 215 216 // walk runs f func on each file/dir from p directory. 217 func (t *trg) walk(p string, fn func(os.FileInfo) error) error { 218 fp, err := os.Open(p) 219 if err != nil { 220 return err 221 } 222 ls, err := fp.Readdir(0) 223 fp.Close() 224 if err != nil { 225 return err 226 } 227 for i := range ls { 228 if err := fn(ls[i]); err != nil { 229 return err 230 } 231 } 232 return nil 233 } 234 235 func (t *trg) unwatch(p string, fi os.FileInfo) error { 236 if fi.IsDir() { 237 err := t.walk(p, func(fi os.FileInfo) error { 238 err := t.singleunwatch(filepath.Join(p, fi.Name()), ndir) 239 if err != errNotWatched { 240 return err 241 } 242 return nil 243 }) 244 if err != nil { 245 return err 246 } 247 } 248 return t.singleunwatch(p, dir) 249 } 250 251 // Watch implements Watcher interface. 252 func (t *trg) Watch(p string, e Event) error { 253 fi, err := os.Stat(p) 254 if err != nil { 255 return err 256 } 257 t.Lock() 258 err = t.watch(p, e, fi) 259 t.Unlock() 260 return err 261 } 262 263 // Unwatch implements Watcher interface. 264 func (t *trg) Unwatch(p string) error { 265 fi, err := os.Stat(p) 266 if err != nil { 267 return err 268 } 269 t.Lock() 270 err = t.unwatch(p, fi) 271 t.Unlock() 272 return err 273 } 274 275 // Rewatch implements Watcher interface. 276 // 277 // TODO(rjeczalik): This is a naive hack. Rewrite might help. 278 func (t *trg) Rewatch(p string, _, e Event) error { 279 fi, err := os.Stat(p) 280 if err != nil { 281 return err 282 } 283 t.Lock() 284 if err = t.unwatch(p, fi); err == nil { 285 // TODO(rjeczalik): If watch fails then we leave trigger in inconsistent 286 // state. Handle? Panic? Native version of rewatch? 287 err = t.watch(p, e, fi) 288 } 289 t.Unlock() 290 return nil 291 } 292 293 func (*trg) file(w *watched, n interface{}, e Event) (evn []event) { 294 evn = append(evn, event{w.p, e, w.fi.IsDir(), n}) 295 return 296 } 297 298 func (t *trg) dir(w *watched, n interface{}, e, ge Event) (evn []event) { 299 // If it's dir and delete we have to send it and continue, because 300 // other processing relies on opening (in this case not existing) dir. 301 // Events for contents of this dir are reported by native impl. 302 // However events for rename must be generated for all monitored files 303 // inside of moved directory, because native impl does not report it independently 304 // for each file descriptor being moved in result of move action on 305 // parent directory. 306 if (ge & (not2nat[Rename] | not2nat[Remove])) != 0 { 307 // Write is reported also for Remove on directory. Because of that 308 // we have to filter it out explicitly. 309 evn = append(evn, event{w.p, e & ^Write & ^not2nat[Write], true, n}) 310 if ge¬2nat[Rename] != 0 { 311 for p := range t.pthLkp { 312 if strings.HasPrefix(p, w.p+string(os.PathSeparator)) { 313 if err := t.singleunwatch(p, both); err != nil && err != errNotWatched && 314 !os.IsNotExist(err) { 315 dbgprintf("trg: failed stop watching moved file (%q): %q\n", 316 p, err) 317 } 318 if (w.eDir|w.eNonDir)&(not2nat[Rename]|Rename) != 0 { 319 evn = append(evn, event{ 320 p, (w.eDir | w.eNonDir) & e &^ Write &^ not2nat[Write], 321 w.fi.IsDir(), nil, 322 }) 323 } 324 } 325 } 326 } 327 t.t.Del(w) 328 return 329 } 330 if (ge & not2nat[Write]) != 0 { 331 switch err := t.walk(w.p, func(fi os.FileInfo) error { 332 p := filepath.Join(w.p, fi.Name()) 333 switch err := t.singlewatch(p, w.eDir, ndir, fi); { 334 case os.IsNotExist(err) && ((w.eDir & Remove) != 0): 335 evn = append(evn, event{p, Remove, fi.IsDir(), n}) 336 case err == errAlreadyWatched: 337 case err != nil: 338 dbgprintf("trg: watching %q failed: %q", p, err) 339 case (w.eDir & Create) != 0: 340 evn = append(evn, event{p, Create, fi.IsDir(), n}) 341 default: 342 } 343 return nil 344 }); { 345 case os.IsNotExist(err): 346 return 347 case err != nil: 348 dbgprintf("trg: dir processing failed: %q", err) 349 default: 350 } 351 } 352 return 353 } 354 355 type mode uint 356 357 const ( 358 dir mode = iota 359 ndir 360 both 361 ) 362 363 // unwatch stops watching p file/directory. 364 func (t *trg) singleunwatch(p string, direct mode) error { 365 w, ok := t.pthLkp[p] 366 if !ok { 367 return errNotWatched 368 } 369 switch direct { 370 case dir: 371 w.eDir = 0 372 case ndir: 373 w.eNonDir = 0 374 case both: 375 w.eDir, w.eNonDir = 0, 0 376 } 377 if err := t.t.Unwatch(w); err != nil { 378 return err 379 } 380 if w.eNonDir|w.eDir != 0 { 381 mod := dir 382 if w.eNonDir != 0 { 383 mod = ndir 384 } 385 if err := t.singlewatch(p, w.eNonDir|w.eDir, mod, 386 w.fi); err != nil && err != errAlreadyWatched { 387 return err 388 } 389 } else { 390 t.t.Del(w) 391 } 392 return nil 393 } 394 395 func (t *trg) monitor() { 396 var ( 397 n interface{} 398 err error 399 ) 400 for { 401 switch n, err = t.t.Wait(); { 402 case err == syscall.EINTR: 403 case t.t.IsStop(n, err): 404 t.s <- struct{}{} 405 return 406 case err != nil: 407 dbgprintf("trg: failed to read events: %q\n", err) 408 default: 409 t.send(t.process(n)) 410 } 411 } 412 } 413 414 // process event returned by native call. 415 func (t *trg) process(n interface{}) (evn []event) { 416 t.Lock() 417 w, ge, err := t.t.Watched(n) 418 if err != nil { 419 t.Unlock() 420 dbgprintf("trg: %v event lookup failed: %q", Event(ge), err) 421 return 422 } 423 424 e := decode(ge, w.eDir|w.eNonDir) 425 if ge&int64(not2nat[Remove]|not2nat[Rename]) == 0 { 426 switch fi, err := os.Stat(w.p); { 427 case err != nil: 428 default: 429 if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil { 430 dbgprintf("trg: %q is no longer watched: %q", w.p, err) 431 t.t.Del(w) 432 } 433 } 434 } 435 if e == Event(0) && (!w.fi.IsDir() || (ge&int64(not2nat[Write])) == 0) { 436 t.Unlock() 437 return 438 } 439 440 if w.fi.IsDir() { 441 evn = append(evn, t.dir(w, n, e, Event(ge))...) 442 } else { 443 evn = append(evn, t.file(w, n, e)...) 444 } 445 if Event(ge)&(not2nat[Remove]|not2nat[Rename]) != 0 { 446 t.t.Del(w) 447 } 448 t.Unlock() 449 return 450 }