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