github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/exp/fsnotify/fsnotify_bsd.go (about)

     1  // Copyright 2010 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build freebsd openbsd netbsd darwin
     6  
     7  package fsnotify
     8  
     9  import (
    10  	"errors"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"os"
    14  	"path/filepath"
    15  	"sync"
    16  	"syscall"
    17  )
    18  
    19  const (
    20  	// Flags (from <sys/event.h>)
    21  	sys_NOTE_DELETE = 0x0001 /* vnode was removed */
    22  	sys_NOTE_WRITE  = 0x0002 /* data contents changed */
    23  	sys_NOTE_EXTEND = 0x0004 /* size increased */
    24  	sys_NOTE_ATTRIB = 0x0008 /* attributes changed */
    25  	sys_NOTE_LINK   = 0x0010 /* link count changed */
    26  	sys_NOTE_RENAME = 0x0020 /* vnode was renamed */
    27  	sys_NOTE_REVOKE = 0x0040 /* vnode access was revoked */
    28  
    29  	// Watch all events
    30  	sys_NOTE_ALLEVENTS = sys_NOTE_DELETE | sys_NOTE_WRITE | sys_NOTE_ATTRIB | sys_NOTE_RENAME
    31  
    32  	// Block for 100 ms on each call to kevent
    33  	keventWaitTime = 100e6
    34  )
    35  
    36  type FileEvent struct {
    37  	mask   uint32 // Mask of events
    38  	Name   string // File name (optional)
    39  	create bool   // set by fsnotify package if found new file
    40  }
    41  
    42  // IsCreate reports whether the FileEvent was triggered by a creation
    43  func (e *FileEvent) IsCreate() bool { return e.create }
    44  
    45  // IsDelete reports whether the FileEvent was triggered by a delete
    46  func (e *FileEvent) IsDelete() bool { return (e.mask & sys_NOTE_DELETE) == sys_NOTE_DELETE }
    47  
    48  // IsModify reports whether the FileEvent was triggered by a file modification
    49  func (e *FileEvent) IsModify() bool {
    50  	return ((e.mask&sys_NOTE_WRITE) == sys_NOTE_WRITE || (e.mask&sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB)
    51  }
    52  
    53  // IsRename reports whether the FileEvent was triggered by a change name
    54  func (e *FileEvent) IsRename() bool { return (e.mask & sys_NOTE_RENAME) == sys_NOTE_RENAME }
    55  
    56  // IsAttrib reports whether the FileEvent was triggered by a change in the file metadata.
    57  func (e *FileEvent) IsAttrib() bool {
    58  	return (e.mask & sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB
    59  }
    60  
    61  type Watcher struct {
    62  	mu              sync.Mutex          // Mutex for the Watcher itself.
    63  	kq              int                 // File descriptor (as returned by the kqueue() syscall)
    64  	watches         map[string]int      // Map of watched file descriptors (key: path)
    65  	wmut            sync.Mutex          // Protects access to watches.
    66  	enFlags         map[string]uint32   // Map of watched files to evfilt note flags used in kqueue
    67  	enmut           sync.Mutex          // Protects access to enFlags.
    68  	paths           map[int]string      // Map of watched paths (key: watch descriptor)
    69  	finfo           map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor)
    70  	pmut            sync.Mutex          // Protects access to paths and finfo.
    71  	fileExists      map[string]bool     // Keep track of if we know this file exists (to stop duplicate create events)
    72  	femut           sync.Mutex          // Protects access to fileExists.
    73  	externalWatches map[string]bool     // Map of watches added by user of the library.
    74  	ewmut           sync.Mutex          // Protects access to externalWatches.
    75  	Error           chan error          // Errors are sent on this channel
    76  	Event           chan *FileEvent     // Events are returned on this channel
    77  	done            chan bool           // Channel for sending a "quit message" to the reader goroutine
    78  	isClosed        bool                // Set to true when Close() is first called
    79  	kbuf            [1]syscall.Kevent_t // An event buffer for Add/Remove watch
    80  	bufmut          sync.Mutex          // Protects access to kbuf.
    81  }
    82  
    83  // NewWatcher creates and returns a new kevent instance using kqueue(2)
    84  func NewWatcher() (*Watcher, error) {
    85  	fd, errno := syscall.Kqueue()
    86  	if fd == -1 {
    87  		return nil, os.NewSyscallError("kqueue", errno)
    88  	}
    89  	w := &Watcher{
    90  		kq:              fd,
    91  		watches:         make(map[string]int),
    92  		enFlags:         make(map[string]uint32),
    93  		paths:           make(map[int]string),
    94  		finfo:           make(map[int]os.FileInfo),
    95  		fileExists:      make(map[string]bool),
    96  		externalWatches: make(map[string]bool),
    97  		Event:           make(chan *FileEvent),
    98  		Error:           make(chan error),
    99  		done:            make(chan bool, 1),
   100  	}
   101  
   102  	go w.readEvents()
   103  	return w, nil
   104  }
   105  
   106  // Close closes a kevent watcher instance
   107  // It sends a message to the reader goroutine to quit and removes all watches
   108  // associated with the kevent instance
   109  func (w *Watcher) Close() error {
   110  	w.mu.Lock()
   111  	if w.isClosed {
   112  		w.mu.Unlock()
   113  		return nil
   114  	}
   115  	w.isClosed = true
   116  	w.mu.Unlock()
   117  
   118  	// Send "quit" message to the reader goroutine
   119  	w.done <- true
   120  	w.pmut.Lock()
   121  	ws := w.watches
   122  	w.pmut.Unlock()
   123  	for path := range ws {
   124  		w.removeWatch(path)
   125  	}
   126  
   127  	return nil
   128  }
   129  
   130  // AddWatch adds path to the watched file set.
   131  // The flags are interpreted as described in kevent(2).
   132  func (w *Watcher) addWatch(path string, flags uint32) error {
   133  	w.mu.Lock()
   134  	if w.isClosed {
   135  		w.mu.Unlock()
   136  		return errors.New("kevent instance already closed")
   137  	}
   138  	w.mu.Unlock()
   139  
   140  	watchDir := false
   141  
   142  	w.wmut.Lock()
   143  	watchfd, found := w.watches[path]
   144  	w.wmut.Unlock()
   145  	if !found {
   146  		fi, errstat := os.Lstat(path)
   147  		if errstat != nil {
   148  			return errstat
   149  		}
   150  
   151  		// don't watch socket
   152  		if fi.Mode()&os.ModeSocket == os.ModeSocket {
   153  			return nil
   154  		}
   155  
   156  		// Follow Symlinks
   157  		// Unfortunately, Linux can add bogus symlinks to watch list without
   158  		// issue, and Windows can't do symlinks period (AFAIK). To  maintain
   159  		// consistency, we will act like everything is fine. There will simply
   160  		// be no file events for broken symlinks.
   161  		// Hence the returns of nil on errors.
   162  		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
   163  			path, err := filepath.EvalSymlinks(path)
   164  			if err != nil {
   165  				return nil
   166  			}
   167  
   168  			fi, errstat = os.Lstat(path)
   169  			if errstat != nil {
   170  				return nil
   171  			}
   172  		}
   173  
   174  		fd, errno := syscall.Open(path, open_FLAGS, 0700)
   175  		if fd == -1 {
   176  			return errno
   177  		}
   178  		watchfd = fd
   179  
   180  		w.wmut.Lock()
   181  		w.watches[path] = watchfd
   182  		w.wmut.Unlock()
   183  
   184  		w.pmut.Lock()
   185  		w.paths[watchfd] = path
   186  		w.finfo[watchfd] = fi
   187  		w.pmut.Unlock()
   188  	}
   189  	// Watch the directory if it has not been watched before.
   190  	w.pmut.Lock()
   191  	w.enmut.Lock()
   192  	if w.finfo[watchfd].IsDir() &&
   193  		(flags&sys_NOTE_WRITE) == sys_NOTE_WRITE &&
   194  		(!found || (w.enFlags[path]&sys_NOTE_WRITE) != sys_NOTE_WRITE) {
   195  		watchDir = true
   196  	}
   197  	w.enmut.Unlock()
   198  	w.pmut.Unlock()
   199  
   200  	w.enmut.Lock()
   201  	w.enFlags[path] = flags
   202  	w.enmut.Unlock()
   203  
   204  	w.bufmut.Lock()
   205  	watchEntry := &w.kbuf[0]
   206  	watchEntry.Fflags = flags
   207  	syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_ADD|syscall.EV_CLEAR)
   208  	entryFlags := watchEntry.Flags
   209  	w.bufmut.Unlock()
   210  
   211  	wd, errno := syscall.Kevent(w.kq, w.kbuf[:], nil, nil)
   212  	if wd == -1 {
   213  		return errno
   214  	} else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR {
   215  		return errors.New("kevent add error")
   216  	}
   217  
   218  	if watchDir {
   219  		errdir := w.watchDirectoryFiles(path)
   220  		if errdir != nil {
   221  			return errdir
   222  		}
   223  	}
   224  	return nil
   225  }
   226  
   227  // Watch adds path to the watched file set, watching all events.
   228  func (w *Watcher) watch(path string) error {
   229  	w.ewmut.Lock()
   230  	w.externalWatches[path] = true
   231  	w.ewmut.Unlock()
   232  	return w.addWatch(path, sys_NOTE_ALLEVENTS)
   233  }
   234  
   235  // RemoveWatch removes path from the watched file set.
   236  func (w *Watcher) removeWatch(path string) error {
   237  	w.wmut.Lock()
   238  	watchfd, ok := w.watches[path]
   239  	w.wmut.Unlock()
   240  	if !ok {
   241  		return errors.New(fmt.Sprintf("can't remove non-existent kevent watch for: %s", path))
   242  	}
   243  	w.bufmut.Lock()
   244  	watchEntry := &w.kbuf[0]
   245  	syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_DELETE)
   246  	success, errno := syscall.Kevent(w.kq, w.kbuf[:], nil, nil)
   247  	w.bufmut.Unlock()
   248  	if success == -1 {
   249  		return os.NewSyscallError("kevent_rm_watch", errno)
   250  	} else if (watchEntry.Flags & syscall.EV_ERROR) == syscall.EV_ERROR {
   251  		return errors.New("kevent rm error")
   252  	}
   253  	syscall.Close(watchfd)
   254  	w.wmut.Lock()
   255  	delete(w.watches, path)
   256  	w.wmut.Unlock()
   257  	w.enmut.Lock()
   258  	delete(w.enFlags, path)
   259  	w.enmut.Unlock()
   260  	w.pmut.Lock()
   261  	delete(w.paths, watchfd)
   262  	fInfo := w.finfo[watchfd]
   263  	delete(w.finfo, watchfd)
   264  	w.pmut.Unlock()
   265  
   266  	// Find all watched paths that are in this directory that are not external.
   267  	if fInfo.IsDir() {
   268  		var pathsToRemove []string
   269  		w.pmut.Lock()
   270  		for _, wpath := range w.paths {
   271  			wdir, _ := filepath.Split(wpath)
   272  			if filepath.Clean(wdir) == filepath.Clean(path) {
   273  				w.ewmut.Lock()
   274  				if !w.externalWatches[wpath] {
   275  					pathsToRemove = append(pathsToRemove, wpath)
   276  				}
   277  				w.ewmut.Unlock()
   278  			}
   279  		}
   280  		w.pmut.Unlock()
   281  		for _, p := range pathsToRemove {
   282  			// Since these are internal, not much sense in propagating error
   283  			// to the user, as that will just confuse them with an error about
   284  			// a path they did not explicitly watch themselves.
   285  			w.removeWatch(p)
   286  		}
   287  	}
   288  
   289  	return nil
   290  }
   291  
   292  // readEvents reads from the kqueue file descriptor, converts the
   293  // received events into Event objects and sends them via the Event channel
   294  func (w *Watcher) readEvents() {
   295  	var (
   296  		eventbuf [10]syscall.Kevent_t // Event buffer
   297  		events   []syscall.Kevent_t   // Received events
   298  		twait    *syscall.Timespec    // Time to block waiting for events
   299  		n        int                  // Number of events returned from kevent
   300  		errno    error                // Syscall errno
   301  	)
   302  	events = eventbuf[0:0]
   303  	twait = new(syscall.Timespec)
   304  	*twait = syscall.NsecToTimespec(keventWaitTime)
   305  
   306  	for {
   307  		// See if there is a message on the "done" channel
   308  		var done bool
   309  		select {
   310  		case done = <-w.done:
   311  		default:
   312  		}
   313  
   314  		// If "done" message is received
   315  		if done {
   316  			errno := syscall.Close(w.kq)
   317  			if errno != nil {
   318  				w.Error <- os.NewSyscallError("close", errno)
   319  			}
   320  			close(w.Event)
   321  			close(w.Error)
   322  			return
   323  		}
   324  
   325  		// Get new events
   326  		if len(events) == 0 {
   327  			n, errno = syscall.Kevent(w.kq, nil, eventbuf[:], twait)
   328  
   329  			// EINTR is okay, basically the syscall was interrupted before
   330  			// timeout expired.
   331  			if errno != nil && errno != syscall.EINTR {
   332  				w.Error <- os.NewSyscallError("kevent", errno)
   333  				continue
   334  			}
   335  
   336  			// Received some events
   337  			if n > 0 {
   338  				events = eventbuf[0:n]
   339  			}
   340  		}
   341  
   342  		// Flush the events we received to the events channel
   343  		for len(events) > 0 {
   344  			fileEvent := new(FileEvent)
   345  			watchEvent := &events[0]
   346  			fileEvent.mask = uint32(watchEvent.Fflags)
   347  			w.pmut.Lock()
   348  			fileEvent.Name = w.paths[int(watchEvent.Ident)]
   349  			fileInfo := w.finfo[int(watchEvent.Ident)]
   350  			w.pmut.Unlock()
   351  			if fileInfo != nil && fileInfo.IsDir() && !fileEvent.IsDelete() {
   352  				// Double check to make sure the directory exist. This can happen when
   353  				// we do a rm -fr on a recursively watched folders and we receive a
   354  				// modification event first but the folder has been deleted and later
   355  				// receive the delete event
   356  				if _, err := os.Lstat(fileEvent.Name); os.IsNotExist(err) {
   357  					// mark is as delete event
   358  					fileEvent.mask |= sys_NOTE_DELETE
   359  				}
   360  			}
   361  
   362  			if fileInfo != nil && fileInfo.IsDir() && fileEvent.IsModify() && !fileEvent.IsDelete() {
   363  				w.sendDirectoryChangeEvents(fileEvent.Name)
   364  			} else {
   365  				// Send the event on the events channel
   366  				w.Event <- fileEvent
   367  			}
   368  
   369  			// Move to next event
   370  			events = events[1:]
   371  
   372  			if fileEvent.IsRename() {
   373  				w.removeWatch(fileEvent.Name)
   374  				w.femut.Lock()
   375  				delete(w.fileExists, fileEvent.Name)
   376  				w.femut.Unlock()
   377  			}
   378  			if fileEvent.IsDelete() {
   379  				w.removeWatch(fileEvent.Name)
   380  				w.femut.Lock()
   381  				delete(w.fileExists, fileEvent.Name)
   382  				w.femut.Unlock()
   383  
   384  				// Look for a file that may have overwritten this
   385  				// (ie mv f1 f2 will delete f2 then create f2)
   386  				fileDir, _ := filepath.Split(fileEvent.Name)
   387  				fileDir = filepath.Clean(fileDir)
   388  				w.wmut.Lock()
   389  				_, found := w.watches[fileDir]
   390  				w.wmut.Unlock()
   391  				if found {
   392  					// make sure the directory exist before we watch for changes. When we
   393  					// do a recursive watch and perform rm -fr, the parent directory might
   394  					// have gone missing, ignore the missing directory and let the
   395  					// upcoming delete event remove the watch form the parent folder
   396  					if _, err := os.Lstat(fileDir); !os.IsNotExist(err) {
   397  						w.sendDirectoryChangeEvents(fileDir)
   398  					}
   399  				}
   400  			}
   401  		}
   402  	}
   403  }
   404  
   405  func (w *Watcher) watchDirectoryFiles(dirPath string) error {
   406  	// Get all files
   407  	files, err := ioutil.ReadDir(dirPath)
   408  	if err != nil {
   409  		return err
   410  	}
   411  
   412  	// Search for new files
   413  	for _, fileInfo := range files {
   414  		filePath := filepath.Join(dirPath, fileInfo.Name())
   415  
   416  		if fileInfo.IsDir() == false {
   417  			// Watch file to mimic linux fsnotify
   418  			e := w.addWatch(filePath, sys_NOTE_ALLEVENTS)
   419  			if e != nil {
   420  				return e
   421  			}
   422  		} else {
   423  			// If the user is currently watching directory
   424  			// we want to preserve the flags used
   425  			w.enmut.Lock()
   426  			currFlags, found := w.enFlags[filePath]
   427  			w.enmut.Unlock()
   428  			var newFlags uint32 = sys_NOTE_DELETE
   429  			if found {
   430  				newFlags |= currFlags
   431  			}
   432  
   433  			// Linux gives deletes if not explicitly watching
   434  			e := w.addWatch(filePath, newFlags)
   435  			if e != nil {
   436  				return e
   437  			}
   438  		}
   439  		w.femut.Lock()
   440  		w.fileExists[filePath] = true
   441  		w.femut.Unlock()
   442  	}
   443  
   444  	return nil
   445  }
   446  
   447  // sendDirectoryEvents searches the directory for newly created files
   448  // and sends them over the event channel. This functionality is to have
   449  // the BSD version of fsnotify match linux fsnotify which provides a
   450  // create event for files created in a watched directory.
   451  func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
   452  	// Get all files
   453  	files, err := ioutil.ReadDir(dirPath)
   454  	if err != nil {
   455  		w.Error <- err
   456  	}
   457  
   458  	// Search for new files
   459  	for _, fileInfo := range files {
   460  		filePath := filepath.Join(dirPath, fileInfo.Name())
   461  		w.femut.Lock()
   462  		_, doesExist := w.fileExists[filePath]
   463  		w.femut.Unlock()
   464  		if !doesExist {
   465  			// Send create event
   466  			fileEvent := new(FileEvent)
   467  			fileEvent.Name = filePath
   468  			fileEvent.create = true
   469  			w.Event <- fileEvent
   470  		}
   471  		w.femut.Lock()
   472  		w.fileExists[filePath] = true
   473  		w.femut.Unlock()
   474  	}
   475  	w.watchDirectoryFiles(dirPath)
   476  }