vendor/github.com/fsnotify/fsnotify/backend_inotify.go
changeset 265 05c40b36d3b2
equal deleted inserted replaced
264:8f478162d991 265:05c40b36d3b2
       
     1 //go:build linux
       
     2 // +build linux
       
     3 
       
     4 package fsnotify
       
     5 
       
     6 import (
       
     7 	"errors"
       
     8 	"fmt"
       
     9 	"io"
       
    10 	"os"
       
    11 	"path/filepath"
       
    12 	"strings"
       
    13 	"sync"
       
    14 	"unsafe"
       
    15 
       
    16 	"golang.org/x/sys/unix"
       
    17 )
       
    18 
       
    19 // Watcher watches a set of paths, delivering events on a channel.
       
    20 //
       
    21 // A watcher should not be copied (e.g. pass it by pointer, rather than by
       
    22 // value).
       
    23 //
       
    24 // # Linux notes
       
    25 //
       
    26 // When a file is removed a Remove event won't be emitted until all file
       
    27 // descriptors are closed, and deletes will always emit a Chmod. For example:
       
    28 //
       
    29 //     fp := os.Open("file")
       
    30 //     os.Remove("file")        // Triggers Chmod
       
    31 //     fp.Close()               // Triggers Remove
       
    32 //
       
    33 // This is the event that inotify sends, so not much can be changed about this.
       
    34 //
       
    35 // The fs.inotify.max_user_watches sysctl variable specifies the upper limit
       
    36 // for the number of watches per user, and fs.inotify.max_user_instances
       
    37 // specifies the maximum number of inotify instances per user. Every Watcher you
       
    38 // create is an "instance", and every path you add is a "watch".
       
    39 //
       
    40 // These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
       
    41 // /proc/sys/fs/inotify/max_user_instances
       
    42 //
       
    43 // To increase them you can use sysctl or write the value to the /proc file:
       
    44 //
       
    45 //     # Default values on Linux 5.18
       
    46 //     sysctl fs.inotify.max_user_watches=124983
       
    47 //     sysctl fs.inotify.max_user_instances=128
       
    48 //
       
    49 // To make the changes persist on reboot edit /etc/sysctl.conf or
       
    50 // /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
       
    51 // your distro's documentation):
       
    52 //
       
    53 //     fs.inotify.max_user_watches=124983
       
    54 //     fs.inotify.max_user_instances=128
       
    55 //
       
    56 // Reaching the limit will result in a "no space left on device" or "too many open
       
    57 // files" error.
       
    58 //
       
    59 // # kqueue notes (macOS, BSD)
       
    60 //
       
    61 // kqueue requires opening a file descriptor for every file that's being watched;
       
    62 // so if you're watching a directory with five files then that's six file
       
    63 // descriptors. You will run in to your system's "max open files" limit faster on
       
    64 // these platforms.
       
    65 //
       
    66 // The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
       
    67 // control the maximum number of open files, as well as /etc/login.conf on BSD
       
    68 // systems.
       
    69 //
       
    70 // # macOS notes
       
    71 //
       
    72 // Spotlight indexing on macOS can result in multiple events (see [#15]). A
       
    73 // temporary workaround is to add your folder(s) to the "Spotlight Privacy
       
    74 // Settings" until we have a native FSEvents implementation (see [#11]).
       
    75 //
       
    76 // [#11]: https://github.com/fsnotify/fsnotify/issues/11
       
    77 // [#15]: https://github.com/fsnotify/fsnotify/issues/15
       
    78 type Watcher struct {
       
    79 	// Events sends the filesystem change events.
       
    80 	//
       
    81 	// fsnotify can send the following events; a "path" here can refer to a
       
    82 	// file, directory, symbolic link, or special file like a FIFO.
       
    83 	//
       
    84 	//   fsnotify.Create    A new path was created; this may be followed by one
       
    85 	//                      or more Write events if data also gets written to a
       
    86 	//                      file.
       
    87 	//
       
    88 	//   fsnotify.Remove    A path was removed.
       
    89 	//
       
    90 	//   fsnotify.Rename    A path was renamed. A rename is always sent with the
       
    91 	//                      old path as Event.Name, and a Create event will be
       
    92 	//                      sent with the new name. Renames are only sent for
       
    93 	//                      paths that are currently watched; e.g. moving an
       
    94 	//                      unmonitored file into a monitored directory will
       
    95 	//                      show up as just a Create. Similarly, renaming a file
       
    96 	//                      to outside a monitored directory will show up as
       
    97 	//                      only a Rename.
       
    98 	//
       
    99 	//   fsnotify.Write     A file or named pipe was written to. A Truncate will
       
   100 	//                      also trigger a Write. A single "write action"
       
   101 	//                      initiated by the user may show up as one or multiple
       
   102 	//                      writes, depending on when the system syncs things to
       
   103 	//                      disk. For example when compiling a large Go program
       
   104 	//                      you may get hundreds of Write events, so you
       
   105 	//                      probably want to wait until you've stopped receiving
       
   106 	//                      them (see the dedup example in cmd/fsnotify).
       
   107 	//
       
   108 	//   fsnotify.Chmod     Attributes were changed. On Linux this is also sent
       
   109 	//                      when a file is removed (or more accurately, when a
       
   110 	//                      link to an inode is removed). On kqueue it's sent
       
   111 	//                      and on kqueue when a file is truncated. On Windows
       
   112 	//                      it's never sent.
       
   113 	Events chan Event
       
   114 
       
   115 	// Errors sends any errors.
       
   116 	Errors chan error
       
   117 
       
   118 	// Store fd here as os.File.Read() will no longer return on close after
       
   119 	// calling Fd(). See: https://github.com/golang/go/issues/26439
       
   120 	fd          int
       
   121 	mu          sync.Mutex // Map access
       
   122 	inotifyFile *os.File
       
   123 	watches     map[string]*watch // Map of inotify watches (key: path)
       
   124 	paths       map[int]string    // Map of watched paths (key: watch descriptor)
       
   125 	done        chan struct{}     // Channel for sending a "quit message" to the reader goroutine
       
   126 	doneResp    chan struct{}     // Channel to respond to Close
       
   127 }
       
   128 
       
   129 // NewWatcher creates a new Watcher.
       
   130 func NewWatcher() (*Watcher, error) {
       
   131 	// Create inotify fd
       
   132 	// Need to set the FD to nonblocking mode in order for SetDeadline methods to work
       
   133 	// Otherwise, blocking i/o operations won't terminate on close
       
   134 	fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
       
   135 	if fd == -1 {
       
   136 		return nil, errno
       
   137 	}
       
   138 
       
   139 	w := &Watcher{
       
   140 		fd:          fd,
       
   141 		inotifyFile: os.NewFile(uintptr(fd), ""),
       
   142 		watches:     make(map[string]*watch),
       
   143 		paths:       make(map[int]string),
       
   144 		Events:      make(chan Event),
       
   145 		Errors:      make(chan error),
       
   146 		done:        make(chan struct{}),
       
   147 		doneResp:    make(chan struct{}),
       
   148 	}
       
   149 
       
   150 	go w.readEvents()
       
   151 	return w, nil
       
   152 }
       
   153 
       
   154 // Returns true if the event was sent, or false if watcher is closed.
       
   155 func (w *Watcher) sendEvent(e Event) bool {
       
   156 	select {
       
   157 	case w.Events <- e:
       
   158 		return true
       
   159 	case <-w.done:
       
   160 	}
       
   161 	return false
       
   162 }
       
   163 
       
   164 // Returns true if the error was sent, or false if watcher is closed.
       
   165 func (w *Watcher) sendError(err error) bool {
       
   166 	select {
       
   167 	case w.Errors <- err:
       
   168 		return true
       
   169 	case <-w.done:
       
   170 		return false
       
   171 	}
       
   172 }
       
   173 
       
   174 func (w *Watcher) isClosed() bool {
       
   175 	select {
       
   176 	case <-w.done:
       
   177 		return true
       
   178 	default:
       
   179 		return false
       
   180 	}
       
   181 }
       
   182 
       
   183 // Close removes all watches and closes the events channel.
       
   184 func (w *Watcher) Close() error {
       
   185 	w.mu.Lock()
       
   186 	if w.isClosed() {
       
   187 		w.mu.Unlock()
       
   188 		return nil
       
   189 	}
       
   190 
       
   191 	// Send 'close' signal to goroutine, and set the Watcher to closed.
       
   192 	close(w.done)
       
   193 	w.mu.Unlock()
       
   194 
       
   195 	// Causes any blocking reads to return with an error, provided the file
       
   196 	// still supports deadline operations.
       
   197 	err := w.inotifyFile.Close()
       
   198 	if err != nil {
       
   199 		return err
       
   200 	}
       
   201 
       
   202 	// Wait for goroutine to close
       
   203 	<-w.doneResp
       
   204 
       
   205 	return nil
       
   206 }
       
   207 
       
   208 // Add starts monitoring the path for changes.
       
   209 //
       
   210 // A path can only be watched once; attempting to watch it more than once will
       
   211 // return an error. Paths that do not yet exist on the filesystem cannot be
       
   212 // added. A watch will be automatically removed if the path is deleted.
       
   213 //
       
   214 // A path will remain watched if it gets renamed to somewhere else on the same
       
   215 // filesystem, but the monitor will get removed if the path gets deleted and
       
   216 // re-created, or if it's moved to a different filesystem.
       
   217 //
       
   218 // Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
       
   219 // filesystems (/proc, /sys, etc.) generally don't work.
       
   220 //
       
   221 // # Watching directories
       
   222 //
       
   223 // All files in a directory are monitored, including new files that are created
       
   224 // after the watcher is started. Subdirectories are not watched (i.e. it's
       
   225 // non-recursive).
       
   226 //
       
   227 // # Watching files
       
   228 //
       
   229 // Watching individual files (rather than directories) is generally not
       
   230 // recommended as many tools update files atomically. Instead of "just" writing
       
   231 // to the file a temporary file will be written to first, and if successful the
       
   232 // temporary file is moved to to destination removing the original, or some
       
   233 // variant thereof. The watcher on the original file is now lost, as it no
       
   234 // longer exists.
       
   235 //
       
   236 // Instead, watch the parent directory and use Event.Name to filter out files
       
   237 // you're not interested in. There is an example of this in [cmd/fsnotify/file.go].
       
   238 func (w *Watcher) Add(name string) error {
       
   239 	name = filepath.Clean(name)
       
   240 	if w.isClosed() {
       
   241 		return errors.New("inotify instance already closed")
       
   242 	}
       
   243 
       
   244 	var flags uint32 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
       
   245 		unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
       
   246 		unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
       
   247 
       
   248 	w.mu.Lock()
       
   249 	defer w.mu.Unlock()
       
   250 	watchEntry := w.watches[name]
       
   251 	if watchEntry != nil {
       
   252 		flags |= watchEntry.flags | unix.IN_MASK_ADD
       
   253 	}
       
   254 	wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
       
   255 	if wd == -1 {
       
   256 		return errno
       
   257 	}
       
   258 
       
   259 	if watchEntry == nil {
       
   260 		w.watches[name] = &watch{wd: uint32(wd), flags: flags}
       
   261 		w.paths[wd] = name
       
   262 	} else {
       
   263 		watchEntry.wd = uint32(wd)
       
   264 		watchEntry.flags = flags
       
   265 	}
       
   266 
       
   267 	return nil
       
   268 }
       
   269 
       
   270 // Remove stops monitoring the path for changes.
       
   271 //
       
   272 // Directories are always removed non-recursively. For example, if you added
       
   273 // /tmp/dir and /tmp/dir/subdir then you will need to remove both.
       
   274 //
       
   275 // Removing a path that has not yet been added returns [ErrNonExistentWatch].
       
   276 func (w *Watcher) Remove(name string) error {
       
   277 	name = filepath.Clean(name)
       
   278 
       
   279 	// Fetch the watch.
       
   280 	w.mu.Lock()
       
   281 	defer w.mu.Unlock()
       
   282 	watch, ok := w.watches[name]
       
   283 
       
   284 	// Remove it from inotify.
       
   285 	if !ok {
       
   286 		return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
       
   287 	}
       
   288 
       
   289 	// We successfully removed the watch if InotifyRmWatch doesn't return an
       
   290 	// error, we need to clean up our internal state to ensure it matches
       
   291 	// inotify's kernel state.
       
   292 	delete(w.paths, int(watch.wd))
       
   293 	delete(w.watches, name)
       
   294 
       
   295 	// inotify_rm_watch will return EINVAL if the file has been deleted;
       
   296 	// the inotify will already have been removed.
       
   297 	// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
       
   298 	// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
       
   299 	// so that EINVAL means that the wd is being rm_watch()ed or its file removed
       
   300 	// by another thread and we have not received IN_IGNORE event.
       
   301 	success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
       
   302 	if success == -1 {
       
   303 		// TODO: Perhaps it's not helpful to return an error here in every case;
       
   304 		//       The only two possible errors are:
       
   305 		//
       
   306 		//       - EBADF, which happens when w.fd is not a valid file descriptor
       
   307 		//         of any kind.
       
   308 		//       - EINVAL, which is when fd is not an inotify descriptor or wd
       
   309 		//         is not a valid watch descriptor. Watch descriptors are
       
   310 		//         invalidated when they are removed explicitly or implicitly;
       
   311 		//         explicitly by inotify_rm_watch, implicitly when the file they
       
   312 		//         are watching is deleted.
       
   313 		return errno
       
   314 	}
       
   315 
       
   316 	return nil
       
   317 }
       
   318 
       
   319 // WatchList returns all paths added with [Add] (and are not yet removed).
       
   320 func (w *Watcher) WatchList() []string {
       
   321 	w.mu.Lock()
       
   322 	defer w.mu.Unlock()
       
   323 
       
   324 	entries := make([]string, 0, len(w.watches))
       
   325 	for pathname := range w.watches {
       
   326 		entries = append(entries, pathname)
       
   327 	}
       
   328 
       
   329 	return entries
       
   330 }
       
   331 
       
   332 type watch struct {
       
   333 	wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
       
   334 	flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
       
   335 }
       
   336 
       
   337 // readEvents reads from the inotify file descriptor, converts the
       
   338 // received events into Event objects and sends them via the Events channel
       
   339 func (w *Watcher) readEvents() {
       
   340 	defer func() {
       
   341 		close(w.doneResp)
       
   342 		close(w.Errors)
       
   343 		close(w.Events)
       
   344 	}()
       
   345 
       
   346 	var (
       
   347 		buf   [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
       
   348 		errno error                                // Syscall errno
       
   349 	)
       
   350 	for {
       
   351 		// See if we have been closed.
       
   352 		if w.isClosed() {
       
   353 			return
       
   354 		}
       
   355 
       
   356 		n, err := w.inotifyFile.Read(buf[:])
       
   357 		switch {
       
   358 		case errors.Unwrap(err) == os.ErrClosed:
       
   359 			return
       
   360 		case err != nil:
       
   361 			if !w.sendError(err) {
       
   362 				return
       
   363 			}
       
   364 			continue
       
   365 		}
       
   366 
       
   367 		if n < unix.SizeofInotifyEvent {
       
   368 			var err error
       
   369 			if n == 0 {
       
   370 				// If EOF is received. This should really never happen.
       
   371 				err = io.EOF
       
   372 			} else if n < 0 {
       
   373 				// If an error occurred while reading.
       
   374 				err = errno
       
   375 			} else {
       
   376 				// Read was too short.
       
   377 				err = errors.New("notify: short read in readEvents()")
       
   378 			}
       
   379 			if !w.sendError(err) {
       
   380 				return
       
   381 			}
       
   382 			continue
       
   383 		}
       
   384 
       
   385 		var offset uint32
       
   386 		// We don't know how many events we just read into the buffer
       
   387 		// While the offset points to at least one whole event...
       
   388 		for offset <= uint32(n-unix.SizeofInotifyEvent) {
       
   389 			var (
       
   390 				// Point "raw" to the event in the buffer
       
   391 				raw     = (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
       
   392 				mask    = uint32(raw.Mask)
       
   393 				nameLen = uint32(raw.Len)
       
   394 			)
       
   395 
       
   396 			if mask&unix.IN_Q_OVERFLOW != 0 {
       
   397 				if !w.sendError(ErrEventOverflow) {
       
   398 					return
       
   399 				}
       
   400 			}
       
   401 
       
   402 			// If the event happened to the watched directory or the watched file, the kernel
       
   403 			// doesn't append the filename to the event, but we would like to always fill the
       
   404 			// the "Name" field with a valid filename. We retrieve the path of the watch from
       
   405 			// the "paths" map.
       
   406 			w.mu.Lock()
       
   407 			name, ok := w.paths[int(raw.Wd)]
       
   408 			// IN_DELETE_SELF occurs when the file/directory being watched is removed.
       
   409 			// This is a sign to clean up the maps, otherwise we are no longer in sync
       
   410 			// with the inotify kernel state which has already deleted the watch
       
   411 			// automatically.
       
   412 			if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
       
   413 				delete(w.paths, int(raw.Wd))
       
   414 				delete(w.watches, name)
       
   415 			}
       
   416 			w.mu.Unlock()
       
   417 
       
   418 			if nameLen > 0 {
       
   419 				// Point "bytes" at the first byte of the filename
       
   420 				bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
       
   421 				// The filename is padded with NULL bytes. TrimRight() gets rid of those.
       
   422 				name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
       
   423 			}
       
   424 
       
   425 			event := w.newEvent(name, mask)
       
   426 
       
   427 			// Send the events that are not ignored on the events channel
       
   428 			if mask&unix.IN_IGNORED == 0 {
       
   429 				if !w.sendEvent(event) {
       
   430 					return
       
   431 				}
       
   432 			}
       
   433 
       
   434 			// Move to the next event in the buffer
       
   435 			offset += unix.SizeofInotifyEvent + nameLen
       
   436 		}
       
   437 	}
       
   438 }
       
   439 
       
   440 // newEvent returns an platform-independent Event based on an inotify mask.
       
   441 func (w *Watcher) newEvent(name string, mask uint32) Event {
       
   442 	e := Event{Name: name}
       
   443 	if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
       
   444 		e.Op |= Create
       
   445 	}
       
   446 	if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
       
   447 		e.Op |= Remove
       
   448 	}
       
   449 	if mask&unix.IN_MODIFY == unix.IN_MODIFY {
       
   450 		e.Op |= Write
       
   451 	}
       
   452 	if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
       
   453 		e.Op |= Rename
       
   454 	}
       
   455 	if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
       
   456 		e.Op |= Chmod
       
   457 	}
       
   458 	return e
       
   459 }