1// Package fsnotify provides a cross-platform interface for file system
2// notifications.
3//
4// Currently supported systems:
5//
6// - Linux via inotify
7// - BSD, macOS via kqueue
8// - Windows via ReadDirectoryChangesW
9// - illumos via FEN
10//
11// # FSNOTIFY_DEBUG
12//
13// Set the FSNOTIFY_DEBUG environment variable to "1" to print debug messages to
14// stderr. This can be useful to track down some problems, especially in cases
15// where fsnotify is used as an indirect dependency.
16//
17// Every event will be printed as soon as there's something useful to print,
18// with as little processing from fsnotify.
19//
20// Example output:
21//
22// FSNOTIFY_DEBUG: 11:34:23.633087586 256:IN_CREATE → "/tmp/file-1"
23// FSNOTIFY_DEBUG: 11:34:23.633202319 4:IN_ATTRIB → "/tmp/file-1"
24// FSNOTIFY_DEBUG: 11:34:28.989728764 512:IN_DELETE → "/tmp/file-1"
25package fsnotify
26
27import (
28 "errors"
29 "fmt"
30 "os"
31 "path/filepath"
32 "strings"
33)
34
35// Watcher watches a set of paths, delivering events on a channel.
36//
37// A watcher should not be copied (e.g. pass it by pointer, rather than by
38// value).
39//
40// # Linux notes
41//
42// When a file is removed a Remove event won't be emitted until all file
43// descriptors are closed, and deletes will always emit a Chmod. For example:
44//
45// fp := os.Open("file")
46// os.Remove("file") // Triggers Chmod
47// fp.Close() // Triggers Remove
48//
49// This is the event that inotify sends, so not much can be changed about this.
50//
51// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
52// for the number of watches per user, and fs.inotify.max_user_instances
53// specifies the maximum number of inotify instances per user. Every Watcher you
54// create is an "instance", and every path you add is a "watch".
55//
56// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
57// /proc/sys/fs/inotify/max_user_instances
58//
59// To increase them you can use sysctl or write the value to the /proc file:
60//
61// # Default values on Linux 5.18
62// sysctl fs.inotify.max_user_watches=124983
63// sysctl fs.inotify.max_user_instances=128
64//
65// To make the changes persist on reboot edit /etc/sysctl.conf or
66// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
67// your distro's documentation):
68//
69// fs.inotify.max_user_watches=124983
70// fs.inotify.max_user_instances=128
71//
72// Reaching the limit will result in a "no space left on device" or "too many open
73// files" error.
74//
75// # kqueue notes (macOS, BSD)
76//
77// kqueue requires opening a file descriptor for every file that's being watched;
78// so if you're watching a directory with five files then that's six file
79// descriptors. You will run in to your system's "max open files" limit faster on
80// these platforms.
81//
82// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
83// control the maximum number of open files, as well as /etc/login.conf on BSD
84// systems.
85//
86// # Windows notes
87//
88// Paths can be added as "C:\\path\\to\\dir", but forward slashes
89// ("C:/path/to/dir") will also work.
90//
91// When a watched directory is removed it will always send an event for the
92// directory itself, but may not send events for all files in that directory.
93// Sometimes it will send events for all files, sometimes it will send no
94// events, and often only for some files.
95//
96// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
97// value that is guaranteed to work with SMB filesystems. If you have many
98// events in quick succession this may not be enough, and you will have to use
99// [WithBufferSize] to increase the value.
100type Watcher struct {
101 b backend
102
103 // Events sends the filesystem change events.
104 //
105 // fsnotify can send the following events; a "path" here can refer to a
106 // file, directory, symbolic link, or special file like a FIFO.
107 //
108 // fsnotify.Create A new path was created; this may be followed by one
109 // or more Write events if data also gets written to a
110 // file.
111 //
112 // fsnotify.Remove A path was removed.
113 //
114 // fsnotify.Rename A path was renamed. A rename is always sent with the
115 // old path as Event.Name, and a Create event will be
116 // sent with the new name. Renames are only sent for
117 // paths that are currently watched; e.g. moving an
118 // unmonitored file into a monitored directory will
119 // show up as just a Create. Similarly, renaming a file
120 // to outside a monitored directory will show up as
121 // only a Rename.
122 //
123 // fsnotify.Write A file or named pipe was written to. A Truncate will
124 // also trigger a Write. A single "write action"
125 // initiated by the user may show up as one or multiple
126 // writes, depending on when the system syncs things to
127 // disk. For example when compiling a large Go program
128 // you may get hundreds of Write events, and you may
129 // want to wait until you've stopped receiving them
130 // (see the dedup example in cmd/fsnotify).
131 //
132 // Some systems may send Write event for directories
133 // when the directory content changes.
134 //
135 // fsnotify.Chmod Attributes were changed. On Linux this is also sent
136 // when a file is removed (or more accurately, when a
137 // link to an inode is removed). On kqueue it's sent
138 // when a file is truncated. On Windows it's never
139 // sent.
140 Events chan Event
141
142 // Errors sends any errors.
143 Errors chan error
144}
145
146// Event represents a file system notification.
147type Event struct {
148 // Path to the file or directory.
149 //
150 // Paths are relative to the input; for example with Add("dir") the Name
151 // will be set to "dir/file" if you create that file, but if you use
152 // Add("/path/to/dir") it will be "/path/to/dir/file".
153 Name string
154
155 // File operation that triggered the event.
156 //
157 // This is a bitmask and some systems may send multiple operations at once.
158 // Use the Event.Has() method instead of comparing with ==.
159 Op Op
160
161 // Create events will have this set to the old path if it's a rename. This
162 // only works when both the source and destination are watched. It's not
163 // reliable when watching individual files, only directories.
164 //
165 // For example "mv /tmp/file /tmp/rename" will emit:
166 //
167 // Event{Op: Rename, Name: "/tmp/file"}
168 // Event{Op: Create, Name: "/tmp/rename", RenamedFrom: "/tmp/file"}
169 renamedFrom string
170}
171
172// Op describes a set of file operations.
173type Op uint32
174
175// The operations fsnotify can trigger; see the documentation on [Watcher] for a
176// full description, and check them with [Event.Has].
177const (
178 // A new pathname was created.
179 Create Op = 1 << iota
180
181 // The pathname was written to; this does *not* mean the write has finished,
182 // and a write can be followed by more writes.
183 Write
184
185 // The path was removed; any watches on it will be removed. Some "remove"
186 // operations may trigger a Rename if the file is actually moved (for
187 // example "remove to trash" is often a rename).
188 Remove
189
190 // The path was renamed to something else; any watches on it will be
191 // removed.
192 Rename
193
194 // File attributes were changed.
195 //
196 // It's generally not recommended to take action on this event, as it may
197 // get triggered very frequently by some software. For example, Spotlight
198 // indexing on macOS, anti-virus software, backup software, etc.
199 Chmod
200
201 // File descriptor was opened.
202 //
203 // Only works on Linux and FreeBSD.
204 xUnportableOpen
205
206 // File was read from.
207 //
208 // Only works on Linux and FreeBSD.
209 xUnportableRead
210
211 // File opened for writing was closed.
212 //
213 // Only works on Linux and FreeBSD.
214 //
215 // The advantage of using this over Write is that it's more reliable than
216 // waiting for Write events to stop. It's also faster (if you're not
217 // listening to Write events): copying a file of a few GB can easily
218 // generate tens of thousands of Write events in a short span of time.
219 xUnportableCloseWrite
220
221 // File opened for reading was closed.
222 //
223 // Only works on Linux and FreeBSD.
224 xUnportableCloseRead
225)
226
227var (
228 // ErrNonExistentWatch is used when Remove() is called on a path that's not
229 // added.
230 ErrNonExistentWatch = errors.New("fsnotify: can't remove non-existent watch")
231
232 // ErrClosed is used when trying to operate on a closed Watcher.
233 ErrClosed = errors.New("fsnotify: watcher already closed")
234
235 // ErrEventOverflow is reported from the Errors channel when there are too
236 // many events:
237 //
238 // - inotify: inotify returns IN_Q_OVERFLOW – because there are too
239 // many queued events (the fs.inotify.max_queued_events
240 // sysctl can be used to increase this).
241 // - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
242 // - kqueue, fen: Not used.
243 ErrEventOverflow = errors.New("fsnotify: queue or buffer overflow")
244
245 // ErrUnsupported is returned by AddWith() when WithOps() specified an
246 // Unportable event that's not supported on this platform.
247 xErrUnsupported = errors.New("fsnotify: not supported with this backend")
248)
249
250// NewWatcher creates a new Watcher.
251func NewWatcher() (*Watcher, error) {
252 ev, errs := make(chan Event), make(chan error)
253 b, err := newBackend(ev, errs)
254 if err != nil {
255 return nil, err
256 }
257 return &Watcher{b: b, Events: ev, Errors: errs}, nil
258}
259
260// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
261// channel.
262//
263// The main use case for this is situations with a very large number of events
264// where the kernel buffer size can't be increased (e.g. due to lack of
265// permissions). An unbuffered Watcher will perform better for almost all use
266// cases, and whenever possible you will be better off increasing the kernel
267// buffers instead of adding a large userspace buffer.
268func NewBufferedWatcher(sz uint) (*Watcher, error) {
269 ev, errs := make(chan Event), make(chan error)
270 b, err := newBufferedBackend(sz, ev, errs)
271 if err != nil {
272 return nil, err
273 }
274 return &Watcher{b: b, Events: ev, Errors: errs}, nil
275}
276
277// Add starts monitoring the path for changes.
278//
279// A path can only be watched once; watching it more than once is a no-op and will
280// not return an error. Paths that do not yet exist on the filesystem cannot be
281// watched.
282//
283// A watch will be automatically removed if the watched path is deleted or
284// renamed. The exception is the Windows backend, which doesn't remove the
285// watcher on renames.
286//
287// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
288// filesystems (/proc, /sys, etc.) generally don't work.
289//
290// Returns [ErrClosed] if [Watcher.Close] was called.
291//
292// See [Watcher.AddWith] for a version that allows adding options.
293//
294// # Watching directories
295//
296// All files in a directory are monitored, including new files that are created
297// after the watcher is started. Subdirectories are not watched (i.e. it's
298// non-recursive).
299//
300// # Watching files
301//
302// Watching individual files (rather than directories) is generally not
303// recommended as many programs (especially editors) update files atomically: it
304// will write to a temporary file which is then moved to destination,
305// overwriting the original (or some variant thereof). The watcher on the
306// original file is now lost, as that no longer exists.
307//
308// The upshot of this is that a power failure or crash won't leave a
309// half-written file.
310//
311// Watch the parent directory and use Event.Name to filter out files you're not
312// interested in. There is an example of this in cmd/fsnotify/file.go.
313func (w *Watcher) Add(path string) error { return w.b.Add(path) }
314
315// AddWith is like [Watcher.Add], but allows adding options. When using Add()
316// the defaults described below are used.
317//
318// Possible options are:
319//
320// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
321// other platforms. The default is 64K (65536 bytes).
322func (w *Watcher) AddWith(path string, opts ...addOpt) error { return w.b.AddWith(path, opts...) }
323
324// Remove stops monitoring the path for changes.
325//
326// Directories are always removed non-recursively. For example, if you added
327// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
328//
329// Removing a path that has not yet been added returns [ErrNonExistentWatch].
330//
331// Returns nil if [Watcher.Close] was called.
332func (w *Watcher) Remove(path string) error { return w.b.Remove(path) }
333
334// Close removes all watches and closes the Events channel.
335func (w *Watcher) Close() error { return w.b.Close() }
336
337// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
338// yet removed).
339//
340// Returns nil if [Watcher.Close] was called.
341func (w *Watcher) WatchList() []string { return w.b.WatchList() }
342
343// Supports reports if all the listed operations are supported by this platform.
344//
345// Create, Write, Remove, Rename, and Chmod are always supported. It can only
346// return false for an Op starting with Unportable.
347func (w *Watcher) xSupports(op Op) bool { return w.b.xSupports(op) }
348
349func (o Op) String() string {
350 var b strings.Builder
351 if o.Has(Create) {
352 b.WriteString("|CREATE")
353 }
354 if o.Has(Remove) {
355 b.WriteString("|REMOVE")
356 }
357 if o.Has(Write) {
358 b.WriteString("|WRITE")
359 }
360 if o.Has(xUnportableOpen) {
361 b.WriteString("|OPEN")
362 }
363 if o.Has(xUnportableRead) {
364 b.WriteString("|READ")
365 }
366 if o.Has(xUnportableCloseWrite) {
367 b.WriteString("|CLOSE_WRITE")
368 }
369 if o.Has(xUnportableCloseRead) {
370 b.WriteString("|CLOSE_READ")
371 }
372 if o.Has(Rename) {
373 b.WriteString("|RENAME")
374 }
375 if o.Has(Chmod) {
376 b.WriteString("|CHMOD")
377 }
378 if b.Len() == 0 {
379 return "[no events]"
380 }
381 return b.String()[1:]
382}
383
384// Has reports if this operation has the given operation.
385func (o Op) Has(h Op) bool { return o&h != 0 }
386
387// Has reports if this event has the given operation.
388func (e Event) Has(op Op) bool { return e.Op.Has(op) }
389
390// String returns a string representation of the event with their path.
391func (e Event) String() string {
392 if e.renamedFrom != "" {
393 return fmt.Sprintf("%-13s %q ← %q", e.Op.String(), e.Name, e.renamedFrom)
394 }
395 return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name)
396}
397
398type (
399 backend interface {
400 Add(string) error
401 AddWith(string, ...addOpt) error
402 Remove(string) error
403 WatchList() []string
404 Close() error
405 xSupports(Op) bool
406 }
407 addOpt func(opt *withOpts)
408 withOpts struct {
409 bufsize int
410 op Op
411 noFollow bool
412 sendCreate bool
413 }
414)
415
416var debug = func() bool {
417 // Check for exactly "1" (rather than mere existence) so we can add
418 // options/flags in the future. I don't know if we ever want that, but it's
419 // nice to leave the option open.
420 return os.Getenv("FSNOTIFY_DEBUG") == "1"
421}()
422
423var defaultOpts = withOpts{
424 bufsize: 65536, // 64K
425 op: Create | Write | Remove | Rename | Chmod,
426}
427
428func getOptions(opts ...addOpt) withOpts {
429 with := defaultOpts
430 for _, o := range opts {
431 if o != nil {
432 o(&with)
433 }
434 }
435 return with
436}
437
438// WithBufferSize sets the [ReadDirectoryChangesW] buffer size.
439//
440// This only has effect on Windows systems, and is a no-op for other backends.
441//
442// The default value is 64K (65536 bytes) which is the highest value that works
443// on all filesystems and should be enough for most applications, but if you
444// have a large burst of events it may not be enough. You can increase it if
445// you're hitting "queue or buffer overflow" errors ([ErrEventOverflow]).
446//
447// [ReadDirectoryChangesW]: https://learn.microsoft.com/en-gb/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
448func WithBufferSize(bytes int) addOpt {
449 return func(opt *withOpts) { opt.bufsize = bytes }
450}
451
452// WithOps sets which operations to listen for. The default is [Create],
453// [Write], [Remove], [Rename], and [Chmod].
454//
455// Excluding operations you're not interested in can save quite a bit of CPU
456// time; in some use cases there may be hundreds of thousands of useless Write
457// or Chmod operations per second.
458//
459// This can also be used to add unportable operations not supported by all
460// platforms; unportable operations all start with "Unportable":
461// [UnportableOpen], [UnportableRead], [UnportableCloseWrite], and
462// [UnportableCloseRead].
463//
464// AddWith returns an error when using an unportable operation that's not
465// supported. Use [Watcher.Support] to check for support.
466func withOps(op Op) addOpt {
467 return func(opt *withOpts) { opt.op = op }
468}
469
470// WithNoFollow disables following symlinks, so the symlinks themselves are
471// watched.
472func withNoFollow() addOpt {
473 return func(opt *withOpts) { opt.noFollow = true }
474}
475
476// "Internal" option for recursive watches on inotify.
477func withCreate() addOpt {
478 return func(opt *withOpts) { opt.sendCreate = true }
479}
480
481var enableRecurse = false
482
483// Check if this path is recursive (ends with "/..." or "\..."), and return the
484// path with the /... stripped.
485func recursivePath(path string) (string, bool) {
486 path = filepath.Clean(path)
487 if !enableRecurse { // Only enabled in tests for now.
488 return path, false
489 }
490 if filepath.Base(path) == "..." {
491 return filepath.Dir(path), true
492 }
493 return path, false
494}