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