|
1 //go:build windows |
|
2 // +build windows |
|
3 |
|
4 package fsnotify |
|
5 |
|
6 import ( |
|
7 "errors" |
|
8 "fmt" |
|
9 "os" |
|
10 "path/filepath" |
|
11 "reflect" |
|
12 "runtime" |
|
13 "strings" |
|
14 "sync" |
|
15 "unsafe" |
|
16 |
|
17 "golang.org/x/sys/windows" |
|
18 ) |
|
19 |
|
20 // Watcher watches a set of paths, delivering events on a channel. |
|
21 // |
|
22 // A watcher should not be copied (e.g. pass it by pointer, rather than by |
|
23 // value). |
|
24 // |
|
25 // # Linux notes |
|
26 // |
|
27 // When a file is removed a Remove event won't be emitted until all file |
|
28 // descriptors are closed, and deletes will always emit a Chmod. For example: |
|
29 // |
|
30 // fp := os.Open("file") |
|
31 // os.Remove("file") // Triggers Chmod |
|
32 // fp.Close() // Triggers Remove |
|
33 // |
|
34 // This is the event that inotify sends, so not much can be changed about this. |
|
35 // |
|
36 // The fs.inotify.max_user_watches sysctl variable specifies the upper limit |
|
37 // for the number of watches per user, and fs.inotify.max_user_instances |
|
38 // specifies the maximum number of inotify instances per user. Every Watcher you |
|
39 // create is an "instance", and every path you add is a "watch". |
|
40 // |
|
41 // These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and |
|
42 // /proc/sys/fs/inotify/max_user_instances |
|
43 // |
|
44 // To increase them you can use sysctl or write the value to the /proc file: |
|
45 // |
|
46 // # Default values on Linux 5.18 |
|
47 // sysctl fs.inotify.max_user_watches=124983 |
|
48 // sysctl fs.inotify.max_user_instances=128 |
|
49 // |
|
50 // To make the changes persist on reboot edit /etc/sysctl.conf or |
|
51 // /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check |
|
52 // your distro's documentation): |
|
53 // |
|
54 // fs.inotify.max_user_watches=124983 |
|
55 // fs.inotify.max_user_instances=128 |
|
56 // |
|
57 // Reaching the limit will result in a "no space left on device" or "too many open |
|
58 // files" error. |
|
59 // |
|
60 // # kqueue notes (macOS, BSD) |
|
61 // |
|
62 // kqueue requires opening a file descriptor for every file that's being watched; |
|
63 // so if you're watching a directory with five files then that's six file |
|
64 // descriptors. You will run in to your system's "max open files" limit faster on |
|
65 // these platforms. |
|
66 // |
|
67 // The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to |
|
68 // control the maximum number of open files, as well as /etc/login.conf on BSD |
|
69 // systems. |
|
70 // |
|
71 // # macOS notes |
|
72 // |
|
73 // Spotlight indexing on macOS can result in multiple events (see [#15]). A |
|
74 // temporary workaround is to add your folder(s) to the "Spotlight Privacy |
|
75 // Settings" until we have a native FSEvents implementation (see [#11]). |
|
76 // |
|
77 // [#11]: https://github.com/fsnotify/fsnotify/issues/11 |
|
78 // [#15]: https://github.com/fsnotify/fsnotify/issues/15 |
|
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, so you |
|
106 // probably want to wait until you've stopped receiving |
|
107 // them (see the dedup example in cmd/fsnotify). |
|
108 // |
|
109 // fsnotify.Chmod Attributes were changed. On Linux this is also sent |
|
110 // when a file is removed (or more accurately, when a |
|
111 // link to an inode is removed). On kqueue it's sent |
|
112 // and on kqueue when a file is truncated. On Windows |
|
113 // it's never sent. |
|
114 Events chan Event |
|
115 |
|
116 // Errors sends any errors. |
|
117 Errors chan error |
|
118 |
|
119 port windows.Handle // Handle to completion port |
|
120 input chan *input // Inputs to the reader are sent on this channel |
|
121 quit chan chan<- error |
|
122 |
|
123 mu sync.Mutex // Protects access to watches, isClosed |
|
124 watches watchMap // Map of watches (key: i-number) |
|
125 isClosed bool // Set to true when Close() is first called |
|
126 } |
|
127 |
|
128 // NewWatcher creates a new Watcher. |
|
129 func NewWatcher() (*Watcher, error) { |
|
130 port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0) |
|
131 if err != nil { |
|
132 return nil, os.NewSyscallError("CreateIoCompletionPort", err) |
|
133 } |
|
134 w := &Watcher{ |
|
135 port: port, |
|
136 watches: make(watchMap), |
|
137 input: make(chan *input, 1), |
|
138 Events: make(chan Event, 50), |
|
139 Errors: make(chan error), |
|
140 quit: make(chan chan<- error, 1), |
|
141 } |
|
142 go w.readEvents() |
|
143 return w, nil |
|
144 } |
|
145 |
|
146 func (w *Watcher) sendEvent(name string, mask uint64) bool { |
|
147 if mask == 0 { |
|
148 return false |
|
149 } |
|
150 |
|
151 event := w.newEvent(name, uint32(mask)) |
|
152 select { |
|
153 case ch := <-w.quit: |
|
154 w.quit <- ch |
|
155 case w.Events <- event: |
|
156 } |
|
157 return true |
|
158 } |
|
159 |
|
160 // Returns true if the error was sent, or false if watcher is closed. |
|
161 func (w *Watcher) sendError(err error) bool { |
|
162 select { |
|
163 case w.Errors <- err: |
|
164 return true |
|
165 case <-w.quit: |
|
166 } |
|
167 return false |
|
168 } |
|
169 |
|
170 // Close removes all watches and closes the events channel. |
|
171 func (w *Watcher) Close() error { |
|
172 w.mu.Lock() |
|
173 if w.isClosed { |
|
174 w.mu.Unlock() |
|
175 return nil |
|
176 } |
|
177 w.isClosed = true |
|
178 w.mu.Unlock() |
|
179 |
|
180 // Send "quit" message to the reader goroutine |
|
181 ch := make(chan error) |
|
182 w.quit <- ch |
|
183 if err := w.wakeupReader(); err != nil { |
|
184 return err |
|
185 } |
|
186 return <-ch |
|
187 } |
|
188 |
|
189 // Add starts monitoring the path for changes. |
|
190 // |
|
191 // A path can only be watched once; attempting to watch it more than once will |
|
192 // return an error. Paths that do not yet exist on the filesystem cannot be |
|
193 // added. A watch will be automatically removed if the path is deleted. |
|
194 // |
|
195 // A path will remain watched if it gets renamed to somewhere else on the same |
|
196 // filesystem, but the monitor will get removed if the path gets deleted and |
|
197 // re-created, or if it's moved to a different filesystem. |
|
198 // |
|
199 // Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special |
|
200 // filesystems (/proc, /sys, etc.) generally don't work. |
|
201 // |
|
202 // # Watching directories |
|
203 // |
|
204 // All files in a directory are monitored, including new files that are created |
|
205 // after the watcher is started. Subdirectories are not watched (i.e. it's |
|
206 // non-recursive). |
|
207 // |
|
208 // # Watching files |
|
209 // |
|
210 // Watching individual files (rather than directories) is generally not |
|
211 // recommended as many tools update files atomically. Instead of "just" writing |
|
212 // to the file a temporary file will be written to first, and if successful the |
|
213 // temporary file is moved to to destination removing the original, or some |
|
214 // variant thereof. The watcher on the original file is now lost, as it no |
|
215 // longer exists. |
|
216 // |
|
217 // Instead, watch the parent directory and use Event.Name to filter out files |
|
218 // you're not interested in. There is an example of this in [cmd/fsnotify/file.go]. |
|
219 func (w *Watcher) Add(name string) error { |
|
220 w.mu.Lock() |
|
221 if w.isClosed { |
|
222 w.mu.Unlock() |
|
223 return errors.New("watcher already closed") |
|
224 } |
|
225 w.mu.Unlock() |
|
226 |
|
227 in := &input{ |
|
228 op: opAddWatch, |
|
229 path: filepath.Clean(name), |
|
230 flags: sysFSALLEVENTS, |
|
231 reply: make(chan error), |
|
232 } |
|
233 w.input <- in |
|
234 if err := w.wakeupReader(); err != nil { |
|
235 return err |
|
236 } |
|
237 return <-in.reply |
|
238 } |
|
239 |
|
240 // Remove stops monitoring the path for changes. |
|
241 // |
|
242 // Directories are always removed non-recursively. For example, if you added |
|
243 // /tmp/dir and /tmp/dir/subdir then you will need to remove both. |
|
244 // |
|
245 // Removing a path that has not yet been added returns [ErrNonExistentWatch]. |
|
246 func (w *Watcher) Remove(name string) error { |
|
247 in := &input{ |
|
248 op: opRemoveWatch, |
|
249 path: filepath.Clean(name), |
|
250 reply: make(chan error), |
|
251 } |
|
252 w.input <- in |
|
253 if err := w.wakeupReader(); err != nil { |
|
254 return err |
|
255 } |
|
256 return <-in.reply |
|
257 } |
|
258 |
|
259 // WatchList returns all paths added with [Add] (and are not yet removed). |
|
260 func (w *Watcher) WatchList() []string { |
|
261 w.mu.Lock() |
|
262 defer w.mu.Unlock() |
|
263 |
|
264 entries := make([]string, 0, len(w.watches)) |
|
265 for _, entry := range w.watches { |
|
266 for _, watchEntry := range entry { |
|
267 entries = append(entries, watchEntry.path) |
|
268 } |
|
269 } |
|
270 |
|
271 return entries |
|
272 } |
|
273 |
|
274 // These options are from the old golang.org/x/exp/winfsnotify, where you could |
|
275 // add various options to the watch. This has long since been removed. |
|
276 // |
|
277 // The "sys" in the name is misleading as they're not part of any "system". |
|
278 // |
|
279 // This should all be removed at some point, and just use windows.FILE_NOTIFY_* |
|
280 const ( |
|
281 sysFSALLEVENTS = 0xfff |
|
282 sysFSATTRIB = 0x4 |
|
283 sysFSCREATE = 0x100 |
|
284 sysFSDELETE = 0x200 |
|
285 sysFSDELETESELF = 0x400 |
|
286 sysFSMODIFY = 0x2 |
|
287 sysFSMOVE = 0xc0 |
|
288 sysFSMOVEDFROM = 0x40 |
|
289 sysFSMOVEDTO = 0x80 |
|
290 sysFSMOVESELF = 0x800 |
|
291 sysFSIGNORED = 0x8000 |
|
292 ) |
|
293 |
|
294 func (w *Watcher) newEvent(name string, mask uint32) Event { |
|
295 e := Event{Name: name} |
|
296 if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO { |
|
297 e.Op |= Create |
|
298 } |
|
299 if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF { |
|
300 e.Op |= Remove |
|
301 } |
|
302 if mask&sysFSMODIFY == sysFSMODIFY { |
|
303 e.Op |= Write |
|
304 } |
|
305 if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM { |
|
306 e.Op |= Rename |
|
307 } |
|
308 if mask&sysFSATTRIB == sysFSATTRIB { |
|
309 e.Op |= Chmod |
|
310 } |
|
311 return e |
|
312 } |
|
313 |
|
314 const ( |
|
315 opAddWatch = iota |
|
316 opRemoveWatch |
|
317 ) |
|
318 |
|
319 const ( |
|
320 provisional uint64 = 1 << (32 + iota) |
|
321 ) |
|
322 |
|
323 type input struct { |
|
324 op int |
|
325 path string |
|
326 flags uint32 |
|
327 reply chan error |
|
328 } |
|
329 |
|
330 type inode struct { |
|
331 handle windows.Handle |
|
332 volume uint32 |
|
333 index uint64 |
|
334 } |
|
335 |
|
336 type watch struct { |
|
337 ov windows.Overlapped |
|
338 ino *inode // i-number |
|
339 path string // Directory path |
|
340 mask uint64 // Directory itself is being watched with these notify flags |
|
341 names map[string]uint64 // Map of names being watched and their notify flags |
|
342 rename string // Remembers the old name while renaming a file |
|
343 buf [65536]byte // 64K buffer |
|
344 } |
|
345 |
|
346 type ( |
|
347 indexMap map[uint64]*watch |
|
348 watchMap map[uint32]indexMap |
|
349 ) |
|
350 |
|
351 func (w *Watcher) wakeupReader() error { |
|
352 err := windows.PostQueuedCompletionStatus(w.port, 0, 0, nil) |
|
353 if err != nil { |
|
354 return os.NewSyscallError("PostQueuedCompletionStatus", err) |
|
355 } |
|
356 return nil |
|
357 } |
|
358 |
|
359 func (w *Watcher) getDir(pathname string) (dir string, err error) { |
|
360 attr, err := windows.GetFileAttributes(windows.StringToUTF16Ptr(pathname)) |
|
361 if err != nil { |
|
362 return "", os.NewSyscallError("GetFileAttributes", err) |
|
363 } |
|
364 if attr&windows.FILE_ATTRIBUTE_DIRECTORY != 0 { |
|
365 dir = pathname |
|
366 } else { |
|
367 dir, _ = filepath.Split(pathname) |
|
368 dir = filepath.Clean(dir) |
|
369 } |
|
370 return |
|
371 } |
|
372 |
|
373 func (w *Watcher) getIno(path string) (ino *inode, err error) { |
|
374 h, err := windows.CreateFile(windows.StringToUTF16Ptr(path), |
|
375 windows.FILE_LIST_DIRECTORY, |
|
376 windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, |
|
377 nil, windows.OPEN_EXISTING, |
|
378 windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OVERLAPPED, 0) |
|
379 if err != nil { |
|
380 return nil, os.NewSyscallError("CreateFile", err) |
|
381 } |
|
382 |
|
383 var fi windows.ByHandleFileInformation |
|
384 err = windows.GetFileInformationByHandle(h, &fi) |
|
385 if err != nil { |
|
386 windows.CloseHandle(h) |
|
387 return nil, os.NewSyscallError("GetFileInformationByHandle", err) |
|
388 } |
|
389 ino = &inode{ |
|
390 handle: h, |
|
391 volume: fi.VolumeSerialNumber, |
|
392 index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow), |
|
393 } |
|
394 return ino, nil |
|
395 } |
|
396 |
|
397 // Must run within the I/O thread. |
|
398 func (m watchMap) get(ino *inode) *watch { |
|
399 if i := m[ino.volume]; i != nil { |
|
400 return i[ino.index] |
|
401 } |
|
402 return nil |
|
403 } |
|
404 |
|
405 // Must run within the I/O thread. |
|
406 func (m watchMap) set(ino *inode, watch *watch) { |
|
407 i := m[ino.volume] |
|
408 if i == nil { |
|
409 i = make(indexMap) |
|
410 m[ino.volume] = i |
|
411 } |
|
412 i[ino.index] = watch |
|
413 } |
|
414 |
|
415 // Must run within the I/O thread. |
|
416 func (w *Watcher) addWatch(pathname string, flags uint64) error { |
|
417 dir, err := w.getDir(pathname) |
|
418 if err != nil { |
|
419 return err |
|
420 } |
|
421 |
|
422 ino, err := w.getIno(dir) |
|
423 if err != nil { |
|
424 return err |
|
425 } |
|
426 w.mu.Lock() |
|
427 watchEntry := w.watches.get(ino) |
|
428 w.mu.Unlock() |
|
429 if watchEntry == nil { |
|
430 _, err := windows.CreateIoCompletionPort(ino.handle, w.port, 0, 0) |
|
431 if err != nil { |
|
432 windows.CloseHandle(ino.handle) |
|
433 return os.NewSyscallError("CreateIoCompletionPort", err) |
|
434 } |
|
435 watchEntry = &watch{ |
|
436 ino: ino, |
|
437 path: dir, |
|
438 names: make(map[string]uint64), |
|
439 } |
|
440 w.mu.Lock() |
|
441 w.watches.set(ino, watchEntry) |
|
442 w.mu.Unlock() |
|
443 flags |= provisional |
|
444 } else { |
|
445 windows.CloseHandle(ino.handle) |
|
446 } |
|
447 if pathname == dir { |
|
448 watchEntry.mask |= flags |
|
449 } else { |
|
450 watchEntry.names[filepath.Base(pathname)] |= flags |
|
451 } |
|
452 |
|
453 err = w.startRead(watchEntry) |
|
454 if err != nil { |
|
455 return err |
|
456 } |
|
457 |
|
458 if pathname == dir { |
|
459 watchEntry.mask &= ^provisional |
|
460 } else { |
|
461 watchEntry.names[filepath.Base(pathname)] &= ^provisional |
|
462 } |
|
463 return nil |
|
464 } |
|
465 |
|
466 // Must run within the I/O thread. |
|
467 func (w *Watcher) remWatch(pathname string) error { |
|
468 dir, err := w.getDir(pathname) |
|
469 if err != nil { |
|
470 return err |
|
471 } |
|
472 ino, err := w.getIno(dir) |
|
473 if err != nil { |
|
474 return err |
|
475 } |
|
476 |
|
477 w.mu.Lock() |
|
478 watch := w.watches.get(ino) |
|
479 w.mu.Unlock() |
|
480 |
|
481 err = windows.CloseHandle(ino.handle) |
|
482 if err != nil { |
|
483 w.sendError(os.NewSyscallError("CloseHandle", err)) |
|
484 } |
|
485 if watch == nil { |
|
486 return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname) |
|
487 } |
|
488 if pathname == dir { |
|
489 w.sendEvent(watch.path, watch.mask&sysFSIGNORED) |
|
490 watch.mask = 0 |
|
491 } else { |
|
492 name := filepath.Base(pathname) |
|
493 w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED) |
|
494 delete(watch.names, name) |
|
495 } |
|
496 |
|
497 return w.startRead(watch) |
|
498 } |
|
499 |
|
500 // Must run within the I/O thread. |
|
501 func (w *Watcher) deleteWatch(watch *watch) { |
|
502 for name, mask := range watch.names { |
|
503 if mask&provisional == 0 { |
|
504 w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED) |
|
505 } |
|
506 delete(watch.names, name) |
|
507 } |
|
508 if watch.mask != 0 { |
|
509 if watch.mask&provisional == 0 { |
|
510 w.sendEvent(watch.path, watch.mask&sysFSIGNORED) |
|
511 } |
|
512 watch.mask = 0 |
|
513 } |
|
514 } |
|
515 |
|
516 // Must run within the I/O thread. |
|
517 func (w *Watcher) startRead(watch *watch) error { |
|
518 err := windows.CancelIo(watch.ino.handle) |
|
519 if err != nil { |
|
520 w.sendError(os.NewSyscallError("CancelIo", err)) |
|
521 w.deleteWatch(watch) |
|
522 } |
|
523 mask := w.toWindowsFlags(watch.mask) |
|
524 for _, m := range watch.names { |
|
525 mask |= w.toWindowsFlags(m) |
|
526 } |
|
527 if mask == 0 { |
|
528 err := windows.CloseHandle(watch.ino.handle) |
|
529 if err != nil { |
|
530 w.sendError(os.NewSyscallError("CloseHandle", err)) |
|
531 } |
|
532 w.mu.Lock() |
|
533 delete(w.watches[watch.ino.volume], watch.ino.index) |
|
534 w.mu.Unlock() |
|
535 return nil |
|
536 } |
|
537 |
|
538 rdErr := windows.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0], |
|
539 uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0) |
|
540 if rdErr != nil { |
|
541 err := os.NewSyscallError("ReadDirectoryChanges", rdErr) |
|
542 if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 { |
|
543 // Watched directory was probably removed |
|
544 w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) |
|
545 err = nil |
|
546 } |
|
547 w.deleteWatch(watch) |
|
548 w.startRead(watch) |
|
549 return err |
|
550 } |
|
551 return nil |
|
552 } |
|
553 |
|
554 // readEvents reads from the I/O completion port, converts the |
|
555 // received events into Event objects and sends them via the Events channel. |
|
556 // Entry point to the I/O thread. |
|
557 func (w *Watcher) readEvents() { |
|
558 var ( |
|
559 n uint32 |
|
560 key uintptr |
|
561 ov *windows.Overlapped |
|
562 ) |
|
563 runtime.LockOSThread() |
|
564 |
|
565 for { |
|
566 qErr := windows.GetQueuedCompletionStatus(w.port, &n, &key, &ov, windows.INFINITE) |
|
567 // This error is handled after the watch == nil check below. NOTE: this |
|
568 // seems odd, note sure if it's correct. |
|
569 |
|
570 watch := (*watch)(unsafe.Pointer(ov)) |
|
571 if watch == nil { |
|
572 select { |
|
573 case ch := <-w.quit: |
|
574 w.mu.Lock() |
|
575 var indexes []indexMap |
|
576 for _, index := range w.watches { |
|
577 indexes = append(indexes, index) |
|
578 } |
|
579 w.mu.Unlock() |
|
580 for _, index := range indexes { |
|
581 for _, watch := range index { |
|
582 w.deleteWatch(watch) |
|
583 w.startRead(watch) |
|
584 } |
|
585 } |
|
586 |
|
587 err := windows.CloseHandle(w.port) |
|
588 if err != nil { |
|
589 err = os.NewSyscallError("CloseHandle", err) |
|
590 } |
|
591 close(w.Events) |
|
592 close(w.Errors) |
|
593 ch <- err |
|
594 return |
|
595 case in := <-w.input: |
|
596 switch in.op { |
|
597 case opAddWatch: |
|
598 in.reply <- w.addWatch(in.path, uint64(in.flags)) |
|
599 case opRemoveWatch: |
|
600 in.reply <- w.remWatch(in.path) |
|
601 } |
|
602 default: |
|
603 } |
|
604 continue |
|
605 } |
|
606 |
|
607 switch qErr { |
|
608 case windows.ERROR_MORE_DATA: |
|
609 if watch == nil { |
|
610 w.sendError(errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")) |
|
611 } else { |
|
612 // The i/o succeeded but the buffer is full. |
|
613 // In theory we should be building up a full packet. |
|
614 // In practice we can get away with just carrying on. |
|
615 n = uint32(unsafe.Sizeof(watch.buf)) |
|
616 } |
|
617 case windows.ERROR_ACCESS_DENIED: |
|
618 // Watched directory was probably removed |
|
619 w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) |
|
620 w.deleteWatch(watch) |
|
621 w.startRead(watch) |
|
622 continue |
|
623 case windows.ERROR_OPERATION_ABORTED: |
|
624 // CancelIo was called on this handle |
|
625 continue |
|
626 default: |
|
627 w.sendError(os.NewSyscallError("GetQueuedCompletionPort", qErr)) |
|
628 continue |
|
629 case nil: |
|
630 } |
|
631 |
|
632 var offset uint32 |
|
633 for { |
|
634 if n == 0 { |
|
635 w.sendError(errors.New("short read in readEvents()")) |
|
636 break |
|
637 } |
|
638 |
|
639 // Point "raw" to the event in the buffer |
|
640 raw := (*windows.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset])) |
|
641 |
|
642 // Create a buf that is the size of the path name |
|
643 size := int(raw.FileNameLength / 2) |
|
644 var buf []uint16 |
|
645 // TODO: Use unsafe.Slice in Go 1.17; https://stackoverflow.com/questions/51187973 |
|
646 sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) |
|
647 sh.Data = uintptr(unsafe.Pointer(&raw.FileName)) |
|
648 sh.Len = size |
|
649 sh.Cap = size |
|
650 name := windows.UTF16ToString(buf) |
|
651 fullname := filepath.Join(watch.path, name) |
|
652 |
|
653 var mask uint64 |
|
654 switch raw.Action { |
|
655 case windows.FILE_ACTION_REMOVED: |
|
656 mask = sysFSDELETESELF |
|
657 case windows.FILE_ACTION_MODIFIED: |
|
658 mask = sysFSMODIFY |
|
659 case windows.FILE_ACTION_RENAMED_OLD_NAME: |
|
660 watch.rename = name |
|
661 case windows.FILE_ACTION_RENAMED_NEW_NAME: |
|
662 // Update saved path of all sub-watches. |
|
663 old := filepath.Join(watch.path, watch.rename) |
|
664 w.mu.Lock() |
|
665 for _, watchMap := range w.watches { |
|
666 for _, ww := range watchMap { |
|
667 if strings.HasPrefix(ww.path, old) { |
|
668 ww.path = filepath.Join(fullname, strings.TrimPrefix(ww.path, old)) |
|
669 } |
|
670 } |
|
671 } |
|
672 w.mu.Unlock() |
|
673 |
|
674 if watch.names[watch.rename] != 0 { |
|
675 watch.names[name] |= watch.names[watch.rename] |
|
676 delete(watch.names, watch.rename) |
|
677 mask = sysFSMOVESELF |
|
678 } |
|
679 } |
|
680 |
|
681 sendNameEvent := func() { |
|
682 w.sendEvent(fullname, watch.names[name]&mask) |
|
683 } |
|
684 if raw.Action != windows.FILE_ACTION_RENAMED_NEW_NAME { |
|
685 sendNameEvent() |
|
686 } |
|
687 if raw.Action == windows.FILE_ACTION_REMOVED { |
|
688 w.sendEvent(fullname, watch.names[name]&sysFSIGNORED) |
|
689 delete(watch.names, name) |
|
690 } |
|
691 |
|
692 w.sendEvent(fullname, watch.mask&w.toFSnotifyFlags(raw.Action)) |
|
693 if raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME { |
|
694 fullname = filepath.Join(watch.path, watch.rename) |
|
695 sendNameEvent() |
|
696 } |
|
697 |
|
698 // Move to the next event in the buffer |
|
699 if raw.NextEntryOffset == 0 { |
|
700 break |
|
701 } |
|
702 offset += raw.NextEntryOffset |
|
703 |
|
704 // Error! |
|
705 if offset >= n { |
|
706 w.sendError(errors.New( |
|
707 "Windows system assumed buffer larger than it is, events have likely been missed.")) |
|
708 break |
|
709 } |
|
710 } |
|
711 |
|
712 if err := w.startRead(watch); err != nil { |
|
713 w.sendError(err) |
|
714 } |
|
715 } |
|
716 } |
|
717 |
|
718 func (w *Watcher) toWindowsFlags(mask uint64) uint32 { |
|
719 var m uint32 |
|
720 if mask&sysFSMODIFY != 0 { |
|
721 m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE |
|
722 } |
|
723 if mask&sysFSATTRIB != 0 { |
|
724 m |= windows.FILE_NOTIFY_CHANGE_ATTRIBUTES |
|
725 } |
|
726 if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 { |
|
727 m |= windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
|
728 } |
|
729 return m |
|
730 } |
|
731 |
|
732 func (w *Watcher) toFSnotifyFlags(action uint32) uint64 { |
|
733 switch action { |
|
734 case windows.FILE_ACTION_ADDED: |
|
735 return sysFSCREATE |
|
736 case windows.FILE_ACTION_REMOVED: |
|
737 return sysFSDELETE |
|
738 case windows.FILE_ACTION_MODIFIED: |
|
739 return sysFSMODIFY |
|
740 case windows.FILE_ACTION_RENAMED_OLD_NAME: |
|
741 return sysFSMOVEDFROM |
|
742 case windows.FILE_ACTION_RENAMED_NEW_NAME: |
|
743 return sysFSMOVEDTO |
|
744 } |
|
745 return 0 |
|
746 } |