aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHector Chu <hectorchu@gmail.com>2011-10-18 21:09:58 +0100
committerHector Chu <hectorchu@gmail.com>2011-10-18 21:09:58 +0100
commit7ecf6c997e786b4812ae5d5f6afade15a3717fa9 (patch)
tree2307b79dcb0b6ca5ad3def91ca277961e47ff5e8
parent033585d6755a19308314b89f1252ec1438e24fe0 (diff)
downloadgo-7ecf6c997e786b4812ae5d5f6afade15a3717fa9.tar.gz
go-7ecf6c997e786b4812ae5d5f6afade15a3717fa9.zip
exp/winfsnotify: filesystem watcher for Windows
R=rsc, alex.brainman, bradfitz CC=bsiegert, go.peter.90, golang-dev https://golang.org/cl/4188047
-rw-r--r--src/pkg/exp/winfsnotify/Makefile11
-rw-r--r--src/pkg/exp/winfsnotify/winfsnotify.go569
-rw-r--r--src/pkg/exp/winfsnotify/winfsnotify_test.go124
-rw-r--r--src/pkg/syscall/syscall_windows.go2
-rw-r--r--src/pkg/syscall/zsyscall_windows_386.go36
-rw-r--r--src/pkg/syscall/zsyscall_windows_amd64.go36
-rw-r--r--src/pkg/syscall/ztypes_windows.go30
7 files changed, 808 insertions, 0 deletions
diff --git a/src/pkg/exp/winfsnotify/Makefile b/src/pkg/exp/winfsnotify/Makefile
new file mode 100644
index 0000000000..f0fe096d99
--- /dev/null
+++ b/src/pkg/exp/winfsnotify/Makefile
@@ -0,0 +1,11 @@
+# Copyright 2011 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+include ../../../Make.inc
+
+TARG=exp/winfsnotify
+GOFILES=\
+ winfsnotify.go\
+
+include ../../../Make.pkg
diff --git a/src/pkg/exp/winfsnotify/winfsnotify.go b/src/pkg/exp/winfsnotify/winfsnotify.go
new file mode 100644
index 0000000000..c5dfe99ad7
--- /dev/null
+++ b/src/pkg/exp/winfsnotify/winfsnotify.go
@@ -0,0 +1,569 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package winfsnotify allows the user to receive
+// file system event notifications on Windows.
+package winfsnotify
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "syscall"
+ "unsafe"
+)
+
+// Event is the type of the notification messages
+// received on the watcher's Event channel.
+type Event struct {
+ Mask uint32 // Mask of events
+ Cookie uint32 // Unique cookie associating related events (for rename)
+ Name string // File name (optional)
+}
+
+const (
+ opAddWatch = iota
+ opRemoveWatch
+)
+
+const (
+ provisional uint64 = 1 << (32 + iota)
+)
+
+type input struct {
+ op int
+ path string
+ flags uint32
+ reply chan os.Error
+}
+
+type inode struct {
+ handle syscall.Handle
+ volume uint32
+ index uint64
+}
+
+type watch struct {
+ ov syscall.Overlapped
+ ino *inode // i-number
+ path string // Directory path
+ mask uint64 // Directory itself is being watched with these notify flags
+ names map[string]uint64 // Map of names being watched and their notify flags
+ rename string // Remembers the old name while renaming a file
+ buf [4096]byte
+}
+
+type indexMap map[uint64]*watch
+type watchMap map[uint32]indexMap
+
+// A Watcher waits for and receives event notifications
+// for a specific set of files and directories.
+type Watcher struct {
+ port syscall.Handle // Handle to completion port
+ watches watchMap // Map of watches (key: i-number)
+ input chan *input // Inputs to the reader are sent on this channel
+ Event chan *Event // Events are returned on this channel
+ Error chan os.Error // Errors are sent on this channel
+ isClosed bool // Set to true when Close() is first called
+ quit chan chan<- os.Error
+ cookie uint32
+}
+
+// NewWatcher creates and returns a Watcher.
+func NewWatcher() (*Watcher, os.Error) {
+ port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
+ if e != 0 {
+ return nil, os.NewSyscallError("CreateIoCompletionPort", e)
+ }
+ w := &Watcher{
+ port: port,
+ watches: make(watchMap),
+ input: make(chan *input, 1),
+ Event: make(chan *Event, 50),
+ Error: make(chan os.Error),
+ quit: make(chan chan<- os.Error, 1),
+ }
+ go w.readEvents()
+ return w, nil
+}
+
+// Close closes a Watcher.
+// It sends a message to the reader goroutine to quit and removes all watches
+// associated with the watcher.
+func (w *Watcher) Close() os.Error {
+ if w.isClosed {
+ return nil
+ }
+ w.isClosed = true
+
+ // Send "quit" message to the reader goroutine
+ ch := make(chan os.Error)
+ w.quit <- ch
+ if err := w.wakeupReader(); err != nil {
+ return err
+ }
+ return <-ch
+}
+
+// AddWatch adds path to the watched file set.
+func (w *Watcher) AddWatch(path string, flags uint32) os.Error {
+ if w.isClosed {
+ return os.NewError("watcher already closed")
+ }
+ in := &input{
+ op: opAddWatch,
+ path: filepath.Clean(path),
+ flags: flags,
+ reply: make(chan os.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.
+func (w *Watcher) Watch(path string) os.Error {
+ return w.AddWatch(path, FS_ALL_EVENTS)
+}
+
+// RemoveWatch removes path from the watched file set.
+func (w *Watcher) RemoveWatch(path string) os.Error {
+ in := &input{
+ op: opRemoveWatch,
+ path: filepath.Clean(path),
+ reply: make(chan os.Error),
+ }
+ w.input <- in
+ if err := w.wakeupReader(); err != nil {
+ return err
+ }
+ return <-in.reply
+}
+
+func (w *Watcher) wakeupReader() os.Error {
+ e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
+ if e != 0 {
+ return os.NewSyscallError("PostQueuedCompletionStatus", e)
+ }
+ return nil
+}
+
+func getDir(pathname string) (dir string, err os.Error) {
+ attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
+ if e != 0 {
+ 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 os.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 != 0 {
+ return nil, os.NewSyscallError("CreateFile", e)
+ }
+ var fi syscall.ByHandleFileInformation
+ if e = syscall.GetFileInformationByHandle(h, &fi); e != 0 {
+ 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.
+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.
+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.
+func (w *Watcher) addWatch(pathname string, flags uint64) os.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 != 0 {
+ 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.
+func (w *Watcher) removeWatch(pathname string) os.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.
+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.
+func (w *Watcher) startRead(watch *watch) os.Error {
+ if e := syscall.CancelIo(watch.ino.handle); e != 0 {
+ 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 != 0 {
+ 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 != 0 {
+ err := os.NewSyscallError("ReadDirectoryChanges", e)
+ if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
+ // Watched directory was probably removed
+ 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
+// received events into Event objects and sends them via the Event channel.
+// Entry point to the I/O thread.
+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 os.Error
+ if e := syscall.CloseHandle(w.port); e != 0 {
+ err = os.NewSyscallError("CloseHandle", e)
+ }
+ close(w.Event)
+ 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.removeWatch(in.path)
+ }
+ default:
+ }
+ continue
+ }
+
+ switch e {
+ case syscall.ERROR_ACCESS_DENIED:
+ // Watched directory was probably removed
+ 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
+ continue
+ default:
+ w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e)
+ continue
+ case 0:
+ }
+
+ var offset uint32
+ for {
+ if n == 0 {
+ w.Event <- &Event{Mask: FS_Q_OVERFLOW}
+ w.Error <- os.NewError("short read in readEvents()")
+ break
+ }
+
+ // Point "raw" to the event in the buffer
+ 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
+ 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 := &Event{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
+}
+
+// String formats the event e in the form
+// "filename: 0xEventMask = FS_ACCESS|FS_ATTRIB_|..."
+func (e *Event) String() string {
+ var events string
+ m := e.Mask
+ for _, b := range eventBits {
+ if m&b.Value != 0 {
+ m &^= b.Value
+ events += "|" + b.Name
+ }
+ }
+ if m != 0 {
+ events += fmt.Sprintf("|%#x", m)
+ }
+ if len(events) > 0 {
+ events = " == " + events[1:]
+ }
+ return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)
+}
+
+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
+ FS_ONESHOT = 0x80000000
+ FS_ONLYDIR = 0x1000000
+
+ // Events
+ 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
+ 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"},
+}
diff --git a/src/pkg/exp/winfsnotify/winfsnotify_test.go b/src/pkg/exp/winfsnotify/winfsnotify_test.go
new file mode 100644
index 0000000000..edf2165c0e
--- /dev/null
+++ b/src/pkg/exp/winfsnotify/winfsnotify_test.go
@@ -0,0 +1,124 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package winfsnotify
+
+import (
+ "os"
+ "time"
+ "testing"
+)
+
+func expect(t *testing.T, eventstream <-chan *Event, name string, mask uint32) {
+ t.Logf(`expected: "%s": 0x%x`, name, mask)
+ select {
+ case event := <-eventstream:
+ if event == nil {
+ t.Fatal("nil event received")
+ }
+ t.Logf("received: %s", event)
+ if event.Name != name || event.Mask != mask {
+ t.Fatal("did not receive expected event")
+ }
+ case <-time.After(1e9):
+ t.Fatal("timed out waiting for event")
+ }
+}
+
+func TestNotifyEvents(t *testing.T) {
+ watcher, err := NewWatcher()
+ if err != nil {
+ t.Fatalf("NewWatcher() failed: %s", err)
+ }
+
+ testDir := "TestNotifyEvents.testdirectory"
+ testFile := testDir + "/TestNotifyEvents.testfile"
+ testFile2 := testFile + ".new"
+ const mask = FS_ALL_EVENTS & ^(FS_ATTRIB|FS_CLOSE) | FS_IGNORED
+
+ // Add a watch for testDir
+ os.RemoveAll(testDir)
+ if err = os.Mkdir(testDir, 0777); err != nil {
+ t.Fatalf("Failed to create test directory", err)
+ }
+ defer os.RemoveAll(testDir)
+ err = watcher.AddWatch(testDir, mask)
+ if err != nil {
+ t.Fatalf("Watcher.Watch() failed: %s", err)
+ }
+
+ // Receive errors on the error channel on a separate goroutine
+ go func() {
+ for err := range watcher.Error {
+ t.Fatalf("error received: %s", err)
+ }
+ }()
+
+ // Create a file
+ file, err := os.Create(testFile)
+ if err != nil {
+ t.Fatalf("creating test file failed: %s", err)
+ }
+ expect(t, watcher.Event, testFile, FS_CREATE)
+
+ err = watcher.AddWatch(testFile, mask)
+ if err != nil {
+ t.Fatalf("Watcher.Watch() failed: %s", err)
+ }
+
+ if _, err = file.WriteString("hello, world"); err != nil {
+ t.Fatalf("failed to write to test file: %s", err)
+ }
+ if err = file.Sync(); err != nil {
+ t.Fatalf("failed to sync test file: %s", err)
+ }
+ expect(t, watcher.Event, testFile, FS_MODIFY)
+ expect(t, watcher.Event, testFile, FS_MODIFY)
+
+ if err = file.Close(); err != nil {
+ t.Fatalf("failed to close test file: %s", err)
+ }
+
+ if err = os.Rename(testFile, testFile2); err != nil {
+ t.Fatalf("failed to rename test file: %s", err)
+ }
+ expect(t, watcher.Event, testFile, FS_MOVED_FROM)
+ expect(t, watcher.Event, testFile2, FS_MOVED_TO)
+ expect(t, watcher.Event, testFile, FS_MOVE_SELF)
+
+ if err = os.RemoveAll(testDir); err != nil {
+ t.Fatalf("failed to remove test directory: %s", err)
+ }
+ expect(t, watcher.Event, testFile2, FS_DELETE_SELF)
+ expect(t, watcher.Event, testFile2, FS_IGNORED)
+ expect(t, watcher.Event, testFile2, FS_DELETE)
+ expect(t, watcher.Event, testDir, FS_DELETE_SELF)
+ expect(t, watcher.Event, testDir, FS_IGNORED)
+
+ t.Log("calling Close()")
+ if err = watcher.Close(); err != nil {
+ t.Fatalf("failed to close watcher: %s", err)
+ }
+}
+
+func TestNotifyClose(t *testing.T) {
+ watcher, _ := NewWatcher()
+ watcher.Close()
+
+ done := false
+ go func() {
+ watcher.Close()
+ done = true
+ }()
+
+ time.Sleep(50e6) // 50 ms
+ if !done {
+ t.Fatal("double Close() test failed: second Close() call didn't return")
+ }
+
+ err := watcher.Watch("_test")
+ if err == nil {
+ t.Fatal("expected error on Watch() after Close(), got nil")
+ }
+}
diff --git a/src/pkg/syscall/syscall_windows.go b/src/pkg/syscall/syscall_windows.go
index 77634bf535..25e90eb6f3 100644
--- a/src/pkg/syscall/syscall_windows.go
+++ b/src/pkg/syscall/syscall_windows.go
@@ -114,6 +114,7 @@ func NewCallback(fn interface{}) uintptr
//sys GetTimeZoneInformation(tzi *Timezoneinformation) (rc uint32, errno int) [failretval==0xffffffff]
//sys CreateIoCompletionPort(filehandle Handle, cphandle Handle, key uint32, threadcnt uint32) (handle Handle, errno int)
//sys GetQueuedCompletionStatus(cphandle Handle, qty *uint32, key *uint32, overlapped **Overlapped, timeout uint32) (errno int)
+//sys PostQueuedCompletionStatus(cphandle Handle, qty uint32, key uint32, overlapped *Overlapped) (errno int)
//sys CancelIo(s Handle) (errno int)
//sys CreateProcess(appName *uint16, commandLine *uint16, procSecurity *SecurityAttributes, threadSecurity *SecurityAttributes, inheritHandles bool, creationFlags uint32, env *uint16, currentDir *uint16, startupInfo *StartupInfo, outProcInfo *ProcessInformation) (errno int) = CreateProcessW
//sys OpenProcess(da uint32, inheritHandle bool, pid uint32) (handle Handle, errno int)
@@ -150,6 +151,7 @@ func NewCallback(fn interface{}) uintptr
//sys VirtualLock(addr uintptr, length uintptr) (errno int)
//sys VirtualUnlock(addr uintptr, length uintptr) (errno int)
//sys TransmitFile(s Handle, handle Handle, bytesToWrite uint32, bytsPerSend uint32, overlapped *Overlapped, transmitFileBuf *TransmitFileBuffers, flags uint32) (errno int) = mswsock.TransmitFile
+//sys ReadDirectoryChanges(handle Handle, buf *byte, buflen uint32, watchSubTree bool, mask uint32, retlen *uint32, overlapped *Overlapped, completionRoutine uintptr) (errno int) = kernel32.ReadDirectoryChangesW
//sys CertOpenSystemStore(hprov Handle, name *uint16) (store Handle, errno int) = crypt32.CertOpenSystemStoreW
//sys CertEnumCertificatesInStore(store Handle, prevContext *CertContext) (context *CertContext) = crypt32.CertEnumCertificatesInStore
//sys CertCloseStore(store Handle, flags uint32) (errno int) = crypt32.CertCloseStore
diff --git a/src/pkg/syscall/zsyscall_windows_386.go b/src/pkg/syscall/zsyscall_windows_386.go
index fa12ce3c71..e519fe3c5b 100644
--- a/src/pkg/syscall/zsyscall_windows_386.go
+++ b/src/pkg/syscall/zsyscall_windows_386.go
@@ -45,6 +45,7 @@ var (
procGetTimeZoneInformation = modkernel32.NewProc("GetTimeZoneInformation")
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
+ procPostQueuedCompletionStatus = modkernel32.NewProc("PostQueuedCompletionStatus")
procCancelIo = modkernel32.NewProc("CancelIo")
procCreateProcessW = modkernel32.NewProc("CreateProcessW")
procOpenProcess = modkernel32.NewProc("OpenProcess")
@@ -81,6 +82,7 @@ var (
procVirtualLock = modkernel32.NewProc("VirtualLock")
procVirtualUnlock = modkernel32.NewProc("VirtualUnlock")
procTransmitFile = modmswsock.NewProc("TransmitFile")
+ procReadDirectoryChangesW = modkernel32.NewProc("ReadDirectoryChangesW")
procCertOpenSystemStoreW = modcrypt32.NewProc("CertOpenSystemStoreW")
procCertEnumCertificatesInStore = modcrypt32.NewProc("CertEnumCertificatesInStore")
procCertCloseStore = modcrypt32.NewProc("CertCloseStore")
@@ -520,6 +522,20 @@ func GetQueuedCompletionStatus(cphandle Handle, qty *uint32, key *uint32, overla
return
}
+func PostQueuedCompletionStatus(cphandle Handle, qty uint32, key uint32, overlapped *Overlapped) (errno int) {
+ r1, _, e1 := Syscall6(procPostQueuedCompletionStatus.Addr(), 4, uintptr(cphandle), uintptr(qty), uintptr(key), uintptr(unsafe.Pointer(overlapped)), 0, 0)
+ if int(r1) == 0 {
+ if e1 != 0 {
+ errno = int(e1)
+ } else {
+ errno = EINVAL
+ }
+ } else {
+ errno = 0
+ }
+ return
+}
+
func CancelIo(s Handle) (errno int) {
r1, _, e1 := Syscall(procCancelIo.Addr(), 1, uintptr(s), 0, 0)
if int(r1) == 0 {
@@ -1047,6 +1063,26 @@ func TransmitFile(s Handle, handle Handle, bytesToWrite uint32, bytsPerSend uint
return
}
+func ReadDirectoryChanges(handle Handle, buf *byte, buflen uint32, watchSubTree bool, mask uint32, retlen *uint32, overlapped *Overlapped, completionRoutine uintptr) (errno int) {
+ var _p0 uint32
+ if watchSubTree {
+ _p0 = 1
+ } else {
+ _p0 = 0
+ }
+ r1, _, e1 := Syscall9(procReadDirectoryChangesW.Addr(), 8, uintptr(handle), uintptr(unsafe.Pointer(buf)), uintptr(buflen), uintptr(_p0), uintptr(mask), uintptr(unsafe.Pointer(retlen)), uintptr(unsafe.Pointer(overlapped)), uintptr(completionRoutine), 0)
+ if int(r1) == 0 {
+ if e1 != 0 {
+ errno = int(e1)
+ } else {
+ errno = EINVAL
+ }
+ } else {
+ errno = 0
+ }
+ return
+}
+
func CertOpenSystemStore(hprov Handle, name *uint16) (store Handle, errno int) {
r0, _, e1 := Syscall(procCertOpenSystemStoreW.Addr(), 2, uintptr(hprov), uintptr(unsafe.Pointer(name)), 0)
store = Handle(r0)
diff --git a/src/pkg/syscall/zsyscall_windows_amd64.go b/src/pkg/syscall/zsyscall_windows_amd64.go
index 1d9a1f8736..f7dc79d3fb 100644
--- a/src/pkg/syscall/zsyscall_windows_amd64.go
+++ b/src/pkg/syscall/zsyscall_windows_amd64.go
@@ -45,6 +45,7 @@ var (
procGetTimeZoneInformation = modkernel32.NewProc("GetTimeZoneInformation")
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
+ procPostQueuedCompletionStatus = modkernel32.NewProc("PostQueuedCompletionStatus")
procCancelIo = modkernel32.NewProc("CancelIo")
procCreateProcessW = modkernel32.NewProc("CreateProcessW")
procOpenProcess = modkernel32.NewProc("OpenProcess")
@@ -81,6 +82,7 @@ var (
procVirtualLock = modkernel32.NewProc("VirtualLock")
procVirtualUnlock = modkernel32.NewProc("VirtualUnlock")
procTransmitFile = modmswsock.NewProc("TransmitFile")
+ procReadDirectoryChangesW = modkernel32.NewProc("ReadDirectoryChangesW")
procCertOpenSystemStoreW = modcrypt32.NewProc("CertOpenSystemStoreW")
procCertEnumCertificatesInStore = modcrypt32.NewProc("CertEnumCertificatesInStore")
procCertCloseStore = modcrypt32.NewProc("CertCloseStore")
@@ -520,6 +522,20 @@ func GetQueuedCompletionStatus(cphandle Handle, qty *uint32, key *uint32, overla
return
}
+func PostQueuedCompletionStatus(cphandle Handle, qty uint32, key uint32, overlapped *Overlapped) (errno int) {
+ r1, _, e1 := Syscall6(procPostQueuedCompletionStatus.Addr(), 4, uintptr(cphandle), uintptr(qty), uintptr(key), uintptr(unsafe.Pointer(overlapped)), 0, 0)
+ if int(r1) == 0 {
+ if e1 != 0 {
+ errno = int(e1)
+ } else {
+ errno = EINVAL
+ }
+ } else {
+ errno = 0
+ }
+ return
+}
+
func CancelIo(s Handle) (errno int) {
r1, _, e1 := Syscall(procCancelIo.Addr(), 1, uintptr(s), 0, 0)
if int(r1) == 0 {
@@ -1047,6 +1063,26 @@ func TransmitFile(s Handle, handle Handle, bytesToWrite uint32, bytsPerSend uint
return
}
+func ReadDirectoryChanges(handle Handle, buf *byte, buflen uint32, watchSubTree bool, mask uint32, retlen *uint32, overlapped *Overlapped, completionRoutine uintptr) (errno int) {
+ var _p0 uint32
+ if watchSubTree {
+ _p0 = 1
+ } else {
+ _p0 = 0
+ }
+ r1, _, e1 := Syscall9(procReadDirectoryChangesW.Addr(), 8, uintptr(handle), uintptr(unsafe.Pointer(buf)), uintptr(buflen), uintptr(_p0), uintptr(mask), uintptr(unsafe.Pointer(retlen)), uintptr(unsafe.Pointer(overlapped)), uintptr(completionRoutine), 0)
+ if int(r1) == 0 {
+ if e1 != 0 {
+ errno = int(e1)
+ } else {
+ errno = EINVAL
+ }
+ } else {
+ errno = 0
+ }
+ return
+}
+
func CertOpenSystemStore(hprov Handle, name *uint16) (store Handle, errno int) {
r0, _, e1 := Syscall(procCertOpenSystemStoreW.Addr(), 2, uintptr(hprov), uintptr(unsafe.Pointer(name)), 0)
store = Handle(r0)
diff --git a/src/pkg/syscall/ztypes_windows.go b/src/pkg/syscall/ztypes_windows.go
index 9db81edbe2..c8db2ee785 100644
--- a/src/pkg/syscall/ztypes_windows.go
+++ b/src/pkg/syscall/ztypes_windows.go
@@ -4,6 +4,7 @@ const (
// Windows errors.
ERROR_FILE_NOT_FOUND = 2
ERROR_PATH_NOT_FOUND = 3
+ ERROR_ACCESS_DENIED = 5
ERROR_NO_MORE_FILES = 18
ERROR_BROKEN_PIPE = 109
ERROR_BUFFER_OVERFLOW = 111
@@ -54,6 +55,7 @@ const (
GENERIC_EXECUTE = 0x20000000
GENERIC_ALL = 0x10000000
+ FILE_LIST_DIRECTORY = 0x00000001
FILE_APPEND_DATA = 0x00000004
FILE_WRITE_ATTRIBUTES = 0x00000100
@@ -75,6 +77,9 @@ const (
OPEN_ALWAYS = 4
TRUNCATE_EXISTING = 5
+ FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
+ FILE_FLAG_OVERLAPPED = 0x40000000
+
HANDLE_FLAG_INHERIT = 0x00000001
STARTF_USESTDHANDLES = 0x00000100
STARTF_USESHOWWINDOW = 0x00000001
@@ -134,6 +139,24 @@ const (
)
const (
+ FILE_NOTIFY_CHANGE_FILE_NAME = 1 << iota
+ FILE_NOTIFY_CHANGE_DIR_NAME
+ FILE_NOTIFY_CHANGE_ATTRIBUTES
+ FILE_NOTIFY_CHANGE_SIZE
+ FILE_NOTIFY_CHANGE_LAST_WRITE
+ FILE_NOTIFY_CHANGE_LAST_ACCESS
+ FILE_NOTIFY_CHANGE_CREATION
+)
+
+const (
+ FILE_ACTION_ADDED = iota + 1
+ FILE_ACTION_REMOVED
+ FILE_ACTION_MODIFIED
+ FILE_ACTION_RENAMED_OLD_NAME
+ FILE_ACTION_RENAMED_NEW_NAME
+)
+
+const (
// wincrypt.h
PROV_RSA_FULL = 1
PROV_RSA_SIG = 2
@@ -191,6 +214,13 @@ type Overlapped struct {
HEvent Handle
}
+type FileNotifyInformation struct {
+ NextEntryOffset uint32
+ Action uint32
+ FileNameLength uint32
+ FileName uint16
+}
+
type Filetime struct {
LowDateTime uint32
HighDateTime uint32