github.com/DiversionCompany/notify@v0.9.9/watcher_readdcw.go (about)

     1  // Copyright (c) 2014-2020 The Notify Authors. All rights reserved.
     2  // Use of this source code is governed by the MIT license that can be
     3  // found in the LICENSE file.
     4  
     5  //go:build windows
     6  // +build windows
     7  
     8  package notify
     9  
    10  import (
    11  	"errors"
    12  	"runtime"
    13  	"sync"
    14  	"sync/atomic"
    15  	"syscall"
    16  	"unsafe"
    17  )
    18  
    19  // readBufferSize defines the size of an array in which read statuses are stored.
    20  // The buffer have to be DWORD-aligned and, if notify is used in monitoring a
    21  // directory over the network, its size must not be greater than 64KB. Each of
    22  // watched directories uses its own buffer for storing events.
    23  const readBufferSize = 4096
    24  
    25  // Since all operations which go through the Windows completion routine are done
    26  // asynchronously, filter may set one of the constants below. They were defined
    27  // in order to distinguish whether current folder should be re-registered in
    28  // ReadDirectoryChangesW function or some control operations need to be executed.
    29  const (
    30  	stateRewatch uint32 = 1 << (28 + iota)
    31  	stateUnwatch
    32  	stateCPClose
    33  )
    34  
    35  // Filter used in current implementation was split into four segments:
    36  //   - bits  0-11 store ReadDirectoryChangesW filters,
    37  //   - bits 12-19 store File notify actions,
    38  //   - bits 20-27 store notify specific events and flags,
    39  //   - bits 28-31 store states which are used in loop's FSM.
    40  //
    41  // Constants below are used as masks to retrieve only specific filter parts.
    42  const (
    43  	onlyNotifyChanges uint32 = 0x00000FFF
    44  	onlyNGlobalEvents uint32 = 0x0FF00000
    45  	onlyMachineStates uint32 = 0xF0000000
    46  )
    47  
    48  // grip represents a single watched directory. It stores the data required by
    49  // ReadDirectoryChangesW function. Only the filter, recursive, and handle members
    50  // may by modified by watcher implementation. Rest of the them have to remain
    51  // constant since they are used by Windows completion routine. This indicates that
    52  // grip can be removed only when all operations on the file handle are finished.
    53  type grip struct {
    54  	handle    syscall.Handle
    55  	filter    uint32
    56  	recursive bool
    57  	pathw     []uint16
    58  	buffer    [readBufferSize]byte
    59  	parent    *watched
    60  	ovlapped  *overlappedEx
    61  }
    62  
    63  // overlappedEx stores information used in asynchronous input and output.
    64  // Additionally, overlappedEx contains a pointer to 'grip' item which is used in
    65  // order to gather the structure in which the overlappedEx object was created.
    66  type overlappedEx struct {
    67  	syscall.Overlapped
    68  	parent *grip
    69  }
    70  
    71  // newGrip creates a new file handle that can be used in overlapped operations.
    72  // Then, the handle is associated with I/O completion port 'cph' and its value
    73  // is stored in newly created 'grip' object.
    74  func newGrip(cph syscall.Handle, parent *watched, filter uint32) (*grip, error) {
    75  	g := &grip{
    76  		handle:    syscall.InvalidHandle,
    77  		filter:    filter,
    78  		recursive: parent.recursive,
    79  		pathw:     parent.pathw,
    80  		parent:    parent,
    81  		ovlapped:  &overlappedEx{},
    82  	}
    83  	if err := g.register(cph); err != nil {
    84  		return nil, err
    85  	}
    86  	g.ovlapped.parent = g
    87  	return g, nil
    88  }
    89  
    90  // NOTE : Thread safe
    91  func (g *grip) register(cph syscall.Handle) (err error) {
    92  	if g.handle, err = syscall.CreateFile(
    93  		&g.pathw[0],
    94  		syscall.FILE_LIST_DIRECTORY,
    95  		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
    96  		nil,
    97  		syscall.OPEN_EXISTING,
    98  		syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED,
    99  		0,
   100  	); err != nil {
   101  		return
   102  	}
   103  	if _, err = syscall.CreateIoCompletionPort(g.handle, cph, 0, 0); err != nil {
   104  		syscall.CloseHandle(g.handle)
   105  		return
   106  	}
   107  	return g.readDirChanges()
   108  }
   109  
   110  // readDirChanges tells the system to store file change information in grip's
   111  // buffer. Directory changes that occur between calls to this function are added
   112  // to the buffer and then, returned with the next call.
   113  func (g *grip) readDirChanges() error {
   114  	handle := syscall.Handle(atomic.LoadUintptr((*uintptr)(&g.handle)))
   115  	if handle == syscall.InvalidHandle {
   116  		return nil // Handle was closed.
   117  	}
   118  
   119  	return syscall.ReadDirectoryChanges(
   120  		handle,
   121  		&g.buffer[0],
   122  		uint32(unsafe.Sizeof(g.buffer)),
   123  		g.recursive,
   124  		encode(g.filter),
   125  		nil,
   126  		(*syscall.Overlapped)(unsafe.Pointer(g.ovlapped)),
   127  		0,
   128  	)
   129  }
   130  
   131  // encode transforms a generic filter, which contains platform independent and
   132  // implementation specific bit fields, to value that can be used as NotifyFilter
   133  // parameter in ReadDirectoryChangesW function.
   134  func encode(filter uint32) uint32 {
   135  	e := Event(filter & (onlyNGlobalEvents | onlyNotifyChanges))
   136  	if e&dirmarker != 0 {
   137  		return uint32(FileNotifyChangeDirName)
   138  	}
   139  	if e&Create != 0 {
   140  		e = (e ^ Create) | FileNotifyChangeFileName
   141  	}
   142  	if e&Remove != 0 {
   143  		e = (e ^ Remove) | FileNotifyChangeFileName
   144  	}
   145  	if e&Write != 0 {
   146  		e = (e ^ Write) | FileNotifyChangeAttributes | FileNotifyChangeSize |
   147  			FileNotifyChangeCreation | FileNotifyChangeSecurity
   148  	}
   149  	if e&Rename != 0 {
   150  		e = (e ^ Rename) | FileNotifyChangeFileName
   151  	}
   152  	return uint32(e)
   153  }
   154  
   155  // watched is made in order to check whether an action comes from a directory or
   156  // file. This approach requires two file handlers per single monitored folder. The
   157  // second grip handles actions which include creating or deleting a directory. If
   158  // these processes are not monitored, only the first grip is created.
   159  type watched struct {
   160  	filter    uint32
   161  	recursive bool
   162  	count     uint8
   163  	pathw     []uint16
   164  	digrip    [2]*grip
   165  }
   166  
   167  // newWatched creates a new watched instance. It splits the filter variable into
   168  // two parts. The first part is responsible for watching all events which can be
   169  // created for a file in watched directory structure and the second one watches
   170  // only directory Create/Remove actions. If all operations succeed, the Create
   171  // message is sent to I/O completion port queue for further processing.
   172  func newWatched(cph syscall.Handle, filter uint32, recursive bool,
   173  	path string) (wd *watched, err error) {
   174  	wd = &watched{
   175  		filter:    filter,
   176  		recursive: recursive,
   177  	}
   178  	if wd.pathw, err = syscall.UTF16FromString(path); err != nil {
   179  		return
   180  	}
   181  	if err = wd.recreate(cph); err != nil {
   182  		return
   183  	}
   184  	return wd, nil
   185  }
   186  
   187  // TODO : doc
   188  func (wd *watched) recreate(cph syscall.Handle) (err error) {
   189  	filefilter := wd.filter &^ uint32(FileNotifyChangeDirName)
   190  	if err = wd.updateGrip(0, cph, filefilter == 0, filefilter); err != nil {
   191  		return
   192  	}
   193  	dirfilter := wd.filter & uint32(FileNotifyChangeDirName|Create|Remove)
   194  	if err = wd.updateGrip(1, cph, dirfilter == 0, wd.filter|uint32(dirmarker)); err != nil {
   195  		return
   196  	}
   197  	wd.filter &^= onlyMachineStates
   198  	return
   199  }
   200  
   201  // TODO : doc
   202  func (wd *watched) updateGrip(idx int, cph syscall.Handle, reset bool,
   203  	newflag uint32) (err error) {
   204  	if reset {
   205  		wd.digrip[idx] = nil
   206  	} else {
   207  		if wd.digrip[idx] == nil {
   208  			if wd.digrip[idx], err = newGrip(cph, wd, newflag); err != nil {
   209  				wd.closeHandle()
   210  				return
   211  			}
   212  		} else {
   213  			wd.digrip[idx].filter = newflag
   214  			wd.digrip[idx].recursive = wd.recursive
   215  			if err = wd.digrip[idx].register(cph); err != nil {
   216  				wd.closeHandle()
   217  				return
   218  			}
   219  		}
   220  		wd.count++
   221  	}
   222  	return
   223  }
   224  
   225  // closeHandle closes handles that are stored in digrip array. Function always
   226  // tries to close all of the handlers before it exits, even when there are errors
   227  // returned from the operating system kernel.
   228  func (wd *watched) closeHandle() (err error) {
   229  	for _, g := range wd.digrip {
   230  		if g == nil {
   231  			continue
   232  		}
   233  
   234  		for {
   235  			handle := syscall.Handle(atomic.LoadUintptr((*uintptr)(&g.handle)))
   236  			if handle == syscall.InvalidHandle {
   237  				break // Already closed.
   238  			}
   239  
   240  			e := syscall.CloseHandle(handle)
   241  			if e != nil && err == nil {
   242  				err = e
   243  			}
   244  
   245  			// Set invalid handle even when CloseHandle fails. This will leak
   246  			// the handle but, since we can't close it anyway, there won't be
   247  			// any difference.
   248  			if atomic.CompareAndSwapUintptr((*uintptr)(&g.handle),
   249  				(uintptr)(handle), (uintptr)(syscall.InvalidHandle)) {
   250  				break
   251  			}
   252  		}
   253  	}
   254  	return
   255  }
   256  
   257  // watcher implements Watcher interface. It stores a set of watched directories.
   258  // All operations which remove watched objects from map `m` must be performed in
   259  // loop goroutine since these structures are used internally by operating system.
   260  type readdcw struct {
   261  	sync.Mutex
   262  	m     map[string]*watched
   263  	cph   syscall.Handle
   264  	start bool
   265  	wg    sync.WaitGroup
   266  	c     chan<- EventInfo
   267  }
   268  
   269  // NewWatcher creates new non-recursive watcher backed by ReadDirectoryChangesW.
   270  func newWatcher(c chan<- EventInfo) watcher {
   271  	r := &readdcw{
   272  		m:   make(map[string]*watched),
   273  		cph: syscall.InvalidHandle,
   274  		c:   c,
   275  	}
   276  	runtime.SetFinalizer(r, func(r *readdcw) {
   277  		if r.cph != syscall.InvalidHandle {
   278  			syscall.CloseHandle(r.cph)
   279  		}
   280  	})
   281  	return r
   282  }
   283  
   284  // Watch implements notify.Watcher interface.
   285  func (r *readdcw) Watch(path string, event Event) error {
   286  	return r.watch(path, event, false)
   287  }
   288  
   289  // RecursiveWatch implements notify.RecursiveWatcher interface.
   290  func (r *readdcw) RecursiveWatch(path string, event Event) error {
   291  	return r.watch(path, event, true)
   292  }
   293  
   294  // watch inserts a directory to the group of watched folders. If watched folder
   295  // already exists, function tries to rewatch it with new filters(NOT VALID). Moreover,
   296  // watch starts the main event loop goroutine when called for the first time.
   297  func (r *readdcw) watch(path string, event Event, recursive bool) error {
   298  	if event&^(All|fileNotifyChangeAll) != 0 {
   299  		return errors.New("notify: unknown event")
   300  	}
   301  
   302  	r.Lock()
   303  	defer r.Unlock()
   304  
   305  	if wd, ok := r.m[path]; ok {
   306  		dbgprint("watch: already exists")
   307  		wd.filter &^= stateUnwatch
   308  		return nil
   309  	}
   310  
   311  	if err := r.lazyinit(); err != nil {
   312  		return err
   313  	}
   314  
   315  	wd, err := newWatched(r.cph, uint32(event), recursive, path)
   316  	if err != nil {
   317  		return err
   318  	}
   319  
   320  	r.m[path] = wd
   321  	dbgprint("watch: new watch added")
   322  
   323  	return nil
   324  }
   325  
   326  // lazyinit creates an I/O completion port and starts the main event loop.
   327  func (r *readdcw) lazyinit() (err error) {
   328  	invalid := uintptr(syscall.InvalidHandle)
   329  
   330  	if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid {
   331  		cph := syscall.InvalidHandle
   332  		if cph, err = syscall.CreateIoCompletionPort(cph, 0, 0, 0); err != nil {
   333  			return
   334  		}
   335  
   336  		r.cph, r.start = cph, true
   337  		go r.loop()
   338  	}
   339  
   340  	return
   341  }
   342  
   343  // TODO(pknap) : doc
   344  func (r *readdcw) loop() {
   345  	var n, key uint32
   346  	var overlapped *syscall.Overlapped
   347  	for {
   348  		err := syscall.GetQueuedCompletionStatus(r.cph, &n, &key, &overlapped, syscall.INFINITE)
   349  		if key == stateCPClose {
   350  			r.Lock()
   351  			handle := r.cph
   352  			r.cph = syscall.InvalidHandle
   353  			r.Unlock()
   354  			syscall.CloseHandle(handle)
   355  			r.wg.Done()
   356  			return
   357  		}
   358  		if overlapped == nil {
   359  			// TODO: check key == rewatch delete or 0(panic)
   360  			continue
   361  		}
   362  		overEx := (*overlappedEx)(unsafe.Pointer(overlapped))
   363  		if overEx == nil || overEx.parent == nil {
   364  			dbgprintf("incomplete completion status transferred=%d, overlapped=%#v, key=%#b", n, overEx, key)
   365  			continue
   366  		} else if n != 0 {
   367  			r.loopevent(n, overEx)
   368  		}
   369  		if err = overEx.parent.readDirChanges(); err != nil {
   370  			// TODO: error handling
   371  		}
   372  		r.loopstate(overEx)
   373  	}
   374  }
   375  
   376  // TODO(pknap) : doc
   377  func (r *readdcw) loopstate(overEx *overlappedEx) {
   378  	r.Lock()
   379  	defer r.Unlock()
   380  	filter := overEx.parent.parent.filter
   381  	if filter&onlyMachineStates == 0 {
   382  		return
   383  	}
   384  	if overEx.parent.parent.count--; overEx.parent.parent.count == 0 {
   385  		switch filter & onlyMachineStates {
   386  		case stateRewatch:
   387  			dbgprint("loopstate rewatch")
   388  			overEx.parent.parent.recreate(r.cph)
   389  		case stateUnwatch:
   390  			dbgprint("loopstate unwatch")
   391  			overEx.parent.parent.closeHandle()
   392  			delete(r.m, syscall.UTF16ToString(overEx.parent.pathw))
   393  		case stateCPClose:
   394  		default:
   395  			panic(`notify: windows loopstate logic error`)
   396  		}
   397  	}
   398  }
   399  
   400  // TODO(pknap) : doc
   401  func (r *readdcw) loopevent(n uint32, overEx *overlappedEx) {
   402  	events := []*event{}
   403  	var currOffset uint32
   404  	for {
   405  		raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&overEx.parent.buffer[currOffset]))
   406  		name := syscall.UTF16ToString((*[syscall.MAX_LONG_PATH]uint16)(unsafe.Pointer(&raw.FileName))[:raw.FileNameLength>>1])
   407  		events = append(events, &event{
   408  			pathw:  overEx.parent.pathw,
   409  			filter: overEx.parent.filter,
   410  			action: raw.Action,
   411  			name:   name,
   412  		})
   413  		if raw.NextEntryOffset == 0 {
   414  			break
   415  		}
   416  		if currOffset += raw.NextEntryOffset; currOffset >= n {
   417  			break
   418  		}
   419  	}
   420  	r.send(events)
   421  }
   422  
   423  // TODO(pknap) : doc
   424  func (r *readdcw) send(es []*event) {
   425  	for _, e := range es {
   426  		var syse Event
   427  		if e.e, syse = decode(e.filter, e.action); e.e == 0 && syse == 0 {
   428  			continue
   429  		}
   430  		switch {
   431  		case e.action == syscall.FILE_ACTION_MODIFIED:
   432  			e.ftype = fTypeUnknown
   433  		case e.filter&uint32(dirmarker) != 0:
   434  			e.ftype = fTypeDirectory
   435  		default:
   436  			e.ftype = fTypeFile
   437  		}
   438  		switch {
   439  		case e.e == 0:
   440  			e.e = syse
   441  		case syse != 0:
   442  			r.c <- &event{
   443  				pathw:  e.pathw,
   444  				name:   e.name,
   445  				ftype:  e.ftype,
   446  				action: e.action,
   447  				filter: e.filter,
   448  				e:      syse,
   449  			}
   450  		}
   451  		r.c <- e
   452  	}
   453  }
   454  
   455  // Rewatch implements notify.Rewatcher interface.
   456  func (r *readdcw) Rewatch(path string, oldevent, newevent Event) error {
   457  	return r.rewatch(path, uint32(oldevent), uint32(newevent), false)
   458  }
   459  
   460  // RecursiveRewatch implements notify.RecursiveRewatcher interface.
   461  func (r *readdcw) RecursiveRewatch(oldpath, newpath string, oldevent,
   462  	newevent Event) error {
   463  	if oldpath != newpath {
   464  		if err := r.unwatch(oldpath); err != nil {
   465  			return err
   466  		}
   467  		return r.watch(newpath, newevent, true)
   468  	}
   469  	return r.rewatch(newpath, uint32(oldevent), uint32(newevent), true)
   470  }
   471  
   472  // TODO : (pknap) doc.
   473  func (r *readdcw) rewatch(path string, oldevent, newevent uint32, recursive bool) (err error) {
   474  	if Event(newevent)&^(All|fileNotifyChangeAll) != 0 {
   475  		return errors.New("notify: unknown event")
   476  	}
   477  	var wd *watched
   478  	r.Lock()
   479  	defer r.Unlock()
   480  	if wd, err = r.nonStateWatchedLocked(path); err != nil {
   481  		return
   482  	}
   483  	if wd.filter&(onlyNotifyChanges|onlyNGlobalEvents) != oldevent {
   484  		panic(`notify: windows re-watcher logic error`)
   485  	}
   486  	wd.filter = stateRewatch | newevent
   487  	wd.recursive, recursive = recursive, wd.recursive
   488  	if err = wd.closeHandle(); err != nil {
   489  		wd.filter = oldevent
   490  		wd.recursive = recursive
   491  		return
   492  	}
   493  	return
   494  }
   495  
   496  // TODO : pknap
   497  func (r *readdcw) nonStateWatchedLocked(path string) (wd *watched, err error) {
   498  	wd, ok := r.m[path]
   499  	if !ok || wd == nil {
   500  		err = errors.New(`notify: ` + path + ` path is unwatched`)
   501  		return
   502  	}
   503  	if wd.filter&onlyMachineStates != 0 {
   504  		err = errors.New(`notify: another re/unwatching operation in progress`)
   505  		return
   506  	}
   507  	return
   508  }
   509  
   510  // Unwatch implements notify.Watcher interface.
   511  func (r *readdcw) Unwatch(path string) error {
   512  	return r.unwatch(path)
   513  }
   514  
   515  // RecursiveUnwatch implements notify.RecursiveWatcher interface.
   516  func (r *readdcw) RecursiveUnwatch(path string) error {
   517  	return r.unwatch(path)
   518  }
   519  
   520  // TODO : pknap
   521  func (r *readdcw) unwatch(path string) (err error) {
   522  	var wd *watched
   523  
   524  	r.Lock()
   525  	defer r.Unlock()
   526  	if wd, err = r.nonStateWatchedLocked(path); err != nil {
   527  		return
   528  	}
   529  
   530  	wd.filter |= stateUnwatch
   531  	dbgprint("unwatch: set unwatch state")
   532  
   533  	if _, attrErr := syscall.GetFileAttributes(&wd.pathw[0]); attrErr != nil {
   534  		for _, g := range wd.digrip {
   535  			if g == nil {
   536  				continue
   537  			}
   538  
   539  			dbgprint("unwatch: posting")
   540  			if err = syscall.PostQueuedCompletionStatus(r.cph, 0, 0, (*syscall.Overlapped)(unsafe.Pointer(g.ovlapped))); err != nil {
   541  				wd.filter &^= stateUnwatch
   542  				return
   543  			}
   544  		}
   545  	}
   546  
   547  	return
   548  }
   549  
   550  // Close resets the whole watcher object, closes all existing file descriptors,
   551  // and sends stateCPClose state as completion key to the main watcher's loop.
   552  func (r *readdcw) Close() (err error) {
   553  	r.Lock()
   554  	if !r.start {
   555  		r.Unlock()
   556  		return nil
   557  	}
   558  	for _, wd := range r.m {
   559  		wd.filter &^= onlyMachineStates
   560  		wd.filter |= stateCPClose
   561  		if e := wd.closeHandle(); e != nil && err == nil {
   562  			err = e
   563  		}
   564  	}
   565  	r.start = false
   566  	r.Unlock()
   567  	r.wg.Add(1)
   568  	if e := syscall.PostQueuedCompletionStatus(r.cph, 0, stateCPClose, nil); e != nil && err == nil {
   569  		return e
   570  	}
   571  	r.wg.Wait()
   572  	return
   573  }
   574  
   575  // decode creates a notify event from both non-raw filter and action which was
   576  // returned from completion routine. Function may return Event(0) in case when
   577  // filter was replaced by a new value which does not contain fields that are
   578  // valid with passed action.
   579  func decode(filter, action uint32) (Event, Event) {
   580  	switch action {
   581  	case syscall.FILE_ACTION_ADDED:
   582  		return gensys(filter, Create, FileActionAdded)
   583  	case syscall.FILE_ACTION_REMOVED:
   584  		return gensys(filter, Remove, FileActionRemoved)
   585  	case syscall.FILE_ACTION_MODIFIED:
   586  		return gensys(filter, Write, FileActionModified)
   587  	case syscall.FILE_ACTION_RENAMED_OLD_NAME:
   588  		return gensys(filter, Rename, FileActionRenamedOldName)
   589  	case syscall.FILE_ACTION_RENAMED_NEW_NAME:
   590  		return gensys(filter, Rename, FileActionRenamedNewName)
   591  	}
   592  	dbgprintf("cannot decode internal mask: %d", action)
   593  
   594  	return 0, 0
   595  }
   596  
   597  // gensys decides whether the Windows action, system-independent event or both
   598  // of them should be returned. Since the grip's filter may be atomically changed
   599  // during watcher lifetime, it is possible that neither Windows nor notify masks
   600  // are watched by the user when this function is called.
   601  func gensys(filter uint32, ge, se Event) (gene, syse Event) {
   602  	isdir := filter&uint32(dirmarker) != 0
   603  	if isdir && filter&uint32(FileNotifyChangeDirName) != 0 ||
   604  		!isdir && filter&uint32(FileNotifyChangeFileName) != 0 ||
   605  		filter&uint32(fileNotifyChangeModified) != 0 {
   606  		syse = se
   607  	}
   608  	if filter&uint32(ge) != 0 {
   609  		gene = ge
   610  	}
   611  	return
   612  }