deb: survive crashed sessions and config-blind resumes
Two failures from resumed kernel sessions: A resumed build re-runs debian/rules build, but the kernels keep their wrapper-step stamps in debian/stamps and the flavour config rule there exports .config from the annotations with no config prerequisite: the resumed build silently kept the previous attempt's configuration and a config edit never reached the .deb. Dropping the stamp cache unconditionally would impose the kernel packaging's shape on every package, so it is data-driven instead: packages declare the caches under a resume_clear quirk, and resumed builds remove those tree-relative paths before the build. The inner kbuild keeps its own incremental state, so only the cheap wrapper passes re-run and config-affected objects recompile. A SIGKILLed build (OOM) leaves its overlay mounts and /proc bind mount behind, and overlayfs creates root-owned work state inside the workdir: the next plain build (recording is always on, it replaces the session of its identity) failed to clear the leftovers with a permission error. Session clearing now unmounts everything under each entry at depth (re-reading /proc/mounts, tolerating mount stacks from consecutive crashes) and escalates through sudo; when the tree still cannot be cleared, the build reports it and continues without a session instead of layering over a half-cleared one.
This commit is contained in:
+16
-36
@@ -350,20 +350,7 @@ impl Session {
|
||||
/// attempt's tree would merge stale build artifacts and resurrect
|
||||
/// host-deleted files into the build.
|
||||
pub fn wipe_staged_tree(&self) -> io::Result<()> {
|
||||
let staged = self.chroot_path().join("tmp/pkh-build");
|
||||
fs::create_dir_all(&staged)?;
|
||||
for entry in fs::read_dir(&staged)?.flatten() {
|
||||
let path = entry.path();
|
||||
let result = if path.is_dir() {
|
||||
fs::remove_dir_all(&path)
|
||||
} else {
|
||||
fs::remove_file(&path)
|
||||
}
|
||||
.or_else(|_| crate::deb::ephemeral::privileged_remove(&path));
|
||||
result
|
||||
.map_err(|e| io::Error::other(format!("cannot clear {}: {e}", staged.display())))?;
|
||||
}
|
||||
Ok(())
|
||||
crate::deb::ephemeral::clear_dir(&self.chroot_path().join("tmp/pkh-build"))
|
||||
}
|
||||
|
||||
/// Remove the whole session tree (unmounting anything mounted under
|
||||
@@ -557,6 +544,9 @@ fn open_from_dir(root: PathBuf) -> Option<Session> {
|
||||
pub enum SessionOpenError {
|
||||
/// The session is locked by a concurrent build.
|
||||
Busy(SessionBusy),
|
||||
/// The previous session could not be cleared (crashed-build leftovers
|
||||
/// that even the privileged removal could not lift).
|
||||
Unusable(String),
|
||||
/// Anything else (I/O, manifest errors).
|
||||
Other(String),
|
||||
}
|
||||
@@ -565,6 +555,7 @@ impl std::fmt::Display for SessionOpenError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SessionOpenError::Busy(busy) => write!(f, "{busy}"),
|
||||
SessionOpenError::Unusable(message) => write!(f, "{message}"),
|
||||
SessionOpenError::Other(message) => write!(f, "{message}"),
|
||||
}
|
||||
}
|
||||
@@ -618,28 +609,17 @@ pub fn fresh_session(
|
||||
|
||||
session.acquire().map_err(SessionOpenError::Busy)?;
|
||||
|
||||
// Wipe the previous attempt (the lock file lives outside the root): a
|
||||
// previous chroot holds root-owned device nodes, so escalate on
|
||||
// failure, like the prune removal does.
|
||||
for entry in fs::read_dir(&root)
|
||||
.map_err(|e| SessionOpenError::Other(format!("cannot clear {}: {e}", root.display())))?
|
||||
.flatten()
|
||||
{
|
||||
let path = entry.path();
|
||||
let result = if path.is_dir() {
|
||||
fs::remove_dir_all(&path)
|
||||
} else {
|
||||
fs::remove_file(&path)
|
||||
}
|
||||
.or_else(|_| crate::deb::ephemeral::privileged_remove(&path));
|
||||
if let Err(e) = result {
|
||||
log::warn!(
|
||||
"Failed to clear the previous session file '{}': {}",
|
||||
path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
// Wipe the previous attempt (the lock file lives outside the root). A
|
||||
// crashed build leaves its mounts behind and the kernel creates
|
||||
// root-owned state inside overlay workdirs, so the clearing unmounts
|
||||
// at depth and escalates via sudo when needed; see clear_dir.
|
||||
crate::deb::ephemeral::clear_dir(&root).map_err(|e| {
|
||||
SessionOpenError::Unusable(format!(
|
||||
"cannot clear the previous build session {}: {e}. \
|
||||
Free it with `pkh prune` (or remove it manually) and rebuild",
|
||||
root.display()
|
||||
))
|
||||
})?;
|
||||
|
||||
session.reset_fresh(host_tree, identity, tree_version);
|
||||
Ok(session)
|
||||
|
||||
Reference in New Issue
Block a user