| 1 |
#!/usr/bin/perl -w
|
| 2 |
|
| 3 |
# apt-show-versions - Lists available package versions with distribution
|
| 4 |
|
| 5 |
# This program parses the dpkg status file and the APT lists for the
|
| 6 |
# installed and available package versions and distribution and shows
|
| 7 |
# upgrade options within the specific distribution of the selected
|
| 8 |
# package
|
| 9 |
|
| 10 |
# Copyright (C) 2001 Christoph Martin
|
| 11 |
|
| 12 |
# Author: Christoph Martin <martin@uni-mainz.de>
|
| 13 |
# Maintainer: Christoph Martin <martin@uni-mainz.de>
|
| 14 |
# Version: 0.16
|
| 15 |
|
| 16 |
# This file is free software; you can redistribute it and/or modify it
|
| 17 |
# under the terms of the GNU General Public License as published by the
|
| 18 |
# Free Software Foundation; either version 2, or (at your option) any
|
| 19 |
# later version.
|
| 20 |
|
| 21 |
# This file is distributed in the hope that it will be
|
| 22 |
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
| 23 |
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
| 24 |
# General Public License for more details.
|
| 25 |
|
| 26 |
# You should have received a copy of the GNU General Public License
|
| 27 |
# along with this file; see the file 'copyright' respectively
|
| 28 |
# '/usr/share/common-licenses/GPL-2'. If not, write to the
|
| 29 |
# Free Software Foundation, Inc.,
|
| 30 |
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
| 31 |
|
| 32 |
use strict;
|
| 33 |
use Getopt::Long;
|
| 34 |
use Storable qw(store retrieve);
|
| 35 |
|
| 36 |
my $apackagescachefile="/var/cache/apt-show-versions/apackages";
|
| 37 |
my $ipackagescachefile="/var/cache/apt-show-versions/ipackages";
|
| 38 |
my $filescachefile="/var/cache/apt-show-versions/files";
|
| 39 |
|
| 40 |
use AptPkg::Cache;
|
| 41 |
use AptPkg::Config '$_config';
|
| 42 |
use AptPkg::Policy;
|
| 43 |
use AptPkg::System '$_system';
|
| 44 |
use AptPkg::Version;
|
| 45 |
|
| 46 |
Getopt::Long::Configure('no_ignore_case'); # Distinguish options -R and -r.
|
| 47 |
|
| 48 |
$_config->init;
|
| 49 |
$_config->{quiet} = 2; # Suppress cache building messages.
|
| 50 |
$_system = $_config->system;
|
| 51 |
my $vs = $_system->versioning;
|
| 52 |
my $cache = AptPkg::Cache->new;
|
| 53 |
my $policy = $cache->policy;
|
| 54 |
|
| 55 |
my $VERSION;
|
| 56 |
# Used by ExtUtils::MakeMaker:
|
| 57 |
$VERSION ='0.16';
|
| 58 |
|
| 59 |
# Provide some constants (to avoid redundant literals).
|
| 60 |
my $ARCHIVE = 'Archive';
|
| 61 |
my $CODENAME = 'Codename';
|
| 62 |
my $NAME = 'Name';
|
| 63 |
my $PACKAGE = 'Package';
|
| 64 |
my $RELEASE = 'Release';
|
| 65 |
my $STATUS = 'Status';
|
| 66 |
my $SUITE = 'Suite';
|
| 67 |
my $UNKNOWN = 'unknown';
|
| 68 |
my $VERS = 'Version';
|
| 69 |
|
| 70 |
# process commandline parameters
|
| 71 |
my %opts;
|
| 72 |
# If more than one packages are requested by name, each one gets registered
|
| 73 |
# in hash %pkg_names as a key (with a true value).
|
| 74 |
my %pkg_names = ();
|
| 75 |
unless (GetOptions (\%opts,
|
| 76 |
'status-file|stf=s',
|
| 77 |
'list-dir|ld=s',
|
| 78 |
'package|p=s',
|
| 79 |
'regex|r',
|
| 80 |
'regex-all|R',
|
| 81 |
'allversions|a',
|
| 82 |
'upgradeable|u',
|
| 83 |
'brief|b',
|
| 84 |
'nohold|nh',
|
| 85 |
'initialize|i',
|
| 86 |
'verbose|v',
|
| 87 |
'help|h')) {
|
| 88 |
exit 1;
|
| 89 |
}
|
| 90 |
if (scalar @ARGV == 1) {
|
| 91 |
if (exists $opts{'package'}) {
|
| 92 |
&die(1, "apt-show-versions: too many arguments\n");
|
| 93 |
}
|
| 94 |
$opts{'package'} = $ARGV[0];
|
| 95 |
} elsif (scalar @ARGV > 1) {
|
| 96 |
if (exists($opts{'package'}) or exists($opts{'regex'})) {
|
| 97 |
&die(1, "apt-show-versions: too many arguments\n");
|
| 98 |
}
|
| 99 |
$pkg_names{$_} = 1 foreach (@ARGV);
|
| 100 |
}
|
| 101 |
|
| 102 |
# Consider implicit option dependencies.
|
| 103 |
$opts{'regex'} = 1 if ($opts{'regex-all'});
|
| 104 |
|
| 105 |
# Determine call mode.
|
| 106 |
my $MODE_SINGLE = 1; # Called with one package name.
|
| 107 |
my $MODE_MULTIPLE = 2; # Called with several packages names.
|
| 108 |
my $MODE_REGEX = 3; # Called with a package name regular expression.
|
| 109 |
my $MODE_ALL = 4; # Called without any package information: all packages.
|
| 110 |
my $mode;
|
| 111 |
if (%pkg_names) {
|
| 112 |
$mode = $MODE_MULTIPLE;
|
| 113 |
}
|
| 114 |
elsif ($opts{'regex'}) {
|
| 115 |
$mode = $MODE_REGEX;
|
| 116 |
}
|
| 117 |
elsif ($opts{'package'}) {
|
| 118 |
$mode = $MODE_SINGLE;
|
| 119 |
}
|
| 120 |
else {
|
| 121 |
$mode = $MODE_ALL;
|
| 122 |
}
|
| 123 |
|
| 124 |
if (exists $opts{'help'}) {
|
| 125 |
print <<EOF;
|
| 126 |
Apt-Show-Versions v.$VERSION (c) Christoph Martin
|
| 127 |
|
| 128 |
Usage:
|
| 129 |
apt-show-versions shows available versions of installed packages.
|
| 130 |
|
| 131 |
Options:
|
| 132 |
-stf|--status-file=<file> Use <file> as the dpkg status file instead
|
| 133 |
of /var/lib/dpkg/status
|
| 134 |
-ld|list-dir=<directory> Use <directory> as path to apt's list files instead
|
| 135 |
of /var/state/apt/lists/ or /var/lib/apt/lists/
|
| 136 |
-p|--package=<package> Print versions for <package>.
|
| 137 |
-r|--regex Read package with -p as regex
|
| 138 |
-R|--regex-all Like --regex, but also show not installed packages.
|
| 139 |
-u|--upgradeable Print only upgradeable packages
|
| 140 |
-a|--allversions Print all available versions.
|
| 141 |
-b|--brief Short output.
|
| 142 |
-nh|--nohold Don't treat holded packages.
|
| 143 |
-i|--initialize Initialize or update package cache only (as root).
|
| 144 |
-v|--verbose Verbose messages.
|
| 145 |
-h|--help Print this help.
|
| 146 |
EOF
|
| 147 |
exit;
|
| 148 |
}
|
| 149 |
|
| 150 |
# Path to apt's list files
|
| 151 |
my $list_dir;
|
| 152 |
if ($opts{'list-dir'}) {
|
| 153 |
$list_dir = $opts{'list-dir'};
|
| 154 |
}
|
| 155 |
else {
|
| 156 |
$list_dir = $_config->get_dir("Dir::State::lists");
|
| 157 |
}
|
| 158 |
|
| 159 |
# Path to dpkg status file
|
| 160 |
my $status_file = $opts{'status-file'} || "/var/lib/dpkg/status";
|
| 161 |
my @files;
|
| 162 |
my $filesref;
|
| 163 |
|
| 164 |
my %used_suites = ();
|
| 165 |
# Determine the release names currently used by this host.
|
| 166 |
# %releasenames structure example:
|
| 167 |
# ('ftp.de.debian.org_debian_dists_unstable' => {'Suite' => 'unstable',
|
| 168 |
# 'Name' => 'unstable',
|
| 169 |
# 'Codename' => 'sid'},
|
| 170 |
# 'debian.udsorg.ru_dists_unstable' => {'Suite' => 'unknown',
|
| 171 |
# 'Name' => 'unknown',
|
| 172 |
# 'Codename' => 'unknown'},
|
| 173 |
# ...)
|
| 174 |
my %releasenames = &determine_releasenames();
|
| 175 |
|
| 176 |
if (exists $opts{'initialize'}) {
|
| 177 |
unlink $apackagescachefile;
|
| 178 |
unlink $ipackagescachefile;
|
| 179 |
unlink $filescachefile;
|
| 180 |
}
|
| 181 |
|
| 182 |
# Get Packages-files list from cache or create new list and write
|
| 183 |
# cache if root
|
| 184 |
if (-e $filescachefile and -M $filescachefile < -M $list_dir) {
|
| 185 |
$filesref = retrieve($filescachefile);
|
| 186 |
@files = @$filesref unless !ref($filesref);
|
| 187 |
}
|
| 188 |
# Test also to be sure $filescachefile is not corrupt and returns a ref to it
|
| 189 |
if (!-e $filescachefile or -M $list_dir < -M $filescachefile or !ref($filesref)) {
|
| 190 |
opendir(DIR, $list_dir) or &die("Can't opendir $list_dir: $!\n");
|
| 191 |
@files = map { $list_dir . $_} grep /Packages$/, readdir(DIR);
|
| 192 |
($< == 0) and (store(\@files, $filescachefile) or
|
| 193 |
warn "Can't write $filescachefile\n");
|
| 194 |
|
| 195 |
closedir DIR ;
|
| 196 |
}
|
| 197 |
unless (@files > 0) {
|
| 198 |
&die("Error: No information about packages! (Maybe no deb entries?)\n");
|
| 199 |
}
|
| 200 |
|
| 201 |
# Get hash with all installed packages from cache or create new hash
|
| 202 |
# and write cache if root
|
| 203 |
# $ipackages structure example:
|
| 204 |
# {'dblatex' => {'Version' => '0.2.8-6',
|
| 205 |
# 'Status' => 'install ok installed',
|
| 206 |
# 'Package' => 'dblatex'},
|
| 207 |
# ...}
|
| 208 |
my $ipackages;
|
| 209 |
|
| 210 |
if (-e $ipackagescachefile and -M $ipackagescachefile < -M $status_file) {
|
| 211 |
$ipackages = retrieve($ipackagescachefile);
|
| 212 |
}
|
| 213 |
if (!-e $ipackagescachefile or -M $status_file < -M $ipackagescachefile or !ref($ipackages)) {
|
| 214 |
($ipackages, undef) = parse_file ($status_file, 1);
|
| 215 |
($< == 0) and (store($ipackages, $ipackagescachefile) or
|
| 216 |
warn "Can't write $ipackagescachefile\n");
|
| 217 |
}
|
| 218 |
|
| 219 |
# Get available packages list from cache if possible
|
| 220 |
# $apackages structure example:
|
| 221 |
# {'dblatex' => {'ftp.de.debian.org_debian_dists_stable'
|
| 222 |
# => {'Version' => '0.2-2',
|
| 223 |
# 'Release' => 'ftp.de.debian.org_debian_dists_stable',
|
| 224 |
# 'Package' => 'dblatex'},
|
| 225 |
# 'ftp.de.debian.org_debian_dists_testing'
|
| 226 |
# => {'Version' => '0.2.8-2',
|
| 227 |
# 'Release' => 'ftp.de.debian.org_debian_dists_testing',
|
| 228 |
# 'Package' => 'dblatex'},
|
| 229 |
# 'ftp.de.debian.org_debian_dists_unstable'
|
| 230 |
# => {'Version' => '0.2.8-6',
|
| 231 |
# 'Release' => 'ftp.de.debian.org_debian_dists_unstable',
|
| 232 |
# 'Package' => 'dblatex'}},
|
| 233 |
# ...}
|
| 234 |
my $apackages;
|
| 235 |
my $cache_file_corrupt;
|
| 236 |
-e $apackagescachefile and $apackages = retrieve($apackagescachefile);
|
| 237 |
unless (ref($apackages)) {
|
| 238 |
$cache_file_corrupt = 1;
|
| 239 |
undef $apackages;
|
| 240 |
}
|
| 241 |
|
| 242 |
my $default_release = $_config->get("APT::Default-Release");
|
| 243 |
|
| 244 |
my @official_suites = qw(oldstable stable proposed-updates testing unstable);
|
| 245 |
# %official_suites:
|
| 246 |
# - Keys: Known official suite names
|
| 247 |
# - Values: Order index
|
| 248 |
my %official_suites;
|
| 249 |
$official_suites{$official_suites[$_]} = $_ foreach (0 .. $#official_suites);
|
| 250 |
|
| 251 |
# Get available package information out of all Packages files
|
| 252 |
foreach (@files) {
|
| 253 |
# Parse Packages file if creation time is newer than packages cache
|
| 254 |
if (! -e $apackagescachefile or -C $_ < -M $apackagescachefile
|
| 255 |
or $cache_file_corrupt) {
|
| 256 |
my ($href, $release) = &parse_file ($_);
|
| 257 |
foreach (keys %$href) {
|
| 258 |
$apackages->{$_}{$release} = $href->{$_};
|
| 259 |
}
|
| 260 |
}
|
| 261 |
}
|
| 262 |
# Store if we are root
|
| 263 |
($< == 0) and (store($apackages, $apackagescachefile) or
|
| 264 |
&die("Warning: Can't write to $apackagescachefile!\n"));
|
| 265 |
# Exit if we are root and using the -i option
|
| 266 |
($< == 0) and (exists $opts{'initialize'}) and exit;
|
| 267 |
|
| 268 |
# print info for selected package
|
| 269 |
if ($mode == $MODE_SINGLE) {
|
| 270 |
my $key = $opts{'package'};
|
| 271 |
|
| 272 |
print_package ($key);
|
| 273 |
}
|
| 274 |
elsif ($mode == $MODE_MULTIPLE) {
|
| 275 |
print_package($_) foreach (sort keys %pkg_names);
|
| 276 |
}
|
| 277 |
else {
|
| 278 |
# print info for all packages or packages matching regex
|
| 279 |
my $pkgs = ($opts{'regex-all'}) ? $apackages : $ipackages;
|
| 280 |
foreach my $key (sort keys %$pkgs) {
|
| 281 |
next if (exists $opts{'package'} &&
|
| 282 |
exists $opts{'regex'} &&
|
| 283 |
!($key =~ m/$opts{'package'}/));
|
| 284 |
print_package ($key);
|
| 285 |
}
|
| 286 |
}
|
| 287 |
|
| 288 |
################################################################################
|
| 289 |
# Collect uptodate or up/downgradeable status of package depending on
|
| 290 |
# distribution. Return: ($version_indicator, @version_info)
|
| 291 |
# - $version_indicator:
|
| 292 |
# * 0: Version is not to be installed/kept.
|
| 293 |
# * 1: Version is to be kept.
|
| 294 |
# * 2: Version is to be up/downgraded.
|
| 295 |
# - @version_info:
|
| 296 |
# Version information to be printed (undef if no information exists)
|
| 297 |
################################################################################
|
| 298 |
sub print_version {
|
| 299 |
my ($archiv, $package, $iversion, $aversion, $cand) = @_;
|
| 300 |
|
| 301 |
if (defined($aversion) and $cache->{$package}) {
|
| 302 |
if ($cand and $aversion eq $cand->{VerStr})
|
| 303 |
{
|
| 304 |
my $cmp_versions = $vs->compare($aversion, $iversion);
|
| 305 |
if ($cmp_versions != 0) {
|
| 306 |
my $direction = ($cmp_versions > 0) ? 'up' : 'down';
|
| 307 |
return(2, "$package/$archiv",
|
| 308 |
(defined($opts{'brief'})) ? "\n" :
|
| 309 |
" ${direction}gradeable from $iversion to $aversion\n");
|
| 310 |
}
|
| 311 |
else {
|
| 312 |
return(1, "$package/$archiv", defined($opts{'brief'}) ? "\n"
|
| 313 |
: " uptodate $iversion\n");
|
| 314 |
}
|
| 315 |
}
|
| 316 |
}
|
| 317 |
# Default outcome: either version is definitely not to be installed/kept
|
| 318 |
# or information for a decicion is lacking.
|
| 319 |
return(0, undef);
|
| 320 |
}
|
| 321 |
|
| 322 |
# print information about package
|
| 323 |
|
| 324 |
sub print_package {
|
| 325 |
my ($package) = @_;
|
| 326 |
|
| 327 |
# Sort all releases of package.
|
| 328 |
my @pkg_releases = sort sort_pkg_releases values(%{$apackages->{$package}});
|
| 329 |
|
| 330 |
# All print information of package must be buffered, as the decision
|
| 331 |
# whether to suppress all printing for the package can be executed only at
|
| 332 |
# a later stage.
|
| 333 |
my @print_info = ();
|
| 334 |
my $is_upgradeable = 0; # Intialize with: not upgradeable.
|
| 335 |
# To guarantee tabular printing of the package's releases some further
|
| 336 |
# variables are needed:
|
| 337 |
my $max_package_len = 0;
|
| 338 |
my $max_version_len = 0;
|
| 339 |
my $max_name_len = 0;
|
| 340 |
|
| 341 |
# print more information if required
|
| 342 |
if ($opts{'allversions'}) {
|
| 343 |
if ($ipackages->{$package}->{$PACKAGE}) {
|
| 344 |
push @print_info, "$ipackages->{$package}->{$PACKAGE} ";
|
| 345 |
unless ($ipackages->{$package}->{$STATUS} =~ /not-installed/ ||
|
| 346 |
$ipackages->{$package}->{$STATUS} =~ /config-files/) {
|
| 347 |
push @print_info, "$ipackages->{$package}->{$VERS} ";
|
| 348 |
}
|
| 349 |
push @print_info, "$ipackages->{$package}->{$STATUS}\n";
|
| 350 |
} else {
|
| 351 |
push @print_info, "Not installed\n";
|
| 352 |
}
|
| 353 |
|
| 354 |
# Index to @official_suites: Next official suite to mention if missing.
|
| 355 |
my $official_idx = 0;
|
| 356 |
# Print preparation loop
|
| 357 |
foreach my $pkg (@pkg_releases) {
|
| 358 |
# First handle missing official suites to be listed before current
|
| 359 |
# release.
|
| 360 |
my $cur_idx = $official_suites{&get_rel_suite($pkg->{$RELEASE})};
|
| 361 |
if (defined $cur_idx) {
|
| 362 |
# Current release is an official one:
|
| 363 |
# List prepending missing suites.
|
| 364 |
foreach ($official_idx .. $cur_idx - 1) {
|
| 365 |
if ($used_suites{$official_suites[$_]}) {
|
| 366 |
push @print_info, "No $official_suites[$_] version\n";
|
| 367 |
}
|
| 368 |
}
|
| 369 |
# All official suites including current one are handled.
|
| 370 |
$official_idx = $cur_idx + 1;
|
| 371 |
}
|
| 372 |
# Then handle current release.
|
| 373 |
(my $archive = $pkg->{$RELEASE}) =~ s/_.*//;
|
| 374 |
push @print_info, {$PACKAGE => $pkg->{$PACKAGE},
|
| 375 |
$VERS => $pkg->{$VERS},
|
| 376 |
$NAME => &get_rel_name($pkg->{$RELEASE}),
|
| 377 |
$ARCHIVE => $archive};
|
| 378 |
$max_package_len = &max(length($pkg->{$PACKAGE}), $max_package_len);
|
| 379 |
$max_version_len = &max(length($pkg->{$VERS}), $max_version_len);
|
| 380 |
$max_name_len = &max(length(&get_rel_name($pkg->{$RELEASE})),
|
| 381 |
$max_name_len);
|
| 382 |
}
|
| 383 |
# Finally handle missing official suites after last existing release.
|
| 384 |
foreach ($official_idx .. $#official_suites) {
|
| 385 |
if ($used_suites{$official_suites[$_]}) {
|
| 386 |
push @print_info, "No $official_suites[$_] version\n";
|
| 387 |
}
|
| 388 |
}
|
| 389 |
}
|
| 390 |
|
| 391 |
my $iversion = $ipackages->{$package}->{$VERS};
|
| 392 |
|
| 393 |
# print info about upgrade status (only if package is installed)
|
| 394 |
|
| 395 |
if (($ipackages->{$package}->{$VERS}) &&
|
| 396 |
(!($ipackages->{$package}->{$STATUS} =~ /config-files/))) {
|
| 397 |
# Reorder package version structures to prefer the default release.
|
| 398 |
@pkg_releases = &reorder_pkg_releases(@pkg_releases);
|
| 399 |
my $found = 0;
|
| 400 |
my $aversion = 0;
|
| 401 |
my $cand;
|
| 402 |
if ($cache->{$package}) {
|
| 403 |
$cand = $policy->candidate($cache->{$package});
|
| 404 |
}
|
| 405 |
foreach (@pkg_releases) {
|
| 406 |
my $version = $_->{$VERS};
|
| 407 |
if ($version) {
|
| 408 |
my @version_info;
|
| 409 |
($found, @version_info) =
|
| 410 |
&print_version(&get_rel_name($_->{$RELEASE}),
|
| 411 |
$package, $iversion, $version, $cand);
|
| 412 |
push @print_info, @version_info if ($found);
|
| 413 |
$aversion = $version;
|
| 414 |
}
|
| 415 |
$is_upgradeable = 1 if ($found == 2);
|
| 416 |
last if $found;
|
| 417 |
}
|
| 418 |
if ($aversion && ($vs->compare($iversion, $aversion) > 0)) {
|
| 419 |
# Test whether installed version is newer
|
| 420 |
# than all available versions.
|
| 421 |
my $newer_indic = 1;
|
| 422 |
foreach (@pkg_releases) {
|
| 423 |
my $cmp_version = $_->{$VERS};
|
| 424 |
if ($cmp_version and
|
| 425 |
$vs->compare($iversion, $cmp_version) <= 0)
|
| 426 |
{
|
| 427 |
$newer_indic = 0;
|
| 428 |
last;
|
| 429 |
}
|
| 430 |
}
|
| 431 |
if ($newer_indic and not defined($opts{'brief'}))
|
| 432 |
{
|
| 433 |
push(@print_info,
|
| 434 |
"$package $iversion newer than version in archive\n");
|
| 435 |
}
|
| 436 |
} elsif (not $found) {
|
| 437 |
# Check for manual upgrade possibility:
|
| 438 |
# There are cases where the APT policy doesn't find a better
|
| 439 |
# candidate than the installed version, but which itself isn't
|
| 440 |
# available any longer because it has been replaced by a newer
|
| 441 |
# version in the archives. As the newer version isn't chosen by
|
| 442 |
# the policy, the upgrade can only be executed manually.
|
| 443 |
if ($cand and $iversion eq $cand->{VerStr}) {
|
| 444 |
foreach my $release (@pkg_releases) {
|
| 445 |
my $cmp_version = $release->{$VERS};
|
| 446 |
if ($cmp_version and
|
| 447 |
$vs->compare($iversion, $cmp_version) < 0)
|
| 448 |
{
|
| 449 |
push(@print_info,
|
| 450 |
$package,
|
| 451 |
'/',
|
| 452 |
&get_rel_name($release->{$RELEASE}),
|
| 453 |
(defined($opts{'brief'})) ? "\n" :
|
| 454 |
" *manually* upgradeable from $iversion to " .
|
| 455 |
"$aversion\n");
|
| 456 |
$found = 1;
|
| 457 |
$is_upgradeable = 1;
|
| 458 |
last;
|
| 459 |
}
|
| 460 |
}
|
| 461 |
}
|
| 462 |
if (not $found) {
|
| 463 |
push(@print_info, "$package $iversion installed: No available ",
|
| 464 |
"version in archive\n");
|
| 465 |
}
|
| 466 |
}
|
| 467 |
} else {
|
| 468 |
push(@print_info, "$package not installed",
|
| 469 |
($mode == $MODE_SINGLE and not keys(%{$apackages->{$package}}))
|
| 470 |
? " (even not available)\n" : "\n");
|
| 471 |
}
|
| 472 |
|
| 473 |
if ($opts{'upgradeable'}
|
| 474 |
and not $is_upgradeable
|
| 475 |
and $mode == $MODE_SINGLE) {
|
| 476 |
# Caller expects single given package to be upgradeable.
|
| 477 |
# Signal failure of this expectation with a special exit code.
|
| 478 |
exit 2;
|
| 479 |
}
|
| 480 |
|
| 481 |
# Print loop
|
| 482 |
unless ($opts{'upgradeable'} and not $is_upgradeable) {
|
| 483 |
foreach my $print_info (@print_info) {
|
| 484 |
if (ref $print_info) {
|
| 485 |
printf("%*s %*s %*s %s\n",
|
| 486 |
-$max_package_len, $print_info->{$PACKAGE},
|
| 487 |
-$max_version_len, $print_info->{$VERS},
|
| 488 |
-$max_name_len, $print_info->{$NAME},
|
| 489 |
$print_info->{$ARCHIVE});
|
| 490 |
}
|
| 491 |
else {
|
| 492 |
print $print_info;
|
| 493 |
}
|
| 494 |
}
|
| 495 |
}
|
| 496 |
}
|
| 497 |
|
| 498 |
# ------------------------------------------------------
|
| 499 |
# FUNCTION: HASHREF, RELEASE_KEY = parse_file FILE (STATUS)
|
| 500 |
#
|
| 501 |
# Parses FILE into an HASHREF of Hashes
|
| 502 |
# Set STATUS when the file should be parsed just for
|
| 503 |
# installed packages (here the dpkg status file)
|
| 504 |
# Returns HASHREF and key of corresponding %releasenames record.
|
| 505 |
# ------------------------------------------------------
|
| 506 |
|
| 507 |
sub parse_file {
|
| 508 |
my ($file, $status) = @_;
|
| 509 |
my ($key, $value, $package, $packages);
|
| 510 |
|
| 511 |
my $release = &determine_pkgfile_release($file);
|
| 512 |
open FILE, $file or &die("Can't open file $file: $!\n");
|
| 513 |
if ($opts{'verbose'}) {print "Parsing $file...";};
|
| 514 |
while (<FILE>) {
|
| 515 |
if (/^$/){
|
| 516 |
unless (defined $package) {next};
|
| 517 |
|
| 518 |
if ($status) { # Are we parsing the status file?
|
| 519 |
# if we did not specify a package or pattern
|
| 520 |
# only include installed packages
|
| 521 |
unless ($mode == $MODE_ALL and
|
| 522 |
($package->{$STATUS} =~ /not-installed|config-files/ or
|
| 523 |
# don't print holded packages if requested
|
| 524 |
($opts{'nohold'} and $package->{$STATUS} =~ /hold/))) {
|
| 525 |
$packages->{ $package->{$PACKAGE}} = $package;
|
| 526 |
}
|
| 527 |
}
|
| 528 |
else {
|
| 529 |
if (!defined $packages->{$package->{$PACKAGE}} or
|
| 530 |
$vs->compare($packages->{$package->{$PACKAGE}}{$VERS},
|
| 531 |
$package->{$VERS}) < 0) {
|
| 532 |
$package->{$RELEASE} = $release;
|
| 533 |
$packages->{$package->{$PACKAGE}} = $package;
|
| 534 |
}
|
| 535 |
}
|
| 536 |
undef $package;
|
| 537 |
next;
|
| 538 |
}
|
| 539 |
unless ((/^Package/) || (/^Version/) || (/^Status/) || (/^Source/)) {next};
|
| 540 |
($key, $value) = split /: /, $_;
|
| 541 |
$value =~ s/\n//;
|
| 542 |
$value =~ s/\s\(.*\)$//; # Remove any Version information in ()
|
| 543 |
$package->{$key} = $value;
|
| 544 |
}
|
| 545 |
if ($opts{'verbose'}) {print " completed.\n"};
|
| 546 |
close FILE;
|
| 547 |
return $packages, $release;
|
| 548 |
}
|
| 549 |
|
| 550 |
################################################################################
|
| 551 |
# Determine the release of the specified package file.
|
| 552 |
# If no corresponding %releasenames record exists, one gets created.
|
| 553 |
# Argument $pkgfile is either full package file name or name core, e.g.:
|
| 554 |
# - /var/lib/apt/lists/ftp.de.debian.org_debian_dists_unstable_main_binary-i386_Packages
|
| 555 |
# - ftp.de.debian.org_debian_dists_unstable
|
| 556 |
################################################################################
|
| 557 |
sub determine_pkgfile_release {
|
| 558 |
my $pkgfile = shift;
|
| 559 |
|
| 560 |
return $pkgfile if ($releasenames{$pkgfile});
|
| 561 |
$pkgfile =~ s{.*/}{};
|
| 562 |
return undef if ($pkgfile eq 'status');
|
| 563 |
foreach (keys %releasenames) {
|
| 564 |
return $_ if ($_ eq substr($pkgfile, 0, length($_)));
|
| 565 |
}
|
| 566 |
# As package file has no release file, create a fallback %releasenames
|
| 567 |
# record based on the information of the package file name.
|
| 568 |
my $releasename;
|
| 569 |
foreach my $suite (@official_suites) {
|
| 570 |
if (index($pkgfile, "_${suite}_") != -1) {
|
| 571 |
# Packagefile belongs to a known suite.
|
| 572 |
($releasename = $pkgfile) =~ s/(.*$suite).*/$1/;
|
| 573 |
$releasenames{$releasename}{$SUITE} = $suite;
|
| 574 |
$releasenames{$releasename}{$NAME} = $suite;
|
| 575 |
last;
|
| 576 |
}
|
| 577 |
}
|
| 578 |
unless ($releasename) {
|
| 579 |
# No release information available for this package file:
|
| 580 |
# create a dummy %releasenames record.
|
| 581 |
$releasename = $pkgfile;
|
| 582 |
$releasenames{$releasename}{$SUITE} = $UNKNOWN;
|
| 583 |
$releasenames{$releasename}{$NAME} = $UNKNOWN;
|
| 584 |
}
|
| 585 |
$releasenames{$releasename}{$CODENAME} = $UNKNOWN;
|
| 586 |
return $releasename;
|
| 587 |
}
|
| 588 |
|
| 589 |
################################################################################
|
| 590 |
# Determine the release names currently used by this host.
|
| 591 |
################################################################################
|
| 592 |
sub determine_releasenames {
|
| 593 |
my %rel_names;
|
| 594 |
opendir LIST_DIR, $list_dir
|
| 595 |
or &die("Failed to open directory $list_dir: $!\n");
|
| 596 |
while (defined(my $rel_file = readdir LIST_DIR)) {
|
| 597 |
my $file_name = "$list_dir/$rel_file";
|
| 598 |
if ($rel_file =~ m/(.*)_Release$/) {
|
| 599 |
$rel_file = $1;
|
| 600 |
}
|
| 601 |
else {
|
| 602 |
next;
|
| 603 |
}
|
| 604 |
open RELEASE_FILE, "< $file_name"
|
| 605 |
or &die("Failed to open file $file_name for reading: $!\n");
|
| 606 |
while (defined (my $line = <RELEASE_FILE>)) {
|
| 607 |
if ($line =~ m/^\s*($SUITE|$CODENAME):\s*(\S+)\s*$/o) {
|
| 608 |
$rel_names{$rel_file}{$1} = $2;
|
| 609 |
}
|
| 610 |
# After extracting values for Suite and Codename, do not parse
|
| 611 |
# rest of release file.
|
| 612 |
# Thus normally only the first lines of the release file must be
|
| 613 |
# read, whereas the much bigger rest may be skipped.
|
| 614 |
if (defined $rel_names{$rel_file}{$SUITE} and
|
| 615 |
defined $rel_names{$rel_file}{$CODENAME}) {
|
| 616 |
last;
|
| 617 |
}
|
| 618 |
}
|
| 619 |
close RELEASE_FILE
|
| 620 |
or &die("Failed to close file $file_name: $!\n");
|
| 621 |
# Register suite as used.
|
| 622 |
if (defined $rel_names{$rel_file}{$SUITE}) {
|
| 623 |
$used_suites{$rel_names{$rel_file}{$SUITE}} = 1;
|
| 624 |
}
|
| 625 |
# Provide default values for missing fields.
|
| 626 |
foreach ($SUITE, $CODENAME) {
|
| 627 |
unless (defined $rel_names{$rel_file}{$_}) {
|
| 628 |
$rel_names{$rel_file}{$_} = $UNKNOWN;
|
| 629 |
}
|
| 630 |
}
|
| 631 |
# Determine name relevant to user (as used in sources.list):
|
| 632 |
# either Suite or Codename.
|
| 633 |
if ($rel_file =~ m/_$rel_names{$rel_file}{$SUITE}/) {
|
| 634 |
$rel_names{$rel_file}{$NAME} = $rel_names{$rel_file}{$SUITE};
|
| 635 |
}
|
| 636 |
elsif ($rel_file =~ m/_$rel_names{$rel_file}{$CODENAME}/) {
|
| 637 |
$rel_names{$rel_file}{$NAME} = $rel_names{$rel_file}{$CODENAME};
|
| 638 |
}
|
| 639 |
else {
|
| 640 |
# Fall back to Suite.
|
| 641 |
$rel_names{$rel_file}{$NAME} = $rel_names{$rel_file}{$SUITE};
|
| 642 |
}
|
| 643 |
}
|
| 644 |
closedir LIST_DIR
|
| 645 |
or &die("Failed to close directory $list_dir: $!\n");
|
| 646 |
return %rel_names;
|
| 647 |
}
|
| 648 |
|
| 649 |
################################################################################
|
| 650 |
# Return the numerically biger of the two specified arguments.
|
| 651 |
################################################################################
|
| 652 |
sub max {
|
| 653 |
return ($_[0] > $_[1]) ? $_[0] : $_[1];
|
| 654 |
}
|
| 655 |
|
| 656 |
################################################################################
|
| 657 |
# Reorder package releases in a way that within the releases of the same
|
| 658 |
# version number the default release gets placed first.
|
| 659 |
################################################################################
|
| 660 |
sub reorder_pkg_releases {
|
| 661 |
my @releases = @_;
|
| 662 |
|
| 663 |
if (@releases and $default_release) {
|
| 664 |
# Reordering strategy:
|
| 665 |
# - Precondition: The releases are sorted by version already.
|
| 666 |
# - Iterate over the release list: from "left" to "right".
|
| 667 |
# - For the releases of each version:
|
| 668 |
# - Find the first release that is not the default release.
|
| 669 |
# - If right from this "move candidate" the default release is found,
|
| 670 |
# move it left before the move candidate.
|
| 671 |
my $move_idx; # Index of move candidate
|
| 672 |
foreach my $idx (0 .. $#releases) {
|
| 673 |
my $rel_key = $releases[$idx]->{$RELEASE};
|
| 674 |
if (defined $move_idx) {
|
| 675 |
# There exists a move candidate.
|
| 676 |
if ($releases[$idx]->{$VERS} eq $releases[$move_idx]->{$VERS}) {
|
| 677 |
# Current release is of same version as move candidate.
|
| 678 |
if (&get_rel_suite($rel_key) eq $default_release
|
| 679 |
or &get_rel_codename($rel_key) eq $default_release) {
|
| 680 |
# Move current release before move candidate in order to
|
| 681 |
# place default release first.
|
| 682 |
my $rel = splice @releases, $idx, 1;
|
| 683 |
splice @releases, $move_idx, 0, $rel;
|
| 684 |
$move_idx = $idx;
|
| 685 |
}
|
| 686 |
}
|
| 687 |
else {
|
| 688 |
# Version change
|
| 689 |
undef $move_idx;
|
| 690 |
}
|
| 691 |
}
|
| 692 |
unless (defined $move_idx) {
|
| 693 |
# Test whether current release is move candidate.
|
| 694 |
if (&get_rel_suite($rel_key) ne $default_release
|
| 695 |
and &get_rel_codename($rel_key) ne $default_release) {
|
| 696 |
$move_idx = $idx;
|
| 697 |
}
|
| 698 |
}
|
| 699 |
}
|
| 700 |
}
|
| 701 |
return @releases;
|
| 702 |
}
|
| 703 |
|
| 704 |
################################################################################
|
| 705 |
# Sorting function for package releases
|
| 706 |
# Sorting hierarchy:
|
| 707 |
# 1) Release number
|
| 708 |
# 2) @official_suites (in array order) before other ones
|
| 709 |
# 3) Release name
|
| 710 |
################################################################################
|
| 711 |
sub sort_pkg_releases {
|
| 712 |
my $cmp_versions = $vs->compare($a->{$VERS}, $b->{$VERS});
|
| 713 |
return $cmp_versions if ($cmp_versions);
|
| 714 |
my $cmp_suites = (&suite_idx(&get_rel_suite($a->{$RELEASE})) <=>
|
| 715 |
&suite_idx(&get_rel_suite($b->{$RELEASE})));
|
| 716 |
return $cmp_suites if ($cmp_suites);
|
| 717 |
return(&get_rel_name($a->{$RELEASE}) cmp &get_rel_name($b->{$RELEASE}));
|
| 718 |
}
|
| 719 |
|
| 720 |
################################################################################
|
| 721 |
# Return the sorting index of the specified suite name.
|
| 722 |
# Unofficial suites are sorted last.
|
| 723 |
################################################################################
|
| 724 |
sub suite_idx {
|
| 725 |
return(defined($official_suites{$_[0]}) ? $official_suites{$_[0]}
|
| 726 |
: $#official_suites + 1);
|
| 727 |
}
|
| 728 |
|
| 729 |
################################################################################
|
| 730 |
# Simple die wrapper which controls the exit code.
|
| 731 |
# If first parameter is a number, it is used as the exit code. Otherwise it
|
| 732 |
# gets interpreted (like the remaining parameters) as the error message.
|
| 733 |
################################################################################
|
| 734 |
sub die {
|
| 735 |
$! = ($_[0] =~ m/^\d+$/) ? shift() : 255;
|
| 736 |
die @_;
|
| 737 |
}
|
| 738 |
|
| 739 |
################################################################################
|
| 740 |
# Access function for the fields of the %releasenames structure
|
| 741 |
# Missing fields are augmented on the fly (compare BTS report #515328).
|
| 742 |
################################################################################
|
| 743 |
sub get_rel_codename {
|
| 744 |
my $rel_key = shift;
|
| 745 |
&determine_pkgfile_release($rel_key) unless $releasenames{$rel_key};
|
| 746 |
return $releasenames{$rel_key}{$CODENAME};
|
| 747 |
}
|
| 748 |
sub get_rel_name {
|
| 749 |
my $rel_key = shift;
|
| 750 |
&determine_pkgfile_release($rel_key) unless $releasenames{$rel_key};
|
| 751 |
return $releasenames{$rel_key}{$NAME};
|
| 752 |
}
|
| 753 |
sub get_rel_suite {
|
| 754 |
my $rel_key = shift;
|
| 755 |
&determine_pkgfile_release($rel_key) unless $releasenames{$rel_key};
|
| 756 |
return $releasenames{$rel_key}{$SUITE};
|
| 757 |
}
|
| 758 |
|
| 759 |
# script documentation (POD style)
|
| 760 |
|
| 761 |
=encoding utf8
|
| 762 |
|
| 763 |
=head1 NAME
|
| 764 |
|
| 765 |
apt-show-versions - Lists available package versions with distribution
|
| 766 |
|
| 767 |
=head1 DESCRIPTION
|
| 768 |
|
| 769 |
apt-show-versions parses the dpkg status file and the APT lists for
|
| 770 |
the installed and available package versions and distribution and
|
| 771 |
shows upgrade options within the specific distribution of the selected
|
| 772 |
package.
|
| 773 |
|
| 774 |
This is really useful if you have a mixed stable/testing environment
|
| 775 |
and want to list all packages which are from testing and can be
|
| 776 |
upgraded in testing.
|
| 777 |
|
| 778 |
apt-show-versions uses caching for the status information of installed
|
| 779 |
and available packages. If you run apt-show-versions as root the
|
| 780 |
cache is updated as needed. If you run as non-root uses the newest
|
| 781 |
available information, but can't update the cache. If you run as root
|
| 782 |
with the option B<-i> the cache is initialized or updated only.
|
| 783 |
|
| 784 |
=head1 SYNOPSIS
|
| 785 |
|
| 786 |
B<apt-show-versions> [B<-h>] [[B<-p>] I<package name>] [B<-a>] [B<-b>]
|
| 787 |
|
| 788 |
=head1 OPTIONS
|
| 789 |
|
| 790 |
If you don't give any options the status of all installed packages is
|
| 791 |
printed.
|
| 792 |
|
| 793 |
=over 4
|
| 794 |
|
| 795 |
=item B<-p> I<package>, B<--package>=I<package>
|
| 796 |
|
| 797 |
Print available and installed versions for specified I<package>. You
|
| 798 |
can also specify a package name without the option B<-p>. If B<-p> and
|
| 799 |
a package name are missing, all installed packages are displayed.
|
| 800 |
|
| 801 |
=item B<-r>, B<--regex>
|
| 802 |
|
| 803 |
interpret I<package> from option B<-p> as a regex.
|
| 804 |
|
| 805 |
=item B<-R>, B<--regex-all>
|
| 806 |
|
| 807 |
like B<--regex>, but also show matching packages which are not installed
|
| 808 |
|
| 809 |
=item B<-u>, B<--upgradeable>
|
| 810 |
|
| 811 |
Print only upgradeable packages
|
| 812 |
|
| 813 |
=item B<-a>, B<--allversions>
|
| 814 |
|
| 815 |
Print all available versions of the selected packages
|
| 816 |
|
| 817 |
=item B<-b>, B<--brief>
|
| 818 |
|
| 819 |
Print only package_name/distribution for upgradeable packages
|
| 820 |
|
| 821 |
=item B<-v>, B<--verbose>
|
| 822 |
|
| 823 |
Prints out verbose messages.
|
| 824 |
|
| 825 |
=item B<-i>, B<--initialize>
|
| 826 |
|
| 827 |
Initialize or update package cache only (as root). Do this every time
|
| 828 |
when the status of the installed or available packages has changed.
|
| 829 |
|
| 830 |
=item B<-stf> I<file>, B<--status-file>=I<file>
|
| 831 |
|
| 832 |
Use I<file> as the dpkg status file instead of /var/lib/dpkg/status
|
| 833 |
|
| 834 |
=item B<-ld> I<directory>, B<--list-dir>=I<directory>
|
| 835 |
|
| 836 |
Use I<directory> as path to apt's list files instead of
|
| 837 |
/var/state/apt/lists/ or /var/lib/apt/lists/
|
| 838 |
|
| 839 |
=item B<-h>, B<--help>
|
| 840 |
|
| 841 |
Prints out command-line help.
|
| 842 |
|
| 843 |
=back
|
| 844 |
|
| 845 |
=head1 EXIT CODES
|
| 846 |
|
| 847 |
=over 4
|
| 848 |
|
| 849 |
=item 0
|
| 850 |
|
| 851 |
No error
|
| 852 |
|
| 853 |
=item 1
|
| 854 |
|
| 855 |
Wrong usage
|
| 856 |
|
| 857 |
=item 2
|
| 858 |
|
| 859 |
apt-show-versions has been called with exactly one package and upgradeable
|
| 860 |
option set, but package is uptodate. As no output has been requested, this
|
| 861 |
case gets signaled using the exit code.
|
| 862 |
|
| 863 |
=item 255
|
| 864 |
|
| 865 |
Unspecified error
|
| 866 |
|
| 867 |
=back
|
| 868 |
|
| 869 |
=head1 EXAMPLES
|
| 870 |
|
| 871 |
If you want to know for all your installed packages whether they are
|
| 872 |
uptodate or upgradeable, use:
|
| 873 |
|
| 874 |
apt-show-versions
|
| 875 |
|
| 876 |
If you want to have a list of all upgradeable packages:
|
| 877 |
|
| 878 |
apt-show-versions -u
|
| 879 |
|
| 880 |
To get a list of all available versions of libc6:
|
| 881 |
|
| 882 |
apt-show-versions -a -p libc6
|
| 883 |
|
| 884 |
To get information about several packages:
|
| 885 |
|
| 886 |
apt-show-versions dpkg apt
|
| 887 |
|
| 888 |
apt-show-versions -r ^texlive
|
| 889 |
|
| 890 |
To upgrade all packages in testing:
|
| 891 |
|
| 892 |
apt-get install `apt-show-versions -u -b | fgrep testing`
|
| 893 |
|
| 894 |
=head1 AUTHOR
|
| 895 |
|
| 896 |
Christoph Martin, martin@uni-mainz.de
|
| 897 |
|
| 898 |
=head1 SEE ALSO
|
| 899 |
|
| 900 |
apt(8), dpkg(1)
|
| 901 |
|
| 902 |
=cut
|