diff --git a/crates/format/src/atomic.rs b/crates/format/src/atomic.rs index c06dcb264a..896de0179d 100644 --- a/crates/format/src/atomic.rs +++ b/crates/format/src/atomic.rs @@ -251,14 +251,17 @@ fn dir_sync_unsupported(e: &io::Error) -> bool { return true; } // errno values: EINVAL is 22 everywhere; ENOTSUP and EOPNOTSUPP are 95 on Linux and - // Android, 45 and 102 on Apple platforms, 45 on the BSDs; ENOTTY is 25. + // Android, 45 and 102 on Apple platforms, 45 on the BSDs; ENOTTY is 25. Elsewhere 45 means + // something else (illumos: EDEADLK), so only EINVAL and `Unsupported` count there. const EINVAL: i32 = 22; let unsupported: &[i32] = if cfg!(any(target_os = "linux", target_os = "android")) { &[EINVAL, 95] } else if cfg!(target_vendor = "apple") { &[EINVAL, 45, 102, 25] - } else if cfg!(unix) { + } else if cfg!(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly")) { &[EINVAL, 45] + } else if cfg!(unix) { + &[EINVAL] } else { &[] }; diff --git a/crates/format/src/store.rs b/crates/format/src/store.rs index d9f2f0406f..0eca1e3e6d 100644 --- a/crates/format/src/store.rs +++ b/crates/format/src/store.rs @@ -909,20 +909,16 @@ impl PcraftWriter { // Pin all GC directories before removing stale previews or objects. // Unix cleanup then stays within these directories even if a pathname is swapped. let gc = BundleGc::new(dir)?; + write_bundle_file(dir, &dir.join(MANIFEST), &p.manifest)?; + // The manifest is committed: the save has succeeded. Removing previews and objects it no + // longer references is best-effort housekeeping (a vanished or locked file must not turn a + // complete save into a reported failure); a leftover is retried on the next save. Previews + // go only now too: if the manifest can't be committed, the previous one still names them. for stale in [THUMB, COMPOSITE] { if !p.previews.iter().any(|(n, _)| *n == stale) { - let path = dir.join(stale); - validate_bundle_file(dir, &path)?; - match remove_bundle_file(dir, &path, &gc) { - Err(FormatError::Io(e)) if e.kind() == std::io::ErrorKind::NotFound => {} - other => other?, - } + let _ = remove_bundle_file(dir, &dir.join(stale), &gc); } } - write_bundle_file(dir, &dir.join(MANIFEST), &p.manifest)?; - // The manifest is committed: the save has succeeded. Removing objects it no longer - // references is best-effort housekeeping (a vanished or locked file must not turn a - // complete save into a reported failure); a leftover is retried on the next save. for path in existing { if !p.objects.contains_key(&path) { if remove_bundle_file(dir, &dir.join(&path), &gc).is_ok() { @@ -1003,8 +999,9 @@ fn gc_pinned_directory_handles_resist_subdirectory_swap() { std::fs::remove_dir_all(root).unwrap(); } -/// #4968: a manifest whose folder can't be flushed fails the save, and the objects the previous -/// manifest references stay in place; the next save that flushes removes what is unreferenced. +/// #4968: a manifest whose folder can't be flushed fails the save, and the objects and previews the +/// previous manifest references stay in place; the next save that flushes removes what is +/// unreferenced. #[cfg(test)] #[test] fn a_failed_manifest_flush_keeps_the_previous_objects() { @@ -1021,7 +1018,10 @@ fn a_failed_manifest_flush_keeps_the_previous_objects() { }; paint(&mut doc, 1.0); let mut w = PcraftWriter::new(); - w.save_dir(&doc, &dir, &SaveOptions::default()).unwrap(); + // The first save has a thumbnail; the next ones drop it. + let thumb = SaveOptions { thumbnail: Some(Rgba8Image::new(4, 3)), composite: None }; + w.save_dir(&doc, &dir, &thumb).unwrap(); + assert!(dir.join(THUMB).exists()); let old = list_objects(&dir).unwrap(); paint(&mut doc, 0.0); FAIL_DIR_FLUSH.with(|f| f.set(Some(MANIFEST))); @@ -1030,8 +1030,10 @@ fn a_failed_manifest_flush_keeps_the_previous_objects() { let now = list_objects(&dir).unwrap(); assert!(old.is_subset(&now), "the previous manifest's objects are kept"); assert!(now.len() > old.len(), "the new objects were written"); + assert!(dir.join(THUMB).exists(), "the previous manifest's thumbnail is kept"); let stats = w.save_dir(&doc, &dir, &SaveOptions::default()).unwrap(); assert!(stats.objects_removed > 0, "the next save removes the unreferenced objects"); + assert!(!dir.join(THUMB).exists(), "and the thumbnail it no longer names"); assert_eq!(crate::load_path(&dir).unwrap(), doc); std::fs::remove_dir_all(dir).unwrap(); }