#! /usr/bin/env perl # # plan-downgrade.pl - Tool to find dependencies of package downgrade # Copyright (C) 2011 Tommi Saviranta # # 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 . # # # This script depends on the following packages: # - libapt-pkg-perl (developed with 0.1.25) # - libwww-curl-perl (developed with 4.15-1+b1) # # # Version history: # 1 5 Oct 2011 - Initial version # 2 6 Oct 2011 - Script can now find Debian snapshot archive timestamps use strict; use warnings FATAL => 'all'; use IO::Handle; use Getopt::Long; use AptPkg::Config '$_config'; use AptPkg::System '$_system'; use AptPkg::Cache; sub fetch($$) { use WWW::Curl::Easy; my $curl = shift; my $url = shift; $curl->setopt(CURLOPT_HEADER, 1); $curl->setopt(CURLOPT_URL, $url); my $response; $curl->setopt(CURLOPT_WRITEDATA, \$response); my $retcode = $curl->perform; if ($retcode == 0 && $curl->getinfo(CURLINFO_HTTP_CODE) == 200) { return $response; } else { print STDERR "Snapshot server returned error: " . $curl->strerror($retcode) . " " . $curl->errbuf . "\n"; return undef; } } sub list_versions($$) { my ($data, $show) = @_; my (%link, %version); my $n = 1; foreach my $html_raw ($data =~ /
  • (.*?)<\/li>/gi) { my ($link, $ver) = $html_raw =~ /(.*?) /; $link{$n} = $link; $link{$ver} = $link; $version{$n} = $ver; $version{$ver} = $ver; ($show) && printf "%3d: %s\n", $n, $ver; $n++; } return (\%link, \%version); } sub show_and_select_version($$$) { my ($data, $pkgname, $pkgver) = @_; my $link; if ($pkgver) { my ($r0, $r1) = list_versions($data, 0); my %link = %{$r0}; $link = $link{$pkgver}; unless ($link) { print STDERR "$pkgname=$pkgver is not available!\n"; } } if (! $link) { print "\nAvailable versions:\n"; my ($r0, $r1) = list_versions($data, 1); my %link = %{$r0}; my %version = %{$r1}; print "\n"; while (! $link) { print "Which version? "; STDOUT->flush(); my $i = ; chomp $i; $link = $link{$i}; $pkgver = $version{$i}; } } return ($link, $pkgver); } sub find_timestamp($$$) { my ($data, $pkgname, $arch) = @_; foreach my $farch ($arch, "all") { my ($ts) = $data =~ /href=".*?\/([0-9TZ]+)\/pool.*\/${pkgname}_.*?_${farch}.deb/i; defined($ts) && return $ts; } return undef; } (my $self = $0) =~ s#.*/##; my %process_list = ( "Installed" => 1, "UnPacked" => 1, "HalfConfigured" => 1, "HalfInstalled" => 1 ); my $arch; my $allow_upgrade = 0; my $find_snapshots = 0; my $help = 0; my $getopt_result = GetOptions( "allow-upgrade" => \$allow_upgrade, "find-snapshots" => \$find_snapshots, "arch=s" => \$arch, "help" => \$help); if ($help or $#ARGV == -1) { print <; close(PIPE); chomp $arch; print STDERR "Using architecture: $arch\n"; } my %timestamps; my %missing; my $curl = WWW::Curl::Easy->new; foreach my $pkgname_raw (@ARGV) { my ($pkgname, $pkgver) = split(/=/, $pkgname_raw); print "\nPackage: $pkgname\n"; my $url = "http://snapshot.debian.org/binary/$pkgname/"; print STDERR "Retrieving available versions for $pkgname...\n"; my $ver_data = fetch($curl, $url); next unless ($ver_data); my $link; while (1) { ($link, $pkgver) = show_and_select_version( $ver_data, $pkgname, $pkgver); print STDERR "Retrieving timestamps " . "for $pkgname=$pkgver...\n"; my $ts_data = fetch($curl, $url . $link); next unless ($ts_data); my $timestamp = find_timestamp($ts_data, $pkgname, $arch); if ($timestamp) { $timestamps{$timestamp} = 1; last; } else { print STDERR "No timestamp found for " . "$pkgname=$pkgver!\n"; print "Try another version [Y|n]? "; STDOUT->flush(); my $i = ; chomp $i; if ($i =~ /^n/i) { $missing{$pkgname} = 1; last; } } } } my @timestamps = keys %timestamps; if ($#timestamps >= 0) { print "\n"; print "Snapshot repositories:\n"; foreach my $ts (@timestamps) { print "$ts http://snapshot.debian.org/" . "archive/debian/$ts/\n"; } } my @missing = keys %missing; if ($#missing >= 0) { print "\n"; print "Missing packages:\n"; foreach my $pkg (@missing) { print "\t$pkg\n"; } } exit(0); } $_system = $_config->system; my $cache = AptPkg::Cache->new(); my $version = $_system->versioning; my @plan_pkg; my %inst_pkg; push(@plan_pkg, @ARGV); while ($#plan_pkg >= 0) { my $pkgname_raw = shift(@plan_pkg); my ($pkgname, $pkgver) = split(/=/, $pkgname_raw); print STDERR "Investigating $pkgname_raw...\n"; flush STDERR; my $pkg = $cache->{$pkgname}; unless ($pkg) { warn "$pkgname not known\n"; next; } my $ver_avail = $pkg->{VersionList}; unless ($ver_avail) { warn "$pkgname has no versions available\n"; next; } my $ver_found = 0; for my $ver (@$ver_avail) { if ($ver->{VerStr} eq $pkgver) { $ver_found = 1; last; } } unless ($ver_found) { warn "$pkgname has no version $pkgver available\n"; next; } $inst_pkg{$pkgname} = $pkgver; my $rdeps = $pkg->{RevDependsList}; next unless ($rdeps); for my $rdep (@$rdeps) { my $rdep_pkgname = $rdep->{ParentPkg}{Name}; my $rdep_pkg = $cache->{$rdep_pkgname}; next unless ($process_list{$rdep_pkg->{CurrentState}}); next unless ($rdep->{TargetVer}); if ($rdep->{DepType} eq "Depends") { my $rdep_ver = $rdep->{ParentVer}{VerStr}; next if ($rdep_pkg->{CurrentVer} and ! $allow_upgrade and $version->check_dep( $rdep_pkg->{CurrentVer}{VerStr}, "<<", $rdep_ver)); next unless ($version->check_dep($pkgver, $rdep->{CompTypeDeb}, $rdep->{TargetVer})); my $queued = $inst_pkg{$rdep_pkgname}; next unless (! defined($queued) or $version->check_dep( $queued, "<<", $rdep_ver)); $inst_pkg{$rdep_pkgname} = $rdep_ver; push(@plan_pkg, "$rdep_pkgname=$rdep_ver"); } } } while (my ($name, $ver) = each %inst_pkg) { print "$name=$ver "; } print "\n";