vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
changeset 265 05c40b36d3b2
equal deleted inserted replaced
264:8f478162d991 265:05c40b36d3b2
       
     1 //go:build freebsd || openbsd || netbsd || dragonfly || darwin
       
     2 // +build freebsd openbsd netbsd dragonfly darwin
       
     3 
       
     4 package fsnotify
       
     5 
       
     6 import (
       
     7 	"errors"
       
     8 	"fmt"
       
     9 	"io/ioutil"
       
    10 	"os"
       
    11 	"path/filepath"
       
    12 	"sync"
       
    13 
       
    14 	"golang.org/x/sys/unix"
       
    15 )
       
    16 
       
    17 // Watcher watches a set of paths, delivering events on a channel.
       
    18 //
       
    19 // A watcher should not be copied (e.g. pass it by pointer, rather than by
       
    20 // value).
       
    21 //
       
    22 // # Linux notes
       
    23 //
       
    24 // When a file is removed a Remove event won't be emitted until all file
       
    25 // descriptors are closed, and deletes will always emit a Chmod. For example:
       
    26 //
       
    27 //     fp := os.Open("file")
       
    28 //     os.Remove("file")        // Triggers Chmod
       
    29 //     fp.Close()               // Triggers Remove
       
    30 //
       
    31 // This is the event that inotify sends, so not much can be changed about this.
       
    32 //
       
    33 // The fs.inotify.max_user_watches sysctl variable specifies the upper limit
       
    34 // for the number of watches per user, and fs.inotify.max_user_instances
       
    35 // specifies the maximum number of inotify instances per user. Every Watcher you
       
    36 // create is an "instance", and every path you add is a "watch".
       
    37 //
       
    38 // These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
       
    39 // /proc/sys/fs/inotify/max_user_instances
       
    40 //
       
    41 // To increase them you can use sysctl or write the value to the /proc file:
       
    42 //
       
    43 //     # Default values on Linux 5.18
       
    44 //     sysctl fs.inotify.max_user_watches=124983
       
    45 //     sysctl fs.inotify.max_user_instances=128
       
    46 //
       
    47 // To make the changes persist on reboot edit /etc/sysctl.conf or
       
    48 // /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
       
    49 // your distro's documentation):
       
    50 //
       
    51 //     fs.inotify.max_user_watches=124983
       
    52 //     fs.inotify.max_user_instances=128
       
    53 //
       
    54 // Reaching the limit will result in a "no space left on device" or "too many open
       
    55 // files" error.
       
    56 //
       
    57 // # kqueue notes (macOS, BSD)
       
    58 //
       
    59 // kqueue requires opening a file descriptor for every file that's being watched;
       
    60 // so if you're watching a directory with five files then that's six file
       
    61 // descriptors. You will run in to your system's "max open files" limit faster on
       
    62 // these platforms.
       
    63 //
       
    64 // The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
       
    65 // control the maximum number of open files, as well as /etc/login.conf on BSD
       
    66 // systems.
       
    67 //
       
    68 // # macOS notes
       
    69 //
       
    70 // Spotlight indexing on macOS can result in multiple events (see [#15]). A
       
    71 // temporary workaround is to add your folder(s) to the "Spotlight Privacy
       
    72 // Settings" until we have a native FSEvents implementation (see [#11]).
       
    73 //
       
    74 // [#11]: https://github.com/fsnotify/fsnotify/issues/11
       
    75 // [#15]: https://github.com/fsnotify/fsnotify/issues/15
       
    76 type Watcher struct {
       
    77 	// Events sends the filesystem change events.
       
    78 	//
       
    79 	// fsnotify can send the following events; a "path" here can refer to a
       
    80 	// file, directory, symbolic link, or special file like a FIFO.
       
    81 	//
       
    82 	//   fsnotify.Create    A new path was created; this may be followed by one
       
    83 	//                      or more Write events if data also gets written to a
       
    84 	//                      file.
       
    85 	//
       
    86 	//   fsnotify.Remove    A path was removed.
       
    87 	//
       
    88 	//   fsnotify.Rename    A path was renamed. A rename is always sent with the
       
    89 	//                      old path as Event.Name, and a Create event will be
       
    90 	//                      sent with the new name. Renames are only sent for
       
    91 	//                      paths that are currently watched; e.g. moving an
       
    92 	//                      unmonitored file into a monitored directory will
       
    93 	//                      show up as just a Create. Similarly, renaming a file
       
    94 	//                      to outside a monitored directory will show up as
       
    95 	//                      only a Rename.
       
    96 	//
       
    97 	//   fsnotify.Write     A file or named pipe was written to. A Truncate will
       
    98 	//                      also trigger a Write. A single "write action"
       
    99 	//                      initiated by the user may show up as one or multiple
       
   100 	//                      writes, depending on when the system syncs things to
       
   101 	//                      disk. For example when compiling a large Go program
       
   102 	//                      you may get hundreds of Write events, so you
       
   103 	//                      probably want to wait until you've stopped receiving
       
   104 	//                      them (see the dedup example in cmd/fsnotify).
       
   105 	//
       
   106 	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
       
   107 	//                      when a file is removed (or more accurately, when a
       
   108 	//                      link to an inode is removed). On kqueue it's sent
       
   109 	//                      and on kqueue when a file is truncated. On Windows
       
   110 	//                      it's never sent.
       
   111 	Events chan Event
       
   112 
       
   113 	// Errors sends any errors.
       
   114 	Errors chan error
       
   115 
       
   116 	done         chan struct{}
       
   117 	kq           int                         // File descriptor (as returned by the kqueue() syscall).
       
   118 	closepipe    [2]int                      // Pipe used for closing.
       
   119 	mu           sync.Mutex                  // Protects access to watcher data
       
   120 	watches      map[string]int              // Watched file descriptors (key: path).
       
   121 	watchesByDir map[string]map[int]struct{} // Watched file descriptors indexed by the parent directory (key: dirname(path)).
       
   122 	userWatches  map[string]struct{}         // Watches added with Watcher.Add()
       
   123 	dirFlags     map[string]uint32           // Watched directories to fflags used in kqueue.
       
   124 	paths        map[int]pathInfo            // File descriptors to path names for processing kqueue events.
       
   125 	fileExists   map[string]struct{}         // Keep track of if we know this file exists (to stop duplicate create events).
       
   126 	isClosed     bool                        // Set to true when Close() is first called
       
   127 }
       
   128 
       
   129 type pathInfo struct {
       
   130 	name  string
       
   131 	isDir bool
       
   132 }
       
   133 
       
   134 // NewWatcher creates a new Watcher.
       
   135 func NewWatcher() (*Watcher, error) {
       
   136 	kq, closepipe, err := newKqueue()
       
   137 	if err != nil {
       
   138 		return nil, err
       
   139 	}
       
   140 
       
   141 	w := &Watcher{
       
   142 		kq:           kq,
       
   143 		closepipe:    closepipe,
       
   144 		watches:      make(map[string]int),
       
   145 		watchesByDir: make(map[string]map[int]struct{}),
       
   146 		dirFlags:     make(map[string]uint32),
       
   147 		paths:        make(map[int]pathInfo),
       
   148 		fileExists:   make(map[string]struct{}),
       
   149 		userWatches:  make(map[string]struct{}),
       
   150 		Events:       make(chan Event),
       
   151 		Errors:       make(chan error),
       
   152 		done:         make(chan struct{}),
       
   153 	}
       
   154 
       
   155 	go w.readEvents()
       
   156 	return w, nil
       
   157 }
       
   158 
       
   159 // newKqueue creates a new kernel event queue and returns a descriptor.
       
   160 //
       
   161 // This registers a new event on closepipe, which will trigger an event when
       
   162 // it's closed. This way we can use kevent() without timeout/polling; without
       
   163 // the closepipe, it would block forever and we wouldn't be able to stop it at
       
   164 // all.
       
   165 func newKqueue() (kq int, closepipe [2]int, err error) {
       
   166 	kq, err = unix.Kqueue()
       
   167 	if kq == -1 {
       
   168 		return kq, closepipe, err
       
   169 	}
       
   170 
       
   171 	// Register the close pipe.
       
   172 	err = unix.Pipe(closepipe[:])
       
   173 	if err != nil {
       
   174 		unix.Close(kq)
       
   175 		return kq, closepipe, err
       
   176 	}
       
   177 
       
   178 	// Register changes to listen on the closepipe.
       
   179 	changes := make([]unix.Kevent_t, 1)
       
   180 	// SetKevent converts int to the platform-specific types.
       
   181 	unix.SetKevent(&changes[0], closepipe[0], unix.EVFILT_READ,
       
   182 		unix.EV_ADD|unix.EV_ENABLE|unix.EV_ONESHOT)
       
   183 
       
   184 	ok, err := unix.Kevent(kq, changes, nil, nil)
       
   185 	if ok == -1 {
       
   186 		unix.Close(kq)
       
   187 		unix.Close(closepipe[0])
       
   188 		unix.Close(closepipe[1])
       
   189 		return kq, closepipe, err
       
   190 	}
       
   191 	return kq, closepipe, nil
       
   192 }
       
   193 
       
   194 // Returns true if the event was sent, or false if watcher is closed.
       
   195 func (w *Watcher) sendEvent(e Event) bool {
       
   196 	select {
       
   197 	case w.Events <- e:
       
   198 		return true
       
   199 	case <-w.done:
       
   200 	}
       
   201 	return false
       
   202 }
       
   203 
       
   204 // Returns true if the error was sent, or false if watcher is closed.
       
   205 func (w *Watcher) sendError(err error) bool {
       
   206 	select {
       
   207 	case w.Errors <- err:
       
   208 		return true
       
   209 	case <-w.done:
       
   210 	}
       
   211 	return false
       
   212 }
       
   213 
       
   214 // Close removes all watches and closes the events channel.
       
   215 func (w *Watcher) Close() error {
       
   216 	w.mu.Lock()
       
   217 	if w.isClosed {
       
   218 		w.mu.Unlock()
       
   219 		return nil
       
   220 	}
       
   221 	w.isClosed = true
       
   222 
       
   223 	// copy paths to remove while locked
       
   224 	pathsToRemove := make([]string, 0, len(w.watches))
       
   225 	for name := range w.watches {
       
   226 		pathsToRemove = append(pathsToRemove, name)
       
   227 	}
       
   228 	w.mu.Unlock() // Unlock before calling Remove, which also locks
       
   229 	for _, name := range pathsToRemove {
       
   230 		w.Remove(name)
       
   231 	}
       
   232 
       
   233 	// Send "quit" message to the reader goroutine.
       
   234 	unix.Close(w.closepipe[1])
       
   235 	close(w.done)
       
   236 
       
   237 	return nil
       
   238 }
       
   239 
       
   240 // Add starts monitoring the path for changes.
       
   241 //
       
   242 // A path can only be watched once; attempting to watch it more than once will
       
   243 // return an error. Paths that do not yet exist on the filesystem cannot be
       
   244 // added. A watch will be automatically removed if the path is deleted.
       
   245 //
       
   246 // A path will remain watched if it gets renamed to somewhere else on the same
       
   247 // filesystem, but the monitor will get removed if the path gets deleted and
       
   248 // re-created, or if it's moved to a different filesystem.
       
   249 //
       
   250 // Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
       
   251 // filesystems (/proc, /sys, etc.) generally don't work.
       
   252 //
       
   253 // # Watching directories
       
   254 //
       
   255 // All files in a directory are monitored, including new files that are created
       
   256 // after the watcher is started. Subdirectories are not watched (i.e. it's
       
   257 // non-recursive).
       
   258 //
       
   259 // # Watching files
       
   260 //
       
   261 // Watching individual files (rather than directories) is generally not
       
   262 // recommended as many tools update files atomically. Instead of "just" writing
       
   263 // to the file a temporary file will be written to first, and if successful the
       
   264 // temporary file is moved to to destination removing the original, or some
       
   265 // variant thereof. The watcher on the original file is now lost, as it no
       
   266 // longer exists.
       
   267 //
       
   268 // Instead, watch the parent directory and use Event.Name to filter out files
       
   269 // you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
       
   270 func (w *Watcher) Add(name string) error {
       
   271 	w.mu.Lock()
       
   272 	w.userWatches[name] = struct{}{}
       
   273 	w.mu.Unlock()
       
   274 	_, err := w.addWatch(name, noteAllEvents)
       
   275 	return err
       
   276 }
       
   277 
       
   278 // Remove stops monitoring the path for changes.
       
   279 //
       
   280 // Directories are always removed non-recursively. For example, if you added
       
   281 // /tmp/dir and /tmp/dir/subdir then you will need to remove both.
       
   282 //
       
   283 // Removing a path that has not yet been added returns [ErrNonExistentWatch].
       
   284 func (w *Watcher) Remove(name string) error {
       
   285 	name = filepath.Clean(name)
       
   286 	w.mu.Lock()
       
   287 	watchfd, ok := w.watches[name]
       
   288 	w.mu.Unlock()
       
   289 	if !ok {
       
   290 		return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
       
   291 	}
       
   292 
       
   293 	err := w.register([]int{watchfd}, unix.EV_DELETE, 0)
       
   294 	if err != nil {
       
   295 		return err
       
   296 	}
       
   297 
       
   298 	unix.Close(watchfd)
       
   299 
       
   300 	w.mu.Lock()
       
   301 	isDir := w.paths[watchfd].isDir
       
   302 	delete(w.watches, name)
       
   303 	delete(w.userWatches, name)
       
   304 
       
   305 	parentName := filepath.Dir(name)
       
   306 	delete(w.watchesByDir[parentName], watchfd)
       
   307 
       
   308 	if len(w.watchesByDir[parentName]) == 0 {
       
   309 		delete(w.watchesByDir, parentName)
       
   310 	}
       
   311 
       
   312 	delete(w.paths, watchfd)
       
   313 	delete(w.dirFlags, name)
       
   314 	delete(w.fileExists, name)
       
   315 	w.mu.Unlock()
       
   316 
       
   317 	// Find all watched paths that are in this directory that are not external.
       
   318 	if isDir {
       
   319 		var pathsToRemove []string
       
   320 		w.mu.Lock()
       
   321 		for fd := range w.watchesByDir[name] {
       
   322 			path := w.paths[fd]
       
   323 			if _, ok := w.userWatches[path.name]; !ok {
       
   324 				pathsToRemove = append(pathsToRemove, path.name)
       
   325 			}
       
   326 		}
       
   327 		w.mu.Unlock()
       
   328 		for _, name := range pathsToRemove {
       
   329 			// Since these are internal, not much sense in propagating error
       
   330 			// to the user, as that will just confuse them with an error about
       
   331 			// a path they did not explicitly watch themselves.
       
   332 			w.Remove(name)
       
   333 		}
       
   334 	}
       
   335 
       
   336 	return nil
       
   337 }
       
   338 
       
   339 // WatchList returns all paths added with [Add] (and are not yet removed).
       
   340 func (w *Watcher) WatchList() []string {
       
   341 	w.mu.Lock()
       
   342 	defer w.mu.Unlock()
       
   343 
       
   344 	entries := make([]string, 0, len(w.userWatches))
       
   345 	for pathname := range w.userWatches {
       
   346 		entries = append(entries, pathname)
       
   347 	}
       
   348 
       
   349 	return entries
       
   350 }
       
   351 
       
   352 // Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
       
   353 const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
       
   354 
       
   355 // addWatch adds name to the watched file set.
       
   356 // The flags are interpreted as described in kevent(2).
       
   357 // Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
       
   358 func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
       
   359 	var isDir bool
       
   360 	// Make ./name and name equivalent
       
   361 	name = filepath.Clean(name)
       
   362 
       
   363 	w.mu.Lock()
       
   364 	if w.isClosed {
       
   365 		w.mu.Unlock()
       
   366 		return "", errors.New("kevent instance already closed")
       
   367 	}
       
   368 	watchfd, alreadyWatching := w.watches[name]
       
   369 	// We already have a watch, but we can still override flags.
       
   370 	if alreadyWatching {
       
   371 		isDir = w.paths[watchfd].isDir
       
   372 	}
       
   373 	w.mu.Unlock()
       
   374 
       
   375 	if !alreadyWatching {
       
   376 		fi, err := os.Lstat(name)
       
   377 		if err != nil {
       
   378 			return "", err
       
   379 		}
       
   380 
       
   381 		// Don't watch sockets or named pipes
       
   382 		if (fi.Mode()&os.ModeSocket == os.ModeSocket) || (fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe) {
       
   383 			return "", nil
       
   384 		}
       
   385 
       
   386 		// Follow Symlinks
       
   387 		//
       
   388 		// Linux can add unresolvable symlinks to the watch list without issue,
       
   389 		// and Windows can't do symlinks period. To maintain consistency, we
       
   390 		// will act like everything is fine if the link can't be resolved.
       
   391 		// There will simply be no file events for broken symlinks. Hence the
       
   392 		// returns of nil on errors.
       
   393 		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
       
   394 			name, err = filepath.EvalSymlinks(name)
       
   395 			if err != nil {
       
   396 				return "", nil
       
   397 			}
       
   398 
       
   399 			w.mu.Lock()
       
   400 			_, alreadyWatching = w.watches[name]
       
   401 			w.mu.Unlock()
       
   402 
       
   403 			if alreadyWatching {
       
   404 				return name, nil
       
   405 			}
       
   406 
       
   407 			fi, err = os.Lstat(name)
       
   408 			if err != nil {
       
   409 				return "", nil
       
   410 			}
       
   411 		}
       
   412 
       
   413 		// Retry on EINTR; open() can return EINTR in practice on macOS.
       
   414 		// See #354, and go issues 11180 and 39237.
       
   415 		for {
       
   416 			watchfd, err = unix.Open(name, openMode, 0)
       
   417 			if err == nil {
       
   418 				break
       
   419 			}
       
   420 			if errors.Is(err, unix.EINTR) {
       
   421 				continue
       
   422 			}
       
   423 
       
   424 			return "", err
       
   425 		}
       
   426 
       
   427 		isDir = fi.IsDir()
       
   428 	}
       
   429 
       
   430 	err := w.register([]int{watchfd}, unix.EV_ADD|unix.EV_CLEAR|unix.EV_ENABLE, flags)
       
   431 	if err != nil {
       
   432 		unix.Close(watchfd)
       
   433 		return "", err
       
   434 	}
       
   435 
       
   436 	if !alreadyWatching {
       
   437 		w.mu.Lock()
       
   438 		parentName := filepath.Dir(name)
       
   439 		w.watches[name] = watchfd
       
   440 
       
   441 		watchesByDir, ok := w.watchesByDir[parentName]
       
   442 		if !ok {
       
   443 			watchesByDir = make(map[int]struct{}, 1)
       
   444 			w.watchesByDir[parentName] = watchesByDir
       
   445 		}
       
   446 		watchesByDir[watchfd] = struct{}{}
       
   447 
       
   448 		w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
       
   449 		w.mu.Unlock()
       
   450 	}
       
   451 
       
   452 	if isDir {
       
   453 		// Watch the directory if it has not been watched before,
       
   454 		// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
       
   455 		w.mu.Lock()
       
   456 
       
   457 		watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
       
   458 			(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
       
   459 		// Store flags so this watch can be updated later
       
   460 		w.dirFlags[name] = flags
       
   461 		w.mu.Unlock()
       
   462 
       
   463 		if watchDir {
       
   464 			if err := w.watchDirectoryFiles(name); err != nil {
       
   465 				return "", err
       
   466 			}
       
   467 		}
       
   468 	}
       
   469 	return name, nil
       
   470 }
       
   471 
       
   472 // readEvents reads from kqueue and converts the received kevents into
       
   473 // Event values that it sends down the Events channel.
       
   474 func (w *Watcher) readEvents() {
       
   475 	defer func() {
       
   476 		err := unix.Close(w.kq)
       
   477 		if err != nil {
       
   478 			w.Errors <- err
       
   479 		}
       
   480 		unix.Close(w.closepipe[0])
       
   481 		close(w.Events)
       
   482 		close(w.Errors)
       
   483 	}()
       
   484 
       
   485 	eventBuffer := make([]unix.Kevent_t, 10)
       
   486 	for closed := false; !closed; {
       
   487 		kevents, err := w.read(eventBuffer)
       
   488 		// EINTR is okay, the syscall was interrupted before timeout expired.
       
   489 		if err != nil && err != unix.EINTR {
       
   490 			if !w.sendError(fmt.Errorf("fsnotify.readEvents: %w", err)) {
       
   491 				closed = true
       
   492 			}
       
   493 			continue
       
   494 		}
       
   495 
       
   496 		// Flush the events we received to the Events channel
       
   497 		for _, kevent := range kevents {
       
   498 			var (
       
   499 				watchfd = int(kevent.Ident)
       
   500 				mask    = uint32(kevent.Fflags)
       
   501 			)
       
   502 
       
   503 			// Shut down the loop when the pipe is closed, but only after all
       
   504 			// other events have been processed.
       
   505 			if watchfd == w.closepipe[0] {
       
   506 				closed = true
       
   507 				continue
       
   508 			}
       
   509 
       
   510 			w.mu.Lock()
       
   511 			path := w.paths[watchfd]
       
   512 			w.mu.Unlock()
       
   513 
       
   514 			event := w.newEvent(path.name, mask)
       
   515 
       
   516 			if path.isDir && !event.Has(Remove) {
       
   517 				// Double check to make sure the directory exists. This can
       
   518 				// happen when we do a rm -fr on a recursively watched folders
       
   519 				// and we receive a modification event first but the folder has
       
   520 				// been deleted and later receive the delete event.
       
   521 				if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
       
   522 					event.Op |= Remove
       
   523 				}
       
   524 			}
       
   525 
       
   526 			if event.Has(Rename) || event.Has(Remove) {
       
   527 				w.Remove(event.Name)
       
   528 				w.mu.Lock()
       
   529 				delete(w.fileExists, event.Name)
       
   530 				w.mu.Unlock()
       
   531 			}
       
   532 
       
   533 			if path.isDir && event.Has(Write) && !event.Has(Remove) {
       
   534 				w.sendDirectoryChangeEvents(event.Name)
       
   535 			} else {
       
   536 				if !w.sendEvent(event) {
       
   537 					closed = true
       
   538 					continue
       
   539 				}
       
   540 			}
       
   541 
       
   542 			if event.Has(Remove) {
       
   543 				// Look for a file that may have overwritten this.
       
   544 				// For example, mv f1 f2 will delete f2, then create f2.
       
   545 				if path.isDir {
       
   546 					fileDir := filepath.Clean(event.Name)
       
   547 					w.mu.Lock()
       
   548 					_, found := w.watches[fileDir]
       
   549 					w.mu.Unlock()
       
   550 					if found {
       
   551 						// make sure the directory exists before we watch for changes. When we
       
   552 						// do a recursive watch and perform rm -fr, the parent directory might
       
   553 						// have gone missing, ignore the missing directory and let the
       
   554 						// upcoming delete event remove the watch from the parent directory.
       
   555 						if _, err := os.Lstat(fileDir); err == nil {
       
   556 							w.sendDirectoryChangeEvents(fileDir)
       
   557 						}
       
   558 					}
       
   559 				} else {
       
   560 					filePath := filepath.Clean(event.Name)
       
   561 					if fileInfo, err := os.Lstat(filePath); err == nil {
       
   562 						w.sendFileCreatedEventIfNew(filePath, fileInfo)
       
   563 					}
       
   564 				}
       
   565 			}
       
   566 		}
       
   567 	}
       
   568 }
       
   569 
       
   570 // newEvent returns an platform-independent Event based on kqueue Fflags.
       
   571 func (w *Watcher) newEvent(name string, mask uint32) Event {
       
   572 	e := Event{Name: name}
       
   573 	if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
       
   574 		e.Op |= Remove
       
   575 	}
       
   576 	if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
       
   577 		e.Op |= Write
       
   578 	}
       
   579 	if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
       
   580 		e.Op |= Rename
       
   581 	}
       
   582 	if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
       
   583 		e.Op |= Chmod
       
   584 	}
       
   585 	return e
       
   586 }
       
   587 
       
   588 // watchDirectoryFiles to mimic inotify when adding a watch on a directory
       
   589 func (w *Watcher) watchDirectoryFiles(dirPath string) error {
       
   590 	// Get all files
       
   591 	files, err := ioutil.ReadDir(dirPath)
       
   592 	if err != nil {
       
   593 		return err
       
   594 	}
       
   595 
       
   596 	for _, fileInfo := range files {
       
   597 		path := filepath.Join(dirPath, fileInfo.Name())
       
   598 
       
   599 		cleanPath, err := w.internalWatch(path, fileInfo)
       
   600 		if err != nil {
       
   601 			// No permission to read the file; that's not a problem: just skip.
       
   602 			// But do add it to w.fileExists to prevent it from being picked up
       
   603 			// as a "new" file later (it still shows up in the directory
       
   604 			// listing).
       
   605 			switch {
       
   606 			case errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM):
       
   607 				cleanPath = filepath.Clean(path)
       
   608 			default:
       
   609 				return fmt.Errorf("%q: %w", filepath.Join(dirPath, fileInfo.Name()), err)
       
   610 			}
       
   611 		}
       
   612 
       
   613 		w.mu.Lock()
       
   614 		w.fileExists[cleanPath] = struct{}{}
       
   615 		w.mu.Unlock()
       
   616 	}
       
   617 
       
   618 	return nil
       
   619 }
       
   620 
       
   621 // Search the directory for new files and send an event for them.
       
   622 //
       
   623 // This functionality is to have the BSD watcher match the inotify, which sends
       
   624 // a create event for files created in a watched directory.
       
   625 func (w *Watcher) sendDirectoryChangeEvents(dir string) {
       
   626 	// Get all files
       
   627 	files, err := ioutil.ReadDir(dir)
       
   628 	if err != nil {
       
   629 		if !w.sendError(fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)) {
       
   630 			return
       
   631 		}
       
   632 	}
       
   633 
       
   634 	// Search for new files
       
   635 	for _, fi := range files {
       
   636 		err := w.sendFileCreatedEventIfNew(filepath.Join(dir, fi.Name()), fi)
       
   637 		if err != nil {
       
   638 			return
       
   639 		}
       
   640 	}
       
   641 }
       
   642 
       
   643 // sendFileCreatedEvent sends a create event if the file isn't already being tracked.
       
   644 func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
       
   645 	w.mu.Lock()
       
   646 	_, doesExist := w.fileExists[filePath]
       
   647 	w.mu.Unlock()
       
   648 	if !doesExist {
       
   649 		if !w.sendEvent(Event{Name: filePath, Op: Create}) {
       
   650 			return
       
   651 		}
       
   652 	}
       
   653 
       
   654 	// like watchDirectoryFiles (but without doing another ReadDir)
       
   655 	filePath, err = w.internalWatch(filePath, fileInfo)
       
   656 	if err != nil {
       
   657 		return err
       
   658 	}
       
   659 
       
   660 	w.mu.Lock()
       
   661 	w.fileExists[filePath] = struct{}{}
       
   662 	w.mu.Unlock()
       
   663 
       
   664 	return nil
       
   665 }
       
   666 
       
   667 func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
       
   668 	if fileInfo.IsDir() {
       
   669 		// mimic Linux providing delete events for subdirectories
       
   670 		// but preserve the flags used if currently watching subdirectory
       
   671 		w.mu.Lock()
       
   672 		flags := w.dirFlags[name]
       
   673 		w.mu.Unlock()
       
   674 
       
   675 		flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
       
   676 		return w.addWatch(name, flags)
       
   677 	}
       
   678 
       
   679 	// watch file to mimic Linux inotify
       
   680 	return w.addWatch(name, noteAllEvents)
       
   681 }
       
   682 
       
   683 // Register events with the queue.
       
   684 func (w *Watcher) register(fds []int, flags int, fflags uint32) error {
       
   685 	changes := make([]unix.Kevent_t, len(fds))
       
   686 	for i, fd := range fds {
       
   687 		// SetKevent converts int to the platform-specific types.
       
   688 		unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
       
   689 		changes[i].Fflags = fflags
       
   690 	}
       
   691 
       
   692 	// Register the events.
       
   693 	success, err := unix.Kevent(w.kq, changes, nil, nil)
       
   694 	if success == -1 {
       
   695 		return err
       
   696 	}
       
   697 	return nil
       
   698 }
       
   699 
       
   700 // read retrieves pending events, or waits until an event occurs.
       
   701 func (w *Watcher) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) {
       
   702 	n, err := unix.Kevent(w.kq, nil, events, nil)
       
   703 	if err != nil {
       
   704 		return nil, err
       
   705 	}
       
   706 	return events[0:n], nil
       
   707 }