@@ -20,272 +20,379 @@
# Boston, MA 02110-1301,
# USA.
#
-# $Id: createpatch,v 1.5 2007/12/11 13:42:01 lrupp Exp lrupp $
+# $Id: createpatch 186 2009-08-07 18:00:30Z lrupp $
#
+BEGIN {
+ $abuild_base_dir = "/usr/share/inst-source-utils";
+ unshift @INC, "$abuild_base_dir/modules";
+}
+
+$| = 1;
+
use strict;
+use Cwd;
+use Data::Dumper;
use File::stat;
-#use Data::Dumper;
+use File::Temp qw/ tempdir /;
+use RPMQ;
+use Time::localtime;
+use Getopt::Long;
+
+# cleanup the environment
+$ENV{'PATH'}='/bin:/usr/bin:/sbin:/usr/sbin:';
+$ENV{'BASH_ENV'}='';
+$ENV{'ENV'}='';
+$ENV{"LC_ALL"} = "C";
+our $DEBUG=0;
+our %config;
+$config{'basedir'} = cwd();
+$config{'category'} = "recommended";
+$config{'do_signing'} = 0;
+$config{'do_susedata'} = 0;
+$config{'license_file'} = "";
+$config{'patch_name'} = "usefirst";
+$config{'patch_version'} = 0;
+$config{'patch_summary'} = "";
+$config{'patch_description'} = "";
+$config{'sign_id'} = "";
+$config{'update_repo'} = 0;
+$config{'loglevel'} = 7;
+my $patch_id = `hostname -d`;
+$config{'patch_id'} = chomp($patch_id);
-my $basedir;
-my @packagelist;
-my $update_repo = 0;
-my $do_signing = 0;
+our %patchinfo_data=();
+our $print_help="";
+our $configfile="";
+our @packagelist;
+our @patchsupplements;
+our @pkgrefresh;
+our $LOCKFILE="/var/tmp/createpatch.lock";
+
+###############################################################################
+# Functions
+###############################################################################
-my $patch_id = `hostname -d`;
-chomp $patch_id;
+sub usage {
+ my $exitcode=shift || 1;
+ print <<EOF
-my $patch_name = "usefirst";
-my $patch_version = 0;
-my $patch_summary = "";
-my $patch_description = "";
-my $sign_id = "";
-my $category = "recommended";
-my $license_file = "";
-my $validate = 1;
-my @patchsupplements;
-my @pkgrefresh;
-
-while (my $param = shift(@ARGV)) {
- if ( $param eq "-h" ) {
- &usage;
- }
- if ( $param eq "-C" ) {
-
- $category = shift(@ARGV);
- if (!($category =~ /^recommended$|^security$|^optional$/)) {
- print("Invalid category supplied with -C\n");
- &usage;
- }
- }
- if ( $param eq "-i" ) {
- $patch_id = shift(@ARGV);
- next;
- }
- if ( $param eq "-n" ) {
- $patch_name = shift(@ARGV);
- next;
- }
- if ( $param eq "-v" ) {
- $patch_version = shift(@ARGV);
- next;
- }
- if ( $param eq "-s" ) {
- $patch_summary = shift(@ARGV);
- next;
- }
- if ( $param eq "-d" ) {
- $patch_description = shift(@ARGV);
- next;
- }
- if ( $param eq "-u" ) {
- $update_repo = 1;
- next;
- }
- if ( $param eq "-S" ) {
- $do_signing = 1;
- next;
- }
- if ( $param eq "-I" ) {
- $sign_id = shift(@ARGV);
- next;
- }
- if ( $param eq "-L" ) {
- $license_file = shift(@ARGV);
- next;
- }
- if ( $param eq "-p" ) {
- @packagelist = split (',',shift (@ARGV));
- next;
- }
- if ( $param eq "--validate" ){
- $validate = 0;
- next;
- }
- if ( $param eq "--novalidate" ){
- $validate = 0;
- next;
- }
- if ( $param eq "--patchsupplements" ){
- @patchsupplements = split (',',shift (@ARGV));
- next;
- }
- if ( $param eq "--pkgfreshens" ){
- @pkgrefresh = split (',',shift (@ARGV));
- next;
- }
- $basedir = $param;
-}
-
-$patch_id =~ s/\./_/g;
-
-die "basedir not specified or not existant\n"
- unless ( $basedir
- && -d $basedir
- && -d "$basedir/repodata" );
+$0 Usage Information:
-my $first_package = "";
-$first_package = $packagelist[0] if ( @packagelist && $packagelist[0] );
+ $0 [OPTION] ...
-if ( $patch_name eq "usefirst" ) {
- $patch_name = $first_package if ( $first_package );
+ -b <base_dir> : is the base directory to the repository.
+
+ -c <CONFIGFILE> : file containing the option/values mentioned below
+ Notes: * use the fullnames as option
+ * values in config file overwrite commandline
+ values!
+ -e|--do_susedata : create special susedata.xml files
+ -i <PATCH_ID> : Patch id, needs to be unique in world, will be prefixed
+ by "hostname -d" as default followed by the name of the
+ first package. Dots in "hostname -d" will be converted
+ to "_"s.
+ -n <PATCH_NAME> : required parameter, terse patch name, like aaa_base
+ -v <PATCH_VERSION> : default to "0"/first non-existant if not given
+ -s <PATCH_SUMMARY> : default to the package summary of the first RPM
+ specified on the command line
+ -d <PATCH_DESCRTIPON> : Long description, defaults to the package description
+ of the first RPM specified on commandline
+ -C <CATEGORY> : Category for the patch. Defaults to recommended.
+ Possible values: security, recommended, optional
+
+ -u : run createrepo to update repository and take care of keeping
+ the patch*xml files - use when augmenting existing repository
+ with new patches.
+ -s : Create special susedata.xml files
+ -S : detached sign the repomd.xml file
+ -I <KEY_ID> : key-id to use for signing the repomd.xml file, if not given
+ gpg will use the default signing key
+ -L <CONFIRMATION_FILE> : add a confirmation request (EULA or Reboot request) to the patch,
+ read from the file specified
+ --validate : use xmllint to validate the resulting xml file
+
+ -p <rpm_basename>[,rpm_basename...] : List of RPMs for this patch.
+ You need at least one.
+ --patchsupplements <PATCH_NAME>[,PATCH_NAME] : List of patches which are supplemented
+ by this patch
+ --pkgfreshens rpm_basename : optional parameter which will override
+ the default freshens value
+EOF
+;
+
+ exit $exitcode;
}
-if ( $update_repo ) {
- if ( opendir ( DIR, "$basedir/repodata" ) ) {
- mkdir "$basedir/repo_save";
- for (readdir(DIR)) {
- unlink "$basedir/repodata/$_" if ( /\.key$/ || /\.asc$/ );
- next unless ( /^patch/ || /^product/ );
- if ( /^patches.xml/ ) {
- unlink "$basedir/repodata/patches.xml";
- next;
- }
- link "$basedir/repodata/$_","$basedir/repo_save/$_";
- unlink "$basedir/repodata/$_";
+sub parsePrimaryXml ($) {
+ my $data=shift;
+
+ my @package_data;
+ my %packdata;
+ my $field = "package";
+ my $lastfield = "package";
+
+ # start extremely primitive xml parser ;)
+ for (@$data){
+ next if ( /^<\?xml/ );
+ for (split ('>',$_)) {
+ $_ =~ s/^\s*(.*?)\s*$/$1/s;
+ if ( /^<([^\ ]*)\ (.*)/ ) {
+ $lastfield = $1;
+ $field .= ".$lastfield";
+ my $trail = $2;
+ my $field_ends = 0;
+ $field_ends = 1 if ( $trail =~ /\/$/ );
+ $trail =~ s/\/$//;
+ for (split('\ ',$trail)) {
+ my ($key,$val) = split ('=',$_);
+ $val =~ s/^\"(.*)\"$/$1/;
+ $packdata{"$field.$key"} = $val;
+ #print "'$field.$key' val = \"".$packdata{"$field.$key"}."\" (3)\n";
+ }
+ if ( $field_ends ) {
+ if ( $field =~ /\./ ) {
+ $field =~ s/\.[^\.]*$//;
+ $lastfield = $field;
+ $lastfield =~ s/^.*([^\.]*)$/$1/;
+ } else {
+ $field = "package";
+ $lastfield = "package";
+ if ($packdata{'package.name'}) {
+ my %pack_data_tmp = %packdata;
+ push @package_data, \%pack_data_tmp;
+ }
+ %packdata = ();
+ }
+ }
+ } elsif ( /^<\/(.*)/ ) {
+ if ( $field =~ /\./ ) {
+ $field =~ s/\.[^\.]*$//;
+ $lastfield = $field;
+ $lastfield =~ s/^.*([^\.]*)$/$1/;
+ } else {
+ $field = "package";
+ $lastfield = "package";
+ if ($packdata{'package.name'}) {
+ my %pack_data_tmp = %packdata;
+ push @package_data, \%pack_data_tmp;
+ }
+ %packdata = ();
+ }
+ } elsif ( /^<([^\ >]*)/ ) {
+ my $tfield = $1;
+ if ( $tfield !~ /\/$/ ) {
+ $field .= ".$tfield";
+ $lastfield = $tfield;
+ }
+ } elsif ( /^([^<]*)<\/$lastfield/ ) {
+ $packdata{"$field"} = $1;
+ #print "'$field' val = \"".$packdata{"$field"}."\" (2)\n";
+ if ( $field =~ /\./ ) {
+ $field =~ s/\.[^\.]*$//;
+ $lastfield = $field;
+ $lastfield =~ s/^.*([^\.]*)$/$1/;
+ } else {
+ $field = "package";
+ $lastfield = "package";
+ if ($packdata{'package.name'}) {
+ my %pack_data_tmp = %packdata;
+ push @package_data, \%pack_data_tmp;
+ }
+ %packdata = ();
+ }
+ } elsif ( /^(.*)=(.*)$/ ) {
+ my $key = $1;
+ my $val = $2;
+ $val =~ s/^\"(.*)\"$/$1/;
+ $packdata{"$field.$key"} = $val;
+ #print "'$field.$key' val = \"".$packdata{"$field.$key"}."\" lastfield = \"$lastfield\" (1)\n";
+ }
+ }
}
- closedir (DIR);
- }
- if (-x '/usr/bin/createrepo'){
- system("createrepo -x \"*.patch.rpm\" -x \"*.delta.rpm\" $basedir");
- } else {
+ # end extremely primitive xml parser ;)
+ # for debugging only
+ print "parsePrimaryXml: ".Dumper($package_data[0]) if ($DEBUG);
+ return \@package_data;
+}
+
+sub updateRepo ($) {
+ my $basedir=shift;
+ my $res=0;
+ my $savedir=tempdir( "createpatch-XXXXXX", TMPDIR => 1, CLEANUP => 1 );
+ if (! -x '/usr/bin/createrepo'){
warn "/usr/bin/createrepo not found or not executable.\n";
warn "Please check if package createrepo is installed on your system.\n";
- mkdir "$basedir/repodata" if (!-d "$basedir/repodata");
+ return 1;
}
- if ( opendir ( DIR, "$basedir/repo_save" ) ) {
- for (readdir(DIR)) {
- link "$basedir/repo_save/$_","$basedir/repodata/$_";
- unlink "$basedir/repo_save/$_";
+ if ( opendir ( DIR, "$basedir")){
+ for (readdir(DIR)) {
+ unlink "$basedir/repodata/$_" if ( /\.key$/ || /\.asc$/ );
+ next unless ( /^patch/ || /^product/ );
+ if ( /^patches.xml/ ) {
+ unlink "$basedir/repodata/patches.xml";
+ next;
+ }
+ link "$basedir/repodata/$_","$savedir/$_";
+ unlink "$basedir/repodata/$_";
+ }
+ closedir (DIR);
}
- closedir (DIR);
- rmdir "$basedir/repo_save";
+ $res=system("/usr/bin/createrepo -x \"*.patch.rpm\" -x \"*.delta.rpm\" $basedir 1>&2 2>/dev/null");
+ print "createrepo: $res - by running /usr/bin/createrepo -x \"*.patch.rpm\" -x \"*.delta.rpm\" $basedir 1>&2 2>/dev/null\n" if ($DEBUG);
+ if ( opendir ( DIR, "$savedir" ) ) {
+ for (readdir(DIR)) {
+ link "$savedir/$_","$basedir/repodata/$_";
+ unlink "$savedir/$_";
+ }
+ closedir (DIR);
}
+ return $res;
}
-my $repodata = "$basedir/repodata";
+sub GeneratePatchesXml($) {
+ my ($patches_directory) = @_;
-if ( -f "$repodata/primary.xml.gz" ) {
- open ( PRIMARY, "zcat $repodata/primary.xml.gz |");
-} else {
- open ( PRIMARY, "$repodata/primary.xml");
-}
+ opendir(PDIR,"$patches_directory");
+ my @all_patches = grep {/^patch-.*\.xml$/} readdir(PDIR);
+ closedir(PDIR);
-my @PRIMARY;
-{
- local $/ = "<package ";
- @PRIMARY = <PRIMARY>;
- chomp (@PRIMARY);
+ if (@all_patches) {
+ open (NEWDIR,">$patches_directory/patches.xml");
+ print NEWDIR "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ print NEWDIR "<patches xmlns=\"http://novell.com/package/metadata/suse/patches\">\n";
+ my $pdirname = $patches_directory;
+ $pdirname =~ s/^.*\///;
+ for my $current_patch (@all_patches) {
+ my ($checksum,$dummy) = split('\s+',`sha1sum "$patches_directory/$current_patch"`);
+ my $category = "";
+ if ($patches_directory =~ /\/you/) {
+ open(PATCH,"$patches_directory/$current_patch");
+ my @TMP = <PATCH>;
+ close(PATCH);
+ chomp(@TMP);
+ $category = (grep { /<category>/ } @TMP)[0];
+ if ($category) {
+ $category =~ s/^\s+//;
+ $category =~ s/\s+$//;
+ $category = " $category\n";
+ }
+ LOG("$current_patch: $category",7);
+ }
+ $current_patch =~ s/.xml$//;
+ my $name = $current_patch;
+ $name =~ s/^patch-//;
+ print NEWDIR " <patch id=\"$name\">\n";
+ print NEWDIR " <checksum type=\"sha\">$checksum</checksum>\n";
+ print NEWDIR " <location href=\"$pdirname/$current_patch.xml\"/>\n$category";
+ print NEWDIR " </patch>\n";
+ }
+ print NEWDIR "</patches>\n";
+ close (NEWDIR);
+ } else {
+ LOG("GeneratePatchesXml: no patches found, removing patches.xml",3);
+ unlink "$patches_directory/patches.xml";
+ }
}
-close ( PRIMARY );
-my @package_data;
-my %packdata;
-my $field = "package";
-my $lastfield = "package";
-
-# start extremely primitive xml parser ;)
-for (@PRIMARY) {
- next if ( /^<\?xml/ );
- for (split ('>',$_)) {
- $_ =~ s/^\s*(.*?)\s*$/$1/s;
- if ( /^<([^\ ]*)\ (.*)/ ) {
- $lastfield = $1;
- $field .= ".$lastfield";
- my $trail = $2;
- my $field_ends = 0;
- $field_ends = 1 if ( $trail =~ /\/$/ );
- $trail =~ s/\/$//;
- for (split('\ ',$trail)) {
- my ($key,$val) = split ('=',$_);
- $val =~ s/^\"(.*)\"$/$1/;
- $packdata{"$field.$key"} = $val;
- #print "'$field.$key' val = \"".$packdata{"$field.$key"}."\" (3)\n";
- }
- if ( $field_ends ) {
- if ( $field =~ /\./ ) {
- $field =~ s/\.[^\.]*$//;
- $lastfield = $field;
- $lastfield =~ s/^.*([^\.]*)$/$1/;
- } else {
- $field = "package";
- $lastfield = "package";
- if ($packdata{'package.name'}) {
- my %pack_data_tmp = %packdata;
- push @package_data, \%pack_data_tmp;
- }
- %packdata = ();
- }
- }
- } elsif ( /^<\/(.*)/ ) {
- if ( $field =~ /\./ ) {
- $field =~ s/\.[^\.]*$//;
- $lastfield = $field;
- $lastfield =~ s/^.*([^\.]*)$/$1/;
- } else {
- $field = "package";
- $lastfield = "package";
- if ($packdata{'package.name'}) {
- my %pack_data_tmp = %packdata;
- push @package_data, \%pack_data_tmp;
- }
- %packdata = ();
- }
- } elsif ( /^<([^\ >]*)/ ) {
- my $tfield = $1;
- if ( $tfield !~ /\/$/ ) {
- $field .= ".$tfield";
- $lastfield = $tfield;
- }
- } elsif ( /^([^<]*)<\/$lastfield/ ) {
- $packdata{"$field"} = $1;
- #print "'$field' val = \"".$packdata{"$field"}."\" (2)\n";
- if ( $field =~ /\./ ) {
- $field =~ s/\.[^\.]*$//;
- $lastfield = $field;
- $lastfield =~ s/^.*([^\.]*)$/$1/;
- } else {
- $field = "package";
- $lastfield = "package";
- if ($packdata{'package.name'}) {
- my %pack_data_tmp = %packdata;
- push @package_data, \%pack_data_tmp;
- }
- %packdata = ();
- }
- } elsif ( /^(.*)=(.*)$/ ) {
- my $key = $1;
- my $val = $2;
- $val =~ s/^\"(.*)\"$/$1/;
- $packdata{"$field.$key"} = $val;
- #print "'$field.$key' val = \"".$packdata{"$field.$key"}."\" lastfield = \"$lastfield\" (1)\n";
- }
+sub GenerateRepomdXml($) {
+ my ($patches_directory) = @_;
+ opendir(PDIR,"$patches_directory");
+ my @all_patches = grep {/\.xml(\.gz)?$/} readdir(PDIR);
+ closedir(PDIR);
+ open (NEWDIR,">$patches_directory/repomd.xml");
+ print NEWDIR "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ print NEWDIR "<repomd xmlns=\"http://linux.duke.edu/metadata/repo\">\n";
+ my $pdirname = $patches_directory;
+ $pdirname =~ s/^.*\///;
+ for (@all_patches) {
+ next if (/^patch-/);
+ next if (/^repomd/);
+ my ($checksum,$dummy) = split('\s+',`sha1sum "$patches_directory/$_"`);
+ my $o_checksum = $checksum;
+ if ( /\.gz/ ) {
+ ($o_checksum,my $dummy) = split('\s+',`gzip -dc "$patches_directory/$_" | sha1sum`);
+ }
+ my $timestamp = stat("$patches_directory/$_")->mtime;
+ my $filename = $_;
+ $_ =~ s/.xml(\.gz)?$//;
+ print NEWDIR " <data type=\"$_\">\n";
+ print NEWDIR " <location href=\"$pdirname/$filename\"/>\n";
+ print NEWDIR " <checksum type=\"sha\">$checksum</checksum>\n";
+ print NEWDIR " <timestamp>$timestamp</timestamp>\n";
+ print NEWDIR " <open-checksum type=\"sha\">$o_checksum</open-checksum>\n";
+ print NEWDIR " </data>\n";
}
+ print NEWDIR "</repomd>\n";
+ close ( NEWDIR );
}
-# end extremely primitive xml parser ;)
-# for debugging only
-#print Dumper($package_data[0]);
+sub xml_escape($) {
+ my ($text) = @_;
+ $text =~ s/&/&/sg;
+ $text =~ s/</</sg;
+ $text =~ s/>/>/sg;
+ $text =~ s/"/"/sg;
+ #$text =~ s/([\x80-\xff])/$1 lt "\xC0" ? "\xC2$1" : "\xC3".chr(ord($1)-64)/ge;
+ return $text;
+}
+# poor mans utf8 converter
+sub to_xml_utf8 {
+ my ($in_text) = @_;
+ my $text = xml_escape($in_text);
+ $text =~ s/([\x80-\xff])/$1 lt "\xC0" ? "\xC2$1" : "\xC3".chr(ord($1)-64)/ge;
+ return $text;
+}
-for (@package_data) {
- my $pack_name = $_->{'package.name'};
- my $long_name = $_->{'package.location.href'};
- $long_name =~ s/^.*\/([^\/]*)/$1/;
- next unless ( $pack_name eq $first_package || $long_name eq $first_package );
- # primitive approach: use summary and description of the first package found
- $patch_summary = $_->{'package.summary'} unless ($patch_summary);
- $patch_description = $_->{'package.description'} unless ($patch_description);
+sub trim($){
+ my $string = shift;
+ $string =~ s/^\s*//;
+ $string =~ s/\s*$//;
+ return $string;
}
+sub ParseConfig($) {
+ my $conf = shift;
+ my %config;
+ open(CONF,"$conf") or die ("Could not open $conf: $!\n");
+ while (<CONF>){
+ next if (/^#/);
+ next if /^\s*$/;
+
+ my ($tag, $data);
+ if (/=/){
+ ($tag,$data)=split(/\s*=\s*/, $_, 2);
+ $tag=trim($tag);
+ $data=trim($data);
+ } elsif (/:/){
+ ($tag,$data) = split(/\s*:\s*/, $_, 2);
+ $tag=trim($tag);
+ $data=trim($data);
+ } elsif (/ /){
+ ($tag, $data) = split(/\s*/, $_, 2);
+ $tag=trim($tag);
+ $data=trim($data);
+ }
+ $config{$tag}="$data";
+ }
+ close(CONF);
+ return \%config;
+}
-if ($#packagelist ge 0) {
+sub writePatchFile($$$) {
+ my $configref=shift;
+ my $repodata=shift;
+ my $package_dataref=shift;
+ my %config=%$configref;
+ my @package_data=@$package_dataref;
+ if ($#packagelist ge 0) {
my $timestamp = time;
- my $iteration = $patch_version;
- while ( -f "$repodata/patch-$patch_id-$patch_name-$iteration.xml" ) {
- warn ("patch-$patch_id-$patch_name-$iteration.xml does already exist, increasing version\n");
- $iteration++;
+ my $iteration = $config{'patch_version'};
+ while ( -f "$repodata/patch-$config{'patch_id'}-$config{'patch_name'}-$iteration.xml" ) {
+ warn ("patch-$config{'patch_id'}-$config{'patch_name'}-$iteration.xml does already exist, increasing version\n");
+ $iteration++;
}
# patch header, mostly static
@@ -294,27 +401,27 @@
$patch_text .= " xmlns:yum=\"http://linux.duke.edu/metadata/common\"\n";
$patch_text .= " xmlns:rpm=\"http://linux.duke.edu/metadata/rpm\"\n";
$patch_text .= " xmlns:suse=\"http://novell.com/package/metadata/suse/common\"\n";
- $patch_text .= " patchid=\"$patch_id-$patch_name-$iteration\"\n timestamp=\"$timestamp\"\n engine=\"1.0\">\n";
- $patch_text .= " <yum:name>$patch_id-$patch_name</yum:name>\n";
- $patch_text .= " <summary lang=\"en\">".xml_escape($patch_summary)."</summary>\n";
- $patch_text .= " <description lang=\"en\">".xml_escape($patch_description)."</description>\n";
+ $patch_text .= " patchid=\"$config{'patch_id'}-$config{'patch_name'}-$iteration\"\n timestamp=\"$timestamp\"\n engine=\"1.0\">\n";
+ $patch_text .= " <yum:name>$config{'patch_id'}-$config{'patch_name'}</yum:name>\n";
+ $patch_text .= " <summary lang=\"en\">".xml_escape($config{'patch_summary'})."</summary>\n";
+ $patch_text .= " <description lang=\"en\">".xml_escape($config{'patch_description'})."</description>\n";
$patch_text .= " <yum:version ver=\"$iteration\" rel=\"0\"/>\n";
$patch_text .= " <rpm:requires>\n";
my %seen_already;
for (@package_data) {
- # this package wanted in patch ?
- my $pack_name = $_->{'package.name'};
- my $long_name = $_->{'package.location.href'};
- $long_name =~ s/^.*\/([^\/]*)/$1/;
- next unless grep { $_ eq $pack_name || $_ eq $long_name } @packagelist;
-
- # duplicate filter
- next if ( $seen_already{"$_->{'package.name'}-$_->{'package.version.epoch'}-$_->{'package.version.ver'}-$_->{'package.version.rel'}"} );
- $seen_already{"$_->{'package.name'}-$_->{'package.version.epoch'}-$_->{'package.version.ver'}-$_->{'package.version.rel'}"} = 1;
+ # this package wanted in patch ?
+ my $pack_name = $_->{'package.name'};
+ my $long_name = $_->{'package.location.href'};
+ $long_name =~ s/^.*\/([^\/]*)/$1/;
+ next unless grep { $_ eq $pack_name || $_ eq $long_name } @packagelist;
+
+ # duplicate filter
+ next if ( $seen_already{"$_->{'package.name'}-$_->{'package.version.epoch'}-$_->{'package.version.ver'}-$_->{'package.version.rel'}"} );
+ $seen_already{"$_->{'package.name'}-$_->{'package.version.epoch'}-$_->{'package.version.ver'}-$_->{'package.version.rel'}"} = 1;
- # make the patch require the atom associated with this package
- $patch_text .= " <rpm:entry kind=\"atom\" name=\"$_->{'package.name'}\"";
+ # make the patch require the atom associated with this package
+ $patch_text .= " <rpm:entry kind=\"atom\" name=\"$_->{'package.name'}\"";
$patch_text .= " epoch=\"$_->{'package.version.epoch'}\"";
$patch_text .= " ver=\"$_->{'package.version.ver'}\"";
$patch_text .= " rel=\"$_->{'package.version.rel'}\" flags=\"EQ\"/>\n";
@@ -323,18 +430,18 @@
# do we have any supplements tags at all
if ($#patchsupplements ge 0){
- $patch_text .= " <rpm:supplements>\n";
- for (@patchsupplements){
- $patch_text .= " <rpm:entry kind=\"patch\" name=\"$_\"/>\n";
- }
- $patch_text .= " </rpm:supplements>\n";
+ $patch_text .= " <rpm:supplements>\n";
+ for (@patchsupplements){
+ $patch_text .= " <rpm:entry kind=\"patch\" name=\"$_\"/>\n";
+ }
+ $patch_text .= " </rpm:supplements>\n";
}
- $patch_text .=" <category>$category</category>\n";
+ $patch_text .=" <category>$config{'category'}</category>\n";
- if ( $license_file && -f $license_file ) {
+ if ( $config{'license_file'} && -f "$config{'license_file'}" ) {
$patch_text .= " <license-to-confirm>\n";
- open ( LIC, "< $license_file");
+ open ( LIC, "< $config{'license_file'}");
while ( <LIC> ) {
$patch_text .= xml_escape($_);
}
@@ -344,191 +451,720 @@
$patch_text .= " <atoms>\n";
for (@package_data) {
- # this package wanted in patch ?
- my $pack_name = $_->{'package.name'};
- my $long_name = $_->{'package.location.href'};
- my $freshens;
- $long_name =~ s/^.*\/([^\/]*)/$1/;
- next unless grep { $_ eq $pack_name || $_ eq $long_name } @packagelist;
- # create atom data, mostly just copies from primary package data
- $patch_text .= " <package xmlns=\"http://linux.duke.edu/metadata/common\" type=\"rpm\">\n";
- $patch_text .= " <name>$_->{'package.name'}</name>\n";
- $patch_text .= " <arch>$_->{'package.arch'}</arch>\n";
- $patch_text .= " <version epoch=\"$_->{'package.version.epoch'}\" ver=\"$_->{'package.version.ver'}\" rel=\"$_->{'package.version.rel'}\"/>\n";
- $patch_text .= " <checksum type=\"sha\" pkgid=\"YES\">$_->{'package.checksum'}</checksum>\n";
- $patch_text .= " <time file=\"$_->{'package.time.file'}\" build=\"$_->{'package.time.build'}\"/>\n";
- $patch_text .= " <size package=\"$_->{'package.size.package'}\" installed=\"$_->{'package.size.installed'}\" archive=\"$_->{'package.size.archive'}\"/>\n";
- $patch_text .= " <location href=\"$_->{'package.location.href'}\"/>\n";
- # here starts the association of the atom to the real package
- $patch_text .= " <format>\n <rpm:requires>\n";
- $patch_text .= " <rpm:entry kind=\"package\"";
- $patch_text .= " name=\"$_->{'package.name'}\"";
- $patch_text .= " epoch=\"$_->{'package.version.epoch'}\"";
- $patch_text .= " ver=\"$_->{'package.version.ver'}\"";
- $patch_text .= " rel=\"$_->{'package.version.rel'}\"";
- $patch_text .= " flags=\"GE\"/>\n";
- $patch_text .= " </rpm:requires>\n";
- # now have the atom pulled in, if this package is installed on the system
-
- $patch_text .= " <suse:freshens>\n";
- # do we need to override the freshens tag
- if ($#pkgrefresh ge 0){
- for(@pkgrefresh){
- $patch_text .= " <suse:entry kind=\"package\" name=\"$_\"/>\n";
+ # this package wanted in patch ?
+ my $pack_name = $_->{'package.name'};
+ my $long_name = $_->{'package.location.href'};
+ my $freshens;
+ $long_name =~ s/^.*\/([^\/]*)/$1/;
+ next unless grep { $_ eq $pack_name || $_ eq $long_name } @packagelist;
+ # create atom data, mostly just copies from primary package data
+ $patch_text .= " <package xmlns=\"http://linux.duke.edu/metadata/common\" type=\"rpm\">\n";
+ $patch_text .= " <name>$_->{'package.name'}</name>\n";
+ $patch_text .= " <arch>$_->{'package.arch'}</arch>\n";
+ $patch_text .= " <version epoch=\"$_->{'package.version.epoch'}\" ver=\"$_->{'package.version.ver'}\" rel=\"$_->{'package.version.rel'}\"/>\n";
+ $patch_text .= " <checksum type=\"sha\" pkgid=\"YES\">$_->{'package.checksum'}</checksum>\n";
+ $patch_text .= " <time file=\"$_->{'package.time.file'}\" build=\"$_->{'package.time.build'}\"/>\n";
+ $patch_text .= " <size package=\"$_->{'package.size.package'}\" installed=\"$_->{'package.size.installed'}\" archive=\"$_->{'package.size.archive'}\"/>\n";
+ $patch_text .= " <location href=\"$_->{'package.location.href'}\"/>\n";
+ # here starts the association of the atom to the real package
+ $patch_text .= " <format>\n <rpm:requires>\n";
+ $patch_text .= " <rpm:entry kind=\"package\"";
+ $patch_text .= " name=\"$_->{'package.name'}\"";
+ $patch_text .= " epoch=\"$_->{'package.version.epoch'}\"";
+ $patch_text .= " ver=\"$_->{'package.version.ver'}\"";
+ $patch_text .= " rel=\"$_->{'package.version.rel'}\"";
+ $patch_text .= " flags=\"GE\"/>\n";
+ $patch_text .= " </rpm:requires>\n";
+ # now have the atom pulled in, if this package is installed on the system
+ $patch_text .= " <suse:freshens>\n";
+ # do we need to override the freshens tag
+ if ($#pkgrefresh ge 0){
+ for(@pkgrefresh){
+ $patch_text .= " <suse:entry kind=\"package\" name=\"$_\"/>\n";
+ }
}
- }
- else{
- $patch_text .= " <suse:entry kind=\"package\" name=\"$_->{'package.name'}\"/>\n";
- }
-
- $patch_text .= " </suse:freshens>\n </format>\n </package>\n";
+ else{
+ $patch_text .= " <suse:entry kind=\"package\" name=\"$_->{'package.name'}\"/>\n";
+ }
+ $patch_text .= " </suse:freshens>\n </format>\n </package>\n";
}
$patch_text .= " </atoms>\n</patch>\n";
- open ( PATCH, "> $repodata/patch-$patch_id-$patch_name-$iteration.xml");
+ open ( PATCH, "> $repodata/patch-$config{'patch_id'}-$config{'patch_name'}-$iteration.xml");
print PATCH $patch_text;
close ( PATCH );
+ }
}
-GeneratePatchesXml($repodata);
-GenerateRepomdXml($repodata);
-unlink "$repodata/repomd.xml.asc";
-unlink "$repodata/repomd.xml.key";
-if ( $do_signing ) {
- if ( $sign_id ) {
- system("gpg -a -b --default-key \"$sign_id\" $repodata/repomd.xml");
+#
+# write out the xml file and gzip it, atomic
+#
+sub UpdateXmlFile {
+ my ($patches_directory,$xname,$data) = @_;
+ open ( PRIMARY, "| gzip > $patches_directory/$xname.xml.gz.new");
+ print PRIMARY "$data->{header}\n";
+ # actually faster this way according to testing
+ for (@{$data->{newdata}}) {
+ print PRIMARY "$_\n" if defined $_;
+ }
+ print PRIMARY "$data->{footer}\n";
+ close ( PRIMARY );
+ rename ( "$patches_directory/$xname.xml.gz.new", "$patches_directory/$xname.xml.gz");
+ unlink ("$patches_directory/$xname.xml") if ( -f "$patches_directory/$xname.xml" );
+}
+
+#
+# extremely primitive xml input: split records at "<package "
+#
+sub ReadXmlFile {
+ my ($patches_directory,$xname,$dataname) = @_;
+ my @PRIMARY;
+
+ if ( -f "$patches_directory/$xname.xml.gz" ) {
+ open ( PRIMARY, "zcat $patches_directory/$xname.xml.gz |");
} else {
- system("gpg -a -b $repodata/repomd.xml");
- $sign_id = `gpg --verify $repodata/repomd.xml.asc 2>&1 | sed -ne "s/.* ID //p"`;
- chomp ($sign_id);
+ open ( PRIMARY, "$patches_directory/$xname.xml");
}
- system("gpg -a --export \"$sign_id\" > $repodata/repomd.xml.key") if ( $sign_id );
+ {
+ local $/ = "<package ";
+ @PRIMARY = <PRIMARY>;
+ chomp (@PRIMARY);
+ }
+ close ( PRIMARY );
+
+ unless ( $PRIMARY[1] ) {
+ $PRIMARY[0] =~ s/<\/$dataname>//g;
+ push @PRIMARY, "nil></package>\n</$dataname>";
+ }
+ my $header_primary = shift(@PRIMARY);
+ $header_primary =~ s/^\s*(.*?)\s*$/$1/s;
+
+ my $footer_primary = $PRIMARY[$#PRIMARY];
+ $PRIMARY[$#PRIMARY] =~ s/<\/package\>.*$/<\/package\>/s;
+ $footer_primary =~ s/^.*<\/package>//s;
+ $footer_primary =~ s/^\s*(.*?)\s*$/$1/s;
+
+ return ($header_primary,$footer_primary,\@PRIMARY);
}
-if ( $validate && @packagelist ){
- validate($patch_name);
+sub cleanup(){
+ unlink "$LOCKFILE" if ( -f "$LOCKFILE");
}
-#system("//bin/sign -d $patches_directory/repomd.xml");
-#system("cp $patchinfo_lib_dir/public-key $patches_directory/repomd.xml.key");
+# simple logfile writer
+sub LOG {
+ my $entry = shift;
+ my $level = shift || 1;
+ my $xtime=`date`;
+ chomp($xtime);
+ if ( $level lt $config{'loglevel'} ){
+ print OUTPUT_LOG "$xtime: $entry\n";
+ }
+ print "$xtime: $entry\n" if ($DEBUG);
+}
-sub GeneratePatchesXml {
- my ($patches_directory) = @_;
- opendir(PDIR,"$patches_directory");
- my @all_patches = grep {/^patch-.*\.xml$/} readdir(PDIR);
- closedir(PDIR);
- open (NEWDIR,">$patches_directory/patches.xml");
- print NEWDIR "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
- print NEWDIR "<patches xmlns=\"http://novell.com/package/metadata/suse/patches\">\n";
- my $pdirname = $patches_directory;
- $pdirname =~ s/^.*\///;
- for (@all_patches) {
- my ($checksum,$dummy) = split('\s+',`sha1sum "$patches_directory/$_"`);
- $_ =~ s/.xml$//;
- my $name = $_;
- $name =~ s/^patch-//;
- print NEWDIR " <patch id=\"$name\">\n";
- print NEWDIR " <checksum type=\"sha\">$checksum</checksum>\n";
- print NEWDIR " <location href=\"$pdirname/$_.xml\"/>\n";
- print NEWDIR " </patch>\n";
+#
+# helper functions for GetPackageDataFromRpm
+#
+# only weak deps (enhances, suggests)
+sub filter_weak {
+ my ($tn, $tf) = @_;
+ my @tf = @{$tf || []};
+ my @res;
+ for (@{$tn || []}) {
+ push @res, $_ unless (shift @tf) & 0x8000000;
+ }
+ return @res;
+}
+
+# only strong ones (recommends, supplements)
+sub filter_strong {
+ my ($tn, $tf) = @_;
+ my @tf = @{$tf || []};
+ my @res;
+ for (@{$tn || []}) {
+ push @res, $_ if (shift @tf) & 0x8000000;
+ }
+ return @res;
+}
+
+# combine deps and their relations and flags
+sub add_flagsvers_special {
+ my $res = shift;
+ my $name = shift;
+ my $flags = shift;
+ my $vers = shift;
+ my $filter = shift;
+
+ my @raw_dep_names = @{$res->{$name} || []};
+ my @raw_dep_flags = @{$res->{$flags} || []};
+ my @raw_dep_vers = @{$res->{$vers} || []};
+ if ($filter && @raw_dep_flags) {
+ @raw_dep_names = $filter->(\@raw_dep_names, \@raw_dep_flags);
+ @raw_dep_vers = $filter->(\@raw_dep_vers, \@raw_dep_flags);
+ @raw_dep_flags = $filter->(\@raw_dep_flags, \@raw_dep_flags);
+ }
+ my @raw_provides = ();
+ for (@raw_dep_names) {
+ my %prov_line = ();
+ $prov_line{'name'} = $_;
+ my ($epoch, $version) = $raw_dep_vers[0] =~ /^(?:(\d+):)?(.*?)$/;
+ my $release = '';
+ ($version, $release) = ($1, $2) if $version =~ /^(.*)-(.*?)$/;
+ $prov_line{'epoch'} = $epoch;
+ $prov_line{'ver'} = $version;
+ $prov_line{'rel'} = $release;
+ if (@raw_dep_flags && ($raw_dep_flags[0] & 0xe) && @raw_dep_vers) {
+ my @rels = qw{FALSE FALSE LT LT GT GT NE NE EQ EQ LE LE GE GE TRUE TRUE};
+ $prov_line{'flags'} = $rels[$raw_dep_flags[0] & 0xe];
+ }
+ if (@raw_dep_flags && $raw_dep_flags[0] & 64) {
+ $prov_line{'pre'} = 1;
+ }
+ shift @raw_dep_flags;
+ shift @raw_dep_vers;
+ push @raw_provides, \%prov_line;
+ }
+ return \@raw_provides;
+}
+
+# find out all about the given package
+sub GetPackageDataFromRpm {
+ my ($package_file) = @_;
+ my %package_data = ();
+ my $dummy = "";
+ $package_data{"fullpath"} = $package_file;
+ $package_file =~ /.*\/([^\/]*)$/;
+ $package_data{"basename"} = $1;
+ $package_data{"filesize"} = stat($package_file)->size;
+ $package_data{"filetime"} = stat($package_file)->mtime;
+ ($package_data{"checksum_sha"},$dummy) = split('\s+',`sha1sum $package_file`);
+ my %res = RPMQ::rpmq_many($package_file,'NAME','EPOCH','VERSION','RELEASE','SIZE','BUILDTIME','GROUP','ARCH','LICENSE',
+ 'SOURCERPM','PROVIDENAME','PROVIDEFLAGS','PROVIDEVERSION',
+ 'REQUIRENAME','REQUIREFLAGS','REQUIREVERSION',
+ 'CONFLICTNAME','CONFLICTFLAGS','CONFLICTVERSION',
+ 'OBSOLETENAME','OBSOLETEFLAGS','OBSOLETEVERSION',
+ 'SUGGESTSNAME','SUGGESTSFLAGS','SUGGESTSVERSION',
+ 'ENHANCESNAME','ENHANCESFLAGS','ENHANCESVERSION',
+ 'FILENAMES','FILEMODES','SUMMARY','DESCRIPTION','HEADERSTART','HEADEREND',
+ 'CHANGELOGTIME','CHANGELOGNAME','CHANGELOGTEXT','PACKAGER','VENDOR','URL',
+ 'ARCHIVESIZE','SIGTAG_PAYLOADSIZE','BUILDHOST');
+ $package_data{"name"} = ($res{'NAME'} || [])->[0];
+ $package_data{"epoch"} = ($res{'EPOCH'} || [])->[0];
+ $package_data{"epoch"} = "0" unless ($package_data{"epoch"});
+ $package_data{"version_only"} = ($res{'VERSION'} || [])->[0];
+ $package_data{"release"} = ($res{'RELEASE'} || [])->[0];
+ $package_data{"version"} = "$package_data{version_only}-$package_data{release}";
+ $package_data{"rpmsize"} = ($res{'SIZE'} || [])->[0];
+ $package_data{"archivesize"} = ($res{'ARCHIVESIZE'} || [])->[0];
+ $package_data{"archivesize"} = ($res{'SIGTAG_PAYLOADSIZE'} || [])->[0] unless ($package_data{"archivesize"});
+ $package_data{"buildtime"} = ($res{'BUILDTIME'} || [])->[0];
+ $package_data{"rpmgroup"} = ($res{'GROUP'} || [])->[0];
+ $package_data{"rpmarch"} = ($res{'ARCH'} || [])->[0];
+ $package_data{"license"} = ($res{'LICENSE'} || [])->[0];
+ $package_data{"sourcerpm"} = ($res{'SOURCERPM'} || [])->[0];
+ $package_data{"packager"} = ($res{'PACKAGER'} || [])->[0];
+ $package_data{"vendor"} = ($res{'VENDOR'} || [])->[0];
+ $package_data{"url"} = ($res{'URL'} || [])->[0];
+ $package_data{"rpmarch"} = "src" unless ( $package_data{"sourcerpm"} );
+ $package_data{"headerstart"} = $res{'HEADERSTART'};
+ $package_data{"headerend"} = $res{'HEADEREND'};
+ $package_data{"summary"} = ($res{'SUMMARY'} || [])->[0];
+ $package_data{"buildhost"} = ($res{'BUILDHOST'} || [])->[0];
+ $package_data{"description"} = join(' ',@{$res{'DESCRIPTION'}});
+ my @dirs = ();
+ my @files = ();
+ for (@{$res{'FILENAMES'}}) {
+ if ( @{$res{'FILEMODES'}}[0] & 0040000 ) {
+ push @dirs, $_;
+ } else {
+ push @files, $_;
}
- print NEWDIR "</patches>\n";
+ shift @{$res{FILEMODES}}
+ }
+ $package_data{"rpmlistdirs"} = \@dirs;
+ $package_data{"rpmlistfiles"} = \@files;
+ # raw deps
+ $package_data{"providesraw"} = add_flagsvers_special(\%res,'PROVIDENAME','PROVIDEFLAGS','PROVIDEVERSION');
+ $package_data{"requiresraw"} = add_flagsvers_special(\%res,'REQUIRENAME','REQUIREFLAGS','REQUIREVERSION');
+ $package_data{"conflictsraw"} = add_flagsvers_special(\%res,'CONFLICTNAME','CONFLICTFLAGS','CONFLICTVERSION');
+ $package_data{"obsoletesraw"} = add_flagsvers_special(\%res,'OBSOLETENAME','OBSOLETEFLAGS','OBSOLETEVERSION');
+ $package_data{"supplementsraw"} = add_flagsvers_special(\%res,'ENHANCESNAME','ENHANCESFLAGS','ENHANCESVERSION', \&filter_strong);
+ $package_data{"enhancesraw"} = add_flagsvers_special(\%res,'ENHANCESNAME','ENHANCESFLAGS','ENHANCESVERSION', \&filter_weak);
+ $package_data{"recommendsraw"} = add_flagsvers_special(\%res,'SUGGESTSNAME','SUGGESTSFLAGS','SUGGESTSVERSION', \&filter_strong);
+ $package_data{"suggestsraw"} = add_flagsvers_special(\%res,'SUGGESTSNAME','SUGGESTSFLAGS','SUGGESTSVERSION', \&filter_weak);
+
+ # cooked deps
+ RPMQ::rpmq_add_flagsvers(\%res,'PROVIDENAME','PROVIDEFLAGS','PROVIDEVERSION');
+ RPMQ::rpmq_add_flagsvers(\%res,'REQUIRENAME','REQUIREFLAGS','REQUIREVERSION');
+ $package_data{"provides"} = join(' ',@{$res{'PROVIDENAME'}});
+ $package_data{"requires"} = join(' ',@{$res{'REQUIRENAME'}});
+ $package_data{"provides"} =~ s/\s+/ /g;
+ $package_data{"provides"} =~ s/\s+$//g;
+ $package_data{"requires"} =~ s/\s+/ /g;
+ $package_data{"requires"} =~ s/\s+$//g;
+ $package_data{"changelogtime"} = $res{'CHANGELOGTIME'};
+ $package_data{"changelogname"} = $res{'CHANGELOGNAME'};
+ $package_data{"changelogname"} =~ s/^- //;
+ $package_data{"changelogtext"} = $res{'CHANGELOGTEXT'};
+ return %package_data;
}
-sub GenerateRepomdXml {
+
+sub ReadFileToHash($){
+ my $file=shift;
+ my %temp;
+ open(FILE,"< $file") or return undef;
+ while (<FILE>){
+ chomp;
+ last if $_ =~ /^:END/ ;
+ next if ( $_ =~ /^\#/ );
+ next if ( $_ =~ /^\s$/ );
+ my ($le,$ri) = split (/:/,$_,2);
+ $le=trim($le);
+ $ri=trim($ri);
+ $ri=~ s/\\n/\n/g;
+ $temp{$le}=$ri;
+ }
+ close(FILE);
+ return \%temp;
+}
+
+#
+# create package information in xml format as given for repomd
+#
+sub PackageDataToXML {
+my ($curpack) = @_;
+ my $boilerplate .= " <package xmlns=\"http://linux.duke.edu/metadata/common\" type=\"rpm\">\n";
+ my $header = " <name>".$curpack->{"name"}."</name>\n";
+ $header .= " <arch>".$curpack->{"rpmarch"}."</arch>\n";
+ $header .= " <version epoch=\"$curpack->{epoch}\" ver=\"$curpack->{version_only}\" rel=\"$curpack->{release}\"/>\n";
+ $header .= " <checksum type=\"sha\" pkgid=\"YES\">".$curpack->{"checksum_sha"}."</checksum>\n";
+ $header .= " <summary lang=\"en\">".to_xml_utf8($curpack->{summary})."</summary>\n";
+ $header .= " <description lang=\"en\">".to_xml_utf8($curpack->{description})."</description>\n";
+ $header .= " <packager>$curpack->{packager}</packager>\n";
+ $header .= " <url>".to_xml_utf8($curpack->{url})."</url>\n";
+ $header .= " <time file=\"$curpack->{filetime}\" build=\"$curpack->{buildtime}\"/>\n";
+ $header .= " <size package=\"$curpack->{filesize}\" installed=\"$curpack->{rpmsize}\" archive=\"$curpack->{archivesize}\"/>\n";
+ $header .= " <location href=\"rpm/$curpack->{rpmarch}/$curpack->{basename}\"/>\n";
+
+ #
+ ## FORMAT START
+ #
+ my $data = " <rpm:license>$curpack->{license}</rpm:license>\n";
+ $data .= " <rpm:vendor>$curpack->{vendor}</rpm:vendor>\n";
+ $data .= " <rpm:group>$curpack->{rpmgroup}</rpm:group>\n";
+ $data .= " <rpm:buildhost>$curpack->{buildhost}</rpm:buildhost>\n";
+ if ( $curpack->{sourcerpm} ) {
+ $data .= " <rpm:sourcerpm>$curpack->{sourcerpm}</rpm:sourcerpm>\n";
+ } else {
+ $data .= " <rpm:sourcerpm/>\n";
+ }
+ $data .= " <rpm:header-range start=\"$curpack->{headerstart}\" end=\"$curpack->{headerend}\"/>\n";
+ for my $dep_type (qw{provides requires conflicts obsoletes suggests enhances recommends supplements}) {
+ my $dep_list = $curpack->{"${dep_type}raw"};
+ if ($dep_list && @$dep_list) {
+ $data .= " <rpm:$dep_type>\n";
+ for my $dep_data (@$dep_list) {
+ $data .= " <rpm:entry";
+ # name,epoch,ver,rel,pre,flags
+ for (qw{name flags epoch ver rel pre}) {
+ $data .= " $_=\"$dep_data->{$_}\"" if defined ($dep_data->{$_}) && $dep_data->{$_} ne "";
+ }
+ $data .= "/>\n";
+ }
+ $data .= " </rpm:$dep_type>\n";
+ }
+ }
+ for (grep {/.*bin\/.*/ || /^\/etc\/.*/} @{$curpack->{rpmlistdirs}} ) {
+ $data .= " <file type=\"dir\">$_</file>\n";
+ }
+ for (grep {/.*bin\/.*/ || /^\/usr\/lib\/sendmail$/ || /^\/etc\/.*/} @{$curpack->{rpmlistfiles}} ) {
+ $data .= " <file>$_</file>\n";
+ }
+ #
+ ## FORMAT END
+ #
+ my $filelist = "";
+ for (@{$curpack->{rpmlistdirs}}) {
+ $filelist .= " <file type=\"dir\">".to_xml_utf8($_)."</file>\n";
+ }
+ for (@{$curpack->{rpmlistfiles}}) {
+ $filelist .= " <file>".to_xml_utf8($_)."</file>\n";
+ }
+ return ($boilerplate , $header, $data, $filelist);
+}
+
+#
+# regenerate the updateinfo.xml.gz file: scan all updateinfo-foo.xml and include literally
+#
+sub GenerateUpdateinfoXml {
my ($patches_directory) = @_;
- opendir(PDIR,"$patches_directory");
- my @all_patches = grep {/\.xml(\.gz)?$/} readdir(PDIR);
+
+ opendir(PDIR,"$patches_directory/../repoparts");
+ my @all_files = readdir(PDIR);
+ my @all_patches = grep {/^updateinfo-.*\.xml$/} @all_files;
+ my @all_deltas = grep {/^deltainfo-.*\.xml$/} @all_files;
closedir(PDIR);
- open (NEWDIR,">$patches_directory/repomd.xml");
- print NEWDIR "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
- print NEWDIR "<repomd xmlns=\"http://linux.duke.edu/metadata/repo\">\n";
- my $pdirname = $patches_directory;
- $pdirname =~ s/^.*\///;
- for (@all_patches) {
- next if (/^patch-/);
- next if (/^repomd/);
- my ($checksum,$dummy) = split('\s+',`sha1sum "$patches_directory/$_"`);
- my $o_checksum = $checksum;
- if ( /\.gz/ ) {
- ($o_checksum,my $dummy) = split('\s+',`gzip -dc "$patches_directory/$_" | sha1sum`);
+
+ if (@all_patches) {
+ # concat for updateinfo
+ open (NEWDIR,"| gzip > $patches_directory/updateinfo.xml.gz.new");
+ print NEWDIR "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ print NEWDIR "<updates xmlns=\"http://novell.com/package/metadata/suse/updateinfo\">\n";
+ for my $current_patch (@all_patches) {
+ open (PATCH, "< $patches_directory/../repoparts/$current_patch");
+ print NEWDIR join('',grep {$_ !~ /^<.xml version/} <PATCH>);
+ close (PATCH);
}
- my $timestamp = stat("$patches_directory/$_")->mtime;
- my $filename = $_;
- $_ =~ s/.xml(\.gz)?$//;
- print NEWDIR " <data type=\"$_\">\n";
- print NEWDIR " <location href=\"$pdirname/$filename\"/>\n";
- print NEWDIR " <checksum type=\"sha\">$checksum</checksum>\n";
- print NEWDIR " <timestamp>$timestamp</timestamp>\n";
- print NEWDIR " <open-checksum type=\"sha\">$o_checksum</open-checksum>\n";
- print NEWDIR " </data>\n";
+ print NEWDIR "</updates>\n";
+ close (NEWDIR);
+ my $xname = "updateinfo";
+ rename ( "$patches_directory/$xname.xml.gz.new", "$patches_directory/$xname.xml.gz");
+ unlink ("$patches_directory/$xname.xml") if ( -f "$patches_directory/$xname.xml" );
+ # concat for deltainfo
+ open (NEWDIR,"| gzip > $patches_directory/deltainfo.xml.gz.new");
+ print NEWDIR "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ print NEWDIR "<deltainfo>\n";
+ for my $current_delta (@all_deltas) {
+ open (PATCH, "< $patches_directory/../repoparts/$current_delta");
+ print NEWDIR join('',grep {$_ !~ /^<.xml version/} <PATCH>);
+ close (PATCH);
+ }
+ print NEWDIR "</deltainfo>\n";
+ close (NEWDIR);
+ $xname = "deltainfo";
+ rename ( "$patches_directory/$xname.xml.gz.new", "$patches_directory/$xname.xml.gz");
+ unlink ("$patches_directory/$xname.xml") if ( -f "$patches_directory/$xname.xml" );
+ unlink ("$patches_directory/patches.xml") if ( -f "$patches_directory/patches.xml" );
+ } else {
+ LOG ("GenerateUpdateinfoXml: no updates found, removing updateinfo.xml",2);
+ unlink "$patches_directory/updateinfo.xml";
+ unlink "$patches_directory/updateinfo.xml.gz";
}
- print NEWDIR "</repomd>\n";
- close ( NEWDIR );
}
-sub usage {
- print <<EOF
+###############################################################################
+# Main
+###############################################################################
+Getopt::Long::Configure('bundling');
+
+GetOptions(
+ 'h|help' => \$print_help,
+ 'b|basedir=s' => \$config{'basedir'},
+ 'c|configfile=s' => \$configfile,
+ 'C|category=s' => \$config{'category'},
+ 'e|do_susedata' => \$config{'do_susedata'},
+ 'i|patch_id=s' => \$config{'patch_id'},
+ 'l|logfile=s' => \$config{'logfile'},
+ 'k|keywordfile=s' => \$config{'keywordfile'},
+ 'n|patch_name=s' => \$config{'patch_name'},
+ 'v|patch_version=i' => \$config{'patch_version'},
+ 's|patch_summary=s' => \$config{'patch_summary'},
+ 'd|patch_description=s' => \$config{'patch_description'},
+ 'u|update_repo' => \$config{'update_repo'},
+ 'S|do_signing' => \$config{'do_signing'},
+ 'I|sign_id=s' => \$config{'sign_id'},
+ 'L|license_file=s' => \$config{'license_file'},
+ 'p|packagelist=s' => \@packagelist,
+ 'patchsupplements=s' => \@patchsupplements,
+ 'pkgfreshens=s' => \@pkgrefresh,
+);
+
+usage(0) if ($print_help);
+
+if ("$configfile" ne ""){
+ if (-r "$configfile"){
+ my $configref=ParseConfig("$configfile");
+ %config=%$configref;
+ } else {
+ die ("Could not open $configfile\n");
+ }
+}
+$config{'patch_id'} =~ s/\./_/g;
-$0 Usage Information:
+#die ("Lockfile ($LOCKFILE) already exists at ".ctime()."\n") if (-f "$LOCKFILE");
+#open (LOCK,">$LOCKFILE") || die "Cannot write lockfile $LOCKFILE : $!\n";
+#print LOCK "$$\n";
+#close(LOCK);
+
+$::SIG{"__DIE__"} = sub {
+ die (@_) if $^S;
+ \&cleanup;
+};
- $0 [OPTION] ... <base_dir>
+open ( OUTPUT_LOG , ">>$config{'logfile'}");
+select (OUTPUT_LOG); $| = 1; select (STDOUT);
- <base_dir> is the base directory to the repository.
+my $repodata = "$config{'basedir'}/repodata";
+my $first_package = "";
- -i <PATCH_ID> : Patch id, needs to be unique in world, will be prefixed
- by "hostname -d" as default followed by the name of the
- first package. Dots in "hostname -d" will be converted
- to "_"s.
- -n <PATCH_NAME> : required parameter, terse patch name, like aaa_base
- -v <PATCH_VERSION> : default to "0"/first non-existant if not given
- -s <PATCH_SUMMARY> : default to the package summary of the first RPM
- specified on the command line
- -d <PATCH_DESCRTIPON> : Long description, defaults to the package description
- of the first RPM specified on commandline
- -C <CATEGORY> : Category for the patch. Defaults to recommended.
- Possible values: security, recommended, optional
-
- -u : run createrepo to update repository and take care of keeping
- the patch*xml files - use when augmenting existing repository
- with new patches.
- -S : detached sign the repomd.xml file
- -I <KEY_ID> : key-id to use for signing the repomd.xml file, if not given
- gpg will use the default signing key
- -L <CONFIRMATION_FILE> : add a confirmation request (EULA or Reboot request) to the patch,
- read from the file specified
- --validate : use xmllint to validate the resulting xml file
-
- -p <rpm_basename>[,rpm_basename...] : List of RPMs for this patch.
- You need at least one.
- --patchsupplements <PATCH_NAME>[,PATCH_NAME] : List of patches which are supplemented
- by this patch
- --pkgfreshens rpm_basename : optional parameter which will override
- the default freshens value
-EOF
-;
+if ( @packagelist && $packagelist[0] ){
+ $first_package = $packagelist[0];
+ @packagelist=split(/,/,join(',',@packagelist));
+}
+@patchsupplements=split(/,/,join(',',@patchsupplements)) if ( @patchsupplements );
+@pkgrefresh=split(/,/,join(',',@pkgrefresh)) if (@pkgrefresh);
- exit 1;
+if ( $config{'patch_name'} eq "usefirst" ) {
+ $config{'patch_name'} = "$first_package" if ( "$first_package" );
}
-sub xml_escape {
- my ($text) = @_;
- $text =~ s/&/&/sg;
- $text =~ s/</</sg;
- $text =~ s/>/>/sg;
- $text =~ s/"/"/sg;
- #$text =~ s/([\x80-\xff])/$1 lt "\xC0" ? "\xC2$1" : "\xC3".chr(ord($1)-64)/ge;
- return $text;
+if ($DEBUG){
+ print "config: ".Data::Dumper->Dump([\%config]);
+ for (@patchsupplements){
+ print "patchsupplements : $_\n";
+ }
+ for (@pkgrefresh){
+ print "pkgrefresh : $_\n";
+ }
+ for (@packagelist){
+ print "packagelist : $_\n";
+ }
}
-sub validate {
- my $file = shift;
- my $result = `xmllint --valid $file`;
- print $result;
+LOG("Start creation for $repodata",1);
+my $res=updateRepo("$config{'basedir'}") if ($config{'update_repo'});
+die "Could not update the Repository\n" if ($res);
+
+if ( -f "$repodata/primary.xml.gz" ) {
+ open ( PRIMARY, "zcat $repodata/primary.xml.gz |") || warn ("Could not open primary.xml.gz : $!\n");
+} else {
+ open ( PRIMARY, "$repodata/primary.xml") || warn ("Could not open primary.xml : $!\n");
}
+my @PRIMARY;
+{
+ local $/ = "<package ";
+ @PRIMARY = <PRIMARY>;
+ chomp (@PRIMARY);
+}
+close ( PRIMARY );
+
+my $package_dataref=parsePrimaryXml(\@PRIMARY);
+my @package_data=@$package_dataref;
+
+for (@package_data) {
+ my $pack_name = $_->{'package.name'};
+ my $long_name = $_->{'package.location.href'};
+ $long_name =~ s/^.*\/([^\/]*)/$1/;
+ next unless ( $pack_name eq $first_package || $long_name eq $first_package );
+ # primitive approach: use summary and description of the first package found
+ $config{'patch_summary'} = $_->{'package.summary'} unless ($config{'patch_summary'});
+ $config{'patch_description'} = $_->{'package.description'} unless ($config{'patch_description'});
+}
+
+if ($config{'do_susedata'}){
+ my %xfiles = ( 'susedata' => 'susedata',
+ 'primary' => 'metadata',
+ 'filelists' => 'filelists',
+ 'other' => 'otherdata',
+ );
+ our %data;
+ for my $name (sort(keys %xfiles)){
+ my $tag = $xfiles{$name};
+ if ( ! -f "$repodata/$name.xml.gz" && ! -f "$repodata/$name.xml"){
+ open (IDXFILE, ">$repodata/$name.xml") || die "Could not open $repodata/$name.xml : $!\n";
+ print IDXFILE "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ print IDXFILE "<$tag xmlns=\"http://linux.duke.edu/metadata/$name\" packages=\"0\">\n";
+ print IDXFILE "<package nil></package></$tag>\n";
+ close ( IDXFILE );
+ }
+ ($data{"${name}_header"},$data{"${name}_footer"},$data{"${name}_data"}) = ReadXmlFile("$repodata","$name","$tag");
+ }
+
+ my $number_packages = 0;
+ my $number_discarded = 0;
+ my %seen_rpms = ();
+ my %stat_all_rpms = ();
+ my @rpm_dirs;
+
+ if (opendir(D, "$repodata/../rpm")) {
+ @rpm_dirs = grep {!/^\./} readdir(D);
+ closedir D;
+ }
+ for my $rpm_dir (@rpm_dirs) {
+ if (opendir(D, "$repodata/../rpm/$rpm_dir")) {
+ for (grep { /^[^\.].*\.rpm/ } readdir(D)) {
+ $stat_all_rpms{"$repodata/../rpm/$rpm_dir/$_"} = lstat("$repodata/../rpm/$rpm_dir/$_");
+ }
+ closedir D;
+ }
+ }
+
+ LOG("Starting susedata creation",5);
+ my $valid_pkgids;
+
+ for my $index (0...$#{$data{'primary_data'}}) {
+ my $filename = $data{'primary_data'}->[$index];
+ my $pkgid = $data{'primary_data'}->[$index];
+ #
+ $filename =~ s/^[\s\S]*<location href="([^"]*)"[\s\S]*$/$1/;
+ $pkgid =~ s/^[\s\S]*<checksum type="sha" pkgid="YES">([^<]*)<[\s\S]*$/$1/;
+ my $filebase = $filename;
+ $filebase =~ s/^.*\///;
+ #
+ my $pack_ok = 0;
+ my $f_stat = $stat_all_rpms{"$repodata/../$filename"};
+ # get file time
+ if ( $f_stat ) {
+ my $fil_time = $f_stat->mtime;
+ my $rec_time = $data{'primary_data'}->[$index];
+ $rec_time =~ s/.*time file=\"([^\"]*).*/$1/s;
+ $pack_ok = 1 if ( $rec_time eq $fil_time );
+ LOG("$filename $rec_time $fil_time",5) unless $pack_ok;
+ }
+ if ( $pack_ok && $data{'filelists_data'}->[$index] && $data{'other_data'}->[$index] && $data{'filelists_data'}->[$index] =~ /pkgid="$pkgid"/ && $data{'other_data'}->[$index] =~ /pkgid="$pkgid"/ ) {
+ $data{'primary_data'}->[$index] =~ s/\s+$//s;
+ $data{'primary_data'}->[$index] = " <package ".$data{'primary_data'}->[$index];
+ $data{'filelists_data'}->[$index] =~ s/\s+$//s;
+ $data{'filelists_data'}->[$index] = "<package ".$data{'filelists_data'}->[$index];
+ $data{'other_data'}->[$index] =~ s/\s+$//s;
+ $data{'other_data'}->[$index] = "<package ".$data{'other_data'}->[$index];
+
+ $seen_rpms{$filename} = 1;
+ $number_packages += 1;
+ $valid_pkgids->{$pkgid} = 1;
+ } else {
+ undef $data{'primary_data'}->[$index];
+ undef $data{'filelists_data'}->[$index];
+ undef $data{'other_data'}->[$index];
+
+ $number_discarded += 1;
+ LOG("discarding data for $filename",3);
+ }
+ }
+ for my $index (0...$#{$data{'suse_data'}}) {
+ my $pkgid = $data{'suse_data'}->[$index];
+ $pkgid =~ s/^.*pkgid=\"([^\"]*)\".*$/$1/s;
+ if ($valid_pkgids->{$pkgid}) {
+ $data{'suse_data'}->[$index] =~ s/\s+$//s;
+ $data{'suse_data'}->[$index] = " <package ".$data{'suse_data'}->[$index];
+ } else {
+ undef $data{'suse_data'}->[$index];
+ LOG("discarding susedata for **$pkgid**",3);
+ }
+ }
+
+ LOG("re-using data for $number_packages rpms, discarded data for $number_discarded rpms (".($#{$data{'primary_data'}} + 1).")",3);
+
+ for my $current_rpm (keys %stat_all_rpms) {
+ my $short = $current_rpm;
+ $short =~ s/$repodata\/..\///;
+# next if ( $seen_rpms{$short} );
+# next if (S_ISLNK($stat_all_rpms{$current_rpm}->mode));
+ next if ( $current_rpm =~ /\.patch\.rpm$/ );
+ next if ( $current_rpm =~ /\.delta\.rpm$/ );
+
+ my $filebase = $current_rpm;
+ $filebase =~ s/^.*\///;
+
+ LOG("adding data for $current_rpm",3);
+
+ my %curpack = GetPackageDataFromRpm($current_rpm);
+ if ($config{'license_file'} && -f "$config{'license_file'}"){
+ my @lic_text=();
+ open (LIC,"< $config{'license_file'}");
+ while (<LIC>){
+ push @lic_text, xml_escape($_);
+ }
+ close(LIC);
+ $curpack{'confirmlic'}=join("\n",@lic_text);
+ }
+ my ($a1,$a2,$a3,$a4) = PackageDataToXML(\%curpack);
+
+ my $package_header = "<package pkgid=\"".$curpack{'checksum_sha'}."\" name=\"".$curpack{'name'}."\" arch=\"".$curpack{'rpmarch'}."\"\>\n";
+ $package_header .= "<version epoch=\"".$curpack{'epoch'}."\" ver=\"".$curpack{'version_only'}."\" rel=\"".$curpack{'release'}."\"/>\n";
+ my $suse_data_entry = $package_header;
+
+ # license to confirm...
+ my $conf_lic = "";
+ if ($curpack{'confirmlic'}){
+ $conf_lic = " <suse:license-to-confirm>\n".to_xml_utf8($curpack{'confirmlic'})."\n </suse:license-to-confirm>\n";
+ $suse_data_entry .= "<eula>\n".to_xml_utf8($curpack{'confirmlic'})."\n </eula>\n";
+ LOG("adding eula to $current_rpm to susedata",5);
+ }
+ # support keywords...
+ my %keyword_data=();
+ if ($config{'keywordfile'}){
+ my $keywords=ReadFileToHash("$config{'keywordfile'}") if ($config{'keywordfile'});
+ %keyword_data=%$keywords;
+ print "$filebase has keyword" if (defined($keyword_data{$filebase}));
+ if ($keyword_data{$curpack{'name'}}) {
+ for (@{$keyword_data{$curpack{'name'}}}) {
+ $suse_data_entry .= "<keyword>".$_."</keyword>\n";
+ }
+ }
+ }
+ push @{$data{'suse_data'}}, "$suse_data_entry</package>";
+ push @{$data{'primary_data'}}, "$a1$a2 <format>\n$a3 </format>\n$conf_lic </package>";
+ push @{$data{'filelists_data'}}, "$package_header$a4</package>";
+
+ my $other_entry;
+ for (@{$curpack{'changelogtime'}}) {
+ $other_entry .= "<changelog author=\"".to_xml_utf8(@{$curpack{'changelogname'}}[0])."\" date=\"".$_."\">";
+ $other_entry .= to_xml_utf8(@{$curpack{'changelogtext'}}[0])."</changelog>\n";
+ shift @{$curpack{'changelogname'}};
+ shift @{$curpack{'changelogtext'}};
+ }
+ push @{$data{'other_data'}}, "$package_header$other_entry</package>";;
+ $number_packages += 1;
+ }
+} else {
+ writePatchFile(\%config,$repodata,\@package_data);
+ GeneratePatchesXml($repodata);
+}
+
+GenerateRepomdXml($repodata);
+unlink "$repodata/repomd.xml.asc";
+unlink "$repodata/repomd.xml.key";
+
+if ( $config{'do_signing'} ) {
+ if ( $config{'sign_id'} ) {
+ system("gpg -a -b --default-key \"$config{'sign_id'}\" $repodata/repomd.xml");
+ } else {
+ system("gpg -a -b $repodata/repomd.xml");
+ $config{'sign_id'} = `gpg --verify $repodata/repomd.xml.asc 2>&1 | sed -ne "s/.* ID //p"`;
+ chomp ($config{'sign_id'});
+ }
+ system("gpg -a --export \"$config{'sign_id'}\" > $repodata/repomd.xml.key") if ( $config{'sign_id'} );
+}
+
+LOG("Finished creation for $repodata",1);
+close (OUTPUT_LOG);
+cleanup();
+
+#system("//bin/sign -d $patches_directory/repomd.xml");
+#system("cp $patchinfo_lib_dir/public-key $patches_directory/repomd.xml.key");
+
# Logfile:
-# $Log: createpatch,v $
+# $Log: createpatch.pl,v $
+# Revision 1.6 2009/04/27 09:02:13 lrupp
+# - enable keywords support
+# - added PackageDataToXML()
+# - use/fillup suse_data primary_data filelists_data
+#
+# Revision 1.5 2009/04/17 15:13:47 lrupp
+# - weekend :-)
+#
+# Revision 1.4 2009/04/17 08:15:28 lrupp
+# - new writePatchFile()
+# - remove validate option: we always produce valid metadata ;-)
+# - add logfile and lockfile
+#
+# Revision 1.3 2009/04/16 16:00:27 lrupp
+# - use %config for all configuration now
+# - be backwarts compatible: allow basedir as argument without option
+# - added configfile option
+#
+# Revision 1.2 2009/04/16 14:09:02 lrupp
+# - use temdir for update_repo
+# - new parsePrimaryXml()
+# - use Getopt for config options
+#
+# Revision 1.1 2009/04/16 13:34:01 lrupp
+# Initial revision
+#
# Revision 1.5 2007/12/11 13:42:01 lrupp
# - added "license-to-confirm"
# - check for createrepo and warn user if not exist
|