mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-03 04:16:42 +00:00
Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
828 lines
23 KiB
Go
828 lines
23 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
// Windows backend based on ReadDirectoryChangesW()
|
|
//
|
|
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
|
|
//
|
|
// Note: the documentation on the Watcher type and methods is generated from
|
|
// mkdoc.zsh
|
|
|
|
package fsnotify
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// Watcher watches a set of paths, delivering events on a channel.
|
|
//
|
|
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
|
// value).
|
|
//
|
|
// # Linux notes
|
|
//
|
|
// When a file is removed a Remove event won't be emitted until all file
|
|
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
|
//
|
|
// fp := os.Open("file")
|
|
// os.Remove("file") // Triggers Chmod
|
|
// fp.Close() // Triggers Remove
|
|
//
|
|
// This is the event that inotify sends, so not much can be changed about this.
|
|
//
|
|
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
|
// for the number of watches per user, and fs.inotify.max_user_instances
|
|
// specifies the maximum number of inotify instances per user. Every Watcher you
|
|
// create is an "instance", and every path you add is a "watch".
|
|
//
|
|
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
|
// /proc/sys/fs/inotify/max_user_instances
|
|
//
|
|
// To increase them you can use sysctl or write the value to the /proc file:
|
|
//
|
|
// # Default values on Linux 5.18
|
|
// sysctl fs.inotify.max_user_watches=124983
|
|
// sysctl fs.inotify.max_user_instances=128
|
|
//
|
|
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
|
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
|
// your distro's documentation):
|
|
//
|
|
// fs.inotify.max_user_watches=124983
|
|
// fs.inotify.max_user_instances=128
|
|
//
|
|
// Reaching the limit will result in a "no space left on device" or "too many open
|
|
// files" error.
|
|
//
|
|
// # kqueue notes (macOS, BSD)
|
|
//
|
|
// kqueue requires opening a file descriptor for every file that's being watched;
|
|
// so if you're watching a directory with five files then that's six file
|
|
// descriptors. You will run in to your system's "max open files" limit faster on
|
|
// these platforms.
|
|
//
|
|
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
|
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
|
// systems.
|
|
//
|
|
// # Windows notes
|
|
//
|
|
// Paths can be added as "C:\path\to\dir", but forward slashes
|
|
// ("C:/path/to/dir") will also work.
|
|
//
|
|
// When a watched directory is removed it will always send an event for the
|
|
// directory itself, but may not send events for all files in that directory.
|
|
// Sometimes it will send events for all times, sometimes it will send no
|
|
// events, and often only for some files.
|
|
//
|
|
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
|
|
// value that is guaranteed to work with SMB filesystems. If you have many
|
|
// events in quick succession this may not be enough, and you will have to use
|
|
// [WithBufferSize] to increase the value.
|
|
type Watcher struct {
|
|
// Events sends the filesystem change events.
|
|
//
|
|
// fsnotify can send the following events; a "path" here can refer to a
|
|
// file, directory, symbolic link, or special file like a FIFO.
|
|
//
|
|
// fsnotify.Create A new path was created; this may be followed by one
|
|
// or more Write events if data also gets written to a
|
|
// file.
|
|
//
|
|
// fsnotify.Remove A path was removed.
|
|
//
|
|
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
|
// old path as Event.Name, and a Create event will be
|
|
// sent with the new name. Renames are only sent for
|
|
// paths that are currently watched; e.g. moving an
|
|
// unmonitored file into a monitored directory will
|
|
// show up as just a Create. Similarly, renaming a file
|
|
// to outside a monitored directory will show up as
|
|
// only a Rename.
|
|
//
|
|
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
|
// also trigger a Write. A single "write action"
|
|
// initiated by the user may show up as one or multiple
|
|
// writes, depending on when the system syncs things to
|
|
// disk. For example when compiling a large Go program
|
|
// you may get hundreds of Write events, and you may
|
|
// want to wait until you've stopped receiving them
|
|
// (see the dedup example in cmd/fsnotify).
|
|
//
|
|
// Some systems may send Write event for directories
|
|
// when the directory content changes.
|
|
//
|
|
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
|
// when a file is removed (or more accurately, when a
|
|
// link to an inode is removed). On kqueue it's sent
|
|
// when a file is truncated. On Windows it's never
|
|
// sent.
|
|
Events chan Event
|
|
|
|
// Errors sends any errors.
|
|
//
|
|
// ErrEventOverflow is used to indicate there are too many events:
|
|
//
|
|
// - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
|
|
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
|
|
// - kqueue, fen: Not used.
|
|
Errors chan error
|
|
|
|
port windows.Handle // Handle to completion port
|
|
input chan *input // Inputs to the reader are sent on this channel
|
|
quit chan chan<- error
|
|
|
|
mu sync.Mutex // Protects access to watches, closed
|
|
watches watchMap // Map of watches (key: i-number)
|
|
closed bool // Set to true when Close() is first called
|
|
}
|
|
|
|
// NewWatcher creates a new Watcher.
|
|
func NewWatcher() (*Watcher, error) {
|
|
return NewBufferedWatcher(50)
|
|
}
|
|
|
|
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
|
|
// channel.
|
|
//
|
|
// The main use case for this is situations with a very large number of events
|
|
// where the kernel buffer size can't be increased (e.g. due to lack of
|
|
// permissions). An unbuffered Watcher will perform better for almost all use
|
|
// cases, and whenever possible you will be better off increasing the kernel
|
|
// buffers instead of adding a large userspace buffer.
|
|
func NewBufferedWatcher(sz uint) (*Watcher, error) {
|
|
port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
|
|
if err != nil {
|
|
return nil, os.NewSyscallError("CreateIoCompletionPort", err)
|
|
}
|
|
w := &Watcher{
|
|
port: port,
|
|
watches: make(watchMap),
|
|
input: make(chan *input, 1),
|
|
Events: make(chan Event, sz),
|
|
Errors: make(chan error),
|
|
quit: make(chan chan<- error, 1),
|
|
}
|
|
go w.readEvents()
|
|
return w, nil
|
|
}
|
|
|
|
func (w *Watcher) isClosed() bool {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
return w.closed
|
|
}
|
|
|
|
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
|
if mask == 0 {
|
|
return false
|
|
}
|
|
|
|
event := w.newEvent(name, uint32(mask))
|
|
select {
|
|
case ch := <-w.quit:
|
|
w.quit <- ch
|
|
case w.Events <- event:
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Returns true if the error was sent, or false if watcher is closed.
|
|
func (w *Watcher) sendError(err error) bool {
|
|
select {
|
|
case w.Errors <- err:
|
|
return true
|
|
case <-w.quit:
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Close removes all watches and closes the Events channel.
|
|
func (w *Watcher) Close() error {
|
|
if w.isClosed() {
|
|
return nil
|
|
}
|
|
|
|
w.mu.Lock()
|
|
w.closed = true
|
|
w.mu.Unlock()
|
|
|
|
// Send "quit" message to the reader goroutine
|
|
ch := make(chan error)
|
|
w.quit <- ch
|
|
if err := w.wakeupReader(); err != nil {
|
|
return err
|
|
}
|
|
return <-ch
|
|
}
|
|
|
|
// Add starts monitoring the path for changes.
|
|
//
|
|
// A path can only be watched once; watching it more than once is a no-op and will
|
|
// not return an error. Paths that do not yet exist on the filesystem cannot be
|
|
// watched.
|
|
//
|
|
// A watch will be automatically removed if the watched path is deleted or
|
|
// renamed. The exception is the Windows backend, which doesn't remove the
|
|
// watcher on renames.
|
|
//
|
|
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
|
// filesystems (/proc, /sys, etc.) generally don't work.
|
|
//
|
|
// Returns [ErrClosed] if [Watcher.Close] was called.
|
|
//
|
|
// See [Watcher.AddWith] for a version that allows adding options.
|
|
//
|
|
// # Watching directories
|
|
//
|
|
// All files in a directory are monitored, including new files that are created
|
|
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
|
// non-recursive).
|
|
//
|
|
// # Watching files
|
|
//
|
|
// Watching individual files (rather than directories) is generally not
|
|
// recommended as many programs (especially editors) update files atomically: it
|
|
// will write to a temporary file which is then moved to to destination,
|
|
// overwriting the original (or some variant thereof). The watcher on the
|
|
// original file is now lost, as that no longer exists.
|
|
//
|
|
// The upshot of this is that a power failure or crash won't leave a
|
|
// half-written file.
|
|
//
|
|
// Watch the parent directory and use Event.Name to filter out files you're not
|
|
// interested in. There is an example of this in cmd/fsnotify/file.go.
|
|
func (w *Watcher) Add(name string) error { return w.AddWith(name) }
|
|
|
|
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
|
|
// the defaults described below are used.
|
|
//
|
|
// Possible options are:
|
|
//
|
|
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
|
|
// other platforms. The default is 64K (65536 bytes).
|
|
func (w *Watcher) AddWith(name string, opts ...addOpt) error {
|
|
if w.isClosed() {
|
|
return ErrClosed
|
|
}
|
|
|
|
with := getOptions(opts...)
|
|
if with.bufsize < 4096 {
|
|
return fmt.Errorf("fsnotify.WithBufferSize: buffer size cannot be smaller than 4096 bytes")
|
|
}
|
|
|
|
in := &input{
|
|
op: opAddWatch,
|
|
path: filepath.Clean(name),
|
|
flags: sysFSALLEVENTS,
|
|
reply: make(chan error),
|
|
bufsize: with.bufsize,
|
|
}
|
|
w.input <- in
|
|
if err := w.wakeupReader(); err != nil {
|
|
return err
|
|
}
|
|
return <-in.reply
|
|
}
|
|
|
|
// Remove stops monitoring the path for changes.
|
|
//
|
|
// Directories are always removed non-recursively. For example, if you added
|
|
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
|
//
|
|
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
|
//
|
|
// Returns nil if [Watcher.Close] was called.
|
|
func (w *Watcher) Remove(name string) error {
|
|
if w.isClosed() {
|
|
return nil
|
|
}
|
|
|
|
in := &input{
|
|
op: opRemoveWatch,
|
|
path: filepath.Clean(name),
|
|
reply: make(chan error),
|
|
}
|
|
w.input <- in
|
|
if err := w.wakeupReader(); err != nil {
|
|
return err
|
|
}
|
|
return <-in.reply
|
|
}
|
|
|
|
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
|
|
// yet removed).
|
|
//
|
|
// Returns nil if [Watcher.Close] was called.
|
|
func (w *Watcher) WatchList() []string {
|
|
if w.isClosed() {
|
|
return nil
|
|
}
|
|
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
entries := make([]string, 0, len(w.watches))
|
|
for _, entry := range w.watches {
|
|
for _, watchEntry := range entry {
|
|
entries = append(entries, watchEntry.path)
|
|
}
|
|
}
|
|
|
|
return entries
|
|
}
|
|
|
|
// These options are from the old golang.org/x/exp/winfsnotify, where you could
|
|
// add various options to the watch. This has long since been removed.
|
|
//
|
|
// The "sys" in the name is misleading as they're not part of any "system".
|
|
//
|
|
// This should all be removed at some point, and just use windows.FILE_NOTIFY_*
|
|
const (
|
|
sysFSALLEVENTS = 0xfff
|
|
sysFSCREATE = 0x100
|
|
sysFSDELETE = 0x200
|
|
sysFSDELETESELF = 0x400
|
|
sysFSMODIFY = 0x2
|
|
sysFSMOVE = 0xc0
|
|
sysFSMOVEDFROM = 0x40
|
|
sysFSMOVEDTO = 0x80
|
|
sysFSMOVESELF = 0x800
|
|
sysFSIGNORED = 0x8000
|
|
)
|
|
|
|
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
|
e := Event{Name: name}
|
|
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
|
e.Op |= Create
|
|
}
|
|
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
|
e.Op |= Remove
|
|
}
|
|
if mask&sysFSMODIFY == sysFSMODIFY {
|
|
e.Op |= Write
|
|
}
|
|
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
|
e.Op |= Rename
|
|
}
|
|
return e
|
|
}
|
|
|
|
const (
|
|
opAddWatch = iota
|
|
opRemoveWatch
|
|
)
|
|
|
|
const (
|
|
provisional uint64 = 1 << (32 + iota)
|
|
)
|
|
|
|
type input struct {
|
|
op int
|
|
path string
|
|
flags uint32
|
|
bufsize int
|
|
reply chan error
|
|
}
|
|
|
|
type inode struct {
|
|
handle windows.Handle
|
|
volume uint32
|
|
index uint64
|
|
}
|
|
|
|
type watch struct {
|
|
ov windows.Overlapped
|
|
ino *inode // i-number
|
|
recurse bool // Recursive watch?
|
|
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 []byte // buffer, allocated later
|
|
}
|
|
|
|
type (
|
|
indexMap map[uint64]*watch
|
|
watchMap map[uint32]indexMap
|
|
)
|
|
|
|
func (w *Watcher) wakeupReader() error {
|
|
err := windows.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
|
if err != nil {
|
|
return os.NewSyscallError("PostQueuedCompletionStatus", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *Watcher) getDir(pathname string) (dir string, err error) {
|
|
attr, err := windows.GetFileAttributes(windows.StringToUTF16Ptr(pathname))
|
|
if err != nil {
|
|
return "", os.NewSyscallError("GetFileAttributes", err)
|
|
}
|
|
if attr&windows.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
|
dir = pathname
|
|
} else {
|
|
dir, _ = filepath.Split(pathname)
|
|
dir = filepath.Clean(dir)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (w *Watcher) getIno(path string) (ino *inode, err error) {
|
|
h, err := windows.CreateFile(windows.StringToUTF16Ptr(path),
|
|
windows.FILE_LIST_DIRECTORY,
|
|
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
|
|
nil, windows.OPEN_EXISTING,
|
|
windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OVERLAPPED, 0)
|
|
if err != nil {
|
|
return nil, os.NewSyscallError("CreateFile", err)
|
|
}
|
|
|
|
var fi windows.ByHandleFileInformation
|
|
err = windows.GetFileInformationByHandle(h, &fi)
|
|
if err != nil {
|
|
windows.CloseHandle(h)
|
|
return nil, os.NewSyscallError("GetFileInformationByHandle", err)
|
|
}
|
|
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, bufsize int) error {
|
|
//pathname, recurse := recursivePath(pathname)
|
|
recurse := false
|
|
|
|
dir, err := w.getDir(pathname)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ino, err := w.getIno(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.mu.Lock()
|
|
watchEntry := w.watches.get(ino)
|
|
w.mu.Unlock()
|
|
if watchEntry == nil {
|
|
_, err := windows.CreateIoCompletionPort(ino.handle, w.port, 0, 0)
|
|
if err != nil {
|
|
windows.CloseHandle(ino.handle)
|
|
return os.NewSyscallError("CreateIoCompletionPort", err)
|
|
}
|
|
watchEntry = &watch{
|
|
ino: ino,
|
|
path: dir,
|
|
names: make(map[string]uint64),
|
|
recurse: recurse,
|
|
buf: make([]byte, bufsize),
|
|
}
|
|
w.mu.Lock()
|
|
w.watches.set(ino, watchEntry)
|
|
w.mu.Unlock()
|
|
flags |= provisional
|
|
} else {
|
|
windows.CloseHandle(ino.handle)
|
|
}
|
|
if pathname == dir {
|
|
watchEntry.mask |= flags
|
|
} else {
|
|
watchEntry.names[filepath.Base(pathname)] |= flags
|
|
}
|
|
|
|
err = w.startRead(watchEntry)
|
|
if 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) remWatch(pathname string) error {
|
|
pathname, recurse := recursivePath(pathname)
|
|
|
|
dir, err := w.getDir(pathname)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ino, err := w.getIno(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w.mu.Lock()
|
|
watch := w.watches.get(ino)
|
|
w.mu.Unlock()
|
|
|
|
if recurse && !watch.recurse {
|
|
return fmt.Errorf("can't use \\... with non-recursive watch %q", pathname)
|
|
}
|
|
|
|
err = windows.CloseHandle(ino.handle)
|
|
if err != nil {
|
|
w.sendError(os.NewSyscallError("CloseHandle", err))
|
|
}
|
|
if watch == nil {
|
|
return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname)
|
|
}
|
|
if pathname == dir {
|
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
|
watch.mask = 0
|
|
} else {
|
|
name := filepath.Base(pathname)
|
|
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
|
|
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(filepath.Join(watch.path, name), mask&sysFSIGNORED)
|
|
}
|
|
delete(watch.names, name)
|
|
}
|
|
if watch.mask != 0 {
|
|
if watch.mask&provisional == 0 {
|
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
|
}
|
|
watch.mask = 0
|
|
}
|
|
}
|
|
|
|
// Must run within the I/O thread.
|
|
func (w *Watcher) startRead(watch *watch) error {
|
|
err := windows.CancelIo(watch.ino.handle)
|
|
if err != nil {
|
|
w.sendError(os.NewSyscallError("CancelIo", err))
|
|
w.deleteWatch(watch)
|
|
}
|
|
mask := w.toWindowsFlags(watch.mask)
|
|
for _, m := range watch.names {
|
|
mask |= w.toWindowsFlags(m)
|
|
}
|
|
if mask == 0 {
|
|
err := windows.CloseHandle(watch.ino.handle)
|
|
if err != nil {
|
|
w.sendError(os.NewSyscallError("CloseHandle", err))
|
|
}
|
|
w.mu.Lock()
|
|
delete(w.watches[watch.ino.volume], watch.ino.index)
|
|
w.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// We need to pass the array, rather than the slice.
|
|
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&watch.buf))
|
|
rdErr := windows.ReadDirectoryChanges(watch.ino.handle,
|
|
(*byte)(unsafe.Pointer(hdr.Data)), uint32(hdr.Len),
|
|
watch.recurse, mask, nil, &watch.ov, 0)
|
|
if rdErr != nil {
|
|
err := os.NewSyscallError("ReadDirectoryChanges", rdErr)
|
|
if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
|
// Watched directory was probably removed
|
|
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
|
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 Events channel.
|
|
// Entry point to the I/O thread.
|
|
func (w *Watcher) readEvents() {
|
|
var (
|
|
n uint32
|
|
key uintptr
|
|
ov *windows.Overlapped
|
|
)
|
|
runtime.LockOSThread()
|
|
|
|
for {
|
|
// This error is handled after the watch == nil check below.
|
|
qErr := windows.GetQueuedCompletionStatus(w.port, &n, &key, &ov, windows.INFINITE)
|
|
|
|
watch := (*watch)(unsafe.Pointer(ov))
|
|
if watch == nil {
|
|
select {
|
|
case ch := <-w.quit:
|
|
w.mu.Lock()
|
|
var indexes []indexMap
|
|
for _, index := range w.watches {
|
|
indexes = append(indexes, index)
|
|
}
|
|
w.mu.Unlock()
|
|
for _, index := range indexes {
|
|
for _, watch := range index {
|
|
w.deleteWatch(watch)
|
|
w.startRead(watch)
|
|
}
|
|
}
|
|
|
|
err := windows.CloseHandle(w.port)
|
|
if err != nil {
|
|
err = os.NewSyscallError("CloseHandle", err)
|
|
}
|
|
close(w.Events)
|
|
close(w.Errors)
|
|
ch <- err
|
|
return
|
|
case in := <-w.input:
|
|
switch in.op {
|
|
case opAddWatch:
|
|
in.reply <- w.addWatch(in.path, uint64(in.flags), in.bufsize)
|
|
case opRemoveWatch:
|
|
in.reply <- w.remWatch(in.path)
|
|
}
|
|
default:
|
|
}
|
|
continue
|
|
}
|
|
|
|
switch qErr {
|
|
case nil:
|
|
// No error
|
|
case windows.ERROR_MORE_DATA:
|
|
if watch == nil {
|
|
w.sendError(errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer"))
|
|
} else {
|
|
// The i/o succeeded but the buffer is full.
|
|
// In theory we should be building up a full packet.
|
|
// In practice we can get away with just carrying on.
|
|
n = uint32(unsafe.Sizeof(watch.buf))
|
|
}
|
|
case windows.ERROR_ACCESS_DENIED:
|
|
// Watched directory was probably removed
|
|
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
|
w.deleteWatch(watch)
|
|
w.startRead(watch)
|
|
continue
|
|
case windows.ERROR_OPERATION_ABORTED:
|
|
// CancelIo was called on this handle
|
|
continue
|
|
default:
|
|
w.sendError(os.NewSyscallError("GetQueuedCompletionPort", qErr))
|
|
continue
|
|
}
|
|
|
|
var offset uint32
|
|
for {
|
|
if n == 0 {
|
|
w.sendError(ErrEventOverflow)
|
|
break
|
|
}
|
|
|
|
// Point "raw" to the event in the buffer
|
|
raw := (*windows.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
|
|
|
// Create a buf that is the size of the path name
|
|
size := int(raw.FileNameLength / 2)
|
|
var buf []uint16
|
|
// TODO: Use unsafe.Slice in Go 1.17; https://stackoverflow.com/questions/51187973
|
|
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
|
sh.Data = uintptr(unsafe.Pointer(&raw.FileName))
|
|
sh.Len = size
|
|
sh.Cap = size
|
|
name := windows.UTF16ToString(buf)
|
|
fullname := filepath.Join(watch.path, name)
|
|
|
|
var mask uint64
|
|
switch raw.Action {
|
|
case windows.FILE_ACTION_REMOVED:
|
|
mask = sysFSDELETESELF
|
|
case windows.FILE_ACTION_MODIFIED:
|
|
mask = sysFSMODIFY
|
|
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
|
watch.rename = name
|
|
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
|
// Update saved path of all sub-watches.
|
|
old := filepath.Join(watch.path, watch.rename)
|
|
w.mu.Lock()
|
|
for _, watchMap := range w.watches {
|
|
for _, ww := range watchMap {
|
|
if strings.HasPrefix(ww.path, old) {
|
|
ww.path = filepath.Join(fullname, strings.TrimPrefix(ww.path, old))
|
|
}
|
|
}
|
|
}
|
|
w.mu.Unlock()
|
|
|
|
if watch.names[watch.rename] != 0 {
|
|
watch.names[name] |= watch.names[watch.rename]
|
|
delete(watch.names, watch.rename)
|
|
mask = sysFSMOVESELF
|
|
}
|
|
}
|
|
|
|
sendNameEvent := func() {
|
|
w.sendEvent(fullname, watch.names[name]&mask)
|
|
}
|
|
if raw.Action != windows.FILE_ACTION_RENAMED_NEW_NAME {
|
|
sendNameEvent()
|
|
}
|
|
if raw.Action == windows.FILE_ACTION_REMOVED {
|
|
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
|
|
delete(watch.names, name)
|
|
}
|
|
|
|
w.sendEvent(fullname, watch.mask&w.toFSnotifyFlags(raw.Action))
|
|
if raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
|
|
fullname = filepath.Join(watch.path, watch.rename)
|
|
sendNameEvent()
|
|
}
|
|
|
|
// Move to the next event in the buffer
|
|
if raw.NextEntryOffset == 0 {
|
|
break
|
|
}
|
|
offset += raw.NextEntryOffset
|
|
|
|
// Error!
|
|
if offset >= n {
|
|
//lint:ignore ST1005 Windows should be capitalized
|
|
w.sendError(errors.New(
|
|
"Windows system assumed buffer larger than it is, events have likely been missed"))
|
|
break
|
|
}
|
|
}
|
|
|
|
if err := w.startRead(watch); err != nil {
|
|
w.sendError(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (w *Watcher) toWindowsFlags(mask uint64) uint32 {
|
|
var m uint32
|
|
if mask&sysFSMODIFY != 0 {
|
|
m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE
|
|
}
|
|
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
|
m |= windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME
|
|
}
|
|
return m
|
|
}
|
|
|
|
func (w *Watcher) toFSnotifyFlags(action uint32) uint64 {
|
|
switch action {
|
|
case windows.FILE_ACTION_ADDED:
|
|
return sysFSCREATE
|
|
case windows.FILE_ACTION_REMOVED:
|
|
return sysFSDELETE
|
|
case windows.FILE_ACTION_MODIFIED:
|
|
return sysFSMODIFY
|
|
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
|
return sysFSMOVEDFROM
|
|
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
|
return sysFSMOVEDTO
|
|
}
|
|
return 0
|
|
}
|