|
@@ -0,0 +1,582 @@
|
|
|
|
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
|
|
|
|
+// Use of this source code is governed by the MIT license that can be
|
|
|
|
+// found in the LICENSE file.
|
|
|
|
+
|
|
|
|
+// +build windows
|
|
|
|
+
|
|
|
|
+package notify
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "errors"
|
|
|
|
+ "runtime"
|
|
|
|
+ "sync"
|
|
|
|
+ "sync/atomic"
|
|
|
|
+ "syscall"
|
|
|
|
+ "unsafe"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+// readBufferSize defines the size of an array in which read statuses are stored.
|
|
|
|
+// The buffer have to be DWORD-aligned and, if notify is used in monitoring a
|
|
|
|
+// directory over the network, its size must not be greater than 64KB. Each of
|
|
|
|
+// watched directories uses its own buffer for storing events.
|
|
|
|
+const readBufferSize = 4096
|
|
|
|
+
|
|
|
|
+// Since all operations which go through the Windows completion routine are done
|
|
|
|
+// asynchronously, filter may set one of the constants belor. They were defined
|
|
|
|
+// in order to distinguish whether current folder should be re-registered in
|
|
|
|
+// ReadDirectoryChangesW function or some control operations need to be executed.
|
|
|
|
+const (
|
|
|
|
+ stateRewatch uint32 = 1 << (28 + iota)
|
|
|
|
+ stateUnwatch
|
|
|
|
+ stateCPClose
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+// Filter used in current implementation was split into four segments:
|
|
|
|
+// - bits 0-11 store ReadDirectoryChangesW filters,
|
|
|
|
+// - bits 12-19 store File notify actions,
|
|
|
|
+// - bits 20-27 store notify specific events and flags,
|
|
|
|
+// - bits 28-31 store states which are used in loop's FSM.
|
|
|
|
+// Constants below are used as masks to retrieve only specific filter parts.
|
|
|
|
+const (
|
|
|
|
+ onlyNotifyChanges uint32 = 0x00000FFF
|
|
|
|
+ onlyNGlobalEvents uint32 = 0x0FF00000
|
|
|
|
+ onlyMachineStates uint32 = 0xF0000000
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+// grip represents a single watched directory. It stores the data required by
|
|
|
|
+// ReadDirectoryChangesW function. Only the filter, recursive, and handle members
|
|
|
|
+// may by modified by watcher implementation. Rest of the them have to remain
|
|
|
|
+// constant since they are used by Windows completion routine. This indicates that
|
|
|
|
+// grip can be removed only when all operations on the file handle are finished.
|
|
|
|
+type grip struct {
|
|
|
|
+ handle syscall.Handle
|
|
|
|
+ filter uint32
|
|
|
|
+ recursive bool
|
|
|
|
+ pathw []uint16
|
|
|
|
+ buffer [readBufferSize]byte
|
|
|
|
+ parent *watched
|
|
|
|
+ ovlapped *overlappedEx
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// overlappedEx stores information used in asynchronous input and output.
|
|
|
|
+// Additionally, overlappedEx contains a pointer to 'grip' item which is used in
|
|
|
|
+// order to gather the structure in which the overlappedEx object was created.
|
|
|
|
+type overlappedEx struct {
|
|
|
|
+ syscall.Overlapped
|
|
|
|
+ parent *grip
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// newGrip creates a new file handle that can be used in overlapped operations.
|
|
|
|
+// Then, the handle is associated with I/O completion port 'cph' and its value
|
|
|
|
+// is stored in newly created 'grip' object.
|
|
|
|
+func newGrip(cph syscall.Handle, parent *watched, filter uint32) (*grip, error) {
|
|
|
|
+ g := &grip{
|
|
|
|
+ handle: syscall.InvalidHandle,
|
|
|
|
+ filter: filter,
|
|
|
|
+ recursive: parent.recursive,
|
|
|
|
+ pathw: parent.pathw,
|
|
|
|
+ parent: parent,
|
|
|
|
+ ovlapped: &overlappedEx{},
|
|
|
|
+ }
|
|
|
|
+ if err := g.register(cph); err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ g.ovlapped.parent = g
|
|
|
|
+ return g, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// NOTE : Thread safe
|
|
|
|
+func (g *grip) register(cph syscall.Handle) (err error) {
|
|
|
|
+ if g.handle, err = syscall.CreateFile(
|
|
|
|
+ &g.pathw[0],
|
|
|
|
+ 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,
|
|
|
|
+ ); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if _, err = syscall.CreateIoCompletionPort(g.handle, cph, 0, 0); err != nil {
|
|
|
|
+ syscall.CloseHandle(g.handle)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ return g.readDirChanges()
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// readDirChanges tells the system to store file change information in grip's
|
|
|
|
+// buffer. Directory changes that occur between calls to this function are added
|
|
|
|
+// to the buffer and then, returned with the next call.
|
|
|
|
+func (g *grip) readDirChanges() error {
|
|
|
|
+ return syscall.ReadDirectoryChanges(
|
|
|
|
+ g.handle,
|
|
|
|
+ &g.buffer[0],
|
|
|
|
+ uint32(unsafe.Sizeof(g.buffer)),
|
|
|
|
+ g.recursive,
|
|
|
|
+ encode(g.filter),
|
|
|
|
+ nil,
|
|
|
|
+ (*syscall.Overlapped)(unsafe.Pointer(g.ovlapped)),
|
|
|
|
+ 0,
|
|
|
|
+ )
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// encode transforms a generic filter, which contains platform independent and
|
|
|
|
+// implementation specific bit fields, to value that can be used as NotifyFilter
|
|
|
|
+// parameter in ReadDirectoryChangesW function.
|
|
|
|
+func encode(filter uint32) uint32 {
|
|
|
|
+ e := Event(filter & (onlyNGlobalEvents | onlyNotifyChanges))
|
|
|
|
+ if e&dirmarker != 0 {
|
|
|
|
+ return uint32(FileNotifyChangeDirName)
|
|
|
|
+ }
|
|
|
|
+ if e&Create != 0 {
|
|
|
|
+ e = (e ^ Create) | FileNotifyChangeFileName
|
|
|
|
+ }
|
|
|
|
+ if e&Remove != 0 {
|
|
|
|
+ e = (e ^ Remove) | FileNotifyChangeFileName
|
|
|
|
+ }
|
|
|
|
+ if e&Write != 0 {
|
|
|
|
+ e = (e ^ Write) | FileNotifyChangeAttributes | FileNotifyChangeSize |
|
|
|
|
+ FileNotifyChangeCreation | FileNotifyChangeSecurity
|
|
|
|
+ }
|
|
|
|
+ if e&Rename != 0 {
|
|
|
|
+ e = (e ^ Rename) | FileNotifyChangeFileName
|
|
|
|
+ }
|
|
|
|
+ return uint32(e)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// watched is made in order to check whether an action comes from a directory or
|
|
|
|
+// file. This approach requires two file handlers per single monitored folder. The
|
|
|
|
+// second grip handles actions which include creating or deleting a directory. If
|
|
|
|
+// these processes are not monitored, only the first grip is created.
|
|
|
|
+type watched struct {
|
|
|
|
+ filter uint32
|
|
|
|
+ recursive bool
|
|
|
|
+ count uint8
|
|
|
|
+ pathw []uint16
|
|
|
|
+ digrip [2]*grip
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// newWatched creates a new watched instance. It splits the filter variable into
|
|
|
|
+// two parts. The first part is responsible for watching all events which can be
|
|
|
|
+// created for a file in watched directory structure and the second one watches
|
|
|
|
+// only directory Create/Remove actions. If all operations succeed, the Create
|
|
|
|
+// message is sent to I/O completion port queue for further processing.
|
|
|
|
+func newWatched(cph syscall.Handle, filter uint32, recursive bool,
|
|
|
|
+ path string) (wd *watched, err error) {
|
|
|
|
+ wd = &watched{
|
|
|
|
+ filter: filter,
|
|
|
|
+ recursive: recursive,
|
|
|
|
+ }
|
|
|
|
+ if wd.pathw, err = syscall.UTF16FromString(path); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if err = wd.recreate(cph); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ return wd, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// TODO : doc
|
|
|
|
+func (wd *watched) recreate(cph syscall.Handle) (err error) {
|
|
|
|
+ filefilter := wd.filter &^ uint32(FileNotifyChangeDirName)
|
|
|
|
+ if err = wd.updateGrip(0, cph, filefilter == 0, filefilter); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ dirfilter := wd.filter & uint32(FileNotifyChangeDirName|Create|Remove)
|
|
|
|
+ if err = wd.updateGrip(1, cph, dirfilter == 0, wd.filter|uint32(dirmarker)); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ wd.filter &^= onlyMachineStates
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// TODO : doc
|
|
|
|
+func (wd *watched) updateGrip(idx int, cph syscall.Handle, reset bool,
|
|
|
|
+ newflag uint32) (err error) {
|
|
|
|
+ if reset {
|
|
|
|
+ wd.digrip[idx] = nil
|
|
|
|
+ } else {
|
|
|
|
+ if wd.digrip[idx] == nil {
|
|
|
|
+ if wd.digrip[idx], err = newGrip(cph, wd, newflag); err != nil {
|
|
|
|
+ wd.closeHandle()
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ wd.digrip[idx].filter = newflag
|
|
|
|
+ wd.digrip[idx].recursive = wd.recursive
|
|
|
|
+ if err = wd.digrip[idx].register(cph); err != nil {
|
|
|
|
+ wd.closeHandle()
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ wd.count++
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// closeHandle closes handles that are stored in digrip array. Function always
|
|
|
|
+// tries to close all of the handlers before it exits, even when there are errors
|
|
|
|
+// returned from the operating system kernel.
|
|
|
|
+func (wd *watched) closeHandle() (err error) {
|
|
|
|
+ for _, g := range wd.digrip {
|
|
|
|
+ if g != nil && g.handle != syscall.InvalidHandle {
|
|
|
|
+ switch suberr := syscall.CloseHandle(g.handle); {
|
|
|
|
+ case suberr == nil:
|
|
|
|
+ g.handle = syscall.InvalidHandle
|
|
|
|
+ case err == nil:
|
|
|
|
+ err = suberr
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// watcher implements Watcher interface. It stores a set of watched directories.
|
|
|
|
+// All operations which remove watched objects from map `m` must be performed in
|
|
|
|
+// loop goroutine since these structures are used internally by operating system.
|
|
|
|
+type readdcw struct {
|
|
|
|
+ sync.Mutex
|
|
|
|
+ m map[string]*watched
|
|
|
|
+ cph syscall.Handle
|
|
|
|
+ start bool
|
|
|
|
+ wg sync.WaitGroup
|
|
|
|
+ c chan<- EventInfo
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// NewWatcher creates new non-recursive watcher backed by ReadDirectoryChangesW.
|
|
|
|
+func newWatcher(c chan<- EventInfo) watcher {
|
|
|
|
+ r := &readdcw{
|
|
|
|
+ m: make(map[string]*watched),
|
|
|
|
+ cph: syscall.InvalidHandle,
|
|
|
|
+ c: c,
|
|
|
|
+ }
|
|
|
|
+ runtime.SetFinalizer(r, func(r *readdcw) {
|
|
|
|
+ if r.cph != syscall.InvalidHandle {
|
|
|
|
+ syscall.CloseHandle(r.cph)
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ return r
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Watch implements notify.Watcher interface.
|
|
|
|
+func (r *readdcw) Watch(path string, event Event) error {
|
|
|
|
+ return r.watch(path, event, false)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// RecursiveWatch implements notify.RecursiveWatcher interface.
|
|
|
|
+func (r *readdcw) RecursiveWatch(path string, event Event) error {
|
|
|
|
+ return r.watch(path, event, true)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// watch inserts a directory to the group of watched folders. If watched folder
|
|
|
|
+// already exists, function tries to rewatch it with new filters(NOT VALID). Moreover,
|
|
|
|
+// watch starts the main event loop goroutine when called for the first time.
|
|
|
|
+func (r *readdcw) watch(path string, event Event, recursive bool) (err error) {
|
|
|
|
+ if event&^(All|fileNotifyChangeAll) != 0 {
|
|
|
|
+ return errors.New("notify: unknown event")
|
|
|
|
+ }
|
|
|
|
+ r.Lock()
|
|
|
|
+ wd, ok := r.m[path]
|
|
|
|
+ r.Unlock()
|
|
|
|
+ if !ok {
|
|
|
|
+ if err = r.lazyinit(); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ r.Lock()
|
|
|
|
+ defer r.Unlock()
|
|
|
|
+ if wd, ok = r.m[path]; ok {
|
|
|
|
+ dbgprint("watch: exists already")
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if wd, err = newWatched(r.cph, uint32(event), recursive, path); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ r.m[path] = wd
|
|
|
|
+ dbgprint("watch: new watch added")
|
|
|
|
+ } else {
|
|
|
|
+ dbgprint("watch: exists already")
|
|
|
|
+ }
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// lazyinit creates an I/O completion port and starts the main event processing
|
|
|
|
+// loop. This method uses Double-Checked Locking optimization.
|
|
|
|
+func (r *readdcw) lazyinit() (err error) {
|
|
|
|
+ invalid := uintptr(syscall.InvalidHandle)
|
|
|
|
+ if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid {
|
|
|
|
+ r.Lock()
|
|
|
|
+ defer r.Unlock()
|
|
|
|
+ if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid {
|
|
|
|
+ cph := syscall.InvalidHandle
|
|
|
|
+ if cph, err = syscall.CreateIoCompletionPort(cph, 0, 0, 0); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ r.cph, r.start = cph, true
|
|
|
|
+ go r.loop()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// TODO(pknap) : doc
|
|
|
|
+func (r *readdcw) loop() {
|
|
|
|
+ var n, key uint32
|
|
|
|
+ var overlapped *syscall.Overlapped
|
|
|
|
+ for {
|
|
|
|
+ err := syscall.GetQueuedCompletionStatus(r.cph, &n, &key, &overlapped, syscall.INFINITE)
|
|
|
|
+ if key == stateCPClose {
|
|
|
|
+ r.Lock()
|
|
|
|
+ handle := r.cph
|
|
|
|
+ r.cph = syscall.InvalidHandle
|
|
|
|
+ r.Unlock()
|
|
|
|
+ syscall.CloseHandle(handle)
|
|
|
|
+ r.wg.Done()
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if overlapped == nil {
|
|
|
|
+ // TODO: check key == rewatch delete or 0(panic)
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ overEx := (*overlappedEx)(unsafe.Pointer(overlapped))
|
|
|
|
+ if n != 0 {
|
|
|
|
+ r.loopevent(n, overEx)
|
|
|
|
+ if err = overEx.parent.readDirChanges(); err != nil {
|
|
|
|
+ // TODO: error handling
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ r.loopstate(overEx)
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// TODO(pknap) : doc
|
|
|
|
+func (r *readdcw) loopstate(overEx *overlappedEx) {
|
|
|
|
+ r.Lock()
|
|
|
|
+ defer r.Unlock()
|
|
|
|
+ filter := overEx.parent.parent.filter
|
|
|
|
+ if filter&onlyMachineStates == 0 {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if overEx.parent.parent.count--; overEx.parent.parent.count == 0 {
|
|
|
|
+ switch filter & onlyMachineStates {
|
|
|
|
+ case stateRewatch:
|
|
|
|
+ dbgprint("loopstate rewatch")
|
|
|
|
+ overEx.parent.parent.recreate(r.cph)
|
|
|
|
+ case stateUnwatch:
|
|
|
|
+ dbgprint("loopstate unwatch")
|
|
|
|
+ delete(r.m, syscall.UTF16ToString(overEx.parent.pathw))
|
|
|
|
+ case stateCPClose:
|
|
|
|
+ default:
|
|
|
|
+ panic(`notify: windows loopstate logic error`)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// TODO(pknap) : doc
|
|
|
|
+func (r *readdcw) loopevent(n uint32, overEx *overlappedEx) {
|
|
|
|
+ events := []*event{}
|
|
|
|
+ var currOffset uint32
|
|
|
|
+ for {
|
|
|
|
+ raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&overEx.parent.buffer[currOffset]))
|
|
|
|
+ name := syscall.UTF16ToString((*[syscall.MAX_LONG_PATH]uint16)(unsafe.Pointer(&raw.FileName))[:raw.FileNameLength>>1])
|
|
|
|
+ events = append(events, &event{
|
|
|
|
+ pathw: overEx.parent.pathw,
|
|
|
|
+ filter: overEx.parent.filter,
|
|
|
|
+ action: raw.Action,
|
|
|
|
+ name: name,
|
|
|
|
+ })
|
|
|
|
+ if raw.NextEntryOffset == 0 {
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ if currOffset += raw.NextEntryOffset; currOffset >= n {
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ r.send(events)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// TODO(pknap) : doc
|
|
|
|
+func (r *readdcw) send(es []*event) {
|
|
|
|
+ for _, e := range es {
|
|
|
|
+ var syse Event
|
|
|
|
+ if e.e, syse = decode(e.filter, e.action); e.e == 0 && syse == 0 {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ switch {
|
|
|
|
+ case e.action == syscall.FILE_ACTION_MODIFIED:
|
|
|
|
+ e.ftype = fTypeUnknown
|
|
|
|
+ case e.filter&uint32(dirmarker) != 0:
|
|
|
|
+ e.ftype = fTypeDirectory
|
|
|
|
+ default:
|
|
|
|
+ e.ftype = fTypeFile
|
|
|
|
+ }
|
|
|
|
+ switch {
|
|
|
|
+ case e.e == 0:
|
|
|
|
+ e.e = syse
|
|
|
|
+ case syse != 0:
|
|
|
|
+ r.c <- &event{
|
|
|
|
+ pathw: e.pathw,
|
|
|
|
+ name: e.name,
|
|
|
|
+ ftype: e.ftype,
|
|
|
|
+ action: e.action,
|
|
|
|
+ filter: e.filter,
|
|
|
|
+ e: syse,
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ r.c <- e
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Rewatch implements notify.Rewatcher interface.
|
|
|
|
+func (r *readdcw) Rewatch(path string, oldevent, newevent Event) error {
|
|
|
|
+ return r.rewatch(path, uint32(oldevent), uint32(newevent), false)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// RecursiveRewatch implements notify.RecursiveRewatcher interface.
|
|
|
|
+func (r *readdcw) RecursiveRewatch(oldpath, newpath string, oldevent,
|
|
|
|
+ newevent Event) error {
|
|
|
|
+ if oldpath != newpath {
|
|
|
|
+ if err := r.unwatch(oldpath); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ return r.watch(newpath, newevent, true)
|
|
|
|
+ }
|
|
|
|
+ return r.rewatch(newpath, uint32(oldevent), uint32(newevent), true)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// TODO : (pknap) doc.
|
|
|
|
+func (r *readdcw) rewatch(path string, oldevent, newevent uint32, recursive bool) (err error) {
|
|
|
|
+ if Event(newevent)&^(All|fileNotifyChangeAll) != 0 {
|
|
|
|
+ return errors.New("notify: unknown event")
|
|
|
|
+ }
|
|
|
|
+ var wd *watched
|
|
|
|
+ r.Lock()
|
|
|
|
+ defer r.Unlock()
|
|
|
|
+ if wd, err = r.nonStateWatchedLocked(path); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if wd.filter&(onlyNotifyChanges|onlyNGlobalEvents) != oldevent {
|
|
|
|
+ panic(`notify: windows re-watcher logic error`)
|
|
|
|
+ }
|
|
|
|
+ wd.filter = stateRewatch | newevent
|
|
|
|
+ wd.recursive, recursive = recursive, wd.recursive
|
|
|
|
+ if err = wd.closeHandle(); err != nil {
|
|
|
|
+ wd.filter = oldevent
|
|
|
|
+ wd.recursive = recursive
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// TODO : pknap
|
|
|
|
+func (r *readdcw) nonStateWatchedLocked(path string) (wd *watched, err error) {
|
|
|
|
+ wd, ok := r.m[path]
|
|
|
|
+ if !ok || wd == nil {
|
|
|
|
+ err = errors.New(`notify: ` + path + ` path is unwatched`)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if wd.filter&onlyMachineStates != 0 {
|
|
|
|
+ err = errors.New(`notify: another re/unwatching operation in progress`)
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Unwatch implements notify.Watcher interface.
|
|
|
|
+func (r *readdcw) Unwatch(path string) error {
|
|
|
|
+ return r.unwatch(path)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// RecursiveUnwatch implements notify.RecursiveWatcher interface.
|
|
|
|
+func (r *readdcw) RecursiveUnwatch(path string) error {
|
|
|
|
+ return r.unwatch(path)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// TODO : pknap
|
|
|
|
+func (r *readdcw) unwatch(path string) (err error) {
|
|
|
|
+ var wd *watched
|
|
|
|
+ r.Lock()
|
|
|
|
+ defer r.Unlock()
|
|
|
|
+ if wd, err = r.nonStateWatchedLocked(path); err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ wd.filter |= stateUnwatch
|
|
|
|
+ if err = wd.closeHandle(); err != nil {
|
|
|
|
+ wd.filter &^= stateUnwatch
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if _, attrErr := syscall.GetFileAttributes(&wd.pathw[0]); attrErr != nil {
|
|
|
|
+ for _, g := range wd.digrip {
|
|
|
|
+ if g != nil {
|
|
|
|
+ dbgprint("unwatch: posting")
|
|
|
|
+ if err = syscall.PostQueuedCompletionStatus(r.cph, 0, 0, (*syscall.Overlapped)(unsafe.Pointer(g.ovlapped))); err != nil {
|
|
|
|
+ wd.filter &^= stateUnwatch
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// Close resets the whole watcher object, closes all existing file descriptors,
|
|
|
|
+// and sends stateCPClose state as completion key to the main watcher's loop.
|
|
|
|
+func (r *readdcw) Close() (err error) {
|
|
|
|
+ r.Lock()
|
|
|
|
+ if !r.start {
|
|
|
|
+ r.Unlock()
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+ for _, wd := range r.m {
|
|
|
|
+ wd.filter &^= onlyMachineStates
|
|
|
|
+ wd.filter |= stateCPClose
|
|
|
|
+ if e := wd.closeHandle(); e != nil && err == nil {
|
|
|
|
+ err = e
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ r.start = false
|
|
|
|
+ r.Unlock()
|
|
|
|
+ r.wg.Add(1)
|
|
|
|
+ if e := syscall.PostQueuedCompletionStatus(r.cph, 0, stateCPClose, nil); e != nil && err == nil {
|
|
|
|
+ return e
|
|
|
|
+ }
|
|
|
|
+ r.wg.Wait()
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// decode creates a notify event from both non-raw filter and action which was
|
|
|
|
+// returned from completion routine. Function may return Event(0) in case when
|
|
|
|
+// filter was replaced by a new value which does not contain fields that are
|
|
|
|
+// valid with passed action.
|
|
|
|
+func decode(filter, action uint32) (Event, Event) {
|
|
|
|
+ switch action {
|
|
|
|
+ case syscall.FILE_ACTION_ADDED:
|
|
|
|
+ return gensys(filter, Create, FileActionAdded)
|
|
|
|
+ case syscall.FILE_ACTION_REMOVED:
|
|
|
|
+ return gensys(filter, Remove, FileActionRemoved)
|
|
|
|
+ case syscall.FILE_ACTION_MODIFIED:
|
|
|
|
+ return gensys(filter, Write, FileActionModified)
|
|
|
|
+ case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
|
|
|
+ return gensys(filter, Rename, FileActionRenamedOldName)
|
|
|
|
+ case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
|
|
|
+ return gensys(filter, Rename, FileActionRenamedNewName)
|
|
|
|
+ }
|
|
|
|
+ panic(`notify: cannot decode internal mask`)
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// gensys decides whether the Windows action, system-independent event or both
|
|
|
|
+// of them should be returned. Since the grip's filter may be atomically changed
|
|
|
|
+// during watcher lifetime, it is possible that neither Windows nor notify masks
|
|
|
|
+// are watched by the user when this function is called.
|
|
|
|
+func gensys(filter uint32, ge, se Event) (gene, syse Event) {
|
|
|
|
+ isdir := filter&uint32(dirmarker) != 0
|
|
|
|
+ if isdir && filter&uint32(FileNotifyChangeDirName) != 0 ||
|
|
|
|
+ !isdir && filter&uint32(FileNotifyChangeFileName) != 0 ||
|
|
|
|
+ filter&uint32(fileNotifyChangeModified) != 0 {
|
|
|
|
+ syse = se
|
|
|
|
+ }
|
|
|
|
+ if filter&uint32(ge) != 0 {
|
|
|
|
+ gene = ge
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+}
|