forked from mirrors/statsd_exporter
Merge pull request #243 from prometheus/mr/remove-inotify
Reload on SIGHUP instead of watching the file
This commit is contained in:
commit
d59f306cd9
17 changed files with 20 additions and 1887 deletions
|
@ -94,7 +94,7 @@ NOTE: Version 0.7.0 switched to the [kingpin](https://github.com/alecthomas/king
|
||||||
|
|
||||||
The `statsd_exporter` can be configured to translate specific dot-separated StatsD
|
The `statsd_exporter` can be configured to translate specific dot-separated StatsD
|
||||||
metrics into labeled Prometheus metrics via a simple mapping language. The config
|
metrics into labeled Prometheus metrics via a simple mapping language. The config
|
||||||
file is watched for changes and automatically reloaded.
|
file is reloaded on SIGHUP.
|
||||||
|
|
||||||
A mapping definition starts with a line matching the StatsD metric in question,
|
A mapping definition starts with a line matching the StatsD metric in question,
|
||||||
with `*`s acting as wildcards for each dot-separated metric component. The
|
with `*`s acting as wildcards for each dot-separated metric component. The
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -2,7 +2,6 @@ module github.com/prometheus/statsd_exporter
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/hashicorp/golang-lru v0.5.1
|
github.com/hashicorp/golang-lru v0.5.1
|
||||||
github.com/howeyc/fsnotify v0.0.0-20151003194602-f0c08ee9c607
|
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.0.0
|
github.com/prometheus/client_golang v1.0.0
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -19,8 +19,6 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/howeyc/fsnotify v0.0.0-20151003194602-f0c08ee9c607 h1:+7wvV++11s0Okyl1dekihkIiCIYDz+Qk2LvxAShINU4=
|
|
||||||
github.com/howeyc/fsnotify v0.0.0-20151003194602-f0c08ee9c607/go.mod h1:41HzSPxBGeFRQKEEwgh49TRw/nKBsYZ2cF1OzPjSJsA=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
|
|
48
main.go
48
main.go
|
@ -23,7 +23,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/howeyc/fsnotify"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/prometheus/common/log"
|
"github.com/prometheus/common/log"
|
||||||
|
@ -91,35 +90,24 @@ func tcpAddrFromString(addr string) *net.TCPAddr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func watchConfig(fileName string, mapper *mapper.MetricMapper, cacheSize int) {
|
func configReloader(fileName string, mapper *mapper.MetricMapper, cacheSize int) {
|
||||||
watcher, err := fsnotify.NewWatcher()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = watcher.WatchFlags(fileName, fsnotify.FSN_MODIFY)
|
signals := make(chan os.Signal, 1)
|
||||||
if err != nil {
|
signal.Notify(signals, syscall.SIGHUP)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
for s := range signals {
|
||||||
select {
|
if fileName == "" {
|
||||||
case ev := <-watcher.Event:
|
log.Warnf("Received %s but no mapping config to reload", s)
|
||||||
log.Infof("Config file changed (%s), attempting reload", ev)
|
continue
|
||||||
err = mapper.InitFromFile(fileName, cacheSize)
|
}
|
||||||
if err != nil {
|
log.Infof("Received %s, attempting reload", s)
|
||||||
log.Errorln("Error reloading config:", err)
|
err := mapper.InitFromFile(fileName, cacheSize)
|
||||||
configLoads.WithLabelValues("failure").Inc()
|
if err != nil {
|
||||||
} else {
|
log.Errorln("Error reloading config:", err)
|
||||||
log.Infoln("Config reloaded successfully")
|
configLoads.WithLabelValues("failure").Inc()
|
||||||
configLoads.WithLabelValues("success").Inc()
|
} else {
|
||||||
}
|
log.Infoln("Config reloaded successfully")
|
||||||
// Re-add the file watcher since it can get lost on some changes. E.g.
|
configLoads.WithLabelValues("success").Inc()
|
||||||
// saving a file with vim results in a RENAME-MODIFY-DELETE event
|
|
||||||
// sequence, after which the newly written file is no longer watched.
|
|
||||||
_ = watcher.WatchFlags(fileName, fsnotify.FSN_MODIFY)
|
|
||||||
case err := <-watcher.Error:
|
|
||||||
log.Errorln("Error watching config:", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,10 +250,12 @@ func main() {
|
||||||
log.Fatal("Error dumping FSM:", err)
|
log.Fatal("Error dumping FSM:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
go watchConfig(*mappingConfig, mapper, *cacheSize)
|
|
||||||
} else {
|
} else {
|
||||||
mapper.InitCache(*cacheSize)
|
mapper.InitCache(*cacheSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go configReloader(*mappingConfig, mapper, *cacheSize)
|
||||||
|
|
||||||
exporter := NewExporter(mapper)
|
exporter := NewExporter(mapper)
|
||||||
|
|
||||||
signals := make(chan os.Signal, 1)
|
signals := make(chan os.Signal, 1)
|
||||||
|
|
5
vendor/github.com/howeyc/fsnotify/.gitignore
generated
vendored
5
vendor/github.com/howeyc/fsnotify/.gitignore
generated
vendored
|
@ -1,5 +0,0 @@
|
||||||
# Setup a Global .gitignore for OS and editor generated files:
|
|
||||||
# https://help.github.com/articles/ignoring-files
|
|
||||||
# git config --global core.excludesfile ~/.gitignore_global
|
|
||||||
|
|
||||||
.vagrant
|
|
28
vendor/github.com/howeyc/fsnotify/AUTHORS
generated
vendored
28
vendor/github.com/howeyc/fsnotify/AUTHORS
generated
vendored
|
@ -1,28 +0,0 @@
|
||||||
# Names should be added to this file as
|
|
||||||
# Name or Organization <email address>
|
|
||||||
# The email address is not required for organizations.
|
|
||||||
|
|
||||||
# You can update this list using the following command:
|
|
||||||
#
|
|
||||||
# $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
|
|
||||||
|
|
||||||
# Please keep the list sorted.
|
|
||||||
|
|
||||||
Adrien Bustany <adrien@bustany.org>
|
|
||||||
Caleb Spare <cespare@gmail.com>
|
|
||||||
Case Nelson <case@teammating.com>
|
|
||||||
Chris Howey <howeyc@gmail.com> <chris@howey.me>
|
|
||||||
Christoffer Buchholz <christoffer.buchholz@gmail.com>
|
|
||||||
Dave Cheney <dave@cheney.net>
|
|
||||||
Francisco Souza <f@souza.cc>
|
|
||||||
John C Barstow
|
|
||||||
Kelvin Fo <vmirage@gmail.com>
|
|
||||||
Nathan Youngman <git@nathany.com>
|
|
||||||
Paul Hammond <paul@paulhammond.org>
|
|
||||||
Pursuit92 <JoshChase@techpursuit.net>
|
|
||||||
Rob Figueiredo <robfig@gmail.com>
|
|
||||||
Travis Cline <travis.cline@gmail.com>
|
|
||||||
Tudor Golubenco <tudor.g@gmail.com>
|
|
||||||
bronze1man <bronze1man@gmail.com>
|
|
||||||
debrando <denis.brandolini@gmail.com>
|
|
||||||
henrikedwards <henrik.edwards@gmail.com>
|
|
160
vendor/github.com/howeyc/fsnotify/CHANGELOG.md
generated
vendored
160
vendor/github.com/howeyc/fsnotify/CHANGELOG.md
generated
vendored
|
@ -1,160 +0,0 @@
|
||||||
# Changelog
|
|
||||||
|
|
||||||
## v0.9.0 / 2014-01-17
|
|
||||||
|
|
||||||
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
|
||||||
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
|
||||||
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
|
||||||
|
|
||||||
## v0.8.12 / 2013-11-13
|
|
||||||
|
|
||||||
* [API] Remove FD_SET and friends from Linux adapter
|
|
||||||
|
|
||||||
## v0.8.11 / 2013-11-02
|
|
||||||
|
|
||||||
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
|
||||||
* [Doc] Spotlight and double modify events on OS X [#62][] (reported by @paulhammond)
|
|
||||||
|
|
||||||
## v0.8.10 / 2013-10-19
|
|
||||||
|
|
||||||
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
|
||||||
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
|
||||||
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
|
||||||
|
|
||||||
## v0.8.9 / 2013-09-08
|
|
||||||
|
|
||||||
* [Doc] Contributing (thanks @nathany)
|
|
||||||
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
|
||||||
* [Doc] GoCI badge in README (Linux only) [#60][]
|
|
||||||
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
|
||||||
|
|
||||||
## v0.8.8 / 2013-06-17
|
|
||||||
|
|
||||||
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
|
||||||
|
|
||||||
## v0.8.7 / 2013-06-03
|
|
||||||
|
|
||||||
* [API] Make syscall flags internal
|
|
||||||
* [Fix] inotify: ignore event changes
|
|
||||||
* [Fix] race in symlink test [#45][] (reported by @srid)
|
|
||||||
* [Fix] tests on Windows
|
|
||||||
* lower case error messages
|
|
||||||
|
|
||||||
## v0.8.6 / 2013-05-23
|
|
||||||
|
|
||||||
* kqueue: Use EVT_ONLY flag on Darwin
|
|
||||||
* [Doc] Update README with full example
|
|
||||||
|
|
||||||
## v0.8.5 / 2013-05-09
|
|
||||||
|
|
||||||
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
|
||||||
|
|
||||||
## v0.8.4 / 2013-04-07
|
|
||||||
|
|
||||||
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
|
||||||
|
|
||||||
## v0.8.3 / 2013-03-13
|
|
||||||
|
|
||||||
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
|
||||||
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
|
||||||
|
|
||||||
## v0.8.2 / 2013-02-07
|
|
||||||
|
|
||||||
* [Doc] add Authors
|
|
||||||
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
|
||||||
|
|
||||||
## v0.8.1 / 2013-01-09
|
|
||||||
|
|
||||||
* [Fix] Windows path separators
|
|
||||||
* [Doc] BSD License
|
|
||||||
|
|
||||||
## v0.8.0 / 2012-11-09
|
|
||||||
|
|
||||||
* kqueue: directory watching improvements (thanks @vmirage)
|
|
||||||
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
|
||||||
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
|
||||||
|
|
||||||
## v0.7.4 / 2012-10-09
|
|
||||||
|
|
||||||
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
|
||||||
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
|
||||||
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
|
||||||
* [Fix] kqueue: modify after recreation of file
|
|
||||||
|
|
||||||
## v0.7.3 / 2012-09-27
|
|
||||||
|
|
||||||
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
|
||||||
* [Fix] kqueue: no longer get duplicate CREATE events
|
|
||||||
|
|
||||||
## v0.7.2 / 2012-09-01
|
|
||||||
|
|
||||||
* kqueue: events for created directories
|
|
||||||
|
|
||||||
## v0.7.1 / 2012-07-14
|
|
||||||
|
|
||||||
* [Fix] for renaming files
|
|
||||||
|
|
||||||
## v0.7.0 / 2012-07-02
|
|
||||||
|
|
||||||
* [Feature] FSNotify flags
|
|
||||||
* [Fix] inotify: Added file name back to event path
|
|
||||||
|
|
||||||
## v0.6.0 / 2012-06-06
|
|
||||||
|
|
||||||
* kqueue: watch files after directory created (thanks @tmc)
|
|
||||||
|
|
||||||
## v0.5.1 / 2012-05-22
|
|
||||||
|
|
||||||
* [Fix] inotify: remove all watches before Close()
|
|
||||||
|
|
||||||
## v0.5.0 / 2012-05-03
|
|
||||||
|
|
||||||
* [API] kqueue: return errors during watch instead of sending over channel
|
|
||||||
* kqueue: match symlink behavior on Linux
|
|
||||||
* inotify: add `DELETE_SELF` (requested by @taralx)
|
|
||||||
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
|
||||||
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
|
||||||
|
|
||||||
## v0.4.0 / 2012-03-30
|
|
||||||
|
|
||||||
* Go 1 released: build with go tool
|
|
||||||
* [Feature] Windows support using winfsnotify
|
|
||||||
* Windows does not have attribute change notifications
|
|
||||||
* Roll attribute notifications into IsModify
|
|
||||||
|
|
||||||
## v0.3.0 / 2012-02-19
|
|
||||||
|
|
||||||
* kqueue: add files when watch directory
|
|
||||||
|
|
||||||
## v0.2.0 / 2011-12-30
|
|
||||||
|
|
||||||
* update to latest Go weekly code
|
|
||||||
|
|
||||||
## v0.1.0 / 2011-10-19
|
|
||||||
|
|
||||||
* kqueue: add watch on file creation to match inotify
|
|
||||||
* kqueue: create file event
|
|
||||||
* inotify: ignore `IN_IGNORED` events
|
|
||||||
* event String()
|
|
||||||
* linux: common FileEvent functions
|
|
||||||
* initial commit
|
|
||||||
|
|
||||||
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
|
||||||
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
|
||||||
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
|
||||||
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
|
||||||
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
|
||||||
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
|
||||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
|
||||||
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
|
||||||
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
|
||||||
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
|
||||||
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
|
||||||
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
|
||||||
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
|
||||||
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
|
||||||
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
|
||||||
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
|
||||||
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
|
||||||
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
|
||||||
[#1]: https://github.com/howeyc/fsnotify/issues/1
|
|
7
vendor/github.com/howeyc/fsnotify/CONTRIBUTING.md
generated
vendored
7
vendor/github.com/howeyc/fsnotify/CONTRIBUTING.md
generated
vendored
|
@ -1,7 +0,0 @@
|
||||||
# Contributing
|
|
||||||
|
|
||||||
## Moving Notice
|
|
||||||
|
|
||||||
There is a fork being actively developed with a new API in preparation for the Go Standard Library:
|
|
||||||
[github.com/go-fsnotify/fsnotify](https://github.com/go-fsnotify/fsnotify)
|
|
||||||
|
|
28
vendor/github.com/howeyc/fsnotify/LICENSE
generated
vendored
28
vendor/github.com/howeyc/fsnotify/LICENSE
generated
vendored
|
@ -1,28 +0,0 @@
|
||||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
|
||||||
Copyright (c) 2012 fsnotify Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
93
vendor/github.com/howeyc/fsnotify/README.md
generated
vendored
93
vendor/github.com/howeyc/fsnotify/README.md
generated
vendored
|
@ -1,93 +0,0 @@
|
||||||
# File system notifications for Go
|
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/howeyc/fsnotify?status.png)](http://godoc.org/github.com/howeyc/fsnotify)
|
|
||||||
|
|
||||||
Cross platform: Windows, Linux, BSD and OS X.
|
|
||||||
|
|
||||||
## Moving Notice
|
|
||||||
|
|
||||||
There is a fork being actively developed with a new API in preparation for the Go Standard Library:
|
|
||||||
[github.com/go-fsnotify/fsnotify](https://github.com/go-fsnotify/fsnotify)
|
|
||||||
|
|
||||||
## Example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/howeyc/fsnotify"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
watcher, err := fsnotify.NewWatcher()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan bool)
|
|
||||||
|
|
||||||
// Process events
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case ev := <-watcher.Event:
|
|
||||||
log.Println("event:", ev)
|
|
||||||
case err := <-watcher.Error:
|
|
||||||
log.Println("error:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = watcher.Watch("testDir")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hang so program doesn't exit
|
|
||||||
<-done
|
|
||||||
|
|
||||||
/* ... do stuff ... */
|
|
||||||
watcher.Close()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
For each event:
|
|
||||||
* Name
|
|
||||||
* IsCreate()
|
|
||||||
* IsDelete()
|
|
||||||
* IsModify()
|
|
||||||
* IsRename()
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
**When a file is moved to another directory is it still being watched?**
|
|
||||||
|
|
||||||
No (it shouldn't be, unless you are watching where it was moved to).
|
|
||||||
|
|
||||||
**When I watch a directory, are all subdirectories watched as well?**
|
|
||||||
|
|
||||||
No, you must add watches for any directory you want to watch (a recursive watcher is in the works [#56][]).
|
|
||||||
|
|
||||||
**Do I have to watch the Error and Event channels in a separate goroutine?**
|
|
||||||
|
|
||||||
As of now, yes. Looking into making this single-thread friendly (see [#7][])
|
|
||||||
|
|
||||||
**Why am I receiving multiple events for the same file on OS X?**
|
|
||||||
|
|
||||||
Spotlight indexing on OS X can result in multiple events (see [#62][]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#54][]).
|
|
||||||
|
|
||||||
**How many files can be watched at once?**
|
|
||||||
|
|
||||||
There are OS-specific limits as to how many watches can be created:
|
|
||||||
* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit,
|
|
||||||
reaching this limit results in a "no space left on device" error.
|
|
||||||
* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error.
|
|
||||||
|
|
||||||
|
|
||||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
|
||||||
[#56]: https://github.com/howeyc/fsnotify/issues/56
|
|
||||||
[#54]: https://github.com/howeyc/fsnotify/issues/54
|
|
||||||
[#7]: https://github.com/howeyc/fsnotify/issues/7
|
|
||||||
|
|
111
vendor/github.com/howeyc/fsnotify/fsnotify.go
generated
vendored
111
vendor/github.com/howeyc/fsnotify/fsnotify.go
generated
vendored
|
@ -1,111 +0,0 @@
|
||||||
// Copyright 2012 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.
|
|
||||||
|
|
||||||
// Package fsnotify implements file system notification.
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
const (
|
|
||||||
FSN_CREATE = 1
|
|
||||||
FSN_MODIFY = 2
|
|
||||||
FSN_DELETE = 4
|
|
||||||
FSN_RENAME = 8
|
|
||||||
|
|
||||||
FSN_ALL = FSN_MODIFY | FSN_DELETE | FSN_RENAME | FSN_CREATE
|
|
||||||
)
|
|
||||||
|
|
||||||
// Purge events from interal chan to external chan if passes filter
|
|
||||||
func (w *Watcher) purgeEvents() {
|
|
||||||
for ev := range w.internalEvent {
|
|
||||||
sendEvent := false
|
|
||||||
w.fsnmut.Lock()
|
|
||||||
fsnFlags := w.fsnFlags[ev.Name]
|
|
||||||
w.fsnmut.Unlock()
|
|
||||||
|
|
||||||
if (fsnFlags&FSN_CREATE == FSN_CREATE) && ev.IsCreate() {
|
|
||||||
sendEvent = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fsnFlags&FSN_MODIFY == FSN_MODIFY) && ev.IsModify() {
|
|
||||||
sendEvent = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fsnFlags&FSN_DELETE == FSN_DELETE) && ev.IsDelete() {
|
|
||||||
sendEvent = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fsnFlags&FSN_RENAME == FSN_RENAME) && ev.IsRename() {
|
|
||||||
sendEvent = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if sendEvent {
|
|
||||||
w.Event <- ev
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's no file, then no more events for user
|
|
||||||
// BSD must keep watch for internal use (watches DELETEs to keep track
|
|
||||||
// what files exist for create events)
|
|
||||||
if ev.IsDelete() {
|
|
||||||
w.fsnmut.Lock()
|
|
||||||
delete(w.fsnFlags, ev.Name)
|
|
||||||
w.fsnmut.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(w.Event)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch a given file path
|
|
||||||
func (w *Watcher) Watch(path string) error {
|
|
||||||
return w.WatchFlags(path, FSN_ALL)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch a given file path for a particular set of notifications (FSN_MODIFY etc.)
|
|
||||||
func (w *Watcher) WatchFlags(path string, flags uint32) error {
|
|
||||||
w.fsnmut.Lock()
|
|
||||||
w.fsnFlags[path] = flags
|
|
||||||
w.fsnmut.Unlock()
|
|
||||||
return w.watch(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a watch on a file
|
|
||||||
func (w *Watcher) RemoveWatch(path string) error {
|
|
||||||
w.fsnmut.Lock()
|
|
||||||
delete(w.fsnFlags, path)
|
|
||||||
w.fsnmut.Unlock()
|
|
||||||
return w.removeWatch(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// String formats the event e in the form
|
|
||||||
// "filename: DELETE|MODIFY|..."
|
|
||||||
func (e *FileEvent) String() string {
|
|
||||||
var events string = ""
|
|
||||||
|
|
||||||
if e.IsCreate() {
|
|
||||||
events += "|" + "CREATE"
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.IsDelete() {
|
|
||||||
events += "|" + "DELETE"
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.IsModify() {
|
|
||||||
events += "|" + "MODIFY"
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.IsRename() {
|
|
||||||
events += "|" + "RENAME"
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.IsAttrib() {
|
|
||||||
events += "|" + "ATTRIB"
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(events) > 0 {
|
|
||||||
events = events[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%q: %s", e.Name, events)
|
|
||||||
}
|
|
496
vendor/github.com/howeyc/fsnotify/fsnotify_bsd.go
generated
vendored
496
vendor/github.com/howeyc/fsnotify/fsnotify_bsd.go
generated
vendored
|
@ -1,496 +0,0 @@
|
||||||
// Copyright 2010 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 freebsd openbsd netbsd dragonfly darwin
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Flags (from <sys/event.h>)
|
|
||||||
sys_NOTE_DELETE = 0x0001 /* vnode was removed */
|
|
||||||
sys_NOTE_WRITE = 0x0002 /* data contents changed */
|
|
||||||
sys_NOTE_EXTEND = 0x0004 /* size increased */
|
|
||||||
sys_NOTE_ATTRIB = 0x0008 /* attributes changed */
|
|
||||||
sys_NOTE_LINK = 0x0010 /* link count changed */
|
|
||||||
sys_NOTE_RENAME = 0x0020 /* vnode was renamed */
|
|
||||||
sys_NOTE_REVOKE = 0x0040 /* vnode access was revoked */
|
|
||||||
|
|
||||||
// Watch all events
|
|
||||||
sys_NOTE_ALLEVENTS = sys_NOTE_DELETE | sys_NOTE_WRITE | sys_NOTE_ATTRIB | sys_NOTE_RENAME
|
|
||||||
|
|
||||||
// Block for 100 ms on each call to kevent
|
|
||||||
keventWaitTime = 100e6
|
|
||||||
)
|
|
||||||
|
|
||||||
type FileEvent struct {
|
|
||||||
mask uint32 // Mask of events
|
|
||||||
Name string // File name (optional)
|
|
||||||
create bool // set by fsnotify package if found new file
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCreate reports whether the FileEvent was triggered by a creation
|
|
||||||
func (e *FileEvent) IsCreate() bool { return e.create }
|
|
||||||
|
|
||||||
// IsDelete reports whether the FileEvent was triggered by a delete
|
|
||||||
func (e *FileEvent) IsDelete() bool { return (e.mask & sys_NOTE_DELETE) == sys_NOTE_DELETE }
|
|
||||||
|
|
||||||
// IsModify reports whether the FileEvent was triggered by a file modification
|
|
||||||
func (e *FileEvent) IsModify() bool {
|
|
||||||
return ((e.mask&sys_NOTE_WRITE) == sys_NOTE_WRITE || (e.mask&sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRename reports whether the FileEvent was triggered by a change name
|
|
||||||
func (e *FileEvent) IsRename() bool { return (e.mask & sys_NOTE_RENAME) == sys_NOTE_RENAME }
|
|
||||||
|
|
||||||
// IsAttrib reports whether the FileEvent was triggered by a change in the file metadata.
|
|
||||||
func (e *FileEvent) IsAttrib() bool {
|
|
||||||
return (e.mask & sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB
|
|
||||||
}
|
|
||||||
|
|
||||||
type Watcher struct {
|
|
||||||
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.
|
|
||||||
fsnFlags map[string]uint32 // Map of watched files to flags used for filter
|
|
||||||
fsnmut sync.Mutex // Protects access to fsnFlags.
|
|
||||||
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.
|
|
||||||
Error chan error // Errors are sent on this channel
|
|
||||||
internalEvent chan *FileEvent // Events are queued on this channel
|
|
||||||
Event chan *FileEvent // Events are returned on this channel
|
|
||||||
done chan bool // Channel for sending a "quit message" to the reader goroutine
|
|
||||||
isClosed bool // Set to true when Close() is first called
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWatcher creates and returns a new kevent instance using kqueue(2)
|
|
||||||
func NewWatcher() (*Watcher, error) {
|
|
||||||
fd, errno := syscall.Kqueue()
|
|
||||||
if fd == -1 {
|
|
||||||
return nil, os.NewSyscallError("kqueue", errno)
|
|
||||||
}
|
|
||||||
w := &Watcher{
|
|
||||||
kq: fd,
|
|
||||||
watches: make(map[string]int),
|
|
||||||
fsnFlags: make(map[string]uint32),
|
|
||||||
enFlags: make(map[string]uint32),
|
|
||||||
paths: make(map[int]string),
|
|
||||||
finfo: make(map[int]os.FileInfo),
|
|
||||||
fileExists: make(map[string]bool),
|
|
||||||
externalWatches: make(map[string]bool),
|
|
||||||
internalEvent: make(chan *FileEvent),
|
|
||||||
Event: make(chan *FileEvent),
|
|
||||||
Error: make(chan error),
|
|
||||||
done: make(chan bool, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
go w.readEvents()
|
|
||||||
go w.purgeEvents()
|
|
||||||
return w, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes a kevent watcher instance
|
|
||||||
// It sends a message to the reader goroutine to quit and removes all watches
|
|
||||||
// associated with the kevent instance
|
|
||||||
func (w *Watcher) Close() error {
|
|
||||||
w.mu.Lock()
|
|
||||||
if w.isClosed {
|
|
||||||
w.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
w.isClosed = true
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
// Send "quit" message to the reader goroutine
|
|
||||||
w.done <- true
|
|
||||||
w.wmut.Lock()
|
|
||||||
ws := w.watches
|
|
||||||
w.wmut.Unlock()
|
|
||||||
for path := range ws {
|
|
||||||
w.removeWatch(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddWatch adds path to the watched file set.
|
|
||||||
// The flags are interpreted as described in kevent(2).
|
|
||||||
func (w *Watcher) addWatch(path string, flags uint32) error {
|
|
||||||
w.mu.Lock()
|
|
||||||
if w.isClosed {
|
|
||||||
w.mu.Unlock()
|
|
||||||
return errors.New("kevent instance already closed")
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't watch socket
|
|
||||||
if fi.Mode()&os.ModeSocket == os.ModeSocket {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Follow Symlinks
|
|
||||||
// Unfortunately, Linux can add bogus symlinks to watch list without
|
|
||||||
// issue, and Windows can't do symlinks period (AFAIK). To maintain
|
|
||||||
// consistency, we will act like everything is fine. There will simply
|
|
||||||
// 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)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fi, errstat = os.Lstat(path)
|
|
||||||
if errstat != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, errno := syscall.Open(path, open_FLAGS, 0700)
|
|
||||||
if fd == -1 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
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&sys_NOTE_WRITE) == sys_NOTE_WRITE &&
|
|
||||||
(!found || (w.enFlags[path]&sys_NOTE_WRITE) != sys_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 {
|
|
||||||
errdir := w.watchDirectoryFiles(path)
|
|
||||||
if errdir != nil {
|
|
||||||
return errdir
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch adds path to the watched file set, watching all events.
|
|
||||||
func (w *Watcher) watch(path string) error {
|
|
||||||
w.ewmut.Lock()
|
|
||||||
w.externalWatches[path] = true
|
|
||||||
w.ewmut.Unlock()
|
|
||||||
return w.addWatch(path, sys_NOTE_ALLEVENTS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveWatch removes path from the watched file set.
|
|
||||||
func (w *Watcher) removeWatch(path string) error {
|
|
||||||
w.wmut.Lock()
|
|
||||||
watchfd, ok := w.watches[path]
|
|
||||||
w.wmut.Unlock()
|
|
||||||
if !ok {
|
|
||||||
return errors.New(fmt.Sprintf("can't remove non-existent kevent watch for: %s", path))
|
|
||||||
}
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
syscall.Close(watchfd)
|
|
||||||
w.wmut.Lock()
|
|
||||||
delete(w.watches, path)
|
|
||||||
w.wmut.Unlock()
|
|
||||||
w.enmut.Lock()
|
|
||||||
delete(w.enFlags, path)
|
|
||||||
w.enmut.Unlock()
|
|
||||||
w.pmut.Lock()
|
|
||||||
delete(w.paths, watchfd)
|
|
||||||
fInfo := w.finfo[watchfd]
|
|
||||||
delete(w.finfo, watchfd)
|
|
||||||
w.pmut.Unlock()
|
|
||||||
|
|
||||||
// Find all watched paths that are in this directory that are not external.
|
|
||||||
if fInfo.IsDir() {
|
|
||||||
var pathsToRemove []string
|
|
||||||
w.pmut.Lock()
|
|
||||||
for _, wpath := range w.paths {
|
|
||||||
wdir, _ := filepath.Split(wpath)
|
|
||||||
if filepath.Clean(wdir) == filepath.Clean(path) {
|
|
||||||
w.ewmut.Lock()
|
|
||||||
if !w.externalWatches[wpath] {
|
|
||||||
pathsToRemove = append(pathsToRemove, wpath)
|
|
||||||
}
|
|
||||||
w.ewmut.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.pmut.Unlock()
|
|
||||||
for _, p := 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
|
|
||||||
// a path they did not explicitly watch themselves.
|
|
||||||
w.removeWatch(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readEvents reads from the kqueue file descriptor, converts the
|
|
||||||
// received events into Event objects and sends them via the Event channel
|
|
||||||
func (w *Watcher) readEvents() {
|
|
||||||
var (
|
|
||||||
eventbuf [10]syscall.Kevent_t // Event buffer
|
|
||||||
events []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
|
|
||||||
)
|
|
||||||
events = eventbuf[0:0]
|
|
||||||
twait = new(syscall.Timespec)
|
|
||||||
*twait = syscall.NsecToTimespec(keventWaitTime)
|
|
||||||
|
|
||||||
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.Error <- os.NewSyscallError("close", errno)
|
|
||||||
}
|
|
||||||
close(w.internalEvent)
|
|
||||||
close(w.Error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get new events
|
|
||||||
if len(events) == 0 {
|
|
||||||
n, errno = syscall.Kevent(w.kq, nil, eventbuf[:], twait)
|
|
||||||
|
|
||||||
// EINTR is okay, basically the syscall was interrupted before
|
|
||||||
// timeout expired.
|
|
||||||
if errno != nil && errno != syscall.EINTR {
|
|
||||||
w.Error <- os.NewSyscallError("kevent", errno)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Received some events
|
|
||||||
if n > 0 {
|
|
||||||
events = eventbuf[0:n]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush the events we received to the events channel
|
|
||||||
for len(events) > 0 {
|
|
||||||
fileEvent := new(FileEvent)
|
|
||||||
watchEvent := &events[0]
|
|
||||||
fileEvent.mask = uint32(watchEvent.Fflags)
|
|
||||||
w.pmut.Lock()
|
|
||||||
fileEvent.Name = w.paths[int(watchEvent.Ident)]
|
|
||||||
fileInfo := w.finfo[int(watchEvent.Ident)]
|
|
||||||
w.pmut.Unlock()
|
|
||||||
if fileInfo != nil && fileInfo.IsDir() && !fileEvent.IsDelete() {
|
|
||||||
// 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
|
|
||||||
// modification event first but the folder has been deleted and later
|
|
||||||
// receive the delete event
|
|
||||||
if _, err := os.Lstat(fileEvent.Name); os.IsNotExist(err) {
|
|
||||||
// mark is as delete event
|
|
||||||
fileEvent.mask |= sys_NOTE_DELETE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fileInfo != nil && fileInfo.IsDir() && fileEvent.IsModify() && !fileEvent.IsDelete() {
|
|
||||||
w.sendDirectoryChangeEvents(fileEvent.Name)
|
|
||||||
} else {
|
|
||||||
// Send the event on the events channel
|
|
||||||
w.internalEvent <- fileEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to next event
|
|
||||||
events = events[1:]
|
|
||||||
|
|
||||||
if fileEvent.IsRename() {
|
|
||||||
w.removeWatch(fileEvent.Name)
|
|
||||||
w.femut.Lock()
|
|
||||||
delete(w.fileExists, fileEvent.Name)
|
|
||||||
w.femut.Unlock()
|
|
||||||
}
|
|
||||||
if fileEvent.IsDelete() {
|
|
||||||
w.removeWatch(fileEvent.Name)
|
|
||||||
w.femut.Lock()
|
|
||||||
delete(w.fileExists, fileEvent.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(fileEvent.Name)
|
|
||||||
fileDir = filepath.Clean(fileDir)
|
|
||||||
w.wmut.Lock()
|
|
||||||
_, found := w.watches[fileDir]
|
|
||||||
w.wmut.Unlock()
|
|
||||||
if found {
|
|
||||||
// make sure the directory exist 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) {
|
|
||||||
w.sendDirectoryChangeEvents(fileDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
|
||||||
// Get all files
|
|
||||||
files, err := ioutil.ReadDir(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for new files
|
|
||||||
for _, fileInfo := range files {
|
|
||||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
|
||||||
|
|
||||||
// Inherit fsnFlags from parent directory
|
|
||||||
w.fsnmut.Lock()
|
|
||||||
if flags, found := w.fsnFlags[dirPath]; found {
|
|
||||||
w.fsnFlags[filePath] = flags
|
|
||||||
} else {
|
|
||||||
w.fsnFlags[filePath] = FSN_ALL
|
|
||||||
}
|
|
||||||
w.fsnmut.Unlock()
|
|
||||||
|
|
||||||
if fileInfo.IsDir() == false {
|
|
||||||
// Watch file to mimic linux fsnotify
|
|
||||||
e := w.addWatch(filePath, sys_NOTE_ALLEVENTS)
|
|
||||||
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 = sys_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.fileExists[filePath] = true
|
|
||||||
w.femut.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// create event for files created in a watched directory.
|
|
||||||
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
|
||||||
// Get all files
|
|
||||||
files, err := ioutil.ReadDir(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
w.Error <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for new files
|
|
||||||
for _, fileInfo := range files {
|
|
||||||
filePath := filepath.Join(dirPath, fileInfo.Name())
|
|
||||||
w.femut.Lock()
|
|
||||||
_, doesExist := w.fileExists[filePath]
|
|
||||||
w.femut.Unlock()
|
|
||||||
if !doesExist {
|
|
||||||
// Inherit fsnFlags from parent directory
|
|
||||||
w.fsnmut.Lock()
|
|
||||||
if flags, found := w.fsnFlags[dirPath]; found {
|
|
||||||
w.fsnFlags[filePath] = flags
|
|
||||||
} else {
|
|
||||||
w.fsnFlags[filePath] = FSN_ALL
|
|
||||||
}
|
|
||||||
w.fsnmut.Unlock()
|
|
||||||
|
|
||||||
// Send create event
|
|
||||||
fileEvent := new(FileEvent)
|
|
||||||
fileEvent.Name = filePath
|
|
||||||
fileEvent.create = true
|
|
||||||
w.internalEvent <- fileEvent
|
|
||||||
}
|
|
||||||
w.femut.Lock()
|
|
||||||
w.fileExists[filePath] = true
|
|
||||||
w.femut.Unlock()
|
|
||||||
}
|
|
||||||
w.watchDirectoryFiles(dirPath)
|
|
||||||
}
|
|
304
vendor/github.com/howeyc/fsnotify/fsnotify_linux.go
generated
vendored
304
vendor/github.com/howeyc/fsnotify/fsnotify_linux.go
generated
vendored
|
@ -1,304 +0,0 @@
|
||||||
// Copyright 2010 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"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Options for inotify_init() are not exported
|
|
||||||
// sys_IN_CLOEXEC uint32 = syscall.IN_CLOEXEC
|
|
||||||
// sys_IN_NONBLOCK uint32 = syscall.IN_NONBLOCK
|
|
||||||
|
|
||||||
// Options for AddWatch
|
|
||||||
sys_IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
|
|
||||||
sys_IN_ONESHOT uint32 = syscall.IN_ONESHOT
|
|
||||||
sys_IN_ONLYDIR uint32 = syscall.IN_ONLYDIR
|
|
||||||
|
|
||||||
// The "sys_IN_MASK_ADD" option is not exported, as AddWatch
|
|
||||||
// adds it automatically, if there is already a watch for the given path
|
|
||||||
// sys_IN_MASK_ADD uint32 = syscall.IN_MASK_ADD
|
|
||||||
|
|
||||||
// Events
|
|
||||||
sys_IN_ACCESS uint32 = syscall.IN_ACCESS
|
|
||||||
sys_IN_ALL_EVENTS uint32 = syscall.IN_ALL_EVENTS
|
|
||||||
sys_IN_ATTRIB uint32 = syscall.IN_ATTRIB
|
|
||||||
sys_IN_CLOSE uint32 = syscall.IN_CLOSE
|
|
||||||
sys_IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
|
|
||||||
sys_IN_CLOSE_WRITE uint32 = syscall.IN_CLOSE_WRITE
|
|
||||||
sys_IN_CREATE uint32 = syscall.IN_CREATE
|
|
||||||
sys_IN_DELETE uint32 = syscall.IN_DELETE
|
|
||||||
sys_IN_DELETE_SELF uint32 = syscall.IN_DELETE_SELF
|
|
||||||
sys_IN_MODIFY uint32 = syscall.IN_MODIFY
|
|
||||||
sys_IN_MOVE uint32 = syscall.IN_MOVE
|
|
||||||
sys_IN_MOVED_FROM uint32 = syscall.IN_MOVED_FROM
|
|
||||||
sys_IN_MOVED_TO uint32 = syscall.IN_MOVED_TO
|
|
||||||
sys_IN_MOVE_SELF uint32 = syscall.IN_MOVE_SELF
|
|
||||||
sys_IN_OPEN uint32 = syscall.IN_OPEN
|
|
||||||
|
|
||||||
sys_AGNOSTIC_EVENTS = sys_IN_MOVED_TO | sys_IN_MOVED_FROM | sys_IN_CREATE | sys_IN_ATTRIB | sys_IN_MODIFY | sys_IN_MOVE_SELF | sys_IN_DELETE | sys_IN_DELETE_SELF
|
|
||||||
|
|
||||||
// Special events
|
|
||||||
sys_IN_ISDIR uint32 = syscall.IN_ISDIR
|
|
||||||
sys_IN_IGNORED uint32 = syscall.IN_IGNORED
|
|
||||||
sys_IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
|
|
||||||
sys_IN_UNMOUNT uint32 = syscall.IN_UNMOUNT
|
|
||||||
)
|
|
||||||
|
|
||||||
type FileEvent struct {
|
|
||||||
mask uint32 // Mask of events
|
|
||||||
cookie uint32 // Unique cookie associating related events (for rename(2))
|
|
||||||
Name string // File name (optional)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCreate reports whether the FileEvent was triggered by a creation
|
|
||||||
func (e *FileEvent) IsCreate() bool {
|
|
||||||
return (e.mask&sys_IN_CREATE) == sys_IN_CREATE || (e.mask&sys_IN_MOVED_TO) == sys_IN_MOVED_TO
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDelete reports whether the FileEvent was triggered by a delete
|
|
||||||
func (e *FileEvent) IsDelete() bool {
|
|
||||||
return (e.mask&sys_IN_DELETE_SELF) == sys_IN_DELETE_SELF || (e.mask&sys_IN_DELETE) == sys_IN_DELETE
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsModify reports whether the FileEvent was triggered by a file modification or attribute change
|
|
||||||
func (e *FileEvent) IsModify() bool {
|
|
||||||
return ((e.mask&sys_IN_MODIFY) == sys_IN_MODIFY || (e.mask&sys_IN_ATTRIB) == sys_IN_ATTRIB)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRename reports whether the FileEvent was triggered by a change name
|
|
||||||
func (e *FileEvent) IsRename() bool {
|
|
||||||
return ((e.mask&sys_IN_MOVE_SELF) == sys_IN_MOVE_SELF || (e.mask&sys_IN_MOVED_FROM) == sys_IN_MOVED_FROM)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAttrib reports whether the FileEvent was triggered by a change in the file metadata.
|
|
||||||
func (e *FileEvent) IsAttrib() bool {
|
|
||||||
return (e.mask & sys_IN_ATTRIB) == sys_IN_ATTRIB
|
|
||||||
}
|
|
||||||
|
|
||||||
type watch struct {
|
|
||||||
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
|
||||||
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Watcher struct {
|
|
||||||
mu sync.Mutex // Map access
|
|
||||||
fd int // File descriptor (as returned by the inotify_init() syscall)
|
|
||||||
watches map[string]*watch // Map of inotify watches (key: path)
|
|
||||||
fsnFlags map[string]uint32 // Map of watched files to flags used for filter
|
|
||||||
fsnmut sync.Mutex // Protects access to fsnFlags.
|
|
||||||
paths map[int]string // Map of watched paths (key: watch descriptor)
|
|
||||||
Error chan error // Errors are sent on this channel
|
|
||||||
internalEvent chan *FileEvent // Events are queued on this channel
|
|
||||||
Event chan *FileEvent // Events are returned on this channel
|
|
||||||
done chan bool // Channel for sending a "quit message" to the reader goroutine
|
|
||||||
isClosed bool // Set to true when Close() is first called
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWatcher creates and returns a new inotify instance using inotify_init(2)
|
|
||||||
func NewWatcher() (*Watcher, error) {
|
|
||||||
fd, errno := syscall.InotifyInit()
|
|
||||||
if fd == -1 {
|
|
||||||
return nil, os.NewSyscallError("inotify_init", errno)
|
|
||||||
}
|
|
||||||
w := &Watcher{
|
|
||||||
fd: fd,
|
|
||||||
watches: make(map[string]*watch),
|
|
||||||
fsnFlags: make(map[string]uint32),
|
|
||||||
paths: make(map[int]string),
|
|
||||||
internalEvent: make(chan *FileEvent),
|
|
||||||
Event: make(chan *FileEvent),
|
|
||||||
Error: make(chan error),
|
|
||||||
done: make(chan bool, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
go w.readEvents()
|
|
||||||
go w.purgeEvents()
|
|
||||||
return w, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes an inotify watcher instance
|
|
||||||
// It sends a message to the reader goroutine to quit and removes all watches
|
|
||||||
// associated with the inotify instance
|
|
||||||
func (w *Watcher) Close() error {
|
|
||||||
if w.isClosed {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
w.isClosed = true
|
|
||||||
|
|
||||||
// Remove all watches
|
|
||||||
for path := range w.watches {
|
|
||||||
w.RemoveWatch(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send "quit" message to the reader goroutine
|
|
||||||
w.done <- true
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddWatch adds path to the watched file set.
|
|
||||||
// The flags are interpreted as described in inotify_add_watch(2).
|
|
||||||
func (w *Watcher) addWatch(path string, flags uint32) error {
|
|
||||||
if w.isClosed {
|
|
||||||
return errors.New("inotify instance already closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
watchEntry, found := w.watches[path]
|
|
||||||
w.mu.Unlock()
|
|
||||||
if found {
|
|
||||||
watchEntry.flags |= flags
|
|
||||||
flags |= syscall.IN_MASK_ADD
|
|
||||||
}
|
|
||||||
wd, errno := syscall.InotifyAddWatch(w.fd, path, flags)
|
|
||||||
if wd == -1 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
w.watches[path] = &watch{wd: uint32(wd), flags: flags}
|
|
||||||
w.paths[wd] = path
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch adds path to the watched file set, watching all events.
|
|
||||||
func (w *Watcher) watch(path string) error {
|
|
||||||
return w.addWatch(path, sys_AGNOSTIC_EVENTS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveWatch removes path from the watched file set.
|
|
||||||
func (w *Watcher) removeWatch(path string) error {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
watch, ok := w.watches[path]
|
|
||||||
if !ok {
|
|
||||||
return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path))
|
|
||||||
}
|
|
||||||
success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
|
|
||||||
if success == -1 {
|
|
||||||
return os.NewSyscallError("inotify_rm_watch", errno)
|
|
||||||
}
|
|
||||||
delete(w.watches, path)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readEvents reads from the inotify file descriptor, converts the
|
|
||||||
// received events into Event objects and sends them via the Event channel
|
|
||||||
func (w *Watcher) readEvents() {
|
|
||||||
var (
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
// See if there is a message on the "done" channel
|
|
||||||
select {
|
|
||||||
case <-w.done:
|
|
||||||
syscall.Close(w.fd)
|
|
||||||
close(w.internalEvent)
|
|
||||||
close(w.Error)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
n, errno = syscall.Read(w.fd, buf[:])
|
|
||||||
|
|
||||||
// If EOF is received
|
|
||||||
if n == 0 {
|
|
||||||
syscall.Close(w.fd)
|
|
||||||
close(w.internalEvent)
|
|
||||||
close(w.Error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if n < 0 {
|
|
||||||
w.Error <- os.NewSyscallError("read", errno)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if n < syscall.SizeofInotifyEvent {
|
|
||||||
w.Error <- errors.New("inotify: short read in readEvents()")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset uint32 = 0
|
|
||||||
// We don't know how many events we just read into the buffer
|
|
||||||
// While the offset points to at least one whole event...
|
|
||||||
for offset <= uint32(n-syscall.SizeofInotifyEvent) {
|
|
||||||
// Point "raw" to the event in the buffer
|
|
||||||
raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
|
||||||
event := new(FileEvent)
|
|
||||||
event.mask = uint32(raw.Mask)
|
|
||||||
event.cookie = uint32(raw.Cookie)
|
|
||||||
nameLen := uint32(raw.Len)
|
|
||||||
// If the event happened to the watched directory or the watched file, the kernel
|
|
||||||
// doesn't append the filename to the event, but we would like to always fill the
|
|
||||||
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
|
||||||
// the "paths" map.
|
|
||||||
w.mu.Lock()
|
|
||||||
event.Name = w.paths[int(raw.Wd)]
|
|
||||||
w.mu.Unlock()
|
|
||||||
watchedName := event.Name
|
|
||||||
if nameLen > 0 {
|
|
||||||
// Point "bytes" at the first byte of the filename
|
|
||||||
bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
|
|
||||||
// The filename is padded with NUL bytes. TrimRight() gets rid of those.
|
|
||||||
event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the events that are not ignored on the events channel
|
|
||||||
if !event.ignoreLinux() {
|
|
||||||
// Setup FSNotify flags (inherit from directory watch)
|
|
||||||
w.fsnmut.Lock()
|
|
||||||
if _, fsnFound := w.fsnFlags[event.Name]; !fsnFound {
|
|
||||||
if fsnFlags, watchFound := w.fsnFlags[watchedName]; watchFound {
|
|
||||||
w.fsnFlags[event.Name] = fsnFlags
|
|
||||||
} else {
|
|
||||||
w.fsnFlags[event.Name] = FSN_ALL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.fsnmut.Unlock()
|
|
||||||
|
|
||||||
w.internalEvent <- event
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to the next event in the buffer
|
|
||||||
offset += syscall.SizeofInotifyEvent + nameLen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Certain types of events can be "ignored" and not sent over the Event
|
|
||||||
// channel. Such as events marked ignore by the kernel, or MODIFY events
|
|
||||||
// against files that do not exist.
|
|
||||||
func (e *FileEvent) ignoreLinux() bool {
|
|
||||||
// Ignore anything the inotify API says to ignore
|
|
||||||
if e.mask&sys_IN_IGNORED == sys_IN_IGNORED {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the event is not a DELETE or RENAME, the file must exist.
|
|
||||||
// Otherwise the event is ignored.
|
|
||||||
// *Note*: this was put in place because it was seen that a MODIFY
|
|
||||||
// event was sent after the DELETE. This ignores that MODIFY and
|
|
||||||
// assumes a DELETE will come or has come if the file doesn't exist.
|
|
||||||
if !(e.IsDelete() || e.IsRename()) {
|
|
||||||
_, statErr := os.Lstat(e.Name)
|
|
||||||
return os.IsNotExist(statErr)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
11
vendor/github.com/howeyc/fsnotify/fsnotify_open_bsd.go
generated
vendored
11
vendor/github.com/howeyc/fsnotify/fsnotify_open_bsd.go
generated
vendored
|
@ -1,11 +0,0 @@
|
||||||
// Copyright 2013 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 freebsd openbsd netbsd dragonfly
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
const open_FLAGS = syscall.O_NONBLOCK | syscall.O_RDONLY
|
|
11
vendor/github.com/howeyc/fsnotify/fsnotify_open_darwin.go
generated
vendored
11
vendor/github.com/howeyc/fsnotify/fsnotify_open_darwin.go
generated
vendored
|
@ -1,11 +0,0 @@
|
||||||
// Copyright 2013 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 darwin
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
const open_FLAGS = syscall.O_EVTONLY
|
|
598
vendor/github.com/howeyc/fsnotify/fsnotify_windows.go
generated
vendored
598
vendor/github.com/howeyc/fsnotify/fsnotify_windows.go
generated
vendored
|
@ -1,598 +0,0 @@
|
||||||
// Copyright 2011 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 windows
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Options for AddWatch
|
|
||||||
sys_FS_ONESHOT = 0x80000000
|
|
||||||
sys_FS_ONLYDIR = 0x1000000
|
|
||||||
|
|
||||||
// Events
|
|
||||||
sys_FS_ACCESS = 0x1
|
|
||||||
sys_FS_ALL_EVENTS = 0xfff
|
|
||||||
sys_FS_ATTRIB = 0x4
|
|
||||||
sys_FS_CLOSE = 0x18
|
|
||||||
sys_FS_CREATE = 0x100
|
|
||||||
sys_FS_DELETE = 0x200
|
|
||||||
sys_FS_DELETE_SELF = 0x400
|
|
||||||
sys_FS_MODIFY = 0x2
|
|
||||||
sys_FS_MOVE = 0xc0
|
|
||||||
sys_FS_MOVED_FROM = 0x40
|
|
||||||
sys_FS_MOVED_TO = 0x80
|
|
||||||
sys_FS_MOVE_SELF = 0x800
|
|
||||||
|
|
||||||
// Special events
|
|
||||||
sys_FS_IGNORED = 0x8000
|
|
||||||
sys_FS_Q_OVERFLOW = 0x4000
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// TODO(nj): Use syscall.ERROR_MORE_DATA from ztypes_windows in Go 1.3+
|
|
||||||
sys_ERROR_MORE_DATA syscall.Errno = 234
|
|
||||||
)
|
|
||||||
|
|
||||||
// Event is the type of the notification messages
|
|
||||||
// received on the watcher's Event channel.
|
|
||||||
type FileEvent struct {
|
|
||||||
mask uint32 // Mask of events
|
|
||||||
cookie uint32 // Unique cookie associating related events (for rename)
|
|
||||||
Name string // File name (optional)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCreate reports whether the FileEvent was triggered by a creation
|
|
||||||
func (e *FileEvent) IsCreate() bool { return (e.mask & sys_FS_CREATE) == sys_FS_CREATE }
|
|
||||||
|
|
||||||
// IsDelete reports whether the FileEvent was triggered by a delete
|
|
||||||
func (e *FileEvent) IsDelete() bool {
|
|
||||||
return ((e.mask&sys_FS_DELETE) == sys_FS_DELETE || (e.mask&sys_FS_DELETE_SELF) == sys_FS_DELETE_SELF)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsModify reports whether the FileEvent was triggered by a file modification or attribute change
|
|
||||||
func (e *FileEvent) IsModify() bool {
|
|
||||||
return ((e.mask&sys_FS_MODIFY) == sys_FS_MODIFY || (e.mask&sys_FS_ATTRIB) == sys_FS_ATTRIB)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRename reports whether the FileEvent was triggered by a change name
|
|
||||||
func (e *FileEvent) IsRename() bool {
|
|
||||||
return ((e.mask&sys_FS_MOVE) == sys_FS_MOVE || (e.mask&sys_FS_MOVE_SELF) == sys_FS_MOVE_SELF || (e.mask&sys_FS_MOVED_FROM) == sys_FS_MOVED_FROM || (e.mask&sys_FS_MOVED_TO) == sys_FS_MOVED_TO)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAttrib reports whether the FileEvent was triggered by a change in the file metadata.
|
|
||||||
func (e *FileEvent) IsAttrib() bool {
|
|
||||||
return (e.mask & sys_FS_ATTRIB) == sys_FS_ATTRIB
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
opAddWatch = iota
|
|
||||||
opRemoveWatch
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
provisional uint64 = 1 << (32 + iota)
|
|
||||||
)
|
|
||||||
|
|
||||||
type input struct {
|
|
||||||
op int
|
|
||||||
path string
|
|
||||||
flags uint32
|
|
||||||
reply chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
type inode struct {
|
|
||||||
handle syscall.Handle
|
|
||||||
volume uint32
|
|
||||||
index uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type watch struct {
|
|
||||||
ov syscall.Overlapped
|
|
||||||
ino *inode // i-number
|
|
||||||
path string // Directory path
|
|
||||||
mask uint64 // Directory itself is being watched with these notify flags
|
|
||||||
names map[string]uint64 // Map of names being watched and their notify flags
|
|
||||||
rename string // Remembers the old name while renaming a file
|
|
||||||
buf [4096]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type indexMap map[uint64]*watch
|
|
||||||
type watchMap map[uint32]indexMap
|
|
||||||
|
|
||||||
// A Watcher waits for and receives event notifications
|
|
||||||
// for a specific set of files and directories.
|
|
||||||
type Watcher struct {
|
|
||||||
mu sync.Mutex // Map access
|
|
||||||
port syscall.Handle // Handle to completion port
|
|
||||||
watches watchMap // Map of watches (key: i-number)
|
|
||||||
fsnFlags map[string]uint32 // Map of watched files to flags used for filter
|
|
||||||
fsnmut sync.Mutex // Protects access to fsnFlags.
|
|
||||||
input chan *input // Inputs to the reader are sent on this channel
|
|
||||||
internalEvent chan *FileEvent // Events are queued on this channel
|
|
||||||
Event chan *FileEvent // Events are returned on this channel
|
|
||||||
Error chan error // Errors are sent on this channel
|
|
||||||
isClosed bool // Set to true when Close() is first called
|
|
||||||
quit chan chan<- error
|
|
||||||
cookie uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWatcher creates and returns a Watcher.
|
|
||||||
func NewWatcher() (*Watcher, error) {
|
|
||||||
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
|
|
||||||
if e != nil {
|
|
||||||
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
|
|
||||||
}
|
|
||||||
w := &Watcher{
|
|
||||||
port: port,
|
|
||||||
watches: make(watchMap),
|
|
||||||
fsnFlags: make(map[string]uint32),
|
|
||||||
input: make(chan *input, 1),
|
|
||||||
Event: make(chan *FileEvent, 50),
|
|
||||||
internalEvent: make(chan *FileEvent),
|
|
||||||
Error: make(chan error),
|
|
||||||
quit: make(chan chan<- error, 1),
|
|
||||||
}
|
|
||||||
go w.readEvents()
|
|
||||||
go w.purgeEvents()
|
|
||||||
return w, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes a Watcher.
|
|
||||||
// It sends a message to the reader goroutine to quit and removes all watches
|
|
||||||
// associated with the watcher.
|
|
||||||
func (w *Watcher) Close() error {
|
|
||||||
if w.isClosed {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
w.isClosed = true
|
|
||||||
|
|
||||||
// Send "quit" message to the reader goroutine
|
|
||||||
ch := make(chan error)
|
|
||||||
w.quit <- ch
|
|
||||||
if err := w.wakeupReader(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return <-ch
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddWatch adds path to the watched file set.
|
|
||||||
func (w *Watcher) AddWatch(path string, flags uint32) error {
|
|
||||||
if w.isClosed {
|
|
||||||
return errors.New("watcher already closed")
|
|
||||||
}
|
|
||||||
in := &input{
|
|
||||||
op: opAddWatch,
|
|
||||||
path: filepath.Clean(path),
|
|
||||||
flags: flags,
|
|
||||||
reply: make(chan error),
|
|
||||||
}
|
|
||||||
w.input <- in
|
|
||||||
if err := w.wakeupReader(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return <-in.reply
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch adds path to the watched file set, watching all events.
|
|
||||||
func (w *Watcher) watch(path string) error {
|
|
||||||
return w.AddWatch(path, sys_FS_ALL_EVENTS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveWatch removes path from the watched file set.
|
|
||||||
func (w *Watcher) removeWatch(path string) error {
|
|
||||||
in := &input{
|
|
||||||
op: opRemoveWatch,
|
|
||||||
path: filepath.Clean(path),
|
|
||||||
reply: make(chan error),
|
|
||||||
}
|
|
||||||
w.input <- in
|
|
||||||
if err := w.wakeupReader(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return <-in.reply
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) wakeupReader() error {
|
|
||||||
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
|
||||||
if e != nil {
|
|
||||||
return os.NewSyscallError("PostQueuedCompletionStatus", e)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDir(pathname string) (dir string, err error) {
|
|
||||||
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
|
|
||||||
if e != nil {
|
|
||||||
return "", os.NewSyscallError("GetFileAttributes", e)
|
|
||||||
}
|
|
||||||
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
|
||||||
dir = pathname
|
|
||||||
} else {
|
|
||||||
dir, _ = filepath.Split(pathname)
|
|
||||||
dir = filepath.Clean(dir)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIno(path string) (ino *inode, err error) {
|
|
||||||
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
|
|
||||||
syscall.FILE_LIST_DIRECTORY,
|
|
||||||
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
|
|
||||||
nil, syscall.OPEN_EXISTING,
|
|
||||||
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
|
|
||||||
if e != nil {
|
|
||||||
return nil, os.NewSyscallError("CreateFile", e)
|
|
||||||
}
|
|
||||||
var fi syscall.ByHandleFileInformation
|
|
||||||
if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
|
|
||||||
syscall.CloseHandle(h)
|
|
||||||
return nil, os.NewSyscallError("GetFileInformationByHandle", e)
|
|
||||||
}
|
|
||||||
ino = &inode{
|
|
||||||
handle: h,
|
|
||||||
volume: fi.VolumeSerialNumber,
|
|
||||||
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
|
||||||
}
|
|
||||||
return ino, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (m watchMap) get(ino *inode) *watch {
|
|
||||||
if i := m[ino.volume]; i != nil {
|
|
||||||
return i[ino.index]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (m watchMap) set(ino *inode, watch *watch) {
|
|
||||||
i := m[ino.volume]
|
|
||||||
if i == nil {
|
|
||||||
i = make(indexMap)
|
|
||||||
m[ino.volume] = i
|
|
||||||
}
|
|
||||||
i[ino.index] = watch
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (w *Watcher) addWatch(pathname string, flags uint64) error {
|
|
||||||
dir, err := getDir(pathname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if flags&sys_FS_ONLYDIR != 0 && pathname != dir {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ino, err := getIno(dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.mu.Lock()
|
|
||||||
watchEntry := w.watches.get(ino)
|
|
||||||
w.mu.Unlock()
|
|
||||||
if watchEntry == nil {
|
|
||||||
if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
|
|
||||||
syscall.CloseHandle(ino.handle)
|
|
||||||
return os.NewSyscallError("CreateIoCompletionPort", e)
|
|
||||||
}
|
|
||||||
watchEntry = &watch{
|
|
||||||
ino: ino,
|
|
||||||
path: dir,
|
|
||||||
names: make(map[string]uint64),
|
|
||||||
}
|
|
||||||
w.mu.Lock()
|
|
||||||
w.watches.set(ino, watchEntry)
|
|
||||||
w.mu.Unlock()
|
|
||||||
flags |= provisional
|
|
||||||
} else {
|
|
||||||
syscall.CloseHandle(ino.handle)
|
|
||||||
}
|
|
||||||
if pathname == dir {
|
|
||||||
watchEntry.mask |= flags
|
|
||||||
} else {
|
|
||||||
watchEntry.names[filepath.Base(pathname)] |= flags
|
|
||||||
}
|
|
||||||
if err = w.startRead(watchEntry); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if pathname == dir {
|
|
||||||
watchEntry.mask &= ^provisional
|
|
||||||
} else {
|
|
||||||
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (w *Watcher) remWatch(pathname string) error {
|
|
||||||
dir, err := getDir(pathname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ino, err := getIno(dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.mu.Lock()
|
|
||||||
watch := w.watches.get(ino)
|
|
||||||
w.mu.Unlock()
|
|
||||||
if watch == nil {
|
|
||||||
return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
|
|
||||||
}
|
|
||||||
if pathname == dir {
|
|
||||||
w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED)
|
|
||||||
watch.mask = 0
|
|
||||||
} else {
|
|
||||||
name := filepath.Base(pathname)
|
|
||||||
w.sendEvent(watch.path+"\\"+name, watch.names[name]&sys_FS_IGNORED)
|
|
||||||
delete(watch.names, name)
|
|
||||||
}
|
|
||||||
return w.startRead(watch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (w *Watcher) deleteWatch(watch *watch) {
|
|
||||||
for name, mask := range watch.names {
|
|
||||||
if mask&provisional == 0 {
|
|
||||||
w.sendEvent(watch.path+"\\"+name, mask&sys_FS_IGNORED)
|
|
||||||
}
|
|
||||||
delete(watch.names, name)
|
|
||||||
}
|
|
||||||
if watch.mask != 0 {
|
|
||||||
if watch.mask&provisional == 0 {
|
|
||||||
w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED)
|
|
||||||
}
|
|
||||||
watch.mask = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (w *Watcher) startRead(watch *watch) error {
|
|
||||||
if e := syscall.CancelIo(watch.ino.handle); e != nil {
|
|
||||||
w.Error <- os.NewSyscallError("CancelIo", e)
|
|
||||||
w.deleteWatch(watch)
|
|
||||||
}
|
|
||||||
mask := toWindowsFlags(watch.mask)
|
|
||||||
for _, m := range watch.names {
|
|
||||||
mask |= toWindowsFlags(m)
|
|
||||||
}
|
|
||||||
if mask == 0 {
|
|
||||||
if e := syscall.CloseHandle(watch.ino.handle); e != nil {
|
|
||||||
w.Error <- os.NewSyscallError("CloseHandle", e)
|
|
||||||
}
|
|
||||||
w.mu.Lock()
|
|
||||||
delete(w.watches[watch.ino.volume], watch.ino.index)
|
|
||||||
w.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
|
|
||||||
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
|
|
||||||
if e != nil {
|
|
||||||
err := os.NewSyscallError("ReadDirectoryChanges", e)
|
|
||||||
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
|
||||||
// Watched directory was probably removed
|
|
||||||
if w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF) {
|
|
||||||
if watch.mask&sys_FS_ONESHOT != 0 {
|
|
||||||
watch.mask = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
w.deleteWatch(watch)
|
|
||||||
w.startRead(watch)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readEvents reads from the I/O completion port, converts the
|
|
||||||
// received events into Event objects and sends them via the Event channel.
|
|
||||||
// Entry point to the I/O thread.
|
|
||||||
func (w *Watcher) readEvents() {
|
|
||||||
var (
|
|
||||||
n, key uint32
|
|
||||||
ov *syscall.Overlapped
|
|
||||||
)
|
|
||||||
runtime.LockOSThread()
|
|
||||||
|
|
||||||
for {
|
|
||||||
e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
|
|
||||||
watch := (*watch)(unsafe.Pointer(ov))
|
|
||||||
|
|
||||||
if watch == nil {
|
|
||||||
select {
|
|
||||||
case ch := <-w.quit:
|
|
||||||
w.mu.Lock()
|
|
||||||
var indexes []indexMap
|
|
||||||
for _, index := range w.watches {
|
|
||||||
indexes = append(indexes, index)
|
|
||||||
}
|
|
||||||
w.mu.Unlock()
|
|
||||||
for _, index := range indexes {
|
|
||||||
for _, watch := range index {
|
|
||||||
w.deleteWatch(watch)
|
|
||||||
w.startRead(watch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
if e := syscall.CloseHandle(w.port); e != nil {
|
|
||||||
err = os.NewSyscallError("CloseHandle", e)
|
|
||||||
}
|
|
||||||
close(w.internalEvent)
|
|
||||||
close(w.Error)
|
|
||||||
ch <- err
|
|
||||||
return
|
|
||||||
case in := <-w.input:
|
|
||||||
switch in.op {
|
|
||||||
case opAddWatch:
|
|
||||||
in.reply <- w.addWatch(in.path, uint64(in.flags))
|
|
||||||
case opRemoveWatch:
|
|
||||||
in.reply <- w.remWatch(in.path)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch e {
|
|
||||||
case sys_ERROR_MORE_DATA:
|
|
||||||
if watch == nil {
|
|
||||||
w.Error <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
|
|
||||||
} else {
|
|
||||||
// The i/o succeeded but the buffer is full.
|
|
||||||
// In theory we should be building up a full packet.
|
|
||||||
// In practice we can get away with just carrying on.
|
|
||||||
n = uint32(unsafe.Sizeof(watch.buf))
|
|
||||||
}
|
|
||||||
case syscall.ERROR_ACCESS_DENIED:
|
|
||||||
// Watched directory was probably removed
|
|
||||||
w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF)
|
|
||||||
w.deleteWatch(watch)
|
|
||||||
w.startRead(watch)
|
|
||||||
continue
|
|
||||||
case syscall.ERROR_OPERATION_ABORTED:
|
|
||||||
// CancelIo was called on this handle
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e)
|
|
||||||
continue
|
|
||||||
case nil:
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset uint32
|
|
||||||
for {
|
|
||||||
if n == 0 {
|
|
||||||
w.internalEvent <- &FileEvent{mask: sys_FS_Q_OVERFLOW}
|
|
||||||
w.Error <- errors.New("short read in readEvents()")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Point "raw" to the event in the buffer
|
|
||||||
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
|
||||||
buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
|
|
||||||
name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
|
|
||||||
fullname := watch.path + "\\" + name
|
|
||||||
|
|
||||||
var mask uint64
|
|
||||||
switch raw.Action {
|
|
||||||
case syscall.FILE_ACTION_REMOVED:
|
|
||||||
mask = sys_FS_DELETE_SELF
|
|
||||||
case syscall.FILE_ACTION_MODIFIED:
|
|
||||||
mask = sys_FS_MODIFY
|
|
||||||
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
|
||||||
watch.rename = name
|
|
||||||
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
|
||||||
if watch.names[watch.rename] != 0 {
|
|
||||||
watch.names[name] |= watch.names[watch.rename]
|
|
||||||
delete(watch.names, watch.rename)
|
|
||||||
mask = sys_FS_MOVE_SELF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendNameEvent := func() {
|
|
||||||
if w.sendEvent(fullname, watch.names[name]&mask) {
|
|
||||||
if watch.names[name]&sys_FS_ONESHOT != 0 {
|
|
||||||
delete(watch.names, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
|
||||||
sendNameEvent()
|
|
||||||
}
|
|
||||||
if raw.Action == syscall.FILE_ACTION_REMOVED {
|
|
||||||
w.sendEvent(fullname, watch.names[name]&sys_FS_IGNORED)
|
|
||||||
delete(watch.names, name)
|
|
||||||
}
|
|
||||||
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
|
|
||||||
if watch.mask&sys_FS_ONESHOT != 0 {
|
|
||||||
watch.mask = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
|
||||||
fullname = watch.path + "\\" + watch.rename
|
|
||||||
sendNameEvent()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to the next event in the buffer
|
|
||||||
if raw.NextEntryOffset == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
offset += raw.NextEntryOffset
|
|
||||||
|
|
||||||
// Error!
|
|
||||||
if offset >= n {
|
|
||||||
w.Error <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := w.startRead(watch); err != nil {
|
|
||||||
w.Error <- err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
|
||||||
if mask == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
event := &FileEvent{mask: uint32(mask), Name: name}
|
|
||||||
if mask&sys_FS_MOVE != 0 {
|
|
||||||
if mask&sys_FS_MOVED_FROM != 0 {
|
|
||||||
w.cookie++
|
|
||||||
}
|
|
||||||
event.cookie = w.cookie
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case ch := <-w.quit:
|
|
||||||
w.quit <- ch
|
|
||||||
case w.Event <- event:
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func toWindowsFlags(mask uint64) uint32 {
|
|
||||||
var m uint32
|
|
||||||
if mask&sys_FS_ACCESS != 0 {
|
|
||||||
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
|
|
||||||
}
|
|
||||||
if mask&sys_FS_MODIFY != 0 {
|
|
||||||
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
|
|
||||||
}
|
|
||||||
if mask&sys_FS_ATTRIB != 0 {
|
|
||||||
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
|
||||||
}
|
|
||||||
if mask&(sys_FS_MOVE|sys_FS_CREATE|sys_FS_DELETE) != 0 {
|
|
||||||
m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func toFSnotifyFlags(action uint32) uint64 {
|
|
||||||
switch action {
|
|
||||||
case syscall.FILE_ACTION_ADDED:
|
|
||||||
return sys_FS_CREATE
|
|
||||||
case syscall.FILE_ACTION_REMOVED:
|
|
||||||
return sys_FS_DELETE
|
|
||||||
case syscall.FILE_ACTION_MODIFIED:
|
|
||||||
return sys_FS_MODIFY
|
|
||||||
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
|
||||||
return sys_FS_MOVED_FROM
|
|
||||||
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
|
||||||
return sys_FS_MOVED_TO
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
|
@ -10,8 +10,6 @@ github.com/golang/protobuf/proto
|
||||||
# github.com/hashicorp/golang-lru v0.5.1
|
# github.com/hashicorp/golang-lru v0.5.1
|
||||||
github.com/hashicorp/golang-lru
|
github.com/hashicorp/golang-lru
|
||||||
github.com/hashicorp/golang-lru/simplelru
|
github.com/hashicorp/golang-lru/simplelru
|
||||||
# github.com/howeyc/fsnotify v0.0.0-20151003194602-f0c08ee9c607
|
|
||||||
github.com/howeyc/fsnotify
|
|
||||||
# github.com/konsorten/go-windows-terminal-sequences v1.0.1
|
# github.com/konsorten/go-windows-terminal-sequences v1.0.1
|
||||||
github.com/konsorten/go-windows-terminal-sequences
|
github.com/konsorten/go-windows-terminal-sequences
|
||||||
# github.com/matttproud/golang_protobuf_extensions v1.0.1
|
# github.com/matttproud/golang_protobuf_extensions v1.0.1
|
||||||
|
|
Loading…
Reference in a new issue