Hello World

File unpack_slsa_provenance of Package build

#!/usr/bin/perl

BEGIN {
  unshift @INC, ($::ENV{'BUILD_DIR'} || '/usr/lib/build');
}

use JSON::XS ();
use MIME::Base64 ();
use Data::Dumper;

use Build;
use Build::Download;

use strict;

sub readstr {
  my ($fn) = @_;
  my $f;
  open($f, '<', $fn) || die("$fn: $!\n");
  my $d = '';
  1 while sysread($f, $d, 8192, length($d));
  close $f;
  return $d;
}

sub writestr {
  my ($fn, $fnf, $d) = @_;
  my $f;
  open($f, '>', $fn) || die("$fn: $!\n");
  if (length($d)) {
    (syswrite($f, $d) || 0) == length($d) || die("$fn write: $!\n");
  }
  close($f) || die("$fn close: $!\n");
  return unless defined $fnf;
  rename($fn, $fnf) || die("rename $fn $fnf: $!\n");
}


sub material2digest {
  my ($material) = @_;
  my $digests = $material->{'digest'};
  return undef unless ref($digests) eq 'HASH';
  my $digest;
  my $digest_t;
  for my $t (sort keys %$digests) {
    my $v = $digests->{$t};
    next if $digest && length($v) < length($digest);
    $digest = $v;
    $digest_t = $t;
  }
  return undef unless $digest;
  return lc($digest_t).":$digest";
}

sub check_existing {
  my ($fn, $digest) = @_;
  if (-l $fn || -e _) {
    if ($digest) {
      eval { Build::Download::checkfiledigest($fn, $digest) };
      return 1 unless $@;
    }
    unlink($fn) || die("unlink $fn: $!\n");
  }
  return 0;
}

die("usage: unpack_slsa_provenance <provenance.json> <dir>\n") unless @ARGV == 2;
my ($provenance_file, $dir) = @ARGV;

die("$provenance_file: $!\n") unless -e $provenance_file;
die("$dir: $!\n") unless -e $dir;
die("$dir: Not a directory\n") unless -d $dir;

my $provenance = readstr($provenance_file);
$provenance = JSON::XS::decode_json($provenance);
if ($provenance->{'payload'}) {
  $provenance = MIME::Base64::decode_base64($provenance->{'payload'});
  $provenance = JSON::XS::decode_json($provenance);
}
my $predicate = $provenance->{'predicate'};
die("no predicate in provenance?\n") unless ref($predicate) eq 'HASH';

my $materials = $predicate->{'materials'};
die("no materials in predicate?\n") unless ref($materials) eq 'ARRAY';

my $invocation = $predicate->{'invocation'};
die("no invocation in predicate?\n") unless ref($invocation) eq 'HASH';

my $configsource = $invocation->{'configSource'};
die("no configSource in invocation?\n") unless ref($configsource) eq 'HASH';

my $recipefile = $configsource->{'entryPoint'};
die("no entryPoint in configSource?\n") unless defined($recipefile) && ref($recipefile) eq '';

my @rpmlist;

$| = 1;

print "fetching sources\n";
my $recipe_found;
for my $material (@$materials) {
  my $uri = $material->{'uri'};
  next if $uri =~ /\/_slsa\//;
  my $digest = material2digest($material);
  my $fn = $uri;
  $fn =~ s/%([a-fA-F0-9]{2})/chr(hex($1))/sge;
  $fn =~ s/.*\///;
  die("bad file name $fn\n") if $fn eq '.' || $fn eq '..' || $fn eq '';
  die("bad file name $fn\n") if $fn =~ /^\.build\./;
  $recipe_found = 1 if $fn eq $recipefile;
  next if check_existing("$dir/$fn", $digest);
  Build::Download::download($uri, "$dir/$fn", undef, 'digest' => $digest);
}
die("recipefile $recipefile is missing from source\n") unless $recipe_found;

print "fetching build environment\n";
mkdir("$dir/.build.binaries") || die("mkdir $dir/.build.binaries: $!\n") unless -d "$dir/.build.binaries";
for my $material (@$materials) {
  my $uri = $material->{'uri'};
  next unless $uri =~ /\/_slsa\//;
  next if $uri =~ /\/_config\/[^\/]+$/;
  my $digest = material2digest($material);
  my $fn = $uri;
  $fn =~ s/%([a-fA-F0-9]{2})/chr(hex($1))/sge;
  $fn =~ s/\/[^\/]+$//;
  $fn =~ s/.*\///;
  die("bad file name $fn\n") if $fn eq '.' || $fn eq '..' || $fn eq '';
  if ($fn =~ /^(.*)\.rpm$/) {
    push @rpmlist, "$1 $dir/.build.binaries/$fn";
  }
  next if check_existing("$dir/.build.binaries/$fn", $digest);
  Build::Download::download($uri, "$dir/.build.binaries/$fn", undef, 'digest' => $digest);
}

print "fetching build config\n";
for my $material (@$materials) {
  my $uri = $material->{'uri'};
  next unless $uri =~ /\/_slsa\//;
  next unless $uri =~ /\/_config\/[^\/]+$/;
  my $digest = material2digest($material);
  next if check_existing("$dir/.build.config", $digest);
  Build::Download::download($uri, "$dir/.build.config", undef, 'digest' => $digest);
}

# parse the config to get preinstall/vminstall/runscripts information
my $bconf = Build::read_config('noarch', "$dir/.build.config");
die("cannot expand preinstalls\n") if $bconf->{'expandflags:preinstallexpand'};
my @preinstalls = Build::get_preinstalls($bconf);
my @vminstalls = Build::get_vminstalls($bconf);
my @runscripts = Build::get_runscripts($bconf);
push @rpmlist, "preinstall: @preinstalls";
push @rpmlist, "vminstall: @vminstalls";
push @rpmlist, "runscripts: @runscripts";

writestr("$dir/.build.rpmlist", undef, join("\n", @rpmlist)."\n");

my @params;
if (ref($invocation->{'parameters'}) eq 'HASH') {
  my $parameters = $invocation->{'parameters'};
  for my $k (sort keys %$parameters) {
    next unless defined $parameters->{$k} && !ref($parameters->{$k});
    push @params, 'release', $parameters->{$k} if $k eq 'release';
    push @params, 'debuginfo', 1 if $k eq 'debuginfo' && $parameters->{$k};
  }
}
push @params, 'recipe', $recipefile;
my $params = '';
while (@params) {
  my ($k, $v) = splice(@params, 0, 2);
  $v =~ s/[\r\n].*\z//s;
  $params .= "$k=$v\n";
}
writestr("$dir/.build.params", undef, $params);