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