fix(image): run provision unlinks when no files are staged

The early return keyed on files alone, so an unlink-only call was a
silent no-op. No caller passes an empty file set today, but the
signature invites one, and the control-file unlink is the case that
must never vanish.
This commit is contained in:
vhaudiquet committed 2026-10-30 23:13:03 +01:00
1 parent 4b3c66af3c
commit a368a2e7cb
1 file changed
+23 -4
+23 -4
View File
@@ -172,7 +172,7 @@ pub(crate) fn provision(
unlinks: &[&str], unlinks: &[&str],
files: &[(&Path, &str, u32)], files: &[(&Path, &str, u32)],
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
if files.is_empty() { if files.is_empty() && unlinks.is_empty() {
return Ok(()); return Ok(());
} }
let cmds = image.with_extension("provision"); let cmds = image.with_extension("provision");
@@ -238,9 +238,8 @@ pub(crate) fn stat_exists(image: &Path, guest: &str) -> anyhow::Result<bool> {
Ok(String::from_utf8_lossy(&out.stdout).contains("Inode:")) Ok(String::from_utf8_lossy(&out.stdout).contains("Inode:"))
} }
/// Which shell the image ships, probed in the image. The order mirrors /// Which shell the image ships, probed in the image: bash before sh,
/// `utils::detect_shell` over an extracted tree, which the image build /// /bin before /usr/bin. The final fallback fails in the guest with a
/// replaced; the fallback is the same, and fails in the guest with a
/// clear exec error if the image ships no shell at all. /// clear exec error if the image ships no shell at all.
pub(crate) fn detect_shell(image: &Path) -> anyhow::Result<&'static str> { pub(crate) fn detect_shell(image: &Path) -> anyhow::Result<&'static str> {
for shell in ["/bin/bash", "/bin/sh", "/usr/bin/bash", "/usr/bin/sh"] { for shell in ["/bin/bash", "/bin/sh", "/usr/bin/bash", "/usr/bin/sh"] {
@@ -404,6 +403,26 @@ mod tests {
assert!(stat.contains("Mode: 0755"), "{stat}"); assert!(stat.contains("Mode: 0755"), "{stat}");
} }
#[test]
fn unlinks_run_without_files() {
if !have_e2fsprogs() {
eprintln!("SKIP: no e2fsprogs");
return;
}
let work = tempfile::tempdir().unwrap();
let tarball = work.path().join("rootfs.tar");
fixture(&tarball);
let image = work.path().join("rootfs.img");
build(&tarball, &image);
assert!(stat_exists(&image, "/etc/marker").unwrap());
// No files staged: the unlink set still runs. This is the
// control-file path — a stale /.ecrou-exit must die even when
// a run provisions nothing else.
provision(&image, &["/etc/marker"], &[]).unwrap();
assert!(!stat_exists(&image, "/etc/marker").unwrap());
}
#[test] #[test]
fn a_plain_tarball_builds_the_same_way() { fn a_plain_tarball_builds_the_same_way() {
if !have_e2fsprogs() { if !have_e2fsprogs() {