|
| 1 | +// Copyright 2010 The Go Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by a BSD-style |
| 3 | +// license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +/* |
| 6 | +Package inotify implements a wrapper for the Linux inotify system. |
| 7 | +
|
| 8 | +Example: |
| 9 | + watcher, err := inotify.NewWatcher() |
| 10 | + if err != nil { |
| 11 | + log.Fatal(err) |
| 12 | + } |
| 13 | + err = watcher.Watch("/tmp") |
| 14 | + if err != nil { |
| 15 | + log.Fatal(err) |
| 16 | + } |
| 17 | + for { |
| 18 | + select { |
| 19 | + case ev := <-watcher.Event: |
| 20 | + log.Println("event:", ev) |
| 21 | + case err := <-watcher.Error: |
| 22 | + log.Println("error:", err) |
| 23 | + } |
| 24 | + } |
| 25 | +
|
| 26 | +*/ |
| 27 | +package inotify // import "k8s.io/utils/inotify" |
| 28 | + |
| 29 | +import ( |
| 30 | + "errors" |
| 31 | + "fmt" |
| 32 | + "os" |
| 33 | + "strings" |
| 34 | + "sync" |
| 35 | + "syscall" |
| 36 | + "unsafe" |
| 37 | +) |
| 38 | + |
| 39 | +type Event struct { |
| 40 | + Mask uint32 // Mask of events |
| 41 | + Cookie uint32 // Unique cookie associating related events (for rename(2)) |
| 42 | + Name string // File name (optional) |
| 43 | +} |
| 44 | + |
| 45 | +type watch struct { |
| 46 | + wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) |
| 47 | + flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) |
| 48 | +} |
| 49 | + |
| 50 | +type Watcher struct { |
| 51 | + mu sync.Mutex |
| 52 | + fd int // File descriptor (as returned by the inotify_init() syscall) |
| 53 | + watches map[string]*watch // Map of inotify watches (key: path) |
| 54 | + paths map[int]string // Map of watched paths (key: watch descriptor) |
| 55 | + Error chan error // Errors are sent on this channel |
| 56 | + Event chan *Event // Events are returned on this channel |
| 57 | + done chan bool // Channel for sending a "quit message" to the reader goroutine |
| 58 | + isClosed bool // Set to true when Close() is first called |
| 59 | +} |
| 60 | + |
| 61 | +// NewWatcher creates and returns a new inotify instance using inotify_init(2) |
| 62 | +func NewWatcher() (*Watcher, error) { |
| 63 | + fd, errno := syscall.InotifyInit() |
| 64 | + if fd == -1 { |
| 65 | + return nil, os.NewSyscallError("inotify_init", errno) |
| 66 | + } |
| 67 | + w := &Watcher{ |
| 68 | + fd: fd, |
| 69 | + watches: make(map[string]*watch), |
| 70 | + paths: make(map[int]string), |
| 71 | + Event: make(chan *Event), |
| 72 | + Error: make(chan error), |
| 73 | + done: make(chan bool, 1), |
| 74 | + } |
| 75 | + |
| 76 | + go w.readEvents() |
| 77 | + return w, nil |
| 78 | +} |
| 79 | + |
| 80 | +// Close closes an inotify watcher instance |
| 81 | +// It sends a message to the reader goroutine to quit and removes all watches |
| 82 | +// associated with the inotify instance |
| 83 | +func (w *Watcher) Close() error { |
| 84 | + if w.isClosed { |
| 85 | + return nil |
| 86 | + } |
| 87 | + w.isClosed = true |
| 88 | + |
| 89 | + // Send "quit" message to the reader goroutine |
| 90 | + w.done <- true |
| 91 | + for path := range w.watches { |
| 92 | + w.RemoveWatch(path) |
| 93 | + } |
| 94 | + |
| 95 | + return nil |
| 96 | +} |
| 97 | + |
| 98 | +// AddWatch adds path to the watched file set. |
| 99 | +// The flags are interpreted as described in inotify_add_watch(2). |
| 100 | +func (w *Watcher) AddWatch(path string, flags uint32) error { |
| 101 | + if w.isClosed { |
| 102 | + return errors.New("inotify instance already closed") |
| 103 | + } |
| 104 | + |
| 105 | + watchEntry, found := w.watches[path] |
| 106 | + if found { |
| 107 | + watchEntry.flags |= flags |
| 108 | + flags |= syscall.IN_MASK_ADD |
| 109 | + } |
| 110 | + |
| 111 | + w.mu.Lock() // synchronize with readEvents goroutine |
| 112 | + |
| 113 | + wd, err := syscall.InotifyAddWatch(w.fd, path, flags) |
| 114 | + if err != nil { |
| 115 | + w.mu.Unlock() |
| 116 | + return &os.PathError{ |
| 117 | + Op: "inotify_add_watch", |
| 118 | + Path: path, |
| 119 | + Err: err, |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + if !found { |
| 124 | + w.watches[path] = &watch{wd: uint32(wd), flags: flags} |
| 125 | + w.paths[wd] = path |
| 126 | + } |
| 127 | + w.mu.Unlock() |
| 128 | + return nil |
| 129 | +} |
| 130 | + |
| 131 | +// Watch adds path to the watched file set, watching all events. |
| 132 | +func (w *Watcher) Watch(path string) error { |
| 133 | + return w.AddWatch(path, IN_ALL_EVENTS) |
| 134 | +} |
| 135 | + |
| 136 | +// RemoveWatch removes path from the watched file set. |
| 137 | +func (w *Watcher) RemoveWatch(path string) error { |
| 138 | + watch, ok := w.watches[path] |
| 139 | + if !ok { |
| 140 | + return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path)) |
| 141 | + } |
| 142 | + success, errno := syscall.InotifyRmWatch(w.fd, watch.wd) |
| 143 | + if success == -1 { |
| 144 | + return os.NewSyscallError("inotify_rm_watch", errno) |
| 145 | + } |
| 146 | + delete(w.watches, path) |
| 147 | + // Locking here to protect the read from paths in readEvents. |
| 148 | + w.mu.Lock() |
| 149 | + delete(w.paths, int(watch.wd)) |
| 150 | + w.mu.Unlock() |
| 151 | + return nil |
| 152 | +} |
| 153 | + |
| 154 | +// readEvents reads from the inotify file descriptor, converts the |
| 155 | +// received events into Event objects and sends them via the Event channel |
| 156 | +func (w *Watcher) readEvents() { |
| 157 | + var buf [syscall.SizeofInotifyEvent * 4096]byte |
| 158 | + |
| 159 | + for { |
| 160 | + n, err := syscall.Read(w.fd, buf[:]) |
| 161 | + // See if there is a message on the "done" channel |
| 162 | + var done bool |
| 163 | + select { |
| 164 | + case done = <-w.done: |
| 165 | + default: |
| 166 | + } |
| 167 | + |
| 168 | + // If EOF or a "done" message is received |
| 169 | + if n == 0 || done { |
| 170 | + // The syscall.Close can be slow. Close |
| 171 | + // w.Event first. |
| 172 | + close(w.Event) |
| 173 | + err := syscall.Close(w.fd) |
| 174 | + if err != nil { |
| 175 | + w.Error <- os.NewSyscallError("close", err) |
| 176 | + } |
| 177 | + close(w.Error) |
| 178 | + return |
| 179 | + } |
| 180 | + if n < 0 { |
| 181 | + w.Error <- os.NewSyscallError("read", err) |
| 182 | + continue |
| 183 | + } |
| 184 | + if n < syscall.SizeofInotifyEvent { |
| 185 | + w.Error <- errors.New("inotify: short read in readEvents()") |
| 186 | + continue |
| 187 | + } |
| 188 | + |
| 189 | + var offset uint32 = 0 |
| 190 | + // We don't know how many events we just read into the buffer |
| 191 | + // While the offset points to at least one whole event... |
| 192 | + for offset <= uint32(n-syscall.SizeofInotifyEvent) { |
| 193 | + // Point "raw" to the event in the buffer |
| 194 | + raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset])) |
| 195 | + event := new(Event) |
| 196 | + event.Mask = uint32(raw.Mask) |
| 197 | + event.Cookie = uint32(raw.Cookie) |
| 198 | + nameLen := uint32(raw.Len) |
| 199 | + // If the event happened to the watched directory or the watched file, the kernel |
| 200 | + // doesn't append the filename to the event, but we would like to always fill the |
| 201 | + // the "Name" field with a valid filename. We retrieve the path of the watch from |
| 202 | + // the "paths" map. |
| 203 | + w.mu.Lock() |
| 204 | + name, ok := w.paths[int(raw.Wd)] |
| 205 | + w.mu.Unlock() |
| 206 | + if ok { |
| 207 | + event.Name = name |
| 208 | + if nameLen > 0 { |
| 209 | + // Point "bytes" at the first byte of the filename |
| 210 | + bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent])) |
| 211 | + // The filename is padded with NUL bytes. TrimRight() gets rid of those. |
| 212 | + event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") |
| 213 | + } |
| 214 | + // Send the event on the events channel |
| 215 | + w.Event <- event |
| 216 | + } |
| 217 | + // Move to the next event in the buffer |
| 218 | + offset += syscall.SizeofInotifyEvent + nameLen |
| 219 | + } |
| 220 | + } |
| 221 | +} |
| 222 | + |
| 223 | +// String formats the event e in the form |
| 224 | +// "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..." |
| 225 | +func (e *Event) String() string { |
| 226 | + var events string = "" |
| 227 | + |
| 228 | + m := e.Mask |
| 229 | + for _, b := range eventBits { |
| 230 | + if m&b.Value == b.Value { |
| 231 | + m &^= b.Value |
| 232 | + events += "|" + b.Name |
| 233 | + } |
| 234 | + } |
| 235 | + |
| 236 | + if m != 0 { |
| 237 | + events += fmt.Sprintf("|%#x", m) |
| 238 | + } |
| 239 | + if len(events) > 0 { |
| 240 | + events = " == " + events[1:] |
| 241 | + } |
| 242 | + |
| 243 | + return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events) |
| 244 | +} |
| 245 | + |
| 246 | +const ( |
| 247 | + // Options for inotify_init() are not exported |
| 248 | + // IN_CLOEXEC uint32 = syscall.IN_CLOEXEC |
| 249 | + // IN_NONBLOCK uint32 = syscall.IN_NONBLOCK |
| 250 | + |
| 251 | + // Options for AddWatch |
| 252 | + IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW |
| 253 | + IN_ONESHOT uint32 = syscall.IN_ONESHOT |
| 254 | + IN_ONLYDIR uint32 = syscall.IN_ONLYDIR |
| 255 | + |
| 256 | + // The "IN_MASK_ADD" option is not exported, as AddWatch |
| 257 | + // adds it automatically, if there is already a watch for the given path |
| 258 | + // IN_MASK_ADD uint32 = syscall.IN_MASK_ADD |
| 259 | + |
| 260 | + // Events |
| 261 | + IN_ACCESS uint32 = syscall.IN_ACCESS |
| 262 | + IN_ALL_EVENTS uint32 = syscall.IN_ALL_EVENTS |
| 263 | + IN_ATTRIB uint32 = syscall.IN_ATTRIB |
| 264 | + IN_CLOSE uint32 = syscall.IN_CLOSE |
| 265 | + IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE |
| 266 | + IN_CLOSE_WRITE uint32 = syscall.IN_CLOSE_WRITE |
| 267 | + IN_CREATE uint32 = syscall.IN_CREATE |
| 268 | + IN_DELETE uint32 = syscall.IN_DELETE |
| 269 | + IN_DELETE_SELF uint32 = syscall.IN_DELETE_SELF |
| 270 | + IN_MODIFY uint32 = syscall.IN_MODIFY |
| 271 | + IN_MOVE uint32 = syscall.IN_MOVE |
| 272 | + IN_MOVED_FROM uint32 = syscall.IN_MOVED_FROM |
| 273 | + IN_MOVED_TO uint32 = syscall.IN_MOVED_TO |
| 274 | + IN_MOVE_SELF uint32 = syscall.IN_MOVE_SELF |
| 275 | + IN_OPEN uint32 = syscall.IN_OPEN |
| 276 | + |
| 277 | + // Special events |
| 278 | + IN_ISDIR uint32 = syscall.IN_ISDIR |
| 279 | + IN_IGNORED uint32 = syscall.IN_IGNORED |
| 280 | + IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW |
| 281 | + IN_UNMOUNT uint32 = syscall.IN_UNMOUNT |
| 282 | +) |
| 283 | + |
| 284 | +var eventBits = []struct { |
| 285 | + Value uint32 |
| 286 | + Name string |
| 287 | +}{ |
| 288 | + {IN_ACCESS, "IN_ACCESS"}, |
| 289 | + {IN_ATTRIB, "IN_ATTRIB"}, |
| 290 | + {IN_CLOSE, "IN_CLOSE"}, |
| 291 | + {IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"}, |
| 292 | + {IN_CLOSE_WRITE, "IN_CLOSE_WRITE"}, |
| 293 | + {IN_CREATE, "IN_CREATE"}, |
| 294 | + {IN_DELETE, "IN_DELETE"}, |
| 295 | + {IN_DELETE_SELF, "IN_DELETE_SELF"}, |
| 296 | + {IN_MODIFY, "IN_MODIFY"}, |
| 297 | + {IN_MOVE, "IN_MOVE"}, |
| 298 | + {IN_MOVED_FROM, "IN_MOVED_FROM"}, |
| 299 | + {IN_MOVED_TO, "IN_MOVED_TO"}, |
| 300 | + {IN_MOVE_SELF, "IN_MOVE_SELF"}, |
| 301 | + {IN_OPEN, "IN_OPEN"}, |
| 302 | + {IN_ISDIR, "IN_ISDIR"}, |
| 303 | + {IN_IGNORED, "IN_IGNORED"}, |
| 304 | + {IN_Q_OVERFLOW, "IN_Q_OVERFLOW"}, |
| 305 | + {IN_UNMOUNT, "IN_UNMOUNT"}, |
| 306 | +} |
0 commit comments