From 4826fff7346f831d5bd7d17ef3218ac3dd7e1012 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Mon, 26 Oct 2026 11:34:06 -0700 Subject: [PATCH] Originals: a Windows file index of 0 does not identify a file same_file::is_same_file treats two files with the same volume serial number and file index as one file, also when both report index 0. The Windows comparison now reads both identities through winapi-util and accepts a match only when both file indexes are nonzero; otherwise the resolved paths are compared, as for a volume without file indexes. --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/catalog/Cargo.toml | 2 +- crates/catalog/src/safe_file.rs | 33 +++++++++++++++++++++++++++++---- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3e9acf98c..0fcf8a35c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3221,11 +3221,11 @@ dependencies = [ "lightcraft-testkit", "log", "proptest", - "same-file", "serde", "serde_json", "thiserror 2.0.21", "web-time", + "winapi-util", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index fc2d90d2e..4e122eb24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ log = "0.4" # All date/time conversion goes through chrono (no hand-rolled calendar code beyond the 400-year range extension; AGENTS.md). chrono = { version = "0.4.45", default-features = false, features = ["std"] } rayon = "1" -same-file = "1" # lightcraft-catalog reads a file's volume serial number and file index with it on Windows +winapi-util = "0.1" # lightcraft-catalog reads a file's volume serial number and file index with it on Windows proptest = "1" bytemuck = { version = "1", features = ["derive"] } crc32fast = "1" diff --git a/crates/catalog/Cargo.toml b/crates/catalog/Cargo.toml index 7a9c80439..b12e18803 100644 --- a/crates/catalog/Cargo.toml +++ b/crates/catalog/Cargo.toml @@ -21,7 +21,7 @@ chrono = { workspace = true } web-time = { workspace = true } [target.'cfg(windows)'.dependencies] -same-file = { workspace = true } +winapi-util = { workspace = true } [dev-dependencies] lightcraft-geom = { workspace = true } diff --git a/crates/catalog/src/safe_file.rs b/crates/catalog/src/safe_file.rs index 9b8bf5bcd..bc05d4139 100644 --- a/crates/catalog/src/safe_file.rs +++ b/crates/catalog/src/safe_file.rs @@ -301,13 +301,13 @@ fn compare_files(a: &Path, b: &Path, strict: bool) -> io::Result { return Ok(false); } // A hard link has its own path but shares the volume serial number and file index with - // its original. A volume that does not report them is compared by resolved path below. + // its original. A volume that reports file index 0 is compared by resolved path below. // On ReFS two files can share the 64-bit file index, so the lengths are compared first. #[cfg(windows)] { - match ::same_file::is_same_file(a, b) { - Ok(true) => return Ok(true), - Ok(false) => {} + match file_ids(a, b) { + Ok((ia, ib)) if same_file_id(ia, ib) => return Ok(true), + Ok(_) => {} Err(e) if strict => return Err(e), Err(_) => {} } @@ -319,6 +319,22 @@ fn compare_files(a: &Path, b: &Path, strict: bool) -> io::Result { } } +/// The volume serial number and file index of `a` and `b`, read while both files are open. +#[cfg(windows)] +fn file_ids(a: &Path, b: &Path) -> io::Result<((u64, u64), (u64, u64))> { + use winapi_util::{Handle, file::information}; + let (ha, hb) = (Handle::from_path_any(a)?, Handle::from_path_any(b)?); + let (ia, ib) = (information(&ha)?, information(&hb)?); + Ok(((ia.volume_serial_number(), ia.file_index()), (ib.volume_serial_number(), ib.file_index()))) +} + +/// Whether two (volume serial number, file index) pairs name one file. A file index of 0 is not +/// an identity and matches nothing. +#[cfg(any(windows, test))] +fn same_file_id(a: (u64, u64), b: (u64, u64)) -> bool { + a.1 != 0 && b.1 != 0 && a == b +} + #[cfg(test)] mod tests { use super::*; @@ -480,4 +496,13 @@ mod tests { assert!(!same_file_strict(&p.join("under-a-file.jpg"), &p).unwrap()); let _ = fs::remove_dir_all(&d); } + + #[test] + fn a_zero_file_index_is_not_a_file_identity() { + assert!(same_file_id((7, 42), (7, 42))); + assert!(!same_file_id((7, 42), (7, 43))); + assert!(!same_file_id((7, 42), (8, 42))); + assert!(!same_file_id((7, 0), (7, 0)), "two files on a volume that reports index 0 are not the same file"); + assert!(!same_file_id((0, 0), (0, 0))); + } }