mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-29 04:27:30 -06:00
Originals: on Windows a hard link to an original counts as that original
safe_file::same_file compared length and canonical paths on every platform other than Unix. On Windows canonicalize returns a hard link's own path, so a hard link to a library original under another name was not the same file, and pin_write_target accepted it as a screenshot destination. For this reason the engine test write_targets_pin_absent_paths_and_recheck_the_current_library failed on Windows. On Windows same_file now first compares the volume serial number and file index through the same-file crate (already in Cargo.lock through walkdir), then falls back to the resolved-path comparison. The catalog crate takes same-file as a Windows-only dependency.
This commit is contained in:
Generated
+1
@@ -3220,6 +3220,7 @@ dependencies = [
|
||||
"lightcraft-raw",
|
||||
"log",
|
||||
"proptest",
|
||||
"same-file",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.21",
|
||||
|
||||
@@ -53,6 +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
|
||||
proptest = "1"
|
||||
bytemuck = { version = "1", features = ["derive"] }
|
||||
crc32fast = "1"
|
||||
|
||||
@@ -20,6 +20,9 @@ log = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
web-time = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
same-file = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
lightcraft-geom = { workspace = true }
|
||||
proptest = { workspace = true }
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
//! replacing anything** (a hard link, which fails on a taken name; where the volume has no hard
|
||||
//! links, an exclusive create that claims the name, then a rename over that placeholder).
|
||||
//! - [`same_file`] tells whether two paths name the same file on disk (device + inode on Unix,
|
||||
//! volume serial number + file index on Windows,
|
||||
//! resolved paths elsewhere), for "is this target an original?" checks.
|
||||
|
||||
use std::cell::Cell;
|
||||
@@ -267,7 +268,8 @@ fn same_content(path: &Path, data: &[u8]) -> io::Result<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Do `a` and `b` name the same existing file (through links, `..`, case-insensitive names)?
|
||||
/// Do `a` and `b` name the same existing file (through hard links, symlinks, `..`,
|
||||
/// case-insensitive names)?
|
||||
pub fn same_file(a: &Path, b: &Path) -> bool {
|
||||
let (Ok(ma), Ok(mb)) = (fs::metadata(a), fs::metadata(b)) else { return false };
|
||||
#[cfg(unix)]
|
||||
@@ -277,6 +279,14 @@ pub fn same_file(a: &Path, b: &Path) -> bool {
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
// 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.
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if ::same_file::is_same_file(a, b).unwrap_or(false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ma.len() != mb.len() {
|
||||
return false;
|
||||
}
|
||||
@@ -397,6 +407,20 @@ mod tests {
|
||||
let _ = fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_file_sees_a_hard_link_under_another_name() {
|
||||
let d = temp("same-link");
|
||||
let p = d.join("IMG_1.jpg");
|
||||
fs::write(&p, b"original").unwrap();
|
||||
let linked = d.join("linked.png");
|
||||
fs::hard_link(&p, &linked).unwrap();
|
||||
fs::write(d.join("copy.png"), b"original").unwrap();
|
||||
assert!(same_file(&p, &linked), "a hard link is the same file");
|
||||
assert!(same_file(&linked, &p));
|
||||
assert!(!same_file(&p, &d.join("copy.png")), "a copy with the same content and length is another file");
|
||||
let _ = fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_file_sees_through_other_spellings() {
|
||||
let d = temp("same");
|
||||
|
||||
Reference in New Issue
Block a user