parent
b5d815d452
commit
a8beb93e39
@ -0,0 +1,52 @@ |
||||
# 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. |
||||
|
||||
Aaron L <aaron@bettercoder.net> |
||||
Adrien Bustany <adrien@bustany.org> |
||||
Amit Krishnan <amit.krishnan@oracle.com> |
||||
Anmol Sethi <me@anmol.io> |
||||
Bjรธrn Erik Pedersen <bjorn.erik.pedersen@gmail.com> |
||||
Bruno Bigras <bigras.bruno@gmail.com> |
||||
Caleb Spare <cespare@gmail.com> |
||||
Case Nelson <case@teammating.com> |
||||
Chris Howey <chris@howey.me> <howeyc@gmail.com> |
||||
Christoffer Buchholz <christoffer.buchholz@gmail.com> |
||||
Daniel Wagner-Hall <dawagner@gmail.com> |
||||
Dave Cheney <dave@cheney.net> |
||||
Evan Phoenix <evan@fallingsnow.net> |
||||
Francisco Souza <f@souza.cc> |
||||
Hari haran <hariharan.uno@gmail.com> |
||||
John C Barstow |
||||
Kelvin Fo <vmirage@gmail.com> |
||||
Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp> |
||||
Matt Layher <mdlayher@gmail.com> |
||||
Nathan Youngman <git@nathany.com> |
||||
Nickolai Zeldovich <nickolai@csail.mit.edu> |
||||
Patrick <patrick@dropbox.com> |
||||
Paul Hammond <paul@paulhammond.org> |
||||
Pawel Knap <pawelknap88@gmail.com> |
||||
Pieter Droogendijk <pieter@binky.org.uk> |
||||
Pursuit92 <JoshChase@techpursuit.net> |
||||
Riku Voipio <riku.voipio@linaro.org> |
||||
Rob Figueiredo <robfig@gmail.com> |
||||
Rodrigo Chiossi <rodrigochiossi@gmail.com> |
||||
Slawek Ligus <root@ooz.ie> |
||||
Soge Zhang <zhssoge@gmail.com> |
||||
Tiffany Jernigan <tiffany.jernigan@intel.com> |
||||
Tilak Sharma <tilaks@google.com> |
||||
Tom Payne <twpayne@gmail.com> |
||||
Travis Cline <travis.cline@gmail.com> |
||||
Tudor Golubenco <tudor.g@gmail.com> |
||||
Vahe Khachikyan <vahe@live.ca> |
||||
Yukang <moorekang@gmail.com> |
||||
bronze1man <bronze1man@gmail.com> |
||||
debrando <denis.brandolini@gmail.com> |
||||
henrikedwards <henrik.edwards@gmail.com> |
||||
้ๅฅ <guotie.9@gmail.com> |
@ -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. |
@ -0,0 +1,37 @@ |
||||
// 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 solaris
|
||||
|
||||
package fsnotify |
||||
|
||||
import ( |
||||
"errors" |
||||
) |
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct { |
||||
Events chan Event |
||||
Errors chan error |
||||
} |
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) { |
||||
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n") |
||||
} |
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error { |
||||
return nil |
||||
} |
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error { |
||||
return nil |
||||
} |
||||
|
||||
// Remove stops watching the the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error { |
||||
return nil |
||||
} |
@ -0,0 +1,66 @@ |
||||
// 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
|
||||
|
||||
// Package fsnotify provides a platform-independent interface for file system notifications.
|
||||
package fsnotify |
||||
|
||||
import ( |
||||
"bytes" |
||||
"errors" |
||||
"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 |
||||
) |
||||
|
||||
func (op Op) String() string { |
||||
// Use a buffer for efficient string concatenation
|
||||
var buffer bytes.Buffer |
||||
|
||||
if op&Create == Create { |
||||
buffer.WriteString("|CREATE") |
||||
} |
||||
if op&Remove == Remove { |
||||
buffer.WriteString("|REMOVE") |
||||
} |
||||
if op&Write == Write { |
||||
buffer.WriteString("|WRITE") |
||||
} |
||||
if op&Rename == Rename { |
||||
buffer.WriteString("|RENAME") |
||||
} |
||||
if op&Chmod == Chmod { |
||||
buffer.WriteString("|CHMOD") |
||||
} |
||||
if buffer.Len() == 0 { |
||||
return "" |
||||
} |
||||
return buffer.String()[1:] // Strip leading pipe
|
||||
} |
||||
|
||||
// String returns a string representation of the event in the form
|
||||
// "file: REMOVE|WRITE|..."
|
||||
func (e Event) String() string { |
||||
return fmt.Sprintf("%q: %s", e.Name, e.Op.String()) |
||||
} |
||||
|
||||
// Common errors that can be reported by a watcher
|
||||
var ErrEventOverflow = errors.New("fsnotify queue overflow") |
@ -0,0 +1,337 @@ |
||||
// 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" |
||||
"io" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
"sync" |
||||
"unsafe" |
||||
|
||||
"golang.org/x/sys/unix" |
||||
) |
||||
|
||||
// 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 |
||||
poller *fdPoller |
||||
watches map[string]*watch // Map of inotify watches (key: path)
|
||||
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
doneResp chan struct{} // Channel to respond to Close
|
||||
} |
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) { |
||||
// Create inotify fd
|
||||
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC) |
||||
if fd == -1 { |
||||
return nil, errno |
||||
} |
||||
// Create epoll
|
||||
poller, err := newFdPoller(fd) |
||||
if err != nil { |
||||
unix.Close(fd) |
||||
return nil, err |
||||
} |
||||
w := &Watcher{ |
||||
fd: fd, |
||||
poller: poller, |
||||
watches: make(map[string]*watch), |
||||
paths: make(map[int]string), |
||||
Events: make(chan Event), |
||||
Errors: make(chan error), |
||||
done: make(chan struct{}), |
||||
doneResp: make(chan struct{}), |
||||
} |
||||
|
||||
go w.readEvents() |
||||
return w, nil |
||||
} |
||||
|
||||
func (w *Watcher) isClosed() bool { |
||||
select { |
||||
case <-w.done: |
||||
return true |
||||
default: |
||||
return false |
||||
} |
||||
} |
||||
|
||||
// Close removes all watches and closes the events channel.
|
||||
func (w *Watcher) Close() error { |
||||
if w.isClosed() { |
||||
return nil |
||||
} |
||||
|
||||
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
||||
close(w.done) |
||||
|
||||
// Wake up goroutine
|
||||
w.poller.wake() |
||||
|
||||
// Wait for goroutine to close
|
||||
<-w.doneResp |
||||
|
||||
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 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM | |
||||
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY | |
||||
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF |
||||
|
||||
var flags uint32 = agnosticEvents |
||||
|
||||
w.mu.Lock() |
||||
defer w.mu.Unlock() |
||||
watchEntry := w.watches[name] |
||||
if watchEntry != nil { |
||||
flags |= watchEntry.flags | unix.IN_MASK_ADD |
||||
} |
||||
wd, errno := unix.InotifyAddWatch(w.fd, name, flags) |
||||
if wd == -1 { |
||||
return errno |
||||
} |
||||
|
||||
if watchEntry == nil { |
||||
w.watches[name] = &watch{wd: uint32(wd), flags: flags} |
||||
w.paths[wd] = name |
||||
} else { |
||||
watchEntry.wd = uint32(wd) |
||||
watchEntry.flags = flags |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Remove stops watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Remove(name string) error { |
||||
name = filepath.Clean(name) |
||||
|
||||
// Fetch the watch.
|
||||
w.mu.Lock() |
||||
defer w.mu.Unlock() |
||||
watch, ok := w.watches[name] |
||||
|
||||
// Remove it from inotify.
|
||||
if !ok { |
||||
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name) |
||||
} |
||||
|
||||
// We successfully removed the watch if InotifyRmWatch doesn't return an
|
||||
// error, we need to clean up our internal state to ensure it matches
|
||||
// inotify's kernel state.
|
||||
delete(w.paths, int(watch.wd)) |
||||
delete(w.watches, name) |
||||
|
||||
// inotify_rm_watch will return EINVAL if the file has been deleted;
|
||||
// the inotify will already have been removed.
|
||||
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
|
||||
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
|
||||
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
|
||||
// by another thread and we have not received IN_IGNORE event.
|
||||
success, errno := unix.InotifyRmWatch(w.fd, watch.wd) |
||||
if success == -1 { |
||||
// TODO: Perhaps it's not helpful to return an error here in every case.
|
||||
// the only two possible errors are:
|
||||
// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
|
||||
// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
|
||||
// Watch descriptors are invalidated when they are removed explicitly or implicitly;
|
||||
// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
|
||||
return errno |
||||
} |
||||
|
||||
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 [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||
n int // Number of bytes read with read()
|
||||
errno error // Syscall errno
|
||||
ok bool // For poller.wait
|
||||
) |
||||
|
||||
defer close(w.doneResp) |
||||
defer close(w.Errors) |
||||
defer close(w.Events) |
||||
defer unix.Close(w.fd) |
||||
defer w.poller.close() |
||||
|
||||
for { |
||||
// See if we have been closed.
|
||||
if w.isClosed() { |
||||
return |
||||
} |
||||
|
||||
ok, errno = w.poller.wait() |
||||
if errno != nil { |
||||
select { |
||||
case w.Errors <- errno: |
||||
case <-w.done: |
||||
return |
||||
} |
||||
continue |
||||
} |
||||
|
||||
if !ok { |
||||
continue |
||||
} |
||||
|
||||
n, errno = unix.Read(w.fd, buf[:]) |
||||
// If a signal interrupted execution, see if we've been asked to close, and try again.
|
||||
// http://man7.org/linux/man-pages/man7/signal.7.html :
|
||||
// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
|
||||
if errno == unix.EINTR { |
||||
continue |
||||
} |
||||
|
||||
// unix.Read might have been woken up by Close. If so, we're done.
|
||||
if w.isClosed() { |
||||
return |
||||
} |
||||
|
||||
if n < unix.SizeofInotifyEvent { |
||||
var err error |
||||
if n == 0 { |
||||
// If EOF is received. This should really never happen.
|
||||
err = io.EOF |
||||
} else if n < 0 { |
||||
// If an error occurred while reading.
|
||||
err = errno |
||||
} else { |
||||
// Read was too short.
|
||||
err = errors.New("notify: short read in readEvents()") |
||||
} |
||||
select { |
||||
case w.Errors <- err: |
||||
case <-w.done: |
||||
return |
||||
} |
||||
continue |
||||
} |
||||
|
||||
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-unix.SizeofInotifyEvent) { |
||||
// Point "raw" to the event in the buffer
|
||||
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) |
||||
|
||||
mask := uint32(raw.Mask) |
||||
nameLen := uint32(raw.Len) |
||||
|
||||
if mask&unix.IN_Q_OVERFLOW != 0 { |
||||
select { |
||||
case w.Errors <- ErrEventOverflow: |
||||
case <-w.done: |
||||
return |
||||
} |
||||
} |
||||
|
||||
// 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, ok := w.paths[int(raw.Wd)] |
||||
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
|
||||
// This is a sign to clean up the maps, otherwise we are no longer in sync
|
||||
// with the inotify kernel state which has already deleted the watch
|
||||
// automatically.
|
||||
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF { |
||||
delete(w.paths, int(raw.Wd)) |
||||
delete(w.watches, name) |
||||
} |
||||
w.mu.Unlock() |
||||
|
||||
if nameLen > 0 { |
||||
// Point "bytes" at the first byte of the filename
|
||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.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) { |
||||
select { |
||||
case w.Events <- event: |
||||
case <-w.done: |
||||
return |
||||
} |
||||
} |
||||
|
||||
// Move to the next event in the buffer
|
||||
offset += unix.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&unix.IN_IGNORED == unix.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&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO { |
||||
e.Op |= Create |
||||
} |
||||
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE { |
||||
e.Op |= Remove |
||||
} |
||||
if mask&unix.IN_MODIFY == unix.IN_MODIFY { |
||||
e.Op |= Write |
||||
} |
||||
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM { |
||||
e.Op |= Rename |
||||
} |
||||
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB { |
||||
e.Op |= Chmod |
||||
} |
||||
return e |
||||
} |
@ -0,0 +1,187 @@ |
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux
|
||||
|
||||
package fsnotify |
||||
|
||||
import ( |
||||
"errors" |
||||
|
||||
"golang.org/x/sys/unix" |
||||
) |
||||
|
||||
type fdPoller struct { |
||||
fd int // File descriptor (as returned by the inotify_init() syscall)
|
||||
epfd int // Epoll file descriptor
|
||||
pipe [2]int // Pipe for waking up
|
||||
} |
||||
|
||||
func emptyPoller(fd int) *fdPoller { |
||||
poller := new(fdPoller) |
||||
poller.fd = fd |
||||
poller.epfd = -1 |
||||
poller.pipe[0] = -1 |
||||
poller.pipe[1] = -1 |
||||
return poller |
||||
} |
||||
|
||||
// Create a new inotify poller.
|
||||
// This creates an inotify handler, and an epoll handler.
|
||||
func newFdPoller(fd int) (*fdPoller, error) { |
||||
var errno error |
||||
poller := emptyPoller(fd) |
||||
defer func() { |
||||
if errno != nil { |
||||
poller.close() |
||||
} |
||||
}() |
||||
poller.fd = fd |
||||
|
||||
// Create epoll fd
|
||||
poller.epfd, errno = unix.EpollCreate1(0) |
||||
if poller.epfd == -1 { |
||||
return nil, errno |
||||
} |
||||
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
|
||||
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK) |
||||
if errno != nil { |
||||
return nil, errno |
||||
} |
||||
|
||||
// Register inotify fd with epoll
|
||||
event := unix.EpollEvent{ |
||||
Fd: int32(poller.fd), |
||||
Events: unix.EPOLLIN, |
||||
} |
||||
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event) |
||||
if errno != nil { |
||||
return nil, errno |
||||
} |
||||
|
||||
// Register pipe fd with epoll
|
||||
event = unix.EpollEvent{ |
||||
Fd: int32(poller.pipe[0]), |
||||
Events: unix.EPOLLIN, |
||||
} |
||||
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event) |
||||
if errno != nil { |
||||
return nil, errno |
||||
} |
||||
|
||||
return poller, nil |
||||
} |
||||
|
||||
// Wait using epoll.
|
||||
// Returns true if something is ready to be read,
|
||||
// false if there is not.
|
||||
func (poller *fdPoller) wait() (bool, error) { |
||||
// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
|
||||
// I don't know whether epoll_wait returns the number of events returned,
|
||||
// or the total number of events ready.
|
||||
// I decided to catch both by making the buffer one larger than the maximum.
|
||||
events := make([]unix.EpollEvent, 7) |
||||
for { |
||||
n, errno := unix.EpollWait(poller.epfd, events, -1) |
||||
if n == -1 { |
||||
if errno == unix.EINTR { |
||||
continue |
||||
} |
||||
return false, errno |
||||
} |
||||
if n == 0 { |
||||
// If there are no events, try again.
|
||||
continue |
||||
} |
||||
if n > 6 { |
||||
// This should never happen. More events were returned than should be possible.
|
||||
return false, errors.New("epoll_wait returned more events than I know what to do with") |
||||
} |
||||
ready := events[:n] |
||||
epollhup := false |
||||
epollerr := false |
||||
epollin := false |
||||
for _, event := range ready { |
||||
if event.Fd == int32(poller.fd) { |
||||
if event.Events&unix.EPOLLHUP != 0 { |
||||
// This should not happen, but if it does, treat it as a wakeup.
|
||||
epollhup = true |
||||
} |
||||
if event.Events&unix.EPOLLERR != 0 { |
||||
// If an error is waiting on the file descriptor, we should pretend
|
||||
// something is ready to read, and let unix.Read pick up the error.
|
||||
epollerr = true |
||||
} |
||||
if event.Events&unix.EPOLLIN != 0 { |
||||
// There is data to read.
|
||||
epollin = true |
||||
} |
||||
} |
||||
if event.Fd == int32(poller.pipe[0]) { |
||||
if event.Events&unix.EPOLLHUP != 0 { |
||||
// Write pipe descriptor was closed, by us. This means we're closing down the
|
||||
// watcher, and we should wake up.
|
||||
} |
||||
if event.Events&unix.EPOLLERR != 0 { |
||||
// If an error is waiting on the pipe file descriptor.
|
||||
// This is an absolute mystery, and should never ever happen.
|
||||
return false, errors.New("Error on the pipe descriptor.") |
||||
} |
||||
if event.Events&unix.EPOLLIN != 0 { |
||||
// This is a regular wakeup, so we have to clear the buffer.
|
||||
err := poller.clearWake() |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if epollhup || epollerr || epollin { |
||||
return true, nil |
||||
} |
||||
return false, nil |
||||
} |
||||
} |
||||
|
||||
// Close the write end of the poller.
|
||||
func (poller *fdPoller) wake() error { |
||||
buf := make([]byte, 1) |
||||
n, errno := unix.Write(poller.pipe[1], buf) |
||||
if n == -1 { |
||||
if errno == unix.EAGAIN { |
||||
// Buffer is full, poller will wake.
|
||||
return nil |
||||
} |
||||
return errno |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (poller *fdPoller) clearWake() error { |
||||
// You have to be woken up a LOT in order to get to 100!
|
||||
buf := make([]byte, 100) |
||||
n, errno := unix.Read(poller.pipe[0], buf) |
||||
if n == -1 { |
||||
if errno == unix.EAGAIN { |
||||
// Buffer is empty, someone else cleared our wake.
|
||||
return nil |
||||
} |
||||
return errno |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Close all poller file descriptors, but not the one passed to it.
|
||||
func (poller *fdPoller) close() { |
||||
if poller.pipe[1] != -1 { |
||||
unix.Close(poller.pipe[1]) |
||||
} |
||||
if poller.pipe[0] != -1 { |
||||
unix.Close(poller.pipe[0]) |
||||
} |
||||
if poller.epfd != -1 { |
||||
unix.Close(poller.epfd) |
||||
} |
||||
} |
@ -0,0 +1,521 @@ |
||||
// 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" |
||||
"time" |
||||
|
||||
"golang.org/x/sys/unix" |
||||
) |
||||
|
||||
// Watcher watches a set of files, delivering events to a channel.
|
||||
type Watcher struct { |
||||
Events chan Event |
||||
Errors chan error |
||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||
|
||||
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||
|
||||
mu sync.Mutex // Protects access to watcher data
|
||||
watches map[string]int // Map of watched file descriptors (key: path).
|
||||
externalWatches map[string]bool // Map of watches added by user of the library.
|
||||
dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
|
||||
paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
|
||||
fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
|
||||
isClosed bool // Set to true when Close() is first called
|
||||
} |
||||
|
||||
type pathInfo struct { |
||||
name string |
||||
isDir bool |
||||
} |
||||
|
||||
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||
func NewWatcher() (*Watcher, error) { |
||||
kq, err := kqueue() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
w := &Watcher{ |
||||
kq: kq, |
||||
watches: make(map[string]int), |
||||
dirFlags: make(map[string]uint32), |
||||
paths: make(map[int]pathInfo), |
||||
fileExists: make(map[string]bool), |
||||
externalWatches: make(map[string]bool), |
||||
Events: make(chan Event), |
||||
Errors: make(chan error), |
||||
done: make(chan struct{}), |
||||
} |
||||
|
||||
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 |
||||
|
||||
// copy paths to remove while locked
|
||||
var pathsToRemove = make([]string, 0, len(w.watches)) |
||||
for name := range w.watches { |
||||
pathsToRemove = append(pathsToRemove, name) |
||||
} |
||||
w.mu.Unlock() |
||||
// unlock before calling Remove, which also locks
|
||||
|
||||
for _, name := range pathsToRemove { |
||||
w.Remove(name) |
||||
} |
||||
|
||||
// send a "quit" message to the reader goroutine
|
||||
close(w.done) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Add starts watching the named file or directory (non-recursively).
|
||||
func (w *Watcher) Add(name string) error { |
||||
w.mu.Lock() |
||||
w.externalWatches[name] = true |
||||
w.mu.Unlock() |
||||
_, err := w.addWatch(name, noteAllEvents) |
||||
return err |
||||
} |
||||
|
||||
// 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() |
||||
watchfd, ok := w.watches[name] |
||||
w.mu.Unlock() |
||||
if !ok { |
||||
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name) |
||||
} |
||||
|
||||
const registerRemove = unix.EV_DELETE |
||||
if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil { |
||||
return err |
||||
} |
||||
|
||||
unix.Close(watchfd) |
||||
|
||||
w.mu.Lock() |
||||
isDir := w.paths[watchfd].isDir |
||||
delete(w.watches, name) |
||||
delete(w.paths, watchfd) |
||||
delete(w.dirFlags, name) |
||||
w.mu.Unlock() |
||||
|
||||
// Find all watched paths that are in this directory that are not external.
|
||||
if isDir { |
||||
var pathsToRemove []string |
||||
w.mu.Lock() |
||||
for _, path := range w.paths { |
||||
wdir, _ := filepath.Split(path.name) |
||||
if filepath.Clean(wdir) == name { |
||||
if !w.externalWatches[path.name] { |
||||
pathsToRemove = append(pathsToRemove, path.name) |
||||
} |
||||
} |
||||
} |
||||
w.mu.Unlock() |
||||
for _, name := range pathsToRemove { |
||||
// Since these are internal, not much sense in propagating error
|
||||
// to the user, as that will just confuse them with an error about
|
||||
// a path they did not explicitly watch themselves.
|
||||
w.Remove(name) |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||||
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME |
||||
|
||||
// keventWaitTime to block on each read from kevent
|
||||
var keventWaitTime = durationToTimespec(100 * time.Millisecond) |
||||
|
||||
// addWatch adds name to the watched file set.
|
||||
// The flags are interpreted as described in kevent(2).
|
||||
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
|
||||
func (w *Watcher) addWatch(name string, flags uint32) (string, error) { |
||||
var isDir bool |
||||
// Make ./name and name equivalent
|
||||
name = filepath.Clean(name) |
||||
|
||||
w.mu.Lock() |
||||
if w.isClosed { |
||||
w.mu.Unlock() |
||||
return "", errors.New("kevent instance already closed") |
||||
} |
||||
watchfd, alreadyWatching := w.watches[name] |
||||
// We already have a watch, but we can still override flags.
|
||||
if alreadyWatching { |
||||
isDir = w.paths[watchfd].isDir |
||||
} |
||||
w.mu.Unlock() |
||||
|
||||
if !alreadyWatching { |
||||
fi, err := os.Lstat(name) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
// Don't watch sockets.
|
||||
if fi.Mode()&os.ModeSocket == os.ModeSocket { |
||||
return "", nil |
||||
} |
||||
|
||||
// Don't watch named pipes.
|
||||
if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe { |
||||
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 { |
||||
name, err = filepath.EvalSymlinks(name) |
||||
if err != nil { |
||||
return "", nil |
||||
} |
||||
|
||||
w.mu.Lock() |
||||
_, alreadyWatching = w.watches[name] |
||||
w.mu.Unlock() |
||||
|
||||
if alreadyWatching { |
||||
return name, nil |
||||
} |
||||
|
||||
fi, err = os.Lstat(name) |
||||
if err != nil { |
||||
return "", nil |
||||
} |
||||
} |
||||
|
||||
watchfd, err = unix.Open(name, openMode, 0700) |
||||
if watchfd == -1 { |
||||
return "", err |
||||
} |
||||
|
||||
isDir = fi.IsDir() |
||||
} |
||||
|
||||
const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE |
||||
if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil { |
||||
unix.Close(watchfd) |
||||
return "", err |
||||
} |
||||
|
||||
if !alreadyWatching { |
||||
w.mu.Lock() |
||||
w.watches[name] = watchfd |
||||
w.paths[watchfd] = pathInfo{name: name, isDir: isDir} |
||||
w.mu.Unlock() |
||||
} |
||||
|
||||
if isDir { |
||||
// Watch the directory if it has not been watched before,
|
||||
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||||
w.mu.Lock() |
||||
|
||||
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE && |
||||
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE) |
||||
// Store flags so this watch can be updated later
|
||||
w.dirFlags[name] = flags |
||||
w.mu.Unlock() |
||||
|
||||
if watchDir { |
||||
if err := w.watchDirectoryFiles(name); err != nil { |
||||
return "", err |
||||
} |
||||
} |
||||
} |
||||
return name, nil |
||||
} |
||||
|
||||
// readEvents reads from kqueue and converts the received kevents into
|
||||
// Event values that it sends down the Events channel.
|
||||
func (w *Watcher) readEvents() { |
||||
eventBuffer := make([]unix.Kevent_t, 10) |
||||
|
||||
loop: |
||||
for { |
||||
// See if there is a message on the "done" channel
|
||||
select { |
||||
case <-w.done: |
||||
break loop |
||||
default: |
||||
} |
||||
|
||||
// Get new events
|
||||
kevents, err := read(w.kq, eventBuffer, &keventWaitTime) |
||||
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||
if err != nil && err != unix.EINTR { |
||||
select { |
||||
case w.Errors <- err: |
||||
case <-w.done: |
||||
break loop |
||||
} |
||||
continue |
||||
} |
||||
|
||||
// Flush the events we received to the Events channel
|
||||
for len(kevents) > 0 { |
||||
kevent := &kevents[0] |
||||
watchfd := int(kevent.Ident) |
||||
mask := uint32(kevent.Fflags) |
||||
w.mu.Lock() |
||||
path := w.paths[watchfd] |
||||
w.mu.Unlock() |
||||
event := newEvent(path.name, mask) |
||||
|
||||
if path.isDir && !(event.Op&Remove == Remove) { |
||||
// Double check to make sure the directory exists. This can happen when
|
||||
// we do a rm -fr on a recursively watched folders and we receive a
|
||||
// modification event first but the folder has been deleted and later
|
||||
// receive the delete event
|
||||
if _, err := os.Lstat(event.Name); os.IsNotExist(err) { |
||||
// mark is as delete event
|
||||
event.Op |= Remove |
||||
} |
||||
} |
||||
|
||||
if event.Op&Rename == Rename || event.Op&Remove == Remove { |
||||
w.Remove(event.Name) |
||||
w.mu.Lock() |
||||
delete(w.fileExists, event.Name) |
||||
w.mu.Unlock() |
||||
} |
||||
|
||||
if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) { |
||||
w.sendDirectoryChangeEvents(event.Name) |
||||
} else { |
||||
// Send the event on the Events channel.
|
||||
select { |
||||
case w.Events <- event: |
||||
case <-w.done: |
||||
break loop |
||||
} |
||||
} |
||||
|
||||
if event.Op&Remove == Remove { |
||||
// Look for a file that may have overwritten this.
|
||||
// For example, mv f1 f2 will delete f2, then create f2.
|
||||
if path.isDir { |
||||
fileDir := filepath.Clean(event.Name) |
||||
w.mu.Lock() |
||||
_, found := w.watches[fileDir] |
||||
w.mu.Unlock() |
||||
if found { |
||||
// make sure the directory exists before we watch for changes. When we
|
||||
// do a recursive watch and perform rm -fr, the parent directory might
|
||||
// have gone missing, ignore the missing directory and let the
|
||||
// upcoming delete event remove the watch from the parent directory.
|
||||
if _, err := os.Lstat(fileDir); err == nil { |
||||
w.sendDirectoryChangeEvents(fileDir) |
||||
} |
||||
} |
||||
} else { |
||||
filePath := filepath.Clean(event.Name) |
||||
if fileInfo, err := os.Lstat(filePath); err == nil { |
||||
w.sendFileCreatedEventIfNew(filePath, fileInfo) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Move to next event
|
||||
kevents = kevents[1:] |
||||
} |
||||
} |
||||
|
||||
// cleanup
|
||||
err := unix.Close(w.kq) |
||||
if err != nil { |
||||
// only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors.
|
||||
select { |
||||
case w.Errors <- err: |
||||
default: |
||||
} |
||||
} |
||||
close(w.Events) |
||||
close(w.Errors) |
||||
} |
||||
|
||||
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||||
func newEvent(name string, mask uint32) Event { |
||||
e := Event{Name: name} |
||||
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE { |
||||
e.Op |= Remove |
||||
} |
||||
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE { |
||||
e.Op |= Write |
||||
} |
||||
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME { |
||||
e.Op |= Rename |
||||
} |
||||
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB { |
||||
e.Op |= Chmod |
||||
} |
||||
return e |
||||
} |
||||
|
||||
func newCreateEvent(name string) Event { |
||||
return Event{Name: name, Op: Create} |
||||
} |
||||
|
||||
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
||||
func (w *Watcher) watchDirectoryFiles(dirPath string) error { |
||||
// Get all files
|
||||
files, err := ioutil.ReadDir(dirPath) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
for _, fileInfo := range files { |
||||
filePath := filepath.Join(dirPath, fileInfo.Name()) |
||||
filePath, err = w.internalWatch(filePath, fileInfo) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
w.mu.Lock() |
||||
w.fileExists[filePath] = true |
||||
w.mu.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 inotify 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 { |
||||
select { |
||||
case w.Errors <- err: |
||||
case <-w.done: |
||||
return |
||||
} |
||||
} |
||||
|
||||
// Search for new files
|
||||
for _, fileInfo := range files { |
||||
filePath := filepath.Join(dirPath, fileInfo.Name()) |
||||
err := w.sendFileCreatedEventIfNew(filePath, fileInfo) |
||||
|
||||
if err != nil { |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
|
||||
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) { |
||||
w.mu.Lock() |
||||
_, doesExist := w.fileExists[filePath] |
||||
w.mu.Unlock() |
||||
if !doesExist { |
||||
// Send create event
|
||||
select { |
||||
case w.Events <- newCreateEvent(filePath): |
||||
case <-w.done: |
||||
return |
||||
} |
||||
} |
||||
|
||||
// like watchDirectoryFiles (but without doing another ReadDir)
|
||||
filePath, err = w.internalWatch(filePath, fileInfo) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
w.mu.Lock() |
||||
w.fileExists[filePath] = true |
||||
w.mu.Unlock() |
||||
|
||||
return nil |
||||
} |
||||
|
||||