diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index 2d6077981..9ba2e5f6c 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -423,25 +423,65 @@ pub fn atomic_write(path: &Path, data: &[u8]) -> Result<(), AppError> { let replace_error = std::io::Error::last_os_error(); // WSL UNC paths reject ReplaceFileW with ERROR_NOT_SUPPORTED (50). - // Fall back to the pre-v3.19.2 remove-then-rename behavior there. + // Fall back to rename-based replacement with a rollback backup there. let replace_not_supported = replace_error.raw_os_error() == Some(ERROR_NOT_SUPPORTED as i32); - if replace_error.kind() != std::io::ErrorKind::NotFound && !replace_not_supported { - last_error = Some(replace_error); - break; - } - if replace_not_supported { - match fs::remove_file(path) { - Ok(()) => {} - Err(source) if source.kind() == std::io::ErrorKind::NotFound => {} + let mut backup_name = tmp + .file_name() + .expect("temporary path must have a file name") + .to_os_string(); + backup_name.push(".backup"); + let backup = tmp.with_file_name(backup_name); + let backup_created = match fs::rename(path, &backup) { + Ok(()) => true, + Err(source) if source.kind() == std::io::ErrorKind::NotFound => false, Err(source) => { last_error = Some(source); break; } + }; + + match fs::rename(&tmp, path) { + Ok(()) => { + if backup_created { + if let Err(source) = fs::remove_file(&backup) { + log::warn!( + "Failed to remove atomic-write backup {}: {}", + backup.display(), + source + ); + } + } + completed = true; + break; + } + Err(source) => { + if backup_created { + if let Err(restore_error) = fs::rename(&backup, path) { + return Err(AppError::IoContext { + context: format!( + "原子替换及回滚失败: {} -> {}; 原文件保留在 {}; 回滚错误: {}", + tmp.display(), + path.display(), + backup.display(), + restore_error + ), + source, + }); + } + } + last_error = Some(source); + break; + } } } + if replace_error.kind() != std::io::ErrorKind::NotFound { + last_error = Some(replace_error); + break; + } + match fs::rename(&tmp, path) { Ok(()) => { completed = true;