ソースを参照

Add github.com/go-fsnotify/fsnotify
Docker-DCO-1.1-Signed-off-by: Tianon Gravi <admwiggin@gmail.com>

Tianon Gravi 10 年 前
コミット
edbf1ed680

+ 2 - 0
project/vendor.sh

@@ -55,6 +55,8 @@ clone git github.com/docker/libtrust 230dfd18c232
 
 clone git github.com/Sirupsen/logrus v0.6.0
 
+clone git github.com/go-fsnotify/fsnotify v1.0.4
+
 # get Go tip's archive/tar, for xattr support and improved performance
 # TODO after Go 1.4 drops, bump our minimum supported version and drop this vendored dep
 if [ "$1" = '--go' ]; then

+ 6 - 0
vendor/src/github.com/go-fsnotify/fsnotify/.gitignore

@@ -0,0 +1,6 @@
+# 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
+*.sublime-project

+ 13 - 0
vendor/src/github.com/go-fsnotify/fsnotify/.travis.yml

@@ -0,0 +1,13 @@
+language: go
+
+go:
+  - 1.2
+  - tip
+
+# not yet https://github.com/travis-ci/travis-ci/issues/2318
+os:
+  - linux
+  - osx
+
+notifications:
+  email: false

+ 32 - 0
vendor/src/github.com/go-fsnotify/fsnotify/AUTHORS

@@ -0,0 +1,32 @@
+# 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>
+Hari haran <hariharan.uno@gmail.com>
+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>
+Soge Zhang <zhssoge@gmail.com>
+Tilak Sharma <tilaks@google.com>
+Travis Cline <travis.cline@gmail.com>
+Tudor Golubenco <tudor.g@gmail.com>
+Yukang <moorekang@gmail.com>
+bronze1man <bronze1man@gmail.com>
+debrando <denis.brandolini@gmail.com>
+henrikedwards <henrik.edwards@gmail.com>

+ 237 - 0
vendor/src/github.com/go-fsnotify/fsnotify/CHANGELOG.md

@@ -0,0 +1,237 @@
+# Changelog
+
+## v1.0.4 / 2014-09-07
+
+* kqueue: add dragonfly to the build tags.
+* Rename source code files, rearrange code so exported APIs are at the top.
+* Add done channel to example code. [#37](https://github.com/go-fsnotify/fsnotify/pull/37) (thanks @chenyukang)
+
+## v1.0.3 / 2014-08-19
+
+* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/go-fsnotify/fsnotify/issues/36)
+
+## v1.0.2 / 2014-08-17
+
+* [Fix] Missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso)
+* [Fix] Make ./path and path equivalent. (thanks @zhsso)
+
+## v1.0.0 / 2014-08-15
+
+* [API] Remove AddWatch on Windows, use Add.
+* Improve documentation for exported identifiers. [#30](https://github.com/go-fsnotify/fsnotify/issues/30)
+* Minor updates based on feedback from golint.
+
+## dev / 2014-07-09
+
+* Moved to [github.com/go-fsnotify/fsnotify](https://github.com/go-fsnotify/fsnotify).
+* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
+ 
+## dev / 2014-07-04
+
+* kqueue: fix incorrect mutex used in Close()
+* Update example to demonstrate usage of Op.
+
+## dev / 2014-06-28
+
+* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/go-fsnotify/fsnotify/issues/4)
+* Fix for String() method on Event (thanks Alex Brainman)
+* Don't build on Plan 9 or Solaris (thanks @4ad)
+
+## dev / 2014-06-21
+
+* Events channel of type Event rather than *Event.
+* [internal] use syscall constants directly for inotify and kqueue.
+* [internal] kqueue: rename events to kevents and fileEvent to event.
+
+## dev / 2014-06-19
+
+* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
+* [internal] remove cookie from Event struct (unused).
+* [internal] Event struct has the same definition across every OS.
+* [internal] remove internal watch and removeWatch methods.
+
+## dev / 2014-06-12
+
+* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
+* [API] Pluralized channel names: Events and Errors.
+* [API] Renamed FileEvent struct to Event.
+* [API] Op constants replace methods like IsCreate().
+
+## dev / 2014-06-12
+
+* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
+
+## dev / 2014-05-23
+
+* [API] Remove current implementation of WatchFlags.
+    * current implementation doesn't take advantage of OS for efficiency
+    * provides little benefit over filtering events as they are received, but has  extra bookkeeping and mutexes
+    * no tests for the current implementation
+    * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
+
+## 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)
+
+## v0.9.1 / 2014-06-12
+
+* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
+
+## 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
+

+ 56 - 0
vendor/src/github.com/go-fsnotify/fsnotify/CONTRIBUTING.md

@@ -0,0 +1,56 @@
+# Contributing
+
+* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com). 
+
+### 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.
+
+### 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).
+
+Please indicate that you have signed the CLA in your pull request.
+
+To hack on fsnotify:
+
+1. Install as usual (`go get -u github.com/go-fsnotify/fsnotify`)
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Ensure everything works and the tests pass (see below)
+4. Commit your changes (`git commit -am 'Add some feature'`)
+
+Contribute upstream:
+
+1. Fork fsnotify on GitHub
+2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
+3. Push to the branch (`git push fork my-new-feature`)
+4. Create a new Pull Request on GitHub
+
+If other team members need your patch before I merge it:
+
+1. Install as usual (`go get -u github.com/go-fsnotify/fsnotify`)
+2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
+3. Pull your revisions (`git fetch fork; git checkout -b my-new-feature fork/my-new-feature`)
+
+Notice: For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
+
+Note: The maintainers will update the CHANGELOG on your behalf. Please don't modify it in your pull request.
+
+### Testing
+
+fsnotify uses build tags to compile different code on Linux, BSD, OS X, and Windows.
+
+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.
+
+* 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.
+* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
+* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd go-fsnotify/fsnotify; go test'`.
+* When you're done, you will want to halt or destroy the Vagrant boxes.
+
+Notice: fsnotify file system events don't work on shared folders. The tests get around this limitation by using a tmp directory, but it is something to be aware of.
+
+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).

+ 28 - 0
vendor/src/github.com/go-fsnotify/fsnotify/LICENSE

@@ -0,0 +1,28 @@
+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.

+ 53 - 0
vendor/src/github.com/go-fsnotify/fsnotify/README.md

@@ -0,0 +1,53 @@
+# File system notifications for Go
+
+[![Coverage](http://gocover.io/_badge/github.com/go-fsnotify/fsnotify)](http://gocover.io/github.com/go-fsnotify/fsnotify) [![GoDoc](https://godoc.org/gopkg.in/fsnotify.v1?status.svg)](https://godoc.org/gopkg.in/fsnotify.v1)
+
+Cross platform: Windows, Linux, BSD and OS X.
+
+|Adapter   |OS        |Status    |
+|----------|----------|----------|
+|inotify   |Linux, Android\*|Supported|
+|kqueue    |BSD, OS X, iOS\*|Supported|
+|ReadDirectoryChangesW|Windows|Supported|
+|FSEvents  |OS X          |[Planned](https://github.com/go-fsnotify/fsnotify/issues/11)|
+|FEN       |Solaris 11    |[Planned](https://github.com/go-fsnotify/fsnotify/issues/12)|
+|fanotify  |Linux 2.6.37+ | |
+|Polling   |*All*         |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/9)|
+|          |Plan 9        | |
+
+\* Android and iOS are untested.
+
+Please see [the documentation](https://godoc.org/gopkg.in/fsnotify.v1) for usage. Consult the [Wiki](https://github.com/go-fsnotify/fsnotify/wiki) for the FAQ and further information.
+
+## API stability
+
+Two major versions of fsnotify exist. 
+
+**[fsnotify.v1](https://gopkg.in/fsnotify.v1)** provides [a new API](https://godoc.org/gopkg.in/fsnotify.v1) based on [this design document](http://goo.gl/MrYxyA). You can import v1 with:
+
+```go
+import "gopkg.in/fsnotify.v1"
+```
+
+\* Refer to the package as fsnotify (without the .v1 suffix).
+
+**[fsnotify.v0](https://gopkg.in/fsnotify.v0)** is API-compatible with [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify). Bugfixes *may* be backported, but I recommend upgrading to v1.
+
+```go
+import "gopkg.in/fsnotify.v0"
+```
+
+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.
+
+## Contributing
+
+* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com). 
+* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues).
+
+A future version of Go will have [fsnotify in the standard library](https://code.google.com/p/go/issues/detail?id=4068), therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so we need you to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
+
+Please read [CONTRIBUTING](https://github.com/go-fsnotify/fsnotify/blob/master/CONTRIBUTING.md) before opening a pull request.
+
+## Example
+
+See [example_test.go](https://github.com/go-fsnotify/fsnotify/blob/master/example_test.go).

+ 42 - 0
vendor/src/github.com/go-fsnotify/fsnotify/example_test.go

@@ -0,0 +1,42 @@
+// 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.
+
+// +build !plan9,!solaris
+
+package fsnotify_test
+
+import (
+	"log"
+
+	"gopkg.in/fsnotify.v1"
+)
+
+func ExampleNewWatcher() {
+	watcher, err := fsnotify.NewWatcher()
+	if err != nil {
+		log.Fatal(err)
+	}
+	defer watcher.Close()
+
+	done := make(chan bool)
+	go func() {
+		for {
+			select {
+			case event := <-watcher.Events:
+				log.Println("event:", event)
+				if event.Op&fsnotify.Write == fsnotify.Write {
+					log.Println("modified file:", event.Name)
+				}
+			case err := <-watcher.Errors:
+				log.Println("error:", err)
+			}
+		}
+	}()
+
+	err = watcher.Add("/tmp/foo")
+	if err != nil {
+		log.Fatal(err)
+	}
+	<-done
+}

+ 56 - 0
vendor/src/github.com/go-fsnotify/fsnotify/fsnotify.go

@@ -0,0 +1,56 @@
+// 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.
+
+// +build !plan9,!solaris
+
+// Package fsnotify provides a platform-independent interface for file system notifications.
+package fsnotify
+
+import "fmt"
+
+// Event represents a single file system notification.
+type Event struct {
+	Name string // Relative path to the file or directory.
+	Op   Op     // File operation that triggered the event.
+}
+
+// Op describes a set of file operations.
+type Op uint32
+
+// These are the generalized file operations that can trigger a notification.
+const (
+	Create Op = 1 << iota
+	Write
+	Remove
+	Rename
+	Chmod
+)
+
+// String returns a string representation of the event in the form
+// "file: REMOVE|WRITE|..."
+func (e Event) String() string {
+	events := ""
+
+	if e.Op&Create == Create {
+		events += "|CREATE"
+	}
+	if e.Op&Remove == Remove {
+		events += "|REMOVE"
+	}
+	if e.Op&Write == Write {
+		events += "|WRITE"
+	}
+	if e.Op&Rename == Rename {
+		events += "|RENAME"
+	}
+	if e.Op&Chmod == Chmod {
+		events += "|CHMOD"
+	}
+
+	if len(events) > 0 {
+		events = events[1:]
+	}
+
+	return fmt.Sprintf("%q: %s", e.Name, events)
+}

+ 239 - 0
vendor/src/github.com/go-fsnotify/fsnotify/inotify.go

@@ -0,0 +1,239 @@
+// 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"
+	"path/filepath"
+	"strings"
+	"sync"
+	"syscall"
+	"unsafe"
+)
+
+// Watcher watches a set of files, delivering events to a channel.
+type Watcher struct {
+	Events   chan Event
+	Errors   chan error
+	mu       sync.Mutex        // Map access
+	fd       int               // File descriptor (as returned by the inotify_init() syscall)
+	watches  map[string]*watch // Map of inotify watches (key: path)
+	paths    map[int]string    // Map of watched paths (key: watch descriptor)
+	done     chan bool         // Channel for sending a "quit message" to the reader goroutine
+	isClosed bool              // Set to true when Close() is first called
+}
+
+// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
+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),
+		paths:   make(map[int]string),
+		Events:  make(chan Event),
+		Errors:  make(chan error),
+		done:    make(chan bool, 1),
+	}
+
+	go w.readEvents()
+	return w, nil
+}
+
+// Close removes all watches and closes the events channel.
+func (w *Watcher) Close() error {
+	if w.isClosed {
+		return nil
+	}
+	w.isClosed = true
+
+	// Remove all watches
+	for name := range w.watches {
+		w.Remove(name)
+	}
+
+	// Send "quit" message to the reader goroutine
+	w.done <- true
+
+	return nil
+}
+
+// Add starts watching the named file or directory (non-recursively).
+func (w *Watcher) Add(name string) error {
+	name = filepath.Clean(name)
+	if w.isClosed {
+		return errors.New("inotify instance already closed")
+	}
+
+	const agnosticEvents = syscall.IN_MOVED_TO | syscall.IN_MOVED_FROM |
+		syscall.IN_CREATE | syscall.IN_ATTRIB | syscall.IN_MODIFY |
+		syscall.IN_MOVE_SELF | syscall.IN_DELETE | syscall.IN_DELETE_SELF
+
+	var flags uint32 = agnosticEvents
+
+	w.mu.Lock()
+	watchEntry, found := w.watches[name]
+	w.mu.Unlock()
+	if found {
+		watchEntry.flags |= flags
+		flags |= syscall.IN_MASK_ADD
+	}
+	wd, errno := syscall.InotifyAddWatch(w.fd, name, flags)
+	if wd == -1 {
+		return os.NewSyscallError("inotify_add_watch", errno)
+	}
+
+	w.mu.Lock()
+	w.watches[name] = &watch{wd: uint32(wd), flags: flags}
+	w.paths[wd] = name
+	w.mu.Unlock()
+
+	return nil
+}
+
+// Remove stops watching the the named file or directory (non-recursively).
+func (w *Watcher) Remove(name string) error {
+	name = filepath.Clean(name)
+	w.mu.Lock()
+	defer w.mu.Unlock()
+	watch, ok := w.watches[name]
+	if !ok {
+		return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
+	}
+	success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
+	if success == -1 {
+		return os.NewSyscallError("inotify_rm_watch", errno)
+	}
+	delete(w.watches, name)
+	return nil
+}
+
+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)
+}
+
+// readEvents reads from the inotify file descriptor, converts the
+// received events into Event objects and sends them via the Events 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.Events)
+			close(w.Errors)
+			return
+		default:
+		}
+
+		n, errno = syscall.Read(w.fd, buf[:])
+
+		// If EOF is received
+		if n == 0 {
+			syscall.Close(w.fd)
+			close(w.Events)
+			close(w.Errors)
+			return
+		}
+
+		if n < 0 {
+			w.Errors <- os.NewSyscallError("read", errno)
+			continue
+		}
+		if n < syscall.SizeofInotifyEvent {
+			w.Errors <- errors.New("inotify: short read in readEvents()")
+			continue
+		}
+
+		var offset uint32
+		// 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]))
+
+			mask := uint32(raw.Mask)
+			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()
+			name := w.paths[int(raw.Wd)]
+			w.mu.Unlock()
+			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 NULL bytes. TrimRight() gets rid of those.
+				name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
+			}
+
+			event := newEvent(name, mask)
+
+			// Send the events that are not ignored on the events channel
+			if !event.ignoreLinux(mask) {
+				w.Events <- 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 Events
+// channel. Such as events marked ignore by the kernel, or MODIFY events
+// against files that do not exist.
+func (e *Event) ignoreLinux(mask uint32) bool {
+	// Ignore anything the inotify API says to ignore
+	if mask&syscall.IN_IGNORED == syscall.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.Op&Remove == Remove || e.Op&Rename == Rename) {
+		_, statErr := os.Lstat(e.Name)
+		return os.IsNotExist(statErr)
+	}
+	return false
+}
+
+// newEvent returns an platform-independent Event based on an inotify mask.
+func newEvent(name string, mask uint32) Event {
+	e := Event{Name: name}
+	if mask&syscall.IN_CREATE == syscall.IN_CREATE || mask&syscall.IN_MOVED_TO == syscall.IN_MOVED_TO {
+		e.Op |= Create
+	}
+	if mask&syscall.IN_DELETE_SELF == syscall.IN_DELETE_SELF || mask&syscall.IN_DELETE == syscall.IN_DELETE {
+		e.Op |= Remove
+	}
+	if mask&syscall.IN_MODIFY == syscall.IN_MODIFY {
+		e.Op |= Write
+	}
+	if mask&syscall.IN_MOVE_SELF == syscall.IN_MOVE_SELF || mask&syscall.IN_MOVED_FROM == syscall.IN_MOVED_FROM {
+		e.Op |= Rename
+	}
+	if mask&syscall.IN_ATTRIB == syscall.IN_ATTRIB {
+		e.Op |= Chmod
+	}
+	return e
+}

+ 1120 - 0
vendor/src/github.com/go-fsnotify/fsnotify/integration_test.go

@@ -0,0 +1,1120 @@
+// 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 !plan9,!solaris
+
+package fsnotify
+
+import (
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"sync/atomic"
+	"testing"
+	"time"
+)
+
+// An atomic counter
+type counter struct {
+	val int32
+}
+
+func (c *counter) increment() {
+	atomic.AddInt32(&c.val, 1)
+}
+
+func (c *counter) value() int32 {
+	return atomic.LoadInt32(&c.val)
+}
+
+func (c *counter) reset() {
+	atomic.StoreInt32(&c.val, 0)
+}
+
+// tempMkdir makes a temporary directory
+func tempMkdir(t *testing.T) string {
+	dir, err := ioutil.TempDir("", "fsnotify")
+	if err != nil {
+		t.Fatalf("failed to create test directory: %s", err)
+	}
+	return dir
+}
+
+// newWatcher initializes an fsnotify Watcher instance.
+func newWatcher(t *testing.T) *Watcher {
+	watcher, err := NewWatcher()
+	if err != nil {
+		t.Fatalf("NewWatcher() failed: %s", err)
+	}
+	return watcher
+}
+
+// addWatch adds a watch for a directory
+func addWatch(t *testing.T, watcher *Watcher, dir string) {
+	if err := watcher.Add(dir); err != nil {
+		t.Fatalf("watcher.Add(%q) failed: %s", dir, err)
+	}
+}
+
+func TestFsnotifyMultipleOperations(t *testing.T) {
+	watcher := newWatcher(t)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create directory that's not watched
+	testDirToMoveFiles := tempMkdir(t)
+	defer os.RemoveAll(testDirToMoveFiles)
+
+	testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile")
+	testFileRenamed := filepath.Join(testDirToMoveFiles, "TestFsnotifySeqRename.testfile")
+
+	addWatch(t, watcher, testDir)
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var createReceived, modifyReceived, deleteReceived, renameReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
+				t.Logf("event received: %s", event)
+				if event.Op&Remove == Remove {
+					deleteReceived.increment()
+				}
+				if event.Op&Write == Write {
+					modifyReceived.increment()
+				}
+				if event.Op&Create == Create {
+					createReceived.increment()
+				}
+				if event.Op&Rename == Rename {
+					renameReceived.increment()
+				}
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	time.Sleep(time.Millisecond)
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	if err := testRename(testFile, testFileRenamed); err != nil {
+		t.Fatalf("rename failed: %s", err)
+	}
+
+	// Modify the file outside of the watched dir
+	f, err = os.Open(testFileRenamed)
+	if err != nil {
+		t.Fatalf("open test renamed file failed: %s", err)
+	}
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	// Recreate the file that was moved
+	f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Close()
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	cReceived := createReceived.value()
+	if cReceived != 2 {
+		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
+	}
+	mReceived := modifyReceived.value()
+	if mReceived != 1 {
+		t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1)
+	}
+	dReceived := deleteReceived.value()
+	rReceived := renameReceived.value()
+	if dReceived+rReceived != 1 {
+		t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", rReceived+dReceived, 1)
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+}
+
+func TestFsnotifyMultipleCreates(t *testing.T) {
+	watcher := newWatcher(t)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile")
+
+	addWatch(t, watcher, testDir)
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var createReceived, modifyReceived, deleteReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
+				t.Logf("event received: %s", event)
+				if event.Op&Remove == Remove {
+					deleteReceived.increment()
+				}
+				if event.Op&Create == Create {
+					createReceived.increment()
+				}
+				if event.Op&Write == Write {
+					modifyReceived.increment()
+				}
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	time.Sleep(time.Millisecond)
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	os.Remove(testFile)
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	// Recreate the file
+	f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Close()
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	// Modify
+	f, err = os.OpenFile(testFile, os.O_WRONLY, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	time.Sleep(time.Millisecond)
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	// Modify
+	f, err = os.OpenFile(testFile, os.O_WRONLY, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	time.Sleep(time.Millisecond)
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	cReceived := createReceived.value()
+	if cReceived != 2 {
+		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
+	}
+	mReceived := modifyReceived.value()
+	if mReceived < 3 {
+		t.Fatalf("incorrect number of modify events received after 500 ms (%d vs atleast %d)", mReceived, 3)
+	}
+	dReceived := deleteReceived.value()
+	if dReceived != 1 {
+		t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", dReceived, 1)
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+}
+
+func TestFsnotifyDirOnly(t *testing.T) {
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create a file before watching directory
+	// This should NOT add any events to the fsnotify event queue
+	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
+	{
+		var f *os.File
+		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
+		if err != nil {
+			t.Fatalf("creating test file failed: %s", err)
+		}
+		f.Sync()
+		f.Close()
+	}
+
+	addWatch(t, watcher, testDir)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	testFile := filepath.Join(testDir, "TestFsnotifyDirOnly.testfile")
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var createReceived, modifyReceived, deleteReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileAlreadyExists) {
+				t.Logf("event received: %s", event)
+				if event.Op&Remove == Remove {
+					deleteReceived.increment()
+				}
+				if event.Op&Write == Write {
+					modifyReceived.increment()
+				}
+				if event.Op&Create == Create {
+					createReceived.increment()
+				}
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	time.Sleep(time.Millisecond)
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
+
+	os.Remove(testFile)
+	os.Remove(testFileAlreadyExists)
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	cReceived := createReceived.value()
+	if cReceived != 1 {
+		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 1)
+	}
+	mReceived := modifyReceived.value()
+	if mReceived != 1 {
+		t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1)
+	}
+	dReceived := deleteReceived.value()
+	if dReceived != 2 {
+		t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2)
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+}
+
+func TestFsnotifyDeleteWatchedDir(t *testing.T) {
+	watcher := newWatcher(t)
+	defer watcher.Close()
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create a file before watching directory
+	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
+	{
+		var f *os.File
+		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
+		if err != nil {
+			t.Fatalf("creating test file failed: %s", err)
+		}
+		f.Sync()
+		f.Close()
+	}
+
+	addWatch(t, watcher, testDir)
+
+	// Add a watch for testFile
+	addWatch(t, watcher, testFileAlreadyExists)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var deleteReceived counter
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFileAlreadyExists) {
+				t.Logf("event received: %s", event)
+				if event.Op&Remove == Remove {
+					deleteReceived.increment()
+				}
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+	}()
+
+	os.RemoveAll(testDir)
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	dReceived := deleteReceived.value()
+	if dReceived < 2 {
+		t.Fatalf("did not receive at least %d delete events, received %d after 500 ms", 2, dReceived)
+	}
+}
+
+func TestFsnotifySubDir(t *testing.T) {
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	testFile1 := filepath.Join(testDir, "TestFsnotifyFile1.testfile")
+	testSubDir := filepath.Join(testDir, "sub")
+	testSubDirFile := filepath.Join(testDir, "sub/TestFsnotifyFile1.testfile")
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var createReceived, deleteReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testSubDir) || event.Name == filepath.Clean(testFile1) {
+				t.Logf("event received: %s", event)
+				if event.Op&Create == Create {
+					createReceived.increment()
+				}
+				if event.Op&Remove == Remove {
+					deleteReceived.increment()
+				}
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	addWatch(t, watcher, testDir)
+
+	// Create sub-directory
+	if err := os.Mkdir(testSubDir, 0777); err != nil {
+		t.Fatalf("failed to create test sub-directory: %s", err)
+	}
+
+	// Create a file
+	var f *os.File
+	f, err := os.OpenFile(testFile1, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+	f.Close()
+
+	// Create a file (Should not see this! we are not watching subdir)
+	var fs *os.File
+	fs, err = os.OpenFile(testSubDirFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	fs.Sync()
+	fs.Close()
+
+	time.Sleep(200 * time.Millisecond)
+
+	// Make sure receive deletes for both file and sub-directory
+	os.RemoveAll(testSubDir)
+	os.Remove(testFile1)
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	cReceived := createReceived.value()
+	if cReceived != 2 {
+		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
+	}
+	dReceived := deleteReceived.value()
+	if dReceived != 2 {
+		t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2)
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+}
+
+func TestFsnotifyRename(t *testing.T) {
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	addWatch(t, watcher, testDir)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	testFile := filepath.Join(testDir, "TestFsnotifyEvents.testfile")
+	testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var renameReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) {
+				if event.Op&Rename == Rename {
+					renameReceived.increment()
+				}
+				t.Logf("event received: %s", event)
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	// Add a watch for testFile
+	addWatch(t, watcher, testFile)
+
+	if err := testRename(testFile, testFileRenamed); err != nil {
+		t.Fatalf("rename failed: %s", err)
+	}
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	if renameReceived.value() == 0 {
+		t.Fatal("fsnotify rename events have not been received after 500 ms")
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+
+	os.Remove(testFileRenamed)
+}
+
+func TestFsnotifyRenameToCreate(t *testing.T) {
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create directory to get file
+	testDirFrom := tempMkdir(t)
+	defer os.RemoveAll(testDirFrom)
+
+	addWatch(t, watcher, testDir)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile")
+	testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var createReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) {
+				if event.Op&Create == Create {
+					createReceived.increment()
+				}
+				t.Logf("event received: %s", event)
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+	f.Close()
+
+	if err := testRename(testFile, testFileRenamed); err != nil {
+		t.Fatalf("rename failed: %s", err)
+	}
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	if createReceived.value() == 0 {
+		t.Fatal("fsnotify create events have not been received after 500 ms")
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+
+	os.Remove(testFileRenamed)
+}
+
+func TestFsnotifyRenameToOverwrite(t *testing.T) {
+	switch runtime.GOOS {
+	case "plan9", "windows":
+		t.Skipf("skipping test on %q (os.Rename over existing file does not create event).", runtime.GOOS)
+	}
+
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create directory to get file
+	testDirFrom := tempMkdir(t)
+	defer os.RemoveAll(testDirFrom)
+
+	testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile")
+	testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
+
+	// Create a file
+	var fr *os.File
+	fr, err := os.OpenFile(testFileRenamed, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	fr.Sync()
+	fr.Close()
+
+	addWatch(t, watcher, testDir)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	var eventReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testFileRenamed) {
+				eventReceived.increment()
+				t.Logf("event received: %s", event)
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+	f.Close()
+
+	if err := testRename(testFile, testFileRenamed); err != nil {
+		t.Fatalf("rename failed: %s", err)
+	}
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+	if eventReceived.value() == 0 {
+		t.Fatal("fsnotify events have not been received after 500 ms")
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(2 * time.Second):
+		t.Fatal("event stream was not closed after 2 seconds")
+	}
+
+	os.Remove(testFileRenamed)
+}
+
+func TestRemovalOfWatch(t *testing.T) {
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create a file before watching directory
+	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
+	{
+		var f *os.File
+		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
+		if err != nil {
+			t.Fatalf("creating test file failed: %s", err)
+		}
+		f.Sync()
+		f.Close()
+	}
+
+	watcher := newWatcher(t)
+	defer watcher.Close()
+
+	addWatch(t, watcher, testDir)
+	if err := watcher.Remove(testDir); err != nil {
+		t.Fatalf("Could not remove the watch: %v\n", err)
+	}
+
+	go func() {
+		select {
+		case ev := <-watcher.Events:
+			t.Fatalf("We received event: %v\n", ev)
+		case <-time.After(500 * time.Millisecond):
+			t.Log("No event received, as expected.")
+		}
+	}()
+
+	time.Sleep(200 * time.Millisecond)
+	// Modify the file outside of the watched dir
+	f, err := os.Open(testFileAlreadyExists)
+	if err != nil {
+		t.Fatalf("Open test file failed: %s", err)
+	}
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+	if err := os.Chmod(testFileAlreadyExists, 0700); err != nil {
+		t.Fatalf("chmod failed: %s", err)
+	}
+	time.Sleep(400 * time.Millisecond)
+}
+
+func TestFsnotifyAttrib(t *testing.T) {
+	if runtime.GOOS == "windows" {
+		t.Skip("attributes don't work on Windows.")
+	}
+
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for err := range watcher.Errors {
+			t.Fatalf("error received: %s", err)
+		}
+	}()
+
+	testFile := filepath.Join(testDir, "TestFsnotifyAttrib.testfile")
+
+	// Receive events on the event channel on a separate goroutine
+	eventstream := watcher.Events
+	// The modifyReceived counter counts IsModify events that are not IsAttrib,
+	// and the attribReceived counts IsAttrib events (which are also IsModify as
+	// a consequence).
+	var modifyReceived counter
+	var attribReceived counter
+	done := make(chan bool)
+	go func() {
+		for event := range eventstream {
+			// Only count relevant events
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
+				if event.Op&Write == Write {
+					modifyReceived.increment()
+				}
+				if event.Op&Chmod == Chmod {
+					attribReceived.increment()
+				}
+				t.Logf("event received: %s", event)
+			} else {
+				t.Logf("unexpected event received: %s", event)
+			}
+		}
+		done <- true
+	}()
+
+	// Create a file
+	// This should add at least one event to the fsnotify event queue
+	var f *os.File
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
+	if err != nil {
+		t.Fatalf("creating test file failed: %s", err)
+	}
+	f.Sync()
+
+	f.WriteString("data")
+	f.Sync()
+	f.Close()
+
+	// Add a watch for testFile
+	addWatch(t, watcher, testFile)
+
+	if err := os.Chmod(testFile, 0700); err != nil {
+		t.Fatalf("chmod failed: %s", err)
+	}
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	// Creating/writing a file changes also the mtime, so IsAttrib should be set to true here
+	time.Sleep(500 * time.Millisecond)
+	if modifyReceived.value() != 0 {
+		t.Fatal("received an unexpected modify event when creating a test file")
+	}
+	if attribReceived.value() == 0 {
+		t.Fatal("fsnotify attribute events have not received after 500 ms")
+	}
+
+	// Modifying the contents of the file does not set the attrib flag (although eg. the mtime
+	// might have been modified).
+	modifyReceived.reset()
+	attribReceived.reset()
+
+	f, err = os.OpenFile(testFile, os.O_WRONLY, 0)
+	if err != nil {
+		t.Fatalf("reopening test file failed: %s", err)
+	}
+
+	f.WriteString("more data")
+	f.Sync()
+	f.Close()
+
+	time.Sleep(500 * time.Millisecond)
+
+	if modifyReceived.value() != 1 {
+		t.Fatal("didn't receive a modify event after changing test file contents")
+	}
+
+	if attribReceived.value() != 0 {
+		t.Fatal("did receive an unexpected attrib event after changing test file contents")
+	}
+
+	modifyReceived.reset()
+	attribReceived.reset()
+
+	// Doing a chmod on the file should trigger an event with the "attrib" flag set (the contents
+	// of the file are not changed though)
+	if err := os.Chmod(testFile, 0600); err != nil {
+		t.Fatalf("chmod failed: %s", err)
+	}
+
+	time.Sleep(500 * time.Millisecond)
+
+	if attribReceived.value() != 1 {
+		t.Fatal("didn't receive an attribute change after 500ms")
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+	t.Log("waiting for the event channel to become closed...")
+	select {
+	case <-done:
+		t.Log("event channel closed")
+	case <-time.After(1e9):
+		t.Fatal("event stream was not closed after 1 second")
+	}
+
+	os.Remove(testFile)
+}
+
+func TestFsnotifyClose(t *testing.T) {
+	watcher := newWatcher(t)
+	watcher.Close()
+
+	var done int32
+	go func() {
+		watcher.Close()
+		atomic.StoreInt32(&done, 1)
+	}()
+
+	time.Sleep(50e6) // 50 ms
+	if atomic.LoadInt32(&done) == 0 {
+		t.Fatal("double Close() test failed: second Close() call didn't return")
+	}
+
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	if err := watcher.Add(testDir); err == nil {
+		t.Fatal("expected error on Watch() after Close(), got nil")
+	}
+}
+
+func TestFsnotifyFakeSymlink(t *testing.T) {
+	if runtime.GOOS == "windows" {
+		t.Skip("symlinks don't work on Windows.")
+	}
+
+	watcher := newWatcher(t)
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	var errorsReceived counter
+	// Receive errors on the error channel on a separate goroutine
+	go func() {
+		for errors := range watcher.Errors {
+			t.Logf("Received error: %s", errors)
+			errorsReceived.increment()
+		}
+	}()
+
+	// Count the CREATE events received
+	var createEventsReceived, otherEventsReceived counter
+	go func() {
+		for ev := range watcher.Events {
+			t.Logf("event received: %s", ev)
+			if ev.Op&Create == Create {
+				createEventsReceived.increment()
+			} else {
+				otherEventsReceived.increment()
+			}
+		}
+	}()
+
+	addWatch(t, watcher, testDir)
+
+	if err := os.Symlink(filepath.Join(testDir, "zzz"), filepath.Join(testDir, "zzznew")); err != nil {
+		t.Fatalf("Failed to create bogus symlink: %s", err)
+	}
+	t.Logf("Created bogus symlink")
+
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
+	time.Sleep(500 * time.Millisecond)
+
+	// Should not be error, just no events for broken links (watching nothing)
+	if errorsReceived.value() > 0 {
+		t.Fatal("fsnotify errors have been received.")
+	}
+	if otherEventsReceived.value() > 0 {
+		t.Fatal("fsnotify other events received on the broken link")
+	}
+
+	// Except for 1 create event (for the link itself)
+	if createEventsReceived.value() == 0 {
+		t.Fatal("fsnotify create events were not received after 500 ms")
+	}
+	if createEventsReceived.value() > 1 {
+		t.Fatal("fsnotify more create events received than expected")
+	}
+
+	// Try closing the fsnotify instance
+	t.Log("calling Close()")
+	watcher.Close()
+}
+
+// TestConcurrentRemovalOfWatch tests that concurrent calls to RemoveWatch do not race.
+// See https://codereview.appspot.com/103300045/
+// go test -test.run=TestConcurrentRemovalOfWatch -test.cpu=1,1,1,1,1 -race
+func TestConcurrentRemovalOfWatch(t *testing.T) {
+	if runtime.GOOS != "darwin" {
+		t.Skip("regression test for race only present on darwin")
+	}
+
+	// Create directory to watch
+	testDir := tempMkdir(t)
+	defer os.RemoveAll(testDir)
+
+	// Create a file before watching directory
+	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
+	{
+		var f *os.File
+		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
+		if err != nil {
+			t.Fatalf("creating test file failed: %s", err)
+		}
+		f.Sync()
+		f.Close()
+	}
+
+	watcher := newWatcher(t)
+	defer watcher.Close()
+
+	addWatch(t, watcher, testDir)
+
+	// Test that RemoveWatch can be invoked concurrently, with no data races.
+	removed1 := make(chan struct{})
+	go func() {
+		defer close(removed1)
+		watcher.Remove(testDir)
+	}()
+	removed2 := make(chan struct{})
+	go func() {
+		close(removed2)
+		watcher.Remove(testDir)
+	}()
+	<-removed1
+	<-removed2
+}
+
+func testRename(file1, file2 string) error {
+	switch runtime.GOOS {
+	case "windows", "plan9":
+		return os.Rename(file1, file2)
+	default:
+		cmd := exec.Command("mv", file1, file2)
+		return cmd.Run()
+	}
+}

+ 479 - 0
vendor/src/github.com/go-fsnotify/fsnotify/kqueue.go

@@ -0,0 +1,479 @@
+// 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"
+)
+
+// Watcher watches a set of files, delivering events to a channel.
+type Watcher struct {
+	Events          chan Event
+	Errors          chan error
+	mu              sync.Mutex          // Mutex for the Watcher itself.
+	kq              int                 // File descriptor (as returned by the kqueue() syscall).
+	watches         map[string]int      // Map of watched file descriptors (key: path).
+	wmut            sync.Mutex          // Protects access to watches.
+	enFlags         map[string]uint32   // Map of watched files to evfilt note flags used in kqueue.
+	enmut           sync.Mutex          // Protects access to enFlags.
+	paths           map[int]string      // Map of watched paths (key: watch descriptor).
+	finfo           map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor).
+	pmut            sync.Mutex          // Protects access to paths and finfo.
+	fileExists      map[string]bool     // Keep track of if we know this file exists (to stop duplicate create events).
+	femut           sync.Mutex          // Protects access to fileExists.
+	externalWatches map[string]bool     // Map of watches added by user of the library.
+	ewmut           sync.Mutex          // Protects access to externalWatches.
+	done            chan bool           // Channel for sending a "quit message" to the reader goroutine
+	isClosed        bool                // Set to true when Close() is first called
+}
+
+// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
+func NewWatcher() (*Watcher, error) {
+	fd, errno := syscall.Kqueue()
+	if fd == -1 {
+		return nil, os.NewSyscallError("kqueue", errno)
+	}
+	w := &Watcher{
+		kq:              fd,
+		watches:         make(map[string]int),
+		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),
+		Events:          make(chan Event),
+		Errors:          make(chan error),
+		done:            make(chan bool, 1),
+	}
+
+	go w.readEvents()
+	return w, nil
+}
+
+// Close removes all watches and closes the events channel.
+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 name := range ws {
+		w.Remove(name)
+	}
+
+	return nil
+}
+
+// Add starts watching the named file or directory (non-recursively).
+func (w *Watcher) Add(name string) error {
+	w.ewmut.Lock()
+	w.externalWatches[name] = true
+	w.ewmut.Unlock()
+	return w.addWatch(name, noteAllEvents)
+}
+
+// Remove stops watching the the named file or directory (non-recursively).
+func (w *Watcher) Remove(name string) error {
+	name = filepath.Clean(name)
+	w.wmut.Lock()
+	watchfd, ok := w.watches[name]
+	w.wmut.Unlock()
+	if !ok {
+		return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
+	}
+	var kbuf [1]syscall.Kevent_t
+	watchEntry := &kbuf[0]
+	syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_DELETE)
+	entryFlags := watchEntry.Flags
+	success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil)
+	if success == -1 {
+		return os.NewSyscallError("kevent_rm_watch", errno)
+	} else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR {
+		return errors.New("kevent rm error")
+	}
+	syscall.Close(watchfd)
+	w.wmut.Lock()
+	delete(w.watches, name)
+	w.wmut.Unlock()
+	w.enmut.Lock()
+	delete(w.enFlags, name)
+	w.enmut.Unlock()
+	w.pmut.Lock()
+	delete(w.paths, watchfd)
+	fInfo := w.finfo[watchfd]
+	delete(w.finfo, watchfd)
+	w.pmut.Unlock()
+
+	// 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(name) {
+				w.ewmut.Lock()
+				if !w.externalWatches[wpath] {
+					pathsToRemove = append(pathsToRemove, wpath)
+				}
+				w.ewmut.Unlock()
+			}
+		}
+		w.pmut.Unlock()
+		for _, name := range pathsToRemove {
+			// Since these are internal, not much sense in propagating error
+			// to the user, as that will just confuse them with an error about
+			// a path they did not explicitly watch themselves.
+			w.Remove(name)
+		}
+	}
+
+	return nil
+}
+
+const (
+	// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
+	noteAllEvents = syscall.NOTE_DELETE | syscall.NOTE_WRITE | syscall.NOTE_ATTRIB | syscall.NOTE_RENAME
+
+	// Block for 100 ms on each call to kevent
+	keventWaitTime = 100e6
+)
+
+// 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 {
+	path = filepath.Clean(path)
+	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, openMode, 0700)
+		if fd == -1 {
+			return os.NewSyscallError("Open", 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&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 {
+		errdir := w.watchDirectoryFiles(path)
+		if errdir != nil {
+			return errdir
+		}
+	}
+	return nil
+}
+
+// readEvents reads from the kqueue file descriptor, converts the
+// received events into Event objects and sends them via the Events channel
+func (w *Watcher) readEvents() {
+	var (
+		keventbuf [10]syscall.Kevent_t // Event buffer
+		kevents   []syscall.Kevent_t   // Received events
+		twait     *syscall.Timespec    // Time to block waiting for events
+		n         int                  // Number of events returned from kevent
+		errno     error                // Syscall errno
+	)
+	kevents = keventbuf[0:0]
+	twait = new(syscall.Timespec)
+	*twait = syscall.NsecToTimespec(keventWaitTime)
+
+	for {
+		// See if there is a message on the "done" channel
+		var done bool
+		select {
+		case done = <-w.done:
+		default:
+		}
+
+		// If "done" message is received
+		if done {
+			errno := syscall.Close(w.kq)
+			if errno != nil {
+				w.Errors <- os.NewSyscallError("close", errno)
+			}
+			close(w.Events)
+			close(w.Errors)
+			return
+		}
+
+		// Get new events
+		if len(kevents) == 0 {
+			n, errno = syscall.Kevent(w.kq, nil, keventbuf[:], twait)
+
+			// EINTR is okay, basically the syscall was interrupted before
+			// timeout expired.
+			if errno != nil && errno != syscall.EINTR {
+				w.Errors <- os.NewSyscallError("kevent", errno)
+				continue
+			}
+
+			// Received some events
+			if n > 0 {
+				kevents = keventbuf[0:n]
+			}
+		}
+
+		// Flush the events we received to the Events channel
+		for len(kevents) > 0 {
+			watchEvent := &kevents[0]
+			mask := uint32(watchEvent.Fflags)
+			w.pmut.Lock()
+			name := w.paths[int(watchEvent.Ident)]
+			fileInfo := w.finfo[int(watchEvent.Ident)]
+			w.pmut.Unlock()
+
+			event := newEvent(name, mask, false)
+
+			if fileInfo != nil && fileInfo.IsDir() && !(event.Op&Remove == Remove) {
+				// Double check to make sure the directory exist. This can happen when
+				// 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(event.Name); os.IsNotExist(err) {
+					// mark is as delete event
+					event.Op |= Remove
+				}
+			}
+
+			if fileInfo != nil && fileInfo.IsDir() && event.Op&Write == Write && !(event.Op&Remove == Remove) {
+				w.sendDirectoryChangeEvents(event.Name)
+			} else {
+				// Send the event on the Events channel
+				w.Events <- event
+			}
+
+			// Move to next event
+			kevents = kevents[1:]
+
+			if event.Op&Rename == Rename {
+				w.Remove(event.Name)
+				w.femut.Lock()
+				delete(w.fileExists, event.Name)
+				w.femut.Unlock()
+			}
+			if event.Op&Remove == Remove {
+				w.Remove(event.Name)
+				w.femut.Lock()
+				delete(w.fileExists, event.Name)
+				w.femut.Unlock()
+
+				// Look for a file that may have overwritten this
+				// (ie mv f1 f2 will delete f2 then create f2)
+				fileDir, _ := filepath.Split(event.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)
+					}
+				}
+			}
+		}
+	}
+}
+
+// newEvent returns an platform-independent Event based on kqueue Fflags.
+func newEvent(name string, mask uint32, create bool) Event {
+	e := Event{Name: name}
+	if create {
+		e.Op |= Create
+	}
+	if mask&syscall.NOTE_DELETE == syscall.NOTE_DELETE {
+		e.Op |= Remove
+	}
+	if mask&syscall.NOTE_WRITE == syscall.NOTE_WRITE {
+		e.Op |= Write
+	}
+	if mask&syscall.NOTE_RENAME == syscall.NOTE_RENAME {
+		e.Op |= Rename
+	}
+	if mask&syscall.NOTE_ATTRIB == syscall.NOTE_ATTRIB {
+		e.Op |= Chmod
+	}
+	return e
+}
+
+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())
+
+		if fileInfo.IsDir() == false {
+			// Watch file to mimic linux fsnotify
+			e := w.addWatch(filePath, noteAllEvents)
+			if e != nil {
+				return e
+			}
+		} else {
+			// If the user is currently watching directory
+			// we want to preserve the flags used
+			w.enmut.Lock()
+			currFlags, found := w.enFlags[filePath]
+			w.enmut.Unlock()
+			var newFlags uint32 = syscall.NOTE_DELETE
+			if found {
+				newFlags |= currFlags
+			}
+
+			// Linux gives deletes if not explicitly watching
+			e := w.addWatch(filePath, newFlags)
+			if e != nil {
+				return e
+			}
+		}
+		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.Errors <- 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 {
+			// Send create event (mask=0)
+			event := newEvent(filePath, 0, true)
+			w.Events <- event
+		}
+
+		// watchDirectoryFiles (but without doing another ReadDir)
+		if fileInfo.IsDir() == false {
+			// Watch file to mimic linux fsnotify
+			w.addWatch(filePath, noteAllEvents)
+		} else {
+			// If the user is currently watching directory
+			// we want to preserve the flags used
+			w.enmut.Lock()
+			currFlags, found := w.enFlags[filePath]
+			w.enmut.Unlock()
+			var newFlags uint32 = syscall.NOTE_DELETE
+			if found {
+				newFlags |= currFlags
+			}
+
+			// Linux gives deletes if not explicitly watching
+			w.addWatch(filePath, newFlags)
+		}
+
+		w.femut.Lock()
+		w.fileExists[filePath] = true
+		w.femut.Unlock()
+	}
+}

+ 11 - 0
vendor/src/github.com/go-fsnotify/fsnotify/open_mode_bsd.go

@@ -0,0 +1,11 @@
+// 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 openMode = syscall.O_NONBLOCK | syscall.O_RDONLY

+ 12 - 0
vendor/src/github.com/go-fsnotify/fsnotify/open_mode_darwin.go

@@ -0,0 +1,12 @@
+// 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"
+
+// note: this constant is not defined on BSD
+const openMode = syscall.O_EVTONLY

+ 561 - 0
vendor/src/github.com/go-fsnotify/fsnotify/windows.go

@@ -0,0 +1,561 @@
+// 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"
+)
+
+// Watcher watches a set of files, delivering events to a channel.
+type Watcher struct {
+	Events   chan Event
+	Errors   chan error
+	isClosed bool           // Set to true when Close() is first called
+	mu       sync.Mutex     // Map access
+	port     syscall.Handle // Handle to completion port
+	watches  watchMap       // Map of watches (key: i-number)
+	input    chan *input    // Inputs to the reader are sent on this channel
+	quit     chan chan<- error
+}
+
+// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
+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),
+		input:   make(chan *input, 1),
+		Events:  make(chan Event, 50),
+		Errors:  make(chan error),
+		quit:    make(chan chan<- error, 1),
+	}
+	go w.readEvents()
+	return w, nil
+}
+
+// Close removes all watches and closes the events channel.
+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
+}
+
+// Add starts watching the named file or directory (non-recursively).
+func (w *Watcher) Add(name string) error {
+	if w.isClosed {
+		return errors.New("watcher already closed")
+	}
+	in := &input{
+		op:    opAddWatch,
+		path:  filepath.Clean(name),
+		flags: sys_FS_ALL_EVENTS,
+		reply: make(chan error),
+	}
+	w.input <- in
+	if err := w.wakeupReader(); err != nil {
+		return err
+	}
+	return <-in.reply
+}
+
+// Remove stops watching the the named file or directory (non-recursively).
+func (w *Watcher) Remove(name string) error {
+	in := &input{
+		op:    opRemoveWatch,
+		path:  filepath.Clean(name),
+		reply: make(chan error),
+	}
+	w.input <- in
+	if err := w.wakeupReader(); err != nil {
+		return err
+	}
+	return <-in.reply
+}
+
+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
+)
+
+func newEvent(name string, mask uint32) Event {
+	e := Event{Name: name}
+	if mask&sys_FS_CREATE == sys_FS_CREATE || mask&sys_FS_MOVED_TO == sys_FS_MOVED_TO {
+		e.Op |= Create
+	}
+	if mask&sys_FS_DELETE == sys_FS_DELETE || mask&sys_FS_DELETE_SELF == sys_FS_DELETE_SELF {
+		e.Op |= Remove
+	}
+	if mask&sys_FS_MODIFY == sys_FS_MODIFY {
+		e.Op |= Write
+	}
+	if mask&sys_FS_MOVE == sys_FS_MOVE || mask&sys_FS_MOVE_SELF == sys_FS_MOVE_SELF || mask&sys_FS_MOVED_FROM == sys_FS_MOVED_FROM {
+		e.Op |= Rename
+	}
+	if mask&sys_FS_ATTRIB == sys_FS_ATTRIB {
+		e.Op |= Chmod
+	}
+	return e
+}
+
+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
+
+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.Errors <- 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.Errors <- 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 Events 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.Events)
+				close(w.Errors)
+				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 syscall.ERROR_MORE_DATA:
+			if watch == nil {
+				w.Errors <- 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.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
+			continue
+		case nil:
+		}
+
+		var offset uint32
+		for {
+			if n == 0 {
+				w.Events <- newEvent("", sys_FS_Q_OVERFLOW)
+				w.Errors <- 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.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
+				break
+			}
+		}
+
+		if err := w.startRead(watch); err != nil {
+			w.Errors <- err
+		}
+	}
+}
+
+func (w *Watcher) sendEvent(name string, mask uint64) bool {
+	if mask == 0 {
+		return false
+	}
+	event := newEvent(name, uint32(mask))
+	select {
+	case ch := <-w.quit:
+		w.quit <- ch
+	case w.Events <- 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
+}