1 // Copyright 2011 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 windows |
|
6 // +build windows |
|
7 |
|
8 package fsnotify |
|
9 |
|
10 import ( |
|
11 "errors" |
|
12 "fmt" |
|
13 "os" |
|
14 "path/filepath" |
|
15 "reflect" |
|
16 "runtime" |
|
17 "sync" |
|
18 "syscall" |
|
19 "unsafe" |
|
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 isClosed bool // Set to true when Close() is first called |
|
27 mu sync.Mutex // Map access |
|
28 port syscall.Handle // Handle to completion port |
|
29 watches watchMap // Map of watches (key: i-number) |
|
30 input chan *input // Inputs to the reader are sent on this channel |
|
31 quit chan chan<- error |
|
32 } |
|
33 |
|
34 // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. |
|
35 func NewWatcher() (*Watcher, error) { |
|
36 port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0) |
|
37 if e != nil { |
|
38 return nil, os.NewSyscallError("CreateIoCompletionPort", e) |
|
39 } |
|
40 w := &Watcher{ |
|
41 port: port, |
|
42 watches: make(watchMap), |
|
43 input: make(chan *input, 1), |
|
44 Events: make(chan Event, 50), |
|
45 Errors: make(chan error), |
|
46 quit: make(chan chan<- error, 1), |
|
47 } |
|
48 go w.readEvents() |
|
49 return w, nil |
|
50 } |
|
51 |
|
52 // Close removes all watches and closes the events channel. |
|
53 func (w *Watcher) Close() error { |
|
54 if w.isClosed { |
|
55 return nil |
|
56 } |
|
57 w.isClosed = true |
|
58 |
|
59 // Send "quit" message to the reader goroutine |
|
60 ch := make(chan error) |
|
61 w.quit <- ch |
|
62 if err := w.wakeupReader(); err != nil { |
|
63 return err |
|
64 } |
|
65 return <-ch |
|
66 } |
|
67 |
|
68 // Add starts watching the named file or directory (non-recursively). |
|
69 func (w *Watcher) Add(name string) error { |
|
70 if w.isClosed { |
|
71 return errors.New("watcher already closed") |
|
72 } |
|
73 in := &input{ |
|
74 op: opAddWatch, |
|
75 path: filepath.Clean(name), |
|
76 flags: sysFSALLEVENTS, |
|
77 reply: make(chan error), |
|
78 } |
|
79 w.input <- in |
|
80 if err := w.wakeupReader(); err != nil { |
|
81 return err |
|
82 } |
|
83 return <-in.reply |
|
84 } |
|
85 |
|
86 // Remove stops watching the the named file or directory (non-recursively). |
|
87 func (w *Watcher) Remove(name string) error { |
|
88 in := &input{ |
|
89 op: opRemoveWatch, |
|
90 path: filepath.Clean(name), |
|
91 reply: make(chan error), |
|
92 } |
|
93 w.input <- in |
|
94 if err := w.wakeupReader(); err != nil { |
|
95 return err |
|
96 } |
|
97 return <-in.reply |
|
98 } |
|
99 |
|
100 // WatchList returns the directories and files that are being monitered. |
|
101 func (w *Watcher) WatchList() []string { |
|
102 w.mu.Lock() |
|
103 defer w.mu.Unlock() |
|
104 |
|
105 entries := make([]string, 0, len(w.watches)) |
|
106 for _, entry := range w.watches { |
|
107 for _, watchEntry := range entry { |
|
108 entries = append(entries, watchEntry.path) |
|
109 } |
|
110 } |
|
111 |
|
112 return entries |
|
113 } |
|
114 |
|
115 const ( |
|
116 // Options for AddWatch |
|
117 sysFSONESHOT = 0x80000000 |
|
118 sysFSONLYDIR = 0x1000000 |
|
119 |
|
120 // Events |
|
121 sysFSACCESS = 0x1 |
|
122 sysFSALLEVENTS = 0xfff |
|
123 sysFSATTRIB = 0x4 |
|
124 sysFSCLOSE = 0x18 |
|
125 sysFSCREATE = 0x100 |
|
126 sysFSDELETE = 0x200 |
|
127 sysFSDELETESELF = 0x400 |
|
128 sysFSMODIFY = 0x2 |
|
129 sysFSMOVE = 0xc0 |
|
130 sysFSMOVEDFROM = 0x40 |
|
131 sysFSMOVEDTO = 0x80 |
|
132 sysFSMOVESELF = 0x800 |
|
133 |
|
134 // Special events |
|
135 sysFSIGNORED = 0x8000 |
|
136 sysFSQOVERFLOW = 0x4000 |
|
137 ) |
|
138 |
|
139 func newEvent(name string, mask uint32) Event { |
|
140 e := Event{Name: name} |
|
141 if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO { |
|
142 e.Op |= Create |
|
143 } |
|
144 if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF { |
|
145 e.Op |= Remove |
|
146 } |
|
147 if mask&sysFSMODIFY == sysFSMODIFY { |
|
148 e.Op |= Write |
|
149 } |
|
150 if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM { |
|
151 e.Op |= Rename |
|
152 } |
|
153 if mask&sysFSATTRIB == sysFSATTRIB { |
|
154 e.Op |= Chmod |
|
155 } |
|
156 return e |
|
157 } |
|
158 |
|
159 const ( |
|
160 opAddWatch = iota |
|
161 opRemoveWatch |
|
162 ) |
|
163 |
|
164 const ( |
|
165 provisional uint64 = 1 << (32 + iota) |
|
166 ) |
|
167 |
|
168 type input struct { |
|
169 op int |
|
170 path string |
|
171 flags uint32 |
|
172 reply chan error |
|
173 } |
|
174 |
|
175 type inode struct { |
|
176 handle syscall.Handle |
|
177 volume uint32 |
|
178 index uint64 |
|
179 } |
|
180 |
|
181 type watch struct { |
|
182 ov syscall.Overlapped |
|
183 ino *inode // i-number |
|
184 path string // Directory path |
|
185 mask uint64 // Directory itself is being watched with these notify flags |
|
186 names map[string]uint64 // Map of names being watched and their notify flags |
|
187 rename string // Remembers the old name while renaming a file |
|
188 buf [4096]byte |
|
189 } |
|
190 |
|
191 type indexMap map[uint64]*watch |
|
192 type watchMap map[uint32]indexMap |
|
193 |
|
194 func (w *Watcher) wakeupReader() error { |
|
195 e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil) |
|
196 if e != nil { |
|
197 return os.NewSyscallError("PostQueuedCompletionStatus", e) |
|
198 } |
|
199 return nil |
|
200 } |
|
201 |
|
202 func getDir(pathname string) (dir string, err error) { |
|
203 attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname)) |
|
204 if e != nil { |
|
205 return "", os.NewSyscallError("GetFileAttributes", e) |
|
206 } |
|
207 if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { |
|
208 dir = pathname |
|
209 } else { |
|
210 dir, _ = filepath.Split(pathname) |
|
211 dir = filepath.Clean(dir) |
|
212 } |
|
213 return |
|
214 } |
|
215 |
|
216 func getIno(path string) (ino *inode, err error) { |
|
217 h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path), |
|
218 syscall.FILE_LIST_DIRECTORY, |
|
219 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, |
|
220 nil, syscall.OPEN_EXISTING, |
|
221 syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0) |
|
222 if e != nil { |
|
223 return nil, os.NewSyscallError("CreateFile", e) |
|
224 } |
|
225 var fi syscall.ByHandleFileInformation |
|
226 if e = syscall.GetFileInformationByHandle(h, &fi); e != nil { |
|
227 syscall.CloseHandle(h) |
|
228 return nil, os.NewSyscallError("GetFileInformationByHandle", e) |
|
229 } |
|
230 ino = &inode{ |
|
231 handle: h, |
|
232 volume: fi.VolumeSerialNumber, |
|
233 index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow), |
|
234 } |
|
235 return ino, nil |
|
236 } |
|
237 |
|
238 // Must run within the I/O thread. |
|
239 func (m watchMap) get(ino *inode) *watch { |
|
240 if i := m[ino.volume]; i != nil { |
|
241 return i[ino.index] |
|
242 } |
|
243 return nil |
|
244 } |
|
245 |
|
246 // Must run within the I/O thread. |
|
247 func (m watchMap) set(ino *inode, watch *watch) { |
|
248 i := m[ino.volume] |
|
249 if i == nil { |
|
250 i = make(indexMap) |
|
251 m[ino.volume] = i |
|
252 } |
|
253 i[ino.index] = watch |
|
254 } |
|
255 |
|
256 // Must run within the I/O thread. |
|
257 func (w *Watcher) addWatch(pathname string, flags uint64) error { |
|
258 dir, err := getDir(pathname) |
|
259 if err != nil { |
|
260 return err |
|
261 } |
|
262 if flags&sysFSONLYDIR != 0 && pathname != dir { |
|
263 return nil |
|
264 } |
|
265 ino, err := getIno(dir) |
|
266 if err != nil { |
|
267 return err |
|
268 } |
|
269 w.mu.Lock() |
|
270 watchEntry := w.watches.get(ino) |
|
271 w.mu.Unlock() |
|
272 if watchEntry == nil { |
|
273 if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil { |
|
274 syscall.CloseHandle(ino.handle) |
|
275 return os.NewSyscallError("CreateIoCompletionPort", e) |
|
276 } |
|
277 watchEntry = &watch{ |
|
278 ino: ino, |
|
279 path: dir, |
|
280 names: make(map[string]uint64), |
|
281 } |
|
282 w.mu.Lock() |
|
283 w.watches.set(ino, watchEntry) |
|
284 w.mu.Unlock() |
|
285 flags |= provisional |
|
286 } else { |
|
287 syscall.CloseHandle(ino.handle) |
|
288 } |
|
289 if pathname == dir { |
|
290 watchEntry.mask |= flags |
|
291 } else { |
|
292 watchEntry.names[filepath.Base(pathname)] |= flags |
|
293 } |
|
294 if err = w.startRead(watchEntry); err != nil { |
|
295 return err |
|
296 } |
|
297 if pathname == dir { |
|
298 watchEntry.mask &= ^provisional |
|
299 } else { |
|
300 watchEntry.names[filepath.Base(pathname)] &= ^provisional |
|
301 } |
|
302 return nil |
|
303 } |
|
304 |
|
305 // Must run within the I/O thread. |
|
306 func (w *Watcher) remWatch(pathname string) error { |
|
307 dir, err := getDir(pathname) |
|
308 if err != nil { |
|
309 return err |
|
310 } |
|
311 ino, err := getIno(dir) |
|
312 if err != nil { |
|
313 return err |
|
314 } |
|
315 w.mu.Lock() |
|
316 watch := w.watches.get(ino) |
|
317 w.mu.Unlock() |
|
318 if watch == nil { |
|
319 return fmt.Errorf("can't remove non-existent watch for: %s", pathname) |
|
320 } |
|
321 if pathname == dir { |
|
322 w.sendEvent(watch.path, watch.mask&sysFSIGNORED) |
|
323 watch.mask = 0 |
|
324 } else { |
|
325 name := filepath.Base(pathname) |
|
326 w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED) |
|
327 delete(watch.names, name) |
|
328 } |
|
329 return w.startRead(watch) |
|
330 } |
|
331 |
|
332 // Must run within the I/O thread. |
|
333 func (w *Watcher) deleteWatch(watch *watch) { |
|
334 for name, mask := range watch.names { |
|
335 if mask&provisional == 0 { |
|
336 w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED) |
|
337 } |
|
338 delete(watch.names, name) |
|
339 } |
|
340 if watch.mask != 0 { |
|
341 if watch.mask&provisional == 0 { |
|
342 w.sendEvent(watch.path, watch.mask&sysFSIGNORED) |
|
343 } |
|
344 watch.mask = 0 |
|
345 } |
|
346 } |
|
347 |
|
348 // Must run within the I/O thread. |
|
349 func (w *Watcher) startRead(watch *watch) error { |
|
350 if e := syscall.CancelIo(watch.ino.handle); e != nil { |
|
351 w.Errors <- os.NewSyscallError("CancelIo", e) |
|
352 w.deleteWatch(watch) |
|
353 } |
|
354 mask := toWindowsFlags(watch.mask) |
|
355 for _, m := range watch.names { |
|
356 mask |= toWindowsFlags(m) |
|
357 } |
|
358 if mask == 0 { |
|
359 if e := syscall.CloseHandle(watch.ino.handle); e != nil { |
|
360 w.Errors <- os.NewSyscallError("CloseHandle", e) |
|
361 } |
|
362 w.mu.Lock() |
|
363 delete(w.watches[watch.ino.volume], watch.ino.index) |
|
364 w.mu.Unlock() |
|
365 return nil |
|
366 } |
|
367 e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0], |
|
368 uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0) |
|
369 if e != nil { |
|
370 err := os.NewSyscallError("ReadDirectoryChanges", e) |
|
371 if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 { |
|
372 // Watched directory was probably removed |
|
373 if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) { |
|
374 if watch.mask&sysFSONESHOT != 0 { |
|
375 watch.mask = 0 |
|
376 } |
|
377 } |
|
378 err = nil |
|
379 } |
|
380 w.deleteWatch(watch) |
|
381 w.startRead(watch) |
|
382 return err |
|
383 } |
|
384 return nil |
|
385 } |
|
386 |
|
387 // readEvents reads from the I/O completion port, converts the |
|
388 // received events into Event objects and sends them via the Events channel. |
|
389 // Entry point to the I/O thread. |
|
390 func (w *Watcher) readEvents() { |
|
391 var ( |
|
392 n, key uint32 |
|
393 ov *syscall.Overlapped |
|
394 ) |
|
395 runtime.LockOSThread() |
|
396 |
|
397 for { |
|
398 e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE) |
|
399 watch := (*watch)(unsafe.Pointer(ov)) |
|
400 |
|
401 if watch == nil { |
|
402 select { |
|
403 case ch := <-w.quit: |
|
404 w.mu.Lock() |
|
405 var indexes []indexMap |
|
406 for _, index := range w.watches { |
|
407 indexes = append(indexes, index) |
|
408 } |
|
409 w.mu.Unlock() |
|
410 for _, index := range indexes { |
|
411 for _, watch := range index { |
|
412 w.deleteWatch(watch) |
|
413 w.startRead(watch) |
|
414 } |
|
415 } |
|
416 var err error |
|
417 if e := syscall.CloseHandle(w.port); e != nil { |
|
418 err = os.NewSyscallError("CloseHandle", e) |
|
419 } |
|
420 close(w.Events) |
|
421 close(w.Errors) |
|
422 ch <- err |
|
423 return |
|
424 case in := <-w.input: |
|
425 switch in.op { |
|
426 case opAddWatch: |
|
427 in.reply <- w.addWatch(in.path, uint64(in.flags)) |
|
428 case opRemoveWatch: |
|
429 in.reply <- w.remWatch(in.path) |
|
430 } |
|
431 default: |
|
432 } |
|
433 continue |
|
434 } |
|
435 |
|
436 switch e { |
|
437 case syscall.ERROR_MORE_DATA: |
|
438 if watch == nil { |
|
439 w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer") |
|
440 } else { |
|
441 // The i/o succeeded but the buffer is full. |
|
442 // In theory we should be building up a full packet. |
|
443 // In practice we can get away with just carrying on. |
|
444 n = uint32(unsafe.Sizeof(watch.buf)) |
|
445 } |
|
446 case syscall.ERROR_ACCESS_DENIED: |
|
447 // Watched directory was probably removed |
|
448 w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) |
|
449 w.deleteWatch(watch) |
|
450 w.startRead(watch) |
|
451 continue |
|
452 case syscall.ERROR_OPERATION_ABORTED: |
|
453 // CancelIo was called on this handle |
|
454 continue |
|
455 default: |
|
456 w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e) |
|
457 continue |
|
458 case nil: |
|
459 } |
|
460 |
|
461 var offset uint32 |
|
462 for { |
|
463 if n == 0 { |
|
464 w.Events <- newEvent("", sysFSQOVERFLOW) |
|
465 w.Errors <- errors.New("short read in readEvents()") |
|
466 break |
|
467 } |
|
468 |
|
469 // Point "raw" to the event in the buffer |
|
470 raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset])) |
|
471 // TODO: Consider using unsafe.Slice that is available from go1.17 |
|
472 // https://stackoverflow.com/questions/51187973/how-to-create-an-array-or-a-slice-from-an-array-unsafe-pointer-in-golang |
|
473 // instead of using a fixed syscall.MAX_PATH buf, we create a buf that is the size of the path name |
|
474 size := int(raw.FileNameLength / 2) |
|
475 var buf []uint16 |
|
476 sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) |
|
477 sh.Data = uintptr(unsafe.Pointer(&raw.FileName)) |
|
478 sh.Len = size |
|
479 sh.Cap = size |
|
480 name := syscall.UTF16ToString(buf) |
|
481 fullname := filepath.Join(watch.path, name) |
|
482 |
|
483 var mask uint64 |
|
484 switch raw.Action { |
|
485 case syscall.FILE_ACTION_REMOVED: |
|
486 mask = sysFSDELETESELF |
|
487 case syscall.FILE_ACTION_MODIFIED: |
|
488 mask = sysFSMODIFY |
|
489 case syscall.FILE_ACTION_RENAMED_OLD_NAME: |
|
490 watch.rename = name |
|
491 case syscall.FILE_ACTION_RENAMED_NEW_NAME: |
|
492 if watch.names[watch.rename] != 0 { |
|
493 watch.names[name] |= watch.names[watch.rename] |
|
494 delete(watch.names, watch.rename) |
|
495 mask = sysFSMOVESELF |
|
496 } |
|
497 } |
|
498 |
|
499 sendNameEvent := func() { |
|
500 if w.sendEvent(fullname, watch.names[name]&mask) { |
|
501 if watch.names[name]&sysFSONESHOT != 0 { |
|
502 delete(watch.names, name) |
|
503 } |
|
504 } |
|
505 } |
|
506 if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME { |
|
507 sendNameEvent() |
|
508 } |
|
509 if raw.Action == syscall.FILE_ACTION_REMOVED { |
|
510 w.sendEvent(fullname, watch.names[name]&sysFSIGNORED) |
|
511 delete(watch.names, name) |
|
512 } |
|
513 if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) { |
|
514 if watch.mask&sysFSONESHOT != 0 { |
|
515 watch.mask = 0 |
|
516 } |
|
517 } |
|
518 if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME { |
|
519 fullname = filepath.Join(watch.path, watch.rename) |
|
520 sendNameEvent() |
|
521 } |
|
522 |
|
523 // Move to the next event in the buffer |
|
524 if raw.NextEntryOffset == 0 { |
|
525 break |
|
526 } |
|
527 offset += raw.NextEntryOffset |
|
528 |
|
529 // Error! |
|
530 if offset >= n { |
|
531 w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.") |
|
532 break |
|
533 } |
|
534 } |
|
535 |
|
536 if err := w.startRead(watch); err != nil { |
|
537 w.Errors <- err |
|
538 } |
|
539 } |
|
540 } |
|
541 |
|
542 func (w *Watcher) sendEvent(name string, mask uint64) bool { |
|
543 if mask == 0 { |
|
544 return false |
|
545 } |
|
546 event := newEvent(name, uint32(mask)) |
|
547 select { |
|
548 case ch := <-w.quit: |
|
549 w.quit <- ch |
|
550 case w.Events <- event: |
|
551 } |
|
552 return true |
|
553 } |
|
554 |
|
555 func toWindowsFlags(mask uint64) uint32 { |
|
556 var m uint32 |
|
557 if mask&sysFSACCESS != 0 { |
|
558 m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS |
|
559 } |
|
560 if mask&sysFSMODIFY != 0 { |
|
561 m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE |
|
562 } |
|
563 if mask&sysFSATTRIB != 0 { |
|
564 m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES |
|
565 } |
|
566 if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 { |
|
567 m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME |
|
568 } |
|
569 return m |
|
570 } |
|
571 |
|
572 func toFSnotifyFlags(action uint32) uint64 { |
|
573 switch action { |
|
574 case syscall.FILE_ACTION_ADDED: |
|
575 return sysFSCREATE |
|
576 case syscall.FILE_ACTION_REMOVED: |
|
577 return sysFSDELETE |
|
578 case syscall.FILE_ACTION_MODIFIED: |
|
579 return sysFSMODIFY |
|
580 case syscall.FILE_ACTION_RENAMED_OLD_NAME: |
|
581 return sysFSMOVEDFROM |
|
582 case syscall.FILE_ACTION_RENAMED_NEW_NAME: |
|
583 return sysFSMOVEDTO |
|
584 } |
|
585 return 0 |
|
586 } |
|