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