Revert "deb: run the rootless build step under fakeroot, sharing the state"

This reverts commit 5f36a54db42e0e5b8f5243dded89f68cd93fa689.

Fakeroot's IPC degrades under kernel-scale parallel builds: a
linux-riscv build with make -j plus LTO floods the faked daemon and
libfakeroot prints 'internal error: payload not recognized' per
mangled message, with the ownership database losing entries. The
benefit the wrap bought is vacuous anyway: the .deb is assembled in
the binary step's own fakeroot session, which records files unknown
to its database as root-owned — verified with a sysroot package
whose build chown fails: the produced .deb still carries root/root.

A rules chown failing for the unprivileged build user stays expected
and harmless; the visible noise is collapsed at the classifier level
instead (next commit). The bare build step also matches modern dpkg
semantics: with Rules-Requires-Root: binary-targets (pkh's default
export), dpkg-buildpackage itself runs the build targets unprivileged
and fakes root only for the binary targets.
This commit is contained in:
2026-10-02 18:42:36 +02:00
parent b8adb318bd
commit 6050da7dbb
+12 -45
View File
@@ -250,14 +250,6 @@ fn rules_command<'a>(
}
}
/// The fakeroot state file shared by the rules steps of a rootless
/// build, in the upload directory next to the staged tree: outside the
/// package tree, so it pollutes neither the sources nor the collected
/// artifacts.
fn fakeroot_state_file(upload_dir: &str) -> PathBuf {
Path::new(upload_dir).join(".pkh-fakeroot.state")
}
#[allow(clippy::too_many_arguments)]
pub async fn build(
package: &str,
@@ -688,10 +680,16 @@ pub async fn build(
.unwrap_or_else(|| "binary-targets".to_string());
env.insert("DEB_RULES_REQUIRES_ROOT".to_string(), rules_requires_root);
// Rootless builds: the rules (fakeroot-wrapped, see below) run as an
// unprivileged user owning the staged tree. Everything before this
// point - dependency installation, quilt patch application, staging
// cleanup - legitimately needs the context's root and stays with it.
// Rootless builds: the rules run as an unprivileged user owning the
// staged tree. Everything before this point - dependency installation,
// quilt patch application, staging cleanup - legitimately needs the
// context's root and stays with it. The build step runs BARE (no
// fakeroot): fakeroot's IPC degrades under kernel-scale parallel
// builds ('libfakeroot internal error: payload not recognized' per
// LTO/make -j process flood), and a rules chown that fails for the
// build user is cosmetic — the .deb is assembled in the binary step's
// own fakeroot session, where files unknown to its database are
// recorded as root-owned anyway.
let rule_ids = match build_user {
Some(_) => {
let workroot = Path::new(package_dir_str)
@@ -704,34 +702,12 @@ pub async fn build(
None => None,
};
// Rootless: the rules run under fakeroot with one session state
// shared between the build and binary steps, the way
// dpkg-buildpackage -rfakeroot wraps every rules target. An
// ownership change made during the build (a sysroot chown) then
// succeeds — faked — and carries into the .deb, instead of failing
// per file with EPERM spam. With the context's own user the build
// step runs bare; the binary step keeps its unconditional wrap.
let fakeroot_state = fakeroot_state_file(upload_dir);
if rule_ids.is_some() {
// A state left by an interrupted previous attempt must not
// seed this session.
let _ = ctx.command("rm").arg("-f").arg(&fakeroot_state).status();
}
let mut rules = match rule_ids.as_ref() {
Some(_) => {
let mut cmd = rules_command(&ctx, rule_ids.as_ref(), "fakeroot");
cmd.arg("-s").arg(&fakeroot_state).arg("debian/rules");
cmd
}
None => rules_command(&ctx, None, "debian/rules"),
};
// Run the build step
log::debug!("Building (debian/rules build) package...");
enter_phase(view, Phase::Building, profiler);
let started = Instant::now();
let status = cap(
rules
rules_command(&ctx, rule_ids.as_ref(), "debian/rules")
.current_dir(package_dir_str)
.envs(env.clone())
.arg("build"),
@@ -760,17 +736,8 @@ pub async fn build(
// Run the 'binary' step to produce deb
enter_phase(view, Phase::ProducingBinaries, profiler);
let started = Instant::now();
let mut rules = rules_command(&ctx, rule_ids.as_ref(), "fakeroot");
if rule_ids.is_some() {
// Pick up the ownership changes the build step recorded.
rules
.arg("-i")
.arg(&fakeroot_state)
.arg("-s")
.arg(&fakeroot_state);
}
let status = cap(
rules
rules_command(&ctx, rule_ids.as_ref(), "fakeroot")
.current_dir(package_dir_str)
.envs(env.clone())
.arg("debian/rules")