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&not2nat[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  }