From 0491c5b7337ecf5141a3f8c96de649efa88c0e07 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:48:11 -0300 Subject: [PATCH 1/3] test(xcat-core): the openEuler ppc64le core build cannot write its own resultdir Every package of xcat-core-build-openeuler24.03-ppc64le #1 died before rpmbuild started: ERROR: Could not create dir dist/openeuler-24.03-ppc64le/rpms/SRPMS/. Error: [Errno 13] Permission denied: 'dist' 30 such lines, 0 srpms. buildrpms.pl passes a relative --resultdir inside a source tree owned by root, and mock 6.8 creates that directory as chrootuid. openeuler-24.03-ppc64le.cfg declares chrootuid 1000 for the native dependency prerequisites, and createmockconfig copied the target configuration verbatim, so the core build inherited a uid that cannot write there. No openEuler x86_64 configuration declares one, which is why only the ppc64le target fails. createmockconfig had no unit test because it built its text inline and wrote it straight to /etc/mock. The rendering moves to XCAT::BuildUtils::mock_config_text, which takes the base text and returns the rendered text; the script keeps the two file operations. buildrpms_mock_resultdir_uid.t drives it directly and fails 2 of its 11 checks, both on the uid the rendered configuration carries. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- build-utils/lib/XCAT/BuildUtils.pm | 40 +++++++++++++ buildrpms.pl | 22 ++----- xCAT-test/unit/buildrpms_mock_resultdir_uid.t | 57 +++++++++++++++++++ 3 files changed, 101 insertions(+), 18 deletions(-) create mode 100644 xCAT-test/unit/buildrpms_mock_resultdir_uid.t diff --git a/build-utils/lib/XCAT/BuildUtils.pm b/build-utils/lib/XCAT/BuildUtils.pm index 9bf034c1e..7427ebf3d 100644 --- a/build-utils/lib/XCAT/BuildUtils.pm +++ b/build-utils/lib/XCAT/BuildUtils.pm @@ -42,6 +42,7 @@ our @EXPORT_OK = qw( buildinfo_text targetarch_from_target openeuler_build_target openeuler_repo_subdir + mock_config_text genesis_chroot_name genesis_target_arch genesis_build_plan genesis_log_errors genesis_log_deny_rules deb_belongs_to_dist genesis_dists genesis_dist_reason @@ -1115,3 +1116,42 @@ sub _pid_alive { } 1; + +#------------------------------------------------------------------------------- + +=head3 mock_config_text + + Descriptions: Render the per-package mock configuration from a target configuration. + + Arguments: + $pkg - the package being built, e.g. xCAT or perl-xCAT + $target - the mock target, e.g. openeuler-24.03-ppc64le + $base - the text of /etc/mock/.cfg + $epoch - SOURCE_DATE_EPOCH, passed into the chroot + + Returns: the text to write to /etc/mock/-.cfg + +=cut + +#------------------------------------------------------------------------------- +sub mock_config_text { + my ($pkg, $target, $base, $epoch) = @_; + my $chroot = "$pkg-$target"; + my $text = $base; + $text =~ s/config_opts\['root'\]\s+=.*/config_opts['root'] = \"$chroot\"/; + if ($pkg eq "perl-xCAT" && $target !~ /suse|sles|leap/i) { + # perl-generators exports perl(xCAT::...) provides on RHEL/Fedora; it does not + # exist on openSUSE/SLES (rpm there generates perl provides itself), so injecting + # it into a SUSE chroot aborts chroot setup. Suppress it for SUSE targets. + $text .= "config_opts['chroot_additional_packages'] = 'perl-generators'\n"; + } + $text .= "config_opts['environment']['SOURCE_DATE_EPOCH'] = '$epoch'\n"; + # Avoid systemd-nspawn: it INTERMITTENTLY fails chroot setup with + # "Failed to determine whether the unified cgroups hierarchy is used: No medium found" + # (ENOMEDIUM), which drops that package from the (still-signed) core -> an incomplete build + # that only surfaces later as a confusing MN install failure. 'simple' isolation is a plain + # chroot -- reliable for these RPM builds -- and sidesteps the nspawn cgroup race entirely. + $text .= "config_opts['isolation'] = 'simple'\n"; + return $text; +} + diff --git a/buildrpms.pl b/buildrpms.pl index c350d0351..1b2ca65e3 100755 --- a/buildrpms.pl +++ b/buildrpms.pl @@ -59,7 +59,8 @@ use XCAT::BuildUtils qw(git_revision source_date_epoch sh sh_or_die usage buildi sweep_build_sources_dirs stage_xcat_probe_sources stage_xcatsn_templates stage_genesis_base_sources write_script read_line targetarch_from_target - openeuler_build_target openeuler_repo_subdir); + openeuler_build_target openeuler_repo_subdir + mock_config_text); use POSIX (); # EEXIST, for the directory build lock (see main()) use Getopt::Long qw(GetOptions); use POSIX qw(strftime); @@ -325,23 +326,8 @@ sub createmockconfig { my $chroot = "$pkg-$target$ext"; my $cfgfile = "/etc/mock/$chroot.cfg"; return if -f $cfgfile && ! $opts{force}; - cp "/etc/mock/$target.cfg", $cfgfile; - my $contents = read_text($cfgfile); - $contents =~ s/config_opts\['root'\]\s+=.*/config_opts['root'] = \"$chroot\"/; - if ($pkg eq "perl-xCAT" && $target !~ /suse|sles|leap/i) { - # perl-generators exports perl(xCAT::...) provides on RHEL/Fedora; it does not - # exist on openSUSE/SLES (rpm there generates perl provides itself), so injecting - # it into a SUSE chroot aborts chroot setup. Suppress it for SUSE targets. - $contents .= "config_opts['chroot_additional_packages'] = 'perl-generators'\n"; - } - $contents .= "config_opts['environment']['SOURCE_DATE_EPOCH'] = '$SOURCE_DATE_EPOCH'\n"; - # Avoid systemd-nspawn: it INTERMITTENTLY fails chroot setup with - # "Failed to determine whether the unified cgroups hierarchy is used: No medium found" - # (ENOMEDIUM), which drops that package from the (still-signed) core -> an incomplete build that - # only surfaces later as a confusing MN install failure. 'simple' isolation is a plain chroot -- - # reliable for these RPM builds -- and sidesteps the nspawn cgroup race entirely. - $contents .= "config_opts['isolation'] = 'simple'\n"; - write_text($cfgfile, $contents); + write_text($cfgfile, + mock_config_text($pkg, "$target$ext", read_text("/etc/mock/$target.cfg"), $SOURCE_DATE_EPOCH)); } sub buildsources_genesis_base($) { diff --git a/xCAT-test/unit/buildrpms_mock_resultdir_uid.t b/xCAT-test/unit/buildrpms_mock_resultdir_uid.t new file mode 100644 index 000000000..13c04f718 --- /dev/null +++ b/xCAT-test/unit/buildrpms_mock_resultdir_uid.t @@ -0,0 +1,57 @@ +#!/usr/bin/env perl +# mock creates --resultdir as chrootuid, and buildrpms.pl passes a relative one under a +# root-owned tree. These assertions read the RENDERED configuration, not the builder. +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/../../build-utils/lib"; +use Test::More; +use XCAT::BuildUtils qw(mock_config_text); + +my $ppc = <<'CFG'; +config_opts['root'] = 'openeuler-24.03-ppc64le' +config_opts['target_arch'] = 'ppc64le' +config_opts['chrootuid'] = 1000 +config_opts['chrootgid'] = 1000 +config_opts['use_bootstrap'] = False +CFG + +my $x86 = <<'CFG'; +include('templates/openeuler-24.03.tpl') +config_opts['root'] = 'openeuler-24.03sp4-x86_64' +config_opts['target_arch'] = 'x86_64' +CFG + +# mock takes the last assignment. +sub effective { + my ($text, $opt) = @_; + my $v; + for my $line (split /\n/, $text) { + $v = $1 if $line =~ /\Aconfig_opts\['\Q$opt\E'\]\s*=\s*(.+?)\s*\z/; + } + return $v; +} + +my $out = mock_config_text('xCAT', 'openeuler-24.03-ppc64le', $ppc, 1790988617); +is(effective($out, 'chrootuid'), '0', 'the ppc64le core build writes its resultdir as root'); +is(effective($out, 'chrootgid'), '1000', 'the group the target declared is left alone'); +is(effective($out, 'root'), '"xCAT-openeuler-24.03-ppc64le"', 'the chroot is still named per package'); + +# mock also reads this file for the chroot, so the inherited line stays. +like($out, qr/\Qconfig_opts['chrootuid'] = 1000\E/, 'the declaration xcat-dep ships is left in place'); + +my $plain = mock_config_text('xCAT', 'openeuler-24.03sp4-x86_64', $x86, 1790988617); +is(effective($plain, 'chrootuid'), '0', 'a target that declares no uid is pinned to root too'); +is(effective($plain, 'chrootgid'), undef, 'and no group is invented for it'); +is(effective($plain, 'target_arch'), "'x86_64'", 'nothing else in the base configuration changes'); + +like($out, qr/\Qconfig_opts['environment']['SOURCE_DATE_EPOCH'] = '1790988617'\E/, + 'the reproducible build date is passed into the chroot'); +like($out, qr/\Qconfig_opts['isolation'] = 'simple'\E/, 'nspawn is avoided'); +like(mock_config_text('perl-xCAT', 'openeuler-24.03-ppc64le', $ppc, 1), + qr/\Qconfig_opts['chroot_additional_packages'] = 'perl-generators'\E/, + 'perl-xCAT still gets perl-generators on an rpm-md target'); +unlike(mock_config_text('perl-xCAT', 'opensuse-leap-15.6-x86_64', $x86, 1), + qr/perl-generators/, 'and still does not on a SUSE target'); + +done_testing(); From 871f8362f91416ed65426b91b5a546d8e1ea0362 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:48:20 -0300 Subject: [PATCH 2/3] fix(xcat-core): the openEuler ppc64le core build cannot write its own resultdir mock refuses every package of the openeuler-24.03-ppc64le target with "Could not create dir dist/openeuler-24.03-ppc64le/rpms/SRPMS/. Error: [Errno 13] Permission denied: 'dist'", so the target builds nothing. mock 6.8 reloads its uid manager from chrootuid, so chrootuid now also owns --resultdir and the three log files mock writes beside it. buildrpms.pl passes a relative resultdir under a source tree owned by root. xcat-dep's openeuler-24.03-ppc64le.cfg declares chrootuid 1000, for the native dependency prerequisites that need a non-root build identity, and mock_config_text copied the target configuration verbatim. mock_config_text now assigns chrootuid 0 after the inherited lines. An xcat-core rpm build has no declared non-root identity, and every other target already builds as root because no other configuration sets the option. The inherited declaration stays in the file, so the chroot still sees what xcat-dep shipped. chrootgid is left alone. mock creates the in-chroot mock group with it, and `groupadd mock -g 0` fails because the root group already holds gid 0: pinning both took the target from a resultdir failure to "mock --init failed" on all fourteen packages. root creates the resultdir whatever group it carries. buildrpms_mock_resultdir_uid.t fails 2 of 11 without this change. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- build-utils/lib/XCAT/BuildUtils.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build-utils/lib/XCAT/BuildUtils.pm b/build-utils/lib/XCAT/BuildUtils.pm index 7427ebf3d..7a26358aa 100644 --- a/build-utils/lib/XCAT/BuildUtils.pm +++ b/build-utils/lib/XCAT/BuildUtils.pm @@ -1152,6 +1152,10 @@ sub mock_config_text { # that only surfaces later as a confusing MN install failure. 'simple' isolation is a plain # chroot -- reliable for these RPM builds -- and sidesteps the nspawn cgroup race entirely. $text .= "config_opts['isolation'] = 'simple'\n"; + # mock creates --resultdir and its own logs as chrootuid, and buildrpms.pl passes a + # relative one under a root-owned tree. The last assignment wins; the inherited line + # stays for the chroot. chrootgid is left alone: gid 0 collides with the root group. + $text .= "config_opts['chrootuid'] = 0\n"; return $text; } From 324507651c9a0ecd2b8c960880c3845217f17cfe Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Tue, 6 Oct 2026 22:05:25 -0300 Subject: [PATCH 3/3] fix(xcat-core): the chrootuid comment and its assertion describe mock wrongly build-utils/lib/XCAT/BuildUtils.pm said the inherited chrootuid 1000 stays in effect for the chroot. A mock configuration is Python, so the later assignment replaces the value and nothing reads 1000 afterwards. The in-chroot build user becomes uid 0. buildrpms_mock_resultdir_uid.t asserted that the inherited line is still present. The pattern is a line of the test's own fixture and mock_config_text only appends, so the assertion passes even when the function returns the base text unchanged. It is removed. The comment now states the replacement and its consequence, and records why chrootgid is left unset. Without the chrootuid assignment the file fails 2 of 10. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- build-utils/lib/XCAT/BuildUtils.pm | 7 ++++--- xCAT-test/unit/buildrpms_mock_resultdir_uid.t | 3 --- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/build-utils/lib/XCAT/BuildUtils.pm b/build-utils/lib/XCAT/BuildUtils.pm index 7a26358aa..6e610ee23 100644 --- a/build-utils/lib/XCAT/BuildUtils.pm +++ b/build-utils/lib/XCAT/BuildUtils.pm @@ -1152,10 +1152,11 @@ sub mock_config_text { # that only surfaces later as a confusing MN install failure. 'simple' isolation is a plain # chroot -- reliable for these RPM builds -- and sidesteps the nspawn cgroup race entirely. $text .= "config_opts['isolation'] = 'simple'\n"; - # mock creates --resultdir and its own logs as chrootuid, and buildrpms.pl passes a - # relative one under a root-owned tree. The last assignment wins; the inherited line - # stays for the chroot. chrootgid is left alone: gid 0 collides with the root group. + # mock creates --resultdir and its logs as chrootuid, and buildrpms.pl passes a relative + # one under a tree it owns as root. This assignment replaces any uid the target declares, + # so the in-chroot build user becomes uid 0 as well. $text .= "config_opts['chrootuid'] = 0\n"; + # chrootgid is left unset on purpose: groupadd for the in-chroot group fails on gid 0. return $text; } diff --git a/xCAT-test/unit/buildrpms_mock_resultdir_uid.t b/xCAT-test/unit/buildrpms_mock_resultdir_uid.t index 13c04f718..4d0cfc982 100644 --- a/xCAT-test/unit/buildrpms_mock_resultdir_uid.t +++ b/xCAT-test/unit/buildrpms_mock_resultdir_uid.t @@ -37,9 +37,6 @@ is(effective($out, 'chrootuid'), '0', 'the ppc64le core build writes its resultd is(effective($out, 'chrootgid'), '1000', 'the group the target declared is left alone'); is(effective($out, 'root'), '"xCAT-openeuler-24.03-ppc64le"', 'the chroot is still named per package'); -# mock also reads this file for the chroot, so the inherited line stays. -like($out, qr/\Qconfig_opts['chrootuid'] = 1000\E/, 'the declaration xcat-dep ships is left in place'); - my $plain = mock_config_text('xCAT', 'openeuler-24.03sp4-x86_64', $x86, 1790988617); is(effective($plain, 'chrootuid'), '0', 'a target that declares no uid is pinned to root too'); is(effective($plain, 'chrootgid'), undef, 'and no group is invented for it');