backend_inotify.go

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