123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827 |
- //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
- }
|