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