summaryrefslogtreecommitdiff
path: root/src/archive.rs
diff options
context:
space:
mode:
authorHermes Agent <hermes@localhost>2026-08-12 05:45:02 +0000
committerHermes Agent <hermes@localhost>2026-08-12 05:45:02 +0000
commit7842b48e60cd54be0e5f248563821a766590b0f0 (patch)
tree2e49bdd2ca877d094df08c3bf168e11f7ccf264e /src/archive.rs
parentb6a122940830d0f438edb24e000ee9447e8419fe (diff)
Prevent archive overwrite races
Diffstat (limited to 'src/archive.rs')
-rw-r--r--src/archive.rs12
1 files changed, 10 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);