435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
338 lines
9.2 KiB
Go
338 lines
9.2 KiB
Go
// 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
|
|
}
|