mirror of https://github.com/docker/docs.git
Update fsnotify to 1.2.0
Signed-off-by: Andrew "Tianon" Page <admwiggin@gmail.com>
This commit is contained in:
parent
0ad6f90127
commit
e1ccfabdc5
|
@ -55,7 +55,7 @@ clone git github.com/docker/libtrust 230dfd18c232
|
||||||
|
|
||||||
clone git github.com/Sirupsen/logrus v0.7.2
|
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
|
clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
sudo: false
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.2
|
- 1.4.1
|
||||||
- tip
|
|
||||||
|
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:
|
os:
|
||||||
- linux
|
- linux
|
||||||
- osx
|
- osx
|
||||||
|
|
|
@ -18,8 +18,10 @@ Francisco Souza <f@souza.cc>
|
||||||
Hari haran <hariharan.uno@gmail.com>
|
Hari haran <hariharan.uno@gmail.com>
|
||||||
John C Barstow
|
John C Barstow
|
||||||
Kelvin Fo <vmirage@gmail.com>
|
Kelvin Fo <vmirage@gmail.com>
|
||||||
|
Matt Layher <mdlayher@gmail.com>
|
||||||
Nathan Youngman <git@nathany.com>
|
Nathan Youngman <git@nathany.com>
|
||||||
Paul Hammond <paul@paulhammond.org>
|
Paul Hammond <paul@paulhammond.org>
|
||||||
|
Pieter Droogendijk <pieter@binky.org.uk>
|
||||||
Pursuit92 <JoshChase@techpursuit.net>
|
Pursuit92 <JoshChase@techpursuit.net>
|
||||||
Rob Figueiredo <robfig@gmail.com>
|
Rob Figueiredo <robfig@gmail.com>
|
||||||
Soge Zhang <zhssoge@gmail.com>
|
Soge Zhang <zhssoge@gmail.com>
|
||||||
|
|
|
@ -1,5 +1,27 @@
|
||||||
# Changelog
|
# 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
|
## v1.0.4 / 2014-09-07
|
||||||
|
|
||||||
* kqueue: add dragonfly to the build tags.
|
* kqueue: add dragonfly to the build tags.
|
||||||
|
@ -69,6 +91,10 @@
|
||||||
* no tests for the current implementation
|
* no tests for the current implementation
|
||||||
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
* 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
|
## 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)
|
* [Backport] Fix missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
|
|
@ -1,21 +1,34 @@
|
||||||
# Contributing
|
# 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).
|
* 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.
|
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`)
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||||
3. Ensure everything works and the tests pass (see below)
|
3. Ensure everything works and the tests pass (see below)
|
||||||
4. Commit your changes (`git commit -am 'Add some feature'`)
|
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`)
|
3. Push to the branch (`git push fork my-new-feature`)
|
||||||
4. Create a new Pull Request on GitHub
|
4. Create a new Pull Request on GitHub
|
||||||
|
|
||||||
If other team members need your patch before I merge it:
|
This workflow is [thoroughly explained by Katrina Owen](https://blog.splice.com/contributing-open-source-git-repositories-go/).
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
### Testing
|
### 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.
|
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/)
|
* 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.
|
* 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'`.
|
* 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.
|
* 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
|
||||||
|
|
|
@ -2,18 +2,20 @@
|
||||||
|
|
||||||
[](http://gocover.io/github.com/go-fsnotify/fsnotify) [](https://godoc.org/gopkg.in/fsnotify.v1)
|
[](http://gocover.io/github.com/go-fsnotify/fsnotify) [](https://godoc.org/gopkg.in/fsnotify.v1)
|
||||||
|
|
||||||
|
Go 1.3+ required.
|
||||||
|
|
||||||
Cross platform: Windows, Linux, BSD and OS X.
|
Cross platform: Windows, Linux, BSD and OS X.
|
||||||
|
|
||||||
|Adapter |OS |Status |
|
|Adapter |OS |Status |
|
||||||
|----------|----------|----------|
|
|----------|----------|----------|
|
||||||
|inotify |Linux, Android\*|Supported|
|
|inotify |Linux, Android\*|Supported [](https://travis-ci.org/go-fsnotify/fsnotify)|
|
||||||
|kqueue |BSD, OS X, iOS\*|Supported|
|
|kqueue |BSD, OS X, iOS\*|Supported [](https://circleci.com/gh/go-fsnotify/fsnotify)|
|
||||||
|ReadDirectoryChangesW|Windows|Supported|
|
|ReadDirectoryChangesW|Windows|Supported [](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)|
|
||||||
|FSEvents |OS X |[Planned](https://github.com/go-fsnotify/fsnotify/issues/11)|
|
|FSEvents |OS X |[Planned](https://github.com/go-fsnotify/fsnotify/issues/11)|
|
||||||
|FEN |Solaris 11 |[Planned](https://github.com/go-fsnotify/fsnotify/issues/12)|
|
|FEN |Solaris 11 |[Planned](https://github.com/go-fsnotify/fsnotify/issues/12)|
|
||||||
|fanotify |Linux 2.6.37+ | |
|
|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)|
|
|Polling |*All* |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/9)|
|
||||||
| |Plan 9 | |
|
|
||||||
|
|
||||||
\* Android and iOS are untested.
|
\* 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.
|
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.
|
**[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
|
```go
|
||||||
import "gopkg.in/fsnotify.v0"
|
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.
|
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
|
## Contributing
|
||||||
|
|
||||||
* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com).
|
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
|
||||||
* 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.
|
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
See [example_test.go](https://github.com/go-fsnotify/fsnotify/blob/master/example_test.go).
|
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
|
||||||
|
|
|
@ -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
|
|
@ -9,7 +9,7 @@ package fsnotify_test
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"gopkg.in/fsnotify.v1"
|
"github.com/go-fsnotify/fsnotify"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleNewWatcher() {
|
func ExampleNewWatcher() {
|
||||||
|
|
|
@ -7,7 +7,10 @@
|
||||||
// Package fsnotify provides a platform-independent interface for file system notifications.
|
// Package fsnotify provides a platform-independent interface for file system notifications.
|
||||||
package fsnotify
|
package fsnotify
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
// Event represents a single file system notification.
|
// Event represents a single file system notification.
|
||||||
type Event struct {
|
type Event struct {
|
||||||
|
@ -30,27 +33,30 @@ const (
|
||||||
// String returns a string representation of the event in the form
|
// String returns a string representation of the event in the form
|
||||||
// "file: REMOVE|WRITE|..."
|
// "file: REMOVE|WRITE|..."
|
||||||
func (e Event) String() string {
|
func (e Event) String() string {
|
||||||
events := ""
|
// Use a buffer for efficient string concatenation
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
if e.Op&Create == Create {
|
if e.Op&Create == Create {
|
||||||
events += "|CREATE"
|
buffer.WriteString("|CREATE")
|
||||||
}
|
}
|
||||||
if e.Op&Remove == Remove {
|
if e.Op&Remove == Remove {
|
||||||
events += "|REMOVE"
|
buffer.WriteString("|REMOVE")
|
||||||
}
|
}
|
||||||
if e.Op&Write == Write {
|
if e.Op&Write == Write {
|
||||||
events += "|WRITE"
|
buffer.WriteString("|WRITE")
|
||||||
}
|
}
|
||||||
if e.Op&Rename == Rename {
|
if e.Op&Rename == Rename {
|
||||||
events += "|RENAME"
|
buffer.WriteString("|RENAME")
|
||||||
}
|
}
|
||||||
if e.Op&Chmod == Chmod {
|
if e.Op&Chmod == Chmod {
|
||||||
events += "|CHMOD"
|
buffer.WriteString("|CHMOD")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(events) > 0 {
|
// If buffer remains empty, return no event names
|
||||||
events = events[1:]
|
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:])
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ package fsnotify
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -21,47 +22,66 @@ import (
|
||||||
type Watcher struct {
|
type Watcher struct {
|
||||||
Events chan Event
|
Events chan Event
|
||||||
Errors chan error
|
Errors chan error
|
||||||
mu sync.Mutex // Map access
|
mu sync.Mutex // Map access
|
||||||
fd int // File descriptor (as returned by the inotify_init() syscall)
|
fd int
|
||||||
|
poller *fdPoller
|
||||||
watches map[string]*watch // Map of inotify watches (key: path)
|
watches map[string]*watch // Map of inotify watches (key: path)
|
||||||
paths map[int]string // Map of watched paths (key: watch descriptor)
|
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||||
done chan bool // Channel for sending a "quit message" to the reader goroutine
|
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||||
isClosed bool // Set to true when Close() is first called
|
doneResp chan struct{} // Channel to respond to Close
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
func NewWatcher() (*Watcher, error) {
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
// Create inotify fd
|
||||||
fd, errno := syscall.InotifyInit()
|
fd, errno := syscall.InotifyInit()
|
||||||
if fd == -1 {
|
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{
|
w := &Watcher{
|
||||||
fd: fd,
|
fd: fd,
|
||||||
watches: make(map[string]*watch),
|
poller: poller,
|
||||||
paths: make(map[int]string),
|
watches: make(map[string]*watch),
|
||||||
Events: make(chan Event),
|
paths: make(map[int]string),
|
||||||
Errors: make(chan error),
|
Events: make(chan Event),
|
||||||
done: make(chan bool, 1),
|
Errors: make(chan error),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
doneResp: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
go w.readEvents()
|
go w.readEvents()
|
||||||
return w, nil
|
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.
|
// Close removes all watches and closes the events channel.
|
||||||
func (w *Watcher) Close() error {
|
func (w *Watcher) Close() error {
|
||||||
if w.isClosed {
|
if w.isClosed() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
w.isClosed = true
|
|
||||||
|
|
||||||
// Remove all watches
|
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
||||||
for name := range w.watches {
|
close(w.done)
|
||||||
w.Remove(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send "quit" message to the reader goroutine
|
// Wake up goroutine
|
||||||
w.done <- true
|
w.poller.wake()
|
||||||
|
|
||||||
|
// Wait for goroutine to close
|
||||||
|
<-w.doneResp
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -69,7 +89,7 @@ func (w *Watcher) Close() error {
|
||||||
// Add starts watching the named file or directory (non-recursively).
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
func (w *Watcher) Add(name string) error {
|
func (w *Watcher) Add(name string) error {
|
||||||
name = filepath.Clean(name)
|
name = filepath.Clean(name)
|
||||||
if w.isClosed {
|
if w.isClosed() {
|
||||||
return errors.New("inotify instance already closed")
|
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)
|
wd, errno := syscall.InotifyAddWatch(w.fd, name, flags)
|
||||||
if wd == -1 {
|
if wd == -1 {
|
||||||
return os.NewSyscallError("inotify_add_watch", errno)
|
return errno
|
||||||
}
|
}
|
||||||
|
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
|
@ -99,20 +119,33 @@ func (w *Watcher) Add(name string) error {
|
||||||
return nil
|
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 {
|
func (w *Watcher) Remove(name string) error {
|
||||||
name = filepath.Clean(name)
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
// Fetch the watch.
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
defer w.mu.Unlock()
|
defer w.mu.Unlock()
|
||||||
watch, ok := w.watches[name]
|
watch, ok := w.watches[name]
|
||||||
|
|
||||||
|
// Remove it from inotify.
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
|
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)
|
success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
|
||||||
if success == -1 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,35 +161,65 @@ func (w *Watcher) readEvents() {
|
||||||
buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||||
n int // Number of bytes read with read()
|
n int // Number of bytes read with read()
|
||||||
errno error // Syscall errno
|
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 {
|
for {
|
||||||
// See if there is a message on the "done" channel
|
// See if we have been closed.
|
||||||
select {
|
if w.isClosed() {
|
||||||
case <-w.done:
|
|
||||||
syscall.Close(w.fd)
|
|
||||||
close(w.Events)
|
|
||||||
close(w.Errors)
|
|
||||||
return
|
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[:])
|
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
|
// syscall.Read might have been woken up by Close. If so, we're done.
|
||||||
if n == 0 {
|
if w.isClosed() {
|
||||||
syscall.Close(w.fd)
|
|
||||||
close(w.Events)
|
|
||||||
close(w.Errors)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if n < 0 {
|
|
||||||
w.Errors <- os.NewSyscallError("read", errno)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if n < syscall.SizeofInotifyEvent {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +250,11 @@ func (w *Watcher) readEvents() {
|
||||||
|
|
||||||
// Send the events that are not ignored on the events channel
|
// Send the events that are not ignored on the events channel
|
||||||
if !event.ignoreLinux(mask) {
|
if !event.ignoreLinux(mask) {
|
||||||
w.Events <- event
|
select {
|
||||||
|
case w.Events <- event:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move to the next event in the buffer
|
// Move to the next event in the buffer
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1109,6 +1109,21 @@ func TestConcurrentRemovalOfWatch(t *testing.T) {
|
||||||
<-removed2
|
<-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 {
|
func testRename(file1, file2 string) error {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows", "plan9":
|
case "windows", "plan9":
|
||||||
|
|
|
@ -14,46 +14,48 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Watcher watches a set of files, delivering events to a channel.
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
type Watcher struct {
|
type Watcher struct {
|
||||||
Events chan Event
|
Events chan Event
|
||||||
Errors chan error
|
Errors chan error
|
||||||
mu sync.Mutex // Mutex for the Watcher itself.
|
done chan bool // Channel for sending a "quit message" to the reader goroutine
|
||||||
kq int // File descriptor (as returned by the kqueue() syscall).
|
|
||||||
watches map[string]int // Map of watched file descriptors (key: path).
|
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||||
wmut sync.Mutex // Protects access to watches.
|
|
||||||
enFlags map[string]uint32 // Map of watched files to evfilt note flags used in kqueue.
|
mu sync.Mutex // Protects access to watcher data
|
||||||
enmut sync.Mutex // Protects access to enFlags.
|
watches map[string]int // Map of watched file descriptors (key: path).
|
||||||
paths map[int]string // Map of watched paths (key: watch descriptor).
|
externalWatches map[string]bool // Map of watches added by user of the library.
|
||||||
finfo map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor).
|
dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
|
||||||
pmut sync.Mutex // Protects access to paths and finfo.
|
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).
|
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.
|
isClosed bool // Set to true when Close() is first called
|
||||||
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
|
type pathInfo struct {
|
||||||
isClosed bool // Set to true when Close() is first called
|
name string
|
||||||
|
isDir bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
func NewWatcher() (*Watcher, error) {
|
func NewWatcher() (*Watcher, error) {
|
||||||
fd, errno := syscall.Kqueue()
|
kq, err := kqueue()
|
||||||
if fd == -1 {
|
if err != nil {
|
||||||
return nil, os.NewSyscallError("kqueue", errno)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
w := &Watcher{
|
w := &Watcher{
|
||||||
kq: fd,
|
kq: kq,
|
||||||
watches: make(map[string]int),
|
watches: make(map[string]int),
|
||||||
enFlags: make(map[string]uint32),
|
dirFlags: make(map[string]uint32),
|
||||||
paths: make(map[int]string),
|
paths: make(map[int]pathInfo),
|
||||||
finfo: make(map[int]os.FileInfo),
|
|
||||||
fileExists: make(map[string]bool),
|
fileExists: make(map[string]bool),
|
||||||
externalWatches: make(map[string]bool),
|
externalWatches: make(map[string]bool),
|
||||||
Events: make(chan Event),
|
Events: make(chan Event),
|
||||||
Errors: make(chan error),
|
Errors: make(chan error),
|
||||||
done: make(chan bool, 1),
|
done: make(chan bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
go w.readEvents()
|
go w.readEvents()
|
||||||
|
@ -70,73 +72,68 @@ func (w *Watcher) Close() error {
|
||||||
w.isClosed = true
|
w.isClosed = true
|
||||||
w.mu.Unlock()
|
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:
|
// Send "quit" message to the reader goroutine:
|
||||||
w.done <- true
|
w.done <- true
|
||||||
w.wmut.Lock()
|
|
||||||
ws := w.watches
|
|
||||||
w.wmut.Unlock()
|
|
||||||
for name := range ws {
|
|
||||||
w.Remove(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add starts watching the named file or directory (non-recursively).
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
func (w *Watcher) Add(name string) error {
|
func (w *Watcher) Add(name string) error {
|
||||||
w.ewmut.Lock()
|
w.mu.Lock()
|
||||||
w.externalWatches[name] = true
|
w.externalWatches[name] = true
|
||||||
w.ewmut.Unlock()
|
w.mu.Unlock()
|
||||||
return w.addWatch(name, noteAllEvents)
|
return w.addWatch(name, noteAllEvents)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove stops watching the the named file or directory (non-recursively).
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
func (w *Watcher) Remove(name string) error {
|
func (w *Watcher) Remove(name string) error {
|
||||||
name = filepath.Clean(name)
|
name = filepath.Clean(name)
|
||||||
w.wmut.Lock()
|
w.mu.Lock()
|
||||||
watchfd, ok := w.watches[name]
|
watchfd, ok := w.watches[name]
|
||||||
w.wmut.Unlock()
|
w.mu.Unlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
|
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
|
||||||
}
|
}
|
||||||
var kbuf [1]syscall.Kevent_t
|
|
||||||
watchEntry := &kbuf[0]
|
const registerRemove = syscall.EV_DELETE
|
||||||
syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_DELETE)
|
if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
|
||||||
entryFlags := watchEntry.Flags
|
return err
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
syscall.Close(watchfd)
|
syscall.Close(watchfd)
|
||||||
w.wmut.Lock()
|
|
||||||
|
w.mu.Lock()
|
||||||
|
isDir := w.paths[watchfd].isDir
|
||||||
delete(w.watches, name)
|
delete(w.watches, name)
|
||||||
w.wmut.Unlock()
|
|
||||||
w.enmut.Lock()
|
|
||||||
delete(w.enFlags, name)
|
|
||||||
w.enmut.Unlock()
|
|
||||||
w.pmut.Lock()
|
|
||||||
delete(w.paths, watchfd)
|
delete(w.paths, watchfd)
|
||||||
fInfo := w.finfo[watchfd]
|
delete(w.dirFlags, name)
|
||||||
delete(w.finfo, watchfd)
|
w.mu.Unlock()
|
||||||
w.pmut.Unlock()
|
|
||||||
|
|
||||||
// Find all watched paths that are in this directory that are not external.
|
// Find all watched paths that are in this directory that are not external.
|
||||||
if fInfo.IsDir() {
|
if isDir {
|
||||||
var pathsToRemove []string
|
var pathsToRemove []string
|
||||||
w.pmut.Lock()
|
w.mu.Lock()
|
||||||
for _, wpath := range w.paths {
|
for _, path := range w.paths {
|
||||||
wdir, _ := filepath.Split(wpath)
|
wdir, _ := filepath.Split(path.name)
|
||||||
if filepath.Clean(wdir) == filepath.Clean(name) {
|
if filepath.Clean(wdir) == name {
|
||||||
w.ewmut.Lock()
|
if !w.externalWatches[path.name] {
|
||||||
if !w.externalWatches[wpath] {
|
pathsToRemove = append(pathsToRemove, path.name)
|
||||||
pathsToRemove = append(pathsToRemove, wpath)
|
|
||||||
}
|
}
|
||||||
w.ewmut.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.pmut.Unlock()
|
w.mu.Unlock()
|
||||||
for _, name := range pathsToRemove {
|
for _, name := range pathsToRemove {
|
||||||
// Since these are internal, not much sense in propagating error
|
// Since these are internal, not much sense in propagating error
|
||||||
// to the user, as that will just confuse them with an error about
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||||||
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
const noteAllEvents = syscall.NOTE_DELETE | syscall.NOTE_WRITE | syscall.NOTE_ATTRIB | syscall.NOTE_RENAME
|
||||||
noteAllEvents = syscall.NOTE_DELETE | syscall.NOTE_WRITE | syscall.NOTE_ATTRIB | syscall.NOTE_RENAME
|
|
||||||
|
|
||||||
// Block for 100 ms on each call to kevent
|
// keventWaitTime to block on each read from kevent
|
||||||
keventWaitTime = 100e6
|
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).
|
// The flags are interpreted as described in kevent(2).
|
||||||
func (w *Watcher) addWatch(path string, flags uint32) error {
|
func (w *Watcher) addWatch(name string, flags uint32) error {
|
||||||
path = filepath.Clean(path)
|
var isDir bool
|
||||||
|
// Make ./name and name equivalent
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
w.mu.Lock()
|
w.mu.Lock()
|
||||||
if w.isClosed {
|
if w.isClosed {
|
||||||
w.mu.Unlock()
|
w.mu.Unlock()
|
||||||
return errors.New("kevent instance already closed")
|
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()
|
w.mu.Unlock()
|
||||||
|
|
||||||
watchDir := false
|
if !alreadyWatching {
|
||||||
|
fi, err := os.Lstat(name)
|
||||||
w.wmut.Lock()
|
if err != nil {
|
||||||
watchfd, found := w.watches[path]
|
return err
|
||||||
w.wmut.Unlock()
|
|
||||||
if !found {
|
|
||||||
fi, errstat := os.Lstat(path)
|
|
||||||
if errstat != nil {
|
|
||||||
return errstat
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't watch socket
|
// Don't watch sockets.
|
||||||
if fi.Mode()&os.ModeSocket == os.ModeSocket {
|
if fi.Mode()&os.ModeSocket == os.ModeSocket {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -190,131 +188,96 @@ func (w *Watcher) addWatch(path string, flags uint32) error {
|
||||||
// be no file events for broken symlinks.
|
// be no file events for broken symlinks.
|
||||||
// Hence the returns of nil on errors.
|
// Hence the returns of nil on errors.
|
||||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
path, err := filepath.EvalSymlinks(path)
|
name, err = filepath.EvalSymlinks(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, errstat = os.Lstat(path)
|
fi, err = os.Lstat(name)
|
||||||
if errstat != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fd, errno := syscall.Open(path, openMode, 0700)
|
watchfd, err = syscall.Open(name, openMode, 0700)
|
||||||
if fd == -1 {
|
if watchfd == -1 {
|
||||||
return os.NewSyscallError("Open", errno)
|
return err
|
||||||
}
|
}
|
||||||
watchfd = fd
|
|
||||||
|
|
||||||
w.wmut.Lock()
|
isDir = fi.IsDir()
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if watchDir {
|
const registerAdd = syscall.EV_ADD | syscall.EV_CLEAR | syscall.EV_ENABLE
|
||||||
errdir := w.watchDirectoryFiles(path)
|
if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
|
||||||
if errdir != nil {
|
syscall.Close(watchfd)
|
||||||
return errdir
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readEvents reads from the kqueue file descriptor, converts the
|
// readEvents reads from kqueue and converts the received kevents into
|
||||||
// received events into Event objects and sends them via the Events channel
|
// Event values that it sends down the Events channel.
|
||||||
func (w *Watcher) readEvents() {
|
func (w *Watcher) readEvents() {
|
||||||
var (
|
eventBuffer := make([]syscall.Kevent_t, 10)
|
||||||
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)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// See if there is a message on the "done" channel
|
// See if there is a message on the "done" channel
|
||||||
var done bool
|
|
||||||
select {
|
select {
|
||||||
case done = <-w.done:
|
case <-w.done:
|
||||||
default:
|
err := syscall.Close(w.kq)
|
||||||
}
|
if err != nil {
|
||||||
|
w.Errors <- err
|
||||||
// If "done" message is received
|
|
||||||
if done {
|
|
||||||
errno := syscall.Close(w.kq)
|
|
||||||
if errno != nil {
|
|
||||||
w.Errors <- os.NewSyscallError("close", errno)
|
|
||||||
}
|
}
|
||||||
close(w.Events)
|
close(w.Events)
|
||||||
close(w.Errors)
|
close(w.Errors)
|
||||||
return
|
return
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get new events
|
// Get new events
|
||||||
if len(kevents) == 0 {
|
kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
|
||||||
n, errno = syscall.Kevent(w.kq, nil, keventbuf[:], twait)
|
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||||
|
if err != nil && err != syscall.EINTR {
|
||||||
// EINTR is okay, basically the syscall was interrupted before
|
w.Errors <- err
|
||||||
// timeout expired.
|
continue
|
||||||
if errno != nil && errno != syscall.EINTR {
|
|
||||||
w.Errors <- os.NewSyscallError("kevent", errno)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Received some events
|
|
||||||
if n > 0 {
|
|
||||||
kevents = keventbuf[0:n]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush the events we received to the Events channel
|
// Flush the events we received to the Events channel
|
||||||
for len(kevents) > 0 {
|
for len(kevents) > 0 {
|
||||||
watchEvent := &kevents[0]
|
kevent := &kevents[0]
|
||||||
mask := uint32(watchEvent.Fflags)
|
watchfd := int(kevent.Ident)
|
||||||
w.pmut.Lock()
|
mask := uint32(kevent.Fflags)
|
||||||
name := w.paths[int(watchEvent.Ident)]
|
w.mu.Lock()
|
||||||
fileInfo := w.finfo[int(watchEvent.Ident)]
|
path := w.paths[watchfd]
|
||||||
w.pmut.Unlock()
|
w.mu.Unlock()
|
||||||
|
event := newEvent(path.name, mask)
|
||||||
|
|
||||||
event := newEvent(name, mask, false)
|
if path.isDir && !(event.Op&Remove == Remove) {
|
||||||
|
// Double check to make sure the directory exists. This can happen when
|
||||||
if fileInfo != nil && fileInfo.IsDir() && !(event.Op&Remove == Remove) {
|
|
||||||
// Double check to make sure the directory exist. This can happen when
|
|
||||||
// we do a rm -fr on a recursively watched folders and we receive a
|
// 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
|
// modification event first but the folder has been deleted and later
|
||||||
// receive the delete event
|
// 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)
|
w.sendDirectoryChangeEvents(event.Name)
|
||||||
} else {
|
} else {
|
||||||
// Send the event on the Events channel
|
// Send the event on the Events channel
|
||||||
w.Events <- event
|
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 {
|
if event.Op&Remove == Remove {
|
||||||
w.Remove(event.Name)
|
// Look for a file that may have overwritten this.
|
||||||
w.femut.Lock()
|
// For example, mv f1 f2 will delete f2, then create f2.
|
||||||
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)
|
|
||||||
fileDir, _ := filepath.Split(event.Name)
|
fileDir, _ := filepath.Split(event.Name)
|
||||||
fileDir = filepath.Clean(fileDir)
|
fileDir = filepath.Clean(fileDir)
|
||||||
w.wmut.Lock()
|
w.mu.Lock()
|
||||||
_, found := w.watches[fileDir]
|
_, found := w.watches[fileDir]
|
||||||
w.wmut.Unlock()
|
w.mu.Unlock()
|
||||||
if found {
|
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
|
// do a recursive watch and perform rm -fr, the parent directory might
|
||||||
// have gone missing, ignore the missing directory and let the
|
// have gone missing, ignore the missing directory and let the
|
||||||
// upcoming delete event remove the watch form the parent folder
|
// upcoming delete event remove the watch from the parent directory.
|
||||||
if _, err := os.Lstat(fileDir); !os.IsNotExist(err) {
|
if _, err := os.Lstat(fileDir); os.IsExist(err) {
|
||||||
w.sendDirectoryChangeEvents(fileDir)
|
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.
|
// 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}
|
e := Event{Name: name}
|
||||||
if create {
|
|
||||||
e.Op |= Create
|
|
||||||
}
|
|
||||||
if mask&syscall.NOTE_DELETE == syscall.NOTE_DELETE {
|
if mask&syscall.NOTE_DELETE == syscall.NOTE_DELETE {
|
||||||
e.Op |= Remove
|
e.Op |= Remove
|
||||||
}
|
}
|
||||||
|
@ -388,6 +345,11 @@ func newEvent(name string, mask uint32, create bool) Event {
|
||||||
return e
|
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 {
|
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||||
// Get all files
|
// Get all files
|
||||||
files, err := ioutil.ReadDir(dirPath)
|
files, err := ioutil.ReadDir(dirPath)
|
||||||
|
@ -395,36 +357,15 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for new files
|
|
||||||
for _, fileInfo := range files {
|
for _, fileInfo := range files {
|
||||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||||
|
if err := w.internalWatch(filePath, fileInfo); err != nil {
|
||||||
if fileInfo.IsDir() == false {
|
return err
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
w.femut.Lock()
|
|
||||||
|
w.mu.Lock()
|
||||||
w.fileExists[filePath] = true
|
w.fileExists[filePath] = true
|
||||||
w.femut.Unlock()
|
w.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -432,7 +373,7 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||||
|
|
||||||
// sendDirectoryEvents searches the directory for newly created files
|
// sendDirectoryEvents searches the directory for newly created files
|
||||||
// and sends them over the event channel. This functionality is to have
|
// 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.
|
// create event for files created in a watched directory.
|
||||||
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
||||||
// Get all files
|
// Get all files
|
||||||
|
@ -444,36 +385,79 @@ func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
||||||
// Search for new files
|
// Search for new files
|
||||||
for _, fileInfo := range files {
|
for _, fileInfo := range files {
|
||||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||||
w.femut.Lock()
|
w.mu.Lock()
|
||||||
_, doesExist := w.fileExists[filePath]
|
_, doesExist := w.fileExists[filePath]
|
||||||
w.femut.Unlock()
|
w.mu.Unlock()
|
||||||
if !doesExist {
|
if !doesExist {
|
||||||
// Send create event (mask=0)
|
// Send create event
|
||||||
event := newEvent(filePath, 0, true)
|
w.Events <- newCreateEvent(filePath)
|
||||||
w.Events <- event
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// watchDirectoryFiles (but without doing another ReadDir)
|
// like watchDirectoryFiles (but without doing another ReadDir)
|
||||||
if fileInfo.IsDir() == false {
|
if err := w.internalWatch(filePath, fileInfo); err != nil {
|
||||||
// Watch file to mimic linux fsnotify
|
return
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.femut.Lock()
|
w.mu.Lock()
|
||||||
w.fileExists[filePath] = true
|
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())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue