1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304
|
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package fsnotify
import (
"errors"
"fmt"
"os"
"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)
fsnFlags map[string]uint32 // Map of watched files to flags used for filter
fsnmut sync.Mutex // Protects access to fsnFlags.
paths map[int]string // Map of watched paths (key: watch descriptor)
Error chan error // Errors are sent on this channel
internalEvent chan *FileEvent // Events are queued 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),
fsnFlags: make(map[string]uint32),
paths: make(map[int]string),
internalEvent: make(chan *FileEvent),
Event: make(chan *FileEvent),
Error: make(chan error),
done: make(chan bool, 1),
}
go w.readEvents()
go w.purgeEvents()
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.internalEvent)
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.internalEvent)
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()
watchedName := event.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 events that are not ignored on the events channel
if !event.ignoreLinux() {
// Setup FSNotify flags (inherit from directory watch)
w.fsnmut.Lock()
if _, fsnFound := w.fsnFlags[event.Name]; !fsnFound {
if fsnFlags, watchFound := w.fsnFlags[watchedName]; watchFound {
w.fsnFlags[event.Name] = fsnFlags
} else {
w.fsnFlags[event.Name] = FSN_ALL
}
}
w.fsnmut.Unlock()
w.internalEvent <- 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
}
|