From 6050da7dbb2c2e8f1dd612888318810e7624af84 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Fri, 2 Oct 2026 18:42:36 +0200 Subject: [PATCH] Revert "deb: run the rootless build step under fakeroot, sharing the state" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/deb/local.rs | 57 ++++++++++-------------------------------------- 1 file changed, 12 insertions(+), 45 deletions(-) diff --git a/src/deb/local.rs b/src/deb/local.rs index 3a7441d..5c5d055 100644 --- a/src/deb/local.rs +++ b/src/deb/local.rs @@ -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")