github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/exp/fsnotify/fsnotify_linux.go (about) 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 package fsnotify 6 7 import ( 8 "errors" 9 "fmt" 10 "os" 11 "strings" 12 "sync" 13 "syscall" 14 "unsafe" 15 ) 16 17 const ( 18 // Options for inotify_init() are not exported 19 // sys_IN_CLOEXEC uint32 = syscall.IN_CLOEXEC 20 // sys_IN_NONBLOCK uint32 = syscall.IN_NONBLOCK 21 22 // Options for AddWatch 23 sys_IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW 24 sys_IN_ONESHOT uint32 = syscall.IN_ONESHOT 25 sys_IN_ONLYDIR uint32 = syscall.IN_ONLYDIR 26 27 // The "sys_IN_MASK_ADD" option is not exported, as AddWatch 28 // adds it automatically, if there is already a watch for the given path 29 // sys_IN_MASK_ADD uint32 = syscall.IN_MASK_ADD 30 31 // Events 32 sys_IN_ACCESS uint32 = syscall.IN_ACCESS 33 sys_IN_ALL_EVENTS uint32 = syscall.IN_ALL_EVENTS 34 sys_IN_ATTRIB uint32 = syscall.IN_ATTRIB 35 sys_IN_CLOSE uint32 = syscall.IN_CLOSE 36 sys_IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE 37 sys_IN_CLOSE_WRITE uint32 = syscall.IN_CLOSE_WRITE 38 sys_IN_CREATE uint32 = syscall.IN_CREATE 39 sys_IN_DELETE uint32 = syscall.IN_DELETE 40 sys_IN_DELETE_SELF uint32 = syscall.IN_DELETE_SELF 41 sys_IN_MODIFY uint32 = syscall.IN_MODIFY 42 sys_IN_MOVE uint32 = syscall.IN_MOVE 43 sys_IN_MOVED_FROM uint32 = syscall.IN_MOVED_FROM 44 sys_IN_MOVED_TO uint32 = syscall.IN_MOVED_TO 45 sys_IN_MOVE_SELF uint32 = syscall.IN_MOVE_SELF 46 sys_IN_OPEN uint32 = syscall.IN_OPEN 47 48 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 49 50 // Special events 51 sys_IN_ISDIR uint32 = syscall.IN_ISDIR 52 sys_IN_IGNORED uint32 = syscall.IN_IGNORED 53 sys_IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW 54 sys_IN_UNMOUNT uint32 = syscall.IN_UNMOUNT 55 ) 56 57 type FileEvent struct { 58 mask uint32 // Mask of events 59 cookie uint32 // Unique cookie associating related events (for rename(2)) 60 Name string // File name (optional) 61 } 62 63 // IsCreate reports whether the FileEvent was triggered by a creation 64 func (e *FileEvent) IsCreate() bool { 65 return (e.mask&sys_IN_CREATE) == sys_IN_CREATE || (e.mask&sys_IN_MOVED_TO) == sys_IN_MOVED_TO 66 } 67 68 // IsDelete reports whether the FileEvent was triggered by a delete 69 func (e *FileEvent) IsDelete() bool { 70 return (e.mask&sys_IN_DELETE_SELF) == sys_IN_DELETE_SELF || (e.mask&sys_IN_DELETE) == sys_IN_DELETE 71 } 72 73 // IsModify reports whether the FileEvent was triggered by a file modification or attribute change 74 func (e *FileEvent) IsModify() bool { 75 return ((e.mask&sys_IN_MODIFY) == sys_IN_MODIFY || (e.mask&sys_IN_ATTRIB) == sys_IN_ATTRIB) 76 } 77 78 // IsRename reports whether the FileEvent was triggered by a change name 79 func (e *FileEvent) IsRename() bool { 80 return ((e.mask&sys_IN_MOVE_SELF) == sys_IN_MOVE_SELF || (e.mask&sys_IN_MOVED_FROM) == sys_IN_MOVED_FROM) 81 } 82 83 // IsAttrib reports whether the FileEvent was triggered by a change in the file metadata. 84 func (e *FileEvent) IsAttrib() bool { 85 return (e.mask & sys_IN_ATTRIB) == sys_IN_ATTRIB 86 } 87 88 type watch struct { 89 wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) 90 flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) 91 } 92 93 type Watcher struct { 94 mu sync.Mutex // Map access 95 fd int // File descriptor (as returned by the inotify_init() syscall) 96 watches map[string]*watch // Map of inotify watches (key: path) 97 paths map[int]string // Map of watched paths (key: watch descriptor) 98 Error chan error // Errors are sent on this channel 99 Event chan *FileEvent // Events are returned on this channel 100 done chan bool // Channel for sending a "quit message" to the reader goroutine 101 isClosed bool // Set to true when Close() is first called 102 } 103 104 // NewWatcher creates and returns a new inotify instance using inotify_init(2) 105 func NewWatcher() (*Watcher, error) { 106 fd, errno := syscall.InotifyInit() 107 if fd == -1 { 108 return nil, os.NewSyscallError("inotify_init", errno) 109 } 110 w := &Watcher{ 111 fd: fd, 112 watches: make(map[string]*watch), 113 paths: make(map[int]string), 114 Event: make(chan *FileEvent), 115 Error: make(chan error), 116 done: make(chan bool, 1), 117 } 118 119 go w.readEvents() 120 return w, nil 121 } 122 123 // Close closes an inotify watcher instance 124 // It sends a message to the reader goroutine to quit and removes all watches 125 // associated with the inotify instance 126 func (w *Watcher) Close() error { 127 if w.isClosed { 128 return nil 129 } 130 w.isClosed = true 131 132 // Remove all watches 133 for path := range w.watches { 134 w.RemoveWatch(path) 135 } 136 137 // Send "quit" message to the reader goroutine 138 w.done <- true 139 140 return nil 141 } 142 143 // AddWatch adds path to the watched file set. 144 // The flags are interpreted as described in inotify_add_watch(2). 145 func (w *Watcher) addWatch(path string, flags uint32) error { 146 if w.isClosed { 147 return errors.New("inotify instance already closed") 148 } 149 150 w.mu.Lock() 151 watchEntry, found := w.watches[path] 152 w.mu.Unlock() 153 if found { 154 watchEntry.flags |= flags 155 flags |= syscall.IN_MASK_ADD 156 } 157 wd, errno := syscall.InotifyAddWatch(w.fd, path, flags) 158 if wd == -1 { 159 return errno 160 } 161 162 w.mu.Lock() 163 w.watches[path] = &watch{wd: uint32(wd), flags: flags} 164 w.paths[wd] = path 165 w.mu.Unlock() 166 167 return nil 168 } 169 170 // Watch adds path to the watched file set, watching all events. 171 func (w *Watcher) watch(path string) error { 172 return w.addWatch(path, sys_AGNOSTIC_EVENTS) 173 } 174 175 // RemoveWatch removes path from the watched file set. 176 func (w *Watcher) removeWatch(path string) error { 177 w.mu.Lock() 178 defer w.mu.Unlock() 179 watch, ok := w.watches[path] 180 if !ok { 181 return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path)) 182 } 183 success, errno := syscall.InotifyRmWatch(w.fd, watch.wd) 184 if success == -1 { 185 return os.NewSyscallError("inotify_rm_watch", errno) 186 } 187 delete(w.watches, path) 188 return nil 189 } 190 191 // readEvents reads from the inotify file descriptor, converts the 192 // received events into Event objects and sends them via the Event channel 193 func (w *Watcher) readEvents() { 194 var ( 195 buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events 196 n int // Number of bytes read with read() 197 errno error // Syscall errno 198 ) 199 200 for { 201 // See if there is a message on the "done" channel 202 select { 203 case <-w.done: 204 syscall.Close(w.fd) 205 close(w.Event) 206 close(w.Error) 207 return 208 default: 209 } 210 211 n, errno = syscall.Read(w.fd, buf[:]) 212 213 // If EOF is received 214 if n == 0 { 215 syscall.Close(w.fd) 216 close(w.Event) 217 close(w.Error) 218 return 219 } 220 221 if n < 0 { 222 w.Error <- os.NewSyscallError("read", errno) 223 continue 224 } 225 if n < syscall.SizeofInotifyEvent { 226 w.Error <- errors.New("inotify: short read in readEvents()") 227 continue 228 } 229 230 var offset uint32 = 0 231 // We don't know how many events we just read into the buffer 232 // While the offset points to at least one whole event... 233 for offset <= uint32(n-syscall.SizeofInotifyEvent) { 234 // Point "raw" to the event in the buffer 235 raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset])) 236 event := new(FileEvent) 237 event.mask = uint32(raw.Mask) 238 event.cookie = uint32(raw.Cookie) 239 nameLen := uint32(raw.Len) 240 // If the event happened to the watched directory or the watched file, the kernel 241 // doesn't append the filename to the event, but we would like to always fill the 242 // the "Name" field with a valid filename. We retrieve the path of the watch from 243 // the "paths" map. 244 w.mu.Lock() 245 event.Name = w.paths[int(raw.Wd)] 246 w.mu.Unlock() 247 if nameLen > 0 { 248 // Point "bytes" at the first byte of the filename 249 bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent])) 250 // The filename is padded with NUL bytes. TrimRight() gets rid of those. 251 event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") 252 } 253 254 // Send the events that are not ignored on the events channel 255 if !event.ignoreLinux() { 256 w.Event <- event 257 } 258 259 // Move to the next event in the buffer 260 offset += syscall.SizeofInotifyEvent + nameLen 261 } 262 } 263 } 264 265 // Certain types of events can be "ignored" and not sent over the Event 266 // channel. Such as events marked ignore by the kernel, or MODIFY events 267 // against files that do not exist. 268 func (e *FileEvent) ignoreLinux() bool { 269 // Ignore anything the inotify API says to ignore 270 if e.mask&sys_IN_IGNORED == sys_IN_IGNORED { 271 return true 272 } 273 274 // If the event is not a DELETE or RENAME, the file must exist. 275 // Otherwise the event is ignored. 276 // *Note*: this was put in place because it was seen that a MODIFY 277 // event was sent after the DELETE. This ignores that MODIFY and 278 // assumes a DELETE will come or has come if the file doesn't exist. 279 if !(e.IsDelete() || e.IsRename()) { 280 _, statErr := os.Lstat(e.Name) 281 return os.IsNotExist(statErr) 282 } 283 return false 284 }