[exp] x/exp/fsnotify: remove fsnotify

281 views
Skip to first unread message

Nathan Youngman (Gerrit)

unread,
Feb 5, 2015, 8:25:38 PM2/5/15
to Ian Lance Taylor, Nathan Youngman, golang-co...@googlegroups.com
Nathan Youngman uploaded a change:
https://go-review.googlesource.com/3990

x/exp/fsnotify: remove fsnotify

This is an outdated clone of gopkg.in/fsnotify.v1

Change-Id: I1035dfebb85f7c07a4f730f5fc6912105096cb94
---
D fsnotify/example_test.go
D fsnotify/fsnotify.go
D fsnotify/fsnotify_bsd.go
D fsnotify/fsnotify_linux.go
D fsnotify/fsnotify_open_bsd.go
D fsnotify/fsnotify_open_darwin.go
D fsnotify/fsnotify_symlink_test.go
D fsnotify/fsnotify_test.go
D fsnotify/fsnotify_windows.go
9 files changed, 0 insertions(+), 2,546 deletions(-)



diff --git a/fsnotify/example_test.go b/fsnotify/example_test.go
deleted file mode 100644
index 4ef8bc2..0000000
--- a/fsnotify/example_test.go
+++ /dev/null
@@ -1,36 +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.
-
-// +build !plan9,!solaris
-
-package fsnotify_test
-
-import (
- "log"
-
- "golang.org/x/exp/fsnotify"
-)
-
-func ExampleNewWatcher() {
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- log.Fatal(err)
- }
-
- go func() {
- for {
- select {
- case ev := <-watcher.Event:
- log.Println("event:", ev)
- case err := <-watcher.Error:
- log.Println("error:", err)
- }
- }
- }()
-
- err = watcher.Watch("/tmp/foo")
- if err != nil {
- log.Fatal(err)
- }
-}
diff --git a/fsnotify/fsnotify.go b/fsnotify/fsnotify.go
deleted file mode 100644
index d48753e..0000000
--- a/fsnotify/fsnotify.go
+++ /dev/null
@@ -1,52 +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.
-
-// +build !plan9,!solaris
-
-// Package fsnotify implements file system notification.
-package fsnotify // import "golang.org/x/exp/fsnotify"
-
-import "fmt"
-
-// Watch a given file path
-func (w *Watcher) Watch(path string) error {
- return w.watch(path)
-}
-
-// Remove a watch on a file
-func (w *Watcher) RemoveWatch(path string) error {
- 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)
-}
diff --git a/fsnotify/fsnotify_bsd.go b/fsnotify/fsnotify_bsd.go
deleted file mode 100644
index 1941f9b..0000000
--- a/fsnotify/fsnotify_bsd.go
+++ /dev/null
@@ -1,476 +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 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.
- 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
- 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
- kbuf [1]syscall.Kevent_t // An event buffer for Add/Remove
watch
- bufmut sync.Mutex // Protects access to kbuf.
-}
-
-// 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),
- 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),
- Event: make(chan *FileEvent),
- Error: make(chan error),
- done: make(chan bool, 1),
- }
-
- go w.readEvents()
- 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.pmut.Lock()
- ws := w.watches
- w.pmut.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()
-
- w.bufmut.Lock()
- watchEntry := &w.kbuf[0]
- watchEntry.Fflags = flags
- syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE,
syscall.EV_ADD|syscall.EV_CLEAR)
- entryFlags := watchEntry.Flags
- w.bufmut.Unlock()
-
- wd, errno := syscall.Kevent(w.kq, w.kbuf[:], nil, nil)
- if wd == -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))
- }
- w.bufmut.Lock()
- watchEntry := &w.kbuf[0]
- syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE,
syscall.EV_DELETE)
- success, errno := syscall.Kevent(w.kq, w.kbuf[:], nil, nil)
- w.bufmut.Unlock()
- if success == -1 {
- return os.NewSyscallError("kevent_rm_watch", errno)
- } else if (watchEntry.Flags & 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.Event)
- 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.Event <- 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())
-
- 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 {
- // Send create event
- fileEvent := new(FileEvent)
- fileEvent.Name = filePath
- fileEvent.create = true
- w.Event <- fileEvent
- }
- w.femut.Lock()
- w.fileExists[filePath] = true
- w.femut.Unlock()
- }
- w.watchDirectoryFiles(dirPath)
-}
diff --git a/fsnotify/fsnotify_linux.go b/fsnotify/fsnotify_linux.go
deleted file mode 100644
index 8dead73..0000000
--- a/fsnotify/fsnotify_linux.go
+++ /dev/null
@@ -1,284 +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.
-
-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)
- paths map[int]string // Map of watched paths (key: watch descriptor)
- Error chan error // Errors are sent 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),
- paths: make(map[int]string),
- Event: make(chan *FileEvent),
- Error: make(chan error),
- done: make(chan bool, 1),
- }
-
- go w.readEvents()
- 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.Event)
- 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.Event)
- 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()
- 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() {
- w.Event <- 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
-}
diff --git a/fsnotify/fsnotify_open_bsd.go b/fsnotify/fsnotify_open_bsd.go
deleted file mode 100644
index 37ea998..0000000
--- a/fsnotify/fsnotify_open_bsd.go
+++ /dev/null
@@ -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
-
-package fsnotify
-
-import "syscall"
-
-const open_FLAGS = syscall.O_NONBLOCK | syscall.O_RDONLY
diff --git a/fsnotify/fsnotify_open_darwin.go
b/fsnotify/fsnotify_open_darwin.go
deleted file mode 100644
index d450318..0000000
--- a/fsnotify/fsnotify_open_darwin.go
+++ /dev/null
@@ -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
diff --git a/fsnotify/fsnotify_symlink_test.go
b/fsnotify/fsnotify_symlink_test.go
deleted file mode 100644
index 39061f8..0000000
--- a/fsnotify/fsnotify_symlink_test.go
+++ /dev/null
@@ -1,74 +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 darwin linux
-
-package fsnotify
-
-import (
- "os"
- "path/filepath"
- "testing"
- "time"
-)
-
-func TestFsnotifyFakeSymlink(t *testing.T) {
- 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.Error {
- t.Logf("Received error: %s", errors)
- errorsReceived.increment()
- }
- }()
-
- // Count the CREATE events received
- var createEventsReceived, otherEventsReceived counter
- go func() {
- for ev := range watcher.Event {
- t.Logf("event received: %s", ev)
- if ev.IsCreate() {
- 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()
-}
diff --git a/fsnotify/fsnotify_test.go b/fsnotify/fsnotify_test.go
deleted file mode 100644
index 944adc1..0000000
--- a/fsnotify/fsnotify_test.go
+++ /dev/null
@@ -1,1012 +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 !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.Watch(dir); err != nil {
- t.Fatalf("watcher.Watch(%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.Error {
- 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.Event
- 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.IsDelete() {
- deleteReceived.increment()
- }
- if event.IsModify() {
- modifyReceived.increment()
- }
- if event.IsCreate() {
- createReceived.increment()
- }
- if event.IsRename() {
- 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.Error {
- 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.Event
- 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.IsDelete() {
- deleteReceived.increment()
- }
- if event.IsCreate() {
- createReceived.increment()
- }
- if event.IsModify() {
- 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.Error {
- t.Fatalf("error received: %s", err)
- }
- }()
-
- testFile := filepath.Join(testDir, "TestFsnotifyDirOnly.testfile")
-
- // Receive events on the event channel on a separate goroutine
- eventstream := watcher.Event
- 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.IsDelete() {
- deleteReceived.increment()
- }
- if event.IsModify() {
- modifyReceived.increment()
- }
- if event.IsCreate() {
- 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.Error {
- t.Fatalf("error received: %s", err)
- }
- }()
-
- // Receive events on the event channel on a separate goroutine
- eventstream := watcher.Event
- 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.IsDelete() {
- 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.Error {
- t.Fatalf("error received: %s", err)
- }
- }()
-
- // Receive events on the event channel on a separate goroutine
- eventstream := watcher.Event
- 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.IsCreate() {
- createReceived.increment()
- }
- if event.IsDelete() {
- 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.Error {
- 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.Event
- 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.IsRename() {
- 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.Error {
- 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.Event
- 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.IsCreate() {
- 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.Error {
- t.Fatalf("error received: %s", err)
- }
- }()
-
- // Receive events on the event channel on a separate goroutine
- eventstream := watcher.Event
- 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.RemoveWatch(testDir); err != nil {
- t.Fatalf("Could not remove the watch: %v\n", err)
- }
-
- go func() {
- select {
- case ev := <-watcher.Event:
- 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.Error {
- t.Fatalf("error received: %s", err)
- }
- }()
-
- testFile := filepath.Join(testDir, "TestFsnotifyAttrib.testfile")
-
- // Receive events on the event channel on a separate goroutine
- eventstream := watcher.Event
- // 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.IsModify() {
- modifyReceived.increment()
- }
- if event.IsAttrib() {
- 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("fsnotify modify events have not received after 500 ms")
- }
- 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.Watch(testDir); err == nil {
- t.Fatal("expected error on Watch() after Close(), got nil")
- }
-}
-
-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()
- }
-}
diff --git a/fsnotify/fsnotify_windows.go b/fsnotify/fsnotify_windows.go
deleted file mode 100644
index 78125ef..0000000
--- a/fsnotify/fsnotify_windows.go
+++ /dev/null
@@ -1,590 +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.
-
-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)
- input chan *input // Inputs to the reader are sent 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),
- input: make(chan *input, 1),
- Event: make(chan *FileEvent, 50),
- Error: make(chan error),
- quit: make(chan chan<- error, 1),
- }
- go w.readEvents()
- 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.Event)
- 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.Event <- &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
-}

--
https://go-review.googlesource.com/3990

Mikio Hara (Gerrit)

unread,
Feb 6, 2015, 1:28:01 AM2/6/15
to Nathan Youngman, golang-co...@googlegroups.com
Mikio Hara has posted comments on this change.

x/exp/fsnotify: remove fsnotify

Patch Set 1: Code-Review+1

(1 comment)

https://go-review.googlesource.com/#/c/3990/1//COMMIT_MSG
Commit Message:

Line 7: x/exp/fsnotify
drop the prefix "x/exp/", just fsnotify


--
https://go-review.googlesource.com/3990
Gerrit-Reviewer: Mikio Hara <mikioh...@gmail.com>
Gerrit-Reviewer: Nathan Youngman <he...@nathany.com>
Gerrit-HasComments: Yes

Nathan Youngman (Gerrit)

unread,
Feb 6, 2015, 1:44:07 AM2/6/15
to Nathan Youngman, Mikio Hara, golang-co...@googlegroups.com
Nathan Youngman has posted comments on this change.

x/exp/fsnotify: remove fsnotify

Patch Set 1:

Sorry about that. So I did "git change" and made the change (which does
amend, right?), but it's not pushed here.

The next step in the contribution guidelines is to run "git mail" again but
it explodes with:

git mail
fatal: unable to access 'https://go.googlesource.com/exp/': Server aborted
the SSL handshake
(running: git push -q origin HEAD:refs/for/master)
git-codereview: exit status 128

--
https://go-review.googlesource.com/3990
Gerrit-Reviewer: Mikio Hara <mikioh...@gmail.com>
Gerrit-Reviewer: Nathan Youngman <he...@nathany.com>
Gerrit-HasComments: No

Nathan Youngman (Gerrit)

unread,
Feb 6, 2015, 1:50:37 AM2/6/15
to Nathan Youngman, Mikio Hara, golang-co...@googlegroups.com
Reviewers: Mikio Hara

Nathan Youngman uploaded a new patch set:
https://go-review.googlesource.com/3990

fsnotify: remove fsnotify

This is an outdated clone of gopkg.in/fsnotify.v1

Change-Id: I1035dfebb85f7c07a4f730f5fc6912105096cb94
---
D fsnotify/example_test.go
D fsnotify/fsnotify.go
D fsnotify/fsnotify_bsd.go
D fsnotify/fsnotify_linux.go
D fsnotify/fsnotify_open_bsd.go
D fsnotify/fsnotify_open_darwin.go
D fsnotify/fsnotify_symlink_test.go
D fsnotify/fsnotify_test.go
D fsnotify/fsnotify_windows.go
9 files changed, 0 insertions(+), 2,546 deletions(-)


Nathan Youngman (Gerrit)

unread,
Feb 6, 2015, 1:53:27 AM2/6/15
to Nathan Youngman, Mikio Hara, golang-co...@googlegroups.com
Nathan Youngman has posted comments on this change.

fsnotify: remove fsnotify

Patch Set 2:

git mail worked that time (see patch set 2). weird. oh well.

thanks for reviewing this.

--
https://go-review.googlesource.com/3990
Gerrit-Reviewer: Mikio Hara <mikioh...@gmail.com>
Gerrit-Reviewer: Nathan Youngman <he...@nathany.com>
Gerrit-HasComments: No

Dave Cheney (Gerrit)

unread,
Feb 6, 2015, 2:41:40 AM2/6/15
to Nathan Youngman, Mikio Hara, Andrew Gerrand, Rob Pike, golang-co...@googlegroups.com
Dave Cheney has posted comments on this change.

fsnotify: remove fsnotify

Patch Set 2: Code-Review+1

I'm +1 on deleting this code but I think adg or r should be consulted.

--
https://go-review.googlesource.com/3990
Gerrit-Reviewer: Andrew Gerrand <a...@golang.org>
Gerrit-Reviewer: Dave Cheney <da...@cheney.net>
Gerrit-Reviewer: Mikio Hara <mikioh...@gmail.com>
Gerrit-Reviewer: Nathan Youngman <he...@nathany.com>
Gerrit-Reviewer: Rob Pike <rob...@gmail.com>
Gerrit-HasComments: No

Nathan Youngman (Gerrit)

unread,
Feb 6, 2015, 9:09:47 AM2/6/15
to Nathan Youngman, Andrew Gerrand, Rob Pike, Dave Cheney, golang-co...@googlegroups.com
Nathan Youngman has posted comments on this change.

fsnotify: remove fsnotify

Patch Set 2:

This is something I proposed on the ML some time ago and just finally got
around to:
https://groups.google.com/forum/m/#!searchin/golang-dev/Fsnotify/golang-dev/-__vD-kOF5s

--
https://go-review.googlesource.com/3990
Gerrit-Reviewer: Andrew Gerrand <a...@golang.org>
Gerrit-Reviewer: Dave Cheney <da...@cheney.net>

Rob Pike (Gerrit)

unread,
Feb 6, 2015, 10:11:36 AM2/6/15
to Nathan Youngman, Andrew Gerrand, Rob Pike, Rob Pike, Dave Cheney, golang-co...@googlegroups.com
Rob Pike has posted comments on this change.

fsnotify: remove fsnotify

Patch Set 2:

People depend on this package. It should leave a forwarding note.

--
https://go-review.googlesource.com/3990
Gerrit-Reviewer: Andrew Gerrand <a...@golang.org>
Gerrit-Reviewer: Dave Cheney <da...@cheney.net>
Gerrit-Reviewer: Nathan Youngman <he...@nathany.com>
Gerrit-Reviewer: Rob Pike <r...@golang.org>

Rob Pike

unread,
Feb 6, 2015, 10:51:33 AM2/6/15
to r...@golang.org, Nathan Youngman, Andrew Gerrand, Dave Cheney, golang-co...@googlegroups.com
Someone sent this to rob...@gmail.com. Please don't do that. My work mail is r...@golang.org

-rob

Nathan Youngman (Gerrit)

unread,
Feb 6, 2015, 11:31:55 AM2/6/15
to Nathan Youngman, Dave Cheney, Rob Pike, Andrew Gerrand, golang-co...@googlegroups.com
Reviewers: Dave Cheney

Nathan Youngman uploaded a new patch set:
https://go-review.googlesource.com/3990

fsnotify: remove fsnotify

This is an outdated clone of gopkg.in/fsnotify.v1

Change-Id: I1035dfebb85f7c07a4f730f5fc6912105096cb94
---
A fsnotify/error.go
D fsnotify/example_test.go
D fsnotify/fsnotify.go
D fsnotify/fsnotify_bsd.go
D fsnotify/fsnotify_linux.go
D fsnotify/fsnotify_open_bsd.go
D fsnotify/fsnotify_open_darwin.go
D fsnotify/fsnotify_symlink_test.go
D fsnotify/fsnotify_test.go
D fsnotify/fsnotify_windows.go
10 files changed, 5 insertions(+), 2,546 deletions(-)

Nathan Youngman (Gerrit)

unread,
Feb 6, 2015, 11:35:58 AM2/6/15
to Nathan Youngman, Andrew Gerrand, Rob Pike, Dave Cheney, golang-co...@googlegroups.com
Nathan Youngman has posted comments on this change.

fsnotify: remove fsnotify

Patch Set 3:

This is how the mgo and amz packages leave a forwarding note on their
master branches.

Will it cause any problem with the builders? Would a .txt file be preferred?

--
https://go-review.googlesource.com/3990
Gerrit-Reviewer: Andrew Gerrand <a...@golang.org>
Gerrit-Reviewer: Dave Cheney <da...@cheney.net>
Gerrit-Reviewer: Nathan Youngman <he...@nathany.com>
Gerrit-Reviewer: Rob Pike <r...@golang.org>
Gerrit-HasComments: No

Rob Pike (Gerrit)

unread,
Feb 6, 2015, 12:39:25 PM2/6/15
to Nathan Youngman, Andrew Gerrand, Rob Pike, Dave Cheney, golang-co...@googlegroups.com
Rob Pike has posted comments on this change.

fsnotify: remove fsnotify

Patch Set 3:

Not sure why this is the right way. Can you explain the thinking?

I had in mind something like a README

Nathan Youngman (Gerrit)

unread,
Feb 6, 2015, 1:27:40 PM2/6/15
to Nathan Youngman, Andrew Gerrand, Rob Pike, Dave Cheney, golang-co...@googlegroups.com
Nathan Youngman has posted comments on this change.

fsnotify: remove fsnotify

Patch Set 3:

I guess this is the idea, but I can just do a README.txt instead:

go get github.com/go-mgo/mgo
# github.com/go-mgo/mgo
../../go-mgo/mgo/error.go:4: "ERROR: the correct import path is
gopkg.in/mgo.v2 ... " evaluated but not used

Nathan Youngman (Gerrit)

unread,
Feb 6, 2015, 1:31:05 PM2/6/15
to Nathan Youngman, Dave Cheney, Rob Pike, Andrew Gerrand, golang-co...@googlegroups.com
Reviewers: Dave Cheney

Nathan Youngman uploaded a new patch set:
https://go-review.googlesource.com/3990

fsnotify: remove fsnotify

This is an outdated clone of gopkg.in/fsnotify.v1

Change-Id: I1035dfebb85f7c07a4f730f5fc6912105096cb94
---
A fsnotify/README.txt
D fsnotify/example_test.go
D fsnotify/fsnotify.go
D fsnotify/fsnotify_bsd.go
D fsnotify/fsnotify_linux.go
D fsnotify/fsnotify_open_bsd.go
D fsnotify/fsnotify_open_darwin.go
D fsnotify/fsnotify_symlink_test.go
D fsnotify/fsnotify_test.go
D fsnotify/fsnotify_windows.go
10 files changed, 3 insertions(+), 2,546 deletions(-)

Rob Pike (Gerrit)

unread,
Feb 6, 2015, 2:45:24 PM2/6/15
to Nathan Youngman, Andrew Gerrand, Dave Cheney, Rob Pike, golang-co...@googlegroups.com
Rob Pike has posted comments on this change.

fsnotify: remove fsnotify

Patch Set 4: Code-Review+1

I will +2 and submit if no one objects in the next day or so

--
https://go-review.googlesource.com/3990
Gerrit-Reviewer: Andrew Gerrand <a...@golang.org>
Gerrit-Reviewer: Dave Cheney <da...@cheney.net>
Gerrit-Reviewer: Nathan Youngman <he...@nathany.com>
Gerrit-Reviewer: Rob Pike <r...@golang.org>
Gerrit-HasComments: No

Mike Danese (Gerrit)

unread,
Feb 6, 2015, 6:48:13 PM2/6/15
to Nathan Youngman, Andrew Gerrand, Dave Cheney, Rob Pike, golang-co...@googlegroups.com
Mike Danese has posted comments on this change.

fsnotify: remove fsnotify

Patch Set 4:

Thanks for the heads up, Nathan. I'll be migrating to gopkg.in/fsnotify.v1
shortly.

--
https://go-review.googlesource.com/3990
Gerrit-Reviewer: Andrew Gerrand <a...@golang.org>
Gerrit-Reviewer: Dave Cheney <da...@cheney.net>
Gerrit-Reviewer: Mike Danese <miked...@gmail.com>

Andrew Gerrand (Gerrit)

unread,
Feb 7, 2015, 6:52:51 AM2/7/15
to Nathan Youngman, Mike Danese, Dave Cheney, Rob Pike, golang-co...@googlegroups.com
Andrew Gerrand has posted comments on this change.

fsnotify: remove fsnotify

Patch Set 4: Code-Review+2

Nathan Youngman (Gerrit)

unread,
Feb 13, 2015, 1:15:56 PM2/13/15
to Nathan Youngman, Mike Danese, Dave Cheney, Andrew Gerrand, Rob Pike, golang-co...@googlegroups.com
Nathan Youngman has posted comments on this change.

fsnotify: remove fsnotify

Patch Set 4:

It looks like nobody is objecting.
Thanks. :-)

Andrew Gerrand (Gerrit)

unread,
Feb 16, 2015, 7:19:34 PM2/16/15
to Nathan Youngman, golang-...@googlegroups.com, Mike Danese, Dave Cheney, Rob Pike, golang-co...@googlegroups.com
Andrew Gerrand has submitted this change and it was merged.

fsnotify: remove fsnotify

This is an outdated clone of gopkg.in/fsnotify.v1

Change-Id: I1035dfebb85f7c07a4f730f5fc6912105096cb94
Reviewed-on: https://go-review.googlesource.com/3990
Reviewed-by: Rob Pike <r...@golang.org>
Reviewed-by: Andrew Gerrand <a...@golang.org>
---
A fsnotify/README.txt
D fsnotify/example_test.go
D fsnotify/fsnotify.go
D fsnotify/fsnotify_bsd.go
D fsnotify/fsnotify_linux.go
D fsnotify/fsnotify_open_bsd.go
D fsnotify/fsnotify_open_darwin.go
D fsnotify/fsnotify_symlink_test.go
D fsnotify/fsnotify_test.go
D fsnotify/fsnotify_windows.go
10 files changed, 3 insertions(+), 2,546 deletions(-)

Approvals:
Rob Pike: Looks good to me, but someone else must approve
Andrew Gerrand: Looks good to me, approved
Reply all
Reply to author
Forward
0 new messages