summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHermes Agent <hermes@localhost>2026-08-12 06:02:57 +0000
committerHermes Agent <hermes@localhost>2026-08-12 06:02:57 +0000
commitc7dc1a0173a14e9cb52adcb09ae566cf7eeb314a (patch)
tree62661aaa98e09a9abadb676dec1863469464ce5a
parent35af190cac33650b90e8056b890b8c2c03dca7e7 (diff)
Protect companion manifests from overwrite
-rw-r--r--src/build.rs4
-rw-r--r--src/manifest.rs10
-rw-r--r--tests/build.rs23
-rw-r--r--tests/compare.rs15
4 files changed, 50 insertions, 2 deletions
diff --git a/src/build.rs b/src/build.rs
index 42c57ef..fcacfc0 100644
--- a/src/build.rs
+++ b/src/build.rs
@@ -42,6 +42,10 @@ impl<I: PackageInstaller, B: InitramfsBuilder> BuildExecutor<I, B> {
if artifact.exists() {
bail!("output artifact already exists; refusing to overwrite it: {}", artifact.display());
}
+ let manifest = ArtifactManifest::path_beside(artifact)?;
+ if manifest.exists() {
+ bail!("output manifest already exists; refusing to overwrite it: {}", manifest.display());
+ }
let parent = artifact
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
diff --git a/src/manifest.rs b/src/manifest.rs
index 0848a98..4cd256c 100644
--- a/src/manifest.rs
+++ b/src/manifest.rs
@@ -1,6 +1,6 @@
use std::collections::{BTreeMap, HashSet};
use std::fs;
-use std::io::Read;
+use std::io::{Read, Write};
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
@@ -51,7 +51,13 @@ impl ArtifactManifest {
pub fn write_beside(&self, artifact: impl AsRef<Path>) -> Result<PathBuf> {
let path = Self::path_beside(artifact)?;
let text = toml::to_string_pretty(self).context("serialize artifact manifest")?;
- fs::write(&path, text).with_context(|| format!("write artifact manifest {}", path.display()))?;
+ fs::OpenOptions::new()
+ .write(true)
+ .create_new(true)
+ .open(&path)
+ .with_context(|| format!("create artifact manifest {}", path.display()))?
+ .write_all(text.as_bytes())
+ .with_context(|| format!("write artifact manifest {}", path.display()))?;
Ok(path)
}
diff --git a/tests/build.rs b/tests/build.rs
index 6613cc7..c6ed70d 100644
--- a/tests/build.rs
+++ b/tests/build.rs
@@ -113,6 +113,29 @@ fn rejects_an_existing_artifact_before_creating_the_workspace_or_installing() {
}
#[test]
+fn rejects_an_existing_companion_manifest_before_creating_the_workspace_or_installing() {
+ let fixture = tempdir().expect("fixture directory");
+ let spec = ImageSpec::load(std::path::Path::new("profiles/alt-controller.toml"))
+ .expect("load controller spec");
+ let plan = BuildPlan::compile(spec).expect("compile build plan");
+ let installer = FixtureInstaller::default();
+ let mut executor = BuildExecutor::new(&installer, FixtureInitramfsBuilder);
+ let workspace = fixture.path().join("work");
+ let artifact = fixture.path().join("controller.tar");
+ let companion = ArtifactManifest::path_beside(&artifact).expect("companion manifest path");
+ fs::write(&companion, "do not replace").expect("write pre-existing companion manifest");
+
+ let error = executor
+ .execute(&plan, &workspace, "profiles/apt.conf", &artifact)
+ .expect_err("pre-existing companion manifest must be rejected");
+
+ assert!(error.to_string().contains("output manifest already exists"));
+ assert_eq!(fs::read_to_string(&companion).expect("read companion manifest"), "do not replace");
+ assert!(!workspace.exists());
+ assert!(installer.requests.borrow().is_empty());
+}
+
+#[test]
fn rejects_a_missing_apt_configuration_before_creating_the_workspace() {
let fixture = tempdir().expect("fixture directory");
let spec = ImageSpec::load(std::path::Path::new("profiles/alt-controller.toml"))
diff --git a/tests/compare.rs b/tests/compare.rs
index f7b3a04..4f48721 100644
--- a/tests/compare.rs
+++ b/tests/compare.rs
@@ -159,6 +159,21 @@ fn writes_distinct_manifests_for_artifacts_in_the_same_directory() {
}
#[test]
+fn refuses_to_overwrite_an_existing_companion_manifest() {
+ let fixture = tempdir().expect("temporary directory");
+ let artifact = fixture.path().join("controller.tar");
+ let manifest_path = ArtifactManifest::path_beside(&artifact).expect("manifest path");
+ fs::write(&manifest_path, "preserve this manifest").expect("write existing manifest");
+
+ let error = baseline()
+ .write_beside(&artifact)
+ .expect_err("existing companion manifests must not be overwritten");
+
+ assert!(error.to_string().contains("create artifact manifest"));
+ assert_eq!(fs::read_to_string(&manifest_path).expect("read existing manifest"), "preserve this manifest");
+}
+
+#[test]
fn rejects_duplicate_semantic_keys() {
let error = ArtifactManifest::new(
vec![PackageRecord::new("controller", "1"), PackageRecord::new("controller", "2")],