notify-rs / notify

🔭 Cross-platform filesystem notification library for Rust.
https://docs.rs/notify
2.77k stars 222 forks source link

Non-recursive `KqueueWatcher` reports wrong event paths #644

Open gabi-250 opened 1 month ago

gabi-250 commented 1 month ago

System details

Note: the issue doesn't seem to be FreeBSD-specific (as far as I can tell, it happens on all platforms that use kqueue).

What you did (as detailed as you can)

I set up a non-recursive watcher for a directory that contains a single file foo. I created another file bar in the directory, expecting to receive an Event associated with the path of bar, but instead got an event for foo.

use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
use std::path::PathBuf;
use std::sync::mpsc;
use temp_dir::TempDir;

fn write_file(dir: &TempDir, name: &str, data: &[u8]) -> PathBuf {
    let path = dir.path().join(name);
    std::fs::write(&path, data).unwrap();
    path
}

fn main() {
    let temp_dir = TempDir::new().unwrap();

    // Prepopulate temp_dir with a file.
    let _: PathBuf = write_file(&temp_dir, "foo", b"hello");

    let (tx, rx) = mpsc::channel();
    let mut watcher =
        RecommendedWatcher::new(move |event| tx.send(event).unwrap(), Config::default()).unwrap();

    println!("watching {}", temp_dir.path().display());
    watcher
        .watch(temp_dir.path(), RecursiveMode::NonRecursive)
        .unwrap();

    // We haven't triggered any events since starting the watcher
    assert!(rx.try_recv().is_err());

    // Write a file to the watched directory.
    // This should trigger an event where one of the paths is
    // "<temp_dir>/bar".
    let expected_path = write_file(&temp_dir, "bar", b"good-bye");

    let ev = rx.recv().unwrap().unwrap();
    println!("{:#?}", ev);

    assert!(
        ev.paths.contains(&expected_path),
        "expected event for {}, but got event for {:?}?!",
        expected_path.display(),
        ev.paths,
    );

    // Note: as expected, writing the second file triggers a single event:
    //   * with the inotify backend, the event is associated with the newly created file "bar",
    //     as expected
    //   * with the kqueue backend, the event is wrongly associated with file "foo"
}

with Cargo.toml:

[package]
name = "notify-repro"
version = "0.1.0"
edition = "2021"

[dependencies]
notify = "6.1.1"
temp-dir = "0.1.14"

The assertion fails on platforms using the kqueue backend. With the inotify backend, it works as expected.

What you expected

watching /tmp/tb59-0
Event {
    kind: Create(
        File,
    ),
    paths: [
        "/tmp/tb59-0/bar",
    ],
    attr:tracker: None,
    attr:flag: None,
    attr:info: None,
    attr:source: None,
}

What happened

The assertion failed

watching /tmp/tb59-0
Event {
    kind: Create(
        File,
    ),
    paths: [
        "/tmp/tb59-0/foo",
    ],
    attr:tracker: None,
    attr:flag: None,
    attr:info: None,
    attr:source: None,
}
thread main panicked at src/main.rs:38:5:
expected event for /tmp/tb59-0/bar, but got event for ["/tmp/tb59-0/foo"]?!
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

I think the bug is in handle_kqueue's handling of kqueue::Vnode::Write:

I can try to come up with a patch to fix this, if you'd like.