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 dragonfly darwin 6 7package fsnotify 8 9import ( 10 "errors" 11 "fmt" 12 "io/ioutil" 13 "os" 14 "path/filepath" 15 "sync" 16 "time" 17 18 "golang.org/x/sys/unix" 19) 20 21// Watcher watches a set of files, delivering events to a channel. 22type Watcher struct { 23 Events chan Event 24 Errors chan error 25 done chan struct{} // Channel for sending a "quit message" to the reader goroutine 26 27 kq int // File descriptor (as returned by the kqueue() syscall). 28 29 mu sync.Mutex // Protects access to watcher data 30 watches map[string]int // Map of watched file descriptors (key: path). 31 externalWatches map[string]bool // Map of watches added by user of the library. 32 dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue. 33 paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events. 34 fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events). 35 isClosed bool // Set to true when Close() is first called 36} 37 38type pathInfo struct { 39 name string 40 isDir bool 41} 42 43// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. 44func NewWatcher() (*Watcher, error) { 45 kq, err := kqueue() 46 if err != nil { 47 return nil, err 48 } 49 50 w := &Watcher{ 51 kq: kq, 52 watches: make(map[string]int), 53 dirFlags: make(map[string]uint32), 54 paths: make(map[int]pathInfo), 55 fileExists: make(map[string]bool), 56 externalWatches: make(map[string]bool), 57 Events: make(chan Event), 58 Errors: make(chan error), 59 done: make(chan struct{}), 60 } 61 62 go w.readEvents() 63 return w, nil 64} 65 66// Close removes all watches and closes the events channel. 67func (w *Watcher) Close() error { 68 w.mu.Lock() 69 if w.isClosed { 70 w.mu.Unlock() 71 return nil 72 } 73 w.isClosed = true 74 75 // copy paths to remove while locked 76 var pathsToRemove = make([]string, 0, len(w.watches)) 77 for name := range w.watches { 78 pathsToRemove = append(pathsToRemove, name) 79 } 80 w.mu.Unlock() 81 // unlock before calling Remove, which also locks 82 83 for _, name := range pathsToRemove { 84 w.Remove(name) 85 } 86 87 // send a "quit" message to the reader goroutine 88 close(w.done) 89 90 return nil 91} 92 93// Add starts watching the named file or directory (non-recursively). 94func (w *Watcher) Add(name string) error { 95 w.mu.Lock() 96 w.externalWatches[name] = true 97 w.mu.Unlock() 98 _, err := w.addWatch(name, noteAllEvents) 99 return err 100} 101 102// Remove stops watching the the named file or directory (non-recursively). 103func (w *Watcher) Remove(name string) error { 104 name = filepath.Clean(name) 105 w.mu.Lock() 106 watchfd, ok := w.watches[name] 107 w.mu.Unlock() 108 if !ok { 109 return fmt.Errorf("can't remove non-existent kevent watch for: %s", name) 110 } 111 112 const registerRemove = unix.EV_DELETE 113 if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil { 114 return err 115 } 116 117 unix.Close(watchfd) 118 119 w.mu.Lock() 120 isDir := w.paths[watchfd].isDir 121 delete(w.watches, name) 122 delete(w.paths, watchfd) 123 delete(w.dirFlags, name) 124 w.mu.Unlock() 125 126 // Find all watched paths that are in this directory that are not external. 127 if isDir { 128 var pathsToRemove []string 129 w.mu.Lock() 130 for _, path := range w.paths { 131 wdir, _ := filepath.Split(path.name) 132 if filepath.Clean(wdir) == name { 133 if !w.externalWatches[path.name] { 134 pathsToRemove = append(pathsToRemove, path.name) 135 } 136 } 137 } 138 w.mu.Unlock() 139 for _, name := range pathsToRemove { 140 // Since these are internal, not much sense in propagating error 141 // to the user, as that will just confuse them with an error about 142 // a path they did not explicitly watch themselves. 143 w.Remove(name) 144 } 145 } 146 147 return nil 148} 149 150// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE) 151const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME 152 153// keventWaitTime to block on each read from kevent 154var keventWaitTime = durationToTimespec(100 * time.Millisecond) 155 156// addWatch adds name to the watched file set. 157// The flags are interpreted as described in kevent(2). 158// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks. 159func (w *Watcher) addWatch(name string, flags uint32) (string, error) { 160 var isDir bool 161 // Make ./name and name equivalent 162 name = filepath.Clean(name) 163 164 w.mu.Lock() 165 if w.isClosed { 166 w.mu.Unlock() 167 return "", errors.New("kevent instance already closed") 168 } 169 watchfd, alreadyWatching := w.watches[name] 170 // We already have a watch, but we can still override flags. 171 if alreadyWatching { 172 isDir = w.paths[watchfd].isDir 173 } 174 w.mu.Unlock() 175 176 if !alreadyWatching { 177 fi, err := os.Lstat(name) 178 if err != nil { 179 return "", err 180 } 181 182 // Don't watch sockets. 183 if fi.Mode()&os.ModeSocket == os.ModeSocket { 184 return "", nil 185 } 186 187 // Don't watch named pipes. 188 if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe { 189 return "", nil 190 } 191 192 // Follow Symlinks 193 // Unfortunately, Linux can add bogus symlinks to watch list without 194 // issue, and Windows can't do symlinks period (AFAIK). To maintain 195 // consistency, we will act like everything is fine. There will simply 196 // be no file events for broken symlinks. 197 // Hence the returns of nil on errors. 198 if fi.Mode()&os.ModeSymlink == os.ModeSymlink { 199 name, err = filepath.EvalSymlinks(name) 200 if err != nil { 201 return "", nil 202 } 203 204 w.mu.Lock() 205 _, alreadyWatching = w.watches[name] 206 w.mu.Unlock() 207 208 if alreadyWatching { 209 return name, nil 210 } 211 212 fi, err = os.Lstat(name) 213 if err != nil { 214 return "", nil 215 } 216 } 217 218 watchfd, err = unix.Open(name, openMode, 0700) 219 if watchfd == -1 { 220 return "", err 221 } 222 223 isDir = fi.IsDir() 224 } 225 226 const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE 227 if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil { 228 unix.Close(watchfd) 229 return "", err 230 } 231 232 if !alreadyWatching { 233 w.mu.Lock() 234 w.watches[name] = watchfd 235 w.paths[watchfd] = pathInfo{name: name, isDir: isDir} 236 w.mu.Unlock() 237 } 238 239 if isDir { 240 // Watch the directory if it has not been watched before, 241 // or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles) 242 w.mu.Lock() 243 244 watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE && 245 (!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE) 246 // Store flags so this watch can be updated later 247 w.dirFlags[name] = flags 248 w.mu.Unlock() 249 250 if watchDir { 251 if err := w.watchDirectoryFiles(name); err != nil { 252 return "", err 253 } 254 } 255 } 256 return name, nil 257} 258 259// readEvents reads from kqueue and converts the received kevents into 260// Event values that it sends down the Events channel. 261func (w *Watcher) readEvents() { 262 eventBuffer := make([]unix.Kevent_t, 10) 263 264loop: 265 for { 266 // See if there is a message on the "done" channel 267 select { 268 case <-w.done: 269 break loop 270 default: 271 } 272 273 // Get new events 274 kevents, err := read(w.kq, eventBuffer, &keventWaitTime) 275 // EINTR is okay, the syscall was interrupted before timeout expired. 276 if err != nil && err != unix.EINTR { 277 select { 278 case w.Errors <- err: 279 case <-w.done: 280 break loop 281 } 282 continue 283 } 284 285 // Flush the events we received to the Events channel 286 for len(kevents) > 0 { 287 kevent := &kevents[0] 288 watchfd := int(kevent.Ident) 289 mask := uint32(kevent.Fflags) 290 w.mu.Lock() 291 path := w.paths[watchfd] 292 w.mu.Unlock() 293 event := newEvent(path.name, mask) 294 295 if path.isDir && !(event.Op&Remove == Remove) { 296 // Double check to make sure the directory exists. This can happen when 297 // we do a rm -fr on a recursively watched folders and we receive a 298 // modification event first but the folder has been deleted and later 299 // receive the delete event 300 if _, err := os.Lstat(event.Name); os.IsNotExist(err) { 301 // mark is as delete event 302 event.Op |= Remove 303 } 304 } 305 306 if event.Op&Rename == Rename || event.Op&Remove == Remove { 307 w.Remove(event.Name) 308 w.mu.Lock() 309 delete(w.fileExists, event.Name) 310 w.mu.Unlock() 311 } 312 313 if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) { 314 w.sendDirectoryChangeEvents(event.Name) 315 } else { 316 // Send the event on the Events channel. 317 select { 318 case w.Events <- event: 319 case <-w.done: 320 break loop 321 } 322 } 323 324 if event.Op&Remove == Remove { 325 // Look for a file that may have overwritten this. 326 // For example, mv f1 f2 will delete f2, then create f2. 327 if path.isDir { 328 fileDir := filepath.Clean(event.Name) 329 w.mu.Lock() 330 _, found := w.watches[fileDir] 331 w.mu.Unlock() 332 if found { 333 // make sure the directory exists before we watch for changes. When we 334 // do a recursive watch and perform rm -fr, the parent directory might 335 // have gone missing, ignore the missing directory and let the 336 // upcoming delete event remove the watch from the parent directory. 337 if _, err := os.Lstat(fileDir); err == nil { 338 w.sendDirectoryChangeEvents(fileDir) 339 } 340 } 341 } else { 342 filePath := filepath.Clean(event.Name) 343 if fileInfo, err := os.Lstat(filePath); err == nil { 344 w.sendFileCreatedEventIfNew(filePath, fileInfo) 345 } 346 } 347 } 348 349 // Move to next event 350 kevents = kevents[1:] 351 } 352 } 353 354 // cleanup 355 err := unix.Close(w.kq) 356 if err != nil { 357 // only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors. 358 select { 359 case w.Errors <- err: 360 default: 361 } 362 } 363 close(w.Events) 364 close(w.Errors) 365} 366 367// newEvent returns an platform-independent Event based on kqueue Fflags. 368func newEvent(name string, mask uint32) Event { 369 e := Event{Name: name} 370 if mask&unix.NOTE_DELETE == unix.NOTE_DELETE { 371 e.Op |= Remove 372 } 373 if mask&unix.NOTE_WRITE == unix.NOTE_WRITE { 374 e.Op |= Write 375 } 376 if mask&unix.NOTE_RENAME == unix.NOTE_RENAME { 377 e.Op |= Rename 378 } 379 if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB { 380 e.Op |= Chmod 381 } 382 return e 383} 384 385func newCreateEvent(name string) Event { 386 return Event{Name: name, Op: Create} 387} 388 389// watchDirectoryFiles to mimic inotify when adding a watch on a directory 390func (w *Watcher) watchDirectoryFiles(dirPath string) error { 391 // Get all files 392 files, err := ioutil.ReadDir(dirPath) 393 if err != nil { 394 return err 395 } 396 397 for _, fileInfo := range files { 398 filePath := filepath.Join(dirPath, fileInfo.Name()) 399 filePath, err = w.internalWatch(filePath, fileInfo) 400 if err != nil { 401 return err 402 } 403 404 w.mu.Lock() 405 w.fileExists[filePath] = true 406 w.mu.Unlock() 407 } 408 409 return nil 410} 411 412// sendDirectoryEvents searches the directory for newly created files 413// and sends them over the event channel. This functionality is to have 414// the BSD version of fsnotify match Linux inotify which provides a 415// create event for files created in a watched directory. 416func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { 417 // Get all files 418 files, err := ioutil.ReadDir(dirPath) 419 if err != nil { 420 select { 421 case w.Errors <- err: 422 case <-w.done: 423 return 424 } 425 } 426 427 // Search for new files 428 for _, fileInfo := range files { 429 filePath := filepath.Join(dirPath, fileInfo.Name()) 430 err := w.sendFileCreatedEventIfNew(filePath, fileInfo) 431 432 if err != nil { 433 return 434 } 435 } 436} 437 438// sendFileCreatedEvent sends a create event if the file isn't already being tracked. 439func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) { 440 w.mu.Lock() 441 _, doesExist := w.fileExists[filePath] 442 w.mu.Unlock() 443 if !doesExist { 444 // Send create event 445 select { 446 case w.Events <- newCreateEvent(filePath): 447 case <-w.done: 448 return 449 } 450 } 451 452 // like watchDirectoryFiles (but without doing another ReadDir) 453 filePath, err = w.internalWatch(filePath, fileInfo) 454 if err != nil { 455 return err 456 } 457 458 w.mu.Lock() 459 w.fileExists[filePath] = true 460 w.mu.Unlock() 461 462 return nil 463} 464 465func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) { 466 if fileInfo.IsDir() { 467 // mimic Linux providing delete events for subdirectories 468 // but preserve the flags used if currently watching subdirectory 469 w.mu.Lock() 470 flags := w.dirFlags[name] 471 w.mu.Unlock() 472 473 flags |= unix.NOTE_DELETE | unix.NOTE_RENAME 474 return w.addWatch(name, flags) 475 } 476 477 // watch file to mimic Linux inotify 478 return w.addWatch(name, noteAllEvents) 479} 480 481// kqueue creates a new kernel event queue and returns a descriptor. 482func kqueue() (kq int, err error) { 483 kq, err = unix.Kqueue() 484 if kq == -1 { 485 return kq, err 486 } 487 return kq, nil 488} 489 490// register events with the queue 491func register(kq int, fds []int, flags int, fflags uint32) error { 492 changes := make([]unix.Kevent_t, len(fds)) 493 494 for i, fd := range fds { 495 // SetKevent converts int to the platform-specific types: 496 unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags) 497 changes[i].Fflags = fflags 498 } 499 500 // register the events 501 success, err := unix.Kevent(kq, changes, nil, nil) 502 if success == -1 { 503 return err 504 } 505 return nil 506} 507 508// read retrieves pending events, or waits until an event occurs. 509// A timeout of nil blocks indefinitely, while 0 polls the queue. 510func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) { 511 n, err := unix.Kevent(kq, nil, events, timeout) 512 if err != nil { 513 return nil, err 514 } 515 return events[0:n], nil 516} 517 518// durationToTimespec prepares a timeout value 519func durationToTimespec(d time.Duration) unix.Timespec { 520 return unix.NsecToTimespec(d.Nanoseconds()) 521} 522