#!/usr/bin/perl -w # {{{ Legal stuff # Lintian -- Debian package checker # # Copyright (C) 1998 Christian Schwarz and Richard Braakman # # This program is free software. It is distributed under the terms of # the GNU General Public License as published by the Free Software # Foundation; either version 2 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, you can find it on the World Wide # Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, # MA 02110-1301, USA. # }}} # {{{ libraries and such use strict; use Getopt::Long; use FileHandle; # }}} # {{{ Global Variables my $lintian_info_cmd = 'lintian-info'; #Command to run for ? my $LINTIAN_VERSION = ""; #External Version number my $BANNER = "Lintian v$LINTIAN_VERSION"; #Version Banner - text form my $LAB_FORMAT = 8; #Lab format Version Number #increased whenever incompatible #changes are done to the lab #so that all packages are re-unpacked # Variables used to record commandline options # Commented out variables have "defined" checks somewhere to determine if # they were set via commandline or environment variables my $pkg_mode = 'a'; # auto -- automatically search for # binary and source pkgs use vars qw($verbose); $verbose = 0; #flag for -v|--verbose switch our $debug = 0; #flag for -d|--debug switch our $quiet = 0; #flag for -q|--quiet switch my @debug; my $check_everything = 0; #flag for -a|--all switch my $lintian_info = 0; #flag for -i|--info switch our $display_infotags = 0; #flag for -I|--display-info switch our $display_experimentaltags = 0; #flag for -E|--display-experimental switch my $unpack_level = undef; #flag for -l|--unpack-level switch our $no_override = 0; #flag for -o|--no-override switch our $show_overrides = 0; #flag for --show-overrides switch our $color = 'never'; #flag for --color switch my $check_checksums = 0; #flag for -m|--md5sums|--checksums switch my $allow_root = 0; #flag for --allow-root switch my $fail_on_warnings = 0; #flag for --fail-on-warnings switch my $keep_lab = 0; #flag for --keep-lab switch my $packages_file = 0; #string for the -p option my $OPT_LINTIAN_LAB = ""; #string for the --lab option my $OPT_LINTIAN_ARCHIVEDIR = "";#string for the --archivedir option my $OPT_LINTIAN_DIST = ""; #string for the --dist option my $OPT_LINTIAN_ARCH = ""; #string for the --arch option my $OPT_LINTIAN_SECTION = ""; #string for the --release option # These options can also be used via default or environment variables my $LINTIAN_CFG = ""; #config file to use our $LINTIAN_ROOT; #location of the lintian modules my $experimental_output_opts = undef; my @packages; my $action; my $checks; my $dont_check; my $unpack_info; my $cwd; my $cleanup_filename; my $exit_code = 0; my $LAB; my %collection_info; my %already_scheduled; my %checks; my %check_abbrev; my %unpack_infos; my %check_info; # reset configuration variables my $LINTIAN_LAB = undef; my $LINTIAN_ARCHIVEDIR = undef; my $LINTIAN_DIST = undef; my $LINTIAN_UNPACK_LEVEL = undef; my $LINTIAN_ARCH = undef; my $LINTIAN_SECTION = undef; # }}} # {{{ Setup Code #turn off file buffering $| = 1; # reset locale definition (necessary for tar) $ENV{'LC_ALL'} = 'C'; # }}} # {{{ Process Command Line ####################################### # Subroutines called by various options # in the options hash below. These are # invoked to process the commandline # options ####################################### # Display Command Syntax # Options: -h|--help sub syntax { print "$BANNER\n"; print <<"EOT-EOT-EOT"; Syntax: lintian [action] [options] [--] [packages] ... Actions: -S, --setup-lab set up static lab -R, --remove-lab remove static lab -c, --check check packages (default action) -C X, --check-part X check only certain aspects -X X, --dont-check-part X don\'t check certain aspects -u, --unpack only unpack packages in the lab -r, --remove remove package from the lab General options: -h, --help display short help text -v, --verbose verbose messages -V, --version display Lintian version and exit --print-version print unadorned version number and exit -d, --debug turn Lintian\'s debug messages ON -q, --quiet suppress all informational messages Behaviour options: -i, --info give detailed info about tags -I, --display-info display "I:" tags (normally suppressed) -E, --display-experimental display "X:" tags (normally suppressed) -l X, --unpack-level X set default unpack level to X -o, --no-override ignore overrides --show-overrides output tags that have been overriden --color never/always/auto disable, enable, or enable color for TTY -U X, --unpack-info X specify which info should be collected -m, --md5sums, --checksums check checksums when processing a .changes file --allow-root suppress lintian\'s warning when run as root --fail-on-warnings return a non-zero exit status if warnings found --keep-lab keep lab after run, even if temporary Configuration options: --cfg CONFIGFILE read CONFIGFILE for configuration --lab LABDIR use LABDIR as permanent laboratory --archivedir ARCHIVEDIR location of Debian archive to scan for packages --dist DIST scan packages in this distribution (e.g. sid) --section RELEASE scan packages in this section (e.g. main) --arch ARCH scan packages with architecture ARCH --root ROOTDIR use ROOTDIR instead of /usr/share/lintian Package selection options: -a, --all process all packages in distribution -b, --binary process only binary packages -s, --source process only source packages --udeb process only udeb packages -p X, --packages-file X process all files in file (special syntax!) EOT-EOT-EOT exit 0; } # Display Version Banner # Options: -V|--version, --print-version sub banner { if ($_[0] eq 'print-version') { print "$LINTIAN_VERSION\n"; } else { print "$BANNER\n"; } exit 0; } # Record action requested # Options: -S, -R, -c, -u, -r sub record_action { if ($action) { die("too many actions specified: $_[0]"); } $action = "$_[0]"; } # Record Parts requested for checking # Options: -C|--check-part sub record_check_part { if (defined $action and $action eq 'check' and $checks) { die("multiple -C or --check-part options not allowed"); } if ($dont_check) { die("both -C or --check-part and -X or --dont-check-part options not allowed"); } if ($action) { die("too many actions specified: $_[0]"); } $action = 'check'; $checks = "$_[1]"; } # Record Parts requested not to check # Options: -X|--dont-check-part X sub record_dont_check_part { if (defined $action and $action eq 'check' and $dont_check) { die("multiple -x or --dont-check-part options not allowed"); } if ($checks) { die("both -C or --check-part and -X or --dont-check-part options not allowed"); } if ($action) { die("too many actions specified: $_[0]"); } $action = 'check'; $dont_check = "$_[1]"; } # Process for -U|--unpack-info flag sub record_unpack_info { if ($unpack_info) { die("multiple -U or --unpack-info options not allowed"); } $unpack_info = "$_[1]"; } # Record what type of data is specified # Options: -b|--binary, -s|--source, --udeb sub record_pkgmode { $pkg_mode = 'b' if $_[0] eq 'binary'; $pkg_mode = 's' if $_[0] eq 'source'; $pkg_mode = 'u' if $_[0] eq 'udeb'; } # Hash used to process commandline options my %opthash = ( # ------------------ actions "setup-lab|S" => \&record_action, "remove-lab|R" => \&record_action, "check|c" => \&record_action, "check-part|C=s" => \&record_check_part, "dont-check-part|X=s" => \&record_dont_check_part, "unpack|u" => \&record_action, "remove|r" => \&record_action, # ------------------ general options "help|h" => \&syntax, "version|V" => \&banner, "print-version" => \&banner, "verbose|v" => \$verbose, "debug|d" => \@debug, # Count the -d flags "quiet|q" => \$quiet, # ------------------ behaviour options "info|i" => \$lintian_info, "display-info|I" => \$display_infotags, "display-experimental|E" => \$display_experimentaltags, "unpack-level|l=i" => \$unpack_level, "no-override|o" => \$no_override, "show-overrides" => \$show_overrides, "color=s" => \$color, "unpack-info|U=s" => \&record_unpack_info, "checksums|md5sums|m" => \$check_checksums, "allow-root" => \$allow_root, "fail-on-warnings" => \$fail_on_warnings, "keep-lab" => \$keep_lab, # Note: Ubuntu has (and other derivatives might gain) a # -D/--debian option to make lintian behave like in Debian, that # is, to revert distribution-specific changes # ------------------ configuration options "cfg=s" => \$LINTIAN_CFG, "lab=s" => \$OPT_LINTIAN_LAB, "archivedir=s" => \$OPT_LINTIAN_ARCHIVEDIR, "dist=s" => \$OPT_LINTIAN_DIST, "section=s" => \$OPT_LINTIAN_SECTION, "arch=s" => \$OPT_LINTIAN_ARCH, "root=s" => \$LINTIAN_ROOT, # ------------------ package selection options "all|a" => \$check_everything, "binary|b" => \&record_pkgmode, "source|s" => \&record_pkgmode, "udeb" => \&record_pkgmode, "packages-file|p=s" => \$packages_file, # ------------------ experimental "exp-output:s" => \$experimental_output_opts, ); # init commandline parser Getopt::Long::config('bundling', 'no_getopt_compat', 'no_auto_abbrev'); # process commandline options GetOptions(%opthash) or die("error parsing options\n"); # determine current working directory--we'll need this later chop($cwd = `pwd`); # determine LINTIAN_ROOT if it was not set with --root. $LINTIAN_ROOT = $LINTIAN_ROOT || $ENV{'LINTIAN_ROOT'}; if (defined $LINTIAN_ROOT) { unless ($LINTIAN_ROOT =~ m,^/,) { $LINTIAN_ROOT = "$cwd/$LINTIAN_ROOT"; } # see if it has a frontend directory if (-d "$LINTIAN_ROOT/frontend") { $lintian_info_cmd = "$LINTIAN_ROOT/frontend/lintian-info"; } } else { $LINTIAN_ROOT = '/usr/share/lintian'; } $debug = $#debug + 1; $verbose = 1 if $debug; $::verbose = $verbose; # that's $main::verbose # keep-lab implies unpack-level=2 unless explicetly # given otherwise if ($keep_lab and not defined $unpack_level) { $unpack_level = 2; } # option --all and packages specified at the same time? if (($check_everything or $packages_file) and $#ARGV+1 > 0) { print STDERR "warning: options -a or -p can't be mixed with package parameters!\n"; print STDERR "(will ignore -a or -p option)\n"; undef $check_everything; undef $packages_file; } # check permitted values for --color if ($color and $color !~ /^(never|always|auto|html)$/) { die "invalid argument to --color: $color\n"; } # check specified action $action = 'check' unless $action; # check for arguments if ($action =~ /^(check|unpack|remove)$/ and $#ARGV == -1 and not $check_everything and not $packages_file) { syntax(); } # }}} # {{{ Setup Configuration # # root permissions? # check if effective UID is 0 if ($> == 0 and not $allow_root) { print STDERR "warning: lintian's authors do not recommend running it with root privileges!\n"; } # search for configuration file if it was not set with --cfg # do not search the default locations if it was set. if ($LINTIAN_CFG) { } elsif (exists $ENV{'LINTIAN_CFG'} && -f ($LINTIAN_CFG = $ENV{'LINTIAN_CFG'})) { } elsif (-f ($LINTIAN_CFG = $LINTIAN_ROOT . '/lintianrc')) { } elsif (exists $ENV{'HOME'} && -f ($LINTIAN_CFG = $ENV{'HOME'} . '/.lintianrc')) { } elsif (-f ($LINTIAN_CFG = '/etc/lintianrc')) { } else { undef $LINTIAN_CFG; } # read configuration file if ($LINTIAN_CFG) { open(CFG, '<', $LINTIAN_CFG) or fail("cannot open configuration file $LINTIAN_CFG for reading: $!"); while () { chop; s/\#.*$//go; s/\"//go; # " for emacs :) next if m/^\s*$/o; # substitute some special variables s,\$HOME/,$ENV{'HOME'}/,go; s,\~/,$ENV{'HOME'}/,go; if (m/^\s*LINTIAN_LAB\s*=\s*(.*\S)\s*$/i) { $LINTIAN_LAB = $1; } elsif (m/^\s*LINTIAN_ARCHIVEDIR\s*=\s*(.*\S)\s*$/i) { $LINTIAN_ARCHIVEDIR = $1; } elsif (m/^\s*LINTIAN_DIST\s*=\s*(.*\S)\s*$/i) { $LINTIAN_DIST = $1; } elsif (m/^\s*LINTIAN_UNPACK_LEVEL\s*=\s*(.*\S)\s*$/i) { $LINTIAN_UNPACK_LEVEL = $1; } elsif (/^\s*LINTIAN_SECTION\s*=\s*(.*\S)\s*$/i) { $LINTIAN_SECTION = $1; } elsif (m/^\s*LINTIAN_ARCH\s*=\s*(.*\S)\s*$/i) { $LINTIAN_ARCH = $1; } else { fail("syntax error in configuration file: $_","(Note, that the syntax of the configuration file has been changed\nwith Lintian v0.3.0. In most cases, you don't need a configuration\nfile anymore -- just remove it.)"); } } close(CFG); } # environment variables overwrite settings in conf file: $LINTIAN_LAB = $ENV{'LINTIAN_LAB'} if $ENV{'LINTIAN_LAB'}; $LINTIAN_ARCHIVEDIR = $ENV{'LINTIAN_ARCHIVEDIR'} if $ENV{'LINTIAN_ARCHIVEDIR'}; $LINTIAN_DIST = $ENV{'LINTIAN_DIST'} if $ENV{'LINTIAN_DIST'}; $LINTIAN_UNPACK_LEVEL = $ENV{'LINTIAN_UNPACK_LEVEL'} if $ENV{'LINTIAN_UNPACK_LEVEL'}; $LINTIAN_SECTION = $ENV{'LINTIAN_SECTION'} if $ENV{'LINTIAN_SECTION'}; $LINTIAN_ARCH = $ENV{'LINTIAN_ARCH'} if $ENV{'LINTIAN_ARCH'}; # command-line options override everything $LINTIAN_LAB = $OPT_LINTIAN_LAB if $OPT_LINTIAN_LAB; $LINTIAN_ARCHIVEDIR = $OPT_LINTIAN_ARCHIVEDIR if $OPT_LINTIAN_ARCHIVEDIR; $LINTIAN_DIST = $OPT_LINTIAN_DIST if $OPT_LINTIAN_DIST; $LINTIAN_SECTION = $OPT_LINTIAN_SECTION if $OPT_LINTIAN_SECTION; $LINTIAN_ARCH = $OPT_LINTIAN_ARCH if $OPT_LINTIAN_ARCH; # LINTIAN_ARCH must have a value. unless (defined $LINTIAN_ARCH) { if ($LINTIAN_DIST) { chop($LINTIAN_ARCH=`dpkg --print-architecture`); } else { $LINTIAN_ARCH = 'any'; } } # export current settings for our helper scripts if ($LINTIAN_ROOT) { $ENV{'LINTIAN_ROOT'} = $LINTIAN_ROOT; } else { $ENV{'LINTIAN_ROOT'} = ""; } if ($LINTIAN_CFG) { $ENV{'LINTIAN_CFG'} = $LINTIAN_CFG; } else { $ENV{'LINTIAN_CFG'} = ""; } if ($LINTIAN_LAB) { $ENV{'LINTIAN_LAB'} = $LINTIAN_LAB; } else { $ENV{'LINTIAN_LAB'} = ""; $LINTIAN_LAB = ""; } if ($LINTIAN_ARCHIVEDIR) { $ENV{'LINTIAN_ARCHIVEDIR'} = $LINTIAN_ARCHIVEDIR; } else { $ENV{'LINTIAN_ARCHIVEDIR'} = ""; $LINTIAN_ARCHIVEDIR = ""; } if ($LINTIAN_DIST) { $ENV{'LINTIAN_DIST'} = $LINTIAN_DIST; } else { $ENV{'LINTIAN_DIST'} = ""; $LINTIAN_DIST = ""; } if ($LINTIAN_SECTION) { $ENV{'LINTIAN_SECTION'} = $LINTIAN_SECTION; } else { $ENV{'LINTIAN_SECTION'} = ""; $LINTIAN_SECTION = ""; } if ($LINTIAN_ARCH) { $ENV{'LINTIAN_ARCH'} = $LINTIAN_ARCH; } else { $ENV{'LINTIAN_ARCH'} = ""; } $ENV{'LINTIAN_DEBUG'} = $debug; # determine requested unpack level if (defined($unpack_level)) { # specified through command line } elsif (defined($LINTIAN_UNPACK_LEVEL)) { # specified via configuration file or env variable $unpack_level = $LINTIAN_UNPACK_LEVEL; } else { # determine by action if (($action eq 'unpack') or ($action eq 'check')) { $unpack_level = 1; } else { $unpack_level = 0; } } unless (($unpack_level == 0) or ($unpack_level == 1) or ($unpack_level == 2)) { fail("bad unpack level $unpack_level specified"); } $LINTIAN_UNPACK_LEVEL = $unpack_level; $ENV{'LINTIAN_UNPACK_LEVEL'} = $LINTIAN_UNPACK_LEVEL; # }}} # {{{ Loading lintian's own libraries (now LINTIAN_ROOT is known) unshift @INC, "$LINTIAN_ROOT/lib"; require Lab; require Util; require Pipeline; require Read_pkglists; import Util; import Pipeline; require Tags; import Tags; my @l_secs = read_dpkg_control("$LINTIAN_ROOT/checks/lintian.desc"); shift(@l_secs); map Tags::add_tag($_), @l_secs; # }}} # {{{ No clue why this code is here... use vars qw(%source_info %binary_info %udeb_info); # from the above # Print Debug banner if ($debug) { print "N: $BANNER\n"; print "N: Lintian root directory: $LINTIAN_ROOT\n"; print "N: Configuration file: $LINTIAN_CFG\n"; print "N: Laboratory: $LINTIAN_LAB\n"; print "N: Archive directory: $LINTIAN_ARCHIVEDIR\n"; print "N: Distribution: $LINTIAN_DIST\n"; print "N: Default unpack level: $LINTIAN_UNPACK_LEVEL\n"; print "N: Architecture: $LINTIAN_ARCH\n"; print "N: ----\n"; } # Set up clean-up handlers. undef $cleanup_filename; $SIG{'INT'} = \&interrupted; $SIG{'QUIT'} = \&interrupted; # }}} # {{{ Create/Maintain Lab and add any specified Debian Archives (*.debs) $LAB = new Lab( $LINTIAN_LAB, $LINTIAN_DIST ); ####################################### # Process -S option if ($action eq 'setup-lab') { if ($#ARGV+1 > 0) { print STDERR "warning: ignoring additional command line arguments\n"; } $LAB->setup_static() or fail("There was an error while setting up the static lab."); exit 0; ####################################### # Process -R option } elsif ($action eq 'remove-lab') { if ($#ARGV+1 > 0) { print STDERR "warning: ignoring additional command line arguments\n"; } $LAB->delete_static() or fail("There was an error while removing the static lab."); exit 0; ####################################### # Check for non deb specific actions } elsif (not (($action eq 'unpack') or ($action eq 'check') or ($action eq 'remove'))) { fail("bad action $action specified"); } # sanity check: fail("lintian lab has not been set up correctly (perhaps you forgot to run lintian --setup-lab?)") unless $LAB->is_lab(); #XXX: There has to be a cleaner way to do this $LINTIAN_LAB = $LAB->{dir}; # }}} # {{{ Setup the lintian-info pipe # pipe output through lintian-info? # note: if any E:/W: lines are added above this point, this block needs to # be moved up if ($lintian_info) { open(OUTPUT_PIPE, '|-', $lintian_info_cmd) or fail("cannot open output pipe to $lintian_info_cmd: $!"); select OUTPUT_PIPE; } # }}} # {{{ Compile list of files to process # process package/file arguments while (my $arg = shift) { # file? if (-f $arg) { # $arg contains absolute dir spec? unless ($arg =~ m,^/,) { $arg = "$cwd/$arg"; } # .deb file? if ($arg =~ /\.deb$/) { my $info = get_deb_info($arg); if (not defined $info) { print STDERR "$arg is a zero-byte file, skipping\n"; next; } schedule_package('b', $info->{'package'}, $info->{'version'}, $arg); } # .udeb file? elsif ($arg =~ /\.udeb$/) { my $info = get_deb_info($arg); if (not defined $info) { print STDERR "$arg is a zero-byte file, skipping\n"; next; } schedule_package('u', $info->{'package'}, $info->{'version'}, $arg); } # .dsc file? elsif ($arg =~ /\.dsc$/) { my $info = get_dsc_info($arg); if (not defined $info) { print STDERR "$arg is a zero-byte file, skipping\n"; next; } schedule_package('s', $info->{'source'}, $info->{'version'}, $arg); } # .changes file? elsif ($arg =~ /\.changes$/) { # get directory and filename part of $arg my ($arg_dir, $arg_name) = $arg =~ m,(.*)/([^/]+)$,; print "N: Processing changes file $arg_name ...\n" if $verbose; my ($data) = read_dpkg_control($arg); if (not defined $data) { warn "$arg is a zero-byte file, skipping\n"; next; } Tags::set_pkg( $arg, $arg_name, "", "", 'binary' ); # Description is mandated by dak, but only makes sense if binary # packages are included. Don't tag pure source uploads. if (!$data->{'description'} && $data->{'architecture'} ne 'source') { tag("no-description-in-changes-file"); } # check distribution field if (defined $data->{distribution}) { if ($data->{distribution} eq 'UNRELEASED') { # ignore } elsif ($data->{'version'} =~ /ubuntu|intrepid|hardy|gutsy|feisty|edgy|dapper/) { my @ubuntu_dists = qw(intrepid hardy gutsy feisty edgy dapper); my $regex = '^(' . join ('|', @ubuntu_dists) . ')'; if ($data->{distribution} !~ /^$regex(-(proposed|updates|backports|security))?$/ ) { tag("bad-ubuntu-distribution-in-changes-file", $data->{distribution}); } } elsif (! (($data->{distribution} eq 'stable') or ($data->{distribution} eq 'testing') or ($data->{distribution} eq 'unstable') or ($data->{distribution} eq 'experimental') or ($data->{distribution} =~ /\w+-backports/) or ($data->{distribution} =~ /\w+-proposed-updates/) or ($data->{distribution} =~ /\w+-security/)) ) { # bad distribution entry tag("bad-distribution-in-changes-file", $data->{distribution}); } } # Urgency is only recommended by Policy. if (!$data->{'urgency'}) { tag("no-urgency-in-changes-file"); } else { my $urgency = lc $data->{'urgency'}; $urgency =~ s/ .*//; unless ($urgency =~ /^(low|medium|high|critical|emergency)$/i) { tag("bad-urgency-in-changes-file", $data->{'urgency'}); } } # process all listed `files:' my %files; my $file_list = $data->{files} || ''; for ( split /\n/, $file_list ) { chomp; s/^\s+//o; next if $_ eq ''; my ($md5sum,$size,$section,$priority,$file) = split(/\s+/o, $_); $files{$file}{md5} = $md5sum; $files{$file}{size} = $size; # check section if (($section eq 'non-free') or ($section eq 'contrib')) { tag( "bad-section-in-changes-file", $file, $section ); } } foreach my $alg (qw(sha1 sha256)) { my $list = $data->{"checksums-$alg"} || ''; for ( split /\n/, $list ) { chomp; s/^\s+//o; next if $_ eq ''; my ($checksum,$size,$file) = split(/\s+/o, $_); $files{$file}{$alg} = $checksum; if ($files{$file}{size} != $size) { tag( "file-size-mismatch-in-changes-file", $file ); } } } foreach my $file (keys %files) { my $filename = $arg_dir . '/' . $file; # check size if (not -f $filename) { warn "E: $file does not exist, exiting\n"; exit(-1); } if (-s _ ne $files{$file}{size}) { print "N: size is $files{$file}{size}, argname is $arg_name, filename is $filename\n"; tag( "file-size-mismatch-in-changes-file", $file ); } # check checksums if ($check_checksums or $file =~ /\.dsc$/) { foreach my $alg (qw(md5 sha1 sha256)) { next unless exists $files{$file}{$alg}; my $real_checksum = get_file_checksum($alg, $filename); if ($real_checksum ne $files{$file}{$alg}) { tag( "checksum-mismatch-in-changes-file", $alg, $file ); } } } # process file? if ($file =~ /\.dsc$/) { my $info = get_dsc_info($filename); schedule_package('s', $info->{'source'}, $info->{'version'}, $filename); } elsif ($file =~ /\.deb$/) { my $info = get_deb_info($filename); schedule_package('b', $info->{'package'}, $info->{'version'}, $filename); } elsif ($file =~ /\.udeb$/) { my $info = get_deb_info($filename); schedule_package('u', $info->{'package'}, $info->{'version'}, $filename); } } unless ($exit_code) { my $stats = Tags::get_stats( $arg ); if ($stats->{severity}{4}) { $exit_code = 1; } elsif ($fail_on_warnings && $stats->{severity}{2}) { $exit_code = 1; } } } else { fail("bad package file name $arg (neither .deb, .udeb or .dsc file)"); } } else { # parameter is a package name--so look it up # search the distribution first, then the lab # special case: search only in lab if action is `remove' my $search; if ($action eq 'remove') { # search only in lab--see below $search = 'lab'; } else { # search in dist, then in lab $search = 'dist or lab'; my $found = 0; # read package info read_src_list("$LINTIAN_LAB/info/source-packages", 0); read_bin_list("$LINTIAN_LAB/info/binary-packages", 0); read_udeb_list("$LINTIAN_LAB/info/udeb-packages", 0); if (($pkg_mode eq 'b') or ($pkg_mode eq 'a')) { if ($binary_info{$arg}) { schedule_package('b', $binary_info{$arg}->{'package'}, $binary_info{$arg}->{'version'}, "$LINTIAN_ARCHIVEDIR/$binary_info{$arg}->{'file'}"); $found = 1; } } if (($pkg_mode eq 'u') or ($pkg_mode eq 'a')) { if ($udeb_info{$arg}) { schedule_package('u', $udeb_info{$arg}->{'package'}, $udeb_info{$arg}->{'version'}, "$LINTIAN_ARCHIVEDIR/$udeb_info{$arg}->{'file'}"); $found = 1; } } if (($pkg_mode eq 's') or ($pkg_mode eq 'a')) { if ($source_info{$arg}) { schedule_package('s', $source_info{$arg}->{'source'}, $source_info{$arg}->{'version'}, "$LINTIAN_ARCHIVEDIR/$source_info{$arg}->{'file'}"); $found = 1; } } next if $found; } # nothing found so far, so search the lab my $b = "$LINTIAN_LAB/binary/$arg"; my $s = "$LINTIAN_LAB/source/$arg"; my $u = "$LINTIAN_LAB/udeb/$arg"; if ($pkg_mode eq 'b') { unless (-d $b) { warn "error: cannot find binary package $arg in $search (skipping)\n"; $exit_code = 2; next; } } elsif ($pkg_mode eq 's') { unless (-d $s) { warn "error: cannot find source package $arg in $search (skipping)\n"; $exit_code = 2; next; } } elsif ($pkg_mode eq 'u') { unless (-d $u) { warn "error: cannot find udeb package $arg in $search (skipping)\n"; $exit_code = 2; next; } } else { # $pkg_mode eq 'a' unless (-d $b or -d $s or -d $u) { warn "error: cannot find binary, udeb or source package $arg in $search (skipping)\n"; $exit_code = 2; next; } } if (($pkg_mode eq 'b') or (($pkg_mode eq 'a') and (-d $b))) { schedule_package('b', get_bin_info_from_lab($b)); } if (($pkg_mode eq 's') or (($pkg_mode eq 'a') and (-d $s))) { schedule_package('s', get_src_info_from_lab($s)); } if (($pkg_mode eq 'u') or (($pkg_mode eq 'a') and (-d $u))) { schedule_package('u', get_bin_info_from_lab($u)); } } } if (not $check_everything and not $packages_file and ($#packages == -1)) { print "N: No packages selected.\n" if $verbose; exit $exit_code; } # }}} # {{{ A lone subroutine #---------------------------------------------------------------------------- # Check to make sure there are packages to check. sub set_value { my ($f,$target,$field,$source,$required) = @_; if ($required and not $source->{$field}) { fail("description file $f does not define required tag $field"); } $target->{$field} = $source->{$field}; delete $source->{$field}; } # }}} # {{{ Load information about collector scripts opendir(COLLDIR, "$LINTIAN_ROOT/collection") or fail("cannot read directory $LINTIAN_ROOT/collection"); for my $f (readdir COLLDIR) { next if $f =~ /^\./; next unless $f =~ /\.desc$/; print "N: Reading collector description file $f ...\n" if $debug >= 2; my @secs = read_dpkg_control("$LINTIAN_ROOT/collection/$f"); my $script; ($#secs+1 == 1) or fail("syntax error in description file $f: too many sections"); ($script = $secs[0]->{'collector-script'}) or fail("error in description file $f: `Collector-Script:' not defined"); delete $secs[0]->{'collector-script'}; $collection_info{$script}->{'script'} = $script; my $p = $collection_info{$script}; set_value($f, $p,'type',$secs[0],1); # convert Type: my ($b,$s,$u) = ( "", "", "" );; for (split(/\s*,\s*/o,$p->{'type'})) { if ($_ eq 'binary') { $b = 'b'; } elsif ($_ eq 'source') { $s = 's'; } elsif ($_ eq 'udeb') { $u = 'u'; } else { fail("unknown type $_ specified in description file $f"); } } $p->{'type'} = "$s$b$u"; set_value($f,$p,'unpack-level',$secs[0],1); set_value($f,$p,'output',$secs[0],1); set_value($f,$p,'order',$secs[0],1); if (exists $secs[0]->{'needs-info'} && defined $secs[0]->{'needs-info'}) { for (split(/\s*,\s*/o,$secs[0]->{'needs-info'})) { $p->{$_} = 1; } delete $secs[0]->{'needs-info'}; } # ignore Info: and other fields for now delete $secs[0]->{'info'}; delete $secs[0]->{'author'}; for (keys %{$secs[0]}) { print STDERR "warning: unused tag $_ in description file $f\n"; } if ($debug >= 2) { for (sort keys %$p) { print "N: $_: $p->{$_}\n"; } } } closedir(COLLDIR); # }}} # {{{ Now we're ready to load info about checks & tags no warnings 'once'; if (defined $experimental_output_opts) { $Tags::output_formatter = \&Tags::print_tag_new; my %opts = map { split(/=/) } split( /,/, $experimental_output_opts ); foreach (keys %opts) { if ($_ eq 'format') { if ($opts{$_} eq 'colons') { require Tags::ColonSeparated; $Tags::output_formatter = \&Tags::ColonSeparated::print_tag; } } no strict 'refs'; ${"Tags::$_"} = $opts{$_}; } } $Tags::show_info = $display_infotags; $Tags::show_experimental = $display_experimentaltags; $Tags::show_overrides = $show_overrides; $Tags::color = $color; use warnings; # load information about checker scripts opendir(CHECKDIR, "$LINTIAN_ROOT/checks") or fail("cannot read directory $LINTIAN_ROOT/checks"); for my $f (readdir CHECKDIR) { next if $f =~ /^\./; next unless $f =~ /\.desc$/; print "N: Reading checker description file $f ...\n" if $debug >= 2; my @secs = read_dpkg_control("$LINTIAN_ROOT/checks/$f"); my $script; ($script = $secs[0]->{'check-script'}) or fail("error in description file $f: `Check-Script:' not defined"); # ignore check `lintian' (this check is a special case and contains the # tag info for the lintian frontend--this script here) if ($secs[0]->{'check-script'} ne 'lintian') { delete $secs[0]->{'check-script'}; $check_info{$script}->{'script'} = $script; my $p = $check_info{$script}; set_value($f,$p,'type',$secs[0],1); # convert Type: my ($b,$s,$u) = ( "", "", "" ); for (split(/\s*,\s*/o,$p->{'type'})) { if ($_ eq 'binary') { $b = 'b'; } elsif ($_ eq 'source') { $s = 's'; } elsif ($_ eq 'udeb') { $u = 'u'; } else { fail("unknown type $_ specified in description file $f"); } } $p->{'type'} = "$s$b$u"; set_value($f,$p,'unpack-level',$secs[0],1); set_value($f,$p,'abbrev',$secs[0],1); if (exists $secs[0]->{'needs-info'} && defined $secs[0]->{'needs-info'}) { for (split(/\s*,\s*/o,$secs[0]->{'needs-info'})) { $p->{$_} = 1; } delete $secs[0]->{'needs-info'}; } # ignore Info: and other fields for now... delete $secs[0]->{'info'}; delete $secs[0]->{'standards-version'}; delete $secs[0]->{'author'}; for (keys %{$secs[0]}) { print STDERR "warning: unused tag $_ in description file $f\n"; } if ($debug >= 2) { for (sort keys %$p) { print "N: $_: $p->{$_}\n"; } } shift(@secs); map Tags::add_tag($_), @secs; } # end: if ne lintian } closedir(CHECKDIR); # }}} # {{{ Again some lone code the author just dumped where his cursor just happened to be if ($unpack_info) { # determine which info has been requested for my $i (split(/,/,$unpack_info)) { unless ($collection_info{$i}) { fail("unknown info specified: $i"); } $unpack_infos{$i} = 1; } } # create check_abbrev hash for my $c (keys %check_info) { $check_abbrev{$check_info{$c}->{'abbrev'}} = $c; } # }}} # {{{ determine which checks have been requested if ($action eq 'check') { my %dont_check = map { $_ => 1 } (split m/,/, ($dont_check || "")); $checks or ($checks = join(',',keys %check_info)); for my $c (split(/,/,$checks)) { if ($check_info{$c}) { if ($dont_check{$c} || ($check_info{$c}->{'abbrev'} && $dont_check{$check_info{$c}->{'abbrev'}})) { #user requested not to run this check } else { $checks{$c} = 1; } } elsif (exists $check_abbrev{$c}) { #abbrevs only used when -C is given, so we don't need %dont_check $checks{$check_abbrev{$c}} = 1; } else { fail("unknown check specified: $c"); } } # determine which info is needed by the checks for my $c (keys %checks) { for my $i (keys %collection_info) { # required by $c ? if ($check_info{$c}->{$i}) { $unpack_infos{$i} = 1; } } } } # }}} # {{{ determine which info is needed by the collection scripts for my $c (keys %unpack_infos) { for my $i (keys %collection_info) { # required by $c ? if ($collection_info{$c}->{$i}) { $unpack_infos{$i} = 1; } } } # }}} # {{{ process all packages in the archive? if ($check_everything) { # make sure package info is available read_src_list("$LINTIAN_LAB/info/source-packages", 0); read_bin_list("$LINTIAN_LAB/info/binary-packages", 0); read_udeb_list("$LINTIAN_LAB/info/udeb-packages", 0); if ($debug >= 2) { print STDERR "pkg_mode = $pkg_mode\n"; for my $arg (keys %source_info) { print STDERR $arg."\n"; } } if (($pkg_mode eq 'a') or ($pkg_mode eq 's')) { for my $arg (keys %source_info) { print STDERR "doing stuff with $LINTIAN_ARCHIVEDIR/$source_info{$arg}->{'file'}\n" if $debug; push(@packages,"s $source_info{$arg}->{'source'} $source_info{$arg}->{'version'} $LINTIAN_ARCHIVEDIR/$source_info{$arg}->{'file'}"); } } if (($pkg_mode eq 'a') or ($pkg_mode eq 'b')) { for my $arg (keys %binary_info) { print STDERR "doing stuff with $LINTIAN_ARCHIVEDIR/$binary_info{$arg}->{'file'}\n" if $debug; push(@packages,"b $binary_info{$arg}->{'package'} $binary_info{$arg}->{'version'} $LINTIAN_ARCHIVEDIR/$binary_info{$arg}->{'file'}"); } } if (($pkg_mode eq 'a') or ($pkg_mode eq 'u')) { for my $arg (keys %udeb_info) { print STDERR "doing stuff with $LINTIAN_ARCHIVEDIR/$udeb_info{$arg}->{'file'}\n" if $debug; push(@packages,"u $udeb_info{$arg}->{'package'} $udeb_info{$arg}->{'version'} $LINTIAN_ARCHIVEDIR/$udeb_info{$arg}->{'file'}"); } } # package list still empty? if ($#packages == -1) { print STDERR "warning: no packages found in distribution directory\n"; } } elsif ($packages_file) { # process all packages listed in packages file? open(IN, '<', $packages_file) or fail("cannot open packages file $packages_file for reading: $!"); while () { chop; push(@packages,$_); } close(IN); } # }}} # {{{ Some silent exit if ($#packages == -1) { print "N: No packages selected.\n" if $verbose; exit 0; } # }}} # {{{ Okay, now really processing the packages in one huge loop $unpack_infos{ "override-file" } = 1 unless $no_override; printf "N: Processing %d packages...\n",$#packages+1 if $verbose; if ($debug) { print "N: Selected action: $action\n"; print "N: Requested unpack level: $unpack_level\n"; printf "N: Requested data to collect: %s\n",join(',',keys %unpack_infos); printf "N: Selected checks: %s\n",join(',',keys %checks); } require Checker; require Lintian::Collect; # for each package (the `reverse sort' is to make sure that source packages are # before the corresponding binary packages--this has the advantage that binary # can use information from the source packages if these are unpacked) my %overrides; PACKAGE: for (reverse sort @packages) { m/^([bsu]) (\S+) (\S+) (.+)$/ or fail("internal error: syntax error in \@packages array: $_"); my ($type,$pkg,$ver,$file) = ($1,$2,$3,$4); my $long_type = ($type eq 'b' ? 'binary' : ($type eq 's' ? 'source' : 'udeb' )); print "N: ----\n" if $verbose; if ($verbose) { print "N: Processing $long_type package $pkg (version $ver) ...\n"; } # determine base directory my $base = "$LINTIAN_LAB/$long_type/$pkg"; unless ($base =~ m,^/,) { $base = "$cwd/$base"; } print "N: Base directory in lab: $base\n" if $debug; my $act_unpack_level = 0; # unpacked package up-to-date? if (-d $base) { my $remove_basedir = 0; # there's a base dir, so we assume that at least # one level of unpacking has been done $act_unpack_level = 1; # lintian status file exists? unless (-f "$base/.lintian-status") { print "N: No lintian status file found (removing old directory in lab)\n" if $verbose; $remove_basedir = 1; goto REMOVE_BASEDIR; } # read unpack status -- catch any possible errors my $data; eval { ($data) = read_dpkg_control("$base/.lintian-status"); }; if ($@) { # error! print "N: $@\n" if $verbose; $remove_basedir = 1; goto REMOVE_BASEDIR; } # compatible lintian version? if (not exists $data->{'lab-format'} or ($data->{'lab-format'} < $LAB_FORMAT)) { print "N: Lab directory was created by incompatible lintian version\n" if $verbose; $remove_basedir = 1; goto REMOVE_BASEDIR; } # version up to date? if (not exists $data->{'version'} or ($data->{'version'} ne $ver)) { print "N: Removing package in lab (newer version exists) ...\n" if $debug; $remove_basedir = 1; goto REMOVE_BASEDIR; } # unpack level defined? unless (exists $data->{'unpack-level'}) { print "N: warning: cannot determine unpack-level of package\n" if $verbose; $remove_basedir = 1; goto REMOVE_BASEDIR; } else { $act_unpack_level = $data->{'unpack-level'}; } # file modified? my $timestamp; my @stat; unless (@stat = stat $file) { print "N: Cannot stat file $file: $!\n"; } else { $timestamp = $stat[9]; } if ((not defined $timestamp) or (not exists $data->{'timestamp'}) or ($data->{'timestamp'} != $timestamp)) { print "N: Removing package in lab (package has been changed) ...\n" if $debug; $remove_basedir = 1; goto REMOVE_BASEDIR; } REMOVE_BASEDIR: if ($remove_basedir) { print "N: Removing $pkg\n" if $verbose; unless (remove_pkg($base)) { print "N: Skipping $action of $long_type package $pkg\n"; $exit_code = 2; next PACKAGE; } $act_unpack_level = 0; } } # unpack to requested unpack level $act_unpack_level = unpack_pkg($type,$base,$file,$act_unpack_level, $unpack_level); if ($act_unpack_level == -1) { print STDERR "internal error: could not unpack package to desired level: $!\n"; print "N: Skipping $action of $long_type package $pkg\n"; $exit_code = 2; next PACKAGE; } if (($action eq 'unpack') or ($action eq 'check')) { # collect info for my $coll (sort by_collection_order keys %unpack_infos) { my $ci = $collection_info{$coll}; # current type? next unless ($ci->{'type'} =~ m/$type/); # info already available? next if (-e "$base/$ci->{'output'}"); # unpack to desired unpack level (if necessary) $act_unpack_level = unpack_pkg($type,$base,$file,$act_unpack_level,$ci->{'unpack-level'}); if ($act_unpack_level == -1) { print STDERR "internal error: could not unpack package to desired level: $!\n"; print "N: Skipping $action of $long_type package $pkg\n"; $exit_code = 2; next PACKAGE; } # chdir to base directory unless (chdir($base)) { print STDERR "internal error: could not chdir into directory $base: $!\n"; print "N: Skipping $action of $long_type package $pkg\n"; $exit_code = 2; next PACKAGE; } # collect info remove_status_file($base); print "N: Collecting info: $coll ...\n" if $debug; if (spawn("$LINTIAN_ROOT/collection/$ci->{'script'}", $pkg, $long_type) != 0) { print STDERR "internal error: collect info $coll about package $pkg: $?\n"; print "N: Skipping $action of $long_type package $pkg\n"; $exit_code = 2; next PACKAGE; } } } if ($action eq 'check') { # read override file Tags::set_pkg( $file, $pkg, "", "", $long_type ); unless ($no_override) { if (open(O, '<', "$base/override")) { while () { chomp; next if m,^\s*(\#|\z),o; s/^\s+//o; s/\s+$//o; s/\s+/ /go; my $override = $_; $override =~ s/^\Q$pkg\E( \Q$long_type\E)?: //; if ($override eq '' or $override !~ /^[\w0-9.+-]+(\s+.*)?$/) { tag ('malformed-override', $_); } else { Tags::add_override($override); } } close(O); } } # perform checks my $info = Lintian::Collect->new($pkg, $long_type); for my $check (keys %checks) { my $ci = $check_info{$check}; # current type? next unless ($ci->{'type'} =~ m/$type/); # unpack to desired unpack level (if necessary) $act_unpack_level = unpack_pkg($type,$base,$file,$act_unpack_level,$ci->{'unpack-level'}); if ($act_unpack_level == -1) { print STDERR "internal error: could not unpack package to desired level: $!\n"; print "N: Skipping $action of $long_type package $pkg\n"; $exit_code = 2; next PACKAGE; } # chdir to base directory unless (chdir($base)) { print STDERR "internal error: could not chdir into directory $base: $!\n"; print "N: Skipping $action of $long_type package $pkg\n"; $exit_code = 2; next PACKAGE; } my $returnvalue = Checker::runcheck($pkg, $long_type, $info, $check); # Set exit_code correctly if there was not yet an exit code $exit_code = $returnvalue unless $exit_code; if ($returnvalue == 2) { print "N: Skipping $action of $long_type package $pkg\n"; next PACKAGE; } } unless ($exit_code) { my $stats = Tags::get_stats( $file ); if ($stats->{severity}{4}) { $exit_code = 1; } elsif ($fail_on_warnings && $stats->{severity}{2}) { $exit_code = 1; } } # report unused overrides if (not $no_override) { my $overrides = Tags::get_overrides( $file ); for my $o (sort keys %$overrides) { next if $overrides->{$o}; tag( "unused-override", $o ); } } # Report override statistics. if (not $no_override and not $show_overrides) { my $stats = Tags::get_stats($file); my $short = $file; $short =~ s%.*/%%; my $errors = $stats->{overrides}{by_severity}{4} || 0; my $warnings = $stats->{overrides}{by_severity}{2} || 0; my $info = $stats->{overrides}{by_severity}{0} || 0; $overrides{errors} += $errors; $overrides{warnings} += $warnings; $overrides{info} += $info; } } # chdir to lintian root directory (to unlock $base so it can be removed below) unless (chdir($LINTIAN_ROOT)) { print STDERR "internal error: could not chdir into directory $LINTIAN_ROOT: $!\n"; print "N: Skipping $action of $long_type package $pkg\n"; $exit_code = 2; next PACKAGE; } # clean up if ($act_unpack_level > $unpack_level) { $act_unpack_level = clean_pkg($type,$base,$file,$act_unpack_level,$unpack_level); if ($act_unpack_level == -1) { print STDERR "error: could not clean up laboratory for package $pkg: $!\n"; print "N: Skipping clean up\n"; $exit_code = 2; next PACKAGE; } } # create Lintian status file if (($act_unpack_level > 0) and (not -f "$base/.lintian-status")) { my @stat; unless (@stat = stat $file) { print STDERR "internal error: cannot stat file $file: $!\n"; print "N: Skipping creation of status file\n"; $exit_code = 2; next PACKAGE; } my $timestamp = $stat[9]; unless (open(STATUS, '>', "$base/.lintian-status")) { print STDERR "internal error: could not create status file $base/.lintian-status for package $pkg: $!\n"; $exit_code = 2; next PACKAGE; } print STATUS "Lintian-Version: $LINTIAN_VERSION\n"; print STATUS "Lab-Format: $LAB_FORMAT\n"; print STATUS "Package: $pkg\n"; print STATUS "Version: $ver\n"; print STATUS "Type: $type\n"; print STATUS "Unpack-Level: $act_unpack_level\n"; print STATUS "Timestamp: $timestamp\n"; close(STATUS); } } if ($action eq 'check' and not $quiet and not $no_override and not $show_overrides) { my $errors = $overrides{errors} || 0; my $warnings = $overrides{warnings} || 0; my $info = $overrides{info} || 0; my $total = $errors + $warnings + $info; if ($total > 0) { my $total = ($total == 1) ? "$total tag overridden" : "$total tags overridden"; my @output; if ($errors) { push (@output, ($errors == 1) ? "$errors error" : "$errors errors"); } if ($warnings) { push (@output, ($warnings == 1) ? "$warnings warning" : "$warnings warnings"); } if ($info) { push (@output, "$info info"); } print "N: $total (", join (', ', @output), ")\n"; } } # }}} # {{{ close up lintian-info pipe if needed # did I pipe output through lintian-info? if ($lintian_info) { close(OUTPUT_PIPE) or fail("cannot close output pipe to $lintian_info_cmd: $!"); select STDOUT; } # }}} exit $exit_code; # {{{ Some subroutines sub unpack_pkg { my ($type,$base,$file,$cur_level,$new_level) = @_; printf("N: Current unpack level is %d\n",$cur_level) if $debug; return $cur_level if $cur_level == $new_level; # remove .lintian-status file remove_status_file($base); if ( ($cur_level == 0) and (-d $base) ) { # We were lied to, there's something already there - clean it up first remove_pkg($base) or return -1; } if ( ($new_level >= 1) and (not defined ($cur_level) or ($cur_level < 1)) ) { # create new directory print "N: Unpacking package to level 1 ...\n" if $debug; if (($type eq 'b') || ($type eq 'u')) { spawn("$LINTIAN_ROOT/unpack/unpack-binpkg-l1", $base, $file) == 0 or return -1; } else { spawn("$LINTIAN_ROOT/unpack/unpack-srcpkg-l1", $base, $file) == 0 or return -1; } $cur_level = 1; } if ( ($new_level >= 2) and (not defined ($cur_level) or ($cur_level < 2)) ) { # unpack package contents print "N: Unpacking package to level 2 ...\n" if $debug; if (($type eq 'b') || ($type eq 'u')) { spawn("$LINTIAN_ROOT/unpack/unpack-binpkg-l2", $base) == 0 or return -1; } else { print "N: $LINTIAN_ROOT/unpack/unpack-srcpkg-l2 $base\n" if $debug; spawn("$LINTIAN_ROOT/unpack/unpack-srcpkg-l2", $base) == 0 or return -1; } $cur_level = 2; } return $cur_level; } # TODO: is this the best way to clean dirs in perl? # no, look at File::Path module sub clean_pkg { my ($type,$base,$file,$cur_level,$new_level) = @_; return $cur_level if $cur_level == $new_level; if ($new_level < 1) { # remove base directory remove_pkg($base) or return -1; return 0; } if ( ($new_level < 2) and defined ($cur_level) and ($cur_level >= 2) ) { # remove .lintian-status file remove_status_file($base); # remove unpacked/ directory print "N: Decreasing unpack level to 1 (removing files) ...\n" if $debug; if ( -l "$base/unpacked" ) { spawn('rm', '-rf', '--', "$base/".readlink( "$base/unpacked" )) == 0 or return -1; spawn('rm', '-rf', '--', "$base/unpacked") == 0 or return -1; } else { spawn('rm', '-rf', '--', "$base/unpacked") == 0 or return -1; } $cur_level = 1; } return $cur_level; } # this function removes a package's base directory in the lab completely sub remove_pkg { my ($base) = @_; print "N: Removing package in lab ...\n" if $debug; if (spawn('rm', '-rf', '--', $base) != 0) { print STDERR "error: cannot remove directory $base: $!\n"; return 0; } return 1; } sub remove_status_file { my ($base) = @_; # status file exists? if (not -e "$base/.lintian-status") { return 1; } if (not unlink("$base/.lintian-status")) { print STDERR "internal error: cannot remove status file $base/.lintian-status: $!\n"; return 0; } return 1; } # ------------------------------- # get package name, version, and file name from the lab sub get_bin_info_from_lab { my ($base_dir) = @_; my ($pkg,$ver,$file); ($pkg = read_file("$base_dir/fields/package")) or fail("cannot read file $base_dir/fields/package: $!"); ($ver = read_file("$base_dir/fields/version")) or fail("cannot read file $base_dir/fields/version: $!"); ($file = readlink("$base_dir/deb")) or fail("cannot read link $base_dir/deb: $!"); return ($pkg,$ver,$file); } # get package name, version, and file name from the lab sub get_src_info_from_lab { my ($base_dir) = @_; my ($pkg,$ver,$file); ($pkg = read_file("$base_dir/fields/source")) or fail("cannot read file $base_dir/fields/source: $!"); ($ver = read_file("$base_dir/fields/version")) or fail("cannot read file $base_dir/fields/version: $!"); ($file = readlink("$base_dir/dsc")) or fail("cannot read link $base_dir/dsc: $!"); return ($pkg,$ver,$file); } # schedule a package for processing sub schedule_package { my ($type,$pkg,$ver,$file) = @_; my $s = "$type $pkg $ver $file"; if ( $already_scheduled{$s}++ ) { if ($verbose) { printf "N: Ignoring duplicate %s package $pkg (version $ver)\n", $type eq 'b' ? 'binary' : ($type eq 's' ? 'source': 'udeb'); } return; } push(@packages,$s); } # ------------------------------- # read first line of a file sub read_file { my $t; open(T, '<', $_[0]) or return; chop($t = ); close(T) or return; return $t; } # sort collection list by `order' sub by_collection_order { $collection_info{$a}->{'order'} <=> $collection_info{$b}->{'order'}; } # }}} # {{{ Exit handler. sub END { # Prevent Lab::delete from affecting the exit code. local $?; $SIG{'INT'} = 'DEFAULT'; $SIG{'QUIT'} = 'DEFAULT'; $LAB->delete() if $LAB and not $keep_lab; } sub interrupted { $SIG{$_[0]} = 'DEFAULT'; die "N: Interrupted.\n"; } # }}} # Local Variables: # indent-tabs-mode: t # cperl-indent-level: 4 # End: # vim: sw=4 ts=8 noet fdm=marker