vendor/github.com/fsnotify/fsnotify/kqueue.go
changeset 265 05c40b36d3b2
parent 264 8f478162d991
child 266 80973a656b81
equal deleted inserted replaced
264:8f478162d991 265:05c40b36d3b2
     1 // Copyright 2010 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 //go:build freebsd || openbsd || netbsd || dragonfly || darwin
       
     6 // +build freebsd openbsd netbsd dragonfly darwin
       
     7 
       
     8 package fsnotify
       
     9 
       
    10 import (
       
    11 	"errors"
       
    12 	"fmt"
       
    13 	"io/ioutil"
       
    14 	"os"
       
    15 	"path/filepath"
       
    16 	"sync"
       
    17 	"time"
       
    18 
       
    19 	"golang.org/x/sys/unix"
       
    20 )
       
    21 
       
    22 // Watcher watches a set of files, delivering events to a channel.
       
    23 type Watcher struct {
       
    24 	Events chan Event
       
    25 	Errors chan error
       
    26 	done   chan struct{} // Channel for sending a "quit message" to the reader goroutine
       
    27 
       
    28 	kq int // File descriptor (as returned by the kqueue() syscall).
       
    29 
       
    30 	mu              sync.Mutex        // Protects access to watcher data
       
    31 	watches         map[string]int    // Map of watched file descriptors (key: path).
       
    32 	externalWatches map[string]bool   // Map of watches added by user of the library.
       
    33 	dirFlags        map[string]uint32 // Map of watched directories to fflags used in kqueue.
       
    34 	paths           map[int]pathInfo  // Map file descriptors to path names for processing kqueue events.
       
    35 	fileExists      map[string]bool   // Keep track of if we know this file exists (to stop duplicate create events).
       
    36 	isClosed        bool              // Set to true when Close() is first called
       
    37 }
       
    38 
       
    39 type pathInfo struct {
       
    40 	name  string
       
    41 	isDir bool
       
    42 }
       
    43 
       
    44 // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
       
    45 func NewWatcher() (*Watcher, error) {
       
    46 	kq, err := kqueue()
       
    47 	if err != nil {
       
    48 		return nil, err
       
    49 	}
       
    50 
       
    51 	w := &Watcher{
       
    52 		kq:              kq,
       
    53 		watches:         make(map[string]int),
       
    54 		dirFlags:        make(map[string]uint32),
       
    55 		paths:           make(map[int]pathInfo),
       
    56 		fileExists:      make(map[string]bool),
       
    57 		externalWatches: make(map[string]bool),
       
    58 		Events:          make(chan Event),
       
    59 		Errors:          make(chan error),
       
    60 		done:            make(chan struct{}),
       
    61 	}
       
    62 
       
    63 	go w.readEvents()
       
    64 	return w, nil
       
    65 }
       
    66 
       
    67 // Close removes all watches and closes the events channel.
       
    68 func (w *Watcher) Close() error {
       
    69 	w.mu.Lock()
       
    70 	if w.isClosed {
       
    71 		w.mu.Unlock()
       
    72 		return nil
       
    73 	}
       
    74 	w.isClosed = true
       
    75 
       
    76 	// copy paths to remove while locked
       
    77 	var pathsToRemove = make([]string, 0, len(w.watches))
       
    78 	for name := range w.watches {
       
    79 		pathsToRemove = append(pathsToRemove, name)
       
    80 	}
       
    81 	w.mu.Unlock()
       
    82 	// unlock before calling Remove, which also locks
       
    83 
       
    84 	for _, name := range pathsToRemove {
       
    85 		w.Remove(name)
       
    86 	}
       
    87 
       
    88 	// send a "quit" message to the reader goroutine
       
    89 	close(w.done)
       
    90 
       
    91 	return nil
       
    92 }
       
    93 
       
    94 // Add starts watching the named file or directory (non-recursively).
       
    95 func (w *Watcher) Add(name string) error {
       
    96 	w.mu.Lock()
       
    97 	w.externalWatches[name] = true
       
    98 	w.mu.Unlock()
       
    99 	_, err := w.addWatch(name, noteAllEvents)
       
   100 	return err
       
   101 }
       
   102 
       
   103 // Remove stops watching the the named file or directory (non-recursively).
       
   104 func (w *Watcher) Remove(name string) error {
       
   105 	name = filepath.Clean(name)
       
   106 	w.mu.Lock()
       
   107 	watchfd, ok := w.watches[name]
       
   108 	w.mu.Unlock()
       
   109 	if !ok {
       
   110 		return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
       
   111 	}
       
   112 
       
   113 	const registerRemove = unix.EV_DELETE
       
   114 	if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
       
   115 		return err
       
   116 	}
       
   117 
       
   118 	unix.Close(watchfd)
       
   119 
       
   120 	w.mu.Lock()
       
   121 	isDir := w.paths[watchfd].isDir
       
   122 	delete(w.watches, name)
       
   123 	delete(w.paths, watchfd)
       
   124 	delete(w.dirFlags, name)
       
   125 	w.mu.Unlock()
       
   126 
       
   127 	// Find all watched paths that are in this directory that are not external.
       
   128 	if isDir {
       
   129 		var pathsToRemove []string
       
   130 		w.mu.Lock()
       
   131 		for _, path := range w.paths {
       
   132 			wdir, _ := filepath.Split(path.name)
       
   133 			if filepath.Clean(wdir) == name {
       
   134 				if !w.externalWatches[path.name] {
       
   135 					pathsToRemove = append(pathsToRemove, path.name)
       
   136 				}
       
   137 			}
       
   138 		}
       
   139 		w.mu.Unlock()
       
   140 		for _, name := range pathsToRemove {
       
   141 			// Since these are internal, not much sense in propagating error
       
   142 			// to the user, as that will just confuse them with an error about
       
   143 			// a path they did not explicitly watch themselves.
       
   144 			w.Remove(name)
       
   145 		}
       
   146 	}
       
   147 
       
   148 	return nil
       
   149 }
       
   150 
       
   151 // WatchList returns the directories and files that are being monitered.
       
   152 func (w *Watcher) WatchList() []string {
       
   153 	w.mu.Lock()
       
   154 	defer w.mu.Unlock()
       
   155 
       
   156 	entries := make([]string, 0, len(w.watches))
       
   157 	for pathname := range w.watches {
       
   158 		entries = append(entries, pathname)
       
   159 	}
       
   160 
       
   161 	return entries
       
   162 }
       
   163 
       
   164 // Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
       
   165 const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
       
   166 
       
   167 // keventWaitTime to block on each read from kevent
       
   168 var keventWaitTime = durationToTimespec(100 * time.Millisecond)
       
   169 
       
   170 // addWatch adds name to the watched file set.
       
   171 // The flags are interpreted as described in kevent(2).
       
   172 // Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
       
   173 func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
       
   174 	var isDir bool
       
   175 	// Make ./name and name equivalent
       
   176 	name = filepath.Clean(name)
       
   177 
       
   178 	w.mu.Lock()
       
   179 	if w.isClosed {
       
   180 		w.mu.Unlock()
       
   181 		return "", errors.New("kevent instance already closed")
       
   182 	}
       
   183 	watchfd, alreadyWatching := w.watches[name]
       
   184 	// We already have a watch, but we can still override flags.
       
   185 	if alreadyWatching {
       
   186 		isDir = w.paths[watchfd].isDir
       
   187 	}
       
   188 	w.mu.Unlock()
       
   189 
       
   190 	if !alreadyWatching {
       
   191 		fi, err := os.Lstat(name)
       
   192 		if err != nil {
       
   193 			return "", err
       
   194 		}
       
   195 
       
   196 		// Don't watch sockets.
       
   197 		if fi.Mode()&os.ModeSocket == os.ModeSocket {
       
   198 			return "", nil
       
   199 		}
       
   200 
       
   201 		// Don't watch named pipes.
       
   202 		if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
       
   203 			return "", nil
       
   204 		}
       
   205 
       
   206 		// Follow Symlinks
       
   207 		// Unfortunately, Linux can add bogus symlinks to watch list without
       
   208 		// issue, and Windows can't do symlinks period (AFAIK). To  maintain
       
   209 		// consistency, we will act like everything is fine. There will simply
       
   210 		// be no file events for broken symlinks.
       
   211 		// Hence the returns of nil on errors.
       
   212 		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
       
   213 			name, err = filepath.EvalSymlinks(name)
       
   214 			if err != nil {
       
   215 				return "", nil
       
   216 			}
       
   217 
       
   218 			w.mu.Lock()
       
   219 			_, alreadyWatching = w.watches[name]
       
   220 			w.mu.Unlock()
       
   221 
       
   222 			if alreadyWatching {
       
   223 				return name, nil
       
   224 			}
       
   225 
       
   226 			fi, err = os.Lstat(name)
       
   227 			if err != nil {
       
   228 				return "", nil
       
   229 			}
       
   230 		}
       
   231 
       
   232 		watchfd, err = unix.Open(name, openMode, 0700)
       
   233 		if watchfd == -1 {
       
   234 			return "", err
       
   235 		}
       
   236 
       
   237 		isDir = fi.IsDir()
       
   238 	}
       
   239 
       
   240 	const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
       
   241 	if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
       
   242 		unix.Close(watchfd)
       
   243 		return "", err
       
   244 	}
       
   245 
       
   246 	if !alreadyWatching {
       
   247 		w.mu.Lock()
       
   248 		w.watches[name] = watchfd
       
   249 		w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
       
   250 		w.mu.Unlock()
       
   251 	}
       
   252 
       
   253 	if isDir {
       
   254 		// Watch the directory if it has not been watched before,
       
   255 		// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
       
   256 		w.mu.Lock()
       
   257 
       
   258 		watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
       
   259 			(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
       
   260 		// Store flags so this watch can be updated later
       
   261 		w.dirFlags[name] = flags
       
   262 		w.mu.Unlock()
       
   263 
       
   264 		if watchDir {
       
   265 			if err := w.watchDirectoryFiles(name); err != nil {
       
   266 				return "", err
       
   267 			}
       
   268 		}
       
   269 	}
       
   270 	return name, nil
       
   271 }
       
   272 
       
   273 // readEvents reads from kqueue and converts the received kevents into
       
   274 // Event values that it sends down the Events channel.
       
   275 func (w *Watcher) readEvents() {
       
   276 	eventBuffer := make([]unix.Kevent_t, 10)
       
   277 
       
   278 loop:
       
   279 	for {
       
   280 		// See if there is a message on the "done" channel
       
   281 		select {
       
   282 		case <-w.done:
       
   283 			break loop
       
   284 		default:
       
   285 		}
       
   286 
       
   287 		// Get new events
       
   288 		kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
       
   289 		// EINTR is okay, the syscall was interrupted before timeout expired.
       
   290 		if err != nil && err != unix.EINTR {
       
   291 			select {
       
   292 			case w.Errors <- err:
       
   293 			case <-w.done:
       
   294 				break loop
       
   295 			}
       
   296 			continue
       
   297 		}
       
   298 
       
   299 		// Flush the events we received to the Events channel
       
   300 		for len(kevents) > 0 {
       
   301 			kevent := &kevents[0]
       
   302 			watchfd := int(kevent.Ident)
       
   303 			mask := uint32(kevent.Fflags)
       
   304 			w.mu.Lock()
       
   305 			path := w.paths[watchfd]
       
   306 			w.mu.Unlock()
       
   307 			event := newEvent(path.name, mask)
       
   308 
       
   309 			if path.isDir && !(event.Op&Remove == Remove) {
       
   310 				// Double check to make sure the directory exists. This can happen when
       
   311 				// we do a rm -fr on a recursively watched folders and we receive a
       
   312 				// modification event first but the folder has been deleted and later
       
   313 				// receive the delete event
       
   314 				if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
       
   315 					// mark is as delete event
       
   316 					event.Op |= Remove
       
   317 				}
       
   318 			}
       
   319 
       
   320 			if event.Op&Rename == Rename || event.Op&Remove == Remove {
       
   321 				w.Remove(event.Name)
       
   322 				w.mu.Lock()
       
   323 				delete(w.fileExists, event.Name)
       
   324 				w.mu.Unlock()
       
   325 			}
       
   326 
       
   327 			if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
       
   328 				w.sendDirectoryChangeEvents(event.Name)
       
   329 			} else {
       
   330 				// Send the event on the Events channel.
       
   331 				select {
       
   332 				case w.Events <- event:
       
   333 				case <-w.done:
       
   334 					break loop
       
   335 				}
       
   336 			}
       
   337 
       
   338 			if event.Op&Remove == Remove {
       
   339 				// Look for a file that may have overwritten this.
       
   340 				// For example, mv f1 f2 will delete f2, then create f2.
       
   341 				if path.isDir {
       
   342 					fileDir := filepath.Clean(event.Name)
       
   343 					w.mu.Lock()
       
   344 					_, found := w.watches[fileDir]
       
   345 					w.mu.Unlock()
       
   346 					if found {
       
   347 						// make sure the directory exists before we watch for changes. When we
       
   348 						// do a recursive watch and perform rm -fr, the parent directory might
       
   349 						// have gone missing, ignore the missing directory and let the
       
   350 						// upcoming delete event remove the watch from the parent directory.
       
   351 						if _, err := os.Lstat(fileDir); err == nil {
       
   352 							w.sendDirectoryChangeEvents(fileDir)
       
   353 						}
       
   354 					}
       
   355 				} else {
       
   356 					filePath := filepath.Clean(event.Name)
       
   357 					if fileInfo, err := os.Lstat(filePath); err == nil {
       
   358 						w.sendFileCreatedEventIfNew(filePath, fileInfo)
       
   359 					}
       
   360 				}
       
   361 			}
       
   362 
       
   363 			// Move to next event
       
   364 			kevents = kevents[1:]
       
   365 		}
       
   366 	}
       
   367 
       
   368 	// cleanup
       
   369 	err := unix.Close(w.kq)
       
   370 	if err != nil {
       
   371 		// only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors.
       
   372 		select {
       
   373 		case w.Errors <- err:
       
   374 		default:
       
   375 		}
       
   376 	}
       
   377 	close(w.Events)
       
   378 	close(w.Errors)
       
   379 }
       
   380 
       
   381 // newEvent returns an platform-independent Event based on kqueue Fflags.
       
   382 func newEvent(name string, mask uint32) Event {
       
   383 	e := Event{Name: name}
       
   384 	if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
       
   385 		e.Op |= Remove
       
   386 	}
       
   387 	if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
       
   388 		e.Op |= Write
       
   389 	}
       
   390 	if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
       
   391 		e.Op |= Rename
       
   392 	}
       
   393 	if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
       
   394 		e.Op |= Chmod
       
   395 	}
       
   396 	return e
       
   397 }
       
   398 
       
   399 func newCreateEvent(name string) Event {
       
   400 	return Event{Name: name, Op: Create}
       
   401 }
       
   402 
       
   403 // watchDirectoryFiles to mimic inotify when adding a watch on a directory
       
   404 func (w *Watcher) watchDirectoryFiles(dirPath string) error {
       
   405 	// Get all files
       
   406 	files, err := ioutil.ReadDir(dirPath)
       
   407 	if err != nil {
       
   408 		return err
       
   409 	}
       
   410 
       
   411 	for _, fileInfo := range files {
       
   412 		filePath := filepath.Join(dirPath, fileInfo.Name())
       
   413 		filePath, err = w.internalWatch(filePath, fileInfo)
       
   414 		if err != nil {
       
   415 			return err
       
   416 		}
       
   417 
       
   418 		w.mu.Lock()
       
   419 		w.fileExists[filePath] = true
       
   420 		w.mu.Unlock()
       
   421 	}
       
   422 
       
   423 	return nil
       
   424 }
       
   425 
       
   426 // sendDirectoryEvents searches the directory for newly created files
       
   427 // and sends them over the event channel. This functionality is to have
       
   428 // the BSD version of fsnotify match Linux inotify which provides a
       
   429 // create event for files created in a watched directory.
       
   430 func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
       
   431 	// Get all files
       
   432 	files, err := ioutil.ReadDir(dirPath)
       
   433 	if err != nil {
       
   434 		select {
       
   435 		case w.Errors <- err:
       
   436 		case <-w.done:
       
   437 			return
       
   438 		}
       
   439 	}
       
   440 
       
   441 	// Search for new files
       
   442 	for _, fileInfo := range files {
       
   443 		filePath := filepath.Join(dirPath, fileInfo.Name())
       
   444 		err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
       
   445 
       
   446 		if err != nil {
       
   447 			return
       
   448 		}
       
   449 	}
       
   450 }
       
   451 
       
   452 // sendFileCreatedEvent sends a create event if the file isn't already being tracked.
       
   453 func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
       
   454 	w.mu.Lock()
       
   455 	_, doesExist := w.fileExists[filePath]
       
   456 	w.mu.Unlock()
       
   457 	if !doesExist {
       
   458 		// Send create event
       
   459 		select {
       
   460 		case w.Events <- newCreateEvent(filePath):
       
   461 		case <-w.done:
       
   462 			return
       
   463 		}
       
   464 	}
       
   465 
       
   466 	// like watchDirectoryFiles (but without doing another ReadDir)
       
   467 	filePath, err = w.internalWatch(filePath, fileInfo)
       
   468 	if err != nil {
       
   469 		return err
       
   470 	}
       
   471 
       
   472 	w.mu.Lock()
       
   473 	w.fileExists[filePath] = true
       
   474 	w.mu.Unlock()
       
   475 
       
   476 	return nil
       
   477 }
       
   478 
       
   479 func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
       
   480 	if fileInfo.IsDir() {
       
   481 		// mimic Linux providing delete events for subdirectories
       
   482 		// but preserve the flags used if currently watching subdirectory
       
   483 		w.mu.Lock()
       
   484 		flags := w.dirFlags[name]
       
   485 		w.mu.Unlock()
       
   486 
       
   487 		flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
       
   488 		return w.addWatch(name, flags)
       
   489 	}
       
   490 
       
   491 	// watch file to mimic Linux inotify
       
   492 	return w.addWatch(name, noteAllEvents)
       
   493 }
       
   494 
       
   495 // kqueue creates a new kernel event queue and returns a descriptor.
       
   496 func kqueue() (kq int, err error) {
       
   497 	kq, err = unix.Kqueue()
       
   498 	if kq == -1 {
       
   499 		return kq, err
       
   500 	}
       
   501 	return kq, nil
       
   502 }
       
   503 
       
   504 // register events with the queue
       
   505 func register(kq int, fds []int, flags int, fflags uint32) error {
       
   506 	changes := make([]unix.Kevent_t, len(fds))
       
   507 
       
   508 	for i, fd := range fds {
       
   509 		// SetKevent converts int to the platform-specific types:
       
   510 		unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
       
   511 		changes[i].Fflags = fflags
       
   512 	}
       
   513 
       
   514 	// register the events
       
   515 	success, err := unix.Kevent(kq, changes, nil, nil)
       
   516 	if success == -1 {
       
   517 		return err
       
   518 	}
       
   519 	return nil
       
   520 }
       
   521 
       
   522 // read retrieves pending events, or waits until an event occurs.
       
   523 // A timeout of nil blocks indefinitely, while 0 polls the queue.
       
   524 func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
       
   525 	n, err := unix.Kevent(kq, nil, events, timeout)
       
   526 	if err != nil {
       
   527 		return nil, err
       
   528 	}
       
   529 	return events[0:n], nil
       
   530 }
       
   531 
       
   532 // durationToTimespec prepares a timeout value
       
   533 func durationToTimespec(d time.Duration) unix.Timespec {
       
   534 	return unix.NsecToTimespec(d.Nanoseconds())
       
   535 }