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.
20 // Watcher watches a set of files, delivering events to a channel.
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
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)
36 return nil, os.NewSyscallError("CreateIoCompletionPort", e)
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),
50 // Close removes all watches and closes the events channel.
51 func (w *Watcher) Close() error {
57 // Send "quit" message to the reader goroutine
58 ch := make(chan error)
60 if err := w.wakeupReader(); err != nil {
66 // Add starts watching the named file or directory (non-recursively).
67 func (w *Watcher) Add(name string) error {
69 return errors.New("watcher already closed")
73 path: filepath.Clean(name),
74 flags: sysFSALLEVENTS,
75 reply: make(chan error),
78 if err := w.wakeupReader(); err != nil {
84 // Remove stops watching the the named file or directory (non-recursively).
85 func (w *Watcher) Remove(name string) error {
88 path: filepath.Clean(name),
89 reply: make(chan error),
92 if err := w.wakeupReader(); err != nil {
99 // Options for AddWatch
100 sysFSONESHOT = 0x80000000
101 sysFSONLYDIR = 0x1000000
105 sysFSALLEVENTS = 0xfff
110 sysFSDELETESELF = 0x400
113 sysFSMOVEDFROM = 0x40
115 sysFSMOVESELF = 0x800
118 sysFSIGNORED = 0x8000
119 sysFSQOVERFLOW = 0x4000
122 func newEvent(name string, mask uint32) Event {
123 e := Event{Name: name}
124 if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
127 if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
130 if mask&sysFSMODIFY == sysFSMODIFY {
133 if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
136 if mask&sysFSATTRIB == sysFSATTRIB {
148 provisional uint64 = 1 << (32 + iota)
159 handle syscall.Handle
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
174 type indexMap map[uint64]*watch
175 type watchMap map[uint32]indexMap
177 func (w *Watcher) wakeupReader() error {
178 e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
180 return os.NewSyscallError("PostQueuedCompletionStatus", e)
185 func getDir(pathname string) (dir string, err error) {
186 attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
188 return "", os.NewSyscallError("GetFileAttributes", e)
190 if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
193 dir, _ = filepath.Split(pathname)
194 dir = filepath.Clean(dir)
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)
206 return nil, os.NewSyscallError("CreateFile", e)
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)
215 volume: fi.VolumeSerialNumber,
216 index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
221 // Must run within the I/O thread.
222 func (m watchMap) get(ino *inode) *watch {
223 if i := m[ino.volume]; i != nil {
229 // Must run within the I/O thread.
230 func (m watchMap) set(ino *inode, watch *watch) {
239 // Must run within the I/O thread.
240 func (w *Watcher) addWatch(pathname string, flags uint64) error {
241 dir, err := getDir(pathname)
245 if flags&sysFSONLYDIR != 0 && pathname != dir {
248 ino, err := getIno(dir)
253 watchEntry := w.watches.get(ino)
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)
263 names: make(map[string]uint64),
266 w.watches.set(ino, watchEntry)
270 syscall.CloseHandle(ino.handle)
273 watchEntry.mask |= flags
275 watchEntry.names[filepath.Base(pathname)] |= flags
277 if err = w.startRead(watchEntry); err != nil {
281 watchEntry.mask &= ^provisional
283 watchEntry.names[filepath.Base(pathname)] &= ^provisional
288 // Must run within the I/O thread.
289 func (w *Watcher) remWatch(pathname string) error {
290 dir, err := getDir(pathname)
294 ino, err := getIno(dir)
299 watch := w.watches.get(ino)
302 return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
305 w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
308 name := filepath.Base(pathname)
309 w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
310 delete(watch.names, name)
312 return w.startRead(watch)
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)
321 delete(watch.names, name)
324 if watch.mask&provisional == 0 {
325 w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
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)
337 mask := toWindowsFlags(watch.mask)
338 for _, m := range watch.names {
339 mask |= toWindowsFlags(m)
342 if e := syscall.CloseHandle(watch.ino.handle); e != nil {
343 w.Errors <- os.NewSyscallError("CloseHandle", e)
346 delete(w.watches[watch.ino.volume], watch.ino.index)
350 e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
351 uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
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 {
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() {
376 ov *syscall.Overlapped
378 runtime.LockOSThread()
381 e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
382 watch := (*watch)(unsafe.Pointer(ov))
388 var indexes []indexMap
389 for _, index := range w.watches {
390 indexes = append(indexes, index)
393 for _, index := range indexes {
394 for _, watch := range index {
400 if e := syscall.CloseHandle(w.port); e != nil {
401 err = os.NewSyscallError("CloseHandle", e)
407 case in := <-w.input:
410 in.reply <- w.addWatch(in.path, uint64(in.flags))
412 in.reply <- w.remWatch(in.path)
420 case syscall.ERROR_MORE_DATA:
422 w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
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))
429 case syscall.ERROR_ACCESS_DENIED:
430 // Watched directory was probably removed
431 w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
435 case syscall.ERROR_OPERATION_ABORTED:
436 // CancelIo was called on this handle
439 w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
447 w.Events <- newEvent("", sysFSQOVERFLOW)
448 w.Errors <- errors.New("short read in readEvents()")
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)
460 case syscall.FILE_ACTION_REMOVED:
461 mask = sysFSDELETESELF
462 case syscall.FILE_ACTION_MODIFIED:
464 case syscall.FILE_ACTION_RENAMED_OLD_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)
474 sendNameEvent := func() {
475 if w.sendEvent(fullname, watch.names[name]&mask) {
476 if watch.names[name]&sysFSONESHOT != 0 {
477 delete(watch.names, name)
481 if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
484 if raw.Action == syscall.FILE_ACTION_REMOVED {
485 w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
486 delete(watch.names, name)
488 if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
489 if watch.mask&sysFSONESHOT != 0 {
493 if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
494 fullname = filepath.Join(watch.path, watch.rename)
498 // Move to the next event in the buffer
499 if raw.NextEntryOffset == 0 {
502 offset += raw.NextEntryOffset
506 w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
511 if err := w.startRead(watch); err != nil {
517 func (w *Watcher) sendEvent(name string, mask uint64) bool {
521 event := newEvent(name, uint32(mask))
525 case w.Events <- event:
530 func toWindowsFlags(mask uint64) uint32 {
532 if mask&sysFSACCESS != 0 {
533 m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
535 if mask&sysFSMODIFY != 0 {
536 m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
538 if mask&sysFSATTRIB != 0 {
539 m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
541 if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
542 m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
547 func toFSnotifyFlags(action uint32) uint64 {
549 case syscall.FILE_ACTION_ADDED:
551 case syscall.FILE_ACTION_REMOVED:
553 case syscall.FILE_ACTION_MODIFIED:
555 case syscall.FILE_ACTION_RENAMED_OLD_NAME:
556 return sysFSMOVEDFROM
557 case syscall.FILE_ACTION_RENAMED_NEW_NAME: