From 3c06e768d603ea4452fc95cc48951af9270f76a1 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Wed, 11 Feb 2026 22:00:46 +0000 Subject: feat: добавить поддержку атомарной замены файлов для Windows и тесты на максимальную длину имени MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/nres/src/lib.rs | 49 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) (limited to 'crates/nres/src/lib.rs') diff --git a/crates/nres/src/lib.rs b/crates/nres/src/lib.rs index 1fa3b39..e0631e3 100644 --- a/crates/nres/src/lib.rs +++ b/crates/nres/src/lib.rs @@ -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 = src.as_os_str().encode_wide().chain(iter::once(0)).collect(); + let dst_wide: Vec = 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(()) } } -- cgit v1.2.3