github.com/checksum/notify@v0.0.0-20190119234841-59aa2d88664f/watcher_readdcw.go (about)

     1  // Copyright (c) 2014-2015 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 belor. 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  	return syscall.ReadDirectoryChanges(
   113  		g.handle,
   114  		&g.buffer[0],
   115  		uint32(unsafe.Sizeof(g.buffer)),
   116  		g.recursive,
   117  		encode(g.filter),
   118  		nil,
   119  		(*syscall.Overlapped)(unsafe.Pointer(g.ovlapped)),
   120  		0,
   121  	)
   122  }
   123  
   124  // encode transforms a generic filter, which contains platform independent and
   125  // implementation specific bit fields, to value that can be used as NotifyFilter
   126  // parameter in ReadDirectoryChangesW function.
   127  func encode(filter uint32) uint32 {
   128  	e := Event(filter & (onlyNGlobalEvents | onlyNotifyChanges))
   129  	if e&dirmarker != 0 {
   130  		return uint32(FileNotifyChangeDirName)
   131  	}
   132  	if e&Create != 0 {
   133  		e = (e ^ Create) | FileNotifyChangeFileName
   134  	}
   135  	if e&Remove != 0 {
   136  		e = (e ^ Remove) | FileNotifyChangeFileName
   137  	}
   138  	if e&Write != 0 {
   139  		e = (e ^ Write) | FileNotifyChangeAttributes | FileNotifyChangeSize |
   140  			FileNotifyChangeCreation | FileNotifyChangeSecurity
   141  	}
   142  	if e&Rename != 0 {
   143  		e = (e ^ Rename) | FileNotifyChangeFileName
   144  	}
   145  	return uint32(e)
   146  }
   147  
   148  // watched is made in order to check whether an action comes from a directory or
   149  // file. This approach requires two file handlers per single monitored folder. The
   150  // second grip handles actions which include creating or deleting a directory. If
   151  // these processes are not monitored, only the first grip is created.
   152  type watched struct {
   153  	filter    uint32
   154  	recursive bool
   155  	count     uint8
   156  	pathw     []uint16
   157  	digrip    [2]*grip
   158  }
   159  
   160  // newWatched creates a new watched instance. It splits the filter variable into
   161  // two parts. The first part is responsible for watching all events which can be
   162  // created for a file in watched directory structure and the second one watches
   163  // only directory Create/Remove actions. If all operations succeed, the Create
   164  // message is sent to I/O completion port queue for further processing.
   165  func newWatched(cph syscall.Handle, filter uint32, recursive bool,
   166  	path string) (wd *watched, err error) {
   167  	wd = &watched{
   168  		filter:    filter,
   169  		recursive: recursive,
   170  	}
   171  	if wd.pathw, err = syscall.UTF16FromString(path); err != nil {
   172  		return
   173  	}
   174  	if err = wd.recreate(cph); err != nil {
   175  		return
   176  	}
   177  	return wd, nil
   178  }
   179  
   180  // TODO : doc
   181  func (wd *watched) recreate(cph syscall.Handle) (err error) {
   182  	filefilter := wd.filter &^ uint32(FileNotifyChangeDirName)
   183  	if err = wd.updateGrip(0, cph, filefilter == 0, filefilter); err != nil {
   184  		return
   185  	}
   186  	dirfilter := wd.filter & uint32(FileNotifyChangeDirName|Create|Remove)
   187  	if err = wd.updateGrip(1, cph, dirfilter == 0, wd.filter|uint32(dirmarker)); err != nil {
   188  		return
   189  	}
   190  	wd.filter &^= onlyMachineStates
   191  	return
   192  }
   193  
   194  // TODO : doc
   195  func (wd *watched) updateGrip(idx int, cph syscall.Handle, reset bool,
   196  	newflag uint32) (err error) {
   197  	if reset {
   198  		wd.digrip[idx] = nil
   199  	} else {
   200  		if wd.digrip[idx] == nil {
   201  			if wd.digrip[idx], err = newGrip(cph, wd, newflag); err != nil {
   202  				wd.closeHandle()
   203  				return
   204  			}
   205  		} else {
   206  			wd.digrip[idx].filter = newflag
   207  			wd.digrip[idx].recursive = wd.recursive
   208  			if err = wd.digrip[idx].register(cph); err != nil {
   209  				wd.closeHandle()
   210  				return
   211  			}
   212  		}
   213  		wd.count++
   214  	}
   215  	return
   216  }
   217  
   218  // closeHandle closes handles that are stored in digrip array. Function always
   219  // tries to close all of the handlers before it exits, even when there are errors
   220  // returned from the operating system kernel.
   221  func (wd *watched) closeHandle() (err error) {
   222  	for _, g := range wd.digrip {
   223  		if g != nil && g.handle != syscall.InvalidHandle {
   224  			switch suberr := syscall.CloseHandle(g.handle); {
   225  			case suberr == nil:
   226  				g.handle = syscall.InvalidHandle
   227  			case err == nil:
   228  				err = suberr
   229  			}
   230  		}
   231  	}
   232  	return
   233  }
   234  
   235  // watcher implements Watcher interface. It stores a set of watched directories.
   236  // All operations which remove watched objects from map `m` must be performed in
   237  // loop goroutine since these structures are used internally by operating system.
   238  type readdcw struct {
   239  	sync.Mutex
   240  	m     map[string]*watched
   241  	cph   syscall.Handle
   242  	start bool
   243  	wg    sync.WaitGroup
   244  	c     chan<- EventInfo
   245  }
   246  
   247  // NewWatcher creates new non-recursive watcher backed by ReadDirectoryChangesW.
   248  func newWatcher(c chan<- EventInfo) watcher {
   249  	r := &readdcw{
   250  		m:   make(map[string]*watched),
   251  		cph: syscall.InvalidHandle,
   252  		c:   c,
   253  	}
   254  	runtime.SetFinalizer(r, func(r *readdcw) {
   255  		if r.cph != syscall.InvalidHandle {
   256  			syscall.CloseHandle(r.cph)
   257  		}
   258  	})
   259  	return r
   260  }
   261  
   262  // Watch implements notify.Watcher interface.
   263  func (r *readdcw) Watch(path string, event Event) error {
   264  	return r.watch(path, event, false)
   265  }
   266  
   267  // RecursiveWatch implements notify.RecursiveWatcher interface.
   268  func (r *readdcw) RecursiveWatch(path string, event Event) error {
   269  	return r.watch(path, event, true)
   270  }
   271  
   272  // watch inserts a directory to the group of watched folders. If watched folder
   273  // already exists, function tries to rewatch it with new filters(NOT VALID). Moreover,
   274  // watch starts the main event loop goroutine when called for the first time.
   275  func (r *readdcw) watch(path string, event Event, recursive bool) (err error) {
   276  	if event&^(All|fileNotifyChangeAll) != 0 {
   277  		return errors.New("notify: unknown event")
   278  	}
   279  	r.Lock()
   280  	wd, ok := r.m[path]
   281  	r.Unlock()
   282  	if !ok {
   283  		if err = r.lazyinit(); err != nil {
   284  			return
   285  		}
   286  		r.Lock()
   287  		defer r.Unlock()
   288  		if wd, ok = r.m[path]; ok {
   289  			dbgprint("watch: exists already")
   290  			return
   291  		}
   292  		if wd, err = newWatched(r.cph, uint32(event), recursive, path); err != nil {
   293  			return
   294  		}
   295  		r.m[path] = wd
   296  		dbgprint("watch: new watch added")
   297  	} else {
   298  		dbgprint("watch: exists already")
   299  	}
   300  	return nil
   301  }
   302  
   303  // lazyinit creates an I/O completion port and starts the main event processing
   304  // loop. This method uses Double-Checked Locking optimization.
   305  func (r *readdcw) lazyinit() (err error) {
   306  	invalid := uintptr(syscall.InvalidHandle)
   307  	if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid {
   308  		r.Lock()
   309  		defer r.Unlock()
   310  		if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid {
   311  			cph := syscall.InvalidHandle
   312  			if cph, err = syscall.CreateIoCompletionPort(cph, 0, 0, 0); err != nil {
   313  				return
   314  			}
   315  			r.cph, r.start = cph, true
   316  			go r.loop()
   317  		}
   318  	}
   319  	return
   320  }
   321  
   322  // TODO(pknap) : doc
   323  func (r *readdcw) loop() {
   324  	var n, key uint32
   325  	var overlapped *syscall.Overlapped
   326  	for {
   327  		err := syscall.GetQueuedCompletionStatus(r.cph, &n, &key, &overlapped, syscall.INFINITE)
   328  		if key == stateCPClose {
   329  			r.Lock()
   330  			handle := r.cph
   331  			r.cph = syscall.InvalidHandle
   332  			r.Unlock()
   333  			syscall.CloseHandle(handle)
   334  			r.wg.Done()
   335  			return
   336  		}
   337  		if overlapped == nil {
   338  			// TODO: check key == rewatch delete or 0(panic)
   339  			continue
   340  		}
   341  		overEx := (*overlappedEx)(unsafe.Pointer(overlapped))
   342  		if n != 0 {
   343  			r.loopevent(n, overEx)
   344  			if err = overEx.parent.readDirChanges(); err != nil {
   345  				// TODO: error handling
   346  			}
   347  		}
   348  		r.loopstate(overEx)
   349  	}
   350  }
   351  
   352  // TODO(pknap) : doc
   353  func (r *readdcw) loopstate(overEx *overlappedEx) {
   354  	r.Lock()
   355  	defer r.Unlock()
   356  	filter := overEx.parent.parent.filter
   357  	if filter&onlyMachineStates == 0 {
   358  		return
   359  	}
   360  	if overEx.parent.parent.count--; overEx.parent.parent.count == 0 {
   361  		switch filter & onlyMachineStates {
   362  		case stateRewatch:
   363  			dbgprint("loopstate rewatch")
   364  			overEx.parent.parent.recreate(r.cph)
   365  		case stateUnwatch:
   366  			dbgprint("loopstate unwatch")
   367  			delete(r.m, syscall.UTF16ToString(overEx.parent.pathw))
   368  		case stateCPClose:
   369  		default:
   370  			panic(`notify: windows loopstate logic error`)
   371  		}
   372  	}
   373  }
   374  
   375  // TODO(pknap) : doc
   376  func (r *readdcw) loopevent(n uint32, overEx *overlappedEx) {
   377  	events := []*event{}
   378  	var currOffset uint32
   379  	for {
   380  		raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&overEx.parent.buffer[currOffset]))
   381  		name := syscall.UTF16ToString((*[syscall.MAX_LONG_PATH]uint16)(unsafe.Pointer(&raw.FileName))[:raw.FileNameLength>>1])
   382  		events = append(events, &event{
   383  			pathw:  overEx.parent.pathw,
   384  			filter: overEx.parent.filter,
   385  			action: raw.Action,
   386  			name:   name,
   387  		})
   388  		if raw.NextEntryOffset == 0 {
   389  			break
   390  		}
   391  		if currOffset += raw.NextEntryOffset; currOffset >= n {
   392  			break
   393  		}
   394  	}
   395  	r.send(events)
   396  }
   397  
   398  // TODO(pknap) : doc
   399  func (r *readdcw) send(es []*event) {
   400  	for _, e := range es {
   401  		var syse Event
   402  		if e.e, syse = decode(e.filter, e.action); e.e == 0 && syse == 0 {
   403  			continue
   404  		}
   405  		switch {
   406  		case e.action == syscall.FILE_ACTION_MODIFIED:
   407  			e.ftype = fTypeUnknown
   408  		case e.filter&uint32(dirmarker) != 0:
   409  			e.ftype = fTypeDirectory
   410  		default:
   411  			e.ftype = fTypeFile
   412  		}
   413  		switch {
   414  		case e.e == 0:
   415  			e.e = syse
   416  		case syse != 0:
   417  			r.c <- &event{
   418  				pathw:  e.pathw,
   419  				name:   e.name,
   420  				ftype:  e.ftype,
   421  				action: e.action,
   422  				filter: e.filter,
   423  				e:      syse,
   424  			}
   425  		}
   426  		r.c <- e
   427  	}
   428  }
   429  
   430  // Rewatch implements notify.Rewatcher interface.
   431  func (r *readdcw) Rewatch(path string, oldevent, newevent Event) error {
   432  	return r.rewatch(path, uint32(oldevent), uint32(newevent), false)
   433  }
   434  
   435  // RecursiveRewatch implements notify.RecursiveRewatcher interface.
   436  func (r *readdcw) RecursiveRewatch(oldpath, newpath string, oldevent,
   437  	newevent Event) error {
   438  	if oldpath != newpath {
   439  		if err := r.unwatch(oldpath); err != nil {
   440  			return err
   441  		}
   442  		return r.watch(newpath, newevent, true)
   443  	}
   444  	return r.rewatch(newpath, uint32(oldevent), uint32(newevent), true)
   445  }
   446  
   447  // TODO : (pknap) doc.
   448  func (r *readdcw) rewatch(path string, oldevent, newevent uint32, recursive bool) (err error) {
   449  	if Event(newevent)&^(All|fileNotifyChangeAll) != 0 {
   450  		return errors.New("notify: unknown event")
   451  	}
   452  	var wd *watched
   453  	r.Lock()
   454  	defer r.Unlock()
   455  	if wd, err = r.nonStateWatchedLocked(path); err != nil {
   456  		return
   457  	}
   458  	if wd.filter&(onlyNotifyChanges|onlyNGlobalEvents) != oldevent {
   459  		panic(`notify: windows re-watcher logic error`)
   460  	}
   461  	wd.filter = stateRewatch | newevent
   462  	wd.recursive, recursive = recursive, wd.recursive
   463  	if err = wd.closeHandle(); err != nil {
   464  		wd.filter = oldevent
   465  		wd.recursive = recursive
   466  		return
   467  	}
   468  	return
   469  }
   470  
   471  // TODO : pknap
   472  func (r *readdcw) nonStateWatchedLocked(path string) (wd *watched, err error) {
   473  	wd, ok := r.m[path]
   474  	if !ok || wd == nil {
   475  		err = errors.New(`notify: ` + path + ` path is unwatched`)
   476  		return
   477  	}
   478  	if wd.filter&onlyMachineStates != 0 {
   479  		err = errors.New(`notify: another re/unwatching operation in progress`)
   480  		return
   481  	}
   482  	return
   483  }
   484  
   485  // Unwatch implements notify.Watcher interface.
   486  func (r *readdcw) Unwatch(path string) error {
   487  	return r.unwatch(path)
   488  }
   489  
   490  // RecursiveUnwatch implements notify.RecursiveWatcher interface.
   491  func (r *readdcw) RecursiveUnwatch(path string) error {
   492  	return r.unwatch(path)
   493  }
   494  
   495  // TODO : pknap
   496  func (r *readdcw) unwatch(path string) (err error) {
   497  	var wd *watched
   498  	r.Lock()
   499  	defer r.Unlock()
   500  	if wd, err = r.nonStateWatchedLocked(path); err != nil {
   501  		return
   502  	}
   503  	wd.filter |= stateUnwatch
   504  	if err = wd.closeHandle(); err != nil {
   505  		wd.filter &^= stateUnwatch
   506  		return
   507  	}
   508  	if _, attrErr := syscall.GetFileAttributes(&wd.pathw[0]); attrErr != nil {
   509  		for _, g := range wd.digrip {
   510  			if g != nil {
   511  				dbgprint("unwatch: posting")
   512  				if err = syscall.PostQueuedCompletionStatus(r.cph, 0, 0, (*syscall.Overlapped)(unsafe.Pointer(g.ovlapped))); err != nil {
   513  					wd.filter &^= stateUnwatch
   514  					return
   515  				}
   516  			}
   517  		}
   518  	}
   519  	return
   520  }
   521  
   522  // Close resets the whole watcher object, closes all existing file descriptors,
   523  // and sends stateCPClose state as completion key to the main watcher's loop.
   524  func (r *readdcw) Close() (err error) {
   525  	r.Lock()
   526  	if !r.start {
   527  		r.Unlock()
   528  		return nil
   529  	}
   530  	for _, wd := range r.m {
   531  		wd.filter &^= onlyMachineStates
   532  		wd.filter |= stateCPClose
   533  		if e := wd.closeHandle(); e != nil && err == nil {
   534  			err = e
   535  		}
   536  	}
   537  	r.start = false
   538  	r.Unlock()
   539  	r.wg.Add(1)
   540  	if e := syscall.PostQueuedCompletionStatus(r.cph, 0, stateCPClose, nil); e != nil && err == nil {
   541  		return e
   542  	}
   543  	r.wg.Wait()
   544  	return
   545  }
   546  
   547  // decode creates a notify event from both non-raw filter and action which was
   548  // returned from completion routine. Function may return Event(0) in case when
   549  // filter was replaced by a new value which does not contain fields that are
   550  // valid with passed action.
   551  func decode(filter, action uint32) (Event, Event) {
   552  	switch action {
   553  	case syscall.FILE_ACTION_ADDED:
   554  		return gensys(filter, Create, FileActionAdded)
   555  	case syscall.FILE_ACTION_REMOVED:
   556  		return gensys(filter, Remove, FileActionRemoved)
   557  	case syscall.FILE_ACTION_MODIFIED:
   558  		return gensys(filter, Write, FileActionModified)
   559  	case syscall.FILE_ACTION_RENAMED_OLD_NAME:
   560  		return gensys(filter, Rename, FileActionRenamedOldName)
   561  	case syscall.FILE_ACTION_RENAMED_NEW_NAME:
   562  		return gensys(filter, Rename, FileActionRenamedNewName)
   563  	}
   564  	panic(`notify: cannot decode internal mask`)
   565  }
   566  
   567  // gensys decides whether the Windows action, system-independent event or both
   568  // of them should be returned. Since the grip's filter may be atomically changed
   569  // during watcher lifetime, it is possible that neither Windows nor notify masks
   570  // are watched by the user when this function is called.
   571  func gensys(filter uint32, ge, se Event) (gene, syse Event) {
   572  	isdir := filter&uint32(dirmarker) != 0
   573  	if isdir && filter&uint32(FileNotifyChangeDirName) != 0 ||
   574  		!isdir && filter&uint32(FileNotifyChangeFileName) != 0 ||
   575  		filter&uint32(fileNotifyChangeModified) != 0 {
   576  		syse = se
   577  	}
   578  	if filter&uint32(ge) != 0 {
   579  		gene = ge
   580  	}
   581  	return
   582  }