backend_inotify.go 15 KB

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