File rpm-rembrand.pl of Package rembrand
#! /usr/bin/perl -w
#
# rpm-rembrand.pl -- Remove Branding.
#
# (C) 2006 jw@suse.de
# Distribute under GPL 2.0
#
# A tool to find and remove openSUSE and Novell marks
# from an opensuse distribution.
#
# Use of this tool shall be limited to the extent allowed and/or
# required by the openSUSE End User License Agreement.
#
# One tool to rule them all, one tool to find them, one tool
# to change them all and in the tempdir bind them.
#
# We run rpm packages (passed as parameters on the command line) against
# a builtin list of images, and check where we have to replace something.
# replacements are taken from the directory indicated by the environment variable
# $REMBRAND_INPUT_DIR or - if missing - are created on the fly as blank white images
# of the same size.
# rpm packages are modified inplace.
# A list of substituted files and their original md5sums are written into the file
# named by $REMBRAND_ORIG_LIST or (orig_list.txt if missing.)
# This list can later be used to verify that a product was indeed debranded.
#
# rembrand.pl
#
# 2006-11-19, V0.1, jw -- unpacking, checking, replacing done.
# cannot re-pack RPMs yet.
# 2006-11-21, V0.2, jw -- added option parser and usage.
# 2006-11-22, V0.3, jw -- added loop around rpminjectfile. Ready for testing.
# 2006-11-24, V0.4, jw -- added more icons. optional flag added.
# emtpy.svg, empty.eps, empty.mng added as defaults.
# svg, svgz, bmp, gif, mng, eps not yet supported.
# 2006-11-30, V1.0, jw -- print a usage with suggested package list when
# called without parameters. Small bugfixes.
# svg, svgz, bmp, gif, mng, eps added.
# 2006-11-30, V1.1, jw -- eps support added.
# 2006-12-01, V1.1a, jw -- creating missing directories.
# 2006-12-01, V1.1b, jw -- added missing mng files in kdebase3
# 2006-12-04, V1.1c, jw -- added option -a, added titlebar_decor.png
# Picks RPMs from a directory, if given a directory
# 2006-12-04, V1.1d, jw -- added some gnome splash and wallpapers.
# Typos in matching opensuse-updater kdebas3-SuSE.
# 2006-12-05, V1.2, jw -- Some white PNG's were falsly flagged optional.
# Option -n added. One level of magic subdirectory
# expansion is now supported.
#
# TODO:
#------
# * kicker/ui/kmenubase.ui contains a hexadecimal representation of 'image0',
# (found in kdebase3/kickoff.diff). I believe this is the 'openSUSE' button in the
# kickoff menue.
# This graphics gets compiled into the binary. No chance to postprocess it.
#
# * what about initrd?
#
# * modifying buildrelease is a dangerous default. It break e.g. OOo dependencies.
# Do we want to do that?
#
use Digest::MD5;
use Data::Dumper;
$Data::Dumper::Terse = 1;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Indent = 1;
my $version = '1.2';
my $cp = '/bin/cp';
my $rpm = '/bin/rpm';
my $cpio = '/usr/bin/cpio --quiet';
my $file = '/usr/bin/file';
my $gzip = '/usr/bin/gzip -f';
my $djpeg = '/usr/bin/djpeg';
my $cjpeg = '/usr/bin/cjpeg';
my $ppmmake = '/usr/bin/ppmmake';
my $ppmtobmp = '/usr/bin/ppmtobmp 2> /dev/null';
my $ppmtogif = '/usr/bin/ppmtogif 2> /dev/null';
my $pnmtopng = '/usr/bin/pnmtopng --quiet';
my $rpminjectfile = 'rpminjectfile --quiet'; # not yet packaged?
my $defaults_dir = '/usr/share/rembrand/defaults';
my $verbose = 1;
my $input_dir = $ENV{REMBRAND_INPUT_DIR};
my $output_dir = $ENV{REMBRAND_OUTOUT_DIR} || "/tmp/rembrand-$$/%s";
my $md5list_file = $ENV{REMBRAND_ORIG_LIST} || 'orig_list.txt';
my $rpmout_dir = $ENV{REMBRAND_RPMOUT_DIR} || 'rpm_out';
my $dump_only = 0;
my $inputlist_file;
my $release_pat = "%s.mod";
my $user = $ENV{LOGNAME} || 'unknown';
my $vendor_pat = "$user (originally from %s)";
my $bgcolor = "#777777"; # a medium gray.
my $skip_optional = 1;
my $noexec = 0;
while (defined(my $arg = shift))
{
if ($arg =~ m{^-v}) { $verbose++; }
elsif ($arg =~ m{^-q}) { $verbose = 0; }
elsif ($arg =~ m{^--$}) { last; }
elsif ($arg =~ m{^-i}) { $inputlist_file = shift; }
elsif ($arg =~ m{^-d}) { $dump_only++; }
elsif ($arg =~ m{^-a}) { $skip_optional = 0; }
elsif ($arg =~ m{^-o}) { $md5list_file = shift; }
elsif ($arg =~ m{^-I}) { $input_dir = shift; }
elsif ($arg =~ m{^-O}) { $output_dir = shift; }
elsif ($arg =~ m{^-R}) { $rpmout_dir = shift; }
elsif ($arg =~ m{^-b}) { $bgcolor = shift; }
elsif ($arg =~ m{^-n}) { $noexec++; }
elsif ($arg =~ m{^-rel}) { $release_pat = shift; }
elsif ($arg =~ m{^-ven}) { $vendor_pat = shift; }
elsif ($arg =~ m{^-}) { usage("unknown option '$arg'"); }
else { unshift @ARGV, $arg; last }
}
$md5list_file = "$rpmout_dir/$md5list_file" unless $md5list_file =~ m{/};
# flags:package:path
# flags o=optional
my @imagelist = qw(
:opensuse-updater:/opt/kde3/share/icons/hicolor/*/apps/opensuse*updater.png
:opensuse-updater:/opt/kde3/share/apps/opensuseupdater/pics/suse_logo.png
o:opensuse-updater:/opt/kde3/share/apps/opensuseupdater/pics/suse_green.png
o:opensuse-updater:/opt/kde3/share/apps/opensuseupdater/pics/suse_yellow.png
o:opensuse-updater:/opt/kde3/share/apps/opensuseupdater/pics/suse_red.png
o:opensuse-updater:/opt/kde3/share/apps/opensuseupdater/pics/opensuseupdater.png
:kdebase3-SuSE:/opt/kde3/share/icons/*/*/apps/suse.png
:kdebase3-SuSE:/opt/kde3/share/icons/*/*/apps/susehelpcenter.png
:kdebase3-SuSE:/opt/kde3/share/icons/*/*/apps/kmenu.png
:kdebase3-SuSE:/opt/kde3/share/icons/*/*/apps/SuSEmenu.png
:kdebase3-SuSE:/opt/kde3/share/icons/*/*/apps/SuSEconf.png
:kdebase3-SuSE:/opt/kde3/share/icons/*/*/apps/SuSElogo1.png
:kdebase3-SuSE:/opt/kde3/share/icons/*/*/apps/suse_doc.png
:kdebase3-SuSE:/opt/kde3/share/icons/*/*/apps/suse_link.png
:kdebase3-SuSE:/opt/kde3/share/icons/*/*/apps/suse_sdb.png
:kdebase3-SuSE:/opt/kde3/share/icons/*/*/apps/gnome-suse.png
:kdebase3-SuSE:/opt/kde3/share/apps/SUSEgreeter/bg-head-800x58.png
:kdebase3-SuSE:/opt/kde3/share/apps/SUSEgreeter/cr64-novell.png
:kdebase3-SuSE:/opt/kde3/share/apps/SUSEgreeter/cr64-suse.png
:kdebase3-SuSE:/opt/kde3/share/apps/SUSEgreeter/suse-logo.png
:kdebase3-SuSE:/opt/kde3/share/apps/SUSEgreeter/misc48.png
:kdebase3-SuSE:/opt/kde3/share/apps/SUSEgreeter/online_update.png
:kdebase3-SuSE:/opt/kde3/share/apps/SUSEgreeter/system_update.png
:kdebase3-SuSE:/opt/kde3/share/apps/SUSEgreeter/suse_doc.png
:kdebase3-SuSE:/opt/kde3/share/apps/SUSEgreeter/YaST.png
:kdebase3-SuSE:/opt/kde3/share/icons/hicolor/scalable/apps/SuSEmenu.svgz
:kdebase3-SuSE:/opt/kde3/share/apps/ksplash/Themes/ksplashx-suse/1600x1200/spinner.png
:kdebase3-SuSE:/opt/kde3/share/apps/ksplash/Themes/ksplashx-suse/Preview.png
:kdebase3-SuSE:/opt/kde3/share/apps/kdm/themes/SUSE/opensuse.png
:kdebase3-SuSE:/opt/kde3/share/apps/kdm/themes/SUSE/suse.png
:kdebase3-SuSE:/opt/kde3/share/apps/kdm/themes/SUSE/novell.png
:kdebase3-SuSE:/opt/kde3/share/apps/ksplash/Themes/ksplashx-suse/1600x1200/title.png
o:kdebase3-SuSE:/opt/kde3/share/apps/kdm/themes/SUSE/Background.jpeg
o:kdebase3-SuSE:/opt/kde3/share/apps/kdm/themes/SUSE/screenshot.jpg
o:kdebase3-SuSE:/opt/kde3/share/apps/kdm/themes/SUSE/screenshot.png
o:kdebase3-SuSE:/opt/kde3/share/apps/kicker/wallpapers/SuSE.png
o:kdebase3-SuSE:/etc/opt/kde3/share/doc/HTML/en/common/suse.png
o:kdebase3-SuSE:/opt/kde3/share/apps/ksplash/Themes/ksplashx-suse/1600x1200/background.png
o:kdebase3-SuSE:/opt/kde3/share/apps/kthememanager/themes/SUSE_Default/SUSE_Default.preview.png
o:kdebase3-SuSE:/opt/kde3/share/icons/hicolor/*/apps/suse_portal.png
o:kdebase3-SuSE:/opt/kde3/share/icons/hicolor/*/apps/suse_tour.png
:kdebase3:/opt/kde3/share/apps/kicker/pics/kmenu_basic.mng
:kdebase3:/opt/kde3/share/apps/kicker/pics/kmenu_flipped.mng
:kdebase3:/opt/kde3/share/apps/kicker/pics/kmenu_vertical.mng
:gnome2-SuSE:/opt/gnome/share/dist/icons/suse-yast.png
:gnome2-SuSE:/opt/gnome/share/dist/icons/susehelpcenter.png
:gnome2-SuSE:/opt/gnome/share/pixmaps/suse-menustripe.png
:gnome2-SuSE:/opt/gnome/share/dist/icons/SuSElogo1.png
:gnome2-SuSE:/opt/gnome/share/dist/icons/SuSEmenu.png
:gnome2-SuSE:/opt/gnome/share/dist/splash/dist-splash.png
:gnome2-SuSE:/opt/gnome/share/dist/wallpapers/dist-Desktop_*.jpg
:gnome2-SuSE:/opt/gnome/share/gdm/themes/GDM-SuSE/opensuse.png
:gnome2-SuSE:/opt/gnome/share/gdm/themes/GDM-SuSE/suse.png
:gnome2-SuSE:/opt/gnome/share/gdm/themes/GDM-SuSE/novell.png
o:gnome2-SuSE:/opt/gnome/share/dist/icons/assis.png
o:gnome2-SuSE:/opt/gnome/share/dist/icons/32x32.png
o:gnome2-SuSE:/opt/gnome/share/gdm/themes/GDM-SuSE/Background.jpeg
o:gnome2-SuSE:/opt/gnome/share/gdm/themes/GDM-SuSE/screenshot.png
o:gnome-icon-theme:/opt/gnome/share/icons/gnome/*/apps/susehelpcenter.png
o:tango-icon-theme:/opt/gnome/share/icons/Tango/*/apps/susehelpcenter.png
:gnome-desktop:/opt/gnome/share/pixmaps/gnome-suse.png
:compiz:/usr/share/compiz/opensuse.png
:susehelp:/usr/share/susehelp/img/suse_150.png
o:susehelp:/usr/share/susehelp/img/susehelpcenter.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/control-center/title-bar-right.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/wizard/title-bar-right.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/desktops/gnome.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/desktops/kde.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/misc.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/misc2.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/SuSEconf.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/susehelpcenter.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/SuSEmenu.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/bootdisk_create.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-bootfloppy.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/suse_doc.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-desktop-select.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-online_update.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-software.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-sw_single.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-sw_source.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-sysconfig.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-update.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-you_server.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/YaST.png
:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/testpage/logo.eps
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/suse_link.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/suse_portal.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/suse_tour.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-backup.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-bootfloppy.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-cd-creator.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-cd_update.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-checkmedia.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-dirinstall.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-xen-dirinstall.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-instserver.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-release-notes.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-restore.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/icons/*/apps/yast-x11.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/control-center/title-bar-left.png
o:yast2-theme-openSUSE:/usr/share/YaST2/theme/openSUSE/wizard/title-bar-left.png
:bootsplash-theme-SuSE:/etc/bootsplash/themes/SuSE/animations/spinner.mng
:bootsplash-theme-SuSE:/etc/bootsplash/themes/SuSE/images/logo.mng
:bootsplash-theme-SuSE:/etc/bootsplash/themes/SuSE/images/logov.mng
o:bootsplash-theme-SuSE:/etc/bootsplash/themes/SuSE/images/bootsplash-*.jpg
o:bootsplash-theme-SuSE:/etc/bootsplash/themes/SuSE/images/silent-*.jpg
o:desktop-data-SuSE:/usr/share/wallpapers/suse*.jpg
:OpenOffice_org:/usr/lib/ooo-2.0/program/openabout_suse.bmp
:OpenOffice_org:/usr/lib/ooo-2.0/program/openintro_suse.bmp
:gnome-icon-theme:/opt/gnome/share/icons/*/scalable/apps/im-nov.svg
o:gnome-icon-theme:/opt/gnome/share/icons/*/scalable/apps/susehelpcenter.svg
o:gnome-icon-theme:/opt/gnome/share/icons/*/gnome/scalable/places/novell-button.svg
o:sax2-gui:/usr/share/xfine/figures/minisuse.gif
o:sax2-gui:/usr/share/xfine/figures/suse.gif
:kwin-decor-suse2:/opt/kde3/share/apps/kwin/pics/titlebar_decor.png
);
if ($dump_only)
{
print join("\n", @imagelist). "\n";
exit 0;
}
if ($inputlist_file)
{
open IN, "<$inputlist_file" or die "cannot read inputlist_file $inputlist_file: $!\n";
chomp (@imagelist = <IN>);
close IN;
}
my %orig_list;
my %imagelist;
for my $i (@imagelist)
{
if ($i =~ m{^([^:]*):([^:]+):\s*(\S+)})
{
$orig_list{$2}{$3} .= $1;
}
}
for my $p (keys %orig_list)
{
for my $i (keys %{$orig_list{$p}})
{
push @{$imagelist{$p}}, $i;
my $flags = $orig_list{$p}{$i};
$orig_list{$p}{$i} = {missing => 1};
$orig_list{$p}{$i}{flags} = $flags if $flags;
}
}
my $rpm_filter_pat = join '|', map { "\Q$_\E" } sort keys %orig_list;
$rpm_filter_pat = qr{^($rpm_filter_pat)(-\d.*)?\.rpm$};
my %subdir;
## look for directories, replace them by matching rpm files.
for my $i (reverse 0..$#ARGV)
{
next unless -d $ARGV[$i];
opendir DIR, $ARGV[$i] or die "opendir($ARGV[$i]) failed: $!\n";
my @rpms = grep { /$rpm_filter_pat/ } readdir DIR;
rewinddir DIR;
my @subdirs = grep { !/^\./ and -d "$ARGV[$i]/$_" } readdir DIR;
closedir DIR;
# Now here comes a trick:
# we allow for one level of subdirecories before taking the RPM.
# If that produces any RPMs, we record the single subdirectoy name
# in %subdir, so that we can maintain such a subdriectory
# in $rpm_out directory.
#
for my $d (@subdirs)
{
my $dir = "$ARGV[$i]/$d";
opendir DIR, $dir or die "opendir($dir) failed: $!\n";
my @sub_rpms = map { "$d/$_" } grep { /$rpm_filter_pat/ } readdir DIR;
closedir DIR;
$subdir{"$ARGV[$i]/$_"} = $d for @sub_rpms;
push @rpms, @sub_rpms;
}
splice @ARGV, $i, 1, map { "$ARGV[$i]/$_" } @rpms;
}
unless (scalar @ARGV)
{
my @pkg = sort keys %orig_list;
usage("No RPM package names or no RPMs found in given directories.\nSuggested packages to process:\n\n\t*/" . join(".rpm */", @pkg) . ".rpm\n\n");
}
my $new = 0;
for my $rpm_file (@ARGV)
{
my $name = rpm_header($rpm_file, 'name');
if ($imagelist{$name})
{
$new += rembrand_rpm($rpm_file, $name, $imagelist{$name}, $input_dir ? (sprintf $input_dir, $name) : undef, $subdir{$rpm_file});
}
else
{
print "$rpm_file ($name) skipped\n" if $verbose;
}
}
## no empty hashes, please.
my $missing;
for my $p (keys %orig_list)
{
my $v = $orig_list{$p};
for my $k (keys %$v)
{
$missing->{$p}++ if $v->{$k}{missing} and ($v->{$k}{flags}||'') !~ m{o};
delete $v->{$k} unless keys %{$v->{$k}};
}
}
## okay, record what we did.
mkdir_pf($md5list_file);
open OUT, ">$md5list_file" or die "after all, cannot write $md5list_file: $!";
print OUT Dumper \%orig_list;
close OUT or die "could not write $md5list_file: $!";
printf "%d other RPMs need processing.\n", scalar(keys %$missing) if $verbose and $missing;
print "Original files stored in '$output_dir'\n" if $verbose;
print "$new files created in '$input_dir'\n" if $input_dir and $verbose;
print "$md5list_file written.\n" if $verbose;
exit 0;
#################################################################
sub rembrand_rpm
{
my ($pkg, $name, $images, $path, $subdir) = @_;
my @l = rpm_filelist($pkg);
$name = $pkg unless $name;
$name =~ s{[/\.]}{_}g;
my $create_count = 0;
my @files_o = ();
my @files = ();
for my $i (@$images)
{
my $re = "^$i\$";
$re =~ s{\*}{.*}g; # glob2re
my @m = grep { /$re/ } @l;
delete $orig_list{$name}{$i}{missing} if scalar @m; # delete here, in case it is a glob.
if (($orig_list{$name}{$i}{flags}||'') =~ m{o})
{
push @files_o, @m;
}
else
{
push @files, @m;
}
}
push @files, @files_o unless $skip_optional;
unless (scalar @files)
{
printf "no matching files in $pkg, expected at least %d\n", scalar @$images
unless scalar(@files_o) => scalar(@$images);
return;
}
printf "$pkg: %d files.\n", scalar(@files) if $verbose;
printf "(%d optional files skipped.)\n", scalar @files_o if $verbose and $skip_optional and scalar @files_o;
my $root = sprintf "$output_dir", $name; # where created files go.
mkdir_pf("$root/.");
unrpm($pkg, $root, \@files);
for my $i (@files)
{
my $f = "$root/$i";
if (-f $f)
{
delete $orig_list{$name}{$i}{missing}; # may already be deleted
delete $orig_list{$name}{$i}{listed}; # may not yet exist...
$orig_list{$name}{$i}{orig_size} = -s $f;
$orig_list{$name}{$i}{orig_md5sum} = file_md5sum($f);
my $geom = file_imgsize($f);
unlink $f or die "unlink $f failed: $!";
$create_count += file_substitute($f, $path, $i, $geom);
}
else
{
$orig_list{$name}{$i} = {missing => 1, listed => $pkg } unless $orig_list{$name}{$i}{size};
# not packaged here, let us hope it is in some other rpm,
# if not, report it as missing although listed in the rpm package.
}
}
$subdir = $subdir ? "/$subdir" : '';
my ($outname) = $pkg =~ m{([^/]+)$};
print "$pkg -> $rpmout_dir$subdir/$outname\n";
mkdir_pf("$rpmout_dir$subdir/$outname");
my $tmp_pkg_out = "$rpmout_dir$subdir/$outname-$$";
my $tmp_pkg_in = "$pkg";
my $rel = sprintf $release_pat, ($release_pat =~ m{%}) ? rpm_header($pkg, 'release') : undef;
my $ven = sprintf $vendor_pat, ($vendor_pat =~ m{%}) ? rpm_header($pkg, 'vendor') : undef;
die "Can not use single-quotes (') in release or vendor string.\n"
if $rel =~ m{'} or $ven =~ m{'};
my $opt = '';
$opt .= "--release '$rel' " if length $rel;
$opt .= "--vendor '$ven' " if length $ven;
for my $i (@files)
{
print STDERR "." if $verbose;
run_cmd("$rpminjectfile $opt '$tmp_pkg_in' '$tmp_pkg_out' '$root/$i' '$i'");
$tmp_pkg_in = "$tmp_pkg_out-in";
$opt = '';
rename "$tmp_pkg_out", "$tmp_pkg_in" or die "rename($tmp_pkg_out) failed: $!\n";
}
print STDERR "\n" if $verbose;
rename "$tmp_pkg_in", "$rpmout_dir/$outname" or die "rename($tmp_pkg_in) failed: $!\n";
return $create_count;
}
sub rpm_header
{
my ($file, $name) = @_;
my $cmd = "$rpm -qp --qf '%{$name}' '$file'";
open RPM, "$cmd|" or die "cannot run $cmd: $!";
my $r = join '', <RPM>;
close RPM or die "failed to run $cmd: $!";
die "$cmd: failed\n" unless length $r;
return $r;
}
sub rpm_filelist
{
my ($file, $name) = @_;
my $cmd = "$rpm -qpl '$file'";
open RPM, "$cmd|" or die "cannot run $cmd: $!";
my @r = <RPM>;
chomp @r;
close RPM or die "failed to run $cmd: $!";
die "$cmd: failed\n" unless scalar @r;
return @r;
}
# extract an RPM file, optionally limited to certain files.
#
sub unrpm
{
my ($pkg, $dir, $files) = @_;
my $pattern = '';
if ($files)
{
# we put leading dots in front of the file name list.
# This is how rpm2cpio exports the names.
# And we escape '?' and '*' because cpio reads shell globbing
# patterns, not files.
$pattern = " '." . join("' '.", @$files) . "'" if $files;
$pattern =~ s{([\*\?])}{\\$1}g;
}
run_cmd("rpm2cpio '$pkg' | (cd $dir && $cpio -uidm$pattern)");
}
sub file_md5sum
{
my ($file) = @_;
my $md5 = Digest::MD5->new;
open IN, "<$file" or die "cannot read $file:$!";
$md5->addfile(*IN);
close IN;
return $md5->hexdigest;
}
sub file_imgsize
{
my ($img) = @_;
my $magic = `$file -b '$img'`;
if ($magic =~ m{PNG image data,\s+(\d+)\s*x\s*(\d+)})
{
return { t => 'png', w => $1, h => $2 };
}
if ($magic =~ m{^JPEG image data})
{
my $cmd = "$djpeg '$img' | head -2";
my $r = `$cmd`;
if ($r =~ m{^P\d\s+(\d+)\s+(\d+)}s)
{
return { t => 'jpeg', w => $1, h => $2 };
}
}
if ($magic =~ m{^PC bitmap data,.*\s(\d+)\s*x\s*(\d+)\s*x\s*(\d+)\s*$})
{
return { t => 'bmp', w => $1, h => $2 };
}
if ($magic =~ m{^MNG video data,\s+(\d+)\s*x\s*(\d+)})
{
return { t => 'mng', w => $1, h => $2 };
}
if ($magic =~ m{^GIF image data,.*\s+(\d+)\s*x\s*(\d+)})
{
return { t => 'gif', w => $1, h => $2 };
}
if ($magic =~ m{^XML} and $img =~ m{\.svg}i)
{
return { t => 'svg', w => 1, h => 1 };
}
if ($magic =~ m{^PostScript document text .* type EPS$})
{
return { t => 'eps', w => 1, h => 1 };
}
if ($magic =~ m{^gzip compressed} and $img =~ m{\.svgz}i)
{
return { t => 'svgz', w => 1, h => 1 };
}
return { t => $magic };
}
sub file_substitute
{
my ($file, $dir, $name, $img) = @_;
my $cmd;
my $substitute = "$dir/$name" if $dir;
if ($substitute && -f $substitute)
{
run_cmd("$cp '$substitute' '$file'");
return 0;
}
print "creating blank $name $img->{w}x$img->{h}\n" if $verbose;
$cmd = qq{$ppmmake "$bgcolor" $img->{w} $img->{h}};
if ($img->{t} eq 'jpeg')
{
$cmd .= "|$cjpeg > '$file'";
}
elsif ($img->{t} eq 'png')
{
$cmd .= "|$pnmtopng > '$file'";
}
elsif ($img->{t} eq 'gif')
{
$cmd .= "|$ppmtogif > '$file'";
}
elsif ($img->{t} eq 'bmp')
{
$cmd .= "|$ppmtobmp > '$file'";
}
elsif ($img->{t} eq 'mng' || $img->{t} eq 'eps' || $img->{t} eq 'svg')
{
$cmd = "cp '$defaults_dir/empty.$img->{t}' '$file'";
}
elsif ($img->{t} eq 'svgz')
{
$cmd = "$gzip -c '$defaults_dir/empty.svg' > '$file'";
}
else
{
die "cannot create unknown image type $img->{t},\nplease update file_imgsize()";
}
run_cmd($cmd);
if ($substitute)
{
mkdir_pf($substitute);
run_cmd("$cp '$file' '$substitute'"); # expose the white image
}
return 1;
}
sub run_cmd
{
my ($cmd) = @_;
print "\n$cmd\n" if $verbose > 1 or $noexec;
return if $noexec;
system "$cmd" and die "$cmd failed: $@ $!";
}
##
## this version of mkdir_pf handles relative and absolute paths.
##
sub mkdir_pf
{
my ($path) = @_;
my @dirs = split "/", $path;
pop @dirs; # nuke trailing filename
$path = ($dirs[0] eq '') ? '/' : '';
for my $d (@dirs)
{
$path .= "$d/";
mkdir $path, 0777 or die "mkdir $path failed: $!" unless -d $path;
}
return 1; # success, be mkdir compatible
}
## rm_rf -- recursive file tree delete
##
## fn is a predicate, that receives a file path name as parameter.
## If fn returns zero for a file object, it is excluded from removal.
## Directories are removed unconditionally, if we can empty them first.
##
## Directories are traversed in reverse alphabetical order;
## thus dotfiles usually still exist while fn is called for other files.
## fn defaults to a true value.
## Adds write perm on the dir, if unlink/rmdir fails.
##
## Caution: No taint checks here.
##
## Does not follow symlinks for opendir; this could carry us out of the tree.
## Tries unlink after rmdir, just in case it is a weird directory symlik.
##
## rm_rf fails on very deep directory structures. It should
## - chdir downward (remembering inode numbers),
## - clear one level,
## - cd(..), check if
## - inode matches, clear one level
## - or, if it does not match redo from start.
## - done.
##
sub rm_rf
{
my ($path, $fn, $comment) = @_;
$comment ||='';
if (!-l $path and opendir DIR, $path)
{
my @e = grep { !/^(\.|\.\.)$/ } readdir DIR;
closedir DIR;
rm_rf("$path/$_") for reverse sort @e;
$path = $1 if $path =~ m{^(.*)$};
print "rmdir $path\n" if $verbose > 2;
unless (unlink $path or rmdir $path)
{
my $dir; $dir = $1 if $path =~ m{^(.*/).};
$dir = '.' unless defined $dir;
chmod 0777, $dir;
return if unlink $path or rmdir $path;
warn "rm_rf: rmdir($path) failed: $!\n";
}
}
else
{
if (!$fn or &$fn($path))
{
$path = $1 if $path =~ m{^(.*)$}; # UNTAINT. brute
print "unlink $path\n" if $verbose > 2;
unless (unlink $path)
{
my $dir; $dir = $1 if $path =~ m{^(.*/).};
$dir = '.' unless defined $dir;
chmod 0777, $dir;
return if unlink $path;
warn "rm_rf: unlink($path) failed: $!\n";
}
}
else
{
print "rm_rf: skip $path, $comment\n" if $verbose > 2;
}
}
}
sub usage
{
my ($msg) = @_;
$msg .= "\n" if $msg and $msg !~ m{\n$};
$msg = "Error: " . $msg if $msg;
$input_dir ||= "<undefined>";
print qq{
rembrand.pl version $version
$msg
Usage:
$0 [options] *.rpm [directory ...]
Valid options are:
-v Be more verbose. Default: $verbose
-q Be quiet.
-a Replace all files. Default: skip those marked as optional.
-b bgcolor Color used for blank images. Default: "$bgcolor".
-i input_list File with substitution list. Defaults to a built in list.
-d Dump built in substitution list and exit.
-n Do nothing. Just print the command that would execute.
-o out_list Write filenames substituted to listfile.
Default $md5list_file or \$REMBRAND_ORIG_LIST
-I input_dir A root directory containing replacement files in their respective
subdirectories. Default $input_dir or \$REMBRAND_INPUT_DIR
The name of the current RPM is substituted for '%s'.
-O output_dir A root directory containing the original versions of the replaced files.
Default $output_dir or \$REMBRAND_OUTPUT_DIR
The name of the current RPM is substituted for '%s'.
-R rpmout_dir Directory, where to write the modified RPM packages.
Default $rpmout_dir or \$REMBRAND_RPMOUT_DIR
-rel release_nr Modify RPM header 'Release'. Default : $release_pat
The release of the current RPM is substituted for '%s'.
-ven vendor_str Modify RPM header 'Vendor'. Default : $vendor_pat
The vendor of the current RPM is substituted for '%s'.
Blank files are used for missing files below input_dir.
These are also placed into input_dir.
};
exit 0;
}