#!/usr/bin/perl # build-versions-db builds the versions mldmb database # and is released under the terms of the GNU GPL version 3, or any # later version, at your option. See the file README and COPYING for # more information. # Copyright 2016 by Don Armstrong . use warnings; use strict; use Getopt::Long; use Pod::Usage; =head1 NAME build-versions-db -- builds source and source maintainers file =head1 SYNOPSIS build-versions-db [options] versions.idx.new versions.idx.new \ /srv/bugs.debian.org/versions/indices/ftp Options: --debug, -d debugging level (Default 0) --help, -h display this help --man, -m display manual =head1 OPTIONS =over =item B<--update> Update an existing database; the default. B<--no-update> will regenerate an existing database from scratch. =item B<--debug, -d> Debug verbosity. (Default 0) =item B<--help, -h> Display brief usage information. =item B<--man, -m> Display this manual. =back =head1 EXAMPLES build-versions-db versions.idx.new versions.idx.new \ /srv/bugs.debian.org/versions/indices/ftp \ stable =cut use vars qw($DEBUG); use Debbugs::Versions::Dpkg; use Debbugs::Config qw(:config); use File::Copy; use MLDBM qw(DB_File Storable); use Fcntl; my %options = (debug => 0, help => 0, man => 0, update => 1, ); GetOptions(\%options, 'update!', 'debug|d+','help|h|?','man|m'); pod2usage() if $options{help}; pod2usage({verbose=>2}) if $options{man}; $DEBUG = $options{debug}; my @USAGE_ERRORS; if (not @ARGV >= 4) { push @USAGE_ERRORS, "You must provide at least four arguments, two databases, ". "a top level directory and at least one suite"; } pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS; my $versions = shift @ARGV; my $versions_time = shift @ARGV; my $versions_new = $versions."_".$$."_".time; my $versions_time_new = $versions_time."_".$$."_".time; my $toplevel = shift @ARGV; my @suites = @ARGV; $MLDBM::DumpMeth=q(portable); my $time = time; my %db; my %db2; if ($options{update}) { copy($versions,$versions_new); copy($versions_time,$versions_time_new); } tie %db, "MLDBM", $versions_new, O_CREAT|O_RDWR, 0664 or die "tie $versions: $!"; tie %db2, "MLDBM", $versions_time_new,O_CREAT|O_RDWR, 0664 or die "tie $versions_time failed: $!"; update_versions_suites(\%db,\%db2,\@suites); versions_time_cleanup(\%db2) if $options{update}; move($versions_new,$versions); move($versions_time_new,$versions_time); sub open_compressed_file { my ($file) = @_; my $fh; my $mode = '<:encoding(UTF-8)'; my @opts; if ($file =~ /\.gz$/) { $mode = '-|:encoding(UTF-8)'; push @opts,'gzip','-dc'; } if ($file =~ /^\.xz$/) { $mode = '-|:encoding(UTF-8)'; push @opts,'xz','-dc'; } if ($file =~ /^\.bz2$/) { $mode = '-|:encoding(UTF-8)'; push @opts,'bzip2','-dc'; } open($fh,$mode,@opts,$file); return $fh; } # Read Package, Version, and Source fields from a Packages.gz file. sub read_packages { my ($db,$db2,$packages, $component,$arch,$dist) = @_; my $PACKAGES = open_compressed_file($packages) or die "Unable to open $packages for reading: $!"; local $_; local $/ = ''; # paragraph mode print STDERR "reading packages $packages\n" if $DEBUG; for (<$PACKAGES>) { /^Package: (.+)/im or next; my $pkg = $1; /^Version: (.+)/im or next; my $ver = $1; my $extra_source_only = 0; if (/^Extra-Source-Only: yes/im) { $extra_source_only = 1; } update_package_version($db,$db2,$dist,$arch,$pkg,$ver,$time) unless $extra_source_only; } } sub update_package_version { my ($db,$db2,$d,$a,$p,$v,$t) = @_; # see MLDBM(3pm)/BUGS my $tmp = $db->{$p}; # we allow multiple versions in an architecture now; this # should really only happen in the case of source, however. push @{$tmp->{$d}{$a}}, $v; $db->{$p} = $tmp; $tmp = $db2->{$p}; $tmp->{$d}{$a}{$v} = $time if not exists $tmp->{$d}{$a}{$v}; $db2->{$p} = $tmp; } sub update_versions_suites { my ($db,$db2,$suites) = @_; # Iterate through all Packages and Sources files. for my $suite (@{$suites}) { my $suitedir = "$toplevel/$suite"; for my $component ('main', 'main/debian-installer', 'contrib', 'non-free') { my $componentdir = "$suitedir/$component"; next unless -d $componentdir; my $COMPONENT; opendir $COMPONENT, $componentdir or die "opendir $componentdir: $!"; # debian-installer is really a section rather than a component # (ugh). (my $viscomponent = $component) =~ s[/.*][]; my $sources = (grep { -f $_ } glob "$suitedir/$component/source/Sources.*")[0]; next unless defined $sources; read_packages($db,$db2,$sources, $viscomponent,'source',$suite); for my $arch (readdir $COMPONENT) { next unless $arch =~ s/^binary-//; my $archdir = "$componentdir/binary-$arch"; my $packages = (grep { -f $_ } glob("$archdir/Packages.*"))[0]; next unless defined $packages; read_packages($db,$db2,$packages, $viscomponent,$arch,$suite); } closedir $COMPONENT; } } } sub versions_time_cleanup { my ($db) = @_; my $time = time; for my $package (keys %{$db}) { my $temp = $db->{$package}; for my $dist (keys %{$temp}) { for my $arch (keys %{$temp->{$dist}}) { my @versions = (sort {$temp->{$dist}{$arch}{$a} <=> $temp->{$dist}{$arch}{$b} } keys %{$temp->{$dist}{$arch}}); next unless @versions > 1; for my $i (0 .. ($#versions-1)) { last if $temp->{$dist}{$arch}{$versions[$i+1]} > ($time - $config{remove_age}*60*60*24); last if keys %{$temp->{$dist}{$arch}} <= 1; delete $temp->{$dist}{$arch}{$versions[$i]}; } } } $db->{$package} = $temp; } }