github.com/DiversionCompany/notify@v0.9.9/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&not2nat[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  }