From 30732125393981c2fc6a0e723ba17212db6aa7cb Mon Sep 17 00:00:00 2001 From: Pavel Korolev Date: Sun, 26 Jul 2026 17:41:43 +0300 Subject: [PATCH 1/2] fix(fs): skip directory fsync outside unix - avoid opening directory paths on Windows and other non-Unix targets - cover the Windows no-op behavior with a missing-path regression test --- src/support/fs.rs | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/support/fs.rs b/src/support/fs.rs index 73556a8..fa23540 100644 --- a/src/support/fs.rs +++ b/src/support/fs.rs @@ -252,25 +252,27 @@ 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)?; - let error = std::io::Error::last_os_error(); - if error.raw_os_error() == Some(libc::EINVAL) { - return Ok(()); + 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(()); + } + Err(error) } - return Err(error); } #[cfg(not(unix))] { - let _ = dir; + let _ = path; Ok(()) } } @@ -610,6 +612,8 @@ fn with_rollback_context( #[cfg(test)] mod tests { + #[cfg(windows)] + use super::best_effort_fsync_dir; 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 +828,15 @@ mod tests { assert_eq!(fs::read_to_string(&destination).expect("dest"), "new"); } + #[cfg(windows)] + #[test] + fn windows_fsync_ignores_missing_directory() { + let dir = tempdir().expect("tempdir"); + let missing_path = dir.path().join("missing-directory"); + + best_effort_fsync_dir(&missing_path).expect("best-effort fsync"); + } + #[cfg(windows)] #[test] fn windows_reports_missing_process_as_not_live() { From 5365888b08fb083feb18d4b58819c4ae0d8e0d08 Mon Sep 17 00:00:00 2001 From: alkoleft Date: Thu, 30 Jul 2026 18:09:28 +0300 Subject: [PATCH 2/2] test(fs): cover Windows directory publication - publish a staged directory into a fresh target in the regression test - document the fsync unsafe invariant --- src/support/fs.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/support/fs.rs b/src/support/fs.rs index fa23540..b44841b 100644 --- a/src/support/fs.rs +++ b/src/support/fs.rs @@ -256,6 +256,8 @@ pub fn best_effort_fsync_dir(path: &Path) -> std::io::Result<()> { { 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 { @@ -613,7 +615,7 @@ fn with_rollback_context( #[cfg(test)] mod tests { #[cfg(windows)] - use super::best_effort_fsync_dir; + 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, @@ -830,11 +832,29 @@ mod tests { #[cfg(windows)] #[test] - fn windows_fsync_ignores_missing_directory() { + fn windows_publishes_staged_directory_to_new_target() { let dir = tempdir().expect("tempdir"); - let missing_path = dir.path().join("missing-directory"); + 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"); - best_effort_fsync_dir(&missing_path).expect("best-effort fsync"); + 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)]