vendor/github.com/fsnotify/fsnotify/backend_windows.go
changeset 265 05c40b36d3b2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/github.com/fsnotify/fsnotify/backend_windows.go	Sat Feb 04 12:58:35 2023 +0100
@@ -0,0 +1,746 @@
+//go:build windows
+// +build windows
+
+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.
+//
+// # macOS notes
+//
+// Spotlight indexing on macOS can result in multiple events (see [#15]). A
+// temporary workaround is to add your folder(s) to the "Spotlight Privacy
+// Settings" until we have a native FSEvents implementation (see [#11]).
+//
+// [#11]: https://github.com/fsnotify/fsnotify/issues/11
+// [#15]: https://github.com/fsnotify/fsnotify/issues/15
+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, so you
+	//                      probably want to wait until you've stopped receiving
+	//                      them (see the dedup example in cmd/fsnotify).
+	//
+	//   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
+	//                      and on kqueue when a file is truncated. On Windows
+	//                      it's never sent.
+	Events chan Event
+
+	// Errors sends any errors.
+	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, isClosed
+	watches  watchMap   // Map of watches (key: i-number)
+	isClosed bool       // Set to true when Close() is first called
+}
+
+// NewWatcher creates a new Watcher.
+func NewWatcher() (*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, 50),
+		Errors:  make(chan error),
+		quit:    make(chan chan<- error, 1),
+	}
+	go w.readEvents()
+	return w, nil
+}
+
+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 {
+	w.mu.Lock()
+	if w.isClosed {
+		w.mu.Unlock()
+		return nil
+	}
+	w.isClosed = 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; attempting to watch it more than once will
+// return an error. Paths that do not yet exist on the filesystem cannot be
+// added. A watch will be automatically removed if the path is deleted.
+//
+// A path will remain watched if it gets renamed to somewhere else on the same
+// filesystem, but the monitor will get removed if the path gets deleted and
+// re-created, or if it's moved to a different filesystem.
+//
+// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
+// filesystems (/proc, /sys, etc.) generally don't work.
+//
+// # 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 tools update files atomically. Instead of "just" writing
+// to the file a temporary file will be written to first, and if successful the
+// temporary file is moved to to destination removing the original, or some
+// variant thereof. The watcher on the original file is now lost, as it no
+// longer exists.
+//
+// Instead, 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 {
+	w.mu.Lock()
+	if w.isClosed {
+		w.mu.Unlock()
+		return errors.New("watcher already closed")
+	}
+	w.mu.Unlock()
+
+	in := &input{
+		op:    opAddWatch,
+		path:  filepath.Clean(name),
+		flags: sysFSALLEVENTS,
+		reply: make(chan error),
+	}
+	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].
+func (w *Watcher) Remove(name string) error {
+	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 added with [Add] (and are not yet removed).
+func (w *Watcher) WatchList() []string {
+	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
+	sysFSATTRIB     = 0x4
+	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
+	}
+	if mask&sysFSATTRIB == sysFSATTRIB {
+		e.Op |= Chmod
+	}
+	return e
+}
+
+const (
+	opAddWatch = iota
+	opRemoveWatch
+)
+
+const (
+	provisional uint64 = 1 << (32 + iota)
+)
+
+type input struct {
+	op    int
+	path  string
+	flags uint32
+	reply chan error
+}
+
+type inode struct {
+	handle windows.Handle
+	volume uint32
+	index  uint64
+}
+
+type watch struct {
+	ov     windows.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    [65536]byte       // 64K buffer
+}
+
+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) error {
+	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),
+		}
+		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 {
+	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()
+
+	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
+	}
+
+	rdErr := windows.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
+		uint32(unsafe.Sizeof(watch.buf)), false, 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 {
+		qErr := windows.GetQueuedCompletionStatus(w.port, &n, &key, &ov, windows.INFINITE)
+		// This error is handled after the watch == nil check below. NOTE: this
+		// seems odd, note sure if it's correct.
+
+		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))
+				case opRemoveWatch:
+					in.reply <- w.remWatch(in.path)
+				}
+			default:
+			}
+			continue
+		}
+
+		switch qErr {
+		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
+		case nil:
+		}
+
+		var offset uint32
+		for {
+			if n == 0 {
+				w.sendError(errors.New("short read in readEvents()"))
+				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 {
+				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&sysFSATTRIB != 0 {
+		m |= windows.FILE_NOTIFY_CHANGE_ATTRIBUTES
+	}
+	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
+}