From 32eb096bfe167fb44f3e960d478cde9966bd3f75 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Thu, 8 Oct 2026 11:31:07 -0300 Subject: [PATCH] fix(xcat-core): the mock result directory owner ignores include() and stale logs mock_build_owner parsed chrootuid and chrootgid from the text of the rendered configuration. A value set in an included file, an assignment with an inline comment or a double-quoted key, and an include after an assignment all gave a uid that mock does not build as. Also, a root build that stopped part way left root-owned 0644 logs in the result directories, and the next build, as uid 1000, could not append to them. mock_build_owner now asks mock's own loader, mockbuild.config.load_config, for chrootuid and chrootgid of the chroot that buildrpms.pl passes to mock -r. It fails with a clear message when mock is not installed. prepare_mock_resultdirs moves to XCAT::BuildUtils. It also gives the files already in both result directories to that owner, so a retry can append to the old logs and replace a package of the same version. When mock builds as the caller, nothing changes owner. native/mock_resultdir_owner.t gave the wrong uid for four of its configuration cases, and failed the 11 retry checks, before this change. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- build-utils/lib/XCAT/BuildUtils.pm | 85 +++++++++++++++++++++++------- buildrpms.pl | 19 ++----- 2 files changed, 69 insertions(+), 35 deletions(-) diff --git a/build-utils/lib/XCAT/BuildUtils.pm b/build-utils/lib/XCAT/BuildUtils.pm index 3f165dbe9..91e2bb792 100644 --- a/build-utils/lib/XCAT/BuildUtils.pm +++ b/build-utils/lib/XCAT/BuildUtils.pm @@ -42,7 +42,7 @@ our @EXPORT_OK = qw( buildinfo_text targetarch_from_target openeuler_build_target openeuler_repo_subdir - mock_config_text mock_build_owner + mock_config_text mock_build_owner prepare_mock_resultdirs 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 @@ -1160,32 +1160,77 @@ sub mock_config_text { =head3 mock_build_owner - Descriptions: Report the uid and gid mock creates its --resultdir as. - - mock creates the result directory and its logs as chrootuid, and buildrpms.pl - passes a relative one under a tree it owns as root. The caller gives the - directory to this owner before the build starts. - - No mock template declares chrootuid or chrootgid (56 on the ppc64le builder, - none), so the target configuration carries the whole answer. + Descriptions: Report the uid and gid mock builds as, from mock's own + configuration loader. The loader resolves include() and + the assignments around it as a build does. Arguments: - $text - the rendered mock configuration + $chroot - the name passed to mock -r + $configdir - the mock configuration directory; mock's default when undef - Returns: ($uid, $gid), each undef when the configuration declares none. mock - then builds as the calling user, and the directory needs no change. + Returns: ($uid, $gid). With no chrootuid, $uid is the caller, as in mock. + $gid is undef when the loader reports none. =cut #------------------------------------------------------------------------------- +my $MOCK_OWNER_LOADER = <<'PYTHON'; +import sys +try: + from mockbuild.config import MOCKCONFDIR, load_config +except ImportError: + sys.exit('mock is not installed: cannot import mockbuild.config') +opts = load_config(sys.argv[2] or MOCKCONFDIR, sys.argv[1]) +print(opts['chrootuid'], opts.get('chrootgid', '')) +PYTHON + sub mock_build_owner { - my ($text) = @_; - my ($uid, $gid); - # mock takes the last assignment. - for my $line (split /\n/, $text) { - $uid = $1 if $line =~ /\Aconfig_opts\['chrootuid'\]\s*=\s*(\d+)\s*\z/; - $gid = $1 if $line =~ /\Aconfig_opts\['chrootgid'\]\s*=\s*(\d+)\s*\z/; - } - return ($uid, $gid); + my ($chroot, $configdir) = @_; + open my $pipe, '-|', 'python3', '-c', $MOCK_OWNER_LOADER, $chroot, $configdir // '' + or die "Cannot run python3 to load the mock configuration $chroot: $!\n"; + my $out = do { local $/; <$pipe> } // ''; + close $pipe or die "mock cannot load the configuration $chroot\n"; + my ($uid, $gid) = $out =~ /\A(\d+) (\d*)\n\z/ + or die "mock gives no chrootuid for $chroot\n"; + return ($uid, length $gid ? $gid : undef); +} + +#------------------------------------------------------------------------------- + +=head3 prepare_mock_resultdirs + + Descriptions: Give the --resultdir directories, and the files a previous + build left in them, to the uid and gid mock builds as. + mock writes its logs and packages there as chrootuid. A + root build that stopped part way leaves root-owned 0644 + files that a non-root build cannot append to or replace. + + Arguments: + $chroot - the name passed to mock -r + $configdir - the mock configuration directory; mock's default when undef + @dirs - the result directories, created when missing + + Returns: nothing. Dies when a chown fails. + +=cut + +#------------------------------------------------------------------------------- +sub prepare_mock_resultdirs { + my ($chroot, $configdir, @dirs) = @_; + my ($uid, $gid) = mock_build_owner($chroot, $configdir); + make_path(@dirs); + return if $uid == $>; + $gid //= -1; + for my $dir (@dirs) { + opendir(my $dh, $dir) or die "Cannot read $dir: $!\n"; + my @files = grep { -f $_ && !-l $_ } map { "$dir/$_" } readdir $dh; + closedir $dh; + for my $path ($dir, @files) { + chown($uid, $gid, $path) == 1 + or die "FATAL: cannot give $path to uid $uid: $!\n"; + } + # mock writes the packages as chrootuid:chrootgid. + chmod 0775, $dir; + } } diff --git a/buildrpms.pl b/buildrpms.pl index bc5d3ae86..3c623dd07 100755 --- a/buildrpms.pl +++ b/buildrpms.pl @@ -60,7 +60,7 @@ use XCAT::BuildUtils qw(git_revision source_date_epoch sh sh_or_die usage buildi stage_genesis_base_sources write_script read_line targetarch_from_target openeuler_build_target openeuler_repo_subdir - mock_config_text mock_build_owner); + mock_config_text); use POSIX (); # EEXIST, for the directory build lock (see main()) use Getopt::Long qw(GetOptions); use POSIX qw(strftime); @@ -330,23 +330,12 @@ sub createmockconfig { mock_config_text($pkg, "$target$ext", read_text("/etc/mock/$target.cfg"), $SOURCE_DATE_EPOCH)); } -# mock creates --resultdir and the logs under it as chrootuid, and the two mock calls below -# pass a relative directory under dist/, which this script owns as root. Give the directory -# to the build uid before mock runs, so the build does not need to be root to write its own -# results. openeuler-24.03-ppc64le is the only target that declares a uid; everywhere else -# mock builds as the caller and there is nothing to change. +# mock writes --resultdir as chrootuid, and this script runs as root. sub prepare_mock_resultdirs { my ($pkg, $target) = @_; my $ext = $opts{mock_uniqueext} ? "-$opts{mock_uniqueext}" : ""; - my ($uid, $gid) = mock_build_owner(read_text("/etc/mock/$pkg-$target$ext.cfg")); - return unless defined $uid || defined $gid; - for my $dir ("dist/$target/rpms", "dist/$target/rpms/SRPMS") { - make_path($dir); - chown(defined $uid ? $uid : -1, defined $gid ? $gid : -1, $dir) == 1 - or die "FATAL: cannot give $dir to the build uid: $!\n"; - # The group needs the write bit: mock writes the rpms as chrootuid:chrootgid. - chmod 0775, $dir; - } + XCAT::BuildUtils::prepare_mock_resultdirs("$pkg-$target$ext", undef, + "dist/$target/rpms", "dist/$target/rpms/SRPMS"); } sub buildsources_genesis_base($) {