feat: добавить поддержку атомарной замены файлов для Windows и тесты на максимальную длину имени

This commit is contained in:
2026-02-11 22:00:46 +00:00
parent 70ed6480c2
commit 3c06e768d6
4 changed files with 171 additions and 12 deletions

View File

@@ -651,18 +651,43 @@ fn write_atomic(path: &Path, content: &[u8]) -> Result<()> {
file.flush()?;
drop(file);
match fs::rename(&tmp_path, path) {
Ok(()) => Ok(()),
Err(rename_err) => {
if path.exists() {
fs::remove_file(path)?;
fs::rename(&tmp_path, path)?;
Ok(())
} else {
let _ = fs::remove_file(&tmp_path);
Err(Error::Io(rename_err))
}
}
if let Err(err) = replace_file_atomically(&tmp_path, path) {
let _ = fs::remove_file(&tmp_path);
return Err(Error::Io(err));
}
Ok(())
}
#[cfg(not(windows))]
fn replace_file_atomically(src: &Path, dst: &Path) -> std::io::Result<()> {
fs::rename(src, dst)
}
#[cfg(windows)]
fn replace_file_atomically(src: &Path, dst: &Path) -> std::io::Result<()> {
use std::iter;
use std::os::windows::ffi::OsStrExt;
use windows_sys::Win32::Storage::FileSystem::{
MoveFileExW, MOVEFILE_REPLACE_EXISTING, MOVEFILE_WRITE_THROUGH,
};
let src_wide: Vec<u16> = src.as_os_str().encode_wide().chain(iter::once(0)).collect();
let dst_wide: Vec<u16> = dst.as_os_str().encode_wide().chain(iter::once(0)).collect();
// Replace destination in one OS call, avoiding remove+rename gaps on Windows.
let ok = unsafe {
MoveFileExW(
src_wide.as_ptr(),
dst_wide.as_ptr(),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH,
)
};
if ok == 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(())
}
}

View File

@@ -770,6 +770,41 @@ fn nres_synthetic_read_find_and_edit() {
let _ = fs::remove_file(&path);
}
#[test]
fn nres_max_name_length_roundtrip() {
let max_name = "12345678901234567890123456789012345";
assert_eq!(max_name.len(), 35);
let src = build_nres_bytes(&[SyntheticEntry {
kind: 9,
attr1: 1,
attr2: 2,
attr3: 3,
name: max_name,
data: b"payload",
}]);
let archive = Archive::open_bytes(Arc::from(src.into_boxed_slice()), OpenOptions::default())
.expect("open synthetic nres failed");
assert_eq!(archive.entry_count(), 1);
assert_eq!(archive.find(max_name), Some(EntryId(0)));
assert_eq!(
archive.find(&max_name.to_ascii_lowercase()),
Some(EntryId(0))
);
let entry = archive.get(EntryId(0)).expect("missing entry 0");
assert_eq!(entry.meta.name, max_name);
assert_eq!(
archive
.read(EntryId(0))
.expect("read payload failed")
.as_slice(),
b"payload"
);
}
#[test]
fn nres_find_falls_back_when_sort_index_is_out_of_range() {
let mut bytes = build_nres_bytes(&[