From 4535255844ae6e0e57d148dea48b3ec197c6f1a7 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Fri, 2 Oct 2026 18:42:36 +0200 Subject: [PATCH] logfmt: collapse the rootless chown EPERM flood into one digest line A rules chown failing for the unprivileged build user is expected and harmless (see the revert of the build-step fakeroot wrap), but a sysroot tree can print thousands of 'chown: changing ownership of ...: Operation not permitted' lines. MakeClassifier now shows one warning digest for the flood and hides the rest; every other line, make errors included, keeps its classification. The full tee in the record's build.log is untouched. --- src/logfmt.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/logfmt.rs b/src/logfmt.rs index 70f14ac..e1a0bd6 100644 --- a/src/logfmt.rs +++ b/src/logfmt.rs @@ -227,9 +227,17 @@ impl Classifier for QuiltClassifier { /// /// Detects `[ 42%]`-style progress markers, hides directory enter/leave /// noise, and shows compile/link/dh lines as well as kbuild status-tag -/// lines and warnings. +/// lines and warnings. Collapses the per-file `chown: ... Operation not +/// permitted` flood of a rootless build into one digest line: a rules +/// chown failing for the unprivileged build user is expected and harmless +/// (the .deb is assembled in the binary step's fakeroot session, which +/// records root ownership anyway), but a sysroot can emit thousands of +/// lines. #[derive(Default)] -pub struct MakeClassifier {} +pub struct MakeClassifier { + /// How many chown-EPERM lines were collapsed so far. + suppressed_chown: usize, +} impl MakeClassifier { /// Create a new classifier @@ -255,6 +263,11 @@ impl Classifier for MakeClassifier { .unwrap() }); + static CHOWN_EPERM_RE: LazyLock = LazyLock::new(|| { + Regex::new(r"^chown: (changing ownership|cannot read directory|cannot operate on)") + .unwrap() + }); + if let Some(caps) = percent_re.captures(line) { let pct: u64 = caps[1].parse().unwrap_or(0); return vec![ @@ -266,6 +279,18 @@ impl Classifier for MakeClassifier { ]; } + if CHOWN_EPERM_RE.is_match(line) { + self.suppressed_chown += 1; + return if self.suppressed_chown == 1 { + vec![Action::Warning(truncate( + "chown: ownership changes skipped by the unprivileged build user \ + (harmless for the .deb; further messages collapsed)", + ))] + } else { + vec![Action::Hidden] + }; + } + if line.contains("error:") || line.contains("Error ") || line.contains("*** [") @@ -538,6 +563,52 @@ mod tests { ); } + /// A rootless build's per-file chown failures collapse into one + /// digest warning; every other line keeps its normal classification. + #[test] + fn test_make_collapses_chown_eperm_flood() { + let mut c = MakeClassifier::new(); + let first = "chown: changing ownership of \ + '/tmp/pkh-build/k3/demos/sysroot/usr/share/zoneinfo/Asia/Sao_Paulo': \ + Operation not permitted"; + assert_eq!( + feed_one(&mut c, first), + vec![Action::Warning( + "chown: ownership changes skipped by the unprivileged build user \ + (harmless for the .deb; further messages collapsed)" + .to_string() + )] + ); + assert_eq!( + feed_one( + &mut c, + "chown: changing ownership of \ + '/tmp/pkh-build/k3/demos/sysroot/usr/share/zoneinfo/Asia/Tokyo': \ + Operation not permitted" + ), + vec![Action::Hidden] + ); + assert_eq!( + feed_one( + &mut c, + "chown: cannot read directory '/tmp/pkh-build/k3/demos': Permission denied" + ), + vec![Action::Hidden] + ); + // A non-chown line keeps its own classification after the flood. + assert_eq!( + feed_one(&mut c, " CC [M] drivers/net/foo.o"), + vec![Action::Shown(" CC [M] drivers/net/foo.o".to_string())] + ); + // The digest must not swallow real make errors. + assert_eq!( + feed_one(&mut c, "make[1]: *** [Makefile:531: all] Error 2"), + vec![Action::Error( + "make[1]: *** [Makefile:531: all] Error 2".to_string() + )] + ); + } + #[test] fn test_make_shows_kbuild_status_tags() { let mut c = MakeClassifier::new();