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 linux |
|
6 // +build linux |
|
7 |
|
8 package fsnotify |
|
9 |
|
10 import ( |
|
11 "errors" |
|
12 "fmt" |
|
13 "io" |
|
14 "os" |
|
15 "path/filepath" |
|
16 "strings" |
|
17 "sync" |
|
18 "unsafe" |
|
19 |
|
20 "golang.org/x/sys/unix" |
|
21 ) |
|
22 |
|
23 // Watcher watches a set of files, delivering events to a channel. |
|
24 type Watcher struct { |
|
25 Events chan Event |
|
26 Errors chan error |
|
27 mu sync.Mutex // Map access |
|
28 fd int |
|
29 poller *fdPoller |
|
30 watches map[string]*watch // Map of inotify watches (key: path) |
|
31 paths map[int]string // Map of watched paths (key: watch descriptor) |
|
32 done chan struct{} // Channel for sending a "quit message" to the reader goroutine |
|
33 doneResp chan struct{} // Channel to respond to Close |
|
34 } |
|
35 |
|
36 // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. |
|
37 func NewWatcher() (*Watcher, error) { |
|
38 // Create inotify fd |
|
39 fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC) |
|
40 if fd == -1 { |
|
41 return nil, errno |
|
42 } |
|
43 // Create epoll |
|
44 poller, err := newFdPoller(fd) |
|
45 if err != nil { |
|
46 unix.Close(fd) |
|
47 return nil, err |
|
48 } |
|
49 w := &Watcher{ |
|
50 fd: fd, |
|
51 poller: poller, |
|
52 watches: make(map[string]*watch), |
|
53 paths: make(map[int]string), |
|
54 Events: make(chan Event), |
|
55 Errors: make(chan error), |
|
56 done: make(chan struct{}), |
|
57 doneResp: make(chan struct{}), |
|
58 } |
|
59 |
|
60 go w.readEvents() |
|
61 return w, nil |
|
62 } |
|
63 |
|
64 func (w *Watcher) isClosed() bool { |
|
65 select { |
|
66 case <-w.done: |
|
67 return true |
|
68 default: |
|
69 return false |
|
70 } |
|
71 } |
|
72 |
|
73 // Close removes all watches and closes the events channel. |
|
74 func (w *Watcher) Close() error { |
|
75 if w.isClosed() { |
|
76 return nil |
|
77 } |
|
78 |
|
79 // Send 'close' signal to goroutine, and set the Watcher to closed. |
|
80 close(w.done) |
|
81 |
|
82 // Wake up goroutine |
|
83 w.poller.wake() |
|
84 |
|
85 // Wait for goroutine to close |
|
86 <-w.doneResp |
|
87 |
|
88 return nil |
|
89 } |
|
90 |
|
91 // Add starts watching the named file or directory (non-recursively). |
|
92 func (w *Watcher) Add(name string) error { |
|
93 name = filepath.Clean(name) |
|
94 if w.isClosed() { |
|
95 return errors.New("inotify instance already closed") |
|
96 } |
|
97 |
|
98 const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM | |
|
99 unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY | |
|
100 unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF |
|
101 |
|
102 var flags uint32 = agnosticEvents |
|
103 |
|
104 w.mu.Lock() |
|
105 defer w.mu.Unlock() |
|
106 watchEntry := w.watches[name] |
|
107 if watchEntry != nil { |
|
108 flags |= watchEntry.flags | unix.IN_MASK_ADD |
|
109 } |
|
110 wd, errno := unix.InotifyAddWatch(w.fd, name, flags) |
|
111 if wd == -1 { |
|
112 return errno |
|
113 } |
|
114 |
|
115 if watchEntry == nil { |
|
116 w.watches[name] = &watch{wd: uint32(wd), flags: flags} |
|
117 w.paths[wd] = name |
|
118 } else { |
|
119 watchEntry.wd = uint32(wd) |
|
120 watchEntry.flags = flags |
|
121 } |
|
122 |
|
123 return nil |
|
124 } |
|
125 |
|
126 // Remove stops watching the named file or directory (non-recursively). |
|
127 func (w *Watcher) Remove(name string) error { |
|
128 name = filepath.Clean(name) |
|
129 |
|
130 // Fetch the watch. |
|
131 w.mu.Lock() |
|
132 defer w.mu.Unlock() |
|
133 watch, ok := w.watches[name] |
|
134 |
|
135 // Remove it from inotify. |
|
136 if !ok { |
|
137 return fmt.Errorf("can't remove non-existent inotify watch for: %s", name) |
|
138 } |
|
139 |
|
140 // We successfully removed the watch if InotifyRmWatch doesn't return an |
|
141 // error, we need to clean up our internal state to ensure it matches |
|
142 // inotify's kernel state. |
|
143 delete(w.paths, int(watch.wd)) |
|
144 delete(w.watches, name) |
|
145 |
|
146 // inotify_rm_watch will return EINVAL if the file has been deleted; |
|
147 // the inotify will already have been removed. |
|
148 // watches and pathes are deleted in ignoreLinux() implicitly and asynchronously |
|
149 // by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE |
|
150 // so that EINVAL means that the wd is being rm_watch()ed or its file removed |
|
151 // by another thread and we have not received IN_IGNORE event. |
|
152 success, errno := unix.InotifyRmWatch(w.fd, watch.wd) |
|
153 if success == -1 { |
|
154 // TODO: Perhaps it's not helpful to return an error here in every case. |
|
155 // the only two possible errors are: |
|
156 // EBADF, which happens when w.fd is not a valid file descriptor of any kind. |
|
157 // EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor. |
|
158 // Watch descriptors are invalidated when they are removed explicitly or implicitly; |
|
159 // explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted. |
|
160 return errno |
|
161 } |
|
162 |
|
163 return nil |
|
164 } |
|
165 |
|
166 // WatchList returns the directories and files that are being monitered. |
|
167 func (w *Watcher) WatchList() []string { |
|
168 w.mu.Lock() |
|
169 defer w.mu.Unlock() |
|
170 |
|
171 entries := make([]string, 0, len(w.watches)) |
|
172 for pathname := range w.watches { |
|
173 entries = append(entries, pathname) |
|
174 } |
|
175 |
|
176 return entries |
|
177 } |
|
178 |
|
179 type watch struct { |
|
180 wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) |
|
181 flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) |
|
182 } |
|
183 |
|
184 // readEvents reads from the inotify file descriptor, converts the |
|
185 // received events into Event objects and sends them via the Events channel |
|
186 func (w *Watcher) readEvents() { |
|
187 var ( |
|
188 buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events |
|
189 n int // Number of bytes read with read() |
|
190 errno error // Syscall errno |
|
191 ok bool // For poller.wait |
|
192 ) |
|
193 |
|
194 defer close(w.doneResp) |
|
195 defer close(w.Errors) |
|
196 defer close(w.Events) |
|
197 defer unix.Close(w.fd) |
|
198 defer w.poller.close() |
|
199 |
|
200 for { |
|
201 // See if we have been closed. |
|
202 if w.isClosed() { |
|
203 return |
|
204 } |
|
205 |
|
206 ok, errno = w.poller.wait() |
|
207 if errno != nil { |
|
208 select { |
|
209 case w.Errors <- errno: |
|
210 case <-w.done: |
|
211 return |
|
212 } |
|
213 continue |
|
214 } |
|
215 |
|
216 if !ok { |
|
217 continue |
|
218 } |
|
219 |
|
220 n, errno = unix.Read(w.fd, buf[:]) |
|
221 // If a signal interrupted execution, see if we've been asked to close, and try again. |
|
222 // http://man7.org/linux/man-pages/man7/signal.7.html : |
|
223 // "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable" |
|
224 if errno == unix.EINTR { |
|
225 continue |
|
226 } |
|
227 |
|
228 // unix.Read might have been woken up by Close. If so, we're done. |
|
229 if w.isClosed() { |
|
230 return |
|
231 } |
|
232 |
|
233 if n < unix.SizeofInotifyEvent { |
|
234 var err error |
|
235 if n == 0 { |
|
236 // If EOF is received. This should really never happen. |
|
237 err = io.EOF |
|
238 } else if n < 0 { |
|
239 // If an error occurred while reading. |
|
240 err = errno |
|
241 } else { |
|
242 // Read was too short. |
|
243 err = errors.New("notify: short read in readEvents()") |
|
244 } |
|
245 select { |
|
246 case w.Errors <- err: |
|
247 case <-w.done: |
|
248 return |
|
249 } |
|
250 continue |
|
251 } |
|
252 |
|
253 var offset uint32 |
|
254 // We don't know how many events we just read into the buffer |
|
255 // While the offset points to at least one whole event... |
|
256 for offset <= uint32(n-unix.SizeofInotifyEvent) { |
|
257 // Point "raw" to the event in the buffer |
|
258 raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) |
|
259 |
|
260 mask := uint32(raw.Mask) |
|
261 nameLen := uint32(raw.Len) |
|
262 |
|
263 if mask&unix.IN_Q_OVERFLOW != 0 { |
|
264 select { |
|
265 case w.Errors <- ErrEventOverflow: |
|
266 case <-w.done: |
|
267 return |
|
268 } |
|
269 } |
|
270 |
|
271 // If the event happened to the watched directory or the watched file, the kernel |
|
272 // doesn't append the filename to the event, but we would like to always fill the |
|
273 // the "Name" field with a valid filename. We retrieve the path of the watch from |
|
274 // the "paths" map. |
|
275 w.mu.Lock() |
|
276 name, ok := w.paths[int(raw.Wd)] |
|
277 // IN_DELETE_SELF occurs when the file/directory being watched is removed. |
|
278 // This is a sign to clean up the maps, otherwise we are no longer in sync |
|
279 // with the inotify kernel state which has already deleted the watch |
|
280 // automatically. |
|
281 if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF { |
|
282 delete(w.paths, int(raw.Wd)) |
|
283 delete(w.watches, name) |
|
284 } |
|
285 w.mu.Unlock() |
|
286 |
|
287 if nameLen > 0 { |
|
288 // Point "bytes" at the first byte of the filename |
|
289 bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen] |
|
290 // The filename is padded with NULL bytes. TrimRight() gets rid of those. |
|
291 name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") |
|
292 } |
|
293 |
|
294 event := newEvent(name, mask) |
|
295 |
|
296 // Send the events that are not ignored on the events channel |
|
297 if !event.ignoreLinux(mask) { |
|
298 select { |
|
299 case w.Events <- event: |
|
300 case <-w.done: |
|
301 return |
|
302 } |
|
303 } |
|
304 |
|
305 // Move to the next event in the buffer |
|
306 offset += unix.SizeofInotifyEvent + nameLen |
|
307 } |
|
308 } |
|
309 } |
|
310 |
|
311 // Certain types of events can be "ignored" and not sent over the Events |
|
312 // channel. Such as events marked ignore by the kernel, or MODIFY events |
|
313 // against files that do not exist. |
|
314 func (e *Event) ignoreLinux(mask uint32) bool { |
|
315 // Ignore anything the inotify API says to ignore |
|
316 if mask&unix.IN_IGNORED == unix.IN_IGNORED { |
|
317 return true |
|
318 } |
|
319 |
|
320 // If the event is not a DELETE or RENAME, the file must exist. |
|
321 // Otherwise the event is ignored. |
|
322 // *Note*: this was put in place because it was seen that a MODIFY |
|
323 // event was sent after the DELETE. This ignores that MODIFY and |
|
324 // assumes a DELETE will come or has come if the file doesn't exist. |
|
325 if !(e.Op&Remove == Remove || e.Op&Rename == Rename) { |
|
326 _, statErr := os.Lstat(e.Name) |
|
327 return os.IsNotExist(statErr) |
|
328 } |
|
329 return false |
|
330 } |
|
331 |
|
332 // newEvent returns an platform-independent Event based on an inotify mask. |
|
333 func newEvent(name string, mask uint32) Event { |
|
334 e := Event{Name: name} |
|
335 if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO { |
|
336 e.Op |= Create |
|
337 } |
|
338 if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE { |
|
339 e.Op |= Remove |
|
340 } |
|
341 if mask&unix.IN_MODIFY == unix.IN_MODIFY { |
|
342 e.Op |= Write |
|
343 } |
|
344 if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM { |
|
345 e.Op |= Rename |
|
346 } |
|
347 if mask&unix.IN_ATTRIB == unix.IN_ATTRIB { |
|
348 e.Op |= Chmod |
|
349 } |
|
350 return e |
|
351 } |
|