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