diff options
| -rw-r--r-- | src/archive.rs | 12 | ||||
| -rw-r--r-- | tests/archive.rs | 18 |
2 files changed, 28 insertions, 2 deletions
diff --git a/src/archive.rs b/src/archive.rs index ad424c4..c0a2195 100644 --- a/src/archive.rs +++ b/src/archive.rs @@ -27,6 +27,9 @@ impl NativeTarWriter { let output = output.as_ref(); reject_output_inside_rootfs(rootfs, output)?; + if output.exists() { + bail!("archive output already exists; refusing to overwrite it: {}", output.display()); + } let temporary = temporary_output_path(output)?; let result = (|| { let file = OpenOptions::new() @@ -39,13 +42,18 @@ impl NativeTarWriter { archive .finish() .with_context(|| format!("finish archive {}", temporary.display()))?; - fs::rename(&temporary, output).with_context(|| { + // `rename` replaces an existing destination on Unix. Publishing via + // a hard link instead gives this same-directory temporary file an + // atomic no-replace final name. + fs::hard_link(&temporary, output).with_context(|| { format!( "publish completed archive {} as {}", temporary.display(), output.display() ) - }) + })?; + fs::remove_file(&temporary) + .with_context(|| format!("remove temporary archive {}", temporary.display())) })(); if result.is_err() { let _ = fs::remove_file(&temporary); diff --git a/tests/archive.rs b/tests/archive.rs index 6f803ef..6e59837 100644 --- a/tests/archive.rs +++ b/tests/archive.rs @@ -125,6 +125,24 @@ fn rejects_an_archive_output_inside_the_rootfs() { } #[test] +fn refuses_to_replace_an_existing_archive() { + let fixture = tempdir().expect("fixture directory"); + let rootfs = fixture.path().join("rootfs"); + fs::create_dir(&rootfs).expect("create rootfs"); + fs::write(rootfs.join("controller.conf"), "controller=true\n").expect("write rootfs file"); + let artifact = fixture.path().join("controller.tar"); + fs::write(&artifact, "existing artifact").expect("write existing artifact"); + + let error = NativeTarWriter::new() + .write(&rootfs, &artifact) + .expect_err("an existing archive must not be replaced"); + + assert!(error.to_string().contains("archive output already exists")); + assert_eq!(fs::read_to_string(&artifact).expect("read existing artifact"), "existing artifact"); + assert!(!fixture.path().join("controller.tar.partial").exists()); +} + +#[test] fn removes_partial_output_when_an_unsupported_entry_stops_packaging() { let fixture = tempdir().expect("fixture directory"); let rootfs = fixture.path().join("rootfs"); |