backend_inotify.go (15446B)
1 //go:build linux && !appengine 2 3 package fsnotify 4 5 import ( 6 "errors" 7 "fmt" 8 "io" 9 "io/fs" 10 "os" 11 "path/filepath" 12 "strings" 13 "sync" 14 "time" 15 "unsafe" 16 17 "github.com/fsnotify/fsnotify/internal" 18 "golang.org/x/sys/unix" 19 ) 20 21 type inotify struct { 22 *shared 23 Events chan Event 24 Errors chan error 25 26 // Store fd here as os.File.Read() will no longer return on close after 27 // calling Fd(). See: https://github.com/golang/go/issues/26439 28 fd int 29 inotifyFile *os.File 30 watches *watches 31 doneResp chan struct{} // Channel to respond to Close 32 33 // Store rename cookies in an array, with the index wrapping to 0. Almost 34 // all of the time what we get is a MOVED_FROM to set the cookie and the 35 // next event inotify sends will be MOVED_TO to read it. However, this is 36 // not guaranteed – as described in inotify(7) – and we may get other events 37 // between the two MOVED_* events (including other MOVED_* ones). 38 // 39 // A second issue is that moving a file outside the watched directory will 40 // trigger a MOVED_FROM to set the cookie, but we never see the MOVED_TO to 41 // read and delete it. So just storing it in a map would slowly leak memory. 42 // 43 // Doing it like this gives us a simple fast LRU-cache that won't allocate. 44 // Ten items should be more than enough for our purpose, and a loop over 45 // such a short array is faster than a map access anyway (not that it hugely 46 // matters since we're talking about hundreds of ns at the most, but still). 47 cookies [10]koekje 48 cookieIndex uint8 49 cookiesMu sync.Mutex 50 } 51 52 type ( 53 watches struct { 54 wd map[uint32]*watch // wd → watch 55 path map[string]uint32 // pathname → wd 56 } 57 watch struct { 58 wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) 59 flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) 60 path string // Watch path. 61 watchFlags watchFlag 62 } 63 koekje struct { 64 cookie uint32 65 path string 66 } 67 ) 68 69 func (w watch) byUser() bool { return w.watchFlags&flagByUser != 0 } 70 func (w watch) recurse() bool { return w.watchFlags&flagRecurse != 0 } 71 72 func newWatches() *watches { 73 return &watches{ 74 wd: make(map[uint32]*watch), 75 path: make(map[string]uint32), 76 } 77 } 78 79 func (w *watches) byPath(path string) *watch { return w.wd[w.path[path]] } 80 func (w *watches) byWd(wd uint32) *watch { return w.wd[wd] } 81 func (w *watches) len() int { return len(w.wd) } 82 func (w *watches) add(ww *watch) { w.wd[ww.wd] = ww; w.path[ww.path] = ww.wd } 83 func (w *watches) remove(watch *watch) { delete(w.path, watch.path); delete(w.wd, watch.wd) } 84 85 func isSameOrDescendantPath(path, root string) bool { 86 if path == root { 87 return true 88 } 89 return strings.HasPrefix(path, root+string(os.PathSeparator)) 90 } 91 92 func (w *watches) removePath(path string) ([]uint32, error) { 93 path, recurse := recursivePath(path) 94 wd, ok := w.path[path] 95 if !ok { 96 return nil, fmt.Errorf("%w: %s", ErrNonExistentWatch, path) 97 } 98 99 watch := w.wd[wd] 100 if recurse && !watch.recurse() { 101 return nil, fmt.Errorf("can't use /... with non-recursive watch %q", path) 102 } 103 104 delete(w.path, path) 105 delete(w.wd, wd) 106 if !watch.recurse() { 107 return []uint32{wd}, nil 108 } 109 110 wds := make([]uint32, 0, 8) 111 wds = append(wds, wd) 112 for p, rwd := range w.path { 113 if isSameOrDescendantPath(p, path) { 114 delete(w.path, p) 115 delete(w.wd, rwd) 116 wds = append(wds, rwd) 117 } 118 } 119 return wds, nil 120 } 121 122 func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error { 123 var existing *watch 124 wd, ok := w.path[path] 125 if ok { 126 existing = w.wd[wd] 127 } 128 129 upd, err := f(existing) 130 if err != nil { 131 return err 132 } 133 if upd != nil { 134 w.wd[upd.wd] = upd 135 w.path[upd.path] = upd.wd 136 137 if upd.wd != wd { 138 delete(w.wd, wd) 139 } 140 } 141 142 return nil 143 } 144 145 var defaultBufferSize = 0 146 147 func newBackend(ev chan Event, errs chan error) (backend, error) { 148 // Need to set nonblocking mode for SetDeadline to work, otherwise blocking 149 // I/O operations won't terminate on close. 150 fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK) 151 if fd == -1 { 152 return nil, fmt.Errorf("couldn't initialize inotify: %w", errno) 153 } 154 155 w := &inotify{ 156 shared: newShared(ev, errs), 157 Events: ev, 158 Errors: errs, 159 fd: fd, 160 inotifyFile: os.NewFile(uintptr(fd), ""), 161 watches: newWatches(), 162 doneResp: make(chan struct{}), 163 } 164 165 go w.readEvents() 166 return w, nil 167 } 168 169 func (w *inotify) Close() error { 170 if w.shared.close() { 171 return nil 172 } 173 174 // Causes any blocking reads to return with an error, provided the file 175 // still supports deadline operations. 176 err := w.inotifyFile.Close() 177 if err != nil { 178 return err 179 } 180 181 <-w.doneResp // Wait for readEvents() to finish. 182 return nil 183 } 184 185 func (w *inotify) Add(name string) error { return w.AddWith(name) } 186 187 func (w *inotify) AddWith(path string, opts ...addOpt) error { 188 if w.isClosed() { 189 return ErrClosed 190 } 191 if debug { 192 fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s AddWith(%q)\n", 193 time.Now().Format("15:04:05.000000000"), path) 194 } 195 196 with := getOptions(opts...) 197 if !w.xSupports(with.op) { 198 return fmt.Errorf("%w: %s", xErrUnsupported, with.op) 199 } 200 201 add := func(path string, with withOpts, wf watchFlag) error { 202 var flags uint32 203 if with.op.Has(Create) { 204 flags |= unix.IN_CREATE 205 } 206 if with.op.Has(Write) { 207 flags |= unix.IN_MODIFY 208 } 209 if with.op.Has(Remove) { 210 flags |= unix.IN_DELETE | unix.IN_DELETE_SELF 211 } 212 if with.op.Has(Rename) { 213 flags |= unix.IN_MOVED_TO | unix.IN_MOVED_FROM | unix.IN_MOVE_SELF 214 } 215 if with.op.Has(Chmod) { 216 flags |= unix.IN_ATTRIB 217 } 218 if with.op.Has(xUnportableOpen) { 219 flags |= unix.IN_OPEN 220 } 221 if with.op.Has(xUnportableRead) { 222 flags |= unix.IN_ACCESS 223 } 224 if with.op.Has(xUnportableCloseWrite) { 225 flags |= unix.IN_CLOSE_WRITE 226 } 227 if with.op.Has(xUnportableCloseRead) { 228 flags |= unix.IN_CLOSE_NOWRITE 229 } 230 return w.register(path, flags, wf) 231 } 232 233 w.mu.Lock() 234 defer w.mu.Unlock() 235 path, recurse := recursivePath(path) 236 if recurse { 237 return filepath.WalkDir(path, func(root string, d fs.DirEntry, err error) error { 238 if err != nil { 239 return err 240 } 241 if !d.IsDir() { 242 if root == path { 243 return fmt.Errorf("fsnotify: not a directory: %q", path) 244 } 245 return nil 246 } 247 248 // Send a Create event when adding new directory from a recursive 249 // watch; this is for "mkdir -p one/two/three". Usually all those 250 // directories will be created before we can set up watchers on the 251 // subdirectories, so only "one" would be sent as a Create event and 252 // not "one/two" and "one/two/three" (inotifywait -r has the same 253 // problem). 254 if with.sendCreate && root != path { 255 w.sendEvent(Event{Name: root, Op: Create}) 256 } 257 258 wf := flagRecurse 259 if root == path { 260 wf |= flagByUser 261 } 262 return add(root, with, wf) 263 }) 264 } 265 266 return add(path, with, 0) 267 } 268 269 func (w *inotify) register(path string, flags uint32, wf watchFlag) error { 270 return w.watches.updatePath(path, func(existing *watch) (*watch, error) { 271 if existing != nil { 272 flags |= existing.flags | unix.IN_MASK_ADD 273 } 274 275 wd, err := unix.InotifyAddWatch(w.fd, path, flags) 276 if wd == -1 { 277 return nil, err 278 } 279 280 if e, ok := w.watches.wd[uint32(wd)]; ok { 281 return e, nil 282 } 283 284 if existing == nil { 285 return &watch{ 286 wd: uint32(wd), 287 path: path, 288 flags: flags, 289 watchFlags: wf, 290 }, nil 291 } 292 293 existing.wd = uint32(wd) 294 existing.flags = flags 295 return existing, nil 296 }) 297 } 298 299 func (w *inotify) Remove(name string) error { 300 if w.isClosed() { 301 return nil 302 } 303 if debug { 304 fmt.Fprintf(os.Stderr, "FSNOTIFY_DEBUG: %s Remove(%q)\n", 305 time.Now().Format("15:04:05.000000000"), name) 306 } 307 308 w.mu.Lock() 309 defer w.mu.Unlock() 310 return w.remove(filepath.Clean(name)) 311 } 312 313 func (w *inotify) remove(name string) error { 314 wds, err := w.watches.removePath(name) 315 if err != nil { 316 return err 317 } 318 319 for _, wd := range wds { 320 _, err := unix.InotifyRmWatch(w.fd, wd) 321 if err != nil { 322 // TODO: Perhaps it's not helpful to return an error here in every 323 // case; the only two possible errors are: 324 // 325 // EBADF, which happens when w.fd is not a valid file descriptor of 326 // any kind. 327 // 328 // EINVAL, which is when fd is not an inotify descriptor or wd is 329 // not a valid watch descriptor. Watch descriptors are invalidated 330 // when they are removed explicitly or implicitly; explicitly by 331 // inotify_rm_watch, implicitly when the file they are watching is 332 // deleted. 333 return err 334 } 335 } 336 return nil 337 } 338 339 func (w *inotify) WatchList() []string { 340 if w.isClosed() { 341 return nil 342 } 343 344 w.mu.Lock() 345 defer w.mu.Unlock() 346 entries := make([]string, 0, w.watches.len()) 347 for pathname := range w.watches.path { 348 entries = append(entries, pathname) 349 } 350 return entries 351 } 352 353 // readEvents reads from the inotify file descriptor, converts the 354 // received events into Event objects and sends them via the Events channel 355 func (w *inotify) readEvents() { 356 defer func() { 357 close(w.doneResp) 358 close(w.Errors) 359 close(w.Events) 360 }() 361 362 var buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events 363 for { 364 if w.isClosed() { 365 return 366 } 367 368 n, err := w.inotifyFile.Read(buf[:]) 369 if err != nil { 370 if errors.Is(err, os.ErrClosed) { 371 return 372 } 373 if !w.sendError(err) { 374 return 375 } 376 continue 377 } 378 379 if n < unix.SizeofInotifyEvent { 380 err := errors.New("notify: short read in readEvents()") // Read was too short. 381 if n == 0 { 382 err = io.EOF // If EOF is received. This should really never happen. 383 } 384 if !w.sendError(err) { 385 return 386 } 387 continue 388 } 389 390 // We don't know how many events we just read into the buffer While the 391 // offset points to at least one whole event. 392 var offset uint32 393 for offset <= uint32(n-unix.SizeofInotifyEvent) { 394 // Point to the event in the buffer. 395 inEvent := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset])) 396 397 if inEvent.Mask&unix.IN_Q_OVERFLOW != 0 { 398 if !w.sendError(ErrEventOverflow) { 399 return 400 } 401 } 402 403 ev, ok := w.handleEvent(inEvent, &buf, offset) 404 if !ok { 405 return 406 } 407 if !w.sendEvent(ev) { 408 return 409 } 410 411 // Move to the next event in the buffer 412 offset += unix.SizeofInotifyEvent + inEvent.Len 413 } 414 } 415 } 416 417 func (w *inotify) handleEvent(inEvent *unix.InotifyEvent, buf *[65536]byte, offset uint32) (Event, bool) { 418 w.mu.Lock() 419 defer w.mu.Unlock() 420 421 /// If the event happened to the watched directory or the watched file, the 422 /// kernel doesn't append the filename to the event, but we would like to 423 /// always fill the the "Name" field with a valid filename. We retrieve the 424 /// path of the watch from the "paths" map. 425 /// 426 /// Can be nil if Remove() was called in another goroutine for this path 427 /// inbetween reading the events from the kernel and reading the internal 428 /// state. Not much we can do about it, so just skip. See #616. 429 watch := w.watches.byWd(uint32(inEvent.Wd)) 430 if watch == nil { 431 return Event{}, true 432 } 433 434 var ( 435 name = watch.path 436 nameLen = uint32(inEvent.Len) 437 ) 438 if nameLen > 0 { 439 name += "/" + inotifyEventName(buf, offset, nameLen) 440 } 441 442 if debug { 443 internal.Debug(name, inEvent.Mask, inEvent.Cookie) 444 } 445 446 if inEvent.Mask&unix.IN_IGNORED != 0 || inEvent.Mask&unix.IN_UNMOUNT != 0 { 447 w.watches.remove(watch) 448 return Event{}, true 449 } 450 451 // inotify will automatically remove the watch on deletes; just need 452 // to clean our state here. 453 if inEvent.Mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF { 454 w.watches.remove(watch) 455 } 456 457 // We can't really update the state when a watched path is moved; only 458 // IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove the watch. 459 if inEvent.Mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF { 460 // Watch is set up as part of recurse: do nothing as the move gets 461 // registered from the parent directory. 462 if watch.recurse() && !watch.byUser() { 463 return Event{}, true 464 } 465 466 err := w.remove(watch.path) 467 if err != nil && !errors.Is(err, ErrNonExistentWatch) { 468 if !w.sendError(err) { 469 return Event{}, false 470 } 471 } 472 473 if watch.recurse() { 474 return Event{Name: watch.path, Op: Rename}, true 475 } 476 } 477 478 /// Skip if we're watching both this path and the parent; the parent will 479 /// already send a delete so no need to do it twice. 480 if inEvent.Mask&unix.IN_DELETE_SELF != 0 { 481 _, ok := w.watches.path[filepath.Dir(watch.path)] 482 if ok { 483 return Event{}, true 484 } 485 } 486 487 ev := w.newEvent(name, inEvent.Mask, inEvent.Cookie) 488 // Need to update watch path for recurse. 489 if watch.recurse() { 490 isDir := inEvent.Mask&unix.IN_ISDIR == unix.IN_ISDIR 491 /// New directory created: set up watch on it. 492 if isDir && ev.Has(Create) { 493 err := w.register(ev.Name, watch.flags, flagRecurse) 494 if !w.sendError(err) { 495 return Event{}, false 496 } 497 498 // This was a directory rename, so we need to update all the 499 // children. 500 // 501 // TODO: this is of course pretty slow; we should use a better data 502 // structure for storing all of this, e.g. store children in the 503 // watch. I have some code for this in my kqueue refactor we can use 504 // in the future. For now I'm okay with this as it's not publicly 505 // available. Correctness first, performance second. 506 if ev.renamedFrom != "" { 507 for k, ww := range w.watches.wd { 508 if k == watch.wd || ww.path == ev.Name { 509 continue 510 } 511 if isSameOrDescendantPath(ww.path, ev.renamedFrom) { 512 ww.path = strings.Replace(ww.path, ev.renamedFrom, ev.Name, 1) 513 w.watches.wd[k] = ww 514 } 515 } 516 } 517 } 518 } 519 520 return ev, true 521 } 522 523 func inotifyEventName(buf *[65536]byte, offset, nameLen uint32) string { 524 start := int(offset + unix.SizeofInotifyEvent) 525 bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[start]))[:nameLen:nameLen] 526 for nameLen > 0 && bytes[nameLen-1] == 0 { 527 nameLen-- 528 } 529 return string(bytes[:nameLen]) 530 } 531 532 func (w *inotify) newEvent(name string, mask, cookie uint32) Event { 533 e := Event{Name: name} 534 if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO { 535 e.Op |= Create 536 } 537 if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE { 538 e.Op |= Remove 539 } 540 if mask&unix.IN_MODIFY == unix.IN_MODIFY { 541 e.Op |= Write 542 } 543 if mask&unix.IN_OPEN == unix.IN_OPEN { 544 e.Op |= xUnportableOpen 545 } 546 if mask&unix.IN_ACCESS == unix.IN_ACCESS { 547 e.Op |= xUnportableRead 548 } 549 if mask&unix.IN_CLOSE_WRITE == unix.IN_CLOSE_WRITE { 550 e.Op |= xUnportableCloseWrite 551 } 552 if mask&unix.IN_CLOSE_NOWRITE == unix.IN_CLOSE_NOWRITE { 553 e.Op |= xUnportableCloseRead 554 } 555 if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM { 556 e.Op |= Rename 557 } 558 if mask&unix.IN_ATTRIB == unix.IN_ATTRIB { 559 e.Op |= Chmod 560 } 561 562 if cookie != 0 { 563 if mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM { 564 w.cookiesMu.Lock() 565 w.cookies[w.cookieIndex] = koekje{cookie: cookie, path: e.Name} 566 w.cookieIndex++ 567 if w.cookieIndex > 9 { 568 w.cookieIndex = 0 569 } 570 w.cookiesMu.Unlock() 571 } else if mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO { 572 w.cookiesMu.Lock() 573 var prev string 574 for _, c := range w.cookies { 575 if c.cookie == cookie { 576 prev = c.path 577 break 578 } 579 } 580 w.cookiesMu.Unlock() 581 e.renamedFrom = prev 582 } 583 } 584 return e 585 } 586 587 func (w *inotify) xSupports(op Op) bool { 588 return true // Supports everything. 589 } 590 591 func (w *inotify) state() { 592 w.mu.Lock() 593 defer w.mu.Unlock() 594 for wd, ww := range w.watches.wd { 595 fmt.Fprintf(os.Stderr, "%4d: %q watchFlags=0x%x\n", wd, ww.path, ww.watchFlags) 596 } 597 }