// 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
+package fsnotify
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "syscall"
+ "unsafe"
+)
+
// 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
+type FileEvent struct {
+ mask uint32 // Mask of events\r
+ cookie uint32 // Unique cookie associating related events (for rename)\r
+ Name string // File name (optional)\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
+func (e *FileEvent) IsCreate() bool { return (e.mask & FS_CREATE) == FS_CREATE }
+
// IsDelete reports whether the FileEvent was triggerd by a delete\r
-func (e *FileEvent) IsDelete() bool { return ((e.mask & FS_DELETE) == FS_DELETE || (e.mask & FS_DELETE_SELF) == FS_DELETE_SELF) }\r
-\r
+func (e *FileEvent) IsDelete() bool {
+ return ((e.mask&FS_DELETE) == FS_DELETE || (e.mask&FS_DELETE_SELF) == FS_DELETE_SELF)
+}
+
// IsModify reports whether the FileEvent was triggerd by a file modification or attribute change\r
-func (e *FileEvent) IsModify() bool { return ((e.mask & FS_MODIFY) == FS_MODIFY || (e.mask & FS_ATTRIB) == FS_ATTRIB) }\r
-\r
+func (e *FileEvent) IsModify() bool {
+ return ((e.mask&FS_MODIFY) == FS_MODIFY || (e.mask&FS_ATTRIB) == FS_ATTRIB)
+}
+
// 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
+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)
+}
+
+const (
+ opAddWatch = iota
+ opRemoveWatch
+)
+
+const (
+ provisional uint64 = 1 << (32 + iota)
+)
+
+type input struct {
+ op int
+ path string
+ flags uint32
+ reply chan error
+}
+
+type inode struct {
+ handle syscall.Handle
+ volume uint32
+ index uint64
+}
+
+type watch struct {
+ ov syscall.Overlapped
+ 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
+}
+
+type indexMap map[uint64]*watch
+type watchMap map[uint32]indexMap
+
// 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
+type Watcher struct {
+ port syscall.Handle // Handle to completion port\r
+ watches watchMap // Map of watches (key: i-number)\r
+ fsnFlags map[string]uint32 // Map of watched files to flags used for filter\r
+ input chan *input // Inputs to the reader are sent on this channel\r
+ internalEvent chan *FileEvent // Events are queued 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
+ cookie uint32
+}
+
// 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
+func NewWatcher() (*Watcher, error) {
+ port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
+ if e != nil {
+ return nil, os.NewSyscallError("CreateIoCompletionPort", e)
+ }
+ w := &Watcher{
+ port: port,
+ watches: make(watchMap),
+ fsnFlags: make(map[string]uint32),
+ input: make(chan *input, 1),
+ Event: make(chan *FileEvent, 50),
+ internalEvent: make(chan *FileEvent),
+ Error: make(chan error),
+ quit: make(chan chan<- error, 1),
+ }
+ go w.readEvents()
+ go w.purgeEvents()
+ return w, nil
+}
+
// 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
+func (w *Watcher) Close() error {
+ if w.isClosed {
+ return nil
+ }
+ w.isClosed = true
+
+ // Send "quit" message to the reader goroutine\r
+ ch := make(chan error)
+ w.quit <- ch
+ if err := w.wakeupReader(); err != nil {
+ return err
+ }
+ return <-ch
+}
+
// 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
+func (w *Watcher) AddWatch(path string, flags uint32) error {
+ if w.isClosed {
+ return errors.New("watcher already closed")
+ }
+ in := &input{
+ op: opAddWatch,
+ path: filepath.Clean(path),
+ flags: flags,
+ reply: make(chan error),
+ }
+ w.input <- in
+ if err := w.wakeupReader(); err != nil {
+ return err
+ }
+ return <-in.reply
+}
+
// 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
+func (w *Watcher) watch(path string) error {
+ return w.AddWatch(path, FS_ALL_EVENTS)
+}
+
// 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
+func (w *Watcher) removeWatch(path string) error {
+ in := &input{
+ op: opRemoveWatch,
+ path: filepath.Clean(path),
+ reply: make(chan error),
+ }
+ w.input <- in
+ if err := w.wakeupReader(); err != nil {
+ return err
+ }
+ return <-in.reply
+}
+
+func (w *Watcher) wakeupReader() error {
+ e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
+ if e != nil {
+ return os.NewSyscallError("PostQueuedCompletionStatus", e)
+ }
+ return nil
+}
+
+func getDir(pathname string) (dir string, err error) {
+ attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
+ if e != nil {
+ return "", os.NewSyscallError("GetFileAttributes", e)
+ }
+ if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
+ dir = pathname
+ } else {
+ dir, _ = filepath.Split(pathname)
+ dir = filepath.Clean(dir)
+ }
+ return
+}
+
+func getIno(path string) (ino *inode, err error) {
+ h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
+ syscall.FILE_LIST_DIRECTORY,
+ syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
+ nil, syscall.OPEN_EXISTING,
+ syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
+ if e != nil {
+ return nil, os.NewSyscallError("CreateFile", e)
+ }
+ var fi syscall.ByHandleFileInformation
+ if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
+ syscall.CloseHandle(h)
+ return nil, os.NewSyscallError("GetFileInformationByHandle", e)
+ }
+ ino = &inode{
+ handle: h,
+ volume: fi.VolumeSerialNumber,
+ index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
+ }
+ return ino, nil
+}
+
// 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
+func (m watchMap) get(ino *inode) *watch {
+ if i := m[ino.volume]; i != nil {
+ return i[ino.index]
+ }
+ return nil
+}
+
// 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
+func (m watchMap) set(ino *inode, watch *watch) {
+ i := m[ino.volume]
+ if i == nil {
+ i = make(indexMap)
+ m[ino.volume] = i
+ }
+ i[ino.index] = watch
+}
+
// 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
+func (w *Watcher) addWatch(pathname string, flags uint64) error {
+ dir, err := getDir(pathname)
+ if err != nil {
+ return err
+ }
+ if flags&FS_ONLYDIR != 0 && pathname != dir {
+ return nil
+ }
+ ino, err := getIno(dir)
+ if err != nil {
+ return err
+ }
+ watchEntry := w.watches.get(ino)
+ if watchEntry == nil {
+ if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
+ syscall.CloseHandle(ino.handle)
+ return os.NewSyscallError("CreateIoCompletionPort", e)
+ }
+ watchEntry = &watch{
+ ino: ino,
+ path: dir,
+ names: make(map[string]uint64),
+ }
+ w.watches.set(ino, watchEntry)
+ flags |= provisional
+ } else {
+ syscall.CloseHandle(ino.handle)
+ }
+ if pathname == dir {
+ watchEntry.mask |= flags
+ } else {
+ watchEntry.names[filepath.Base(pathname)] |= flags
+ }
+ if err = w.startRead(watchEntry); err != nil {
+ return err
+ }
+ if pathname == dir {
+ watchEntry.mask &= ^provisional
+ } else {
+ watchEntry.names[filepath.Base(pathname)] &= ^provisional
+ }
+ return nil
+}
+
// 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
+func (w *Watcher) remWatch(pathname string) error {
+ dir, err := getDir(pathname)
+ if err != nil {
+ return err
+ }
+ ino, err := getIno(dir)
+ if err != nil {
+ return err
+ }
+ watch := w.watches.get(ino)
+ if watch == nil {
+ return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
+ }
+ if pathname == dir {
+ w.sendEvent(watch.path, watch.mask&FS_IGNORED)
+ watch.mask = 0
+ } else {
+ name := filepath.Base(pathname)
+ w.sendEvent(watch.path+"/"+name, watch.names[name]&FS_IGNORED)
+ delete(watch.names, name)
+ }
+ return w.startRead(watch)
+}
+
// 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
+func (w *Watcher) deleteWatch(watch *watch) {
+ for name, mask := range watch.names {
+ if mask&provisional == 0 {
+ w.sendEvent(watch.path+"/"+name, mask&FS_IGNORED)
+ }
+ delete(watch.names, name)
+ }
+ if watch.mask != 0 {
+ if watch.mask&provisional == 0 {
+ w.sendEvent(watch.path, watch.mask&FS_IGNORED)
+ }
+ watch.mask = 0
+ }
+}
+
// 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
+func (w *Watcher) startRead(watch *watch) error {
+ if e := syscall.CancelIo(watch.ino.handle); e != nil {
+ w.Error <- os.NewSyscallError("CancelIo", e)
+ w.deleteWatch(watch)
+ }
+ mask := toWindowsFlags(watch.mask)
+ for _, m := range watch.names {
+ mask |= toWindowsFlags(m)
+ }
+ if mask == 0 {
+ if e := syscall.CloseHandle(watch.ino.handle); e != nil {
+ w.Error <- os.NewSyscallError("CloseHandle", e)
+ }
+ delete(w.watches[watch.ino.volume], watch.ino.index)
+ return nil
+ }
+ e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
+ uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
+ if e != nil {
+ err := os.NewSyscallError("ReadDirectoryChanges", e)
+ if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
+ // Watched directory was probably removed\r
+ if w.sendEvent(watch.path, watch.mask&FS_DELETE_SELF) {
+ if watch.mask&FS_ONESHOT != 0 {
+ watch.mask = 0
+ }
+ }
+ err = nil
+ }
+ w.deleteWatch(watch)
+ w.startRead(watch)
+ return err
+ }
+ return nil
+}
+
// 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
- w.Event <- &FileEvent{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
+func (w *Watcher) readEvents() {
+ var (
+ n, key uint32
+ ov *syscall.Overlapped
+ )
+ runtime.LockOSThread()
+
+ for {
+ e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
+ watch := (*watch)(unsafe.Pointer(ov))
+
+ if watch == nil {
+ select {
+ case ch := <-w.quit:
+ for _, index := range w.watches {
+ for _, watch := range index {
+ w.deleteWatch(watch)
+ w.startRead(watch)
+ }
+ }
+ var err error
+ if e := syscall.CloseHandle(w.port); e != nil {
+ err = os.NewSyscallError("CloseHandle", e)
+ }
+ close(w.internalEvent)
+ close(w.Error)
+ ch <- err
+ return
+ case in := <-w.input:
+ switch in.op {
+ case opAddWatch:
+ in.reply <- w.addWatch(in.path, uint64(in.flags))
+ case opRemoveWatch:
+ in.reply <- w.remWatch(in.path)
+ }
+ default:
+ }
+ continue
+ }
+
+ switch e {
+ case syscall.ERROR_ACCESS_DENIED:
+ // Watched directory was probably removed\r
+ w.sendEvent(watch.path, watch.mask&FS_DELETE_SELF)
+ w.deleteWatch(watch)
+ w.startRead(watch)
+ continue
+ case syscall.ERROR_OPERATION_ABORTED:
+ // CancelIo was called on this handle\r
+ continue
+ default:
+ w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e)
+ continue
+ case nil:
+ }
+
+ var offset uint32
+ for {
+ if n == 0 {
+ w.internalEvent <- &FileEvent{mask: FS_Q_OVERFLOW}
+ w.Error <- errors.New("short read in readEvents()")
+ break
+ }
+
+ // Point "raw" to the event in the buffer\r
+ raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
+ buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
+ name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
+ fullname := watch.path + "/" + name
+
+ var mask uint64
+ switch raw.Action {
+ case syscall.FILE_ACTION_REMOVED:
+ mask = FS_DELETE_SELF
+ case syscall.FILE_ACTION_MODIFIED:
+ mask = FS_MODIFY
+ case syscall.FILE_ACTION_RENAMED_OLD_NAME:
+ watch.rename = name
+ case syscall.FILE_ACTION_RENAMED_NEW_NAME:
+ if watch.names[watch.rename] != 0 {
+ watch.names[name] |= watch.names[watch.rename]
+ delete(watch.names, watch.rename)
+ mask = FS_MOVE_SELF
+ }
+ }
+
+ sendNameEvent := func() {
+ if w.sendEvent(fullname, watch.names[name]&mask) {
+ if watch.names[name]&FS_ONESHOT != 0 {
+ delete(watch.names, name)
+ }
+ }
+ }
+ if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
+ sendNameEvent()
+ }
+ if raw.Action == syscall.FILE_ACTION_REMOVED {
+ w.sendEvent(fullname, watch.names[name]&FS_IGNORED)
+ delete(watch.names, name)
+ }
+ if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
+ if watch.mask&FS_ONESHOT != 0 {
+ watch.mask = 0
+ }
+ }
+ if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
+ fullname = watch.path + "/" + watch.rename
+ sendNameEvent()
+ }
+
+ // Move to the next event in the buffer\r
+ if raw.NextEntryOffset == 0 {
+ break
+ }
+ offset += raw.NextEntryOffset
+ }
+
+ if err := w.startRead(watch); err != nil {
+ w.Error <- err
+ }
+ }
+}
+
+func (w *Watcher) sendEvent(name string, mask uint64) bool {
+ if mask == 0 {
+ return false
+ }
+ event := &FileEvent{mask: uint32(mask), Name: name}
+ if mask&FS_MOVE != 0 {
+ if mask&FS_MOVED_FROM != 0 {
+ w.cookie++
+ }
+ event.cookie = w.cookie
+ }
+ select {
+ case ch := <-w.quit:
+ w.quit <- ch
+ case w.Event <- event:
+ }
+ return true
+}
+
+func toWindowsFlags(mask uint64) uint32 {
+ var m uint32
+ if mask&FS_ACCESS != 0 {
+ m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
+ }
+ if mask&FS_MODIFY != 0 {
+ m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
+ }
+ if mask&FS_ATTRIB != 0 {
+ m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
+ }
+ if mask&(FS_MOVE|FS_CREATE|FS_DELETE) != 0 {
+ m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
+ }
+ return m
+}
+
+func toFSnotifyFlags(action uint32) uint64 {
+ switch action {
+ case syscall.FILE_ACTION_ADDED:
+ return FS_CREATE
+ case syscall.FILE_ACTION_REMOVED:
+ return FS_DELETE
+ case syscall.FILE_ACTION_MODIFIED:
+ return FS_MODIFY
+ case syscall.FILE_ACTION_RENAMED_OLD_NAME:
+ return FS_MOVED_FROM
+ case syscall.FILE_ACTION_RENAMED_NEW_NAME:
+ return FS_MOVED_TO
+ }
+ return 0
+}
+
+const (
+ // Options for AddWatch\r
+ FS_ONESHOT = 0x80000000
+ FS_ONLYDIR = 0x1000000
+
+ // Events\r
+ FS_ACCESS = 0x1
+ FS_ALL_EVENTS = 0xfff
+ FS_ATTRIB = 0x4
+ FS_CLOSE = 0x18
+ FS_CREATE = 0x100
+ FS_DELETE = 0x200
+ FS_DELETE_SELF = 0x400
+ FS_MODIFY = 0x2
+ FS_MOVE = 0xc0
+ FS_MOVED_FROM = 0x40
+ FS_MOVED_TO = 0x80
+ FS_MOVE_SELF = 0x800
+
+ // Special events\r
+ FS_IGNORED = 0x8000
+ FS_Q_OVERFLOW = 0x4000
+)
+
+var eventBits = []struct {
+ Value uint32
+ Name string
+}{
+ {FS_ACCESS, "FS_ACCESS"},
+ {FS_ATTRIB, "FS_ATTRIB"},
+ {FS_CREATE, "FS_CREATE"},
+ {FS_DELETE, "FS_DELETE"},
+ {FS_DELETE_SELF, "FS_DELETE_SELF"},
+ {FS_MODIFY, "FS_MODIFY"},
+ {FS_MOVED_FROM, "FS_MOVED_FROM"},
+ {FS_MOVED_TO, "FS_MOVED_TO"},
+ {FS_MOVE_SELF, "FS_MOVE_SELF"},
+ {FS_IGNORED, "FS_IGNORED"},
+ {FS_Q_OVERFLOW, "FS_Q_OVERFLOW"},
+}