src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

backend_inotify.go (15446B)


      1 //go:build linux && !appengine
      2 
      3 package fsnotify
      4 
      5 import (
      6 	"errors"
      7 	"fmt"
      8 	"io"
      9 	"io/fs"
     10 	"os"
     11 	"path/filepath"
     12 	"strings"
     13 	"sync"
     14 	"time"
     15 	"unsafe"
     16 
     17 	"github.com/fsnotify/fsnotify/internal"
     18 	"golang.org/x/sys/unix"
     19 )
     20 
     21 type inotify struct {
     22 	*shared
     23 	Events chan Event
     24 	Errors chan error
     25 
     26 	// Store fd here as os.File.Read() will no longer return on close after
     27 	// calling Fd(). See: https://github.com/golang/go/issues/26439
     28 	fd          int
     29 	inotifyFile *os.File
     30 	watches     *watches
     31 	doneResp    chan struct{} // Channel to respond to Close
     32 
     33 	// Store rename cookies in an array, with the index wrapping to 0. Almost
     34 	// all of the time what we get is a MOVED_FROM to set the cookie and the
     35 	// next event inotify sends will be MOVED_TO to read it. However, this is
     36 	// not guaranteed – as described in inotify(7) – and we may get other events
     37 	// between the two MOVED_* events (including other MOVED_* ones).
     38 	//
     39 	// A second issue is that moving a file outside the watched directory will
     40 	// trigger a MOVED_FROM to set the cookie, but we never see the MOVED_TO to
     41 	// read and delete it. So just storing it in a map would slowly leak memory.
     42 	//
     43 	// Doing it like this gives us a simple fast LRU-cache that won't allocate.
     44 	// Ten items should be more than enough for our purpose, and a loop over
     45 	// such a short array is faster than a map access anyway (not that it hugely
     46 	// matters since we're talking about hundreds of ns at the most, but still).
     47 	cookies     [10]koekje
     48 	cookieIndex uint8
     49 	cookiesMu   sync.Mutex
     50 }
     51 
     52 type (
     53 	watches struct {
     54 		wd   map[uint32]*watch // wd → watch
     55 		path map[string]uint32 // pathname → wd
     56 	}
     57 	watch struct {
     58 		wd         uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
     59 		flags      uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
     60 		path       string // Watch path.
     61 		watchFlags watchFlag
     62 	}
     63 	koekje struct {
     64 		cookie uint32
     65 		path   string
     66 	}
     67 )
     68 
     69 func (w watch) byUser() bool  { return w.watchFlags&flagByUser != 0 }
     70 func (w watch) recurse() bool { return w.watchFlags&flagRecurse != 0 }
     71 
     72 func newWatches() *watches {
     73 	return &watches{
     74 		wd:   make(map[uint32]*watch),
     75 		path: make(map[string]uint32),
     76 	}
     77 }
     78 
     79 func (w *watches) byPath(path string) *watch { return w.wd[w.path[path]] }
     80 func (w *watches) byWd(wd uint32) *watch     { return w.wd[wd] }
     81 func (w *watches) len() int                  { return len(w.wd) }
     82 func (w *watches) add(ww *watch)             { w.wd[ww.wd] = ww; w.path[ww.path] = ww.wd }
     83 func (w *watches) remove(watch *watch)       { delete(w.path, watch.path); delete(w.wd, watch.wd) }
     84 
     85 func isSameOrDescendantPath(path, root string) bool {
     86 	if path == root {
     87 		return true
     88 	}
     89 	return strings.HasPrefix(path, root+string(os.PathSeparator))
     90 }
     91 
     92 func (w *watches) removePath(path string) ([]uint32, error) {
     93 	path, recurse := recursivePath(path)
     94 	wd, ok := w.path[path]
     95 	if !ok {
     96 		return nil, fmt.Errorf("%w: %s", ErrNonExistentWatch, path)
     97 	}
     98 
     99 	watch := w.wd[wd]
    100 	if recurse && !watch.recurse() {
    101 		return nil, fmt.Errorf("can't use /... with non-recursive watch %q", path)
    102 	}
    103 
    104 	delete(w.path, path)
    105 	delete(w.wd, wd)
    106 	if !watch.recurse() {
    107 		return []uint32{wd}, nil
    108 	}
    109 
    110 	wds := make([]uint32, 0, 8)
    111 	wds = append(wds, wd)
    112 	for p, rwd := range w.path {
    113 		if isSameOrDescendantPath(p, path) {
    114 			delete(w.path, p)
    115 			delete(w.wd, rwd)
    116 			wds = append(wds, rwd)
    117 		}
    118 	}
    119 	return wds, nil
    120 }
    121 
    122 func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error {
    123 	var existing *watch
    124 	wd, ok := w.path[path]
    125 	if ok {
    126 		existing = w.wd[wd]
    127 	}
    128 
    129 	upd, err := f(existing)
    130 	if err != nil {
    131 		return err
    132 	}
    133 	if upd != nil {
    134 		w.wd[upd.wd] = upd
    135 		w.path[upd.path] = upd.wd
    136 
    137 		if upd.wd != wd {
    138 			delete(w.wd, wd)
    139 		}
    140 	}
    141 
    142 	return nil
    143 }
    144 
    145 var defaultBufferSize = 0
    146 
    147 func newBackend(ev chan Event, errs chan error) (backend, error) {
    148 	// Need to set nonblocking mode for SetDeadline to work, otherwise blocking
    149 	// I/O operations won't terminate on close.
    150 	fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
    151 	if fd == -1 {
    152 		return nil, fmt.Errorf("couldn't initialize inotify: %w", errno)
    153 	}
    154 
    155 	w := &inotify{
    156 		shared:      newShared(ev, errs),
    157 		Events:      ev,
    158 		Errors:      errs,
    159 		fd:          fd,
    160 		inotifyFile: os.NewFile(uintptr(fd), ""),
    161 		watches:     newWatches(),
    162 		doneResp:    make(chan struct{}),
    163 	}
    164 
    165 	go w.readEvents()
    166 	return w, nil
    167 }
    168 
    169 func (w *inotify) Close() error {
    170 	if w.shared.close() {
    171 		return nil
    172 	}
    173 
    174 	// Causes any blocking reads to return with an error, provided the file
    175 	// still supports deadline operations.
    176 	err := w.inotifyFile.Close()
    177 	if err != nil {
    178 		return err
    179 	}
    180 
    181 	<-w.doneResp // Wait for readEvents() to finish.
    182 	return nil
    183 }
    184 
    185 func (w *inotify) Add(name string) error { return w.AddWith(name) }
    186 
    187 func (w *inotify) AddWith(path string, opts ...addOpt) error {
    188 	if w.isClosed() {
    189 		return ErrClosed
    190 	}
    191 	if debug {
    192 		fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  AddWith(%q)\n",
    193 			time.Now().Format("15:04:05.000000000"), path)
    194 	}
    195 
    196 	with := getOptions(opts...)
    197 	if !w.xSupports(with.op) {
    198 		return fmt.Errorf("%w: %s", xErrUnsupported, with.op)
    199 	}
    200 
    201 	add := func(path string, with withOpts, wf watchFlag) error {
    202 		var flags uint32
    203 		if with.op.Has(Create) {
    204 			flags |= unix.IN_CREATE
    205 		}
    206 		if with.op.Has(Write) {
    207 			flags |= unix.IN_MODIFY
    208 		}
    209 		if with.op.Has(Remove) {
    210 			flags |= unix.IN_DELETE | unix.IN_DELETE_SELF
    211 		}
    212 		if with.op.Has(Rename) {
    213 			flags |= unix.IN_MOVED_TO | unix.IN_MOVED_FROM | unix.IN_MOVE_SELF
    214 		}
    215 		if with.op.Has(Chmod) {
    216 			flags |= unix.IN_ATTRIB
    217 		}
    218 		if with.op.Has(xUnportableOpen) {
    219 			flags |= unix.IN_OPEN
    220 		}
    221 		if with.op.Has(xUnportableRead) {
    222 			flags |= unix.IN_ACCESS
    223 		}
    224 		if with.op.Has(xUnportableCloseWrite) {
    225 			flags |= unix.IN_CLOSE_WRITE
    226 		}
    227 		if with.op.Has(xUnportableCloseRead) {
    228 			flags |= unix.IN_CLOSE_NOWRITE
    229 		}
    230 		return w.register(path, flags, wf)
    231 	}
    232 
    233 	w.mu.Lock()
    234 	defer w.mu.Unlock()
    235 	path, recurse := recursivePath(path)
    236 	if recurse {
    237 		return filepath.WalkDir(path, func(root string, d fs.DirEntry, err error) error {
    238 			if err != nil {
    239 				return err
    240 			}
    241 			if !d.IsDir() {
    242 				if root == path {
    243 					return fmt.Errorf("fsnotify: not a directory: %q", path)
    244 				}
    245 				return nil
    246 			}
    247 
    248 			// Send a Create event when adding new directory from a recursive
    249 			// watch; this is for "mkdir -p one/two/three". Usually all those
    250 			// directories will be created before we can set up watchers on the
    251 			// subdirectories, so only "one" would be sent as a Create event and
    252 			// not "one/two" and "one/two/three" (inotifywait -r has the same
    253 			// problem).
    254 			if with.sendCreate && root != path {
    255 				w.sendEvent(Event{Name: root, Op: Create})
    256 			}
    257 
    258 			wf := flagRecurse
    259 			if root == path {
    260 				wf |= flagByUser
    261 			}
    262 			return add(root, with, wf)
    263 		})
    264 	}
    265 
    266 	return add(path, with, 0)
    267 }
    268 
    269 func (w *inotify) register(path string, flags uint32, wf watchFlag) error {
    270 	return w.watches.updatePath(path, func(existing *watch) (*watch, error) {
    271 		if existing != nil {
    272 			flags |= existing.flags | unix.IN_MASK_ADD
    273 		}
    274 
    275 		wd, err := unix.InotifyAddWatch(w.fd, path, flags)
    276 		if wd == -1 {
    277 			return nil, err
    278 		}
    279 
    280 		if e, ok := w.watches.wd[uint32(wd)]; ok {
    281 			return e, nil
    282 		}
    283 
    284 		if existing == nil {
    285 			return &watch{
    286 				wd:         uint32(wd),
    287 				path:       path,
    288 				flags:      flags,
    289 				watchFlags: wf,
    290 			}, nil
    291 		}
    292 
    293 		existing.wd = uint32(wd)
    294 		existing.flags = flags
    295 		return existing, nil
    296 	})
    297 }
    298 
    299 func (w *inotify) Remove(name string) error {
    300 	if w.isClosed() {
    301 		return nil
    302 	}
    303 	if debug {
    304 		fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s  Remove(%q)\n",
    305 			time.Now().Format("15:04:05.000000000"), name)
    306 	}
    307 
    308 	w.mu.Lock()
    309 	defer w.mu.Unlock()
    310 	return w.remove(filepath.Clean(name))
    311 }
    312 
    313 func (w *inotify) remove(name string) error {
    314 	wds, err := w.watches.removePath(name)
    315 	if err != nil {
    316 		return err
    317 	}
    318 
    319 	for _, wd := range wds {
    320 		_, err := unix.InotifyRmWatch(w.fd, wd)
    321 		if err != nil {
    322 			// TODO: Perhaps it's not helpful to return an error here in every
    323 			// case; the only two possible errors are:
    324 			//
    325 			// EBADF, which happens when w.fd is not a valid file descriptor of
    326 			// any kind.
    327 			//
    328 			// EINVAL, which is when fd is not an inotify descriptor or wd is
    329 			// not a valid watch descriptor. Watch descriptors are invalidated
    330 			// when they are removed explicitly or implicitly; explicitly by
    331 			// inotify_rm_watch, implicitly when the file they are watching is
    332 			// deleted.
    333 			return err
    334 		}
    335 	}
    336 	return nil
    337 }
    338 
    339 func (w *inotify) WatchList() []string {
    340 	if w.isClosed() {
    341 		return nil
    342 	}
    343 
    344 	w.mu.Lock()
    345 	defer w.mu.Unlock()
    346 	entries := make([]string, 0, w.watches.len())
    347 	for pathname := range w.watches.path {
    348 		entries = append(entries, pathname)
    349 	}
    350 	return entries
    351 }
    352 
    353 // readEvents reads from the inotify file descriptor, converts the
    354 // received events into Event objects and sends them via the Events channel
    355 func (w *inotify) readEvents() {
    356 	defer func() {
    357 		close(w.doneResp)
    358 		close(w.Errors)
    359 		close(w.Events)
    360 	}()
    361 
    362 	var buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
    363 	for {
    364 		if w.isClosed() {
    365 			return
    366 		}
    367 
    368 		n, err := w.inotifyFile.Read(buf[:])
    369 		if err != nil {
    370 			if errors.Is(err, os.ErrClosed) {
    371 				return
    372 			}
    373 			if !w.sendError(err) {
    374 				return
    375 			}
    376 			continue
    377 		}
    378 
    379 		if n < unix.SizeofInotifyEvent {
    380 			err := errors.New("notify: short read in readEvents()") // Read was too short.
    381 			if n == 0 {
    382 				err = io.EOF // If EOF is received. This should really never happen.
    383 			}
    384 			if !w.sendError(err) {
    385 				return
    386 			}
    387 			continue
    388 		}
    389 
    390 		// We don't know how many events we just read into the buffer While the
    391 		// offset points to at least one whole event.
    392 		var offset uint32
    393 		for offset <= uint32(n-unix.SizeofInotifyEvent) {
    394 			// Point to the event in the buffer.
    395 			inEvent := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
    396 
    397 			if inEvent.Mask&unix.IN_Q_OVERFLOW != 0 {
    398 				if !w.sendError(ErrEventOverflow) {
    399 					return
    400 				}
    401 			}
    402 
    403 			ev, ok := w.handleEvent(inEvent, &buf, offset)
    404 			if !ok {
    405 				return
    406 			}
    407 			if !w.sendEvent(ev) {
    408 				return
    409 			}
    410 
    411 			// Move to the next event in the buffer
    412 			offset += unix.SizeofInotifyEvent + inEvent.Len
    413 		}
    414 	}
    415 }
    416 
    417 func (w *inotify) handleEvent(inEvent *unix.InotifyEvent, buf *[65536]byte, offset uint32) (Event, bool) {
    418 	w.mu.Lock()
    419 	defer w.mu.Unlock()
    420 
    421 	/// If the event happened to the watched directory or the watched file, the
    422 	/// kernel doesn't append the filename to the event, but we would like to
    423 	/// always fill the the "Name" field with a valid filename. We retrieve the
    424 	/// path of the watch from the "paths" map.
    425 	///
    426 	/// Can be nil if Remove() was called in another goroutine for this path
    427 	/// inbetween reading the events from the kernel and reading the internal
    428 	/// state. Not much we can do about it, so just skip. See #616.
    429 	watch := w.watches.byWd(uint32(inEvent.Wd))
    430 	if watch == nil {
    431 		return Event{}, true
    432 	}
    433 
    434 	var (
    435 		name    = watch.path
    436 		nameLen = uint32(inEvent.Len)
    437 	)
    438 	if nameLen > 0 {
    439 		name += "/" + inotifyEventName(buf, offset, nameLen)
    440 	}
    441 
    442 	if debug {
    443 		internal.Debug(name, inEvent.Mask, inEvent.Cookie)
    444 	}
    445 
    446 	if inEvent.Mask&unix.IN_IGNORED != 0 || inEvent.Mask&unix.IN_UNMOUNT != 0 {
    447 		w.watches.remove(watch)
    448 		return Event{}, true
    449 	}
    450 
    451 	// inotify will automatically remove the watch on deletes; just need
    452 	// to clean our state here.
    453 	if inEvent.Mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
    454 		w.watches.remove(watch)
    455 	}
    456 
    457 	// We can't really update the state when a watched path is moved; only
    458 	// IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove the watch.
    459 	if inEvent.Mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
    460 		// Watch is set up as part of recurse: do nothing as the move gets
    461 		// registered from the parent directory.
    462 		if watch.recurse() && !watch.byUser() {
    463 			return Event{}, true
    464 		}
    465 
    466 		err := w.remove(watch.path)
    467 		if err != nil && !errors.Is(err, ErrNonExistentWatch) {
    468 			if !w.sendError(err) {
    469 				return Event{}, false
    470 			}
    471 		}
    472 
    473 		if watch.recurse() {
    474 			return Event{Name: watch.path, Op: Rename}, true
    475 		}
    476 	}
    477 
    478 	/// Skip if we're watching both this path and the parent; the parent will
    479 	/// already send a delete so no need to do it twice.
    480 	if inEvent.Mask&unix.IN_DELETE_SELF != 0 {
    481 		_, ok := w.watches.path[filepath.Dir(watch.path)]
    482 		if ok {
    483 			return Event{}, true
    484 		}
    485 	}
    486 
    487 	ev := w.newEvent(name, inEvent.Mask, inEvent.Cookie)
    488 	// Need to update watch path for recurse.
    489 	if watch.recurse() {
    490 		isDir := inEvent.Mask&unix.IN_ISDIR == unix.IN_ISDIR
    491 		/// New directory created: set up watch on it.
    492 		if isDir && ev.Has(Create) {
    493 			err := w.register(ev.Name, watch.flags, flagRecurse)
    494 			if !w.sendError(err) {
    495 				return Event{}, false
    496 			}
    497 
    498 			// This was a directory rename, so we need to update all the
    499 			// children.
    500 			//
    501 			// TODO: this is of course pretty slow; we should use a better data
    502 			// structure for storing all of this, e.g. store children in the
    503 			// watch. I have some code for this in my kqueue refactor we can use
    504 			// in the future. For now I'm okay with this as it's not publicly
    505 			// available. Correctness first, performance second.
    506 			if ev.renamedFrom != "" {
    507 				for k, ww := range w.watches.wd {
    508 					if k == watch.wd || ww.path == ev.Name {
    509 						continue
    510 					}
    511 					if isSameOrDescendantPath(ww.path, ev.renamedFrom) {
    512 						ww.path = strings.Replace(ww.path, ev.renamedFrom, ev.Name, 1)
    513 						w.watches.wd[k] = ww
    514 					}
    515 				}
    516 			}
    517 		}
    518 	}
    519 
    520 	return ev, true
    521 }
    522 
    523 func inotifyEventName(buf *[65536]byte, offset, nameLen uint32) string {
    524 	start := int(offset + unix.SizeofInotifyEvent)
    525 	bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[start]))[:nameLen:nameLen]
    526 	for nameLen > 0 && bytes[nameLen-1] == 0 {
    527 		nameLen--
    528 	}
    529 	return string(bytes[:nameLen])
    530 }
    531 
    532 func (w *inotify) newEvent(name string, mask, cookie uint32) Event {
    533 	e := Event{Name: name}
    534 	if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
    535 		e.Op |= Create
    536 	}
    537 	if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
    538 		e.Op |= Remove
    539 	}
    540 	if mask&unix.IN_MODIFY == unix.IN_MODIFY {
    541 		e.Op |= Write
    542 	}
    543 	if mask&unix.IN_OPEN == unix.IN_OPEN {
    544 		e.Op |= xUnportableOpen
    545 	}
    546 	if mask&unix.IN_ACCESS == unix.IN_ACCESS {
    547 		e.Op |= xUnportableRead
    548 	}
    549 	if mask&unix.IN_CLOSE_WRITE == unix.IN_CLOSE_WRITE {
    550 		e.Op |= xUnportableCloseWrite
    551 	}
    552 	if mask&unix.IN_CLOSE_NOWRITE == unix.IN_CLOSE_NOWRITE {
    553 		e.Op |= xUnportableCloseRead
    554 	}
    555 	if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
    556 		e.Op |= Rename
    557 	}
    558 	if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
    559 		e.Op |= Chmod
    560 	}
    561 
    562 	if cookie != 0 {
    563 		if mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
    564 			w.cookiesMu.Lock()
    565 			w.cookies[w.cookieIndex] = koekje{cookie: cookie, path: e.Name}
    566 			w.cookieIndex++
    567 			if w.cookieIndex > 9 {
    568 				w.cookieIndex = 0
    569 			}
    570 			w.cookiesMu.Unlock()
    571 		} else if mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
    572 			w.cookiesMu.Lock()
    573 			var prev string
    574 			for _, c := range w.cookies {
    575 				if c.cookie == cookie {
    576 					prev = c.path
    577 					break
    578 				}
    579 			}
    580 			w.cookiesMu.Unlock()
    581 			e.renamedFrom = prev
    582 		}
    583 	}
    584 	return e
    585 }
    586 
    587 func (w *inotify) xSupports(op Op) bool {
    588 	return true // Supports everything.
    589 }
    590 
    591 func (w *inotify) state() {
    592 	w.mu.Lock()
    593 	defer w.mu.Unlock()
    594 	for wd, ww := range w.watches.wd {
    595 		fmt.Fprintf(os.Stderr, "%4d: %q  watchFlags=0x%x\n", wd, ww.path, ww.watchFlags)
    596 	}
    597 }