github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/exp/fsnotify/fsnotify_bsd.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 // +build freebsd openbsd netbsd darwin 6 7 package fsnotify 8 9 import ( 10 "errors" 11 "fmt" 12 "io/ioutil" 13 "os" 14 "path/filepath" 15 "sync" 16 "syscall" 17 ) 18 19 const ( 20 // Flags (from <sys/event.h>) 21 sys_NOTE_DELETE = 0x0001 /* vnode was removed */ 22 sys_NOTE_WRITE = 0x0002 /* data contents changed */ 23 sys_NOTE_EXTEND = 0x0004 /* size increased */ 24 sys_NOTE_ATTRIB = 0x0008 /* attributes changed */ 25 sys_NOTE_LINK = 0x0010 /* link count changed */ 26 sys_NOTE_RENAME = 0x0020 /* vnode was renamed */ 27 sys_NOTE_REVOKE = 0x0040 /* vnode access was revoked */ 28 29 // Watch all events 30 sys_NOTE_ALLEVENTS = sys_NOTE_DELETE | sys_NOTE_WRITE | sys_NOTE_ATTRIB | sys_NOTE_RENAME 31 32 // Block for 100 ms on each call to kevent 33 keventWaitTime = 100e6 34 ) 35 36 type FileEvent struct { 37 mask uint32 // Mask of events 38 Name string // File name (optional) 39 create bool // set by fsnotify package if found new file 40 } 41 42 // IsCreate reports whether the FileEvent was triggered by a creation 43 func (e *FileEvent) IsCreate() bool { return e.create } 44 45 // IsDelete reports whether the FileEvent was triggered by a delete 46 func (e *FileEvent) IsDelete() bool { return (e.mask & sys_NOTE_DELETE) == sys_NOTE_DELETE } 47 48 // IsModify reports whether the FileEvent was triggered by a file modification 49 func (e *FileEvent) IsModify() bool { 50 return ((e.mask&sys_NOTE_WRITE) == sys_NOTE_WRITE || (e.mask&sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB) 51 } 52 53 // IsRename reports whether the FileEvent was triggered by a change name 54 func (e *FileEvent) IsRename() bool { return (e.mask & sys_NOTE_RENAME) == sys_NOTE_RENAME } 55 56 // IsAttrib reports whether the FileEvent was triggered by a change in the file metadata. 57 func (e *FileEvent) IsAttrib() bool { 58 return (e.mask & sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB 59 } 60 61 type Watcher struct { 62 mu sync.Mutex // Mutex for the Watcher itself. 63 kq int // File descriptor (as returned by the kqueue() syscall) 64 watches map[string]int // Map of watched file descriptors (key: path) 65 wmut sync.Mutex // Protects access to watches. 66 enFlags map[string]uint32 // Map of watched files to evfilt note flags used in kqueue 67 enmut sync.Mutex // Protects access to enFlags. 68 paths map[int]string // Map of watched paths (key: watch descriptor) 69 finfo map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor) 70 pmut sync.Mutex // Protects access to paths and finfo. 71 fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events) 72 femut sync.Mutex // Protects access to fileExists. 73 externalWatches map[string]bool // Map of watches added by user of the library. 74 ewmut sync.Mutex // Protects access to externalWatches. 75 Error chan error // Errors are sent on this channel 76 Event chan *FileEvent // Events are returned on this channel 77 done chan bool // Channel for sending a "quit message" to the reader goroutine 78 isClosed bool // Set to true when Close() is first called 79 kbuf [1]syscall.Kevent_t // An event buffer for Add/Remove watch 80 bufmut sync.Mutex // Protects access to kbuf. 81 } 82 83 // NewWatcher creates and returns a new kevent instance using kqueue(2) 84 func NewWatcher() (*Watcher, error) { 85 fd, errno := syscall.Kqueue() 86 if fd == -1 { 87 return nil, os.NewSyscallError("kqueue", errno) 88 } 89 w := &Watcher{ 90 kq: fd, 91 watches: make(map[string]int), 92 enFlags: make(map[string]uint32), 93 paths: make(map[int]string), 94 finfo: make(map[int]os.FileInfo), 95 fileExists: make(map[string]bool), 96 externalWatches: make(map[string]bool), 97 Event: make(chan *FileEvent), 98 Error: make(chan error), 99 done: make(chan bool, 1), 100 } 101 102 go w.readEvents() 103 return w, nil 104 } 105 106 // Close closes a kevent watcher instance 107 // It sends a message to the reader goroutine to quit and removes all watches 108 // associated with the kevent instance 109 func (w *Watcher) Close() error { 110 w.mu.Lock() 111 if w.isClosed { 112 w.mu.Unlock() 113 return nil 114 } 115 w.isClosed = true 116 w.mu.Unlock() 117 118 // Send "quit" message to the reader goroutine 119 w.done <- true 120 w.pmut.Lock() 121 ws := w.watches 122 w.pmut.Unlock() 123 for path := range ws { 124 w.removeWatch(path) 125 } 126 127 return nil 128 } 129 130 // AddWatch adds path to the watched file set. 131 // The flags are interpreted as described in kevent(2). 132 func (w *Watcher) addWatch(path string, flags uint32) error { 133 w.mu.Lock() 134 if w.isClosed { 135 w.mu.Unlock() 136 return errors.New("kevent instance already closed") 137 } 138 w.mu.Unlock() 139 140 watchDir := false 141 142 w.wmut.Lock() 143 watchfd, found := w.watches[path] 144 w.wmut.Unlock() 145 if !found { 146 fi, errstat := os.Lstat(path) 147 if errstat != nil { 148 return errstat 149 } 150 151 // don't watch socket 152 if fi.Mode()&os.ModeSocket == os.ModeSocket { 153 return nil 154 } 155 156 // Follow Symlinks 157 // Unfortunately, Linux can add bogus symlinks to watch list without 158 // issue, and Windows can't do symlinks period (AFAIK). To maintain 159 // consistency, we will act like everything is fine. There will simply 160 // be no file events for broken symlinks. 161 // Hence the returns of nil on errors. 162 if fi.Mode()&os.ModeSymlink == os.ModeSymlink { 163 path, err := filepath.EvalSymlinks(path) 164 if err != nil { 165 return nil 166 } 167 168 fi, errstat = os.Lstat(path) 169 if errstat != nil { 170 return nil 171 } 172 } 173 174 fd, errno := syscall.Open(path, open_FLAGS, 0700) 175 if fd == -1 { 176 return errno 177 } 178 watchfd = fd 179 180 w.wmut.Lock() 181 w.watches[path] = watchfd 182 w.wmut.Unlock() 183 184 w.pmut.Lock() 185 w.paths[watchfd] = path 186 w.finfo[watchfd] = fi 187 w.pmut.Unlock() 188 } 189 // Watch the directory if it has not been watched before. 190 w.pmut.Lock() 191 w.enmut.Lock() 192 if w.finfo[watchfd].IsDir() && 193 (flags&sys_NOTE_WRITE) == sys_NOTE_WRITE && 194 (!found || (w.enFlags[path]&sys_NOTE_WRITE) != sys_NOTE_WRITE) { 195 watchDir = true 196 } 197 w.enmut.Unlock() 198 w.pmut.Unlock() 199 200 w.enmut.Lock() 201 w.enFlags[path] = flags 202 w.enmut.Unlock() 203 204 w.bufmut.Lock() 205 watchEntry := &w.kbuf[0] 206 watchEntry.Fflags = flags 207 syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_ADD|syscall.EV_CLEAR) 208 entryFlags := watchEntry.Flags 209 w.bufmut.Unlock() 210 211 wd, errno := syscall.Kevent(w.kq, w.kbuf[:], nil, nil) 212 if wd == -1 { 213 return errno 214 } else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR { 215 return errors.New("kevent add error") 216 } 217 218 if watchDir { 219 errdir := w.watchDirectoryFiles(path) 220 if errdir != nil { 221 return errdir 222 } 223 } 224 return nil 225 } 226 227 // Watch adds path to the watched file set, watching all events. 228 func (w *Watcher) watch(path string) error { 229 w.ewmut.Lock() 230 w.externalWatches[path] = true 231 w.ewmut.Unlock() 232 return w.addWatch(path, sys_NOTE_ALLEVENTS) 233 } 234 235 // RemoveWatch removes path from the watched file set. 236 func (w *Watcher) removeWatch(path string) error { 237 w.wmut.Lock() 238 watchfd, ok := w.watches[path] 239 w.wmut.Unlock() 240 if !ok { 241 return errors.New(fmt.Sprintf("can't remove non-existent kevent watch for: %s", path)) 242 } 243 w.bufmut.Lock() 244 watchEntry := &w.kbuf[0] 245 syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_DELETE) 246 success, errno := syscall.Kevent(w.kq, w.kbuf[:], nil, nil) 247 w.bufmut.Unlock() 248 if success == -1 { 249 return os.NewSyscallError("kevent_rm_watch", errno) 250 } else if (watchEntry.Flags & syscall.EV_ERROR) == syscall.EV_ERROR { 251 return errors.New("kevent rm error") 252 } 253 syscall.Close(watchfd) 254 w.wmut.Lock() 255 delete(w.watches, path) 256 w.wmut.Unlock() 257 w.enmut.Lock() 258 delete(w.enFlags, path) 259 w.enmut.Unlock() 260 w.pmut.Lock() 261 delete(w.paths, watchfd) 262 fInfo := w.finfo[watchfd] 263 delete(w.finfo, watchfd) 264 w.pmut.Unlock() 265 266 // Find all watched paths that are in this directory that are not external. 267 if fInfo.IsDir() { 268 var pathsToRemove []string 269 w.pmut.Lock() 270 for _, wpath := range w.paths { 271 wdir, _ := filepath.Split(wpath) 272 if filepath.Clean(wdir) == filepath.Clean(path) { 273 w.ewmut.Lock() 274 if !w.externalWatches[wpath] { 275 pathsToRemove = append(pathsToRemove, wpath) 276 } 277 w.ewmut.Unlock() 278 } 279 } 280 w.pmut.Unlock() 281 for _, p := range pathsToRemove { 282 // Since these are internal, not much sense in propagating error 283 // to the user, as that will just confuse them with an error about 284 // a path they did not explicitly watch themselves. 285 w.removeWatch(p) 286 } 287 } 288 289 return nil 290 } 291 292 // readEvents reads from the kqueue file descriptor, converts the 293 // received events into Event objects and sends them via the Event channel 294 func (w *Watcher) readEvents() { 295 var ( 296 eventbuf [10]syscall.Kevent_t // Event buffer 297 events []syscall.Kevent_t // Received events 298 twait *syscall.Timespec // Time to block waiting for events 299 n int // Number of events returned from kevent 300 errno error // Syscall errno 301 ) 302 events = eventbuf[0:0] 303 twait = new(syscall.Timespec) 304 *twait = syscall.NsecToTimespec(keventWaitTime) 305 306 for { 307 // See if there is a message on the "done" channel 308 var done bool 309 select { 310 case done = <-w.done: 311 default: 312 } 313 314 // If "done" message is received 315 if done { 316 errno := syscall.Close(w.kq) 317 if errno != nil { 318 w.Error <- os.NewSyscallError("close", errno) 319 } 320 close(w.Event) 321 close(w.Error) 322 return 323 } 324 325 // Get new events 326 if len(events) == 0 { 327 n, errno = syscall.Kevent(w.kq, nil, eventbuf[:], twait) 328 329 // EINTR is okay, basically the syscall was interrupted before 330 // timeout expired. 331 if errno != nil && errno != syscall.EINTR { 332 w.Error <- os.NewSyscallError("kevent", errno) 333 continue 334 } 335 336 // Received some events 337 if n > 0 { 338 events = eventbuf[0:n] 339 } 340 } 341 342 // Flush the events we received to the events channel 343 for len(events) > 0 { 344 fileEvent := new(FileEvent) 345 watchEvent := &events[0] 346 fileEvent.mask = uint32(watchEvent.Fflags) 347 w.pmut.Lock() 348 fileEvent.Name = w.paths[int(watchEvent.Ident)] 349 fileInfo := w.finfo[int(watchEvent.Ident)] 350 w.pmut.Unlock() 351 if fileInfo != nil && fileInfo.IsDir() && !fileEvent.IsDelete() { 352 // Double check to make sure the directory exist. This can happen when 353 // we do a rm -fr on a recursively watched folders and we receive a 354 // modification event first but the folder has been deleted and later 355 // receive the delete event 356 if _, err := os.Lstat(fileEvent.Name); os.IsNotExist(err) { 357 // mark is as delete event 358 fileEvent.mask |= sys_NOTE_DELETE 359 } 360 } 361 362 if fileInfo != nil && fileInfo.IsDir() && fileEvent.IsModify() && !fileEvent.IsDelete() { 363 w.sendDirectoryChangeEvents(fileEvent.Name) 364 } else { 365 // Send the event on the events channel 366 w.Event <- fileEvent 367 } 368 369 // Move to next event 370 events = events[1:] 371 372 if fileEvent.IsRename() { 373 w.removeWatch(fileEvent.Name) 374 w.femut.Lock() 375 delete(w.fileExists, fileEvent.Name) 376 w.femut.Unlock() 377 } 378 if fileEvent.IsDelete() { 379 w.removeWatch(fileEvent.Name) 380 w.femut.Lock() 381 delete(w.fileExists, fileEvent.Name) 382 w.femut.Unlock() 383 384 // Look for a file that may have overwritten this 385 // (ie mv f1 f2 will delete f2 then create f2) 386 fileDir, _ := filepath.Split(fileEvent.Name) 387 fileDir = filepath.Clean(fileDir) 388 w.wmut.Lock() 389 _, found := w.watches[fileDir] 390 w.wmut.Unlock() 391 if found { 392 // make sure the directory exist before we watch for changes. When we 393 // do a recursive watch and perform rm -fr, the parent directory might 394 // have gone missing, ignore the missing directory and let the 395 // upcoming delete event remove the watch form the parent folder 396 if _, err := os.Lstat(fileDir); !os.IsNotExist(err) { 397 w.sendDirectoryChangeEvents(fileDir) 398 } 399 } 400 } 401 } 402 } 403 } 404 405 func (w *Watcher) watchDirectoryFiles(dirPath string) error { 406 // Get all files 407 files, err := ioutil.ReadDir(dirPath) 408 if err != nil { 409 return err 410 } 411 412 // Search for new files 413 for _, fileInfo := range files { 414 filePath := filepath.Join(dirPath, fileInfo.Name()) 415 416 if fileInfo.IsDir() == false { 417 // Watch file to mimic linux fsnotify 418 e := w.addWatch(filePath, sys_NOTE_ALLEVENTS) 419 if e != nil { 420 return e 421 } 422 } else { 423 // If the user is currently watching directory 424 // we want to preserve the flags used 425 w.enmut.Lock() 426 currFlags, found := w.enFlags[filePath] 427 w.enmut.Unlock() 428 var newFlags uint32 = sys_NOTE_DELETE 429 if found { 430 newFlags |= currFlags 431 } 432 433 // Linux gives deletes if not explicitly watching 434 e := w.addWatch(filePath, newFlags) 435 if e != nil { 436 return e 437 } 438 } 439 w.femut.Lock() 440 w.fileExists[filePath] = true 441 w.femut.Unlock() 442 } 443 444 return nil 445 } 446 447 // sendDirectoryEvents searches the directory for newly created files 448 // and sends them over the event channel. This functionality is to have 449 // the BSD version of fsnotify match linux fsnotify which provides a 450 // create event for files created in a watched directory. 451 func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { 452 // Get all files 453 files, err := ioutil.ReadDir(dirPath) 454 if err != nil { 455 w.Error <- err 456 } 457 458 // Search for new files 459 for _, fileInfo := range files { 460 filePath := filepath.Join(dirPath, fileInfo.Name()) 461 w.femut.Lock() 462 _, doesExist := w.fileExists[filePath] 463 w.femut.Unlock() 464 if !doesExist { 465 // Send create event 466 fileEvent := new(FileEvent) 467 fileEvent.Name = filePath 468 fileEvent.create = true 469 w.Event <- fileEvent 470 } 471 w.femut.Lock() 472 w.fileExists[filePath] = true 473 w.femut.Unlock() 474 } 475 w.watchDirectoryFiles(dirPath) 476 }