From 9a66f8f7dfe35a3dbfa3cc8c8ac46d98cc1ecd34 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Tue, 22 Sep 2026 19:57:01 +0200 Subject: [PATCH] deb: retry the interrupt chroot removal while children die off dpkg defers SIGINT until it reaches a safe state, so it can still be writing into the chroot when the watchdog's rm -rf starts racing through it, failing with "directory not empty" and leaving the tree half-removed. Retry the removal for a few seconds while the interrupted children finish dying off; a genuinely stuck tree still ends in the pkh prune message. --- src/deb/ephemeral.rs | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/src/deb/ephemeral.rs b/src/deb/ephemeral.rs index f69c178..687b092 100644 --- a/src/deb/ephemeral.rs +++ b/src/deb/ephemeral.rs @@ -60,31 +60,48 @@ fn sigint_cleanup_chroot(chroot_path: &Path) { } } - // Remove the chroot tree itself (tolerates a missing directory) - let status = privileged_command("rm", is_root) - .arg("-rf") - .arg(chroot_path) - .status(); - match status { - Ok(status) if status.success() => { + // Remove the chroot tree itself (tolerates a missing directory). A + // child the Ctrl+C interrupted may still be finishing its writeout — + // dpkg defers SIGINT until it reaches a safe state — so retry while rm + // reports the tree non-empty instead of leaving it half-removed. + const RETRIES: usize = 10; + const RETRY_DELAY: std::time::Duration = std::time::Duration::from_millis(300); + let mut last = None; + for attempt in 0..=RETRIES { + if attempt > 0 { + std::thread::sleep(RETRY_DELAY); + } + last = Some( + privileged_command("rm", is_root) + .arg("-rf") + .arg(chroot_path) + .status(), + ); + if matches!(&last, Some(Ok(status)) if status.success()) { + break; + } + } + match last { + Some(Ok(status)) if status.success() => { log::debug!( "Removed chroot {} during interrupt cleanup", chroot_path.display() ); } - Ok(status) => { + Some(Ok(status)) => { log::error!( "Failed to remove chroot {} during interrupt cleanup \ (rm exited with {status}); run `pkh prune`", chroot_path.display() ); } - Err(e) => { + Some(Err(e)) => { log::error!( "Failed to run rm for chroot {} during interrupt cleanup: {e}; run `pkh prune`", chroot_path.display() ); } + None => unreachable!("at least one rm attempt ran"), } }