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