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