diff --git a/hack/vendor.sh b/hack/vendor.sh index 50cff1690f..15846ab973 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -55,7 +55,7 @@ clone git github.com/docker/libtrust 230dfd18c232 clone git github.com/Sirupsen/logrus v0.7.2 -clone git github.com/go-fsnotify/fsnotify v1.0.4 +clone git github.com/go-fsnotify/fsnotify v1.2.0 clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673 diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/.travis.yml b/vendor/src/github.com/go-fsnotify/fsnotify/.travis.yml index f8e76fc660..67467e1407 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/.travis.yml +++ b/vendor/src/github.com/go-fsnotify/fsnotify/.travis.yml @@ -1,10 +1,12 @@ +sudo: false language: go go: - - 1.2 - - tip + - 1.4.1 + +before_script: + - FIXED=$(go fmt ./... | wc -l); if [ $FIXED -gt 0 ]; then echo "gofmt - $FIXED file(s) not formatted correctly, please run gofmt to fix this." && exit 1; fi -# not yet https://github.com/travis-ci/travis-ci/issues/2318 os: - linux - osx diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/AUTHORS b/vendor/src/github.com/go-fsnotify/fsnotify/AUTHORS index 306091eda6..4e0e8284e9 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/AUTHORS +++ b/vendor/src/github.com/go-fsnotify/fsnotify/AUTHORS @@ -18,8 +18,10 @@ Francisco Souza Hari haran John C Barstow Kelvin Fo +Matt Layher Nathan Youngman Paul Hammond +Pieter Droogendijk Pursuit92 Rob Figueiredo Soge Zhang diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/CHANGELOG.md b/vendor/src/github.com/go-fsnotify/fsnotify/CHANGELOG.md index 79f4ddbaa1..ea9428a2a4 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/CHANGELOG.md +++ b/vendor/src/github.com/go-fsnotify/fsnotify/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## v1.2.0 / 2015-02-08 + +* inotify: use epoll to wake up readEvents [#66](https://github.com/go-fsnotify/fsnotify/pull/66) (thanks @PieterD) +* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/go-fsnotify/fsnotify/pull/63) (thanks @PieterD) +* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/go-fsnotify/fsnotify/issues/59) + +## v1.1.1 / 2015-02-05 + +* inotify: Retry read on EINTR [#61](https://github.com/go-fsnotify/fsnotify/issues/61) (thanks @PieterD) + +## v1.1.0 / 2014-12-12 + +* kqueue: rework internals [#43](https://github.com/go-fsnotify/fsnotify/pull/43) + * add low-level functions + * only need to store flags on directories + * less mutexes [#13](https://github.com/go-fsnotify/fsnotify/issues/13) + * done can be an unbuffered channel + * remove calls to os.NewSyscallError +* More efficient string concatenation for Event.String() [#52](https://github.com/go-fsnotify/fsnotify/pull/52) (thanks @mdlayher) +* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/go-fsnotify/fsnotify/issues/48) +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/go-fsnotify/fsnotify/issues/51) + ## v1.0.4 / 2014-09-07 * kqueue: add dragonfly to the build tags. @@ -69,6 +91,10 @@ * no tests for the current implementation * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195) +## v0.9.3 / 2014-12-31 + +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/go-fsnotify/fsnotify/issues/51) + ## v0.9.2 / 2014-08-17 * [Backport] Fix missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso) diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/CONTRIBUTING.md b/vendor/src/github.com/go-fsnotify/fsnotify/CONTRIBUTING.md index 2fd0423cca..0f377f341b 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/CONTRIBUTING.md +++ b/vendor/src/github.com/go-fsnotify/fsnotify/CONTRIBUTING.md @@ -1,21 +1,34 @@ # Contributing -* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com). - -### Issues +## Issues * Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues). -* Please indicate the platform you are running on. +* Please indicate the platform you are using fsnotify on. +* A code example to reproduce the problem is appreciated. -### Pull Requests +## Pull Requests -A future version of Go will have [fsnotify in the standard library](https://code.google.com/p/go/issues/detail?id=4068), therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so we need you to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual). +### Contributor License Agreement + +fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/go-fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual). Please indicate that you have signed the CLA in your pull request. -To hack on fsnotify: +### How fsnotify is Developed -1. Install as usual (`go get -u github.com/go-fsnotify/fsnotify`) +* Development is done on feature branches. +* Tests are run on BSD, Linux, OS X and Windows. +* Pull requests are reviewed and [applied to master][am] using [hub][]. + * Maintainers may modify or squash commits rather than asking contributors to. +* To issue a new release, the maintainers will: + * Update the CHANGELOG + * Tag a version, which will become available through gopkg.in. + +### How to Fork + +For smooth sailing, always use the original import path. Installing with `go get` makes this easy. + +1. Install from GitHub (`go get -u github.com/go-fsnotify/fsnotify`) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Ensure everything works and the tests pass (see below) 4. Commit your changes (`git commit -am 'Add some feature'`) @@ -27,15 +40,7 @@ Contribute upstream: 3. Push to the branch (`git push fork my-new-feature`) 4. Create a new Pull Request on GitHub -If other team members need your patch before I merge it: - -1. Install as usual (`go get -u github.com/go-fsnotify/fsnotify`) -2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`) -3. Pull your revisions (`git fetch fork; git checkout -b my-new-feature fork/my-new-feature`) - -Notice: For smooth sailing, always use the original import path. Installing with `go get` makes this easy. - -Note: The maintainers will update the CHANGELOG on your behalf. Please don't modify it in your pull request. +This workflow is [thoroughly explained by Katrina Owen](https://blog.splice.com/contributing-open-source-git-repositories-go/). ### Testing @@ -43,7 +48,7 @@ fsnotify uses build tags to compile different code on Linux, BSD, OS X, and Wind Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on. -To make cross-platform testing easier, I've created a Vagrantfile for Linux and BSD. +To aid in cross-platform testing there is a Vagrantfile for Linux and BSD. * Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) * Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder. @@ -51,6 +56,22 @@ To make cross-platform testing easier, I've created a Vagrantfile for Linux and * Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd go-fsnotify/fsnotify; go test'`. * When you're done, you will want to halt or destroy the Vagrant boxes. -Notice: fsnotify file system events don't work on shared folders. The tests get around this limitation by using a tmp directory, but it is something to be aware of. +Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory. -Right now I don't have an equivalent solution for Windows and OS X, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads). +Right now there is no equivalent solution for Windows and OS X, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads). + +### Maintainers + +Help maintaining fsnotify is welcome. To be a maintainer: + +* Submit a pull request and sign the CLA as above. +* You must be able to run the test suite on Mac, Windows, Linux and BSD. + +To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][]. + +All code changes should be internal pull requests. + +Releases are tagged using [Semantic Versioning](http://semver.org/). + +[hub]: https://github.com/github/hub +[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/NotUsed.xcworkspace b/vendor/src/github.com/go-fsnotify/fsnotify/NotUsed.xcworkspace new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/README.md b/vendor/src/github.com/go-fsnotify/fsnotify/README.md index 0759284269..7a0b247364 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/README.md +++ b/vendor/src/github.com/go-fsnotify/fsnotify/README.md @@ -2,18 +2,20 @@ [![Coverage](http://gocover.io/_badge/github.com/go-fsnotify/fsnotify)](http://gocover.io/github.com/go-fsnotify/fsnotify) [![GoDoc](https://godoc.org/gopkg.in/fsnotify.v1?status.svg)](https://godoc.org/gopkg.in/fsnotify.v1) +Go 1.3+ required. + Cross platform: Windows, Linux, BSD and OS X. |Adapter |OS |Status | |----------|----------|----------| -|inotify |Linux, Android\*|Supported| -|kqueue |BSD, OS X, iOS\*|Supported| -|ReadDirectoryChangesW|Windows|Supported| +|inotify |Linux, Android\*|Supported [![Build Status](https://travis-ci.org/go-fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/go-fsnotify/fsnotify)| +|kqueue |BSD, OS X, iOS\*|Supported [![Circle CI](https://circleci.com/gh/go-fsnotify/fsnotify.svg?style=svg)](https://circleci.com/gh/go-fsnotify/fsnotify)| +|ReadDirectoryChangesW|Windows|Supported [![Build status](https://ci.appveyor.com/api/projects/status/ivwjubaih4r0udeh/branch/master?svg=true)](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)| |FSEvents |OS X |[Planned](https://github.com/go-fsnotify/fsnotify/issues/11)| |FEN |Solaris 11 |[Planned](https://github.com/go-fsnotify/fsnotify/issues/12)| |fanotify |Linux 2.6.37+ | | +|USN Journals |Windows |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/53)| |Polling |*All* |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/9)| -| |Plan 9 | | \* Android and iOS are untested. @@ -23,31 +25,35 @@ Please see [the documentation](https://godoc.org/gopkg.in/fsnotify.v1) for usage Two major versions of fsnotify exist. -**[fsnotify.v1](https://gopkg.in/fsnotify.v1)** provides [a new API](https://godoc.org/gopkg.in/fsnotify.v1) based on [this design document](http://goo.gl/MrYxyA). You can import v1 with: - -```go -import "gopkg.in/fsnotify.v1" -``` - -\* Refer to the package as fsnotify (without the .v1 suffix). - **[fsnotify.v0](https://gopkg.in/fsnotify.v0)** is API-compatible with [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify). Bugfixes *may* be backported, but I recommend upgrading to v1. ```go import "gopkg.in/fsnotify.v0" ``` +\* Refer to the package as fsnotify (without the .v0 suffix). + +**[fsnotify.v1](https://gopkg.in/fsnotify.v1)** provides [a new API](https://godoc.org/gopkg.in/fsnotify.v1) based on [this design document](http://goo.gl/MrYxyA). You can import v1 with: + +```go +import "gopkg.in/fsnotify.v1" +``` + Further API changes are [planned](https://github.com/go-fsnotify/fsnotify/milestones), but a new major revision will be tagged, so you can depend on the v1 API. +**Master** may have unreleased changes. Use it to test the very latest code or when [contributing][], but don't expect it to remain API-compatible: + +```go +import "github.com/go-fsnotify/fsnotify" +``` + ## Contributing -* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com). -* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues). - -A future version of Go will have [fsnotify in the standard library](https://code.google.com/p/go/issues/detail?id=4068), therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so we need you to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual). - -Please read [CONTRIBUTING](https://github.com/go-fsnotify/fsnotify/blob/master/CONTRIBUTING.md) before opening a pull request. +Please refer to [CONTRIBUTING][] before opening an issue or pull request. ## Example See [example_test.go](https://github.com/go-fsnotify/fsnotify/blob/master/example_test.go). + + +[contributing]: https://github.com/go-fsnotify/fsnotify/blob/master/CONTRIBUTING.md diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/circle.yml b/vendor/src/github.com/go-fsnotify/fsnotify/circle.yml new file mode 100644 index 0000000000..204217fb0b --- /dev/null +++ b/vendor/src/github.com/go-fsnotify/fsnotify/circle.yml @@ -0,0 +1,26 @@ +## OS X build (CircleCI iOS beta) + +# Pretend like it's an Xcode project, at least to get it running. +machine: + environment: + XCODE_WORKSPACE: NotUsed.xcworkspace + XCODE_SCHEME: NotUsed + # This is where the go project is actually checked out to: + CIRCLE_BUILD_DIR: $HOME/.go_project/src/github.com/go-fsnotify/fsnotify + +dependencies: + pre: + - brew upgrade go + +test: + override: + - go test ./... + +# Idealized future config, eventually with cross-platform build matrix :-) + +# machine: +# go: +# version: 1.4 +# os: +# - osx +# - linux diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/example_test.go b/vendor/src/github.com/go-fsnotify/fsnotify/example_test.go index 9f2c63f475..3063796602 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/example_test.go +++ b/vendor/src/github.com/go-fsnotify/fsnotify/example_test.go @@ -9,7 +9,7 @@ package fsnotify_test import ( "log" - "gopkg.in/fsnotify.v1" + "github.com/go-fsnotify/fsnotify" ) func ExampleNewWatcher() { diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/fsnotify.go b/vendor/src/github.com/go-fsnotify/fsnotify/fsnotify.go index 7b5233f4bb..c899ee0083 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/fsnotify.go +++ b/vendor/src/github.com/go-fsnotify/fsnotify/fsnotify.go @@ -7,7 +7,10 @@ // Package fsnotify provides a platform-independent interface for file system notifications. package fsnotify -import "fmt" +import ( + "bytes" + "fmt" +) // Event represents a single file system notification. type Event struct { @@ -30,27 +33,30 @@ const ( // String returns a string representation of the event in the form // "file: REMOVE|WRITE|..." func (e Event) String() string { - events := "" + // Use a buffer for efficient string concatenation + var buffer bytes.Buffer if e.Op&Create == Create { - events += "|CREATE" + buffer.WriteString("|CREATE") } if e.Op&Remove == Remove { - events += "|REMOVE" + buffer.WriteString("|REMOVE") } if e.Op&Write == Write { - events += "|WRITE" + buffer.WriteString("|WRITE") } if e.Op&Rename == Rename { - events += "|RENAME" + buffer.WriteString("|RENAME") } if e.Op&Chmod == Chmod { - events += "|CHMOD" + buffer.WriteString("|CHMOD") } - if len(events) > 0 { - events = events[1:] + // If buffer remains empty, return no event names + if buffer.Len() == 0 { + return fmt.Sprintf("%q: ", e.Name) } - return fmt.Sprintf("%q: %s", e.Name, events) + // Return a list of event names, with leading pipe character stripped + return fmt.Sprintf("%q: %s", e.Name, buffer.String()[1:]) } diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/inotify.go b/vendor/src/github.com/go-fsnotify/fsnotify/inotify.go index f5c0aaef04..d7759ec8c8 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/inotify.go +++ b/vendor/src/github.com/go-fsnotify/fsnotify/inotify.go @@ -9,6 +9,7 @@ package fsnotify import ( "errors" "fmt" + "io" "os" "path/filepath" "strings" @@ -21,47 +22,66 @@ import ( type Watcher struct { Events chan Event Errors chan error - mu sync.Mutex // Map access - fd int // File descriptor (as returned by the inotify_init() syscall) + mu sync.Mutex // Map access + fd int + poller *fdPoller watches map[string]*watch // Map of inotify watches (key: path) paths map[int]string // Map of watched paths (key: watch descriptor) - done chan bool // Channel for sending a "quit message" to the reader goroutine - isClosed bool // Set to true when Close() is first called + done chan struct{} // Channel for sending a "quit message" to the reader goroutine + doneResp chan struct{} // Channel to respond to Close } // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. func NewWatcher() (*Watcher, error) { + // Create inotify fd fd, errno := syscall.InotifyInit() if fd == -1 { - return nil, os.NewSyscallError("inotify_init", errno) + return nil, errno + } + // Create epoll + poller, err := newFdPoller(fd) + if err != nil { + syscall.Close(fd) + return nil, err } w := &Watcher{ - fd: fd, - watches: make(map[string]*watch), - paths: make(map[int]string), - Events: make(chan Event), - Errors: make(chan error), - done: make(chan bool, 1), + fd: fd, + poller: poller, + watches: make(map[string]*watch), + paths: make(map[int]string), + Events: make(chan Event), + Errors: make(chan error), + done: make(chan struct{}), + doneResp: make(chan struct{}), } go w.readEvents() return w, nil } +func (w *Watcher) isClosed() bool { + select { + case <-w.done: + return true + default: + return false + } +} + // Close removes all watches and closes the events channel. func (w *Watcher) Close() error { - if w.isClosed { + if w.isClosed() { return nil } - w.isClosed = true - // Remove all watches - for name := range w.watches { - w.Remove(name) - } + // Send 'close' signal to goroutine, and set the Watcher to closed. + close(w.done) - // Send "quit" message to the reader goroutine - w.done <- true + // Wake up goroutine + w.poller.wake() + + // Wait for goroutine to close + <-w.doneResp return nil } @@ -69,7 +89,7 @@ func (w *Watcher) Close() error { // Add starts watching the named file or directory (non-recursively). func (w *Watcher) Add(name string) error { name = filepath.Clean(name) - if w.isClosed { + if w.isClosed() { return errors.New("inotify instance already closed") } @@ -88,7 +108,7 @@ func (w *Watcher) Add(name string) error { } wd, errno := syscall.InotifyAddWatch(w.fd, name, flags) if wd == -1 { - return os.NewSyscallError("inotify_add_watch", errno) + return errno } w.mu.Lock() @@ -99,20 +119,33 @@ func (w *Watcher) Add(name string) error { return nil } -// Remove stops watching the the named file or directory (non-recursively). +// Remove stops watching the named file or directory (non-recursively). func (w *Watcher) Remove(name string) error { name = filepath.Clean(name) + + // Fetch the watch. w.mu.Lock() defer w.mu.Unlock() watch, ok := w.watches[name] + + // Remove it from inotify. if !ok { return fmt.Errorf("can't remove non-existent inotify watch for: %s", name) } + // inotify_rm_watch will return EINVAL if the file has been deleted; + // the inotify will already have been removed. + // That means we can safely delete it from our watches, whatever inotify_rm_watch does. + delete(w.watches, name) success, errno := syscall.InotifyRmWatch(w.fd, watch.wd) if success == -1 { - return os.NewSyscallError("inotify_rm_watch", errno) + // TODO: Perhaps it's not helpful to return an error here in every case. + // the only two possible errors are: + // EBADF, which happens when w.fd is not a valid file descriptor of any kind. + // EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor. + // Watch descriptors are invalidated when they are removed explicitly or implicitly; + // explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted. + return errno } - delete(w.watches, name) return nil } @@ -128,35 +161,65 @@ func (w *Watcher) readEvents() { buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events n int // Number of bytes read with read() errno error // Syscall errno + ok bool // For poller.wait ) + defer close(w.doneResp) + defer close(w.Errors) + defer close(w.Events) + defer syscall.Close(w.fd) + defer w.poller.close() + for { - // See if there is a message on the "done" channel - select { - case <-w.done: - syscall.Close(w.fd) - close(w.Events) - close(w.Errors) + // See if we have been closed. + if w.isClosed() { return - default: + } + + ok, errno = w.poller.wait() + if errno != nil { + select { + case w.Errors <- errno: + case <-w.done: + return + } + continue + } + + if !ok { + continue } n, errno = syscall.Read(w.fd, buf[:]) + // If a signal interrupted execution, see if we've been asked to close, and try again. + // http://man7.org/linux/man-pages/man7/signal.7.html : + // "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable" + if errno == syscall.EINTR { + continue + } - // If EOF is received - if n == 0 { - syscall.Close(w.fd) - close(w.Events) - close(w.Errors) + // syscall.Read might have been woken up by Close. If so, we're done. + if w.isClosed() { return } - if n < 0 { - w.Errors <- os.NewSyscallError("read", errno) - continue - } if n < syscall.SizeofInotifyEvent { - w.Errors <- errors.New("inotify: short read in readEvents()") + var err error + if n == 0 { + // If EOF is received. This should really never happen. + err = io.EOF + } else if n < 0 { + // If an error occured while reading. + err = errno + } else { + // Read was too short. + err = errors.New("notify: short read in readEvents()") + } + select { + case w.Errors <- err: + case <-w.done: + return + } continue } @@ -187,7 +250,11 @@ func (w *Watcher) readEvents() { // Send the events that are not ignored on the events channel if !event.ignoreLinux(mask) { - w.Events <- event + select { + case w.Events <- event: + case <-w.done: + return + } } // Move to the next event in the buffer diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller.go b/vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller.go new file mode 100644 index 0000000000..3b41784041 --- /dev/null +++ b/vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller.go @@ -0,0 +1,186 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux + +package fsnotify + +import ( + "errors" + "syscall" +) + +type fdPoller struct { + fd int // File descriptor (as returned by the inotify_init() syscall) + epfd int // Epoll file descriptor + pipe [2]int // Pipe for waking up +} + +func emptyPoller(fd int) *fdPoller { + poller := new(fdPoller) + poller.fd = fd + poller.epfd = -1 + poller.pipe[0] = -1 + poller.pipe[1] = -1 + return poller +} + +// Create a new inotify poller. +// This creates an inotify handler, and an epoll handler. +func newFdPoller(fd int) (*fdPoller, error) { + var errno error + poller := emptyPoller(fd) + defer func() { + if errno != nil { + poller.close() + } + }() + poller.fd = fd + + // Create epoll fd + poller.epfd, errno = syscall.EpollCreate(1) + if poller.epfd == -1 { + return nil, errno + } + // Create pipe; pipe[0] is the read end, pipe[1] the write end. + errno = syscall.Pipe2(poller.pipe[:], syscall.O_NONBLOCK) + if errno != nil { + return nil, errno + } + + // Register inotify fd with epoll + event := syscall.EpollEvent{ + Fd: int32(poller.fd), + Events: syscall.EPOLLIN, + } + errno = syscall.EpollCtl(poller.epfd, syscall.EPOLL_CTL_ADD, poller.fd, &event) + if errno != nil { + return nil, errno + } + + // Register pipe fd with epoll + event = syscall.EpollEvent{ + Fd: int32(poller.pipe[0]), + Events: syscall.EPOLLIN, + } + errno = syscall.EpollCtl(poller.epfd, syscall.EPOLL_CTL_ADD, poller.pipe[0], &event) + if errno != nil { + return nil, errno + } + + return poller, nil +} + +// Wait using epoll. +// Returns true if something is ready to be read, +// false if there is not. +func (poller *fdPoller) wait() (bool, error) { + // 3 possible events per fd, and 2 fds, makes a maximum of 6 events. + // I don't know whether epoll_wait returns the number of events returned, + // or the total number of events ready. + // I decided to catch both by making the buffer one larger than the maximum. + events := make([]syscall.EpollEvent, 7) + for { + n, errno := syscall.EpollWait(poller.epfd, events, -1) + if n == -1 { + if errno == syscall.EINTR { + continue + } + return false, errno + } + if n == 0 { + // If there are no events, try again. + continue + } + if n > 6 { + // This should never happen. More events were returned than should be possible. + return false, errors.New("epoll_wait returned more events than I know what to do with") + } + ready := events[:n] + epollhup := false + epollerr := false + epollin := false + for _, event := range ready { + if event.Fd == int32(poller.fd) { + if event.Events&syscall.EPOLLHUP != 0 { + // This should not happen, but if it does, treat it as a wakeup. + epollhup = true + } + if event.Events&syscall.EPOLLERR != 0 { + // If an error is waiting on the file descriptor, we should pretend + // something is ready to read, and let syscall.Read pick up the error. + epollerr = true + } + if event.Events&syscall.EPOLLIN != 0 { + // There is data to read. + epollin = true + } + } + if event.Fd == int32(poller.pipe[0]) { + if event.Events&syscall.EPOLLHUP != 0 { + // Write pipe descriptor was closed, by us. This means we're closing down the + // watcher, and we should wake up. + } + if event.Events&syscall.EPOLLERR != 0 { + // If an error is waiting on the pipe file descriptor. + // This is an absolute mystery, and should never ever happen. + return false, errors.New("Error on the pipe descriptor.") + } + if event.Events&syscall.EPOLLIN != 0 { + // This is a regular wakeup, so we have to clear the buffer. + err := poller.clearWake() + if err != nil { + return false, err + } + } + } + } + + if epollhup || epollerr || epollin { + return true, nil + } + return false, nil + } +} + +// Close the write end of the poller. +func (poller *fdPoller) wake() error { + buf := make([]byte, 1) + n, errno := syscall.Write(poller.pipe[1], buf) + if n == -1 { + if errno == syscall.EAGAIN { + // Buffer is full, poller will wake. + return nil + } + return errno + } + return nil +} + +func (poller *fdPoller) clearWake() error { + // You have to be woken up a LOT in order to get to 100! + buf := make([]byte, 100) + n, errno := syscall.Read(poller.pipe[0], buf) + if n == -1 { + if errno == syscall.EAGAIN { + // Buffer is empty, someone else cleared our wake. + return nil + } + return errno + } + return nil +} + +// Close all poller file descriptors, but not the one passed to it. +func (poller *fdPoller) close() { + if poller.pipe[1] != -1 { + syscall.Close(poller.pipe[1]) + } + if poller.pipe[0] != -1 { + syscall.Close(poller.pipe[0]) + } + if poller.epfd != -1 { + syscall.Close(poller.epfd) + } +} diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller_test.go b/vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller_test.go new file mode 100644 index 0000000000..af9f407f8d --- /dev/null +++ b/vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller_test.go @@ -0,0 +1,228 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux + +package fsnotify + +import ( + "syscall" + "testing" + "time" +) + +type testFd [2]int + +func makeTestFd(t *testing.T) testFd { + var tfd testFd + errno := syscall.Pipe(tfd[:]) + if errno != nil { + t.Fatalf("Failed to create pipe: %v", errno) + } + return tfd +} + +func (tfd testFd) fd() int { + return tfd[0] +} + +func (tfd testFd) closeWrite(t *testing.T) { + errno := syscall.Close(tfd[1]) + if errno != nil { + t.Fatalf("Failed to close write end of pipe: %v", errno) + } +} + +func (tfd testFd) put(t *testing.T) { + buf := make([]byte, 10) + _, errno := syscall.Write(tfd[1], buf) + if errno != nil { + t.Fatalf("Failed to write to pipe: %v", errno) + } +} + +func (tfd testFd) get(t *testing.T) { + buf := make([]byte, 10) + _, errno := syscall.Read(tfd[0], buf) + if errno != nil { + t.Fatalf("Failed to read from pipe: %v", errno) + } +} + +func (tfd testFd) close() { + syscall.Close(tfd[1]) + syscall.Close(tfd[0]) +} + +func makePoller(t *testing.T) (testFd, *fdPoller) { + tfd := makeTestFd(t) + poller, err := newFdPoller(tfd.fd()) + if err != nil { + t.Fatalf("Failed to create poller: %v", err) + } + return tfd, poller +} + +func TestPollerWithBadFd(t *testing.T) { + _, err := newFdPoller(-1) + if err != syscall.EBADF { + t.Fatalf("Expected EBADF, got: %v", err) + } +} + +func TestPollerWithData(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + tfd.put(t) + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if !ok { + t.Fatalf("expected poller to return true") + } + tfd.get(t) +} + +func TestPollerWithWakeup(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + err := poller.wake() + if err != nil { + t.Fatalf("wake failed: %v", err) + } + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if ok { + t.Fatalf("expected poller to return false") + } +} + +func TestPollerWithClose(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + tfd.closeWrite(t) + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if !ok { + t.Fatalf("expected poller to return true") + } +} + +func TestPollerWithWakeupAndData(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + tfd.put(t) + err := poller.wake() + if err != nil { + t.Fatalf("wake failed: %v", err) + } + + // both data and wakeup + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if !ok { + t.Fatalf("expected poller to return true") + } + + // data is still in the buffer, wakeup is cleared + ok, err = poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if !ok { + t.Fatalf("expected poller to return true") + } + + tfd.get(t) + // data is gone, only wakeup now + err = poller.wake() + if err != nil { + t.Fatalf("wake failed: %v", err) + } + ok, err = poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if ok { + t.Fatalf("expected poller to return false") + } +} + +func TestPollerConcurrent(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + oks := make(chan bool) + live := make(chan bool) + defer close(live) + go func() { + defer close(oks) + for { + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + oks <- ok + if !<-live { + return + } + } + }() + + // Try a write + select { + case <-time.After(50 * time.Millisecond): + case <-oks: + t.Fatalf("poller did not wait") + } + tfd.put(t) + if !<-oks { + t.Fatalf("expected true") + } + tfd.get(t) + live <- true + + // Try a wakeup + select { + case <-time.After(50 * time.Millisecond): + case <-oks: + t.Fatalf("poller did not wait") + } + err := poller.wake() + if err != nil { + t.Fatalf("wake failed: %v", err) + } + if <-oks { + t.Fatalf("expected false") + } + live <- true + + // Try a close + select { + case <-time.After(50 * time.Millisecond): + case <-oks: + t.Fatalf("poller did not wait") + } + tfd.closeWrite(t) + if !<-oks { + t.Fatalf("expected true") + } + tfd.get(t) +} diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/inotify_test.go b/vendor/src/github.com/go-fsnotify/fsnotify/inotify_test.go new file mode 100644 index 0000000000..035ee8f95d --- /dev/null +++ b/vendor/src/github.com/go-fsnotify/fsnotify/inotify_test.go @@ -0,0 +1,292 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux + +package fsnotify + +import ( + "os" + "path/filepath" + "syscall" + "testing" + "time" +) + +func TestInotifyCloseRightAway(t *testing.T) { + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher") + } + + // Close immediately; it won't even reach the first syscall.Read. + w.Close() + + // Wait for the close to complete. + <-time.After(50 * time.Millisecond) + isWatcherReallyClosed(t, w) +} + +func TestInotifyCloseSlightlyLater(t *testing.T) { + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher") + } + + // Wait until readEvents has reached syscall.Read, and Close. + <-time.After(50 * time.Millisecond) + w.Close() + + // Wait for the close to complete. + <-time.After(50 * time.Millisecond) + isWatcherReallyClosed(t, w) +} + +func TestInotifyCloseSlightlyLaterWithWatch(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher") + } + w.Add(testDir) + + // Wait until readEvents has reached syscall.Read, and Close. + <-time.After(50 * time.Millisecond) + w.Close() + + // Wait for the close to complete. + <-time.After(50 * time.Millisecond) + isWatcherReallyClosed(t, w) +} + +func TestInotifyCloseAfterRead(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher") + } + + err = w.Add(testDir) + if err != nil { + t.Fatalf("Failed to add .") + } + + // Generate an event. + os.Create(filepath.Join(testDir, "somethingSOMETHINGsomethingSOMETHING")) + + // Wait for readEvents to read the event, then close the watcher. + <-time.After(50 * time.Millisecond) + w.Close() + + // Wait for the close to complete. + <-time.After(50 * time.Millisecond) + isWatcherReallyClosed(t, w) +} + +func isWatcherReallyClosed(t *testing.T, w *Watcher) { + select { + case err, ok := <-w.Errors: + if ok { + t.Fatalf("w.Errors is not closed; readEvents is still alive after closing (error: %v)", err) + } + default: + t.Fatalf("w.Errors would have blocked; readEvents is still alive!") + } + + select { + case _, ok := <-w.Events: + if ok { + t.Fatalf("w.Events is not closed; readEvents is still alive after closing") + } + default: + t.Fatalf("w.Events would have blocked; readEvents is still alive!") + } +} + +func TestInotifyCloseCreate(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher: %v", err) + } + defer w.Close() + + err = w.Add(testDir) + if err != nil { + t.Fatalf("Failed to add testDir: %v", err) + } + h, err := os.Create(filepath.Join(testDir, "testfile")) + if err != nil { + t.Fatalf("Failed to create file in testdir: %v", err) + } + h.Close() + select { + case _ = <-w.Events: + case err := <-w.Errors: + t.Fatalf("Error from watcher: %v", err) + case <-time.After(50 * time.Millisecond): + t.Fatalf("Took too long to wait for event") + } + + // At this point, we've received one event, so the goroutine is ready. + // It's also blocking on syscall.Read. + // Now we try to swap the file descriptor under its nose. + w.Close() + w, err = NewWatcher() + defer w.Close() + if err != nil { + t.Fatalf("Failed to create second watcher: %v", err) + } + + <-time.After(50 * time.Millisecond) + err = w.Add(testDir) + if err != nil { + t.Fatalf("Error adding testDir again: %v", err) + } +} + +func TestInotifyStress(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + testFile := filepath.Join(testDir, "testfile") + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher: %v", err) + } + defer w.Close() + + killchan := make(chan struct{}) + defer close(killchan) + + err = w.Add(testDir) + if err != nil { + t.Fatalf("Failed to add testDir: %v", err) + } + + proc, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatalf("Error finding process: %v", err) + } + + go func() { + for { + select { + case <-time.After(5 * time.Millisecond): + err := proc.Signal(syscall.SIGUSR1) + if err != nil { + t.Fatalf("Signal failed: %v", err) + } + case <-killchan: + return + } + } + }() + + go func() { + for { + select { + case <-time.After(11 * time.Millisecond): + err := w.poller.wake() + if err != nil { + t.Fatalf("Wake failed: %v", err) + } + case <-killchan: + return + } + } + }() + + go func() { + for { + select { + case <-killchan: + return + default: + handle, err := os.Create(testFile) + if err != nil { + t.Fatalf("Create failed: %v", err) + } + handle.Close() + time.Sleep(time.Millisecond) + err = os.Remove(testFile) + if err != nil { + t.Fatalf("Remove failed: %v", err) + } + } + } + }() + + creates := 0 + removes := 0 + after := time.After(5 * time.Second) + for { + select { + case <-after: + if creates-removes > 1 || creates-removes < -1 { + t.Fatalf("Creates and removes should not be off by more than one: %d creates, %d removes", creates, removes) + } + if creates < 50 { + t.Fatalf("Expected at least 50 creates, got %d", creates) + } + return + case err := <-w.Errors: + t.Fatalf("Got an error from watcher: %v", err) + case evt := <-w.Events: + if evt.Name != testFile { + t.Fatalf("Got an event for an unknown file: %s", evt.Name) + } + if evt.Op == Create { + creates++ + } + if evt.Op == Remove { + removes++ + } + } + } +} + +func TestInotifyRemoveTwice(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + testFile := filepath.Join(testDir, "testfile") + + handle, err := os.Create(testFile) + if err != nil { + t.Fatalf("Create failed: %v", err) + } + handle.Close() + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher: %v", err) + } + defer w.Close() + + err = w.Add(testFile) + if err != nil { + t.Fatalf("Failed to add testFile: %v", err) + } + + err = os.Remove(testFile) + if err != nil { + t.Fatalf("Failed to remove testFile: %v", err) + } + + err = w.Remove(testFile) + if err != syscall.EINVAL { + t.Fatalf("Expected EINVAL from Remove, got: %v", err) + } + + err = w.Remove(testFile) + if err == syscall.EINVAL { + t.Fatalf("Got EINVAL again, watch was not removed") + } +} diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/integration_test.go b/vendor/src/github.com/go-fsnotify/fsnotify/integration_test.go index ad51ab60b2..59169c6afa 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/integration_test.go +++ b/vendor/src/github.com/go-fsnotify/fsnotify/integration_test.go @@ -1109,6 +1109,21 @@ func TestConcurrentRemovalOfWatch(t *testing.T) { <-removed2 } +func TestClose(t *testing.T) { + // Regression test for #59 bad file descriptor from Close + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + watcher := newWatcher(t) + if err := watcher.Add(testDir); err != nil { + t.Fatalf("Expected no error on Add, got %v", err) + } + err := watcher.Close() + if err != nil { + t.Fatalf("Expected no error on Close, got %v.", err) + } +} + func testRename(file1, file2 string) error { switch runtime.GOOS { case "windows", "plan9": diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/kqueue.go b/vendor/src/github.com/go-fsnotify/fsnotify/kqueue.go index 5ef1346c0d..265622d201 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/kqueue.go +++ b/vendor/src/github.com/go-fsnotify/fsnotify/kqueue.go @@ -14,46 +14,48 @@ import ( "path/filepath" "sync" "syscall" + "time" ) // Watcher watches a set of files, delivering events to a channel. type Watcher struct { - Events chan Event - Errors chan error - mu sync.Mutex // Mutex for the Watcher itself. - kq int // File descriptor (as returned by the kqueue() syscall). - watches map[string]int // Map of watched file descriptors (key: path). - wmut sync.Mutex // Protects access to watches. - enFlags map[string]uint32 // Map of watched files to evfilt note flags used in kqueue. - enmut sync.Mutex // Protects access to enFlags. - paths map[int]string // Map of watched paths (key: watch descriptor). - finfo map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor). - pmut sync.Mutex // Protects access to paths and finfo. - fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events). - femut sync.Mutex // Protects access to fileExists. - externalWatches map[string]bool // Map of watches added by user of the library. - ewmut sync.Mutex // Protects access to externalWatches. - done chan bool // Channel for sending a "quit message" to the reader goroutine - isClosed bool // Set to true when Close() is first called + Events chan Event + Errors chan error + done chan bool // Channel for sending a "quit message" to the reader goroutine + + kq int // File descriptor (as returned by the kqueue() syscall). + + mu sync.Mutex // Protects access to watcher data + watches map[string]int // Map of watched file descriptors (key: path). + externalWatches map[string]bool // Map of watches added by user of the library. + dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue. + paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events. + fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events). + isClosed bool // Set to true when Close() is first called +} + +type pathInfo struct { + name string + isDir bool } // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. func NewWatcher() (*Watcher, error) { - fd, errno := syscall.Kqueue() - if fd == -1 { - return nil, os.NewSyscallError("kqueue", errno) + kq, err := kqueue() + if err != nil { + return nil, err } + w := &Watcher{ - kq: fd, + kq: kq, watches: make(map[string]int), - enFlags: make(map[string]uint32), - paths: make(map[int]string), - finfo: make(map[int]os.FileInfo), + dirFlags: make(map[string]uint32), + paths: make(map[int]pathInfo), fileExists: make(map[string]bool), externalWatches: make(map[string]bool), Events: make(chan Event), Errors: make(chan error), - done: make(chan bool, 1), + done: make(chan bool), } go w.readEvents() @@ -70,73 +72,68 @@ func (w *Watcher) Close() error { w.isClosed = true w.mu.Unlock() + w.mu.Lock() + ws := w.watches + w.mu.Unlock() + + var err error + for name := range ws { + if e := w.Remove(name); e != nil && err == nil { + err = e + } + } + // Send "quit" message to the reader goroutine: w.done <- true - w.wmut.Lock() - ws := w.watches - w.wmut.Unlock() - for name := range ws { - w.Remove(name) - } return nil } // Add starts watching the named file or directory (non-recursively). func (w *Watcher) Add(name string) error { - w.ewmut.Lock() + w.mu.Lock() w.externalWatches[name] = true - w.ewmut.Unlock() + w.mu.Unlock() return w.addWatch(name, noteAllEvents) } // Remove stops watching the the named file or directory (non-recursively). func (w *Watcher) Remove(name string) error { name = filepath.Clean(name) - w.wmut.Lock() + w.mu.Lock() watchfd, ok := w.watches[name] - w.wmut.Unlock() + w.mu.Unlock() if !ok { return fmt.Errorf("can't remove non-existent kevent watch for: %s", name) } - var kbuf [1]syscall.Kevent_t - watchEntry := &kbuf[0] - syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_DELETE) - entryFlags := watchEntry.Flags - success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil) - if success == -1 { - return os.NewSyscallError("kevent_rm_watch", errno) - } else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR { - return errors.New("kevent rm error") + + const registerRemove = syscall.EV_DELETE + if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil { + return err } + syscall.Close(watchfd) - w.wmut.Lock() + + w.mu.Lock() + isDir := w.paths[watchfd].isDir delete(w.watches, name) - w.wmut.Unlock() - w.enmut.Lock() - delete(w.enFlags, name) - w.enmut.Unlock() - w.pmut.Lock() delete(w.paths, watchfd) - fInfo := w.finfo[watchfd] - delete(w.finfo, watchfd) - w.pmut.Unlock() + delete(w.dirFlags, name) + w.mu.Unlock() // Find all watched paths that are in this directory that are not external. - if fInfo.IsDir() { + if isDir { var pathsToRemove []string - w.pmut.Lock() - for _, wpath := range w.paths { - wdir, _ := filepath.Split(wpath) - if filepath.Clean(wdir) == filepath.Clean(name) { - w.ewmut.Lock() - if !w.externalWatches[wpath] { - pathsToRemove = append(pathsToRemove, wpath) + w.mu.Lock() + for _, path := range w.paths { + wdir, _ := filepath.Split(path.name) + if filepath.Clean(wdir) == name { + if !w.externalWatches[path.name] { + pathsToRemove = append(pathsToRemove, path.name) } - w.ewmut.Unlock() } } - w.pmut.Unlock() + w.mu.Unlock() for _, name := range pathsToRemove { // Since these are internal, not much sense in propagating error // to the user, as that will just confuse them with an error about @@ -148,37 +145,38 @@ func (w *Watcher) Remove(name string) error { return nil } -const ( - // Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE) - noteAllEvents = syscall.NOTE_DELETE | syscall.NOTE_WRITE | syscall.NOTE_ATTRIB | syscall.NOTE_RENAME +// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE) +const noteAllEvents = syscall.NOTE_DELETE | syscall.NOTE_WRITE | syscall.NOTE_ATTRIB | syscall.NOTE_RENAME - // Block for 100 ms on each call to kevent - keventWaitTime = 100e6 -) +// keventWaitTime to block on each read from kevent +var keventWaitTime = durationToTimespec(100 * time.Millisecond) -// addWatch adds path to the watched file set. +// addWatch adds name to the watched file set. // The flags are interpreted as described in kevent(2). -func (w *Watcher) addWatch(path string, flags uint32) error { - path = filepath.Clean(path) +func (w *Watcher) addWatch(name string, flags uint32) error { + var isDir bool + // Make ./name and name equivalent + name = filepath.Clean(name) + w.mu.Lock() if w.isClosed { w.mu.Unlock() return errors.New("kevent instance already closed") } + watchfd, alreadyWatching := w.watches[name] + // We already have a watch, but we can still override flags. + if alreadyWatching { + isDir = w.paths[watchfd].isDir + } w.mu.Unlock() - watchDir := false - - w.wmut.Lock() - watchfd, found := w.watches[path] - w.wmut.Unlock() - if !found { - fi, errstat := os.Lstat(path) - if errstat != nil { - return errstat + if !alreadyWatching { + fi, err := os.Lstat(name) + if err != nil { + return err } - // don't watch socket + // Don't watch sockets. if fi.Mode()&os.ModeSocket == os.ModeSocket { return nil } @@ -190,131 +188,96 @@ func (w *Watcher) addWatch(path string, flags uint32) error { // be no file events for broken symlinks. // Hence the returns of nil on errors. if fi.Mode()&os.ModeSymlink == os.ModeSymlink { - path, err := filepath.EvalSymlinks(path) + name, err = filepath.EvalSymlinks(name) if err != nil { return nil } - fi, errstat = os.Lstat(path) - if errstat != nil { + fi, err = os.Lstat(name) + if err != nil { return nil } } - fd, errno := syscall.Open(path, openMode, 0700) - if fd == -1 { - return os.NewSyscallError("Open", errno) + watchfd, err = syscall.Open(name, openMode, 0700) + if watchfd == -1 { + return err } - watchfd = fd - w.wmut.Lock() - w.watches[path] = watchfd - w.wmut.Unlock() - - w.pmut.Lock() - w.paths[watchfd] = path - w.finfo[watchfd] = fi - w.pmut.Unlock() - } - // Watch the directory if it has not been watched before. - w.pmut.Lock() - w.enmut.Lock() - if w.finfo[watchfd].IsDir() && - (flags&syscall.NOTE_WRITE) == syscall.NOTE_WRITE && - (!found || (w.enFlags[path]&syscall.NOTE_WRITE) != syscall.NOTE_WRITE) { - watchDir = true - } - w.enmut.Unlock() - w.pmut.Unlock() - - w.enmut.Lock() - w.enFlags[path] = flags - w.enmut.Unlock() - - var kbuf [1]syscall.Kevent_t - watchEntry := &kbuf[0] - watchEntry.Fflags = flags - syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_ADD|syscall.EV_CLEAR) - entryFlags := watchEntry.Flags - success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil) - if success == -1 { - return errno - } else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR { - return errors.New("kevent add error") + isDir = fi.IsDir() } - if watchDir { - errdir := w.watchDirectoryFiles(path) - if errdir != nil { - return errdir + const registerAdd = syscall.EV_ADD | syscall.EV_CLEAR | syscall.EV_ENABLE + if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil { + syscall.Close(watchfd) + return err + } + + if !alreadyWatching { + w.mu.Lock() + w.watches[name] = watchfd + w.paths[watchfd] = pathInfo{name: name, isDir: isDir} + w.mu.Unlock() + } + + if isDir { + // Watch the directory if it has not been watched before, + // or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles) + w.mu.Lock() + watchDir := (flags&syscall.NOTE_WRITE) == syscall.NOTE_WRITE && + (!alreadyWatching || (w.dirFlags[name]&syscall.NOTE_WRITE) != syscall.NOTE_WRITE) + // Store flags so this watch can be updated later + w.dirFlags[name] = flags + w.mu.Unlock() + + if watchDir { + if err := w.watchDirectoryFiles(name); err != nil { + return err + } } } return nil } -// readEvents reads from the kqueue file descriptor, converts the -// received events into Event objects and sends them via the Events channel +// readEvents reads from kqueue and converts the received kevents into +// Event values that it sends down the Events channel. func (w *Watcher) readEvents() { - var ( - keventbuf [10]syscall.Kevent_t // Event buffer - kevents []syscall.Kevent_t // Received events - twait *syscall.Timespec // Time to block waiting for events - n int // Number of events returned from kevent - errno error // Syscall errno - ) - kevents = keventbuf[0:0] - twait = new(syscall.Timespec) - *twait = syscall.NsecToTimespec(keventWaitTime) + eventBuffer := make([]syscall.Kevent_t, 10) for { // See if there is a message on the "done" channel - var done bool select { - case done = <-w.done: - default: - } - - // If "done" message is received - if done { - errno := syscall.Close(w.kq) - if errno != nil { - w.Errors <- os.NewSyscallError("close", errno) + case <-w.done: + err := syscall.Close(w.kq) + if err != nil { + w.Errors <- err } close(w.Events) close(w.Errors) return + default: } // Get new events - if len(kevents) == 0 { - n, errno = syscall.Kevent(w.kq, nil, keventbuf[:], twait) - - // EINTR is okay, basically the syscall was interrupted before - // timeout expired. - if errno != nil && errno != syscall.EINTR { - w.Errors <- os.NewSyscallError("kevent", errno) - continue - } - - // Received some events - if n > 0 { - kevents = keventbuf[0:n] - } + kevents, err := read(w.kq, eventBuffer, &keventWaitTime) + // EINTR is okay, the syscall was interrupted before timeout expired. + if err != nil && err != syscall.EINTR { + w.Errors <- err + continue } // Flush the events we received to the Events channel for len(kevents) > 0 { - watchEvent := &kevents[0] - mask := uint32(watchEvent.Fflags) - w.pmut.Lock() - name := w.paths[int(watchEvent.Ident)] - fileInfo := w.finfo[int(watchEvent.Ident)] - w.pmut.Unlock() + kevent := &kevents[0] + watchfd := int(kevent.Ident) + mask := uint32(kevent.Fflags) + w.mu.Lock() + path := w.paths[watchfd] + w.mu.Unlock() + event := newEvent(path.name, mask) - event := newEvent(name, mask, false) - - if fileInfo != nil && fileInfo.IsDir() && !(event.Op&Remove == Remove) { - // Double check to make sure the directory exist. This can happen when + if path.isDir && !(event.Op&Remove == Remove) { + // Double check to make sure the directory exists. This can happen when // we do a rm -fr on a recursively watched folders and we receive a // modification event first but the folder has been deleted and later // receive the delete event @@ -324,55 +287,49 @@ func (w *Watcher) readEvents() { } } - if fileInfo != nil && fileInfo.IsDir() && event.Op&Write == Write && !(event.Op&Remove == Remove) { + if event.Op&Rename == Rename || event.Op&Remove == Remove { + w.Remove(event.Name) + w.mu.Lock() + delete(w.fileExists, event.Name) + w.mu.Unlock() + } + + if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) { w.sendDirectoryChangeEvents(event.Name) } else { // Send the event on the Events channel w.Events <- event } - // Move to next event - kevents = kevents[1:] - - if event.Op&Rename == Rename { - w.Remove(event.Name) - w.femut.Lock() - delete(w.fileExists, event.Name) - w.femut.Unlock() - } if event.Op&Remove == Remove { - w.Remove(event.Name) - w.femut.Lock() - delete(w.fileExists, event.Name) - w.femut.Unlock() - - // Look for a file that may have overwritten this - // (ie mv f1 f2 will delete f2 then create f2) + // Look for a file that may have overwritten this. + // For example, mv f1 f2 will delete f2, then create f2. fileDir, _ := filepath.Split(event.Name) fileDir = filepath.Clean(fileDir) - w.wmut.Lock() + w.mu.Lock() _, found := w.watches[fileDir] - w.wmut.Unlock() + w.mu.Unlock() if found { - // make sure the directory exist before we watch for changes. When we + // make sure the directory exists before we watch for changes. When we // do a recursive watch and perform rm -fr, the parent directory might // have gone missing, ignore the missing directory and let the - // upcoming delete event remove the watch form the parent folder - if _, err := os.Lstat(fileDir); !os.IsNotExist(err) { + // upcoming delete event remove the watch from the parent directory. + if _, err := os.Lstat(fileDir); os.IsExist(err) { w.sendDirectoryChangeEvents(fileDir) + // FIXME: should this be for events on files or just isDir? } } } + + // Move to next event + kevents = kevents[1:] } } } // newEvent returns an platform-independent Event based on kqueue Fflags. -func newEvent(name string, mask uint32, create bool) Event { +func newEvent(name string, mask uint32) Event { e := Event{Name: name} - if create { - e.Op |= Create - } if mask&syscall.NOTE_DELETE == syscall.NOTE_DELETE { e.Op |= Remove } @@ -388,6 +345,11 @@ func newEvent(name string, mask uint32, create bool) Event { return e } +func newCreateEvent(name string) Event { + return Event{Name: name, Op: Create} +} + +// watchDirectoryFiles to mimic inotify when adding a watch on a directory func (w *Watcher) watchDirectoryFiles(dirPath string) error { // Get all files files, err := ioutil.ReadDir(dirPath) @@ -395,36 +357,15 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error { return err } - // Search for new files for _, fileInfo := range files { filePath := filepath.Join(dirPath, fileInfo.Name()) - - if fileInfo.IsDir() == false { - // Watch file to mimic linux fsnotify - e := w.addWatch(filePath, noteAllEvents) - if e != nil { - return e - } - } else { - // If the user is currently watching directory - // we want to preserve the flags used - w.enmut.Lock() - currFlags, found := w.enFlags[filePath] - w.enmut.Unlock() - var newFlags uint32 = syscall.NOTE_DELETE - if found { - newFlags |= currFlags - } - - // Linux gives deletes if not explicitly watching - e := w.addWatch(filePath, newFlags) - if e != nil { - return e - } + if err := w.internalWatch(filePath, fileInfo); err != nil { + return err } - w.femut.Lock() + + w.mu.Lock() w.fileExists[filePath] = true - w.femut.Unlock() + w.mu.Unlock() } return nil @@ -432,7 +373,7 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error { // sendDirectoryEvents searches the directory for newly created files // and sends them over the event channel. This functionality is to have -// the BSD version of fsnotify match linux fsnotify which provides a +// the BSD version of fsnotify match Linux inotify which provides a // create event for files created in a watched directory. func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { // Get all files @@ -444,36 +385,79 @@ func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { // Search for new files for _, fileInfo := range files { filePath := filepath.Join(dirPath, fileInfo.Name()) - w.femut.Lock() + w.mu.Lock() _, doesExist := w.fileExists[filePath] - w.femut.Unlock() + w.mu.Unlock() if !doesExist { - // Send create event (mask=0) - event := newEvent(filePath, 0, true) - w.Events <- event + // Send create event + w.Events <- newCreateEvent(filePath) } - // watchDirectoryFiles (but without doing another ReadDir) - if fileInfo.IsDir() == false { - // Watch file to mimic linux fsnotify - w.addWatch(filePath, noteAllEvents) - } else { - // If the user is currently watching directory - // we want to preserve the flags used - w.enmut.Lock() - currFlags, found := w.enFlags[filePath] - w.enmut.Unlock() - var newFlags uint32 = syscall.NOTE_DELETE - if found { - newFlags |= currFlags - } - - // Linux gives deletes if not explicitly watching - w.addWatch(filePath, newFlags) + // like watchDirectoryFiles (but without doing another ReadDir) + if err := w.internalWatch(filePath, fileInfo); err != nil { + return } - w.femut.Lock() + w.mu.Lock() w.fileExists[filePath] = true - w.femut.Unlock() + w.mu.Unlock() } } + +func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) error { + if fileInfo.IsDir() { + // mimic Linux providing delete events for subdirectories + // but preserve the flags used if currently watching subdirectory + w.mu.Lock() + flags := w.dirFlags[name] + w.mu.Unlock() + + flags |= syscall.NOTE_DELETE + return w.addWatch(name, flags) + } + + // watch file to mimic Linux inotify + return w.addWatch(name, noteAllEvents) +} + +// kqueue creates a new kernel event queue and returns a descriptor. +func kqueue() (kq int, err error) { + kq, err = syscall.Kqueue() + if kq == -1 { + return kq, err + } + return kq, nil +} + +// register events with the queue +func register(kq int, fds []int, flags int, fflags uint32) error { + changes := make([]syscall.Kevent_t, len(fds)) + + for i, fd := range fds { + // SetKevent converts int to the platform-specific types: + syscall.SetKevent(&changes[i], fd, syscall.EVFILT_VNODE, flags) + changes[i].Fflags = fflags + } + + // register the events + success, err := syscall.Kevent(kq, changes, nil, nil) + if success == -1 { + return err + } + return nil +} + +// read retrieves pending events, or waits until an event occurs. +// A timeout of nil blocks indefinitely, while 0 polls the queue. +func read(kq int, events []syscall.Kevent_t, timeout *syscall.Timespec) ([]syscall.Kevent_t, error) { + n, err := syscall.Kevent(kq, nil, events, timeout) + if err != nil { + return nil, err + } + return events[0:n], nil +} + +// durationToTimespec prepares a timeout value +func durationToTimespec(d time.Duration) syscall.Timespec { + return syscall.NsecToTimespec(d.Nanoseconds()) +}