#!/usr/bin/perl -w
use strict;
use utf8;

package UpdateInfoReader;
my $dn = `dirname $0`;chomp($dn);
my $pwd = `pwd`;chomp($pwd);
if ($dn !~ /^\//) { $dn = $pwd . "/" . $dn; }
push @INC,$dn;
my $cverepobase=`dirname $dn`;
chomp($cverepobase);

require CanDBReader;

use XML::Bare;
use POSIX qw/strftime setlocale LC_TIME/;
use List::MoreUtils qw/uniq/;
use Data::Dumper;
use JSON;
use LWP;
use LWP::UserAgent;
use Compress::Zlib;
use File::Temp qw/ tempfile /;
use DateTime;


my $ua = LWP::UserAgent->new("keep_alive" => 1);
$ua->agent("cve-database/UpdateInfo.pm");
$ua->timeout(60);

sub
updateinfo_get_url($) {
        my ($url) = @_;

        # print STDERR "getting $url\n";
# Create a request
        my $req = HTTP::Request->new(GET => $url);
        my $res = $ua->request($req);
        if ($res->is_success) {
                if ($res->content eq "") {
                        print STDERR "empty result on retrieving $url?\n";
                }
                return $res->content;
        } else {
                print STDERR "ERROR on: $url:\n" .  $res->status_line . "\n" if -t STDERR;
                return undef;
        }
}


my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
my $curtime = POSIX::strftime("%Y-%m-%dT%H:%M:%S",$sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst );

my $osc = "osc -A https://api.suse.de/";

system("$osc ls SUSE:Channels >/dev/null") && die "$osc ls SUSE:Channels does not work\n";

my %fetched = ();

my %replacelist;
my %replacepkg;
my %filterlist;

# CVEs -> {
#	Product -> {
#		"patchnames" -> "xxxx",
#		"packages" -> { package -> version-release }
#		"srcpackages" -> { package -> version-release }
#		"packagearchs" -> { package -> { arch -> 1} }
#	}
#}


%UpdateInfoReader::products = ();
%UpdateInfoReader::productsinqa = ();
%UpdateInfoReader::patches = ();		# { Product -> { patches:1, }}
# We assume patchids have unique descriptions, as the ID encodes the product-sp-version
%UpdateInfoReader::patchtitle = ();		# { patchid -> titletext }
%UpdateInfoReader::patchtype = ();		# { patchid -> type }
%UpdateInfoReader::patchseverity = ();		# { patchid -> severity }
%UpdateInfoReader::patchdescription = ();	# { patchid -> text }
%UpdateInfoReader::patchreferences = ();	# { patchid -> { cve:1, }}
%UpdateInfoReader::patchpackages = ();		# { patchid -> { package:version-release } }
%UpdateInfoReader::patchpackagearchs = ();	# { patchid -> { package -> {arch:1} } }
%UpdateInfoReader::patchinqa = ();		# { patchid -> 1/0 }
%UpdateInfoReader::patchissued = ();		# { patchid -> epoch }
%UpdateInfoReader::patchvirtual = ();		# { patchid -> 1/0 } 1 for synthesized patches

%UpdateInfoReader::product2packages = ();	# { Product -> { srcpkg -> [ binarypkg1, binarypkg2, binarypkg3] } }	currently without codestreams

# %UpdateInfoReader::gapackages = ();		# { Product -> { pkg -> version-release } }
# %UpdateInfoReader::gasrpm2rpm = ();		# { Product -> { srpmname -> rpm } }

# map codestream.package -> list of containers for easy affectedness usage.
# {codestream.srcpackage} -> {container:1}
%UpdateInfoReader::codestreampkg2container = ();# { "codestream.srcpkg" -> {container:1 }}
%UpdateInfoReader::containerpkgfixedcve = ();	# { "container" -> { "binarypkg" { cve:1 }}}

# channel files content
%UpdateInfoReader::product2codestream2src2binary = ();# { "product" => { "codestream" => { "srcpkg" -> { "binary" => 1 }}}}
%UpdateInfoReader::codestream2product2src2binary = ();# { "codestream" => { "product" => { "srcpkg" -> { "binary" => 1 }}}}


# { "kernel-default-4.12.42-23"  => { arch => 1 } }
%UpdateInfoReader::allkerneldefaultversions = ();

sub
blacklistedproduct($) {
	my ($product) = @_;
	return 1 if ($product =~ /(Carwos|Build System Kit|Open Buildservice Development Tools|ERICS|12.SP2.LTSS.SAP|SLES-LTSS-SAP|OpenStack Cloud Magnum Orchestration|^Redhat|SUSE CaaS Platform ALL|^Ubuntu|VMWare|SLES_TERADATA-10-SP3|SLE.-10-SP.|SLES_LTSS-10-SP4|SLE_9_SP3-TERADATA|SLE-10-SP2|openSUSE-11.4|SLE-10-SP3-MANAGER|SLE-9-SP4)/i);
	return 0;
}

# compares 2 rpm versions (without release)
sub
xcompare($$) {
	my ($a,$b) = @_;

	my $origa = $a;
	my $origb = $b;

	my @a;
	my @b;

	return 0 if ($a eq $b);

	do {
		#print "CMP at $a $b\n";

		# Emptied one string? than its larger
		return  1 if ($a ne "" && $b eq "");
		return -1 if ($a eq "" && $b ne "");

		my $vera;
		my $verb;

		# Number
		if ($a =~ /^(\d+)/) {
			$vera = $1;
			$a =~ s/\d*//;


			if ($b !~ /^(\d+)/) {
				return 1;	# 1.x is larger than bp152.x
			} else {
				$verb = $1;
				$b =~ s/\d*//;
			}


			#print "CMP number $vera $verb\n";

			return -1 if ($vera < $verb);
			return  1 if ($vera > $verb);
		} else {
			if ($b =~ /^(\d+)/) { # a is not a number => < 0, b is a number so it is higher...
				return -1;	# 1.x is larger than bp152.x
			}
			# alpha
			$a =~ s/^([^\d]+)//;
			$vera = $1;
			$b =~ s/^([^\d]+)//;
			$verb = $1;
			my $ret = $vera cmp $verb;
			#print "CMP alphanumeric $vera $verb is $ret\n";
			return -1 if ($ret < 0);
			return  1 if ($ret > 0);
		}
	} while (($a ne "") || ($b ne ""));
	#print "SAME\n";
	return 0;
}

# compares 2 rpm version-release pairs.
sub
versioncompare($$) {
	my ($a,$b) = @_;
	my @a;
	my @b;

	return 0 if ($a eq $b);

	@a = split(/-/,$a);
	@b = split(/-/,$b);

	foreach my $subvera (@a) {
		my $subverb = shift @b;
		my $ret = xcompare($subvera,$subverb);
		return $ret if ($ret < 0);
		return $ret if ($ret > 0);
	}
	return 0;
}

# OSC output memory caching ... as every osc call takes 1-2 seconds, try reading
# from a memory cache on commands where we know the content likely wont change during
# the script run. (so something like channel content, or list of files in projects)

my %cachedosc = ();
my $osccachehit = 0;
my $osccachemiss = 0;

sub
cached_osc_call($) {
	my ($cmdline) = @_;

	if ($cachedosc{$cmdline}) {
		print STDERR "CACHED: $osc $cmdline\n" if -t STDERR;
		$osccachehit++;
		return @{$cachedosc{$cmdline}};
	}

	print STDERR "UNCACHED: $osc $cmdline\n" if -t STDERR;
	open(OSC,"$osc $cmdline|");
	my @lines = <OSC>;
	close(OSC)||warn "$osc $cmdline:$!";

	$cachedosc{$cmdline} = \@lines;
	return @lines;
}

# OSC output file caching ... as every osc call takes 1-2 seconds, try reading
# from a filesystem cache on commands where we know the content will never change.
# (e.g. content of timestamped containers, files with build-release encoded etc.)

my $filecachehit = 0;
my $filecachemiss = 0;
sub
filecached_osc_call($) {
	my ($cmdline) = @_;
	my $fn = $cmdline;
	my @lines;

	# in memory caching has no additiional, as we use those mostly once.
	$fn =~ s/ /_/g;
	$fn =~ s/\//_/g;
	if (-f "$cverepobase/smash/osccached/$fn") {
		print STDERR "FILECACHED: $osc $cmdline\n" if -t STDERR;
		open(CACHE,"<$cverepobase/smash/osccached/$fn")||die "$cverepobase/smash/osccached/$fn:$!";
		@lines = <CACHE>;
		close(CACHE);
		$filecachehit++;
	} else {
		$filecachemiss++;
		print STDERR "UNCACHED: $osc $cmdline\n" if -t STDERR;
		open(OSC,"$osc $cmdline|");
		@lines = <OSC>;
		close(OSC)||warn "$osc $cmdline:$!";

		open(CACHE,">$cverepobase/smash/osccached/$fn")||die "$cverepobase/smash/osccached/$fn:$!";
		print CACHE join("",@lines);
		close(CACHE);
	}
	return @lines;
}

# this parses the repo-md primary info and extracts some things we use like sourcerpm -> binaryrpm maps
# could perhaps also abstract this via channels
# also records GA kernel-default versions for the livepatching handling.
sub
parse_primaryinfo($$$) {
	my ($name,$xml,$inqa) = @_;
	my $xmlparser = new XML::Bare( text => $xml ) || die "$name XML: $xml";
	my $xmlroot = $xmlparser->parse()||die;
	my $packages = XML::Bare::forcearray($xmlroot->{'metadata'}->{'package'});

	# print STDERR "parsing primary for $name\n" if -t STDERR;

	for my $package (@{$packages}) {
		my $srpm 	= $package->{'format'}->{'rpm:sourcerpm'}->{'value'};
		my $pkgname	= $package->{'name'}->{'value'};
		my $pkgver	= $package->{'version'}->{'ver'}->{'value'};
		my $pkgrel	= $package->{'version'}->{'rel'}->{'value'};

		if (($pkgname eq "kernel-default") || ($pkgname eq "kernel-xen") || ($pkgname eq "kernel-rt")) {
			my $arch = $package->{'arch'}->{'value'};
			$UpdateInfoReader::allkerneldefaultversions{"$pkgver-$pkgrel"}->{$arch} = 1;
			# print STDERR "$name GA kernel-default: $pkgver-$pkgrel\n";
		}

		# FIXME only needed in backports.yaml 
		#if (defined($srpm)) {
		#	$srpm =~ s/-[^-]*-[^-]*$//;
		#	# print STDERR $package->{'name'}->{'value'} . " => $srpm\n" if -t STDERR;
		#	$UpdateInfoReader::gasrpm2rpm{$name}->{$srpm}->{$pkgname} = 1;
		#}
		# $UpdateInfoReader::gapackages{$name}->{$pkgname} = "$pkgver-$pkgrel";
	}
}

# This parses one update entry out of a updateinfo list.
# This stores info on patches, their references, packages, titles, etc. etc. in perl maps.
sub
parse_update($$$) {
	my ($name,$upd,$inqa) = @_;
	my $patchid;
	# print STDERR "update type " . $upd->{'type'}->{'value'} . " version=" . $upd->{'version'}->{'value'} . "\n" if (-t STDERR);

	#return if ($upd->{'type'}->{'value'} ne "security");


	# retracted updates should not be tracked.
	return if ($upd->{'status'}->{'value'} eq "retracted");


	$patchid = $upd->{'id'}->{'value'};

	$UpdateInfoReader::patchdescription{$patchid}	= $upd->{'description'}->{'value'};

	# simple mans xml unescape
	if (defined($UpdateInfoReader::patchdescription{$patchid})) {
		$UpdateInfoReader::patchdescription{$patchid} =~ s/&quot;/'/g;
		$UpdateInfoReader::patchdescription{$patchid} =~ s/&lt;/</g;
		$UpdateInfoReader::patchdescription{$patchid} =~ s/&gt;/>/g;
		$UpdateInfoReader::patchdescription{$patchid} =~ s/&amp;/&/g;
		# in public cloud module 12 in a cloud-init update is a utf8 character in this name.
		#$UpdateInfoReader::patchdescription{$patchid} =~ s/Gon.*ri Le Bouder/Goneri Le Bouder/g;
		#$UpdateInfoReader::patchdescription{$patchid} =~ s/Bluetooth..?BR.EDR/Bluetooth BR\/EDR/g;
		#$UpdateInfoReader::patchdescription{$patchid} =~ s/Ren.* Korthaus/Rene Korthaus/g;
		#$UpdateInfoReader::patchdescription{$patchid} =~ s/Felix D.*rre/Felix Doerre/g;
		#$UpdateInfoReader::patchdescription{$patchid} =~ s/Ga.*l Portay/Gael Portay/g;
		#$UpdateInfoReader::patchdescription{$patchid} =~ s/Intel\0xAE Process/Intel Process/g;

	}

	$UpdateInfoReader::patchtitle{$patchid}		= $upd->{'title'}->{'value'};
	$UpdateInfoReader::patchtype{$patchid}		= $upd->{'type'}->{'value'};
	$UpdateInfoReader::patchseverity{$patchid}	= $upd->{'severity'}->{'value'};
	$UpdateInfoReader::patchissued{$patchid}	= $upd->{'issued'}->{'date'}->{'value'};
	$UpdateInfoReader::patchvirtual{$patchid}	= 0;
	$UpdateInfoReader::patchinqa{$patchid}		= $inqa;

	$UpdateInfoReader::patches{$name}->{$patchid}=1;

	my %srcpackagemap = ();
	my %packagemap = ();
	my %packagearchmap = ();

	if (defined($UpdateInfoReader::patchsrcpackages{$patchid})) {
		%srcpackagemap = %{$UpdateInfoReader::patchsrcpackages{$patchid}};
	}
	if (defined($UpdateInfoReader::patchpackages{$patchid})) {
		%packagemap = %{$UpdateInfoReader::patchpackages{$patchid}};
	}
	if (defined($UpdateInfoReader::patchpackagearchs{$patchid})) {
		%packagearchmap = %{$UpdateInfoReader::patchpackagearchs{$patchid}};
	}


	if (defined($upd->{'pkglist'}->{'collection'}->{'package'})) {
		my $arr = XML::Bare::forcearray($upd->{'pkglist'}->{'collection'}->{'package'});
		foreach my $pkg (@{$arr}) {
			my $name = $pkg->{'name'}->{'value'};
			my $version  = $pkg->{'version'}->{'value'};
			my $release  = $pkg->{'release'}->{'value'};
			my $arch = $pkg->{'arch'}->{'value'};

			#print "\tpackage $j name=" . $name . ", version=" . $version . " - " . $release . "." . $arch ."\n";

			if (($arch eq "src") || ($arch eq "nosrc")) {
				# filter weird multibuild names for glibc.
				$name = "glibc" if ($name eq "glibc.i686");
				$name = "glibc" if ($name eq "glibc-utils-src");
				$srcpackagemap{$name} = "$version-$release";
				next;
			}
			# we dont need to show them, do not store them.
			next if ($name =~ /-debuginfo/);
			next if ($name =~ /-debugsource/);

			if (!defined($version)) {
				# bugs in RES updateinfo.xml apparently
				# print STDERR "no version in $name entry? Full entry: " . Dumper($pkg) . "\n";
				next;
			}
			$packagemap{$name} = "$version-$release";

			# register all kernel-defaults
			if (($name eq "kernel-default") || ($name eq "kernel-xen")) {
				$UpdateInfoReader::allkerneldefaultversions{"$version-$release"}->{$arch} = 1;
			}
			$packagearchmap{$name}->{$arch} = 1;
		}
	} else {
		warn "no pkglist for $name, $patchid\n";
	}
	$UpdateInfoReader::patchsrcpackages{$patchid} = \%srcpackagemap;
	$UpdateInfoReader::patchpackages{$patchid} = \%packagemap;
	$UpdateInfoReader::patchpackagearchs{$patchid} = \%packagearchmap;


	my %cve = ();
	my $havecve = 0;

	if (defined($upd->{'references'}->{'reference'})) {
		#   <reference href="https://bugzilla.novell.com/show_bug.cgi?id=815236" id="815236" title="bug number 815236" type="bugzilla"/>
		#   <reference href="http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-2944" id="CVE-2013-2944" title="CVE-2013-2944" type="cve"/>
		my $arr = XML::Bare::forcearray($upd->{'references'}->{'reference'});

		foreach my $ref (@{$arr}) {
			if ($ref->{'type'}->{'value'} eq "cve") {
				$havecve = 1;
				my $cve = $ref->{'id'}->{'value'};
				$cve =~ s/^\s*//;
				$cve =~ s/\s*$//;
				$cve =~ s/:$//;
				if ($cve =~ /^201/) {
					print STDERR "adjusting CVE id $cve in $patchid of $name\n" if (-t STDERR);
					$cve{"CVE-$cve"} = 1;
				} else {
					unless ($cve =~ /^CVE-\d\d\d\d-\d\d\d\d\d*$/) {
						print STDERR "incorrect CVE name $cve in $patchid of $name\n" if (-t STDERR);
						next;
					}
					$cve{$cve} = 1;
				}
			}
			if ($ref->{'type'}->{'value'} eq "bugzilla") {
				$cve{$ref->{'id'}->{'value'}} = 1;
			}
			#print "\treference $j: id=" . $ref->{'id'}->{'value'} . ", type=" . $ref->{'type'}->{'value'} . "\n";
		}
	} else {
		print STDERR "no references in " . $upd->{'title'}->{'value'} . "\n" if (-t STDERR);
	}
	if (!$havecve) {
		# this is a security update, but no CVE? Try parsing the description
		my $desc = $UpdateInfoReader::patchdescription{$patchid};

		if (defined($desc)) {
			while ($desc =~ /(CVE-\d\d\d\d-\d\d\d\d\d*)/) {
				$cve{$1} = 1;
				$desc =~ s/(CVE-\d\d\d\d-\d\d\d\d\d*)//;
			}
		}
		#print STDERR "found " . join(",",sort(keys %cve)) . " cves in $patchid\n";
	}

	# Replace misspelled CVEs by correct spelling.
	foreach my $replacecve (keys %replacelist) {
		if ($cve{$replacecve}) {
			if (defined($replacepkg{$replacecve})) {
				next unless (grep(/$replacepkg{$replacecve}/,keys %packagemap));
			}
			delete $cve{$replacecve};
			$cve{$replacelist{$replacecve}}=1;
		}
	}
	delete $cve{"CVE-2018-15919"};	# FIXME: hack: remove CVE-2018-15919 from updateinfo, we only issued a release + revert

	if (!defined($UpdateInfoReader::patchreferences{$patchid})) {
		$UpdateInfoReader::patchreferences{$patchid} = {};
	}
	foreach my $xcve (keys %cve) {
		$UpdateInfoReader::patchreferences{$patchid}->{$xcve} = 1;
	}

	return if ($name =~ /^SUSE:/); # do not register patches per cve in codestreams.

	foreach my $cve (sort keys %cve) {
		next unless ($cve =~ /CVE-/); # we also have bugnrs in the cve map now

		my $product;

		if ($inqa) {
			if (defined($UpdateInfoReader::productsinqa{$cve})) {
				$product = $UpdateInfoReader::productsinqa{$cve};
			}
		} else {
			if (defined($UpdateInfoReader::products{$cve})) {
				$product = $UpdateInfoReader::products{$cve};
			}
		}
		$product->{$name}->{'patchnames'}->{$patchid} = 1;
		foreach my $pkg (keys %packagemap) {
			if ($filterlist{$cve}) {
				unless ($pkg =~ /$filterlist{$cve}/i) {
					print STDERR "$cve -> skipping $pkg due to filter list\n" if -t STDERR;
					next;
				}
			}
			if (defined($product->{$name}->{'packages'}->{$pkg})) {
				if (versioncompare($product->{$name}->{'packages'}->{$pkg},$packagemap{$pkg}) >= 0) {
					#print STDERR "$pkgmap{$pkg} > $packagemap{$pkg}\n";
					next;
				}
			}
			$product->{$name}->{'packages'}->{$pkg} = $packagemap{$pkg};
			$product->{$name}->{'packagearchs'}->{$pkg} = $packagearchmap{$pkg};
		}
		# also do the same for source packages
		foreach my $pkg (keys %srcpackagemap) {
			if ($filterlist{$cve}) {
				unless ($pkg =~ /$filterlist{$cve}/i) {
					print STDERR "$cve -> skipping $pkg due to filter list\n" if -t STDERR;
					next;
				}
			}
			if (defined($product->{$name}->{'srcpackages'}->{$pkg})) {
				if (versioncompare($product->{$name}->{'srcpackages'}->{$pkg},$srcpackagemap{$pkg}) >= 0) {
					#print STDERR "$pkgmap{$pkg} > $srcpackagemap{$pkg}\n";
					next;
				}
			}
			$product->{$name}->{'srcpackages'}->{$pkg} = $srcpackagemap{$pkg};
		}
		if ($inqa) {
			$UpdateInfoReader::productsinqa{$cve} = $product;
		} else {
			$UpdateInfoReader::products{$cve} = $product;
		}
	}
}

# parse all of the updateinfo, iterates over the <update> entries.
sub
parse_updateinfo($$$) {
	my ($name,$xml,$inqa) = @_;
	my $xmlparser = new XML::Bare( text => $xml ) || die "$name XML: $xml";
	my $xmlroot = $xmlparser->parse()||die;
	my $updates = XML::Bare::forcearray($xmlroot->{'updates'}->{'update'});

	for my $update (@{$updates}) {
		parse_update($name,$update,$inqa);
	}
}

# parse the static data/ga/ info lists into something that looks like virtual updates.
sub
parse_gainfo($) {
	my ($name) = @_;

	my $csv = "\L$name";
	$csv =~ s/ /_/g;

	$csv = "$cverepobase/data/ga/$csv.csv";

	if (! -f $csv) {
		print STDERR "gainfo $name no file $csv.\n" if (-t STDERR);
		return;
	}

	my $opensuseid = 10000;

	open(CSV,"<$csv")||die;
	while (<CSV>) {
		chomp;
		my @line = split(/,/);
		my $pkg = shift @line;
		my $srcpkg = "";

		my $opensuseyear = 2024;
		my $opensusemonth = 0;
		my $opensuseday = 0;

		# for opensuse tumbleweed we add the date now
		if ($pkg =~ /^(\d\d\d\d)(\d\d)(\d\d)/) {
			$opensuseyear = $1;
			$opensusemonth = $2;
			$opensuseday = $3;
			$srcpkg = shift @line;
			$pkg = shift @line;
		}

		my @pkgs = split(/;/,$pkg);

		$pkg = $pkgs[0];
		if (!defined($pkg)) {
			print STDERR "empty line ($_) in file $csv \n";
			next;
		}
		$pkg =~ /(.*)-([^-]*-[^-]*)$/;
		my $pkgname = $1;
		my $pkgversion = $2;

		die "ga reader: pkgname is empty, $pkg" if (!defined($pkgname));

		my %cve = ();
		foreach my $cve (@line) { $cve{$cve} = 1; }

		my $patchid = "$name GA $pkgname-$pkgversion";

		# synthesize patches for openSUSE Tumbleweed
		if ($name =~ /tumbleweed/i) {
			$patchid = "openSUSE-Tumbleweed-$opensuseyear-$opensuseid";
			my $noticeid = "openSUSE-SU-$opensuseyear:$opensuseid-1";
			$opensuseid++;

			if (%CanDBReader::susenotice2patches) {
				$CanDBReader::susenotice2patches{$noticeid}->{$patchid} = 1;
				$CanDBReader::patch2susenotices{$patchid} = $noticeid;
			}
		}

		$UpdateInfoReader::patchreferences{$patchid} = \%cve;

		# Register us as GA patch
		$UpdateInfoReader::patches{$name}->{$patchid} = 1;

		$UpdateInfoReader::patchseverity{$patchid} = "moderate";
		$UpdateInfoReader::patchtitle{$patchid} = "$pkgname-$pkgversion on GA media";
		$UpdateInfoReader::patchtype{$patchid} = "security";
		$UpdateInfoReader::patchdescription{$patchid} = "These are all security issues fixed in the $pkgname-$pkgversion package on the GA media of $name.";

		if ($opensusemonth == 0) {
			my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($csv);
			$UpdateInfoReader::patchissued{$patchid} = $ctime;
		} else {
			$UpdateInfoReader::patchissued{$patchid} = DateTime->new( year => $opensuseyear, month => $opensusemonth, day => $opensuseday)->epoch;
		}

		$UpdateInfoReader::patchvirtual{$patchid}= 1;

		my %packagemap = ();
		my %packagesrcmap = ();
		my %packagearchmap = ();
		if (defined($UpdateInfoReader::patchpackages{$patchid})) {
			%packagemap = %{$UpdateInfoReader::patchpackages{$patchid}};
		}
		if (defined($UpdateInfoReader::patchsrcpackages{$patchid})) {
			%packagesrcmap = %{$UpdateInfoReader::patchsrcpackages{$patchid}};
		}
		if (defined($UpdateInfoReader::patchpackagearchs{$patchid})) {
			%packagearchmap = %{$UpdateInfoReader::patchpackagearchs{$patchid}};
		}

		foreach my $pkg (@pkgs) {
			$pkg =~ /(.*)-([^-]*-[^-]*)$/;
			my $pkgname = $1;
			my $pkgversion = $2;

			$packagemap{$pkgname} = $pkgversion;
			if ($srcpkg ne "") {
				$packagesrcmap{$srcpkg} = $pkgversion;
			}
			$packagearchmap{$pkgname} = { "x86_64" => 1, "aarch64" => 1, "ppc64le" => 1, "s390x" => 1 };
		}

		$UpdateInfoReader::patchpackages{$patchid} = \%packagemap;
		$UpdateInfoReader::patchsrcpackages{$patchid} = \%packagesrcmap;
		$UpdateInfoReader::patchpackagearchs{$patchid} = \%packagearchmap;

		foreach my $cve (sort keys %cve) {
			$UpdateInfoReader::products{$cve}->{$name}->{'patchnames'}->{$patchid} = 1;
			foreach my $pkg (keys %packagemap) {
				if ($srcpkg ne "") {
					$UpdateInfoReader::products{$cve}->{$name}->{'srcpackages'}->{$srcpkg} = $packagemap{$pkg};
				}
				$UpdateInfoReader::products{$cve}->{$name}->{'packages'}->{$pkg} = $packagemap{$pkg};
				$UpdateInfoReader::products{$cve}->{$name}->{'packagearchs'}->{$pkg} = { "x86_64" => 1, "aarch64" => 1, "ppc64le" => 1, "s390x" => 1 };
			}
			# FIXME: source packages
			# &SMASHData::read_smash_issue($cve);
			if (defined($SMASHData::severity{$cve}) && ($SMASHData::severity{$cve} ne $UpdateInfoReader::patchseverity{$patchid})) {
				if (	($UpdateInfoReader::patchseverity{$patchid} eq "moderate") &&
					(($SMASHData::severity{$cve} eq "important") || ($SMASHData::severity{$cve} eq "critical"))
				) {
					$UpdateInfoReader::patchseverity{$patchid} = $SMASHData::severity{$cve};
				}
				if (	($UpdateInfoReader::patchseverity{$patchid} eq "important") &&
					($SMASHData::severity{$cve} eq "critical")
				) {
					$UpdateInfoReader::patchseverity{$patchid} = $SMASHData::severity{$cve};
				}
			}
		}

	}
	close(CSV);
}

# Mapping of filesystem names to marketing names.
my %name2product = (
	'EL'	=> 'SUSE Manager Client Tools for RHEL, Liberty and Clones',
        'RES'    => 'Redhat Expanded Support',
        'Ubuntu'    => 'SUSE Manager Ubuntu',
        'Debian'    => 'SUSE Manager Debian',
        'openSUSE-SLE'    => 'openSUSE Leap',
        'openSUSE-Leap-Micro'    => 'openSUSE Leap Micro',
        'SLE-SERVER'    => 'SUSE Linux Enterprise Server',
        'SLE-RPI'    => 'SUSE Linux Enterprise Server for Raspberry Pi',
        'SLE-INSTALLER'    => 'SUSE Linux Enterprise Installer Updates',
        'SLE-SERVER-INSTALLER'    => 'SUSE Linux Enterprise Server Installer Updates',
        'SLE-RPI-INSTALLER'    => 'SUSE Linux Enterprise Server Installer Updates for Raspberry Pi',
        'SLE-DESKTOP'   => 'SUSE Linux Enterprise Desktop',
        'SLE-DESKTOP-INSTALLER'   => 'SUSE Linux Enterprise Desktop Installer Updates',
        'SLE-HA'        => 'SUSE Linux Enterprise High Availability Extension',
        'SLE-HA-GEO'    => 'SUSE Linux Enterprise High Availability GEO Extension',
        'SLE-SDK'       => 'SUSE Linux Enterprise Software Development Kit',
        'SLE-BSK'       => 'SUSE Linux Enterprise Build System Kit',
        'SLE-WE'        => 'SUSE Linux Enterprise Workstation Extension',
	'SLE-PRELOAD-WYSE' => 'SUSE Linux Enterprise Desktop preloaded on WYSE',
        'SLE-Manager-Tools'     => 'SUSE Manager Tools',
        'SLE-Manager-Tools-Beta-For-Micro'     => 'SUSE Manager Tools Beta for SLE Micro',
	'SLE-Manager-Tools-For-Micro'	=> 'SUSE Manager Tools for SLE Micro',
        'SLE-Module-Adv-Systems-Management' => 'SUSE Linux Enterprise Module for Advanced Systems Management',
        'SLE-Module-Confidential-Computing' => 'SUSE Linux Enterprise Module for Confidential Computing Technical Preview',
        'SLE-Module-Basesystem' => 'SUSE Linux Enterprise Module for Basesystem',
        'SLE-Module-SUSE-Manager-Proxy' => 'SUSE Manager Proxy Module',
        'SLE-Module-SUSE-Manager-Server' => 'SUSE Manager Server Module',
        'SLE-Module-SUSE-Manager-Retail-Branch-Server' => 'SUSE Manager Retail Branch Server Module',
        'SLE-Module-CAP-Tools' => 'SUSE Linux Enterprise Module for CAP',
        'SLE-Module-Containers' => 'SUSE Linux Enterprise Module for Containers',
        'SLE-Module-Desktop-Applications' => 'SUSE Linux Enterprise Module for Desktop Applications',
        'SLE-Module-Development-Tools-OBS' => 'SUSE Linux Enterprise Module for Open Buildservice Development Tools',
        'SLE-Module-Development-Tools' => 'SUSE Linux Enterprise Module for Development Tools',
        'SLE-Module-HPC' => 'SUSE Linux Enterprise Module for HPC',
        'SLE-Module-Legacy' => 'SUSE Linux Enterprise Module for Legacy',
        'SLE-Module-Certifications' => 'SUSE Linux Enterprise Module for Certifications',
        'SLE-Module-Live-Patching' => 'SUSE Linux Enterprise Live Patching',	# name slightly different.
        'SLE-Module-NVIDIA-Compute' => 'SUSE Linux Enterprise Module for NVIDIA Compute',
        'SLE-Module-Public-Cloud' => 'SUSE Linux Enterprise Module for Public Cloud',
	'SLE-Module-Public-Cloud-Unrestricted' => 'SUSE Linux Enterprise Module for Public Cloud',
        'SLE-Module-SAP-Applications' => 'SUSE Linux Enterprise Module for SAP Applications',
        'SLE-Module-SAP-Business-One' => 'SUSE Linux Enterprise Module for SAP Business One',
        'SLE-Module-Server-Applications' => 'SUSE Linux Enterprise Module for Server Applications',
        'SLE-Module-Toolchain' => 'SUSE Linux Enterprise Module for Toolchain',
        'SLE-Module-Web-Scripting' => 'SUSE Linux Enterprise Module for Web and Scripting',
        'SLE-Module-Packagehub-Subpackages' => 'SUSE Linux Enterprise Module for Package Hub',
        'SLE-Module-Python2' => 'SUSE Linux Enterprise Module for Python 2',
        'SLE-Module-Python3' => 'SUSE Linux Enterprise Module for Python 3',
        'SLE-Module-RT' => 'SUSE Real Time Module',
        'SLE-Module-Transactional-Server' => 'SUSE Linux Enterprise Module for Transactional Server',
        'SLE-Live-Patching' => 'SUSE Linux Enterprise Live Patching',
        'Carwos' => 'SUSE Carwos',
        'ElementalTeal' => 'Elemental Teal',
        'Elemental' => 'Elemental',
        'Cloud' => 'SUSE Cloud',
	'OpenStack-Cloud' => 'SUSE OpenStack Cloud',
	'HPE-Helion-OpenStack-Cloud' => 'HPE Helion OpenStack Cloud',
	'HPE-Helion-OpenStack' => 'HPE Helion OpenStack',
	'OpenStack-Cloud-Crowbar' => 'SUSE OpenStack Cloud Crowbar',
        'SLE-POS' => 'SUSE Linux Enterprise Point of Sale',
        'SLE-RT' => 'SUSE Linux Enterprise Real Time',
        'SLE-SAP-AIO' => 'SUSE Linux Enterprise Server for SAP Applications',
	'SLE-SAP-INSTALLER' => 'SUSE Linux Enterprise Server for SAP Installer Updates',
        'SLE-SAP' => 'SUSE Linux Enterprise Server for SAP Applications',
        'SLE-HAE' => 'SUSE Linux Enterprise High Availability Extension',
        'SLE-Product-HA' => 'SUSE Linux Enterprise High Availability Extension',
        'SLE-HPC' => 'SUSE Linux Enterprise High Performance Computing',
        'SLE-Product-HPC' => 'SUSE Linux Enterprise High Performance Computing',
        'SLE-Product-SLES' => 'SUSE Linux Enterprise Server',
        'SLE-Product-SLES_SAP' => 'SUSE Linux Enterprise Server for SAP Applications',
        'SLE-Product-SLED' => 'SUSE Linux Enterprise Desktop',
        'SLE-Product-WE' => 'SUSE Linux Enterprise Workstation Extension',
        'SLE-Product-RT' => 'SUSE Linux Enterprise Real Time',
        'SLE-Product-SLES-BCL' => 'SUSE Linux Enterprise Server Business Critical Linux',
        'SLE-Product-SUSE-Manager-Proxy' => 'SUSE Manager Proxy',
        'SLE-Product-SUSE-Manager-Server' => 'SUSE Manager Server',
        'SLE-Product-SUSE-Manager-Retail-Branch-Server' => 'SUSE Manager Retail Branch Server',
        'SUSE-Manager-Retail-Branch-Server' => 'SUSE Manager Retail Branch Server Extension',
        'SLE-SMT' => 'Subscription Management Tool',
        'SLES4VMWARE' => 'SUSE Linux Enterprise Server for VMWare',
	'12-Cloud-Compute' => 'SUSE Cloud Compute Node for SUSE Linux Enterprise 12',
	'Storage' => 'SUSE Enterprise Storage',
	'SLE-SLMS' => 'SUSE Lifecycle Management Server',
	'SUSE-CLOUD' => 'SUSE OpenStack Cloud',
	'SUSE-MANAGER-PROXY' => 'SUSE Manager Proxy',
	'SUSE-Manager-Proxy' => 'SUSE Manager Proxy',
	'SUSE-Manager-Server' => 'SUSE Manager Server',
	'SUSE-MANAGER' => 'SUSE Manager',
	'SUSE-MicroOS' => 'SUSE Linux Enterprise Micro',
	'SLE-Micro' => 'SUSE Linux Enterprise Micro',
	'SL-Micro' => 'SUSE Linux Enterprise Micro',
	'SL-Micro-Extras' => 'SUSE Linux Enterprise Micro Extras',
	'SUSE-Manager-Tools-For-SL-Micro' => 'SUSE Manager Tools for SL Micro',
	'SUSE-Manager-Retail' => 'SUSE Manager Retail',
	'SLE-STUDIOONSITE' => 'SUSE Studio Onsite',
	'SLE-STUDIOONSITERUNNER' => 'SUSE Studio Onsite Runner',
	'SLE-WEBYAST' => 'SUSE WebYast',
	'OpenStack-Cloud-Magnum-Orchestration' => 'OpenStack Cloud Magnum Orchestration',
	'SUSE-CAASP' => 'SUSE CaaS Platform',
	'SUSE-CaaSP-Toolchain' => 'SUSE CaaS Platform Toolchain',
	'SLE-OBS-Deps' => 'SUSE Linux Enterprise packages for Open Build Service',
);

my %product00map = (
	'sle-ha' => 'SUSE Linux Enterprise High Availability Extension',
	'sle-we' => 'SUSE Linux Enterprise Workstation Extension',
	'sle-module-basesystem' => 'SUSE Linux Enterprise Module for Basesystem',
	'sle-module-legacy' => 'SUSE Linux Enterprise Module for Legacy',
	'sle-module-certifications' => 'SUSE Linux Enterprise Module for Certifications',
	'sle-module-confidential-computing' => 'SUSE Linux Enterprise Module for Confidential Computing Technical Preview',
	'sle-module-development-tools' => 'SUSE Linux Enterprise Module for Development Tools',
	'sle-module-development-tools-obs' => 'SUSE Linux Enterprise Module for Open Buildservice Development Tools',
	'sle-module-hpc' => 'SUSE Linux Enterprise Module for HPC',
	'sle-module-containers' => 'SUSE Linux Enterprise Module for Containers',
	'sle-module-desktop-applications' => 'SUSE Linux Enterprise Module for Desktop Applications',
	'sle-module-server-applications' => 'SUSE Linux Enterprise Module for Server Applications',
	'sle-module-sap-applications' => 'SUSE Linux Enterprise Module for SAP Applications',
	'sle-module-public-cloud' => 'SUSE Linux Enterprise Module for Public Cloud',
	'sle-module-packagehub-subpackages' => 'SUSE Linux Enterprise Module for Package Hub',
	'sle-module-python3' => 'SUSE Linux Enterprise Module for Python 3',
	'sle-module-python2' => 'SUSE Linux Enterprise Module for Python 2',
	'sle-module-live-patching' => 'SUSE Linux Enterprise Live Patching',
	'sle-module-transactional-server' => 'SUSE Linux Enterprise Module for Transactional Server',
	'sle-module-web-scripting' => 'SUSE Linux Enterprise Module for Web and Scripting',
	'sle-module-rt' => 'SUSE Real Time Module',
	'sle-module-sap-business-one' => 'SUSE Linux Enterprise Module for SAP Business One',
	'sle-module-cap-tools' => 'SUSE Linux Enterprise Module for CAP',
	'SLE-Micro' => 'SUSE Linux Enterprise Micro',
	'SLES' => 'SUSE Linux Enterprise Server',
	'SLES_SAP' => 'SUSE Linux Enterprise Server for SAP Applications',
	'sle-sdk' => 'SUSE Linux Enterprise Software Development Kit',
	'sle-ha-geo' => 'SUSE Linux Enterprise High Availability GEO Extension',
	'sle-live-patching' => 'SUSE Linux Enterprise Live Patching',
);
sub
remap_000product_to_name($) {
	my ($name) = @_;

	if (defined($product00map{$name})) {
		return $product00map{$name};
	}
	return "" if ($name eq "sle-bsk");
	print STDERR "$name not mapped in 000product mapper\n";
	return $name;
}
#
# Temporary caches of codestream->package->version info to patches, for use by containers and images.
#

my %project2package2fixes = ();
my %project2package2patches = ();

# { package => [ version-release	=> [CVE, CVE], ]

#<buildinfo project="SUSE:SLE-15:Update:CR" repository="images" package="sles15-image" downloadurl="http://download.suse.de/ibs">
#  <arch>x86_64</arch>
#  <debuginfo>0</debuginfo>
#  <imagetype>docker</imagetype>
#  <bdep name="ca-certificates" noinstall="1" version="2+git20170807.10b2785" release="7.3.3" arch="noarch" project="SUSE:SLE-15:Update" repository="standard"/>
#  <path project="SUSE:SLE-15:Update:CR" repository="images" url="http://download.suse.de/ibs/SUSE:/SLE-15:/Update:/CR/images/"/>
#  <path project="SUSE:SLE-15:Update" repository="standard" url="http://download.suse.de/ibs/SUSE:/SLE-15:/Update/standard/"/>
# <path project="SUSE:SLE-15:GA" repository="standard" url="http://download.suse.de/ibs/SUSE:/SLE-15:/GA/standard/"/>
#</buildinfo>

my %containertag = ();
my %projects = ();

my %cvefixedinlabel = (); # { cve -> { label -> 1 } }

# parse build deps into a structure
sub parse_builddep($$$$$$) {
	my ($project,$containerinfo,$xml,$outputprefix,$incremental,$version) = @_;

	if ($xml eq "") {
		warn "$project has empty xml\n";
		return;
	}
        my $xmlparser = new XML::Bare( text => $xml ) || die "XML: |$xml| in project $project";
        my $xmlroot = $xmlparser->parse()||die;
        my $bdeps = XML::Bare::forcearray($xmlroot->{'report'}->{'binary'});


	print STDERR "outputprefix=$outputprefix version=$version\n" if -t STDERR;

	my $containername;
	if (defined($containerinfo) && ($containerinfo ne "")) {
		my $contref = decode_json($containerinfo);
		my %containerinfo = %{$contref};
		my @tags = @{$containerinfo{'tags'}};

		# find the best tag to specify this container
		#  if there is a "latest tag", do not use any : tag
		#  otherwise look for shortest
		my $containertag = "";
		foreach my $tag (@tags) {
			if ($tag =~ /latest$/) {
				$containertag = $tag;
				$containertag =~ s/:.*//;
				last;
			}
			if (!length($containertag) || (length($tag) < length($containertag))) {
				$containertag = $tag;
			}
		}
		$containername = $containertag;
		print STDERR "Selected $containertag out of " . join(",",sort @tags) . "\n" if -t STDERR;
	}
	# This will include the PubCloud images into the affected software matrix...
	if ((!defined($containername) || ($containername eq "")) && ($outputprefix =~ /^Image (.*)/)) {
		$containername = $1; 
	}

	my %patches;

	#print Dumper(\$bdeps);

	my $outputcves = "";

	foreach my $bdep (@{$bdeps}) {
		#print Dumper(\$bdep);

		my $version = $bdep->{'version'}->{'value'};
		my $release = $bdep->{'release'}->{'value'};
		my $name = $bdep->{'name'}->{'value'};
		my $project = $bdep->{'project'}->{'value'};

		if (!defined($project)) {
			print STDERR "no project ... \n" if -t STDERR;
			next;
		}

		$projects{$project} = 1;

		my $xproj = $project;
		$xproj =~ s/:GA/:Update/;
		if (defined($containername)) {
			unless ($containername =~ /caasp/)	{ 	# filter caasp for now, too many
				$UpdateInfoReader::codestreampkg2container{"$xproj.$name"}->{$containername} = 1;
			}
		}

		#print "$project: $name - $version - $release\n";
		next if ($project =~ /:GA/);
		next if ($project =~ /:CASP40$/);

		allupdates($project);
		# print STDERR "project $project\n" unless ($project eq "SUSE:SLE-15:Update");
		if (defined($project2package2fixes{$project})) {
			#print STDERR "we have: $name $version-$release\n" if -t STDERR;
			my $package2fixes = $project2package2fixes{$project};
			if (defined($package2fixes->{$name})) {
				my %version2cve = %{$package2fixes->{$name}};

				foreach my $xver (sort keys %version2cve) {
					#print STDERR "checking: $xver\n" if -t STDERR;
					if (versioncompare("$version-$release",$xver) >= 0) {
						#print STDERR "LARGER OR SAME: $version-$release >= $xver\n" if -t STDERR;
						#print OUTPUTCVES "$name - $version-$release (fixed in $xver): " . join(",",@{$version2cve{$xver}}) . "\n";
						# We synthesize patches for the containers and images...


						my $origpatchname = $project2package2patches{$project}->{$name}->{$xver};

						my $patchname = $origpatchname;
						$patchname =~ s/SUSE-/$outputprefix-/;

						$patches{$origpatchname} = 1;

						$UpdateInfoReader::patches{$outputprefix}->{$patchname} = 1;

						$UpdateInfoReader::patchtype{$patchname} = $UpdateInfoReader::patchtype{$origpatchname};
						$UpdateInfoReader::patchseverity{$patchname} = $UpdateInfoReader::patchseverity{$origpatchname};
						$UpdateInfoReader::patchdescription{$patchname} = $UpdateInfoReader::patchdescription{$origpatchname};
						$UpdateInfoReader::patchreferences{$patchname} = $UpdateInfoReader::patchreferences{$origpatchname};
						$UpdateInfoReader::patchissued{$patchname} = $UpdateInfoReader::patchissued{$origpatchname};
						$UpdateInfoReader::patchvirtual{$patchname}= 1;
						$UpdateInfoReader::patchinqa{$patchname} = 0;

						# this needs special attention. our patch does not include the *full* set of packages, only the
						# ones in the image.
						$UpdateInfoReader::patchpackages{$patchname}->{$name} = $xver;

						# FIXME, copies all architectures here.
						$UpdateInfoReader::patchpackagearchs{$patchname}->{$name} = $UpdateInfoReader::patchpackagearchs{$origpatchname}->{$name};

						# This puts us into the global CVE mapping
						my @cves = grep(/CVE/,@{$version2cve{$xver}});

						#print STDERR "cves: " . join(",",@cves) . "\n" if -t STDERR;
						foreach my $cve (@cves) {
							# add to updateinfo db

							if (defined($containername)) {
								$UpdateInfoReader::containerpkgfixedcve{$containername}->{$name}->{$cve} = 1;
							}

							#print STDERR "adding $name / $cve to $outputprefix\n" if -t STDERR;
							my $newprefix = $outputprefix;
							if ($outputprefix =~ /Container /) {
								$newprefix =~ s/Container //;

								# Switch to the labeled container where the CVE was fixed
								if (defined($cvefixedinlabel{$cve}->{$newprefix})) {
									$newprefix = $cvefixedinlabel{$cve}->{$newprefix};
								}
								$newprefix = "Container $newprefix";
							}
							$UpdateInfoReader::products{$cve}->{$newprefix}->{'patchnames'}->{$patchname} = 1;
							if ($filterlist{$cve}) {
								unless ($name =~ /$filterlist{$cve}/) {
									print STDERR "$cve -> skipping $name due to filter list\n" if -t STDERR;
									next;
								}
							}
							$UpdateInfoReader::products{$cve}->{$newprefix}->{'packages'}->{$name} = $xver;
							$UpdateInfoReader::products{$cve}->{$newprefix}->{'packagearchs'}->{$name} = $UpdateInfoReader::patchpackagearchs{$origpatchname};
						}
					} else {
						#print "SMALLER: $version-$release > $xver\n";
					}
				}
			}
		}
	}
}

sub
get_updateinfo_from_host($$$) {
	my ($path,$type,$host) = @_;

	my $urlprefix = "$host/$path/";

	my $url = "$urlprefix/repodata/repomd.xml";

	my $repomdxml = updateinfo_get_url($url);
	return undef if (!defined($repomdxml));

	my $xmlparser = new XML::Bare( text => $repomdxml ) || die "$path XML: $repomdxml";
	my $xmlroot = $xmlparser->parse()||die;
	my $repo = XML::Bare::forcearray($xmlroot->{'repomd'}->{'data'});

	foreach my $part (@{$repo}) {
		next unless ($part->{type}->{value} eq $type);
		my $location = $part->{location}->{href}->{value};
		my $content = updateinfo_get_url("$urlprefix/$location");
		return undef if (!defined($content));
		if ($location =~ /gz$/) {
			return Compress::Zlib::memGunzip($content);
		}
		if ($location =~ /zst$/) {
			my ($fh, $filename) = tempfile();

			syswrite ($fh, $content);
			close($fh);
			open(ZSTD,"zstd -q -d -c $filename|");
			my $decomp = join("",<ZSTD>);
			close(ZSTD);
			unlink($filename);
			return $decomp;
		}
	}
	return undef;
}

# find out all CVEs vs package-version-release from a project
sub
allupdates($) {
	my ($project) = @_;

	my $baseurl = "http://download.suse.de/ibs/";

	if (defined($project2package2fixes{$project})) {
		# we fetched it already
		return;
	}

	# avoid trying to load empty projects multiple times, slows us down.
	return if ($fetched{$project});
	$fetched{$project} = 1;

	my %package2fixes = ();
	my %package2patches = ();

	# First fetch the whole update codestream updateinfo.
	my $xproj = $project;
	$xproj =~ s/:/:\//g;

	my $xml = get_updateinfo_from_host("ibs/$xproj/standard/","updateinfo", "https://dist.suse.de");
	if (!defined($xml)) {
		warn "no repomdxml or updateinfo in ibs/$xproj/standard/" if -t STDERR;
		return;
	}

	#print STDERR "reading updateinfo for $project\n";
	&UpdateInfoReader::parse_updateinfo($project,$xml,0);

	# flip the data structure so we can use it better.

	if (!defined($UpdateInfoReader::patches{$project})) {
		warn "no security patches read from $project\n";
		return;
	}
	foreach my $patch (sort keys %{$UpdateInfoReader::patches{$project}}) {
		my %packages = %{$UpdateInfoReader::patchpackages{$patch}};
		my @cves = sort keys %{$UpdateInfoReader::patchreferences{$patch}};

		#print STDERR "processing $patch with " . join(",",@cves) . "\n" if -t STDERR;

		foreach my $package (keys %packages) {
			my $version = $packages{$package};

			$package2fixes{$package}->{$version} = \@cves;
			$package2patches{$package}->{$version} = $patch;
		}
	}

	$project2package2fixes{$project} = \%package2fixes;
	$project2package2patches{$project} = \%package2patches;
}

# Always do this, as its low effort:

# CVE typo replacement mapper
open(REPLACELIST,"<$cverepobase/data/replace")||die "$cverepobase/data/replace: $!";
while (<REPLACELIST>) {
	chomp;
	my $old;
	my $new;
	my $pkg;
	($old,$new,$pkg) = split (/,/);
	$replacelist{$old} = $new;
	if (defined($pkg)) {
		$replacepkg{$old} = $pkg;
	}
}
close(REPLACELIST);

# CVE package filtering (if we have patchinfo with lots of packages, but CVE is only in one)
open(FILTERLIST,"<$cverepobase/data/filter")||die "$cverepobase/data/filter: $!";
while (<FILTERLIST>) {
	chomp;
	my $cve;
	my $pkg;
	($cve,$pkg) = split (/,/);
	$filterlist{$cve} = $pkg;
}
close(FILTERLIST);

# adding (missing CVE) references to existing patches.
# CVE,PATCHID1;PATCHID2;PATCHID3
open(EXTRAREFS,"<$cverepobase/data/extrareferences")||die "$cverepobase/data/extrareferences: $!";
while (<EXTRAREFS>) {
	chomp;
	my $cve;
	my $patches;
	($cve,$patches) = split (/,/);

	foreach my $patch (split(/;/,$patches)) {
		$UpdateInfoReader::patchreferences{$patch}->{$cve} = 1;
	}
}
close(EXTRAREFS);

# primary image importer. Takes 30+ minutes to run currently
sub
import_images() {
	# Read in the previous containers infos to find the exact tags where a CVE was fixed.
	# strip out moving tags like "latest" or "15.1" or so (tags used in more than 1 release)
	# $VAR1 = {
	#         'references' => [
	#                           '1178561',
	#                           'CVE-2021-3997',
	#                           '1194178',
	#                           '1190515'
	#                         ],
	#         'container_name' => 'suse/sle15',
	#         'tags' => [
	#                     'suse/sle15:15.3',
	#                     'suse/sle15:15.3.17.8.57'
	#                   ],
	#         'advisory_id' => 'SUSE-CU-2022:39-1',
	#         'release' => '17.8.57',
	#         'ctype' => 'Container',
	#         'severity' => 'moderate',
	#         'type' => 'security'
	#       };

	my %json = ();
	my @jsons = <$cverepobase/images/*.json>;
	my %alltags = (); # { tag -> count }  to find out moving tags
	my %cvefixedin2container = (); # { cve -> { container -> 1 } }
	foreach my $jsonfile (sort @jsons) {
		my $fn = $jsonfile;

		$fn =~ s/.*\///;

		open(JSONFILE,"$jsonfile");
		my $jsoninfo = join("\n",<JSONFILE>);
		close(JSONFILE);

		my $contref = decode_json($jsoninfo);

		$json{$fn} = $contref;

		foreach my $tag (@{$contref->{tags}}) {
			$alltags{$tag} += 1;
		}
		foreach my $reference (@{$contref->{references}}) {
			next unless ($reference =~ /CVE/);
			$cvefixedin2container{$reference}->{$fn} = 1;
		}
	}
	#print STDERR Dumper($json{"sles15-image.20220111120729.newpatches.json"});
	#print STDERR Dumper(\%cvefixedin2container);

	foreach my $cve (sort keys %cvefixedin2container) {
		foreach my $container (sort keys %{$cvefixedin2container{$cve}}) {
			my $lasttag;
			my $foundtag;
			my $containertag = "unknown";
			foreach my $tag (@{$json{$container}->{tags}}) {
				$lasttag = $tag;
				if ($alltags{$tag} == 1) {	# avoid moving tags (used more than once)
					$foundtag = $tag;
				}
				if ($tag =~ /latest$/) {	# currently latest is fine for the latest container. FIXME for multiple containers
					$containertag = $tag;
				}
				if (length($tag) < length($containertag)) {
					$containertag = $tag;
				}
				if ($containertag =~ /unknown/)  {
					$containertag = $tag;
				}
			}
			if (!defined($foundtag)) {
				print STDERR "only found moving tags, just use last one $lasttag\n" if -t STDERR;
				$foundtag = $lasttag;
			}
			if (defined($lasttag)) {
				print STDERR "$cve fixed in $containertag / $foundtag\n" if -t STDERR;
			}
			$cvefixedinlabel{$cve}->{$containertag} = $foundtag;
		}
	}

	my @images = ();

	# read some projects in a wildcard fashion
	open(IMAGEPROJECTS,"<$cverepobase/data/imageprojects") ||die "$cverepobase/data/imageprojects: $!";
	while (<IMAGEPROJECTS>) {
		chomp;
		my ($project, $repo, $arch) = split(/ /);
		# isc pr SUSE:SLE-15-SP1:Update:PubClouds -r images -a x86_64 --xml|grep succeeded

		print STDERR "IMAGEPROJECTS: $osc pr $project -r $repo -a $arch --xml\n" if -t STDERR;

		open(IMAGES,"$osc pr $project -r $repo -a $arch --xml|");

		my %images = ();
		while (<IMAGES>) {
			print STDERR "debug: $_" if -t STDERR;
			chomp;
			next if /excluded/;
			#next unless /succeeded/;

			#<status package="SLES15-SP1-Manager-4-0-EC2-HVM-BYOS:Server" code="succeeded"/>
			if (/status package="([^"]*)" code/) {
				my $pkg = $1;
				my $name = $1;

				# $name =~ s/:/-/; # avoid :

				if ($name =~ /(.*)\.[0-9]*:([^:]*)$/) {
					$pkg = "$1\.\*:$2";
					$name = "$1:$2";
				} else {
					if ($name =~ /(.*)\.[0-9]*$/) {
						$pkg = "$1\.\*";
						$name = $1;
					} else {
						# keep $name
					}
				}
				# avoid duplication addition, also handle non-timestamped -> timestamped transition
				if (!defined($images{$name}) || ($pkg =~ /\\\.\\\*/)) {
					$images{$name} = "$project $pkg $repo $arch";
				}
			}

		}
		close(IMAGES)|| die "$osc pr $project -r $repo -a $arch --xml:$!";
		foreach my $image (sort keys %images) {
			push @images,"$images{$image},$image";

			print STDERR "IMAGEPROJECTS: added $images{$image},$image\n" if -t STDERR;
		}
	}
	close(IMAGEPROJECTS);
	open(CONTAINERPROJECTS,"$osc ls |grep ^SUSE:Containers:|")||die "$osc ls |grep ^SUSE:Containers|: $!\n";
	while (my $containerproject = <CONTAINERPROJECTS>) {
		chomp $containerproject;

		next if ($containerproject eq "SUSE:Containers:SUSE-CAASP:2");
		next if ($containerproject eq "SUSE:Containers:SUSE-CAASP:3");	# EOL
		next if ($containerproject eq "SUSE:Containers:SUSE-CAASP:5");	# never happened
		next if ($containerproject eq "SUSE:Containers:SLE-SERVER:12");
		next if ($containerproject eq "SUSE:Containers:CAPS:2");	# unsupported, harbor registry not shipped
		next if ($containerproject eq "SUSE:Containers:SUSE-CAASP:4.5");# unsupported since 2022-01-01

		my %containers = ();

		my @lines = cached_osc_call("ls $containerproject");
		foreach my $container (@lines) {
			chomp $container;

			# skip aggregates
			next if ($container =~ /^15/);
			next if ($container =~ /^others/);
			next if ($container =~ /^bci_/);
			next if ($container =~ /^hpc_/);
			next if ($container =~ /^kubevirt/);
			next if ($container =~ /^other/);

			next if ($container =~ /^patchinfo/); # no patchinfos here
			next if ($container =~ /^kured/); # no kured package
			next if ($container eq "sles12sp4-image"); # sles12sp4-image without timestamp
			if ($container =~ /^(.*)\.(\d*)$/) {	# usualy container-image.timestamp entries, translate to 
				$containers{"$1.*"} = $1;
			} else {
				print STDERR "non timestamped container $containerproject $container?\n" if -t STDERR;
				$containers{$container} = $container;
			}
		}
		foreach my $container (sort keys %containers) {
			print STDERR "added $containerproject $container containers x86_64,$containers{$container}\n" if -t STDERR;
			push @images,"$containerproject $container containers x86_64,$containers{$container}";
		}
	}
	close(CONTAINERPROJECTS)||die "$osc ls |grep ^SUSE:Containers:$!";

	# rest projects we read by hand
	# MARCUS: avoid it for now, use autodetection only
	#open(IMAGES,"$cverepobase/data/images")||die  "$cverepobase/data/images: $!";
	#while (my $image = <IMAGES>) {
	#	chomp $image;
	#	push @images,$image;
	#}
	#close(IMAGES);

	# HERE print STDERR "images:\n" . join("\n", @images);

	print STDERR "all images:\n" . join("\n", @images) . "\n" if -t STDERR;

	#@images = grep (/(CHOST|trento|caasp-|sles.*image\.|bci|cdi|csi|virt|ses|ceph|rook|prometheus|golang|nodejs|init|micro|minimal|dotnet|aspnet|ruby|openjdk)/,@images);

	foreach my $image (@images) {
		my ($buildinfo,$output) = split(/,/,$image);

		my ($project,$package,$repo,$arch) = split(/ /,$buildinfo);

		my $filename;
		my $containerinfo;

		if (	($package =~ /(.*)\.\*$/) ||
			($package =~ /(.*)\.\*:(.*)$/)
		) {
			# either IMAGE.TIMESTAMP or IMAGE.TIMESTAMP:FLAVOR
			# This if part currently reads _released_ containers. This is what customers have available.
			# They usually have the pattern of NAME.TIMESTAMP: "containername.20200520123456"
			# We do not need a fallback method.

			my $match = $1;
			my $flavor = $2;
			my $imagename = "unknown";
			if (defined($flavor)) {
				$imagename = "$match:$flavor";
				$match = "$match\..*:$flavor";
			} else {
				$imagename = $match;
				$match = "$match\..*";
			}

			my @images = ();
			my $lastimage;

			# need this to get multibuild flavors
			my @lines = cached_osc_call("pr $project -r $repo -a $arch --xml");
			foreach my $ximage (@lines) {
				next unless ($ximage =~ /status package="([^"]*)"/);
				my $pkg = $1;

				next unless ($pkg =~ /$match$/);

				push @images,$pkg;
				$lastimage = $pkg;
			}

			# we currently look just at the last image, but perhaps we will use them later on  ...
			# the last image might not have valid build data, so go backwards. this allows the $filename -> next to work

			my $containertag = "unknown";
			while (my $ximage = pop @images) {
				next unless ($ximage =~ /$match$/);
				my $index = $1;

				undef $containerinfo;
				@lines = filecached_osc_call("api /build/$project/$repo/$arch/$ximage");
				foreach (@lines) {
					if (/<binary filename="(.*report)" size=/) {
						$filename = $1;
					}
					if (/<binary filename="(.*containerinfo)" size=/) {
						$containerinfo = $1;
					}
				}
				if (!defined($filename)) {
					# happens for helm charts or non kiwi/docker images
					print STDERR "no filename in $osc api /build/$project/$repo/$arch/$ximage?\n" if -t STDERR;
					next;
				}
				print STDERR "filename of $buildinfo is $filename\n" if -t STDERR;
				print STDERR "filename of containerinfo is $containerinfo\n" if -t STDERR;

				my $version = "unknown";
				if ($filename =~ /\.$arch-(.*).report$/) {
					$version = $1;
					print STDERR "version is $version\n" if -t STDERR;
				}

				# get the file
				my @text = filecached_osc_call("api /build/$project/$repo/$arch/$ximage/$filename");

				print STDERR "$buildinfo $ximage\n" if -t STDERR;

				my @cinfo;
				if (defined($containerinfo)) {
					# get containerinfo ... it is a perl map already, so use it....
					@cinfo = filecached_osc_call("api /build/$project/$repo/$arch/$ximage/$containerinfo");
				}
				my $containername = "unknown";
				my $containerinfo = join("",@cinfo);
				# print STDERR "%project $ximage $containerinfo\n";
				if (defined($containerinfo) && ($containerinfo ne "")) {
					my $contref = decode_json($containerinfo);
					my %containerinfo = %{$contref};
					my @tags = @{$containerinfo{'tags'}};

					my $containertag = "unknown unknown unknown";
					# find the best tag to specify this container
					foreach my $tag (@tags) {
						if ($tag =~ /latest$/) {	# currently latest is fine for the latest container. FIXME for multiple containers
							$containertag = $tag;
							last;
						}
						# next if ($tag =~ /^\d*\.\d*$/); # just two digits, assume 15.0 or so
						#
						# look for the otherwise shortest tag.
						if (length($tag) < length($containertag)) {
							$containertag = $tag;
						}
						if ($containertag =~ /unknown/)  {
							$containertag = $tag;
						}
					}
					$containername = "Container $containertag";
				} else {
					$imagename =~ s/:/-/;
					$imagename =~ s/\.\*//;
					$containername = "Image $imagename";
				}
				parse_builddep($buildinfo,join("",@cinfo),join("\n",@text), $containername,1,$version);
				last;
			}
		} else {
			# This is the public cloud image part.
			# They are on a different release schedule, and sometimes might not even build.
			# We try a fallback read from our buildinfo cache

			print STDERR "NON-CACHED: $osc api /build/$project/$repo/$arch/$package\n" if -t STDERR;

			undef $filename;
			undef $containerinfo;
			open(BUILDINFO,"$osc api /build/$project/$repo/$arch/$package|");
			while (<BUILDINFO>) {
				if (/<binary filename="(.*report)" size=/) {
					$filename = $1;
				}
				if (/<binary filename="(.*containerinfo)" size=/) {
					$containerinfo = $1;
				}
			}
			close(BUILDINFO)||die "$osc api /build/$project/$repo/$arch/$package:$!";

			my @text = ();
			if (defined($filename)) {
				print STDERR "filename of $buildinfo is $filename\n" if -t STDERR;
				# get the file
				@text = filecached_osc_call("api /build/$project/$repo/$arch/$package/$filename");
				unless (@text) {
					print STDERR "no files in $project/$repo/$arch/$package/$filename, skipping.\n";
					next;
				}
			} else {
				my @files=<$cverepobase/smash/buildinfo/$package/*>;
				unless (@files) {
					print STDERR "no files in $package\n";
					next;
				}
				$filename = pop @files;
				chomp $filename;

				open(BUILDINFO,"<$filename")|| die "$filename:$!";
				@text = <BUILDINFO>;
				close(BUILDINFO);

				$filename =~ s#^.*/##;	# basename

				# print STDERR "read previously cached $filename of $project / $package\n";
			}

			#SLES15-SP1-CHOST-BYOS.x86_64-1.0.5-GCE-Build2.3.report

			my $version = "unknown";
			if ($filename =~ /\.$arch-(.*).report$/) {
				$version = $1;
				print STDERR "version is $version\n" if -t STDERR;
			}

			my @cinfo;
			if (defined($containerinfo)) {
				# get containerinfo ... it is a perl map already, so use it....
				@cinfo = filecached_osc_call("api /build/$project/$repo/$arch/$package/$containerinfo");
			}

			print STDERR "$buildinfo $image\n" if -t STDERR;

			$output =~ s/:/-/;
			$output =~ s/\.\*//;
			parse_builddep($buildinfo,join("",@cinfo),join("\n",@text),"Image $output",0,$version);
		}
	}

	print STDERR "filecache hit: $filecachehit\n" if -t STDERR;
	print STDERR "filecache miss: $filecachemiss\n" if -t STDERR;
	print STDERR "osccache hit: $osccachehit\n" if -t STDERR;
	print STDERR "osccache miss: $osccachemiss\n" if -t STDERR;
}

# primary regular updateinfo channel importer. takes around 10-15 minutes.
sub 
import_product_updates() {
	# we dont read it from the buildservice, but from a cache directory.
	# this saves 10+ minutes in this script.
	for my $channelproject ('SUSE:Channels','SUSE:Channels:EOL') {
		my @channels =<$cverepobase/smash/channels/$channelproject/*>;
		foreach my $channel (@channels) {
			my $channelfn = $channel;
			chomp $channel;

			$channel =~ s/.*smash\/channels\///;

			# ignore SLE11 for now.
			#if (	($channel =~ /SL.*11/)			||
			##	($channel =~ /SUSE.MANAGER.*11/)	||
			#	($channel =~ /STUDIOONSITE/)		||
			#	($channel =~ /WEBYAST/)
			#) {
			#	print STDERR "skipping $channel for now\n";
			#	next;
			#}

			# Ignore the ALP / SLFO catch virtual channels for displaying
			if ($channel =~ /SUSE-ALP-Source-Standard_1.0/) {
				print "$channel skipped\n";
				next;
			}
			if ($channel =~ /ALP/i) {
				print "ALP $channel not skipped\n";
			}
			next if ($channel =~ /SUSE-SLFO-Main/);

			next if ($channel =~ /EXTRA/);
			next if ($channel =~ /SLE-JAVA/);
			next if ($channel =~ /DEBUGINFO/);
			next if ($channel =~ /^RES/);
			next if ($channel =~ /BDK/);

			# not yet in
			next if ($channel =~ /SLE-Product-SLED_15-SP4-LTSS/);

			if (! -f "$channelfn/_channel") {
				print STDERR "no channel in $channelfn?\n" if (-t STDERR);
				next;
			}

			my $xmlparser = new XML::Bare( file => "$channelfn/_channel" ) || die "$channelfn/_channel XML: $!";
			my $xmlroot = $xmlparser->parse()||die;
			my $targets = XML::Bare::forcearray($xmlroot->{'channel'}->{'target'});
			my $binaries = XML::Bare::forcearray($xmlroot->{'channel'}->{'binaries'});

			# Fetch first all the codestream repos.
			foreach my $binarylist (@{$binaries}) {
			
				#  <binaries project="SUSE:SLE-15-SP4:Update" repository="standard" arch="x86_64">
				#     <binary name="Mesa" package="Mesa" supportstatus="l3"/>
				#...
				allupdates($binarylist->{'project'}->{'value'});
			}

			# Fetch all update infos and ga infos of the listed targets
			foreach my $target (@{$targets}) {
				my $name = $target->{'project'}->{'value'};
				my $fn = $name;
				my $gafn = $name;
				my $testfn = $name;

				my($suse,$updates,$product,$version,$arch) = split(/:/,$name);
				if (!defined($name2product{$product})) {
					print STDERR "$product is not defined in translation list.\n";
				}
				# 11-SP3 -> 11 SP3
				$version =~ s/-SP/ SP/;

				$name = $name2product{$product} . " " . $version;

				# the name of SLES 11 SP4 LTSS EXTREME CORE is in bs with dashes, but in Aimaas / SMASH without dashes.
				if ($name =~ /11.*SP4.*EXTREME.CORE/) {
					$name =~ s/-/ /g;
				}
				if ($name =~ /LTSS-Extended-Security/) {
					$name =~ s/-/ /g;
				}

				my $marketingname = $name;

				print STDERR "parsing for $name\n" if (-t STDERR);

				if (blacklistedproduct($marketingname)) {
					print STDERR "\t$marketingname is blacklisted.\n" if (-t STDERR);
					next;
				}
				# Read and store the channel source - binary mappings.
				foreach my $binarylist (@{$binaries}) {
					#  <binaries project="SUSE:SLE-15-SP4:Update" repository="standard" arch="x86_64">
					#     <binary name="Mesa" package="Mesa" supportstatus="l3"/>
					#...
					my $codestream = $binarylist->{'project'}->{'value'};
					print STDERR "	$product -> $codestream\n" if -t STDERR;

					my @binaryentries = @{XML::Bare::forcearray($binarylist->{'binary'})};

					foreach my $binary (@binaryentries) {
						my $binaryrpm = $binary->{'name'}->{'value'};
						my $srcrpm = $binary->{'package'}->{'value'};

						#%UpdateInfoReader::product2packages = ();	# { Product -> { srcpkg -> binarypkg } }	currently without codestreams
						next if (!defined($binaryrpm));

						next if ($binaryrpm =~ /-debuginfo/);
						next if ($binaryrpm =~ /-debugsource/);
						$srcrpm = "glibc" if ($srcrpm eq "glibc.i686");

						# full channel mapping
						# $UpdateInfoReader::product2codestream2src2binary{$marketingname}->{$codestream}->{$srcrpm}->{$binaryrpm} = 1;
						# another layout
						$UpdateInfoReader::codestream2product2src2binary{$codestream}->{$marketingname}->{$srcrpm}->{$binaryrpm} = 1;

						$UpdateInfoReader::product2packages{$marketingname}->{$srcrpm}->{$binaryrpm} = 1;
					}
				}

				# Load GA Info

				parse_gainfo($name);
				if (	($product eq "SLE-SERVER") &&
					( ($version !~ /TERADATA/) && ($version !~ /ESPOS/) && ($version !~ /ERICS/) && ($version !~ /BCL/) && ($version !~ /LTSS/) && ($version !~ /SECURITY/) && ($version !~ /CLIENT-TOOLS/))
				) {
					parse_gainfo($name2product{$product} . " for SAP Applications " . $version);
				}

				# Load update info of released updates

				$fn =~ s/:/\//g;

				my $xml = get_updateinfo_from_host("ibs/$fn/update/","updateinfo", "https://dist.suse.de");
				if (!defined($xml)) {
					next if ($fn =~ /debian/i);
					next if ($fn =~ /ubuntu/i);
					warn "updateinfo: $fn: not found\n" if -t STDERR;
					next;
				}
				if ($xml eq "") {
					warn "xml empty for $fn/*updateinfo*.xml.gz" if -t STDERR;
					next;
				}
				parse_updateinfo($name,$xml,0);
				if (	($product eq "SLE-SERVER") &&
					( ($version !~ /TERADATA/) && ($version !~ /ESPOS/) && ($version !~ /ERICS/) && ($version !~ /BCL/) && ($version !~ /LTSS/) && ($version !~ /SECURITY/) && ($version !~ /CLIENT-TOOLS/))
				) {
					parse_updateinfo($name2product{$product} . " for SAP Applications " . $version, $xml, 0);
				}

				# Load update info of under test updates

				# SUSE:/Maintenance:/Test:/
				$testfn =~ s/SUSE:Updates://;
				$testfn =~ s/:/:\//g;
				$xml = get_updateinfo_from_host("ibs/SUSE:/Maintenance:/Test:/$testfn/update/","updateinfo", "https://dist.suse.de");
				if (defined($xml)) {	# empty if no updates are in test, or EOL
					parse_updateinfo($name,$xml,1);
					if (	($product eq "SLE-SERVER") &&
						( ($version !~ /TERADATA/) && ($version !~ /ESPOS/) && ($version !~ /ERICS/) && ($version !~ /BCL/) && ($version !~ /LTSS/) && ($version !~ /SECURITY/) && ($version !~ /CLIENT-TOOLS/))
					) {
						parse_updateinfo($name2product{$product} . " for SAP Applications " . $version, $xml, 1);
					}
				}

				# GA DATA

				$gafn =~ s/:/\//g;
				$gafn =~ s/Updates/Products/;
				$gafn =~ s/-LTSS//;
				$gafn =~ s/-ESPOS//;
				$gafn =~ s/-BCL//;
				$xml = get_updateinfo_from_host("ibs/$gafn/product/","primary", "https://dist.suse.de");
				if (!defined($xml)) {
					print STDERR "no primary info for $name\n" if (-t STDERR);
					print STDERR "no xml found in $gafn\n" if -t STDERR;
					next;
				}
				parse_primaryinfo($name,$xml,0);
				if (	($product eq "SLE-SERVER") &&
					( ($version !~ /TERADATA/) && ($version !~ /ESPOS/) && ($version !~ /ERICS/) && ($version !~ /BCL/) && ($version !~ /LTSS/) && ($version !~ /SECURITY/) && ($version !~ /CLIENT-TOOLS/))
				) {
					parse_primaryinfo($name2product{$product} . " for SAP Applications " . $version, $xml, 0);
				}
			}
		}
	}

	## this provides the GA info for SP1...SPlast
	# to mark the affectedness correctly
	# to know the fixed versions.
	# uses buildinfos from the product build, and the update info from the codestream repos
	foreach my $gaprj (
		"SUSE:SLE-15-SP1:GA","SUSE:SLE-15-SP2:GA","SUSE:SLE-15-SP3:GA","SUSE:SLE-15-SP4:GA","SUSE:SLE-15-SP5:GA","SUSE:SLE-15-SP6:GA",
		"SUSE:SLE-12-SP5:GA",
		"SUSE:SLE-15-SP4:Update:Products:Micro54",
		"SUSE:SLE-15-SP5:Update:Products:Micro55",
	) {
		my $product0 = "000product";
		$product0 = "_product" if ($gaprj =~ /SLE-12/);

		my $sp = $gaprj;


		if ($gaprj =~ /Micro/) {
			$sp =~ s/.*Micro//;
			$sp =~ s/(\d)(\d)/$1.$2/;
		} else {
			$sp =~ s/^SUSE:SLE-//;
			$sp =~ s/:GA//;
			$sp =~ s/-/ /;
		}

		my $xmlparser;
		my $prresult;
		if ($gaprj =~ /SLE-12/) {
			$prresult = join("",filecached_osc_call("pr --xml $gaprj"));
			$xmlparser = new XML::Bare( text => $prresult ) || die "isc pr --xml $gaprj XML: $prresult";
		} else {
			# all new products
			$prresult = join("",filecached_osc_call("r --xml $gaprj $product0"));
			$xmlparser = new XML::Bare( text => $prresult ) || die "isc r --xml $gaprj $product0 XML: $prresult";
		}
		my $xmlroot = $xmlparser->parse()||die;
		my $repos = XML::Bare::forcearray($xmlroot->{'resultlist'}->{'result'});

		# print Dumper($repos);

		# <resultlist state="6b43e48421df693e874d4c29c267adb2">
		#   <result project="SUSE:SLE-15-SP5:GA" repository="standard" arch="i586" code="published" state="published">
		#     <status package="000product" code="locked"/>
		#     <status package="000product:SLED-ftp-POOL-x86_64" code="locked"/>

		foreach my $result (@{$repos}) {
			# print STDERR Dumper($result);
			my $status = XML::Bare::forcearray($result->{'status'});

			next unless ($result->{'repository'}->{'value'} eq "images");
			next unless ($result->{'arch'}->{'value'} eq "local");

			# print "images/local\n";
			foreach my $status (@{$result->{'status'}}) {
				my $origproduct = $status->{'package'}->{'value'};
				my $product = $origproduct;

				next unless ($product =~ /$product0.*ftp-POOL/);

				$product =~ s/$product0://;
				$product =~ s/-ftp-POOL.*//;
				next unless ($product =~ /^sle/); #	base products with uppercase SLE are empty

				$product = remap_000product_to_name($product) . " $sp";
				# print "$origproduct > $product\n";

				my @binaries = filecached_osc_call("ls -b $gaprj $origproduct images local");
				foreach my $binary (@binaries) {
					chomp $binary;
					next unless ($binary =~ /Media1.report/);

					my $report = join("",filecached_osc_call("api /build/$gaprj/images/local/$origproduct/$binary"));

					# <report version="15.5" release="102.1" buildtime="1684475478" disturl="obs://build.suse.de/SUSE:SLE-15-SP5:GA/images/50ee9cca816602c77c784214686afbfd-000product:SLES-cd-Full-x86_64">
					#  <binary name="GeoIP-data" version="1.6.12" release="6.3.1" binaryarch="noarch" buildtime="1585297279" disturl="obs://build.suse.de/SUSE:Maintenance:14510/SUSE_SLE-15-SP1_Update/81a76e4b9e3c8671097e6898f2e97b0a-GeoIP.SUSE_SLE-15-SP1_Update" license="CC-BY-SA-3.0" supportstatus="l3" project="SUSE:SLE-15-SP1:Update" repository="snapshot-SP5" package="GeoIP.14510" arch="x86_64">obs://SUSE:SLE-15-SP1:Update/snapshot-SP5/noarch/GeoIP-data-1.6.12-6.3.1.noarch.rpm</binary>

					$xmlparser = new XML::Bare( text => $report ) || die "api /build/$gaprj/images/local/$origproduct/$binary XML: $!";
					$xmlroot = $xmlparser->parse()||die;
					my $binaries = XML::Bare::forcearray($xmlroot->{'report'}->{'binary'});

					foreach my $binary (@{$binaries}) {
						my $srcpackage = $binary->{'package'}->{'value'};
						my $project = $binary->{'project'}->{'value'};
						my $version = $binary->{'version'}->{'value'};
						my $release = $binary->{'release'}->{'value'};
						my $name = $binary->{'name'}->{'value'};

						next if ($project =~ /:GA/);

						$srcpackage =~ s/\.\d\d\d+//;
						# print "$project - src: $srcpackage; $name - $version - $release\n";

						allupdates($project);
						if (defined($project2package2fixes{$project})) {
							print "we have: $name $version-$release\n" if -t STDERR;
							my $package2fixes = $project2package2fixes{$project};
							if (defined($package2fixes->{$name})) {
								my %version2cve = %{$package2fixes->{$name}};

								foreach my $xver (sort keys %version2cve) {
									print STDERR "checking: $xver\n" if -t STDERR;
									if (versioncompare("$version-$release",$xver) >= 0) {
										print STDERR "LARGER OR SAME: $version-$release >= $xver\n" if -t STDERR;
										#print OUTPUTCVES "$name - $version-$release (fixed in $xver): " . join(",",@{$version2cve{$xver}}) . "\n";
										# We synthesize patches for the containers and images...


										my $origpatchname = $project2package2patches{$project}->{$name}->{$xver};

										# use the source packagename to avoid too much patches.
										my $patchname = "$product GA $srcpackage-$version-$release";

										$UpdateInfoReader::patches{$product}->{$patchname} = 1;

										$UpdateInfoReader::patchtype{$patchname} = $UpdateInfoReader::patchtype{$origpatchname};
										$UpdateInfoReader::patchseverity{$patchname} = $UpdateInfoReader::patchseverity{$origpatchname};
										$UpdateInfoReader::patchdescription{$patchname} = $UpdateInfoReader::patchdescription{$origpatchname};
										$UpdateInfoReader::patchreferences{$patchname} = $UpdateInfoReader::patchreferences{$origpatchname};
										$UpdateInfoReader::patchissued{$patchname} = $UpdateInfoReader::patchissued{$origpatchname};
										$UpdateInfoReader::patchvirtual{$patchname} = 1;
										$UpdateInfoReader::patchinqa{$patchname} = 0;

										# this needs special attention. our patch does not include the *full* set of packages, only the
										# ones in the image.
										$UpdateInfoReader::patchpackages{$patchname}->{$name} = $xver;
										$UpdateInfoReader::patchsrcpackages{$patchname}->{$srcpackage} = $xver;

										# FIXME, copies all architectures here.
										$UpdateInfoReader::patchpackagearchs{$patchname}->{$name} = $UpdateInfoReader::patchpackagearchs{$origpatchname}->{$name};

										# This puts us into the global CVE mapping
										my @cves = grep(/CVE/,@{$version2cve{$xver}});

										#print STDERR "cves: " . join(",",@cves) . "\n" if -t STDERR;
										foreach my $cve (@cves) {
											# add to updateinfo db

											$UpdateInfoReader::products{$cve}->{$product}->{'patchnames'}->{$patchname} = 1;
											if ($filterlist{$cve}) {
												unless ($name =~ /$filterlist{$cve}/) {
													print STDERR "$cve -> skipping $name due to filter list\n" if -t STDERR;
													next;
												}
											}
											$UpdateInfoReader::products{$cve}->{$product}->{'packages'}->{$name} = $xver;
											$UpdateInfoReader::products{$cve}->{$product}->{'srcpackages'}->{$srcpackage} = $xver;
											$UpdateInfoReader::products{$cve}->{$product}->{'packagearchs'}->{$name} = $UpdateInfoReader::patchpackagearchs{$origpatchname};
										}
									} else {
										#print "SMALLER: $version-$release > $xver\n";
									}
								}
							}
						}
					}
				}
			}
		}
	}

	# Pre IBS maintenance stuff not present in SUSE:Channels

	foreach my $xname (
		"SLE-SERVER:11:i586",
		"SLE-SERVER:11:x86_64",
		"SLE-SERVER:11:ia64",
		"SLE-SERVER:11:s390x",
		"SLE-SERVER:11:ppc64",

		"SLE-SERVER:11-SP1:i586",
		"SLE-SERVER:11-SP1:x86_64",
		"SLE-SERVER:11-SP1:ia64",
		"SLE-SERVER:11-SP1:s390x",
		"SLE-SERVER:11-SP1:ppc64",

		"SLE-SERVER:11-SP1-FOR-SP2:i586",
		"SLE-SERVER:11-SP1-FOR-SP2:x86_64",
		"SLE-SERVER:11-SP1-FOR-SP2:ia64",
		"SLE-SERVER:11-SP1-FOR-SP2:s390x",
		"SLE-SERVER:11-SP1-FOR-SP2:ppc64",
	) {
		my $fn = "SUSE:Updates:$xname";

		my($product,$version,$arch) = split(/:/,$xname);

		if (!defined($name2product{$product})) {
			print STDERR "$product is not defined in translation list.\n";
		}
		# 11-SP3 -> 11 SP3
		$version =~ s/-SP1-FOR-SP2/-SP2/;
		$version =~ s/-SP/ SP/;

		my $name = $name2product{$product} . " " . $version;

		print STDERR "parsing for $name\n" if (-t STDERR);

		parse_gainfo($name);

		my $xml = get_updateinfo_from_host("ibs/$fn/update/","updateinfo", "https://dist.suse.de");
		if (!defined($xml)) {
			warn "no updateinfo in dist/ibs/$fn/update/" if -t STDERR;
			next;
		}
		parse_updateinfo($name,$xml,0);
	}

	foreach my $xname (
		"SL-Micro:6.0:x86_64", "SL-Micro:6.0:s390x", "SL-Micro:6.0:aarch64",
	) {
		my $fn = "SUSE:Products:$xname";
		$fn =~ s/:/\//g;

		my($product,$version,$arch) = split(/:/,$xname);


		if (!defined($name2product{$product})) {
			print STDERR "$product is not defined in translation list.\n";
		}
		my $name = $name2product{$product} . " " . $version;

		print STDERR "parsing for $name\n" if (-t STDERR);

		parse_gainfo($name);

		my $xml = get_updateinfo_from_host("ibs/$fn/product/","updateinfo", "https://dist.suse.de");
		if (!defined($xml)) {
			warn "no updateinfo in dist/ibs/$fn/product/" if -t STDERR;
			next;
		}
		parse_updateinfo($name,$xml,0);
	}
	# openSUSE. include Evergreen
	foreach my $os (
		"leap/15.0/oss", "leap/15.0/non-oss",
		"leap/15.1/oss", "leap/15.1/non-oss",
		"leap/15.2/oss", "leap/15.2/non-oss",
		"leap/15.3/oss", "leap/15.3/backports", "leap/15.3/sle", "leap/15.3/non-oss",
		"leap/15.4/oss", "leap/15.4/backports", "leap/15.4/sle", "leap/15.4/non-oss",
		"leap/15.5/oss", "leap/15.5/backports", "leap/15.5/sle", "leap/15.5/non-oss",
		"leap/15.6/oss", "leap/15.6/backports", "leap/15.6/sle", "leap/15.6/non-oss"
	) {
		print STDERR "getting https://download.opensuse.org/$os/ updateinfo\n" if (-t STDERR);
		my $xml = get_updateinfo_from_host("/update/$os/","updateinfo","https://download.opensuse.org");
		# print STDERR "$xml\n" if (-t STDERR);

		my $name = $os;
		$name =~ s/-non-oss/ NonFree/;
		$name =~ s/leap\//Leap /;
		$name =~ s/\/non-oss/ NonFree/;
		$name =~ s/\/sle//;
		$name =~ s/\/backports//;
		$name =~ s/\/oss//;
		parse_updateinfo("openSUSE $name",$xml,0);
		parse_gainfo("openSUSE $name");
	}

	# RES
	foreach my $os (
		"RH7.0","RH8.0","RH8.0-CB","RH9.0","RH9.0-CB",
		"RH7.0-HA","RH8.0-AS","RH8.0-HA","RH9.0-AS","RH9.0-HA",
		"RH7.0-LT"
	) {
		my $xml = get_updateinfo_from_host("/RH.epam/$os/x86_64/","updateinfo","https://dist.suse.de");
		my $name = $os;
		my $version = $name;

		$version =~ s/.*(\d\.\d.*)$/$1/;
		$version =~ s/.0//;
		if ($version =~ /-LT/) {
			$version =~ s/-LT/ LTSS/;
		} else {
			$version =~ s/-.*//;;
		}
		parse_updateinfo("SUSE Liberty Linux $version",$xml,0);
	}

	# SUSE PackageHub aka Backports
	# starting with 15 the patches are in :Update
	foreach my $os ("SLE-12","SLE-12-SP1","SLE-12-SP2","SLE-12-SP3", "SLE-15:/Update", "SLE-15-SP1:/Update", "SLE-15-SP2:/Update", "SLE-15-SP3:/Update", "SLE-15-SP4:/Update","SLE-15-SP5:/Update","SLE-15-SP6:/Update") {
		my $newbaseurl = "/repositories/openSUSE:/Backports:/$os/standard/";

		# these have more mirrors...
		if ($os eq "SLE-15-SP3:/Update") { $newbaseurl = "/update/leap/15.3/backports/"; }
		if ($os eq "SLE-15-SP4:/Update") { $newbaseurl = "/update/leap/15.4/backports/"; }
		if ($os eq "SLE-15-SP5:/Update") { $newbaseurl = "/update/leap/15.5/backports/"; }
		if ($os eq "SLE-15-SP6:/Update") { $newbaseurl = "/update/leap/15.6/backports/"; }

		my $xml = get_updateinfo_from_host($newbaseurl,"updateinfo","https://download.opensuse.org");
		my $name = $os;

		$name =~ s/SLE-(\d*)-SP/$1 SP/;
		$name =~ s/SLE-(\d*)/$1/;
		$name =~ s/:\/Update//;	# strip it away
		parse_updateinfo("SUSE Package Hub $name",$xml,0);

		# Leap 15.3+ uses Backports directly.
		if ($os eq "SLE-15-SP3:/Update") {
			parse_updateinfo("openSUSE Leap 15.3",$xml,0);
		}
		if ($os eq "SLE-15-SP4:/Update") {
			parse_updateinfo("openSUSE Leap 15.4",$xml,0);
		}

		if ($os eq "SLE-15-SP5:/Update") {
			parse_updateinfo("openSUSE Leap 15.5",$xml,0);
		}
		if ($os eq "SLE-15-SP6:/Update") {
			parse_updateinfo("openSUSE Leap 15.6",$xml,0);
		}
		#parse_gainfo("SUSE Package Hub $name");
	}
	parse_gainfo("openSUSE Tumbleweed");

	print STDERR "filecache hit: $filecachehit\n" if -t STDERR;
	print STDERR "osccache hit: $osccachehit\n" if -t STDERR;
	#print Dumper(\%UpdateInfoReader::products);
}

#print Dumper($UpdateInfoReader::products{"CVE-2022-23990"});
#import_product_updates();
#print Dumper($UpdateInfoReader::codestream2product2src2binary{"SUSE:ALP:Source:Standard:1.0"});

#print STDERR Dumper \%UpdateInfoReader::patchreferences;
#import_images();

1;


