Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package build for openSUSE:Factory checked in at 2023-02-10 14:34:02 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/build (Old) and /work/SRC/openSUSE:Factory/.build.new.1848 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "build" Fri Feb 10 14:34:02 2023 rev:148 rq:1063831 version:20230208 Changes: -------- --- /work/SRC/openSUSE:Factory/build/build.changes 2023-01-08 21:25:19.511170708 +0100 +++ /work/SRC/openSUSE:Factory/.build.new.1848/build.changes 2023-02-10 14:34:04.253472567 +0100 @@ -1,0 +2,8 @@ +Wed Feb 8 11:54:16 UTC 2023 - Adrian Schröter <adr...@suse.de> + +- SPDX SBOM generation for container and product builds +- Revert & Redo "Better filetype detection for temp changes files" +- Fix typo in glibc hwcaps supplements +- Implement lua string macros + +------------------------------------------------------------------- Old: ---- obs-build-20230105.tar.gz New: ---- obs-build-20230208.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ build.spec ++++++ --- /var/tmp/diff_new_pack.i1AoN4/_old 2023-02-10 14:34:04.793475794 +0100 +++ /var/tmp/diff_new_pack.i1AoN4/_new 2023-02-10 14:34:04.801475842 +0100 @@ -28,7 +28,7 @@ Summary: A Script to Build SUSE Linux RPMs License: GPL-2.0-only OR GPL-3.0-only Group: Development/Tools/Building -Version: 20230105 +Version: 20230208 Release: 0 Source: obs-build-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-build ++++++ PKGBUILD ++++++ --- /var/tmp/diff_new_pack.i1AoN4/_old 2023-02-10 14:34:04.849476129 +0100 +++ /var/tmp/diff_new_pack.i1AoN4/_new 2023-02-10 14:34:04.853476153 +0100 @@ -1,5 +1,5 @@ pkgname=build -pkgver=20230105 +pkgver=20230208 pkgrel=0 pkgdesc="Build packages in sandbox" arch=('i686' 'x86_64') ++++++ _service ++++++ --- /var/tmp/diff_new_pack.i1AoN4/_old 2023-02-10 14:34:04.877476296 +0100 +++ /var/tmp/diff_new_pack.i1AoN4/_new 2023-02-10 14:34:04.881476320 +0100 @@ -1,7 +1,7 @@ <services> <service name="tar_scm" mode="manual"> - <param name="revision">20230105</param> - <param name="version">20230105</param> + <param name="revision">20230208</param> + <param name="version">20230208</param> <param name="url">https://github.com/openSUSE/obs-build.git</param> <param name="scm">git</param> <param name="extract">dist/build.changes</param> ++++++ build.dsc ++++++ --- /var/tmp/diff_new_pack.i1AoN4/_old 2023-02-10 14:34:04.897476415 +0100 +++ /var/tmp/diff_new_pack.i1AoN4/_new 2023-02-10 14:34:04.901476439 +0100 @@ -1,6 +1,6 @@ Format: 1.0 Source: build -Version: 20230105 +Version: 20230208 Binary: build Maintainer: Adrian Schroeter <adr...@suse.de> Architecture: all ++++++ debian.changelog ++++++ --- /var/tmp/diff_new_pack.i1AoN4/_old 2023-02-10 14:34:04.929476607 +0100 +++ /var/tmp/diff_new_pack.i1AoN4/_new 2023-02-10 14:34:04.933476631 +0100 @@ -1,4 +1,4 @@ -build (20230105) unstable; urgency=low +build (20230208) unstable; urgency=low * Update to current git trunk - add sles11sp2 build config and adapt autodetection ++++++ obs-build-20230105.tar.gz -> obs-build-20230208.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-build-20230105/Build/Rpm.pm new/obs-build-20230208/Build/Rpm.pm --- old/obs-build-20230105/Build/Rpm.pm 2023-01-05 13:17:01.000000000 +0100 +++ new/obs-build-20230208/Build/Rpm.pm 2023-02-08 14:13:47.000000000 +0100 @@ -227,6 +227,55 @@ return \%m; } +# no support for most special chars yet +sub luapattern { + my ($pat) = @_; + $pat = "\Q$pat\E"; + $pat =~ s/^\\\^/\^/; + $pat =~ s/\\\$/\$/; + $pat =~ s/\\\./\./; + $pat =~ s/\\\*/\*/; + $pat =~ s/\\\-/\*\?/; + $pat =~ s/\\\?/\?/; + $pat =~ s/\\\+/\+/; + $pat =~ s/\\%([%ds])/\\$1/g; + return $pat; +} + +sub luamacro { + my ($macname, @args) = @_; + push @args, '' unless @args; + return lc($args[0]) if $macname eq 'lower'; + return uc($args[0]) if $macname eq 'upper'; + return length($args[0]) if $macname eq 'len'; + return reverse($args[0]) if $macname eq 'reverse'; + return $args[0] x $args[1] if $macname eq 'rep'; + if ($macname eq 'sub') { + push @args, 1 if @args < 2; + push @args, -1 if @args < 3; + $args[1] -= 1 if $args[1] > 0; + $args[1] = length($args[0]) + $args[1] if $args[1] < 0; + $args[1] = 0 if $args[1] < 0; + $args[2] -= 1 if $args[2] > 0; + $args[2] = length($args[0]) + $args[2] if $args[2] < 0; + return $args[1] <= $args[2] ? substr($args[0], $args[1], $args[2] - $args[1] + 1) : ''; + } + if ($macname eq 'gsub') { + return unless @args >= 3; + my $pat = luapattern($args[1]); + my $rep = $args[3]; + eval { + if (!defined($rep)) { + $args[0] =~ s/$pat/$args[2]/g; + } else { + $args[0] =~ s/$pat(?(?{$rep--<=0})(*F))/$args[2]/g; + } + }; + return $@ ? '' : $args[0]; + } + return ''; +} + sub initmacros { my ($config, $macros, $macros_args) = @_; for my $line (@{$config->{'macros'} || []}) { @@ -383,6 +432,19 @@ $macalt = (expr($macalt))[0]; $macalt =~ s/^[v\"]//; # stringify $expandedline .= $macalt; + } elsif ($macname eq 'gsub' || $macname eq 'len' || $macname eq 'lower' || $macname eq 'upper' || $macname eq 'rep' || $macname eq 'reverse' || $macname eq 'sub') { + my @args; + if (defined $macalt) { + push @args, $macalt; + } elsif (defined $macdata) { + push @args, split(' ', $macdata); + } else { + $line =~ /^\s*([^\n]*).*$/; + push @args, split(' ', $1); + $line = ''; + } + $_ = expandmacros($config, $_, $lineno, $macros, $macros_args, $tries) for @args; + $expandedline .= luamacro($macname, @args); } elsif (exists($macros->{$macname})) { if (!defined($macros->{$macname})) { print STDERR "Warning: spec file parser",($lineno?" line $lineno":''),": can't expand '$macname'\n" if $config->{'warnings'}; @@ -900,6 +962,7 @@ ########################################################################### my %rpmstag = ( + "SIGMD5" => 261, "SIGTAG_SIZE" => 1000, # Header+Payload size in bytes. */ "SIGTAG_PGP" => 1002, # RSA signature over Header+Payload "SIGTAG_MD5" => 1004, # MD5 hash over Header+Payload @@ -911,9 +974,12 @@ "SUMMARY" => 1004, "DESCRIPTION" => 1005, "BUILDTIME" => 1006, + "VENDOR" => 1011, "LICENSE" => 1014, "ARCH" => 1022, "OLDFILENAMES" => 1027, + "FILEMODES" => 1030, + "FILEDIGESTS" => 1035, "SOURCERPM" => 1044, "PROVIDENAME" => 1047, "REQUIREFLAGS" => 1048, @@ -944,7 +1010,8 @@ "OLDENHANCESNAME" => 1159, "OLDENHANCESVERSION" => 1160, "OLDENHANCESFLAGS" => 1161, - "RECOMMENDNAME" => 5046, + "FILEDIGESTALGO" => 5011, + "RECOMMENDNAME" => 5046, "RECOMMENDVERSION" => 5047, "RECOMMENDFLAGS" => 5048, "SUGGESTNAME" => 5049, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-build-20230105/Build/Rpmmd.pm new/obs-build-20230208/Build/Rpmmd.pm --- old/obs-build-20230105/Build/Rpmmd.pm 2023-01-05 13:17:01.000000000 +0100 +++ new/obs-build-20230208/Build/Rpmmd.pm 2023-02-08 14:13:47.000000000 +0100 @@ -129,6 +129,7 @@ 'rpm:enhances' => { 'rpm:entry' => { _start => \&primary_handle_dep , _tag => 'enhances' }, }, 'rpm:obsoletes' => { 'rpm:entry' => { _start => \&primary_handle_dep , _tag => 'obsoletes' }, }, 'rpm:buildhost' => { _text => 1, _end => \&generic_store_text, _tag => 'buildhost' }, + 'rpm:vendor' => { _text => 1, _end => \&generic_store_text, _tag => 'vendor' }, 'rpm:license' => { _text => 1, _end => \&generic_store_text, _tag => 'license' }, 'rpm:sourcerpm' => { _text => 1, _end => \&primary_handle_sourcerpm , _tag => 'source' }, ### currently commented out, as we ignore file provides in expanddeps @@ -210,6 +211,7 @@ } delete $data->{'checksum'} unless $options->{'withchecksum'}; delete $data->{'license'} unless $options->{'withlicense'}; + delete $data->{'vendor'} unless $options->{'withvendor'}; return generic_add_result(@_); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-build-20230105/Makefile new/obs-build-20230208/Makefile --- old/obs-build-20230105/Makefile 2023-01-05 13:17:01.000000000 +0100 +++ new/obs-build-20230208/Makefile 2023-02-08 14:13:47.000000000 +0100 @@ -91,6 +91,7 @@ download_assets \ export_debian_orig_from_git \ unpack_slsa_provenance \ + generate_sbom \ $(DESTDIR)$(pkglibdir) install -m644 \ qemu-reg \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-build-20230105/baselibs_configs/baselibs_global.conf new/obs-build-20230208/baselibs_configs/baselibs_global.conf --- old/obs-build-20230105/baselibs_configs/baselibs_global.conf 2023-01-05 13:17:01.000000000 +0100 +++ new/obs-build-20230208/baselibs_configs/baselibs_global.conf 2023-02-08 14:13:47.000000000 +0100 @@ -48,13 +48,13 @@ targettype x86-64-v4 baselib +^/usr/lib64/(.*\.so.*)$ -> /usr/lib64/glibc-hwcaps/x86-64-v4/$1 targettype x86-64-v2 requires "<match1> = <version>-<release>" targettype x86-64-v2 autoreqprov off -targettype x86-64-v2 supplements "(<match1> = <version> and patterns-glibc-hwcaps-x86_64-v2)" +targettype x86-64-v2 supplements "(<match1> = <version> and patterns-glibc-hwcaps-x86-64-v2)" targettype x86-64-v3 requires "<match1> = <version>-<release>" targettype x86-64-v3 autoreqprov off -targettype x86-64-v3 supplements "(<match1> = <version> and patterns-glibc-hwcaps-x86_64-v3)" +targettype x86-64-v3 supplements "(<match1> = <version> and patterns-glibc-hwcaps-x86-64-v3)" targettype x86-64-v4 requires "<match1> = <version>-<release>" targettype x86-64-v4 autoreqprov off -targettype x86-64-v4 supplements "(<match1> = <version> and patterns-glibc-hwcaps-x86_64-v4)" +targettype x86-64-v4 supplements "(<match1> = <version> and patterns-glibc-hwcaps-x86-64-v4)" post "/sbin/ldconfig" package /(.*)-debuginfo$/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-build-20230105/build-recipe-docker new/obs-build-20230208/build-recipe-docker --- old/obs-build-20230105/build-recipe-docker 2023-01-05 13:17:01.000000000 +0100 +++ new/obs-build-20230208/build-recipe-docker 2023-02-08 14:13:47.000000000 +0100 @@ -287,6 +287,13 @@ fi rm -rf "$BUILD_ROOT/$TOPDIR/SOURCES/repos/UPLOAD" + # create sbom if requested + if test -n "$(queryconfig --dist "$BUILD_DIST" --configdir "$CONFIG_DIR" --archpath "$BUILD_ARCH" buildflags spdx)" ; then + echo "Generating sbom file" + generate_sbom "$BUILD_ROOT$TOPDIR/DOCKER/$FILENAME.tar" > "$BUILD_ROOT$TOPDIR/DOCKER/$FILENAME.sbom.json" + test -s "$BUILD_ROOT$TOPDIR/DOCKER/$FILENAME.sbom.json" || rm -f "$BUILD_ROOT$TOPDIR/DOCKER/$FILENAME.sbom.json" + fi + # We're done. Clean up. recipe_cleanup_docker BUILD_SUCCEEDED=true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-build-20230105/build-recipe-kiwi new/obs-build-20230208/build-recipe-kiwi --- old/obs-build-20230105/build-recipe-kiwi 2023-01-05 13:17:01.000000000 +0100 +++ new/obs-build-20230208/build-recipe-kiwi 2023-02-08 14:13:47.000000000 +0100 @@ -357,6 +357,7 @@ mv "$i" $BUILD_ROOT/$TOPDIR/KIWI/. test -n "$milestone" && echo "$milestone" > $BUILD_ROOT/$TOPDIR/OTHER/${i%.iso}.milestone ;; *.packages) mv $i $BUILD_ROOT/$TOPDIR/OTHER/. ;; + *.sbom.json) mv $i $BUILD_ROOT/$TOPDIR/OTHER/. ;; *.report) mv $i $BUILD_ROOT/$TOPDIR/OTHER/. test -n "$milestone" && echo "$milestone" > $BUILD_ROOT/$TOPDIR/OTHER/${i%.report}.milestone @@ -693,7 +694,16 @@ test -n "$RELEASE" && args=("${args[@]}" --release "$RELEASE") for r in $BUILD_ROOT/$TOPDIR/KIWI/*.tar ; do test -e "$r" && perl -I$BUILD_DIR -MBuild::Kiwi -e Build::Kiwi::showcontainerinfo -- "${args[@]}" $BUILD_ROOT/$TOPDIR/SOURCES/$RECIPEFILE "$r" > "${r%.tar}.containerinfo" - test -s "${r%.tar}.containerinfo" || rm -f "${r%.tar}.containerinfo" + if test -s "${r%.tar}.containerinfo" ; then + # create sbom if requested + if test -n "$(queryconfig --dist "$BUILD_DIST" --configdir "$CONFIG_DIR" --archpath "$BUILD_ARCH" buildflags spdx)" ; then + echo "Generating sbom file for ${r##*/}" + generate_sbom "$r" > "${r%.tar}.sbom.json" + test -s "${r%.tar}.sbom.json" || rm -f "${r%.tar}.sbom.json" + fi + else + rm -f "${r%.tar}.containerinfo" + fi done if test -n "$KIWI_DERIVED_CONTAINER" ; then for r in $BUILD_ROOT/$TOPDIR/KIWI/*.packages ; do diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-build-20230105/dist/build.changes new/obs-build-20230208/dist/build.changes --- old/obs-build-20230105/dist/build.changes 2023-01-05 13:17:01.000000000 +0100 +++ new/obs-build-20230208/dist/build.changes 2023-02-08 14:13:47.000000000 +0100 @@ -1,4 +1,12 @@ ------------------------------------------------------------------- +Wed Feb 8 11:54:16 UTC 2023 - Adrian Schröter <adr...@suse.de> + +- SPDX SBOM generation for container and product builds +- Revert & Redo "Better filetype detection for temp changes files" +- Fix typo in glibc hwcaps supplements +- Implement lua string macros + +------------------------------------------------------------------- Mon Jan 2 08:09:43 UTC 2023 - Adrian Schröter <adr...@suse.de> - configure mkbaselibs to create glibc-hwcaps baselibs as well diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-build-20230105/generate_sbom new/obs-build-20230208/generate_sbom --- old/obs-build-20230105/generate_sbom 1970-01-01 01:00:00.000000000 +0100 +++ new/obs-build-20230208/generate_sbom 2023-02-08 14:13:47.000000000 +0100 @@ -0,0 +1,561 @@ +#!/usr/bin/perl + +################################################################ +# +# Copyright (c) 2023 SUSE Linux LLC +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or 3 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program (see the file COPYING); if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA +# +################################################################ + +BEGIN { + unshift @INC, ($::ENV{'BUILD_DIR'} || '/usr/lib/build'); +} + +use strict; + +use File::Find; +use File::Temp; + +use Digest::SHA; +use Digest::MD5; + +use Build::Rpm; +use Build::SimpleJSON; + +my $spdx_json_template = { + '_order' => [ qw{spdxVersion dataLicense SPDXID name documentNamespace creationInfo packages files relationships} ], + 'creationInfo' => { + '_order' => [ qw{created creators licenseListVersion} ], + }, + 'packages' => { + '_order' => [ qw{name SPDXID versionInfo originator downloadLocation sourceInfo licenseConcluded licenseDeclared copyrightText externalRefs} ], + 'externalRefs' => { + '_order' => [ qw{referenceCategory referenceType referenceLocator} ], + }, + }, + 'files' => { + '_order' => [ qw{fileName SPDXID checksums licenseConcluded copyrightText comment} ], + }, + 'relationships' => { + '_order' => [ qw{spdxElementId relatedSpdxElement relationshipType} ], + }, +}; + +my $intoto_json_template = { + '_order' => [ qw{_type predicateType subject predicate} ], + 'subject' => { + '_order' => [ qw{name digest} ], + }, + 'predicate' => $spdx_json_template, +}; + + +sub urlencode { + my ($str, $iscgi) = @_; + if ($iscgi) { + $str =~ s/([\000-\037<>;\"#\?&\+=%[\177-\377])/sprintf("%%%02X",ord($1))/sge; + $str =~ tr/ /+/; + } else { + $str =~ s/([\000-\040<>;\"#\?&\+=%[\177-\377])/sprintf("%%%02X",ord($1))/sge; + } + return $str; +} + +sub rfc3339time { + my ($t) = @_; + my @gt = gmtime($t || time()); + return sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ", $gt[5] + 1900, $gt[4] + 1, @gt[3,2,1,0]; +} + +sub sha256file { + my ($fn) = @_; + my $ctx = Digest::SHA->new(256); + eval { $ctx->addfile($fn) }; + die("$fn: $@\n") if $@; + return $ctx->hexdigest(); +} + +sub system_chroot { + my ($root, @args) = @_; + my $pid = 0; + if ($args[0] eq 'exec') { + shift @args; + } else { + $pid = fork(); + die("fork: $!\n") unless defined $pid; + } + if (!$pid) { + if ($args[0] eq 'quiet') { + shift @args; + open(STDOUT, '>>', '/dev/null'); + open(STDERR, '>>', '/dev/null'); + } + if ($args[0] eq 'stdout') { + open(STDOUT, '>', $args[1]) || die("$args[1]: $!\n"); + splice(@args, 0, 2); + } + !$root || chroot($root) || die("chroot $root: $!\n"); + exec(@args); + die("exec $args[0]: $!\n"); + } + die unless waitpid($pid, 0) == $pid; + return $?; +} + +sub popen_chroot { + my ($root, @args) = @_; + + my $fd; + if (!$root) { + open($fd, '-|', @args) || die("open: $!\n"); + return $fd; + } + my $pid = open($fd, '-|'); + die("open: $!\n") unless defined $pid; + if ($pid == 0) { + !$root || chroot($root) || die("chroot $root: $!\n"); + exec(@args); + die("exec $args[0]: $!\n"); + } + return $fd; +} + +sub can_run { + my ($root, $fname) = @_; + return 0 if $root && $>; + return -x "$root$fname"; +} + +sub systemq { + my $pid = fork(); + die("fork: $!\n") unless defined $pid; + if (!$pid) { + open(STDOUT, '>', '/dev/null') || die("/dev/null: $!\n"); + exec @_; + die("$_[0]: $!\n"); + } + waitpid($pid, 0) == $pid || die("waitpid: $!\n"); + exit($?) if $?; +} + +sub uncompress_container { + my ($container, $outfile) = @_; + my @decompressor; + if ($container =~ /\.tar$/) { + push @decompressor, 'cat'; + } elsif ($container =~ /\.tar\.gz$/) { + push @decompressor, 'gunzip'; + } elsif ($container =~ /\.tar\.xz$/) { + push @decompressor, 'xzdec'; + } else { + die("$container: unknown format\n"); + } + my $pid = fork(); + die("fork: $!\n") unless defined $pid; + if (!$pid) { + open(STDIN, '<', $container) || die("$container: $!\n"); + open(STDOUT, '>', $outfile) || die("$outfile: $!\n"); + exec @decompressor; + die("$decompressor[0]: $!\n"); + } + waitpid($pid, 0) == $pid || die("waitpid: $!\n"); + exit($?) if $?; +} + +sub unpack_container { + my ($dir, $container) = @_; + uncompress_container($container, "$dir/cont"); + systemq('skopeo', 'copy', "docker-archive:$dir/cont", "oci:$dir/image:latest"); + unlink("$dir/cont"); + systemq('umoci', 'unpack', '--image', "$dir/image:latest", "$dir/unpack"); + return "$dir/unpack/rootfs"; +} + +sub dump_rpmdb { + my ($root, $outfile) = @_; + my $dbpath; + for my $phase (0, 1) { + if (can_run($root, '/usr/bin/rpmdb')) { + # check if we have the exportdb option + if (system_chroot($root, 'quiet', '/usr/bin/rpmdb', '--exportdb', '--version') == 0) { + if ($dbpath) { + system_chroot($root, 'stdout', $outfile, '/usr/bin/rpmdb', '--dbpath', $dbpath, '--exportdb'); + } else { + system_chroot($root, 'stdout', $outfile, '/usr/bin/rpmdb', '--exportdb'); + } + } + exit($?) if $?; + return; + } + # try to get the dbpath from the root if we can + if (!$dbpath && can_run($root, '/usr/bin/rpm')) { + my $fd = popen_chroot($root, '/usr/bin/rpm', '--eval', '%_dbpath'); + my $path = <$fd>; + close($fd); + chomp $path; + $dbpath = $path if $path && $path =~ /^\//; + } + $dbpath ||= '/var/lib/rpm'; # guess + # try to dump with rpmdb_dump + if (-s "$root$dbpath/Packages" && can_run($root, '/usr/lib/rpm/rpmdb_dump')) { + my $outfh; + open($outfh, '>', $outfile) || die("$outfile: $!\n"); + my $fd = popen_chroot($root, '/usr/lib/rpm/rpmdb_dump', "$dbpath/Packages"); + while (<$fd>) { + next unless /^\s*[0-9a-fA-F]{8}/; + chomp; + my $v = <$fd>; + die("unexpected EOF\n") unless $v; + chomp $v; + substr($v, 0, 1, '') while substr($v, 0, 1) eq ' '; + $v = pack('H*', $v); + next if length($v) < 16; + my ($il, $dl) = unpack('NN', $v); + die("bad header length\n") unless length($v) == 8 + $il * 16 + $dl; + die("print: $!\n") unless print $outfh pack('H*', '8eade80100000000'); + die("print: $!\n") unless print $outfh $v; + } + close($fd) || die("rpmdb_dump: $!\n"); + close($outfh) || die("close: $!\n"); + return; + } + + last unless $root; + # try with the system rpm and a dbpath + $dbpath = "$root$dbpath"; + $root = ''; + } + die("could not dump rpm database\n"); +} + +sub gen_filelist { + my ($dir) = @_; + my $fd; + my $pid = open($fd, '-|'); + die("fork: $!\n") unless defined $pid; + if (!$pid) { + chdir($dir) || die("chdir $!\n"); + exec('find', '-print0'); + die("find: $!\n"); + } + local $/ = "\0"; + my @files = <$fd>; + chomp @files; + close($fd) || die("find: $?\n"); + $_ =~ s/^\.\//\// for @files; + $_ = {'name' => $_} for @files; + for my $f (@files) { + if (-l "$dir$f->{'name'}" || ! -f _) { + $f->{'SKIP'} = 1; + next; + } + $f->{'sha256sum'} = sha256file("$dir/$f->{'name'}"); + } + return \@files; +} + +sub read_rpm { + my ($rpm) = @_; + my %r = Build::Rpm::rpmq($rpm, qw{NAME VERSION RELEASE EPOCH ARCH LICENSE SOURCERPM DISTURL FILENAMES VENDOR FILEMODES FILEDIGESTS FILEDIGESTALGO SIGMD5}); + delete $r{$_} for qw{BASENAMES DIRNAMES DIRINDEXES}; # save mem + for (qw{NAME VERSION RELEASE EPOCH ARCH LICENSE SOURCERPM DISTURL VENDOR FILEDIGESTALGO SIGMD5}) { + next unless $r{$_}; + die("bad rpm entry for $_\n") unless ref($r{$_}) eq 'ARRAY' && @{$r{$_}} == 1; + $r{$_} = $r{$_}->[0]; + } + return \%r; +} + +sub read_pkgs_rpmdb { + my ($rpmhdrs) = @_; + my $fd; + open($fd, '<', $rpmhdrs) || die("$rpmhdrs: $!\n"); + my @rpms; + while (1) { + my $hdr = ''; + last unless read($fd, $hdr, 16) == 16; + my ($il, $dl) = unpack('@8NN', $hdr); + die("bad rpm header\n") unless $il && $dl; + die("bad rpm header\n") unless read($fd, $hdr, $il * 16 + $dl, 16) == $il * 16 + $dl; + push @rpms, read_rpm([ $hdr ]); + } + close($fd); + @rpms = sort {$a->{'NAME'} cmp $b->{'NAME'} || $a->{'VERSION'} cmp $b->{'VERSION'} || $a->{'RELEASE'} cmp $b->{'RELEASE'}} @rpms; + return \@rpms; +} + +sub read_pkgs_from_product_directory { + my ($dir) = @_; + my @rpms; + my $addrpmfile = sub { + my $fn = $File::Find::name; + push @rpms, read_rpm($fn) if $fn =~ /\.rpm$/; + }; + find($addrpmfile, $dir); + return \@rpms; +} + +sub read_pkgs_from_rpmmd { + my ($primaryfile) = @_; + + require Build::Rpmmd; + my $fh; + if ($primaryfile =~ /\.gz$/) { + open($fh, '-|', 'gunzip', '-dc', $primaryfile) || die("$primaryfile: $!\n"); + } else { + open($fh, '<', $primaryfile) || die("$primaryfile: $!\n"); + } + my @rpms; + for my $pkg (@{Build::Rpmmd::parse($fh, undef, 'withlicense' => 1, 'withchecksum' => 1, 'withvendor' => 1)}) { + my $r = {}; + for (qw{name epoch version release arch vendor sourcerpm license checksum}) { + $r->{uc($_)} = $pkg->{$_} if defined $pkg->{$_}; + } + push @rpms, $r; + } + close($fh); + return \@rpms; +} + +sub read_dist { + my ($dir) = @_; + my %dist; + my $fd; + if (open($fd, '<', "$dir/etc/os-release") || open($fd, '<', "$dir/usr/lib/os-release")) { + while(<$fd>) { + chomp; + next unless /\s*(\S+)=(.*)/; + my $k = lc($1); + my $v = $2; + $v =~ s/\s+$//; + $v =~ s/^\"(.*)\"$/$1/; + if ($k eq 'id_like') { + push @{$dist{$k}}, $v; + } else { + $dist{$k} = $v; + } + } + close($fd); + } + return %dist ? \%dist : undef; +} + +sub gen_purl_rpm { + my ($p, $distro) = @_; + + my $vr = $p->{'VERSION'}; + $vr = "$vr-$p->{'RELEASE'}" if defined $p->{'RELEASE'}; + my $vendor = lc($p->{'VENDOR'}); + $vendor =~ s/obs:\/\///; # third party OBS builds + $vendor =~ s/\ .*//; # eg. SUSE LLC... + $vendor =~ s/\/?$/\//; + my $purlurl = "pkg:".urlencode("rpm/$vendor$p->{'NAME'}\@$vr").'?'; + $purlurl .= '&epoch='.urlencode($p->{'EPOCH'}) if $p->{'EPOCH'}; + $purlurl .= '&arch='.urlencode($p->{'ARCH'}) if $p->{'ARCH'}; + $purlurl .= '&upstream='.urlencode($p->{'SOURCERPM'}) if $p->{'SOURCERPM'}; + $purlurl .= '&distro='.urlencode($distro) if $distro; + $purlurl =~ s/\?\&/\?/; + $purlurl =~ s/\?$//; + return $purlurl; +} + +my $wrap_intoto; +my $isproduct; +my $distro; +my $rpmmd; + +while (@ARGV && $ARGV[0] =~ /^-/) { + my $opt = shift @ARGV; + if ($opt eq '--distro') { + $distro = shift @ARGV; + } elsif ($opt eq '--intoto') { + $wrap_intoto = 1; + } elsif ($opt eq '--product') { + $isproduct = 1; + } elsif ($opt eq '--rpmmd') { + $rpmmd = 1; + } else { + last if $opt eq '--'; + die("unknown option: $opt\n"); + } +} + +die("usage: generate_spdx_sbom [--disto NAME] [--intoto] [--product] PRODUCT_DIRECTORY|CONTAINER_TAR\n") unless @ARGV == 1; +my $toprocess = $ARGV[0]; + +my $tmpdir = File::Temp::tempdir( CLEANUP => 1 ); + +my $files; +my $pkgs; +my $dist; +if ($isproduct) { + # product case + #$files = gen_filelist($toprocess); + $pkgs = read_pkgs_from_product_directory($toprocess); +} elsif ($rpmmd) { + require Build::Rpmmd; + my $primary; + if (-d $toprocess) { + my %d = map {$_->{'type'} => $_} @{Build::Rpmmd::parse_repomd("$toprocess/repomd.xml")}; + my $primary = $d{'primary'}; + die("no primary type in repomd.xml\n") unless $primary; + my $loc = $primary->{'location'}; + $loc =~ s/.*\///; + $toprocess .= "/$loc"; + } + die("$toprocess: $!\n") unless -e $toprocess; + $pkgs = read_pkgs_from_rpmmd($toprocess); +} else { + # container tar case + my $unpackdir = unpack_container($tmpdir, $toprocess); + dump_rpmdb($unpackdir, "$tmpdir/rpmdb"); + + $files = gen_filelist($unpackdir); + $pkgs = read_pkgs_rpmdb("$tmpdir/rpmdb"); + $dist = read_dist($unpackdir); +} + +my $subjectname = $toprocess; +$subjectname =~ s/.*\///; + +if (!$distro && $dist) { + $distro = $dist->{'id'}; + $distro .= "-$dist->{'version_id'}" if defined($dist->{'version_id'}) && $dist->{'version_id'} ne ''; + $distro .= "-$dist->{'build_id'}" if defined($dist->{'build_id'}) && $dist->{'build_id'} ne ''; +} + +my $spdx = { + 'spdxVersion' => 'SPDX-2.3', + 'dataLicense' => 'CC0-1.0', + 'SPDXID' => 'SPDXRef-DOCUMENT', + 'name' => $subjectname, +}; + +my $creationinfo = { + 'created' => rfc3339time(time()), + 'creators' => [ 'Tool: obs_build_generate_spdx_sbom-1.0' ], + 'licenseListVersion' => '3.19', +}; +$spdx->{'creationInfo'} = $creationinfo; + +for my $p (@$pkgs) { + my $vr = $p->{'VERSION'}; + $vr = "$vr-$p->{'RELEASE'}" if defined $p->{'RELEASE'}; + my $evr = $vr; + $evr = "$p->{'EPOCH'}:$evr" if $p->{'EPOCH'}; + my $spdxpkg = { + 'name' => $p->{'NAME'}, + 'versionInfo' => $evr, + }; + $spdxpkg->{'originator'} = "Organization: $p->{'VENDOR'}" if $p->{'VENDOR'}; + $spdxpkg->{'downloadLocation'} = 'NOASSERTION'; + $spdxpkg->{'sourceInfo'} = 'acquired package info from RPM DB'; + $spdxpkg->{'licenseConcluded'} = 'NOASSERTION'; + $spdxpkg->{'licenseDeclared'} = 'NOASSERTION'; + my $license = $p->{'LICENSE'}; + if ($license) { + $license =~ s/ and / AND /g; + $spdxpkg->{'licenseConcluded'} = $license; + $spdxpkg->{'licenseDeclared'} = $license; + } + $spdxpkg->{'copyrightText'} = 'NOASSERTION'; + my $purlurl = gen_purl_rpm($p, $distro); + my @xref; + push @xref, { 'referenceCategory' => 'PACKAGE-MANAGER', 'referenceType' => 'purl', 'referenceLocator', $purlurl } if $purlurl; + $spdxpkg->{'externalRefs'} = \@xref if @xref; + if (!$p->{'spdx_id'}) { + if ($p->{'SIGMD5'}) { + $p->{'spdx_id'} = "SPDXRef-Package-$p->{'NAME'}-".unpack('H*', $p->{'SIGMD5'}); + } elsif ($p->{'CHECKSUM'}) { + my $id = $p->{'CHECKSUM'}; + $id =~ s/.*://; + $id = substr($id, 0, 32); + $p->{'spdx_id'} = "SPDXRef-Package-$p->{'NAME'}-$id"; + } else { + my $id = Digest::MD5::md5_hex(Build::SimpleJSON::unparse($p)); + $p->{'spdx_id'} = "SPDXRef-Package-$p->{'NAME'}-$id"; + } + $p->{'spdx_id'} =~ s/[^a-zA-Z0-9\.\-]/-/g; + } + $spdxpkg->{'SPDXID'} = $p->{'spdx_id'}; + push @{$spdx->{'packages'}}, $spdxpkg; +} + +for my $f (@$files) { + next if $f->{'SKIP'}; + my $spdxfile = { + 'fileName' => $f->{'name'}, + 'licenseConcluded' => 'NOASSERTION', + 'copyrightText' => '', + }; + my @chks; + push @chks, { 'algorithm' => 'SHA256', 'checksumValue' => $f->{'sha256sum'} } if $f->{'sha256sum'}; + $spdxfile->{'checksums'} = \@chks if @chks; + $f->{'spdx_id'} = "SPDXRef-".Digest::MD5::md5_hex($f->{'name'}.($f->{'sha256sum'} || '')); + $spdxfile->{'SPDXID'} = $f->{'spdx_id'}; + push @{$spdx->{'files'}}, $spdxfile; +} + +if (@$files) { + my %f2p; + for my $p (@$pkgs) { + push @{$f2p{$_}}, $p for @{$p->{'FILENAMES'} || []}; + } + for my $f (@$files) { + next if $f->{'SKIP'}; + #warn("unpackaged file: $f->{'name'}\n") unless @{$f2p{$f->{'name'}} || []}; + for my $p (@{$f2p{$f->{'name'}} || []}) { + next unless $f->{'spdx_id'} && $p->{'spdx_id'}; + my $rel = { + 'spdxElementId' => $p->{'spdx_id'}, + 'relatedSpdxElement' => $f->{'spdx_id'}, + 'relationshipType', 'CONTAINS', + }; + push @{$spdx->{'relationships'}}, $rel; + } + } +} + +push @{$spdx->{'relationships'}}, { + 'spdxElementId' => 'SPDXRef-DOCUMENT', + 'relatedSpdxElement' => 'SPDXRef-DOCUMENT', + 'relationshipType', 'DESCRIBES', +}; + +my $uuid = pack('H*', '1e9d579964de4594a4e835719a1c259f'); # uuid ns +$uuid = substr(Digest::SHA::sha1($uuid . Build::SimpleJSON::unparse($spdx, 'template' => $spdx_json_template, 'keepspecial' => 1)), 0, 16); +substr($uuid, 6, 1, pack('C', unpack('@6C', $uuid) & 0x0f | 0x50)); +substr($uuid, 8, 1, pack('C', unpack('@8C', $uuid) & 0x3f | 0x80)); +$uuid = join('-', unpack("H8H4H4H4H12", $uuid)); + +$spdx->{'documentNamespace'} = 'http://open-build-service.org/spdx/'.urlencode($subjectname).'-'.$uuid; + +if ($wrap_intoto) { + my $subject = { 'name' => $subjectname }; + # no digest for products as it might be a directory. And an iso file would change the checksum later while signing. + $subject->{'digest'} = { 'sha256' => sha256file($toprocess) } unless $isproduct; + my $intoto = { + '_type' => 'https://in-toto.io/Statement/v0.1', + 'subject' => [ $subject ], + 'predicateType' => 'https://spdx.dev/Document', + 'predicate' => $spdx, + }; + print Build::SimpleJSON::unparse($intoto, 'template' => $intoto_json_template, 'keepspecial' => 1)."\n"; +} else { + print Build::SimpleJSON::unparse($spdx, 'template' => $spdx_json_template, 'keepspecial' => 1)."\n"; +} + + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-build-20230105/mkbaselibs new/obs-build-20230208/mkbaselibs --- old/obs-build-20230105/mkbaselibs 2023-01-05 13:17:01.000000000 +0100 +++ new/obs-build-20230208/mkbaselibs 2023-02-08 14:13:47.000000000 +0100 @@ -591,13 +591,18 @@ if ($rr =~ /^(.*?)\s*->\s*(.*?)$/) { $rr = $1; my $mrr = $2; - for my $f (grep {/$rr/} @rpmfiles) { - $files{$f} = 1; - $moves{$f} = $mrr; - if ($mrr =~ /\$[1-9]/) { + if ($mrr =~ /\$[1-9]/) { + for my $f (grep {/$rr/} @rpmfiles) { + $files{$f} = 1; + $moves{$f} = $mrr; my @s = $f =~ /$rr/; $moves{$f} =~ s/\$([1-9])/$s[$1 - 1]/g; } + } else { + if (grep {$_ eq $rr} @rpmfiles) { + $files{$rr} = 1; + $moves{$rr} = $mrr; + } } } else { for (grep {/$rr/} @rpmfiles) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-build-20230105/obs-docker-support new/obs-build-20230208/obs-docker-support --- old/obs-build-20230105/obs-docker-support 2023-01-05 13:17:01.000000000 +0100 +++ new/obs-build-20230208/obs-docker-support 2023-02-08 14:13:47.000000000 +0100 @@ -314,7 +314,11 @@ } if test `id -u` != "0"; then - echo "obs-docker-support: not executed as root user!" + echo <<EOF +obs-docker-support: not executed as root user! + +This can happen if your base container (see FROM line) is setting USER already, then builds in dependent container run as that user/group. +EOF exit 1 fi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/obs-build-20230105/vc new/obs-build-20230208/vc --- old/obs-build-20230105/vc 2023-01-05 13:17:01.000000000 +0100 +++ new/obs-build-20230208/vc 2023-02-08 14:13:47.000000000 +0100 @@ -118,7 +118,7 @@ touch $changelog fi -tmpfile=`mktemp -q vctmp.XXXXXX.$changelog` +tmpfile=`mktemp -q $changelog.vctmp.XXXXXX.changes` if [ $? -ne 0 ]; then echo "$0: Can't create temp file, exiting..." exit 1