From 7ecf6c997e786b4812ae5d5f6afade15a3717fa9 Mon Sep 17 00:00:00 2001 From: Hector Chu Date: Tue, 18 Oct 2011 21:09:58 +0100 Subject: [PATCH] exp/winfsnotify: filesystem watcher for Windows R=rsc, alex.brainman, bradfitz CC=bsiegert, go.peter.90, golang-dev https://golang.org/cl/4188047 --- src/pkg/exp/winfsnotify/Makefile | 11 + src/pkg/exp/winfsnotify/winfsnotify.go | 569 ++++++++++++++++++++ src/pkg/exp/winfsnotify/winfsnotify_test.go | 124 +++++ src/pkg/syscall/syscall_windows.go | 2 + src/pkg/syscall/zsyscall_windows_386.go | 36 ++ src/pkg/syscall/zsyscall_windows_amd64.go | 36 ++ src/pkg/syscall/ztypes_windows.go | 30 ++ 7 files changed, 808 insertions(+) create mode 100644 src/pkg/exp/winfsnotify/Makefile create mode 100644 src/pkg/exp/winfsnotify/winfsnotify.go create mode 100644 src/pkg/exp/winfsnotify/winfsnotify_test.go diff --git a/src/pkg/exp/winfsnotify/Makefile b/src/pkg/exp/winfsnotify/Makefile new file mode 100644 index 00000000000..f0fe096d997 --- /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 00000000000..c5dfe99ad71 --- /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 00000000000..edf2165c0ec --- /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 77634bf5351..25e90eb6f36 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 fa12ce3c71b..e519fe3c5b2 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 1d9a1f87368..f7dc79d3fbc 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 9db81edbe27..c8db2ee785b 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 @@ -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