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}