diff --git a/.github/workflows/genesis-openembedded.yml b/.github/workflows/genesis-openembedded.yml index f3ffdf6..d419823 100644 --- a/.github/workflows/genesis-openembedded.yml +++ b/.github/workflows/genesis-openembedded.yml @@ -78,6 +78,8 @@ jobs: sudo -E prove -v -It/lib t/common-repo-gate.t prove -v t/openeuler.t prove -v t/openeuler_mock_template_include.t + prove -v t/openeuler_native_result_permissions.t + prove -v -Ilib t/openeuler_native_procenv_plugin.t prove -v -Ilib t/openeuler_native_trust_dbpath.t prove -v -Ilib t/openeuler_native_cpio_scan.t bats t/verify-xcatsn-install.bats diff --git a/MockBuildUtils.pm b/MockBuildUtils.pm index 2dca2e3..4280d23 100644 --- a/MockBuildUtils.pm +++ b/MockBuildUtils.pm @@ -25,6 +25,7 @@ our @EXPORT_OK = qw( cross_copy_genesis finalize_xcat_dep bump_dep_release_suffix createrepo_c_cmd sign_and_index_repo build_mock_uniqueext rpm_in_cell resolve_mock_cfg + native_owner_command NATIVE_BUILD_GID openeuler_build_target openeuler_repo_subdir recover_common_repository ); @@ -101,6 +102,26 @@ sub sh_quote { return "'$s'"; } +# The host group the native openEuler builds run as: xcatnative, gid 1000. The native mock overlays +# set chrootgid to it and chrootuid to the catalog's build_uid, so mock creates --resultdir and opens +# state.log, build.log and root.log in it as that uid and group. +use constant NATIVE_BUILD_GID => 1000; + +# native_owner_command: wrap a native build step so it runs against $overlay instead of the target's +# own mock configuration, and with the identity mock will write results as. +# +# A dep builder makes its own result directories before mock starts -- ipxe-xcat/mockbuild.pl calls +# make_path on $work_dir/srpm and $work_dir/rpm and passes each one to mock as --resultdir. The +# script runs as root, so without the gid and the umask below those directories are root root 0755 +# and mock, reloaded to chrootuid 1000, cannot open its logs in them. +sub native_owner_command { + my ($overlay, $mock_cfg, $command) = @_; + my $script = 'umask 0002 && mount --bind ' . sh_quote($overlay) . ' ' . sh_quote($mock_cfg) + . ' && exec sh -c ' . sh_quote($command); + return 'unshare --mount --propagation private -- setpriv --regid ' . NATIVE_BUILD_GID + . ' --clear-groups -- sh -c ' . sh_quote($script); +} + # print_step: print a step banner. sub print_step { my ($msg) = @_; diff --git a/lib/XCAT/NativeInputs.pm b/lib/XCAT/NativeInputs.pm index 065c136..1c3d8cf 100644 --- a/lib/XCAT/NativeInputs.pm +++ b/lib/XCAT/NativeInputs.pm @@ -14,7 +14,7 @@ use File::Temp qw(tempdir); use JSON::PP; our @EXPORT_OK = qw(load_inputs stage_inputs verify_input rpm_identity validate_outputs publisher_trust - trust_dbpath scan_cpio_for_elf); + trust_dbpath scan_cpio_for_elf native_overlay_text); sub read_file { my ($path) = @_; @@ -79,7 +79,7 @@ sub load_inputs { die "Invalid native input type '$type'\n" unless $type =~ /\A(?:srpm|publisher|owner)\z/; if ($type eq 'owner') { die "Unsupported native build owner '$name'\n" unless grep { $_ eq $name } - qw(goconserver grub2-xcat ipmitool-xcat syslinux-xcat xnba-undi xCAT-genesis-base + qw(goconserver grub2-xcat ipmitool-xcat ipxe-xcat syslinux-xcat xnba-undi xCAT-genesis-base perl-Crypt-Rijndael perl-Crypt-SSLeay perl-HTTP-Async perl-IO-Stty perl-Net-HTTPS-NB perl-Net-Telnet); } if ($type ne 'publisher') { @@ -300,4 +300,44 @@ sub validate_outputs { return \%seen; } +#------------------------------------------------------------------------------- + +=head3 native_overlay_text + + Descriptions: Render one native mock overlay configuration. + + Arguments: + $purpose - 1000, 0 or 'procenv'. 'procenv' is the overlay that builds + procenv itself. + $base - path of the snapshotted base mock configuration to include. + $prereqs - path of the local repository holding the built prerequisites. + + Returns: the overlay file content. + +=cut + +#------------------------------------------------------------------------------- +sub native_overlay_text { + my ($purpose, $base, $prereqs) = @_; + my $bootstrap = $purpose eq 'procenv'; + my $uid = $bootstrap ? 1000 : $purpose; + my $url = $prereqs; + $url =~ s{([^A-Za-z0-9_./~-])}{sprintf('%%%02X', ord($1))}ge; + my $repo = "\n[xcat-native-inputs]\nname=xCAT native build prerequisites\nbaseurl=file://$url\n" + . "gpgkey=file://$url/repodata/repomd.xml.key\ngpgcheck=1\nrepo_gpgcheck=1\n" + . "enabled=1\nskip_if_unavailable=0\n"; + my $out = 'include(' . JSON::PP->new->encode($base) . ")\n"; + $out .= "config_opts['chrootuid'] = $uid\nconfig_opts['chrootgid'] = 1000\n"; + $out .= "config_opts['dnf.conf'] += \"\"\"$repo\"\"\"\n"; + $out .= "config_opts['plugin_conf']['bind_mount_enable'] = True\n"; + # mock's procenv plugin runs the HOST's /usr/bin/procenv, not the chroot's, and it installs + # procenv into the buildroot itself through preexisting_deps. Enabling it made every native + # build need a host package neither repository declares, for a log nothing reads. mock's + # default is off; leave it there. + $out .= "config_opts['plugin_conf']['procenv_enable'] = False\n"; + $out .= "config_opts['plugin_conf']['bind_mount_opts']['dirs'].append(" + . '(' . JSON::PP->new->encode($prereqs) . ', ' . JSON::PP->new->encode($prereqs) . "))\n"; + return $out; +} + 1; diff --git a/mockbuild-all.pl b/mockbuild-all.pl index 176999a..b2a0420 100755 --- a/mockbuild-all.pl +++ b/mockbuild-all.pl @@ -19,6 +19,7 @@ use FindBin qw($RealBin); use lib $RealBin, "$RealBin/lib"; use XCAT::NFSLock (); use MockBuildUtils qw(sh_quote print_step version_matches required_pkgs rpm_in_cell resolve_mock_cfg + NATIVE_BUILD_GID carry_over_rpms rpm_name rpm_arch rpm_source_rpm rpm_digests_ok install_deps_packages install_deps_command missing_perl_modules read_manifest derive_target_from_repo_path @@ -156,6 +157,21 @@ my @HELD_LOCKS; my $LOCK_OWNER_PID; my ($COMMON_STAGE, $COMMON_DESTINATION, $COMMON_BACKUP); my %NATIVE_PLANS; + +# Create a directory mock writes for a native build. mock 6.8 reloads its uid manager from +# chrootuid, so it creates --resultdir and opens state.log, build.log and root.log in it as +# chrootuid/chrootgid, not as the user that started mock. This script runs as root, so such a +# directory carries the native build gid and is group writable. native_owner_command gives the step +# the same gid and a 0002 umask, so what the step creates below it inherits both. +sub make_native_path { + my ($native, @dirs) = @_; + make_path(@dirs); + return unless $native; + for my $dir (@dirs) { + chown -1, NATIVE_BUILD_GID, $dir or die "Cannot set the native build group on $dir: $!\n"; + chmod 0775, $dir or die "Cannot make $dir writable by the native build group: $!\n"; + } +} for my $sig (qw(INT TERM HUP)) { $SIG{$sig} = sub { exit 1; }; } @@ -739,6 +755,7 @@ if (!$skip_build) { my $script = $builder->{script}; my $step_result = "$build_root/$name"; my $step_log = "$log_root/$name"; + make_native_path($native, $step_result) if $native && !$dry_run; my $step_uniqueext = build_mock_uniqueext($run_id, ++$build_step_seq, $name); my $mock_cfg = $builder->{noarch} ? $profile->{noarch_cfg} : $target; my $cmd = $builder->{srpm} @@ -778,6 +795,7 @@ if (!$skip_build) { if (!$skip_perl && @perl_pkgs) { my $perl_result = "$build_root/perl/$arch"; my $perl_log = "$log_root/perl/$arch"; + make_native_path($native, $perl_result) if $native && !$dry_run; my $perl_uniqueext = build_mock_uniqueext($run_id, ++$build_step_seq, 'perl-list6'); # Bound the perl builder's OWN internal parallelism to this target's budget; otherwise it # forks one mock build per perl package (~7), which -- multiplied by parallel EL targets -- @@ -858,7 +876,8 @@ if (!$skip_build) { '--gpg-home', shell_quote(File::Spec->rel2abs($gpg_home ne '' ? $gpg_home : $ENV{GNUPGHOME} || "$ENV{HOME}/.gnupg"))) : ()), ); - $cmd = native_owner_command($native, $target, $cmd, 0) if $native; + $cmd = native_owner_command($native, $target, $cmd, + $native->{nodes}{'xCAT-genesis-base'}{build_uid}) if $native; push @build_steps, { id => 'genesis', step => 'Build xCAT-genesis-base (per-target, OS-dependent)', @@ -873,7 +892,8 @@ if (!$skip_build) { if (@build_steps) { if ($native) { for my $step (grep { $_->{id} ne 'genesis' } @build_steps) { - $step->{cmd} = native_owner_command($native, $target, $step->{cmd}, 1000); + $step->{cmd} = native_owner_command($native, $target, $step->{cmd}, + native_step_build_uid($native, $step)); } } # Prefer the caller-supplied cap (global budget / active targets). Fall back to the old @@ -1147,8 +1167,8 @@ sub target_profile { noarch_cfg => $target, forcearch => 0, epel => 0, - dep_builders => [qw(grub2-xcat ipmitool-xcat syslinux-xcat goconserver conserver-xcat xnba-undi python3-scp)], - required => [qw(ipmitool-xcat syslinux-xcat grub2-xcat xnba-undi + dep_builders => [qw(grub2-xcat ipmitool-xcat syslinux-xcat goconserver conserver-xcat xnba-undi ipxe-xcat python3-scp)], + required => [qw(ipmitool-xcat syslinux-xcat grub2-xcat xnba-undi ipxe-xcat perl-IO-Stty perl-HTTP-Async perl-Net-HTTPS-NB)], }; } @@ -1200,9 +1220,10 @@ sub install_mock_cfg { sub source_rpm_build_command { my ($builder, $target, $uniqueext, $result, $log, $work) = @_; + my $native = $NATIVE_PLANS{$target}; my $cfg = "$work/mock-deterministic.cfg"; if (!$dry_run) { - make_path($work, $result); + make_native_path($native, $work, $result); open my $fh, '>', $cfg or die "Cannot write $cfg: $!\n"; print {$fh} "include('/etc/mock/$target.cfg')\n"; print {$fh} "config_opts['environment']['SOURCE_DATE_EPOCH'] = '$SOURCE_DATE_EPOCH'\n"; @@ -1234,7 +1255,7 @@ sub source_rpm_build_command { . ' -i ' . sh_quote($path), log => "$log/spec-patch.log"); } my $restamped = "$work/restamp-srpm"; - make_path($restamped) unless $dry_run; + make_native_path($native, $restamped) unless $dry_run; $prefix = "$mock --buildsrpm --spec " . sh_quote($specs[0]) . ' --sources ' . sh_quote("$top/SOURCES") . ' --resultdir ' . sh_quote($restamped) . ' && set -- ' . sh_quote($restamped) . '/*.src.rpm' @@ -1244,12 +1265,21 @@ sub source_rpm_build_command { return $prefix . "$mock --rebuild $srpm --resultdir " . sh_quote($result); } +# The uid a step builds as, from the catalog. xnba-undi declares 0, like xCAT-genesis-base, and the +# step id does not say so: reading the uid off the id built it as 1000 against its own declaration. +sub native_step_build_uid { + my ($plan, $step) = @_; + my %uids = map { ($plan->{nodes}{$_}{build_uid} // 1000) => 1 } + sort keys %{$step->{native_results} // {}}; + die "Step $step->{id} mixes native build uids: " . join(', ', sort keys %uids) . "\n" + if keys(%uids) > 1; + return (keys %uids)[0] // 1000; +} + sub native_owner_command { my ($plan, $target, $command, $uid) = @_; my $overlay = $plan->{overlays}{$uid} // die "Missing native mock overlay\n"; - my $script = 'mount --bind ' . sh_quote($overlay) . ' ' . sh_quote("/etc/mock/$target.cfg") - . ' && exec sh -c ' . sh_quote($command); - return 'unshare --mount --propagation private -- sh -c ' . sh_quote($script); + return MockBuildUtils::native_owner_command($overlay, "/etc/mock/$target.cfg", $command); } sub native_overlay { @@ -1259,21 +1289,9 @@ sub native_overlay { return if $dry_run; my $base = "$work/native-base.cfg"; copy("/etc/mock/$target.cfg", $base) or die "Cannot snapshot native mock configuration: $!\n"; - my $url = $prereqs; - $url =~ s{([^A-Za-z0-9_./~-])}{sprintf('%%%02X', ord($1))}ge; - my $repo = "\n[xcat-native-inputs]\nname=xCAT native build prerequisites\nbaseurl=file://$url\n" - . "gpgkey=file://$url/repodata/repomd.xml.key\ngpgcheck=1\nrepo_gpgcheck=1\n" - . "enabled=1\nskip_if_unavailable=0\n"; for my $key (1000, 0, 'procenv') { - my $uid = $key eq 'procenv' ? 1000 : $key; open my $fh, '>', $plan->{overlays}{$key} or die "Cannot write native overlay: $!\n"; - print {$fh} 'include(' . JSON::PP->new->encode($base) . ")\n"; - print {$fh} "config_opts['chrootuid'] = $uid\nconfig_opts['chrootgid'] = 1000\n"; - print {$fh} "config_opts['dnf.conf'] += \"\"\"$repo\"\"\"\n"; - print {$fh} "config_opts['plugin_conf']['bind_mount_enable'] = True\n"; - print {$fh} "config_opts['plugin_conf']['procenv_enable'] = " . ($key eq 'procenv' ? 'False' : 'True') . "\n"; - print {$fh} "config_opts['plugin_conf']['bind_mount_opts']['dirs'].append(" - . '(' . JSON::PP->new->encode($prereqs) . ', ' . JSON::PP->new->encode($prereqs) . "))\n"; + print {$fh} XCAT::NativeInputs::native_overlay_text($key, $base, $prereqs); close $fh or die "Cannot close native overlay: $!\n"; } open my $ledger, '>', "$work/native-overlays.json" or die "Cannot record native overlays: $!\n"; @@ -1305,6 +1323,7 @@ sub build_native_inputs { my $node = $plan->{nodes}{$name}; next unless $node->{type} eq 'srpm'; my $result = "$work/native-results/$name"; + make_native_path($plan, "$work/native-results") unless $dry_run; my $log = "$logs/native/$name"; my $uniqueext = build_mock_uniqueext("$target-$run_id", ++$sequence, $name); my %builder = (%$node, srpm => $node->{staged} // "$work/native-inputs/$name/" . basename($node->{url})); diff --git a/native/fixtures/power-mock.py b/native/fixtures/power-mock.py index 9abe53e..42a0326 100644 --- a/native/fixtures/power-mock.py +++ b/native/fixtures/power-mock.py @@ -1,13 +1,36 @@ #!/usr/bin/python3 import json, os, pathlib, shutil, sys -import mockbuild -from mockbuild.util import load_config +from mockbuild.config import load_config args = sys.argv[1:] call = {'mock': args} +config = None def value(name): return args[args.index(name)+1] + +def resultdir(path): + """Create --resultdir and open its logs the way mock does. + + mock 6.8 reloads its uid manager from chrootuid, so it creates --resultdir and opens + state.log, build.log and root.log in it as that uid, not as the user that started mock. + """ + uid, gid = config['chrootuid'], config['chrootgid'] + pid = os.fork() + if pid == 0: + try: + if os.geteuid() == 0: + os.setregid(gid, gid) + os.setresuid(uid, uid, 0) + os.makedirs(path, exist_ok=True) + for name in ('state.log', 'build.log', 'root.log'): + open(os.path.join(path, name), 'a+').close() + except OSError as why: + print('mock: %s' % why, file=sys.stderr) + os._exit(1) + os._exit(0) + if os.waitpid(pid, 0)[1] != 0: + sys.exit(1) + if '--rebuild' in args or '--buildsrpm' in args: - load_config('/etc/mock', value('-r'), None, 'native-contract', - str(pathlib.Path(mockbuild.__file__).parent)) + config = load_config('/etc/mock', value('-r')) call['config_rc'] = 0 if '--rebuild' in args: name = pathlib.Path(value('--rebuild')).name.split('-1-1.oe2403')[0] @@ -16,13 +39,13 @@ if '--buildsrpm' in args: call['spec'] = pathlib.Path(value('--spec')).read_text() with open(os.environ['NATIVE_CALLS'], 'a') as f: f.write(json.dumps(call) + '\n') if '--buildsrpm' in args: - dest = pathlib.Path(value('--resultdir')); dest.mkdir(parents=True, exist_ok=True) + dest = pathlib.Path(value('--resultdir')); resultdir(dest) source = json.loads(pathlib.Path(os.environ['NATIVE_OUTPUTS']).read_text())['native-leaf'][1] shutil.copyfile(source, dest / pathlib.Path(source).name) sys.exit(0) if '--rebuild' not in args: sys.exit(0) if name == os.environ.get('NATIVE_FAIL'): sys.exit(42) -dest = pathlib.Path(value('--resultdir')); dest.mkdir(parents=True, exist_ok=True) +dest = pathlib.Path(value('--resultdir')); resultdir(dest) if name == os.environ.get('NATIVE_EMPTY'): sys.exit(0) fixtures = json.loads(pathlib.Path(os.environ['NATIVE_OUTPUTS']).read_text()) for source in fixtures[name]: shutil.copyfile(source, dest / pathlib.Path(source).name) diff --git a/native/openeuler-power-inputs.t b/native/openeuler-power-inputs.t index b13f3c4..1f0be88 100644 --- a/native/openeuler-power-inputs.t +++ b/native/openeuler-power-inputs.t @@ -294,7 +294,7 @@ for my $case (['publisher-elf', qr/ELF payload/], ['publisher-arch', qr/not a no my @namespace = ('unshare', ($> == 0 ? () : ('--user', '--map-root-user')), '--mount', '--propagation', 'private'); my $can_owner = $host_arch eq 'ppc64le' - && run_capture("$tmp/mock-loader.log", 'python3', '-c', 'from mockbuild.util import load_config') == 0 + && run_capture("$tmp/mock-loader.log", 'python3', '-c', 'from mockbuild.config import load_config') == 0 && run_capture("$tmp/namespace.log", @namespace, 'true') == 0; SKIP: { skip 'Whole native owner requires POWER, native Mock and a private mount namespace', 52 unless $can_owner; diff --git a/openeuler/24.03-ppc64le.inputs.json b/openeuler/24.03-ppc64le.inputs.json index 8c0af3c..cfcc92b 100644 --- a/openeuler/24.03-ppc64le.inputs.json +++ b/openeuler/24.03-ppc64le.inputs.json @@ -1075,6 +1075,17 @@ ], "build_uid": 1000 }, + { + "name": "ipxe-xcat", + "type": "owner", + "outputs": [ + "ipxe-xcat" + ], + "needs": [ + "procenv" + ], + "build_uid": 1000 + }, { "name": "xnba-undi", "type": "owner", diff --git a/packages-manifest.conf b/packages-manifest.conf index ccff757..8e0e5b8 100644 --- a/packages-manifest.conf +++ b/packages-manifest.conf @@ -255,6 +255,7 @@ perl-Sys-Virt=11.10.0 goconserver=>= 0.3.3-snap202011021058 grub2-xcat=1.0 ipmitool-xcat=>= 1.8.18-4 +ipxe-xcat=2.0.0 syslinux-xcat=>= 6.03-1 xnba-undi=>= 1.21.1-1 perl-Crypt-Rijndael=1.13 @@ -271,6 +272,7 @@ xCAT-genesis-base=>= 2:2.18.0 goconserver=>= 0.3.3-snap202011021058 grub2-xcat=1.0 ipmitool-xcat=>= 1.8.18-4 +ipxe-xcat=2.0.0 syslinux-xcat=>= 6.03-1 xnba-undi=>= 1.21.1-1 perl-Crypt-Rijndael=1.13 @@ -313,6 +315,7 @@ xCAT-genesis-base=>= 2:2.18.0 goconserver=>= 0.3.3-snap202011021058 grub2-xcat=1.0 ipmitool-xcat=>= 1.8.18-4 +ipxe-xcat=2.0.0 syslinux-xcat=>= 6.03-1 xnba-undi=>= 1.21.1-1 perl-Crypt-Rijndael=1.13 @@ -327,6 +330,7 @@ xCAT-genesis-base=>= 2:2.18.0 goconserver=>= 0.3.3-snap202011021058 grub2-xcat=1.0 ipmitool-xcat=>= 1.8.18-4 +ipxe-xcat=2.0.0 syslinux-xcat=>= 6.03-1 xnba-undi=>= 1.21.1-1 perl-Crypt-Rijndael=1.13 diff --git a/t/openeuler.t b/t/openeuler.t index a2ea7a8..1d9fa41 100644 --- a/t/openeuler.t +++ b/t/openeuler.t @@ -2,7 +2,8 @@ use strict; use warnings; use FindBin qw($RealBin); -use lib "$RealBin/.."; +use lib "$RealBin/..", "$RealBin/../lib"; +use XCAT::NativeInputs qw(load_inputs); use Test::More; use MockBuildUtils qw(openeuler_build_target openeuler_repo_subdir install_deps_command install_deps_packages derive_target_from_repo_path read_manifest); @@ -55,4 +56,32 @@ for my $path ( is(derive_target_from_repo_path($path), undef, "$path has no native target"); } +# xCAT and xCATsn name the netboot loaders and the console backend as Requires on every arch, so a +# package missing from a target section is not a smaller build: it is "nothing provides " when +# dnf installs xCAT on the management node. The EL section of the same arch is the reference set, +# because openEuler is rpm-md and installs the same core packages. Both exemptions were measured +# against the built core rpms with rpm -qp --requires, not read off a spec conditional. +my %exempt = ( + 'conserver-xcat' => 'xCAT requires goconserver; no core rpm requires conserver-xcat', + 'elilo-xcat' => 'no core rpm requires elilo-xcat', +); +my $reference = 'alma+epel-10-ppc64le'; +ok(scalar keys %{$manifest{$reference}}, "$reference is the reference section and is not empty"); +for my $pkg (sort keys %{$manifest{$reference}}) { + next if $exempt{$pkg}; + ok(exists $manifest{'openeuler-24.03-ppc64le'}{$pkg}, + "openeuler-24.03-ppc64le declares $pkg, which $reference also declares"); +} + +# A manifest entry alone is not enough. The ppc64le target resolves its whole manifest through the +# native input catalog, which refuses a name no node produces, so the catalog has to declare an +# owner for the package as well. +my $plan = eval { load_inputs("$RealBin/..", $manifest{'openeuler-24.03-ppc64le'}) }; +ok($plan, 'every package the ppc64le manifest requires has a native output owner') or diag($@); +# xCAT requires ipxe-xcat on x86_64 (xCAT.spec, upstream 32a688b3), so an x86_64 openEuler section +# without it builds a repo where "dnf install xCAT" answers "nothing provides ipxe-xcat". +for my $target (map { "openeuler-$_-x86_64" } qw(20.03sp4 22.03sp4 24.03sp4)) { + ok(exists $manifest{$target}{'ipxe-xcat'}, "$target declares ipxe-xcat, which xCAT requires on x86_64"); +} + done_testing(); diff --git a/t/openeuler_native_procenv_plugin.t b/t/openeuler_native_procenv_plugin.t new file mode 100644 index 0000000..256906e --- /dev/null +++ b/t/openeuler_native_procenv_plugin.t @@ -0,0 +1,43 @@ +#!/usr/bin/perl +# Every openEuler ppc64le native prerequisite after procenv itself fails before rpmbuild starts: +# +# FileNotFoundError: [Errno 2] No such file or directory: '/usr/bin/procenv' +# +# raised from mock's procenv plugin prebuild hook. The plugin runs ["/usr/bin/procenv"] through +# mockbuild.util.do with no chroot prefix, so it executes the BUILD HOST's copy, and +# xcat-master-ppc has none. Installing procenv in the chroot does not help: mock already appends it +# to preexisting_deps itself, and the run that did so still failed with the same line while +# root.log recorded "Installing : procenv-0.60-1.ppc64le". +# +# mock ships procenv_enable False. The overlays turned it on, which made every native build depend +# on a host package that neither repository declares, for a procenv.log nothing reads. They now +# leave it at the default. +# +# native_overlay_text is pure, so this runs no mock. +use strict; +use warnings; +use Test::More; +use FindBin qw($RealBin); +use lib "$RealBin/../lib"; +use XCAT::NativeInputs qw(native_overlay_text); + +my $base = '/opt/xcat-ci-shared/builds/oe264/work/native-base.cfg'; +my $prereqs = '/opt/xcat-ci-shared/builds/oe264/work/native-prerequisites'; + +for my $purpose (1000, 0, 'procenv') { + my $text = native_overlay_text($purpose, $base, $prereqs); + unlike($text, qr/procenv_enable'\]\s*=\s*True/, + "overlay $purpose does not enable the procenv plugin, which needs a host binary"); + # The overlay must still carry what the native build does depend on. + like($text, qr/\[xcat-native-inputs\]/, "overlay $purpose still declares the prerequisite repository"); + like($text, qr/bind_mount_enable'\]\s*=\s*True/, "overlay $purpose still binds the prerequisite tree"); +} + +# The uid split is declared in the inputs and must not move: genesis builds as root, the rest as +# 1000, and the procenv overlay runs as 1000 too. +is((native_overlay_text(1000, $base, $prereqs) =~ /chrootuid'\]\s*=\s*(\d+)/)[0], 1000, + 'the unprivileged overlay still runs as uid 1000'); +is((native_overlay_text(0, $base, $prereqs) =~ /chrootuid'\]\s*=\s*(\d+)/)[0], 0, + 'the genesis overlay still runs as root'); + +done_testing(); diff --git a/t/openeuler_native_result_permissions.t b/t/openeuler_native_result_permissions.t new file mode 100644 index 0000000..0e0418b --- /dev/null +++ b/t/openeuler_native_result_permissions.t @@ -0,0 +1,39 @@ +#!/usr/bin/perl +# A dep builder creates its own result directories before mock starts. ipxe-xcat/mockbuild.pl calls +# make_path on $work_dir/srpm and $work_dir/rpm and gives each one to mock as --resultdir, and it +# runs as root. The native openEuler overlay keeps chrootuid at the catalog's build_uid and +# chrootgid at the native build gid, so mock opens state.log, build.log and root.log in those +# directories as that uid. They are writable only if the step itself carries the native build gid +# and a group-writable umask. native_owner_command is where the step gets both. +use strict; +use warnings; +use Test::More; +use FindBin qw($RealBin); +use lib "$RealBin/.."; +use MockBuildUtils qw(native_owner_command NATIVE_BUILD_GID); + +my $gid = NATIVE_BUILD_GID; +my $overlay = '/work/native-1000.cfg'; +my $cfg = '/etc/mock/openeuler-24.03-ppc64le.cfg'; +my $step = "perl ipxe-xcat/mockbuild.pl --work-dir /tmp/ipxe-xcat --result-dir /results/ipxe-xcat"; +my $cmd = native_owner_command($overlay, $cfg, $step); + +like($cmd, qr/\bsetpriv\b[^'"]*--regid\s+\Q$gid\E\b/, + "the step runs under setpriv --regid $gid, so it creates its result directories in the native build group"); +like($cmd, qr/\bsetpriv\b[^'"]*--clear-groups\b/, + 'setpriv --clear-groups drops the supplementary groups root started with'); +like($cmd, qr/\bumask\s+0002\b/, + 'the step runs with umask 0002, so a directory it creates stays group writable'); + +my $umask = index($cmd, 'umask 0002'); +my $setpriv = index($cmd, 'setpriv'); +my $builder = index($cmd, 'ipxe-xcat/mockbuild.pl'); +ok($setpriv >= 0 && $setpriv < $umask, + 'setpriv comes before umask 0002, so the umask applies under the native build gid'); +ok($umask >= 0 && $umask < $builder, + 'umask 0002 comes before the builder, so make_path in the builder inherits it'); + +like($cmd, qr/mount --bind \S*\Q$overlay\E\S*\s+\S*\Q$cfg\E/, + 'the wrapper binds the overlay it was given over the target config, so chrootuid stays as the catalog declared it'); + +done_testing();