diff --git a/src/support/fs.rs b/src/support/fs.rs index 73556a8..b44841b 100644 --- a/src/support/fs.rs +++ b/src/support/fs.rs @@ -252,25 +252,29 @@ fn lock_holder_is_live(_pid: u32) -> bool { } pub fn best_effort_fsync_dir(path: &Path) -> std::io::Result<()> { - let dir = File::open(path)?; - #[cfg(unix)] - unsafe { - let rc = libc::fsync(std::os::fd::AsRawFd::as_raw_fd(&dir)); - if rc == 0 { - return Ok(()); - } + { + let dir = File::open(path)?; + + // SAFETY: `dir` owns a valid file descriptor for the duration of the call, + // and `fsync` does not retain it. The return code is checked below. + unsafe { + let rc = libc::fsync(std::os::fd::AsRawFd::as_raw_fd(&dir)); + if rc == 0 { + return Ok(()); + } - let error = std::io::Error::last_os_error(); - if error.raw_os_error() == Some(libc::EINVAL) { - return Ok(()); + let error = std::io::Error::last_os_error(); + if error.raw_os_error() == Some(libc::EINVAL) { + return Ok(()); + } + Err(error) } - return Err(error); } #[cfg(not(unix))] { - let _ = dir; + let _ = path; Ok(()) } } @@ -610,6 +614,8 @@ fn with_rollback_context( #[cfg(test)] mod tests { + #[cfg(windows)] + use super::replace_dir_atomically; use super::{ acquire_advisory_lock, advisory_lock_owner_id, publish_file_atomically, publish_file_atomically_impl, read_advisory_lock_metadata, remove_path_if_exists, @@ -824,6 +830,33 @@ mod tests { assert_eq!(fs::read_to_string(&destination).expect("dest"), "new"); } + #[cfg(windows)] + #[test] + fn windows_publishes_staged_directory_to_new_target() { + let dir = tempdir().expect("tempdir"); + let staging_dir = dir.path().join(".stage"); + let target_dir = dir.path().join("target"); + fs::create_dir(&staging_dir).expect("staging dir"); + fs::write(staging_dir.join("payload.txt"), "payload").expect("payload"); + assert!(!target_dir.exists()); + + let outcome = replace_dir_atomically( + &staging_dir, + &target_dir, + "test-run", + "test-target", + ".backup", + ) + .expect("publish staged directory"); + + assert_eq!(outcome.cleanup_warning, None); + assert!(!staging_dir.exists()); + assert_eq!( + fs::read_to_string(target_dir.join("payload.txt")).expect("target payload"), + "payload" + ); + } + #[cfg(windows)] #[test] fn windows_reports_missing_process_as_not_live() {