Data Recovery: on Windows the lock file check compares file identity

On Windows a recovery area's lock file counted as the one that was
opened when the two had the same creation time. NTFS gives a file made
within 15 seconds under a removed file's name that file's creation
time, so a second running copy could take an area another copy had
just made again. The check now compares the volume serial number and
file index, as Unix compares the device and inode, and counts a file it
cannot read as not the same. The test that a lock file made again is
not the one opened now also runs on Windows.
This commit is contained in:
dotCooCoo
2026-11-11 16:59:13 -08:00
parent a28e15499c
commit 711fc9bccd
4 changed files with 14 additions and 6 deletions
Generated
+1
View File
@@ -6498,6 +6498,7 @@ dependencies = [
"roxmltree 0.21.2",
"rustix 1.1.6",
"ruzstd",
"same-file",
"serde",
"serde_json",
"skrifa 0.47.0",
+5
View File
@@ -67,6 +67,11 @@ log = { workspace = true }
[target.'cfg(unix)'.dependencies]
rustix = { workspace = true, features = ["std", "process"] }
# Whether a recovery area's lock file is still the one that was opened (`FolderStore::still_named`).
# Already in Cargo.lock (walkdir), so this adds no new crate.
[target.'cfg(windows)'.dependencies]
same-file = "1.0.6"
# The tiles of a HEIC photo decode in parallel on native; wasm stays single-threaded.
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
heic-rs = { version = "=0.1.4", default-features = false, features = ["std", "parallel"], optional = true }
+5 -4
View File
@@ -342,7 +342,8 @@ impl FolderStore {
}
/// Does `path` still name `file`? On Unix the two must be the same file (device and inode); on
/// Windows they must have the same creation time; elsewhere `path` must exist.
/// Windows they must be the same file (volume serial number and file index); elsewhere `path`
/// must exist. When either file can't be read, it does not.
fn still_named(file: &std::fs::File, path: &std::path::Path) -> bool {
#[cfg(unix)]
{
@@ -354,9 +355,9 @@ impl FolderStore {
}
#[cfg(windows)]
{
use std::os::windows::fs::MetadataExt as _;
match (file.metadata(), std::fs::symlink_metadata(path)) {
(Ok(a), Ok(b)) => a.creation_time() == b.creation_time(),
let opened = file.try_clone().and_then(same_file::Handle::from_file);
match (opened, same_file::Handle::from_path(path)) {
(Ok(a), Ok(b)) => a == b,
_ => false,
}
}
+3 -2
View File
@@ -572,8 +572,9 @@ fn an_area_tidied_away_before_it_is_locked_is_busy() {
// The folder went before it was checked or its lock file opened.
std::fs::remove_dir(&area).unwrap();
assert!(matches!(FolderStore::lock_area(&area), Ok(Lock::Busy)));
// A lock file made again under the same name is not the one opened.
#[cfg(unix)]
// A lock file made again under the same name is not the one opened, also where it keeps the
// removed one's creation time (NTFS does within 15 seconds).
#[cfg(any(unix, windows))]
{
std::fs::create_dir_all(&area).unwrap();
let opened = open(true);