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.
5 // +build darwin,kqueue dragonfly freebsd netbsd openbsd solaris
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,
20 // - not2nat, nat2not maps.
21 // Required manual operations on filesystem can lead to loss of precision.
33 // trigger is to be implemented by platform implementation like FEN or kqueue.
34 type trigger interface {
35 // Close closes watcher's main native file descriptor.
37 // Stop waiting for new events.
39 // Create new instance of watched.
40 NewWatched(string, os.FileInfo) (*watched, error)
41 // Record internally new *watched instance.
43 // Del removes internal copy of *watched instance.
45 // Watched returns *watched instance and native events for native type.
46 Watched(interface{}) (*watched, int64, error)
47 // Init initializes native watcher call.
49 // Watch starts watching provided file/dir.
50 Watch(os.FileInfo, *watched, int64) error
51 // Unwatch stops watching provided file/dir.
52 Unwatch(*watched) error
53 // Wait for new events.
54 Wait() (interface{}, error)
55 // IsStop checks if Wait finished because of request watcher's stop.
56 IsStop(n interface{}, err error) bool
59 // encode Event to native representation. Implementation is to be provided by
60 // platform specific implementation.
61 var encode func(Event, bool) int64
64 // nat2not matches native events to notify's ones. To be initialized by
65 // platform dependent implementation.
66 nat2not map[Event]Event
67 // not2nat matches notify's events to native ones. To be initialized by
68 // platform dependent implementation.
69 not2nat map[Event]Event
72 // trg is a main structure implementing watcher.
75 // s is a channel used to stop monitoring.
77 // c is a channel used to pass events further.
79 // pthLkp is a data structure mapping file names with data about watching
80 // represented by them files/directories.
81 pthLkp map[string]*watched
82 // t is a platform dependent implementation of trigger.
86 // newWatcher returns new watcher's implementation.
87 func newWatcher(c chan<- EventInfo) watcher {
89 s: make(chan struct{}, 1),
90 pthLkp: make(map[string]*watched, 0),
93 t.t = newTrigger(t.pthLkp)
94 if err := t.t.Init(); err != nil {
101 // Close implements watcher.
102 func (t *trg) Close() (err error) {
104 if err = t.t.Stop(); err != nil {
110 for _, w := range t.pthLkp {
111 if e = t.unwatch(w.p, w.fi); e != nil {
112 dbgprintf("trg: unwatch %q failed: %q\n", w.p, e)
116 if e = t.t.Close(); e != nil {
117 dbgprintf("trg: closing native watch failed: %q\n", e)
124 // send reported events one by one through chan.
125 func (t *trg) send(evn []event) {
131 // singlewatch starts to watch given p file/directory.
132 func (t *trg) singlewatch(p string, e Event, direct mode, fi os.FileInfo) (err error) {
135 if w, err = t.t.NewWatched(p, fi); err != nil {
148 if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil {
155 return errAlreadyWatched
158 // decode converts event received from native to notify.Event
159 // representation taking into account requested events (w).
160 func decode(o int64, w Event) (e Event) {
161 for f, n := range nat2not {
175 func (t *trg) watch(p string, e Event, fi os.FileInfo) error {
176 if err := t.singlewatch(p, e, dir, fi); err != nil {
177 if err != errAlreadyWatched {
182 err := t.walk(p, func(fi os.FileInfo) (err error) {
183 if err = t.singlewatch(filepath.Join(p, fi.Name()), e, ndir,
185 if err != errAlreadyWatched {
198 // walk runs f func on each file/dir from p directory.
199 func (t *trg) walk(p string, fn func(os.FileInfo) error) error {
200 fp, err := os.Open(p)
204 ls, err := fp.Readdir(0)
210 if err := fn(ls[i]); err != nil {
217 func (t *trg) unwatch(p string, fi os.FileInfo) error {
219 err := t.walk(p, func(fi os.FileInfo) error {
220 err := t.singleunwatch(filepath.Join(p, fi.Name()), ndir)
221 if err != errNotWatched {
230 return t.singleunwatch(p, dir)
233 // Watch implements Watcher interface.
234 func (t *trg) Watch(p string, e Event) error {
235 fi, err := os.Stat(p)
240 err = t.watch(p, e, fi)
245 // Unwatch implements Watcher interface.
246 func (t *trg) Unwatch(p string) error {
247 fi, err := os.Stat(p)
252 err = t.unwatch(p, fi)
257 // Rewatch implements Watcher interface.
259 // TODO(rjeczalik): This is a naive hack. Rewrite might help.
260 func (t *trg) Rewatch(p string, _, e Event) error {
261 fi, err := os.Stat(p)
266 if err = t.unwatch(p, fi); err == nil {
267 // TODO(rjeczalik): If watch fails then we leave trigger in inconsistent
268 // state. Handle? Panic? Native version of rewatch?
269 err = t.watch(p, e, fi)
275 func (*trg) file(w *watched, n interface{}, e Event) (evn []event) {
276 evn = append(evn, event{w.p, e, w.fi.IsDir(), n})
280 func (t *trg) dir(w *watched, n interface{}, e, ge Event) (evn []event) {
281 // If it's dir and delete we have to send it and continue, because
282 // other processing relies on opening (in this case not existing) dir.
283 // Events for contents of this dir are reported by native impl.
284 // However events for rename must be generated for all monitored files
285 // inside of moved directory, because native impl does not report it independently
286 // for each file descriptor being moved in result of move action on
288 if (ge & (not2nat[Rename] | not2nat[Remove])) != 0 {
289 // Write is reported also for Remove on directory. Because of that
290 // we have to filter it out explicitly.
291 evn = append(evn, event{w.p, e & ^Write & ^not2nat[Write], true, n})
292 if ge¬2nat[Rename] != 0 {
293 for p := range t.pthLkp {
294 if strings.HasPrefix(p, w.p+string(os.PathSeparator)) {
295 if err := t.singleunwatch(p, both); err != nil && err != errNotWatched &&
296 !os.IsNotExist(err) {
297 dbgprintf("trg: failed stop watching moved file (%q): %q\n",
300 if (w.eDir|w.eNonDir)&(not2nat[Rename]|Rename) != 0 {
301 evn = append(evn, event{
302 p, (w.eDir | w.eNonDir) & e &^ Write &^ not2nat[Write],
312 if (ge & not2nat[Write]) != 0 {
313 switch err := t.walk(w.p, func(fi os.FileInfo) error {
314 p := filepath.Join(w.p, fi.Name())
315 switch err := t.singlewatch(p, w.eDir, ndir, fi); {
316 case os.IsNotExist(err) && ((w.eDir & Remove) != 0):
317 evn = append(evn, event{p, Remove, fi.IsDir(), n})
318 case err == errAlreadyWatched:
320 dbgprintf("trg: watching %q failed: %q", p, err)
321 case (w.eDir & Create) != 0:
322 evn = append(evn, event{p, Create, fi.IsDir(), n})
327 case os.IsNotExist(err):
330 dbgprintf("trg: dir processing failed: %q", err)
345 // unwatch stops watching p file/directory.
346 func (t *trg) singleunwatch(p string, direct mode) error {
357 w.eDir, w.eNonDir = 0, 0
359 if err := t.t.Unwatch(w); err != nil {
362 if w.eNonDir|w.eDir != 0 {
367 if err := t.singlewatch(p, w.eNonDir|w.eDir, mod,
368 w.fi); err != nil && err != errAlreadyWatched {
377 func (t *trg) monitor() {
383 switch n, err = t.t.Wait(); {
384 case err == syscall.EINTR:
385 case t.t.IsStop(n, err):
389 dbgprintf("trg: failed to read events: %q\n", err)
396 // process event returned by native call.
397 func (t *trg) process(n interface{}) (evn []event) {
399 w, ge, err := t.t.Watched(n)
402 dbgprintf("trg: %v event lookup failed: %q", Event(ge), err)
406 e := decode(ge, w.eDir|w.eNonDir)
407 if ge&int64(not2nat[Remove]|not2nat[Rename]) == 0 {
408 switch fi, err := os.Stat(w.p); {
411 if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil {
412 dbgprintf("trg: %q is no longer watched: %q", w.p, err)
417 if e == Event(0) && (!w.fi.IsDir() || (ge&int64(not2nat[Write])) == 0) {
423 evn = append(evn, t.dir(w, n, e, Event(ge))...)
425 evn = append(evn, t.file(w, n, e)...)
427 if Event(ge)&(not2nat[Remove]|not2nat[Rename]) != 0 {