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