github.com/checksum/notify@v0.0.0-20190119234841-59aa2d88664f/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 panic(err) 110 } 111 go t.monitor() 112 return t 113 } 114 115 // Close implements watcher. 116 func (t *trg) Close() (err error) { 117 t.Lock() 118 if err = t.t.Stop(); err != nil { 119 t.Unlock() 120 return 121 } 122 <-t.s 123 var e error 124 for _, w := range t.pthLkp { 125 if e = t.unwatch(w.p, w.fi); e != nil { 126 dbgprintf("trg: unwatch %q failed: %q\n", w.p, e) 127 err = nonil(err, e) 128 } 129 } 130 if e = t.t.Close(); e != nil { 131 dbgprintf("trg: closing native watch failed: %q\n", e) 132 err = nonil(err, e) 133 } 134 if remaining := len(t.pthLkp); remaining != 0 { 135 err = nonil(err, fmt.Errorf("Not all watches were removed: len(t.pthLkp) == %v", len(t.pthLkp))) 136 } 137 t.Unlock() 138 return 139 } 140 141 // send reported events one by one through chan. 142 func (t *trg) send(evn []event) { 143 for i := range evn { 144 t.c <- &evn[i] 145 } 146 } 147 148 // singlewatch starts to watch given p file/directory. 149 func (t *trg) singlewatch(p string, e Event, direct mode, fi os.FileInfo) (err error) { 150 w, ok := t.pthLkp[p] 151 if !ok { 152 if w, err = t.t.NewWatched(p, fi); err != nil { 153 return 154 } 155 } 156 switch direct { 157 case dir: 158 w.eDir |= e 159 case ndir: 160 w.eNonDir |= e 161 case both: 162 w.eDir |= e 163 w.eNonDir |= e 164 } 165 if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil { 166 return 167 } 168 if !ok { 169 t.t.Record(w) 170 return nil 171 } 172 return errAlreadyWatched 173 } 174 175 // decode converts event received from native to notify.Event 176 // representation taking into account requested events (w). 177 func decode(o int64, w Event) (e Event) { 178 for f, n := range nat2not { 179 if o&int64(f) != 0 { 180 if w&f != 0 { 181 e |= f 182 } 183 if w&n != 0 { 184 e |= n 185 } 186 } 187 } 188 189 return 190 } 191 192 func (t *trg) watch(p string, e Event, fi os.FileInfo) error { 193 if err := t.singlewatch(p, e, dir, fi); err != nil { 194 if err != errAlreadyWatched { 195 return err 196 } 197 } 198 if fi.IsDir() { 199 err := t.walk(p, func(fi os.FileInfo) (err error) { 200 if err = t.singlewatch(filepath.Join(p, fi.Name()), e, ndir, 201 fi); err != nil { 202 if err != errAlreadyWatched { 203 return 204 } 205 } 206 return nil 207 }) 208 if err != nil { 209 return err 210 } 211 } 212 return nil 213 } 214 215 // walk runs f func on each file/dir from p directory. 216 func (t *trg) walk(p string, fn func(os.FileInfo) error) error { 217 fp, err := os.Open(p) 218 if err != nil { 219 return err 220 } 221 ls, err := fp.Readdir(0) 222 fp.Close() 223 if err != nil { 224 return err 225 } 226 for i := range ls { 227 if err := fn(ls[i]); err != nil { 228 return err 229 } 230 } 231 return nil 232 } 233 234 func (t *trg) unwatch(p string, fi os.FileInfo) error { 235 if fi.IsDir() { 236 err := t.walk(p, func(fi os.FileInfo) error { 237 err := t.singleunwatch(filepath.Join(p, fi.Name()), ndir) 238 if err != errNotWatched { 239 return err 240 } 241 return nil 242 }) 243 if err != nil { 244 return err 245 } 246 } 247 return t.singleunwatch(p, dir) 248 } 249 250 // Watch implements Watcher interface. 251 func (t *trg) Watch(p string, e Event) error { 252 fi, err := os.Stat(p) 253 if err != nil { 254 return err 255 } 256 t.Lock() 257 err = t.watch(p, e, fi) 258 t.Unlock() 259 return err 260 } 261 262 // Unwatch implements Watcher interface. 263 func (t *trg) Unwatch(p string) error { 264 fi, err := os.Stat(p) 265 if err != nil { 266 return err 267 } 268 t.Lock() 269 err = t.unwatch(p, fi) 270 t.Unlock() 271 return err 272 } 273 274 // Rewatch implements Watcher interface. 275 // 276 // TODO(rjeczalik): This is a naive hack. Rewrite might help. 277 func (t *trg) Rewatch(p string, _, e Event) error { 278 fi, err := os.Stat(p) 279 if err != nil { 280 return err 281 } 282 t.Lock() 283 if err = t.unwatch(p, fi); err == nil { 284 // TODO(rjeczalik): If watch fails then we leave trigger in inconsistent 285 // state. Handle? Panic? Native version of rewatch? 286 err = t.watch(p, e, fi) 287 } 288 t.Unlock() 289 return nil 290 } 291 292 func (*trg) file(w *watched, n interface{}, e Event) (evn []event) { 293 evn = append(evn, event{w.p, e, w.fi.IsDir(), n}) 294 return 295 } 296 297 func (t *trg) dir(w *watched, n interface{}, e, ge Event) (evn []event) { 298 // If it's dir and delete we have to send it and continue, because 299 // other processing relies on opening (in this case not existing) dir. 300 // Events for contents of this dir are reported by native impl. 301 // However events for rename must be generated for all monitored files 302 // inside of moved directory, because native impl does not report it independently 303 // for each file descriptor being moved in result of move action on 304 // parent directory. 305 if (ge & (not2nat[Rename] | not2nat[Remove])) != 0 { 306 // Write is reported also for Remove on directory. Because of that 307 // we have to filter it out explicitly. 308 evn = append(evn, event{w.p, e & ^Write & ^not2nat[Write], true, n}) 309 if ge¬2nat[Rename] != 0 { 310 for p := range t.pthLkp { 311 if strings.HasPrefix(p, w.p+string(os.PathSeparator)) { 312 if err := t.singleunwatch(p, both); err != nil && err != errNotWatched && 313 !os.IsNotExist(err) { 314 dbgprintf("trg: failed stop watching moved file (%q): %q\n", 315 p, err) 316 } 317 if (w.eDir|w.eNonDir)&(not2nat[Rename]|Rename) != 0 { 318 evn = append(evn, event{ 319 p, (w.eDir | w.eNonDir) & e &^ Write &^ not2nat[Write], 320 w.fi.IsDir(), nil, 321 }) 322 } 323 } 324 } 325 } 326 t.t.Del(w) 327 return 328 } 329 if (ge & not2nat[Write]) != 0 { 330 switch err := t.walk(w.p, func(fi os.FileInfo) error { 331 p := filepath.Join(w.p, fi.Name()) 332 switch err := t.singlewatch(p, w.eDir, ndir, fi); { 333 case os.IsNotExist(err) && ((w.eDir & Remove) != 0): 334 evn = append(evn, event{p, Remove, fi.IsDir(), n}) 335 case err == errAlreadyWatched: 336 case err != nil: 337 dbgprintf("trg: watching %q failed: %q", p, err) 338 case (w.eDir & Create) != 0: 339 evn = append(evn, event{p, Create, fi.IsDir(), n}) 340 default: 341 } 342 return nil 343 }); { 344 case os.IsNotExist(err): 345 return 346 case err != nil: 347 dbgprintf("trg: dir processing failed: %q", err) 348 default: 349 } 350 } 351 return 352 } 353 354 type mode uint 355 356 const ( 357 dir mode = iota 358 ndir 359 both 360 ) 361 362 // unwatch stops watching p file/directory. 363 func (t *trg) singleunwatch(p string, direct mode) error { 364 w, ok := t.pthLkp[p] 365 if !ok { 366 return errNotWatched 367 } 368 switch direct { 369 case dir: 370 w.eDir = 0 371 case ndir: 372 w.eNonDir = 0 373 case both: 374 w.eDir, w.eNonDir = 0, 0 375 } 376 if err := t.t.Unwatch(w); err != nil { 377 return err 378 } 379 if w.eNonDir|w.eDir != 0 { 380 mod := dir 381 if w.eNonDir != 0 { 382 mod = ndir 383 } 384 if err := t.singlewatch(p, w.eNonDir|w.eDir, mod, 385 w.fi); err != nil && err != errAlreadyWatched { 386 return err 387 } 388 } else { 389 t.t.Del(w) 390 } 391 return nil 392 } 393 394 func (t *trg) monitor() { 395 var ( 396 n interface{} 397 err error 398 ) 399 for { 400 switch n, err = t.t.Wait(); { 401 case err == syscall.EINTR: 402 case t.t.IsStop(n, err): 403 t.s <- struct{}{} 404 return 405 case err != nil: 406 dbgprintf("trg: failed to read events: %q\n", err) 407 default: 408 t.send(t.process(n)) 409 } 410 } 411 } 412 413 // process event returned by native call. 414 func (t *trg) process(n interface{}) (evn []event) { 415 t.Lock() 416 w, ge, err := t.t.Watched(n) 417 if err != nil { 418 t.Unlock() 419 dbgprintf("trg: %v event lookup failed: %q", Event(ge), err) 420 return 421 } 422 423 e := decode(ge, w.eDir|w.eNonDir) 424 if ge&int64(not2nat[Remove]|not2nat[Rename]) == 0 { 425 switch fi, err := os.Stat(w.p); { 426 case err != nil: 427 default: 428 if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil { 429 dbgprintf("trg: %q is no longer watched: %q", w.p, err) 430 t.t.Del(w) 431 } 432 } 433 } 434 if e == Event(0) && (!w.fi.IsDir() || (ge&int64(not2nat[Write])) == 0) { 435 t.Unlock() 436 return 437 } 438 439 if w.fi.IsDir() { 440 evn = append(evn, t.dir(w, n, e, Event(ge))...) 441 } else { 442 evn = append(evn, t.file(w, n, e)...) 443 } 444 if Event(ge)&(not2nat[Remove]|not2nat[Rename]) != 0 { 445 t.t.Del(w) 446 } 447 t.Unlock() 448 return 449 }