deb: sync upper entries through the privileged removal path

A rootless build's upperdir entries are copy-up'd by whoever wrote
them during the build: the unprivileged build user, which host-side
is a subuid of the invoking user. The host-side sync of a forked
record removes the stale upper copy of every host-modified file, and
the plain fs removals failed with EPERM on those build-user-owned
entries: the kernel incremental run died with a bare
'Permission denied (os error 13)' and no context, ten minutes into
the run.

Route the sync removals through the privileged path (the same sudo
fallback the record teardowns use) and wrap the sync error at the
call site, so a failure names the operation instead of surfacing a
bare errno string.
This commit is contained in:
2026-10-02 23:03:15 +02:00
parent f1e8244e73
commit 8485e7272b
2 changed files with 24 additions and 19 deletions
+6 -1
View File
@@ -627,7 +627,12 @@ async fn build_binary_package_impl(
// not drag it into the chroot.
let mut build_resume = false;
if let Some(s) = &session {
build_resume = session::sync_upper_for_resume(s, &host_tree)?;
build_resume = session::sync_upper_for_resume(s, &host_tree).map_err(|e| {
format!(
"cannot sync the carried tree of the incremental build \
with the host tree: {e}"
)
})?;
if build_resume {
log::info!(
"Reusing the build artifacts of the previous attempt (incremental build)"
+12 -12
View File
@@ -2072,6 +2072,12 @@ pub(crate) fn plan_upper_sync(
/// Apply sync ops to the upperdir, skipping paths already covered by a
/// removed ancestor. Returns how many entries were actually removed.
///
/// Removals go through the privileged path: the upperdir's entries were
/// copy-up'd by whoever wrote them during the build — in a rootless build
/// the unprivileged build user (a subuid of the invoking user host-side) —
/// so a plain removal by the invoking user fails with EPERM exactly when a
/// host-modified file's stale upper copy must go.
pub(crate) fn apply_upper_sync(upper_root: &Path, ops: &[UpperOp]) -> io::Result<usize> {
let mut removed_prefixes: Vec<String> = Vec::new();
let mut removed = 0;
@@ -2084,13 +2090,13 @@ pub(crate) fn apply_upper_sync(upper_root: &Path, ops: &[UpperOp]) -> io::Result
continue;
}
let path = upper_root.join(rel.replace('/', std::path::MAIN_SEPARATOR_STR));
let result = if *dir {
fs::remove_dir_all(&path)
} else {
fs::remove_file(&path)
let Ok(meta) = path.symlink_metadata() else {
continue;
};
match result {
Ok(()) => {
if !meta.is_dir() && !meta.is_file() {
continue;
}
crate::deb::ephemeral::privileged_remove(&path)?;
removed += 1;
if *dir {
let mut prefix = rel.clone();
@@ -2100,12 +2106,6 @@ pub(crate) fn apply_upper_sync(upper_root: &Path, ops: &[UpperOp]) -> io::Result
removed_prefixes.push(prefix);
}
}
Err(e) if e.kind() != io::ErrorKind::NotFound => {
return Err(e);
}
Err(_) => {}
}
}
Ok(removed)
}