diff --git a/Cargo.lock b/Cargo.lock index 9e3103998..8f528c0f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1525,7 +1525,6 @@ version = "0.0.0" dependencies = [ "allocator-api2", "assert2", - "bitflags 2.13.2", "bstr", "bumpalo", "bytemuck", diff --git a/crates/fspy/src/unix/syscall_handler/mod.rs b/crates/fspy/src/unix/syscall_handler/mod.rs index 6e3c0617a..3a4c7ce55 100644 --- a/crates/fspy/src/unix/syscall_handler/mod.rs +++ b/crates/fspy/src/unix/syscall_handler/mod.rs @@ -56,9 +56,9 @@ impl SyscallHandler { } self.ipc_sender.send(&PathAccess { mode: match flags & libc::O_ACCMODE { - libc::O_RDWR => AccessMode::READ | AccessMode::WRITE, - libc::O_WRONLY => AccessMode::WRITE, - _ => AccessMode::READ, + libc::O_RDWR => AccessMode::ReadWrite, + libc::O_WRONLY => AccessMode::Write, + _ => AccessMode::Read, }, path: path.as_os_str().into(), }); @@ -72,7 +72,7 @@ impl SyscallHandler { fn handle_open_dir(&mut self, caller: Caller, fd: Fd) -> io::Result<()> { let path = fd.get_path(caller)?; self.ipc_sender.send(&PathAccess { - mode: AccessMode::READ_DIR, + mode: AccessMode::ReadDir, path: OsStr::from_bytes(path.as_bytes()).into(), }); Ok(()) diff --git a/crates/fspy/tests/node_fs.rs b/crates/fspy/tests/node_fs.rs index 8e138ca46..9d8675eed 100644 --- a/crates/fspy/tests/node_fs.rs +++ b/crates/fspy/tests/node_fs.rs @@ -64,7 +64,7 @@ fn track_script( #[cfg_attr(not(target_env = "musl"), ignore = "requires node")] fn read_sync(runtime: &str) -> anyhow::Result<()> { let accesses = track_script(runtime, "try { fs.readFileSync('hello') } catch {}", &[])?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); + assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::Read); Ok(()) } @@ -76,7 +76,7 @@ fn read_sync(runtime: &str) -> anyhow::Result<()> { #[cfg_attr(not(target_env = "musl"), ignore = "requires node")] fn exist_sync(runtime: &str) -> anyhow::Result<()> { let accesses = track_script(runtime, "try { fs.existsSync('hello') } catch {}", &[])?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); + assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::Read); Ok(()) } @@ -88,7 +88,7 @@ fn exist_sync(runtime: &str) -> anyhow::Result<()> { #[cfg_attr(not(target_env = "musl"), ignore = "requires node")] fn stat_sync(runtime: &str) -> anyhow::Result<()> { let accesses = track_script(runtime, "try { fs.statSync('hello') } catch {}", &[])?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); + assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::Read); Ok(()) } @@ -104,7 +104,7 @@ fn create_read_stream(runtime: &str) -> anyhow::Result<()> { "try { fs.createReadStream('hello').on('error', () => {}) } catch {}", &[], )?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); + assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::Read); Ok(()) } @@ -122,7 +122,7 @@ fn create_write_stream(runtime: &str) -> anyhow::Result<()> { "try { fs.createWriteStream(process.argv.at(-1)).on('error', () => {}) } catch {}", &[file_path.as_os_str()], )?; - assert_contains(&accesses, file_path.as_path(), AccessMode::WRITE); + assert_contains(&accesses, file_path.as_path(), AccessMode::Write); Ok(()) } @@ -140,7 +140,7 @@ fn write_sync(runtime: &str) -> anyhow::Result<()> { "try { fs.writeFileSync(process.argv.at(-1), '') } catch {}", &[file_path.as_os_str()], )?; - assert_contains(&accesses, &file_path, AccessMode::WRITE); + assert_contains(&accesses, &file_path, AccessMode::Write); Ok(()) } @@ -152,7 +152,7 @@ fn write_sync(runtime: &str) -> anyhow::Result<()> { #[cfg_attr(not(target_env = "musl"), ignore = "requires node")] fn read_dir_sync(runtime: &str) -> anyhow::Result<()> { let accesses = track_script(runtime, "try { fs.readdirSync('.') } catch {}", &[])?; - assert_contains(&accesses, ¤t_dir().unwrap(), AccessMode::READ_DIR); + assert_contains(&accesses, ¤t_dir().unwrap(), AccessMode::ReadDir); Ok(()) } @@ -173,6 +173,6 @@ fn subprocess(runtime: &str) -> anyhow::Result<()> { &format!("try {{ child_process.spawnSync({cmd}, {{ stdio: 'ignore' }}) }} catch {{}}"), &[], )?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); + assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::Read); Ok(()) } diff --git a/crates/fspy/tests/node_sea.rs b/crates/fspy/tests/node_sea.rs index 3b432a6f0..a3332fa81 100644 --- a/crates/fspy/tests/node_sea.rs +++ b/crates/fspy/tests/node_sea.rs @@ -87,6 +87,6 @@ async fn node_sea() -> anyhow::Result<()> { command.spawn(tokio_util::sync::CancellationToken::new()).await?.wait_handle.await?; assert!(termination.status.success(), "{:?}", termination.status); let accesses = termination.path_accesses.expect("the test region holds every record"); - assert_contains(&accesses, &input, AccessMode::READ); + assert_contains(&accesses, &input, AccessMode::Read); Ok(()) } diff --git a/crates/fspy/tests/oxlint.rs b/crates/fspy/tests/oxlint.rs index 7c5391b3a..e3d797d5b 100644 --- a/crates/fspy/tests/oxlint.rs +++ b/crates/fspy/tests/oxlint.rs @@ -63,7 +63,7 @@ async fn oxlint_reads_js_file() -> anyhow::Result<()> { let accesses = track_oxlint(&tmpdir_path, &[]).await?; // Check that oxlint read the JS file - test_utils::assert_contains(&accesses, &js_file, AccessMode::READ); + test_utils::assert_contains(&accesses, &js_file, AccessMode::Read); Ok(()) } @@ -80,7 +80,7 @@ async fn oxlint_reads_directory() -> anyhow::Result<()> { // Check that oxlint read the directory to find JS files // This is the key check - if READ_DIR is not tracked, cache won't detect new files - test_utils::assert_contains(&accesses, &tmpdir_path, AccessMode::READ_DIR); + test_utils::assert_contains(&accesses, &tmpdir_path, AccessMode::ReadDir); Ok(()) } @@ -114,7 +114,7 @@ declare const _foo: Foo; let accesses = track_oxlint(&tmpdir_path, &["--type-aware"]).await?; // Check that oxlint read types.ts - test_utils::assert_contains(&accesses, &tmpdir_path.join("types.ts"), AccessMode::READ); + test_utils::assert_contains(&accesses, &tmpdir_path.join("types.ts"), AccessMode::Read); Ok(()) } diff --git a/crates/fspy/tests/rust_std.rs b/crates/fspy/tests/rust_std.rs index 5bdff9df0..ad11ee673 100644 --- a/crates/fspy/tests/rust_std.rs +++ b/crates/fspy/tests/rust_std.rs @@ -16,7 +16,7 @@ async fn open_read() -> anyhow::Result<()> { let _ = File::open("hello"); }) .await?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); + assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::Read); Ok(()) } @@ -30,7 +30,7 @@ async fn open_write() -> anyhow::Result<()> { let _ = OpenOptions::new().write(true).open(tmp_path_str); }) .await?; - assert_contains(&accesses, tmp_path.as_path(), AccessMode::WRITE); + assert_contains(&accesses, tmp_path.as_path(), AccessMode::Write); Ok(()) } @@ -46,7 +46,7 @@ async fn metadata() -> anyhow::Result<()> { let _ = fs::metadata(tmp_path_str); }) .await?; - assert_contains(&accesses, tmp_path.as_path(), AccessMode::READ); + assert_contains(&accesses, tmp_path.as_path(), AccessMode::Read); Ok(()) } @@ -72,7 +72,7 @@ async fn readdir() -> anyhow::Result<()> { let _ = std::fs::read_dir("hello_dir").unwrap().next(); }) .await?; - assert_contains(&accesses, tmpdir_path.join("hello_dir").as_path(), AccessMode::READ_DIR); + assert_contains(&accesses, tmpdir_path.join("hello_dir").as_path(), AccessMode::ReadDir); Ok(()) } @@ -99,7 +99,7 @@ async fn read_in_subprocess() -> anyhow::Result<()> { .unwrap(); }) .await?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); + assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::Read); Ok(()) } @@ -113,7 +113,7 @@ async fn read_program() -> anyhow::Result<()> { assert_contains( &accesses, current_dir().unwrap().join("not_exist.exe").as_path(), - AccessMode::READ, + AccessMode::Read, ); Ok(()) diff --git a/crates/fspy/tests/rust_tokio.rs b/crates/fspy/tests/rust_tokio.rs index 4c68526f9..0853f9624 100644 --- a/crates/fspy/tests/rust_tokio.rs +++ b/crates/fspy/tests/rust_tokio.rs @@ -17,7 +17,7 @@ async fn open_read() -> anyhow::Result<()> { ); }) .await?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); + assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::Read); Ok(()) } @@ -35,7 +35,7 @@ async fn open_write() -> anyhow::Result<()> { ); }) .await?; - assert_contains(&accesses, tmp_path.as_path(), AccessMode::WRITE); + assert_contains(&accesses, tmp_path.as_path(), AccessMode::Write); Ok(()) } @@ -56,7 +56,7 @@ async fn readdir() -> anyhow::Result<()> { ); }) .await?; - assert_contains(&accesses, tmpdir_path.join("hello_dir").as_path(), AccessMode::READ_DIR); + assert_contains(&accesses, tmpdir_path.join("hello_dir").as_path(), AccessMode::ReadDir); Ok(()) } @@ -88,7 +88,7 @@ async fn subprocess() -> anyhow::Result<()> { ); }) .await?; - assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); + assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::Read); Ok(()) } diff --git a/crates/fspy/tests/shebang.rs b/crates/fspy/tests/shebang.rs index 13d9513d4..1253ec83a 100644 --- a/crates/fspy/tests/shebang.rs +++ b/crates/fspy/tests/shebang.rs @@ -36,8 +36,8 @@ async fn spawn_sh_shebang() -> anyhow::Result<()> { }) .await?; - assert_contains(&accesses, Path::new(&shebang_script_path), AccessMode::READ); - assert_contains(&accesses, Path::new("/hello"), AccessMode::READ); + assert_contains(&accesses, Path::new(&shebang_script_path), AccessMode::Read); + assert_contains(&accesses, Path::new("/hello"), AccessMode::Read); Ok(()) } @@ -80,7 +80,7 @@ async fn spawn_shebang_with_long_interpreter_path() -> anyhow::Result<()> { }) .await?; - assert_contains(&accesses, &interpreter, AccessMode::READ); + assert_contains(&accesses, &interpreter, AccessMode::Read); Ok(()) } diff --git a/crates/fspy/tests/static_executable.rs b/crates/fspy/tests/static_executable.rs index e8cb5d61f..f9ccb3709 100644 --- a/crates/fspy/tests/static_executable.rs +++ b/crates/fspy/tests/static_executable.rs @@ -56,67 +56,59 @@ async fn track_test_bin(args: &[&str], cwd: Option<&str>) -> PathAccessIterable #[test(tokio::test)] async fn open_read() { let accesses = track_test_bin(&["open_read", "/hello"], None).await; - assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::READ); + assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::Read); } #[test(tokio::test)] async fn open_write() { let accesses = track_test_bin(&["open_write", "/hello"], None).await; - assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::WRITE); + assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::Write); } #[test(tokio::test)] async fn open_readwrite() { let accesses = track_test_bin(&["open_readwrite", "/hello"], None).await; - assert_contains( - &accesses, - Path::new("/hello"), - fspy::AccessMode::READ | fspy::AccessMode::WRITE, - ); + assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::ReadWrite); } #[test(tokio::test)] async fn openat2_read() { let accesses = track_test_bin(&["openat2_read", "/hello"], None).await; - assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::READ); + assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::Read); } #[test(tokio::test)] async fn openat2_write() { let accesses = track_test_bin(&["openat2_write", "/hello"], None).await; - assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::WRITE); + assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::Write); } #[test(tokio::test)] async fn openat2_readwrite() { let accesses = track_test_bin(&["openat2_readwrite", "/hello"], None).await; - assert_contains( - &accesses, - Path::new("/hello"), - fspy::AccessMode::READ | fspy::AccessMode::WRITE, - ); + assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::ReadWrite); } #[test(tokio::test)] async fn open_relative() { let accesses = track_test_bin(&["open_read", "hello"], Some("/home")).await; - assert_contains(&accesses, Path::new("/home/hello"), fspy::AccessMode::READ); + assert_contains(&accesses, Path::new("/home/hello"), fspy::AccessMode::Read); } #[test(tokio::test)] async fn readdir() { let accesses = track_test_bin(&["readdir", "/home"], None).await; - assert_contains(&accesses, Path::new("/home"), fspy::AccessMode::READ_DIR); + assert_contains(&accesses, Path::new("/home"), fspy::AccessMode::ReadDir); } #[test(tokio::test)] async fn stat() { let accesses = track_test_bin(&["stat", "/hello"], None).await; - assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::READ); + assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::Read); } #[test(tokio::test)] async fn execve() { let accesses = track_test_bin(&["execve", "/hello"], None).await; - assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::READ); + assert_contains(&accesses, Path::new("/hello"), fspy::AccessMode::Read); } diff --git a/crates/fspy/tests/test_utils/mod.rs b/crates/fspy/tests/test_utils/mod.rs index 13d915430..52fa452cb 100644 --- a/crates/fspy/tests/test_utils/mod.rs +++ b/crates/fspy/tests/test_utils/mod.rs @@ -1,4 +1,7 @@ -use std::path::{Path, PathBuf, StripPrefixError}; +use std::{ + collections::BTreeSet, + path::{Path, PathBuf, StripPrefixError}, +}; use fspy::{AccessMode, PathAccessIterable}; // Used by the track_child! macro; not all test files use this macro @@ -26,7 +29,7 @@ pub fn assert_contains( expected_path: &Path, expected_mode: AccessMode, ) { - let mut actual_mode: AccessMode = AccessMode::empty(); + let mut actual_modes = BTreeSet::new(); for access in accesses.iter() { let Ok(stripped) = access.path.strip_path_prefix::<_, Result, _>( @@ -37,18 +40,18 @@ pub fn assert_contains( continue; }; if stripped.as_os_str().is_empty() { - actual_mode.insert(access.mode); + actual_modes.insert(access.mode); } } - if actual_mode.contains(AccessMode::READ_DIR) { - // READ_DIR already implies READ. - actual_mode.remove(AccessMode::READ); + if actual_modes.contains(&AccessMode::ReadDir) { + // ReadDir already implies Read. + actual_modes.remove(&AccessMode::Read); } assert_eq!( - expected_mode, - actual_mode, + BTreeSet::from([expected_mode]), + actual_modes, "Expected to find access to path {} with mode {:?}, but it was not found in: {:?}", expected_path.display(), expected_mode, diff --git a/crates/fspy/tests/winapi.rs b/crates/fspy/tests/winapi.rs index 1bf9b4d9e..1b9fc125c 100644 --- a/crates/fspy/tests/winapi.rs +++ b/crates/fspy/tests/winapi.rs @@ -36,7 +36,7 @@ async fn create_process_a() -> anyhow::Result<()> { }; }) .await?; - assert_contains(&accesses, Path::new("C:\\fspy_test_not_exist_program.exe"), AccessMode::READ); + assert_contains(&accesses, Path::new("C:\\fspy_test_not_exist_program.exe"), AccessMode::Read); Ok(()) } @@ -69,7 +69,7 @@ async fn create_process_w() -> anyhow::Result<()> { }; }) .await?; - assert_contains(&accesses, Path::new("C:\\fspy_test_not_exist_program.exe"), AccessMode::READ); + assert_contains(&accesses, Path::new("C:\\fspy_test_not_exist_program.exe"), AccessMode::Read); Ok(()) } diff --git a/crates/fspy_client_unix/src/convert.rs b/crates/fspy_client_unix/src/convert.rs index 2466e20b1..53c3db99c 100644 --- a/crates/fspy_client_unix/src/convert.rs +++ b/crates/fspy_client_unix/src/convert.rs @@ -191,9 +191,9 @@ pub struct OpenFlags(pub c_int); impl ToAccessMode for OpenFlags { unsafe fn to_access_mode(self) -> AccessMode { match self.0 & libc::O_ACCMODE { - libc::O_RDWR => AccessMode::READ | AccessMode::WRITE, - libc::O_WRONLY => AccessMode::WRITE, - _ => AccessMode::READ, + libc::O_RDWR => AccessMode::ReadWrite, + libc::O_WRONLY => AccessMode::Write, + _ => AccessMode::Read, } } } @@ -207,9 +207,9 @@ impl ToAccessMode for ModeStr { let has_read = mode_str.contains(&b'r'); let has_write = mode_str.contains(&b'w') || mode_str.contains(&b'a'); match (has_read, has_write) { - (false, true) => AccessMode::WRITE, - (true, true) => AccessMode::READ | AccessMode::WRITE, - _ => AccessMode::READ, + (false, true) => AccessMode::Write, + (true, true) => AccessMode::ReadWrite, + _ => AccessMode::Read, } } } diff --git a/crates/fspy_e2e/src/main.rs b/crates/fspy_e2e/src/main.rs index ca3d7a25b..3054b8605 100644 --- a/crates/fspy_e2e/src/main.rs +++ b/crates/fspy_e2e/src/main.rs @@ -1,5 +1,5 @@ use std::{ - collections::{BTreeMap, btree_map::Entry}, + collections::{BTreeMap, BTreeSet}, env::{self, args}, fs::{File, read}, io::{BufWriter, Write as _, stderr}, @@ -25,7 +25,7 @@ struct Case { struct AccessCollector { dir: PathBuf, - accesses: BTreeMap, + accesses: BTreeMap>, } impl AccessCollector { @@ -33,8 +33,8 @@ impl AccessCollector { Self { dir, accesses: BTreeMap::new() } } - pub fn iter(&self) -> impl Iterator { - self.accesses.iter().map(|(k, v)| (k.as_str(), *v)) + pub fn iter(&self) -> impl Iterator)> { + self.accesses.iter().map(|(k, v)| (k.as_str(), v)) } pub fn add(&mut self, access: PathAccess) { @@ -43,15 +43,7 @@ impl AccessCollector { }) else { return; }; - match self.accesses.entry(relative_path) { - Entry::Vacant(vacant) => { - vacant.insert(access.mode); - } - Entry::Occupied(mut occupied) => { - let occupied_mode = occupied.get_mut(); - occupied_mode.insert(access.mode); - } - } + self.accesses.entry(relative_path).or_default().insert(access.mode); } } @@ -117,8 +109,8 @@ async fn main() { } let snap_file = File::create(manifest_dir.join(format!("snaps/{name}.txt"))).unwrap(); let mut snap_writer = BufWriter::new(snap_file); - for (path, mode) in collector.iter() { - writeln!(snap_writer, "{path}: {mode:?}").unwrap(); + for (path, modes) in collector.iter() { + writeln!(snap_writer, "{path}: {modes:?}").unwrap(); } } } diff --git a/crates/fspy_preload_unix/src/interceptions/access.rs b/crates/fspy_preload_unix/src/interceptions/access.rs index 32f743e81..c856a2bbf 100644 --- a/crates/fspy_preload_unix/src/interceptions/access.rs +++ b/crates/fspy_preload_unix/src/interceptions/access.rs @@ -10,7 +10,7 @@ intercept!(access(64): unsafe extern "C" fn(pathname: *const c_char, mode: c_int unsafe extern "C" fn access(pathname: *const c_char, mode: c_int) -> c_int { // SAFETY: pathname is a valid C string pointer provided by the caller of the interposed function unsafe { - handle_open(fspy_nostd::CStr::from_ptr(pathname.cast()), AccessMode::READ); + handle_open(fspy_nostd::CStr::from_ptr(pathname.cast()), AccessMode::Read); } // SAFETY: calling the original libc access() with the same arguments forwarded from the interposed function unsafe { access::original()(pathname, mode) } @@ -25,7 +25,7 @@ unsafe extern "C" fn faccessat( ) -> c_int { // SAFETY: dirfd and pathname are valid arguments provided by the caller of the interposed function unsafe { - handle_open(PathAt::borrow_raw(dirfd, pathname), AccessMode::READ); + handle_open(PathAt::borrow_raw(dirfd, pathname), AccessMode::Read); } // SAFETY: calling the original libc faccessat() with the same arguments forwarded from the interposed function unsafe { faccessat::original()(dirfd, pathname, mode, flags) } diff --git a/crates/fspy_preload_unix/src/interceptions/dirent.rs b/crates/fspy_preload_unix/src/interceptions/dirent.rs index ae2278450..e4d7c781e 100644 --- a/crates/fspy_preload_unix/src/interceptions/dirent.rs +++ b/crates/fspy_preload_unix/src/interceptions/dirent.rs @@ -17,7 +17,7 @@ unsafe extern "C" fn scandir( compar: *const c_void, ) -> c_int { // SAFETY: dirname is a valid C string pointer provided by the caller of the interposed function - unsafe { handle_open(fspy_nostd::CStr::from_ptr(dirname.cast()), AccessMode::READ_DIR) } + unsafe { handle_open(fspy_nostd::CStr::from_ptr(dirname.cast()), AccessMode::ReadDir) } // SAFETY: calling the original libc scandir() with the same arguments forwarded from the interposed function unsafe { scandir::original()(dirname, namelist, select, compar) } } @@ -39,7 +39,7 @@ mod macos_only { compar: *const c_void, ) -> c_int { // SAFETY: dirname is a valid C string pointer provided by the caller of the interposed function - unsafe { handle_open(fspy_nostd::CStr::from_ptr(dirname.cast()), AccessMode::READ_DIR) }; + unsafe { handle_open(fspy_nostd::CStr::from_ptr(dirname.cast()), AccessMode::ReadDir) }; // SAFETY: calling the original libc scandir_b() with the same arguments forwarded from the interposed function unsafe { scandir_b::original()(dirname, namelist, select, compar) } } @@ -52,7 +52,7 @@ mod macos_only { basep: *mut i64, ) -> isize { // SAFETY: fd is a valid file descriptor provided by the caller of __getdirentries64 - unsafe { handle_open(BorrowedFd::borrow_raw(fd), AccessMode::READ_DIR) }; + unsafe { handle_open(BorrowedFd::borrow_raw(fd), AccessMode::ReadDir) }; // SAFETY: calling the original libc __getdirentries64() with the same arguments forwarded from the interposed function unsafe { __getdirentries64::original()(fd, buf, buf_len, basep) } } @@ -66,7 +66,7 @@ unsafe extern "C" fn getdirentries( basep: *mut c_long, ) -> c_int { // SAFETY: fd is a valid file descriptor provided by the caller of the interposed function - unsafe { handle_open(BorrowedFd::borrow_raw(fd), AccessMode::READ_DIR) }; + unsafe { handle_open(BorrowedFd::borrow_raw(fd), AccessMode::ReadDir) }; // SAFETY: calling the original libc getdirentries() with the same arguments forwarded from the interposed function unsafe { getdirentries::original()(fd, buf, nbytes, basep) } } @@ -74,7 +74,7 @@ unsafe extern "C" fn getdirentries( intercept!(fdopendir(64): unsafe extern "C" fn (fd: c_int) -> *mut DIR); unsafe extern "C" fn fdopendir(fd: c_int) -> *mut DIR { // SAFETY: fd is a valid file descriptor provided by the caller of the interposed function - unsafe { handle_open(BorrowedFd::borrow_raw(fd), AccessMode::READ_DIR) }; + unsafe { handle_open(BorrowedFd::borrow_raw(fd), AccessMode::ReadDir) }; // SAFETY: calling the original libc fdopendir() with the same arguments forwarded from the interposed function unsafe { fdopendir::original()(fd) } } @@ -82,7 +82,7 @@ unsafe extern "C" fn fdopendir(fd: c_int) -> *mut DIR { intercept!(opendir(64): unsafe extern "C" fn (*const c_char) -> *mut DIR); unsafe extern "C" fn opendir(dir_name: *const c_char) -> *mut DIR { // SAFETY: dir_name is a valid C string pointer provided by the caller of the interposed function - unsafe { handle_open(fspy_nostd::CStr::from_ptr(dir_name.cast()), AccessMode::READ_DIR) }; + unsafe { handle_open(fspy_nostd::CStr::from_ptr(dir_name.cast()), AccessMode::ReadDir) }; // SAFETY: calling the original libc opendir() with the same arguments forwarded from the interposed function unsafe { opendir::original()(dir_name) } } diff --git a/crates/fspy_preload_unix/src/interceptions/linux_syscall.rs b/crates/fspy_preload_unix/src/interceptions/linux_syscall.rs index edfa972cd..8a22bfb17 100644 --- a/crates/fspy_preload_unix/src/interceptions/linux_syscall.rs +++ b/crates/fspy_preload_unix/src/interceptions/linux_syscall.rs @@ -39,12 +39,12 @@ unsafe extern "C" fn syscall(syscall_no: c_long, mut args: ...) -> c_long { if pathname.is_null() { if flags & libc::AT_EMPTY_PATH != 0 { // SAFETY: dirfd is provided by the statx syscall caller. - unsafe { handle_open(BorrowedFd::borrow_raw(dirfd), AccessMode::READ) }; + unsafe { handle_open(BorrowedFd::borrow_raw(dirfd), AccessMode::Read) }; } } else { // SAFETY: pathname is a non-null C string pointer provided by the statx syscall caller. unsafe { - handle_open(PathAt::borrow_raw(dirfd, pathname), AccessMode::READ); + handle_open(PathAt::borrow_raw(dirfd, pathname), AccessMode::Read); }; } } diff --git a/crates/fspy_preload_unix/src/interceptions/stat.rs b/crates/fspy_preload_unix/src/interceptions/stat.rs index ea34ceff6..050288500 100644 --- a/crates/fspy_preload_unix/src/interceptions/stat.rs +++ b/crates/fspy_preload_unix/src/interceptions/stat.rs @@ -12,7 +12,7 @@ intercept!(stat(64): unsafe extern "C" fn(path: *const c_char, buf: *mut stat_st unsafe extern "C" fn stat(path: *const c_char, buf: *mut stat_struct) -> c_int { // SAFETY: path is a valid C string pointer provided by the caller of the interposed function unsafe { - handle_open(fspy_nostd::CStr::from_ptr(path.cast()), AccessMode::READ); + handle_open(fspy_nostd::CStr::from_ptr(path.cast()), AccessMode::Read); } // SAFETY: calling the original libc stat() with the same arguments forwarded from the interposed function unsafe { stat::original()(path, buf) } @@ -23,7 +23,7 @@ unsafe extern "C" fn lstat(path: *const c_char, buf: *mut stat_struct) -> c_int // TODO: add accessmode ReadNoFollow // SAFETY: path is a valid C string pointer provided by the caller of the interposed function unsafe { - handle_open(fspy_nostd::CStr::from_ptr(path.cast()), AccessMode::READ); + handle_open(fspy_nostd::CStr::from_ptr(path.cast()), AccessMode::Read); } // SAFETY: calling the original libc lstat() with the same arguments forwarded from the interposed function unsafe { lstat::original()(path, buf) } @@ -38,7 +38,7 @@ unsafe extern "C" fn fstatat( ) -> c_int { // SAFETY: dirfd and pathname are valid arguments provided by the caller of the interposed function unsafe { - handle_open(PathAt::borrow_raw(dirfd, pathname), AccessMode::READ); + handle_open(PathAt::borrow_raw(dirfd, pathname), AccessMode::Read); } // SAFETY: calling the original libc fstatat() with the same arguments forwarded from the interposed function unsafe { fstatat::original()(dirfd, pathname, buf, flags) } @@ -71,11 +71,11 @@ unsafe extern "C" fn statx( if pathname.is_null() { if flags & libc::AT_EMPTY_PATH != 0 { // SAFETY: dirfd is provided by the statx caller. - unsafe { handle_open(BorrowedFd::borrow_raw(dirfd), AccessMode::READ) }; + unsafe { handle_open(BorrowedFd::borrow_raw(dirfd), AccessMode::Read) }; } } else { // SAFETY: pathname is a non-null C string pointer provided by the statx caller. - unsafe { handle_open(PathAt::borrow_raw(dirfd, pathname), AccessMode::READ) }; + unsafe { handle_open(PathAt::borrow_raw(dirfd, pathname), AccessMode::Read) }; } // SAFETY: calling the original libc statx() with the same arguments forwarded from the interposed function unsafe { original(dirfd, pathname, flags, mask, statxbuf) } diff --git a/crates/fspy_preload_windows/src/windows/detours/nt.rs b/crates/fspy_preload_windows/src/windows/detours/nt.rs index 968aea178..4ca71bc3b 100644 --- a/crates/fspy_preload_windows/src/windows/detours/nt.rs +++ b/crates/fspy_preload_windows/src/windows/detours/nt.rs @@ -100,7 +100,7 @@ unsafe fn handle_process_image(attribute_list: PPS_ATTRIBUTE_LIST) { // the borrowed PS_ATTRIBUTE_IMAGE_NAME buffer past the NtCreateUserProcess call. // SAFETY: accessing the global client which was initialized during DLL_PROCESS_ATTACH unsafe { global_client() } - .send(PathAccess { mode: AccessMode::READ, path: IpcPath::from_wide(image_path) }); + .send(PathAccess { mode: AccessMode::Read, path: IpcPath::from_wide(image_path) }); } } @@ -271,7 +271,7 @@ static DETOUR_NT_QUERY_ATTRIBUTES_FILE: Detour< file_information: PFILE_BASIC_INFORMATION, ) -> HFILE { // SAFETY: intercepting attribute query to record read access - unsafe { handle_open(AccessMode::READ, object_attributes) }; + unsafe { handle_open(AccessMode::Read, object_attributes) }; // SAFETY: calling the original NtQueryAttributesFile with all original arguments unsafe { (DETOUR_NT_QUERY_ATTRIBUTES_FILE.real())(object_attributes, file_information) @@ -306,7 +306,7 @@ unsafe fn handle_open(access_mode: impl ToAccessMode, path: impl ToAbsolutePath) .rposition(|c| *c == u16::from(b'\\') || *c == u16::from(b'/')) .unwrap_or(0); PathAccess { - mode: AccessMode::READ_DIR, + mode: AccessMode::ReadDir, path: IpcPath::from_wide(&path[..slash_pos]), } }, @@ -443,7 +443,7 @@ static DETOUR_NT_QUERY_DIRECTORY_FILE: Detour< restart_scan: BOOLEAN, ) -> NTSTATUS { // SAFETY: intercepting directory query to record directory read access - unsafe { handle_open(AccessMode::READ_DIR, file_handle) }; + unsafe { handle_open(AccessMode::ReadDir, file_handle) }; // SAFETY: calling the original NtQueryDirectoryFile unsafe { (DETOUR_NT_QUERY_DIRECTORY_FILE.real())( @@ -497,7 +497,7 @@ static DETOUR_NT_QUERY_DIRECTORY_FILE_EX: Detour = file_name: PUNICODE_STRING, ) -> NTSTATUS { // SAFETY: intercepting directory query to record directory read access - unsafe { handle_open(AccessMode::READ_DIR, file_handle) }; + unsafe { handle_open(AccessMode::ReadDir, file_handle) }; // SAFETY: calling the original NtQueryDirectoryFileEx unsafe { (DETOUR_NT_QUERY_DIRECTORY_FILE_EX.real())( diff --git a/crates/fspy_preload_windows/src/windows/winapi_utils.rs b/crates/fspy_preload_windows/src/windows/winapi_utils.rs index 38eba1c19..9acd8126c 100644 --- a/crates/fspy_preload_windows/src/windows/winapi_utils.rs +++ b/crates/fspy_preload_windows/src/windows/winapi_utils.rs @@ -97,9 +97,9 @@ pub const fn access_mask_to_mode(desired_access: ACCESS_MASK) -> AccessMode { let has_write = (desired_access & (FILE_WRITE_DATA | FILE_APPEND_DATA | GENERIC_WRITE)) != 0; let has_read = (desired_access & (FILE_READ_DATA | GENERIC_READ)) != 0; if has_write { - if has_read { AccessMode::READ.union(AccessMode::WRITE) } else { AccessMode::WRITE } + if has_read { AccessMode::ReadWrite } else { AccessMode::Write } } else { - AccessMode::READ + AccessMode::Read } } diff --git a/crates/fspy_shared/Cargo.toml b/crates/fspy_shared/Cargo.toml index 97f75416d..0f055799d 100644 --- a/crates/fspy_shared/Cargo.toml +++ b/crates/fspy_shared/Cargo.toml @@ -8,7 +8,6 @@ publish = false [dependencies] wincode = { workspace = true, features = ["derive"] } allocator-api2 = { workspace = true, features = ["alloc"] } -bitflags = { workspace = true } bumpalo = { workspace = true } bstr = { workspace = true, features = ["alloc", "std"] } bytemuck = { workspace = true, features = ["must_cast", "derive"] } diff --git a/crates/fspy_shared/src/ipc/channel/mod.rs b/crates/fspy_shared/src/ipc/channel/mod.rs index 5ea29c386..fbf516607 100644 --- a/crates/fspy_shared/src/ipc/channel/mod.rs +++ b/crates/fspy_shared/src/ipc/channel/mod.rs @@ -458,7 +458,7 @@ mod tests { for path in paths { let access: PathAccess<'_> = wincode::deserialize_exact(iter.next().unwrap()).unwrap(); assert!(access.path == path); - assert!(access.mode == AccessMode::READ); + assert!(access.mode == AccessMode::Read); } assert!(iter.next().is_none()); } diff --git a/crates/fspy_shared/src/ipc/mod.rs b/crates/fspy_shared/src/ipc/mod.rs index 7e90a4333..1a0e8e656 100644 --- a/crates/fspy_shared/src/ipc/mod.rs +++ b/crates/fspy_shared/src/ipc/mod.rs @@ -1,33 +1,18 @@ pub mod channel; mod ipc_path; -use std::fmt::Debug; +use std::iter; -use bitflags::bitflags; pub use ipc_path::IpcPath; pub use vt_ipc_str::IpcStr; use wincode::{SchemaRead, SchemaWrite}; -#[derive(SchemaWrite, SchemaRead, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub struct AccessMode(u8); - -bitflags! { - impl AccessMode: u8 { - const READ = 1; - const WRITE = 1 << 1; - const READ_DIR = 1 << 2; - } -} - -impl Debug for AccessMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - struct InternalAccessMode(AccessMode); - impl Debug for InternalAccessMode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - bitflags::parser::to_writer(&self.0, f) - } - } - f.debug_tuple("AccessMode").field(&InternalAccessMode(*self)).finish() - } +#[derive(SchemaWrite, SchemaRead, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +#[wincode(tag_encoding = "u8")] +pub enum AccessMode { + Read, + Write, + ReadWrite, + ReadDir, } #[derive(SchemaWrite, SchemaRead, Debug, Clone, Copy, PartialEq, Eq)] @@ -39,10 +24,15 @@ pub struct PathAccess<'a> { impl<'a> PathAccess<'a> { pub fn read(path: impl Into<&'a IpcPath>) -> Self { - Self { mode: AccessMode::READ, path: path.into() } + Self { mode: AccessMode::Read, path: path.into() } } pub fn read_dir(path: impl Into<&'a IpcPath>) -> Self { - Self { mode: AccessMode::READ_DIR, path: path.into() } + Self { mode: AccessMode::ReadDir, path: path.into() } + } + + /// The paths this access touched. + pub fn paths(self) -> impl Iterator { + iter::once(self.path) } } diff --git a/crates/fspy_shared_unix/src/exec/mod.rs b/crates/fspy_shared_unix/src/exec/mod.rs index 094cd6967..7bef16435 100644 --- a/crates/fspy_shared_unix/src/exec/mod.rs +++ b/crates/fspy_shared_unix/src/exec/mod.rs @@ -119,7 +119,7 @@ impl Exec { self.program.as_ref(), path, |path| { - on_path_access(AccessMode::READ, Path::new(OsStr::from_bytes(path))); + on_path_access(AccessMode::Read, Path::new(OsStr::from_bytes(path))); access(OsStr::from_bytes(path), AccessFlags::X_OK) }, |program| Ok(program.to_owned()), @@ -139,7 +139,7 @@ impl Exec { ) -> nix::Result<()> { if let Some(shebang) = parse_shebang( |path, buf| { - on_path_access(AccessMode::READ, path); + on_path_access(AccessMode::Read, path); peek_executable(path, buf) }, Path::new(OsStr::from_bytes(&self.program)), diff --git a/crates/fspy_shared_unix/src/spawn/mod.rs b/crates/fspy_shared_unix/src/spawn/mod.rs index e48125772..a4fabc0ff 100644 --- a/crates/fspy_shared_unix/src/spawn/mod.rs +++ b/crates/fspy_shared_unix/src/spawn/mod.rs @@ -50,7 +50,7 @@ pub fn handle_exec( }; command.resolve(&mut on_path_access, config)?; - on_path_access(AccessMode::READ, Path::new(OsStr::from_bytes(&command.program))); + on_path_access(AccessMode::Read, Path::new(OsStr::from_bytes(&command.program))); os_specific::handle_exec(command, encoded_payload) } diff --git a/crates/vt/src/session/execute/tracked_accesses.rs b/crates/vt/src/session/execute/tracked_accesses.rs index 6f43a469e..91e8a22d0 100644 --- a/crates/vt/src/session/execute/tracked_accesses.rs +++ b/crates/vt/src/session/execute/tracked_accesses.rs @@ -5,8 +5,6 @@ //! two configs are independent. #![cfg(fspy)] -use std::collections::hash_map::Entry; - use fspy::{AccessMode, PathAccessIterable}; use rustc_hash::FxHashSet; use vt_path::{AbsolutePath, RelativePathBuf}; @@ -45,23 +43,29 @@ impl TrackedPathAccesses { continue; }; - if access.mode.contains(AccessMode::READ) { - accesses - .path_reads - .entry(relative_path.clone()) - .or_insert(PathRead { read_dir_entries: false }); - } - if access.mode.contains(AccessMode::WRITE) { - accesses.path_writes.insert(relative_path.clone()); - } - if access.mode.contains(AccessMode::READ_DIR) { - match accesses.path_reads.entry(relative_path) { - Entry::Occupied(mut occupied) => { - occupied.get_mut().read_dir_entries = true; - } - Entry::Vacant(vacant) => { - vacant.insert(PathRead { read_dir_entries: true }); - } + match access.mode { + AccessMode::Read => { + accesses + .path_reads + .entry(relative_path) + .or_insert(PathRead { read_dir_entries: false }); + } + AccessMode::Write => { + accesses.path_writes.insert(relative_path); + } + AccessMode::ReadWrite => { + accesses + .path_reads + .entry(relative_path.clone()) + .or_insert(PathRead { read_dir_entries: false }); + accesses.path_writes.insert(relative_path); + } + AccessMode::ReadDir => { + accesses + .path_reads + .entry(relative_path) + .or_insert(PathRead { read_dir_entries: false }) + .read_dir_entries = true; } } }