context: kubernetes/kubernetes#68478 The inotify code was removed from golang.org/x/exp several years ago. Therefore importing it from that path prevents downstream consumers from using any module that makes use of more recent features of golang.org/x/exp. Given that this code is by definition frozen and that the long term path should be to migrate to fsnotify, replacing the current code by an identical standalone copy doesn't have maintenance cost, and will unlock other activities for kubernetes for example.
307 lines
8.3 KiB
Go
307 lines
8.3 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.
|
|
|
|
/*
|
|
Package inotify implements a wrapper for the Linux inotify system.
|
|
|
|
Example:
|
|
watcher, err := inotify.NewWatcher()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
err = watcher.Watch("/tmp")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
for {
|
|
select {
|
|
case ev := <-watcher.Event:
|
|
log.Println("event:", ev)
|
|
case err := <-watcher.Error:
|
|
log.Println("error:", err)
|
|
}
|
|
}
|
|
|
|
*/
|
|
package inotify
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"unsafe"
|
|
)
|
|
|
|
type Event struct {
|
|
Mask uint32 // Mask of events
|
|
Cookie uint32 // Unique cookie associating related events (for rename(2))
|
|
Name string // File name (optional)
|
|
}
|
|
|
|
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
|
|
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 *Event // 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 *Event),
|
|
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
|
|
|
|
// Send "quit" message to the reader goroutine
|
|
w.done <- true
|
|
for path := range w.watches {
|
|
w.RemoveWatch(path)
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
watchEntry, found := w.watches[path]
|
|
if found {
|
|
watchEntry.flags |= flags
|
|
flags |= syscall.IN_MASK_ADD
|
|
}
|
|
|
|
w.mu.Lock() // synchronize with readEvents goroutine
|
|
|
|
wd, err := syscall.InotifyAddWatch(w.fd, path, flags)
|
|
if err != nil {
|
|
w.mu.Unlock()
|
|
return &os.PathError{
|
|
Op: "inotify_add_watch",
|
|
Path: path,
|
|
Err: err,
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
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, IN_ALL_EVENTS)
|
|
}
|
|
|
|
// RemoveWatch removes path from the watched file set.
|
|
func (w *Watcher) RemoveWatch(path string) error {
|
|
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)
|
|
// Locking here to protect the read from paths in readEvents.
|
|
w.mu.Lock()
|
|
delete(w.paths, int(watch.wd))
|
|
w.mu.Unlock()
|
|
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
|
|
|
|
for {
|
|
n, err := syscall.Read(w.fd, buf[:])
|
|
// See if there is a message on the "done" channel
|
|
var done bool
|
|
select {
|
|
case done = <-w.done:
|
|
default:
|
|
}
|
|
|
|
// If EOF or a "done" message is received
|
|
if n == 0 || done {
|
|
// The syscall.Close can be slow. Close
|
|
// w.Event first.
|
|
close(w.Event)
|
|
err := syscall.Close(w.fd)
|
|
if err != nil {
|
|
w.Error <- os.NewSyscallError("close", err)
|
|
}
|
|
close(w.Error)
|
|
return
|
|
}
|
|
if n < 0 {
|
|
w.Error <- os.NewSyscallError("read", err)
|
|
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(Event)
|
|
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()
|
|
name, ok := w.paths[int(raw.Wd)]
|
|
w.mu.Unlock()
|
|
if ok {
|
|
event.Name = name
|
|
if nameLen > 0 {
|
|
// Point "bytes" at the first byte of the filename
|
|
bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
|
|
// The filename is padded with NUL bytes. TrimRight() gets rid of those.
|
|
event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
|
}
|
|
// Send the event on the events channel
|
|
w.Event <- event
|
|
}
|
|
// Move to the next event in the buffer
|
|
offset += syscall.SizeofInotifyEvent + nameLen
|
|
}
|
|
}
|
|
}
|
|
|
|
// String formats the event e in the form
|
|
// "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..."
|
|
func (e *Event) String() string {
|
|
var events string = ""
|
|
|
|
m := e.Mask
|
|
for _, b := range eventBits {
|
|
if m&b.Value == b.Value {
|
|
m &^= b.Value
|
|
events += "|" + b.Name
|
|
}
|
|
}
|
|
|
|
if m != 0 {
|
|
events += fmt.Sprintf("|%#x", m)
|
|
}
|
|
if len(events) > 0 {
|
|
events = " == " + events[1:]
|
|
}
|
|
|
|
return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)
|
|
}
|
|
|
|
const (
|
|
// Options for inotify_init() are not exported
|
|
// IN_CLOEXEC uint32 = syscall.IN_CLOEXEC
|
|
// IN_NONBLOCK uint32 = syscall.IN_NONBLOCK
|
|
|
|
// Options for AddWatch
|
|
IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
|
|
IN_ONESHOT uint32 = syscall.IN_ONESHOT
|
|
IN_ONLYDIR uint32 = syscall.IN_ONLYDIR
|
|
|
|
// The "IN_MASK_ADD" option is not exported, as AddWatch
|
|
// adds it automatically, if there is already a watch for the given path
|
|
// IN_MASK_ADD uint32 = syscall.IN_MASK_ADD
|
|
|
|
// Events
|
|
IN_ACCESS uint32 = syscall.IN_ACCESS
|
|
IN_ALL_EVENTS uint32 = syscall.IN_ALL_EVENTS
|
|
IN_ATTRIB uint32 = syscall.IN_ATTRIB
|
|
IN_CLOSE uint32 = syscall.IN_CLOSE
|
|
IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
|
|
IN_CLOSE_WRITE uint32 = syscall.IN_CLOSE_WRITE
|
|
IN_CREATE uint32 = syscall.IN_CREATE
|
|
IN_DELETE uint32 = syscall.IN_DELETE
|
|
IN_DELETE_SELF uint32 = syscall.IN_DELETE_SELF
|
|
IN_MODIFY uint32 = syscall.IN_MODIFY
|
|
IN_MOVE uint32 = syscall.IN_MOVE
|
|
IN_MOVED_FROM uint32 = syscall.IN_MOVED_FROM
|
|
IN_MOVED_TO uint32 = syscall.IN_MOVED_TO
|
|
IN_MOVE_SELF uint32 = syscall.IN_MOVE_SELF
|
|
IN_OPEN uint32 = syscall.IN_OPEN
|
|
|
|
// Special events
|
|
IN_ISDIR uint32 = syscall.IN_ISDIR
|
|
IN_IGNORED uint32 = syscall.IN_IGNORED
|
|
IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
|
|
IN_UNMOUNT uint32 = syscall.IN_UNMOUNT
|
|
)
|
|
|
|
var eventBits = []struct {
|
|
Value uint32
|
|
Name string
|
|
}{
|
|
{IN_ACCESS, "IN_ACCESS"},
|
|
{IN_ATTRIB, "IN_ATTRIB"},
|
|
{IN_CLOSE, "IN_CLOSE"},
|
|
{IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"},
|
|
{IN_CLOSE_WRITE, "IN_CLOSE_WRITE"},
|
|
{IN_CREATE, "IN_CREATE"},
|
|
{IN_DELETE, "IN_DELETE"},
|
|
{IN_DELETE_SELF, "IN_DELETE_SELF"},
|
|
{IN_MODIFY, "IN_MODIFY"},
|
|
{IN_MOVE, "IN_MOVE"},
|
|
{IN_MOVED_FROM, "IN_MOVED_FROM"},
|
|
{IN_MOVED_TO, "IN_MOVED_TO"},
|
|
{IN_MOVE_SELF, "IN_MOVE_SELF"},
|
|
{IN_OPEN, "IN_OPEN"},
|
|
{IN_ISDIR, "IN_ISDIR"},
|
|
{IN_IGNORED, "IN_IGNORED"},
|
|
{IN_Q_OVERFLOW, "IN_Q_OVERFLOW"},
|
|
{IN_UNMOUNT, "IN_UNMOUNT"},
|
|
}
|