github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/exp/fsnotify/fsnotify_windows.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fsnotify
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"sync"
    14  	"syscall"
    15  	"unsafe"
    16  )
    17  
    18  const (
    19  	// Options for AddWatch
    20  	sys_FS_ONESHOT = 0x80000000
    21  	sys_FS_ONLYDIR = 0x1000000
    22  
    23  	// Events
    24  	sys_FS_ACCESS      = 0x1
    25  	sys_FS_ALL_EVENTS  = 0xfff
    26  	sys_FS_ATTRIB      = 0x4
    27  	sys_FS_CLOSE       = 0x18
    28  	sys_FS_CREATE      = 0x100
    29  	sys_FS_DELETE      = 0x200
    30  	sys_FS_DELETE_SELF = 0x400
    31  	sys_FS_MODIFY      = 0x2
    32  	sys_FS_MOVE        = 0xc0
    33  	sys_FS_MOVED_FROM  = 0x40
    34  	sys_FS_MOVED_TO    = 0x80
    35  	sys_FS_MOVE_SELF   = 0x800
    36  
    37  	// Special events
    38  	sys_FS_IGNORED    = 0x8000
    39  	sys_FS_Q_OVERFLOW = 0x4000
    40  )
    41  
    42  const (
    43  	// TODO(nj): Use syscall.ERROR_MORE_DATA from ztypes_windows in Go 1.3+
    44  	sys_ERROR_MORE_DATA syscall.Errno = 234
    45  )
    46  
    47  // Event is the type of the notification messages
    48  // received on the watcher's Event channel.
    49  type FileEvent struct {
    50  	mask   uint32 // Mask of events
    51  	cookie uint32 // Unique cookie associating related events (for rename)
    52  	Name   string // File name (optional)
    53  }
    54  
    55  // IsCreate reports whether the FileEvent was triggered by a creation
    56  func (e *FileEvent) IsCreate() bool { return (e.mask & sys_FS_CREATE) == sys_FS_CREATE }
    57  
    58  // IsDelete reports whether the FileEvent was triggered by a delete
    59  func (e *FileEvent) IsDelete() bool {
    60  	return ((e.mask&sys_FS_DELETE) == sys_FS_DELETE || (e.mask&sys_FS_DELETE_SELF) == sys_FS_DELETE_SELF)
    61  }
    62  
    63  // IsModify reports whether the FileEvent was triggered by a file modification or attribute change
    64  func (e *FileEvent) IsModify() bool {
    65  	return ((e.mask&sys_FS_MODIFY) == sys_FS_MODIFY || (e.mask&sys_FS_ATTRIB) == sys_FS_ATTRIB)
    66  }
    67  
    68  // IsRename reports whether the FileEvent was triggered by a change name
    69  func (e *FileEvent) IsRename() bool {
    70  	return ((e.mask&sys_FS_MOVE) == sys_FS_MOVE || (e.mask&sys_FS_MOVE_SELF) == sys_FS_MOVE_SELF || (e.mask&sys_FS_MOVED_FROM) == sys_FS_MOVED_FROM || (e.mask&sys_FS_MOVED_TO) == sys_FS_MOVED_TO)
    71  }
    72  
    73  // IsAttrib reports whether the FileEvent was triggered by a change in the file metadata.
    74  func (e *FileEvent) IsAttrib() bool {
    75  	return (e.mask & sys_FS_ATTRIB) == sys_FS_ATTRIB
    76  }
    77  
    78  const (
    79  	opAddWatch = iota
    80  	opRemoveWatch
    81  )
    82  
    83  const (
    84  	provisional uint64 = 1 << (32 + iota)
    85  )
    86  
    87  type input struct {
    88  	op    int
    89  	path  string
    90  	flags uint32
    91  	reply chan error
    92  }
    93  
    94  type inode struct {
    95  	handle syscall.Handle
    96  	volume uint32
    97  	index  uint64
    98  }
    99  
   100  type watch struct {
   101  	ov     syscall.Overlapped
   102  	ino    *inode            // i-number
   103  	path   string            // Directory path
   104  	mask   uint64            // Directory itself is being watched with these notify flags
   105  	names  map[string]uint64 // Map of names being watched and their notify flags
   106  	rename string            // Remembers the old name while renaming a file
   107  	buf    [4096]byte
   108  }
   109  
   110  type indexMap map[uint64]*watch
   111  type watchMap map[uint32]indexMap
   112  
   113  // A Watcher waits for and receives event notifications
   114  // for a specific set of files and directories.
   115  type Watcher struct {
   116  	mu       sync.Mutex      // Map access
   117  	port     syscall.Handle  // Handle to completion port
   118  	watches  watchMap        // Map of watches (key: i-number)
   119  	input    chan *input     // Inputs to the reader are sent on this channel
   120  	Event    chan *FileEvent // Events are returned on this channel
   121  	Error    chan error      // Errors are sent on this channel
   122  	isClosed bool            // Set to true when Close() is first called
   123  	quit     chan chan<- error
   124  	cookie   uint32
   125  }
   126  
   127  // NewWatcher creates and returns a Watcher.
   128  func NewWatcher() (*Watcher, error) {
   129  	port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
   130  	if e != nil {
   131  		return nil, os.NewSyscallError("CreateIoCompletionPort", e)
   132  	}
   133  	w := &Watcher{
   134  		port:    port,
   135  		watches: make(watchMap),
   136  		input:   make(chan *input, 1),
   137  		Event:   make(chan *FileEvent, 50),
   138  		Error:   make(chan error),
   139  		quit:    make(chan chan<- error, 1),
   140  	}
   141  	go w.readEvents()
   142  	return w, nil
   143  }
   144  
   145  // Close closes a Watcher.
   146  // It sends a message to the reader goroutine to quit and removes all watches
   147  // associated with the watcher.
   148  func (w *Watcher) Close() error {
   149  	if w.isClosed {
   150  		return nil
   151  	}
   152  	w.isClosed = true
   153  
   154  	// Send "quit" message to the reader goroutine
   155  	ch := make(chan error)
   156  	w.quit <- ch
   157  	if err := w.wakeupReader(); err != nil {
   158  		return err
   159  	}
   160  	return <-ch
   161  }
   162  
   163  // AddWatch adds path to the watched file set.
   164  func (w *Watcher) AddWatch(path string, flags uint32) error {
   165  	if w.isClosed {
   166  		return errors.New("watcher already closed")
   167  	}
   168  	in := &input{
   169  		op:    opAddWatch,
   170  		path:  filepath.Clean(path),
   171  		flags: flags,
   172  		reply: make(chan error),
   173  	}
   174  	w.input <- in
   175  	if err := w.wakeupReader(); err != nil {
   176  		return err
   177  	}
   178  	return <-in.reply
   179  }
   180  
   181  // Watch adds path to the watched file set, watching all events.
   182  func (w *Watcher) watch(path string) error {
   183  	return w.AddWatch(path, sys_FS_ALL_EVENTS)
   184  }
   185  
   186  // RemoveWatch removes path from the watched file set.
   187  func (w *Watcher) removeWatch(path string) error {
   188  	in := &input{
   189  		op:    opRemoveWatch,
   190  		path:  filepath.Clean(path),
   191  		reply: make(chan error),
   192  	}
   193  	w.input <- in
   194  	if err := w.wakeupReader(); err != nil {
   195  		return err
   196  	}
   197  	return <-in.reply
   198  }
   199  
   200  func (w *Watcher) wakeupReader() error {
   201  	e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
   202  	if e != nil {
   203  		return os.NewSyscallError("PostQueuedCompletionStatus", e)
   204  	}
   205  	return nil
   206  }
   207  
   208  func getDir(pathname string) (dir string, err error) {
   209  	attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
   210  	if e != nil {
   211  		return "", os.NewSyscallError("GetFileAttributes", e)
   212  	}
   213  	if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
   214  		dir = pathname
   215  	} else {
   216  		dir, _ = filepath.Split(pathname)
   217  		dir = filepath.Clean(dir)
   218  	}
   219  	return
   220  }
   221  
   222  func getIno(path string) (ino *inode, err error) {
   223  	h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
   224  		syscall.FILE_LIST_DIRECTORY,
   225  		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
   226  		nil, syscall.OPEN_EXISTING,
   227  		syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
   228  	if e != nil {
   229  		return nil, os.NewSyscallError("CreateFile", e)
   230  	}
   231  	var fi syscall.ByHandleFileInformation
   232  	if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
   233  		syscall.CloseHandle(h)
   234  		return nil, os.NewSyscallError("GetFileInformationByHandle", e)
   235  	}
   236  	ino = &inode{
   237  		handle: h,
   238  		volume: fi.VolumeSerialNumber,
   239  		index:  uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
   240  	}
   241  	return ino, nil
   242  }
   243  
   244  // Must run within the I/O thread.
   245  func (m watchMap) get(ino *inode) *watch {
   246  	if i := m[ino.volume]; i != nil {
   247  		return i[ino.index]
   248  	}
   249  	return nil
   250  }
   251  
   252  // Must run within the I/O thread.
   253  func (m watchMap) set(ino *inode, watch *watch) {
   254  	i := m[ino.volume]
   255  	if i == nil {
   256  		i = make(indexMap)
   257  		m[ino.volume] = i
   258  	}
   259  	i[ino.index] = watch
   260  }
   261  
   262  // Must run within the I/O thread.
   263  func (w *Watcher) addWatch(pathname string, flags uint64) error {
   264  	dir, err := getDir(pathname)
   265  	if err != nil {
   266  		return err
   267  	}
   268  	if flags&sys_FS_ONLYDIR != 0 && pathname != dir {
   269  		return nil
   270  	}
   271  	ino, err := getIno(dir)
   272  	if err != nil {
   273  		return err
   274  	}
   275  	w.mu.Lock()
   276  	watchEntry := w.watches.get(ino)
   277  	w.mu.Unlock()
   278  	if watchEntry == nil {
   279  		if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
   280  			syscall.CloseHandle(ino.handle)
   281  			return os.NewSyscallError("CreateIoCompletionPort", e)
   282  		}
   283  		watchEntry = &watch{
   284  			ino:   ino,
   285  			path:  dir,
   286  			names: make(map[string]uint64),
   287  		}
   288  		w.mu.Lock()
   289  		w.watches.set(ino, watchEntry)
   290  		w.mu.Unlock()
   291  		flags |= provisional
   292  	} else {
   293  		syscall.CloseHandle(ino.handle)
   294  	}
   295  	if pathname == dir {
   296  		watchEntry.mask |= flags
   297  	} else {
   298  		watchEntry.names[filepath.Base(pathname)] |= flags
   299  	}
   300  	if err = w.startRead(watchEntry); err != nil {
   301  		return err
   302  	}
   303  	if pathname == dir {
   304  		watchEntry.mask &= ^provisional
   305  	} else {
   306  		watchEntry.names[filepath.Base(pathname)] &= ^provisional
   307  	}
   308  	return nil
   309  }
   310  
   311  // Must run within the I/O thread.
   312  func (w *Watcher) remWatch(pathname string) error {
   313  	dir, err := getDir(pathname)
   314  	if err != nil {
   315  		return err
   316  	}
   317  	ino, err := getIno(dir)
   318  	if err != nil {
   319  		return err
   320  	}
   321  	w.mu.Lock()
   322  	watch := w.watches.get(ino)
   323  	w.mu.Unlock()
   324  	if watch == nil {
   325  		return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
   326  	}
   327  	if pathname == dir {
   328  		w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED)
   329  		watch.mask = 0
   330  	} else {
   331  		name := filepath.Base(pathname)
   332  		w.sendEvent(watch.path+"\\"+name, watch.names[name]&sys_FS_IGNORED)
   333  		delete(watch.names, name)
   334  	}
   335  	return w.startRead(watch)
   336  }
   337  
   338  // Must run within the I/O thread.
   339  func (w *Watcher) deleteWatch(watch *watch) {
   340  	for name, mask := range watch.names {
   341  		if mask&provisional == 0 {
   342  			w.sendEvent(watch.path+"\\"+name, mask&sys_FS_IGNORED)
   343  		}
   344  		delete(watch.names, name)
   345  	}
   346  	if watch.mask != 0 {
   347  		if watch.mask&provisional == 0 {
   348  			w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED)
   349  		}
   350  		watch.mask = 0
   351  	}
   352  }
   353  
   354  // Must run within the I/O thread.
   355  func (w *Watcher) startRead(watch *watch) error {
   356  	if e := syscall.CancelIo(watch.ino.handle); e != nil {
   357  		w.Error <- os.NewSyscallError("CancelIo", e)
   358  		w.deleteWatch(watch)
   359  	}
   360  	mask := toWindowsFlags(watch.mask)
   361  	for _, m := range watch.names {
   362  		mask |= toWindowsFlags(m)
   363  	}
   364  	if mask == 0 {
   365  		if e := syscall.CloseHandle(watch.ino.handle); e != nil {
   366  			w.Error <- os.NewSyscallError("CloseHandle", e)
   367  		}
   368  		w.mu.Lock()
   369  		delete(w.watches[watch.ino.volume], watch.ino.index)
   370  		w.mu.Unlock()
   371  		return nil
   372  	}
   373  	e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
   374  		uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
   375  	if e != nil {
   376  		err := os.NewSyscallError("ReadDirectoryChanges", e)
   377  		if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
   378  			// Watched directory was probably removed
   379  			if w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF) {
   380  				if watch.mask&sys_FS_ONESHOT != 0 {
   381  					watch.mask = 0
   382  				}
   383  			}
   384  			err = nil
   385  		}
   386  		w.deleteWatch(watch)
   387  		w.startRead(watch)
   388  		return err
   389  	}
   390  	return nil
   391  }
   392  
   393  // readEvents reads from the I/O completion port, converts the
   394  // received events into Event objects and sends them via the Event channel.
   395  // Entry point to the I/O thread.
   396  func (w *Watcher) readEvents() {
   397  	var (
   398  		n, key uint32
   399  		ov     *syscall.Overlapped
   400  	)
   401  	runtime.LockOSThread()
   402  
   403  	for {
   404  		e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
   405  		watch := (*watch)(unsafe.Pointer(ov))
   406  
   407  		if watch == nil {
   408  			select {
   409  			case ch := <-w.quit:
   410  				w.mu.Lock()
   411  				var indexes []indexMap
   412  				for _, index := range w.watches {
   413  					indexes = append(indexes, index)
   414  				}
   415  				w.mu.Unlock()
   416  				for _, index := range indexes {
   417  					for _, watch := range index {
   418  						w.deleteWatch(watch)
   419  						w.startRead(watch)
   420  					}
   421  				}
   422  				var err error
   423  				if e := syscall.CloseHandle(w.port); e != nil {
   424  					err = os.NewSyscallError("CloseHandle", e)
   425  				}
   426  				close(w.Event)
   427  				close(w.Error)
   428  				ch <- err
   429  				return
   430  			case in := <-w.input:
   431  				switch in.op {
   432  				case opAddWatch:
   433  					in.reply <- w.addWatch(in.path, uint64(in.flags))
   434  				case opRemoveWatch:
   435  					in.reply <- w.remWatch(in.path)
   436  				}
   437  			default:
   438  			}
   439  			continue
   440  		}
   441  
   442  		switch e {
   443  		case sys_ERROR_MORE_DATA:
   444  			if watch == nil {
   445  				w.Error <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
   446  			} else {
   447  				// The i/o succeeded but the buffer is full.
   448  				// In theory we should be building up a full packet.
   449  				// In practice we can get away with just carrying on.
   450  				n = uint32(unsafe.Sizeof(watch.buf))
   451  			}
   452  		case syscall.ERROR_ACCESS_DENIED:
   453  			// Watched directory was probably removed
   454  			w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF)
   455  			w.deleteWatch(watch)
   456  			w.startRead(watch)
   457  			continue
   458  		case syscall.ERROR_OPERATION_ABORTED:
   459  			// CancelIo was called on this handle
   460  			continue
   461  		default:
   462  			w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e)
   463  			continue
   464  		case nil:
   465  		}
   466  
   467  		var offset uint32
   468  		for {
   469  			if n == 0 {
   470  				w.Event <- &FileEvent{mask: sys_FS_Q_OVERFLOW}
   471  				w.Error <- errors.New("short read in readEvents()")
   472  				break
   473  			}
   474  
   475  			// Point "raw" to the event in the buffer
   476  			raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
   477  			buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
   478  			name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
   479  			fullname := watch.path + "\\" + name
   480  
   481  			var mask uint64
   482  			switch raw.Action {
   483  			case syscall.FILE_ACTION_REMOVED:
   484  				mask = sys_FS_DELETE_SELF
   485  			case syscall.FILE_ACTION_MODIFIED:
   486  				mask = sys_FS_MODIFY
   487  			case syscall.FILE_ACTION_RENAMED_OLD_NAME:
   488  				watch.rename = name
   489  			case syscall.FILE_ACTION_RENAMED_NEW_NAME:
   490  				if watch.names[watch.rename] != 0 {
   491  					watch.names[name] |= watch.names[watch.rename]
   492  					delete(watch.names, watch.rename)
   493  					mask = sys_FS_MOVE_SELF
   494  				}
   495  			}
   496  
   497  			sendNameEvent := func() {
   498  				if w.sendEvent(fullname, watch.names[name]&mask) {
   499  					if watch.names[name]&sys_FS_ONESHOT != 0 {
   500  						delete(watch.names, name)
   501  					}
   502  				}
   503  			}
   504  			if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
   505  				sendNameEvent()
   506  			}
   507  			if raw.Action == syscall.FILE_ACTION_REMOVED {
   508  				w.sendEvent(fullname, watch.names[name]&sys_FS_IGNORED)
   509  				delete(watch.names, name)
   510  			}
   511  			if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
   512  				if watch.mask&sys_FS_ONESHOT != 0 {
   513  					watch.mask = 0
   514  				}
   515  			}
   516  			if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
   517  				fullname = watch.path + "\\" + watch.rename
   518  				sendNameEvent()
   519  			}
   520  
   521  			// Move to the next event in the buffer
   522  			if raw.NextEntryOffset == 0 {
   523  				break
   524  			}
   525  			offset += raw.NextEntryOffset
   526  
   527  			// Error!
   528  			if offset >= n {
   529  				w.Error <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
   530  				break
   531  			}
   532  		}
   533  
   534  		if err := w.startRead(watch); err != nil {
   535  			w.Error <- err
   536  		}
   537  	}
   538  }
   539  
   540  func (w *Watcher) sendEvent(name string, mask uint64) bool {
   541  	if mask == 0 {
   542  		return false
   543  	}
   544  	event := &FileEvent{mask: uint32(mask), Name: name}
   545  	if mask&sys_FS_MOVE != 0 {
   546  		if mask&sys_FS_MOVED_FROM != 0 {
   547  			w.cookie++
   548  		}
   549  		event.cookie = w.cookie
   550  	}
   551  	select {
   552  	case ch := <-w.quit:
   553  		w.quit <- ch
   554  	case w.Event <- event:
   555  	}
   556  	return true
   557  }
   558  
   559  func toWindowsFlags(mask uint64) uint32 {
   560  	var m uint32
   561  	if mask&sys_FS_ACCESS != 0 {
   562  		m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
   563  	}
   564  	if mask&sys_FS_MODIFY != 0 {
   565  		m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
   566  	}
   567  	if mask&sys_FS_ATTRIB != 0 {
   568  		m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
   569  	}
   570  	if mask&(sys_FS_MOVE|sys_FS_CREATE|sys_FS_DELETE) != 0 {
   571  		m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
   572  	}
   573  	return m
   574  }
   575  
   576  func toFSnotifyFlags(action uint32) uint64 {
   577  	switch action {
   578  	case syscall.FILE_ACTION_ADDED:
   579  		return sys_FS_CREATE
   580  	case syscall.FILE_ACTION_REMOVED:
   581  		return sys_FS_DELETE
   582  	case syscall.FILE_ACTION_MODIFIED:
   583  		return sys_FS_MODIFY
   584  	case syscall.FILE_ACTION_RENAMED_OLD_NAME:
   585  		return sys_FS_MOVED_FROM
   586  	case syscall.FILE_ACTION_RENAMED_NEW_NAME:
   587  		return sys_FS_MOVED_TO
   588  	}
   589  	return 0
   590  }