]> Cypherpunks repositories - gostls13.git/commitdiff
exp/winfsnotify: filesystem watcher for Windows
authorHector Chu <hectorchu@gmail.com>
Tue, 18 Oct 2011 20:09:58 +0000 (21:09 +0100)
committerHector Chu <hectorchu@gmail.com>
Tue, 18 Oct 2011 20:09:58 +0000 (21:09 +0100)
R=rsc, alex.brainman, bradfitz
CC=bsiegert, go.peter.90, golang-dev
https://golang.org/cl/4188047

src/pkg/exp/winfsnotify/Makefile [new file with mode: 0644]
src/pkg/exp/winfsnotify/winfsnotify.go [new file with mode: 0644]
src/pkg/exp/winfsnotify/winfsnotify_test.go [new file with mode: 0644]
src/pkg/syscall/syscall_windows.go
src/pkg/syscall/zsyscall_windows_386.go
src/pkg/syscall/zsyscall_windows_amd64.go
src/pkg/syscall/ztypes_windows.go

diff --git a/src/pkg/exp/winfsnotify/Makefile b/src/pkg/exp/winfsnotify/Makefile
new file mode 100644 (file)
index 0000000..f0fe096
--- /dev/null
@@ -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 (file)
index 0000000..c5dfe99
--- /dev/null
@@ -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 (file)
index 0000000..edf2165
--- /dev/null
@@ -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")
+       }
+}
index 77634bf5351bd831e639df6beb30a47fe85f8980..25e90eb6f3696c8ae1edb434918a44c1772ce19b 100644 (file)
@@ -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
index fa12ce3c71b3df4e916740ab00d55ad2ca358745..e519fe3c5b24afd7453e64fc13d747923a0b9864 100644 (file)
@@ -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)
index 1d9a1f87368e6081e2d1749b3c278f9432f12b43..f7dc79d3fbcc6e678c77ac3d6f5312d6bdd60d87 100644 (file)
@@ -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)
index 9db81edbe27a9ecbc92a351b9d758f5a2a5b0f55..c8db2ee785bb32fbb9647cd7d7acb6335762a5e3 100644 (file)
@@ -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
@@ -133,6 +138,24 @@ const (
        FILE_MAP_EXECUTE = 0x20
 )
 
+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
@@ -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