backend_fen.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. //go:build solaris
  2. // +build solaris
  3. // Note: the documentation on the Watcher type and methods is generated from
  4. // mkdoc.zsh
  5. package fsnotify
  6. import (
  7. "errors"
  8. "fmt"
  9. "os"
  10. "path/filepath"
  11. "sync"
  12. "golang.org/x/sys/unix"
  13. )
  14. // Watcher watches a set of paths, delivering events on a channel.
  15. //
  16. // A watcher should not be copied (e.g. pass it by pointer, rather than by
  17. // value).
  18. //
  19. // # Linux notes
  20. //
  21. // When a file is removed a Remove event won't be emitted until all file
  22. // descriptors are closed, and deletes will always emit a Chmod. For example:
  23. //
  24. // fp := os.Open("file")
  25. // os.Remove("file") // Triggers Chmod
  26. // fp.Close() // Triggers Remove
  27. //
  28. // This is the event that inotify sends, so not much can be changed about this.
  29. //
  30. // The fs.inotify.max_user_watches sysctl variable specifies the upper limit
  31. // for the number of watches per user, and fs.inotify.max_user_instances
  32. // specifies the maximum number of inotify instances per user. Every Watcher you
  33. // create is an "instance", and every path you add is a "watch".
  34. //
  35. // These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
  36. // /proc/sys/fs/inotify/max_user_instances
  37. //
  38. // To increase them you can use sysctl or write the value to the /proc file:
  39. //
  40. // # Default values on Linux 5.18
  41. // sysctl fs.inotify.max_user_watches=124983
  42. // sysctl fs.inotify.max_user_instances=128
  43. //
  44. // To make the changes persist on reboot edit /etc/sysctl.conf or
  45. // /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
  46. // your distro's documentation):
  47. //
  48. // fs.inotify.max_user_watches=124983
  49. // fs.inotify.max_user_instances=128
  50. //
  51. // Reaching the limit will result in a "no space left on device" or "too many open
  52. // files" error.
  53. //
  54. // # kqueue notes (macOS, BSD)
  55. //
  56. // kqueue requires opening a file descriptor for every file that's being watched;
  57. // so if you're watching a directory with five files then that's six file
  58. // descriptors. You will run in to your system's "max open files" limit faster on
  59. // these platforms.
  60. //
  61. // The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
  62. // control the maximum number of open files, as well as /etc/login.conf on BSD
  63. // systems.
  64. //
  65. // # Windows notes
  66. //
  67. // Paths can be added as "C:\path\to\dir", but forward slashes
  68. // ("C:/path/to/dir") will also work.
  69. //
  70. // When a watched directory is removed it will always send an event for the
  71. // directory itself, but may not send events for all files in that directory.
  72. // Sometimes it will send events for all times, sometimes it will send no
  73. // events, and often only for some files.
  74. //
  75. // The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
  76. // value that is guaranteed to work with SMB filesystems. If you have many
  77. // events in quick succession this may not be enough, and you will have to use
  78. // [WithBufferSize] to increase the value.
  79. type Watcher struct {
  80. // Events sends the filesystem change events.
  81. //
  82. // fsnotify can send the following events; a "path" here can refer to a
  83. // file, directory, symbolic link, or special file like a FIFO.
  84. //
  85. // fsnotify.Create A new path was created; this may be followed by one
  86. // or more Write events if data also gets written to a
  87. // file.
  88. //
  89. // fsnotify.Remove A path was removed.
  90. //
  91. // fsnotify.Rename A path was renamed. A rename is always sent with the
  92. // old path as Event.Name, and a Create event will be
  93. // sent with the new name. Renames are only sent for
  94. // paths that are currently watched; e.g. moving an
  95. // unmonitored file into a monitored directory will
  96. // show up as just a Create. Similarly, renaming a file
  97. // to outside a monitored directory will show up as
  98. // only a Rename.
  99. //
  100. // fsnotify.Write A file or named pipe was written to. A Truncate will
  101. // also trigger a Write. A single "write action"
  102. // initiated by the user may show up as one or multiple
  103. // writes, depending on when the system syncs things to
  104. // disk. For example when compiling a large Go program
  105. // you may get hundreds of Write events, and you may
  106. // want to wait until you've stopped receiving them
  107. // (see the dedup example in cmd/fsnotify).
  108. //
  109. // Some systems may send Write event for directories
  110. // when the directory content changes.
  111. //
  112. // fsnotify.Chmod Attributes were changed. On Linux this is also sent
  113. // when a file is removed (or more accurately, when a
  114. // link to an inode is removed). On kqueue it's sent
  115. // when a file is truncated. On Windows it's never
  116. // sent.
  117. Events chan Event
  118. // Errors sends any errors.
  119. //
  120. // ErrEventOverflow is used to indicate there are too many events:
  121. //
  122. // - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
  123. // - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
  124. // - kqueue, fen: Not used.
  125. Errors chan error
  126. mu sync.Mutex
  127. port *unix.EventPort
  128. done chan struct{} // Channel for sending a "quit message" to the reader goroutine
  129. dirs map[string]struct{} // Explicitly watched directories
  130. watches map[string]struct{} // Explicitly watched non-directories
  131. }
  132. // NewWatcher creates a new Watcher.
  133. func NewWatcher() (*Watcher, error) {
  134. return NewBufferedWatcher(0)
  135. }
  136. // NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
  137. // channel.
  138. //
  139. // The main use case for this is situations with a very large number of events
  140. // where the kernel buffer size can't be increased (e.g. due to lack of
  141. // permissions). An unbuffered Watcher will perform better for almost all use
  142. // cases, and whenever possible you will be better off increasing the kernel
  143. // buffers instead of adding a large userspace buffer.
  144. func NewBufferedWatcher(sz uint) (*Watcher, error) {
  145. w := &Watcher{
  146. Events: make(chan Event, sz),
  147. Errors: make(chan error),
  148. dirs: make(map[string]struct{}),
  149. watches: make(map[string]struct{}),
  150. done: make(chan struct{}),
  151. }
  152. var err error
  153. w.port, err = unix.NewEventPort()
  154. if err != nil {
  155. return nil, fmt.Errorf("fsnotify.NewWatcher: %w", err)
  156. }
  157. go w.readEvents()
  158. return w, nil
  159. }
  160. // sendEvent attempts to send an event to the user, returning true if the event
  161. // was put in the channel successfully and false if the watcher has been closed.
  162. func (w *Watcher) sendEvent(name string, op Op) (sent bool) {
  163. select {
  164. case w.Events <- Event{Name: name, Op: op}:
  165. return true
  166. case <-w.done:
  167. return false
  168. }
  169. }
  170. // sendError attempts to send an error to the user, returning true if the error
  171. // was put in the channel successfully and false if the watcher has been closed.
  172. func (w *Watcher) sendError(err error) (sent bool) {
  173. select {
  174. case w.Errors <- err:
  175. return true
  176. case <-w.done:
  177. return false
  178. }
  179. }
  180. func (w *Watcher) isClosed() bool {
  181. select {
  182. case <-w.done:
  183. return true
  184. default:
  185. return false
  186. }
  187. }
  188. // Close removes all watches and closes the Events channel.
  189. func (w *Watcher) Close() error {
  190. // Take the lock used by associateFile to prevent lingering events from
  191. // being processed after the close
  192. w.mu.Lock()
  193. defer w.mu.Unlock()
  194. if w.isClosed() {
  195. return nil
  196. }
  197. close(w.done)
  198. return w.port.Close()
  199. }
  200. // Add starts monitoring the path for changes.
  201. //
  202. // A path can only be watched once; watching it more than once is a no-op and will
  203. // not return an error. Paths that do not yet exist on the filesystem cannot be
  204. // watched.
  205. //
  206. // A watch will be automatically removed if the watched path is deleted or
  207. // renamed. The exception is the Windows backend, which doesn't remove the
  208. // watcher on renames.
  209. //
  210. // Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
  211. // filesystems (/proc, /sys, etc.) generally don't work.
  212. //
  213. // Returns [ErrClosed] if [Watcher.Close] was called.
  214. //
  215. // See [Watcher.AddWith] for a version that allows adding options.
  216. //
  217. // # Watching directories
  218. //
  219. // All files in a directory are monitored, including new files that are created
  220. // after the watcher is started. Subdirectories are not watched (i.e. it's
  221. // non-recursive).
  222. //
  223. // # Watching files
  224. //
  225. // Watching individual files (rather than directories) is generally not
  226. // recommended as many programs (especially editors) update files atomically: it
  227. // will write to a temporary file which is then moved to to destination,
  228. // overwriting the original (or some variant thereof). The watcher on the
  229. // original file is now lost, as that no longer exists.
  230. //
  231. // The upshot of this is that a power failure or crash won't leave a
  232. // half-written file.
  233. //
  234. // Watch the parent directory and use Event.Name to filter out files you're not
  235. // interested in. There is an example of this in cmd/fsnotify/file.go.
  236. func (w *Watcher) Add(name string) error { return w.AddWith(name) }
  237. // AddWith is like [Watcher.Add], but allows adding options. When using Add()
  238. // the defaults described below are used.
  239. //
  240. // Possible options are:
  241. //
  242. // - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
  243. // other platforms. The default is 64K (65536 bytes).
  244. func (w *Watcher) AddWith(name string, opts ...addOpt) error {
  245. if w.isClosed() {
  246. return ErrClosed
  247. }
  248. if w.port.PathIsWatched(name) {
  249. return nil
  250. }
  251. _ = getOptions(opts...)
  252. // Currently we resolve symlinks that were explicitly requested to be
  253. // watched. Otherwise we would use LStat here.
  254. stat, err := os.Stat(name)
  255. if err != nil {
  256. return err
  257. }
  258. // Associate all files in the directory.
  259. if stat.IsDir() {
  260. err := w.handleDirectory(name, stat, true, w.associateFile)
  261. if err != nil {
  262. return err
  263. }
  264. w.mu.Lock()
  265. w.dirs[name] = struct{}{}
  266. w.mu.Unlock()
  267. return nil
  268. }
  269. err = w.associateFile(name, stat, true)
  270. if err != nil {
  271. return err
  272. }
  273. w.mu.Lock()
  274. w.watches[name] = struct{}{}
  275. w.mu.Unlock()
  276. return nil
  277. }
  278. // Remove stops monitoring the path for changes.
  279. //
  280. // Directories are always removed non-recursively. For example, if you added
  281. // /tmp/dir and /tmp/dir/subdir then you will need to remove both.
  282. //
  283. // Removing a path that has not yet been added returns [ErrNonExistentWatch].
  284. //
  285. // Returns nil if [Watcher.Close] was called.
  286. func (w *Watcher) Remove(name string) error {
  287. if w.isClosed() {
  288. return nil
  289. }
  290. if !w.port.PathIsWatched(name) {
  291. return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
  292. }
  293. // The user has expressed an intent. Immediately remove this name from
  294. // whichever watch list it might be in. If it's not in there the delete
  295. // doesn't cause harm.
  296. w.mu.Lock()
  297. delete(w.watches, name)
  298. delete(w.dirs, name)
  299. w.mu.Unlock()
  300. stat, err := os.Stat(name)
  301. if err != nil {
  302. return err
  303. }
  304. // Remove associations for every file in the directory.
  305. if stat.IsDir() {
  306. err := w.handleDirectory(name, stat, false, w.dissociateFile)
  307. if err != nil {
  308. return err
  309. }
  310. return nil
  311. }
  312. err = w.port.DissociatePath(name)
  313. if err != nil {
  314. return err
  315. }
  316. return nil
  317. }
  318. // readEvents contains the main loop that runs in a goroutine watching for events.
  319. func (w *Watcher) readEvents() {
  320. // If this function returns, the watcher has been closed and we can close
  321. // these channels
  322. defer func() {
  323. close(w.Errors)
  324. close(w.Events)
  325. }()
  326. pevents := make([]unix.PortEvent, 8)
  327. for {
  328. count, err := w.port.Get(pevents, 1, nil)
  329. if err != nil && err != unix.ETIME {
  330. // Interrupted system call (count should be 0) ignore and continue
  331. if errors.Is(err, unix.EINTR) && count == 0 {
  332. continue
  333. }
  334. // Get failed because we called w.Close()
  335. if errors.Is(err, unix.EBADF) && w.isClosed() {
  336. return
  337. }
  338. // There was an error not caused by calling w.Close()
  339. if !w.sendError(err) {
  340. return
  341. }
  342. }
  343. p := pevents[:count]
  344. for _, pevent := range p {
  345. if pevent.Source != unix.PORT_SOURCE_FILE {
  346. // Event from unexpected source received; should never happen.
  347. if !w.sendError(errors.New("Event from unexpected source received")) {
  348. return
  349. }
  350. continue
  351. }
  352. err = w.handleEvent(&pevent)
  353. if err != nil {
  354. if !w.sendError(err) {
  355. return
  356. }
  357. }
  358. }
  359. }
  360. }
  361. func (w *Watcher) handleDirectory(path string, stat os.FileInfo, follow bool, handler func(string, os.FileInfo, bool) error) error {
  362. files, err := os.ReadDir(path)
  363. if err != nil {
  364. return err
  365. }
  366. // Handle all children of the directory.
  367. for _, entry := range files {
  368. finfo, err := entry.Info()
  369. if err != nil {
  370. return err
  371. }
  372. err = handler(filepath.Join(path, finfo.Name()), finfo, false)
  373. if err != nil {
  374. return err
  375. }
  376. }
  377. // And finally handle the directory itself.
  378. return handler(path, stat, follow)
  379. }
  380. // handleEvent might need to emit more than one fsnotify event if the events
  381. // bitmap matches more than one event type (e.g. the file was both modified and
  382. // had the attributes changed between when the association was created and the
  383. // when event was returned)
  384. func (w *Watcher) handleEvent(event *unix.PortEvent) error {
  385. var (
  386. events = event.Events
  387. path = event.Path
  388. fmode = event.Cookie.(os.FileMode)
  389. reRegister = true
  390. )
  391. w.mu.Lock()
  392. _, watchedDir := w.dirs[path]
  393. _, watchedPath := w.watches[path]
  394. w.mu.Unlock()
  395. isWatched := watchedDir || watchedPath
  396. if events&unix.FILE_DELETE != 0 {
  397. if !w.sendEvent(path, Remove) {
  398. return nil
  399. }
  400. reRegister = false
  401. }
  402. if events&unix.FILE_RENAME_FROM != 0 {
  403. if !w.sendEvent(path, Rename) {
  404. return nil
  405. }
  406. // Don't keep watching the new file name
  407. reRegister = false
  408. }
  409. if events&unix.FILE_RENAME_TO != 0 {
  410. // We don't report a Rename event for this case, because Rename events
  411. // are interpreted as referring to the _old_ name of the file, and in
  412. // this case the event would refer to the new name of the file. This
  413. // type of rename event is not supported by fsnotify.
  414. // inotify reports a Remove event in this case, so we simulate this
  415. // here.
  416. if !w.sendEvent(path, Remove) {
  417. return nil
  418. }
  419. // Don't keep watching the file that was removed
  420. reRegister = false
  421. }
  422. // The file is gone, nothing left to do.
  423. if !reRegister {
  424. if watchedDir {
  425. w.mu.Lock()
  426. delete(w.dirs, path)
  427. w.mu.Unlock()
  428. }
  429. if watchedPath {
  430. w.mu.Lock()
  431. delete(w.watches, path)
  432. w.mu.Unlock()
  433. }
  434. return nil
  435. }
  436. // If we didn't get a deletion the file still exists and we're going to have
  437. // to watch it again. Let's Stat it now so that we can compare permissions
  438. // and have what we need to continue watching the file
  439. stat, err := os.Lstat(path)
  440. if err != nil {
  441. // This is unexpected, but we should still emit an event. This happens
  442. // most often on "rm -r" of a subdirectory inside a watched directory We
  443. // get a modify event of something happening inside, but by the time we
  444. // get here, the sudirectory is already gone. Clearly we were watching
  445. // this path but now it is gone. Let's tell the user that it was
  446. // removed.
  447. if !w.sendEvent(path, Remove) {
  448. return nil
  449. }
  450. // Suppress extra write events on removed directories; they are not
  451. // informative and can be confusing.
  452. return nil
  453. }
  454. // resolve symlinks that were explicitly watched as we would have at Add()
  455. // time. this helps suppress spurious Chmod events on watched symlinks
  456. if isWatched {
  457. stat, err = os.Stat(path)
  458. if err != nil {
  459. // The symlink still exists, but the target is gone. Report the
  460. // Remove similar to above.
  461. if !w.sendEvent(path, Remove) {
  462. return nil
  463. }
  464. // Don't return the error
  465. }
  466. }
  467. if events&unix.FILE_MODIFIED != 0 {
  468. if fmode.IsDir() {
  469. if watchedDir {
  470. if err := w.updateDirectory(path); err != nil {
  471. return err
  472. }
  473. } else {
  474. if !w.sendEvent(path, Write) {
  475. return nil
  476. }
  477. }
  478. } else {
  479. if !w.sendEvent(path, Write) {
  480. return nil
  481. }
  482. }
  483. }
  484. if events&unix.FILE_ATTRIB != 0 && stat != nil {
  485. // Only send Chmod if perms changed
  486. if stat.Mode().Perm() != fmode.Perm() {
  487. if !w.sendEvent(path, Chmod) {
  488. return nil
  489. }
  490. }
  491. }
  492. if stat != nil {
  493. // If we get here, it means we've hit an event above that requires us to
  494. // continue watching the file or directory
  495. return w.associateFile(path, stat, isWatched)
  496. }
  497. return nil
  498. }
  499. func (w *Watcher) updateDirectory(path string) error {
  500. // The directory was modified, so we must find unwatched entities and watch
  501. // them. If something was removed from the directory, nothing will happen,
  502. // as everything else should still be watched.
  503. files, err := os.ReadDir(path)
  504. if err != nil {
  505. return err
  506. }
  507. for _, entry := range files {
  508. path := filepath.Join(path, entry.Name())
  509. if w.port.PathIsWatched(path) {
  510. continue
  511. }
  512. finfo, err := entry.Info()
  513. if err != nil {
  514. return err
  515. }
  516. err = w.associateFile(path, finfo, false)
  517. if err != nil {
  518. if !w.sendError(err) {
  519. return nil
  520. }
  521. }
  522. if !w.sendEvent(path, Create) {
  523. return nil
  524. }
  525. }
  526. return nil
  527. }
  528. func (w *Watcher) associateFile(path string, stat os.FileInfo, follow bool) error {
  529. if w.isClosed() {
  530. return ErrClosed
  531. }
  532. // This is primarily protecting the call to AssociatePath but it is
  533. // important and intentional that the call to PathIsWatched is also
  534. // protected by this mutex. Without this mutex, AssociatePath has been seen
  535. // to error out that the path is already associated.
  536. w.mu.Lock()
  537. defer w.mu.Unlock()
  538. if w.port.PathIsWatched(path) {
  539. // Remove the old association in favor of this one If we get ENOENT,
  540. // then while the x/sys/unix wrapper still thought that this path was
  541. // associated, the underlying event port did not. This call will have
  542. // cleared up that discrepancy. The most likely cause is that the event
  543. // has fired but we haven't processed it yet.
  544. err := w.port.DissociatePath(path)
  545. if err != nil && err != unix.ENOENT {
  546. return err
  547. }
  548. }
  549. // FILE_NOFOLLOW means we watch symlinks themselves rather than their
  550. // targets.
  551. events := unix.FILE_MODIFIED | unix.FILE_ATTRIB | unix.FILE_NOFOLLOW
  552. if follow {
  553. // We *DO* follow symlinks for explicitly watched entries.
  554. events = unix.FILE_MODIFIED | unix.FILE_ATTRIB
  555. }
  556. return w.port.AssociatePath(path, stat,
  557. events,
  558. stat.Mode())
  559. }
  560. func (w *Watcher) dissociateFile(path string, stat os.FileInfo, unused bool) error {
  561. if !w.port.PathIsWatched(path) {
  562. return nil
  563. }
  564. return w.port.DissociatePath(path)
  565. }
  566. // WatchList returns all paths explicitly added with [Watcher.Add] (and are not
  567. // yet removed).
  568. //
  569. // Returns nil if [Watcher.Close] was called.
  570. func (w *Watcher) WatchList() []string {
  571. if w.isClosed() {
  572. return nil
  573. }
  574. w.mu.Lock()
  575. defer w.mu.Unlock()
  576. entries := make([]string, 0, len(w.watches)+len(w.dirs))
  577. for pathname := range w.dirs {
  578. entries = append(entries, pathname)
  579. }
  580. for pathname := range w.watches {
  581. entries = append(entries, pathname)
  582. }
  583. return entries
  584. }