--- /dev/null
+// Copyright 2011 The Go Authors. All rights reserved.\r
+// Use of this source code is governed by a BSD-style\r
+// license that can be found in the LICENSE file.\r
+\r
+// +build windows\r
+\r
+// Package fsnotify allows the user to receive\r
+// file system event notifications on Windows.\r
+package fsnotify\r
+\r
+import (\r
+ "errors"\r
+ "fmt"\r
+ "os"\r
+ "path/filepath"\r
+ "runtime"\r
+ "syscall"\r
+ "unsafe"\r
+)\r
+\r
+// Event is the type of the notification messages\r
+// received on the watcher's Event channel.\r
+type FileEvent struct {\r
+ mask uint32 // Mask of events\r
+ cookie uint32 // Unique cookie associating related events (for rename)\r
+ Name string // File name (optional)\r
+}\r
+\r
+// IsCreate reports whether the FileEvent was triggerd by a creation\r
+func (e *FileEvent) IsCreate() bool { return (e.mask & FS_CREATE) == FS_CREATE}\r
+\r
+// IsDelete reports whether the FileEvent was triggerd by a delete\r
+func (e *FileEvent) IsDelete() bool { return (e.mask & FS_DELETE) == FS_DELETE }\r
+\r
+// IsModify reports whether the FileEvent was triggerd by a file modification\r
+func (e *FileEvent) IsModify() bool { return (e.mask & FS_MODIFY) == FS_MODIFY }\r
+\r
+// IsAttribute reports whether the FileEvent was triggerd by a change of attributes\r
+func (e *FileEvent) IsAttribute() bool { return (e.mask & FS_ATTRIB) == FS_ATTRIB }\r
+\r
+// IsRename reports whether the FileEvent was triggerd by a change name\r
+func (e *FileEvent) IsRename() bool { return ((e.mask & FS_MOVE) == FS_MOVE || (e.mask & FS_MOVE_SELF) == FS_MOVE_SELF || (e.mask & FS_MOVED_FROM) == FS_MOVED_FROM || (e.mask & FS_MOVED_TO) == FS_MOVED_TO) }\r
+\r
+const (\r
+ opAddWatch = iota\r
+ opRemoveWatch\r
+)\r
+\r
+const (\r
+ provisional uint64 = 1 << (32 + iota)\r
+)\r
+\r
+type input struct {\r
+ op int\r
+ path string\r
+ flags uint32\r
+ reply chan error\r
+}\r
+\r
+type inode struct {\r
+ handle syscall.Handle\r
+ volume uint32\r
+ index uint64\r
+}\r
+\r
+type watch struct {\r
+ ov syscall.Overlapped\r
+ ino *inode // i-number\r
+ path string // Directory path\r
+ mask uint64 // Directory itself is being watched with these notify flags\r
+ names map[string]uint64 // Map of names being watched and their notify flags\r
+ rename string // Remembers the old name while renaming a file\r
+ buf [4096]byte\r
+}\r
+\r
+type indexMap map[uint64]*watch\r
+type watchMap map[uint32]indexMap\r
+\r
+// A Watcher waits for and receives event notifications\r
+// for a specific set of files and directories.\r
+type Watcher struct {\r
+ port syscall.Handle // Handle to completion port\r
+ watches watchMap // Map of watches (key: i-number)\r
+ input chan *input // Inputs to the reader are sent on this channel\r
+ Event chan *FileEvent// Events are returned on this channel\r
+ Error chan error // Errors are sent on this channel\r
+ isClosed bool // Set to true when Close() is first called\r
+ quit chan chan<- error\r
+ cookie uint32\r
+}\r
+\r
+// NewWatcher creates and returns a Watcher.\r
+func NewWatcher() (*Watcher, error) {\r
+ port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)\r
+ if e != nil {\r
+ return nil, os.NewSyscallError("CreateIoCompletionPort", e)\r
+ }\r
+ w := &Watcher{\r
+ port: port,\r
+ watches: make(watchMap),\r
+ input: make(chan *input, 1),\r
+ Event: make(chan *FileEvent, 50),\r
+ Error: make(chan error),\r
+ quit: make(chan chan<- error, 1),\r
+ }\r
+ go w.readEvents()\r
+ return w, nil\r
+}\r
+\r
+// Close closes a Watcher.\r
+// It sends a message to the reader goroutine to quit and removes all watches\r
+// associated with the watcher.\r
+func (w *Watcher) Close() error {\r
+ if w.isClosed {\r
+ return nil\r
+ }\r
+ w.isClosed = true\r
+\r
+ // Send "quit" message to the reader goroutine\r
+ ch := make(chan error)\r
+ w.quit <- ch\r
+ if err := w.wakeupReader(); err != nil {\r
+ return err\r
+ }\r
+ return <-ch\r
+}\r
+\r
+// AddWatch adds path to the watched file set.\r
+func (w *Watcher) AddWatch(path string, flags uint32) error {\r
+ if w.isClosed {\r
+ return errors.New("watcher already closed")\r
+ }\r
+ in := &input{\r
+ op: opAddWatch,\r
+ path: filepath.Clean(path),\r
+ flags: flags,\r
+ reply: make(chan error),\r
+ }\r
+ w.input <- in\r
+ if err := w.wakeupReader(); err != nil {\r
+ return err\r
+ }\r
+ return <-in.reply\r
+}\r
+\r
+// Watch adds path to the watched file set, watching all events.\r
+func (w *Watcher) Watch(path string) error {\r
+ return w.AddWatch(path, FS_ALL_EVENTS)\r
+}\r
+\r
+// RemoveWatch removes path from the watched file set.\r
+func (w *Watcher) RemoveWatch(path string) error {\r
+ in := &input{\r
+ op: opRemoveWatch,\r
+ path: filepath.Clean(path),\r
+ reply: make(chan error),\r
+ }\r
+ w.input <- in\r
+ if err := w.wakeupReader(); err != nil {\r
+ return err\r
+ }\r
+ return <-in.reply\r
+}\r
+\r
+func (w *Watcher) wakeupReader() error {\r
+ e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)\r
+ if e != nil {\r
+ return os.NewSyscallError("PostQueuedCompletionStatus", e)\r
+ }\r
+ return nil\r
+}\r
+\r
+func getDir(pathname string) (dir string, err error) {\r
+ attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))\r
+ if e != nil {\r
+ return "", os.NewSyscallError("GetFileAttributes", e)\r
+ }\r
+ if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {\r
+ dir = pathname\r
+ } else {\r
+ dir, _ = filepath.Split(pathname)\r
+ dir = filepath.Clean(dir)\r
+ }\r
+ return\r
+}\r
+\r
+func getIno(path string) (ino *inode, err error) {\r
+ h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),\r
+ syscall.FILE_LIST_DIRECTORY,\r
+ syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,\r
+ nil, syscall.OPEN_EXISTING,\r
+ syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)\r
+ if e != nil {\r
+ return nil, os.NewSyscallError("CreateFile", e)\r
+ }\r
+ var fi syscall.ByHandleFileInformation\r
+ if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {\r
+ syscall.CloseHandle(h)\r
+ return nil, os.NewSyscallError("GetFileInformationByHandle", e)\r
+ }\r
+ ino = &inode{\r
+ handle: h,\r
+ volume: fi.VolumeSerialNumber,\r
+ index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),\r
+ }\r
+ return ino, nil\r
+}\r
+\r
+// Must run within the I/O thread.\r
+func (m watchMap) get(ino *inode) *watch {\r
+ if i := m[ino.volume]; i != nil {\r
+ return i[ino.index]\r
+ }\r
+ return nil\r
+}\r
+\r
+// Must run within the I/O thread.\r
+func (m watchMap) set(ino *inode, watch *watch) {\r
+ i := m[ino.volume]\r
+ if i == nil {\r
+ i = make(indexMap)\r
+ m[ino.volume] = i\r
+ }\r
+ i[ino.index] = watch\r
+}\r
+\r
+// Must run within the I/O thread.\r
+func (w *Watcher) addWatch(pathname string, flags uint64) error {\r
+ dir, err := getDir(pathname)\r
+ if err != nil {\r
+ return err\r
+ }\r
+ if flags&FS_ONLYDIR != 0 && pathname != dir {\r
+ return nil\r
+ }\r
+ ino, err := getIno(dir)\r
+ if err != nil {\r
+ return err\r
+ }\r
+ watchEntry := w.watches.get(ino)\r
+ if watchEntry == nil {\r
+ if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {\r
+ syscall.CloseHandle(ino.handle)\r
+ return os.NewSyscallError("CreateIoCompletionPort", e)\r
+ }\r
+ watchEntry = &watch{\r
+ ino: ino,\r
+ path: dir,\r
+ names: make(map[string]uint64),\r
+ }\r
+ w.watches.set(ino, watchEntry)\r
+ flags |= provisional\r
+ } else {\r
+ syscall.CloseHandle(ino.handle)\r
+ }\r
+ if pathname == dir {\r
+ watchEntry.mask |= flags\r
+ } else {\r
+ watchEntry.names[filepath.Base(pathname)] |= flags\r
+ }\r
+ if err = w.startRead(watchEntry); err != nil {\r
+ return err\r
+ }\r
+ if pathname == dir {\r
+ watchEntry.mask &= ^provisional\r
+ } else {\r
+ watchEntry.names[filepath.Base(pathname)] &= ^provisional\r
+ }\r
+ return nil\r
+}\r
+\r
+// Must run within the I/O thread.\r
+func (w *Watcher) removeWatch(pathname string) error {\r
+ dir, err := getDir(pathname)\r
+ if err != nil {\r
+ return err\r
+ }\r
+ ino, err := getIno(dir)\r
+ if err != nil {\r
+ return err\r
+ }\r
+ watch := w.watches.get(ino)\r
+ if watch == nil {\r
+ return fmt.Errorf("can't remove non-existent watch for: %s", pathname)\r
+ }\r
+ if pathname == dir {\r
+ w.sendEvent(watch.path, watch.mask&FS_IGNORED)\r
+ watch.mask = 0\r
+ } else {\r
+ name := filepath.Base(pathname)\r
+ w.sendEvent(watch.path+"/"+name, watch.names[name]&FS_IGNORED)\r
+ delete(watch.names, name)\r
+ }\r
+ return w.startRead(watch)\r
+}\r
+\r
+// Must run within the I/O thread.\r
+func (w *Watcher) deleteWatch(watch *watch) {\r
+ for name, mask := range watch.names {\r
+ if mask&provisional == 0 {\r
+ w.sendEvent(watch.path+"/"+name, mask&FS_IGNORED)\r
+ }\r
+ delete(watch.names, name)\r
+ }\r
+ if watch.mask != 0 {\r
+ if watch.mask&provisional == 0 {\r
+ w.sendEvent(watch.path, watch.mask&FS_IGNORED)\r
+ }\r
+ watch.mask = 0\r
+ }\r
+}\r
+\r
+// Must run within the I/O thread.\r
+func (w *Watcher) startRead(watch *watch) error {\r
+ if e := syscall.CancelIo(watch.ino.handle); e != nil {\r
+ w.Error <- os.NewSyscallError("CancelIo", e)\r
+ w.deleteWatch(watch)\r
+ }\r
+ mask := toWindowsFlags(watch.mask)\r
+ for _, m := range watch.names {\r
+ mask |= toWindowsFlags(m)\r
+ }\r
+ if mask == 0 {\r
+ if e := syscall.CloseHandle(watch.ino.handle); e != nil {\r
+ w.Error <- os.NewSyscallError("CloseHandle", e)\r
+ }\r
+ delete(w.watches[watch.ino.volume], watch.ino.index)\r
+ return nil\r
+ }\r
+ e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],\r
+ uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)\r
+ if e != nil {\r
+ err := os.NewSyscallError("ReadDirectoryChanges", e)\r
+ if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {\r
+ // Watched directory was probably removed\r
+ if w.sendEvent(watch.path, watch.mask&FS_DELETE_SELF) {\r
+ if watch.mask&FS_ONESHOT != 0 {\r
+ watch.mask = 0\r
+ }\r
+ }\r
+ err = nil\r
+ }\r
+ w.deleteWatch(watch)\r
+ w.startRead(watch)\r
+ return err\r
+ }\r
+ return nil\r
+}\r
+\r
+// readEvents reads from the I/O completion port, converts the\r
+// received events into Event objects and sends them via the Event channel.\r
+// Entry point to the I/O thread.\r
+func (w *Watcher) readEvents() {\r
+ var (\r
+ n, key uint32\r
+ ov *syscall.Overlapped\r
+ )\r
+ runtime.LockOSThread()\r
+\r
+ for {\r
+ e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)\r
+ watch := (*watch)(unsafe.Pointer(ov))\r
+\r
+ if watch == nil {\r
+ select {\r
+ case ch := <-w.quit:\r
+ for _, index := range w.watches {\r
+ for _, watch := range index {\r
+ w.deleteWatch(watch)\r
+ w.startRead(watch)\r
+ }\r
+ }\r
+ var err error\r
+ if e := syscall.CloseHandle(w.port); e != nil {\r
+ err = os.NewSyscallError("CloseHandle", e)\r
+ }\r
+ close(w.Event)\r
+ close(w.Error)\r
+ ch <- err\r
+ return\r
+ case in := <-w.input:\r
+ switch in.op {\r
+ case opAddWatch:\r
+ in.reply <- w.addWatch(in.path, uint64(in.flags))\r
+ case opRemoveWatch:\r
+ in.reply <- w.removeWatch(in.path)\r
+ }\r
+ default:\r
+ }\r
+ continue\r
+ }\r
+\r
+ switch e {\r
+ case syscall.ERROR_ACCESS_DENIED:\r
+ // Watched directory was probably removed\r
+ w.sendEvent(watch.path, watch.mask&FS_DELETE_SELF)\r
+ w.deleteWatch(watch)\r
+ w.startRead(watch)\r
+ continue\r
+ case syscall.ERROR_OPERATION_ABORTED:\r
+ // CancelIo was called on this handle\r
+ continue\r
+ default:\r
+ w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e)\r
+ continue\r
+ case nil:\r
+ }\r
+\r
+ var offset uint32\r
+ for {\r
+ if n == 0 {\r
+ // WTF is this??\r
+ //w.Event <- &Event{Mask: FS_Q_OVERFLOW}\r
+ w.Error <- errors.New("short read in readEvents()")\r
+ break\r
+ }\r
+\r
+ // Point "raw" to the event in the buffer\r
+ raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))\r
+ buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))\r
+ name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])\r
+ fullname := watch.path + "/" + name\r
+\r
+ var mask uint64\r
+ switch raw.Action {\r
+ case syscall.FILE_ACTION_REMOVED:\r
+ mask = FS_DELETE_SELF\r
+ case syscall.FILE_ACTION_MODIFIED:\r
+ mask = FS_MODIFY\r
+ case syscall.FILE_ACTION_RENAMED_OLD_NAME:\r
+ watch.rename = name\r
+ case syscall.FILE_ACTION_RENAMED_NEW_NAME:\r
+ if watch.names[watch.rename] != 0 {\r
+ watch.names[name] |= watch.names[watch.rename]\r
+ delete(watch.names, watch.rename)\r
+ mask = FS_MOVE_SELF\r
+ }\r
+ }\r
+\r
+ sendNameEvent := func() {\r
+ if w.sendEvent(fullname, watch.names[name]&mask) {\r
+ if watch.names[name]&FS_ONESHOT != 0 {\r
+ delete(watch.names, name)\r
+ }\r
+ }\r
+ }\r
+ if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {\r
+ sendNameEvent()\r
+ }\r
+ if raw.Action == syscall.FILE_ACTION_REMOVED {\r
+ w.sendEvent(fullname, watch.names[name]&FS_IGNORED)\r
+ delete(watch.names, name)\r
+ }\r
+ if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {\r
+ if watch.mask&FS_ONESHOT != 0 {\r
+ watch.mask = 0\r
+ }\r
+ }\r
+ if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {\r
+ fullname = watch.path + "/" + watch.rename\r
+ sendNameEvent()\r
+ }\r
+\r
+ // Move to the next event in the buffer\r
+ if raw.NextEntryOffset == 0 {\r
+ break\r
+ }\r
+ offset += raw.NextEntryOffset\r
+ }\r
+\r
+ if err := w.startRead(watch); err != nil {\r
+ w.Error <- err\r
+ }\r
+ }\r
+}\r
+\r
+func (w *Watcher) sendEvent(name string, mask uint64) bool {\r
+ if mask == 0 {\r
+ return false\r
+ }\r
+ event := &FileEvent{mask: uint32(mask), Name: name}\r
+ if mask&FS_MOVE != 0 {\r
+ if mask&FS_MOVED_FROM != 0 {\r
+ w.cookie++\r
+ }\r
+ event.cookie = w.cookie\r
+ }\r
+ select {\r
+ case ch := <-w.quit:\r
+ w.quit <- ch\r
+ case w.Event <- event:\r
+ }\r
+ return true\r
+}\r
+\r
+func toWindowsFlags(mask uint64) uint32 {\r
+ var m uint32\r
+ if mask&FS_ACCESS != 0 {\r
+ m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS\r
+ }\r
+ if mask&FS_MODIFY != 0 {\r
+ m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE\r
+ }\r
+ if mask&FS_ATTRIB != 0 {\r
+ m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES\r
+ }\r
+ if mask&(FS_MOVE|FS_CREATE|FS_DELETE) != 0 {\r
+ m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME\r
+ }\r
+ return m\r
+}\r
+\r
+func toFSnotifyFlags(action uint32) uint64 {\r
+ switch action {\r
+ case syscall.FILE_ACTION_ADDED:\r
+ return FS_CREATE\r
+ case syscall.FILE_ACTION_REMOVED:\r
+ return FS_DELETE\r
+ case syscall.FILE_ACTION_MODIFIED:\r
+ return FS_MODIFY\r
+ case syscall.FILE_ACTION_RENAMED_OLD_NAME:\r
+ return FS_MOVED_FROM\r
+ case syscall.FILE_ACTION_RENAMED_NEW_NAME:\r
+ return FS_MOVED_TO\r
+ }\r
+ return 0\r
+}\r
+\r
+const (\r
+ // Options for AddWatch\r
+ FS_ONESHOT = 0x80000000\r
+ FS_ONLYDIR = 0x1000000\r
+\r
+ // Events\r
+ FS_ACCESS = 0x1\r
+ FS_ALL_EVENTS = 0xfff\r
+ FS_ATTRIB = 0x4\r
+ FS_CLOSE = 0x18\r
+ FS_CREATE = 0x100\r
+ FS_DELETE = 0x200\r
+ FS_DELETE_SELF = 0x400\r
+ FS_MODIFY = 0x2\r
+ FS_MOVE = 0xc0\r
+ FS_MOVED_FROM = 0x40\r
+ FS_MOVED_TO = 0x80\r
+ FS_MOVE_SELF = 0x800\r
+\r
+ // Special events\r
+ FS_IGNORED = 0x8000\r
+ FS_Q_OVERFLOW = 0x4000\r
+)\r
+\r
+var eventBits = []struct {\r
+ Value uint32\r
+ Name string\r
+}{\r
+ {FS_ACCESS, "FS_ACCESS"},\r
+ {FS_ATTRIB, "FS_ATTRIB"},\r
+ {FS_CREATE, "FS_CREATE"},\r
+ {FS_DELETE, "FS_DELETE"},\r
+ {FS_DELETE_SELF, "FS_DELETE_SELF"},\r
+ {FS_MODIFY, "FS_MODIFY"},\r
+ {FS_MOVED_FROM, "FS_MOVED_FROM"},\r
+ {FS_MOVED_TO, "FS_MOVED_TO"},\r
+ {FS_MOVE_SELF, "FS_MOVE_SELF"},\r
+ {FS_IGNORED, "FS_IGNORED"},\r
+ {FS_Q_OVERFLOW, "FS_Q_OVERFLOW"},\r
+}\r