1 // Copyright 2010 The Go Authors. All rights reserved. |
|
2 // Use of this source code is governed by a BSD-style |
|
3 // license that can be found in the LICENSE file. |
|
4 |
|
5 //go:build freebsd || openbsd || netbsd || dragonfly || darwin |
|
6 // +build freebsd openbsd netbsd dragonfly darwin |
|
7 |
|
8 package fsnotify |
|
9 |
|
10 import ( |
|
11 "errors" |
|
12 "fmt" |
|
13 "io/ioutil" |
|
14 "os" |
|
15 "path/filepath" |
|
16 "sync" |
|
17 "time" |
|
18 |
|
19 "golang.org/x/sys/unix" |
|
20 ) |
|
21 |
|
22 // Watcher watches a set of files, delivering events to a channel. |
|
23 type Watcher struct { |
|
24 Events chan Event |
|
25 Errors chan error |
|
26 done chan struct{} // Channel for sending a "quit message" to the reader goroutine |
|
27 |
|
28 kq int // File descriptor (as returned by the kqueue() syscall). |
|
29 |
|
30 mu sync.Mutex // Protects access to watcher data |
|
31 watches map[string]int // Map of watched file descriptors (key: path). |
|
32 externalWatches map[string]bool // Map of watches added by user of the library. |
|
33 dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue. |
|
34 paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events. |
|
35 fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events). |
|
36 isClosed bool // Set to true when Close() is first called |
|
37 } |
|
38 |
|
39 type pathInfo struct { |
|
40 name string |
|
41 isDir bool |
|
42 } |
|
43 |
|
44 // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. |
|
45 func NewWatcher() (*Watcher, error) { |
|
46 kq, err := kqueue() |
|
47 if err != nil { |
|
48 return nil, err |
|
49 } |
|
50 |
|
51 w := &Watcher{ |
|
52 kq: kq, |
|
53 watches: make(map[string]int), |
|
54 dirFlags: make(map[string]uint32), |
|
55 paths: make(map[int]pathInfo), |
|
56 fileExists: make(map[string]bool), |
|
57 externalWatches: make(map[string]bool), |
|
58 Events: make(chan Event), |
|
59 Errors: make(chan error), |
|
60 done: make(chan struct{}), |
|
61 } |
|
62 |
|
63 go w.readEvents() |
|
64 return w, nil |
|
65 } |
|
66 |
|
67 // Close removes all watches and closes the events channel. |
|
68 func (w *Watcher) Close() error { |
|
69 w.mu.Lock() |
|
70 if w.isClosed { |
|
71 w.mu.Unlock() |
|
72 return nil |
|
73 } |
|
74 w.isClosed = true |
|
75 |
|
76 // copy paths to remove while locked |
|
77 var pathsToRemove = make([]string, 0, len(w.watches)) |
|
78 for name := range w.watches { |
|
79 pathsToRemove = append(pathsToRemove, name) |
|
80 } |
|
81 w.mu.Unlock() |
|
82 // unlock before calling Remove, which also locks |
|
83 |
|
84 for _, name := range pathsToRemove { |
|
85 w.Remove(name) |
|
86 } |
|
87 |
|
88 // send a "quit" message to the reader goroutine |
|
89 close(w.done) |
|
90 |
|
91 return nil |
|
92 } |
|
93 |
|
94 // Add starts watching the named file or directory (non-recursively). |
|
95 func (w *Watcher) Add(name string) error { |
|
96 w.mu.Lock() |
|
97 w.externalWatches[name] = true |
|
98 w.mu.Unlock() |
|
99 _, err := w.addWatch(name, noteAllEvents) |
|
100 return err |
|
101 } |
|
102 |
|
103 // Remove stops watching the the named file or directory (non-recursively). |
|
104 func (w *Watcher) Remove(name string) error { |
|
105 name = filepath.Clean(name) |
|
106 w.mu.Lock() |
|
107 watchfd, ok := w.watches[name] |
|
108 w.mu.Unlock() |
|
109 if !ok { |
|
110 return fmt.Errorf("can't remove non-existent kevent watch for: %s", name) |
|
111 } |
|
112 |
|
113 const registerRemove = unix.EV_DELETE |
|
114 if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil { |
|
115 return err |
|
116 } |
|
117 |
|
118 unix.Close(watchfd) |
|
119 |
|
120 w.mu.Lock() |
|
121 isDir := w.paths[watchfd].isDir |
|
122 delete(w.watches, name) |
|
123 delete(w.paths, watchfd) |
|
124 delete(w.dirFlags, name) |
|
125 w.mu.Unlock() |
|
126 |
|
127 // Find all watched paths that are in this directory that are not external. |
|
128 if isDir { |
|
129 var pathsToRemove []string |
|
130 w.mu.Lock() |
|
131 for _, path := range w.paths { |
|
132 wdir, _ := filepath.Split(path.name) |
|
133 if filepath.Clean(wdir) == name { |
|
134 if !w.externalWatches[path.name] { |
|
135 pathsToRemove = append(pathsToRemove, path.name) |
|
136 } |
|
137 } |
|
138 } |
|
139 w.mu.Unlock() |
|
140 for _, name := range pathsToRemove { |
|
141 // Since these are internal, not much sense in propagating error |
|
142 // to the user, as that will just confuse them with an error about |
|
143 // a path they did not explicitly watch themselves. |
|
144 w.Remove(name) |
|
145 } |
|
146 } |
|
147 |
|
148 return nil |
|
149 } |
|
150 |
|
151 // WatchList returns the directories and files that are being monitered. |
|
152 func (w *Watcher) WatchList() []string { |
|
153 w.mu.Lock() |
|
154 defer w.mu.Unlock() |
|
155 |
|
156 entries := make([]string, 0, len(w.watches)) |
|
157 for pathname := range w.watches { |
|
158 entries = append(entries, pathname) |
|
159 } |
|
160 |
|
161 return entries |
|
162 } |
|
163 |
|
164 // Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE) |
|
165 const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME |
|
166 |
|
167 // keventWaitTime to block on each read from kevent |
|
168 var keventWaitTime = durationToTimespec(100 * time.Millisecond) |
|
169 |
|
170 // addWatch adds name to the watched file set. |
|
171 // The flags are interpreted as described in kevent(2). |
|
172 // Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks. |
|
173 func (w *Watcher) addWatch(name string, flags uint32) (string, error) { |
|
174 var isDir bool |
|
175 // Make ./name and name equivalent |
|
176 name = filepath.Clean(name) |
|
177 |
|
178 w.mu.Lock() |
|
179 if w.isClosed { |
|
180 w.mu.Unlock() |
|
181 return "", errors.New("kevent instance already closed") |
|
182 } |
|
183 watchfd, alreadyWatching := w.watches[name] |
|
184 // We already have a watch, but we can still override flags. |
|
185 if alreadyWatching { |
|
186 isDir = w.paths[watchfd].isDir |
|
187 } |
|
188 w.mu.Unlock() |
|
189 |
|
190 if !alreadyWatching { |
|
191 fi, err := os.Lstat(name) |
|
192 if err != nil { |
|
193 return "", err |
|
194 } |
|
195 |
|
196 // Don't watch sockets. |
|
197 if fi.Mode()&os.ModeSocket == os.ModeSocket { |
|
198 return "", nil |
|
199 } |
|
200 |
|
201 // Don't watch named pipes. |
|
202 if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe { |
|
203 return "", nil |
|
204 } |
|
205 |
|
206 // Follow Symlinks |
|
207 // Unfortunately, Linux can add bogus symlinks to watch list without |
|
208 // issue, and Windows can't do symlinks period (AFAIK). To maintain |
|
209 // consistency, we will act like everything is fine. There will simply |
|
210 // be no file events for broken symlinks. |
|
211 // Hence the returns of nil on errors. |
|
212 if fi.Mode()&os.ModeSymlink == os.ModeSymlink { |
|
213 name, err = filepath.EvalSymlinks(name) |
|
214 if err != nil { |
|
215 return "", nil |
|
216 } |
|
217 |
|
218 w.mu.Lock() |
|
219 _, alreadyWatching = w.watches[name] |
|
220 w.mu.Unlock() |
|
221 |
|
222 if alreadyWatching { |
|
223 return name, nil |
|
224 } |
|
225 |
|
226 fi, err = os.Lstat(name) |
|
227 if err != nil { |
|
228 return "", nil |
|
229 } |
|
230 } |
|
231 |
|
232 watchfd, err = unix.Open(name, openMode, 0700) |
|
233 if watchfd == -1 { |
|
234 return "", err |
|
235 } |
|
236 |
|
237 isDir = fi.IsDir() |
|
238 } |
|
239 |
|
240 const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE |
|
241 if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil { |
|
242 unix.Close(watchfd) |
|
243 return "", err |
|
244 } |
|
245 |
|
246 if !alreadyWatching { |
|
247 w.mu.Lock() |
|
248 w.watches[name] = watchfd |
|
249 w.paths[watchfd] = pathInfo{name: name, isDir: isDir} |
|
250 w.mu.Unlock() |
|
251 } |
|
252 |
|
253 if isDir { |
|
254 // Watch the directory if it has not been watched before, |
|
255 // or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles) |
|
256 w.mu.Lock() |
|
257 |
|
258 watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE && |
|
259 (!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE) |
|
260 // Store flags so this watch can be updated later |
|
261 w.dirFlags[name] = flags |
|
262 w.mu.Unlock() |
|
263 |
|
264 if watchDir { |
|
265 if err := w.watchDirectoryFiles(name); err != nil { |
|
266 return "", err |
|
267 } |
|
268 } |
|
269 } |
|
270 return name, nil |
|
271 } |
|
272 |
|
273 // readEvents reads from kqueue and converts the received kevents into |
|
274 // Event values that it sends down the Events channel. |
|
275 func (w *Watcher) readEvents() { |
|
276 eventBuffer := make([]unix.Kevent_t, 10) |
|
277 |
|
278 loop: |
|
279 for { |
|
280 // See if there is a message on the "done" channel |
|
281 select { |
|
282 case <-w.done: |
|
283 break loop |
|
284 default: |
|
285 } |
|
286 |
|
287 // Get new events |
|
288 kevents, err := read(w.kq, eventBuffer, &keventWaitTime) |
|
289 // EINTR is okay, the syscall was interrupted before timeout expired. |
|
290 if err != nil && err != unix.EINTR { |
|
291 select { |
|
292 case w.Errors <- err: |
|
293 case <-w.done: |
|
294 break loop |
|
295 } |
|
296 continue |
|
297 } |
|
298 |
|
299 // Flush the events we received to the Events channel |
|
300 for len(kevents) > 0 { |
|
301 kevent := &kevents[0] |
|
302 watchfd := int(kevent.Ident) |
|
303 mask := uint32(kevent.Fflags) |
|
304 w.mu.Lock() |
|
305 path := w.paths[watchfd] |
|
306 w.mu.Unlock() |
|
307 event := newEvent(path.name, mask) |
|
308 |
|
309 if path.isDir && !(event.Op&Remove == Remove) { |
|
310 // Double check to make sure the directory exists. This can happen when |
|
311 // we do a rm -fr on a recursively watched folders and we receive a |
|
312 // modification event first but the folder has been deleted and later |
|
313 // receive the delete event |
|
314 if _, err := os.Lstat(event.Name); os.IsNotExist(err) { |
|
315 // mark is as delete event |
|
316 event.Op |= Remove |
|
317 } |
|
318 } |
|
319 |
|
320 if event.Op&Rename == Rename || event.Op&Remove == Remove { |
|
321 w.Remove(event.Name) |
|
322 w.mu.Lock() |
|
323 delete(w.fileExists, event.Name) |
|
324 w.mu.Unlock() |
|
325 } |
|
326 |
|
327 if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) { |
|
328 w.sendDirectoryChangeEvents(event.Name) |
|
329 } else { |
|
330 // Send the event on the Events channel. |
|
331 select { |
|
332 case w.Events <- event: |
|
333 case <-w.done: |
|
334 break loop |
|
335 } |
|
336 } |
|
337 |
|
338 if event.Op&Remove == Remove { |
|
339 // Look for a file that may have overwritten this. |
|
340 // For example, mv f1 f2 will delete f2, then create f2. |
|
341 if path.isDir { |
|
342 fileDir := filepath.Clean(event.Name) |
|
343 w.mu.Lock() |
|
344 _, found := w.watches[fileDir] |
|
345 w.mu.Unlock() |
|
346 if found { |
|
347 // make sure the directory exists before we watch for changes. When we |
|
348 // do a recursive watch and perform rm -fr, the parent directory might |
|
349 // have gone missing, ignore the missing directory and let the |
|
350 // upcoming delete event remove the watch from the parent directory. |
|
351 if _, err := os.Lstat(fileDir); err == nil { |
|
352 w.sendDirectoryChangeEvents(fileDir) |
|
353 } |
|
354 } |
|
355 } else { |
|
356 filePath := filepath.Clean(event.Name) |
|
357 if fileInfo, err := os.Lstat(filePath); err == nil { |
|
358 w.sendFileCreatedEventIfNew(filePath, fileInfo) |
|
359 } |
|
360 } |
|
361 } |
|
362 |
|
363 // Move to next event |
|
364 kevents = kevents[1:] |
|
365 } |
|
366 } |
|
367 |
|
368 // cleanup |
|
369 err := unix.Close(w.kq) |
|
370 if err != nil { |
|
371 // only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors. |
|
372 select { |
|
373 case w.Errors <- err: |
|
374 default: |
|
375 } |
|
376 } |
|
377 close(w.Events) |
|
378 close(w.Errors) |
|
379 } |
|
380 |
|
381 // newEvent returns an platform-independent Event based on kqueue Fflags. |
|
382 func newEvent(name string, mask uint32) Event { |
|
383 e := Event{Name: name} |
|
384 if mask&unix.NOTE_DELETE == unix.NOTE_DELETE { |
|
385 e.Op |= Remove |
|
386 } |
|
387 if mask&unix.NOTE_WRITE == unix.NOTE_WRITE { |
|
388 e.Op |= Write |
|
389 } |
|
390 if mask&unix.NOTE_RENAME == unix.NOTE_RENAME { |
|
391 e.Op |= Rename |
|
392 } |
|
393 if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB { |
|
394 e.Op |= Chmod |
|
395 } |
|
396 return e |
|
397 } |
|
398 |
|
399 func newCreateEvent(name string) Event { |
|
400 return Event{Name: name, Op: Create} |
|
401 } |
|
402 |
|
403 // watchDirectoryFiles to mimic inotify when adding a watch on a directory |
|
404 func (w *Watcher) watchDirectoryFiles(dirPath string) error { |
|
405 // Get all files |
|
406 files, err := ioutil.ReadDir(dirPath) |
|
407 if err != nil { |
|
408 return err |
|
409 } |
|
410 |
|
411 for _, fileInfo := range files { |
|
412 filePath := filepath.Join(dirPath, fileInfo.Name()) |
|
413 filePath, err = w.internalWatch(filePath, fileInfo) |
|
414 if err != nil { |
|
415 return err |
|
416 } |
|
417 |
|
418 w.mu.Lock() |
|
419 w.fileExists[filePath] = true |
|
420 w.mu.Unlock() |
|
421 } |
|
422 |
|
423 return nil |
|
424 } |
|
425 |
|
426 // sendDirectoryEvents searches the directory for newly created files |
|
427 // and sends them over the event channel. This functionality is to have |
|
428 // the BSD version of fsnotify match Linux inotify which provides a |
|
429 // create event for files created in a watched directory. |
|
430 func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { |
|
431 // Get all files |
|
432 files, err := ioutil.ReadDir(dirPath) |
|
433 if err != nil { |
|
434 select { |
|
435 case w.Errors <- err: |
|
436 case <-w.done: |
|
437 return |
|
438 } |
|
439 } |
|
440 |
|
441 // Search for new files |
|
442 for _, fileInfo := range files { |
|
443 filePath := filepath.Join(dirPath, fileInfo.Name()) |
|
444 err := w.sendFileCreatedEventIfNew(filePath, fileInfo) |
|
445 |
|
446 if err != nil { |
|
447 return |
|
448 } |
|
449 } |
|
450 } |
|
451 |
|
452 // sendFileCreatedEvent sends a create event if the file isn't already being tracked. |
|
453 func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) { |
|
454 w.mu.Lock() |
|
455 _, doesExist := w.fileExists[filePath] |
|
456 w.mu.Unlock() |
|
457 if !doesExist { |
|
458 // Send create event |
|
459 select { |
|
460 case w.Events <- newCreateEvent(filePath): |
|
461 case <-w.done: |
|
462 return |
|
463 } |
|
464 } |
|
465 |
|
466 // like watchDirectoryFiles (but without doing another ReadDir) |
|
467 filePath, err = w.internalWatch(filePath, fileInfo) |
|
468 if err != nil { |
|
469 return err |
|
470 } |
|
471 |
|
472 w.mu.Lock() |
|
473 w.fileExists[filePath] = true |
|
474 w.mu.Unlock() |
|
475 |
|
476 return nil |
|
477 } |
|
478 |
|
479 func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) { |
|
480 if fileInfo.IsDir() { |
|
481 // mimic Linux providing delete events for subdirectories |
|
482 // but preserve the flags used if currently watching subdirectory |
|
483 w.mu.Lock() |
|
484 flags := w.dirFlags[name] |
|
485 w.mu.Unlock() |
|
486 |
|
487 flags |= unix.NOTE_DELETE | unix.NOTE_RENAME |
|
488 return w.addWatch(name, flags) |
|
489 } |
|
490 |
|
491 // watch file to mimic Linux inotify |
|
492 return w.addWatch(name, noteAllEvents) |
|
493 } |
|
494 |
|
495 // kqueue creates a new kernel event queue and returns a descriptor. |
|
496 func kqueue() (kq int, err error) { |
|
497 kq, err = unix.Kqueue() |
|
498 if kq == -1 { |
|
499 return kq, err |
|
500 } |
|
501 return kq, nil |
|
502 } |
|
503 |
|
504 // register events with the queue |
|
505 func register(kq int, fds []int, flags int, fflags uint32) error { |
|
506 changes := make([]unix.Kevent_t, len(fds)) |
|
507 |
|
508 for i, fd := range fds { |
|
509 // SetKevent converts int to the platform-specific types: |
|
510 unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags) |
|
511 changes[i].Fflags = fflags |
|
512 } |
|
513 |
|
514 // register the events |
|
515 success, err := unix.Kevent(kq, changes, nil, nil) |
|
516 if success == -1 { |
|
517 return err |
|
518 } |
|
519 return nil |
|
520 } |
|
521 |
|
522 // read retrieves pending events, or waits until an event occurs. |
|
523 // A timeout of nil blocks indefinitely, while 0 polls the queue. |
|
524 func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) { |
|
525 n, err := unix.Kevent(kq, nil, events, timeout) |
|
526 if err != nil { |
|
527 return nil, err |
|
528 } |
|
529 return events[0:n], nil |
|
530 } |
|
531 |
|
532 // durationToTimespec prepares a timeout value |
|
533 func durationToTimespec(d time.Duration) unix.Timespec { |
|
534 return unix.NsecToTimespec(d.Nanoseconds()) |
|
535 } |
|