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