Linux ip-172-26-7-228 5.4.0-1103-aws #111~18.04.1-Ubuntu SMP Tue May 23 20:04:10 UTC 2023 x86_64
Your IP : 3.144.227.3
#!/usr/bin/perl -w
# Copyright (C) 2008-2010 Modestas Vainius <modax@debian.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
use strict;
use warnings;
use File::Spec;
use File::Basename qw();
use File::Copy qw();
use Getopt::Long qw(:config noignore_case);
use Dpkg::ErrorHandling;
use Dpkg::Arch qw(get_host_arch get_valid_arches);
use Dpkg::Version;
use Dpkg::IPC;
use Debian::PkgKde::SymbolsHelper::SymbolFile;
use Debian::PkgKde::SymbolsHelper::SymbolFileCollection;
use Debian::PkgKde::SymbolsHelper::Patching;
######## Option processing ##################
my $opt_out;
my $opt_backup;
my $opt_in;
my $opt_package;
my $opt_arch = get_host_arch();
my $opt_version;
my $opt_verbose;
sub verify_opt_arch {
my ($opt, $arch) = @_;
error("unknown architecture: $arch")
unless grep /^\Q$arch\E$/, get_valid_arches();
$opt_arch = $arch;
}
sub verify_opt_in {
my ($opt, $input) = @_;
error("input file ($input) does not exit") unless (-f $input);
$opt_in = $input;
}
sub get_common_options {
my $args = shift;
my (%args, %res);
map { $args{$_} = 1 } split(//, $args);
$res{"output|o:s"} = \$opt_out if ($args{o});
$res{"backup|b!"} = \$opt_backup if ($args{b});
$res{"input|i=s"} = \&verify_opt_in if ($args{i});
$res{"package|p=s"} = \$opt_package if ($args{p});
$res{"architecture|a=s"} = \&verify_opt_arch if ($args{a});
$res{"version|v:s"} = \$opt_version if ($args{v});
$res{"verbose|V!"} = \$opt_verbose if ($args{V});
return %res;
}
sub check_mandatory_options {
my ($args, $msg, @opts) = @_;
my %args;
$msg = "" if (!defined $msg);
map { $args{$_} = 1 } split(//, $args);
error("input file option (-i) is mandatory $msg") if (!$opt_in && $args{i});
error("output file option (-o) is mandatory $msg") if (!$opt_out && $args{o});
error("backup option (-b) is mandatory $msg") if (!defined $opt_backup && $args{b});
error("package name option (-p) is mandatory $msg") if (!$opt_package && $args{p});
error("architecture option (-a) is mandatory $msg") if (!$opt_arch && $args{a});
error("version option (-v) is mandatory $msg") if (!$opt_version && $args{v});
while (@opts) {
my $val = shift @opts;
my $msg = shift @opts;
error($msg) if (!$val);
}
return 1;
}
sub sanitize_version {
my ($ver, $default, $prompt) = @_;
my %choices = ( v => $ver );
my $answer;
if ($ver =~ m/^(.+)-(.*[^~])$/) {
# Non-native debian package
$choices{u} = $1;
}
$choices{'d'} = $choices{v}.'~';
if ($prompt && -t STDIN) {
regular_print("Do you want to assign one version to all new symbols %s?",
($default) ? "(default is '".uc($default)."')" : "(ENTER if no)");
print STDERR sprintf("What version? [ %s / enter other ]: ", join(" / ",
(map { sprintf("%s = %s", ($_ eq ($default || '')) ? uc($_) : $_, $choices{$_}) }
grep { exists $choices{$_} } qw(u d v))));
$answer = lc(<STDIN>);
chop $answer;
$answer = $choices{$answer} if exists $choices{$answer};
}
if (! $answer) {
$answer = (defined $default) ? $choices{$default} : $default;
}
return (wantarray) ? (\%choices, $answer) : $answer;
}
############### Common subroutines #################
sub regular_print {
my $msg = shift;
print STDERR sprintf($msg, @_), "\n";
}
sub verbose_print {
regular_print(@_) if $opt_verbose;
}
sub find_package_symbolfile_path {
my ($package, $arch) = @_;
my @PATHS = (
"debian/$package.symbols.$arch",
"debian/symbols.$arch",
"debian/$package.symbols",
"debian/symbols"
);
for my $path (@PATHS) {
return $path if (-f $path);
}
return undef;
}
sub out_symfile {
my ($symfile, %opts) = @_;
return 1 unless defined $symfile;
my $out_fh;
my $out_file = $opt_out;
my $backup = $opt_backup;
if (defined $out_file) {
# If -o is specified, dump to STDOUT if it does not have an argument or
# save to specified file otherwise.
$out_fh = *STDOUT unless $out_file;
} else {
# If -o is not specified, save to the input file.
$out_file = $opt_in;
}
if ($out_file) {
File::Copy::copy($out_file, $out_file.'~') if ($backup && -r $out_file);
$symfile->save($out_file, template_mode => 1, with_deprecated => 1, %opts);
} elsif ($out_fh) {
$symfile->output($out_fh, template_mode => 1, with_deprecated => 1, %opts);
} else {
error("output file could not be determined");
}
return 0;
}
sub tweak_symbol {
my ($sym, $ver) = @_;
$sym->handle_min_version($ver);
}
sub get_all_packages {
open(CONTROL, "<", "debian/control") or syserr("unable to open debian/control");
my @packages;
while (<CONTROL>) {
chop;
if (/^Package:\s+(\S+)/) {
push @packages, $1;
}
}
close(CONTROL);
return @packages;
}
sub kill_dupes {
my $prev;
my @res;
foreach my $a (sort @_) {
if (!defined $prev || $prev ne $a) {
push @res, $a;
}
$prev = $a;
}
return @res;
}
sub print_changes_list {
my ($changes, $header, $print_header_sub, $print_sub) = @_;
if (@$changes) {
&$print_header_sub($header);
foreach my $info (@$changes) {
if ($info =~ /^\s/) {
# Symbol
&$print_sub(" %s", $info);
} else {
# Soname
&$print_sub(" SONAME: %s", $info);
}
}
}
}
# Load patches from supplied files
sub load_patches {
my @files=@_;
my @patches;
sub _grep_patches {
my @patches;
foreach my $patch (@_) {
if (!$patch->has_info()) {
verbose_print("* patch '%s' discarded due to absence of the info header",
$patch->get_name());
next;
}
push @patches, $patch;
}
return @patches;
};
if (@files) {
regular_print "Looking for patches and reading them ....";
} else {
return ();
}
for my $f (@files) {
if (-d $f) {
opendir(DIR, $f) or error("unable to open directory with patches: $f");
while (my $filename = readdir(DIR)) {
next if ($filename =~ /^.{1,2}$/);
my $file = File::Spec->catfile($f, $filename);
push @patches, _grep_patches(parse_patches_from_file($file));
}
closedir DIR;
} elsif (-r $f) {
push @patches, _grep_patches(parse_patches_from_file($f));
} elsif ($f eq "-") {
push @patches, _grep_patches(parse_patches_from_handle(\*STDIN));
}
}
return @patches;
}
sub patch_symfile {
my ($package, $version, $infile, $basefile, $patches, $confirmed_arches) = @_;
my @patches = @$patches;
my @confirmed_arches = @$confirmed_arches;
$basefile = $infile unless $basefile;
@patches = grep { $_->{package} eq $package } @patches;
error("no valid patches found for the '%s' package", $package)
unless (@patches);
# Load input symfile
my $symfile = Debian::PkgKde::SymbolsHelper::SymbolFile->new(
file => $infile, arch => $opt_arch
);
my $curversion = $symfile->get_confirmed_version();
unless ($curversion) {
error("input symbol file template must have 'SymbolsHelper-Confirmed' header");
}
my $base_symfile = $symfile;
if ($basefile ne $infile) {
$base_symfile = Debian::PkgKde::SymbolsHelper::SymbolFile->new(
file => $basefile, arch => $opt_arch
);
}
# Patch the base template with our patches and pick symbol files with
# the highest version for each architecture.
my (%psymfiles, %pversions);
my $latest_ver;
regular_print("Patching symbol file '%s' with supplied patches ...",
$base_symfile->{file});
my @psymfiles = $base_symfile->patch_template(@patches);
foreach my $patch (@patches) {
my $arch = $patch->{arch};
my $str = sprintf("* patch '%s' for %s ... ",
$patch->get_name(), $arch);
if ($patch->is_applied() && $psymfiles[0]->get_arch() eq $arch) {
regular_print($str . "OK.");
my $psymfile = shift @psymfiles;
if (!exists $pversions{$arch} ||
version_compare($patch->{version}, $pversions{$arch}) > 0)
{
$psymfiles{$arch} = $psymfile;
$pversions{$arch} = $patch->{version};
if (!defined $latest_ver ||
version_compare($patch->{version}, $latest_ver) > 0)
{
$latest_ver = $patch->{version};
}
}
} else {
warning($str . "FAILED.");
info("the patch has been ignored. Failure output below:\n" .
$patch->get_apply_output());
}
}
unless (keys %psymfiles) {
error("no valid symbol files could be loaded from the supplied patch files");
}
# Sanitize version
unless (defined $version) {
$version = sanitize_version($latest_ver, undef, 1);
}
if ($curversion && version_compare($version, $curversion) < 0) {
error("input symbol file template version (%s) is higher than the specified one (%s)",
$curversion, $version);
}
# Reset version if requested and drop patched symbol files which have
# lower version than original one
foreach my $arch (keys %psymfiles) {
my $psymfile = $psymfiles{$arch};
if ($version) {
$psymfile->set_confirmed($version, $psymfile->get_confirmed_arches());
}
my $pver = $psymfile->get_confirmed_version();
if (version_compare($pver, $curversion) < 0) {
warning("ignoring obsolete %s symbol file (its version (%s) < original (%s))",
$arch, $pver, $curversion);
delete $psymfiles{$arch};
delete $pversions{$arch};
}
}
error("no valid patched symbol files found") unless keys %psymfiles;
# Fork $orig symbol file for the rest (unpatched) confirmed arches.
my %confirmed_arches; $confirmed_arches{$_} = 1 foreach @confirmed_arches;
my @carches = kill_dupes(
grep { ! exists $psymfiles{$_} && ! exists $confirmed_arches{$_} }
$symfile->get_confirmed_arches()
);
@confirmed_arches = kill_dupes(
grep { ! exists $psymfiles{$_} } @confirmed_arches
);
# Finally create a SymbolFile collection and generate template
my $symfiles = new Debian::PkgKde::SymbolsHelper::SymbolFileCollection($symfile);
$symfiles->add_new_symfiles(values %psymfiles);
$symfiles->add_confirmed_arches(undef, @carches);
# Add assume_arches at current version
$symfiles->add_confirmed_arches($version, @confirmed_arches);
# Detect templinst symbols before substitutions and create template
regular_print("Confirmed arches: %s", join(", ", sort(@carches, @confirmed_arches)))
if @carches || @confirmed_arches;
regular_print("Generating symbol file template .... (this might take a while)");
foreach my $arch ($symfiles->get_new_arches()) {
my $symfile = $symfiles->get_symfile($arch);
foreach my $sym ($symfile->get_symbols()) {
$sym->mark_cpp_templinst_as_optional();
}
}
my $template = $symfiles->create_template()->fork();
# Post process template and print various information about result
my (@changes_new, @changes_lost, @changes_arch);
foreach my $soname (sort $template->get_sonames()) {
push @changes_new, $soname;
push @changes_lost, $soname;
push @changes_arch, $soname;
foreach my $sym (sort { $a->get_symboltempl() cmp $b->get_symboltempl() }
$template->get_symbols($soname),
$template->get_patterns($soname))
{
my $osym = $symfile->get_symbol_object($sym, $soname);
if (defined $osym) {
if ($sym->{deprecated} && ! $osym->{deprecated}) {
push @changes_lost, " ".$sym->get_symbolspec(1);
} elsif (! $sym->{deprecated} && $osym->{deprecated}) {
# Tweak symbol
tweak_symbol($sym, $version);
push @changes_new, " ".$sym->get_symbolspec(1);
} else {
my $arches = $sym->get_tag_value("arch") || '';
my $oarches = $osym->get_tag_value("arch") || '';
if ($arches ne $oarches) {
push @changes_arch,
" ".$sym->get_symbolspec(1)." (was arch=$oarches)";
}
}
} else {
# Tweak symbol
tweak_symbol($sym, $version);
push @changes_new, " ".$sym->get_symbolspec(1);
}
}
# Pop sonames if no symbols added
pop @changes_new if $changes_new[$#changes_new] eq $soname;
pop @changes_lost if $changes_lost[$#changes_lost] eq $soname;
pop @changes_arch if $changes_arch[$#changes_arch] eq $soname;
}
print_changes_list(\@changes_new,
"there are NEW symbols (including optional):",
\&info, \&verbose_print) if $opt_verbose;
print_changes_list(\@changes_lost,
"there are LOST symbols (including optional):",
\&warning, \®ular_print);
print_changes_list(\@changes_arch,
"architecture set changed for the symbols below:",
\&info, \®ular_print);
# Finally adjust confirmed arches list
$template->set_confirmed($symfiles->get_latest_version(),
$symfiles->get_latest_arches());
# Generate diff
if ($opt_verbose) {
my $tmpfile = File::Temp->new(TEMPLATE => "${opt_in}.newXXXXXX");
$template->output($tmpfile,
package => $opt_package,
template_mode => 1,
with_deprecated => 1,
);
$tmpfile->close();
spawn(exec => ["diff", "-u", $symfile->{file}, $tmpfile->filename],
to_handle => \*STDERR, wait_child => 1, nocheck => 1);
}
return $template;
}
############### Subcommands ####################
sub _create_symfile {
my ($file, $filename_re) = @_;
my $filename = File::Basename::basename($file);
my $symfile;
if ($filename =~ $filename_re) {
regular_print("* Loading \"%s\" symbol file '%s' ...", $1, $file);
$symfile = Debian::PkgKde::SymbolsHelper::SymbolFile->new(
file => $file,
arch => "$1"
);
} else {
warning("%s is not named properly. Expected *_<arch> or *.<arch>", $file);
}
return $symfile;
}
sub subcommand_create {
my %opts = (
get_common_options("oav"),
);
if (GetOptions(%opts)) {
unless (defined $opt_out) {
error("output file option (-o) is mandatory");
}
# Load symbol files
my @input_files = @ARGV;
my %symfiles;
my $str_arches = join("|", get_valid_arches());
my $filename_re = qr/.*?[_.]($str_arches)$/;
unless (@input_files) {
error("please pass files/directory with arch specific symbol files");
}
for my $f (@input_files) {
if (-d $f) {
opendir(DIR, $f) or error("unable to open directory: $f");
while (my $filename = readdir(DIR)) {
next if ($filename =~ /^.{1,2}$/);
my $file = File::Spec->catfile($f, $filename);
if (my $symfile = _create_symfile($file, $filename_re)) {
if (exists $symfiles{$symfile->get_arch()}) {
error("duplicate symbol file (%s) for arch %s", $file,
$symfile->get_arch());
}
$symfiles{$symfile->get_arch()} = $symfile;
}
}
closedir DIR;
} elsif (-r $f) {
if (my $symfile = _create_symfile($f, $filename_re)) {
if (exists $symfiles{$symfile->get_arch()}) {
error("duplicate symbol file (%s) for arch (%s)", $f,
$symfile->get_arch());
}
$symfiles{$symfile->get_arch()} = $symfile;
}
} else {
error("unreadale file/directory: %s", $f);
}
}
if (scalar(keys %symfiles) > 0) {
unless (exists $symfiles{$opt_arch}) {
error("symbol file for the specified arch (%s) could not be found/loaded. ".
"Please specify another arch with -a option", $opt_arch);
}
# Set confirmed version
unless ($opt_version) {
my $ver = $symfiles{$opt_arch}->get_highest_version();
$opt_version = sanitize_version($ver, 'd', 1) || $ver;
}
foreach my $symfile (values %symfiles) {
$symfile->set_confirmed($opt_version, $symfile->get_arch());
}
# Create collection and generate template
my $orig_symfile = $symfiles{$opt_arch}->fork();
delete $symfiles{$opt_arch};
my $symfiles = Debian::PkgKde::SymbolsHelper::SymbolFileCollection->new($orig_symfile);
$symfiles->add_new_symfiles(values %symfiles);
$symfiles->add_confirmed_arches(undef, $opt_arch);
# Detect templinst symbols before substitutions and create template
regular_print("Generating symbol file template .... (this might take a while)");
foreach my $symfile ($orig_symfile, $symfiles->get_symfiles()) {
foreach my $sym ($symfile->get_symbols()) {
$sym->mark_cpp_templinst_as_optional();
}
}
my $template = $symfiles->create_template();
# Set confirmed header
$template->set_confirmed($symfiles->get_latest_version(),
$symfiles->get_latest_arches());
foreach my $sym ($template->get_symbols(),
$template->get_patterns())
{
tweak_symbol($sym, $opt_version);
}
return out_symfile($template);
} else {
error("no properly named symbol files located");
}
return 0;
}
return 1;
}
sub subcommand_patch {
my $opt_file2patch;
my $opt_confirmed_arches = '';
my @input_patches;
my %opts = (
get_common_options("obipavV"),
"file-to-patch|f=s" => \$opt_file2patch,
"confirmed-arches|c=s" => \$opt_confirmed_arches,
);
if (GetOptions(%opts)) {
check_mandatory_options("p", "");
@input_patches = @ARGV;
unless ($opt_in) {
$opt_in = find_package_symbolfile_path($opt_package, $opt_arch);
error("symbol template file was not found for package '$opt_package'")
unless (defined $opt_in && -r $opt_in);
}
push @input_patches, "-" unless @input_patches;
my @patches = load_patches(@input_patches);
my @confirmed_arches = split(/[\s,]+/, $opt_confirmed_arches);
error("no valid patches found.") unless @patches;
return out_symfile(
patch_symfile($opt_package, $opt_version, $opt_in, $opt_file2patch,
\@patches, \@confirmed_arches)
);
}
return 1;
}
sub subcommand_batchpatch {
my @opt_packages;
my @input_patches;
my $opt_continue_on_err;
my $opt_confirmed_arches = '';
my $failed_packages = 0;
my %opts = (
get_common_options("bavV"),
"package|p=s" => \@opt_packages,
"continue-on-error!" => \$opt_continue_on_err,
"confirmed-arches|c=s" => \$opt_confirmed_arches,
);
if (GetOptions(%opts)) {
@input_patches = @ARGV;
push @input_patches, "-" unless @input_patches;
my @patches = load_patches(@input_patches);
my @confirmed_arches = split(/[\s,]+/, $opt_confirmed_arches);
my %packages = map({ $_->{package} => 1 }
grep { defined $_->{package} } @patches);
my @packages;
if (@opt_packages) {
foreach my $package (@opt_packages) {
my @pkgs = split(/[\s\n]+/sm, $package);
push @packages, @pkgs;
}
} else {
@packages = grep { exists $packages{$_} } get_all_packages();
}
error("no valid patches found") unless @patches;
error("no packages specified or none to patch for this source")
unless @packages;
foreach my $package (@packages) {
my $msg = sprintf("| Processing %s package |", $package);
regular_print("-" x length($msg));
regular_print($msg);
regular_print("-" x length($msg));
if (my $infile = find_package_symbolfile_path($package, $opt_arch)) {
my $template;
eval {
$template = patch_symfile($package, $opt_version, $infile, undef,
\@patches, \@confirmed_arches);
};
if ($@) {
if ($opt_continue_on_err) {
print STDERR $@;
info("%s patching FAILED. Continuing with subsequent package if any.", $package);
$failed_packages++;
} else {
print STDERR $@;
error("%s patching FAILED. Will NOT continue.", $package);
die $@;
}
} else {
$opt_in = $infile;
out_symfile($template);
}
} else {
regular_print("* UNABLE to find symbol file for %s", $package);
}
}
return $failed_packages;
}
return 1;
}
sub subcommand_rewrite {
my $opt_template = 1;
my $opt_convert;
my %opts = (
get_common_options("boiav"),
"template!" => \$opt_template,
"convert" => \$opt_convert,
);
if (GetOptions(%opts)) {
check_mandatory_options("i");
if (-f $opt_in) {
my $symfile = Debian::PkgKde::SymbolsHelper::SymbolFile->new(file => $opt_in, arch => $opt_arch);
my %o = (
template_mode => $opt_template
);
foreach my $sym ($symfile->get_symbols()) {
$sym->upgrade_virtual_table_symbol($opt_arch) if ($opt_convert);
tweak_symbol($sym, $opt_version);
}
foreach my $pat ($symfile->get_patterns()) {
tweak_symbol($pat, $opt_version);
}
return out_symfile($symfile->fork(), %o);
} else {
error("input symbol file ($opt_in) not found");
return 1;
}
}
return 1;
}
# Boilerplate for the common subcommand handler
sub subcommand_boilerplate {
my %opts = (
get_common_options("obipav"),
);
if (GetOptions(%opts)) {
# check_mandatory_options("o");
return 0;
}
return 1;
}
my %SUBCOMMANDS = (
"create" => [ 1, \&subcommand_create, "create symbol file template" ],
"patch" => [ 2, \&subcommand_patch, "apply dpkg-gensymbols patch(es) to the symbol file template" ],
"batchpatch" => [ 3, \&subcommand_batchpatch, "apply dpkg-gensymbols patches to multiple packages at once" ],
"rewrite" => [ 4, \&subcommand_rewrite, "filter/rewrite symbol file" ],
);
report_options(info_fh => \*STDERR);
my $curcmd = shift @ARGV;
if (defined $curcmd && exists $SUBCOMMANDS{$curcmd}) {
my $ret = &{$SUBCOMMANDS{$curcmd}->[1]}();
exit($ret);
} else {
my $err;
$err = ($curcmd) ? "unrecognized subcommand '$curcmd'." : "subcommand was not specified.";
errormsg($err . " Valid subcommands are:");
for my $cmd (sort({ $SUBCOMMANDS{$a}->[0] <=> $SUBCOMMANDS{$b}->[0] }
keys %SUBCOMMANDS)) {
# Display command and its short help
regular_print(" %s - %s", $cmd, $SUBCOMMANDS{$cmd}->[2]);
}
exit(2);
}
# vi: noexpandtab shiftwidth=4 tabstop=8
|