Using realpath() in watch() caused events to be emitted under the resolved
path (e.g. /private/tmp/...) rather than the path the caller provided (e.g.
/tmp/..., where /tmp is a symlink on macOS). This broke all integration
tests on macOS since the test file paths didn't match the emitted event
paths.
For absolute paths, use them unchanged. For relative paths, join with the
current working directory without further symlink resolution. This
preserves the caller's view of the path while still satisfying the kqueue
backend's requirement for absolute paths.
kqueue's NOTE_WRITE on a directory fires only when directory entries are
added or removed, not when file contents change. This meant writes to
existing files were never reported as 'modified' events on macOS/FreeBSD.
Fix by maintaining a second set of kqueue watches on individual files
(file_watches), registered with NOTE_WRITE|NOTE_EXTEND. When either flag
fires on a file fd, a 'modified' event is emitted. File watches are
registered in take_snapshot (for files already present when watch() is
called) and in scan_dir (for newly created files), and deregistered when
files are deleted or the directory is unwatched.
Also fix two related bugs:
- NOTE_DELETE was incorrectly defined as 0x04 (NOTE_EXTEND); the correct
value is 0x01. This could cause NOTE_EXTEND events on watched directories
to be misreported as directory-deleted events.
- scan_dir emitted created events before deleted events, so a rename
(old name disappears, new name appears) reported the destination before
the source. Swapped the order so deletions are always emitted first.
Simplify thread_fn/arm to pass *KQueueBackend directly now that the backend
lives at a stable heap address inside the heap-allocated Interceptor.
All 10 integration tests now pass on FreeBSD.
Handler callbacks invoked while holding snapshots_mutex could deadlock if
the handler called watch() or unwatch(), which also acquires that lock.
Refactor scan_dir to collect all pending events (dir_created, created,
deleted) into temporary lists under the lock, then emit them after
releasing it. Also consolidate the two directory iteration passes into one.