Added lots more modules from lintian. Maemian appears to work.
authorJeremiah Foster <jeremiah@test.maemo.org>
Wed, 3 Jun 2009 23:05:45 +0000 (02:05 +0300)
committerJeremiah Foster <jeremiah@test.maemo.org>
Wed, 3 Jun 2009 23:05:45 +0000 (02:05 +0300)
136 files changed:
checks/binaries [new file with mode: 0644]
checks/binaries.desc [new file with mode: 0644]
checks/changelog-file [new file with mode: 0644]
checks/changelog-file.desc [new file with mode: 0644]
checks/common_data.pm [new file with mode: 0644]
checks/conffiles [new file with mode: 0644]
checks/conffiles.desc [new file with mode: 0644]
checks/control-file [new file with mode: 0644]
checks/control-file.desc [new file with mode: 0644]
checks/control-files [new file with mode: 0644]
checks/control-files.desc [new file with mode: 0644]
checks/copyright-file [new file with mode: 0644]
checks/copyright-file.desc [new file with mode: 0644]
checks/cruft [new file with mode: 0644]
checks/cruft.desc [new file with mode: 0644]
checks/deb-format [new file with mode: 0644]
checks/deb-format.desc [new file with mode: 0644]
checks/debconf [new file with mode: 0644]
checks/debconf.desc [new file with mode: 0644]
checks/debhelper [new file with mode: 0644]
checks/debhelper.desc [new file with mode: 0644]
checks/debian-readme [new file with mode: 0644]
checks/debian-readme.desc [new file with mode: 0644]
checks/description [new file with mode: 0644]
checks/description.desc [new file with mode: 0644]
checks/etcfiles [new file with mode: 0644]
checks/etcfiles.desc [new file with mode: 0644]
checks/fields [new file with mode: 0644]
checks/fields.desc [new file with mode: 0644]
checks/files [new file with mode: 0644]
checks/files.desc [new file with mode: 0644]
checks/huge-usr-share [new file with mode: 0644]
checks/huge-usr-share.desc [new file with mode: 0644]
checks/infofiles [new file with mode: 0644]
checks/infofiles.desc [new file with mode: 0644]
checks/init.d [new file with mode: 0644]
checks/init.d.desc [new file with mode: 0644]
checks/manpages [new file with mode: 0644]
checks/manpages.desc [new file with mode: 0644]
checks/md5sums [new file with mode: 0644]
checks/md5sums.desc [new file with mode: 0644]
checks/menu-format [new file with mode: 0644]
checks/menu-format.desc [new file with mode: 0644]
checks/menus [new file with mode: 0644]
checks/menus.desc [new file with mode: 0644]
checks/nmu [new file with mode: 0644]
checks/nmu.desc [new file with mode: 0644]
checks/patch-systems [new file with mode: 0644]
checks/patch-systems.desc [new file with mode: 0644]
checks/po-debconf [new file with mode: 0644]
checks/po-debconf.desc [new file with mode: 0644]
checks/rules [new file with mode: 0644]
checks/rules.desc [new file with mode: 0644]
checks/scripts [new file with mode: 0644]
checks/scripts.desc [new file with mode: 0644]
checks/shared-libs [new file with mode: 0644]
checks/shared-libs.desc [new file with mode: 0644]
checks/standards-version [new file with mode: 0644]
checks/standards-version.desc [new file with mode: 0644]
checks/version-substvars [new file with mode: 0644]
checks/version-substvars.desc [new file with mode: 0644]
checks/watch-file [new file with mode: 0644]
checks/watch-file.desc [new file with mode: 0644]
collection/changelog-file [new file with mode: 0755]
collection/changelog-file.desc [new file with mode: 0644]
collection/copyright-file [new file with mode: 0755]
collection/copyright-file.desc [new file with mode: 0644]
collection/debfiles [new file with mode: 0755]
collection/debfiles.desc [new file with mode: 0644]
collection/debian-readme [new file with mode: 0755]
collection/debian-readme.desc [new file with mode: 0644]
collection/diffstat [new file with mode: 0755]
collection/diffstat.desc [new file with mode: 0644]
collection/doc-base-files [new file with mode: 0755]
collection/doc-base-files.desc [new file with mode: 0644]
collection/file-info [new file with mode: 0755]
collection/file-info.desc [new file with mode: 0644]
collection/init.d [new file with mode: 0755]
collection/init.d.desc [new file with mode: 0644]
collection/md5sums [new file with mode: 0755]
collection/md5sums.desc [new file with mode: 0644]
collection/menu-files [new file with mode: 0755]
collection/menu-files.desc [new file with mode: 0644]
collection/objdump-info [new file with mode: 0755]
collection/objdump-info.desc [new file with mode: 0644]
collection/override-file [new file with mode: 0755]
collection/override-file.desc [new file with mode: 0644]
collection/scripts [new file with mode: 0755]
collection/scripts.desc [new file with mode: 0644]
collection/source-control-file [new file with mode: 0755]
collection/source-control-file.desc [new file with mode: 0644]
collection/strings [new file with mode: 0755]
collection/strings.desc [new file with mode: 0644]
data/README [new file with mode: 0644]
data/binaries/multiarch [new file with mode: 0644]
data/changelog-file/ubuntu-dists [new file with mode: 0644]
data/debhelper/dh_commands [new file with mode: 0644]
data/debhelper/dh_packages [new file with mode: 0644]
data/debhelper/filename-config-files [new file with mode: 0644]
data/debhelper/maint_commands [new file with mode: 0644]
data/debhelper/miscDepends_commands [new file with mode: 0644]
data/doc-base/sections [new file with mode: 0644]
data/fields/architectures [new file with mode: 0644]
data/fields/obsolete-packages [new file with mode: 0644]
data/fields/perl-provides [new file with mode: 0644]
data/fields/virtual-packages [new file with mode: 0644]
lib/Checker.pm [new file with mode: 0644]
lib/Maemian/Collect.pm [new file with mode: 0644]
lib/Maemian/Collect/Binary.pm [new file with mode: 0644]
lib/Maemian/Collect/Source.pm [new file with mode: 0644]
lib/Maemian/Command.pm
lib/Maemian/Data.pm
lib/Maemian/Output.pm
lib/Maemian/Relation.pm [new file with mode: 0644]
lib/Maemian/Relation/Version.pm [new file with mode: 0644]
lib/Maemian/Schedule.pm
lib/Maemian/Tag/Info.pm
lib/Read_pkglists.pm
lib/Spelling.pm [new file with mode: 0644]
lib/Tags.pm
lib/Text_utils.pm
lib/Util.pm
maemian
reporting/checkout-release [new file with mode: 0755]
reporting/config [new file with mode: 0644]
reporting/harness [new file with mode: 0755]
reporting/html_reports [new file with mode: 0755]
reporting/lintian-dummy.cfg [new file with mode: 0644]
reporting/lintian.css [new file with mode: 0644]
unpack/list-binpkg [new file with mode: 0755]
unpack/list-srcpkg [new file with mode: 0755]
unpack/list-udebpkg [new file with mode: 0755]
unpack/unpack-binpkg-l1 [new file with mode: 0755]
unpack/unpack-binpkg-l2 [new file with mode: 0755]
unpack/unpack-srcpkg-l1 [new file with mode: 0755]
unpack/unpack-srcpkg-l2 [new file with mode: 0755]

diff --git a/checks/binaries b/checks/binaries
new file mode 100644 (file)
index 0000000..383c6d0
--- /dev/null
@@ -0,0 +1,363 @@
+# binaries -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Christian Schwarz and Richard Braakman
+#
+# 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 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.
+
+package Maemian::binaries;
+use strict;
+use Tags;
+use Util;
+use Spelling;
+
+use File::Spec;
+
+# Table based on checks/emdebian's %archdetecttable, as found in
+# emdebian-tools.
+our %ARCH_REGEX = (
+       '32'             => qr'ELF 32-bit',
+       '64'             => qr'ELF 64-bit',
+       'alpha'          => qr'ELF 64-bit LSB .* Alpha',
+       'amd64'          => qr'ELF 64-bit LSB .* x86-64, .* (?:GNU/Linux|(?!GNU))',
+       'arm'            => qr'ELF 32-bit LSB .* ARM, version \d,',
+       'armeb'          => qr'ELF 32-bit MSB .* ARM',
+       'armel'          => qr'ELF 32-bit LSB .* \(SYSV\)',
+       'hppa'           => qr'ELF 32-bit MSB .* PA-RISC',
+       'hurd-i386'      => qr'ELF 32-bit LSB .* Intel 80386, .* (?:GNU/Hurd|(?!GNU))',
+       'i386'           => qr'ELF 32-bit LSB .* 80386, .* (?:GNU/Linux|(?!GNU))',
+       'ia64'           => qr'ELF 64-bit LSB .* IA-64',
+       'kfreebsd-amd64' => qr'ELF 64-bit LSB .* x86-64, .* (?:GNU/kFreeBSD|(?!GNU))',
+       'kfreebsd-i386'  => qr'ELF 32-bit LSB .* 80386, .* (?:GNU/kFreeBSD|(?!GNU))',
+       'lpia'           => qr'ELF 32-bit LSB .* 80386, .* (?:GNU/Linux|(?!GNU))',
+       'm32r'           => qr'ELF 32-bit MSB .* M32R',
+       'm68k'           => qr'ELF 32-bit MSB .* 680[02]0',
+       'mips'           => qr'ELF 32-bit MSB .* MIPS',
+       'mipsel'         => qr'ELF 32-bit LSB .* MIPS',
+       'mips64'         => qr'ELF 64-bit MSB .* MIPS',
+       'mipsel64'       => qr'ELF 64-bit LSB .* MIPS',
+       'powerpc'        => qr'ELF 32-bit MSB .* PowerPC',
+       'ppc64'          => qr'ELF 64-bit MSB .* PowerPC',
+       's390'           => qr'ELF 32-bit MSB .* S.390',
+       's390x'          => qr'ELF 64-bit MSB .* S.390',
+       'sparc'          => qr'ELF 32-bit MSB .* SPARC',
+       'sparc64'        => qr'ELF 64-bit MSB .* SPARC');
+
+our $multiarch;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+my $arch;
+my $dynsyms = 0;
+my $needs_libc = '';
+my $needs_libc_file;
+my $needs_libc_count = 0;
+my $needs_depends_line = 0;
+my $has_perl_lib = 0;
+
+my %SONAME;
+
+$arch = $info->field('architecture');
+
+foreach my $file (sort keys %{$info->objdump_info}) {
+    my $objdump = $info->objdump_info->{$file};
+    $file = './' . $file;
+
+    if (defined $objdump->{SONAME}) {
+       foreach my $soname (@{$objdump->{SONAME}}) {
+           $SONAME{$soname} ||= [];
+           push @{$SONAME{$soname}}, $file;
+       }
+    }
+    foreach my $symbol (@{$objdump->{SYMBOLS}}) {
+       my ($foo, $sec, $sym) = @{$symbol};
+       if ($arch ne 'hppa') {
+           if ($foo eq '.text' and $sec eq 'Base' and
+               $sym eq '__gmon_start__') {
+               tag "binary-compiled-with-profiling-enabled", "$file";
+           }
+       } else {
+           if ( ($sec =~ /^GLIBC_.*/) and ($sym eq '_mcount') ) {
+               tag "binary-compiled-with-profiling-enabled", "$file";
+           }
+       }
+    }
+    foreach (@{$objdump->{NOTES}}) {
+       if (m/^File format not recognized$/) {
+            tag "apparently-corrupted-elf-binary", "$file";
+       } elsif (m/^File truncated$/) {
+            tag "apparently-truncated-elf-binary", "$file";
+       } elsif (m/^Packed with UPX$/) {
+           tag "binary-file-compressed-with-upx", "$file";
+       } elsif (m/^Invalid operation$/) {
+           tag "binary-with-bad-dynamic-table", "$file" unless $file =~ m%^\./usr/lib/debug/%;
+       }
+    }
+}
+
+# For the package naming check, filter out SONAMEs where all the files are at
+# paths other than /lib, /usr/lib, or /usr/X11R6/lib.  This avoids false
+# positives with plugins like Apache modules, which may have their own SONAMEs
+# but which don't matter for the purposes of this check.  Also filter out
+# nsswitch modules
+sub lib_soname_path {
+    my (@paths) = @_;
+    foreach my $path (@paths) {
+       next if $path =~ m%^(?:\.?/)?lib/libnss_[^.]+\.so(?:\.[0-9]+)$%;
+       return 1 if $path =~ m%^(?:\.?/)?lib/[^/]+$%;
+       return 1 if $path =~ m%^(?:\.?/)?usr/lib/[^/]+$%;
+       return 1 if $path =~ m%^(?:\.?/)?usr/X11R6/lib/[^/]+$%;
+    }
+    return 0;
+}
+my @sonames = sort grep { lib_soname_path (@{$SONAME{$_}}) } keys %SONAME;
+
+# try to identify transition strings
+my $base_pkg = $pkg;
+$base_pkg =~ s/c102\b//o;
+$base_pkg =~ s/c2a?\b//o;
+$base_pkg =~ s/\dg$//o;
+$base_pkg =~ s/gf$//o;
+$base_pkg =~ s/-udeb$//o;
+$base_pkg =~ s/^lib64/lib/o;
+
+my $match_found = 0;
+foreach my $expected_name (@sonames) {
+    $expected_name =~ s/([0-9])\.so\./$1-/;
+    $expected_name =~ s/\.so(?:\.|\z)//;
+    $expected_name =~ s/_/-/g;
+
+    if ((lc($expected_name) eq $pkg)
+       || (lc($expected_name) eq $base_pkg)) {
+       $match_found = 1;
+       last;
+    }
+}
+
+tag "package-name-doesnt-match-sonames", "@sonames"
+    if @sonames && !$match_found;
+
+my %directories;
+foreach (sort keys %{$info->file_info}) {
+    next unless length $_;
+    my $data = $info->file_info->{$_};
+    next unless $data =~ /^directory$/ or $data =~ / link to /;
+    $directories{"/$_"}++;
+}
+
+# If we have an unknown architecture, pretend that all binaries are fine.
+if ($arch ne 'all' and not exists($ARCH_REGEX{$arch})) {
+    $ARCH_REGEX{$arch} = qr/./;
+}
+
+# process all files in package
+foreach my $file (sort keys %{$info->file_info}) {
+    my $fileinfo = $info->file_info->{$file};
+    my $objdump = $info->objdump_info->{$file};
+
+    $file = './' . $file;
+
+    # binary or object file?
+    next unless ($fileinfo =~ m/^[^,]*\bELF\b/) or ($fileinfo =~ m/\bcurrent ar archive\b/);
+
+    # Warn about Architecture: all packages that contain shared libraries, but
+    # only if those libraries aren't installed in a multiarch directory.  The
+    # package may be a support package for cross-compiles.
+    if ($arch eq 'all') {
+       my ($arch) = ($file =~ m,^\./(?:usr/)?lib/([^/]+)/,);
+       $multiarch = Maemian::Data->new('binaries/multiarch')
+           unless defined($multiarch);
+       unless ($arch and $multiarch->known($arch)) {
+           tag "arch-independent-package-contains-binary-or-object", "$file";
+       }
+    }
+
+    # ELF?
+    next unless $fileinfo =~ m/^[^,]*\bELF\b/o;
+
+    if ($file =~ m,^\./etc/,) {
+       tag "binary-in-etc", "$file";
+    }
+
+    if ($file =~ m,^\./usr/share/,) {
+       tag "arch-dependent-file-in-usr-share", "$file";
+    }
+
+    if ($arch ne 'all' and $fileinfo !~ m/$ARCH_REGEX{$arch}/) {
+       if ($file =~ m,/lib(\d{2})/, or $file =~ m,/emul/ia(\d{2}),) {
+           tag "binary-from-other-architecture", $file
+               unless ($fileinfo =~ m/$ARCH_REGEX{$1}/);
+       } elsif ($arch eq 'amd64' and $fileinfo =~ m/$ARCH_REGEX{i386}/) {
+           # Ignore i386 binaries in amd64 packages for right now.
+       } else {
+           $multiarch = Maemian::Data->new('binaries/multiarch')
+               unless defined($multiarch);
+           tag "binary-from-other-architecture", $file
+               unless (grep { $file =~ m,/\Q$_\E/, } $multiarch->all);
+       }
+    }
+
+    # stripped?
+    if ($fileinfo =~ m,not stripped\s*$,o) {
+       # Is it an object file (which generally can not be stripped),
+       # a kernel module, debugging symbols, or perhaps a debugging package?
+       # Ocaml executables are exempted, see #252695
+       unless ($file =~ m,\.k?o$, or $pkg =~ m/-dbg$/ or $pkg =~ m/debug/
+               or $file =~ m,/lib/debug/, or exists $objdump->{OCAML}) {
+           tag "unstripped-binary-or-object", "$file";
+       }
+    } else {
+       # stripped but a debug or profiling library?
+       if (($file =~ m,/lib/debug/,o) or ($file =~ m,/lib/profile/,o)) {
+           tag "library-in-debug-or-profile-should-not-be-stripped", "$file";
+       } else {
+           # appropriately stripped, but is it stripped enough?
+           if (exists $objdump->{NOTE_SECTION}) {
+               tag "binary-has-unneeded-section", "$file .note";
+           }
+           if (exists $objdump->{COMMENT_SECTION}) {
+               tag "binary-has-unneeded-section", "$file .comment";
+           }
+       }
+    }
+
+    # rpath is disallowed, except in private directories
+    if (exists $objdump->{RPATH}) {
+       foreach my $rpath (map {File::Spec->canonpath($_)} keys %{$objdump->{RPATH}}) {
+           next if $rpath =~ m,^/usr/lib/(?:games/)?\Q$pkg\E(?:/|\z),;
+           next if $rpath =~ m,^\$ORIGIN$,;
+           next if $directories{$rpath};
+           tag "binary-or-shlib-defines-rpath", "$file $rpath";
+       }
+    }
+
+    my $strings = slurp_entire_file("strings/$file");
+    spelling_check('spelling-error-in-binary', $strings, $file);
+
+    if ($pkg !~ m/^zlib.+/
+       and $strings =~ /(?:in|de)flate (?:\d[ \w.\-]{1,20}[\w.\-])/m) {
+       tag "embedded-zlib", $file;
+    }
+
+    # binary or shared object?
+    next unless ($fileinfo =~ m/executable/) or ($fileinfo =~ m/shared object/);
+    next if $type eq 'udeb';
+
+    # Perl library?
+    if ($file =~ m,^\./usr/lib/perl5/.*\.so$,) {
+       $has_perl_lib = 1;
+    }
+
+    # Something other than detached debugging symbols in /usr/lib/debug paths.
+    if ($file =~ m,^\./usr/lib/debug/(?:lib\d*|s?bin|usr|opt|dev|emul)/,) {
+       if (exists($objdump->{NEEDED})) {
+           tag "debug-file-should-use-detached-symbols", $file;
+       }
+    }
+
+    # statically linked?
+    if (!exists($objdump->{NEEDED}) || !defined($objdump->{NEEDED})) {
+       if ($fileinfo =~ m/shared object/o) {
+            # Some exceptions: detached debugging information and the dynamic
+            # loader (which itself has no dependencies).
+            next if ($file =~ m%^\./usr/lib/debug/%);
+            next if ($file =~ m%^\./lib/(?:[\w/]+/)?ld-[\d.]+\.so$%);
+           tag "shared-lib-without-dependency-information", "$file";
+       } else {
+           # Some exceptions: files in /boot, /usr/lib/debug/*, named *-static or
+           # *.static, or *-static as package-name.
+           next if ($file =~ m%^./boot/%);
+           # klibc binaries appear to be static.
+           next if ($objdump->{KLIBC});
+           # Location of debugging symbols:
+           next if ($file =~ m%^./usr/lib/debug/%);
+           next if ($file =~ /(?:\.|-)static$/);
+           next if ($pkg =~ /-static$/);
+           tag "statically-linked-binary", "$file";
+       }
+    } else {
+       my $lib;
+       my $no_libc = 1;
+       $needs_depends_line = 1;
+       for $lib (@{$objdump->{NEEDED}}) {
+           if ($lib =~ /^libc\.so\.(\d+.*)/) {
+               $needs_libc = "libc$1";
+               $needs_libc_file = $file unless $needs_libc_file;
+               $needs_libc_count++;
+               $no_libc = 0;
+           }
+       }
+       if ($no_libc and not $file =~ m,/libc\b,) {
+           if ($fileinfo =~ m/shared object/) {
+               tag "library-not-linked-against-libc", "$file";
+           } else {
+               tag "program-not-linked-against-libc", "$file";
+           }
+       }
+    }
+}
+
+# Find the package dependencies, which is used by various checks.
+my $depends = '';
+if (defined $info->field('pre-depends')) {
+    $depends = $info->field('pre-depends');
+}
+if (defined $info->field('depends')) {
+    $depends .= ', ' if $depends;
+    $depends .= $info->field('depends');
+}
+$depends =~ s/\n/ /g;
+
+# Check for a libc dependency.
+if ($needs_depends_line) {
+    if ($depends && $needs_libc && $pkg !~ /^libc[\d.]+(?:-|\z)/) {
+        # Match libcXX or libcXX-*, but not libc3p0.
+        my $re = qr/(?:^|,)\s*\Q$needs_libc\E\b/;
+        if ($depends !~ /$re/) {
+            my $others = '';
+           $needs_libc_count--;
+            if ($needs_libc_count > 0) {
+                $others = " and $needs_libc_count others";
+            }
+            tag "missing-dependency-on-libc",
+               "needed by $needs_libc_file$others";
+        }
+    } elsif (!$depends) {
+       tag "missing-depends-line";
+    }
+}
+
+# Check for a Perl dependency.
+if ($has_perl_lib) {
+    my $re = qr/(?:^|,)\s*perlapi-[\d.]+(?:\s*\[[^\]]+\])?\s*(?:,|\z)/;
+    unless ($depends =~ /$re/) {
+       tag 'missing-dependency-on-perlapi';
+    }
+}
+
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl ts=8 sw=4
diff --git a/checks/binaries.desc b/checks/binaries.desc
new file mode 100644 (file)
index 0000000..f82c230
--- /dev/null
@@ -0,0 +1,249 @@
+Check-Script: binaries
+Author: Christian Schwarz <schwarz@debian.org>
+Abbrev: bin
+Type: binary, udeb
+Unpack-Level: 1
+Needs-Info: objdump-info, file-info, strings
+Info: This script checks binaries and object files for bugs.
+
+Tag: arch-independent-package-contains-binary-or-object
+Severity: serious
+Certainty: possible
+Info: The package contains a binary or object file but is tagged
+ Architecture: all.
+ .
+ If this package contains binaries or objects for cross-compiling or
+ binary blobs for other purposes independent of the host architecture
+ (such as BIOS updates or firmware), please add a Maemian override.
+
+Tag: unstripped-binary-or-object
+Severity: important
+Certainty: certain
+Ref: policy 10.1, policy 10.2
+Info: The package installs an unstripped binary or object file.
+ .
+ Please note, that shared libraries have to be stripped with the
+ <tt>--strip-unneeded</tt> option.
+
+Tag: library-in-debug-or-profile-should-not-be-stripped
+Severity: important
+Certainty: certain
+Info: Libraries in <tt>.../lib/debug</tt> or in
+ <tt>.../lib/profile</tt> usually should not be stripped.
+
+Tag: statically-linked-binary
+Severity: important
+Certainty: possible
+Info: The package installs a statically linked binary or object file.
+ .
+ Usually this is a bug. Otherwise, please install an override if your package
+ is an exception. Note that binaries named *-static and *.static are already
+ excluded, just as any binaries in packages named *-static.
+
+Tag: library-not-linked-against-libc
+Severity: minor
+Certainty: certain
+Info: The package installs a library which is not dynamically linked
+ against libc.
+
+Tag: program-not-linked-against-libc
+Severity: minor
+Certainty: certain
+Info: The package installs a binary which is not dynamically linked
+ against libc.
+
+Tag: binary-or-shlib-defines-rpath
+Severity: normal
+Certainty: certain
+Ref: http://wiki.debian.org/RpathIssue
+Info: The binary or shared library sets RPATH.  This overrides the normal
+ library search path, possibly interfering with local policy and causing
+ problems for multilib, among other issues.
+ .
+ The only time a binary or shared library in a Debian package should set
+ RPATH is if it is linked to private shared libraries in the same package.
+ In that case, place those private shared libraries in
+ <tt>/usr/lib/<i>package</i></tt>.  Libraries used by binaries in other
+ packages should be placed in <tt>/lib</tt> or <tt>/usr/lib</tt> as
+ appropriate, with a proper SONAME, in which case RPATH is unnecessary.
+ .
+ To fix this problem, look for link lines like:
+     gcc test.o -o test -Wl,--rpath,/usr/local/lib
+ or
+     gcc test.o -o test -R/usr/local/lib
+ and remove the <tt>-Wl,--rpath</tt> or <tt>-R</tt> argument.  You can also
+ use the chrpath utility to remove the RPATH.
+
+Tag: binary-has-unneeded-section
+Severity: wishlist
+Certainty: certain
+Info: The binary or shared library is stripped, but still contains a
+ section that is not useful.  You should call strip with
+ <tt>--remove-section=.comment --remove-section=.note</tt> to remove the
+ <tt>.note</tt> and <tt>.comment</tt> sections.
+ .
+ <tt>dh_strip</tt> will do this automatically for you, but
+ <tt>install -s</tt> not because it calls strip without any arguments.
+
+Tag: missing-depends-line
+Severity: normal
+Certainty: certain
+Info: The package contains an ELF binary with dynamic dependencies,
+ but does not have a Depends line in its control file.  This usually
+ means that a call to <tt>dpkg-shlibdeps</tt> is missing from the
+ package's <tt>debian/rules</tt> file.
+
+Tag: shared-lib-without-dependency-information
+Severity: normal
+Certainty: certain
+Info: The listed shared library doesn't include information about which
+ other libraries the library was linked against. (When running "<tt>ldd
+ foo.so</tt>" ldd should report about these other libraries. In your
+ case, ldd just reports "statically linked".)
+ .
+ To fix this, you should explicitly specify the libraries which are
+ used (e.g., "-lc") when building the shared library with "ld".
+ .
+ If you have questions about this, please contact &debdev;.
+
+Tag: arch-dependent-file-in-usr-share
+Severity: important
+Certainty: certain
+Ref: fhs usrsharearchitectureindependentdata
+Info: This package installs an ELF binary in the <tt>/usr/share</tt>
+ hierarchy, which is reserved for architecture-independent files.
+
+Tag: binary-in-etc
+Severity: important
+Certainty: certain
+Ref: fhs etchostspecificsystemconfiguration
+Info: This package installs an ELF binary in <tt>/etc</tt>.
+ The Filesystem Hierarchy Standard forbids this.
+
+Tag: binary-compiled-with-profiling-enabled
+Severity: normal
+Certainty: certain
+Info: While profiling is useful for testing and debugging purposes, enabling
+ it causes a program to leave gmon.out files whenever a user runs it.
+
+Tag: binary-file-compressed-with-upx
+Severity: important
+Certainty: certain
+Info: Debian does not allow binaries to be compressed by UPX.
+
+Tag: package-name-doesnt-match-sonames
+Severity: normal
+Certainty: possible
+Info: The package name of a library package should usually reflect
+ the soname of the included library. The package name can determined
+ from the library file name with the following code snippet:
+ .
+  $ objdump -p /path/to/libfoo-bar.so.1.2.3 | sed -n -e's/^[[:space:]]*SONAME[[:space:]]*//p' | sed -e's/\([0-9]\)\.so\./\1-/; s/\.so\.//'
+Ref: libpkg-guide 5
+
+Tag: binary-with-bad-dynamic-table
+Severity: important
+Certainty: possible
+Info: This appears to be an ELF file but objdump -T cannot parse it.
+ If it is external debugging symbols for another file, it should be
+ installed under /usr/lib/debug.
+
+Tag: apparently-corrupted-elf-binary
+Severity: normal
+Certainty: possible
+Info: This appears to be an ELF file but objdump -T doesn't recognize it
+ as valid.  This may be a mistake or a corrupted file, you may need to
+ install binutils-multiarch on the system running lintian so that
+ non-native binaries are handled correctly, or it may be a
+ misidentification of a file as ELF that actually isn't.
+
+Tag: apparently-truncated-elf-binary
+Severity: normal
+Certainty: possible
+Info: This appears to be an ELF file, but objdump believes it is
+ truncated.  This may be a mistake or a corrupted file, or it may be a
+ misidentification of a file as ELF that actually isn't.
+
+Tag: missing-dependency-on-libc
+Severity: serious
+Certainty: certain
+Ref: policy 8.6
+Info: The listed file appears to be linked against the C library, but the
+ package doesn't depend on the C library package.  Normally this indicates
+ that ${shlibs:Depends} was omitted from the Depends line for this package
+ in <tt>debian/control</tt>.
+ .
+ All shared libraries and compiled binaries must be run through
+ dpkg-shlibdeps to find out any libraries they are linked against (often
+ via the dh_shlibdeps debhelper command).  The package containing these
+ files must then depend on ${shlibs:Depends} in <tt>debian/control</tt> to
+ get the proper package dependencies for those libraries.
+
+Tag: missing-dependency-on-perlapi
+Severity: serious
+Certainty: certain
+Ref: perl-policy 4.4.2
+Info: This package includes a *.so file in <tt>/usr/lib/perl5</tt>,
+ normally indicating that it includes a binary Perl module.  Binary Perl
+ modules must depend on perlapi-$Config{version} (from the Config module).
+ If the package is using debhelper, this problem is usually due to a
+ missing dh_perl call in <tt>debian/rules</tt> or a missing
+ ${perl:Depends} substitution variable in the Depends line in
+ <tt>debian/control</tt>.
+
+Tag: debug-file-should-use-detached-symbols
+Severity: normal
+Certainty: certain
+Ref: devref 6.7.9
+Info: This file is in a location generally used for detached debugging
+ symbols, but it appears to contain a complete copy of the executable or
+ library instead of only the debugging symbols.  Files in subdirectories
+ of <tt>/usr/lib/debug</tt> mirroring the main file system should contain
+ only debugging information generated by <tt>objcopy
+ --only-keep-debug</tt>.  Binaries or shared objects built with extra
+ debugging should be installed directly in <tt>/usr/lib/debug</tt> or in
+ subdirectories corresponding to the package, not in the directories that
+ mirror the main file system.
+ .
+ If you are using dh_strip with the --dbg-package flag, don't also install
+ the library in <tt>/usr/lib/debug</tt>.  dh_strip does all the work for
+ you.
+
+Tag: binary-from-other-architecture
+Severity: serious
+Certainty: possible
+Info: This ELF binary appears to have been built for an architecture other
+ than the one of the binary package being tested.  This may occur when a
+ pre-built binary is shipped in the package or when an attempt to
+ cross-compile didn't work.
+
+Tag: spelling-error-in-binary
+Severity: normal
+Certainty: wild-guess
+Experimental: yes
+Info: Maemian found a spelling error in the given binary.  Maemian has a
+ list of common misspellings that it looks for.  It does not have a
+ dictionary like a spelling checker does.  For Maemian's convenience all
+ words are normalised to lower case.
+ .
+ If the string containing the spelling error is translated with the help
+ of gettext or a similar tool, please fix the error in the translations as
+ well as the English text to avoid making the translations fuzzy.  With
+ gettext, for example, this means you should also fix the spelling mistake
+ in the corresponding msgids in the *.po files.
+ .
+ To find the original word you can run:
+ .
+ <tt>strings &lt;binary&gt; | grep -i &lt;word&gt;</tt>
+ .
+ You can often find the word in the source code by running:
+ .
+ <tt>grep -r '\b&lt;original-word&gt;\b' &lt;source-tree&gt;</tt>
+
+Tag: embedded-zlib
+Severity: important
+Certainty: certain
+Info: The given ELF object appears to have been statically linked to zlib.
+ Doing this is discouraged due to the extra work needed by the security team
+ to fix all the extra embedded copies or trigger the package rebuilds, as
+ appropriate.
diff --git a/checks/changelog-file b/checks/changelog-file
new file mode 100644 (file)
index 0000000..251053d
--- /dev/null
@@ -0,0 +1,409 @@
+# changelog-file -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Christian Schwarz
+#
+# 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 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.
+
+package Maemian::changelog_file;
+use strict;
+
+use Maemian::Relation::Version qw(versions_gt);
+use Spelling;
+use Tags;
+use Util;
+
+use Encode qw(decode);
+use Parse::DebianChangelog;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+my $found_html=0;
+my $found_text=0;
+my $native_pkg;
+my $foreign_pkg;
+my $ppkg = quotemeta($pkg);
+
+my @doc_files;
+
+my %file_info;
+my %is_a_symlink;
+
+# Modify the file_info by following symbolic links.
+for my $file (sort keys %{$info->file_info}) {
+    next unless $file =~ m/doc/o;
+
+    $file_info{$file} = $info->file_info->{$file};
+
+    if ($file_info{$file} =~ m/^(?:broken )?symbolic link to (.*)/) {
+       $is_a_symlink{$file} = 1;
+       # Figure out the link destination.  This algorithm is
+       # not perfect but should be good enough.  (If it fails,
+       # all that happens is that an evil symlink causes a bogus warning).
+       my $newfile;
+       my $link = $1;
+       if ($link =~ m/^\//) {
+           # absolute path; replace
+           $newfile = $link;
+       } else {
+           $newfile = $file;   # relative path; base on $file
+           $newfile =~ s,/[^/]+$,,; # strip final pathname component
+           # strip another component for every leading ../ in $link
+           while ($link =~ m,^\.\./,) {
+               $newfile =~ s,/[^/]+$,,;
+               $link =~ s,^\.\./,,;
+           }
+           # concatenate the results
+           $newfile .= '/' . $link;
+       }
+       if (exists $info->file_info->{$newfile}) {
+           $file_info{$file} = $info->file_info->{$newfile};
+       }
+    }
+}
+
+# Read package contents....  Capitalization errors are dealt with later.
+foreach (sort keys %{$info->index}) {
+    next unless length $_;
+    # skip packages which have a /usr/share/doc/$pkg -> foo symlink
+    if (m,usr/share/doc/$ppkg$, and defined $info->index->{$_}->{link}) {
+       return 0;
+    }
+
+    # we are only interested in files or symlinks in /usr/(share/)?doc/$pkg
+    if (m,usr/(share/)?doc/$ppkg/([^/\s]+), ) {
+       my $file = $2;
+       my $file1 = "usr/share/doc/$pkg/$file";
+
+       push(@doc_files, $file);
+
+       # Check a few things about the NEWS.Debian file.
+       if ($file =~ /^NEWS.Debian(?:\.gz)?$/i) {
+           if (not $file =~ /\.gz$/) {
+               tag "debian-news-file-not-compressed", "$file1";
+           } elsif ($file ne 'NEWS.Debian.gz') {
+               tag "wrong-name-for-debian-news-file", "$file1";
+           }
+       }
+
+       # Check if changelog files are compressed with gzip -9.  It's a bit of
+       # an open question here what we should do with a file named ChangeLog.
+       # If there's also a changelog file, it might be a duplicate, or the
+       # packager may have installed NEWS as changelog intentionally.
+       next unless $file =~ m/^changelog(?:\.html)?(?:\.gz)?$|changelog.Debian(?:\.gz)?$/;
+
+       if (not $file =~ m/\.gz$/) {
+           tag "changelog-file-not-compressed", "$file";
+       } else {
+           my $max_compressed = 0;
+           if (exists $file_info{$file1} && defined $file_info{$file1}) {
+               if ($file_info{$file1} =~ m/max compression/o) {
+                   $max_compressed = 1;
+               }
+           }
+           if (not $max_compressed and $file_info{$file1} =~ m/gzip compressed/) {
+               unless ($is_a_symlink{$file1}) {
+                   tag "changelog-not-compressed-with-max-compression", "$file";
+               }
+           }
+       }
+
+       if ($file =~ m/^changelog\.html(?:\.gz)?$/ ) {
+           $found_html = 1;
+       }
+       if ($file =~ m/^changelog(?:\.gz)?$/ ) {
+           $found_text = 1;
+       }
+    }
+}
+
+# ignore packages which don't have a /usr/share/doc/$pkg directory, since
+# the copyright check will complain about this
+if ($#doc_files < 0) {
+    return 0;
+}
+
+# Check a NEWS.Debian file if we have one.  We should additionally check here
+# that the entries don't begin with an asterisk, but that hasn't been done
+# yet.  Save the version, distribution, and urgency for later checks against
+# the changelog file.
+my $news;
+if (-f 'NEWS.Debian') {
+    my $line = file_is_encoded_in_non_utf8('NEWS.Debian', $type, $pkg);
+    if ($line) {
+       tag "debian-news-file-uses-obsolete-national-encoding", "at line $line"
+    }
+    my $changes = Parse::DebianChangelog->init( { infile => 'NEWS.Debian', quiet => 1 } );
+    if (my @errors = $changes->get_parse_errors) {
+       for (@errors) {
+           tag "syntax-error-in-debian-news-file", "line $_->[1]", "\"$_->[2]\"";
+       }
+    }
+
+    # Some checks on the most recent entry.
+    if ($changes->data and defined (($changes->data)[0])) {
+        ($news) = $changes->data;
+        if ($news->Distribution && $news->Distribution =~ /unreleased/i) {
+            tag "debian-news-entry-has-strange-distribution", $news->Distribution;
+        }
+        spelling_check('spelling-error-in-news-debian', $news->Changes);
+    }
+}
+
+if ( $found_html && !$found_text ) {
+    tag "html-changelog-without-text-version", "";
+}
+
+# is this a native Debian package?
+my $version;
+if (defined $info->field('version')) {
+    $version = $info->field('version');
+} else {
+    fail "Unable to determine version!";
+}
+
+$native_pkg  = $info->native;
+$foreign_pkg = (!$native_pkg and $version !~ m/-0\./);
+# A version of 1.2.3-0.1 could be either, so in that
+# case, both vars are false
+
+if ($native_pkg) {
+    my @foo;
+    # native Debian package
+    if (grep m/^changelog(?:\.gz)?$/,@doc_files) {
+       # everything is fine
+    } elsif (@foo = grep m/^changelog\.debian(?:\.gz)$/i,@doc_files) {
+       tag "wrong-name-for-changelog-of-native-package", "usr/share/doc/$pkg/$foo[0]";
+    } else {
+       tag "changelog-file-missing-in-native-package", "";
+    }
+} else {
+    # non-native (foreign :) Debian package
+
+    # 1. check for upstream changelog
+    my $found_upstream_text_changelog = 0;
+    if (grep m/^changelog(\.html)?(?:\.gz)?$/,@doc_files) {
+       $found_upstream_text_changelog = 1 unless $1;
+       # everything is fine
+    } else {
+       # search for changelogs with wrong file name
+       my $found = 0;
+       for (@doc_files) {
+           if (m/^change/i and not m/debian/i) {
+               tag "wrong-name-for-upstream-changelog", "usr/share/doc/$pkg/$_";
+               $found = 1;
+               last;
+           }
+       }
+       if (not $found) {
+           tag "no-upstream-changelog";
+       }
+    }
+
+    # 2. check for Debian changelog
+    if (grep m/^changelog\.Debian(?:\.gz)?$/,@doc_files) {
+       # everything is fine
+    } elsif (my @foo = grep m/^changelog\.debian(\.gz)?$/i,@doc_files) {
+       tag "wrong-name-for-debian-changelog-file", "usr/share/doc/$pkg/$foo[0]";
+    } else {
+       if ($foreign_pkg && $found_upstream_text_changelog) {
+           tag "debian-changelog-file-missing-or-wrong-name", "";
+       } elsif ($foreign_pkg) {
+           tag "debian-changelog-file-missing", "";
+       }
+       # TODO: if uncertain whether foreign or native, either changelog.gz or
+       # changelog.debian.gz should exists though... but no tests catches
+       # this (extremely rare) border case... Keep in mind this is only
+       # happening if we have a -0.x version number... So not my priority to
+       # fix --Jeroen
+    }
+}
+
+# Everything below involves opening and reading the changelog file, so bail
+# with a warning at this point if all we have is a symlink.
+if (-l 'changelog') {
+    tag "debian-changelog-file-is-a-symlink", "";
+    return 0;
+}
+
+# Bail at this point if the changelog file doesn't exist.  We will have
+# already warned about this.
+unless (-f 'changelog') {
+    return 0;
+}
+
+# check that changelog is UTF-8 encoded
+my $line = file_is_encoded_in_non_utf8("changelog", $type, $pkg);
+if ($line) {
+    tag "debian-changelog-file-uses-obsolete-national-encoding", "at line $line"
+}
+
+my $changes = $info->changelog;
+if (my @errors = $changes->get_parse_errors) {
+    foreach (@errors) {
+       tag "syntax-error-in-debian-changelog", "line $_->[1]", "\"$_->[2]\"";
+    }
+}
+
+my @entries = $changes->data;
+if (@entries) {
+    foreach (@entries) {
+       if ($_->Maintainer) {
+           if ($_->Maintainer =~ /<([^>\@]+\@unknown)>/) {
+               tag "debian-changelog-file-contains-debmake-default-email-address", $1;
+           } elsif ($_->Maintainer =~ /<([^>\@]+\@[^>.]*)>/) {
+               tag "debian-changelog-file-contains-invalid-email-address", $1;
+           }
+       }
+    }
+
+    if (@entries > 1) {
+       my $first_timestamp = $entries[0]->Timestamp;
+       my $second_timestamp = $entries[1]->Timestamp;
+
+       if ($first_timestamp && $second_timestamp) {
+           tag "latest-debian-changelog-entry-without-new-date"
+               unless (($first_timestamp - $second_timestamp) > 0);
+       }
+
+       my $first_version = $entries[0]->Version;
+       my $second_version = $entries[1]->Version;
+       if ($first_version and $second_version) {
+           tag "latest-debian-changelog-entry-without-new-version"
+               unless versions_gt($first_version, $second_version)
+                    or $entries[0]->Changes =~ /backport/i;
+           tag "latest-debian-changelog-entry-changed-to-native"
+               if $native_pkg and $second_version =~ m/-/;
+       }
+
+       my $first_dist = lc $entries[0]->Distribution;
+       my $second_dist = lc $entries[1]->Distribution;
+       if ($first_dist eq 'unstable' and $second_dist eq 'experimental') {
+           unless ($entries[0]->Changes =~ /\bto\s+unstable\b/) {
+               tag "experimental-to-unstable-without-comment";
+           }
+       }
+    }
+
+    # Some checks should only be done against the most recent changelog entry.
+    my $entry = $entries[0];
+    if (@entries == 1 and $entry->Version =~ /-1$/) {
+        tag 'new-package-should-close-itp-bug'
+            unless @{ $entry->Closes };
+    }
+    my $changes = $entry->Changes;
+    while ($changes =~ /(closes\s*(?:bug)?\#?\s?\d{6,})[^\w]/ig) {
+       tag "possible-missing-colon-in-closes", "$1" if $1;
+    }
+    my $closes = $entry->Closes;
+    for my $bug (@$closes) {
+        tag "improbable-bug-number-in-closes", $bug if ($bug < 100);
+    }
+
+    # unstable, testing, and stable shouldn't be used in Debian version
+    # numbers.  unstable should get a normal version increment and testing and
+    # stable should get suite-specific versions.
+    #
+    # NMUs get a free pass because they need to work with the version number
+    # that was already there.
+    my $version;
+    if ($info->native) {
+        $version = $entry->Version;
+    } else {
+        ($version) = (split('-', $entry->Version))[-1];
+    }
+    unless (not $info->native and $version =~ /\./) {
+        if ($info->native and $version =~ /testing|(?:un)?stable/i) {
+            tag 'version-refers-to-distribution', $entry->Version;
+        } elsif ($version =~ /woody|sarge|etch|lenny|squeeze/) {
+            if ($entry->Distribution =~ /^(?:unstable|experimental)$/) {
+                tag 'version-refers-to-distribution', $entry->Version;
+            }
+        }
+    }
+
+    # Compare against NEWS.Debian if available.
+    if ($news and $news->Version and $entry->Version eq $news->Version) {
+        for my $field (qw/Distribution Urgency/) {
+            if ($entry->$field ne $news->$field) {
+                tag 'changelog-news-debian-mismatch', lc ($field),
+                    $entry->$field . ' != ' . $news->$field;
+            }
+        }
+    }
+
+    # We have to decode into UTF-8 to get the right length for the length
+    # check.  For some reason, use open ':utf8' isn't sufficient.  If the
+    # changelog uses a non-UTF-8 encoding, this will mangle it, but it doesn't
+    # matter for the length check.
+    #
+    # Parse::DebianChangelog adds an additional space to the beginning of each
+    # line, so we have to adjust for that in the length check.
+    my @lines = split ("\n", decode ('utf-8', $changes));
+    for my $i (0 .. $#lines) {
+        if (length ($lines[$i]) > 81 && $lines[$i] !~ /^[\s.o*+-]*\S+$/) {
+            tag 'debian-changelog-line-too-long', "line " . ($i + 1);
+        }
+    }
+
+    # Strip out all lines that contain the word spelling to avoid false
+    # positives on changelog entries for spelling fixes.
+    $changes =~ s/^.*spelling.*\n//gm;
+    spelling_check('spelling-error-in-changelog', $changes);
+}
+
+# read the changelog itself
+#
+# emacs only looks at the last "local variables" in a file, and only at
+# one within 3000 chars of EOF and on the last page (^L), but that's a bit
+# pesky to replicate.  Demanding a match of $prefix and $suffix ought to
+# be enough to avoid false positives.
+open (IN, '<', "changelog")
+    or fail("cannot find changelog for $type package $pkg");
+my ($prefix, $suffix);
+while (<IN>) {
+
+    if (/closes:\s*(((?:bug)?\#?\s?\d*)[[:alpha:]]\w*)/io
+       || /closes:\s*(?:bug)?\#?\s?\d+
+             (?:,\s*(?:bug)?\#?\s?\d+)*
+             (?:,\s*(((?:bug)?\#?\s?\d*)[[:alpha:]]\w*))/iox) {
+       tag "wrong-bug-number-in-closes", "l$.:$1" if $2;
+    }
+
+    if (/^(.*)Local\ variables:(.*)$/i) {
+       $prefix = $1;
+       $suffix = $2;
+    }
+    # emacs allows whitespace between prefix and variable, hence \s*
+    if (defined $prefix && defined $suffix
+       && /^\Q$prefix\E\s*add-log-mailing-address:.*\Q$suffix\E$/) {
+       tag "debian-changelog-file-contains-obsolete-user-emacs-settings";
+    }
+}
+close IN;
+
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl ts=8 sw=4
diff --git a/checks/changelog-file.desc b/checks/changelog-file.desc
new file mode 100644 (file)
index 0000000..20346c3
--- /dev/null
@@ -0,0 +1,316 @@
+Check-Script: changelog-file
+Author: Christian Schwarz <schwarz@debian.org>
+Abbrev: chg
+Type: binary
+Unpack-Level: 1
+Needs-Info: file-info, changelog-file
+Info: This script checks if a binary package conforms to policy
+ with regards to changelog files.
+ .
+ Each binary package with a /usr/share/doc/&lt;foo&gt; directory must have
+ a Debian changelog file in "changelog.Debian.gz" unless the Debian
+ changelog and the upstream one is the same file; in this case, it
+ must be in "changelog.gz".
+ .
+ If there is an upstream changelog file, it must be named
+ "changelog.gz".
+ .
+ Both changelog files should be compressed using "gzip -9".
+ Even if they start out small, they will become large with time.
+
+Tag: html-changelog-without-text-version
+Severity: important
+Certainty: certain
+Info: If the upstream changelog file is HTML formatted, a text version
+ should also be accessible as "changelog.gz". (This can be created by
+ "lynx -dump -nolist")
+Ref: policy 12.7
+
+Tag: changelog-file-not-compressed
+Severity: important
+Certainty: certain
+Info: Changelog files should be compressed using "gzip -9".  Even if they
+ start out small, they will become large with time.
+Ref: policy 12.7
+
+Tag: debian-news-file-not-compressed
+Severity: normal
+Certainty: certain
+Info: NEWS.Debian files should be compressed using "gzip -9".  The file
+ must always have the same name.
+Ref: devref 6.3.4
+
+Tag: changelog-not-compressed-with-max-compression
+Severity: normal
+Certainty: certain
+Info: Changelog files should be compressed using "gzip -9"; i.e., using
+ the maximum compression level via the -9 option to gzip.
+Ref: policy 12.7
+
+Tag: wrong-name-for-changelog-of-native-package
+Severity: normal
+Certainty: certain
+Info: The changelog file of a native Debian package (i.e., if there is
+ no upstream source) should usually be installed as
+ /usr/share/doc/<i>pkg</i>/changelog.gz
+Ref: policy 12.7
+
+Tag: changelog-file-missing-in-native-package
+Severity: important
+Certainty: certain
+Info: Each Debian package (which provides a /usr/share/doc/<i>pkg</i>
+ directory) has to install a changelog file. Since this package seems
+ to be a native Debian package (i.e., there is no upstream source),
+ the file should usually be installed as
+ /usr/share/doc/<i>pkg</i>/changelog.gz
+Ref: policy 12.7
+
+Tag: wrong-name-for-upstream-changelog
+Severity: normal
+Certainty: possible
+Info: If there is an upstream changelog file, it should usually be
+ installed as /usr/share/doc/<i>pkg</i>/changelog.gz
+Ref: policy 12.7
+
+Tag: no-upstream-changelog
+Severity: pedantic
+Certainty: wild-guess
+Info: The package does not install an upstream changelog file.  If upstream
+ provides a changelog, it should be accessible as
+ <tt>/usr/share/doc/<i>pkg</i>/changelog.gz</tt>.
+ .
+ It's currently unclear how best to handle multiple binary packages from
+ the same source.  Some maintainers put a copy of the upstream changelog
+ in each package, but it can be quite long.  Some include it in one
+ package and add symlinks to the other packages, but this requires there
+ be dependencies between the packages.  Some only include it in a "central"
+ binary package and omit it from more ancillary packages.
+Ref: policy 12.7
+
+Tag: wrong-name-for-debian-changelog-file
+Severity: important
+Certainty: certain
+Info: The Debian changelog file should usually be installed as
+ /usr/share/doc/<i>pkg</i>/changelog.Debian.gz
+Ref: policy 12.7
+
+Tag: wrong-name-for-debian-news-file
+Severity: normal
+Certainty: possible
+Info: The Debian news file must be installed as
+ /usr/share/doc/<i>pkg</i>/NEWS.Debian.gz with exactly that capitalization
+ or automated tools may not find it correctly.
+Ref: devref 6.3.4
+
+Tag: debian-changelog-file-missing
+Severity: serious
+Certainty: certain
+Info: Each Debian package (which provides a /usr/share/doc/<i>pkg</i>
+ directory) has to install a Debian changelog file
+ /usr/share/doc/<i>pkg</i>/changelog.Debian.gz
+Ref: policy 12.7
+
+Tag: debian-changelog-file-is-a-symlink
+Severity: normal
+Certainty: certain
+Info: The Debian changelog file is a symlink to a file in a different
+ directory or not found in this package. Please don't do this. It makes
+ package checking and manipulation unnecessarily difficult. Because it was
+ a symlink, the Debian changelog file was not checked for other
+ problems. (Symlinks to another file in /usr/share/doc/<i>pkg</i> or a
+ subdirectory thereof are fine and should not trigger this warning.)
+ .
+ To refer to the changelog, copyright, and other documentation files of
+ another package that this one depends on, please symlink the entire
+ /usr/share/doc/<i>pkg</i> directory rather than individual files.
+
+Tag: debian-changelog-file-missing-or-wrong-name
+Severity: serious
+Certainty: certain
+Info: Each Debian package (which provides a /usr/share/doc/<i>pkg</i>
+ directory) must install a Debian changelog file in
+ /usr/share/doc/<i>pkg</i>/changelog.Debian.gz
+ .
+ A common error is to name the Debian changelog like an upstream changelog
+ (/usr/share/doc/<i>pkg</i>/changelog.gz); therefore, lintian will apply
+ further checks to such a file if it exists even after issuing this error.
+Ref: policy 12.7
+
+Tag: debian-changelog-file-contains-obsolete-user-emacs-settings
+Severity: normal
+Certainty: certain
+Info: The add-log-mailing-address variable is no longer honored in
+ debian-changelog-mode, and should not appear in packages' changelog
+ files.  Instead, put something like this in your ~/.emacs:
+ .
+ (setq debian-changelog-mailing-address "userid@debian.org")
+
+Tag: debian-changelog-file-contains-debmake-default-email-address
+Severity: important
+Certainty: certain
+Info: The changelog file contains an email address (&lt;..@unknown&gt;)
+ that was not updated to the maintainer's real address.
+
+Tag: debian-changelog-file-contains-invalid-email-address
+Severity: important
+Certainty: certain
+Info: The changelog file contains an invalid email address: the domain
+ needs at least one dot. This looks like a mistake.
+
+Tag: debian-changelog-file-uses-obsolete-national-encoding
+Severity: serious
+Certainty: certain
+Ref: policy 4.4
+Info: The Debian changelog file must be valid UTF-8, an encoding of
+ the Unicode character set.
+ .
+ There are many ways to convert a changelog from an obsoleted encoding
+ like ISO-8859-1; you may for example use "iconv" like:
+ .
+  $ iconv -f ISO-8859-1 -t UTF-8 changelog &gt; changelog.new
+  $ mv changelog.new changelog
+
+Tag: debian-news-file-uses-obsolete-national-encoding
+Severity: important
+Certainty: certain
+Info: The NEWS.Debian file must be valid UTF-8, an encoding of the Unicode
+ character set.
+ .
+ There are many ways to convert a changelog from an obsoleted encoding
+ like ISO-8859-1; you may for example use "iconv" like:
+ .
+  $ iconv -f ISO-8859-1 -t UTF-8 NEWS.Debian &gt; NEWS.Debian.new
+  $ mv NEWS.Debian.new NEWS.Debian
+
+Tag: latest-debian-changelog-entry-without-new-date
+Severity: normal
+Certainty: certain
+Info: The latest Debian changelog entry has either the same or even an
+ older date as the entry before.
+
+Tag: latest-debian-changelog-entry-without-new-version
+Severity: normal
+Certainty: certain
+Info: The latest Debian changelog entry has a version number that's either
+ the same or smaller than the version number of the entry before.
+
+Tag: latest-debian-changelog-entry-changed-to-native
+Severity: normal
+Certainty: possible
+Info: The latest package has a Debian native version number, while the
+ previous version number was not native. This is usually a mistake made by
+ the maintainer by forgetting to append -1 when uploading a new upstream
+ version.
+
+Tag: experimental-to-unstable-without-comment
+Severity: pedantic
+Certainty: possible
+Info: The previous version of this package had a distribution of
+ experimental, this version has a distribution of unstable, and there's
+ apparently no comment about the change of distributions (Maemian looks
+ for the phrase "to unstable").  This may indicate a mistake in setting
+ the distribution and accidentally uploading to unstable a package
+ intended for experimental.
+
+Tag: syntax-error-in-debian-changelog
+Severity: normal
+Certainty: possible
+Info: While parsing the Debian changelog, a syntax error was found.
+Ref: policy 4.4
+
+Tag: syntax-error-in-debian-news-file
+Severity: normal
+Certainty: possible
+Info: While parsing the NEWS.Debian file, a syntax error was found.
+Ref: devref 6.3.4
+
+Tag: improbable-bug-number-in-closes
+Severity: normal
+Certainty: possible
+Info: The most recent changelog closes a bug numbered less than 100.
+ While this is distantly possible, it's more likely a typo or a
+ placeholder value that mistakenly wasn't filled in.
+
+Tag: wrong-bug-number-in-closes
+Severity: normal
+Certainty: certain
+Info: Bug numbers can only contain digits.
+Ref: policy 4.4
+
+Tag: possible-missing-colon-in-closes
+Severity: important
+Certainty: possible
+Info: To close a bug in the Debian changelog, the word "closes" must be
+ followed by a colon.  This entry looked like it was intended to close a
+ bug, but there's no colon after "closes".
+Ref: policy 4.4
+
+Tag: debian-news-entry-has-strange-distribution
+Severity: normal
+Certainty: certain
+Info: The latest entry in NEWS.Debian has an unusual distribution name.
+ This field is ignored by the archive software, so its value doesn't truly
+ matter, but it may be confusing to users reading the entry if the
+ distribution doesn't match the distribution for the same entry in the
+ Debian changelog file.
+
+Tag: spelling-error-in-changelog
+Severity: normal
+Certainty: certain
+Info: Maemian found a spelling error in the latest entry of the Debian
+ changelog.  Maemian has a list of common misspellings that it looks for.
+ It does not have a dictionary like a spelling checker does.
+ .
+ When writing a changelog entry for a spelling fix that includes the
+ misspelling, ensure the word "spelling" is on the same line as the
+ misspelled word to avoid triggering this warning.
+
+Tag: spelling-error-in-news-debian
+Severity: normal
+Certainty: certain
+Info: Maemian found a spelling error in the latest entry of the
+ NEWS.Debian file.  Maemian has a list of common misspellings that it
+ looks for.  It does not have a dictionary like a spelling checker does.
+
+Tag: new-package-should-close-itp-bug
+Severity: normal
+Certainty: certain
+Info: This package appears to be the first packaging of a new upstream
+ software package (there is only one changelog entry and the Debian
+ revision is 1), but it does not close any bugs.  The initial upload of a
+ new package should close the corresponding ITP bug for that package.
+ .
+ This warning can be ignored if the package is not intended for Debian or
+ if it is a split of an existing Debian package.
+Ref: devref 5.1
+
+Tag: debian-changelog-line-too-long
+Severity: normal
+Certainty: certain
+Info: The given line of the latest changelog entry is over 80 columns.
+ Such changelog entries may look poor in terminal windows and mail
+ messages and be annoying to read.  Please wrap changelog entries at 80
+ columns or less where possible.
+
+Tag: changelog-news-debian-mismatch
+Severity: normal
+Certainty: possible
+Info: The latest entries in the Debian changelog file and NEWS.Debian file
+ are for the same version but the given field doesn't match.  The
+ changelog information is canonical and the NEWS.Debian information is
+ ignored, but it may be confusing to users to have them be different.
+
+Tag: version-refers-to-distribution
+Severity: minor
+Certainty: certain
+Info: The Debian portion of the package version contains a reference to a
+ particular Debian release or distribution.  This should only be done for
+ uploads targeted at a particular release, not at unstable or
+ experimental, and should refer to the release by version number or code
+ name.
+ .
+ Using "testing" or "stable" in a package version targeted at the current
+ testing or stable release is less informative than using the code name or
+ version number and may cause annoying version sequencing issues if the
+ package doesn't change before the next release cycle starts.
+Ref: devref 5.13.3
diff --git a/checks/common_data.pm b/checks/common_data.pm
new file mode 100644 (file)
index 0000000..3aa5884
--- /dev/null
@@ -0,0 +1,43 @@
+#! /usr/bin/perl -w
+
+package common_data;
+use base qw(Exporter);
+
+our @EXPORT = qw
+(
+   %known_source_fields %known_essential $known_shells_regex
+);
+
+# To let "perl -cw" test know we use these variables;
+use vars qw
+(
+  %known_source_fields %known_essential $known_shells_regex
+);
+
+# simple defines for commonly needed data
+
+# The Ubuntu original-maintainer field is handled separately.
+%known_source_fields = map { $_ => 1 }
+    ('source', 'version', 'maintainer', 'binary', 'architecture',
+     'standards-version', 'files', 'build-depends', 'build-depends-indep',
+     'build-conflicts', 'build-conflicts-indep', 'format', 'origin',
+     'uploaders', 'python-version', 'autobuild', 'homepage', 'vcs-arch',
+     'vcs-bzr', 'vcs-cvs', 'vcs-darcs', 'vcs-git', 'vcs-hg', 'vcs-mtn',
+     'vcs-svn', 'vcs-browser', 'dm-upload-allowed', 'bugs', 'checksums-sha1',
+     'checksums-sha256', 'checksums-md5');
+
+%known_essential = map { $_ => 1 }
+    ('base-files', 'base-passwd', 'bash', 'bsdutils', 'coreutils',
+     'debianutils', 'diff', 'dpkg', 'e2fsprogs', 'findutils', 'grep', 'gzip',
+     'hostname', 'login', 'mktemp', 'mount', 'ncurses-base', 'ncurses-bin',
+     'perl-base', 'sed', 'sysvinit', 'sysvinit-utils', 'tar', 'util-linux');
+
+$known_shells_regex = qr'(?:(?:b|d)?a|t?c|(?:pd|m)?k|z)?sh';
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround
diff --git a/checks/conffiles b/checks/conffiles
new file mode 100644 (file)
index 0000000..d3f9851
--- /dev/null
@@ -0,0 +1,69 @@
+# conffiles -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Christian Schwarz
+#
+# 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 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.
+
+package Maemian::conffiles;
+use strict;
+use Tags;
+use Util;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+
+my $cf = "control/conffiles";
+
+# conffiles?
+unless (-f $cf) {
+    return 0;
+}
+
+my %conffiles = ();
+
+open(IN, '<', $cf) or fail("cannot open $cf for reading: $!");
+while (<IN>) {
+    chop;
+    next if m/^\s*$/;
+
+    unless (m,^/,) {
+       tag "relative-conffile", $_;
+       $_ = '/' . $_;
+    }
+
+    $conffiles{$_}++;
+
+    if ($conffiles{$_} > 1) {
+       tag "duplicate-conffile", $_;
+    }
+
+    if (m,^/usr/,) {
+       tag "file-in-usr-marked-as-conffile", $_;
+    } else {
+       unless (m,^/etc/,) {
+           tag "non-etc-file-marked-as-conffile", $_;
+       }
+    }
+
+}
+close(IN);
+
+}
+
+1;
diff --git a/checks/conffiles.desc b/checks/conffiles.desc
new file mode 100644 (file)
index 0000000..3792bef
--- /dev/null
@@ -0,0 +1,38 @@
+Check-Script: conffiles
+Author: Christian Schwarz <schwarz@debian.org>
+Abbrev: cnf
+Type: binary
+Unpack-Level: 1
+Info: This script checks if the conffiles control file of a binary
+ package is correct.
+
+Tag: file-in-usr-marked-as-conffile
+Severity: important
+Certainty: certain
+Info: Files below <tt>/usr</tt> may not be marked as conffiles, since
+ <tt>/usr</tt> might be mounted read-only and thus, the local system
+ administrator would not have a chance to modify this configuration
+ file.
+
+Tag: non-etc-file-marked-as-conffile
+Severity: important
+Certainty: certain
+Info: A file installed in some other directory than <tt>/etc</tt>
+ is marked as conffile. A conffile typically implies a configuration file, and
+ policy mandates such files to be in /etc
+Ref: policy 10.7.2
+
+Tag: relative-conffile
+Severity: important
+Certainty: certain
+Ref: policy E.1
+Info: All entries in the <tt>debian/conffiles</tt> control file should
+ have an absolute path specification.
+
+Tag: duplicate-conffile
+Severity: important
+Certainty: certain
+Info: The file is listed more than once in your <tt>debian/conffiles</tt> file.
+ Usually, this is because debhelper (dh_installdeb, compat level 3 or higher)
+ will add any files in your package located in /etc automatically to the list
+ of conffiles, so if you do that manually too, you'll get duplicates.
diff --git a/checks/control-file b/checks/control-file
new file mode 100644 (file)
index 0000000..aa8ab0b
--- /dev/null
@@ -0,0 +1,246 @@
+# control-file -- lintian check script -*- perl -*-
+#
+# Copyright (C) 2004 Marc Brockschmidt
+#
+# 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 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.
+
+package Maemian::control_file;
+use strict;
+
+use lib "$ENV{'MAEMIAN_ROOT'}/checks/";
+use common_data;
+
+use Maemian::Relation ();
+use Tags;
+use Util;
+
+# The list of libc packages, used for checking for a hard-coded dependency
+# rather than using ${shlibs:Depends}.
+our @LIBCS = qw(libc6 libc6.1 libc0.1 libc0.3);
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+
+if (-l "debfiles/control") {
+    tag "debian-control-file-is-a-symlink", "";
+}
+
+# check that control is UTF-8 encoded
+my $line = file_is_encoded_in_non_utf8("debfiles/control", $type, $pkg);
+if ($line) {
+    tag "debian-control-file-uses-obsolete-national-encoding", "at line $line"
+}
+
+# Check that each field is only used once:
+my $seen_fields = {};
+open (CONTROL, '<', "debfiles/control")
+    or fail "Couldn't read debfiles/control: $!";
+while (<CONTROL>) {
+       s/\s*\n$//;
+       next if /^\#/;
+
+       #Reset seen_fields if we enter a new section:
+       $seen_fields = {} if /^$/;
+
+       #line with field:
+       if (/^(\S+):/) {
+               my $field = lc ($1);
+               if ($seen_fields->{$field}) {
+                       tag "debian-control-with-duplicate-fields", "$field: $$seen_fields{$field}, $.";
+               }
+               $seen_fields->{$field} = $.;
+               if ($field =~ /^xs-vcs-/) {
+                       my $base = $field;
+                       $base =~ s/^xs-//;
+                       tag "xs-vcs-header-in-debian-control", "$field"
+                           if $known_source_fields{$base};
+               }
+               unless (/^\S+: \S/) {
+                       tag 'debian-control-has-unusual-field-spacing', "line $.";
+               }
+       }
+}
+close CONTROL;
+
+my ($header, @binary_controls) = read_dpkg_control("debfiles/control");
+
+for my $binary_control (@binary_controls) {
+       tag "build-info-in-binary-control-file-section", "Package ".$binary_control->{"package"}
+           if ($binary_control->{"build-depends"} || $binary_control->{"build-depends-indep"} ||
+               $binary_control->{"build-conflicts"} || $binary_control->{"build-conflicts-indep"});
+       for my $field (keys %$binary_control) {
+               tag 'binary-control-field-duplicates-source', "field \"$field\" in package ".$binary_control->{'package'},
+                   if ($header->{$field} && $binary_control->{$field} eq $header->{$field});
+       }
+
+       # If two substvars aren't separated by a comma, but at least one of
+       # them expands to an empty string, there will be a lurking bug.  The
+       # result will be syntactically correct, but as soon as both expand
+       # into something non-empty, there will be a syntax error.  Catch that
+       # mistake to avoid problems later.
+       #
+       # Only check the fields that use comma-separated values.
+       for my $field (qw(pre-depends depends recommends suggests breaks
+                         conflicts provides replaces enhances)) {
+               next unless $binary_control->{$field};
+               if ($binary_control->{$field} =~ /(\$\{\S+\})\s+[a-zA-Z0-9\$]/) {
+                       tag 'missing-comma-after-substvar', "in $field field near $1";
+               }
+       }
+}
+
+# Check if comma-separated values that span multiple lines omit commas as in
+# the following example:
+#   Depends: foo, bar
+#    baz
+for my $control ($header, @binary_controls) {
+       for my $field (qw(pre-depends depends recommends suggests breaks
+                         conflicts provides replaces enhances
+                         build-depends build-depends-indep
+                         build-conflics build-conflicts-indep)) {
+               next unless $control->{$field};
+               if ($control->{$field} =~ /
+                       ([^,]+)         # previous entry
+                       \s*\n\s+        # new line + space
+                       ([a-z][^,]+)    # next entry, must start with a letter
+                       /x) {
+
+                       my ($prev, $next) = ($1, $2);
+                       for ($prev, $next) {
+                               s/^\s+//; s/\s+$//;
+                       }
+                       tag "missing-comma-between-items",
+                           "in $field field between '$prev' and '$next', " .
+                           ($control->{source} ? 'source' : $control->{package});
+               }
+       }
+}
+
+# Make sure that a stronger dependency field doesn't imply any of the elements
+# of a weaker dependency field.  dpkg-gencontrol will fix this up for us, but
+# we want to check the source package since dpkg-gencontrol may silently "fix"
+# something that's a more subtle bug.
+#
+# Also check if a package declares a simple dependency on itself, since
+# similarly dpkg-gencontrol will clean this up for us but it may be a sign of
+# another problem, and check that the package doesn't hard-code a dependency
+# on libc.  We have to do the latter check here rather than in checks/fields
+# to distinguish from dependencies created by ${shlibs:Depends}.
+my @dep_fields = qw(pre-depends depends recommends suggests);
+my $libcs = Maemian::Relation->new(join(' | ', @LIBCS));
+for my $control (@binary_controls) {
+       for my $strong (0 .. $#dep_fields) {
+               next unless $control->{$dep_fields[$strong]};
+               my $relation = Maemian::Relation->new($control->{$dep_fields[$strong]});
+               tag "package-depends-on-itself", $control->{package}, $dep_fields[$strong]
+                   if $relation->implies($control->{package});
+               tag 'package-depends-on-hardcoded-libc', $control->{package}, $dep_fields[$strong]
+                   if ($relation->implies($libcs) and $pkg ne "glibc");
+               for my $weak (($strong + 1) .. $#dep_fields) {
+                       next unless $control->{$dep_fields[$weak]};
+                       for my $dependency (split /\s*,\s*/, $control->{$dep_fields[$weak]}) {
+                               next unless $dependency;
+                               tag "stronger-dependency-implies-weaker", $control->{package}, "$dep_fields[$strong] -> $dep_fields[$weak]", $dependency
+                                   if $relation->implies($dependency);
+                       }
+               }
+       }
+}
+
+# Check that every package is in the same archive area, except that
+# sources in main can deliver both main and contrib packages.  The source
+# package may or may not have a section specified; if it doesn't, derive the
+# expected archive area from the first binary package by leaving $area
+# undefined until parsing the first binary section.  Missing sections will be
+# caught by other checks.
+#
+# Also accumulate short and long descriptions for each package so that we can
+# check for duplication, but skip udeb packages.  Ideally, we should check the
+# udeb package descriptions separately for duplication, but udeb packages
+# should be able to duplicate the descriptions of non-udeb packages and the
+# package description for udebs is much less important or significant to the
+# user.
+my $area;
+if ($header->{'section'}) {
+       if ($header->{'section'} =~ m%^([^/]+)/%) {
+               $area = $1;
+       } else {
+               $area = '';
+       }
+} else {
+       tag "no-section-field-for-source", "";
+}
+my @descriptions;
+for my $binary_control (@binary_controls) {
+       if ($binary_control->{'description'}
+           and (not $binary_control->{'xc-package-type'}
+                or $binary_control->{'xc-package-type'} ne 'udeb')) {
+               push(@descriptions,
+                    [ $binary_control->{'package'},
+                      split("\n", $binary_control->{'description'}, 2) ]);
+       }
+       next unless $binary_control->{'section'};
+       if (!defined ($area)) {
+               if ($binary_control->{'section'} =~ m%^([^/]+)/%) {
+                       $area = ($1 eq 'contrib') ? '' : $1;
+               } else {
+                       $area = '';
+               }
+               next;
+       }
+       tag "section-area-mismatch", "Package " . $binary_control->{'package'}
+               if ($area && $binary_control->{'section'} !~ m%^$area/%);
+       tag "section-area-mismatch", "Package " . $binary_control->{'package'}
+               if (!$area && $binary_control->{'section'} =~ m%^([^/]+)/% && $1 ne 'contrib');
+}
+
+# Check for duplicate descriptions.
+my (%seen_short, %seen_long);
+for my $i (0 .. $#descriptions) {
+       my (@short, @long);
+       for my $j (($i + 1) .. $#descriptions) {
+               if ($descriptions[$i][1] eq $descriptions[$j][1]) {
+                       my $package = $descriptions[$j][0];
+                       push(@short, $package) unless $seen_short{$package};
+               }
+               next unless ($descriptions[$i][2] and $descriptions[$j][2]);
+               if ($descriptions[$i][2] eq $descriptions[$j][2]) {
+                       my $package = $descriptions[$j][0];
+                       push(@long, $package) unless $seen_long{$package};
+               }
+       }
+       if (@short) {
+               tag 'duplicate-short-description', $descriptions[$i][0], @short;
+               for (@short) { $seen_short{$_} = 1 }
+       }
+       if (@long) {
+               tag 'duplicate-long-description', $descriptions[$i][0], @long;
+               for (@long) { $seen_long{$_} = 1 }
+       }
+}
+
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 8
+# End:
+# vim: syntax=perl sw=4 ts=4 noet shiftround
diff --git a/checks/control-file.desc b/checks/control-file.desc
new file mode 100644 (file)
index 0000000..3f75157
--- /dev/null
@@ -0,0 +1,157 @@
+Check-Script: control-file
+Author: Marc 'HE' Brockschmidt <marc@marcbrockschmidt.de>
+Abbrev: dctl
+Type: source
+Unpack-Level: 1
+Needs-Info: debfiles
+Info: This script checks debian/control files in source packages
+
+Tag: debian-control-file-uses-obsolete-national-encoding
+Severity: serious
+Certainty: certain
+Ref: policy 5.1
+Info: The Debian control file should be valid UTF-8, an encoding of
+ the Unicode character set.
+ .
+ There are many ways to convert a control from an obsoleted encoding
+ like ISO-8859-1; you may for example use "iconv" like this:
+ .
+  $ iconv -f ISO-8859-1 -t UTF-8 control &gt; control.new
+  $ mv control.new control
+
+Tag: build-info-in-binary-control-file-section
+Severity: important
+Certainty: certain
+Ref: policy 5.2
+Info: The control file has a Build-Depends, Build-Depends-Indep,
+ Build-Conflicts, or Build-Conflicts-Indep field in a binary
+ section.  These specify source package relationships, and should be in
+ the source section of the control file.
+
+Tag: debian-control-with-duplicate-fields
+Severity: important
+Certainty: certain
+Info: One of the paragraphs of your debian/control contains the same
+ field more than once. This can lead to an unexpected behaviour of dpkg
+ and apt.
+
+Tag: debian-control-has-unusual-field-spacing
+Severity: pedantic
+Certainty: certain
+Ref: policy 5.1
+Info: The field on this line of <tt>debian/control</tt> has whitespace
+ other than a single space after the colon.  This is explicitly permitted
+ in the syntax of Debian control files, but as Policy says, it is
+ conventional to put a single space after the colon.
+
+Tag: binary-control-field-duplicates-source
+Severity: wishlist
+Certainty: certain
+Info: In <tt>debian/control</tt>, this field for a binary package
+ duplicates the value inherited from the source package paragraph.  This
+ doesn't hurt anything, but you may want to take advantage of the
+ inheritance and set the value in only one place.  It prevents missing
+ duplicate places that need to be fixed if the value ever changes.
+
+Tag: debian-control-file-is-a-symlink
+Severity: normal
+Certainty: certain
+Info: The <tt>debian/control</tt> file is a symlink rather than a regular
+ file. Using symlinks for required source package files is unnecessary and
+ makes package checking and manipulation more difficult. If the control
+ file should be available in the source package under multiple names, make
+ <tt>debian/control</tt> the real file and the other names symlinks to it.
+
+Tag: no-section-field-for-source
+Severity: normal
+Certainty: certain
+Ref: policy 5.2
+Info: The package does not have a "Section:" field in the source package
+ section of its control file.  The Section field is required for source
+ packages at the request of the Debian ftp-masters.
+
+Tag: section-area-mismatch
+Severity: important
+Certainty: certain
+Info: The <tt>debian/control</tt> file places the indicated binary package
+ in a different archive area (main, contrib, non-free) than its source
+ package or other binary packages built from the same source package.  The
+ source package and any binary packages it builds must be in the same
+ area of the archive, with the single exception that source packages in
+ main may also build binary packages in contrib.
+
+Tag: xs-vcs-header-in-debian-control
+Severity: wishlist
+Certainty: certain
+Info: There is an XS-Vcs-* field in the <tt>debian/control</tt> file.  As
+ of dpkg 1.14.6, the XS- prefix is no longer necessary.  dpkg now
+ recognizes these headers and handles them correctly.  Consider removing
+ the XS- prefix for this field.
+
+Tag: stronger-dependency-implies-weaker
+Severity: normal
+Certainty: certain
+Ref: policy 7.2
+Info: In the <tt>debian/control</tt> stanza for the given package, a
+ stronger dependency field implies one of the dependencies in a weaker
+ dependency field.  In other words, the Depends field of the package
+ requires that one of the packages listed in Recommends or Suggests be
+ installed, or a package is listed in Recommends as well as Suggests.
+ .
+ Current versions of dpkg-gencontrol will silently fix this problem by
+ removing the weaker dependency, but it may indicate a more subtle bug
+ (misspelling or forgetting to remove the stronger dependency when it was
+ moved to the weaker field).
+
+Tag: package-depends-on-itself
+Severity: normal
+Certainty: certain
+Ref: policy 7.2
+Info: The given package declares a dependency on itself in its
+ <tt>debian/control</tt> stanza.  Current versions of dpkg-gencontrol will
+ silently fix this problem by removing the dependency, but it may indicate
+ a more subtle bug (misspelling or cutting and pasting the wrong package
+ name).
+
+Tag: duplicate-short-description
+Severity: wishlist
+Certainty: possible
+Info: The listed binary packages all share the same short description (the
+ first line of the Description control field).  The package names may
+ provide enough additional information to distinguish between the
+ packages, but it's common to also add a word or two to the short
+ description to clarify the difference.
+
+Tag: duplicate-long-description
+Severity: wishlist
+Certainty: certain
+Info: The listed binary packages all share the same extended description.
+ Some additional information in the extended description explaining what
+ is in each package and how it differs from the other packages is useful,
+ particularly for users who aren't familiar with Debian's package naming
+ conventions.
+
+Tag: missing-comma-after-substvar
+Severity: normal
+Certainty: possible
+Info: The given field in the <tt>debian/control</tt> file has a substvar
+ (something of the form <tt>${Variable}</tt>) that isn't followed by a
+ comma.  This is normally a lurking bug.  As long as the variable isn't
+ defined or expands to an empty string, the generated control file will be
+ syntactically valid, but as soon as the variable has a non-empty value,
+ the control file will have a syntax error.  You probably meant to put a
+ comma after the substvar expansion.
+
+Tag: missing-comma-between-items
+Severity: important
+Certainty: certain
+Info: The given field in the <tt>debian/control</tt> file contains a list
+ of items separated by commas.  It appears that when wrapping the list on
+ multiple lines, a comma was missed at the end of a line.  This can lead
+ to bogus or incomplete dependencies, conflicts etc.
+
+Tag: package-depends-on-hardcoded-libc
+Severity: normal
+Certainty: certain
+Info: The given package declares a dependency on libc directly instead
+ of using ${shlibs:Depends} in its <tt>debian/control</tt> stanza.
diff --git a/checks/control-files b/checks/control-files
new file mode 100644 (file)
index 0000000..b370912
--- /dev/null
@@ -0,0 +1,117 @@
+# control-files -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Christian Schwarz and Richard Braakman
+#
+# 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 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.
+
+package Maemian::control_files;
+use strict;
+use Tags;
+use Util;
+
+my %ctrl_deb =
+    (clilibs   => 0644,
+     config    => 0755,
+     control   => 0644,
+     conffiles => 0644,
+     md5sums   => 0644,
+     postinst  => 0755,
+     preinst   => 0755,
+     postrm    => 0755,
+     prerm     => 0755,
+     shlibs    => 0644,
+     symbols   => 0644,
+     templates => 0644,
+     triggers  => 0644);
+
+my %ctrl_udeb =
+    (config        => 0755,
+     control       => 0644,
+     isinstallable => 0755,
+     menutest      => 0755,
+     postinst      => 0755,
+     shlibs        => 0644,
+     symbols       => 0644,
+     templates     => 0644);
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+
+my %ctrl = $type eq 'udeb' ? %ctrl_udeb : %ctrl_deb;
+my %ctrl_alt = $type eq 'udeb' ? %ctrl_deb : %ctrl_udeb;
+
+# process control-index file
+open(IN, '<', "control-index") or fail("cannot open control-index file: $!");
+while (<IN>) {
+    chop;
+
+    my ($perm,$owner,$size,$date,$time,$file) = split(' ', $_, 6);
+    my $operm;
+
+    next if $file eq './';
+
+    $file =~ s,^(\./),,;
+    $file =~ s/ link to .*//;
+    $file =~ s/ -> .*//;
+
+    next if $file eq './';
+
+    # valid control file?
+    unless ( exists $ctrl{$file} ) {
+       if ( exists $ctrl_alt{$file} ) {
+           tag "not-allowed-control-file", "$file";
+           next;
+       } else {
+           tag "unknown-control-file", "$file";
+           next;
+       }
+    }
+
+    # I'm not sure about the udeb case
+    if ($type ne 'udeb' and $size == 0) {
+       tag "control-file-is-empty", "$file";
+    }
+
+
+    # skip `control' control file (that's an exception: dpkg doesn't care and
+    # this file isn't installed on the systems anyways)
+    next if $file eq 'control';
+
+    $operm = perm2oct($perm);
+
+    # correct permissions?
+    unless ($operm == $ctrl{$file}) {
+       tag "control-file-has-bad-permissions",
+           sprintf("$file %04o != %04o",$operm,$ctrl{$file});
+    }
+
+    # correct owner?
+    unless ($owner eq 'root/root') {
+       tag "control-file-has-bad-owner", "$file $owner != root/root";
+    }
+
+# for other maintainer scripts checks, see the scripts check
+}
+close IN;
+
+} # </run>
+
+1;
+
+# vim: syntax=perl sw=4 ts=8
diff --git a/checks/control-files.desc b/checks/control-files.desc
new file mode 100644 (file)
index 0000000..c09d9c9
--- /dev/null
@@ -0,0 +1,38 @@
+Check-Script: control-files
+Author: Christian Schwarz <schwarz@debian.org>
+Abbrev: ctl
+Type: binary, udeb
+Unpack-Level: 1
+
+Tag: unknown-control-file
+Severity: normal
+Certainty: possible
+Ref: policy B.2
+Info: The package contains an unknown control file.  Policy says that
+ putting additional files in the package control area is not generally a
+ good idea.
+
+Tag: not-allowed-control-file
+Severity: important
+Certainty: certain
+Info: The package contains a control file that is not allowed in this
+ type of package. Some control files are only allowed in either .deb
+ or .udeb packages and must not be included in packages of the other
+ type. You should probably just remove the file.
+
+Tag: control-file-is-empty
+Severity: normal
+Certainty: possible
+Info: The package contains an empty control file, which is most probably
+ an error.
+
+Tag: control-file-has-bad-permissions
+Severity: important
+Certainty: certain
+Info: The postinst, postrm, preinst, and prerm control files should use
+ mode 0755; all other control files should use 0644.
+
+Tag: control-file-has-bad-owner
+Severity: important
+Certainty: certain
+Info: All control files should be owned by root/root.
diff --git a/checks/copyright-file b/checks/copyright-file
new file mode 100644 (file)
index 0000000..154fa7b
--- /dev/null
@@ -0,0 +1,335 @@
+# copyright-file -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Christian Schwarz
+#
+# 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 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.
+
+package Maemian::copyright_file;
+use strict;
+
+use lib "$ENV{'MAEMIAN_ROOT'}/checks/";
+use common_data;
+
+use Spelling;
+use Tags;
+use Util;
+
+use Encode qw(decode);
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+my $ppkg = quotemeta($pkg);
+
+my $found = 0;
+my $linked = 0;
+
+# Read package contents...
+foreach (sort keys %{$info->index}) {
+    my $index_info = $info->index->{$_};
+    if (m,usr/(share/)?doc/$ppkg/copyright(\.\S+)?$,) {
+       my $ext = $2;
+
+       $ext = '' if (! defined $ext);
+       #an extension other than .gz doesn't count as copyright file
+       next unless ($ext eq '') or ($ext eq '.gz');
+       $found = 1;
+
+       #search for an extension
+       if ($ext eq '.gz') {
+           tag "copyright-file-compressed", "";
+           last;
+       }
+
+       #make sure copyright is not a symlink
+       if ($index_info->{link}) {
+           tag "copyright-file-is-symlink", "";
+           last;
+       }
+
+       #otherwise, pass
+       if (($ext eq '') and not $index_info->{link}) {
+           # everything is ok.
+           last;
+       }
+       fail("unhandled case: $_");
+
+    } elsif (m,usr/share/doc/$ppkg$, and $index_info->{link}) {
+       my $link = $index_info->{link};
+
+       $found = 1;
+       $linked = 1;
+
+       # check if this symlink references a directory elsewhere
+       if ($link =~ m,^(?:\.\.)?/,) {
+           tag "usr-share-doc-symlink-points-outside-of-usr-share-doc", "$link";
+           last;
+       }
+
+       # The symlink may point to a subdirectory of another /usr/share/doc
+       # directory.  This is allowed if this package depends on link and both
+       # packages come from the same source package.
+       #
+       # Policy requires that packages be built from the same source if
+       # they're going to do this, which by my (rra's) reading means that we
+       # should have a strict version dependency.  However, in practice the
+       # copyright file doesn't change a lot and strict version dependencies
+       # cause other problems (such as with arch: any / arch: all package
+       # combinations and binNMUs).
+       #
+       # We therefore just require the dependency for now and don't worry
+       # about the version number.
+       $link =~ s,/.*,,;
+       if (not depends_on($info, $link)) {
+           tag 'usr-share-doc-symlink-without-dependency', $link;
+           last;
+       }
+
+       # We can only check if both packages come from the same source
+       # if our source package is currently unpacked in the lab, too!
+       if (-d "source") {      # yes, it's unpacked
+
+           # $link from the same source pkg?
+           if (-l "source/binary/$link") {
+               # yes, everything is ok.
+           } else {
+               # no, it is not.
+               tag "usr-share-doc-symlink-to-foreign-package", "$link";
+           }
+       } else {                # no, source is not available
+           tag "cannot-check-whether-usr-share-doc-symlink-points-to-foreign-package", "";
+       }
+
+       # everything is ok.
+       last;
+    } elsif (m,usr/doc/copyright/$ppkg$,) {
+       tag "old-style-copyright-file", "";
+       $found = 1;
+       last;
+    }
+}
+
+if (not $found) {
+    tag "no-copyright-file", "";
+}
+
+# check that copyright is UTF-8 encoded
+my $line = file_is_encoded_in_non_utf8("copyright", $type, $pkg);
+if ($line) {
+    tag "debian-copyright-file-uses-obsolete-national-encoding", "at line $line"
+}
+
+# check contents of copyright file
+$_ = slurp_entire_file('copyright');
+
+my $wrong_directory_detected = 0;
+
+if (m,\<fill in (?:http/)?ftp site\>, or m/\<Must follow here\>/) {
+    tag "helper-templates-in-copyright", "";
+}
+
+if (m,(usr/share/common-licenses/(?:GPL|LGPL|BSD|Artistic)\.gz),) {
+    tag "copyright-refers-to-compressed-license", $1;
+}
+
+# Allow generic GPL references for packages licensed under the same terms as
+# Perl for now.  Perl references GPL version 1, which isn't in
+# common-licenses.
+#
+# Avoid complaining about referring to a versionless license file if the word
+# "version" appears nowhere in the copyright file.  This won't catch all of
+# our false positives for GPL references that don't include a specific version
+# number, but it will get the obvious ones.
+if (m,(usr/share/common-licenses/(L?GPL|GFDL))([^-]),i && !m,as Perl itself,i
+    && !m,License-Alias:\s+Perl,) {
+    my ($ref, $license, $separator) = ($1, $2, $3);
+    if ($separator =~ /[\d\w]/) {
+       tag 'copyright-refers-to-nonexistent-license-file', "$ref$separator";
+    } elsif (m,\b(?:any|or)\s+later(?:\s+version)?\b,i
+            || m,License: $license-[\d\.]+\+,i) {
+       tag "copyright-refers-to-symlink-license", $ref;
+    } else {
+       tag "copyright-refers-to-versionless-license-file", $ref
+           if /\bversion\b/;
+    }
+}
+
+if (m,(usr/share/common-licences),) {
+    tag "copyright-refers-to-incorrect-directory", $1;
+    $wrong_directory_detected = 1;
+}
+
+if (m,usr/share/doc/copyright,) {
+    tag "copyright-refers-to-old-directory", "";
+    $wrong_directory_detected = 1;
+}
+
+if (m,usr/doc/copyright,) {
+    tag "copyright-refers-to-old-directory", "";
+    $wrong_directory_detected = 1;
+}
+
+# Lame check for old FSF zip code.  Try to avoid false positives from other
+# Cambridge, MA addresses.
+if (m/(?:Free\s*Software\s*Foundation.*02139|02111-1307)/s) {
+    tag "old-fsf-address-in-copyright-file", "";
+}
+
+# Whether the package is covered by the GPL, used later for the libssl check.
+my $gpl;
+
+if (length($_) > 12000
+    and ((m/\bGNU GENERAL PUBLIC LICENSE\s*TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\b/m
+          and m/\bVersion 2\s/)
+         or (m/\bGNU GENERAL PUBLIC LICENSE\s*Version 3/ and m/\bTERMS AND CONDITIONS\s/))) {
+    tag "copyright-file-contains-full-gpl-license";
+    $gpl = 1;
+}
+
+if (length($_) > 12000
+    and m/\bGNU Free Documentation License\s*Version 1\.2/ and m/\b1\. APPLICABILITY AND DEFINITIONS/) {
+    tag "copyright-file-contains-full-gfdl-license";
+}
+
+if (length($_) > 10000
+    and m/\bApache License\s+Version 2\.0,/
+    and m/TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION/) {
+    tag "copyright-file-contains-full-apache-2-license";
+}
+
+# wtf?
+if ((m,common-licenses(/\S+),) && (! m,/usr/share/common-licenses/,)) {
+    tag "copyright-does-not-refer-to-common-license-file", "$1";
+}
+
+# This check is a bit prone to false positives, since some other licenses
+# mention the GPL.  Also exclude any mention of the GPL following what looks
+# like mail headers, since sometimes e-mail discussions of licensing are
+# included in the copyright file but aren't referring to the license of the
+# package.
+if (m,/usr/share/common-licenses,
+    || m/Zope Public License/
+    || m/LICENSE AGREEMENT FOR PYTHON 1.6.1/
+    || m/LaTeX Project Public License/
+    || m/(?:^From:.*^To:|^To:.*^From:).*(?:GNU General Public License|GPL)/ms
+    || m/AFFERO GENERAL PUBLIC LICENSE/
+    || m/GNU Free Documentation License[,\s]*Version 1\.1/
+    || m/CeCILL FREE SOFTWARE LICENSE AGREEMENT/ #v2.0
+    || m/FREE SOFTWARE LICENSING AGREEMENT CeCILL/ #v1.1
+    || m/CNRI OPEN SOURCE GPL-COMPATIBLE LICENSE AGREEMENT/
+    || m/GNU GENERAL PUBLIC LICENSE\s+Version 1/
+    || m/compatible\s+with\s+(?:the\s+)?(?:GNU\s+)?GPL/
+    || m/(?:GNU\s+)?GPL\W+compatible/
+    || m/was\s+previously\s+(?:distributed\s+)?under\s+the\s+GNU/
+    || $wrong_directory_detected) {
+    # False positive or correct reference.  Ignore.
+} elsif (m/GNU Free Documentation License/i or m/\bGFDL\b/) {
+    tag "copyright-should-refer-to-common-license-file-for-gfdl";
+} elsif (m/GNU (?:Lesser|Library) General Public License/i or m/\bLGPL\b/) {
+    tag "copyright-should-refer-to-common-license-file-for-lgpl";
+} elsif (m/GNU General Public License/i or m/\bGPL\b/) {
+    tag "copyright-should-refer-to-common-license-file-for-gpl";
+    $gpl = 1;
+}
+if (m,(?:under )?(?:the )?(?:same )?(?:terms )?as Perl itself,i &&
+    !m,usr/share/common-licenses/,) {
+    tag "copyright-file-lacks-pointer-to-perl-license";
+}
+
+# Checks for various packaging helper boilerplate.
+
+if (m/^This copyright info was automatically extracted from the perl module\./) {
+    tag "helper-templates-in-copyright", "";
+}
+
+if (m,Upstream Author\(s\),) {
+    tag "copyright-lists-upstream-authors-with-dh_make-boilerplate";
+}
+
+if (m,url://example\.com,) {
+    tag "copyright-has-url-from-dh_make-boilerplate";
+}
+
+if (m{\# Please also look if there are files or directories which have a\n\# different copyright/license attached and list them here\.}) {
+    tag "copyright-contains-dh_make-todo-boilerplate", "";
+}
+if (m{This copyright info was automatically extracted from the perl module\.\nIt may not be accurate, so you better check the module sources\nif you don\'t want to get into legal troubles\.}) {
+    tag "copyright-contains-dh-make-perl-boilerplate", "";
+}
+
+if (m,The\s+Debian\s+packaging\s+is\s+\(C\)\s+\d+,i) {
+    tag 'copyright-with-old-dh-make-debian-copyright';
+}
+
+# Bad licenses.
+if (m/(The\s+PHP\s+Licen[cs]e,?\s+version\s+2)/si) {
+    tag 'copyright-refers-to-bad-php-license';
+}
+if (m/(The\s+PHP\s+Licen[cs]e,?\s+version\s+3\.0[^\d])/si) {
+    tag 'copyright-refers-to-problematic-php-license';
+}
+
+# Other flaws in the copyright phrasing or contents.
+
+if ($found && !$linked && !/(?:Copyright|Copr\.|\302\251)(?:.*|[\(C\):\s]+)\b\d{4}\b|\bpublic\s+domain\b/i) {
+    tag 'copyright-without-copyright-notice';
+}
+
+spelling_check('spelling-error-in-copyright', $_);
+
+# Now, check for linking against libssl if the package is covered by the GPL.
+# (This check was requested by ftp-master.)  First, see if the package is
+# under the GPL alone and try to exclude packages with a mix of GPL and LGPL
+# or Artistic licensing or with an exception or exemption.
+if ($gpl || m,/usr/share/common-licenses/GPL,) {
+    unless (m,exception|exemption|/usr/share/common-licenses/(?!GPL)\S,) {
+       my @depends;
+       if (defined $info->field('depends')) {
+           @depends = split (/\s*,\s*/, scalar $info->field('depends'));
+       }
+       if (defined $info->field('pre-depends')) {
+           push @depends, split (/\s*,\s*/, scalar $info->field('pre-depends'));
+       }
+       if (grep { /^libssl[0-9.]+(?:\s|\z)/ && !/\|/ } @depends) {
+           tag 'possible-gpl-code-linked-with-openssl';
+       }
+    }
+}
+
+} # </run>
+
+# -----------------------------------
+
+# Returns true if the package whose information is in $info depends $package
+# or if $package is essential.
+sub depends_on {
+    my ($info, $package) = @_;
+    return 1 if $known_essential{$package};
+    return 1 if $info->relation('strong')->implies($package);
+    return 0;
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl ts=8 sw=4
diff --git a/checks/copyright-file.desc b/checks/copyright-file.desc
new file mode 100644 (file)
index 0000000..d45374a
--- /dev/null
@@ -0,0 +1,367 @@
+Check-Script: copyright-file
+Author: Christian Schwarz <schwarz@debian.org>
+Abbrev: cpy
+Type: binary
+Unpack-Level: 1
+Needs-Info: copyright-file
+Info: This script checks if a binary package conforms to policy
+ with regard to copyright files.
+ .
+ Each binary package must either have a
+ /usr/share/doc/&lt;foo&gt;/copyright file or must have a symlink
+ /usr/share/doc/&lt;foo&gt; -&gt; &lt;bar&gt;, where &lt;bar&gt; comes
+ from the same source package and pkg foo declares a "Depends" relation on
+ bar.
+
+Tag: no-copyright-file
+Severity: serious
+Certainty: certain
+Info: Each binary package has to include a plain file
+ /usr/share/doc/<i>pkg</i>/copyright
+Ref: policy 12.5
+Tested: empty
+
+Tag: copyright-refers-to-old-directory
+Severity: important
+Certainty: certain
+Info: The common licenses (GPL, BSD, Artistic, etc) have been moved from
+ /usr/doc/copyright to /usr/share/common-licenses.
+ Copyright files should be updated.
+Ref: policy 12.5
+
+Tag: copyright-file-compressed
+Severity: serious
+Certainty: certain
+Info: The copyright file /usr/share/doc/<i>pkg</i>/copyright must not be
+ compressed.
+Ref: policy 12.5
+
+Tag: copyright-file-is-symlink
+Severity: serious
+Certainty: certain
+Info: The copyright file /usr/share/doc/<i>pkg</i>/copyright must not be a
+ symbolic link.
+Ref: policy 12.5
+
+Tag: copyright-file-contains-full-gpl-license
+Severity: important
+Certainty: certain
+Info: The copyright file /usr/share/doc/<i>pkg</i>/copyright contains the
+ complete text of the GPL v2 or v3.  It should refer to the file
+ <tt>/usr/share/common-licenses/GPL-2</tt> or <tt>GPL-3</tt> instead.
+Ref: policy 12.5
+
+Tag: copyright-file-contains-full-gfdl-license
+Severity: important
+Certainty: certain
+Info: The copyright file /usr/share/doc/<i>pkg</i>/copyright contains the
+ complete text of the GFDL v1.2.  It should refer to the file
+ <tt>/usr/share/common-licenses/GFDL-1.2</tt> instead.
+Ref: policy 12.5
+
+Tag: copyright-file-contains-full-apache-2-license
+Severity: important
+Certainty: certain
+Info: The copyright file /usr/share/doc/<i>pkg</i>/copyright contains the
+ complete text of the Apache 2.0 license.  It should refer to the file
+ <tt>/usr/share/common-licenses/Apache-2.0</tt> instead.
+Ref: policy 12.5
+
+Tag: usr-share-doc-symlink-without-dependency
+Severity: important
+Certainty: certain
+Info: If the package installs a symbolic link /usr/share/doc/<i>pkg1</i> -&gt;
+ <i>pkg2</i>, then <i>pkg1</i> has to depend on <i>pkg2</i> with the same
+ version as <i>pkg1</i>.
+ .
+ Note, that adding the "Depends:" entry just to fix this bug is not a good
+ solution. It's suggested that you include a real /usr/share/doc/<i>pkg1</i>
+ directory within <i>pkg1</i> and copy the copyright file into that directory.
+Ref: policy 12.5
+
+Tag: usr-share-doc-symlink-to-foreign-package
+Severity: important
+Certainty: certain
+Info: If the package installs a symbolic link /usr/share/doc/<i>pkg1</i> -&gt;
+ <i>pkg2</i>, then <i>pkg1</i> and <i>pkg2</i> must both come from the same
+ source package.
+ .
+ It's suggested that you include a real /usr/share/doc/<i>pkg1</i> directory
+ within <i>pkg1</i> and copy the copyright file to that directory.
+Ref: policy 12.5
+
+Tag: cannot-check-whether-usr-share-doc-symlink-points-to-foreign-package
+Severity: minor
+Certainty: possible
+Info: There is a symlink /usr/share/doc/<i>pkg1</i> -&gt; <i>pkg2</i>
+ in your package. This means that <i>pkg1</i> and <i>pkg2</i> must
+ both come from the same source package. I can't check this right now
+ however since I'm only checking a binary package and I only can check
+ this when I'm checking both the binary and the corresponding source
+ package.
+
+Tag: old-style-copyright-file
+Severity: important
+Certainty: certain
+Info: The package installs a /usr/doc/copyright/<i>pkg</i> file. Instead,
+ you should place the copyright file in /usr/share/doc/<i>pkg</i>/copyright.
+Ref: policy 12.5
+
+Tag: old-fsf-address-in-copyright-file
+Severity: normal
+Certainty: certain
+Info: The /usr/share/doc/<i>pkg</i>/copyright file refers to the old postal
+ address of the Free Software Foundation (FSF). The new address is:
+ .
+   Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+   MA 02110-1301, USA.
+
+Tag: helper-templates-in-copyright
+Severity: important
+Certainty: certain
+Info: The /usr/share/doc/<i>pkg</i>/copyright file still contains the template
+ contents from a packaging helper. Please include the actual license and
+ download information about the package.
+
+Tag: copyright-refers-to-compressed-license
+Severity: important
+Certainty: certain
+Info: The /usr/share/doc/<i>pkg</i>/copyright file refers to a standard license
+ /usr/share/common-licenses/{GPL,LGPL,Artistic,BSD}.gz as a compressed
+ file. Please update the reference (the licenses are installed
+ uncompressed).
+
+Tag: usr-share-doc-symlink-points-outside-of-usr-share-doc
+Severity: important
+Certainty: certain
+Info: The /usr/share/doc/<i>pkg</i> symbolic link is pointing to a directory
+ outside of <tt>/usr/share/doc</tt>.
+Ref: policy 12.5
+
+Tag: copyright-does-not-refer-to-common-license-file
+Severity: normal
+Certainty: certain
+Info: If your package uses any one of the licenses in
+ <tt>/usr/share/common-licenses</tt>, the copyright file should refer to
+ files therein.
+Ref: policy 12.5
+
+Tag: copyright-refers-to-incorrect-directory
+Severity: important
+Certainty: certain
+Ref: policy 12.5
+Info: In the directory name /usr/share/common-licenses, licenses is spelled
+ with an "s", not as licences with a "c".
+
+Tag: copyright-file-lacks-pointer-to-perl-license
+Severity: important
+Certainty: possible
+Ref: policy 12.5
+Info: If your package is released under the same terms as Perl itself,
+ it should refer to the Artistic and GPL license files in the
+ <tt>/usr/share/common-licenses</tt> directory.
+
+Tag: copyright-should-refer-to-common-license-file-for-gpl
+Severity: important
+Certainty: possible
+Ref: policy 12.5
+Info: The strings "GNU General Public License" or "GPL" appear in the
+ copyright file for this package, but the copyright file does not
+ reference <tt>/usr/share/common-licenses</tt> as the location of the GPL
+ on Debian systems.
+ .
+ If the package uses some other license that just mentions the GPL and
+ that Maemian should detect as an exception, please file a Maemian bug.
+ If the copyright file must mention the GPL for reasons other than stating
+ the license of the package, please add a Maemian override.
+
+Tag: copyright-should-refer-to-common-license-file-for-gfdl
+Severity: important
+Certainty: possible
+Ref: policy 12.5
+Info: The strings "GNU Free Documentation License" or "GFDL" appear in the
+ copyright file for this package, but the copyright file does not
+ reference <tt>/usr/share/common-licenses</tt> as the location of the GFDL
+ on Debian systems.
+ .
+ If the package uses some other license that just mentions the GFDL and
+ that Maemian should detect as an exception, please file a Maemian bug.
+ If the copyright file must mention the GFDL for reasons other than stating
+ the license of the package, please add a Maemian override.
+
+Tag: copyright-should-refer-to-common-license-file-for-lgpl
+Severity: important
+Certainty: possible
+Ref: policy 12.5
+Info: The strings "GNU Lesser General Public License", "GNU Library
+ General Public License", or "LGPL" appear in the copyright file for this
+ package, but the copyright file does not reference
+ <tt>/usr/share/common-licenses</tt> as the location of the LGPL on Debian
+ systems.
+ .
+ If the package uses some other license that just mentions the LGPL and
+ that Maemian should detect as an exception, please file a Maemian bug.
+ If the copyright file must mention the LGPL for reasons other than stating
+ the license of the package, please add a Maemian override.
+
+Tag: copyright-lists-upstream-authors-with-dh_make-boilerplate
+Severity: normal
+Certainty: certain
+Info: There is "Upstream Author(s)" in your copyright file. This was most
+ likely a remnant from the dh_make template.
+ .
+ There's either one upstream author, in which case you should remove the
+ "(s)", or there are several upstream authors, in which case you should
+ remove the "(" and ")".
+ .
+ o/~ join us now and carefully edit debian/copyright files! o/~
+
+Tag: copyright-has-url-from-dh_make-boilerplate
+Severity: normal
+Certainty: certain
+Ref: policy 12.5
+Info: There is "url://example.com" in your copyright file. This was most
+ likely a remnant from the dh_make template.
+ .
+ Make sure you include the real location where you obtained the
+ upstream sources (if any).
+
+Tag: debian-copyright-file-uses-obsolete-national-encoding
+Severity: normal
+Certainty: certain
+Info: The Debian copyright file should be valid UTF-8, an encoding of
+ the Unicode character set.
+ .
+ There are many ways to convert a copyright file from an obsoleted encoding
+ like ISO-8859-1; you may for example use "iconv" like:
+ .
+  $ iconv -f ISO-8859-1 -t UTF-8 copyright &gt; copyright.new
+  $ mv copyright.new copyright
+
+Tag: copyright-contains-dh_make-todo-boilerplate
+Severity: normal
+Certainty: certain
+Ref: policy 12.5
+Info: The string "Please also look if..." appears in the copyright
+ file, which indicates that you either didn't check the whole source
+ to find additional copyright/license, or that you didn't remove that
+ paragraph after having done so.
+
+Tag: copyright-contains-dh-make-perl-boilerplate
+Severity: normal
+Certainty: certain
+Ref: policy 12.5
+Info: The string "This copyright info was automatically extracted..."
+ appears in the copyright file, which indicates that you either didn't
+ check the whole source to find additional copyright/license, or that
+ you didn't remove that paragraph after having done so.
+
+Tag: copyright-with-old-dh-make-debian-copyright
+Severity: wishlist
+Certainty: certain
+Info: The copyright file contains the incomplete Debian packaging
+ copyright boilerplate from older versions of <tt>dh_make</tt>.
+ <tt>(C)</tt> is not considered as a valid way to express the copyright
+ ownership.  The word <tt>Copyright</tt> or the Â© symbol should be used
+ instead or in addition to <tt>(C)</tt>.
+
+Tag: copyright-refers-to-bad-php-license
+Severity: serious
+Certainty: possible
+Info: This package appears to be covered by version 2.x of the PHP license,
+ which is not appropriate for anything other than PHP itself.
+Ref: http://ftp-master.debian.org/REJECT-FAQ.html
+
+Tag: copyright-refers-to-problematic-php-license
+Severity: serious
+Certainty: wild-guess
+Info: This package appears to be covered by version 3.0 (exactly) of the
+ PHP license.  This license is not applicable to anything that is not PHP
+ and has no contributions from the PHP Group.
+Ref: http://ftp-master.debian.org/REJECT-FAQ.html
+
+Tag: copyright-without-copyright-notice
+Severity: normal
+Certainty: certain
+Ref: http://ftp-master.debian.org/REJECT-FAQ.html
+Info: The copyright file for this package does not appear to contain a
+ copyright notice.  You should copy the copyright notice from the upstream
+ source (or add one of your own for a native package).  A copyright notice
+ must consist of Copyright, Copr., or the Unicode symbol of C in a circle
+ followed by the years and the copyright holder.  A copyright notice is
+ not required for a work to be copyrighted, but Debian requires the
+ copyright file include the authors and years of copyright, and including
+ a valid copyright notice is the best way to do that.
+ .
+ If the package is in the public domain rather than copyrighted, be sure
+ to mention "public domain" in the copyright file.  Please be aware that
+ this is very rare and not the same as a DFSG-free license.  True public
+ domain software is generally limited to such special cases as a work
+ product of a United States government agency.
+
+Tag: spelling-error-in-copyright
+Severity: normal
+Certainty: possible
+Info: Maemian found a spelling error in the copyright file.  Maemian has a
+ list of common misspellings that it looks for.  It does not have a
+ dictionary like a spelling checker does.
+
+Tag: possible-gpl-code-linked-with-openssl
+Severity: serious
+Certainty: wild-guess
+Info: This package appears to be covered by the GNU GPL but depends on
+ the OpenSSL libssl package and does not mention a license exemption or
+ exception for OpenSSL in its copyright file.  The GPL (including version
+ 3) is incompatible with some terms of the OpenSSL license, and therefore
+ Debian does not allow GPL-licensed code linked with OpenSSL libraries
+ unless there is a license exception explicitly permitting this.
+ .
+ If only the Debian packaging, or some other part of the package not
+ linked with OpenSSL, is covered by the GNU GPL, please add a lintian
+ override for this tag.  Maemian currently has no good way of
+ distinguishing between that case and problematic packages.
+
+Tag: copyright-refers-to-symlink-license
+Severity: pedantic
+Certainty: possible
+Info: The copyright file refers to the versionless symlink in
+ <tt>/usr/share/common-licenses</tt> for the full text of the GPL, LGPL,
+ or GFDL license.  This symlink is updated to point to the latest version
+ of the license when a new one is released.  The package appears to allow
+ relicensing under later versions of its license, so this is legally
+ consistent, but it implies that Debian will relicense the package under
+ later versions of those licenses as they're released.  It is normally
+ better to point to the version of the license the package references in
+ its license statement.
+ .
+ For example, if the package says something like "you may redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at your
+ option) any later version", the <tt>debian/copyright</tt> file should
+ refer to <tt>/usr/share/common-licenses/GPL-2</tt>, not <tt>/GPL</tt>.
+
+Tag: copyright-refers-to-versionless-license-file
+Severity: normal
+Certainty: possible
+Info: The copyright file refers to the versionless symlink in
+ <tt>/usr/share/common-licenses</tt> for the full text of the GPL, LGPL,
+ or GFDL license, but the package does not appear to allow distribution
+ under later versions of the license.  This symlink will change with each
+ release of a new version of the license and may therefore point to a
+ different version than the package is released under.
+ <tt>debian/copyright</tt> should instead refers to the specific version
+ of the license that the package references.
+ .
+ For example, if the package says something like "you can redistribute it
+ and/or modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; version 2 dated June, 1991,"
+ the <tt>debian/copyright</tt> file should refer to
+ <tt>/usr/share/common-licenses/GPL-2</tt>, not <tt>/GPL</tt>.
+
+Tag: copyright-refers-to-nonexistent-license-file
+Severity: normal
+Certainty: certain
+Info: The copyright file refers to a license in
+ <tt>/usr/share/common-licenses</tt> that doesn't exist.  Usually this is
+ a typo, such as accidentally omitting the <tt>-</tt> between the license
+ name and the version number.
diff --git a/checks/cruft b/checks/cruft
new file mode 100644 (file)
index 0000000..52eab01
--- /dev/null
@@ -0,0 +1,352 @@
+# cruft -- lintian check script -*- perl -*-
+#
+# based on debhelper check,
+# Copyright (C) 1999 Joey Hess
+# Copyright (C) 2000 Sean 'Shaleh' Perry
+# Copyright (C) 2002 Josip Rodin
+# Copyright (C) 2007 Russ Allbery
+#
+# 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 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.
+
+package Maemian::cruft;
+use strict;
+
+use Maemian::Relation ();
+use Tags;
+use Util;
+
+use Cwd;
+use File::Find;
+use File::Basename;
+
+# All the packages that may provide config.{sub,guess} during the build, used
+# to suppress warnings about outdated autotools helper files.  I'm not
+# thrilled with having the automake exception as well, but people do depend on
+# autoconf and automake and then use autoreconf to update config.guess and
+# config.sub, and automake depends on autotools-dev.
+our $AUTOTOOLS = Maemian::Relation->new(join(' | ',
+    qw(autotools-dev automake automaken automake1.4 automake1.7 automake1.8
+       automake1.9 automake1.10)));
+
+# The files that contain error messages from tar, which we'll check and issue
+# tags for if they contain something unexpected, and their corresponding tags.
+our %ERRORS = ('index-errors'    => 'tar-errors-from-source',
+               'unpacked-errors' => 'tar-errors-from-source');
+
+# Directory checks.  These regexes match a directory that shouldn't be in the
+# source package and associate it with a tag (minus the leading
+# source-contains or diff-contains).  Note that only one of these regexes
+# should trigger for any single directory.
+my @directory_checks =
+    ([ qr,^(.+/)?CVS$,        => 'cvs-control-dir'  ],
+     [ qr,^(.+/)?\.svn$,      => 'svn-control-dir'  ],
+     [ qr,^(.+/)?\.bzr$,      => 'bzr-control-dir'  ],
+     [ qr,^(.+/)?\{arch\}$,   => 'arch-control-dir' ],
+     [ qr,^(.+/)?\.arch-ids$, => 'arch-control-dir' ],
+     [ qr!^(.+/)?,,.+$!       => 'arch-control-dir' ],
+     [ qr,^(.+/)?\.git$,      => 'git-control-dir'  ],
+     [ qr,^(.+/)?\.hg$,       => 'hg-control-dir'   ],
+     [ qr,^(.+/)?\.be$,       => 'bts-control-dir'  ],
+     [ qr,^(.+/)?\.ditrack$,  => 'bts-control-dir'  ],
+    );
+
+# File checks.  These regexes match files that shouldn't be in the source
+# package and associate them with a tag (minus the leading source-contains or
+# diff-contains).  Note that only one of these regexes should trigger for any
+# given file.  If the third column is a true value, don't issue this tag
+# unless the file is included in the diff; it's too common in source packages
+# and not important enough to worry about.
+my @file_checks =
+    ([ qr,^(.+/)?svn-commit\.(.+\.)?tmp$, => 'svn-commit-file'        ],
+     [ qr,^(.+/)?svk-commit.+\.tmp$,      => 'svk-commit-file'        ],
+     [ qr,^(.+/)?\.arch-inventory$,       => 'arch-inventory-file'    ],
+     [ qr,^(.+/)?\.hgtags$,               => 'hg-tags-file'           ],
+     [ qr,^(.+/)?\.\#(.+?)\.\d+(\.\d+)*$, => 'cvs-conflict-copy'      ],
+     [ qr,^(.+/)?(.+?)\.(r\d+)$,          => 'svn-conflict-file'      ],
+     [ qr,\.(orig|rej)$,                  => 'patch-failure-file',  1 ],
+     [ qr,((^|/)\.[^/]+\.swp|~)$,         => 'editor-backup-file',  1 ],
+    );
+
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+if (-e "debfiles/files" and not -z "debfiles/files") {
+    tag 'debian-files-list-in-source';
+}
+
+# This doens't really belong here, but there isn't a better place at the
+# moment to put this check.
+if ($info->native) {
+    my $version = $info->field('version');
+    if ($version =~ /-/ and $version !~ /-0\.[^-]+$/) {
+        tag 'native-package-with-dash-version';
+    }
+}
+
+# Check if this is a documentation package that's not arch: all.  This doesn't
+# really belong here either.
+my $arch;
+if (defined $info->field('architecture')) {
+    my $arch = $info->field('architecture');
+    if ($pkg =~ /-docs?$/ && $arch ne 'all') {
+        tag 'documentation-package-not-architecture-independent';
+    }
+}
+
+# Read build-depends file and see if it depends on autotools-dev or automake.
+my $atdinbd = $info->relation('build-depends')->implies($AUTOTOOLS);
+
+# Create a closure so that we can pass our lexical variables into the find
+# wanted function.  We don't want to make them global because we'll then leak
+# that data across packages in a large Maemian run.
+my %warned;
+my $format = $info->field('format');
+if ($format =~ /^\s*2\.0\s*\z/ or $format =~ /^\s*3\.0\s*\(quilt\)/) {
+    my $wanted = sub { check_debfiles($pkg, $info, \%warned) };
+    find($wanted, 'debfiles');
+} elsif (not $info->native) {
+    check_diffstat("diffstat", \%warned);
+}
+my $wanted = sub { find_cruft($pkg, $info, \%warned, $atdinbd) };
+find($wanted, 'unpacked');
+
+# Look for cruft based on file's results, but allow cruft in test directories
+# where it may be part of a test suite.
+my $file_info = $info->file_info;
+for my $file (keys(%$file_info)) {
+    next if ($file =~ m,(?:^|/)t(?:est(?:s(?:et)?)?)?/,);
+    if ($file_info->{$file} =~ m/\bELF\b/) {
+       tag "source-contains-prebuilt-binary", $file;
+    } elsif ($file_info->{$file} =~ m/\bPE(32|64)\b/) {
+       tag "source-contains-prebuilt-windows-binary", $file;
+    }
+}
+
+# Report any error messages from tar while unpacking the source package if it
+# isn't just tar cruft.
+for my $file (keys %ERRORS) {
+    my $tag = $ERRORS{$file};
+    if (-s $file) {
+        open(ERRORS, '<', $file) or fail("cannot open $file: $!");
+        local $_;
+        while (<ERRORS>) {
+            chomp;
+            s,^(?:[/\w]+/)?tar: ,,;
+
+            # Record size errors are harmless.  Skipping to next header
+            # apparently comes from star files.  Ignore all GnuPG noise from
+            # not having a valid GnuPG configuration directory.  Also ignore
+            # the tar "exiting with failure status" message, since it comes
+            # after some other error.
+            next if /^Record size =/;
+            next if /^Skipping to next header/;
+            next if /^gpgv?: /;
+            next if /^secmem usage: /;
+            next if /^Exiting with failure status due to previous errors/;
+            tag $tag, $_;
+        }
+        close ERRORS;
+    }
+}
+
+} # </run>
+
+# -----------------------------------
+
+# Check the diff for problems.  Record any files we warn about in $warned so
+# that we don't warn again when checking the full unpacked source.  Takes the
+# name of a file containing diffstat output.
+sub check_diffstat {
+    my ($diffstat, $warned) = @_;
+    my $saw_file;
+    open(STAT, '<', $diffstat) or fail("cannot open $diffstat: $!");
+    local $_;
+    while (<STAT>) {
+        my ($file) = (m,^\s+(.*?)\s+\|,)
+            or fail("syntax error in diffstat file: $_");
+        $saw_file = 1;
+
+        # Check for CMake cache files.  These embed the source path and hence
+        # will cause FTBFS on buildds, so they should never be touched in the
+        # diff.
+        if ($file =~ m,(^|/)CMakeCache.txt\z,) {
+            tag 'diff-contains-cmake-cache-file', $file;
+        }
+
+        # For everything else, we only care about diffs that add files.  If
+        # the file is being modified, that's not a problem with the diff and
+        # we'll catch it later when we check the source.  This regex doesn't
+        # catch only file adds, just any diff that doesn't remove lines from a
+        # file, but it's a good guess.
+        next unless m,\|\s+\d+\s+\++$,;
+
+        # diffstat output contains only files, but we consider the directory
+        # checks to trigger if the diff adds any files in those directories.
+        my ($directory) = ($file =~ m,^(.*)/[^/]+$,);
+        if ($directory and not $warned->{$directory}) {
+            for my $rule (@directory_checks) {
+                if ($directory =~ /$rule->[0]/) {
+                    tag "diff-contains-$rule->[1]", $directory;
+                    $warned->{$directory} = 1;
+                }
+            }
+        }
+
+        # Now the simpler file checks.
+        for my $rule (@file_checks) {
+            if ($file =~ /$rule->[0]/) {
+                tag "diff-contains-$rule->[1]", $file;
+                $warned->{$file} = 1;
+            }
+        }
+
+        # Additional special checks only for the diff, not the full source.
+        if ($file =~ m,^debian/substvars$,) {
+            tag 'diff-contains-substvars', $file;
+        }
+    }
+    close(STAT) or fail("error reading diffstat file: $!");
+
+    # If there was nothing in the diffstat output, there was nothing in the
+    # diff, which is probably a mistake.
+    tag 'empty-debian-diff' unless $saw_file;
+}
+
+# Check the debian directory for problems.  This is used for Format: 2.0 and
+# 3.0 (quilt) packages where there is no Debian diff and hence no diffstat
+# output.  Record any files we warn about in $warned so that we don't warn
+# again when checking the full unpacked source.
+sub check_debfiles {
+    my ($pkg, $info, $warned) = @_;
+    (my $name = $File::Find::name) =~ s,^(\./)?debfiles/,,;
+
+    # Check for unwanted directories and files.  This really duplicates the
+    # find_cruft function and we should find a way to combine them.
+    if (-d) {
+        for my $rule (@directory_checks) {
+            if ($name =~ /$rule->[0]/) {
+                tag "diff-contains-$rule->[1]", "debian/$name";
+                $warned->{"debian/$name"} = 1;
+            }
+        }
+    }
+    -f or return;
+    for my $rule (@file_checks) {
+        if ($name =~ /$rule->[0]/) {
+            tag "diff-contains-$rule->[1]", "debian/$name";
+            $warned->{"debian/$name"} = 1;
+        }
+    }
+
+    # Additional special checks only for the diff, not the full source.
+    if ($name eq 'substvars') {
+        tag 'diff-contains-substvars', "debian/$name";
+    }
+}
+
+# Check each file in the source package for problems.  By the time we get to
+# this point, we've already checked the diff and warned about anything added
+# there, so we only warn about things that weren't in the diff here.
+#
+# Report problems with native packages using the "diff-contains" rather than
+# "source-contains" tag.  The tag isn't entirely accurate, but it's better
+# than creating yet a third set of tags, and this gets the severity right.
+sub find_cruft {
+    my ($pkg, $info, $warned, $atdinbd) = @_;
+    (my $name = $File::Find::name) =~ s,^(\./)?unpacked/,,;
+
+    # Ignore files in test suites.  They may be part of the test.
+    if (-d and m,^t(?:est(?:s(?:et)?)?)?\z,) {
+        $File::Find::prune = 1;
+        return;
+    }
+
+    my $prefix = ($info->native ? "diff-contains" : "source-contains");
+    if (-d and not $warned->{$name}) {
+        for my $rule (@directory_checks) {
+            if ($name =~ /$rule->[0]/) {
+                tag "${prefix}-$rule->[1]", $name;
+            }
+        }
+    }
+    -f or return; # we just need normal files for the rest
+
+    unless ($warned->{$name}) {
+        for my $rule (@file_checks) {
+            next if ($rule->[2] and not $info->native);
+            if ($name =~ /$rule->[0]/) {
+                tag "${prefix}-$rule->[1]", $name;
+            }
+        }
+    }
+
+    # Tests of autotools files are a special case.  Ignore debian/config.cache
+    # as anyone doing that probably knows what they're doing and is using it
+    # as part of the build.
+    if ($name =~ m,^(.+/)?config.(?:cache|log|status)$,) {
+        if ($name !~ m,^debian/config\.cache$,) {
+            tag "configure-generated-file-in-source", $name;
+        }
+    } elsif ($name =~ m,^(.+/)?config.(?:guess|sub)$, and not $atdinbd) {
+        my $b = basename $name;
+        open (F, '<', $b) or die "can't open $name: $!";
+        while (<F>) {
+            last if $. > 10; # it's on the 6th line, but be a bit more lenient
+            if (/^(?:timestamp|version)='((\d+)-(\d+).*)'$/) {
+                my ($date, $year, $month) = ($1, $2, $3);
+                if ($year < 2004) {
+                    tag 'ancient-autotools-helper-file', $name, $date;
+                } elsif (($year < 2006) or ($year == 2006 and $month < 6)) {
+                    tag 'outdated-autotools-helper-file', $name, $date;
+                }
+            }
+        }
+        close F;
+    } elsif ($name =~ m,^(.+/)?ltconfig$,) {
+        tag "ancient-libtool", $name;
+    } elsif ($name =~ m,^(.+/)?ltmain\.sh$,) {
+        my $b = basename $name;
+        open (F, '<', $b) or die "can't open $name: $!";
+        while (<F>) {
+            if (/^VERSION=[\"\']?(1\.(\d)\.(\d+)(?:-(\d))?)/) {
+                my ($version, $major, $minor, $debian) = ($1, $2, $3, $4);
+                if ($major < 5 or ($major == 5 and $minor < 2)) {
+                    tag "ancient-libtool", $name, $version;
+                } elsif ($minor == 2 and (!$debian or $debian < 2)) {
+                    tag "ancient-libtool", $name, $version;
+                } elsif ($minor < 24) {
+                    # not entirely sure whether that would be good idea
+#                    tag "outdated-libtool", $name, $version;
+                }
+                last;
+            }
+        }
+        close F;
+    }
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: ts=8 sw=4 noet syntax=perl
diff --git a/checks/cruft.desc b/checks/cruft.desc
new file mode 100644 (file)
index 0000000..7892bff
--- /dev/null
@@ -0,0 +1,401 @@
+Check-Script: cruft
+Author: Sean 'Shaleh' Perry <shaleh@debian.org>
+Abbrev: deb
+Type: source
+Unpack-Level: 2
+Info: This looks for cruft in Debian packaging or upstream source
+Needs-Info: debfiles, diffstat, file-info
+
+Tag: native-package-with-dash-version
+Severity: normal
+Certainty: certain
+Info: Native packaging should only be used if a piece of software was
+ written specifically to be turned into a Debian package. In this case,
+ the version number should not contain a Debian revision part.
+ .
+ Native source packages are sometimes created by accident. In most cases
+ the reason is the location of the original source tarball. dpkg-source
+ searches for this in ../package_upstream-version.orig.tar.gz.
+
+Tag: documentation-package-not-architecture-independent
+Severity: normal
+Certainty: certain
+Info: Documentation packages usually shouldn't carry anything that requires
+ recompiling on various architectures, in order to save space on mirrors.
+
+Tag: debian-files-list-in-source
+Severity: important
+Certainty: certain
+Info: Leaving <tt>debian/files</tt> causes problems for the autobuilders,
+ since that file will likely include the list of .deb files for another
+ architecture, which will cause dpkg-buildpackage run by the buildd to fail.
+ .
+ The clean rule for the package should remove this file.
+Ref: policy 4.12
+
+Tag: diff-contains-cmake-cache-file
+Severity: serious
+Certainty: possible
+Info: The Debian diff contains a CMake cache file.  These files embed the
+ full path of the source tree in which they're created and cause build
+ failures if they exist when the source is built under a different path,
+ so they will always cause errors on the buildds.  The file was probably
+ accidentally included.  If it is present in the upstream source, don't
+ modify it in the Debian diff; instead, delete it before the build in
+ <tt>debian/rules</tt>.
+
+Tag: diff-contains-cvs-control-dir
+Severity: normal
+Certainty: certain
+Info: The Debian diff or native package contains files in a CVS directory.
+ These are usually artifacts of the revision control system used by the
+ Debian maintainer and not useful in a diff or native package.  Passing
+ <tt>-i</tt> to <tt>dpkg-buildpackage</tt> or the equivalent will
+ automatically exclude them.
+
+Tag: source-contains-cvs-control-dir
+Severity: pedantic
+Certainty: certain
+Info: The upstream source contains a CVS directory.  It was most likely
+ included by accident since CVS directories usually don't belong in
+ releases.  When packaging a CVS snapshot, export from CVS rather than use
+ a checkout.  If an upstream release tarball contains CVS directories, you
+ usually should report this as a bug to upstream.
+
+Tag: diff-contains-svn-control-dir
+Severity: normal
+Certainty: certain
+Info: The Debian diff or native package contains files in an .svn
+ directory.  These are usually artifacts of the revision control system
+ used by the Debian maintainer and not useful in a diff or native package.
+ Passing <tt>-i</tt> to <tt>dpkg-buildpackage</tt> or the equivalent will
+ automatically exclude them.
+
+Tag: source-contains-svn-control-dir
+Severity: pedantic
+Certainty: certain
+Info: The upstream source contains an .svn directory.  It was most likely
+ included by accident since Subversion version control directories
+ usually don't belong in releases.  When packaging a Subversion snapshot,
+ export from subversion rather than checkout.  If an upstream release
+ tarball contains .svn directories, this should be reported as a bug to
+ upstream since it can double the size of the tarball to no purpose.
+
+Tag: diff-contains-bzr-control-dir
+Severity: normal
+Certainty: certain
+Info: The Debian diff or native package contains files in a .bzr
+ directory.  These are usually artifacts of the revision control system
+ used by the Debian maintainer and not useful in a diff or native package.
+ Passing <tt>-i</tt> to <tt>dpkg-buildpackage</tt> or the equivalent will
+ automatically exclude them.
+
+Tag: source-contains-bzr-control-dir
+Severity: pedantic
+Certainty: certain
+Info: The upstream source contains a .bzr directory. It was most likely
+ included by accident since bazaar-ng version control directories usually
+ don't belong in releases and may contain the entire repository.  When
+ packaging a bzr snapshot, use bzr export to create a clean tree.  If an
+ upstream release tarball contains .bzr directories, you should usually
+ report this as a bug upstream.
+
+Tag: diff-contains-arch-control-dir
+Severity: normal
+Certainty: certain
+Info: The Debian diff or native package contains files in an {arch} or
+ .arch-ids directory or a directory starting with <tt>,,</tt> (used by baz
+ for debugging traces).  These are usually artifacts of the revision
+ control system used by the Debian maintainer and not useful in a diff or
+ native package.  Passing <tt>-i</tt> to <tt>dpkg-buildpackage</tt> or the
+ equivalent will automatically exclude them.
+
+Tag: source-contains-arch-control-dir
+Severity: pedantic
+Certainty: certain
+Info: The upstream source contains an {arch} or .arch-ids directory or a
+ directory starting with <tt>,,</tt> (used by baz for debugging traces).
+ It was most likely included by accident since Arch version control
+ directories usually don't belong in releases.  If an upstream release
+ tarball contains these directories, you should usually report this as a
+ bug upstream.
+
+Tag: diff-contains-git-control-dir
+Severity: normal
+Certainty: certain
+Info: The Debian diff or native package contains files in a .git
+ directory.  These are usually artifacts of the revision control system
+ used by the Debian maintainer and not useful in a diff or native package.
+ Passing <tt>-i</tt> to <tt>dpkg-buildpackage</tt> or the equivalent will
+ automatically exclude them.
+
+Tag: source-contains-git-control-dir
+Severity: pedantic
+Certainty: certain
+Info: The upstream source contains a .git directory. It was most likely
+ included by accident since git version control directories usually don't
+ belong in releases and may contain a complete copy of the repository.  If
+ an upstream release tarball contains .git directories, you should usually
+ report this as a bug upstream.
+
+Tag: diff-contains-hg-control-dir
+Severity: normal
+Certainty: certain
+Info: The Debian diff or native package contains files in a .hg
+ directory.  These are usually artifacts of the revision control system
+ used by the Debian maintainer and not useful in a diff or native package.
+ Passing <tt>-i</tt> to <tt>dpkg-buildpackage</tt> or the equivalent will
+ automatically exclude them.
+
+Tag: source-contains-hg-control-dir
+Severity: pedantic
+Certainty: certain
+Info: The upstream source contains a .hg directory. It was most likely
+ included by accident since hg version control directories usually don't
+ belong in releases and may contain a complete copy of the repository.  If
+ an upstream release tarball contains .hg directories, you should usually
+ report this as a bug upstream.
+
+Tag: diff-contains-bts-control-dir
+Severity: normal
+Certainty: certain
+Info: The Debian diff or native package contains files in a directory
+ used by a bug tracking system, which are not useful in a diff or native
+ package. Passing <tt>-i</tt> to <tt>dpkg-buildpackage</tt> or the
+ equivalent will automatically exclude them.
+
+Tag: source-contains-bts-control-dir
+Severity: pedantic
+Certainty: certain
+Info: The upstream source contains a directory used by a bug tracking
+ system. It was most likely included by accident since bug tracking system
+ directories usually don't belong in releases.
+
+Tag: diff-contains-svn-commit-file
+Severity: minor
+Certainty: certain
+Info: The Debian diff or native package contains an
+ <tt>svn-commit(.NNN).tmp</tt>, almost certainly a left-over from a failed
+ Subversion commit by the Debian package maintainer.
+
+Tag: source-contains-svn-commit-file
+Severity: pedantic
+Certainty: certain
+Info: The upstream source contains an <tt>svn-commit(.NNN).tmp</tt>,
+ almost certainly a left-over from a failed Subversion commit.  You may
+ want to report this as an upstream bug.
+
+Tag: diff-contains-svk-commit-file
+Severity: minor
+Certainty: certain
+Info: The Debian diff or native package contains an
+ <tt>svk-commitNNN.tmp</tt>, almost certainly a left-over from a failed
+ svk commit by the Debian package maintainer.
+
+Tag: source-contains-svk-commit-file
+Severity: pedantic
+Certainty: certain
+Info: The upstream source contains an <tt>svk-commitNNN.tmp</tt>,
+ almost certainly a left-over from a failed Subversion commit.  You may
+ want to report this as an upstream bug.
+Tag: diff-contains-arch-inventory-file
+Severity: normal
+Certainty: certain
+Info: The Debian diff or native package contains an
+ <tt>.arch-inventory</tt> file.  This is Arch metadata that should
+ normally not be distributed.
+
+Tag: source-contains-arch-inventory-file
+Severity: pedantic
+Certainty: certain
+Info: The upstream source contains an <tt>.arch-inventory</tt> file.  This
+ is Arch metadata that should normally not be distributed.  You may want
+ to report this as an upstream bug.
+
+Tag: diff-contains-hg-tags-file
+Severity: normal
+Certainty: certain
+Info: The Debian diff or native package contains an <tt>.htgags</tt>
+ file.  This file is Mercurial metadata that should normally not be
+ distributed.  It stores hashes of tagged commits in a Mercurial
+ repository and isn't therefore useful without the repository.
+
+Tag: source-contains-hg-tags-file
+Severity: pedantic
+Certainty: certain
+Info: The upstream source contains an <tt>.htgags</tt> file.  This file is
+ Mercurial metadata that should normally not be distributed.  It stores
+ hashes of tagged commits in a Mercurial repository and isn't therefore
+ useful without the repository.  You may want to report this as an
+ upstream bug.
+
+Tag: diff-contains-cvs-conflict-copy
+Severity: normal
+Certainty: certain
+info: The Debian diff or native package contains a CVS conflict copy.
+ These have file names like <tt>.#file.version</tt> and are generated by
+ CVS when a conflict was detected when merging local changes with updates
+ from a source repository.  They're useful only while resolving the
+ conflict and should not be included in the package.
+
+Tag: source-contains-cvs-conflict-copy
+Severity: pedantic
+Certainty: certain
+info: The upstream source contains a CVS conflict copy.  These have file
+ names like <tt>.#file.version</tt> and are generated by CVS when a
+ conflict was detected when merging local changes with updates from a
+ source repository.  They're useful only while resolving the conflict and
+ were probably included by accident.  You may want to report this as an
+ upstream bug.
+Tag: diff-contains-svn-conflict-file
+Severity: normal
+Certainty: certain
+info: The Debian diff or native package contains a file that looks like a
+ Subversion conflict file.  These are generated by Subversion when a
+ conflict was detected while merging local changes with updates from a
+ source repository.  Use <tt>svn resolved</tt> to remove them and clear
+ the Subversion conflict state after you have resolved the conflict.
+
+Tag: source-contains-svn-conflict-file
+Severity: pedantic
+Certainty: certain
+info: The upstream source contains a file that looks like a Subversion
+ conflict file.  These are generated by Subversion when a conflict was
+ detected while merging local changes with updates from a source
+ repository.  They're useful only while resolving the conflict and
+ were probably included by accident.  You may want to report this as an
+ upstream bug.
+
+Tag: diff-contains-patch-failure-file
+Severity: normal
+Certainty: possible
+Info: The Debian diff or native package contains a file that looks like
+ the files left behind by the <tt>patch</tt> utility when it cannot
+ completely apply a diff.  This may be left over from a patch applied by
+ the maintainer.  Normally such files should not be included in the
+ package.
+
+Tag: diff-contains-editor-backup-file
+Severity: minor
+Certainty: certain
+Info: The Debian diff or native package contains a file ending in
+ <tt>~</tt> or of the form <tt>.xxx.swp</tt>, which is normally either an
+ Emacs or vim backup file or a backup file created by programs such as
+ <tt>autoheader</tt> or <tt>debconf-updatepo</tt>.  This usually causes no
+ harm, but it's messy and bloats the size of the Debian diff to no useful
+ purpose.
+
+Tag: diff-contains-substvars
+Severity: normal
+Certainty: certain
+Info: Maemian found a substvars file in the Debian diff for this source 
+ package. The debian/substvars file is usually generated and modified
+ dynamically by debian/rules targets, in which case it must be removed by
+ the clean target.
+Ref: policy 4.9
+
+Tag: empty-debian-diff
+Severity: normal
+Certainty: possible
+Info: The Debian diff of this non-native package appears to be completely
+ empty.  This usually indicates a mistake when generating the upstream
+ tarball, or it may mean that this was intended to be a native package and
+ was built non-native by mistake.
+ .
+ If the Debian packaging is maintained in conjunction with upstream, this
+ may be intentional, but it's not recommended best practice.  If the
+ software is only for Debian, it should be a native package; otherwise,
+ it's better to omit the <tt>debian</tt> directory from upstream releases
+ and add it in the Debian diff.  Otherwise, it can cause problems for some
+ package updates in Debian (files can't be removed from the
+ <tt>debian</tt> directory via the diff, for example).
+
+Tag: configure-generated-file-in-source
+Severity: normal
+Certainty: possible
+Info: Leaving config.cache/status causes autobuilders problems.
+ config.cache and config.status are produced by GNU autoconf's configure
+ scripts. If they are left in the source package, autobuilders may pick
+ up settings for the wrong architecture.
+ .
+ The clean rule in <tt>debian/rules</tt> should remove this file. This
+ should ideally be done by fixing the upstream build system to do it when
+ you run the appropriate cleaning command (and don't forget to forward the
+ fix to the upstream authors so it doesn't happen in the next release). If
+ that is already implemented, then make sure you are indeed cleaning it in
+ the clean rule. If all else fails, a simple rm -f should work.
+ .
+ Note that Maemian cannot reliably detect the removal in the clean rule,
+ so once you fix this, please ignore or override this warning.
+
+Tag: ancient-autotools-helper-file
+Severity: important
+Certainty: possible
+Info: The referenced file has a time stamp older than year 2004 and the
+ package does not build-depend on autotools-dev or automake and therefore
+ apparently does not update it.  This usually means that the source
+ package will not build correctly on all currently released architectures.
+ .
+ Read /usr/share/doc/autotools-dev/README.Debian.gz (from the
+ autotools-dev package) for information on how to fix this problem.  cdbs
+ will automatically update these files if autotools-dev is installed
+ during build, but the build dependency on autotools-dev is still
+ necessary.
+
+Tag: outdated-autotools-helper-file
+Severity: normal
+Certainty: possible
+Info: The referenced file has a time stamp older than June of 2006 and the
+ package does not build-depend on autotools-dev or automake and therefore
+ apparently does not update it.  This usually means that the source
+ package will not build correctly on AVR32, for which a Debian port is
+ currently in progress, and may not support other newer architectures.
+ .
+ Read /usr/share/doc/autotools-dev/README.Debian.gz (from the
+ autotools-dev package) for information on how to fix this problem.  cdbs
+ will automatically update these files if autotools-dev is installed
+ during build, but the build dependency on autotools-dev is still
+ necessary.
+
+Tag: ancient-libtool
+Severity: normal
+Certainty: possible
+Info: The referenced file seems to be from a libtool version
+ older than 1.5.2-2.  This might lead to build errors on some
+ newer architectures not known to this libtool.
+ .
+ Please ask your upstream maintainer to re-libtoolize the
+ package or do it yourself in case there is no active upstream
+ anymore.  Beware that might mean you need to update autoconf, too,
+ if you use a very old version there as well.
+ .
+ If you have not updated the file but fixed architecture-specific
+ issues with minimal patches and verified that it builds correctly
+ please override this tag.  lintian will not be able to verify that.
+
+Tag: source-contains-prebuilt-binary
+Severity: pedantic
+Certainty: certain
+Info: The source tarball contains a prebuilt ELF object.  They are usually
+ left by mistake when generating the tarball by not cleaning the source
+ directory first.  You may want to report this as an upstream bug, in case
+ there is no sign that this was intended.
+
+Tag: source-contains-prebuilt-windows-binary
+Severity: pedantic
+Certainty: certain
+Info: The source tarball contains a prebuilt binary for Microsoft Windows.
+ They are usually left by convenience for end users, although it is possible
+ that upstream also provides tarballs source-only tarballs which you can use.
+ These files usually just take up space in the tarball and are of no use in
+ Debian.  You may want to ask upstream to provide source-only tarballs.
+
+Tag: tar-errors-from-source
+Severity: normal
+Certainty: wild-guess
+Info: tar produced an error while unpacking this source package.  This
+ probably means there's something broken or at least strange about the way
+ the upstream tar file was constructed.  You may want to report this as an
+ upstream bug.
diff --git a/checks/deb-format b/checks/deb-format
new file mode 100644 (file)
index 0000000..bb337de
--- /dev/null
@@ -0,0 +1,134 @@
+# deb-format -- lintian check script -*- perl -*-
+
+# Copyright (C) 2009 Russ Allbery
+#
+# 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 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, see <http://www.gnu.org/licenses/>.
+
+package Maemian::deb_format;
+use strict;
+use Tags;
+
+use Maemian::Command qw(spawn);
+
+# The files that contain error messages from tar, which we'll check and issue
+# tags for if they contain something unexpected, and their corresponding tags.
+our %ERRORS = ('control-errors'       => 'tar-errors-from-control',
+               'control-index-errors' => 'tar-errors-from-control',
+               'index-errors'         => 'tar-errors-from-data',
+               'unpacked-errors'      => 'tar-errors-from-data');
+
+# Used to match Ubuntu distribution names in target distributions.
+our $UBUNTU_REGEX;
+{
+    my $dists = Maemian::Data->new('changelog-file/ubuntu-dists');
+    my $string = join ('|', 'ubuntu', $dists->all);
+    $UBUNTU_REGEX = qr/$string/o;
+}
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+# Run ar t on the *.deb file.  deb will be a symlink to it.
+my $okay = 0;
+my $opts = {};
+my $success = spawn($opts, ['ar', 't', 'deb']);
+if ($success) {
+    my @members = split("\n", ${ $opts->{out} });
+    if (@members != 3) {
+        my $count = scalar(@members);
+        tag 'malformed-deb-archive',
+            "found $count members instead of 3";
+    } elsif ($members[0] ne 'debian-binary') {
+        tag 'malformed-deb-archive',
+            "first member $members[0] not debian-binary";
+    } elsif ($members[1] ne 'control.tar.gz') {
+        tag 'malformed-deb-archive',
+            "second member $members[1] not control.tar.gz";
+    } elsif ($members[2] eq 'data.tar.lzma') {
+        # Ubuntu's archive allows lzma packages.
+        my ($entry) = $info->changelog->data;
+        my $distribution = $entry->Distribution;
+        if ($distribution =~ /$UBUNTU_REGEX/) {
+            $okay = 1;
+        } else {
+            tag 'lzma-deb-archive';
+        }
+    } elsif ($members[2] !~ /^data\.tar\.(gz|bz2)\z/) {
+        tag 'malformed-deb-archive',
+            "third member $members[2] not data.tar.(gz|bz2)";
+    } else {
+        $okay = 1;
+    }
+} else {
+    # unpack will probably fail so we'll never get here, but may as well be
+    # complete just in case.
+    my $error = ${ $opts->{err} };
+    $error =~ s/\n.*//s;
+    $error =~ s/^ar:\s*//;
+    $error =~ s/^deb:\s*//;
+    tag 'malformed-deb-archive', "ar error: $error";
+}
+
+# Check the debian-binary version number.  We probably won't get here because
+# dpkg-deb will decline to unpack the deb, but be thorough just in case.  We
+# may eventually have a case where dpkg supports a newer format but it's not
+# permitted in the archive yet.
+if ($okay) {
+    my $opts = {};
+    my $success = spawn($opts, ['ar', 'p', 'deb', 'debian-binary']);
+    if (not $success) {
+        tag 'malformed-deb-archive', "can't read debian-binary member";
+    } elsif (${ $opts->{out} } !~ /^2\.\d+\n/) {
+        my ($version) = split("\n", ${ $opts->{out} });
+        tag 'malformed-deb-archive', "version $version not 2.0";
+    }
+}
+
+# If either control-errors or index-errors exist, tar produced error output
+# when processing the package.  We want to report those as tags unless they're
+# just tar noise that doesn't represent an actual problem.
+for my $file (keys %ERRORS) {
+    my $tag = $ERRORS{$file};
+    if (-s $file) {
+        open(ERRORS, '<', $file) or fail("cannot open $file: $!");
+        local $_;
+        while (<ERRORS>) {
+            chomp;
+            s,^(?:[/\w]+/)?tar: ,,;
+
+            # Record size errors are harmless.  Ignore implausibly old
+            # timestamps in the data section since we already check for that
+            # elsewhere, but still warn for control.
+            next if /^Record size =/;
+            if ($tag eq 'tar-errors-from-data') {
+                next if /implausibly old time stamp/;
+            }
+            tag $tag, $_;
+        }
+        close ERRORS;
+    }
+}
+
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround
diff --git a/checks/deb-format.desc b/checks/deb-format.desc
new file mode 100644 (file)
index 0000000..dd0ad04
--- /dev/null
@@ -0,0 +1,39 @@
+Check-Script: deb-format
+Author: Russ Allbery <rra@debian.org>
+Abbrev: dfmt
+Type: binary, udeb
+Unpack-Level: 2
+Info: This script checks the format of the deb ar archive itself.
+
+Tag: malformed-deb-archive
+Severity: serious
+Certainty: certain
+Info: The binary package is not a correctly constructed archive.  A binary
+ Debian package must be an ar archive with exactly three members:
+ <tt>debian-binary</tt>, <tt>control.tar.gz</tt>, and
+ <tt>data.tar.gz</tt> or <tt>data.tar.bz2</tt> in exactly that order.  The
+ <tt>debian-binary</tt> member must start with a single line containing
+ the version number, with a major revision of 2.
+Ref: deb(5)
+
+Tag: lzma-deb-archive
+Severity: serious
+Certainty: certain
+Info: The data portion of this binary package is compressed with lzma.
+ This is supported by dpkg but not yet permitted in the Debian archive.
+ Such a package will be rejected by DAK.
+
+Tag: tar-errors-from-control
+Severity: important
+Certainty: possible
+Info: tar produced an error while listing the contents of the
+ <tt>control.tar.gz</tt> member of this package.  This probably means
+ there's something broken or at least strange about the way the package
+ was constructed.
+
+Tag: tar-errors-from-data
+Severity: important
+Certainty: possible
+Info: tar produced an error while listing the contents of the data
+ member of this package.  This probably means there's something broken or
+ at least strange about the way the package was constructed.
diff --git a/checks/debconf b/checks/debconf
new file mode 100644 (file)
index 0000000..3ec145e
--- /dev/null
@@ -0,0 +1,555 @@
+# debconf -- lintian check script -*- perl -*-
+
+# Copyright (C) 2001 Colin Watson
+#
+# 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 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.
+
+package Maemian::debconf;
+use strict;
+use Tags;
+
+use Maemian::Relation;
+use Util;
+
+# From debconf-devel(7), section 'THE TEMPLATES FILE', up to date with debconf
+# version 1.5.24.  Added indices for cdebconf (indicates sort order for
+# choices); debconf doesn't support it, but it ignores it, which is safe
+# behavior.
+my %template_fields = map { $_ => 1 }
+    qw(template type choices indices default description);
+
+# From debconf-devel(7), section 'THE TEMPLATES FILE', up to date with debconf
+# version 1.5.24.
+my %valid_types = map { $_ => 1 }
+    qw(string
+       password
+       boolean
+       select
+       multiselect
+       note
+       error
+       title
+       text);
+
+# From debconf-devel(7), section 'THE DEBCONF PROTOCOL' under 'INPUT', up to
+# date with debconf version 1.5.24.
+my %valid_priorities = map { $_ => 1 }
+    qw(low medium high critical);
+
+# All the packages that provide debconf functionality.  Anything using debconf
+# needs to have dependencies that satisfy one of these.
+my @debconfs = qw(debconf debconf-2.0 cdebconf cdebconf-udeb libdebconfclient0
+                  libdebconfclient0-udeb);
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+my $seenconfig='';
+my $seentemplates='';
+my $usespreinst='';
+my $usesmultiselect='';
+
+if ($type eq 'source') {
+    my $binaries = $info->field('binary');
+    chomp $binaries;
+    my @files = map { "$_.templates" } split /,\s+/, $binaries;
+    push @files, "templates";
+
+    foreach my $file (@files) {
+       my $templates_file = "debfiles/$file";
+       my $binary = $file;
+       $binary =~ s/\.?templates$//;
+       # Single binary package (so @files contains "templates" and
+       # "binary.templates")?
+       if (!$binary and $#files == 1) {
+           $binary = $binaries;
+       }
+
+       if (-f $templates_file) {
+           my @templates = read_dpkg_control($templates_file, "templates file");
+
+           foreach my $template (@templates) {
+               if (exists $template->{template} and exists $template->{_choices}) {
+                   tag "template-uses-unsplit-choices",
+                       "$binary - $template->{template}";
+               }
+           }
+       }
+    }
+
+    # The remainder of the checks are for binary packages, so we exit now
+    return 0;
+}
+
+if (open(PREINST, '<', "control/preinst")) {
+    while (<PREINST>) {
+       s/\#.*//;    # Not perfect for Perl, but should be OK
+       if (m,/usr/share/debconf/confmodule, or
+               m/(?:Debconf|Debian::DebConf)::Client::ConfModule/) {
+           $usespreinst=1;
+           last;
+       }
+    }
+    close PREINST;
+}
+
+if (-f "control/config") {
+    $seenconfig=1;
+}
+if (-f "control/templates") {
+    $seentemplates=1;
+}
+
+# This still misses packages that use debconf only in the postrm.  Packages
+# that ask debconf questions in the postrm should load the confmodule in the
+# postinst so that debconf can register their templates.
+return unless $seenconfig or $seentemplates or $usespreinst;
+
+# parse depends info for later checks
+
+# Consider every package to depend on itself.
+my $version;
+if (defined $info->field('version')) {
+    $_ = $info->field('version');
+    $version = "$pkg (= $_)";
+}
+
+my (%dependencies, @alldeps);
+
+for my $field (qw(depends pre-depends)) {
+    if (defined $info->field($field)) {
+       $_ = $info->field($field);
+       $_ .= ", $version" if defined $version;
+       push @alldeps, $_;
+       $dependencies{$field} = Maemian::Relation->new($_);
+    } else {
+       push @alldeps, $version;
+       $dependencies{$field} = Maemian::Relation->new($version);
+    }
+}
+
+my $alldependencies = Maemian::Relation->new(join ', ', @alldeps);
+
+# See if the package depends on dbconfig-common.  Packages that do are allowed
+# to have a config file with no templates, since they use the dbconfig-common
+# templates.
+my $usesdbconfig = $alldependencies->implies('dbconfig-common');
+
+# Check that both debconf control area files are present.
+if ($seenconfig and not $seentemplates and not $usesdbconfig) {
+    tag "no-debconf-templates", "";
+} elsif ($seentemplates and not $seenconfig and not $usespreinst and $type ne 'udeb') {
+    tag "no-debconf-config", "";
+}
+
+if ($seenconfig and not -x "control/config") {
+    tag "debconf-config-not-executable", "";
+}
+
+# First check that templates look valid
+if ($seentemplates) {
+    open(TMPL, '<', "control/templates")
+        or fail("Can't open control/templates: $!");
+    local $/ = "\n\n";
+    while (<TMPL>) {
+       chomp;
+       my %fields = ();
+       my $name = 'unknown';
+
+       foreach my $line (split "\n", $_) {
+           if ($line =~ s/^([-_.A-Za-z0-9]+):\s*(.+)//) {
+               $fields{$1}++;
+               $name = $2 if ($1 eq 'Template');
+           }
+       }
+
+       foreach (keys %fields) {
+           if ($fields{$_} > 1) {
+               local $/ = "\n";
+               tag "duplicate-fields-in-templates", "$name $_";
+               #  Templates file is corrupted, no need to report
+               #  further errors
+               $seentemplates = '';
+           }
+       }
+    }
+    close TMPL;
+}
+
+# Lots of template checks.
+
+my @templates = $seentemplates ? read_dpkg_control("control/templates", "templates file") : ();
+my %potential_db_abuse;
+my @templates_seen;
+
+foreach my $template (@templates) {
+    my $isselect='';
+
+    if (not exists $template->{template}) {
+       tag "no-template-name", "";
+       $template->{template} = 'no-template-name';
+    } else {
+       push @templates_seen, $template->{template};
+       if ($template->{template}!~m|[A-Za-z0-9.+-](?:/[A-Za-z0-9.+-])|) {
+           tag "malformed-template-name", "$template->{template}";
+       }
+    }
+
+    if (not exists $template->{type}) {
+       tag "no-template-type", "$template->{template}";
+    } elsif (not $valid_types{$template->{type}}) {
+       tag "unknown-template-type", "$template->{type}";
+    } elsif ($template->{type} eq 'select') {
+       $isselect=1;
+    } elsif ($template->{type} eq 'multiselect') {
+       $isselect=1;
+       $usesmultiselect=1;
+    } elsif ($template->{type} eq 'boolean') {
+       tag "boolean-template-has-bogus-default",
+           "$template->{template} $template->{default}"
+               if defined $template->{default}
+                   and $template->{default} ne 'true'
+                   and $template->{default} ne 'false';
+    }
+
+    if ($template->{choices} && ($template->{choices} !~ /^\s*$/)) {
+       my $nrchoices = count_choices ($template->{choices});
+       for my $key (keys %$template) {
+           if ($key =~ /^choices-/) {
+               if (! $template->{$key} || ($template->{$key} =~ /^\s*$/)) {
+                   tag "empty-translated-choices", "$template->{template} $key";
+               }
+               if (count_choices ($template->{$key}) != $nrchoices) {
+                   tag "mismatch-translated-choices", "$template->{template} $key";
+               }
+           }
+       }
+       if ($template->{choices} =~ /^\s*(yes\s*,\s*no|no\s*,\s*yes)\s*$/i) {
+           tag "select-with-boolean-choices", "$template->{template}";
+       }
+    }
+
+    if ($isselect and not exists $template->{choices}) {
+       tag "select-without-choices", "$template->{template}";
+    }
+
+    if (not exists $template->{description}) {
+       tag "no-template-description", "$template->{template}";
+    } elsif ($template->{description}=~m/^\s*(.*?)\s*?\n\s*\1\s*$/) {
+       # Check for duplication. Should all this be folded into the
+       # description checks?
+       tag "duplicate-long-description-in-template",
+             "$template->{template}";
+    }
+
+    my %languages;
+    foreach my $field (sort keys %$template) {
+       # Tests on translations
+       my ($mainfield, $lang) = split m/-/, $field, 2;
+       if (defined $lang) {
+           $languages{$lang}{$mainfield}=1;
+       }
+       unless ($template_fields{$mainfield}) { # Ignore language codes here
+           tag "unknown-field-in-templates", "$template->{template} $field";
+       }
+    }
+
+    if ($template->{template} && $template->{type}) {
+        $potential_db_abuse{$template->{template}} = 1
+            if (($template->{type} eq "note") or ($template->{type} eq "text"));
+    }
+
+    # Check the description against the best practices in the Developer's
+    # Reference, but skip all templates where the short description contains
+    # the string "for internal use".
+    my ($short, $extended);
+    if (defined $template->{description}) {
+        $template->{description} =~ m/^([^\n]*)\n(.*)$/s;
+        ($short, $extended) = ($1, $2);
+        unless (defined $short) {
+            $short = $template->{description};
+        }
+    } else {
+        ($short, $extended) = ('', '');
+    }
+    my $type = $template->{type} || '';
+    unless ($short =~ /for internal use/i) {
+       my $isprompt = grep { $_ eq $type } qw(string password);
+        my $isselect = grep { $_ eq $type } qw(select multiselect);
+       if ($isprompt) {
+           if ($short && ($short !~ m/:$/ || $short =~ m/^(what|who|when|where|which|how)/i)) {
+               tag "malformed-prompt-in-templates", $template->{template};
+           }
+        }
+        if ($isselect) {
+           if ($short =~ /^(Please|Cho+se|Enter|Select|Specify|Give)/) {
+               tag "using-imperative-form-in-templates", $template->{template};
+           }
+       }
+       if ($type eq 'boolean') {
+           if ($short !~ /\?/) {
+               tag "malformed-question-in-templates", $template->{template};
+           }
+       }
+       if (defined ($extended) && $extended =~ /[^\?]\?(\s+|$)/) {
+           tag "using-question-in-extended-description-in-templates", $template->{template};
+       }
+       if ($type eq 'note') {
+           if ($short =~ /[.?;:]$/) {
+               tag "malformed-title-in-templates", $template->{template};
+           }
+       }
+       if (length ($short) > 75) {
+           tag "too-long-short-description-in-templates", $template->{template};
+       }
+        if (defined $template->{description}) {
+            if ($template->{description} =~ /(\A|\s)(I|[Mm]y|[Ww]e|[Oo]ur|[Oo]urs|mine|myself|ourself|me|us)(\Z|\s)/) {
+                tag "using-first-person-in-templates", $template->{template};
+            }
+            if ($template->{description} =~ /[ \'\"]yes[ \'\",;.]/i and $type eq 'boolean') {
+                tag "making-assumptions-about-interfaces-in-templates", $template->{template};
+            }
+        }
+
+       # Check whether the extended description is too long.
+       if ($extended) {
+           my $lines = 0;
+           for my $string (split ("\n", $extended)) {
+               while (length ($string) > 80) {
+                   my $pos = rindex ($string, ' ', 80);
+                   if ($pos == -1) {
+                       $pos = index ($string, ' ');
+                   }
+                   if ($pos == -1) {
+                       $string = '';
+                   } else {
+                       $string = substr ($string, $pos + 1);
+                       $lines++;
+                   }
+               }
+               $lines++;
+           }
+           if ($lines > 20) {
+               tag "too-long-extended-description-in-templates", $template->{template};
+           }
+       }
+    }
+}
+
+# Check the maintainer scripts.
+
+my $config_calls_db_input;
+my $db_purge;
+my %templates_used;
+my %template_aliases;
+for my $file (qw(config prerm postrm preinst postinst)) {
+    my $potential_makedev = {};
+    if (open(IN, '<', "control/$file")) {
+       my $usesconfmodule='';
+       my $obsoleteconfmodule='';
+       my $db_input='';
+       my $isdefault='';
+       my $usesseen='';
+
+       # Only check scripts.
+       my $fl = <IN>;
+       unless ($fl && $fl =~ /^\#!/) {
+           close IN;
+           next;
+       }
+
+       while (<IN>) {
+           s/#.*//;    # Not perfect for Perl, but should be OK
+           next unless m/\S/;
+           while (s%\\$%%) {
+               my $next = <IN>;
+               last unless $next;
+               $_ .= $next;
+           }
+           if (m,(?:\.|source)\s+/usr/share/debconf/confmodule, ||
+                   m/(use|require)\s+Debconf::Client::ConfModule/) {
+               $usesconfmodule=1;
+           }
+           if (not $obsoleteconfmodule and
+               m,(/usr/share/debconf/confmodule\.sh|
+                  Debian::DebConf::Client::ConfModule),x) {
+               tag "loads-obsolete-confmodule", "$file:$. $1";
+               $usesconfmodule=1;
+               $obsoleteconfmodule=1;
+           }
+           if ($file eq 'config' and m/db_input/) {
+               $config_calls_db_input = 1;
+           }
+           if ($file eq 'postinst' and not $db_input and m/db_input/
+               and not $config_calls_db_input) {
+               # TODO: Perl?
+               tag "postinst-uses-db-input", ""
+                   unless $type eq 'udeb';
+               $db_input=1;
+           }
+           if (m%/dev/%) {
+               $potential_makedev->{$.} = 1;
+           }
+           if (m/^\s*(?:db_input|db_text)\s+[\"\']?(\S+?)[\"\']?\s+(\S+)\s/) {
+               my ($priority, $template) = ($1, $2);
+               $templates_used{$template} = 1;
+               if ($priority !~ /^\$\S+$/) {
+                   tag "unknown-debconf-priority", "$file:$. $1"
+                       unless ($valid_priorities{$priority});
+                   tag "possible-debconf-note-abuse", "$file:$. $template"
+                       if ($potential_db_abuse{$template}
+                           and (not ($potential_makedev->{($. - 1)} and ($priority eq "low")))
+                           and ($priority =~ /^(low|medium)$/));
+               }
+           }
+           if (m/^\s*(?:db_get|db_set(?:title)?)\s+[\"\']?(\S+?)[\"\']?(\s|\Z)/) {
+               $templates_used{$1} = 1;
+           }
+           # Try to handle Perl somewhat.
+           if (m/^\s*(?:.*=\s*get|set)\s*\(\s*[\"\'](\S+?)[\"\']/) {
+               $templates_used{$1} = 1;
+           }
+           if (m/^\s*db_register\s+[\"\']?(\S+?)[\"\']?\s+(\S+)\s/) {
+               my ($template, $question) = ($1, $2);
+               push @{$template_aliases{$template}}, $question;
+           }
+           if (not $isdefault and m/db_fset.*isdefault/) {
+               # TODO: Perl?
+               tag "isdefault-flag-is-deprecated", "$file";
+               $isdefault=1;
+           }
+           if (not $db_purge and m/db_purge/) {    # TODO: Perl?
+               $db_purge=1;
+           }
+       }
+
+       if ($file eq 'postinst' or $file eq 'config') {
+           unless ($usesconfmodule) {
+               tag "$file-does-not-load-confmodule", ""
+                   unless ($type eq 'udeb' || ($file eq 'postinst' && !$seenconfig));
+           }
+       }
+
+       if ($file eq 'postrm') {
+           unless ($db_purge) {
+               tag "postrm-does-not-purge-debconf", "";
+           }
+       }
+
+       close IN;
+    } elsif ($file eq 'postinst') {
+       tag "$file-does-not-load-confmodule", ""
+           unless ($type eq 'udeb' || !$seenconfig);
+    } elsif ($file eq 'postrm') {
+       tag "postrm-does-not-purge-debconf", ""
+           unless ($type eq 'udeb');
+    }
+}
+
+foreach my $template (@templates_seen) {
+    $template =~ s/\s+\Z//;
+
+    my $used = 0;
+
+    if ($templates_used{$template}) {
+       $used = 1;
+    } else {
+       foreach my $alias (@{$template_aliases{$template}}) {
+           if ($templates_used{$alias}) {
+               $used = 1;
+               last;
+           }
+       }
+    }
+
+    unless ($used or $pkg eq "debconf" or $type eq 'udeb') {
+       tag "unused-debconf-template", $template
+           unless $template =~ m,^shared/packages-(wordlist|ispell)$,
+               or $template =~ m,/languages$,;
+    }
+}
+
+# Check that the right dependencies are in the control file.  Accept any
+# package that might provide debconf functionality.
+
+my $anydebconf = join (' | ', @debconfs);
+if ($usespreinst) {
+    unless ($dependencies{'pre-depends'}->implies($anydebconf)) {
+       tag "missing-debconf-dependency-for-preinst", ""
+           unless $type eq 'udeb';
+    }
+} else {
+    unless ($alldependencies->implies($anydebconf) or $usesdbconfig) {
+       tag "missing-debconf-dependency", "";
+    }
+}
+
+# Now make sure that no scripts are using debconf as a registry.
+# Unfortunately this requires us to unpack to level 2 and grep all the
+# scripts in the package.
+# the following checks is ignored if the package being checked is debconf
+# itself.
+
+return 0 if ($pkg eq "debconf") || ($type eq 'udeb');
+
+foreach my $filename (sort keys %{$info->scripts}) {
+    open(IN, '<', "unpacked/$filename") or fail("cannot open $filename: $!");
+    while (<IN>) {
+       s/#.*//;    # Not perfect for Perl, but should be OK
+       if (m,/usr/share/debconf/confmodule, or
+               m/(?:Debconf|Debian::DebConf)::Client::ConfModule/) {
+           tag "debconf-is-not-a-registry", "$filename";
+           last;
+       }
+    }
+    close IN;
+}
+
+} # </run>
+
+# -----------------------------------
+
+# Count the number of choices. Splitting code copied from debconf 1.5.8
+# (Debconf::Question).
+sub count_choices {
+    my ($choices) = @_;
+    my @items;
+    my $item = '';
+    for my $chunk (split /(\\[, ]|,\s+)/, $choices) {
+       if ($chunk =~ /^\\([, ])$/) {
+           $item .= $1;
+       } elsif ($chunk =~ /^,\s+$/) {
+           push (@items, $item);
+           $item = '';
+       } else {
+           $item .= $chunk;
+       }
+    }
+    push (@items, $item) if $item ne '';
+    return scalar (@items);
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl ts=8
diff --git a/checks/debconf.desc b/checks/debconf.desc
new file mode 100644 (file)
index 0000000..d4763f9
--- /dev/null
@@ -0,0 +1,355 @@
+Check-Script: debconf
+Author: Colin Watson <cjwatson@debian.org>
+Abbrev: dc
+Type: binary, udeb, source
+Unpack-Level: 2
+Info: This looks for common mistakes in packages using debconf.
+Needs-Info: scripts
+
+Tag: missing-debconf-dependency
+Severity: normal
+Certainty: possible
+Info: Packages using debconf should depend on it, since debconf is not an
+ essential package.
+
+Tag: missing-debconf-dependency-for-preinst
+Severity: normal
+Certainty: possible
+Info: Packages using debconf in their preinst script must pre-depend
+ on debconf, with appropriate discussion on &debdev;.  Since debconf is
+ almost always already installed, this is normally better than depending
+ on it and falling back to a different configuration system.
+Tag: duplicate-fields-in-templates
+Severity: important
+Certainty: certain
+Info: A template contains two identical fields (with identical values or
+ not). This is either an error in this template, or two templates are
+ not properly separated by a lone newline.
+
+Tag: unknown-field-in-templates
+Severity: important
+Certainty: certain
+Info: Valid fields are currently "Template:", "Type:", "Choices:", "Default:",
+ and "Description:".
+Ref: debconf-spec aen45, debconf-devel(7)
+
+Tag: no-template-name
+Severity: important
+Certainty: certain
+Info: The templates file contains a template without a "Template:" field.
+
+Tag: malformed-template-name
+Severity: important
+Certainty: certain
+Info: The "Template:" field should contain more than one component, each
+ separated by a slash ("/"). Each component may only consist of the
+ alphanumeric characters, "+", "-", and ".".
+
+Tag: no-template-type
+Severity: important
+Certainty: certain
+Info: The templates file contains a template without a "Type:" field.
+
+Tag: unknown-template-type
+Severity: important
+Certainty: certain
+Info: A "Type:" field in a templates file provided by this package uses an
+ unknown data type. Valid types are currently "string", "boolean", "select",
+ "multiselect", "note", "text", and "password".
+
+Tag: empty-translated-choices
+Severity: important
+Certainty: possible
+Info: When the translation of a Choices: field is empty, the whole question
+ is skipped (and nothing is selected). Please verify that the translation
+ you're using is valid.
+
+Tag: mismatch-translated-choices
+Severity: important
+Certainty: certain
+Info: A "Choices:" field is a comma separated list, and translated
+ "Choices:" fields must have the exact same number of elements.  One
+ of the translations does not follow this rule, you should contact the
+ translator and request for a new translation where elements of "Choices:"
+ fields have no embedded commas.
+ .
+ Cdebconf understands escaped commas in such fields, but packages
+ outside the scope of debian-installer must not have them until they are
+ also supported by debconf.
+
+Tag: select-without-choices
+Severity: important
+Certainty: certain
+Info: Templates using the "select" or "multiselect" data types must provide
+ a "Choices:" field listing the possible values of the template.
+Ref: debconf-spec aen45, debconf-devel(7)
+
+Tag: boolean-template-has-bogus-default
+Severity: important
+Certainty: certain
+Info: The "boolean" type in a debconf template, can have only two values: true
+ and false. The default has been set to something different.
+Ref: debconf-spec aen45, debconf-devel(7)
+
+Tag: no-template-description
+Severity: important
+Certainty: certain
+Info: The templates file contains a template without a "Description:" field.
+Ref: debconf-spec aen45, debconf-devel(7)
+
+Tag: duplicate-long-description-in-template
+Severity: minor
+Certainty: certain
+Info: The long description of one of the templates provided by this package
+ is a duplicate of the short description. If you cannot provide a good
+ extended description, it is better to leave it blank.
+
+Tag: config-does-not-load-confmodule
+Severity: normal
+Certainty: certain
+Info: The config script must load one of the debconf libraries.
+
+Tag: postinst-uses-db-input
+Severity: normal
+Certainty: certain
+Info: It is generally not a good idea for postinst scripts to use debconf
+ commands like <tt>db_input</tt>. Typically, they should restrict themselves
+ to <tt>db_get</tt> to request previously acquired information, and have the
+ config script do the actual prompting.
+
+Tag: postinst-does-not-load-confmodule
+Severity: normal
+Certainty: certain
+Info: Even if your postinst does not involve debconf, you currently need to
+ make sure it loads one of the debconf libraries. This will be changed in
+ the future.
+
+Tag: loads-obsolete-confmodule
+Severity: normal
+Certainty: certain
+Info: The maintainer script uses an obsolete name for a debconf confmodule.
+ Shell scripts should source <tt>/usr/share/debconf/confmodule</tt>, while
+ Perl scripts should use <tt>Debconf::Client::ConfModule</tt>.
+Ref: debconf-devel(7)
+
+Tag: postrm-does-not-purge-debconf
+Severity: normal
+Certainty: certain
+Info: Packages using debconf should call <tt>db_purge</tt> or its equivalent
+ in their postrm. If the package uses debhelper, dh_installdebconf(1) should
+ take care of this.
+
+Tag: isdefault-flag-is-deprecated
+Severity: normal
+Certainty: possible
+Info: The "isdefault" flag on debconf questions is deprecated as of debconf
+ 0.5.00, and has been replaced by "seen" with the inverse meaning. From
+ debconf 0.5 onwards there should be very few reasons to use isdefault/seen
+ anyway, as backing up works much better now. See
+ /usr/share/doc/debconf-doc/changelog.gz for more information.
+ .
+ The misuse of isdefault often leads to questions being asked twice in one
+ installation run, or, worse, on every upgrade. Please test your package
+ carefully to make sure this does not happen.
+
+Tag: debconf-config-not-executable
+Severity: important
+Certainty: certain
+Info: The debconf "config" script in the package control area must be
+ executable.
+
+Tag: no-debconf-config
+Severity: important
+Certainty: possible
+Info: The package contains a "templates" file in its control area but has no
+ corresponding "config" script. This is occasionally OK, but is usually an
+ error.
+
+Tag: no-debconf-templates
+Severity: normal
+Certainty: possible
+Info: The package contains a "config" script in its control area but has no
+ corresponding "templates" file. This is occasionally OK, but is usually an
+ error.
+
+Tag: debconf-is-not-a-registry
+Severity: important
+Certainty: wild-guess
+Info: In the Unix tradition, Debian packages should have human-readable and
+ human-editable configuration files.  This package uses debconf commands
+ outside its maintainer scripts, which often indicates that it is taking
+ configuration information directly from the debconf database.  Typically,
+ packages should use debconf-supplied information to generate
+ configuration files, and -- to avoid losing configuration information on
+ upgrades -- should parse these configuration files in the <tt>config</tt>
+ script if it is necessary to ask the user for changes.
+ .
+ Some standalone programs may legitimately use debconf to prompt the user
+ for questions.  If you maintain a package containing such a program,
+ please install an override.  Other exceptions to this check include
+ configuration scripts called from the package's post-installation script.
+Ref: devref 6.5.1, debconf-devel(7)
+
+Tag: malformed-prompt-in-templates
+Severity: normal
+Certainty: certain
+Info: The short description of a select, multiselect, string and password
+ debconf template is a prompt and not a title. Avoid question style
+ prompts ("IP Address?") in favour of "opened" prompts ("IP address:").
+ The use of colons is recommended.
+ .
+ If this template is only used internally by the package and not displayed
+ to the user, put "for internal use" in the short description.
+Ref: devref 6.5.4.2
+
+Tag: malformed-title-in-templates
+Severity: normal
+Certainty: certain
+Info: The short description of a note debconf template should be written
+ as a title and therefore should not end with a period, question mark,
+ colon, or semicolon.
+Ref: devref 6.5.4.2.4
+
+Tag: malformed-question-in-templates
+Severity: normal
+Certainty: certain
+Info: The short description of a boolean debconf template should be
+ phrased in the form of a question which should be kept short and should
+ generally end with a question mark. Terse writing style is permitted and
+ even encouraged if the question is rather long.
+ .
+ If this template is only used internally by the package and not displayed
+ to the user, put "for internal use" in the short description.
+Ref: devref 6.5.4.2.2
+
+Tag: using-question-in-extended-description-in-templates
+Severity: normal
+Certainty: certain
+Info: The extended description of a debconf template should never include
+ a question.
+ .
+ If this template is only used internally by the package and not displayed
+ to the user, put "for internal use" in the short description.
+Ref: devref 6.5.3.2
+
+Tag: using-imperative-form-in-templates
+Severity: normal
+Certainty: certain
+Info: Do not use useless imperative constructions such as "Please choose...",
+ "Enter...". The interface will make it obvious that the user needs to
+ choose or enter something.
+Ref: devref 6.5.4.2
+
+Tag: using-first-person-in-templates
+Severity: normal
+Certainty: possible
+Info: You should avoid the use of first person ("I will do this..." or
+ "We recommend..."). The computer is not a person and the Debconf
+ templates do not speak for the Debian developers. You should use neutral
+ construction and often the passive form.
+ .
+ If this template is only used internally by the package and not displayed
+ to the user, put "for internal use" in the short description.
+Ref: devref 6.5.2.5
+
+Tag: making-assumptions-about-interfaces-in-templates
+Severity: normal
+Certainty: possible
+Info: Template text should not make reference to widgets belonging to
+ some debconf interfaces. Sentences like "If you answer Yes..." have no
+ meaning for users of graphical interfaces which use checkboxes for
+ boolean questions.
+Ref: devref 6.5.2.4
+
+Tag: too-long-short-description-in-templates
+Severity: normal
+Certainty: certain
+Info: The short description should be kept short (50 characters or so) so
+ that it may be accommodated by most debconf interfaces. Keeping it short
+ also helps translators, as usually translations tend to end up being
+ longer than the original.
+Ref: devref 6.5.3.2
+
+Tag: too-long-extended-description-in-templates
+Severity: normal
+Certainty: certain
+Info: Some debconf interfaces cannot deal very well with descriptions of
+ more than about 20 lines, so try to keep the extended description below
+ this limit.
+Ref: devref 6.5.3.2
+
+Tag: unknown-debconf-priority
+Severity: important
+Certainty: certain
+Info: The given maintainer script calls db_input or or db_text with a
+ first argument that doesn't match one of the known priorities.  The
+ supported priorities are low, medium, high, and critical.
+Ref: debconf-devel(7)
+
+Tag: possible-debconf-note-abuse
+Severity: normal
+Certainty: possible
+Info: Debconf notes should be used only for important notes that the
+ user really should see, since debconf will go to great pains to make
+ sure the user sees it.
+ .
+ Displaying a note with a low priority is conflicting with this statement,
+ since using a low or medium priority shows that the note is not
+ important.
+ .
+ The right fix is NOT to increase the priority of the note, but to move
+ it somewhere else in the inline documentation, for example in a
+ README.Debian file for notes about package usability or NEWS.Debian for
+ changes in the package behavior, or to simply drop it if it is not
+ needed (e.g. "welcome" notes). Changing the templates type to "error"
+ can also be appropriate, such as for input validation errors.
+Ref: policy 3.9.1
+
+Tag: select-with-boolean-choices
+Severity: normal
+Certainty: possible
+Info: Select templates with only yes and no choices should use the boolean
+ type instead.
+Ref: debconf-devel(7)
+
+Tag: template-uses-unsplit-choices
+Severity: normal
+Certainty: possible
+Experimental: yes
+Info: The use of _Choices in templates is deprecated.
+ An _Choices field must be translated as a single string.
+ .
+ Using __Choices allows each choice to be translated separately, easing
+ translation and is therefore recommended.
+ .
+ Instead of simply replacing all occurrences of "_Choices" by "__Choices",
+ apply the method described in po-debconf(7) under "SPLITTING CHOICES
+ LIST", to avoid breaking existing translations.
+ .
+ If in doubt, please ask for help on the debian-i18n mailing list.
+Ref: po-debconf(7)
+
+Tag: unused-debconf-template
+Severity: minor
+Certainty: possible
+Info: Templates which are not used by the package should be removed from
+ the templates file.
+ .
+ This will reduce the size of the templates database and prevent
+ translators from unnecessarily translating the template's text.
+ .
+ In some cases, the template is used but Maemian is unable to determine
+ this.  Common causes are:
+ .
+ - the maintainer scripts embed a variable in the template name in
+ order to allow a template to be selected from a range of similar
+ templates (e.g. <tt>db_input low start_$service_at_boot</tt>)
+ .
+ - the template is not used by the maintainer scripts but is used by
+ a program in the package
+ .
+ - the maintainer scripts are written in perl.  lintian currently only
+ understands the shell script debconf functions.
+ .
+ If any of the above apply, please install an override.
diff --git a/checks/debhelper b/checks/debhelper
new file mode 100644 (file)
index 0000000..fa46f28
--- /dev/null
@@ -0,0 +1,322 @@
+# debhelper format -- lintian check script -*- perl -*-
+
+# Copyright (C) 1999 by Joey Hess
+#
+# 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 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.
+
+package Maemian::debhelper;
+use strict;
+
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Tags;
+use Util;
+use Maemian::Data;
+
+# If there is no debian/compat file present but cdbs is being used, cdbs will
+# create one automatically.  Currently it always uses compatibility level 5.
+# It may be better to look at what version of cdbs the package depends on and
+# from that derive the compatibility level....
+my $cdbscompat = 5;
+
+my $maint_commands = Maemian::Data->new ('debhelper/maint_commands');
+my $miscDeps_commands = Maemian::Data->new ('debhelper/miscDepends_commands');
+my $dh_commands_depends = Maemian::Data->new ('debhelper/dh_commands', '=');
+my $filename_configs = Maemian::Data->new ('debhelper/filename-config-files');
+
+# The version at which debhelper commands were introduced.  Packages that use
+# one of these commands must have a dependency on that version of debhelper or
+# newer.
+my %versions
+    = (dh                 => '7',
+       dh_auto_configure  => '7',
+       dh_auto_build      => '7',
+       dh_auto_clean      => '7',
+       dh_auto_install    => '7',
+       dh_bugfiles        => '7.2.3~',
+       dh_icons           => '5.0.51~',
+       dh_installifupdown => '5.0.44~',
+       dh_lintian         => '6.0.7~',
+       dh_prep            => '7');
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+my $seencommand = '';
+my $needbuilddepends = '';
+my $needtomodifyscripts = '';
+my $level;
+my $seenversiondepends = '0';
+my $compat = 0;
+my $usescdbs = '';
+my $seendhpython = '';
+my $usescdbspython = '';
+my $seendhcleank = '';
+my $needmiscdepends = 0;
+my %missingbdeps;
+
+open(RULES, '<', "debfiles/rules") or fail("cannot read debian/rules: $!");
+my $dhcompatvalue;
+my @versioncheck;
+while (<RULES>) {
+    if (m/^\s+-?(dh_\w+)/) {
+        my $dhcommand = $1;
+       if ($dhcommand =~ /dh_testversion(?:\s+([^\s]+))?/) {
+           $level = $1 if ($1);
+           tag "dh_testversion-is-deprecated", "";
+       }
+       if ($dhcommand eq 'dh_dhelp') {
+           tag "dh_dhelp-is-deprecated", "";
+       }
+       if ($dhcommand eq 'dh_suidregister') {
+           tag "dh_suidregister-is-obsolete", "";
+       }
+       if ($dhcommand eq 'dh_clean' and m/\s+\-k(\s+.*)?$/) {
+           $seendhcleank = 1;
+       }
+       # if command is passed -n, it does not modify the scripts
+       if ($maint_commands->known($dhcommand) and not m/\s+\-n\s+/) {
+           $needtomodifyscripts = 1;
+       }
+       if ($miscDeps_commands->known($dhcommand)) {
+           $needmiscdepends = 1;
+       }
+       if ($dh_commands_depends->known($dhcommand)) {
+           my $dep = $dh_commands_depends->value($dhcommand);
+
+           # Special-case default-jdk-builddep.  It appears to be a sort of
+           # build-essential for Java applications.
+           if ($dhcommand eq 'dh_nativejava') {
+               $dep = "$dep | default-jdk | default-jdk-builddep";
+           }
+           $missingbdeps{$dep} = $dhcommand;
+       }
+       if ($versions{$dhcommand}) {
+           push (@versioncheck, $dhcommand);
+       }
+       $seencommand = 1;
+       $needbuilddepends = 1;
+    } elsif (m,^\s+dh\s+,) {
+       $seencommand = 1;
+       $needbuilddepends = 1;
+       $needtomodifyscripts = 1;
+       $needmiscdepends = 1;
+       push (@versioncheck, 'dh');
+    } elsif (m,^include\s+/usr/share/cdbs/1/rules/debhelper.mk,) {
+       $seencommand = 1;
+       $needbuilddepends = 1;
+       $needtomodifyscripts = 1;
+       $needmiscdepends = 1;
+
+       # CDBS sets DH_COMPAT but doesn't export it.  It does, however, create
+       # a debian/compat file if none was found; that logic is handled later.
+       $dhcompatvalue = $cdbscompat;
+       $usescdbs = 1;
+    } elsif (/^\s*export\s+DH_COMPAT\s*:?=\s*([^\s]+)/) {
+       $level = $1;
+    } elsif (/^\s*export\s+DH_COMPAT/) {
+       $level = $dhcompatvalue if $dhcompatvalue;
+    } elsif (/^\s*DH_COMPAT\s*:?=\s*([^\s]+)/) {
+       $dhcompatvalue = $1;
+       # one can export and then set the value:
+       $level = $1 if ($level);
+    }
+    if (/^\s+dh_python\s/) {
+        $seendhpython = 1;
+    } elsif (m,^include\s+/usr/share/cdbs/1/class/python-distutils.mk,) {
+        $usescdbspython = 1;
+    }
+}
+close RULES;
+
+return unless $seencommand;
+
+my $pkgs = $info->binaries;
+my $single_pkg = keys(%$pkgs) == 1 ? $pkgs->{(keys(%$pkgs))[0]} : '';
+
+for my $binpkg (keys %$pkgs) {
+    my ($weak_depends, $strong_depends, $depends) = ('','','');
+
+    foreach my $field (qw(pre-depends depends)) {
+       $strong_depends .= $info->binary_field($binpkg, $field);
+    }
+    foreach my $field (qw(recommends suggests)) {
+       $weak_depends .= $info->binary_field($binpkg, $field);
+    }
+    $depends = $weak_depends . $strong_depends;
+
+    tag 'debhelper-but-no-misc-depends', $binpkg
+       if $needmiscdepends and $depends !~ m/\$\{misc:Depends\}/
+          and $pkgs->{$binpkg} eq 'deb';
+
+    tag 'weak-dependency-on-misc-depends', $binpkg
+       if $weak_depends =~ m/\$\{misc:Depends\}/
+          and $pkgs->{$binpkg} eq 'deb';
+}
+
+my $compatnan = 0;
+# Check the compat file.  Do this separately from looping over all of the
+# other files since we use the compat value when checking for brace expansion.
+if (-f 'debfiles/compat') {
+    my $compat_file = slurp_entire_file('debfiles/compat');
+    ($compat) = split(/\n/, $compat_file);
+    $compat =~ s/^\s+$//;
+    if ($compat) {
+       chomp $compat;
+       if ($compat !~ m/^\d+$/) {
+           tag 'debhelper-compat-not-a-number', $compat;
+           $compat =~ s/[^\d]//g;
+           $compatnan = 1;
+       }
+       if ($level) {
+           tag 'declares-possibly-conflicting-debhelper-compat-versions',
+               "rules=$level compat=$compat";
+       } else {
+           # this is not just to fill in the gap, but because debhelper
+           # prefers DH_COMPAT over debian/compat
+           $level = $compat;
+       }
+    } else {
+       tag 'debhelper-compat-file-is-empty';
+    }
+}
+if (defined($level) and $level !~ m/^\d+$/ and not $compatnan) {
+    tag 'debhelper-compatibility-level-not-a-number', $level;
+    $level =~ s/[^\d]//g;
+    $compatnan = 1;
+}
+
+if ($usescdbs and not defined($level)) {
+    $level = $cdbscompat;
+}
+$level ||= 1;
+if ($level < 5) {
+    tag "package-uses-deprecated-debhelper-compat-version", $level;
+}
+
+if ($seendhcleank and $level >= 7) {
+    tag "dh-clean-k-is-deprecated";
+}
+
+
+# Check the files in the debian directory for various debhelper-related
+# things.
+opendir(DEBIAN, 'debfiles')
+    or fail("Can't open debfiles directory.");
+while (defined(my $file=readdir(DEBIAN))) {
+    if ($file =~ m/^(?:(.*)\.)?(?:post|pre)(?:inst|rm)$/) {
+       next unless $needtomodifyscripts;
+
+       # They need to have #DEBHELPER# in their scripts.  Search for scripts
+       # that look like maintainer scripts and make sure the token is there.
+        my $binpkg = $1 || '';
+       open(IN, '<', "debfiles/$file")
+           or fail("Can't open debfiles/$file: $!");
+       my $seentag = '';
+       while (<IN>) {
+           if (m/\#DEBHELPER\#/) {
+               $seentag = 1;
+               last;
+           }
+       }
+       close IN;
+       if (!$seentag) {
+           unless (($binpkg && exists($pkgs->{$binpkg})
+                    && ($pkgs->{$binpkg} eq 'udeb'))
+                   or (!$binpkg && ($single_pkg eq 'udeb'))) {
+               tag "maintainer-script-lacks-debhelper-token", "debian/$file";
+           }
+       }
+    } elsif ($file =~ m/^control$/) {
+       my $bdepends_noarch = $info->relation_noarch('build-depends-all');
+        my $bdepends = $info->relation('build-depends-all');
+        if ($needbuilddepends && ! $bdepends->implies('debhelper')) {
+           tag "package-uses-debhelper-but-lacks-build-depends", "";
+       }
+       while (my ($dep, $command) = each %missingbdeps) {
+           next if $dep eq 'debhelper'; #handled above
+           tag 'missing-build-dependency-for-dh_-command', "$command=$dep"
+               unless ($bdepends_noarch->implies($dep));
+       }
+       my $needed = "debhelper (>= $level)";
+       if ($level > 5 && ! $bdepends->implies($needed)) {
+           tag "package-lacks-versioned-build-depends-on-debhelper", $level;
+       } elsif (@versioncheck) {
+           my %seen;
+           @versioncheck = grep { !$seen{$_}++ } @versioncheck;
+           for my $program (@versioncheck) {
+               my $required = $versions{$program};
+               my $needed = "debhelper (>= $required)";
+               unless ($bdepends->implies($needed)) {
+                   tag 'debhelper-script-needs-versioned-build-depends',
+                       $program, "(>= $required)";
+               }
+           }
+       }
+    } elsif ($file =~ m/^ex\.|\.ex$/i) {
+        tag "dh-make-template-in-source", "debian/$file";
+    } else {
+       my $base = $file;
+       $base =~ s/^[.]+\.//;
+
+       # Check whether this is a debhelper config file that takes a list of
+       # filenames.  If so, check it for brace expansions, which aren't
+       # supported.
+       if ($filename_configs->known($base)) {
+           next if $level < 3;
+           open (IN, '<', "debfiles/$file")
+               or fail("Can't open debfiles/$file: $!");
+           local $_;
+           while (<IN>) {
+               next if /^\s*$/;
+               next if (/^\#/ and $level >= 5);
+               if (m/(?<!\\)\{(?:[^\s\\\}]+?,)+[^\\\}\s]+\}/) {
+                   tag 'brace-expansion-in-debhelper-config-file',
+                       "debian/$file";
+               }
+           }
+           close IN;
+       }
+    }
+}
+closedir(DEBIAN);
+
+# Check for Python policy usage and the required debhelper dependency for
+# dh_python policy support.  Assume people who intentionally set pycompat to
+# something earlier than 2 know what they're doing.  Skip CDBS packages since
+# CDBS creates pycompat internally at build time.
+if ($seendhpython && !$usescdbspython) {
+    if (open(PYCOMPAT, '<', "debfiles/pycompat")) {
+       local $/;
+       my $pycompat = <PYCOMPAT>;
+       close PYCOMPAT;
+    } else {
+       tag "uses-dh-python-with-no-pycompat", "";
+    }
+}
+
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 ts=8 noet shiftround
diff --git a/checks/debhelper.desc b/checks/debhelper.desc
new file mode 100644 (file)
index 0000000..b8805e8
--- /dev/null
@@ -0,0 +1,179 @@
+Check-Script: debhelper
+Author: Joey Hess <joeyh@debian.org>
+Abbrev: dh
+Type: source
+Unpack-Level: 1
+Info: This looks for common mistakes in debhelper source packages.
+Needs-Info: debfiles, source-control-file
+
+Tag: maintainer-script-lacks-debhelper-token
+Severity: normal
+Certainty: possible
+Info: This package is built using debhelper commands that may modify
+ maintainer scripts, but the maintainer scripts do not contain
+ the "#DEBHELPER#" token debhelper uses to modify them.
+ .
+ Adding the token to the scripts is recommended.
+
+Tag: package-uses-debhelper-but-lacks-build-depends
+Severity: important
+Certainty: possible
+Info: If a package uses debhelper, it must declare a Build-Depends
+ on debhelper.
+
+Tag: package-lacks-versioned-build-depends-on-debhelper
+Severity: minor
+Certainty: certain
+Info: The package doesn't declare a versioned build dependency on
+ debhelper.  Recommended practice is to always declare an explicit
+ versioned dependency on debhelper equal to or greater than the
+ compatibility level used by the package, even if the versioned dependency
+ isn't strictly necessary.  Having a versioned dependency also helps with
+ backports to older releases and correct builds on partially updated
+ systems.
+Ref: debhelper(7)
+
+Tag: dh-make-template-in-source
+Severity: normal
+Certainty: possible
+Info: This package contains debian/*.ex and/or debian/ex.* files
+ installed by dh_make. These are intended to be filled in with the
+ package's details and renamed for use by various debhelper commands.
+ If they are not being used, they should be removed.
+
+Tag: dh_testversion-is-deprecated
+Severity: normal
+Certainty: certain
+Ref: dh_testversion(1)
+Info: This package calls dh_testversion in its <tt>debian/rules</tt> file.
+ dh_testversion is deprecated. Packages using debhelper should use
+ versioned build dependencies to ensure that the correct version of
+ debhelper is installed.
+
+Tag: dh_dhelp-is-deprecated
+Severity: normal
+Certainty: certain
+Info: This package calls dh_dhelp in its <tt>debian/rules</tt> file.
+ dh_dhelp is deprecated as it is not part of the canonical debhelper suite
+ and due to be removed since it supports only one single documentation
+ system (dhelp).
+ .
+ Simply use dh_installdocs, which creates doc-base files and supports all of
+ the documentation systems that way.
+
+Tag: dh_suidregister-is-obsolete
+Severity: normal
+Certainty: certain
+Ref: dh_suidregister(1)
+Info: suidregister is obsoleted by dpkg-statoverride, so registration of
+ files in with dh_suidregister is unnecessary, and even harmful.
+
+Tag: dh-clean-k-is-deprecated
+Severity: normal
+Certainty: certain
+Ref: dh_clean(1)
+Info: This package calls dh_clean -k in its <tt>debian/rules</tt> file
+ and declares a debhelper compatibility version of at least 7.
+ .
+ debhelper 7 deprecated dh_clean -k in favour of dh_prep.
+
+Tag: debhelper-compat-file-is-empty
+Severity: important
+Certainty: certain
+Ref: debhelper(7)
+Info: The source package has an empty debian/compat file. This is an error,
+ the compat level of debhelper should be in there. Note that only the first
+ line of the file is relevant.
+
+Tag: declares-possibly-conflicting-debhelper-compat-versions
+Severity: important
+Certainty: certain
+Ref: debhelper(7)
+Info: The source package declares the debhelper compatibility version
+ both in the <tt>debian/compat</tt> file and in the <tt>debian/rules</tt>
+ file. If these ever get out of synchronisation, the package may not build
+ as expected.
+
+Tag: package-uses-deprecated-debhelper-compat-version
+Severity: normal
+Certainty: certain
+Ref: debhelper(7)
+Info: The debhelper compatibility version used by this package is marked
+ as deprecated by the debhelper developer. You should really consider
+ using a newer compatibility version.
+ .
+ The compatibility version can be set in (preferred)
+ <tt>debian/compat</tt> or by setting and exporting DH_COMPAT in
+ <tt>debian/rules</tt>.  If it is not set in either place, debhelper
+ defaults to the deprecated compatibility version 1.
+
+Tag: uses-dh-python-with-no-pycompat
+Severity: normal
+Certainty: possible
+Info: This package uses dh_python but apparently does not tell it to use
+ the current Python policy by putting 2 in <tt>debian/pycompat</tt>.  This
+ may mean that the package has not been converted to the current Python
+ policy, in which case it probably should be.
+
+Tag: debhelper-but-no-misc-depends
+Severity: normal
+Certainty: certain
+Ref: debhelper(7)
+Info: The source package uses debhelper but it does not use ${misc:Depends} in
+ the given binary package's debian/control entry.  This is required so the
+ dependencies are set correctly in case the result of a call to any of the dh_
+ commands cause the package to depend on another package.
+
+Tag: missing-build-dependency-for-dh_-command
+Severity: important
+Certainty: possible
+Info: The source package appears to be using a dh_ command but doesn't build
+ depend on the package that actually provides it.  If it uses it, it must
+ build depend on it.
+
+Tag: debhelper-script-needs-versioned-build-depends
+Severity: normal
+Certainty: certain
+Info: The given debhelper script was introduced in a later version of
+ debhelper than the package Build-Depends on.  The package Build-Depends
+ should be updated to require that version of debhelper.  Giving the
+ version followed by <tt>~</tt> is recommended so that backports will
+ satisfy the dependency.
+ .
+ etch was released with debhelper version 5.0.42, so every package that
+ assumes a newer version should explicitly declare so for the sake of
+ etch backports.
+
+Tag: brace-expansion-in-debhelper-config-file
+Severity: normal
+Certainty: possible
+Info: This debhelper config file appears to use shell brace expansion
+ (such as <tt>{foo,bar}</tt>) to specify files.  This happens to work due
+ to an accident of implementation but is not a supported feature.  Only
+ <tt>?</tt>, <tt>*</tt>, and <tt>[...]</tt> are supported.
+Ref: debhelper(1)
+
+Tag: weak-dependency-on-misc-depends
+Severity: normal
+Certainty: possible
+Ref: debhelper(7)
+Info: The source package declares a weak dependecy on ${misc:Depends} in
+ the given binary package's debian/control entry.  A stronger dependency, that
+ is one that ensures the package's installation, is required so that the
+ additional commands are available to the maintainer scripts when they are run.
+
+Tag: debhelper-compat-not-a-number
+Severity: important
+Certainty: certain
+Ref: debhelper(7)
+Info: The debhelper compatibility level specified in
+ <tt>debian/compat</tt> is not a number.
+
+Tag: debhelper-compatibility-level-not-a-number
+Severity: important
+Certainty: possible
+Info: The debhelper compatibility level specified in <tt>debian/rules</tt>
+ is not a number.  If you're using make functions or other more complex
+ methods to generate the compatibility level, write the output into
+ <tt>debian/compat</tt> instead of setting DH_COMPAT.  The latter should
+ be available for a user to override temporarily.
diff --git a/checks/debian-readme b/checks/debian-readme
new file mode 100644 (file)
index 0000000..672ffd4
--- /dev/null
@@ -0,0 +1,65 @@
+# debian-readme -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Richard Braakman
+#
+# 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 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.
+
+package Maemian::debian_readme;
+use strict;
+use Spelling;
+use Tags;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $readme = "";
+my $template;
+
+if (open(IN, '<', "README.Debian")) {
+    local $_;
+    while (<IN>) {
+        if (m,/usr/doc\b,) {
+            tag 'readme-debian-mentions-usr-doc', "line $.";
+        }
+        $readme .= $_;
+    }
+    close(IN);
+}
+
+my @template =
+    ('Comments regarding the Package',
+     'So far nothing to say',
+     '<possible notes regarding this package - if none, delete this file>');
+my $regex = join ('|', @template);
+if ($readme =~ m/$regex/io) {
+    tag("readme-debian-contains-debmake-template");
+} elsif ($readme =~ m/^\s*-- [^<]*<[^> ]+.\@unknown>/m) {
+    tag("readme-debian-contains-debmake-default-email-address");
+}
+
+spelling_check('spelling-error-in-readme-debian', $readme);
+
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround
diff --git a/checks/debian-readme.desc b/checks/debian-readme.desc
new file mode 100644 (file)
index 0000000..b33672b
--- /dev/null
@@ -0,0 +1,42 @@
+Check-Script: debian-readme
+Author: Richard Braakman <dark@xs4all.nl>
+Abbrev: drm
+Type: binary
+Unpack-Level: 1
+Needs-Info: debian-readme
+Info: This script checks the README.Debian file for various problems.
+
+Tag: readme-debian-mentions-usr-doc
+Severity: normal
+Certainty: possible
+Info: The README.Debian file installed by this package apparently points
+ users at /usr/doc.  /usr/doc has been retired and all documentation
+ migrated to /usr/share/doc.  This reference should probably also be
+ updated.
+
+Tag: readme-debian-contains-debmake-template
+Severity: normal
+Certainty: certain
+Info: The README.Debian file installed by this package contains one of the
+ template phrases from the README.Debian provided by deb-make or dh_make:
+ .
+  Comments regarding the package
+  So far nothing to say
+  &lt;possible notes regarding this package - if none, delete this file&gt;
+ .
+ If there is real information in the file, please delete any generic
+ template phrases.  If there is nothing to say in the file, it is best
+ removed.
+
+Tag: readme-debian-contains-debmake-default-email-address
+Severity: normal
+Certainty: certain
+Info: The README.Debian file contains an email address (&lt;..@unknown&gt;)
+ that was not updated to the maintainer's real address.
+
+Tag: spelling-error-in-readme-debian
+Severity: normal
+Certainty: certain
+Info: Maemian found a spelling error in the README.Debian file.  Maemian
+ has a list of common misspellings that it looks for.  It does not have a
+ dictionary like a spelling checker does.
diff --git a/checks/description b/checks/description
new file mode 100644 (file)
index 0000000..b2bc12a
--- /dev/null
@@ -0,0 +1,187 @@
+# description -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Christian Schwarz
+#
+# 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 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.
+
+package Maemian::description;
+use strict;
+use Spelling;
+use Tags;
+use Util;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+my $tabs = 0;
+my $lines = 0;
+my $template = 0;
+my $unindented_list = 0;
+my $synopsis;
+my $description;
+
+# description?
+my $full_description = $info->field('description');
+unless (defined $full_description) {
+    tag "package-has-no-description", "";
+    return 0;
+}
+
+$full_description =~ m/^([^\n]*)\n(.*)$/s;
+($synopsis, $description) = ($1, $2);
+unless (defined $synopsis) {
+    $synopsis = $full_description;
+    $description = '';
+}
+
+if ($synopsis =~ m/^\s*$/) {
+    tag "description-synopsis-is-empty", "";
+} else {
+    if ($synopsis =~ m/^\Q$pkg\E\b/i) {
+       tag "description-starts-with-package-name", "";
+    }
+    if ($synopsis =~ m/(?<!etc)\.\s*$/i) {
+       tag "description-synopsis-might-not-be-phrased-properly", "";
+    }
+    if ($synopsis =~ m/\t/) {
+       tag "description-contains-tabs", "" unless $tabs++;
+    }
+    if (length($synopsis) >= 80) {
+       tag "description-too-long", "";
+    }
+    if ($synopsis =~ m/^missing\s*$/i) {
+       tag "description-is-debmake-template", "" unless $template++;
+    } elsif ($synopsis =~ m/<insert up to 60 chars description>/) {
+       tag "description-is-dh_make-template", "" unless $template++;
+    }
+}
+
+my $flagged_homepage;
+my $is_dummy;
+foreach (split /\n/, $description) {
+    next if m/^ \.\s*$/o;
+
+    if ($lines == 0) {
+       my $firstline = lc $_;
+       my $lsyn = lc $synopsis;
+       if ($firstline =~ /^\Q$lsyn\E$/) {
+           tag "description-synopsis-is-duplicated", "";
+       } else {
+           $firstline =~ s/[^a-zA-Z0-9]+//g;
+           $lsyn =~ s/[^a-zA-Z0-9]+//g;
+           if ($firstline eq $lsyn) {
+               tag "description-synopsis-is-duplicated", "";
+           }
+       }
+    }
+
+    $lines++;
+
+    if (m/^ \.\s*\S/o) {
+       tag "description-contains-invalid-control-statement", "";
+    } elsif (m/^ [\-\*]/o) {
+       # Print it only the second time.  Just one is not enough to be sure that
+       # it's a list, and after the second there's no need to repeat it.
+       tag "possible-unindented-list-in-extended-description", "" if $unindented_list++ == 2;
+    }
+
+    if (m/\t/o) {
+       tag "description-contains-tabs", "" unless $tabs++;
+    }
+
+    if (m,^\s*Homepage: <?https?://,i) {
+       tag "description-contains-homepage";
+       $flagged_homepage = 1;
+    }
+
+    if (m,This description was automagically extracted from the module by dh-make-perl,i) {
+       tag "description-contains-dh-make-perl-template";
+    }
+
+    my $wo_quotes = $_;
+    $wo_quotes =~ s,(\"|\')(.*?)(\1),,;
+    while ($wo_quotes =~ m,(?:\W|^)((\w+)(\s+(\2))+)(?:\W|\z),i) {
+       my $words = $1;
+       $wo_quotes =~ s/\Q$words//;
+        tag "description-contains-duplicated-word", "$words";
+    }
+
+    my $first_person = $_;
+    while ($first_person =~
+       m/(?:^|\s)(I|[Mm]y|[Oo]urs?|mine|myself|me|us|[Ww]e)(?:$|\s)/) {
+
+       my $word = $1;
+       $first_person =~ s/\Q$word//;
+       tag "using-first-person-in-description", "line $lines: $word";
+    }
+
+    if ($lines == 1) {
+       # checks for the first line of the extended description:
+       if (m/^ \s/o) {
+           tag "description-starts-with-leading-spaces", "";
+       }
+       if (m/^\s*missing\s*$/oi) {
+           tag "description-is-debmake-template", "" unless $template++;
+       } elsif (m/<insert long description, indented with spaces>/) {
+           tag "description-is-dh_make-template", "" unless $template++;
+       }
+    }
+
+    if (length($_) > 80) {
+       tag "extended-description-line-too-long", "";
+    }
+}
+
+if ($type ne 'udeb') {
+    if ($lines == 0) {
+       tag "extended-description-is-empty";
+    } elsif ($lines <= 2 and not $synopsis =~ /(dummy|transition)/i) {
+       tag "extended-description-is-probably-too-short";
+    }
+}
+
+# Check for a package homepage in the description and no Homepage field.  This
+# is less accurate and more of a guess than looking for the old Homepage:
+# convention in the body.
+unless ($info->field('homepage') or $flagged_homepage) {
+    if ($description =~ /homepage|webpage|website|url|upstream|web\s+site
+                         |home\s+page|further\s+information|more\s+info
+                         |official\s+site|project\s+home/xi
+       and $description =~ m,\b(https?://[a-z0-9][^>\s]+),i) {
+       tag 'description-possibly-contains-homepage', $1;
+    } elsif ($description =~ m,\b(https?://[a-z0-9][^>\s]+)>?\.?\s*\z,i) {
+       tag 'description-possibly-contains-homepage', $1;
+    }
+}
+
+if ($description) {
+    spelling_check('spelling-error-in-description', $description);
+    spelling_check_picky('spelling-error-in-description', $description);
+}
+
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 ts=8
diff --git a/checks/description.desc b/checks/description.desc
new file mode 100644 (file)
index 0000000..c1a3245
--- /dev/null
@@ -0,0 +1,171 @@
+Check-Script: description
+Author: Christian Schwarz <schwarz@debian.org>
+Abbrev: des
+Type: binary, udeb
+Unpack-Level: 1
+
+Tag: package-has-no-description
+Severity: serious
+Certainty: certain
+Info: The binary package does not have a "Description:" control field.
+Tested: empty
+
+Tag: description-synopsis-is-empty
+Severity: serious
+Certainty: certain
+Info: The first line in the "Description:" is empty.
+
+Tag: extended-description-is-empty
+Severity: serious
+Certainty: certain
+Status: untested
+Info: The extended description (the lines after the first line of the
+ "Description:" field) is empty.
+
+Tag: extended-description-is-probably-too-short
+Severity: minor
+Certainty: possible
+Ref: devref 6.2.1, devref 6.2.3
+Info: The extended description (the lines after the first line of the
+ "Description:" field) is only one or two lines long.  The extended
+ description should provide a user with enough information to decide
+ whether they want to install this package, what it contains, and how it
+ compares to similar packages.  One or two lines is normally not enough to
+ do this.
+
+Tag: description-contains-invalid-control-statement
+Severity: serious
+Certainty: certain
+Info: The description contains a line starting with a dot (.). This is
+ not allowed.
+Ref: policy 5.6.13
+
+Tag: description-too-long
+Severity: important
+Certainty: certain
+Info: The first line of the "Description:" must not exceed 80 characters.
+Ref: policy 3.4.1
+
+Tag: description-starts-with-package-name
+Severity: important
+Certainty: certain
+Info: The first line of the "Description:" should not start with the
+ package name. For example, the package <tt>foo</tt> should not
+ have a description like this: "foo is a program that...".
+Ref: policy 3.4.1
+
+Tag: description-contains-tabs
+Severity: important
+Certainty: certain
+Info: The package "Description:" must not contain tab characters.
+Ref: policy 5.6.13
+
+Tag: description-starts-with-leading-spaces
+Severity: normal
+Certainty: possible
+Info: The package "Description:" starts with leading spaces. Usually,
+ leading spaces are used to switch "verbatim display" on (i.e., lines
+ are not wrapped) so this might be a bug in the package.
+
+Tag: possible-unindented-list-in-extended-description
+Severity: normal
+Certainty: possible
+Info: The package "Description:" contains an unindented line which
+ starts with a dash (-) or asterisk (*). If this was meant to be a
+ list of items these lines need to be indented (dselect would
+ word-wrap these lines otherwise).
+Ref: policy 5.6.13
+
+Tag: description-is-debmake-template
+Severity: important
+Certainty: certain
+Info: The synopsis or the extended description just says "Missing",
+ which is a template provided by debmake.
+
+Tag: description-is-dh_make-template
+Severity: important
+Certainty: certain
+Info: The synopsis or the extended description has not been changed
+ from the template provided by dh_make.
+
+Tag: description-contains-dh-make-perl-template
+Severity: normal
+Certainty: certain
+Info: The extended description contains the statement that it was
+ automagically extracted by dh-make-perl.  Please check the description
+ for correctness and usefulness and remove the dh-make-perl statement
+ to signal that you have done so. 
+
+Tag: description-synopsis-might-not-be-phrased-properly
+Severity: normal
+Certainty: possible
+Info: The synopsis (first line in the package "Description:" field, the
+ short description) ends with a full stop "." character. This is not
+ necessary, as the synopsis doesn't need to be a full sentence. It is
+ recommended that a descriptive phrase is used instead.
+ .
+ Note also that the synopsis is not part of the rest of the "Description:"
+ field.
+Ref: policy 3.4.1
+
+Tag: description-synopsis-is-duplicated
+Severity: important
+Certainty: certain
+Info: The first line of the extended Description: should not repeat the
+ synopsis exactly. This indicates that either the synopsis is badly formed
+ or that the extended description has been wrongly copied and pasted.
+Ref: policy 3.4.2
+
+Tag: extended-description-line-too-long
+Severity: normal
+Certainty: certain
+Info: One or more lines in the extended part of the "Description:" field
+ have been found to contain more than 80 characters. For the benefit of
+ users of 80x25 terminals, it is recommended that the lines do not exceed
+ 80 characters.
+Ref: policy 3.4.1
+
+Tag: description-contains-homepage
+Severity: normal
+Certainty: certain
+Info: The extended description contains a "Homepage:" pseudo-header
+ following the old Developer's Reference recommendation.  As of 1.14.6,
+ dpkg now supports Homepage: as a regular field in
+ <tt>debian/control</tt>.  This header should be moved from the extended
+ description to the fields for the relevant source or binary packages.
+
+Tag: spelling-error-in-description
+Severity: normal
+Certainty: certain
+Info: Maemian found a spelling or capitalization error in the package
+ description.  Maemian has a list of common misspellings that it looks
+ for.  It does not have a dictionary like a spelling checker does.  It is
+ particularly picky about spelling and capitalization in package
+ descriptions since they're very visible to end users.
+
+Tag: description-contains-duplicated-word
+Severity: normal
+Certainty: possible
+Info: The description contains a duplicated word.  Usually this is a
+ mistake, or at least an awkward phrasing.
+
+Tag: using-first-person-in-description
+Severity: minor
+Certainty: possible
+Info: You should avoid the use of first person ("I will do this..." or
+ "We recommend...").  The computer is not a person and the description
+ does not speak for the maintainer or maintainers.  Instead, use a more
+ neutral construction and try to rephrase into factual statements about
+ the package.
+ .
+ For example, rather than saying "I don't recommend this package if you
+ are short on memory," say something like "this package is not suitable
+ for low-memory systems."
+
+Tag: description-possibly-contains-homepage
+Severity: wishlist
+Certainty: wild-guess
+Info: This package has no Homepage field but has a URL in the description
+ and wording that might indicate this is the package Homepage.  If it is,
+ add a Homepage control field containing it rather than mentioning it in
+ the package description.
diff --git a/checks/etcfiles b/checks/etcfiles
new file mode 100644 (file)
index 0000000..27b470f
--- /dev/null
@@ -0,0 +1,64 @@
+# etcfiles -- lintian check script -*- perl -*-
+
+# Copyright (C) 2000 by Sean 'Shaleh' Perry
+#
+# 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 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.
+
+package Maemian::etcfiles;
+use strict;
+use Tags;
+use Util;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+my %conffiles;
+
+my $conffiles = "control/conffiles";
+
+# load conffiles
+if (open(IN, '<', $conffiles)) {
+    while (<IN>) {
+       chop;
+       next if m/^\s*$/o;
+       s,^/,,;
+       $conffiles{$_} = 1;
+    }
+    close(IN);
+}
+
+# Read package contents...
+foreach my $file (sort keys %{$info->index}) {
+    my $index_info = $info->index->{$file};
+    next unless $file =~ m,^etc, and $index_info->{type}=~ m/^[-h]/;
+
+    # If there is a /etc/foo, it must be a conffile (with a few exceptions).
+    if (not exists($conffiles{$file})
+       and $file !~ m,/README$,
+       and $file ne 'etc/init.d/skeleton'
+       and $file ne 'etc/init.d/rc'
+       and $file ne 'etc/init.d/rcS') {
+       tag "file-in-etc-not-marked-as-conffile", "/$file";
+    }
+}
+
+}
+
+1;
diff --git a/checks/etcfiles.desc b/checks/etcfiles.desc
new file mode 100644 (file)
index 0000000..b9eaaab
--- /dev/null
@@ -0,0 +1,12 @@
+Check-Script: etcfiles
+Author: Sean 'Shaleh' Perry <shaleh@debian.org>
+Abbrev: etc
+Type: binary
+Unpack-Level: 1
+
+Tag: file-in-etc-not-marked-as-conffile
+Severity: important
+Certainty: certain
+Ref: policy 10.7
+Info: Files in <tt>/etc</tt> must be marked conffiles if they are included
+ in a package.  Otherwise they should be created by maintainer scripts.
diff --git a/checks/fields b/checks/fields
new file mode 100644 (file)
index 0000000..571d664
--- /dev/null
@@ -0,0 +1,1119 @@
+# fields -- lintian check script (rewrite) -*- perl -*-
+#
+# Copyright (C) 2004 Marc Brockschmidt
+#
+# Parts of the code were taken from the old check script, which
+# was Copyright (C) 1998 Richard Braakman (also licensed under the
+# GPL 2 or higher)
+#
+# 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 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.
+
+package Maemian::fields;
+use strict;
+
+use lib "$ENV{'MAEMIAN_ROOT'}/checks/";
+use common_data;
+use Tags;
+use Util;
+
+use Maemian::Data ();
+use Maemian::Check qw(check_maintainer);
+use Maemian::Relation ();
+use Maemian::Relation::Version qw(versions_compare);
+
+our $KNOWN_ARCHS = Maemian::Data->new ('fields/architectures');
+
+our %known_archive_parts = map { $_ => 1 }
+    ('non-free', 'contrib');
+
+our %known_sections = map { $_ => 1 }
+    ('admin', 'comm', 'cli-mono', 'database', 'debug', 'devel', 'doc',
+     'editors', 'electronics', 'embedded', 'fonts', 'games', 'gnome', 'gnu-r',
+     'gnustep', 'graphics', 'hamradio', 'haskell', 'httpd', 'interpreters',
+     'java', 'kde', 'libdevel', 'libs', 'lisp', 'localization', 'kernel', 'mail',
+     'math', 'misc', 'net', 'news', 'ocaml', 'oldlibs', 'otherosfs', 'perl',
+     'php', 'python', 'ruby', 'science', 'shells', 'sound', 'tex', 'text',
+     'utils', 'vcs', 'video', 'web', 'x11', 'xfce', 'zope'
+    );
+
+our %known_prios = map { $_ => 1 }
+    ('required', 'important', 'standard', 'optional', 'extra');
+
+# The Ubuntu original-maintainer field is handled separately.
+our %known_binary_fields = map { $_ => 1 }
+    ('package', 'version', 'architecture', 'depends', 'pre-depends',
+     'recommends', 'suggests', 'enhances', 'conflicts', 'provides',
+     'replaces', 'breaks', 'essential', 'maintainer', 'section', 'priority',
+     'source', 'description', 'installed-size', 'python-version', 'homepage',
+     'bugs', 'origin');
+
+# The Ubuntu original-maintainer field is handled separately.
+our %known_udeb_fields = map { $_ => 1 }
+    ('package', 'version', 'architecture', 'subarchitecture', 'depends',
+     'recommends', 'enhances', 'provides', 'replaces', 'breaks', 'replaces',
+     'maintainer', 'section', 'priority', 'source', 'description',
+     'installed-size', 'kernel-version', 'installer-menu-item', 'bugs',
+     'origin');
+
+our %known_obsolete_fields = map { $_ => 1 }
+    ('revision', 'package-revision', 'package_revision',
+     'recommended', 'optional', 'class');
+
+our %known_build_essential = map { $_ => 1 }
+    ('libc6-dev', 'libc-dev', 'gcc', 'g++', 'make', 'dpkg-dev');
+
+# Still in the archive but shouldn't be the primary Emacs dependency.
+our %known_obsolete_emacs = map { $_ => 1 }
+    ('emacs21');
+
+our %known_libstdcs = map { $_ => 1 }
+    ('libstdc++2.9-glibc2.1', 'libstdc++2.10', 'libstdc++2.10-glibc2.2',
+     'libstdc++3', 'libstdc++3.0', 'libstdc++4', 'libstdc++5',
+     'libstdc++6', 'lib64stdc++6',
+    );
+
+our %known_tcls = map { $_ => 1 }
+    ( 'tcl74', 'tcl8.0', 'tcl8.2', 'tcl8.3', 'tcl8.4', 'tcl8.5', );
+
+our %known_tclxs = map { $_ => 1 }
+    ( 'tclx76', 'tclx8.0.4', 'tclx8.2', 'tclx8.3', 'tclx8.4', );
+
+our %known_tks = map { $_ => 1 }
+    ( 'tk40', 'tk8.0', 'tk8.2', 'tk8.3', 'tk8.4', 'tk8.5', );
+
+our %known_tkxs = map { $_ => 1 }
+    ( 'tkx8.2', 'tkx8.3', );
+
+our %known_libpngs = map { $_ => 1 }
+    ( 'libpng12-0', 'libpng2', 'libpng3', );
+
+our %known_x_metapackages = map { $_ => 1 }
+    ( 'x-window-system', 'x-window-system-dev', 'x-window-system-core',
+      'xorg', 'xorg-dev', );
+
+# The allowed Python dependencies currently.  This is the list of alternatives
+# that, either directly or through transitive dependencies that can be relied
+# upon, ensure /usr/bin/python will exist for the use of dh_python.
+our $PYTHON_DEPEND = 'python | python-dev | python-all | python-all-dev | '
+    . join (' | ', map { "python$_ | python$_-dev" } qw(2.4 2.5));
+
+# Certain build tools must be listed in Build-Depends even if there are no
+# arch-specific packages because they're required in order to run the clean
+# rule.  (See Policy 7.6.)  The following is a list of package dependencies;
+# regular expressions that, if they match anywhere in the debian/rules file,
+# say that this package is allowed (and required) in Build-Depends; and
+# optional tags to use for reporting the problem if some information other
+# than the default is required.
+our @GLOBAL_CLEAN_DEPENDS = (
+       [ ant => qr'^include\s*/usr/share/cdbs/1/rules/ant\.mk' ],
+       [ cdbs => qr'^include\s+/usr/share/cdbs/' ],
+       [ dbs => qr'^include\s+/usr/share/dbs/' ],
+       [ 'dh-make-php' => qr'^include\s+/usr/share/cdbs/1/class/pear\.mk' ],
+       [ debhelper => qr'^include\s+/usr/share/cdbs/1/rules/debhelper\.mk' ],
+       [ dpatch => qr'^include\s+/usr/share/cdbs/1/rules/dpatch\.mk' ],
+       [ quilt => qr'^include\s+/usr/share/cdbs/1/rules/patchsys-quilt\.mk' ],
+       [ dpatch => qr'^include\s+/usr/share/dpatch/' ],
+       [ quilt => qr'^include\s+/usr/share/quilt/' ],
+       [ $PYTHON_DEPEND => qr'/usr/share/cdbs/1/class/python-distutils\.mk', 'missing-python-build-dependency' ],
+);
+
+# These are similar, but the resulting dependency is only allowed, not
+# required.
+#
+# The xsfclean rule is specific to the packages maintained by the X Strike
+# Force, but there are enough of those to make the rule worthwhile.
+my @GLOBAL_CLEAN_ALLOWED = (
+       [ patchutils => qr'^include\s+/usr/share/cdbs/1/rules/dpatch\.mk' ],
+       [ patchutils => qr'^include\s+/usr/share/cdbs/1/rules/patchsys-quilt\.mk' ],
+       [ patchutils => qr'^include\s+/usr/share/cdbs/1/rules/simple-patchsys\.mk' ],
+       [ 'python-central' => qr'^DEB_PYTHON_SYSTEM\s*:?=\s*pycentral' ],
+       [ 'python-support' => qr'^DEB_PYTHON_SYSTEM\s*:?=\s*pysupport' ],
+       [ 'python-setuptools' => qr'/usr/share/cdbs/1/class/python-distutils\.mk' ],
+       [ quilt => qr'^clean:\s+xsfclean\b' ],
+);
+
+# A list of packages; regular expressions that, if they match anywhere in the
+# debian/rules file, this package must be listed in either Build-Depends or
+# Build-Depends-Indep as appropriate; and optional tags as above.
+my @GLOBAL_DEPENDS = (
+       [ $PYTHON_DEPEND => qr'^\t\s*dh_python\s', 'missing-dh_python-build-dependency' ],
+       [ 'python-central' => qr'^\t\s*dh_pycentral\s' ],
+       [ 'python-support' => qr'^\t\s*dh_pysupport\s' ],
+       [ 'python-central' => qr'^DEB_PYTHON_SYSTEM\s*:?=\s*pycentral' ],
+       [ 'python-support' => qr'^DEB_PYTHON_SYSTEM\s*:?=\s*pysupport' ],
+);
+
+# Similarly, this list of packages, regexes, and optional tags say that if the
+# regex matches in one of clean, build-arch, binary-arch, or a rule they
+# depend on, this package is allowed (and required) in Build-Depends.
+my @RULE_CLEAN_DEPENDS = (
+       [ ant => qr'^\t\s*ant\s' ],
+       [ debhelper => qr'^\t\s*dh_.+' ],
+       [ dpatch => qr'^\t\s*dpatch\s' ],
+       [ "po-debconf" => qr'^\t\s*debconf-updatepo\s' ],
+       [ $PYTHON_DEPEND => qr'^\t\s*python\s', 'missing-python-build-dependency' ],
+       [ $PYTHON_DEPEND => qr'\ssetup\.py\b', 'missing-python-build-dependency' ],
+       [ quilt => qr'^\t\s*(\S+=\S+\s+)*quilt\s' ],
+       [ yada => qr'^\t\s*yada\s' ],
+);
+
+# Similar, but the resulting dependency is only allowed, not required.  We
+# permit a versioned dependency on perl-base because that used to be the
+# standard suggested dependency.  No package should be depending on just
+# perl-base, since it's Priority: required.
+my @RULE_CLEAN_ALLOWED = (
+       [ patch => q'^\t\s*(?:perl debian/)?yada\s+unpatch' ],
+       [ 'perl | perl-base (>= 5.6.0-16)' => qr'(^\t|\|\|)\s*(perl|\$\(PERL\))\s' ],
+       [ 'perl-modules (>= 5.10) | libmodule-build-perl' => qr'(^\t|\|\|)\s*(perl|\$\(PERL\))\s+Build\b' ],
+       [ 'python-setuptools' => qr'\ssetup\.py\b' ],
+);
+
+# A simple list of regular expressions which, if they match anywhere in
+# debian/rules, indicate the requirements for debian/rules clean are complex
+# enough that we can't know what packages are permitted in Build-Depends and
+# should bypass the build-depends-without-arch-dep check completely.
+my @GLOBAL_CLEAN_BYPASS = (
+       qr'^include\s*/usr/share/cdbs/1/class/ant\.mk',
+       qr'^\s+dh\s+'
+);
+
+# Mapping of package names to section names
+my @NAME_SECTION_MAPPINGS = (
+    [ qr/-docs?$/      => 'doc'      ],
+    [ qr/-dbg$/        => 'debug'    ],
+    [ qr/^python-/     => 'python'   ],
+    [ qr/^r-cran-/     => 'gnu-r'    ],
+    [ qr/^lib.*-perl$/ => 'perl'     ],
+    [ qr/^lib.*-cil$/  => 'cli-mono' ],
+    [ qr/^lib.*-java$/ => 'java'     ],
+    [ qr/^(?:lib)php-/ => 'php'      ],
+    [ qr/^lib(?:hugs|ghc6)-/ => 'haskell'   ],
+    [ qr/^lib.*-ruby(?:1\.\d)?$/ => 'ruby'  ],
+    [ qr/^lib.*-ocaml-dev$/ => 'ocaml'      ],
+    [ qr/^lib.*-dev$/  => 'libdevel' ],
+);
+
+# Valid URI formats for the Vcs-* fields
+# currently only checks the protocol, not the actual format of the URI
+my %VCS_RECOMMENDED_URIS = (
+    browser => qr;^https?://;,
+    arch    => qr;^https?://;,
+    bzr     => qr;^(lp:~|(?:nosmart\+)?https?://);,
+    cvs     => qr;^:pserver:;,
+    darcs   => qr;^https?://;,
+    hg      => qr;^https?://;,
+    git     => qr;^(?:git|https?|rsync)://;,
+    svn     => qr;^(?:svn|(?:svn\+)?https?)://;,
+    mtn     => qr;^[\w.-]+\s+\S+;, # that's a hostname followed by a module name
+);
+my %VCS_VALID_URIS = (
+    arch    => qr;^https?://;,
+    bzr     => qr;^(?:sftp|(?:bzr\+)?ssh)://;,
+    cvs     => qr;^(?:-d\s*)?:(?:ext|pserver):;,
+    git     => qr;^(?:git\+)?ssh://;,
+    svn     => qr;^(?:svn\+)?ssh://;,
+);
+
+our $PERL_CORE_PROVIDES = Maemian::Data->new('fields/perl-provides', '\s+');
+our $OBSOLETE_PACKAGES  = Maemian::Data->new('fields/obsolete-packages');
+our $VIRTUAL_PACKAGES   = Maemian::Data->new('fields/virtual-packages');
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+my $version;
+my $arch_indep;
+
+unless (-d "fields") {
+       fail("directory in lintian laboratory for $type package $pkg missing: fields");
+}
+
+#---- Format
+
+if ($type eq 'source') {
+       my $format = $info->field('format');
+       if (defined($format) and $format !~ /^\s*1\.0\s*\z/) {
+               tag 'unsupported-source-format', $format;
+       }
+}
+
+#---- Package
+
+if ($type eq "binary"){
+       if (not defined $info->field('package')) {
+               tag "no-package-name", "";
+       } else {
+               my $name = $info->field('package');
+
+               unfold("package", \$name);
+               tag "bad-package-name", "" unless $name =~ /^[A-Z0-9][-+\.A-Z0-9]+$/i;
+               tag "package-not-lowercase", "" if ($name =~ /[A-Z]/)
+       }
+}
+
+#---- Version
+
+if (not defined $info->field('version')) {
+       tag "no-version-field", "";
+} else {
+       $version = $info->field('version');
+
+       unfold("version", \$version);
+
+       if (@_ = _valid_version($version)) {
+               my ($epoch, $upstream, $debian) = @_;
+               if ($upstream !~ /^\d/i) {
+                       tag "upstream-version-not-numeric", "$version";
+               }
+               if (defined $debian) {
+                       tag "debian-revision-should-not-be-zero", "$version"
+                               if $debian eq '-0';
+                       my $ubuntu;
+                       $debian =~ /^-([^.]+)(?:\.[^.]+)?(?:\.[^.]+)?(\..*)?$/;
+                       my $extra = $2;
+                       if (defined $extra) {
+                               $debian =~ /^-([^.]+ubuntu[^.]+)(?:\.\d+){1,3}(\..*)?$/;
+                               $ubuntu = 1;
+                               $extra = $2;
+                       }
+                       if (not defined $1 or defined $extra) {
+                               tag "debian-revision-not-well-formed", "$version";
+                       }
+                       if ($debian =~ /^-[^.-]+\.[^.-]+\./ and not $ubuntu) {
+                               tag "binary-nmu-uses-old-version-style", "$version"
+                                       if $type eq 'binary';
+                               tag "binary-nmu-debian-revision-in-source", "$version"
+                                       if $type eq 'source';
+                       }
+               }
+               if ($version =~ /\+b\d+$/ && $type eq "source") {
+                       tag "binary-nmu-debian-revision-in-source", "$version";
+               }
+
+               # Checks for the dfsg convention for repackaged upstream
+               # source.  Only check these against the source package to not
+               # repeat ourselves too much.
+               if ($type eq 'source') {
+                       if ($version =~ /dfsg/ and $info->native) {
+                               tag 'dfsg-version-in-native-package', $version;
+                       } elsif ($version =~ /\.dfsg/) {
+                               tag 'dfsg-version-with-period', $version;
+                       } elsif ($version =~ /dsfg/) {
+                               tag 'dfsg-version-misspelled', $version;
+                       }
+               }
+
+               my $name = $info->field('package');
+               if ($name && $PERL_CORE_PROVIDES->known($name) &&
+                   perl_core_has_version($name, '>=', $upstream)) {
+                       my $core_version = $PERL_CORE_PROVIDES->value($name);
+                       tag "package-superseded-by-perl", "with $core_version"
+               }
+       } else {
+               tag "bad-version-number", "$version";
+       }
+}
+
+#---- Architecture
+
+if (not defined $info->field('architecture')) {
+       tag "no-architecture-field", "";
+} else {
+       my $archs = $info->field('architecture');
+
+       unfold("architecture", \$archs);
+
+       my @archs = split / /, $archs;
+
+       if (@archs > 1 && grep { $_ eq "any" || ($type ne "source" && $_ eq "all") } @archs) {
+               tag "magic-arch-in-arch-list", "";
+       }
+
+       for my $arch (@archs) {
+               tag "unknown-architecture", "$arch" unless $KNOWN_ARCHS->known($arch);
+       }
+
+       if ($type eq "binary") {
+               tag "too-many-architectures", "" if (@archs > 1);
+               tag "arch-any-in-binary-pkg", "" if (grep { $_ eq "any" } @archs);
+                tag "aspell-package-not-arch-all", ""
+                    if ($pkg =~ /^aspell-[a-z]{2}(-.*)?$/ && (@archs > 1 || $archs[0] ne 'all'));
+       }
+
+       # Used for later tests.
+       $arch_indep = 1 if (@archs == 1 && $archs[0] eq 'all');
+}
+
+#---- Subarchitecture (udeb)
+
+if (defined $info->field('subarchitecture')) {
+       my $subarch = $info->field('subarchitecture');
+
+       unfold("subarchitecture", \$subarch);
+}
+
+#---- Maintainer
+#---- Uploaders
+
+for my $f (qw(maintainer uploaders)) {
+       if (not defined $info->field($f)) {
+               tag "no-maintainer-field", "" if $f eq "maintainer";
+       } else {
+               my $maintainer = $info->field($f);
+
+               # Note, not expected to hit on uploaders anymore, as dpkg now strips
+               # newlines for the .dsc, and the newlines don't hurt in debian/control
+               unfold($f, \$maintainer);
+
+               if ($f eq "uploaders") {
+                       my @uploaders = split /\s*,\s*/, $maintainer;
+                       my %duplicate_uploaders;
+                       for my $uploader (@uploaders) {
+                               check_maintainer($uploader, "uploader");
+                               if ( ((grep { $_ eq $uploader } @uploaders) > 1) and
+                                    ($duplicate_uploaders{$uploader}++ == 0)) {
+                                       tag 'duplicate-uploader', $uploader;
+                               }
+                       }
+               } else {
+                       check_maintainer($maintainer, $f);
+                       if ($type eq 'source'
+                           && $maintainer =~ /\@lists(\.alioth)?\.debian\.org\b/
+                           && ! defined $info->field('uploaders')) {
+                               tag 'no-human-maintainers';
+                       }
+               }
+       }
+}
+
+if (defined $info->field('uploaders') && defined $info->field('maintainer')) {
+       my $maint = $info->field('maintainer');
+       tag 'maintainer-also-in-uploaders'
+               if $info->field('uploaders') =~ m/\Q$maint/;
+}
+
+#---- Source
+
+if (not defined $info->field('source')) {
+       tag "no-source-field" if $type eq "source";
+} else {
+       my $source = $info->field('source');
+
+       unfold("source", \$source);
+
+       if ($type eq 'source') {
+               if ($source ne $pkg) {
+                       tag "source-field-does-not-match-pkg-name", "$source != $pkg";
+               }
+       } else {
+               if ($source !~ /[A-Z0-9][-+\.A-Z0-9]+                      #Package name
+                               \s*
+                               (?:\((?:\d+:)?(?:[-\.+:A-Z0-9~]+?)(?:-[\.+A-Z0-9~]+)?\))?\s*$/ix) { #Version
+                       tag "source-field-malformed", "$source";
+               }
+       }
+}
+
+#---- Essential
+
+if (defined $info->field('essential')) {
+       my $essential = $info->field('essential');
+
+       unfold("essential", \$essential);
+
+       tag "essential-in-source-package", "" if ($type eq "source");
+       tag "essential-no-not-needed", "" if ($essential eq "no");
+       tag "unknown-essential-value", "" if ($essential ne "no" and $essential ne "yes");
+       tag "new-essential-package", "" if ($essential eq "yes" and ! $known_essential{$pkg});
+}
+
+#---- Section
+
+if (not defined $info->field('section')) {
+       tag 'no-section-field' if ($type eq 'binary');
+} else {
+       my $section = $info->field('section');
+
+       unfold("section", \$section);
+
+       if ($type eq 'udeb') {
+           unless ($section eq 'debian-installer') {
+               tag "wrong-section-for-udeb", "$section";
+           }
+       } else {
+           my @parts = split /\//, $section, 2;
+
+           if (scalar @parts > 1) {
+               tag "unknown-section", "$section" unless $known_archive_parts{$parts[0]};
+               tag "unknown-section", "$section" unless $known_sections{$parts[1]};
+           } elsif ($parts[0] eq 'unknown') {
+               tag "section-is-dh_make-template";
+           } else {
+               tag "unknown-section", "$section" unless $known_sections{$parts[0]};
+           }
+
+           # Check package name <-> section.
+           foreach my $map (@NAME_SECTION_MAPPINGS) {
+               if ($pkg =~ $map->[0]) {
+                   tag "wrong-section-according-to-package-name", "$pkg => $map->[1]"
+                       unless $parts[-1] eq $map->[1];
+                   last;
+               }
+           }
+        }
+}
+
+#---- Priority
+
+if (not defined $info->field('priority')) {
+       tag "no-priority-field", "" if $type eq "binary";
+} else {
+       my $priority = $info->field('priority');
+
+       unfold("priority", \$priority);
+
+       tag "unknown-priority", "$priority" if (! $known_prios{$priority});
+
+       if ($pkg =~ /-dbg$/) {
+               tag "debug-package-should-be-priority-extra", $pkg
+                   unless $priority eq 'extra';
+        }
+}
+
+#---- Standards-Version
+# handled in checks/standards-version
+
+#---- Description
+# handled in checks/description
+
+#--- Homepage
+
+if (defined $info->field('homepage')) {
+       my $homepage = $info->field('homepage');
+
+       unfold("homepage", \$homepage);
+
+       if ($homepage =~ /^<(?:UR[LI]:)?.*>$/i) {
+               tag "superfluous-clutter-in-homepage", $homepage;
+       }
+
+       require URI;
+       my $uri = URI->new($homepage);
+
+       unless ($uri->scheme) { # not an absolute URI
+               tag "bad-homepage", $homepage;
+       }
+
+       if ($homepage =~ m,/search\.cpan\.org/.*-[0-9._]+/*$,) {
+               tag 'homepage-for-cpan-package-contains-version', $homepage;
+       }
+} elsif ($type eq "binary" and not $info->native) {
+       tag "no-homepage-field";
+}
+
+#---- Installer-Menu-Item (udeb)
+
+if (defined $info->field('installer-menu-item')) {
+       my $menu_item = $info->field('installer-menu-item');
+
+       unfold('installer-menu-item', \$menu_item);
+
+       $menu_item =~ /^\d+$/ or tag "bad-menu-item", "$menu_item";
+}
+
+
+#---- Package relations (binary package)
+
+# Check whether the package looks like a meta-package, used for later
+# dependency checks.  We consider a package to possibly be a meta-package if
+# it is a binary package, arch: all, with no files outside of /usr/share/doc.
+my $metapackage = 0;
+if ($type eq 'binary' && $arch_indep) {
+       $metapackage = 1;
+       foreach my $file (keys %{$info->index}) {
+               $metapackage = 0 unless ($info->index->{$file}->{type} =~ /^d/
+                   || $file =~ m%^usr/share/doc/%);
+       }
+}
+if (($type eq "binary") || ($type eq 'udeb')) {
+       my (%deps, %fields, %parsed);
+       for my $field (qw(depends pre-depends recommends suggests conflicts provides enhances replaces breaks)) {
+               if (defined $info->field($field)) {
+                       #Get data and clean it
+                       my $data = $info->field($field);;
+                       unfold($field, \$data);
+                       $fields{$field} = $data;
+
+                       my (@seen_libstdcs, @seen_tcls, @seen_tclxs, @seen_tks, @seen_tkxs, @seen_libpngs);
+
+                       my $is_dep_field = sub { grep { $_ eq $_[0] } qw(depends pre-depends recommends suggests) };
+
+                       tag "alternates-not-allowed", "$field"
+                           if ($data =~ /\|/ && ! &$is_dep_field($field));
+
+                       for my $dep (split /\s*,\s*/, $data) {
+                               my (@alternatives, @seen_obsolete_packages);
+                               push @alternatives, [_split_dep($_), $_] for (split /\s*\|\s*/, $dep);
+
+                               if (&$is_dep_field($field)) {
+                                       push @seen_libstdcs, $alternatives[0]->[0]
+                                           if defined $known_libstdcs{$alternatives[0]->[0]};
+                                       push @seen_tcls, $alternatives[0]->[0]
+                                           if defined $known_tcls{$alternatives[0]->[0]};
+                                       push @seen_tclxs, $alternatives[0]->[0]
+                                           if defined $known_tclxs{$alternatives[0]->[0]};
+                                       push @seen_tks, $alternatives[0]->[0]
+                                           if defined $known_tks{$alternatives[0]->[0]};
+                                       push @seen_tkxs, $alternatives[0]->[0]
+                                           if defined $known_tkxs{$alternatives[0]->[0]};
+                                       push @seen_libpngs, $alternatives[0]->[0]
+                                           if defined $known_libpngs{$alternatives[0]->[0]};
+                               }
+
+                               # Only for (Pre-)?Depends.
+                               tag "virtual-package-depends-without-real-package-depends", "$field: $alternatives[0]->[0]"
+                                   if ($VIRTUAL_PACKAGES->known($alternatives[0]->[0])
+                                       && ($field eq "depends" || $field eq "pre-depends"));
+
+                                # Check defaults for transitions.  Here, we only care that the first alternative is current.
+                                tag "depends-on-old-emacs", "$field: $alternatives[0]->[0]"
+                                    if (&$is_dep_field($field) && $known_obsolete_emacs{$alternatives[0]->[0]});
+
+                               for my $part_d (@alternatives) {
+                                       my ($d_pkg, $d_version, $d_arch, $rest, $part_d_orig) = @$part_d;
+
+                                       tag "versioned-provides", "$part_d_orig"
+                                           if ($field eq "provides" && $d_version->[0]);
+
+                                       tag "breaks-without-version", "$part_d_orig"
+                                           if ($field eq "breaks" && !$d_version->[0]);
+
+                                       tag "obsolete-relation-form", "$field: $part_d_orig"
+                                           if ($d_version && grep { $d_version->[0] eq $_ } ("<", ">"));
+
+                                       tag "bad-version-in-relation", "$field: $part_d_orig"
+                                           if ($d_version->[0] && ! defined((_valid_version($d_version->[1]))[1]));
+
+                                       tag "package-relation-with-self", "$field: $part_d_orig"
+                                           if ($pkg eq $d_pkg) && ($field ne 'conflicts');
+
+                                       tag "bad-relation", "$field: $part_d_orig"
+                                           if $rest;
+
+                                       push @seen_obsolete_packages, $part_d_orig
+                                           if ($OBSOLETE_PACKAGES->known($d_pkg) && &$is_dep_field($field));
+
+                                       tag "depends-on-x-metapackage", "$field: $part_d_orig"
+                                           if ($known_x_metapackages{$d_pkg} && ! $metapackage && &$is_dep_field($field));
+
+                                       tag "depends-on-essential-package-without-using-version", "$field: $part_d_orig"
+                                           if ($known_essential{$d_pkg} && ! $d_version->[0] && &$is_dep_field($field));
+
+                                       tag "package-depends-on-an-x-font-package", "$field: $part_d_orig"
+                                           if ($field =~ /^(pre-)?depends$/ && $d_pkg =~ /^xfont.*/ && $d_pkg ne 'xfonts-utils' && $d_pkg ne 'xfongs-encodings');
+
+                                       tag "needlessly-depends-on-awk", "$field"
+                                           if ($d_pkg eq "awk" && ! $d_version->[0] && &$is_dep_field($field));
+
+                                       tag "depends-on-libdb1-compat", "$field"
+                                           if ($d_pkg eq "libdb1-compat" && $pkg !~ /^libc(6|6.1|0.3)/ && $field =~ /^(pre-)depends$/);
+
+                                       tag "depends-on-python-minimal", "$field",
+                                           if ($d_pkg =~ /^python[\d.]*-minimal$/ && &$is_dep_field($field)
+                                               && $pkg !~ /^python[\d.]*-minimal$/);
+
+                                       tag "doc-package-depends-on-main-package", "$field"
+                                           if ("$d_pkg-doc" eq $pkg && $field =~ /^(pre-)depends$/);
+
+                                       tag "old-versioned-python-dependency", "$field: $part_d_orig"
+                                           if ($d_pkg eq 'python' && $d_version->[0] eq '<<' && &$is_dep_field($field)
+                                               && $arch_indep && $pkg =~ /^python-/ && ! defined $info->field('python-version')
+                                               && ! $info->relation('depends')->implies('python-support'));
+
+                                       # only trigger this for the the preferred alternative
+                                       tag "versioned-dependency-satisfied-by-perl", "$field: $part_d_orig"
+                                               if $alternatives[0][-1] eq $part_d_orig
+                                               && &$is_dep_field($field)
+                                               && perl_core_has_version($d_pkg, $d_version->[0], $d_version->[1]);
+
+                                       tag "depends-exclusively-on-makedev", "$field",
+                                           if ($field eq 'depends' && $d_pkg eq 'makedev' && @alternatives == 1);
+                               }
+
+                               for my $pkg (@seen_obsolete_packages) {
+                                       if ($pkg eq $alternatives[0]->[0] or
+                                           scalar @seen_obsolete_packages == scalar @alternatives) {
+                                               tag "depends-on-obsolete-package", "$field: $pkg";
+                                       } else {
+                                               tag "ored-depends-on-obsolete-package", "$field: $pkg";
+                                       }
+                               }
+                       }
+                       tag "package-depends-on-multiple-libstdc-versions", @seen_libstdcs
+                           if (scalar @seen_libstdcs > 1);
+                       tag "package-depends-on-multiple-tcl-versions", @seen_tcls
+                           if (scalar @seen_tcls > 1);
+                       tag "package-depends-on-multiple-tclx-versions", @seen_tclxs
+                           if (scalar @seen_tclxs > 1);
+                       tag "package-depends-on-multiple-tk-versions", @seen_tks
+                           if (scalar @seen_tks > 1);
+                       tag "package-depends-on-multiple-tkx-versions", @seen_tkxs
+                           if (scalar @seen_tkxs > 1);
+                       tag "package-depends-on-multiple-libpng-versions", @seen_libpngs
+                           if (scalar @seen_libpngs > 1);
+               }
+       }
+
+       # If Conflicts or Breaks is set, make sure it's not inconsistent with
+       # the other dependency fields.
+       for my $conflict (qw/conflicts breaks/) {
+               next unless $fields{$conflict};
+               for my $field (qw(depends pre-depends recommends suggests)) {
+                       next unless $info->field($field);
+                       my $relation = $info->relation($field);
+                       for my $package (split /\s*,\s*/, $fields{$conflict}) {
+                               tag "conflicts-with-dependency", $field, $package
+                                   if $relation->implies($package);
+                       }
+               }
+       }
+}
+
+#---- Package relations (source package)
+
+if ($type eq "source") {
+
+       my $binpkgs = $info->binaries;
+
+       #Get number of arch-indep packages:
+       my $arch_indep_packages = 0;
+       my $arch_dep_packages = 0;
+       foreach my $binpkg (keys %$binpkgs) {
+               my $arch = $info->binary_field($binpkg, 'architecture');
+               if ($arch eq 'all') {
+                       $arch_indep_packages++;
+               } else {
+                       $arch_dep_packages++;
+               }
+       }
+
+       # Search through rules and determine which dependencies are required.
+       # The keys in %needed and %needed_clean are the dependencies; the
+       # values are the tags to use or the empty string to use the default
+       # tag.
+       my (%needed, %needed_clean, %allowed_clean, $bypass_needed_clean);
+       open (RULES, '<', "debfiles/rules")
+           or fail("cannot read debfiles/rules: $!");
+       my $target = "none";
+       my @rules = qw(clean binary-arch build-arch);
+        my $maybe_skipping;
+       while (<RULES>) {
+               if (/^ifn?(eq|def)\s/) {
+                       $maybe_skipping++;
+               } elsif (/^endif\s/) {
+                       $maybe_skipping--;
+               }
+               for my $rule (@GLOBAL_CLEAN_DEPENDS) {
+                       if ($_ =~ /$rule->[1]/) {
+                               if ($maybe_skipping) {
+                                       $allowed_clean{$rule->[0]} = 1;
+                               } else {
+                                       $needed_clean{$rule->[0]} = $rule->[2] || $needed_clean{$rule->[0]} || '';
+                               }
+                       }
+               }
+               for my $rule (@GLOBAL_CLEAN_ALLOWED) {
+                       if ($_ =~ /$rule->[1]/) {
+                               $allowed_clean{$rule->[0]} = 1;
+                       }
+               }
+               for my $rule (@GLOBAL_CLEAN_BYPASS) {
+                       if ($_ =~ /$rule/) {
+                               $bypass_needed_clean = 1;
+                       }
+               }
+               for my $rule (@GLOBAL_DEPENDS) {
+                       if ($_ =~ /$rule->[1]/ && !$maybe_skipping) {
+                               $needed{$rule->[0]} = $rule->[2] || $needed{$rule->[0]} || '';
+                       }
+               }
+               if (/^(\S+?):+(.*)/) {
+                       $target = $1;
+                       if (grep ($_ eq $target, @rules)) {
+                               push (@rules, split (' ', $2));
+                       }
+               }
+               if (grep ($_ eq $target, @rules)) {
+                       for my $rule (@RULE_CLEAN_DEPENDS) {
+                               if ($_ =~ /$rule->[1]/) {
+                                       if ($maybe_skipping) {
+                                               $allowed_clean{$rule->[0]} = 1;
+                                       } else {
+                                               $needed_clean{$rule->[0]} = $rule->[2] || $needed_clean{$rule->[0]} || '';
+                                       }
+                               }
+                       }
+                       for my $rule (@RULE_CLEAN_ALLOWED) {
+                               if ($_ =~ /$rule->[1]/) {
+                                       $allowed_clean{$rule->[0]} = 1;
+                               }
+                       }
+               }
+       }
+       close RULES;
+
+       tag "build-depends-indep-without-arch-indep", ""
+               if (defined $info->field('build-depends-indep') && $arch_indep_packages == 0);
+
+       my $is_dep_field = sub { grep { $_ eq $_[0] } qw(build-depends build-depends-indep) };
+
+       my %depend;
+       for my $field (qw(build-depends build-depends-indep build-conflicts build-conflicts-indep)) {
+               if (defined $info->field($field)) {
+                       #Get data and clean it
+                       my $data = $info->field($field);;
+                       unfold($field, \$data);
+                       $depend{$field} = $data;
+
+                       for my $dep (split /\s*,\s*/, $data) {
+                               my (@alternatives, @seen_obsolete_packages);
+                               push @alternatives, [_split_dep($_), $_] for (split /\s*\|\s*/, $dep);
+
+                               tag "virtual-package-depends-without-real-package-depends", "$field: $alternatives[0]->[0]"
+                                   if ($VIRTUAL_PACKAGES->known($alternatives[0]->[0]) && &$is_dep_field($field));
+
+                               for my $part_d (@alternatives) {
+                                       my ($d_pkg, $d_version, $d_arch, $rest, $part_d_orig) = @$part_d;
+
+                                       for my $arch (@{$d_arch->[0]}) {
+                                               if (!$KNOWN_ARCHS->known($arch)) {
+                                                       tag "invalid-arch-string-in-source-relation", "$arch [$field: $part_d_orig]"
+                                               }
+                                       }
+
+                                       tag "build-depends-on-build-essential", $field
+                                           if ($d_pkg eq "build-essential");
+
+                                       tag "depends-on-build-essential-package-without-using-version", "$d_pkg [$field: $part_d_orig]"
+                                           if ($known_build_essential{$d_pkg} && ! $d_version->[1]);
+
+                                       tag "build-depends-on-essential-package-without-using-version", "$field: $part_d_orig"
+                                           if ($d_pkg ne "coreutils" && $known_essential{$d_pkg} && ! $d_version->[0]);
+                                       push @seen_obsolete_packages, $part_d_orig
+                                           if ($OBSOLETE_PACKAGES->known($d_pkg) && &$is_dep_field($field));
+
+                                       tag "build-depends-on-x-metapackage", "$field: $part_d_orig"
+                                           if ($known_x_metapackages{$d_pkg} && &$is_dep_field($field));
+
+                                       tag "build-depends-on-1-revision", "$field: $part_d_orig"
+                                           if ($d_version->[0] eq '>=' && $d_version->[1] =~ /-1$/ && &$is_dep_field($field));
+
+                                       tag "bad-relation", "$field: $part_d_orig"
+                                           if $rest;
+
+                                       # only trigger this for the the preferred alternative
+                                       tag "versioned-dependency-satisfied-by-perl", "$field: $part_d_orig"
+                                               if $alternatives[0][-1] eq $part_d_orig
+                                               && &$is_dep_field($field)
+                                               && perl_core_has_version($d_pkg, $d_version->[0], $d_version->[1]);
+                               }
+
+                               for my $pkg (@seen_obsolete_packages) {
+                                       if ($pkg eq $alternatives[0]->[0] or
+                                           scalar @seen_obsolete_packages == scalar @alternatives) {
+                                               tag "build-depends-on-obsolete-package", "$field: $pkg";
+                                       } else {
+                                               tag "ored-build-depends-on-obsolete-package", "$field: $pkg";
+                                       }
+                               }
+                       }
+               }
+       }
+
+       # Check for duplicates.
+       my $build_all = $info->relation('build-depends-all');
+       my @dups = $build_all->duplicates;
+       for my $dup (@dups) {
+               tag "package-has-a-duplicate-build-relation", join (', ', @$dup);
+       }
+
+       # Make sure build dependencies and conflicts are consistent.
+       my %parsed;
+       for ($depend{'build-conflicts'}, $depend{'build-conflicts-indep'}) {
+               next unless $_;
+               for my $conflict (split /\s*,\s*/, $_) {
+                       if ($build_all->implies($conflict)) {
+                               tag "build-conflicts-with-build-dependency", $conflict;
+                       }
+               }
+       }
+
+       # Make sure that all the required build dependencies are there.  Don't
+       # issue missing-build-dependency errors for debhelper, since there's
+       # another test that does that and it would just be a duplicate.
+       my $build_regular = $info->relation('build-depends');
+       my $build_indep   = $info->relation('build-depends-indep');
+       for my $package (keys %needed_clean) {
+               my $tag = $needed_clean{$package} || 'missing-build-dependency';
+               unless ($build_regular->implies($package)) {
+                       if ($build_indep->implies($package)) {
+                               tag "clean-should-be-satisfied-by-build-depends", $package;
+                       } else {
+                               if ($tag eq 'missing-build-dependency') {
+                                       tag $tag, $package if $package ne 'debhelper';
+                               } else {
+                                       tag $tag;
+                               }
+                       }
+               }
+       }
+       my $noarch = $info->relation_noarch('build-depends-all');
+       for my $package (keys %needed) {
+               my $tag = $needed{$package} || 'missing-build-dependency';
+
+               # dh_python deactivates itself if the new Python build policy
+               # is enabled.
+               if ($tag eq 'missing-dh_python-build-dependency') {
+                       next if -f 'debfiles/pycomat';
+                       next if defined $info->field('python-version');
+               }
+               unless ($noarch->implies($package)) {
+                       if ($tag eq 'missing-build-dependency') {
+                               tag $tag, $package;
+                       } else {
+                               tag $tag;
+                       }
+               }
+       }
+
+       # This check is a bit tricky.  We want to allow in Build-Depends a
+       # dependency with any version, since reporting this tag over version
+       # mismatches would be confusing and quite likely wrong.  The approach
+       # taken is to strip the version information off all dependencies
+       # allowed in Build-Depends, strip the version information off of the
+       # dependencies in Build-Depends, and then allow any dependency in
+       # Build-Depends that's implied by the dependencies we require or allow
+       # there.
+       #
+       # We also have to map | to , when building the list of allowed
+       # packages so that the implications will work properly.
+       #
+       # This is confusing.  There should be a better way to do this.
+       if (defined $info->field('build-depends') && $arch_dep_packages == 0 && !$bypass_needed_clean) {
+               my $build_depends = $info->field('build-depends');
+               my @packages = split /\s*,\s*/, $build_depends;
+               my @allowed = map { s/\([^\)]+\)//g; s/\|/,/g; $_ } keys (%needed_clean), keys (%allowed_clean);
+               my $dep = Maemian::Relation->new_noarch(join(',', @allowed));
+               foreach my $pkg (@packages) {
+                       my $name = $pkg;
+                       $name =~ s/[\[\(][^\)\]]+[\)\]]//g;
+                       $name =~ s/\s+$//;
+                       $name =~ s/\s+/ /g;
+                       unless ($dep->implies($name)) {
+                               tag "build-depends-without-arch-dep", $name;
+                       }
+               }
+       }
+
+       my (@arch_dep_pkgs, @dbg_pkgs);
+       foreach my $binpkg (keys %$binpkgs) {
+               if ($binpkg =~ m/-dbg$/) {
+                       push @dbg_pkgs, $binpkg;
+               } elsif ($info->binary_field($binpkg, 'architecture') ne 'all') {
+                       push @arch_dep_pkgs, $binpkg;
+               }
+       }
+       foreach (@dbg_pkgs) {
+               my $deps;
+               $deps  = $info->binary_field($_, 'pre-depends') . ', ';
+               $deps .= $info->binary_field($_, 'depends');
+               tag 'dbg-package-missing-depends', $_
+                  unless (grep {my $quoted_name = qr<\Q$_>; $deps =~ m/(\s|,|^)$quoted_name(\s|,|$)/} @arch_dep_pkgs);
+       }
+}
+
+#----- Origin
+
+if (defined $info->field('origin')) {
+       my $origin = $info->field('origin');
+
+       unfold('origin', \$origin);
+
+       tag "redundant-origin-field", "" if lc($origin) eq 'debian';
+}
+
+#----- Bugs
+
+if (defined $info->field('bugs')) {
+       my $bugs = $info->field('bugs');
+
+       unfold('bugs', \$bugs);
+
+       tag "redundant-bugs-field"
+           if $bugs =~ m,^debbugs://bugs.debian.org/?$,i;
+}
+
+#----- Python-Version
+
+if (defined $info->field('python-version')) {
+       my $pyversion = $info->field('python-version');
+
+       unfold('python-version', \$pyversion);
+
+       my @valid = ([ '\d+\.\d+', '\d+\.\d+' ],
+                    [ '\d+\.\d+' ],
+                    [ '\>=\s*\d+\.\d+', '\<\<\s*\d+\.\d+' ],
+                    [ '\>=\s*\d+\.\d+' ],
+                    [ 'current', '\>=\s*\d+\.\d+' ],
+                    [ 'current' ],
+                    [ 'all' ]);
+
+       my @pyversion = split(/\s*,\s*/, $pyversion);
+       if (@pyversion > 2) {
+               if (grep { !/^\d+\.\d+$/ } @pyversion) {
+                       tag "malformed-python-version", "$pyversion";
+               }
+       } else {
+               my $okay = 0;
+               for my $rule (@valid) {
+                       if ($pyversion[0] =~ /^$rule->[0]$/
+                           && (($pyversion[1] && $rule->[1] && $pyversion[1] =~ /^$rule->[1]$/)
+                                || (! $pyversion[1] && ! $rule->[1]))) {
+                               $okay = 1;
+                               last;
+                       }
+               }
+               tag "malformed-python-version", "$pyversion" unless $okay;
+       }
+}
+
+#----- Dm-Upload-Allowed
+
+if (defined $info->field('dm-upload-allowed')) {
+       my $dmupload = $info->field('dm-upload-allowed');
+
+       unfold('dm-upload-allowed', \$dmupload);
+
+       unless ($dmupload eq 'yes') {
+               tag "malformed-dm-upload-allowed", "$dmupload";
+       }
+}
+
+#----- Vcs-*
+
+while (my ($vcs, $regex) = each %VCS_RECOMMENDED_URIS) {
+    if (defined $info->field("vcs-$vcs")) {
+       my $uri = $info->field("vcs-$vcs");
+       if ($uri !~ $regex) {
+           if ($VCS_VALID_URIS{$vcs} and $uri =~ $VCS_VALID_URIS{$vcs}) {
+               tag "vcs-field-uses-not-recommended-uri-format", "vcs-$vcs", $uri;
+           } else {
+               tag "vcs-field-uses-unknown-uri-format", "vcs-$vcs", $uri;
+           }
+       }
+    }
+}
+
+
+#----- Field checks (without checking the value)
+
+for my $field (glob("fields/*")) {
+       $field =~ s!^fields/!!;
+
+       next if ($field eq 'original-maintainer') and $version =~ /ubuntu/;
+
+       tag "obsolete-field", "$field"
+           if $known_obsolete_fields{$field};
+
+       tag "unknown-field-in-dsc", "$field"
+           if ($type eq "source" && ! $known_source_fields{$field} && ! $known_obsolete_fields{$field});
+
+       tag "unknown-field-in-control", "$field"
+           if ($type eq "binary" && ! $known_binary_fields{$field} && ! $known_obsolete_fields{$field});
+
+       tag "unknown-field-in-control", "$field"
+           if ($type eq "udeb" && ! $known_udeb_fields{$field} && ! $known_obsolete_fields{$field});
+}
+
+}
+
+# splits "foo (>= 1.2.3) [!i386 ia64]" into
+# ( "foo", [ ">=", "1.2.3" ], [ [ "i386", "ia64" ], 1 ], "" )
+#                                                  ^^^   ^^
+#                                 true, if ! was given   ||
+#           rest (should always be "" for valid dependencies)
+sub _split_dep {
+       my $dep = shift;
+       my ($pkg, $version, $darch) = ("", ["",""], [[],""]);
+
+       $pkg = $1 if $dep =~ s/^\s*([^\s\[\(]+)\s*//;
+
+       if (length $dep) {
+               if ($dep =~ s/\s* \( \s* (<<|<=|<|=|>=|>>|>) \s* ([^\s(]+) \s* \) \s*//x) {
+                       @$version = ($1, $2);
+               }
+               if ($dep && $dep =~ s/\s*\[([^\]]+)\]\s*//) {
+                       my $t = $1;
+                       $darch->[1] = 1 if ($t =~ s/!//g);
+                       $darch->[0] = [ split /\s+/, $t ];
+               }
+       }
+
+       return ($pkg, $version, $darch, $dep);
+}
+
+sub _valid_version {
+       my $ver = shift;
+
+       # epoch check means nothing here... This check is only useful to detect
+       # weird characters in version (and to get the debian revision)
+       if ($ver =~ m/^(\d+:)?([-\.+:~A-Z0-9]+?)(-[\.+~A-Z0-9]+)?$/i) {
+               return ($1, $2, $3);
+       } else {
+               return ();
+       }
+}
+
+sub perl_core_has_version {
+       my ($package, $op, $version) = @_;
+       my $core_version = $PERL_CORE_PROVIDES->value($package);
+       return 0 if !defined $core_version;
+       my @version = _valid_version($version);
+       return 0 if !@version;
+       return versions_compare($core_version, $op, $version);
+}
+
+sub unfold {
+       my $field = shift;
+       my $line = shift;
+
+       $$line =~ s/\n$//;
+
+       if ($$line =~ s/\n//g) {
+               tag "multiline-field", "$field";
+       }
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 8
+# End:
+# vim: syntax=perl sw=4 ts=4 noet shiftround
diff --git a/checks/fields.desc b/checks/fields.desc
new file mode 100644 (file)
index 0000000..48dca29
--- /dev/null
@@ -0,0 +1,986 @@
+Check-Script: fields
+Author: Marc 'HE' Brockschmidt <marc@marcbrockschmidt.de>
+Abbrev: fld
+Type: binary, udeb, source
+Unpack-Level: 1
+Needs-Info: debfiles, source-control-file
+Info: This script checks the syntax of the fields in package control files,
+ as described in the Policy Manual.
+
+Tag: unsupported-source-format
+Severity: serious
+Certainty: certain
+Info: This package uses a different source package format than 1.0.  At
+ present, only <tt>Format: 1.0</tt> packages are permitted by the Debian
+ archive software.  Newer package formats are supported by dpkg, but they
+ should not yet be used for upload to Debian.
+
+Tag: no-package-name
+Severity: serious
+Certainty: certain
+Info: The package does not have a "Package:" field in its control file.
+Ref: policy 5.3
+
+Tag: bad-package-name
+Severity: serious
+Certainty: certain
+Info: A package name should be at least two characters long, must consist
+ of the alphanumerics and "+" "-" and ".", and must start with an
+ alphanumeric character.
+Ref: policy 5.6.7
+
+Tag: package-not-lowercase
+Severity: serious
+Certainty: certain
+Info: New packages should not use uppercase characters in their names.
+Ref: policy 5.6.7
+
+Tag: no-version-field
+Severity: serious
+Certainty: certain
+Info: The package does not have a "Version:" field in its control file.
+Ref: policy 5.3
+
+Tag: bad-version-number
+Severity: serious
+Certainty: certain
+Info: The version number fails one of the syntactic requirements of dpkg.
+Ref: policy 5.6.12
+
+Tag: upstream-version-not-numeric
+Severity: important
+Certainty: certain
+Info: The upstream version number should start with a digit. 
+Ref: policy 5.6.12
+
+Tag: debian-revision-not-well-formed
+Severity: normal
+Certainty: certain
+Info: The debian version part (the part after the -) should consist of one
+ or two dot-separated parts: one for a regular maintainer release or two
+ for a source-NMU.
+Ref: devref 5.11.2, policy 5.6.12
+
+Tag: debian-revision-should-not-be-zero
+Severity: important
+Certainty: certain
+Info: The debian version part (the part after the -) should start with one,
+ not with zero. This is to ensure that a correctly-done Maintainer Upload will
+ always have a higher version number than a Non-Maintainer upload: a NMU could
+ have been prepared which introduces this upstream version with
+ Debian-revision -0.1
+Ref: devref 5.11.2
+
+Tag: no-architecture-field
+Severity: serious
+Certainty: certain
+Info: The package does not have an "Architecture:" field in its control file.
+Ref: policy 5.3
+
+Tag: magic-arch-in-arch-list
+Severity: serious
+Certainty: certain
+Info: The special architecture value "any" only make sense if it occurs
+ alone.  The value "all" may appear together with other architectures
+ in a *.dsc file but must occur alone if used in a binary package.
+Ref: policy 5.6.8
+
+Tag: unknown-architecture
+Severity: normal
+Certainty: possible
+Info: This package claims to be for an unknown architecture.  The
+ architecture should be one of the values supported by dpkg or one of the
+ special values "all" or "any".  The special value "source" is only used
+ in *.changes files and does not make sense in a binary package or a *.dsc
+ file.
+
+Tag: too-many-architectures
+Severity: serious
+Certainty: certain
+Info: A binary package should list exactly one architecture (the one it is
+ compiled for), or the special value "all" if it is architecture-independent.
+Ref: policy 5.6.8
+
+Tag: arch-any-in-binary-pkg
+Severity: serious
+Certainty: certain
+Info: The special architecture value "any" does not make sense in a binary
+ package.
+Ref: policy 5.6.8
+
+Tag: aspell-package-not-arch-all
+Severity: normal
+Certainty: certain
+Info: This package appears to be an aspell dictionary package, but it is
+ not Architecture: all.  The binary hashes should be built at install-time
+ by calling aspell-autobuildhash, so the contents of the package should be
+ architecture-independent.
+Ref: aspell-autobuildhash(8)
+
+Tag: no-maintainer-field
+Severity: serious
+Certainty: certain
+Info: The package does not have a "Maintainer:" field in its control file.
+Ref: policy 5.3
+
+Tag: maintainer-name-missing
+Severity: serious
+Certainty: certain
+Info: The maintainer field seems to contain just an email address. It must
+ contain the package maintainer's name and email address.
+Ref: policy 5.6.2
+
+Tag: maintainer-address-missing
+Severity: serious
+Certainty: certain
+Info: The maintainer field should contain the package maintainer's name and
+ email address, with the name followed by the address inside angle
+ brackets (&lt; and &gt;).  The address seems to be missing.
+Ref: policy 5.6.2
+
+Tag: maintainer-address-malformed
+Severity: important
+Certainty: certain
+Info: The maintainer field could not be parsed according to the rules in
+ the Policy Manual.
+Ref: policy 5.6.2
+
+Tag: maintainer-not-full-name
+Severity: normal
+Certainty: possible
+Info: The "name" part of this maintainer field is just one word, so it
+ might not be a full name.
+
+Tag: maintainer-address-looks-weird
+Severity: normal
+Certainty: possible
+Info: The maintainer address does not have whitespace between the name
+ and the email address.
+
+Tag: maintainer-address-is-on-localhost
+Severity: important
+Certainty: certain
+Info: The maintainer address includes localhost(.localdomain), which is
+ an invalid e-mail address.
+Ref: policy 5.6.2
+
+Tag: uploader-name-missing
+Severity: important
+Certainty: certain
+Info: The uploader field seems to contain just an email address. It must
+ contain the package uploader's name and email address.
+Ref: policy 5.6.2
+
+Tag: uploader-address-missing
+Severity: important
+Certainty: certain
+Info: The uploader field should contain the package uploader's name and
+ email address, with the name followed by the address inside angle
+ brackets (&lt; and &gt;).  The address seems to be missing.
+Ref: policy 5.6.2
+
+Tag: uploader-address-malformed
+Severity: important
+Certainty: certain
+Info: The uploader field could not be parsed according to the rules in
+ the Policy Manual.
+Ref: policy 5.6.2
+
+Tag: uploader-not-full-name
+Severity: normal
+Certainty: possible
+Info: The "name" part of this uploader field is just one word, so it
+ might not be a full name.
+
+Tag: uploader-address-looks-weird
+Severity: normal
+Certainty: possible
+Info: The uploader address does not have whitespace between the name
+ and the email address.
+
+Tag: uploader-address-is-on-localhost
+Severity: important
+Certainty: certain
+Info: The uploader address includes localhost(.localdomain), which is
+ an invalid e-mail address.
+Ref: policy 5.6.2
+
+Tag: wrong-debian-qa-address-set-as-maintainer
+Severity: important
+Certainty: certain
+Info: Orphaned packages should no longer have the address
+ &lt;debian-qa@lists.debian.org&gt; in the Maintainer field.
+ .
+ The correct Maintainer field for orphaned packages is
+ Debian QA Group &lt;packages@qa.debian.org&gt;.
+Ref: devref 5.9.4
+
+Tag: wrong-debian-qa-group-name
+Severity: important
+Certainty: certain
+Info: Orphaned packages should have "Debian QA Group
+ &lt;packages@qa.debian.org&gt;" in the maintainer field.
+Ref: devref 5.9.4
+
+Tag: no-human-maintainers
+Severity: normal
+Certainty: possible
+Info: The Maintainer address for this package is a mailing list and there
+ are no Uploaders listed.  Team-maintained packages should list the human
+ maintainers in the Uploaders field.
+Ref: devref 5.12
+
+Tag: no-source-field
+Severity: serious
+Certainty: certain
+Info: The package does not have a "Source:" field in its control file.
+Ref: policy 5.2
+
+Tag: source-field-does-not-match-pkg-name
+Severity: serious
+Certainty: certain
+Info: The source package's filename is not the same as the name given
+ in its Source field.  The Source field should name the package.
+Ref: policy 5.6.1
+
+Tag: source-field-malformed
+Severity: important
+Certainty: certain
+Info: In a binary package, the Source field should identify the source
+ package from which the package was compiled.  It should be the
+ source package name, optionally followed by a version number
+ between parentheses.
+Ref: policy 5.6.1
+
+Tag: essential-in-source-package
+Severity: important
+Certainty: certain
+Info: This field should only appear in binary packages.
+Ref: policy 5.6.9
+Tag: essential-no-not-needed
+Severity: normal
+Certainty: certain
+Info: Having "Essential: no" is the same as not having the field at all,
+ so it just makes the Packages file longer with no benefit.
+Ref: policy 5.6.9
+
+Tag: unknown-essential-value
+Severity: important
+Certainty: certain
+Info: The only valid values for the Essential field are yes and no.
+Ref: policy 5.6.9
+
+Tag: no-section-field
+Severity: normal
+Certainty: certain
+Info: The package does not have a "Section:" field in its control file.
+ .
+ The field is mandatory for source packages and optional for binary
+ packages, which use the source package's value as default is nothing
+ else is specified.
+Ref: policy 5.3
+
+Tag: unknown-section
+Severity: normal
+Certainty: certain
+Info: The "Section:" field in this package's control file is not one of
+ the sections in use on the ftp archive.  Valid sections are currently
+ admin, comm, cli-mono, database, debug, devel, doc,
+ editors, electronics, embedded, fonts, games, gnome, gnu-r,
+ gnustep, graphics, hamradio, haskell, httpd, interpreters,
+ java, kde, libdevel, libs, lisp, localization, kernel, mail,
+ math, misc, net, news, ocaml, oldlibs, otherosfs, perl,
+ php, python, ruby, science, shells, sound, tex, text,
+ utils, vcs, video, web, x11, xfce, zope.
+ .
+ The section name should be preceded by "non-free/" if the package
+ is in the non-free archive area, and by "contrib/" if the package
+ is in the contrib archive area.
+Ref: policy 2.4
+
+Tag: section-is-dh_make-template
+Severity: important
+Certainty: certain
+Info: The "Section:" field in this package's control file is set to
+ unknown.  This is not a valid section, and usually means a dh_make
+ template control file was used and never modified to set the correct
+ section.
+Ref: policy 2.4
+
+Tag: wrong-section-for-udeb
+Severity: normal
+Certainty: certain
+Info: udeb packages should have "Section: debian-installer".
+
+Tag: no-priority-field
+Severity: normal
+Certainty: certain
+Info: The package does not have a "Priority:" field in its control file.
+ .
+ The Priority field can be included in a binary package by passing
+ the -ip or -isp flags to dpkg-gencontrol when building the package.
+ The field is optional in binary packages.
+Ref: policy 5.3
+
+Tag: unknown-priority
+Severity: important
+Certainty: certain
+Info: The "Priority:" field in this package's control file is not one of
+ the priorities defined in the Policy Manual.
+Ref: policy 2.5
+
+Tag: superfluous-clutter-in-homepage
+Severity: normal
+Certainty: certain
+Info: The "Homepage:" field in this package's control file contains
+ superfluous markup around the URL, like enclosing &lt; and &gt;.
+ This is unnecessary and needlessly complicates using this information.
+Ref: policy 5.6.23
+
+Tag: bad-homepage
+Severity: normal
+Certainty: certain
+Info: The "Homepage:" field in this package's control file does not
+ contain a valid absolute URL. Most probably you forgot to specify
+ the scheme (e.g. http).
+
+Tag: no-homepage-field
+Severity: pedantic
+Certainty: possible
+Info: This non-native package lacks a <tt>Homepage</tt> field.  If the
+ package has an upstream home page that contains useful information or
+ resources for the end user, consider adding a <tt>Homepage</tt> control
+ field to <tt>debian/control</tt>.
+Ref: policy 5.6.23
+
+Tag: homepage-for-cpan-package-contains-version
+Severity: minor
+Certainty: certain
+Info: The Homepage field for this package points to CPAN and the URL
+ includes the version.  It's better to link to the unversioned CPAN page
+ so that the URL doesn't have to be updated for each new release.  For
+ example, use:
+ .
+   http://search.cpan.org/~samtregar/HTML-Template/
+ .
+ not:
+ .
+   http://search.cpan.org/~samtregar/HTML-Template-2.9/
+
+Tag: obsolete-field
+Severity: important
+Certainty: certain
+Info: This field is listed in the Policy Manual as obsolete and
+ not-to-be-present in any package.
+Ref: policy D.2.6
+
+Tag: unknown-field-in-dsc
+Severity: minor
+Certainty: certain
+Info: See the Policy Manual for a list of the possible fields in
+ a source package control file.
+Ref: policy 5.4
+
+Tag: unknown-field-in-control
+Severity: minor
+Certainty: possible
+Info: See the Policy Manual for a list of the possible fields in
+ a binary package control file.
+ .
+ In udeb packages the fields pre-depends, conflicts, essential and
+ suggests are disallowed, but they can contain the new fields
+ subarchitecture and installer-menu-item.
+Ref: policy 5.3 
+
+Tag: multiline-field
+Severity: important
+Certainty: certain
+Info: Most control fields must have only a single line of data.
+Ref: policy 5.1
+
+Tag: alternates-not-allowed
+Severity: important
+Certainty: certain
+Info: Only the "Depends", "Recommends", "Suggests" and "Pre-Depends"
+ fields may specify alternate dependencies using the "|" symbol.
+Ref: policy 7.1
+
+Tag: versioned-provides
+Severity: important
+Certainty: certain
+Ref: policy 7.1
+Info: The "Provides" field may not specify a version range.
+
+Tag: obsolete-relation-form
+Ref: policy 7.1
+Severity: normal
+Certainty: certain
+Info: The forms "&lt;" and "&gt;" mean "&lt;=" and "&gt;=", not "&lt;&lt;"
+ and "&gt;&gt;" as one might expect.  For that reason these forms are
+ obsolete, and should not be used in new packages.  Use the longer forms
+ instead.
+
+Tag: bad-version-in-relation
+Ref: policy 5.6.12
+Severity: important
+Certainty: certain
+Info: The version number used in this relationship does not match the
+ defined format of a version number.
+
+Tag: package-relation-with-self
+Severity: normal
+Certainty: possible
+Info: The package declares a relationship with itself.  This is not very
+ useful, except in the case of a package Conflicting with itself, if its
+ package name doubles as a virtual package.
+
+Tag: bad-relation
+Severity: important
+Certainty: certain
+Info: The package declares a relationship that could not be parsed according
+ to the rules given in the Policy Manual.
+Ref: policy 7.1
+
+Tag: new-essential-package
+Severity: important
+Certainty: possible
+Info: This package has the Essential flag set.  New Essential packages
+ are sufficiently rare that it seems worth warning about.  They should
+ be discussed on debian-devel first.
+Ref: policy 3.8
+
+Tag: doc-package-depends-on-main-package
+Severity: normal
+Certainty: possible
+Info: The name of this package suggests that it is a documentation package.
+ It is usually not desirable for documentation packages to depend on the
+ packages they document, because users may want to install the docs before
+ they decide whether they want to install the package.  Also, documentation
+ packages are often architecture-independent, so on other architectures
+ the package on which it depends may not even exist.
+
+Tag: depends-on-obsolete-package
+Severity: important
+Certainty: possible
+Info: The package depends on a package that has been superseded.
+ If the superseded package is part of an ORed group, it should not be
+ the first package in the group.
+
+Tag: ored-depends-on-obsolete-package
+Severity: minor
+Certainty: possible
+Info: The package depends on an ORed group of packages which includes
+ a package that has been superseded.
+
+Tag: build-depends-on-obsolete-package
+Severity: important
+Certainty: possible
+Info: The package build-depends on a package that has been superseded.
+ If the superseded package is part of an ORed group, it should not be
+ the first package in the group.
+
+Tag: ored-build-depends-on-obsolete-package
+Severity: minor
+Certainty: possible
+Info: The package build-depends on an ORed group of packages which includes
+ a package that has been superseded.
+
+Tag: depends-on-old-emacs
+Severity: normal
+Certainty: possible
+Info: The package lists an old version of Emacs as its first dependency.
+ It should probably be updated to support the current version of Emacs
+ in the archive and then list that version first in the list of Emacs
+ flavors it supports.
+ .
+ If the package intentionally only supports older versions of Emacs (if,
+ for example, it was included with later versions of Emacs), add a lintian
+ override.
+
+Tag: depends-on-x-metapackage
+Severity: important
+Certainty: certain
+Info: Packages that are not themselves metapackages must not depend on X
+ Window System metapackages.
+ .
+ The metapackages xorg, xorg-dev, x-window-system, x-window-system-dev, and
+ x-window-system-core exist only for the benefit of users and dependencies
+ for other metapackages and should not be used in regular package
+ dependencies.
+
+Tag: build-depends-on-x-metapackage
+Severity: important
+Certainty: certain
+Info: Packages must not build-depend on X Window System metapackages.
+ .
+ The metapackages xorg, xorg-dev, x-window-system, x-window-system-dev, and
+ x-window-system-core exist only for the benefit of users and should not
+ be used in package build dependencies.
+
+Tag: depends-on-essential-package-without-using-version
+Severity: important
+Certainty: certain
+Ref: policy 3.5
+Info: The package declares a depends on an essential package, e.g. dpkg,
+ without using a versioned depends.  Packages do not need to depend on
+ essential packages; essential means that they will always be present.
+ The only reason to list an explicit dependency on an essential package
+ is if you need a particular version of that package, in which case the
+ version should be given in the dependency.
+
+Tag: build-depends-on-essential-package-without-using-version
+Severity: important
+Certainty: certain
+Ref: policy 4.2
+Info: The package declares a build-depends on an essential package, e.g. dpkg,
+ without using a versioned depends.  Packages do not need to build-depend on
+ essential packages; essential means that they will always be present.
+ The only reason to list an explicit dependency on an essential package
+ is if you need a particular version of that package, in which case the
+ version should be given in the dependency.
+
+Tag: virtual-package-depends-without-real-package-depends
+Severity: normal
+Certainty: possible
+Info: The package declares a depends on a virtual package without listing a
+ real package as an alternative first.
+ .
+ If this package could ever be a build dependency, it should list a real
+ package as the first alternative to any virtual package in its Depends.
+ Otherwise, the build daemons will not be able to provide a consistent
+ build environment.
+ .
+ If it will never be a build dependency, this isn't necessary, but you may
+ want to consider doing so anyway if there is a real package providing
+ that virtual package that most users will want to use.
+
+Tag: invalid-arch-string-in-source-relation
+Severity: important
+Certainty: possible
+Ref: policy 5.6.8
+Info: The architecture string in the source relation does not follow policy.
+ A common cause of this is a comma in the arch, i.e. [i386, m68k], it should
+ be [i386 m68k].
+
+Tag: depends-on-build-essential-package-without-using-version
+Severity: important
+Certainty: certain
+Ref: policy 4.2
+Info: The package declares a depends on a build essential package without
+ using a versioned depends.  Packages do not have to build-depend on any
+ package included in build-essential.  It is the responsibility of anyone
+ building packages to have all build-essential packages installed.  The
+ only reason for an explicit dependency on a package included in
+ build-essential is if a particular version of that package is required,
+ in which case the dependency should include the version.
+
+Tag: package-depends-on-an-x-font-package
+Severity: important
+Certainty: certain
+Info: Packages must not depend on X Window System font packages.
+ .
+ If one or more of the fonts so packaged are necessary for proper operation
+ of the package with which they are associated the font package may be
+ Recommended; if the fonts merely provide an enhancement, a Suggests
+ relationship may be used.
+Ref: policy 11.8.5
+
+Tag: build-depends-indep-without-arch-indep
+Severity: important
+Certainty: certain
+Ref: policy 7.7
+Info: The control file specifies source relations for architecture-independent
+ packages, but no architecture-independent packages are built.
+
+Tag: build-depends-without-arch-dep
+Severity: minor
+Certainty: possible
+Ref: policy 7.7
+Info: The control file lists the given package in Build-Depends, but no
+ architecture-dependent packages are built. If all the packages built are
+ architecture-independent, the only packages that should be listed in
+ Build-Depends are those required to run the clean target (such as
+ debhelper if you use dh_clean). Other build dependencies should be listed
+ in Build-Depends-Indep instead.
+
+Tag: clean-should-be-satisfied-by-build-depends
+Severity: important
+Certainty: certain
+Ref: policy 7.7
+Info: The specified package is required to run the clean target of
+ <tt>debian/rules</tt> and therefore must be listed in Build-Depends, not
+ Build-Depends-Indep, even if no architecture-dependent packages are
+ built.
+
+Tag: missing-build-dependency
+Severity: important
+Certainty: certain
+Ref: policy 4.2
+Info: The package doesn't specify a build dependency on a package that is
+ used in <tt>debian/rules</tt>.
+ .
+ lintian intentionally does not take into account transitive dependencies.
+ Even if the package build-depends on some package that in turn
+ build-depends on the needed package, an explicit build dependency should
+ be added.  Otherwise, a latent bug is created that will appear without
+ warning if the other package is ever updated to change its dependencies.
+ Even if this seems unlikely, please always add explicit build
+ dependencies on every non-essential, non-build-essential package that is
+ used directly during the build.
+
+Tag: missing-python-build-dependency
+Severity: important
+Certainty: certain
+Ref: policy 4.2
+Info: The package appears to use Python as part of its build process in
+ <tt>debian/rules</tt> but doesn't depend on Python.
+ .
+ Normally, packages that use Python as part of the build process should
+ build-depend on one of python, python-all, python-dev, or python-all-dev
+ depending on whether they support multiple versions of Python and whether
+ they're building modules or only using Python as part of the package
+ build process.  Packages that depend on a specific version of Python may
+ build-depend on the appropriate pythonX.Y or pythonX.Y-dev package
+ instead.
+
+Tag: missing-dh_python-build-dependency
+Severity: important
+Certainty: certain
+Ref: dh_python(1)
+Info: The package runs dh_python in <tt>debian/rules</tt> but doesn't
+ build-depend on python or python-dev. dh_python requires
+ <tt>/usr/bin/python</tt> to run, so packages using dh_python must
+ build-depend on python (or python-dev or python-all-dev, which in turn
+ depend on python), even if they don't otherwise need Python to build.
+
+Tag: build-conflicts-with-build-dependency
+Severity: important
+Certainty: certain
+Ref: policy 7.7
+Info: The package build-conflicts with a package that it also
+ build-depends on.
+
+Tag: package-has-a-duplicate-build-relation
+Severity: normal
+Certainty: possible
+Info: The package declares the given build relations on the same package
+ in either Build-Depends or Build-Depends-Indep, but the build relations
+ imply each other and are therefore redundant.
+
+Tag: build-depends-on-1-revision
+Severity: normal
+Certainty: possible
+Info: The package declares a build dependency on a version of a package
+ with a -1 Debian revision such as "libfoo (&gt;= 1.2-1)".  Such a
+ dependency will not be satisfied by a backport of libfoo 1.2-1 and
+ therefore makes backporting unnecessarily difficult.  Normally, the -1
+ version is unneeded and a dependency such as "libfoo (&gt;= 1.2)" would
+ be sufficient.  If there was an earlier -0.X version of libfoo that would
+ not satisfy the dependency, use "libfoo (&gt;= 1.2-1~)" instead.
+
+Tag: needlessly-depends-on-awk
+Severity: important
+Certainty: certain
+Info: The package seems to declare a relation on awk. awk is a virtual
+ package, but it is special since it's de facto essential. If you don't
+ need to depend on a specific version of awk (which wouldn't work anyway,
+ as dpkg doesn't support versioned provides), you should remove the
+ dependency on awk.
+
+Tag: package-depends-on-multiple-libstdc-versions
+Severity: important
+Certainty: possible
+Info: The package seems to declare several relations to a libstdc version.
+ This is not only sloppy but in the case of libraries, it may well break
+ the runtime execution of programs.
+
+Tag: package-depends-on-multiple-tcl-versions
+Severity: important
+Certainty: possible
+Info: The package seems to declare several relations to a tcl version.
+ This is not only sloppy but in the case of libraries, it may well break
+ the runtime execution of programs.
+
+Tag: package-depends-on-multiple-tclx-versions
+Severity: important
+Certainty: possible
+Info: The package seems to declare several relations to a tclx version.
+ This is not only sloppy but in the case of libraries, it may well break
+ the runtime execution of programs.
+
+Tag: package-depends-on-multiple-tk-versions
+Severity: important
+Certainty: possible
+Info: The package seems to declare several relations to a tk version.
+ This is not only sloppy but in the case of libraries, it may well break
+ the runtime execution of programs.
+
+Tag: package-depends-on-multiple-tkx-versions
+Severity: important
+Certainty: possible
+Info: The package seems to declare several relations to a tkx version.
+ This is not only sloppy but in the case of libraries, it may well break
+ the runtime execution of programs.
+
+Tag: package-depends-on-multiple-libpng-versions
+Severity: important
+Certainty: possible
+Info: The package seems to declare several relations to a libpng version.
+ This is not only sloppy but in the case of libraries, it may well break
+ the runtime execution of programs.
+
+Tag: depends-on-libdb1-compat
+Severity: important
+Certainty: certain
+Info: The package seems to declare a relation on libdb1-compat.
+ This library exists for compatibility with applications built against
+ glibc 2.0 or 2.1. There is intentionally no corresponding development
+ package. Do not link new applications against this library!
+
+Tag: depends-on-python-minimal
+Severity: important
+Certainty: certain
+Info: The python-minimal package (and versioned variants thereof) exists
+ only to possibly become an Essential package.  Depending on it is always
+ an error since it should never be installed without python.  If it
+ becomes Essential, there is no need to depend on it, and until then,
+ packages that require Python must depend on python.
+
+Tag: depends-exclusively-on-makedev
+Severity: normal
+Certainty: certain
+Info: This package depends on makedev without a udev alternative.  This
+ probably means that it doesn't have udev rules and relies on makedev to
+ create devices, which won't work if udev is installed and running.
+ Alternatively, it may mean that there are udev rules, but udev was not
+ added as an alternative to the makedev dependency.
+
+Tag: dbg-package-missing-depends
+Severity: normal
+Certainty: certain
+Info: The given binary package has a name of the form of "X-dbg", indicating it
+ contains detached debugging symbols for the package X.  If so, it should
+ depend on the corresponding package, generally with (= ${binary:Version})
+ since the debugging symbols are only useful with the binaries created by
+ the same build.
+ .
+ If this package provides debugging symbols for multiple other
+ packages, it should normally depend on all of those packages as
+ alternatives.  In other words, <tt>pkga (= ${binary:Version}) | pkgb (=
+ ${binary:Version)</tt> and so forth.
+
+Tag: conflicts-with-dependency
+Severity: important
+Certainty: certain
+Ref: policy 7.4
+Info: The package seems to conflict with one of its dependencies,
+ recommendations, or suggestions by listing it in Conflicts or Breaks.
+
+Tag: breaks-without-version
+Severity: normal
+Certainty: possible
+Ref: policy 7.3
+Info: This package declares a Breaks relationship with another package
+ that has no version number.  Normally, Breaks should be used to indicate
+ an incompatibility with a specific version of another package, or with
+ all versions predating a fix.  If the two packages can never be installed
+ at the same time, Conflicts should normally be used instead.
+
+Tag: bad-menu-item
+Severity: important
+Certainty: certain
+Info: The field Installer-Menu-Item should only contain positive integer
+ values.
+
+Tag: redundant-origin-field
+Severity: normal
+Certainty: certain
+Info: You use the Origin field though the field value is the default (Debian).
+ In this case the field is redundant and should be removed.
+
+Tag: binary-nmu-uses-old-version-style
+Severity: normal
+Certainty: certain
+Ref: devref 5.10.2.1
+Info: The version number of a binary NMU should be formed by appending
+ <tt>+b</tt> and a digit to the source version.  This version scheme is
+ special-cased by the archive software.  The -x.x.x version number style
+ should no longer be used.
+
+Tag: binary-nmu-debian-revision-in-source
+Severity: normal
+Certainty: certain
+Ref: devref 5.10.2.1
+Info: The version number of your source package ends in +b and a number or
+ has a Debian revision containing three parts.  These version numbers are
+ used by binary NMUs and should not be used as the source version.  (The
+ +b form is the current standard; the three-part version number now
+ obsolete.)
+
+Tag: dfsg-version-in-native-package
+Severity: normal
+Certainty: certain
+Info: The version number of this package contains "dfsg", but it's a
+ native package.  "dfsg" is conventionally used in the upstream version of
+ packages that are repackaged for Debian Free Software Guidelines
+ compliance reasons.  The convention doesn't make sense in native
+ packages.
+
+Tag: dfsg-version-with-period
+Severity: minor
+Certainty: possible
+Info: The version number of this package contains ".dfsg", probably in a
+ form like "1.2.dfsg1".  There is a suble sorting problem with this
+ version method: 1.2.dfsg1 is considered a later version than 1.2.1.  If
+ upstream adds another level to its versioning, finding a good version
+ number for the next upstream release will be awkward.
+ .
+ Upstream may never do this, in which case this isn't a problem, but it's
+ normally better to use "+dfsg" instead (such as "1.2+dfsg1").  "+" sorts
+ before ".", so 1.2 &lt; 1.2+dfsg1 &lt; 1.2.1 as normally desired.
+
+Tag: dfsg-version-misspelled
+Severity: minor
+Certainty: certain
+Info: The version number of this package contains "dsfg".  You probably
+ meant "dfsg", the conventional marker for upstream packages that are
+ repackaged for Debian Free Software Guidelines compliance reasons.
+
+Tag: redundant-bugs-field
+Severity: normal
+Certainty: certain
+Info: You use the Bugs field though the field value is the default 
+ (debbugs://bugs.debian.org/). In this case the field is redundant and
+ should be removed.
+
+Tag: build-depends-on-build-essential
+Info: You depend on the build-essential package, which is only a
+Severity: important
+Certainty: certain
+Ref: policy 7.7
+ meta-package depending on build tools that have to be installed in all
+ build environments.
+
+Tag: malformed-python-version
+Severity: important
+Certainty: certain
+Ref: python-policy 2.3
+Info: The Python-Version control field is not in one of the valid
+ formats.  It should be in one of the following formats:
+ .
+     all
+     current
+     current, &gt;= X.Y
+     &gt;= X.Y
+     &gt;= A.B, &lt;&lt; X.Y
+     A.B, X.Y
+ .
+ (One or more specific versions may be listed with the last form.)  A.B
+ and X.Y should be Python versions.
+
+Tag: old-versioned-python-dependency
+Severity: normal
+Certainty: certain
+Info: This package appears to be an architecture-independent Python module
+ but has a dependency on a version of python less than a particular
+ version, doesn't use python-support and no Python-Version control field.
+ This normally means that the package isn't using the current Python
+ policy; most architecture-independent Python packages will work with any
+ future version of Python if they follow the new policy.
+ .
+ If this package really does require only a particular range of Python
+ versions and uses python-central, add a Python-Version control field (as
+ described in 2.3 of the Python policy) to resolve this warning.
+
+Tag: malformed-dm-upload-allowed
+Severity: important
+Certainty: certain
+Ref: http://www.debian.org/vote/2007/vote_003
+Info: The Dm-Upload-Allowed field in this package is set to something
+ other than "yes".  The only standardized value for this field in the
+ Debian GR is "yes" and other values (including capitalization variants)
+ may not work as expected.
+
+Tag: wrong-section-according-to-package-name
+Severity: normal
+Certainty: certain
+Info: This package has a name suggesting that it belongs to a section
+ other than the one it is currently categorized in.
+
+Tag: debug-package-should-be-priority-extra
+Severity: normal
+Certainty: certain
+Info: This package has a name suggesting that it contains detached
+ debugging symbols.  If so, it should have priority "extra" since users
+ normally do not need such packages.
+
+Tag: maintainer-also-in-uploaders
+Severity: minor
+Certainty: certain
+Info: The maintainer value also appears on the <tt>Uploaders</tt> field.
+ There were some reasons why this was useful when Uploaders support was
+ first introduced, but those have long-since been fixed and there is no
+ longer any need to list the maintainer in Uploaders.  The duplicate
+ information should probably be removed.
+
+Tag: duplicate-uploader
+Severity: minor
+Certainty: certain
+Info: The uploader appears more than once in the <tt>Uploaders</tt>
+ field.  The duplicate information should be removed.
+
+Tag: versioned-dependency-satisfied-by-perl
+Severity: normal
+Certainty: certain
+Info: This package declares an unnecessary versioned dependency
+ on a package that is also provided by one of the Perl core packages
+ (perl, perl-base, perl-modules) with at least the required version.
+ .
+ As versioned dependencies are not satisfied by provided packages,
+ this unnecessarily pulls in a separately packaged newer version
+ of the module.
+ .
+ The recommended way to express the dependency without needless
+ complications on backporting packages is to use alternative dependencies.
+ The Perl core package should be the preferred alternative and the
+ versioned dependency a secondary one.
+ .
+ Example: perl-modules (&gt;= 5.10.0) | libmodule-build-perl (&gt;= 0.26)
+Ref: policy 7.5
+
+Tag: package-superseded-by-perl
+Severity: normal
+Certainty: certain
+Info: This package is also provided by one of the Perl core packages
+ (perl, perl-base, perl-modules), and the core version is at least
+ as new as this one.
+ .
+ The package should either be upgraded to a newer upstream version
+ or removed from the archive as unnecessary. In the removal case, any
+ versioned dependencies on this package must first be changed to include
+ the Perl core package (because versioned dependencies are not satisfied
+ by provided packages).
+ .
+ The recommended way to express the dependency without needless
+ complications on backporting packages is to use alternative dependencies.
+ The Perl core package should be the preferred alternative and the
+ versioned dependency a secondary one.
+ .
+ Example: perl-modules (&gt;= 5.10.0) | libmodule-build-perl (&gt;= 0.26)
+Ref: policy 7.5
+
+Tag: vcs-field-uses-not-recommended-uri-format
+Severity: minor
+Certainty: possible
+Info: The VCS-* field uses an URI which doesn't match the recommended
+ format, but still looks valid. Examples for not recommended URI formats
+ are protocols that require authentication (like SSH). Instead where
+ possible you should provide an URI that is accessible for everyone
+ without authentication.
+
+Tag: vcs-field-uses-unknown-uri-format
+Severity: normal
+Certainty: possible
+Info: The VCS-* field uses an URI which doesn't match any known format.
+ You might have forgotten the protocol before the hostname.
diff --git a/checks/files b/checks/files
new file mode 100644 (file)
index 0000000..5a1eed3
--- /dev/null
@@ -0,0 +1,1088 @@
+# files -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Christian Schwarz and Richard Braakman
+#
+# 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 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.
+
+package Maemian::files;
+use strict;
+use Tags;
+use Util;
+use Maemian::Data;
+
+our $FONT_PACKAGES;
+
+# A list of known packaged Javascript libraries
+# and the packages providing them
+our @jslibraries = (
+    [ qr,(?i)mochikit\.js(\.gz)?$, => qr'libjs-mochikit' ],
+    [ qr,(?i)jquery(\.(min|lite|pack))?\.js(\.gz)?$, => qr'libjs-jquery' ],
+    [ qr,(?i)prototype(-[\d\.]+)?\.js(\.gz)?$, => qr'libjs-prototype' ],
+    [ qr,(?i)scriptaculous\.js(\.gz)?$, => qr'libjs-scriptaculous' ],
+    [ qr,(?i)fckeditor\.js(\.gz)?$, => qr'fckeditor' ],
+    [ qr,(?i)cropper(\.uncompressed)?\.js(\.gz)?$, => qr'libjs-cropper' ],
+    [ qr,(?i)swfobject\.js(\.gz)?$, => qr'libjs-yui' ],
+    [ qr,(?i)(yahoo|yui)-(dom-event|min)\.js(\.gz)?$, => qr'libjs-yui' ],
+# Disabled due to false positives.  Needs a content check adding to verify
+# that the file being checked is /the/ yahoo.js
+#    [ qr,(?i)yahoo\.js(\.gz)?$, => qr'libjs-yui' ],
+    [ qr,(?i)jsjac(\.packed)?\.js(\.gz)?$, => qr'libjs-jac' ],
+    [ qr,(?i)jsMath(-fallback-\w+)?\.js(\.gz)?$, => qr'jsmath' ],
+    [ qr,(?i)tiny_mce(_(popup|src))?\.js(\.gz)?$, => qr'tinymce2?' ],
+# not yet available in unstable:
+#    [ qr,(?i)(htmlarea|Xinha(Loader|Core))\.js$, => qr'xinha' ],
+);
+
+# A list of known packaged PEAR modules
+# and the packages providing them
+our @pearmodules = (
+    [ qr,(?<!Auth/)HTTP\.php$, => 'php-http' ],
+    [ qr,Auth\.php$, => 'php-auth' ],
+    [ qr,Auth/HTTP\.php$, => 'php-auth-http' ],
+    [ qr,Benchmark/(Timer|Profiler|Iterate)\.php$, => 'php-benchmark' ],
+    [ qr,Cache\.php$, => 'php-cache' ],
+    [ qr,Cache/Lite\.php$, => 'php-cache-lite' ],
+    [ qr,Compat\.php$, => 'php-compat' ],
+    [ qr,Config\.php$, => 'php-config' ],
+    [ qr,CBC\.php$, => 'php-crypt-cbc' ],
+    [ qr,Date\.php$, => 'php-date' ],
+    [ qr,(?<!Container)/DB\.php$, => 'php-db' ],
+    [ qr,(?<!Container)/File\.php$, => 'php-file' ],
+    [ qr,Log\.php$, => 'php-log' ],
+    [ qr,Log/(file|error_log|null|syslog|sql\w*)\.php$, => 'php-log' ],
+    [ qr,Mail\.php$, => 'php-mail' ],
+    [ qr,(?i)mime(Part)?\.php$, => 'php-mail-mime' ],
+    [ qr,mimeDecode\.php$, => 'php-mail-mimedecode' ],
+    [ qr,FTP\.php$, => 'php-net-ftp' ],
+    [ qr,(?<!Container/)IMAP\.php$, => 'php-net-imap' ],
+    [ qr,SMTP\.php$, => 'php-net-smtp' ],
+    [ qr,(?<!FTP/)Socket\.php$, => 'php-net-socket' ],
+    [ qr,IPv4\.php$, => 'php-net-ipv4' ],
+    [ qr,(?<!Container/)LDAP\.php$, => 'php-net-ldap' ],
+);
+
+# A list of known packaged php (!PEAR) libraries
+# and the packages providing them
+our @phplibraries = (
+    [ qr,(?i)adodb\.inc\.php$, => 'libphp-adodb' ],
+    [ qr,(?i)Smarty(_Compiler)?\.class\.php$, => 'smarty' ],
+    [ qr,(?i)class\.phpmailer(\.(php|inc))+$, => 'libphp-phpmailer' ],
+    [ qr,(?i)phpsysinfo\.dtd$, => 'phpsysinfo' ],
+    [ qr,(?i)class\.(Linux|(Open|Net|Free|)BSD)\.inc\.php$, => 'phpsysinfo' ],
+    [ qr,Auth/(OpenID|Yadis/Yadis)\.php$, => 'php-openid' ],
+    [ qr,(?i)Snoopy\.class\.(php|inc)$, => 'libphp-snoopy' ],
+    [ qr,(?i)markdown\.php$, => 'libmarkdown-php' ],
+    [ qr,(?i)geshi\.php$, => 'php-geshi' ],
+    [ qr,(?i)(class[.-])?pclzip\.(inc|lib)?\.php$, => 'libphp-pclzip' ],
+    [ qr,(?i).*layersmenu.*/(lib/)?PHPLIB\.php$, => 'libphp-phplayersmenu' ],
+    [ qr,(?i)phpSniff\.(class|core)\.php$, => 'libphp-phpsniff' ],
+    [ qr,(?i)(class\.)?jabber\.php$, => 'libphp-jabber' ],
+    [ qr,(?i)simplepie(\.(php|inc))+$, => 'libphp-simplepie' ],
+    [ qr,(?i)jpgraph\.php$, => 'libphp-jpgraph' ],
+    [ qr,(?i)fpdf\.php$, => 'php-fpdf' ],
+    [ qr,(?i)getid3\.(lib\.)?(\.(php|inc))+$, => 'php-getid3' ],
+    [ qr,(?i)streams\.php$, => 'php-gettext' ],
+    [ qr,(?i)rss_parse\.(php|inc)$, => 'libphp-magpierss' ],
+    [ qr,(?i)unit_tester\.php$, => 'php-simpletest' ],
+    [ qr,(?i)Sparkline\.php$, => 'libsparkline-php' ],
+# not yet available in unstable:,
+#    [ qr,(?i)IXR_Library(\.inc|\.php)+$, => 'libphp-ixr' ],
+#    [ qr,(?i)(class\.)?kses\.php$, => 'libphp-kses' ],
+);
+
+# A list of known non-free flash executables
+our @flash_nonfree = (
+    qr<(?i)dewplayer(?:-\w+)?\.swf$>,
+    qr<(?i)(?:mp3|flv)player\.swf$>,
+# Situation needs to be clarified:
+#    qr,(?i)multipleUpload\.swf$,
+#    qr,(?i)xspf_jukebox\.swf$,
+);
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+my $file;
+my $source_pkg = "";
+my $pkg_section = "";
+my $is_python;
+my $is_perl;
+my $has_binary_perl_file;
+my @nonbinary_perl_files_in_lib;
+
+my %linked_against_libvga;
+
+# read data from objdump-info file
+foreach my $file (sort keys %{$info->objdump_info}) {
+    my $objdump = $info->objdump_info->{$file};
+    $file = './' . $file;
+
+    if (defined $objdump->{NEEDED}) {
+       my $lib = $objdump->{NEEDED};
+       $linked_against_libvga{$file} = 1
+           if $lib =~ m/libvga/;
+    }
+}
+
+# Get source package name, if possible.
+if (defined $info->field('source')) {
+    $source_pkg = $info->field('source') || "";
+}
+
+# Get section.
+if (defined $info->field('section')) {
+   $pkg_section = $info->field('section');
+}
+
+# find out which files are scripts
+my %script = map {$_ => 1} (sort keys %{$info->scripts});
+
+# We only want to warn about these once.
+my $warned_x11_predepends = 0;
+my $warned_debug_name = 0;
+
+my @devhelp;
+my @devhelp_links;
+
+# Read package contents...
+foreach my $file (sort keys %{$info->index}) {
+    next if $file eq "";
+    my $index_info = $info->index->{$file};
+    my $owner = $index_info->{owner} . '/' . $index_info->{group};
+    my $operm = $index_info->{operm};
+    my $link = $index_info->{link};
+    if ($index_info->{type} eq 'h') {
+       my $link_target_dir = $link;
+       $link_target_dir =~ s,[^/]*$,,;
+
+       # It may look weird to sort the file and link target here, but since
+       # it's a hard link, both files are equal and either could be
+       # legitimately reported first.  tar will generate different tar files
+       # depending on the hashing of the directory, and this sort produces
+       # stable lintian output despite that.
+       #
+       # TODO: actually, policy says 'conffile', not '/etc' -> extend!
+       tag "package-contains-hardlink", join (' -> ', sort ($file, $link))
+           if $file =~ m,^etc/,
+               or $link =~ m,^etc/,
+               or $file !~ m,^\Q$link_target_dir\E[^/]*$,;
+    }
+
+    my ($year) = ($index_info->{date} =~ /^(\d{4})/);
+    if ( $year <= 1984 ) { # value from dak CVS: Dinstall::PastCutOffYear
+       tag "package-contains-ancient-file", "$file " . $index_info->{date};
+    }
+
+    if (!($index_info->{uid} < 100 || $index_info->{uid} == 65534
+         || ($index_info->{uid} >= 60000 && $index_info->{uid} < 65000))
+       || !($index_info->{gid} < 100 || $index_info->{gid} == 65534
+            || ($index_info->{gid} >= 60000 && $index_info->{gid} < 65000))) {
+       tag "wrong-file-owner-uid-or-gid", $file, $index_info->{uid} . '/' . $index_info->{gid};
+    }
+
+    # *.devhelp and *.devhelp2 files must be accessible from a directory in
+    # the devhelp search path: /usr/share/devhelp/books and
+    # /usr/share/gtk-doc/html.  We therefore look for any links in one of
+    # those directories to another directory.  The presence of such a link
+    # blesses any file below that other directory.
+    if (defined $link and $file =~ m,usr/share/(?:devhelp/books|gtk-doc/html)/,) {
+       my $blessed = $link;
+       if ($blessed !~ m,^/,) {
+           my $base = $file;
+           $base =~ s,/+[^/]+$,,;
+           while ($blessed =~ s,^\.\./,,) {
+               $base =~ s,/+[^/]+$,,;
+           }
+           $blessed = "$base/$blessed";
+       }
+       push (@devhelp_links, $blessed);
+    }
+
+    # ---------------- /etc
+    if ($file =~ m,^etc/,) {
+       if ($file =~ m,^etc/nntpserver, ) {
+           tag "package-uses-obsolete-file", "$file";
+       }
+       # ---------------- /etc/cron.daily, etc.
+       elsif ($file =~ m,^etc/cron\.(?:daily|hourly|monthly|weekly)/[^\.].*\., ) {
+           tag "run-parts-cron-filename-contains-full-stop", "$file";
+       }
+       # ---------------- /etc/cron.d
+       elsif ($file =~ m,^etc/cron\.d/\S, and $operm != 0644) {
+           tag "bad-permissions-for-etc-cron.d-script", sprintf("$file %04o != 0644",$operm);
+       }
+       # ---------------- /etc/emacs.*
+       elsif ($file =~ m,^etc/emacs.*/\S, and $index_info->{type} =~ m,^[-h],
+              and $operm != 0644) {
+           tag "bad-permissions-for-etc-emacs-script", sprintf("$file %04o != 0644",$operm);
+       }
+       # ---------------- /etc/gconf/schemas
+       elsif ($file =~ m,^etc/gconf/schemas/\S,) {
+           tag "package-installs-into-etc-gconf-schemas", "$file";
+       }
+       # ---------------- /etc/init.d
+       elsif ($file =~ m,^etc/init\.d/\S,
+              and $file !~ m,^etc/init\.d/(?:README|skeleton)$,
+              and $operm != 0755
+              and $index_info->{type} =~ m,^[-h],) {
+           tag "non-standard-file-permissions-for-etc-init.d-script",
+               sprintf("$file %04o != 0755",$operm);
+       }
+       #----------------- /etc/pam.conf
+       elsif ($file =~ m,^etc/pam.conf, and $pkg ne "libpam-runtime" ) {
+           tag "config-file-reserved", "$file by libpam-runtime";
+       }
+       # ---------------- /etc/rc.d
+       elsif ($type ne 'udeb' and $file =~ m,^etc/rc\.d/\S, and $pkg !~ /^(?:sysvinit|file-rc)$/) {
+           tag "package-installs-into-etc-rc.d", "$file";
+       }
+       # ---------------- /etc/rc?.d
+       elsif ($type ne 'udeb' and $file =~ m,^etc/rc(?:\d|S)\.d/\S, and $pkg !~ /^(?:sysvinit|file-rc)$/) {
+           tag "package-installs-into-etc-rc.d", "$file";
+       }
+       # ---------------- /etc/rc.boot
+       elsif ($file =~ m,^etc/rc\.boot/\S,) {
+           tag "package-installs-into-etc-rc.boot", "$file";
+       }
+    }
+    # ---------------- /usr
+    elsif ($file =~ m,^usr/,) {
+       # ---------------- /usr/share/doc
+       if ($file =~ m,^usr/share/doc/\S,) {
+           if ($type eq 'udeb') {
+               tag "udeb-contains-documentation-file", "$file";
+           } else {
+               # file not owned by root?
+               if ($owner ne 'root/root') {
+                   tag "bad-owner-for-doc-file", "$file $owner != root/root";
+               }
+
+               # file directly in /usr/share/doc ?
+               if ($index_info->{type} =~ m/^[-h]/ and $file =~ m,^usr/share/doc/[^/]+$,) {
+                   tag "file-directly-in-usr-share-doc", "$file";
+               }
+
+               # executable in /usr/share/doc ?
+               if ($index_info->{type} =~ m/^[-h]/ and
+                   $file !~ m,^usr/share/doc/(?:[^/]+/)?examples/, and
+                   ($operm & 01 or $operm & 010 or $operm & 0100)) {
+                   if ($script{$file}) {
+                       tag "script-in-usr-share-doc", "$file";
+                   } else {
+                       tag "executable-in-usr-share-doc", $file, (sprintf "%04o", $operm);
+                   }
+               }
+
+               # zero byte file in /usr/share/doc/
+               if ($index_info->{size} == 0 and $index_info->{type} =~ m,^-,) {
+                   # Exceptions: examples may contain empty files for various
+                   # reasons, Doxygen generates empty *.map files, and Python
+                   # uses __init__.py to mark module directories.
+                   unless ($file =~ m,^usr/share/doc/(?:[^/]+/)?examples/,
+                           or $file =~ m,^usr/share/doc/(?:.+/)?html/.*\.map$,
+                           or $file =~ m,^usr/share/doc/(?:.+/)?__init__\.py$,) {
+                       tag "zero-byte-file-in-doc-directory", "$file";
+                   }
+               }
+               # gzipped zero byte files:
+               # 276 is 255 bytes (maximal length for a filename) + gzip overhead
+               if ($file =~ m,.gz$, and $index_info->{size} <= 276
+                   and $index_info->{type} =~ m,^[-h],
+                   and $info->file_info->{$file} =~ m/gzip compressed/) {
+                   unless (`gzip -dc unpacked/$file`) {
+                       tag "zero-byte-file-in-doc-directory", "$file";
+                   }
+               }
+
+               # contains an INSTALL file?
+               my $tmp = quotemeta($pkg);
+               if ($file =~ m,^usr/share/doc/$tmp/INSTALL(?:\..+)*$,) {
+                   tag "package-contains-upstream-install-documentation", "$file";
+               }
+
+               # contains a README for another distribution/platform?
+               if ($file =~ m,^usr/share/doc/$tmp/readme\.(?:apple|aix|atari|be|beos|bsd|bsdi|
+                               cygwin|darwin|irix|gentoo|freebsd|mac|macos|macosx|netbsd|
+                               openbsd|osf|redhat|sco|sgi|solaris|suse|sun|vms|win32|win9x|
+                               windows)(?:\.txt)?(?:\.gz)?$,xi){
+                   tag "package-contains-readme-for-other-platform-or-distro", "$file";
+               }
+           }
+       }
+       # ---------------- /usr/doc
+       elsif ($file =~ m,^usr/doc/\S,) {
+           if ($file =~ m,^usr/doc/examples/\S+, and $index_info->{type} eq 'd') {
+               tag "old-style-example-dir", "$file";
+           }
+       }
+       # ---------------- /usr/X11R6/lib/X11/app-defaults
+       elsif ($file =~ m,usr/X11R6/lib/X11/app-defaults,) {
+           tag "old-app-defaults-directory", "$file";
+       }
+
+       #----------------- /usr/{include,lib}/X11/
+       # Packages installing files here will need to pre-depend on x11-common
+       # so that the symlinks will be sorted out first on a sarge upgrade.
+       elsif ($file =~ m,^usr/(?:include|lib)/X11(/|\Z),
+              && !$warned_x11_predepends && $pkg ne 'x11-common') {
+           tag "file-in-usr-something-x11-without-pre-depends", "$file"
+               unless $info->relation('pre-depends')->implies('x11-common (>= 1:7.0.0)');
+
+           # Always set this so that we don't redo the check, even if we
+           # didn't warn.  If the first instance didn't warn, none will.
+           $warned_x11_predepends = 1;
+       }
+
+       #----------------- /usr/X11R6/
+       elsif ($file =~ m,^usr/X11R6/bin, && $pkg ne 'x11-common') {
+           tag "package-installs-file-to-usr-x11r6-bin", "$file";
+       }
+       elsif ($file =~ m,^usr/X11R6/lib/X11/fonts,) {
+           tag "package-installs-font-to-usr-x11r6", "$file";
+       }
+       elsif ($file =~ m,^usr/X11R6/, and
+              $index_info->{type} !~ m,^l,) { #links to FHS locations are allowed
+           tag "package-installs-file-to-usr-x11r6", "$file";
+       }
+
+       # ---------------- /usr/lib/debug
+       elsif ($file =~ m,^usr/lib/debug/\S,) {
+           unless ($warned_debug_name) {
+               tag "debug-package-should-be-named-dbg", "$file"
+                   unless ($pkg =~ /-dbg$/);
+               $warned_debug_name = 1;
+           }
+       }
+
+       # ---------------- /usr/lib/sgml
+       elsif ($file =~ m,^usr/lib/sgml/\S,) {
+           tag "file-in-usr-lib-sgml", $file;
+       }
+       # ---------------- perllocal.pod
+       elsif ($file =~ m,^usr/lib/perl.*/perllocal.pod$,) {
+           tag "package-installs-perllocal-pod", "$file";
+       }
+       # ---------------- .packlist files
+       elsif ($file =~ m,^usr/lib/perl.*/.packlist$,) {
+           tag "package-installs-packlist", "$file";
+       }
+       elsif ($file =~ m,^usr/lib/perl5/.*\.(?:pl|pm)$,) {
+           push @nonbinary_perl_files_in_lib, $file;
+       }
+       elsif ($file =~ m,^usr/lib/perl5/.*\.(?:bs|so)$,) {
+           $has_binary_perl_file = 1;
+       }
+       # ---------------- /usr/lib -- needs to go after the other usr/lib/*
+       elsif ($file =~ m,^usr/lib/,) {
+           if ($type ne 'udeb' and $file =~ m,\.(?:gif|jpeg|jpg|png|tiff|xpm|xbm)$, and not defined $link) {
+               tag "image-file-in-usr-lib", "$file"
+           }
+       }
+       # ---------------- /usr/local
+       elsif ($file =~ m,^usr/local/\S+,) {
+           if ($index_info->{type} =~ m/^d/) {
+               tag "dir-in-usr-local", "$file";
+           } else {
+               tag "file-in-usr-local", "$file";
+           }
+       }
+       # ---------------- /usr/share/man and /usr/X11R6/man
+       elsif ($file =~ m,^usr/X11R6/man/\S+, or $file =~ m,^usr/share/man/\S+,) {
+           if ($type eq 'udeb') {
+               tag "documentation-file", "$file";
+           }
+           if ($index_info->{type} =~ m/^d/) {
+               tag "stray-directory-in-manpage-directory", "$file"
+                   if ($file !~ m,^usr/(?:X11R6|share)/man/(?:[^/]+/)?(?:man\d/)?$,);
+           } elsif ($index_info->{type} =~ m,^[-h], and
+               ($operm & 01 or $operm & 010 or $operm & 0100)) {
+               tag "executable-manpage", "$file";
+           }
+       }
+       # ---------------- /usr/share/info
+       elsif ($file =~ m,^usr/share/info\S+,) {
+           if ($type eq 'udeb') {
+               tag "documentation-file", "$file";
+           }
+       }
+       # ---------------- /usr/share/linda/overrides
+       elsif ($file =~ m,^usr/share/linda/overrides/\S+,) {
+           tag "package-contains-linda-override", $file;
+       }
+       # ---------------- /usr/share
+       elsif ($file =~ m,^usr/share/[^/]+$,) {
+           if ($index_info->{type} =~ m/^[-h]/) {
+               tag "file-directly-in-usr-share", "$file";
+           }
+       }
+        # ---------------- /usr/bin
+       elsif ($file =~ m,^usr/bin/,) {
+           if ($index_info->{type} =~ m/^d/ and $file =~ m,^usr/bin/., and $file !~ m,^usr/bin/(?:X11|mh)/,) {
+               tag "subdir-in-usr-bin", "$file";
+           }
+       }
+       # ---------------- /usr subdirs
+       elsif ($type ne 'udeb' and $file =~ m,^usr/[^/]+/$,) { # FSSTND dirs
+           if ( $file =~ m,^usr/(?:dict|doc|etc|info|man|adm|preserve)/,) {
+               tag "FSSTND-dir-in-usr", "$file";
+           }
+           # FHS dirs
+           elsif ($file !~ m,^usr/(?:X11R6|X386|
+                                   bin|games|include|
+                                   lib|lib32|lib64|
+                                   local|sbin|share|
+                                   src|spool|tmp)/,x) {
+               tag "non-standard-dir-in-usr", "$file";
+           } elsif ($file =~ m,^usr/share/doc,) {
+               tag "uses-FHS-doc-dir", "$file";
+           }
+
+           # unless $file =~ m,^usr/[^/]+-linuxlibc1/,; was tied into print
+           # above...
+           # Make an exception for the altdev dirs, which will go away
+           # at some point and are not worth moving.
+       }
+       # ---------------- .desktop files
+       # People have placed them everywhere, but nowadays the consensus seems
+       # to be to stick to the fd.org standard drafts, which says that
+       # .desktop files intended for menus should be placed in
+       # $XDG_DATA_DIRS/applications.  The default for $XDG_DATA_DIRS is
+       # /usr/local/share/:/usr/share/, according to the basedir-spec on
+       # fd.org. As distributor, we should only allow /usr/share.
+       #
+       # KDE hasn't moved its files from /usr/share/applnk, so don't warn
+       # about this yet until KDE adopts the new location.
+       elsif ($file =~ m,^usr/share/gnome/apps/.*\.desktop$,) {
+           tag "desktop-file-in-wrong-dir", $file;
+       }
+
+       # ---------------- png files under /usr/share/apps/*/icons/*
+       elsif ($file =~ m,^usr/share/apps/[^/]+/icons/[^/]+/(\d+x\d+)/.*\.png$,) {
+           my ($dsize, $fsize) = ($1);
+           $info->file_info->{$file} =~ m/,\s*(\d+)\s*x\s*(\d+)\s*,/;
+           $fsize = $1.'x'.$2;
+           tag 'icon-size-and-directory-name-mismatch', $file, $fsize
+               unless ($dsize eq $fsize);
+       }
+    }
+    # ---------------- /var subdirs
+    elsif ($type ne 'udeb' and $file =~ m,^var/[^/]+/$,) { # FSSTND dirs
+       if ( $file =~ m,^var/(?:adm|catman|named|nis|preserve)/, ) {
+           tag "FSSTND-dir-in-var", "$file";
+       }
+       # FHS dirs with exception in Debian policy
+       elsif ( $file !~ m,^var/(?:account|lib|cache|crash|games|lock|log|opt|run|spool|state|tmp|www|yp)/,) {
+           tag "non-standard-dir-in-var", "$file";
+       }
+    }
+    elsif ($type ne 'udeb' and $file =~ m,^var/lib/games/.,) {
+       tag "non-standard-dir-in-var", "$file";
+    }
+    # ---------------- /var/lock, /var/run
+    elsif ($file =~ m,^var/lock/.,) {
+       tag "dir-or-file-in-var-lock", "$file";
+    }
+    elsif ($file =~ m,^var/run/.,) {
+       tag "dir-or-file-in-var-run", "$file";
+    }
+    # ---------------- /var/www
+    # Packages are allowed to create /var/www since it's historically been the
+    # default document root, but they shouldn't be installing stuff under that
+    # directory.
+    elsif ($file =~ m,^var/www/\S+,) {
+       tag "dir-or-file-in-var-www", $file;
+    }
+    # ---------------- /opt
+    elsif ($file =~ m,^opt/.,) {
+       tag "dir-or-file-in-opt", "$file";
+    }
+    elsif ($file =~ m,^hurd/.,) {
+       next;
+    } elsif ($file =~ m,^server/.,) {
+       next;
+    }
+    # ---------------- /tmp, /var/tmp, /usr/tmp
+    elsif ($file =~ m,^tmp/., or $file =~ m,^(?:var|usr)/tmp/.,) {
+       tag "dir-or-file-in-tmp", "$file";
+    }
+    # ---------------- /mnt
+    elsif ($file =~ m,^mnt/.,) {
+       tag "dir-or-file-in-mnt", "$file";
+    }
+    # ---------------- /bin
+    elsif ($file =~ m,^bin/,) {
+       if ($index_info->{type} =~ m/^d/ and $file =~ m,^bin/.,) {
+           tag "subdir-in-bin", "$file";
+       }
+    }
+    # ---------------- /srv
+    elsif ($file =~ m,^srv/.,) {
+       tag "dir-or-file-in-srv", "$file";
+    }
+    # ---------------- FHS directory?
+    elsif ($file =~ m,^[^/]+/$, and $file ne './' and
+          $file !~ m,^(?:bin|boot|dev|etc|home|lib(?:64|32)?|mnt|opt|root|sbin|srv|tmp|usr|var)/,) {
+       # Make an exception for the base-files package here and other similar
+       # packages because they install a slew of top-level directories for
+       # setting up the base system.  (Specifically, /cdrom, /floppy,
+       # /initrd, and /proc are not mentioned in the FHS).
+       #
+       # Also make an exception for /emul, which is used for multiarch
+       # support in Debian at the moment.
+       tag "non-standard-toplevel-dir", "$file"
+           unless $pkg eq 'base-files'
+               or $pkg eq 'hurd'
+               or $pkg =~ /^rootskel(?:-bootfloppy)?/
+               or $file =~ m,^emul/,;
+    }
+
+    # ---------------- compatibility symlinks should not be used
+    if ($file =~ m,^usr/(?:spool|tmp)/, or
+       $file =~ m,^usr/(?:doc|bin)/X11/, or
+       $file =~ m,^var/adm/,) {
+       tag "use-of-compat-symlink", "$file";
+    }
+
+    # ---------------- .ali files (Ada Library Information)
+    if ($file =~ m,^usr/lib/.*\.ali$, && $operm != 0444) {
+       tag "bad-permissions-for-ali-file", "$file";
+    }
+
+    # ---------------- any files
+    if ($index_info->{type} !~ m/^d/) {
+       unless ($type eq 'udeb'
+               or $file =~ m,^usr/(?:bin|dict|doc|games|
+                                   include|info|lib(?:32|64)?|
+                                   man|sbin|share|src|X11R6)/,x
+               or $file =~ m,^lib(?:32|64)?/(?:modules/|libc5-compat/)?,
+               or $file =~ m,^var/(?:games|lib|www|named)/,
+               or $file =~ m,^(?:bin|boot|dev|etc|sbin)/,
+               # non-FHS, but still usual
+               or $file =~ m,^usr/[^/]+-linux[^/]*/,
+               or $file =~ m,^usr/iraf/,
+               or $file =~ m,^emul/ia32-linux/(?:lib|usr/lib)/,
+               # not allowed, but tested indivudually
+               or $file =~ m,^(?:mnt|opt|srv|(?:(?:usr|var)/)?tmp)|var/www/,) {
+           tag "file-in-unusual-dir", "$file";
+       }
+    }
+
+    # ---------------- .pyc (compiled python files
+    if ($file =~ m,^usr/lib/python\d\.\d/.*.pyc$,) {
+       tag "package-installs-python-pyc", "$file"
+    }
+
+    # ---------------- /usr/lib/site-python
+    if ($file =~ m,^usr/lib/site-python/\S,) {
+       tag "file-in-usr-lib-site-python", "$file";
+    }
+
+    # ---------------- pythonX.Y extensions
+    if ($file =~ m,^usr/lib/python\d\.\d/\S,
+       and not $file =~ m,^usr/lib/python\d\.\d/site-packages/,) {
+        # check if it's one of the Python proper packages
+       unless (defined $is_python) {
+           $is_python = 0;
+           if (defined $info->field('source')) {
+               local $_ = $info->field('source');
+               $is_python = 1 if /^python(?:\d\.\d)?(?:$|\s)/;
+           }
+       }
+       tag "third-party-package-in-python-dir", "$file"
+           unless $is_python;
+    }
+    # ---------------- perl modules
+    if ($file =~ m,^usr/(?:share|lib)/perl/\S,) {
+       # check if it's the "perl" package itself
+       unless (defined $is_perl) {
+           $is_perl = 0;
+          if (defined $info->field('source')) {
+               local $_ = $info->field('source');;
+               $is_perl = 1 if /^perl(?:$|\s)/;
+           }
+       }
+       tag "perl-module-in-core-directory", "$file"
+           unless $is_perl;
+    }
+
+    # ---------------- license files
+    if ($file =~ m,(?:copying|licen[cs]e)(?:\.[^/]+)?$,i
+       # Ignore some common extensions; there was at least one file named
+       # "license.el".  These are probably license-displaying code, not
+       # license files.  Also ignore binaries in /usr/bin and friends.
+       #
+       # Another exception is made for .html and .php because preserving
+       # working links is more important than saving some bytes, and
+       # because a package had a HTML form for licenses called like that.
+       # Another exception is made for various picture formats since
+       # those are likely to just be simply pictures.
+       #
+       # DTD files are excluded at the request of the Mozilla suite
+       # maintainers.  Zope products include license files for runtime
+       # display.  underXXXlicense.docbook files are from KDE.
+       #
+       # Ignore extra license files in examples, since various package
+       # building software includes example packages with licenses.
+       and not $file =~ m/\.(?:el|c|h|py|cc|pl|pm|html|php|rb|xpm|png|jpe?g|gif|svg|dtd)$/
+       and not $file =~ m,^usr/share/zope/Products/.*\.(?:dtml|pt|cpt)$,
+       and not $file =~ m,/under\S+License\.docbook$,
+       and not $file =~ m,^(?:usr/)?s?bin/,
+       and not $file =~ m,^usr/share/doc/[^/]+/examples/,
+       and not defined $link) {
+       tag "extra-license-file", "$file";
+    }
+
+    # ---------------- .devhelp2? files
+    if ($file =~ m,\.devhelp2?(?:\.gz)?$,
+       # If the file is located in a directory not searched by devhelp, we
+       # check later to see if it's in a symlinked directory.
+       and not $file =~ m,^usr/share/(?:devhelp/books|gtk-doc/html)/,
+       and not $file =~ m,^usr/share/doc/[^/]+/examples/,) {
+       push (@devhelp, $file);
+    }
+
+    # ---------------- weird file names
+    if ($file =~ m,\s+\z,) {
+       tag "file-name-ends-in-whitespace", "$file";
+    }
+
+    # ---------------- misplaced lintian overrides
+    my $tmp = quotemeta($pkg);
+    if ($file =~ m,^usr/share/doc/$tmp/override\.[lL]intian(?:\.gz)?$, or
+       $file =~ m,^usr/share/lintian/overrides/$tmp/.+,) {
+       tag "override-file-in-wrong-location", "$file";
+    }
+
+    # ---------------- plain files
+    if ($index_info->{type} =~ m/^[-h]/) {
+       my $wanted_operm;
+       # ---------------- backup files and autosave files
+       if ($file =~ /~$/ or $file =~ m,\#[^/]+\#$, or $file =~ m,/\.[^/]+\.swp$,) {
+           tag "backup-file-in-package", "$file";
+       }
+       if ($file =~ m,/\.nfs[^/]+$,) {
+           tag "nfs-temporary-file-in-package", "$file";
+       }
+
+       # ---------------- vcs control files
+       if ($file =~ m/\.(?:(?:cvs|git|hg)ignore|arch-inventory|hgtags|hg_archival\.txt)$/) {
+           tag "package-contains-vcs-control-file", "$file";
+       }
+
+       # ---------------- subversion and svk commit message backups
+       if ($file =~ m/svn-commit.*\.tmp$/) {
+           tag "svn-commit-file-in-package", "$file";
+       }
+       if ($file =~ m/svk-commit.+\.tmp$/) {
+           tag "svk-commit-file-in-package", "$file";
+       }
+
+       # ---------------- executables with language extensions
+       if ($file =~ m,^(?:usr/)?(?:s?bin|games)/[^/]+\.(?:pl|sh|py|php|rb|tcl|bsh|csh|tcl)$,) {
+           tag "script-with-language-extension", "$file";
+       }
+
+       # ---------------- Devel files for Windows
+       if ($file =~ m,/.+\.(?:vcproj|sln|dsp|dsw)(?:\.gz)?$,
+           and $file !~ m,^usr/share/doc/,) {
+           tag "windows-devel-file-in-package", "$file";
+       }
+
+       # ---------------- Autogenerated databases from other OSes
+       if ($file =~ m,/Thumbs\.db(?:\.gz)?$,i) {
+           tag "windows-thumbnail-database-in-package", "$file";
+       }
+       if ($file =~ m,/\.DS_Store(?:\.gz)?$,) {
+           tag "macos-ds-store-file-in-package", "$file";
+       }
+       if ($file =~ m,/\._[^_/][^/]*$, and $file !~ m/\.swp$/) {
+           tag "macos-resource-fork-file-in-package", "$file";
+       }
+
+       # ---------------- embedded Javascript libraries
+       foreach my $jslibrary (@jslibraries) {
+           if ($file =~ m,/$jslibrary->[0], and $pkg !~ m,^$jslibrary->[1]$,) {
+               tag "embedded-javascript-library", "$file";
+           }
+       }
+
+       # ---------------- embedded Feedparser library
+       if ($file =~ m,/feedparser\.py$, and $pkg ne "python-feedparser") {
+           open(FEEDPARSER, "unpacked/$file") or fail("cannot open feedparser.py file: $!");
+           while (<FEEDPARSER>) {
+               if (m,Universal feed parser,) {
+                   tag "embedded-feedparser-library", "$file";
+                   last;
+               }
+           }
+           close(FEEDPARSER);
+       }
+
+       # ---------------- embedded PEAR modules
+       foreach my $pearmodule (@pearmodules) {
+           if ($file =~ m,/$pearmodule->[0], and $pkg ne $pearmodule->[1]) {
+               open (PEAR, "unpacked/$file") or fail("cannot open PHP file: $!");
+               while (<PEAR>) {
+                   if (m,/pear[/.],i) {
+                       tag "embedded-pear-module", "$file";
+                       last;
+                   }
+               }
+               close(PEAR);
+           }
+       }
+
+       # ---------------- embedded php libraries
+       foreach my $phplibrary (@phplibraries) {
+           if ($file =~ m,/$phplibrary->[0], and $pkg ne $phplibrary->[1]) {
+               tag "embedded-php-library", "$file";
+           }
+       }
+
+       # ---------------- fonts
+       if ($file =~ m,/([\w-]+\.(?:[to]tf|pfb|pcf))$,i) {
+           my $font = lc $1;
+           $FONT_PACKAGES = Maemian::Data->new('files/fonts', '\s+')
+               unless defined($FONT_PACKAGES);
+           if ($FONT_PACKAGES->known($font)) {
+               tag 'duplicate-font-file', "$file also in", $FONT_PACKAGES->value($font)
+                   if ($pkg ne $FONT_PACKAGES->value($font) and $type ne 'udeb');
+           } elsif ($pkg !~ m/^[ot]tf-/) {
+               tag 'font-in-non-font-package', $file;
+           }
+       }
+
+       # ---------------- non-free .swf files
+       foreach my $flash (@flash_nonfree) {
+           last if ($pkg_section =~ m,^non-free/,);
+           if ($file =~ m,/$flash,) {
+               tag "non-free-flash", $file;
+           }
+       }
+
+       # ---------------- .gz files
+       if ($file =~ m/\.gz$/) {
+           my $info = $info->file_info->{$file} || '';
+           if ($info !~ m/gzip compressed/) {
+               tag "gz-file-not-gzip", "$file";
+           }
+       }
+
+       # ---------------- general: setuid/setgid files!
+       if ($operm & 04000 or $operm & 02000) {
+           my ($setuid, $setgid) = ("","");
+           # get more info:
+           $setuid = $index_info->{owner} if ($operm & 04000);
+           $setgid = $index_info->{group} if ($operm & 02000);
+
+           # 1st special case: program is using svgalib:
+           if (exists $linked_against_libvga{$file}) {
+               # setuid root is ok, so remove it
+               if ($setuid eq 'root') {
+                   undef $setuid;
+               }
+           }
+
+           # 2nd special case: program is a setgid game
+           if ($file =~ m,usr/lib/games/\S+, or $file =~ m,usr/games/\S+,) {
+               # setgid games is ok, so remove it
+               if ($setgid eq 'games') {
+                   undef $setgid;
+               }
+           }
+
+           # 3rd special case: allow anything with suid in the name
+           if ($pkg =~ m,-suid,) {
+               undef $setuid;
+           }
+
+           # Check for setuid and setgid that isn't expected.
+           if ($setuid and $setgid) {
+               tag "setuid-gid-binary", $file, sprintf("%04o $owner",$operm);
+           } elsif ($setuid) {
+               tag "setuid-binary", $file, sprintf("%04o $owner",$operm);
+           } elsif ($setgid) {
+               tag "setgid-binary", $file, sprintf("%04o $owner",$operm);
+           }
+
+           # Check for permission problems other than the setuid status.
+           if (($operm & 0444) != 0444) {
+               tag "executable-is-not-world-readable", $file,
+                   sprintf("%04o",$operm);
+           } elsif ($operm != 04755 && $operm != 02755 && $operm != 06755 && $operm != 04754) {
+               tag "non-standard-setuid-executable-perm", $file,
+                   sprintf("%04o",$operm);
+           }
+       }
+       # ---------------- general: executable files
+       elsif ($operm & 01 or $operm & 010 or $operm & 0100) {
+           # executable
+           if ($owner =~ m,root/games,) {
+               if ($operm != 2755) {
+                   tag "non-standard-game-executable-perm", $file,
+                       sprintf("%04o != 2755",$operm);
+               }
+           } else {
+               if (($operm & 0444) != 0444) {
+                   tag "executable-is-not-world-readable", $file,
+                       sprintf("%04o != 0755",$operm);
+               } elsif ($operm != 0755) {
+                   tag "non-standard-executable-perm", $file,
+                       sprintf("%04o != 0755",$operm);
+               }
+           }
+       }
+       # ---------------- general: normal (non-executable) files
+       else {
+           # not executable
+           # special case first: game data
+           if ($operm == 0664 and $owner =~ m,root/games, and
+               $file =~ m,var/(lib/)?games/\S+,) {
+               # everything is ok
+           } elsif ($operm == 0444 and $file =~ m,usr/lib/.*\.ali$,) {
+               # Ada library information files should be read-only
+               # since GNAT behaviour depends on that
+               # everything is ok
+           } elsif ($operm == 0600 and $file =~ m,etc/backup.d/,) {
+               # backupninja expects configurations files to be 0600
+           } elsif ($operm != 0644) {
+               tag "non-standard-file-perm", $file,
+                   sprintf("%04o != 0644",$operm);
+           }
+       }
+    }
+    # ---------------- directories
+    elsif ($index_info->{type} =~ m/^d/) {
+       # special cases first:
+        # game directory with setgid bit
+       if ($file =~ m,var/(?:lib/)?games/\S+, and $operm == 02775
+            and $owner =~ m,root/games,) {
+            # do nothing, this is allowed, but not mandatory
+        }
+       # otherwise, complain if it's not 0755.
+       elsif ($operm != 0755) {
+           tag "non-standard-dir-perm", $file,
+               sprintf("%04o != 0755", $operm);
+       }
+       if ($file =~ m,/CVS/?$,) {
+           tag "package-contains-vcs-control-dir", "$file";
+       }
+       if ($file =~ m,/\.(?:svn|bzr|git|hg)/?$,) {
+           tag "package-contains-vcs-control-dir", "$file";
+       }
+       if (($file =~ m,/\.arch-ids/?$,)
+           || ($file =~ m,/\{arch\}/?$,)) {
+           tag "package-contains-vcs-control-dir", "$file";
+       }
+       if ($file =~ m,/\.(?:be|ditrack)/?$,) {
+           tag "package-contains-bts-control-dir", "$file";
+       }
+       if ($file =~ m,/.xvpics/?$,) {
+           tag "package-contains-xvpics-dir", "$file";
+       }
+       if ($file =~ m,usr/share/doc/[^/]+/examples/examples/?$,) {
+           tag "nested-examples-directory", "$file";
+       }
+    }
+    # ---------------- symbolic links
+    elsif ($index_info->{type} =~ m/^l/) {
+       # link
+
+       my $mylink = $link;
+       if ($mylink =~ s,//+,/,g) {
+           tag "symlink-has-double-slash", "$file $link";
+       }
+       if ($mylink =~ s,(.)/$,$1,) {
+           tag "symlink-ends-with-slash", "$file $link";
+       }
+
+       # determine top-level directory of file
+       $file =~ m,^/?([^/]*),;
+       my $filetop = $1;
+
+       if ($mylink =~ m,^/([^/]*),) {
+           # absolute link, including link to /
+
+           # determine top-level directory of link
+           $mylink =~ m,^/([^/]*),;
+           my $linktop = $1;
+
+           if ($type ne 'udeb' and $filetop eq $linktop) {
+               # absolute links within one toplevel directory are _not_ ok!
+               tag "symlink-should-be-relative", "$file $link";
+           }
+
+           # Any other case is already definitely non-recursive
+           tag "symlink-is-self-recursive", "$file $link"
+               if $mylink eq '/';
+
+       } else {
+           # relative link, we can assume from here that the link starts nor
+           # ends with /
+
+           my @filecomponents = split('/', $file);
+           # chop off the name of the symlink
+           pop @filecomponents;
+
+           my @linkcomponents = split('/', $mylink);
+
+           # handle `../' at beginning of $link
+           my $lastpop = undef;
+           my $linkcomponent = undef;
+           while ($linkcomponent = shift @linkcomponents) {
+               if ($linkcomponent eq '.') {
+                   tag "symlink-contains-spurious-segments", "$file $link"
+                       unless $mylink eq '.';
+                   next;
+               }
+               last if $linkcomponent ne '..';
+               if (@filecomponents) {
+                   $lastpop = pop @filecomponents;
+               } else {
+                   tag "symlink-has-too-many-up-segments", "$file $link";
+                   goto NEXT_LINK;
+               }
+           }
+
+           if (!defined $linkcomponent) {
+               # After stripping all starting .. components, nothing left
+               tag "symlink-is-self-recursive", "$file $link";
+           }
+
+           # does the link go up and then down into the same directory?
+           # (lastpop indicates there was a backref at all, no linkcomponent
+           # means the symlink doesn't get up anymore)
+           if (defined $lastpop && defined $linkcomponent &&
+               $linkcomponent eq $lastpop) {
+               tag "lengthy-symlink", "$file $link";
+           }
+
+           if ($#filecomponents == -1) {
+               # we've reached the root directory
+               if (($type ne 'udeb') 
+                   && (!defined $linkcomponent)
+                   || ($filetop ne $linkcomponent)) {
+                   # relative link into other toplevel directory.
+                   # this hits a relative symbolic link in the root too.
+                   tag "symlink-should-be-absolute", "$file $link";
+               }
+           }
+
+           # check additional segments for mistakes like `foo/../bar/'
+           foreach (@linkcomponents) {
+               if ($_ eq '..' || $_ eq '.') {
+                   tag "symlink-contains-spurious-segments", "$file $link";
+                   last;
+               }
+           }
+       }
+    NEXT_LINK:
+
+       if ($link =~ m,\.(gz|z|Z|bz|bz2|tgz|zip)\s*$,) {
+           # symlink is pointing to a compressed file
+
+           # symlink has correct extension?
+           unless ($file =~ m,\.$1\s*$,) {
+               tag "compressed-symlink-with-wrong-ext", "$file $link";
+           }
+       }
+    }
+    # ---------------- special files
+    else {
+       # special file
+       tag "special-file", $file, sprintf("%04o",$operm);
+    }
+}
+
+# Check for section games but nothing in /usr/games.  Check for any binary to
+# save ourselves from game-data false positives:
+my $games = dir_counts($info, "usr/games/");
+my $other = dir_counts($info, "bin/") + dir_counts($info, "usr/bin/");
+if ($pkg_section =~ m,games$, and $games == 0 and $other > 0) {
+    tag "package-section-games-but-contains-no-game";
+}
+if ($pkg_section =~ m,games$, and $games > 0 and $other > 0) {
+    tag "package-section-games-but-has-usr-bin";
+}
+if ($pkg_section !~ m,games$, and $games > 0 and $other == 0) {
+    tag 'games-package-should-be-section-games';
+}
+
+# Warn about empty directories, but ignore empty directories in /var (packages
+# create directories to hold dynamically created data) or /etc (configuration
+# files generated by maintainer scripts).  Also skip base-files, which is a
+# very special case.
+#
+# Empty Perl directories are an ExtUtils::MakeMaker artifact that will be
+# fixed in Perl 5.10, and people can cause more problems by trying to fix it,
+# so just ignore them.
+#
+# python-support needs a directory for each package even it might be empty
+foreach my $dir (sort keys %{$info->index}) {
+    next if $dir eq "" or $info->index->{$dir}->{type} ne 'd';
+    next if ($dir =~ m{^var/} or $dir =~ m{^etc/});
+    next if $pkg eq 'base-files';
+    if (dir_counts($info, $dir) == 0) {
+       if ($dir ne 'usr/lib/perl5/'
+           and $dir ne 'usr/share/perl5/'
+           and $dir !~ m;^usr/share/python-support/;) {
+           tag "package-contains-empty-directory", $dir;
+       }
+    }
+}
+
+if (!$has_binary_perl_file && @nonbinary_perl_files_in_lib) {
+    foreach my $file (@nonbinary_perl_files_in_lib) {
+       tag "package-installs-nonbinary-perl-in-usr-lib-perl5", "$file";
+    }
+}
+
+# Check for .devhelp2? files that aren't symlinked into paths searched by
+# devhelp.
+for my $file (@devhelp) {
+    my $found = 0;
+    for my $link (@devhelp_links) {
+       if ($file =~ m,^\Q$link,) {
+           $found = 1;
+           last;
+       }
+    }
+    tag 'package-contains-devhelp-file-without-symlink', $file unless $found;
+}
+
+}
+
+sub dir_counts {
+    my ($info, $dir) = @_;
+
+    if (defined $info->index->{$dir}) {
+       return $info->index->{$dir}->{count} || 0;
+    } else {
+       return 0;
+    }
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl ts=8 sw=4
diff --git a/checks/files.desc b/checks/files.desc
new file mode 100644 (file)
index 0000000..7f18c8f
--- /dev/null
@@ -0,0 +1,979 @@
+Check-Script: files
+Author: Christian Schwarz <schwarz@debian.org>
+Abbrev: fil
+Type: binary, udeb
+Unpack-Level: 2
+Needs-Info: objdump-info, scripts, file-info
+Info: This script checks if a binary package conforms to policy
+ WRT to files and directories.
+
+Tag: package-contains-ancient-file
+Severity: important
+Certainty: certain
+Info: Your package contains a file that claims to have been generated
+ more than 20 years ago. This is most probably an error. Your package
+ will be rejected by the Debian archive scripts if it contains a file
+ with such a timestamp.
+
+Tag: old-app-defaults-directory
+Severity: important
+Certainty: certain
+Info: The app-defaults files have moved to /etc/X11/app-defaults/. Files in
+ the old directory, /usr/X11R6/lib/X11/app-defaults/, will no longer be
+ used by X. The old directory should not exist in packages at all;
+ this prevents X from replacing it with a compatibility symlink.
+Ref: policy 11.8.6
+
+Tag: package-installs-font-to-usr-x11r6
+Severity: important
+Certainty: certain
+Info: The standard location for X fonts has moved to /usr/share/fonts/X11.
+ Packages providing X fonts must install them into the new path.  Fonts
+ installed into the old /usr/X11R6/lib/X11/fonts path may not be seen by
+ the X server.
+ .
+ If the package uses imake, it must build-depend on xutils-dev (&gt;=
+ 1:1.0.2-2) for the correct paths. If it uses dh_installxfonts to handle X
+ font installation, it must build-depend on debhelper (&gt;= 5.0.31).
+Ref: policy 11.8.5
+
+Tag: package-installs-file-to-usr-x11r6-bin
+Severity: important
+Certainty: certain
+Info: Debian has switched to the modular X tree which now uses the regular
+ FHS paths, and all packages must follow. All packages installing binaries
+ must install them into /usr/bin (or some other appropriate location)
+ instead of /usr/X11R6/bin.
+ .
+ The x11-common package attempts to change /usr/X11R6/bin into a symlink
+ to /usr/bin, so if this migration has already occurred, a package
+ installing files into /usr/X11R6/bin may appear to install
+ successfully. However, such a package will be left in an inconsistent
+ state and may orphan files when the compatibility link goes away.
+ .
+ If the package uses imake, it must build-depend on xutils-dev (&gt;=
+ 1:1.0.2-2) for the correct paths.
+Ref: policy 11.8.7
+
+Tag: file-in-usr-something-x11-without-pre-depends
+Severity: wishlist
+Certainty: certain
+Info: Packages that install files into /usr/include/X11 or /usr/lib/X11
+ should pre-depend on at least x11-common (&gt;= 1:7.0.0).  These directories
+ used to be symlinks and installing files in them while they are still
+ symlinks will put files in the wrong locations and cause stranded files
+ and other problems.  x11-common is responsible for converting the
+ symlinks to regular directories.
+ .
+ A fresh etch (or later) install will not have problems even without this
+ dependency, but this dependency is needed for upgrades from sarge and is
+ therefore still useful for backports.
+Ref: policy 11.8.7
+
+Tag: package-installs-file-to-usr-x11r6
+Severity: important
+Certainty: certain
+Info: Packages using the X Window System should not be configured to install
+ files under the /usr/X11R6/ directory. Debian has switched to the modular
+ X tree which now uses regular FHS paths and all packages should follow.
+ .
+ Programs that use GNU autoconf and automake are usually easily configured
+ at compile time to use /usr/ instead of /usr/X11R6/.  Packages that use
+ imake must build-depend on xutils-dev (&gt;= 1:1.0.2-2) for the correct
+ paths.
+Ref: policy 11.8.7
+
+Tag: config-file-reserved
+Severity: important
+Certainty: certain
+Info: This file is reserved by a specific package.  Please email the
+ maintainer of the package in question if you have questions.
+
+Tag: package-uses-obsolete-file
+Severity: normal
+Certainty: certain
+Info: the file, /etc/nntpserver, is no longer recommenced.  As of policy
+ revision 2.5.1.0, /etc/news/server is the preferred file to use to specify
+ a news server.
+Ref: policy 11.7
+
+Tag: FSSTND-dir-in-usr
+Severity: serious
+Certainty: certain
+Info: As of policy version 3.0.0.0, Debian no longer follows the FSSTND.  
+ .
+ Instead, the Filesystem Hierarchy Standard (FHS), version 2.3, is
+ used. You can find it in /usr/share/doc/debian-policy/fhs/ .
+Ref: policy 9.1.1
+
+Tag: FSSTND-dir-in-var
+Severity: serious
+Certainty: certain
+Info: As of policy version 3.0.0.0, Debian no longer follows the FSSTND.  
+ .
+ Instead, the Filesystem Hierarchy Standard (FHS), version 2.3, is
+ used. You can find it in /usr/share/doc/debian-policy/fhs/ .
+Ref: policy 9.1.1
+
+Tag: package-installs-into-etc-gconf-schemas
+Severity: normal
+Certainty: certain
+Info: The package installs files into the <tt>/etc/gconf/schemas</tt>
+ directory.  No package should do this; this directory is reserved for
+ local overrides.  Instead, schemas should be installed into
+ <tt>/usr/share/gconf/schemas</tt>.
+
+Tag: package-installs-into-etc-rc.d
+Severity: serious
+Certainty: certain
+Info: The package installs files into the <tt>/etc/rc.d</tt> or
+ <tt>/etc/rc?.d</tt> which is not allowed.
+Ref: policy 9.3.3
+
+Tag: package-installs-into-etc-rc.boot
+Severity: serious
+Certainty: certain
+Info: The package installs files in the <tt>/etc/rc.boot</tt> directory,
+ which is obsolete.  See rc.boot(5) for details.
+Ref: policy 9.3.4
+
+Tag: non-standard-file-permissions-for-etc-init.d-script
+Severity: important
+Certainty: certain
+Info: Usually, scripts in the <tt>/etc/init.d</tt> directory should have
+ mode 0755.
+
+Tag: file-directly-in-usr-share
+Severity: serious
+Certainty: certain
+Info: Packages should not install files directly in <tt>/usr/share</tt>,
+ i.e., without a subdirectory.
+ .
+ You should either create a subdirectory <tt>/usr/share/...</tt> for your
+ package or place the file in <tt>/usr/share/misc</tt>.
+
+Tag: file-in-usr-local
+Severity: serious
+Certainty: certain
+Info: The package installs a file in <tt>/usr/local/...</tt> which is
+ not allowed.
+Ref: policy 9.1.2
+
+Tag: stray-directory-in-manpage-directory
+Severity: important
+Certainty: certain
+Info: This package installs a directory under <tt>/usr/share/man</tt> or
+ <tt>/usr/X11R6/man</tt> that isn't a manual section directory or locale
+ directory.
+Ref: fhs usrsharemanmanualpages
+
+Tag: executable-manpage
+Severity: important
+Certainty: certain
+Info: Manual pages are not meant to be executed.
+
+Tag: dir-in-usr-local
+Severity: serious
+Certainty: certain
+Info: The package installs a directory in <tt>/usr/local/...</tt> which is
+ not allowed.
+ .
+ If you want to provide an empty directory in <tt>/usr/local</tt> for
+ convenience of the local system administrator, please follow the rules
+ in the policy manual (section 9.1.2), i.e., create the directories in
+ the postinst script but don't fail if this isn't possible (e.g., if
+ <tt>/usr/local</tt> is mounted read-only).
+Ref: policy 9.1.2
+
+Tag: non-standard-dir-perm
+Severity: normal
+Certainty: possible
+Info: The directory has a mode different from 0755, and it's not one of the
+ known exceptions.
+Ref: policy 10.9
+
+Tag: executable-is-not-world-readable
+Severity: normal
+Certainty: certain
+Info: All executables should be readable by any user.  Since anyone can
+ download the Debian package and obtain a copy of the executable, no
+ security is gained by making the executable unreadable even for setuid
+ binaries.  If only members of a certain group may execute this file,
+ remove execute permission for world, but leave read permission.
+Ref: policy 10.9
+
+Tag: non-standard-executable-perm
+Severity: normal
+Certainty: certain
+Info: Executables that are not setuid or setgid should always have a mode
+ of 0755.  Since anyone can obtain the executable by downloading the
+ Debian package and extracting it, restricting access serves little
+ purpose.
+Ref: policy 10.9
+
+Tag: non-standard-game-executable-perm
+Severity: normal
+Certainty: certain
+Info: The file is owned by the games group but is not mode 2755.  If a
+ game does not have to be setgid games, it should be owned by the root
+ group like any other executable.  This executable is either owned by the
+ wrong group or is not setgid when it should be.
+Ref: policy 11.11
+
+Tag: non-standard-setuid-executable-perm
+Severity: normal
+Certainty: certain
+Info: The file is setuid or setgid and has a mode different from any of
+ 2755, 4755, 4754, or 6755.  Any other permissions on setuid executables
+ is probably a bug.  In particular, removing root write privileges serves
+ no purpose, group-writable setuid or setgid executables are probably bad
+ ideas, and setgid executables that are not world-executable serve little
+ purpose.
+Ref: policy 10.9
+
+Tag: setuid-binary
+Severity: normal
+Certainty: possible
+Info: The file is tagged SETUID. In some cases this is intentional, but in
+ other cases this is a bug. If this is intentional, please add a lintian
+ override to document this fact.
+
+Tag: setgid-binary
+Severity: normal
+Certainty: possible
+Info: The file is tagged SETGID. In some cases this is intentional, but in
+ other cases this is a bug. If this is intentional, please add a lintian
+ override to document this fact.
+
+Tag: setuid-gid-binary
+Severity: normal
+Certainty: possible
+Info: The file is tagged SETUID and SETGID. In some cases this is
+ intentional, but in other cases this is a bug. If this is intentional,
+ please add a lintian override to document this fact.
+
+Tag: non-standard-file-perm
+Severity: normal
+Certainty: certain
+Info: The file has a mode different from 0644. In some cases this is
+ intentional, but in other cases this is a bug.
+Ref: policy 10.9
+
+Tag: special-file
+Severity: serious
+Certainty: certain
+Info: The package contains a <i>special</i> file (e.g., a device file).
+ This is forbidden by current policy. If your program needs this device,
+ you should create it by calling <tt>makedev</tt> from the postinst
+ script.
+Ref: policy 10.6
+
+Tag: old-style-example-dir
+Severity: important
+Certainty: certain
+Info: The package installs some files into the old
+ <tt>/usr/doc/examples</tt> directory.  The new location for examples
+ is <tt>/usr/share/doc/<i>pkg</i>/examples</tt>.
+Ref: policy 12.6
+
+Tag: compressed-symlink-with-wrong-ext
+Severity: important
+Certainty: certain
+Info: The package installs a symbolic link pointing to a compressed file,
+ but the symbolic link does not use the same file extension than the
+ referenced file. In most cases, this can produce troubles when the
+ user or a program tries to access the file through the link.
+Ref: policy 10.5
+
+Tag: symlink-has-double-slash
+Severity: minor
+Certainty: certain
+Info: This symlink contains two successive slashes (//). This is in violation
+ of policy, where it is stated that symlinks should be as short as possible
+ .
+ If you use debhelper, running dh_link after creating the package structure
+ will fix this problem for you.
+Ref: policy 10.5
+
+Tag: symlink-ends-with-slash
+Severity: minor
+Certainty: certain
+Info: This symlink ends with a slash (/). This is in violation
+ of policy, where it is stated that symlinks should be as short as possible
+ .
+ If you use debhelper, running dh_link after creating the package structure
+ will fix this problem for you.
+Ref: policy 10.5
+
+Tag: symlink-should-be-relative
+Severity: normal
+Certainty: certain
+Info: Symlinks to files which are in the same top-level directory should be
+ relative according to policy.  (In other words, a link in /usr to another
+ file in /usr should be relative, while a link in /usr to a file in /etc
+ should be absolute.)
+ .
+ If you use debhelper, running dh_link after creating the package structure
+ will fix this problem for you.
+Ref: policy 10.5
+
+Tag: symlink-should-be-absolute
+Severity: important
+Certainty: certain
+Info: Symbolic links between different top-level directories should be
+ absolute.
+ .
+ If you use debhelper, running dh_link after creating the package structure
+ will fix this problem for you.
+Ref: policy 10.5
+
+Tag: udeb-contains-documentation-file
+Severity: important
+Certainty: certain
+Info: udeb packages should not contain any documentation.
+
+Tag: executable-in-usr-share-doc
+Severity: important
+Certainty: certain
+Info: Usually, documentation files in <tt>/usr/share/doc</tt> should have mode
+ 0644.  If the executable is an example, it should go in
+ <tt>/usr/share/doc/<i>pkg</i>/examples</tt>.
+
+Tag: script-in-usr-share-doc
+Severity: wishlist
+Certainty: certain
+Info: Scripts are usually not documentation files, unless they are
+ examples, in which case they should be in the
+ <tt>/usr/share/doc/<i>pkg</i>/examples</tt> directory.
+
+Tag: symlink-has-too-many-up-segments
+Severity: important
+Certainty: certain
+Info: The symlink references a directory beyond the root directory "/".
+
+Tag: lengthy-symlink
+Severity: important
+Certainty: certain
+Info: This link goes up, and then back down into the same subdirectory.
+ Making it shorter will improve its chances of finding the right file
+ if the user's system has lots of symlinked directories.
+ .
+ If you use debhelper, running dh_link after creating the package structure
+ will fix this problem for you.
+Ref: policy 10.5
+
+Tag: symlink-is-self-recursive
+Severity: normal
+Certainty: possible
+Info: The symbolic link is recursive to a higher directory of the symlink
+ itself. This means, that you can infinitely chdir with this symlink. This is
+ usually not okay, but sometimes wanted behaviour.
+
+Tag: symlink-contains-spurious-segments
+Severity: important
+Certainty: certain
+Info: The symbolic link has needless segments like ".." and "." in the
+ middle.  These are unneeded and make the link longer than it could be,
+ which is in violation of policy.  They can also cause problems in the
+ presence of symlinked directories.
+ .
+ If you use debhelper, running dh_link after creating the package structure
+ will fix this problem for you.
+Ref: policy 10.5
+
+Tag: run-parts-cron-filename-contains-full-stop
+Severity: normal
+Certainty: certain
+Info: The script in /etc/cron.&lt;time-interval&gt; will not be executed by
+ run-parts(8) because the filename contains a "." (full stop).
+Ref: run-parts(8)
+
+Tag: bad-permissions-for-etc-cron.d-script
+Severity: important
+Certainty: certain
+Info: Files in <tt>/etc/cron.d</tt> are configuration files for cron and not
+ scripts. Thus, they should not be marked executable.
+
+Tag: bad-permissions-for-etc-emacs-script
+Severity: important
+Certainty: certain
+Info: Files in the <tt>/etc/emacs*</tt> directories should not be marked
+ executable.
+
+Tag: image-file-in-usr-lib
+Severity: normal
+Certainty: certain
+Info: This package installs a pixmap or a bitmap within <tt>/usr/lib</tt>.
+ According to the Filesystem Hierarchy Standard, architecture-independent
+ files need to be placed within <tt>/usr/share</tt> instead.
+
+Tag: file-directly-in-usr-share-doc
+Severity: serious
+Certainty: certain
+Info: Documentation files have to be installed in
+ <tt>/usr/share/doc/<i>pkg</i></tt>.
+Ref: policy 12.3
+
+Tag: bad-owner-for-doc-file
+Severity: important
+Certainty: certain
+Info: Documentation files should be owned by <tt>root/root</tt>.
+
+Tag: dir-or-file-in-var-lock
+Severity: serious
+Certainty: possible
+Info: <tt>/var/lock</tt> may be a temporary filesystem, so any directories
+ or files needed there must be created dynamically at boot time.
+Ref: policy 9.3.2
+
+Tag: dir-or-file-in-var-run
+Severity: serious
+Certainty: possible
+Info: <tt>/var/run</tt> may be a temporary filesystem, so any directories
+ or files needed there must be created dynamically at boot time.
+Ref: policy 9.3.2
+
+Tag: dir-or-file-in-var-www
+Severity: important
+Certainty: certain
+Ref: fhs thevarhierarchy
+Info: Debian packages should not install files under <tt>/var/www</tt>.
+ This is not one of the <tt>/var</tt> directories in the File Hierarchy
+ Standard and is under the control of the local administrator.  Packages
+ should not assume that it is the document root for a web server; it is
+ very common for users to change the default document root and packages
+ should not assume that users will keep any particular setting.
+ .
+ Packages that want to make files available via an installed web server
+ should instead put instructions for the local administrator in a
+ README.Debian file and ideally include configuration fragments for common
+ web servers such as Apache.
+ .
+ As an exception, packages are permitted to create the <tt>/var/www</tt>
+ directory due to its past history as the default document root, but
+ should at most copy over a default file in postinst for a new install.
+
+Tag: dir-or-file-in-tmp
+Severity: important
+Certainty: certain
+Info: Packages should not install into <tt>/tmp</tt> or <tt>/var/tmp</tt>.
+
+Tag: dir-or-file-in-mnt
+Severity: important
+Certainty: certain
+Info: Packages should not install into <tt>/mnt</tt>.  The FHS states that
+ the contents of this directory is a local issue.
+
+Tag: dir-or-file-in-opt
+Severity: important
+Certainty: certain
+Info: Debian packages should not install into <tt>/opt</tt>, because it
+ is reserved for add-on software.
+
+Tag: dir-or-file-in-srv
+Severity: important
+Certainty: certain
+Ref: fhs therootfilesystem
+Info: Debian packages should not install into <tt>/srv</tt>.  The
+ specification of <tt>/srv</tt> states that its structure is at the
+ discretion of the local administrator and no package should rely on any
+ particular structure.  Debian packages that install files directly into
+ <tt>/srv</tt> can't adjust for local policy about its structure and in
+ essence force a particular structure.
+ .
+ If a package wishes to put its data in <tt>/srv</tt>, it must do this in
+ a way that allows the local administrator to specify and preserve their
+ chosen directory structure (such as through post-install configuration,
+ setup scripts, debconf prompting, etc.).
+
+Tag: third-party-package-in-python-dir
+Severity: normal
+Certainty: certain
+Info: According to <a href="http://www.python.org/doc/essays/packages.html">
+ <tt>http://www.python.org/doc/essays/packages.html</tt></a>, third-party
+ python packages should install their files in
+ <tt>/usr/lib/python1.5/site-packages</tt>.  All other directories in
+ <tt>/usr/lib/python1.5</tt> are for use by the core python packages.
+Tag: perl-module-in-core-directory
+Severity: important
+Certainty: certain
+Info: Packaged modules must not be installed into the core perl
+ directories as those directories change with each upstream perl
+ revision.  The vendor directories are provided for this purpose.
+Ref: perl-policy 3.1
+
+Tag: backup-file-in-package
+Severity: normal
+Certainty: certain
+Info: There is a file in the package whose name matches the format emacs
+ or vim uses for backup and autosave files.  It may have been installed by
+ accident.
+
+Tag: nfs-temporary-file-in-package
+Severity: normal
+Certainty: certain
+Info: There is a file in the package whose name matches the format NFS
+ uses to temporarily save files that were deleted while another process
+ had them open.  It may have been included in the package by accident
+ while building the package in an NFS filesystem.
+
+Tag: windows-thumbnail-database-in-package
+Severity: normal
+Certainty: certain
+Info: There is a file in the package named <tt>Thumbs.db</tt> or
+ <tt>Thumbs.db.gz</tt>, which is normally a Windows image thumbnail
+ database.  Such databases are generally useless in Debian packages and
+ were usually accidentally included by copying complete directories from
+ the source tarball.
+
+Tag: macos-ds-store-file-in-package
+Severity: normal
+Certainty: certain
+Info: There is a file in the package named <tt>.DS_Store</tt> or
+ <tt>.DS_Store.gz</tt>, the file name used by Mac OS X to store folder
+ attributes.  Such files are generally useless in Debian packages and were
+ usually accidentally included by copying complete directories from the
+ source tarball.
+
+Tag: macos-resource-fork-file-in-package
+Severity: normal
+Certainty: certain
+Info: There is a file in the package with a name starting with
+ <tt>._</tt>, the file name pattern used by Mac OS X to store resource
+ forks in non-native file systems.  Such files are generally useless in
+ Debian packages and were usually accidentally included by copying
+ complete directories from the source tarball.
+
+Tag: package-installs-perllocal-pod
+Severity: normal
+Certainty: certain
+Info: This package installs a file <tt>perllocal.pod</tt>.  Since that
+ file is intended for local documentation, it is not likely that it is
+ a good place for documentation supplied by a Debian package.  In fact,
+ installing this package will wipe out whatever local documentation
+ existed there.
+
+Tag: extra-license-file
+Severity: normal
+Certainty: possible
+Ref: policy 12.5
+Info: All license information should be collected in the
+ <tt>debian/copyright</tt> file.  This usually makes it unnecessary
+ for the package to install this information in other places as well.
+
+Tag: non-standard-toplevel-dir
+Severity: important
+Certainty: certain
+Info: The Filesystem Hierarchy Standard forbids the installation of new
+ files or directories in the root directory.
+Ref: fhs therootfilesystem
+
+Tag: subdir-in-bin
+Severity: serious
+Certainty: certain
+Info: The Filesystem Hierarchy Standard forbids the installation of new
+ directories in <tt>/bin</tt>.
+Ref: fhs binessentialusercommandbinaries
+
+Tag: subdir-in-usr-bin
+Severity: serious
+Certainty: certain
+Info: The Filesystem Hierarchy Standard forbids the installation of new
+ directories in <tt>/usr/bin</tt> other than <tt>/usr/bin/mh</tt>.
+Ref: fhs usrbinmostusercommands
+
+Tag: non-standard-dir-in-usr
+Severity: normal
+Certainty: certain
+Info: The FHS says "No large software packages should use a direct
+ subdirectory under the <tt>/usr</tt> hierarchy".  This package contains
+ a directory in <tt>/usr</tt> that is not mentioned in the Filesystem
+ Hierarchy Standard.
+Ref: fhs theusrhierarchy
+
+Tag: non-standard-dir-in-var
+Severity: important
+Certainty: certain
+Info: The FHS says "Applications should generally not add directories to
+ the top level of <tt>/var</tt>.  Such directories should only be added
+ if they have some system-wide implication, and in consultation with the
+ FHS mailing list."
+Ref: fhs thevarhierarchy
+
+Tag: use-of-compat-symlink
+Severity: important
+Certainty: certain
+Info: This package uses a directory that, according to the Filesystem
+ Hierarchy Standard, should exist only as a compatibility symlink.
+ Packages should not traverse such symlinks when installing files, they
+ should use the standard directories instead.
+
+Tag: file-in-unusual-dir
+Severity: normal
+Certainty: certain
+Info: This file or symbolic link is in a directory where files are not
+ normally installed by Debian packages.
+
+Tag: package-installs-packlist
+Severity: important
+Certainty: certain
+Info: Packages built using the perl MakeMaker package will have a file
+ named .packlist in them.  Those files are useless, and (in some cases)
+ have the additional problem of creating an architecture-specific
+ directory name in an architecture-independent package.
+ .
+ They can be suppressed by adding the following to <tt>debian/rules</tt>:
+ .
+   find debian/<i>pkg</i> -type f -name .packlist | xargs rm -f
+ .
+ or by telling MakeMaker to use vendor install dirs; consult a recent
+ version of Perl policy. Perl 5.6.0-12 or higher supports this.
+Ref: perl-policy 4.1
+
+Tag: zero-byte-file-in-doc-directory
+Severity: normal
+Certainty: possible
+Info: The documentation directory for this package contains an empty
+ file.  This is often due to installing an upstream NEWS or README file
+ without realizing it's empty and hence not useful.
+ .
+ Files in the examples subdirectory are excluded from this check, but
+ there are some cases where empty files are legitimate parts of the
+ documentation without being examples.  In those cases, please add an
+ override.
+
+Tag: override-file-in-wrong-location
+Severity: important
+Certainty: certain
+Info: Maemian overrides should be put in a regular file named
+ /usr/share/lintian/overrides/<tt>package</tt>, not in a subdirectory
+ named for the package or in the obsolete location under /usr/share/doc.
+ See the Maemian documentation for more information on proper naming and
+ format.
+Ref: lintian 2.4
+
+Tag: package-contains-upstream-install-documentation
+Severity: normal
+Certainty: possible
+Ref: policy 12.3
+Info: Binary packages do not need to contain the instructions for building
+ and installing the package as this info is not needed by package users.
+ If the info contained is important for configuration perhaps it could be
+ summarized in README.Debian, otherwise an override may be added.
+
+Tag: package-contains-hardlink
+Severity: normal
+Certainty: certain
+Info: The package contains a hardlink in <tt>/etc</tt> or across different
+ directories. This might not work at all if directories are on different
+ filesystems (which can happen anytime as the system administrator sees fit),
+ certain filesystems such as AFS don't even support cross-directory hardlinks
+ at all.
+ .
+ For configuration files, certain editors might break hardlinks, and so
+ does dpkg in certain cases.
+ .
+ A better solution might be using symlinks here.
+Ref: policy 10.7.3
+
+Tag: package-contains-bts-control-dir
+Severity: normal
+Certainty: certain
+Info: The package contains a control directory for a bug tracking system.
+ It was most likely installed by accident, since bug tracking directories
+ usually don't belong in packages.
+
+Tag: package-contains-vcs-control-dir
+Severity: normal
+Certainty: certain
+Info: The package contains a control directory for a version control system.
+ It was most likely installed by accident, since version control directories
+ usually don't belong in packages.
+
+Tag: package-contains-xvpics-dir
+Severity: important
+Certainty: certain
+Info: Package contains a .xvpics directory. It was most likely installed by
+ accident, since thumbnails usually don't belong in packages.
+
+Tag: package-contains-vcs-control-file
+Severity: normal
+Certainty: certain
+Info: The package contains a VCS control file such as .(cvs|git|hg)ignore.
+ Files such as these are used by revision control systems to, for example,
+ specify untracked files it should ignore or inventory files.  This file
+ is generally useless in an installed package and was probably installed
+ by accident.
+
+Tag: svn-commit-file-in-package
+Severity: normal
+Certainty: certain
+Info: The package contains an svn-commit(.NNN).tmp file.  This file is
+ almost certainly a left-over from a failed Subversion commit, and does
+ not belong in a Debian package.
+
+Tag: svk-commit-file-in-package
+Severity: normal
+Certainty: certain
+Info: The package contains an svk-commitNNN.tmp file.  This file is almost
+ certainly a left-over from a failed Subversion commit, and does not
+ belong in a Debian package.
+
+Tag: nested-examples-directory
+Severity: important
+Certainty: certain
+Info: Package contains a <tt>usr/share/doc/something/examples/examples</tt>
+ directory. It was most likely installed by accident, since one examples/
+ directory should be enough for everybody(tm).
+
+Tag: package-installs-nonbinary-perl-in-usr-lib-perl5
+Severity: normal
+Certainty: certain
+Info: Architecture-independent Perl code should be placed in
+ <tt>/usr/share/perl5</tt>, not <tt>/usr/lib/perl5</tt>
+ unless there is at least one architecture-dependent file
+ in the module.
+Ref: perl-policy 2.3
+
+Tag: file-in-usr-lib-site-python
+Severity: important
+Certainty: certain
+Ref: python-policy 1.4
+Info: The directory /usr/lib/site-python has been deprecated as a
+ location for installing Python modules and may be dropped from Python's
+ module search path in a future version.  Most likely this module is a
+ private module and should be packaged in a directory outside of Python's
+ default search path.
+
+Tag: package-installs-python-pyc
+Severity: normal
+Certainty: certain
+Info: Compiled python source files should not be included in the package.
+ These files should be removed from the package and created at package
+ installation time in the postinst. 
+
+Tag: bad-permissions-for-ali-file
+Severity: normal
+Certainty: certain
+Info: Ada Library Information (*.ali) files are required to be read-only
+ (mode 0444) by GNAT. 
+ .
+ If at least one user can write the *.ali file, GNAT considers whether
+ or not to recompile the corresponding source file.  Such recompilation
+ would fail because normal users don't have write permission on the
+ files.  Moreover, such recompilation would defeat the purpose of
+ library packages, which provide *.a and *.so libraries to link against).
+
+Tag: package-contains-readme-for-other-platform-or-distro
+Severity: normal
+Certainty: certain
+Info: package contains a README.(platform) file that contains instructions
+ specific to a platform or distribution other than Debian and thus can
+ most likely be removed.  If it contains information that pertains to 
+ Debian, please consider renaming it, or including it in an already 
+ existing README file.
+
+Tag: desktop-file-in-wrong-dir
+Severity: normal
+Certainty: certain
+Info: The package contains a .desktop file in an obsolete directory.
+ According to the menu-spec draft on freedesktop.org, those .desktop files
+ that are intended to create a menu should be placed in
+ <tt>/usr/share/applications</tt>, not <tt>/usr/share/gnome/apps</tt>.
+
+Tag: script-with-language-extension
+Severity: normal
+Certainty: certain
+Info: When scripts are installed into a directory in the system PATH, the
+ script name should not include an extension such as <tt>.sh</tt> or
+ <tt>.pl</tt> that denotes the scripting language currently used to
+ implement it.  The implementation language may change; if it does,
+ leaving the name the same would be confusing and changing it would be
+ disruptive.
+Ref: policy 10.4
+
+Tag: file-in-usr-lib-sgml
+Severity: normal
+Certainty: certain
+Ref: fhs theusrhierarchy
+Info: This package installs a file in <tt>/usr/lib/sgml</tt>.  This was
+ the old location for SGML catalogs and similar flies.  All those files
+ should now go into <tt>/usr/share/sgml</tt>.
+
+Tag: file-name-ends-in-whitespace
+Severity: normal
+Certainty: possible
+Info: This package installs a file or directory whose name ends in
+ whitespace.  This might be intentional but it's normally a mistake.  If
+ it is intentional, add a lintian override.
+ .
+ One possible cause is using debhelper 5.0.57 or earlier to install a
+ doc-base file with a Document field that ends in whitespace.
+
+Tag: package-contains-empty-directory
+Severity: wishlist
+Certainty: possible
+Info: This package installs an empty directory.  This might be intentional
+ but it's normally a mistake.  If it is intentional, add a lintian override.
+ .
+ If a package ships with or installs empty directories, you can remove them
+ in debian/rules by calling:
+ .
+  $ find path/to/base/dir -type d -empty -delete
+
+Tag: package-section-games-but-contains-no-game
+Severity: important
+Certainty: certain
+Ref: policy 11.11
+Info: This package is marked as part of the section games, but doesn't
+ contain files in <tt>/usr/games</tt>.  Binaries of games must be installed
+ in <tt>/usr/games</tt>.
+
+Tag: package-section-games-but-has-usr-bin
+Severity: normal
+Certainty: possible
+Ref: policy 11.11
+Info: This package is marked as part of the section games, but contains
+ executables in <tt>/bin</tt> or <tt>/usr/bin/</tt>.  This can be intentional,
+ but is usually a mistake.
+
+Tag: games-package-should-be-section-games
+Severity: normal
+Certainty: possible
+Info: All the executables in this package are in <tt>/usr/games</tt>, but
+ the package is not in section games.  This can be intentional but is
+ usually a mistake.
+
+Tag: package-contains-devhelp-file-without-symlink
+Severity: normal
+Certainty: certain
+Info: This package contains a *.devhelp or *.devhelp2 file which is not in
+ the devhelp search path (<tt>/usr/share/devhelp/books</tt> and
+ <tt>/usr/share/gtk-doc/html</tt>) and is apparently not in a directory
+ linked into the devhelp search path.  This will prevent devhelp from
+ finding the documentation.
+ .
+ If the devhelp documentation is installed in a path outside the devhelp
+ search path (such as <tt>/usr/share/doc</tt>), create a symlink in
+ <tt>/usr/share/gtk-doc/html</tt> pointing to the documentation directory.
+
+Tag: debug-package-should-be-named-dbg
+Severity: normal
+Certainty: certain
+Info: This package provides at least one file in <tt>/usr/lib/debug</tt>,
+ which is intended for detached debugging symbols, but the package name
+ does not end in "-dbg".  Detached debugging symbols should be put into a
+ separate package, Priority: extra, with a package name ending in "-dbg".
+Ref: devref 6.7.9
+
+Tag: package-contains-linda-override
+Severity: normal
+Certainty: certain
+Info: This package contains a linda override file in
+ <tt>/usr/share/linda/overrides</tt>.  Linda is obsolete and has been
+ removed from the archive as of 2008-03-04.  Linda overrides should
+ probably be dropped from packages.
+
+Tag: wrong-file-owner-uid-or-gid
+Severity: serious
+Certainty: certain
+Info: The user or group ID of the owner of the file is invalid. The
+ owner user and group IDs must be in the set of globally allocated
+ IDs, because other IDs are dynamically allocated and might be used
+ for varying purposes on different systems, or are reserved. The set
+ of the allowed, globally allocated IDs consists of the ranges 0-99,
+ 64000-64999 and 65534.
+Ref: policy 9.2
+
+Tag: embedded-javascript-library
+Severity: normal
+Certainty: possible
+Info: This package contains an embedded copy of JavaScript libraries
+ that are now available in their own packages (for example, JQuery,
+ Prototype, Mochikit or "Cropper"). Please depend on the appropriate
+ package and symlink the library into the appropriate location.
+Ref: policy 4.13
+
+Tag: embedded-feedparser-library
+Severity: normal
+Certainty: certain
+Info: This package contains an embedded copy of Mark Pilgrim's Universal
+ Feed Parser. Please depend on the "python-feedparser" package and symlink
+ the library into the appropriate location.
+Ref: policy 4.13
+
+Tag: embedded-pear-module
+Severity: normal
+Certainty: possible
+Experimental: yes
+Info: This package appears to contain an embedded copy of a PEAR module.
+ Please depend on the respective PEAR package providing the module and
+ make sure the library can be found by the scripts via the include_path.
+Ref: policy 4.13
+
+Tag: embedded-php-library
+Severity: normal
+Certainty: possible
+Info: This package appears to contain an embedded copy of a PHP library.
+ Please depend on the respective package providing the library and
+ make sure it can be found by the scripts via the include_path.
+Ref: policy 4.13
+
+Tag: windows-devel-file-in-package
+Severity: normal
+Certainty: possible
+Info: This package appears to contain development files only meaningful to
+ Windows environments.  Such files are generally useless in Debian packages and
+ were usually accidentally included by copying complete directories from the
+ source tarball.
+
+Tag: font-in-non-font-package
+Severity: wishlist
+Certainty: possible
+Info: This package contains a *.ttf or *.otf file, file extensions
+ normally used for TrueType or OpenType fonts, but the package does not
+ appear to be a dedicated font package.  (Dedicated font package names
+ should begin with <tt>ttf-</tt> or <tt>otf-</tt>.)  If the font is
+ already packaged, you should depend on that package instead.  Otherwise,
+ normally the font should be packaged separately, since fonts are usually
+ useful outside of the package that embeds them.
+
+Tag: duplicate-font-file
+Severity: normal
+Certainty: possible
+Info: This package appears to include a font file that is already provided
+ by another package in Debian.  Ideally should instead depend on the
+ relevant font package.  If the application in this package loads the font
+ file by name, you may need to include a symlink pointing to the file name
+ of the font in its Debian package.
+ .
+ Sometimes the font package containing the font is huge and you only need
+ one font.  In that case, you have a few options: modify the package (in
+ conjunction with upstream) to use libfontconfig to find the font that you
+ prefer but fall back on whatever installed font is available, ask that
+ the font package be split apart into packages of a more reasonable size,
+ or add an override and be aware of the duplication when new versions of
+ the font are released.
+
+Tag: icon-size-and-directory-name-mismatch
+Severity: normal
+Certainty: certain
+Info: The icon has a size that differs from the size specified by the name
+ of the directory under which it was installed.  The icon was probably
+ mistakenly installed into the wrong directory.
+
+Tag: gz-file-not-gzip
+Severity: normal
+Certainty: possible
+Info: The given file ends with <tt>.gz</tt>, which normally indicates it
+ is compressed with gzip.  However, it doesn't seem to be a
+ gzip-compressed file.  gzip will fail with an error on such files.
+ Normally this indicates a mistake in the installation process of the
+ package.
+
+Tag: non-free-flash
+Severity: serious
+Certainty: possible
+Info: The given Flash file has a filename which suggests that it may be
+ one of a number of known Flash files with non-free content.
diff --git a/checks/huge-usr-share b/checks/huge-usr-share
new file mode 100644 (file)
index 0000000..c4c5ca4
--- /dev/null
@@ -0,0 +1,63 @@
+# huge-usr-share -- lintian check script -*- perl -*-
+
+# Copyright (C) 2004 Jeroen van Wolffelaar <jeroen@wolffelaar.nl>
+#
+# 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 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.
+
+package Maemian::huge_usr_share;
+use strict;
+use Tags;
+
+# Threshold in kB of /usr/share to trigger this warning.  Consider that the
+# changelog alone can be quite big, and cannot be moved away.
+my $THRESHOLD_SIZE_SOFT = 1024;
+my $THRESHOLD_SIZE_HARD = 2048;
+my $THRESHOLD_PERC = 50;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+# Skip architecture-dependent packages.
+my $arch = $info->field('architecture') || '';
+return 0 if $arch eq 'all';
+
+# usr/share missing. other checks will moan about it
+# so just ignore this package
+return 0 if !-d 'unpacked/usr/share';
+
+my $size = `du -ks unpacked`;
+$size =~ s/\t.*//;
+$size = int $size;
+
+my $size_usrshare = `du -ks unpacked/usr/share`;
+$size_usrshare =~ s/\t.*//;
+$size_usrshare = int $size_usrshare;
+
+if ($size_usrshare > $THRESHOLD_SIZE_SOFT) {
+    my $perc = int (100 * $size_usrshare / $size);
+    if (($size_usrshare > $THRESHOLD_SIZE_HARD)
+       || ($perc > $THRESHOLD_PERC)) { 
+       tag "arch-dep-package-has-big-usr-share", "${size_usrshare}kB $perc%";
+    }
+}
+
+}
+
+1;
diff --git a/checks/huge-usr-share.desc b/checks/huge-usr-share.desc
new file mode 100644 (file)
index 0000000..44e3a7c
--- /dev/null
@@ -0,0 +1,19 @@
+Check-Script: huge-usr-share
+Author: Jeroen van Wolffelaar <jeroen@wolffelaar.nl>
+Abbrev: hus
+Type: binary
+Unpack-Level: 2
+Info: This script checks whether an architecture-dependent package doesn't
+ have a significantly big /usr/share
+
+Tag: arch-dep-package-has-big-usr-share
+Severity: wishlist
+Certainty: certain
+Info: The package has a significant amount of architecture-independent data in
+ /usr/share, while it is an architecture-dependent package.
+ This is wasteful of mirror space and bandwidth, as we then end up with
+ multiple copies of this data, one for each architecture.
+ .
+ If the data in /usr/share is not architecture-independent, it is a policy
+ violation, and in this case, you should move that data elsewhere.
+Ref: devref 6.7.5
diff --git a/checks/infofiles b/checks/infofiles
new file mode 100644 (file)
index 0000000..d447dec
--- /dev/null
@@ -0,0 +1,206 @@
+# infofiles -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Christian Schwarz
+# Copyright (C) 2001 Josip Rodin
+#
+# 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 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.
+
+package Maemian::infofiles;
+use strict;
+
+use lib "$ENV{'MAEMIAN_ROOT'}/checks/";
+use common_data;
+
+use Tags;
+use Util;
+use File::Basename;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+my %preinst;
+my %postinst;
+my %prerm;
+my %postrm;
+
+my %missing_section;
+
+# check maintainer scripts (for install-info invocation)
+check_script("preinst", \%preinst) if (-f "control/preinst");
+check_script("postinst", \%postinst) if (-f "control/postinst");
+check_script("prerm", \%prerm) if (-f "control/prerm");
+check_script("postrm", \%postrm) if (-f "control/postrm");
+
+# Read package contents...
+foreach my $file (sort keys %{$info->index}) {
+    my $index_info = $info->index->{$file};
+    my $file_info = $info->file_info->{$file};
+    my $link = $index_info->{link} || '';
+    my ($fname, $path, $suffix) = fileparse($file);
+
+    next unless ($index_info->{type} =~ m,^[\-lh],o)
+           and ($path =~ m,^usr/share/info/, or $path =~ m,^usr/info/,);
+
+    # Analyze the file names making sure the documents are named properly.
+    # Note that Emacs 22 added support for images in info files, so we have to
+    # accept those and ignore them.  Just ignore .png files for now.
+    my @fname_pieces = split /\./, $fname;
+    my $ext = pop @fname_pieces;
+    if ($ext eq "gz") { # ok!
+       if ($index_info->{type} =~ m,^[-h],o) { # compressed with maximum compression rate?
+           if ($file_info !~ m/gzip compressed data/o) {
+               tag "info-document-not-compressed-with-gzip", "$file";
+           } else {
+               if ($file_info !~ m/max compression/o) {
+                   tag "info-document-not-compressed-with-max-compression", "$file";
+               }
+           }
+       }
+    } elsif ($ext eq 'png') {
+        next;
+    } else {
+       push (@fname_pieces, $ext);
+       tag "info-document-not-compressed", "$file";
+    }
+    my $infoext = pop @fname_pieces;
+    unless ($infoext && $infoext =~ /info(-\d)?/) { # it's not foo.info
+       unless (!@fname_pieces) { # it's not foo{,-{1,2,3,...}}
+           tag "info-document-has-wrong-extension", "$file";
+       }
+    }
+
+    # If this is the main info file (no numeric extension). make sure it has
+    # appropriate dir entry information.
+    if ($fname !~ /-\d+\.gz/ && $file_info =~ /gzip compressed data/) {
+       my $pid = open INFO, '-|';
+       if (not defined $pid) {
+           fail("cannot fork: $!");
+       } elsif ($pid == 0) {
+           my %newenv = (LANG => 'C', PATH => $ENV{PATH});
+           undef %ENV;
+           %ENV = %newenv;
+           exec "zcat \Qunpacked/$file\E 2>&1"
+               or fail("cannot run zcat: $!");
+       }
+       local $_;
+       my ($section, $start, $end);
+       while (<INFO>) {
+           $section = 1 if /INFO-DIR-SECTION\s+\S/;
+       }
+       close INFO;
+       $missing_section{$file} = 1 unless $section;
+    }
+}
+
+# policy 13.2 says prerm and postinst
+if ($postrm{'calls-install-info'}) {
+    tag "postrm-calls-install-info", "";
+}
+if ($preinst{'calls-install-info'}) {
+    tag "preinst-calls-install-info", "";
+}
+
+if ($postinst{'calls-install-info'}) {
+    tag "install-info-not-called-with-quiet-option", ""
+       unless $postinst{'calls-install-info-quiet'};
+}
+if ($prerm{'calls-install-info'}) {
+    # it must use the --quiet option
+    tag "install-info-not-called-with-quiet-option", ""
+       unless $prerm{'calls-install-info-quiet'};
+}
+
+# Currently we assume all the info pages are fine if any of them are installed
+# with an explicit --section option.  It would be nice to be stricter.
+for my $file (keys %missing_section) {
+    tag "info-document-missing-dir-section", "$file"
+       unless ($postinst{'calls-install-info-section'});
+}
+
+# Ideally we'd check whether all documents installed are removed,
+# but for now we assume that if any are removed then they all are
+if ($postinst{'calls-install-info'}) {
+    tag "info-documents-not-removed", ""
+       unless ($prerm{'calls-install-info-remove'});
+}
+
+}
+
+# -----------------------------------
+
+sub check_script {
+    my ($script,$pres) = @_;
+    my ($no_check_menu,$no_check_installdocs);
+    my $interp;
+
+    open(IN, '<', "control/$script") or
+       fail("cannot open maintainer script control/$script for reading: $!");
+    $interp = <IN>;
+    $interp = '' unless defined $interp;
+    if ($interp =~ m,^\#\!\s*/bin/$known_shells_regex,) {
+       $interp = 'sh';
+    } elsif ($interp =~ m,^\#\!\s*/usr/bin/perl,) {
+       $interp = 'perl';
+    } else {
+       if ($interp =~ m,^\#\!\s*(.+),) {
+           $interp = $1;
+       }
+       else { # hmm, doesn't seem to start with #!
+           # is it a binary? look for ELF header
+           if ($interp =~ m/^\177ELF/) {
+               return; # nothing to do here
+           }
+           $interp = 'unknown';
+       }
+    }
+
+    my $hold;
+    while (<IN>) {
+       s/\s+#.*$//;
+       # this wraps a previous line continuation into the current line
+       if (defined $hold) {
+           $_ = "$hold $_";
+           $hold = undef;
+       }
+       # check if install-info is called and if so, is it called properly
+       if (m/install-info/o) {
+           if (m,\\$,) {
+               $hold = substr($_, 0, -1);
+               next;
+           }
+           $pres->{'calls-install-info'} = 1;
+           my @pieces = split(/\s+/);
+           for my $piece (@pieces) {
+               if ($piece eq '--quiet') {
+                   $pres->{'calls-install-info-quiet'} = 1;
+               } elsif ($piece eq '--section') {
+                   $pres->{'calls-install-info-section'} = 1;
+               } elsif ($piece eq '--remove' or $piece eq '--remove-exactly') {
+                   $pres->{'calls-install-info-remove'} = 1;
+               }
+           }
+       }
+    }
+    close IN;
+}
+
+1;
+
+# vim: syntax=perl
diff --git a/checks/infofiles.desc b/checks/infofiles.desc
new file mode 100644 (file)
index 0000000..d5a90f2
--- /dev/null
@@ -0,0 +1,82 @@
+Check-Script: infofiles
+Author: Josip Rodin <jrodin@jagor.srce.hr>
+Abbrev: info
+Type: binary
+Unpack-Level: 2
+Needs-Info: file-info
+Info: This script checks if a binary package conforms to info document policy.
+
+Tag: info-document-not-compressed
+Severity: important
+Certainty: certain
+Info: Info documents have to be installed compressed (using <tt>gzip -9</tt>).
+Ref: policy 12.2
+
+Tag: info-document-not-compressed-with-gzip
+Severity: important
+Certainty: certain
+Info: Info documents should be compressed with <tt>gzip -9</tt>.
+Ref: policy 12.2
+
+Tag: info-document-not-compressed-with-max-compression
+Severity: important
+Certainty: certain
+Info: Info documents should be compressed with <tt>gzip -9</tt>.
+Ref: policy 12.2
+
+Tag: info-document-has-wrong-extension
+Severity: normal
+Certainty: certain
+Info: The info document has an extension other than info*.gz.
+Ref: policy 12.2
+
+Tag: info-document-missing-dir-section
+Severity: normal
+Certainty: certain
+Info: This info document has no INFO-DIR-SECTION line and no
+ <tt>--section</tt> option is passed to <tt>install-info</tt> in the
+ package <tt>postinst</tt> maintainer script.  <tt>install-info</tt> will
+ be unable to determine the section into which this info page should be
+ put.  The best solution is to add a line like:
+ .
+   @dircategory Development
+ .
+ to the texinfo source so that the generated info file will contain a
+ section.  See /usr/share/info/dir for sections to choose from.
+ .
+ Another alternative that doesn't require modifying the source is to
+ explicitly pass a <tt>--section</tt> option to <tt>install-info</tt> in
+ the <tt>postinst</tt> maintainer script, although in this case you will
+ need to write the <tt>postinst</tt> yourself and not use tools like
+ debhelper to do it for you.
+Ref: policy 12.2
+
+Tag: postrm-calls-install-info
+Severity: normal
+Certainty: certain
+Ref: policy 12.2
+Info: The postrm script calls the <tt>install-info</tt> command. Usually,
+ this command should be called from the <tt>prerm</tt> maintainer script.
+
+Tag: preinst-calls-install-info
+Severity: important
+Certainty: certain
+Ref: policy 12.2
+Info: The preinst script calls the <tt>install-info</tt> command. Usually,
+ this command should be called from the <tt>postinst</tt> maintainer script.
+
+Tag: install-info-not-called-with-quiet-option
+Severity: normal
+Certainty: certain
+Ref: policy 3.9
+Info: The package installation scripts should avoid producing output which it
+ is unnecessary for the user to see.  Policy specifically mentions install-info
+ output as unnecessary.
+
+Tag: info-documents-not-removed
+Severity: normal
+Certainty: certain
+Ref: policy 12.2
+Info: The postinst script calls the <tt>install-info</tt> command, but the
+ documents installed are not then removed by the <tt>prerm</tt>  maintainer
+ script.
diff --git a/checks/init.d b/checks/init.d
new file mode 100644 (file)
index 0000000..f1d7e6b
--- /dev/null
@@ -0,0 +1,269 @@
+# init.d -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Christian Schwarz
+#
+# 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 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.
+
+package Maemian::init_d;
+use strict;
+use Tags;
+use Util;
+
+# A list of valid LSB keywords.         The value is 0 if optional and 1 if required.
+my %lsb_keywords = (provides           => 1,
+                   'required-start'    => 1,
+                   'required-stop'     => 1,
+                   'should-start'      => 0,
+                   'should-stop'       => 0,
+                   'default-start'     => 1,
+                   'default-stop'      => 1,
+                   'short-description' => 1,
+                   'description'       => 0);
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+
+my $postinst = "control/postinst";
+my $preinst = "control/preinst";
+my $postrm = "control/postrm";
+my $prerm = "control/prerm";
+my $conffiles = "control/conffiles";
+
+my %initd_postinst;
+my %initd_postrm;
+my %conffiles;
+
+my $opts_r = qr/-\S+\s*/;
+my $name_r = qr/[\w.-]+/;
+my $action_r = qr/\w+/;
+my $exclude_r = qr/if\s+\[\s+-x\s+\S*update-rc\.d/;
+
+# read postinst control file
+if (open(IN, '<', $postinst)) {
+    while (<IN>) {
+       next if /$exclude_r/o;
+       s/\#.*$//o;
+       next unless /^(?:.+;|^\s*system[\s\(\']+)?\s*update-rc\.d\s+
+           (?:$opts_r)*($name_r)\s+($action_r)/xo;
+       my ($name,$opt) = ($1,$2);
+       next if $opt eq 'remove';
+       if ($initd_postinst{$name}++ == 1) {
+           tag "duplicate-updaterc.d-calls-in-postinst", "$name";
+           next;
+       }
+       unless (m,>\s*/dev/null,o) {
+           tag "output-of-updaterc.d-not-redirected-to-dev-null", "$name postinst";
+       }
+    }
+}
+close(IN);
+
+# read preinst control file
+if (open(IN, '<', $preinst)) {
+    while (<IN>) {
+       next if /$exclude_r/o;
+       s/\#.*$//o;
+       next unless m/update-rc\.d\s+(?:$opts_r)*($name_r)\s+($action_r)/o;
+       my ($name,$opt) = ($1,$2);
+       next if $opt eq 'remove';
+       tag "preinst-calls-updaterc.d", "$name";
+    }
+    close(IN);
+}
+
+# read postrm control file
+if (open(IN, '<', $postrm)) {
+    while (<IN>) {
+       next if /$exclude_r/o;
+       s/\#.*$//o;
+       next unless m/update-rc\.d\s+($opts_r)*($name_r)/o;
+       if ($initd_postrm{$2}++ == 1) {
+           tag "duplicate-updaterc.d-calls-in-postrm", "$2";
+           next;
+       }
+       unless (m,>\s*/dev/null,o) {
+           tag "output-of-updaterc.d-not-redirected-to-dev-null", "$2 postrm";
+       }
+    }
+    close(IN);
+}
+
+# read prerm control file
+if (open(IN, '<', $prerm)) {
+    while (<IN>) {
+       next if /$exclude_r/o;
+       s/\#.*$//o;
+       next unless m/update-rc\.d\s+($opts_r)*($name_r)/o;
+       tag "prerm-calls-updaterc.d", "$2";
+    }
+    close(IN);
+}
+
+# init.d scripts have to be removed in postrm
+for (keys %initd_postinst) {
+    if ($initd_postrm{$_}) {
+       delete $initd_postrm{$_};
+    } else {
+       tag "postrm-does-not-call-updaterc.d-for-init.d-script", "/etc/init.d/$_";
+    }
+}
+for (keys %initd_postrm) {
+    tag "postrm-contains-additional-updaterc.d-calls", "/etc/init.d/$_";
+}
+
+# load conffiles
+if (open(IN, '<', $conffiles)) {
+    while (<IN>) {
+       chop;
+       next if m/^\s*$/o;
+       $conffiles{$_} = 1;
+
+       if (m,^/?etc/rc.\.d,o) {
+           tag "file-in-etc-rc.d-marked-as-conffile", "$_";
+       }
+    }
+    close(IN);
+}
+
+for (keys %initd_postinst) {
+    next if /^\$/;
+    # init.d scripts have to be marked as conffiles
+    unless ($conffiles{"/etc/init.d/$_"} or $conffiles{"etc/init.d/$_"}) {
+       tag "init.d-script-not-marked-as-conffile", "/etc/init.d/$_";
+    }
+
+    # check if file exists in package
+    my $initd_file = "init.d/$_";
+    if (-f $initd_file) {
+       # yes! check it...
+       open(IN, '<', $initd_file)
+           or fail("cannot open init.d file $initd_file: $!");
+       my (%tag, %lsb);
+       while (defined(my $l = <IN>)) {
+           if ($l =~ m/^\#\#\# BEGIN INIT INFO/) {
+               if ($lsb{BEGIN}) {
+                   tag "init.d-script-has-duplicate-lsb-section", "/etc/init.d/$_";
+                   next;
+               }
+               $lsb{BEGIN} = 1;
+               my $last;
+
+               # We have an LSB keyword section.  Parse it and save the data
+               # in %lsb for analysis.
+               while (defined(my $l = <IN>)) {
+                   if ($l =~ /^\#\#\# END INIT INFO/) {
+                       $lsb{END} = 1;
+                       last;
+                   } elsif ($l !~ /^\#/) {
+                       tag "init.d-script-has-unterminated-lsb-section", "/etc/init.d/$_:$.";
+                       last;
+                   } elsif ($l =~ /^\# ([a-zA-Z-]+):\s*(.*?)\s*$/) {
+                       my $keyword = lc $1;
+                       my $value = $2;
+                       tag "init.d-script-has-duplicate-lsb-keyword", "/etc/init.d/$_:$. $keyword"
+                           if (defined $lsb{$keyword});
+                       tag "init.d-script-has-unknown-lsb-keyword", "/etc/init.d/$_:$. $keyword"
+                           unless (defined ($lsb_keywords{$keyword}) || $keyword =~ /^x-/);
+                       $lsb{$keyword} = $value || '';
+                       $last = $keyword;
+                   } elsif ($l =~ /^\#(\t|  )/ && $last eq 'description') {
+                       my $value = $l;
+                       $value =~ s/^\#\s*//;
+                       $lsb{description} .= ' ' . $value;
+                   } else {
+                       tag "init.d-script-has-bad-lsb-line", "/etc/init.d/$_:$.";
+                   }
+               }
+           }
+
+           while ($l =~ s/(start|stop|restart|force-reload)//o) {
+               $tag{$1} = 1;
+           }
+        }
+       close(IN);
+
+       # Make sure all of the required keywords are present.
+       if (not $lsb{BEGIN}) {
+           tag "init.d-script-missing-lsb-section", "/etc/init.d/$_";
+       } else {
+           for my $keyword (keys %lsb_keywords) {
+               if ($lsb_keywords{$keyword} && !defined $lsb{$keyword}) {
+                   if ($keyword eq 'short-description') {
+                       tag "init.d-script-missing-lsb-short-description", "/etc/init.d/$_";
+                   } else {
+                       tag "init.d-script-missing-lsb-keyword", "/etc/init.d/$_ $keyword";
+                   }
+               }
+           }
+       }
+
+       # Check the runlevels.
+       my %start;
+       if ($lsb{'default-start'}) {
+           for my $runlevel (split (/\s+/, $lsb{'default-start'})) {
+               if ($runlevel =~ /^[sS0-6]$/) {
+                   $start{lc $runlevel} = 1;
+               } else {
+                   tag "init.d-script-has-bad-start-runlevel", "/etc/init.d/$_ $runlevel";
+               }
+           }
+       }
+       if ($lsb{'default-stop'}) {
+           for my $runlevel (split (/\s+/, $lsb{'default-stop'})) {
+               if ($runlevel =~ /^[sS0-6]$/) {
+                   if ($start{$runlevel}) {
+                       tag "init.d-script-has-conflicting-start-stop", "/etc/init.d/$_ $runlevel";
+                   }
+                   if ($runlevel =~ /[sS]/) {
+                       tag "init-d-script-stops-in-s-runlevel", "/etc/init.d/$_";
+                   }
+               } else {
+                   tag "init.d-script-has-bad-stop-runlevel", "/etc/init.d/$_ $runlevel";
+               }
+           }
+       }
+
+       # all tags included in file?
+       $tag{'start'} or tag "init.d-script-does-not-implement-required-option", "/etc/init.d/$_ start";
+       $tag{'stop'} or tag "init.d-script-does-not-implement-required-option", "/etc/init.d/$_ stop";
+       $tag{'restart'} or tag "init.d-script-does-not-implement-required-option", "/etc/init.d/$_ restart";
+       $tag{'force-reload'} or tag "init.d-script-does-not-implement-required-option", "/etc/init.d/$_ force-reload";
+    } else {
+       tag "init.d-script-not-included-in-package", "/etc/init.d/$_";
+    }
+}
+
+# files actually installed in /etc/init.d should match our list :-)
+opendir(INITD, "init.d") or fail("cannot read init.d directory: $!");
+for (readdir(INITD)) {
+    next if $_ eq '.' || $_ eq '..';
+    tag "script-in-etc-init.d-not-registered-via-update-rc.d", "/etc/init.d/$_"
+       unless $initd_postinst{$_};
+}
+closedir(INITD);
+
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl ts=8
diff --git a/checks/init.d.desc b/checks/init.d.desc
new file mode 100644 (file)
index 0000000..6f6f12a
--- /dev/null
@@ -0,0 +1,201 @@
+Check-Script: init.d
+Author: Christian Schwarz <schwarz@debian.org>
+Abbrev: ini
+Type: binary
+Needs-Info: init.d
+Unpack-Level: 1
+
+Tag: duplicate-updaterc.d-calls-in-postinst
+Severity: important
+Certainty: certain
+Info: The <tt>postinst</tt> script calls <tt>update-rc.d</tt> several
+ times for the same <tt>/etc/init.d</tt> script.
+
+Tag: output-of-updaterc.d-not-redirected-to-dev-null
+Severity: wishlist
+Certainty: certain
+Info: The output messages of the <tt>update-rc.d</tt> command should be
+ redirected to <tt>/dev/null</tt> because it is currently very chatty
+ per default.
+
+Tag: preinst-calls-updaterc.d
+Severity: important
+Certainty: certain
+Info: The <tt>preinst</tt> package calls <tt>update-rc.d</tt>. Instead,
+ you should call it in the <tt>postinst</tt> script.
+Ref: policy 9.3.3.1
+
+Tag: duplicate-updaterc.d-calls-in-postrm
+Severity: important
+Certainty: certain
+Info: The <tt>postrm</tt> script calls <tt>update-rc.d</tt> several
+ times for the same <tt>/etc/init.d</tt> script.
+
+Tag: prerm-calls-updaterc.d
+Severity: important
+Certainty: certain
+Info: The <tt>prerm</tt> package calls <tt>update-rc.d</tt>. Instead,
+ you should call it in the <tt>postrm</tt> script.
+Ref: policy 9.3.3.1
+
+Tag: postrm-does-not-call-updaterc.d-for-init.d-script
+Severity: important
+Certainty: certain
+Info: An <tt>/etc/init.d</tt> script which has been registered in the
+ <tt>postinst</tt> script is not de-registered in the
+ <tt>postrm</tt> script.
+Ref: policy 9.3.3.1
+
+Tag: postrm-contains-additional-updaterc.d-calls
+Severity: important
+Certainty: certain
+Info: The <tt>postrm</tt> de-registers an <tt>/etc/init.d</tt> script which
+ has not been registered in the <tt>postinst</tt> script before.
+
+Tag: file-in-etc-rc.d-marked-as-conffile
+Severity: important
+Certainty: certain
+Ref: policy 9.3.3
+Info: The symbolic links in <tt>/etc/rc?.d</tt> may not be marked as conffiles.
+
+Tag: init.d-script-not-marked-as-conffile
+Severity: important
+Certainty: wild-guess
+Ref: policy 9.3.2
+Info: <tt>/etc/init.d</tt> scripts should be marked as conffiles.
+ .
+ This is usually an error, but the Policy allows for managing these files
+ manually in maintainer scripts and Maemian cannot reliably detect that.
+
+Tag: init.d-script-does-not-implement-required-option
+Severity: important
+Certainty: certain
+Ref: policy 9.3.2
+Info: The <tt>/etc/init.d</tt> scripts have to support the following
+ command line arguments: start, stop, restart, force-reload.
+
+Tag: init.d-script-not-included-in-package
+Severity: important
+Certainty: certain
+Info: The <tt>/etc/init.d</tt> script is registered in the
+ <tt>postinst</tt> script, but is not included in the package.
+
+Tag: script-in-etc-init.d-not-registered-via-update-rc.d
+Severity: normal
+Certainty: possible
+Info: The package installs an <tt>/etc/init.d</tt> script which is
+ not registered in the <tt>postinst</tt> script. This is usually a bug,
+ unless you omit the links intentionally for some reason or create the
+ links some other way.
+
+Tag: init.d-script-has-duplicate-lsb-section
+Severity: important
+Certainty: certain
+Ref: http://wiki.debian.org/LSBInitScripts
+Info: This <tt>/etc/init.d</tt> script has more than one LSB keyword
+ section.  These sections start with <tt>### BEGIN INIT INFO</tt> and end
+ with <tt>### END INIT INFO</tt>.  There should be only one such section
+ per init script.
+
+Tag: init.d-script-has-unterminated-lsb-section
+Severity: important
+Certainty: certain
+Ref: http://wiki.debian.org/LSBInitScripts
+Info: This <tt>/etc/init.d</tt> script has an LSB keyword section starting
+ with <tt>### BEGIN INIT INFO</tt> but either has no matching <tt>### END
+ INIT INFO</tt> or has lines between those two markers that are not
+ comments.  The line number given is the first line that doesn't look like
+ part of an LSB keyword section.  There must be an end marker after all
+ the keyword settings and there must not be any lines between those
+ markers that do not begin with <tt>#</tt>.
+
+Tag: init.d-script-has-duplicate-lsb-keyword
+Severity: normal
+Certainty: certain
+Ref: http://wiki.debian.org/LSBInitScripts
+Info: The given keyword was set twice in the LSB keyword section in this
+ <tt>/etc/init.d</tt> script.  This is probably a mistake; the behavior of
+ setting the same keyword twice is undefined.
+
+Tag: init.d-script-has-unknown-lsb-keyword
+Severity: minor
+Certainty: certain
+Ref: http://wiki.debian.org/LSBInitScripts
+Info: The given keyword was set in the LSB keyword section in this
+ <tt>/etc/init.d</tt> script but isn't one of the known LSB keywords and
+ doesn't begin with <tt>X-</tt>.  One of the standard keywords may have
+ been misspelled.
+
+Tag: init.d-script-has-bad-lsb-line
+Severity: normal
+Certainty: certain
+Ref: http://wiki.debian.org/LSBInitScripts
+Info: This line in the LSB keyword section of an <tt>/etc/init.d</tt>
+ script doesn't match the required formatting of that section.  Note that
+ keyword settings must start with <tt>#</tt>, a single space, the keyword,
+ a colon, and some whitespace, followed by the value (if any).  Only the
+ Description keyword allows continuation lines, and continuation lines
+ must begin with <tt>#</tt> and either a tab or two or more spaces.
+
+Tag: init.d-script-missing-lsb-section
+Severity: normal
+Certainty: certain
+Ref: http://wiki.debian.org/LSBInitScripts
+Info: This <tt>/etc/init.d</tt> script does not have an LSB keyword
+ section (or the <tt>### BEGIN INIT INFO</tt> tag is incorrect).  This
+ section provides description and runlevel information in a standard
+ format and provides dependency information that can be used to
+ parallelize the boot process.  Please consider adding it.
+
+Tag: init.d-script-missing-lsb-keyword
+Severity: normal
+Certainty: certain
+Ref: http://wiki.debian.org/LSBInitScripts
+Info: This <tt>/etc/init.d</tt> script has an LSB keyword section, but it
+ is missing the given required LSB keyword.  If the value of this keyword
+ should be empty, please still include it in the LSB keyword section with
+ an empty value.
+
+Tag: init.d-script-missing-lsb-short-description
+Severity: wishlist
+Certainty: certain
+Ref: http://wiki.debian.org/LSBInitScripts
+Info: This <tt>/etc/init.d</tt> script has an LSB keyword section, but it
+ is missing a Short-Description LSB keyword.  This field isn't directly
+ used currently, but adding it is still a good idea for documentation
+ purposes.
+
+Tag: init.d-script-has-bad-start-runlevel
+Severity: normal
+Certainty: certain
+Ref: http://wiki.debian.org/LSBInitScripts
+Info: The given runlevel specified in the Default-Start keyword of the LSB
+ keyword section of this <tt>/etc/init.d</tt> script isn't one of the
+ recognized standard runlevels (S, 0, 1, 2, 3, 4, 5, and 6).
+
+Tag: init.d-script-has-bad-stop-runlevel
+Severity: normal
+Certainty: certain
+Ref: http://wiki.debian.org/LSBInitScripts
+Info: The given runlevel specified in the Default-Stop keyword of the LSB
+ keyword section of this <tt>/etc/init.d</tt> script isn't one of the
+ recognized standard runlevels (0, 1, 2, 3, 4, 5, and 6).
+
+Tag: init.d-script-has-conflicting-start-stop
+Severity: normal
+Certainty: certain
+Ref: http://wiki.debian.org/LSBInitScripts
+Info: The given runlevel was included in both the Default-Start and
+ Default-Stop keywords of the LSB keyword section of this
+ <tt>/etc/init.d</tt> script.  Since it doesn't make sense to both start
+ and stop a service in the same runlevel, there is probably an error in
+ one or the other of these keywords.
+
+Tag: init-d-script-stops-in-s-runlevel
+Severity: normal
+Certainty: certain
+Info: This <tt>/etc/init.d</tt> script specifies the S runlevel in
+ Default-Stop in its LSB keyword section.  The S runlevel is not a real
+ runlevel and is only used during boot.  There is no way to switch to it
+ and hence no use for stop scripts for it, so S should be removed from
+ Default-Stop.
diff --git a/checks/manpages b/checks/manpages
new file mode 100644 (file)
index 0000000..ea3c010
--- /dev/null
@@ -0,0 +1,356 @@
+# manpages -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Christian Schwarz
+#
+# 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 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.
+
+package Maemian::manpages;
+use strict;
+use Tags;
+use Util;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+use File::Basename;
+
+my %binary;
+my %link;
+# my %sect_by_binary;
+# my %sect_by_manpage;
+my %manpage;
+
+# Read package contents...
+foreach my $file (sort keys %{$info->index}) {
+    my $index_info = $info->index->{$file};
+    my $file_info = $info->file_info->{$file};
+    my $link = $index_info->{link} || '';
+    my $perm = $index_info->{type};
+    my ($fname, $path, $suffix) = fileparse($file);
+
+    # Binary that wants a manual page?
+    #
+    # It's tempting to check the section of the man page depending on the
+    # location of the binary, but there are too many mismatches between
+    # bin/sbin and 1/8 that it's not clear it's the right thing to do.
+    if (($perm =~ m,^[\-lh],o) and
+       (($path =~ m,^bin/$,o) or
+        ($path =~ m,^sbin/$,o) or
+        ($path =~ m,^usr/bin/$,o) or
+        ($path =~ m,^usr/bin/X11/$,o) or
+        ($path =~ m,^usr/bin/mh/$,o) or
+        ($path =~ m,^usr/sbin/$,o) or
+        ($path =~ m,^usr/games/$,o) or
+        ($path =~ m,^usr/X11R6/bin/$,o) )) {
+
+       my $bin = $fname;
+       $binary{$bin} = $file;
+       $link{$bin} = $link if $link;
+
+       next;
+    }
+
+    if (($path =~ m,usr/(share|X11R6)/man/$,) and ($fname ne "")) {
+       tag "manpage-in-wrong-directory", "$file";
+       next;
+    }
+
+    # manual page?
+    next unless ($perm =~ m,^[\-lh],o) and
+       (($path =~ m,^usr/man(/\S+),o)
+        or ($path =~ m,^usr/X11R6/man(/\S+),o)
+        or ($path =~ m,^usr/share/man(/\S+),o) );
+
+    my $t = $1;
+    if (not $t =~ m,^.*man(\d)/$,o) {
+       tag "manpage-in-wrong-directory", "$file";
+       next;
+    }
+    my ($section,$name) = ($1,$fname);
+    my $lang = "";
+       $lang = $1 if $t =~ m,^/([^/]+)/man\d/$,o;
+
+    # The country should not be part of the man page locale directory unless
+    # it's one of the known cases where the language is significantly
+    # different between countries.
+    if ($lang =~ /_/ && $lang !~ /^(pt_BR|zh_[A-Z][A-Z])$/) {
+       tag "manpage-locale-dir-country-specific", "$file";
+    }
+
+    my @pieces = split(/\./, $name);
+    my $ext = pop @pieces;
+    if ($ext ne 'gz') {
+        push @pieces, $ext;
+       tag "manpage-not-compressed", "$file";
+    } elsif ($perm =~ m,^[-h],o) { # so it's .gz... files first; links later
+       if ($file_info !~ m/gzip compressed data/o) {
+           tag "manpage-not-compressed-with-gzip", "$file";
+       } elsif ($file_info !~ m/max compression/o) {
+           tag "manpage-not-compressed-with-max-compression", "$file";
+       }
+    }
+    my $fn_section = pop @pieces;
+    my $section_num = $fn_section;
+    if (scalar @pieces && $section_num =~ s/^(\d).*$/$1/) {
+       my $bin = join(".", @pieces);
+              $manpage{$bin} = [] unless $manpage{$bin};
+       push @{$manpage{$bin}}, { file => $file, lang => $lang };
+
+       # number of directory and manpage extension equal?
+       if ($section_num != $section) {
+           tag "manpage-in-wrong-directory", "$file";
+       }
+    } else {
+       tag "manpage-has-wrong-extension", "$file";
+    }
+
+    # special check for manual pages for X11 games
+    if ($path =~ m,^usr/X11R6/man/man6/,o) {
+       tag "x11-games-should-be-in-usr-games", "$file";
+    }
+
+    # check symbolic links to other manual pages
+    if ($perm =~ m,^l,o) {
+       if ($link =~ m,(^|/)undocumented,o) {
+           if ($path =~ m,^usr/share/man,o) {
+               # undocumented link in /usr/share/man -- possibilities
+                #    undocumented... (if in the appropriate section)
+               #    ../man?/undocumented...
+               #    ../../man/man?/undocumented...
+               #    ../../../share/man/man?/undocumented...
+               #    ../../../../usr/share/man/man?/undocumented...
+                if ((($link =~ m,^undocumented\.([237])\.gz,o) and
+                    ($path =~ m,^usr/share/man/man$1,)) or
+                    ($link =~ m,^\.\./man[237]/undocumented\.[237]\.gz$,o) or
+                    ($link =~ m,^\.\./\.\./man/man[237]/undocumented\.[237]\.gz$,o) or
+                    ($link =~ m,^\.\./\.\./\.\./share/man/man[237]/undocumented\.[237]\.gz$,o) or
+                    ($link =~ m,^\.\./\.\./\.\./\.\./usr/share/man/man[237]/undocumented\.[237]\.gz$,o)) {
+                   tag "link-to-undocumented-manpage", "$file";
+                } else {
+                   tag "bad-link-to-undocumented-manpage", "$file";
+               }
+           } else {
+               # undocumented link in /usr/X11R6/man -- possibilities:
+               #    ../../../share/man/man?/undocumented...
+               #    ../../../../usr/share/man/man?/undocumented...
+               if (($link =~ m,^\.\./\.\./\.\./share/man/man[237]/undocumented\.[237]\.gz$,o) or
+                   ($link =~ m,^\.\./\.\./\.\./\.\./usr/share/man/man[237]/undocumented\.[237]\.gz$,o)) {
+                   tag "link-to-undocumented-manpage", "$file";
+               } else {
+                   tag "bad-link-to-undocumented-manpage", "$file";
+               }
+           }
+       }
+    } else { # not a symlink
+       open (MANFILE, '-|', "zcat unpacked/\Q$file\E 2>/dev/null")
+           or fail("cannot open $file: $!");
+       my @manfile = ();
+       while (<MANFILE>) { push @manfile, $_; }
+       close MANFILE;
+       # Is it a .so link?
+       if ($index_info->{size} < 256) {
+           my ($i, $first) = (0, "");
+           do {
+               $first = $manfile[$i++] || ""; 
+           } while ($first =~ /^\.\\"/ && $manfile[$i]); #");
+
+           unless ($first) {
+               tag "empty-manual-page", "$file";
+           } elsif ($first =~ /^\.so\s+(.+)?$/) {
+               my $dest = $1;
+               if ($dest =~ m,^([^/]+)/(.+)$,) {
+                   my ($manxorlang, $rest) = ($1, $2);
+                   if ($manxorlang !~ /^man\d+$/) {
+                       # then it's likely a language subdir, so let's run
+                       # the other component through the same check
+                       if ($rest =~ m,^([^/]+)/(.+)$,) {
+                           my ($lang, $rest) = ($1, $2);
+                           if ($rest !~ m,^[^/]+\.\d(?:\S+)?(?:\.gz)?$,) {
+                               tag "bad-so-link-within-manual-page", "$file";
+                           }
+                       } else {
+                           tag "bad-so-link-within-manual-page", "$file";
+                       }
+                   }
+               } else {
+                   tag "bad-so-link-within-manual-page", "$file";
+               }
+               next;
+           }
+       }
+
+       # If it's not a .so link, use lexgrog to find out if the man page
+       # parses correctly and make sure the short description is reasonable.
+       #
+       # This check is currently not applied to pages in language-specific
+       # hierarchies, because those pages are not currently scanned by
+       # mandb (bug #29448), and because lexgrog can't handle pages in all
+       # languages at the moment, leading to huge numbers of false
+       # negatives. When man-db is fixed, this limitation should be
+       # removed.
+       if ($path =~ m,/man/man\d/,) {
+           my $pid = open LEXGROG, '-|';
+           if (not defined $pid) {
+               fail("cannot run lexgrog: $!");
+           } elsif ($pid == 0) {
+               my %newenv = (LANG => 'C', PATH => $ENV{PATH});
+               undef %ENV;
+               %ENV = %newenv;
+               exec "lexgrog unpacked/\Q$file\E 2>&1"
+                   or fail("cannot run lexgrog: $!");
+           }
+           my $desc = <LEXGROG>;
+           $desc =~ s/^[^:]+: \"(.*)\"$/$1/;
+           if ($desc =~ /(\S+)\s+-\s+manual page for \1/i) {
+               tag "manpage-has-useless-whatis-entry", "$file";
+           } elsif ($desc =~ /(\S+)\s+-\s+programs? to do something/i) {
+               tag "manpage-is-dh_make-template", "$file";
+           }
+           1 while <LEXGROG>;
+           close LEXGROG;
+           tag "manpage-has-bad-whatis-entry", "$file" if $? != 0;
+       }
+
+       # If it's not a .so link, run it through "man" to check for errors.
+       # If it is in a directory with the standard man layout, cd to the
+       # parent directory before running man so that .so directives are
+       # processed properly.  (Yes, there are man pages that include other
+       # pages with .so but aren't simple links; rbash, for instance.)
+       my $cmd;
+       if ($file =~ m,^(.*)/(man\d/.*)$,) {
+           $cmd = "cd unpacked/\Q$1\E && man --warnings -E UTF-8 -l \Q$2\E";
+       } else {
+           $cmd = "man --warnings -E UTF-8 -l unpacked/\Q$file\E";
+       }
+       my $pid = open MANERRS, '-|';
+       if (not defined $pid) {
+           fail("cannot run man -E UTF-8 -l: $!");
+       } elsif ($pid == 0) {
+           my %newenv = (LANG => 'C', PATH => $ENV{PATH}, MANWIDTH => 80);
+           undef %ENV;
+           %ENV = %newenv;
+           exec "($cmd >/dev/null) 2>&1"
+               or fail("cannot run man -E UTF-8 -l: $!");
+       }
+       while (<MANERRS>) {
+           # ignore progress information from man
+           next if /^Reformatting/;
+           next if /^\s*$/;
+           # ignore errors from gzip, will be dealt with at other places
+           next if /^(man|gzip)/;
+           # ignore wrapping failures for Asian man pages (groff problem)
+           if ($lang =~ /^(?:ja|ko|zh)/) {
+               next if /warning \[.*\]: cannot adjust line/;
+               next if /warning \[.*\]: can\'t break line/;
+           }
+           # ignore wrapping failures if they contain URLs
+           next if /:(\d+): warning \[.*\]: (can\'t break|cannot adjust) line/
+               and $manfile[$1 - 1] =~ m,(?:http|ftp)://.+,i;
+           # ignore common undefined macros from pod2man << Perl 5.10
+           next if /warning: \`(Tr|IX)\' not defined/;
+           chomp;
+           s/^[^:]+://o;
+           tag "manpage-has-errors-from-man", "$file", "$_";
+           last;
+       }
+       close(MANERRS);
+       # Now we search through the whole man page for some common errors
+       my $lc = 0;
+       my $hc = 0;
+       foreach my $line (@manfile) {
+           $lc++;
+           chomp $line;
+           next if $line =~ /^\.\\\"/o; # comments .\"
+           if ($line =~ /^\.TH\s/) { # header
+               require Text::ParseWords;
+               my ($th_command, $th_title, $th_section, $th_date ) =
+                   Text::ParseWords::parse_line( '\s+', 0, $line);
+               if ($th_section && (lc($fn_section) ne lc($th_section))) {
+                   tag "manpage-section-mismatch", "$file:$lc $fn_section != $th_section";
+               }
+           }
+           # Catch hyphens used as minus signs by looking for ones at the
+           # beginning of a word, but don't generate false positives on \s-1
+           # (small font), \*(-- (pod2man long dash), or things like \h'-1'.
+           if ($line =~ /^(
+                           ([^\.].*)?
+                           [\s\'\"\`\(\[]
+                           (?<! \\s | \*\( | \(- | \w\' )
+                          )?
+                          (--?\w+)/ox) {
+               $hc++;
+               tag "hyphen-used-as-minus-sign", "$file:$lc" if $hc <= 10 or $ENV{'MAEMIAN_DEBUG'};
+           }
+           if (($line =~ m,(/usr/(dict|doc|etc|info|man|adm|preserve)/),o)
+               || ($line =~ m,(/var/(adm|catman|named|nis|preserve)/),o)) {
+               # FSSTND dirs in man pages
+               # regexes taken from checks/files
+               tag "FSSTND-dir-in-manual-page", "$file:$lc $1";
+           }
+           if ($line =~ m/^.SH "POD ERRORS"$/) {
+               tag "manpage-has-errors-from-pod2man", "$file:$lc";
+           }
+       }
+       tag "hyphen-used-as-minus-sign", $file, ($hc-10), "more occurrences not shown" if $hc > 10 and ! $ENV{'MAEMIAN_DEBUG'};
+    }
+}
+
+for my $f (sort keys %binary) {
+    if (exists $manpage{$f}) {
+       # X11 binary?  This shouldn't happen any more; these are no longer
+       # allowed.
+       if ($binary{$f} =~ m,usr/X11R6, or
+            ($link{$f} && $link{$f} =~ m,(\.\.|usr)/X11R6,)) {
+           # yes. manpage in X11 too?
+           for my $manp_info (@{$manpage{$f}}) {
+               if ($manp_info->{file} =~ m/X11R6/) {
+                   # ok.
+               } else {
+                   tag "manpage-for-x11-binary-in-wrong-directory", "$binary{$f} $manp_info->{file}";
+               }
+           }
+       } else {
+           for my $manp_info (@{$manpage{$f}}) {
+               # no. manpage in X11?
+               if ($manp_info->{file} =~ m/X11R6/) {
+                   tag "manpage-for-non-x11-binary-in-wrong-directory", "$binary{$f} $manp_info->{file}";
+               } else {
+                   # ok.
+               }
+           }
+       }
+
+       if (not grep { $_->{lang} eq "" } @{$manpage{$f}}) {
+           tag "binary-without-english-manpage", "$binary{$f}";
+       }
+    } else {
+       tag "binary-without-manpage", "$binary{$f}";
+    }
+}
+
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 ts=8
diff --git a/checks/manpages.desc b/checks/manpages.desc
new file mode 100644 (file)
index 0000000..be132dd
--- /dev/null
@@ -0,0 +1,277 @@
+Check-Script: manpages
+Author: Christian Schwarz <schwarz@debian.org>
+Abbrev: man
+Type: binary
+Unpack-Level: 2
+Needs-Info: file-info
+Info: This script checks if a binary package conforms to manual page policy.
+
+Tag: bad-link-to-undocumented-manpage
+Severity: important
+Certainty: certain
+Info: The symbolic link should reference
+ "<tt>../man[237]/undocumented.[237].gz</tt>" for manual pages in
+ <tt>/usr/share/man</tt> or
+ "<tt>../../../share/man/man[237]/undocumented.[237].gz</tt>" for manual
+ pages in <tt>/usr/X11R6/man</tt>.
+
+Tag: link-to-undocumented-manpage
+Severity: normal
+Certainty: certain
+Info: Symbolic links to the undocumented(7) manual page may be provided
+ if no manual page is available, but that is deprecated.
+ .
+ The lack of a manual page is still a bug, and if at all possible you
+ should write one yourself.
+ .
+ For help with writing manual pages, refer to the Man-Page-HOWTO at
+ http://www.schweikhardt.net/man_page_howto.html, the examples created
+ by <tt>debmake</tt> or <tt>dh_make</tt>, or the
+ <tt>/usr/share/doc/man-db/examples</tt> directory.
+ If the package provides <tt>--help</tt> output, you might want to use
+ the <tt>help2man</tt> utility to generate a simple manual page.
+Ref: policy 12.1
+
+Tag: binary-without-manpage
+Severity: normal
+Certainty: possible
+Info: Each binary in <tt>/usr/bin</tt>, <tt>/usr/sbin</tt>, <tt>/bin</tt>,
+ <tt>/sbin</tt> or <tt>/usr/games</tt> should have a manual page
+ .
+ Note that though the man program has the capability to check for
+ several program names in the NAMES section, each of these programs
+ should have its own manual page (a symbolic link to the appropriate
+ manual page is sufficient) because other manual page viewers such as
+ xman or tkman don't support this.
+ .
+ If the name of the man page differs from the binary by case, man may
+ be able to find it anyway; however, it is still best practice to make the
+ case of the man page match the case of the binary.
+ .
+ If the man pages are provided by another package on which this package
+ depends, lintian may not be able to determine that man pages are
+ available.  In this case, after confirming that all binaries do have
+ man pages after this package and its dependencies are installed, please
+ add a lintian override.
+Ref: policy 12.1
+
+Tag: manpage-in-wrong-directory
+Severity: important
+Certainty: certain
+Info: The manual page should be installed in the correct directory below
+ <tt>/usr/share/man/</tt> or <tt>/usr/share/man/<i>locale</i></tt>.
+ Only sections 1 through 9 should be used.
+ .
+ The section number in the filename should correspond with the section
+ number in the directory name.
+Ref: policy 12.1
+
+Tag: manpage-has-wrong-extension
+Severity: important
+Certainty: certain
+Info: The manual page has an extension other than
+ "<i>section</i>[<i>program</i>].gz".
+Ref: policy 12.1
+
+Tag: manpage-not-compressed
+Severity: important
+Certainty: certain
+Info: Manual pages have to be installed compressed (using "<tt>gzip -9</tt>").
+Ref: policy 12.1
+
+Tag: x11-games-should-be-in-usr-games
+Severity: important
+Certainty: certain
+Info: Since X11 games should be installed in <tt>/usr/games</tt> (and
+ not in <tt>/usr/X11R6/bin</tt>) and the game's manual pages should be
+ installed in <tt>/usr/share/man/man6</tt>, the directory
+ <tt>/usr/X11R6/man/man6</tt> should be empty.
+Ref: policy 11.11
+
+Tag: manpage-not-compressed-with-gzip
+Severity: important
+Certainty: certain
+Info: Manual pages should be compressed with <tt>gzip -9</tt>.
+Ref: policy 12.1
+
+Tag: manpage-not-compressed-with-max-compression
+Severity: important
+Certainty: certain
+Info: Manual pages should be compressed with <tt>gzip -9</tt>.
+Ref: policy 12.1
+
+Tag: manpage-has-bad-whatis-entry
+Severity: normal
+Certainty: certain
+Info: Each manual page should start with a "NAME" section, which lists the
+ name and a brief description of the page separated by "\-". These sections
+ are parsed by "mandb" and stored in a database for the use of "apropos" and
+ "whatis", so they must be in a certain format. This manual page apparently
+ uses the wrong format and cannot be parsed by "mandb".
+Ref: lexgrog(1), groff_man(7), groff_mdoc(7)
+
+Tag: manpage-has-useless-whatis-entry
+Severity: normal
+Certainty: certain
+Info: The whatis entry for this manual page (the brief description found
+ in the NAME section) is of the form:
+ .
+  program - manual page for program
+ .
+ This conveys no information about what the program is for and is
+ repetitive.  The short description should contain brief information about
+ what the program is for to aid in searching with apropos and similar
+ programs.
+ .
+ If this manpage was generated by help2man, use the -n option to provide a
+ more meaningful description.
+
+Tag: manpage-is-dh_make-template
+Severity: important
+Certainty: certain
+Info: This manual page appears to be an unmodified or insufficiently
+ modified copy of the dh_make manual page template.  It has a whatis entry
+ (the brief description found in the NAME section) of the form:
+ .
+  package - program to do something
+ .
+ Please double-check the manual page and replace the template language
+ with specific information about this program.
+
+Tag: manpage-has-errors-from-man
+Severity: normal
+Certainty: certain
+Info: This man page provokes warnings or errors from man.
+ .
+ "cannot adjust" or "can't break" are trouble with paragraph filling,
+ usually related to long lines.  Adjustment can be helped by left
+ justifying, breaks can be helped with hyphenation, see "Manipulating
+ Filling and Adjusting" and "Manipulating Hyphenation" in the manual.
+ .
+ "can't find numbered character" usually means latin1 etc in the input, and
+ this warning indicates characters will be missing from the output.  You can
+ change to escapes like \[:a] described on the groff_char man page.
+ .
+ Other warnings are often formatting typos, like missing quotes around a
+ string argument to .IP.  These are likely to result in lost or malformed
+ output.  See the groff_man (or groff_mdoc if using mdoc) man page for
+ information on macros.
+ .
+ This test uses <tt>man</tt>'s <tt>--warnings</tt> option to enable groff
+ warnings that catch common mistakes, such as putting <tt>.</tt> or
+ <tt>'</tt> characters at the start of a line when they are intended as
+ literal text rather than groff commands.  This can be fixed either by
+ reformatting the paragraph so that these characters are not at the start of
+ a line, or by adding a zero-width space (<tt>\&</tt>) immediately before
+ them.
+ .
+ At worst, warning messages can be disabled with the .warn directive, see
+ "Debugging" in the groff manual.
+ .
+ To test this for yourself you can use the following command:
+  LANG=C man --warnings -l manpage-file &gt;/dev/null
+
+Tag: manpage-has-errors-from-pod2man
+Severity: normal
+Certainty: certain
+Info: This man page contains a section "POD ERRORS" generated by pod2man.
+ This sections lists errors in the POD syntax found by pod2man during the
+ generation of the man page.
+
+Tag: manpage-for-x11-binary-in-wrong-directory
+Severity: important
+Certainty: certain
+Info: Manual pages for binaries which are located in <tt>/usr/X11R6/bin</tt>
+ should be installed below <tt>/usr/X11R6/man</tt>.
+ .
+ Note that normally only packages that are part of X itself and those that
+ are using some arcane Imakefiles should actually install binaries into
+ <tt>/usr/X11R6/bin</tt>.
+Ref: fhs usrsharemanmanualpages
+
+Tag: manpage-for-non-x11-binary-in-wrong-directory
+Severity: important
+Certainty: certain
+Info: Manual pages for binaries that are not located in <tt>/usr/X11R6/bin</tt>
+ should not be installed below <tt>/usr/X11R6/man</tt>, but below
+ <tt>/usr/share/man</tt>.
+ .
+ Note that moving a binary into <tt>/usr/X11R6/bin</tt> is almost never the
+ proper solution for this problem; move the manual page instead.
+Ref: fhs usrsharemanmanualpages
+
+Tag: bad-so-link-within-manual-page
+Severity: important
+Certainty: certain
+Info: Manual files that use the .so links to include other pages should
+ only point to a path relative to the top-level manual hierarchy, e.g.
+ .
+ <tt>.so man3/boo.1.gz</tt>
+
+Tag: empty-manual-page
+Severity: important
+Certainty: certain
+Info: The referenced manual page is empty.
+
+Tag: manpage-section-mismatch
+Severity: normal
+Certainty: certain
+Info: A man page usually should contain a <tt>.TH</tt> header, specifying the
+ section.  The section in this manpage doesn't match with the section in the
+ filename.
+Ref: groff_man(7), man(1)
+
+Tag: hyphen-used-as-minus-sign
+Severity: wishlist
+Certainty: certain
+Info: Manual page seems to contain a hyphen where a minus sign was intended.
+ "-" chars are interpreted as hyphens (U+2010) by groff, not as minus signs
+ (U+002D). Since options to programs use minus signs (U+002D), this means for
+ example in UTF-8 locales that you cannot cut&amp;paste options, nor search for
+ them easily.
+ .
+ "-" must be escaped ("\-") to be interpreted as minus. If you really intend a
+ hyphen, write it as "\(hy" to emphasise that fact. See groff(7) and
+ especially groff_char(7) for details, and also the thread starting with
+ http://lists.debian.org/debian-devel/2003/debian-devel-200303/msg01481.html
+ .
+ If you use some tool that converts your documentation to groff format, it
+ might be possible that this tool converts dashes of any kind to groff
+ hyphens, while the safe way of converting dashes is usually to convert them
+ to "\-".
+ .
+ Because this error can occur <em>very</em> often we show only the
+ first 10 occurrences for each man page and give the number of
+ suppressed occurrences. If you want to see all warnings, run
+ lintian with the -d/--debug option.
+
+Tag: FSSTND-dir-in-manual-page
+Severity: wishlist
+Certainty: certain
+Info: The manual page references a directory that is specified
+ in the FSSTND but not in the FHS which is used by Debian.
+ This can be an indicator of a mismatch of the location of
+ files as installed for Debian and as described by the man page.
+ .
+ If you have to change file locations to abide by Debian Policy
+ please also patch the man page to mention these new locations.
+
+Tag: binary-without-english-manpage
+Severity: normal
+Certainty: certain
+Info: Each binary in <tt>/usr/bin</tt>, <tt>/usr/sbin</tt>, <tt>/bin</tt>,
+ <tt>/sbin</tt> or <tt>/usr/games</tt> should have a manual page. You don't
+ provide an english, only a translated manpage. Since english is fallback,
+ shipping only a non-english man page leaves most users without a man page
+ at all.
+
+Tag: manpage-locale-dir-country-specific
+Severity: normal
+Certainty: certain
+Ref: policy 12.1
+Info: This package installs a manual page in a locale directory that
+ includes the country name.  A country name should not be included in the
+ directory name unless it indicates a significant difference in the
+ language.  The known cases where country names are appropriate are pt_BR
+ and zh_*.  Please file a bug against Maemian if this is another case
+ where a country name is appropriate.
diff --git a/checks/md5sums b/checks/md5sums
new file mode 100644 (file)
index 0000000..50c5819
--- /dev/null
@@ -0,0 +1,122 @@
+# md5sums -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Christian Schwarz and Richard Braakman
+#
+# 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 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.
+
+package Maemian::md5sums;
+use strict;
+use Tags;
+use Util;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+
+my $control = "control/md5sums";
+
+my %control_entry;
+my %info_entry;
+my %conffile;
+
+# read in md5sums info file
+open(C, '<', "md5sums") or fail("cannot open md5sums info file: $!");
+while (<C>) {
+    chop;
+    next if m/^\s*$/;
+    m/^(\S+)\s*(\S.*)$/ or fail("syntax error in md5sums info file: $_");
+    my $zzsum = $1;
+    my $zzfile = $2;
+    $zzfile =~ s,^(\./)?,,;
+    $info_entry{$zzfile} = $zzsum;
+}
+close(C);
+
+# read in conffiles
+if (-f "control/conffiles") {
+    open(C, '<', "control/conffiles")
+       or fail("cannot open control file conffiles: $!");
+    while (<C>) {
+       chop;
+       next if m/^\s*$/;
+       s,^/,,;
+       $conffile{$_} = 1;
+    }
+    close(C);
+}
+
+# Is there a md5sums control file?
+unless (-f $control) {
+    # ignore if package contains no files
+    return 0 if -z "md5sums";
+
+    # check if package contains non-conffiles
+    # debhelper doesn't create entries in md5sums
+    # for conffiles since this information would
+    # be redundant
+    my $only_conffiles = 1;
+    foreach my $file (keys %info_entry) {
+       unless ($conffile{$file}) {
+           $only_conffiles = 0;
+           last;
+       }
+    }
+
+    tag "no-md5sums-control-file", "" unless $only_conffiles;
+    return 0;
+}
+
+# Is it empty? Then skip it. Tag will be issued by control-files
+if (-z $control) {
+    return 0;
+}
+
+# read in md5sums control file
+open(C, '<', $control)
+    or fail("cannot open md5sums control file $control: $!");
+while (<C>) {
+    chop;
+    next if m/^\s*$/;
+    if (m{^([a-f0-9]+)\s*(?:\./)?(\S.*)$} && length($1) == 32) {
+       $control_entry{$2} = $1;
+    } else {
+       tag "malformed-md5sums-control-file", "line $.";
+    }
+}
+close(C);
+
+for my $file (keys %control_entry) {
+
+    if (not exists $info_entry{$file}) {
+       tag "md5sums-lists-nonexisting-file", "$file";
+    } elsif ($info_entry{$file} ne $control_entry{$file}) {
+       tag "md5sum-mismatch", "$file";
+    }
+
+    delete $info_entry{$file};
+}
+for my $file (keys %info_entry) {
+    tag "file-missing-in-md5sums", "$file"
+       unless ($conffile{$file} || $file =~ m%^var/lib/[ai]spell/.%);
+}
+
+}
+
+1;
+
+# vim: syntax=perl
diff --git a/checks/md5sums.desc b/checks/md5sums.desc
new file mode 100644 (file)
index 0000000..15c0151
--- /dev/null
@@ -0,0 +1,64 @@
+Check-Script: md5sums
+Author: Christian Schwarz <schwarz@debian.org>
+Abbrev: md5
+Type: binary
+Unpack-Level: 1
+Needs-Info: md5sums
+Info: This script checks if md5sum control files are valid, if they are
+ provided by a binary package.
+
+Tag: no-md5sums-control-file
+Severity: wishlist
+Certainty: certain
+Info: This package does not contain an md5sums control file.  This control
+ file listing the MD5 checksums of the contents of the package is not
+ required, but if present debsums can use it to verify that no files
+ shipped with your package have been modified.  Providing it is
+ recommended.
+ .
+ If you are using debhelper to create your package, just add a call to
+ <tt>dh_md5sums</tt> at the end of your binary-indep or binary-arch
+ target, right before <tt>dh_builddeb</tt>.
+
+Tag: malformed-md5sums-control-file
+Severity: important
+Certainty: certain
+Info: The indicated line of the md5sums control file for this package was
+ malformed.  Each line of an md5sums control file should contain an MD5
+ checksum, some whitespace, and then the path to the file corresponding to
+ that checksum.
+
+Tag: md5sum-mismatch
+Severity: important
+Certainty: certain
+Info: The md5sum listed for the file does not match the actual file
+ contents.
+ .
+ Usually, this error occurs during the package build process, if the
+ <tt>debian/tmp/</tt> directory is touched after <tt>dh_md5sums</tt> or
+ <tt>debstd</tt> is run.
+
+Tag: md5sums-lists-nonexisting-file
+Severity: important
+Certainty: certain
+Info: The md5sums control file lists a file which is not included in the
+ package.
+ .
+ Usually, this error occurs during the package build process, if the
+ <tt>debian/tmp/</tt> directory is touched after <tt>dh_md5sums</tt> or
+ <tt>debstd</tt> is run.
+ .
+ If all the files in the <tt>DEBIAN/</tt> subdirectory are listed
+ above, the problem was probably caused by building the package using a
+ buggy debstd/debmake. In this case, rebuilding the package with an
+ updated debstd should fix the problem.
+
+Tag: file-missing-in-md5sums
+Severity: normal
+Certainty: certain
+Info: The package contains a file which isn't listed in the md5sums control
+ file.
+ .
+ Usually, this error occurs during the package build process, if the
+ <tt>debian/tmp/</tt> directory is touched after <tt>dh_md5sums</tt> or
+ <tt>debstd</tt> is run.
diff --git a/checks/menu-format b/checks/menu-format
new file mode 100644 (file)
index 0000000..4e5d40f
--- /dev/null
@@ -0,0 +1,915 @@
+# menu format -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 by Joey Hess
+#
+# 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 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.
+
+# This script also checks desktop entries, since they share quite a bit of
+# code.  At some point, it would make sense to try to refactor this so that
+# shared code is in libraries.
+#
+# Further things that the desktop file validation should be checking:
+#
+#  - Encoding of the file should be UTF-8.
+#  - Additional Categories should be associated with Main Categories.
+#  - List entries (MimeType, Categories) should end with a semicolon.
+#  - Check for GNOME/GTK/X11/etc. dependencies and require the relevant
+#    Additional Category to be present.
+#  - Check all the escape characters supported by Exec.
+#  - Review desktop-file-validate to see what else we're missing.
+
+package Maemian::menu_format;
+use strict;
+use Tags;
+use Util;
+use File::Basename;
+
+# This is a list of all tags that should be in every menu item.
+my @req_tags=qw(needs section title command);
+
+# This is a list of all known tags.
+my @known_tags=qw(
+       needs
+       section
+       title
+       sort
+       command
+       longtitle
+       icon
+       icon16x16
+       icon32x32
+       description
+       hotkey
+       hints
+    );
+
+# These 'needs' tags are always valid, no matter the context, and no other
+# values are valid outside the Window Managers context (don't include wm here,
+# in other words).  It's case insensitive, use lower case here.
+my @needs_tag_vals=qw(x11 text vc);
+
+# Authorative source of menu sections:
+# http://www.debian.org/doc/packaging-manuals/menu-policy/ch2#s2.1
+
+# This is a list of all valid section on the root menu.
+my @root_sections = ('Applications', 'Games', 'Help', 'Screen',
+                     'Window Managers', 'FVWM Modules', 'Window Maker');
+
+# This is a list of all valid sections a menu item or submenu can go in.
+my @sections = ('Applications/Accessibility',
+               'Applications/Amateur Radio',
+               'Applications/Data Management',
+               'Applications/Editors',
+               'Applications/Education',
+               'Applications/Emulators',
+               'Applications/File Management',
+               'Applications/Graphics',
+               'Applications/Mobile Devices',
+               'Applications/Network/Communication',
+               'Applications/Network/File Transfer',
+               'Applications/Network/Monitoring',
+               'Applications/Network/Web Browsing',
+               'Applications/Network/Web News',
+               'Applications/Office',
+               'Applications/Programming',
+               'Applications/Project Management',
+               'Applications/Science/Astronomy',
+               'Applications/Science/Biology',
+               'Applications/Science/Chemistry',
+               'Applications/Science/Data Analysis',
+               'Applications/Science/Electronics',
+               'Applications/Science/Engineering',
+               'Applications/Science/Geoscience',
+               'Applications/Science/Mathematics',
+               'Applications/Science/Medicine',
+               'Applications/Science/Physics',
+               'Applications/Science/Social',
+               'Applications/Shells',
+               'Applications/Sound',
+               'Applications/System/Administration',
+               'Applications/System/Hardware',
+               'Applications/System/Language Environment',
+               'Applications/System/Monitoring',
+               'Applications/System/Package Management',
+               'Applications/System/Security',
+               'Applications/Terminal Emulators',
+               'Applications/Text',
+               'Applications/TV and Radio',
+               'Applications/Video',
+               'Applications/Viewers',
+               'Applications/Web Development',
+               'Games/Action',
+               'Games/Adventure',
+               'Games/Blocks',
+               'Games/Board',
+               'Games/Card',
+               'Games/Puzzles',
+               'Games/Simulation',
+               'Games/Strategy',
+               'Games/Tools',
+               'Games/Toys',
+               'Help',
+               'Screen/Saving',
+               'Screen/Locking',
+               'Window Managers',
+               'FVWM Modules',
+               'Window Maker'
+              );
+
+# Authorative source of desktop keys:
+# http://standards.freedesktop.org/desktop-entry-spec/1.0/
+#
+# This is a list of all keys that should be in every desktop entry.
+my @req_desktop_keys = qw(Type Name);
+
+# This is a list of all known keys.
+my %known_desktop_keys = map { $_ => 1 }
+    qw(
+       Type
+       Version
+       Name
+       GenericName
+       NoDisplay
+       Comment
+       Icon
+       Hidden
+       OnlyShowIn
+       NotShowIn
+       TryExec
+       Exec
+       Path
+       Terminal
+       MimeType
+       Categories
+       MimeType
+       Categories
+       StartupNotify
+       StartupWMClass
+       URL
+      );
+
+my %deprecated_desktop_keys = map { $_ => 1 }
+    qw(
+       Encoding
+       MiniIcon
+       TerminalOptions
+       Protocols
+       Extensions
+       BinaryPattern
+       MapNotify
+       SwallowTitle
+       SwallowExec
+       SortOrder
+       FilePattern
+      );
+
+# KDE uses some additional keys that should start with X-KDE but don't for
+# historical reasons.  Actions will in theory be in a later version of the
+# standard (it's not mentioned in the current standard, but is implemented by
+# KDE and widely used).
+my %kde_desktop_keys = map { $_ => 1 }
+    qw(
+       ServiceTypes
+       DocPath
+       Keywords
+       InitialPreference
+       Dev
+       FSType
+       MountPoint
+       ReadOnly
+       UnmountIcon
+       Actions
+      );
+
+# Known types of desktop entries.
+# http://standards.freedesktop.org/desktop-entry-spec/1.0/ar01s05.html
+my %known_desktop_types = map { $_ => 1 }
+    qw(
+       Application
+       Link
+       Directory
+      );
+
+# Authorative source of desktop categories:
+# http://standards.freedesktop.org/menu-spec/1.0/apa.html
+
+# This is a list of all Main Categories for .desktop files.  Application is
+# added as an exception; it's not listed in the standard, but it's widely used
+# and used as an example in the GNOME documentation.  GNUstep is added as an
+# exception since it's used by GNUstep packages.
+my %main_categories = map { $_ => 1 }
+    qw(
+       AudioVideo
+       Audio
+       Video
+       Development
+       Education
+       Game
+       Graphics
+       Network
+       Office
+       Settings
+       System
+       Utility
+       Application
+       GNUstep
+      );
+
+# This is a list of all Additional Categories for .desktop files.  Ideally we
+# should be checking to be sure the associated Main Categories are present,
+# but we don't have support for that yet.
+my %categories = map { $_ => 1 }
+    qw(
+       Building
+       Debugger
+       IDE
+       GUIDesigner
+       Profiling
+       RevisionControl
+       Translation
+       Calendar
+       ContactManagement
+       Database
+       Dictionary
+       Chart
+       Email
+       Finance
+       FlowChart
+       PDA
+       ProjectManagement
+       Presentation
+       Spreadsheet
+       WordProcessor
+       2DGraphics
+       VectorGraphics
+       RasterGraphics
+       3DGraphics
+       Scanning
+       OCR
+       Photography
+       Publishing
+       Viewer
+       TextTools
+       DesktopSettings
+       HardwareSettings
+       Printing
+       PackageManager
+       Dialup
+       InstantMessaging
+       Chat
+       IRCClient
+       FileTransfer
+       HamRadio
+       News
+       P2P
+       RemoteAccess
+       Telephony
+       TelephonyTools
+       VideoConference
+       WebBrowser
+       WebDevelopment
+       Midi
+       Mixer
+       Sequencer
+       Tuner
+       TV
+       AudioVideoEditing
+       Player
+       Recorder
+       DiscBurning
+       ActionGame
+       AdventureGame
+       ArcadeGame
+       BoardGame
+       BlocksGame
+       CardGame
+       KidsGame
+       LogicGame
+       RolePlaying
+       Simulation
+       SportsGame
+       StrategyGame
+       Art
+       Construction
+       Music
+       Languages
+       Science
+       ArtificialIntelligence
+       Astronomy
+       Biology
+       Chemistry
+       ComputerScience
+       DataVisualization
+       Economy
+       Electricity
+       Geography
+       Geology
+       Geoscience
+       History
+       ImageProcessing
+       Literature
+       Math
+       NumericalAnalysis
+       MedicalSoftware
+       Physics
+       Robotics
+       Sports
+       ParallelComputing
+       Amusement
+       Archiving
+       Compression
+       Electronics
+       Emulator
+       Engineering
+       FileTools
+       FileManager
+       TerminalEmulator
+       Filesystem
+       Monitor
+       Security
+       Accessibility
+       Calculator
+       Clock
+       TextEditor
+       Documentation
+       Core
+       KDE
+       GNOME
+       GTK
+       Qt
+       Motif
+       Java
+       ConsoleOnly
+      );
+
+# This is a list of Reserved Categories for .desktop files.  To use one of
+# these, the desktop entry must also have an OnlyShowIn key limiting the
+# environment to one that supports this category.
+my %reserved_categories = map { $_ => 1 }
+    qw(
+       Screensaver
+       TrayIcon
+       Applet
+       Shell
+      );
+
+# Path in which to search for binaries referenced in menu entries.  These must
+# not have leading slashes.
+my @path = qw(usr/local/bin/ usr/bin/ bin/ usr/X11R6/bin/ usr/games/);
+
+my %known_tags_hash = map { $_ => 1 } @known_tags;
+my %needs_tag_vals_hash = map { $_ => 1 } @needs_tag_vals;
+my %root_sections_hash = map { $_ => 1 } @root_sections;
+my %sections_hash = map { $_ => 1 } @sections;
+
+# -----------------------------------
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+my @menufiles;
+opendir (MENUDIR, "menu/lib") or fail("cannot read menu/lib file directory.");
+push @menufiles, map { "menu/lib/$_" } readdir(MENUDIR);
+closedir MENUDIR;
+opendir (MENUDIR, "menu/share") or fail("cannot read menu/share file directory.");
+push @menufiles, map { "menu/share/$_" } readdir(MENUDIR);
+closedir MENUDIR;
+
+# Find the desktop files in the package for verification.
+my @desktop_files;
+foreach my $file (sort keys %{$info->index}) {
+    my $index_info = $info->index->{$file};
+    my $operm = $index_info->{operm};
+
+    tag 'deprecated-kdelnk-file', "/$file" if ($file =~ m,\.kdelnk$,);
+
+    if ($index_info->{type} =~ m/[-h]/ &&
+       $file =~ m,usr/share/applications/.*\.desktop$,) {
+
+       if ($operm & 0100 or $operm & 010 or $operm & 01) {
+           tag "executable-desktop-file", sprintf("/$file %04o",$operm);
+       }
+        unless ($file =~ m,template,) {
+            push (@desktop_files, $file);
+        }
+    }
+}
+
+# Verify all the desktop files.
+for my $desktop_file (@desktop_files) {
+    VerifyDesktopFile($desktop_file, $desktop_file, $pkg, $info);
+}
+
+# Now all the menu files.
+foreach my $menufile (@menufiles) {
+    next if -x $menufile; # don't try to parse executables
+
+    my $basename = basename $menufile;
+    my $fullname = "/usr/share/menu/$basename";
+    $fullname = "/usr/lib/menu/$basename" if $menufile =~ m,^menu/lib/,o;
+
+    next if $basename eq "README"; # README is a special case
+
+    my $menufile_line ="";
+    open (IN, '<', $menufile) or
+       fail("cannot open menu file $menufile for reading.");
+    # line below is commented out in favour of the while loop
+    # do { $_=<IN>; } while defined && (m/^\s* \#/ || m/^\s*$/);
+    while (<IN>) {
+       if (m/^\s*\#/ || m/^\s*$/) {
+           next;
+       } else {
+           $menufile_line = $_;
+           last;
+       }
+    }
+
+    # Check first line of file to see if it matches the old menu file format.
+    if ($menufile_line =~ m/^(?!\?package\(.*\)).* .* .* .* "?.*"? .*$/o) {
+       tag "old-format-menu-file", $fullname;
+       close IN;
+       next;
+    } elsif ($menufile_line =~ m/^!C\s*menu-2/o) {
+       # we can't parse that yet
+       close IN;
+       next;
+    }
+
+    # Parse entire file as a new format menu file.
+    my $line="";
+    my $lc=0;
+    do {
+       $lc++;
+
+       # Ignore lines that are comments.
+       if ($menufile_line =~ m/^\s*\#/o) {
+           next;
+       }
+       $line .= $menufile_line;
+       # Note that I allow whitespace after the continuation character.
+       # This is caught by VerifyLine().
+       if (! ($menufile_line =~ m/\\\s*?$/)) {
+           VerifyLine($pkg, $info, $type, $menufile, $fullname, $line, $lc);
+           $line="";
+       }
+    } while ($menufile_line = <IN>);
+    VerifyLine($pkg, $info, $type, $menufile, $fullname, $line, $lc);
+
+    close IN;
+}
+
+}
+
+# -----------------------------------
+
+# Pass this a line of a menu file, it sanitizes it and
+# verifies that it is correct.
+sub VerifyLine {
+    my ($pkg, $info, $type, $menufile, $fullname, $line, $linecount) = @_;
+
+    my %vals;
+
+    chomp $line;
+
+    # Replace all line continuation characters with whitespace.
+    # (do not remove them completely, because update-menus doesn't)
+    $line =~ s/\\\n/ /mgo;
+
+    # This is in here to fix a common mistake: whitespace after a '\'
+    # character.
+    if ($line =~ s/\\\s+\n/ /mgo) {
+       tag "whitespace-after-continuation-character", "$fullname:$linecount";
+    }
+
+    # Ignore lines that are all whitespace or empty.
+    return if $line =~ m/^\s+$/o or ! $line;
+
+    # Ignore lines that are comments.
+    return if $line =~ m/^\s*\#/o;
+
+    # Start by testing the package check.
+    if (not $line =~ m/^\?package\((.*?)\):/o) {
+       tag "bad-test-in-menu-item", "$fullname:$linecount";
+       return;
+    }
+    my $pkg_test = $1;
+    my %tested_packages = map { $_ => 1 } split( /\s*,\s*/, $pkg_test);
+    my $tested_packages = scalar keys %tested_packages;
+    unless (exists $tested_packages{$pkg}) {
+       tag "pkg-not-in-package-test", "$pkg_test $fullname";
+    }
+    $line =~ s/^\?package\(.*?\)://;
+
+    # Now collect all the tag=value pairs. I've heavily commented
+    # the killer regexp that's responsible.
+    #
+    # The basic idea here is we start at the beginning of the line.
+    # Each loop pulls off one tag=value pair and advances to the next
+    # when we have no more matches, there should be no text left on
+    # the line - if there is, it's a parse error.
+    while ($line =~ m/
+          \s*?                 # allow whitespace between pairs
+          (                    # capture what follows in $1, it's our tag
+           [^\"\s=]            # a non-quote, non-whitespace, character
+           *                   # match as many as we can
+          )
+          =
+          (                    # capture what follows in $2, it's our value
+           (?:
+            \"                 # this is a quoted string
+            (?:
+             \\.               # any quoted character
+             |                 # or
+             [^\"]             # a non-quote character
+            )
+            *                  # repeat as many times as possible
+            \"                 # end of the quoted value string
+           )
+           |                   # the other possibility is a non-quoted string
+           (?:
+            [^\"\s]            # a non-quote, non-whitespace character
+            *                  # match as many times as we can
+           )
+          )
+          /ogcx) {
+       my $tag = $1;
+       my $value = $2;
+
+       if (exists $vals{$tag}) {
+           tag "duplicated-tag-in-menu-item", "$fullname $1:$linecount";
+       }
+
+       # If the value was quoted, remove those quotes.
+       if ($value =~ m/^\"(.*)\"$/) {
+           $value = $1;
+       } else {
+           tag "unquoted-string-in-menu-item", "$fullname $1:$linecount";
+       }
+
+       # If the value has escaped characters, remove the
+       # escapes.
+       $value =~ s/\\(.)/$1/g;
+
+       $vals{$tag} = $value;
+    }
+
+    # This is not really a no-op. Note the use of the /c
+    # switch - this makes perl keep track of the current
+    # search position. Notice, we did it above in the loop,
+    # too. (I have a /g here just so the /c takes affect.)
+    # We use this below when we look at how far along in the
+    # string we matched. So the point of this line is to allow
+    # trailing whitespace on the end of a line.
+    $line =~ m/\s*/ogc;
+
+    # If that loop didn't match up to end of line, we have a
+    # problem..
+    if (pos($line) < length($line)) {
+       tag "unparsable-menu-item", "$fullname:$linecount";
+       # Give up now, before things just blow up in our face.
+       return;
+    }
+
+    # Now validate the data in the menu file.
+
+    # Test for important tags.
+    foreach my $tag (@req_tags) {
+       unless ( exists($vals{$tag}) && defined($vals{$tag}) ) {
+           tag "menu-item-missing-required-tag", "$tag $fullname:$linecount";
+           # Just give up right away, if such an essential tag is missing,
+           # chance is high the rest doesn't make sense either. And now all
+           # following checks can assume those tags to be there
+           return;
+       }
+    }
+
+    # Make sure all tags are known.
+    foreach my $tag (keys %vals) {
+       if (! $known_tags_hash{$tag}) {
+           tag "menu-item-contains-unknown-tag", "$tag $fullname:$linecount";
+       }
+    }
+
+    # Sanitize the section tag
+    my $section = $vals{'section'};
+    $section =~ tr:/:/:s;      # eliminate duplicate slashes.
+    $section =~ s:/$::;                # remove trailing slash.
+
+    # Be sure the command is provided by the package.
+    my ($okay, $command) = VerifyCmd ($fullname, $linecount, $vals{'command'},
+                                     $pkg, $info);
+    tag "menu-command-not-in-package", "$fullname:$linecount $command"
+        unless ($okay
+                or not $command
+                or ($tested_packages >= 2)
+                or ($section =~ m:^(WindowManagers/Modules|FVWM Modules|Window Maker):));
+
+    if (exists($vals{'icon'})) {
+       VerifyIcon($menufile, $fullname, $linecount, $vals{'icon'}, 32);
+    }
+    if (exists($vals{'icon32x32'})) {
+       VerifyIcon($menufile, $fullname, $linecount, $vals{'icon32x32'}, 32);
+    }
+    if (exists($vals{'icon16x16'})) {
+       VerifyIcon($menufile, $fullname, $linecount, $vals{'icon16x16'}, 16);
+    }
+
+    # Check the needs tag.
+    my $needs = lc($vals{'needs'}); # needs is case insensitive.
+
+    if ($section =~ m:^(WindowManagers/Modules|FVWM Modules|Window Maker):) {
+       # WM/Modules: needs must not be the regular ones nor wm
+       if ($needs_tag_vals_hash{$needs} or $needs eq "wm") {
+           tag "non-wm-module-in-wm-modules-menu-section", "$needs $fullname:$linecount";
+       }
+    } elsif ($section =~ m:^Window ?Managers:) {
+       # Other WM sections: needs must be wm
+        if ($needs ne 'wm') {
+           tag "non-wm-in-windowmanager-menu-section", "$needs $fullname:$linecount";
+       }
+    } else {
+       # Any other section: just only the general ones
+       if ($needs eq "dwww") {
+           tag "menu-item-needs-dwww", "$fullname:$linecount";
+       } elsif (not $needs_tag_vals_hash{$needs}) {
+           tag "menu-item-needs-tag-has-unknown-value", "$needs $fullname:$linecount";
+       }
+    }
+
+    # Check the section tag
+    # Check for historical changes in the section tree.
+    if ($section =~ m:^Apps/Games:) {
+        tag "menu-item-uses-apps-games-section", "$fullname:$linecount";
+        $section =~ s:^Apps/::;
+    }
+    if ($section =~ m:^Apps/:) {
+        tag "menu-item-uses-apps-section", "$fullname:$linecount";
+        $section =~ s:^Apps/:Applications/:;
+    }
+    if ($section =~ m:^WindowManagers:) {
+        tag "menu-item-uses-windowmanagers-section", "$fullname:$linecount";
+        $section =~ s:^WindowManagers:Window Managers:;
+    }
+
+    # Check for Evil new root sections.
+    my ($rootsection) = $section =~ m:([^/]*):;
+    if (not $root_sections_hash{$rootsection}) {
+        if (not $rootsection =~ m/$pkg/i) {
+            tag "menu-item-creates-new-root-section", "$rootsection $fullname:$linecount";
+        }
+    } else {
+        if (not $sections_hash{$section}) {
+            tag "menu-item-creates-new-section", "$vals{section} $fullname:$linecount";
+        }
+    }
+}
+
+
+sub VerifyIcon {
+    my ($menufile, $fullname, $linecount, $icon, $size) = @_;
+    local *IN;
+
+    if ($icon eq 'none') {
+       tag "menu-item-uses-icon-none", "$fullname:$linecount";
+       return;
+    }
+
+    if (not ($icon =~ m/\.xpm$/i)) {
+       tag "menu-icon-not-in-xpm-format", "$icon";
+       return;
+    }
+
+    # Try the explicit location, and if that fails, try the standard path.
+    my $iconfile = "unpacked/$icon";
+    if (! -f $iconfile) {
+       $iconfile = "unpacked/usr/share/pixmaps/$icon";
+    }
+
+    if (! open (IN, '<', $iconfile)) {
+       tag "menu-icon-missing", "$icon";
+       return;
+    }
+
+    my $parse = "XPM header";
+    my $line;
+    do { defined ($line = <IN>) or goto parse_error; }
+    until ($line =~ /\/\*\s*XPM\s*\*\//);
+
+    $parse = "size line";
+    do { defined ($line = <IN>) or goto parse_error; }
+    until ($line =~ /"\s*([0-9]+)\s*([0-9]+)\s*([0-9]+)\s*([0-9]+)\s*"/);
+    my $width = $1 + 0;
+    my $height = $2 + 0;
+    my $numcolours = $3 + 0;
+    my $cpp = $4 + 0;
+
+    if ($width > $size || $height > $size) {
+       tag "menu-icon-too-big", "$icon: ${width}x${height} > ${size}x${size}";
+    }
+
+    close IN or die;
+    return;
+
+parse_error:
+    close IN or die;
+    tag "menu-icon-cannot-be-parsed", "$icon: looking for $parse";
+    return;
+}
+
+
+# Syntax-checks a .desktop file.
+sub VerifyDesktopFile {
+    my ($desktopfile, $file, $pkg, $info) = @_;
+    my %vals;
+    open (DESKTOP, '<', "unpacked/$file")
+       or fail("cannot open desktop file $file: $!");
+    my ($line, $saw_first, $warned_cr);
+    my @pending;
+    while (defined ($line = <DESKTOP>)) {
+        chomp $line;
+        next if ($line =~ m/^\s*\#/ or $line =~ m/^\s*$/);
+        if ($line =~ s/\r//) {
+            tag 'desktop-entry-file-has-crs', "/$file:$." unless $warned_cr;
+            $warned_cr = 1;
+        }
+
+        # Err on the side of caution for now.  If the first non-comment line
+        # is not the required [Desktop Entry] group, ignore this file.  Also
+        # ignore any keys in other groups.
+        last if ($saw_first and $line =~ /^\[(.*)\]\s*$/);
+        unless ($saw_first) {
+            return unless $line =~ /^\[(KDE )?Desktop Entry\]\s*$/;
+            $saw_first = 1;
+            tag 'desktop-contains-deprecated-header', "/$file:$." if ($line =~ /^\[KDE Desktop Entry\]\s*$/);
+        }
+
+       # Tag = Value.  For most errors, just add the error to pending rather
+       # than warning on it immediately since we want to not warn on tag
+       # errors if we didn't know the file type.
+       #
+       # TODO: We do not check for properly formatted localised values for
+       # keys but might be worth checking if they are properly formatted (not
+       # their value)
+       if ($line =~ /^(.*?)\s*=\s*(.*)$/) {
+           my ($tag, $value) = ($1, $2);
+           my $basetag = $tag;
+           my ($encoding) = ($basetag =~ s/\[([^\]]+)\]$//);
+           if (exists $vals{$tag}) {
+               tag "duplicated-key-in-desktop-entry", "/$file:$. $tag";
+           } elsif ($deprecated_desktop_keys{$basetag}) {
+               if ($basetag eq 'Encoding') {
+                   push (@pending, [ "desktop-entry-contains-encoding-key", "/$file:$. $tag" ]);
+               } else {
+                   push (@pending, [ "desktop-entry-contains-deprecated-key", "$file:$. $tag" ]);
+               }
+           } elsif (    not $known_desktop_keys{$basetag}
+                    and not $kde_desktop_keys{$basetag}
+                    and not $basetag =~ /^X-/) {
+               push (@pending, [ "desktop-entry-contains-unknown-key", "/$file:$. $tag" ]);
+           }
+           $vals{$tag} = $value;
+       }
+    }
+    close DESKTOP;
+
+    # Now validate the data in the desktop file, but only if it's a known type.
+    return unless ($vals{'Type'} and $known_desktop_types{$vals{'Type'}});
+
+    # Now we can issue any pending tags.
+    for my $pending (@pending) {
+       tag @$pending;
+    }
+
+    # Test for important keys.
+    for my $tag (@req_desktop_keys) {
+        unless (defined $vals{$tag}) {
+            tag "desktop-entry-missing-required-key", "/$file $tag";
+        }
+    }
+
+    # Only test whether the binary is in the package if the desktop file is
+    # directly under /usr/share/applications.  Too many applications use
+    # desktop files for other purposes with custom paths.
+    #
+    # TODO:  Should check quoting and the check special field
+    # codes in Exec for desktop files.
+    if ($file =~ m,^usr/share/applications/, and $vals{'Exec'} and $vals{'Exec'} =~ /\S/) {
+        my ($okay, $command) = VerifyCmd ($file, undef, $vals{'Exec'}, $pkg,
+                                         $info);
+        tag "desktop-command-not-in-package", "/$file $command"
+            unless $okay or $command eq 'kcmshell';
+    }
+
+    # Check the Category tag.
+    if (defined $vals{'Categories'}) {
+        my @cats = split (';', $vals{'Categories'});
+        my $saw_main;
+        for my $cat (@cats) {
+            next if $cat =~ /^X-/;
+            if ($reserved_categories{$cat}) {
+                tag "desktop-entry-uses-reserved-category", "$cat /$file"
+                    unless $vals{'OnlyShowIn'};
+                $saw_main = 1;
+            } elsif (not $categories{$cat} and not $main_categories{$cat}) {
+                tag "desktop-entry-invalid-category", "$cat /$file";
+            } elsif ($main_categories{$cat}) {
+                $saw_main = 1;
+            }
+        }
+        unless ($saw_main) {
+            tag "desktop-entry-lacks-main-category", "/$file";
+        }
+    }
+}
+
+# Verify whether a command is shipped as part of the package.  Takes the full
+# path to the file being checked (for error reporting) and the binary.
+# Returns a list whose first member is true if the command is present and
+# false otherwise, and whose second member is the command (minus any leading
+# su-to-root wrapper).  Shared between the desktop and menu code.
+sub VerifyCmd {
+    my ($file, $line, $exec, $pkg, $info) = @_;
+    $file = '/' . $file unless $file =~ m,^/,;
+    my $location = ($line ? "$file:$line" : $file);
+
+    # This routine handles su wrappers.  The option parsing here is ugly and
+    # dead-simple, but it's hopefully good enough for what will show up in
+    # desktop files.  su-to-root and sux require -c options, kdesu optionally
+    # allows one, and gksu has the command at the end of its arguments.
+    my @com = split (' ', $exec);
+    my $cmd;
+    if ($com[0] and $com[0] eq "/usr/sbin/su-to-root") {
+        tag 'su-to-root-with-usr-sbin', $location;
+    }
+    if ($com[0] and $com[0] =~ m,^(?:/usr/s?bin/)?(su-to-root|gksu|kdesu|sux)$,) {
+        my $wrapper = $1;
+        shift @com;
+        while (@com) {
+            unless ($com[0]) {
+                shift @com;
+                next;
+            }
+            if ($com[0] eq '-c') {
+                $cmd = $com[1];
+                last;
+            } elsif ($com[0] =~ /^-[Dfmupi]|^--(user|description|message)/) {
+                shift @com;
+                shift @com;
+            } elsif ($com[0] =~ /^-/) {
+                shift @com;
+            } else {
+                last;
+            }
+        }
+        if (!$cmd && $wrapper =~ /^(gk|kde)su$/) {
+            if (@com) {
+                $cmd = $com[0];
+            } else {
+                $cmd = $wrapper;
+               undef $wrapper;
+            }
+        }
+        tag 'su-wrapper-without--c', "$location $wrapper" unless $cmd;
+       if ($wrapper && $wrapper !~ /su-to-root/ && $wrapper ne $pkg) {
+           tag 'su-wrapper-not-su-to-root', "$location $wrapper";
+       }
+    } else {
+        $cmd = $com[0];
+    }
+    my $cmd_file = $cmd;
+    if ($cmd_file) {
+       $cmd_file =~ s,^/,,;
+    }
+    my $okay = $cmd
+       && ($cmd =~ /^[\'\"]/
+           || $info->index->{$cmd_file}
+           || $cmd =~ m,^(/bin/)?sh,
+           || $cmd =~ m,^(/usr/bin/)?sensible-(pager|editor|browser),
+           || grep { $info->index->{$_ . $cmd} } @path);
+    return ($okay, $cmd);
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl ts=8 sw=4
diff --git a/checks/menu-format.desc b/checks/menu-format.desc
new file mode 100644 (file)
index 0000000..416123f
--- /dev/null
@@ -0,0 +1,325 @@
+Check-Script: menu-format
+Author: Joey Hess <joeyh@master.debian.org>
+Abbrev: mnf
+Needs-Info: menu-files
+Type: binary
+Unpack-Level: 2
+Info: This script validates the format of menu files.
+
+Tag: old-format-menu-file
+Severity: important
+Certainty: certain
+Info: The package contains a menu file that is in the old menu file format.
+ This format is deprecated. Convert the menu file to the new menu format.
+ Note that lintian will not test this file for any other problems.
+
+Tag: whitespace-after-continuation-character
+Severity: important
+Certainty: certain
+Info: The menu item is split up over 2 or more lines using '\' at the end of
+ the line to join them together. However, there is some whitespace after
+ the '\' character, which is not guaranteed to be handled correctly.
+ The '\' should be at the end of the line.
+
+Tag: bad-test-in-menu-item
+Severity: important
+Certainty: certain
+Info: The menu file contains an item that does not start with the text
+ "?package(somepackage):". All menu file lines must test for the existence
+ of a package in this way.
+Ref: menu 3.2
+
+Tag: unparsable-menu-item
+Severity: important
+Certainty: certain
+Info: An item of the menu file cannot be parsed as a series of tag=value
+ pairs. This could be because you didn't close a set of double quotes.
+Ref: menu 3.2
+
+Tag: pkg-not-in-package-test
+Severity: normal
+Certainty: possible
+Info: This menu item doesn't test to see if the package containing it is
+ installed.  The start of any menu item is a conditional testing whether
+ the required packages are installed.  Normally this conditional should
+ always check at least the package containing it, since menu items should
+ be included in the package that provides the application the menu refers
+ to.
+ .
+ This error usually indicates a misspelling of the package name in the
+ menu entry or a copied menu entry from another package that doesn't apply
+ to this one.
+Ref: menu 3.2
+
+Tag: duplicated-tag-in-menu-item
+Severity: normal
+Certainty: certain
+Info: The menu item contains two instances of the same tag. This is just a
+ waste of space, as menu will only use one of them.
+Ref: menu 3.2
+
+Tag: menu-item-missing-required-tag
+Severity: important
+Certainty: certain
+Info: The menu item has a line that is missing a required tag. It's likely
+ that the line will have no effect without this tag. <tt>install-menu</tt> may
+ report this as an error during package installation.
+
+Tag: menu-item-contains-unknown-tag
+Severity: minor
+Certainty: certain
+Info: The menu item has a line that has a tag in it that is not one
+ of the standard tags (needs=, section=, title=, longtitle=, command=, etc).
+ While other tags can be used for specialized purposes, this is rare and
+ it's more likely the tag's name is misspelled.
+
+Tag: menu-item-uses-icon-none
+Severity: minor
+Certainty: certain
+Info: The menu item has a line that uses icon=none. This is redundant and
+ deprecated -- if there is no icon, just leave off the icon tag.
+
+Tag: menu-item-needs-tag-has-unknown-value
+Severity: minor
+Certainty: certain
+Info: The menu item has a line that has a needs= field with a strange value.
+ This may be intentional, but it's probably a typo that will make menu
+ ignore the line.
+
+Tag: menu-item-uses-apps-games-section
+Severity: normal
+Certainty: certain
+Info: The menu item has a line that specifies a section under "Apps/Games".
+ This section has been moved to just "Games".
+Ref: menu-policy 2.1
+
+Tag: menu-item-uses-apps-section
+Severity: normal
+Certainty: certain
+Info: The menu item has a line that specifies a section under "Apps".
+ This section has been moved to "Applications".
+Ref: menu-policy 2.1
+
+Tag: menu-item-uses-windowmanagers-section
+Severity: normal
+Certainty: certain
+Info: The menu item has a line that specifies a section under
+ "WindowManagers".  This section has been moved to "Window Managers".
+Ref: menu-policy 2.1
+
+Tag: menu-item-creates-new-section
+Severity: normal
+Certainty: certain
+Info: The menu item has a line that specifies an unknown section or uses a
+ section that is intended only as a menu root, not as a section that
+ applications should use directly.  Check the spelling of the section and
+ check the section against the list in the menu policy.  (The menu
+ sections changed as of June of 2007.)
+Ref: menu-policy 2.1
+
+Tag: menu-item-creates-new-root-section
+Severity: important
+Certainty: certain
+Info: The menu item has a line that specifies a new section to put a menu
+ entry in, and this section appears right in the root menu.  This is
+ almost certainly an error.  No new sections should be added to the root
+ menu without discussion with the author of menu.
+
+Tag: menu-icon-not-in-xpm-format
+Severity: important
+Certainty: certain
+Info: Icons in the Debian menu system should be in XPM format.
+Ref: menu 3.7
+
+Tag: menu-icon-missing
+Severity: normal
+Certainty: possible
+Info: This icon file couldn't be found.  If the path to the icon in the
+ menu file is an absolute path, make sure that icon exists at that path in
+ the package.  If the path is relative or a simple filename, make sure the
+ icon is installed in <tt>/usr/share/pixmaps</tt>, the default location.
+ .
+ If the icon is in a package this package depends on, add a lintian
+ override for this warning.  lintian cannot check icons in other packages.
+Ref: menu 3.7
+
+Tag: menu-icon-too-big
+Severity: important
+Certainty: certain
+Info: Icons in the Debian menu system should be at most 32x32 pixels
+ (icon16x16 icons should of course be at most 16x16 pixels)
+Ref: menu 3.7
+
+Tag: menu-icon-cannot-be-parsed
+Severity: normal
+Certainty: certain
+Info: The icon file could not be parsed.  Perhaps this means a bad XPM file,
+ or perhaps it means the lintian parsing needs to be improved.  If the
+ window managers and standard tools accept the file then probably it's the
+ latter; please file a bug on lintian then.
+
+Tag: su-wrapper-without--c
+Severity: important
+Certainty: certain
+Info: The menu item command or desktop file uses an su wrapper such as
+ su-to-root without the -c flag. This is a syntax error.
+Ref: su-to-root(1)
+
+Tag: su-to-root-with-usr-sbin
+Severity: normal
+Certainty: certain
+Info: The menu item or desktop file command uses su-to-root as
+ /usr/sbin/su-to-root. Since sarge su-to-root is located in /usr/bin and
+ /usr/sbin/su-to-root is only a compatibility symlink that may get dropped
+ in the future.
+ .
+ Since su-to-root is now located in /usr/bin you can use it without
+ absolute path now.
+
+Tag: su-wrapper-not-su-to-root
+Severity: normal
+Certainty: certain
+Info: The menu item or desktop file command uses an su wrapper other than
+ su-to-root.  On Debian systems, please use <tt>su-to-root -X</tt>, which
+ will pick the correct wrapper based on what's installed on the system and
+ the current desktop environment.  Using su-to-root is also important for
+ Live CD systems which need to use sudo rather than su.  su-to-root
+ permits global configuration to use sudo.
+
+Tag: menu-item-needs-dwww
+Severity: normal
+Certainty: certain
+Info: The menu item has needs=dwww. This is deprecated. Instead, you should
+ register your documentation with doc-base, and dwww entries will be 
+ automatically generated.
+
+Tag: non-wm-in-windowmanager-menu-section
+Severity: important
+Certainty: certain
+Info: The menu item is in the Window Manager section but does not needs=wm.
+ Either it is a window manager and it should needs=wm, either it isn't and
+ then it must be moved in another section.
+
+Tag: non-wm-module-in-wm-modules-menu-section
+Severity: important
+Certainty: certain
+Info: The menu item is in the FVWM Modules or Window Maker section but
+ does not declare that it needs a specific window manager (using the needs
+ key in the menu file).  Modules for Fvwm should have needs="fvwmmodule".
+ Modules for WindowMaker should have needs="wmmaker".
+
+Tag: unquoted-string-in-menu-item
+Severity: normal
+Certainty: certain
+Info: The menu item includes a tag with an unquoted string like section=Games
+ instead of section="Games". This is deprecated. Use a quoted string instead.
+Ref: menu 3.2
+
+Tag: menu-command-not-in-package
+Severity: normal
+Certainty: possible
+Info: The menu item specifies a command which is not available in the package.
+ In most cases this is a typo or after you moved a binary around, but forgot
+ to update the menu file.
+
+Tag: executable-desktop-file
+Severity: important
+Certainty: certain
+Info: The desktop entry file is marked executable.  Desktop entries are
+ regular files and should be installed mode 0644.
+
+Tag: desktop-entry-file-has-crs
+Severity: normal
+Certainty: certain
+Info: The desktop entry file has lines ending in CRLF instead of just LF.
+ The Desktop Entry Specification is explicit that lines should end with
+ only LF.  The CR may be taken by some software as part of the field.
+Ref: http://standards.freedesktop.org/desktop-entry-spec/1.0/ar01s02.html
+
+Tag: duplicated-key-in-desktop-entry
+Severity: normal
+Certainty: certain
+Info: The desktop entry contains two instances of the same key.  The
+ behavior of such desktop entries is not well-defined by the standard.
+
+Tag: desktop-entry-missing-required-key
+Severity: important
+Certainty: certain
+Info: Desktop entries must contain, at a minimum, the keys Type and Name.
+Ref: http://standards.freedesktop.org/desktop-entry-spec/1.0/ar01s05.html
+
+Tag: desktop-entry-contains-unknown-key
+Severity: minor
+Certainty: certain
+Info: The key on this line of the desktop entry is not one of the standard
+ keys defined in the FreeDesktop specification, not one of the legacy KDE
+ keywords, and one that does not begin with <tt>X-</tt>.  It's most likely
+ that the key was misspelled.
+Ref: http://standards.freedesktop.org/desktop-entry-spec/1.0/ar01s05.html
+
+Tag: desktop-entry-contains-deprecated-key
+Severity: normal
+Certainty: certain
+Info: The key on this line of the desktop entry has been deprecated in the
+ FreeDesktop specification.
+Ref: http://standards.freedesktop.org/desktop-entry-spec/1.0/apc.html
+
+Tag: desktop-entry-contains-encoding-key
+Severity: wishlist
+Certainty: certain
+Info: The Encoding key is now deprecated by the FreeDesktop standard and
+ all strings are required to be encoded in UTF-8.  This desktop entry
+ explicitly specifies an Encoding of UTF-8, which is harmless but no
+ longer necessary.
+Ref: http://standards.freedesktop.org/desktop-entry-spec/1.0/apc.html
+
+Tag: desktop-entry-lacks-main-category
+Severity: normal
+Certainty: certain
+Info: The categories for this desktop entry do not contain any Main
+ Categories, only Additional Categories.  Additional Categories should
+ only be used on conjunction with one or more Main Categories.
+Ref: http://standards.freedesktop.org/menu-spec/1.0/apa.html
+
+Tag: desktop-entry-uses-reserved-category
+Severity: normal
+Certainty: certain
+Info: This desktop entry includes a Reserved Category, one which has a
+ desktop-specific meaning that has not yet been standardized, but does not
+ include an OnlyShowIn key.  Desktop entries using a Reserved Category
+ must include an OnlyShowIn key limiting the entry to those environments
+ that support the category.
+Ref: http://standards.freedesktop.org/menu-spec/1.0/apa.html
+
+Tag: desktop-entry-invalid-category
+Severity: normal
+Certainty: certain
+Info: This desktop entry lists a category that is not one of the
+ registered Main or Additional Categories in the FreeDesktop
+ specification.
+Ref: http://standards.freedesktop.org/menu-spec/1.0/apa.html
+
+Tag: desktop-command-not-in-package
+Severity: normal
+Certainty: possible
+Info: The desktop entry specifies a command that is not available in the
+ package.  In most cases, this is a typo or a forgotten update of the
+ desktop file after the install location of the binary was changed.  A
+ desktop file for a command should be included in the same package as that
+ command.
+
+Tag: desktop-contains-deprecated-header
+Severity: normal
+Certainty: certain
+Info: The header on this line of the desktop entry has been deprecated in the
+ FreeDesktop specification.  If the header is "KDE Desktop Entry", the right
+ fix is normally changing it to "Desktop Entry".
+Ref: http://standards.freedesktop.org/desktop-entry-spec/1.0/apc.html
+
+Tag: deprecated-kdelnk-file
+Severity: important
+Certainty: certain
+Info: kdelnk files were used by KDE 1 and since KDE 2 desktop files are used
+ instead.  Renaming the file and removing the deprecated header and keys
+ is often all that needs to be done.
+Ref: http://standards.freedesktop.org/desktop-entry-spec/1.0/apc.html
diff --git a/checks/menus b/checks/menus
new file mode 100644 (file)
index 0000000..ca93923
--- /dev/null
@@ -0,0 +1,691 @@
+# menus -- lintian check script -*- perl -*-
+
+# somewhat of a misnomer -- it doesn't only check menus
+
+# Copyright (C) 1998 Christian Schwarz
+#
+# 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 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.
+
+package Maemian::menus;
+use strict;
+
+use lib "$ENV{'MAEMIAN_ROOT'}/checks/";
+use common_data;
+
+use Maemian::Data;
+use Spelling;
+use Tags;
+use Util;
+
+# Supported documentation formats for doc-base files.
+our %known_doc_base_formats = map { $_ => 1 }
+    ( 'html', 'text', 'pdf', 'postscript', 'info', 'dvi', 'debiandoc-sgml' );
+
+# Known fields for doc-base files.  The value is 1 for required fields and 0
+# for optional fields.
+our %KNOWN_DOCBASE_MAIN_FIELDS = (
+       'document' => 1,
+       'title'    => 1,
+       'section'  => 1,
+       'abstract' => 0,
+       'author'   => 0
+);
+our %KNOWN_DOCBASE_FORMAT_FIELDS = (
+       'format'  => 1,
+       'files'   => 1,
+       'index'   => 0
+);
+
+# Will contain the list of valid sections as a Maemian::Data object if it's
+# needed.  We don't load it unless we need it since many packages don't have
+# doc-base files.
+our $SECTIONS;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+my %all_files = ();
+my %all_links = ();
+
+my %preinst;
+my %postinst;
+my %prerm;
+my %postrm;
+
+my $docbase_file;
+my $menu_file;
+my $menumethod_file;
+my $anymenu_file;
+
+if (-f 'control/preinst') {
+    check_script($pkg, 'preinst', \%preinst);
+}
+if (-f 'control/postinst') {
+    check_script($pkg, 'postinst', \%postinst);
+}
+if (-f 'control/prerm') {
+    check_script($pkg, 'prerm', \%prerm);
+}
+if (-f 'control/postrm') {
+    check_script($pkg, 'postrm', \%postrm);
+}
+
+# read package contents
+for my $file (sort keys %{$info->index}) {
+    next if $file eq "";
+
+    add_file_link_info ($info, $file, \%all_files, \%all_links);
+    my $index_info = $info->index->{$file};
+    my $operm = $index_info->{operm};
+
+    if ($index_info->{type} =~ m,^[-h],) { # file checks
+       # menu file?
+       if ($file =~ m,^usr/(lib|share)/menu/\S,o) { # correct permissions?
+           if ($operm & 01 or $operm & 010 or $operm & 0100) {
+               tag "executable-menu-file", sprintf("$file %04o",$operm);
+           }
+
+           next if $file =~ m,^usr/(lib|share)/menu/README$,;
+
+           if ($file =~ m,^usr/lib/,o) {
+               tag "menu-file-in-usr-lib", $file;
+           }
+
+           $menu_file = $file;
+
+           if ($file =~ m,usr/(lib|share)/menu/menu$,o and $pkg ne 'menu') {
+               tag "bad-menu-file-name", $file;
+           }
+       }
+       # doc-base file?
+       elsif ($file =~ m,^usr/share/doc-base/\S,o) { # correct permissions?
+           if ($operm & 01 or $operm & 010 or $operm & 0100) {
+               tag "executable-in-usr-share-docbase", $file, sprintf("%04o",$operm);
+           }
+           $docbase_file = $file;
+       }
+       #menu-methods file?
+       elsif ( $file =~ m,^etc/menu-methods/\S,o ) {
+           #TODO: we should test if the menu-methods file
+           # is made executable in the postinst as recommended by
+           # the menu manual
+
+           my $menumethod_includes_menu_h = 0;
+           $menumethod_file = $file;
+
+           open(MM, '<', "unpacked/$file") or fail("cannot open menu-method file $file: $!");
+           while (<MM>) {
+               chomp;
+               if (m,^!include menu.h,o) {
+                   $menumethod_includes_menu_h = 1;
+                   last;
+               }
+           }
+           close MM;
+           tag "menu-method-should-include-menu-h", "$file"
+               unless $menumethod_includes_menu_h or $pkg eq 'menu';
+       }
+    }
+}
+close IN;
+
+# prerm scripts should not call update-menus
+if ($prerm{'calls-updatemenus'}) {
+    tag "prerm-calls-updatemenus";
+}
+
+# postrm scripts should not call install-docs
+if ($postrm{'calls-installdocs'} or $postrm{'calls-installdocs-r'}) {
+    tag "postrm-calls-installdocs";
+}
+
+# preinst scripts should not call either update-menus nor installdocs
+if ($preinst{'calls-updatemenus'}) {
+    tag "preinst-calls-updatemenus";
+}
+
+if ($preinst{'calls-installdocs'}) {
+    tag "preinst-calls-installdocs";
+}
+
+# don't set the /usr/doc link, the FHS transition is over (2002-10-08)
+if (defined $postinst{'sets-link'} && $postinst{'sets-link'} == 1) {
+    tag "postinst-should-not-set-usr-doc-link";
+}
+
+$anymenu_file = $menu_file || $menumethod_file;
+
+# No one needs to call install-docs any more; triggers now handles that.
+if ($postinst{'calls-installdocs'} or $postinst{'calls-installdocs-r'}) {
+    tag "postinst-has-useless-call-to-install-docs";
+}
+if ($prerm{'calls-installdocs'} or $prerm{'calls-installdocs-r'}) {
+    tag "prerm-has-useless-call-to-install-docs";
+}
+
+# check consistency
+# docbase file?
+if ($docbase_file) {
+    opendir DOCBASEDIR, "doc-base" or fail("cannot read doc-base directory.");
+    my $dbfile;
+    while (defined ($dbfile = readdir DOCBASEDIR)) {
+       # don't try to parse executables, plus we already warned about it
+       next if -x "doc-base/$dbfile";
+       check_doc_base_file($dbfile, $pkg, $type, \%all_files, \%all_links);
+    }
+    closedir DOCBASEDIR;
+}
+
+if ($anymenu_file) {
+    # postinst and postrm should not need to call update-menus unless there is
+    # a menu-method file.  However, update-menus currently won't enable
+    # packages that have outstanding triggers, leading to an update-menus call
+    # being required for at least some packages right now.  Until this bug is
+    # fixed, we still require it.  See #518919 for more information.
+    #
+    # That bug does not require calling update-menus from postrm, but
+    # debhelper apparently currently still adds that to the maintainer script,
+    # so don't warn if it's done.
+    if (not $postinst{'calls-updatemenus'}) {
+       tag "postinst-does-not-call-updatemenus", "$anymenu_file";
+    }
+    if ($menumethod_file and not $postrm{'calls-updatemenus'}) {
+       tag "postrm-does-not-call-updatemenus", "$menumethod_file"
+           unless $pkg eq 'menu';
+    }
+} else {
+    if ($postinst{'calls-updatemenus'}) {
+       tag "postinst-has-useless-call-to-update-menus";
+    }
+    if ($postrm{'calls-updatemenus'}) {
+       tag "postrm-has-useless-call-to-update-menus";
+    }
+}
+
+}
+
+# -----------------------------------
+
+sub check_doc_base_file {
+    my ($dbfile, $pkg, $type, $all_files, $all_links) = @_;
+
+    my $line = file_is_encoded_in_non_utf8("doc-base/$dbfile", $type, $pkg);
+    if ($line) {
+       tag 'doc-base-file-uses-obsolete-national-encoding', "$dbfile:$line";
+    }
+
+    open (IN, '<', "doc-base/$dbfile")
+        or fail("cannot open doc-base file $dbfile for reading.");
+
+    my (@files, $field, @vals);
+    my $knownfields = \%KNOWN_DOCBASE_MAIN_FIELDS;
+    $line           = 0;  # global
+    my %sawfields   = (); # local for each section of control file
+    my %sawformats  = (); # global for control file
+
+    while (<IN>) {
+        chomp;
+
+        # New field.  check previous field, if we have any.
+        if (/^(\S+)\s*:\s*(.*)$/) {
+            my (@new) = ($1, $2);
+            if ($field) {
+               check_doc_base_field($pkg, $dbfile, $line, $field, \@vals,
+                                    \%sawfields, \%sawformats, $knownfields,
+                                    $all_files, $all_links);
+            }
+            $field = lc $new[0];
+            @vals  = ($new[1]);
+            $line  = $.;
+
+        # Continuation of previously defined field.
+        } elsif ($field && /^\s+\S/) {
+            push (@vals, $_);
+
+            # All tags will be reported on the last continuation line of the
+            # doc-base field.
+            $line  = $.;
+
+        # Sections' separator.
+        } elsif (/^(\s*)$/) {
+            tag "doc-base-file-separator-extra-whitespaces", "$dbfile:$."
+                if $1;
+            next unless $field; # skip successive empty lines
+
+            # Check previously defined field and section.
+           check_doc_base_field($pkg, $dbfile, $line, $field, \@vals,
+                                \%sawfields, \%sawformats, $knownfields,
+                                $all_files, $all_links);
+            check_doc_base_file_section($dbfile, $line + 1, \%sawfields,
+                                        \%sawformats, $knownfields);
+
+            # Intialize variables for new section.
+            undef $field;
+            undef $line;
+            @vals      = ();
+            %sawfields = ();
+
+            # Each section except the first one is format section.
+            $knownfields = \%KNOWN_DOCBASE_FORMAT_FIELDS;
+
+        # Everything else is a syntax error.
+        } else {
+            tag "doc-base-file-syntax-error", "$dbfile:$.";
+        }
+    }
+
+    # Check the last field/section of the control file.
+    if ($field) {
+       check_doc_base_field($pkg, $dbfile, $line, $field, \@vals, \%sawfields,
+                            \%sawformats, $knownfields, $all_files,
+                            $all_links);
+        check_doc_base_file_section($dbfile, $line, \%sawfields, \%sawformats,
+                                    $knownfields);
+    }
+
+    # Make sure we saw at least one format.
+    tag "doc-base-file-no-format-section", "$dbfile:$." unless %sawformats;
+
+    close IN;
+}
+
+# Checks one field of a doc-base control file.  $vals is array ref containing
+# all lines of the field.  Modifies $sawfields and $sawformats.
+sub check_doc_base_field {
+    my ($pkg, $dbfile, $line, $field, $vals, $sawfields, $sawformats,
+        $knownfields, $all_files, $all_links) = @_;
+
+    tag "doc-base-file-unknown-field", "$dbfile:$line", "$field"
+        unless defined $knownfields->{$field};
+    tag "doc-base-file-duplicated-field", "$dbfile:$line", "$field"
+        if $sawfields->{$field};
+    $sawfields->{$field} = 1;
+
+    # Index/Files field.
+    #
+    # Check if files referenced by doc-base are included in the package.  The
+    # Index field should refer to only one file without wildcards.  The Files
+    # field is a whitespace-separated list of files and may contain wildcards.
+    # We skip without validating wildcard patterns containing character
+    # classes since otherwise we'd need to deal with wildcards inside
+    # character classes and aren't there yet.
+    if ($field eq 'index' or $field eq 'files') {
+        my @files = map { split ('\s+', $_) } @$vals;
+
+        if ($field eq 'index' && @files > 1) {
+            tag "doc-base-index-references-multiple-files", "$dbfile:$line";
+        }
+        for my $file (@files) {
+           next if $file eq '';
+            if ($file =~ m%^/usr/doc%) {
+                tag "doc-base-file-references-usr-doc", "$dbfile:$line";
+            }
+            my $realfile = delink ($file, $all_links);
+            # openoffice.org-dev-doc has thousands of files listed so try to
+            # use the hash if possible.
+            my $found;
+            if ($realfile =~ /[*?]/) {
+                my $regex = quotemeta ($realfile);
+                unless ($field eq 'index') {
+                    next if $regex =~ /\[/;
+                    $regex =~ s%\\\*%[^/]*%g;
+                    $regex =~ s%\\\?%[^/]%g;
+                    $regex .= '/?';
+                }
+                $found = grep { /^$regex\z/ } keys %$all_files;
+            } else {
+                $found = $all_files->{$realfile} || $all_files->{"$realfile/"};
+            }
+            unless ($found) {
+                tag "doc-base-file-references-missing-file", "$dbfile:$line",
+                    $file;
+            }
+        }
+        undef @files;
+
+    # Format field.
+    } elsif ($field eq 'format') {
+        my $format = join (' ', @$vals);
+        $format =~ s/^\s+//o;
+        $format =~ s/\s+$//o;
+        $format = lc $format;
+        tag "doc-base-file-unknown-format", "$dbfile:$line", $format
+            unless $known_doc_base_formats{$format};
+        tag "doc-base-file-duplicated-format", "$dbfile:$line", $format
+            if $sawformats->{$format};
+        $sawformats->{$format} = 1;
+
+        # Save the current format for the later section check.
+        $sawformats->{' *current* '} = $format;
+
+    # Document field.
+    } elsif ($field eq 'document') {
+        $_ = join (' ', @$vals);
+
+        tag "doc-base-invalid-document-field", "$dbfile:$line", "$_"
+            unless /^[a-z0-9+.-]+$/;
+        tag "doc-base-document-field-ends-in-whitespace", "$dbfile:$line"
+            if /[ \t]$/;
+        tag "doc-base-document-field-not-in-first-line", "$dbfile:$line"
+            unless $line == 1;
+
+    # Title field.
+    } elsif ($field eq 'title') {
+        if (@$vals) {
+            spelling_check("spelling-error-in-doc-base-title-field",
+                           join (' ', @$vals), "$dbfile:$line");
+            spelling_check_picky("spelling-error-in-doc-base-title-field",
+                                 join (' ', @$vals), "$dbfile:$line");
+        }
+
+    # Section field.
+    } elsif ($field eq 'section') {
+       $SECTIONS = Maemian::Data->new('doc-base/sections') unless $SECTIONS;
+       $_ = join (' ', @$vals);
+       unless ($SECTIONS->known($_)) {
+           if (m,^App(?:lication)?s/(.+)$, and $SECTIONS->known($1)) {
+               tag "doc-base-uses-applications-section", "$dbfile:$line", $_;
+           } elsif (m,^(.+)/([^/]+)$, and $SECTIONS->known($1)) {
+               # allows creating a new subsection to a known section
+           } else {
+               tag "doc-base-unknown-section", "$dbfile:$line", $_;
+           }
+       }
+
+    # Abstract field.
+    } elsif ($field eq 'abstract') {
+        # The three following variables are used for checking if the field is
+        # correctly phrased.  We detect if each line (except for the first
+        # line and lines containing single dot) of the field starts with the
+        # same number of spaces, not followed by the same non-space character,
+        # and the number of spaces is > 1.
+        #
+        # We try to match fields like this:
+        #  ||Abstract: The Boost web site provides free peer-reviewed portable
+        #  ||  C++ source libraries.  The emphasis is on libraries which work
+        #  ||  well with the C++ Standard Library.  One goal is to establish
+        #
+        # but not like this:
+        #  ||Abstract:  This is "Ding"
+        #  ||  * a dictionary lookup program for Unix,
+        #  ||  * DIctionary Nice Grep,
+        my $leadsp    = undef; # string with leading spaces from second line
+        my $charafter = undef; # first non-whitespace char of second line
+        my $leadsp_ok = 1;     # are spaces OK?
+
+        # Intentionally skipping the first line.
+        for my $idx (1 .. $#{$vals}) {
+            $_ = $vals->[$idx];
+            if (/manage\s+online\s+manuals\s.*Debian/o) {
+                tag "doc-base-abstract-field-is-template", "$dbfile:$line"
+                    unless $pkg eq "doc-base";
+            } elsif (/^(\s+)\.(\s*)$/o and ($1 ne " " or $2)) {
+                tag "doc-base-abstract-field-separator-extra-whitespaces",
+                    "$dbfile:" . ($line - $#{$vals} + $idx);
+            } elsif (!$leadsp && /^(\s+)(\S)/o) {
+                # The regexp should always match.
+                ($leadsp, $charafter) = ($1, $2);
+                $leadsp_ok = $leadsp eq " ";
+            } elsif (!$leadsp_ok && /^(\s+)(\S)/o) {
+                # The regexp should always match.
+                undef $charafter if $charafter && $charafter ne $2;
+                $leadsp_ok = 1
+                    if ($1 ne $leadsp) || ($1 eq $leadsp && $charafter);
+            }
+        }
+        unless ($leadsp_ok) {
+            tag "doc-base-abstract-might-contain-extra-leading-whitespaces",
+                "$dbfile:$line";
+        }
+
+        # Check spelling.
+        if (@$vals) {
+            spelling_check("spelling-error-in-doc-base-abstract-field",
+                           join (' ', @$vals), "$dbfile:$line");
+            spelling_check_picky("spelling-error-in-doc-base-abstract-field",
+                                 join (' ', @$vals), "$dbfile:$line");
+        }
+    }
+}
+
+# Checks the section of the doc-base control file.  Tries to find required
+# fields missing in the section.
+sub check_doc_base_file_section {
+    my ($dbfile, $line, $sawfields, $sawformats, $knownfields) = @_;
+
+    tag "doc-base-file-no-format", "$dbfile:$line"
+        if ((defined $sawfields->{'files'} || defined $sawfields->{'index'})
+            && !(defined $sawfields->{'format'}));
+
+    # The current format is set by check_doc_base_field.
+    if ($sawfields->{'format'}) {
+        my $format =  $sawformats->{' *current* '};
+        tag "doc-base-file-no-index", "$dbfile:$line"
+            if ($format && ($format eq 'html' || $format eq 'info')
+                && !$sawfields->{'index'});
+    }
+    for my $field (sort keys %$knownfields) {
+        tag "doc-base-file-lacks-required-field", "$dbfile:$line", "$field"
+            if ($knownfields->{$field} == 1 && !$sawfields->{$field});
+    }
+}
+
+# Add file and link to $all_files and $all_links.  Note that both files and
+# links have to include a leading /.
+sub add_file_link_info {
+    my ($info, $file, $all_files, $all_links) = @_;
+    my $link = $info->index->{$file}->{link};
+    my $ishard = ($info->index->{$file}->{type} eq 'h');
+
+    $file = "/" . $file if (not $file =~ m%^/%); # make file absolute
+    $file =~ s%/+%/%g;                          # remove duplicated `/'
+    $all_files->{$file} = 1;
+
+    if (defined $link) {
+       $link = './' . $link if $link !~ m,^/,;
+       if ($ishard) {
+           $link =~ s,^\./,/,;
+       } elsif (not $link =~ m,^/,) {            # not absolute link
+           $link = "/" . $link;                  # make sure link starts with '/'
+           $link =~ s,/+\./+,/,g;                # remove all /./ parts
+           my $dcount = 1;
+           while ($link =~ s,^/+\.\./+,/,) {     #\ count & remove
+              $dcount++;                         #/ any leading /../ parts
+           }
+           my $f = $file;
+           while ($dcount--) {                   #\ remove last $dcount
+               $f =~ s,/[^/]*$,,;                #/ path components from $file
+           }
+           $link = $f . $link;                   # now we should have absolute link
+       }
+       $all_links->{$file} = $link unless ($link eq $file);
+    }
+}
+
+
+# Dereference all symlinks in file.
+sub delink {
+    my ($file, $all_links) = @_;
+
+    $file =~ s%/+%/%g;                           # remove duplicated '/'
+    return $file unless %$all_links;             # package doesn't symlinks
+
+    my $p1 = "";
+    my $p2 = $file;
+    my %used_links = ();
+
+    # In the loop below we split $file into two parts on each '/' until
+    # there's no remaining slashes.  We try substituting the first part with
+    # corresponding symlink and if it succeedes, we start the procedure from
+    # beginning.
+    #
+    # Example:
+    #   Let $all_links{"/a/b"} == "/d", and $file == "/a/b/c"
+    #   Then 0) $p1 == "",     $p2 == "/a/b/c"
+    #        1) $p1 == "/a",   $p2 == "/b/c"
+    #        2) $p1 == "/a/b", $p2 == "/c"      ; substitute "/d" for "/a/b"
+    #        3) $p1 == "",     $p2 == "/d/c"
+    #        4) $p1 == "/d",   $p2 == "/c"
+    #        5) $p1 == "/d/c", $p2 == ""
+    #
+    # Note that the algorithm supposes, that
+    #   i) $all_links{$X} != $X for each $X
+    #  ii) both keys and values of %all_links start with '/'
+
+    while (($p2 =~ s%^(/[^/]*)%%g) > 0) {
+       $p1 .= $1;
+       if (defined $all_links->{$p1}) {
+           return '!!! SYMLINK LOOP !!!' if defined $used_links{$p1};
+           $p2 = $all_links->{$p1} . $p2;
+           $p1 = "";
+           $used_links{$p1} = 1;
+       }
+    }
+
+    # After the loop $p2 should be empty and $p1 should contain the target
+    # file.  In some rare cases when $file contains no slashes, $p1 will be
+    # empty and $p2 will contain the result (which will be equal to $file).
+    return $p1 ne "" ? $p1 : $p2;
+}
+
+sub check_script {
+    my ($pkg, $script, $pres) = @_;
+    my ($no_check_menu,$no_check_installdocs,$no_check_wmmenu,$calls_wmmenu);
+    my $interp;
+
+    open(IN, '<', "control/$script") or
+       fail("cannot open maintainer script control/$script for reading: $!");
+    $interp = <IN>;
+    $interp = '' unless defined $interp;
+    if ($interp =~ m,^\#\!\s*/bin/$known_shells_regex,) {
+        $interp = 'sh';
+    } elsif ($interp =~ m,^\#\!\s*/usr/bin/perl,) {
+        $interp = 'perl';
+    } else {
+       if ($interp =~ m,^\#\!\s*(.+),) {
+            $interp = $1;
+       }
+       else { # hmm, doesn't seem to start with #!
+           # is it a binary? look for ELF header
+           if ($interp =~ m/^\177ELF/) {
+               return; # nothing to do here
+           }
+           $interp = 'unknown';
+       }
+    }
+
+    while (<IN>) {
+       # skip comments
+       s/\#.*$//o;
+
+       ##
+       # either update-menus or wm-menu-config will satisfy
+       # the checks that the menu file installed is properly used
+       ##
+
+       # does the script check whether update-menus exists?
+       if (/-x\s+\S*update-menus/o or /(which|type)\s+update-menus/o
+           or /command\s+.*?update-menus/o) {
+           # yes, it does.
+           $pres->{'checks-for-updatemenus'} = 1;
+       }
+
+       # does the script call update-menus?
+       # TODO this regex-magic should be moved to some lib for checking
+       # whether a certain word is likely called as command... --Jeroen
+       if (/(?:^\s*|[;&|]\s*|(?:then|do)\s+)(?:\/usr\/bin\/)?update-menus(?:\s|[;&|<>]|$)/) {
+           # yes, it does.
+           $pres->{'calls-updatemenus'} = 1;
+
+           # checked first?
+           if (not $pres->{'checks-for-updatemenus'} and $pkg ne 'menu') {
+               tag "maintainer-script-does-not-check-for-existence-of-updatemenus", "$script:$." unless $no_check_menu++;
+           }
+       }
+
+       # does the script check whether wm-menu-config exists?
+       if (s/-x\s+\S*wm-menu-config//o or /(which|type)\s+wm-menu-config/o
+           or s/command\s+.*?wm-menu-config//o) {
+           # yes, it does.
+           $pres->{'checks-for-wmmenuconfig'} = 1;
+       }
+
+       # does the script call wm-menu-config?
+       if (m/(?:^\s*|[;&|]\s*|(?:then|do)\s+)(?:\/usr\/sbin\/)?wm-menu-config(?:\s|[;&|<>]|$)/) {
+           # yes, it does.
+           $pres->{'calls-wmmenuconfig'} = 1;
+           tag "maintainer-script-calls-deprecated-wm-menu-config", "$script:$." unless $calls_wmmenu++;
+
+           # checked first?
+           if (not $pres->{'checks-for-wmmenuconfig'} and $pkg ne 'menu') {
+               tag "maintainer-script-does-not-check-for-existence-of-wm-menu-config", "$script:$." unless $no_check_wmmenu++;
+           }
+       }
+
+       # does the script set a link in /usr/doc?
+       # does the script remove a link in /usr/doc?
+       if ($interp eq 'sh') {
+           if (m,ln\s+(-\w+)?\s+\"?\.\./share/doc/\S+, ) {
+               $pres->{'sets-link'} = 1;
+           }
+           if (m,rm\s+(-\w+\s+)?\"?/usr/doc/\S+, ) {
+               $pres->{'removes-link'} = 1;
+           }
+       } elsif ($interp eq 'perl') {
+           if (m|symlink\s*\(?\s*[\"\']\.\./share/doc/\.+?[\"\']\s*,|) {
+               $pres->{'sets-link'} = 1;
+           } elsif (m,ln\s+(-\w+)?\s+\"?\.\./share/doc/\S+, ) {
+               $pres->{'sets-link'} = 1;
+           }
+       } else {
+           # just fall through for now
+       }
+
+       # does the script check whether install-docs exists?
+       if (s/-x\s+\S*install-docs//o or /(which|type)\s+install-docs/o
+           or s/command\s+.*?install-docs//o) {
+           # yes, it does.
+           $pres->{'checks-for-installdocs'} = 1;
+       }
+
+       # does the script call install-docs?
+       if (m/(?:^\s*|[;&|]\s*|(?:then|do)\s+)(?:\/usr\/sbin\/)?install-docs(?:\s|[;&|<>]|$)/) {
+           # yes, it does.  Does it remove or add a doc?
+           if (m/install-docs\s+(-r|--remove)\s/) {
+               $pres->{'calls-installdocs-r'} = 1;
+           } else {
+               $pres->{'calls-installdocs'} = 1;
+           }
+           # checked first?
+           if (not $pres->{'checks-for-installdocs'}) {
+               tag "maintainer-script-does-not-check-for-existence-of-installdocs", "$script" unless $no_check_installdocs++;
+           }
+       }
+    }
+    close IN;
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl
diff --git a/checks/menus.desc b/checks/menus.desc
new file mode 100644 (file)
index 0000000..9deb981
--- /dev/null
@@ -0,0 +1,353 @@
+Check-Script: menus
+Author: Christian Schwarz <schwarz@debian.org>
+Abbrev: men
+Type: binary
+Unpack-Level: 2
+Needs-Info: doc-base-files
+
+Tag: postinst-should-not-set-usr-doc-link
+Severity: normal
+Certainty: certain
+Info: The technical committee chose the symlink transition method to move
+ from FSSTND to FHS.  That transition has been completed with woody and thus
+ this link is no longer required.
+
+Tag: maintainer-script-does-not-check-for-existence-of-updatemenus
+Severity: important
+Certainty: possible
+Info: The maintainer script calls the <tt>update-menus</tt> command without
+ checking for existence first. (The <tt>menu</tt> package which provides the
+ command is not marked as "essential" package.)
+ .
+ For example, use the following code in your maintainer script:
+  if [ -x /usr/bin/update-menus ] ; then update-menus ; fi
+
+Tag: maintainer-script-does-not-check-for-existence-of-wm-menu-config
+Severity: important
+Certainty: possible
+Info: The maintainer script calls the <tt>wm-menu-config</tt> command without
+ checking for existence first. (The <tt>menu</tt> package which provides
+ the command is not marked as "essential" package.)
+ .
+ For example, use the following code in your maintainer script:
+  if [ -x /usr/sbin/wm-menu ]; then /usr/sbin/wm-menu-config; fi
+
+Tag: maintainer-script-does-not-check-for-existence-of-installdocs
+Severity: important
+Certainty: possible
+Info: The maintainer script calls the <tt>install-docs</tt> command without
+ checking for existence first. (The <tt>doc-base</tt> package which provides
+ the command is not marked as "essential" package.)
+ .
+ For example, use the following code in your maintainer script:
+  if [ -x /usr/sbin/install-docs ]; then
+    /usr/sbin/install-docs -i /usr/share/doc-base/&lt;your-package&gt;
+  fi
+
+Tag: maintainer-script-calls-deprecated-wm-menu-config
+Severity: normal
+Certainty: possible
+Info: The use of the wm-menu-config script is deprecated because its design
+ had some serious flaws.
+Ref: menu 5
+
+Tag: preinst-calls-updatemenus
+Severity: important
+Certainty: certain
+Info: The preinst script calls the <tt>update-menus</tt> command. Usually,
+ this command should be called from the <tt>postinst</tt> maintainer script.
+
+Tag: preinst-calls-installdocs
+Severity: important
+Certainty: certain
+Info: The preinst script calls the <tt>install-docs</tt> command. Usually,
+ this command should be called from the <tt>postinst</tt> maintainer script.
+
+Tag: prerm-calls-updatemenus
+Severity: important
+Certainty: certain
+Info: The prerm script calls the <tt>update-menus</tt> command. Usually,
+ this command should be called from the <tt>postrm</tt> maintainer script.
+
+Tag: postrm-calls-installdocs
+Severity: important
+Certainty: certain
+Info: The postrm script calls the <tt>install-docs</tt> command. Usually,
+ this command should be called from the <tt>prerm</tt> maintainer script.
+
+Tag: executable-menu-file
+Severity: normal
+Certainty: certain
+Info: Menu files should normally not be marked as executables. You only
+ need to do this if your package has to generate menu entries dynamically.
+
+Tag: menu-file-in-usr-lib
+Severity: normal
+Certainty: certain
+Info: As of menu, version 2.1.25, /usr/lib/menu as location for menu
+ files is deprecated (but still works perfectly). Menu files should
+ now be placed in /usr/share/menu instead. Only menu files that are
+ actually binary executables still need to go to /usr/lib/menu.
+Ref: menu 3.1
+
+Tag: executable-in-usr-share-docbase
+Severity: important
+Certainty: certain
+Info: Files in <tt>/usr/share/doc-base</tt> may not be marked as executables.
+
+Tag: postinst-does-not-call-updatemenus
+Severity: important
+Certainty: certain
+Info: Since the package installs a file in <tt>/etc/menu-methods</tt>,
+ <tt>/usr/share/menu</tt>, or <tt>/usr/lib/menu</tt>, the package should
+ probably call the <tt>update-menus</tt> command in it's <tt>postinst</tt>
+ script.
+ .
+ For example, use the following code in your maintainer script:
+  if [ -x /usr/bin/update-menus ] ; then update-menus ; fi
+Ref: menu 4.2
+
+Tag: postrm-does-not-call-updatemenus
+Severity: important
+Certainty: certain
+Info: Since the package installs a file in <tt>/etc/menu-methods</tt>,
+ <tt>/usr/share/menu</tt>, or <tt>/usr/lib/menu</tt>, the package should
+ probably call the <tt>update-menus</tt> command in it's <tt>postrm</tt>
+ script.
+ .
+ For example, use the following code in your maintainer script:
+  if [ -x /usr/bin/update-menus ] ; then update-menus ; fi
+Ref: menu 4.2
+
+Tag: postinst-has-useless-call-to-update-menus
+Severity: minor
+Certainty: certain
+Info: The <tt>postinst</tt> script calls the <tt>update-menus</tt> program
+ though no file is installed in <tt>/etc/menu-methods</tt>,
+ <tt>/usr/share/menu</tt>, or <tt>/usr/lib/menu</tt>.
+
+Tag: postrm-has-useless-call-to-update-menus
+Severity: minor
+Certainty: certain
+Info: The <tt>postrm</tt> script calls the <tt>update-menus</tt> program
+ though no file is installed in <tt>/etc/menu-methods</tt>,
+ <tt>/usr/share/menu</tt>, or <tt>/usr/lib/menu</tt>.
+
+Tag: postinst-has-useless-call-to-install-docs
+Severity: minor
+Certainty: certain
+Info: Explicitly calling <tt>install-docs</tt> in <tt>postinst</tt> is no
+ longer required since doc-base file processing is handled by triggers.
+ If the <tt>install-docs</tt> call was added by debhelper, rebuilding the
+ package with debhelper 7.2.3 or later will fix this problem.
+
+Tag: prerm-has-useless-call-to-install-docs
+Severity: minor
+Certainty: certain
+Info: Explicitly calling <tt>install-docs</tt> in <tt>prerm</tt> is no
+ longer required since doc-base file processing is handled by triggers.
+ If the <tt>install-docs</tt> call was added by debhelper, rebuilding the
+ package with debhelper 7.2.3 or later will fix this problem.
+
+Tag: bad-menu-file-name
+Severity: important
+Certainty: certain
+Info: The package installs a file <tt>/usr/lib/menu/menu</tt>, which is
+ already in use by the <tt>menu</tt> package itself.  The menu file should
+ be named after the package that installs it.
+
+Tag: doc-base-file-references-usr-doc
+Severity: normal
+Certainty: certain
+Info: Files in <tt>/usr/share/doc-base</tt> should only contain links to
+ files in the <tt>/usr/share/doc</tt> directory.
+
+Tag: doc-base-index-references-multiple-files
+Severity: important
+Certainty: certain
+Info: The Index field in a doc-base file should reference the single index
+ file for that document.  Any other files belonging to the same document
+ should be listed in the Files field.
+Ref: doc-base 2.3.2.2
+
+Tag: doc-base-file-references-missing-file
+Severity: important
+Certainty: certain
+Info: One of the files referenced in an Index or Files field in this
+ doc-base control file does not exist in the package.  The doc-base
+ control files should be installed by the package that provides the
+ documents they are registering.
+
+Tag: doc-base-file-unknown-format
+Severity: normal
+Certainty: certain
+Info: The Format field in this doc-base control file declares a format
+ that is not supported.  Recognized formats are "HTML", "Text", "PDF",
+ "PostScript", "Info", "DVI", and "DebianDoc-SGML" (case-insensitive).
+Ref: doc-base 2.3.2.2
+
+Tag: doc-base-file-no-format
+Severity: important
+Certainty: certain
+Info: A format section of this doc-base control file didn't specify a
+ format.  Each section after the first must specify a format.
+Ref: doc-base 2.3.2.2
+
+Tag: doc-base-file-no-format-section
+Severity: important
+Certainty: certain
+Info: This doc-base control file didn't specify any format
+ section.
+Ref: doc-base 2.3.2.2
+
+Tag: doc-base-file-no-index
+Severity: important
+Certainty: certain
+Info: Format sections in doc-base control files for HTML or Info documents
+ must contain an Index field specifying the starting document for the
+ documentation.  Even if the documentation is a single file, this field
+ must be present.
+Ref: doc-base 2.3.2.2
+
+Tag: doc-base-document-field-ends-in-whitespace
+Severity: important
+Certainty: certain
+Info: The Document field in a doc-base file should not end in whitespace.
+ doc-base (at least as of 0.8.5) cannot cope with such fields and
+ debhelper 5.0.57 or earlier may create files ending in whitespace when
+ installing such files.
+
+Tag: doc-base-document-field-not-in-first-line
+Severity: important
+Certainty: certain
+Info: The Document field in doc-base control file must be located at
+ first line of the file.  While unregistering documents, doc-base 0.8
+ and later parses only the first line of the control file for performance
+ reasons.
+Ref: doc-base 2.3.2.1
+
+Tag: doc-base-file-unknown-field
+Severity: important
+Certainty: certain
+Info: The doc-base control file contains field which is either unknown
+ or not valid for the section where was found.  Possible reasons for this
+ error are: a typo in field name, missing empty line between control file
+ sections, or an extra empty line separating sections.
+Ref: doc-base 2.3.2.1, doc-base 2.3.2.2
+
+Tag: doc-base-file-duplicated-field
+Severity: important
+Certainty: certain
+Info: The doc-base control file contains a duplicated field.
+
+Tag: doc-base-file-duplicated-format
+Severity: important
+Certainty: certain
+Info: The doc-base control file contains a duplicated format.  Doc-base
+ files must not register different documents in one control file.
+Ref: doc-base 2.3.2.2
+
+Tag: doc-base-file-lacks-required-field
+Severity: important
+Certainty: certain
+Info: The doc-base control file does not contain a required field for the
+ appropriate section.
+Ref: doc-base 2.3.2.1, doc-base 2.3.2.2
+
+Tag: doc-base-invalid-document-field
+Severity: important
+Certainty: certain
+Info: The Document field should consists only of letters (a-z), digits
+ (0-9), plus (+) or minus (-) signs, and dots (.).  In particular,
+ uppercase letters are not allowed.
+Ref: doc-base 2.2
+
+Tag: doc-base-abstract-field-is-template
+Severity: normal
+Certainty: possible
+Info: The Abstract field of doc-base contains a "manage online manuals"
+ phrase, which was copied verbatim from an example control file found in
+ section 2.3.1 of the Debian doc-base Manual.
+
+Tag: doc-base-abstract-might-contain-extra-leading-whitespaces
+Severity: normal
+Certainty: possible
+Info: Continuation lines of the Abstract field of doc-base control file
+ should start with only one space unless they are meant to be displayed
+ verbatim by frontends.
+Ref: doc-base 2.3.2
+
+Tag: doc-base-abstract-field-separator-extra-whitespaces
+Severity: normal
+Certainty: certain
+Info: Unnecessary spaces were found in the paragraph separator line of the
+ doc-base's Abstract field.  The separator line should consist of a single
+ space followed by a single dot.
+Ref: doc-base 2.3.2
+
+Tag: spelling-error-in-doc-base-title-field
+Severity: normal
+Certainty: possible
+Info: Maemian found a spelling or capitalization error in the Title field
+ of this doc-base control file.  Maemian has a list of common misspellings
+ that it looks for.  It does not have a dictionary like a spelling checker
+ does.
+
+Tag: spelling-error-in-doc-base-abstract-field
+Severity: normal
+Certainty: possible
+Info: Maemian found a spelling or capitalization error in the Abstract
+ field of this doc-base control file.  Maemian has a list of common
+ misspellings that looks for.  It does not have a dictionary like a
+ spelling checker does.
+
+Tag: doc-base-file-syntax-error
+Severity: important
+Certainty: certain
+Info: Maemian found a syntax error in the doc-base control file.
+Ref: doc-base 2.3.2.2
+
+Tag: doc-base-file-separator-extra-whitespaces
+Severity: normal
+Certainty: certain
+Info: Unnecessary spaces were found in the doc-base file sections'
+ separator.  The section separator is an empty line and should not contain
+ any whitespace.
+Ref: doc-base 2.3.2
+
+Tag: doc-base-file-uses-obsolete-national-encoding
+Severity: important
+Certainty: certain
+Info: doc-base files must be valid UTF-8, an encoding of the Unicode
+ character set.
+ .
+ There are many ways to convert a doc-base file from an obsolete encoding
+ like ISO-8859-1.  You may, for example, use "iconv" like:
+ .
+  $ iconv -f ISO-8859-1 -t UTF-8 doc-base &gt; doc-base.new
+  $ mv doc-base.new doc-base
+Ref: doc-base 2.3.2
+
+Tag: doc-base-uses-applications-section
+Severity: normal
+Certainty: certain
+Info: The section indicated in this doc-base control file has a top-level
+ section of Apps or Applications. This section is only used in menu, not
+ in doc-base. Simply removing the top-level section will lead to a valid
+ doc-base section.
+Ref: doc-base 2.3.3
+
+Tag: doc-base-unknown-section
+Severity: normal
+Certainty: certain
+Info: The section indicated in this doc-base control file is not one of
+ the standard doc-base sections.  The doc-base sections are based on the
+ menu sections but are not exactly the same.
+Ref: doc-base 2.3.3
+
+Tag: menu-method-should-include-menu-h
+Severity: important
+Certainty: certain
+Info: A menu-method file must include the menu.h configuration file
+ (using "!include menu.h").
+Ref: menu 5
diff --git a/checks/nmu b/checks/nmu
new file mode 100644 (file)
index 0000000..d1b47ff
--- /dev/null
@@ -0,0 +1,144 @@
+# nmu -- lintian check script -*- perl -*-
+
+# Copyright (C) 2004 Jeroen van Wolffelaar
+#
+# 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 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.
+
+package Maemian::nmu;
+use strict;
+
+use Maemian::Data;
+use Tags;
+use Util;
+
+# Used to match Ubuntu distribution names in target distributions.
+our $UBUNTU_REGEX;
+{
+       my $dists = Maemian::Data->new('changelog-file/ubuntu-dists');
+       my $string = join ('|', 'ubuntu', $dists->all);
+       $UBUNTU_REGEX = qr/$string/o;
+}
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+my $changelog_mentions_nmu = 0;
+my $changelog_mentions_local = 0;
+my $changelog_mentions_qa = 0;
+
+# This isn't really an NMU check, but right now no other check looks at
+# debian/changelog in source packages.  Catch a debian/changelog file that's a
+# symlink.  If it was a symlink to a file we didn't unpack, bail rather than
+# abort.
+if (-l "debfiles/changelog") {
+    tag "changelog-is-symlink", "";
+    return 0 unless -f "debfiles/changelog";
+}
+
+# Get some data from the changelog file.
+my ($entry) = $info->changelog->data;
+my $distribution = $entry->Distribution;
+my $uploader = canonicalize($entry->Maintainer);
+my $changes = $entry->Changes;
+$changes =~ s/^(\s*\n)+//;
+my $firstline = (split('\n', $changes))[0];
+
+# Check the first line for QA and NMU mentions.
+if ($firstline) {
+       local $_ = $firstline;
+       if (/\bnmu\b/i or /non-maintainer upload/i) {
+               unless (/(ackno|\back\b|confir|incorporat).*(\bnmu\b|non-maintainer)/i) {
+                       $changelog_mentions_nmu = 1;
+               }
+       }
+       $changelog_mentions_local = 1 if /\blocal\s+package\b/i;
+       $changelog_mentions_qa = 1 if /orphan/i or /qa (?:group )?upload/i;
+}
+
+my $version = $info->field("version");
+my $maintainer = canonicalize($info->field("maintainer"));
+my $uploaders = $info->field("uploaders");
+
+my $version_nmuness = 0;
+my $version_local = 0;
+if ($version =~ /-[^.-]+(\.[^.-]+)?(\.[^.-]+)?$/) {
+       $version_nmuness = 1 if defined $1;
+       $version_nmuness = 2 if defined $2;
+}
+if ($version =~ /\+nmu\d+$/) {
+       $version_nmuness = 1;
+}
+if ($version =~ /\+b\d+$/) {
+       $version_nmuness = 2;
+}
+if ($version =~ /local/i) {
+       $version_local = 1;
+}
+
+my $upload_is_nmu = $uploader ne $maintainer;
+if (defined $uploaders) {
+       my @uploaders = map { canonicalize($_) } split /,/, $uploaders;
+       $upload_is_nmu = 0 if grep /^\s*\Q$uploader\E\s*$/, @uploaders;
+}
+
+# No such thing as NMUs in Ubuntu-land.
+if ($version =~ /$UBUNTU_REGEX/ or $distribution =~ /$UBUNTU_REGEX/) {
+       $upload_is_nmu = 0;
+       $version_nmuness = 0;
+}
+
+if ($maintainer =~ /packages\@qa.debian.org/) {
+       tag "orphaned-package-should-not-have-uploaders", ""
+               if defined $uploaders;
+       tag "qa-upload-has-incorrect-version-number", "$version"
+               if $version_nmuness == 1;
+       tag "changelog-should-mention-qa", ""
+               if !$changelog_mentions_qa;
+} else {
+       # Local packages may be either NMUs or not.
+       unless ($changelog_mentions_local || $version_local) {
+               tag "changelog-should-mention-nmu", ""
+                   if !$changelog_mentions_nmu && $upload_is_nmu;
+               tag "source-nmu-has-incorrect-version-number", "$version"
+                   if $upload_is_nmu && $version_nmuness != 1;
+       }
+       tag "changelog-should-not-mention-nmu", ""
+               if $changelog_mentions_nmu && !$upload_is_nmu;
+       tag "maintainer-upload-has-incorrect-version-number", "$version"
+               if !$upload_is_nmu && $version_nmuness;
+}
+
+}
+
+# Canonicalize a maintainer address with respect to case.  E-mail addresses
+# are case-insensitive in the right-hand side.
+sub canonicalize {
+       my ($maintainer) = @_;
+       $maintainer =~ s/(<[^>\@]+\@)([\w.-]+)>/$1 . lc ($2)/e;
+       return $maintainer;
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 8
+# End:
+# vim: ts=4 sw=4
diff --git a/checks/nmu.desc b/checks/nmu.desc
new file mode 100644 (file)
index 0000000..7f96052
--- /dev/null
@@ -0,0 +1,90 @@
+Check-Script: nmu
+Author: Jeroen van Wolffelaar <jeroen@wolffelaar.nl>
+Abbrev: nmu
+Type: source
+Unpack-Level: 1
+Needs-Info: debfiles
+Info: This script checks if a source package is consistent about its NMU-ness.
+
+Tag: orphaned-package-should-not-have-uploaders
+Severity: important
+Certainty: certain
+Info: Packages with their maintainer set to packages@qa.debian.org, i.e.
+ orphaned packages, should not have uploaders. Adopt the package properly if
+ you want to resume its maintenance.
+
+Tag: qa-upload-has-incorrect-version-number
+Severity: normal
+Certainty: certain
+Info: A QA upload (uploading an orphaned package without adopting it) is
+ always a maintainer upload: it should not get a NMU revision number.
+
+Tag: source-nmu-has-incorrect-version-number
+Severity: normal
+Certainty: certain
+Info: A source NMU should have a Debian revision of "-x.x" (or "+nmuX" for a
+ native package). This is to prevent stealing version numbers from the
+ maintainer.
+ .
+ Maybe you didn't intend this upload to be a NMU, in that case, please
+ doublecheck that the most recent entry in the changelog is byte-for-byte
+ identical to the maintainer or one of the uploaders.  If this is a local
+ package (not intended for Debian), you can suppress this warning by
+ putting "local" in the version number or "local package" on the first
+ line of the changelog entry.
+Ref: devref 5.11.2
+
+Tag: maintainer-upload-has-incorrect-version-number
+Severity: normal
+Certainty: certain
+Info: A maintainer upload should have a Debian revision without dots.
+ Revisions with dots are reserved for Non-Maintainer Uploads (NMU's), if you
+ do a maintainer-upload with dots, a potential NMU'er has problems choosing a
+ correct version number.
+
+Tag: changelog-should-mention-qa
+Severity: normal
+Certainty: certain
+Info: If this upload is to orphan this package, please mention this fact on
+ the first line of the changelog. If this is a QA upload, please mention "QA
+ (group) upload" there.
+
+Tag: changelog-should-mention-nmu
+Severity: normal
+Certainty: certain
+Info: When you NMU a package, that fact should be mentioned on the first line
+ in the changelog entry.  Use the words "NMU" or "Non-maintainer upload"
+ (case insensitive).
+ .
+ Maybe you didn't intend this upload to be a NMU, in that case, please
+ doublecheck that the most recent entry in the changelog is byte-for-byte
+ identical to the maintainer or one of the uploaders.  If this is a local
+ package (not intended for Debian), you can suppress this warning by
+ putting "local" in the version number or "local package" on the first
+ line of the changelog entry.
+Ref: devref 5.11.3
+
+Tag: changelog-should-not-mention-nmu
+Severity: normal
+Certainty: possible
+Info: The first line of the changelog entry for this package appears to
+ indicate it is a non-maintainer upload (by including either that string
+ or the string "NMU" and not saying that it's an acknowledgement), but the
+ changelog indicates the person making this release is one of the
+ maintainers.
+ .
+ If this was intended to be an NMU, do not add yourself as a maintainer or
+ uploader.  Otherwise, please rephrase your changelog entry to not cause
+ confusion.
+
+Tag: changelog-is-symlink
+Severity: normal
+Certainty: certain
+Info: The file <tt>debian/changelog</tt> is a symlink instead of a regular
+ file. This is unnecessary and makes package checking and manipulation
+ more difficult. If the changelog should be available in the source
+ package under multiple names, make <tt>debian/changelog</tt> the real
+ file and the other names symlinks to it.
+ .
+ This problem may have prevented lintian from performing other checks,
+ leading to undetected changelog errors.
diff --git a/checks/patch-systems b/checks/patch-systems
new file mode 100644 (file)
index 0000000..d14d7a0
--- /dev/null
@@ -0,0 +1,212 @@
+# patch-systems -- lintian check script -*- perl -*-
+#
+# Copyright (C) 2007 Marc Brockschmidt
+# Copyright (C) 2008 Raphael Hertzog
+#
+# 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 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.
+
+package Maemian::patch_systems;
+use strict;
+
+use Tags;
+use Util;
+
+sub run {
+       my ($pkg, $type, $info) = @_;
+
+       unless (-d "fields") {
+       fail("directory in lintian laboratory for $type package $pkg missing: fields");
+       }
+
+       #Some (cruft) checks are valid for every patch system, so we need to record that:
+       my $uses_patch_system = 0;
+
+       #Get build deps so we can decide which build system the maintainer
+       #meant to use:
+       my $build_deps = $info->relation('build-depends-all');
+       # Get source package format
+       my $format = "";
+       if (defined $info->field('format')) {
+               $format = $info->field('format');
+       }
+       my $quilt_format = ($format =~ /3\.\d+ \(quilt\)/) ? 1 : 0;
+
+       #----- dpatch
+       if ($build_deps->implies("dpatch")) {
+               $uses_patch_system++;
+               #check for a debian/patches file:
+               if (! -r "debfiles/patches/00list") {
+                       tag "dpatch-build-dep-but-no-patch-list", $pkg;
+               } else {
+                       my $list_uses_cpp = 0;
+                       if (open(OPTS, '<', "debfiles/patches/00options")) {
+                               while(<OPTS>) {
+                                       if (/DPATCH_OPTION_CPP=1/) {
+                                               $list_uses_cpp = 1;
+                                               last;
+                                       }
+                               }
+                               close(OPTS);
+                       }
+                       foreach my $listfile (glob("debfiles/patches/00list*")) {
+                               my @patches;
+                               if (open(IN, '<', "$listfile")) {
+                                       while(<IN>) {
+                                               chomp;
+                                               next if (/^\#/); #ignore comments or CPP directive
+                                               s%//.*%% if $list_uses_cpp; # remove C++ style comments
+                                               if ($list_uses_cpp && m%/\*%) {
+                                                       # remove C style comments
+                                                       $_ .= <IN> while($_ !~ m%\*/%);
+                                                       s%/\*[^*]*\*/%%g;
+                                               }
+                                               next if (/^\s*$/); #ignore blank lines
+                                               push @patches, split(' ', $_);
+                                       }
+                                       close(IN);
+                               }
+
+                               # Check each patch.
+                               foreach my $patch_file (@patches) {
+                                       $patch_file .= ".dpatch" if -e "debfiles/patches/$patch_file.dpatch"
+                                               and not -e "debfiles/patches/$patch_file";
+                                       if (! -r "debfiles/patches/$patch_file") {
+                                               tag "dpatch-index-references-non-existent-patch", $patch_file;
+                                               next;
+                                       }
+                                       if (open(PATCH_FILE, '<', "debfiles/patches/$patch_file")) {
+                                               my $has_comment = 0;
+                                               while (<PATCH_FILE>) {
+                                                       #stop if something looking like a patch starts:
+                                                       last if /^---/;
+                                                       #note comment if we find a proper one
+                                                       $has_comment = 1 if (/^\#+\s*DP:\s*(\S.*)$/ && $1 !~ /^no description\.?$/i)
+                                               }
+                                               close(PATCH_FILE);
+                                               unless ($has_comment) {
+                                                       tag "dpatch-missing-description", $patch_file;
+                                               }
+                                       }
+                                       check_patch($patch_file);
+                               }
+                       }
+               }
+       }
+
+       #----- quilt
+       if ($build_deps->implies("quilt") or $quilt_format) {
+               $uses_patch_system++;
+               #check for a debian/patches file:
+               if (! -r "debfiles/patches/series") {
+                       tag "quilt-build-dep-but-no-series-file", $pkg unless $quilt_format;
+               } else {
+                       if (open(IN, '<', "debfiles/patches/series")) {
+                               my @patches;
+                               my @badopts;
+                               while(<IN>) {
+                                       chomp; s/^\s+//; s/\s+$//; # Strip leading/trailing spaces
+                                       s/(^|\s+)#.*$//; # Strip comment
+                                       next unless $_;
+                                       if (/^(\S+)\s+(\S.*)$/) {
+                                               $_ = $1;
+                                               if ($2 ne '-p1') {
+                                                       push @badopts, $_;
+                                               }
+                                       }
+                                       push @patches, $_;
+                               }
+                               close(IN);
+                               if (scalar(@badopts)) {
+                                       tag "quilt-patch-with-non-standard-options", @badopts;
+                               }
+
+                               # Check each patch.
+                               foreach my $patch_file (@patches) {
+                                       if (! -r "debfiles/patches/$patch_file") {
+                                               tag "quilt-series-references-non-existent-patch", $patch_file;
+                                               next;
+                                       }
+                                       if (open(PATCH_FILE, '<', "debfiles/patches/$patch_file")) {
+                                               my $has_description = 0;
+                                               while (<PATCH_FILE>) {
+                                                       # stop if something looking like a patch starts:
+                                                       last if /^---/;
+                                                       next if /^\s*$/;
+                                                       # Skip common "lead-in" lines
+                                                       $has_description = 1 unless (/^(Index: |=+$|diff .+)/);
+                                               }
+                                               close(PATCH_FILE);
+                                               unless ($has_description) {
+                                                       tag "quilt-patch-missing-description", $patch_file;
+                                               }
+                                       }
+                                       check_patch($patch_file);
+                               }
+                       }
+               }
+       } else {
+               if (-r "debfiles/patches/series" and
+                   -f "debfiles/patches/series") {
+                       # 3.0 (quilt) sources don't need quilt as dpkg-source will do the work
+                       tag "quilt-series-but-no-build-dep" unless $quilt_format;
+               }
+       }
+
+
+       #----- general cruft checking:
+       if ($uses_patch_system > 1) {
+               tag "more-than-one-patch-system";
+       }
+       my @direct;
+       open(STAT, '<', "diffstat") or fail("cannot open diffstat file: $!");
+       while (<STAT>) {
+               my ($file) = (m,^\s+(.*?)\s+\|,)
+                    or fail("syntax error in diffstat file: $_");
+               push (@direct, $file) if ($file !~ m,^debian/,);
+       }
+       close (STAT) or fail("error reading diffstat file: $!");
+       if (@direct) {
+               my $files = (@direct > 1) ? "$direct[0] and $#direct more" : $direct[0];
+
+               tag "patch-system-but-direct-changes-in-diff", $files
+                       if ($uses_patch_system);
+               tag "direct-changes-in-diff-but-no-patch-system", $files
+                       if (not $uses_patch_system);
+       }
+}
+
+# Checks on patches common to all build systems
+sub check_patch($) {
+       my $patch_file = shift;
+       open(DIFFSTAT, "-|", "diffstat -p0 -l debfiles/patches/$patch_file")
+         or fail("can't fork diffstat");
+       while (<DIFFSTAT>) {
+               chomp;
+               if (m|^(\./)?debian/| or m|^(\./)?[^/]+/debian/|) {
+                       tag "patch-modifying-debian-files", $patch_file, $_;
+               }
+       }
+       close(DIFFSTAT) or fail("cannot close pipe to diffstat on $patch_file: $!");
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 8
+# End:
+# vim: syntax=perl sw=4 ts=4 noet shiftround
diff --git a/checks/patch-systems.desc b/checks/patch-systems.desc
new file mode 100644 (file)
index 0000000..336cbd4
--- /dev/null
@@ -0,0 +1,139 @@
+Check-Script: patch-systems
+Author: Marc Brockschmidt <he@debian.org>
+Abbrev: pat
+Type: source
+Unpack-Level: 1
+Info: This script checks for various possible problems when using patch systems
+Needs-Info: debfiles, diffstat
+
+Tag: dpatch-build-dep-but-no-patch-list
+Severity: normal
+Certainty: certain
+Info: Using dpatch requires you to explicitly list all patches you want
+ to apply in debian/patches/00list. This package build-depends on dpatch,
+ but does not provide a patch list.  You should either remove the dpatch
+ build dependency or add a patch list.
+ .
+ Note that an empty file cannot be represented in the Debian diff, so an
+ empty patch list will disappear in the source package.  If you intended
+ for the series file to be empty, add a comment line.
+
+Tag: dpatch-index-references-non-existent-patch
+Severity: important
+Certainty: certain
+Info: In the 00list file listing all your dpatches, you referenced a file
+ that does not exist. This will lead to a fatal error when calling dpatch.
+
+Tag: dpatch-missing-description
+Severity: wishlist
+Certainty: certain
+Info: dpatch files should carry a description of the included patch.
+ Description lines start with "## DP:".
+ .
+ As well as a description of the purpose and function of the patch, the
+ description should ideally contain author information, a URL for the bug
+ report (if any), Debian or upstream bugs fixed by it, upstream status,
+ the Debian version and date the patch was first included, and any other
+ information that would be useful if someone were investigating the
+ patch and underlying problem.
+
+Tag: quilt-build-dep-but-no-series-file
+Severity: normal
+Certainty: certain
+Info: Using quilt requires you to explicitly list all patches you want
+ to apply in debian/patches/series.  This package build-depends on quilt,
+ but does not provide a patch list.  You should either remove the quilt
+ build dependency or add a series file.
+ .
+ Note that an empty file cannot be represented in the Debian diff, so an
+ empty series file will disappear in the source package.  If you intended
+ for the series file to be empty, add a comment line.
+
+Tag: quilt-series-but-no-build-dep
+Severity: normal
+Certainty: possible
+Info: The package contains a debian/patches/series file usually used by
+ quilt to apply patches at build time, but quilt is not listed in the
+ build dependencies.
+ .
+ You should either remove the series file if it's effectively not useful
+ or add quilt to the build-dependencies if quilt is used during the build
+ process.
+ .
+ If you don't need quilt during build but only during maintenance work,
+ then you can override this warning.
+
+Tag: quilt-patch-with-non-standard-options
+Severity: normal
+Certainty: certain
+Info: The quilt series file contains non-standard options to apply some of
+ the listed patches. Quilt uses '-p1' by default if nothing is specified
+ after the name of the patch and the current series file specify something
+ else for some of the patches listed.
+ .
+ For compatibility with the source "3.0 (quilt)" source package format,
+ you should avoid using any option at all and make sure that your patches
+ apply with "-p1". This can be done by refreshing all patches like this:
+ quilt pop -a; while quilt push; do quilt refresh -pab; done
+
+Tag: quilt-series-references-non-existent-patch
+Severity: important
+Certainty: certain
+Info: In the series file listing all your quilt patches, you referenced a
+ file that does not exist. This will lead to a fatal error when calling quilt.
+
+Tag: quilt-patch-missing-description
+Severity: wishlist
+Certainty: certain
+Info: quilt patch files should start with a description of patch.  All
+ lines before the start of the patch itself are considered part of the
+ description.  You can edit the description with <tt>quilt header -e</tt>
+ when the patch is at the top of the stack.
+ .
+ As well as a description of the purpose and function of the patch, the
+ description should ideally contain author information, a URL for the bug
+ report (if any), Debian or upstream bugs fixed by it, upstream status,
+ the Debian version and date the patch was first included, and any other
+ information that would be useful if someone were investigating the
+ patch and underlying problem.
+
+Tag: patch-modifying-debian-files
+Severity: important
+Certainty: certain
+Info: A patch stored in debian/patches/ should never modify files
+ in the debian directory (even when it's only creating new files) because
+ the debian directory is always directly provided by the Debian packaging.
+ And you shouldn't have to modify what you choose to provide in the first
+ place.
+ .
+ If the patch provides a new file (say a manual page), place that file
+ in the upstream directory hierarchy as if it was ready to be submitted.
+
+Tag: patch-system-but-direct-changes-in-diff
+Severity: minor
+Certainty: certain
+Info: The package uses a patch system, but the Debian diff.gz contains
+ changes to files or creation of additional files outside of the
+ <tt>debian</tt> directory.  This often indicates accidental changes that
+ weren't meant to be in the package or changes that were supposed to be
+ separated out into a patch.  The package will also more easily support
+ possible future source package formats if all changes outside the
+ <tt>debian</tt> directory are stored as patches.
+
+Tag: more-than-one-patch-system
+Severity: minor
+Certainty: certain
+Info: The build-dependencies list more than one patch system and it's
+ unlikely that you need both at the same time.
+ .
+ Currently lintian knows only dpatch and quilt.
+
+Tag: direct-changes-in-diff-but-no-patch-system
+Severity: pedantic
+Certainty: certain
+Info: The Debian diff.gz contains changes to files or creation of additional
+ files outside the <tt>debian</tt> directory.  Keeping the changes as separate
+ patches under the control of a patch system allows for more fine grained
+ control over them.  The package will also more easily support  possible
+ future source package formats if all changes outside the <tt>debian</tt>
+ directory are stored as patches.
diff --git a/checks/po-debconf b/checks/po-debconf
new file mode 100644 (file)
index 0000000..56c80fb
--- /dev/null
@@ -0,0 +1,177 @@
+# po-debconf -- lintian check script -*- perl -*-
+
+# Copyright (C) 2002-2004 by Denis Barbier <barbier@linuxfr.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 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.
+
+package Maemian::po_debconf;
+use strict;
+use Tags;
+use Util;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+
+my $full_translation = 0;
+
+# First, check wether this package seems to use debconf but not po-debconf.
+# Read the templates file and look at the template names it provides, since
+# some shared templates aren't translated.
+opendir(DEB, 'debfiles')
+       or fail("Can't open debfiles directory.");
+my $has_template = my $has_depends = my $has_config = 0;
+my @lang_templates;
+for my $file (readdir(DEB)) {
+       next if -d "debfiles/$file";
+       if ($file =~ m/^(.+\.)?templates(\..+)?$/) {
+               if ($file =~ m/templates\.\w\w(_\w\w)?$/) {
+                       push (@lang_templates, $file);
+               } else {
+                       open(PO, '<', "debfiles/$file")
+                               or fail("Can't open debfiles/$file file.");
+                       while (<PO>) {
+                               tag "translated-default-field", "$file: $."
+                                       if (m/^_Default(Choice)?: [^\[]*$/);
+                               if (/^Template: (\S+)/i) {
+                                       my $template = $1;
+                                       next if $template =~ m,^shared/packages-(wordlist|ispell)$,;
+                                       next if $template =~ m,/languages$,;
+                                       $has_template = 1;
+                               }
+                       }
+                       close PO;
+               }
+       }
+}
+closedir(DEB);
+
+#TODO: check whether all templates are named in TEMPLATES.pot
+if ( $has_template ) {
+    if ( ! -d "debfiles/po" ) {
+       tag "not-using-po-debconf", "";
+       return 0;
+    }
+} else {
+    return 0;
+}
+
+# If we got here, we're using po-debconf, so there shouldn't be any stray
+# language templates left over from debconf-mergetemplate.
+for (@lang_templates) {
+    tag "stray-translated-debconf-templates", $_ unless /templates\.in$/;
+}
+
+# yada builds its template and po/POTFILES.in dynamically at build time, so
+# excuse yada from some of these checks (including the out of date templates
+# check).
+my $missing_files = 0;
+my $yada = 0;
+if (open (RULES, '<', 'debfiles/rules')) {
+    local $_;
+    while (<RULES>) {
+       if (m%^\t\s*(?:perl debian/)?yada\s%) {
+           $yada = 1;
+           $missing_files = 1;
+       }
+    }
+}
+
+if (!$yada && -f "debfiles/po/POTFILES.in") {
+       open(POTFILES, '<', "debfiles/po/POTFILES.in")
+               or fail("Can't open debfiles/po/POTFILES.in.");
+       while (<POTFILES>) {
+               chomp;
+               s/.*\]\s*//;
+               #  Cannot check files which are not under debian/
+               next if m,^\.\./, or $_ eq '';
+               unless (-f "debfiles/$_") {
+                       tag "missing-file-from-potfiles-in", "$_";
+                       $missing_files = 1;
+               } 
+       }
+       close(POTFILES);
+} elsif (!$yada) {
+       tag "missing-potfiles-in", "";
+       $missing_files = 1;
+}
+if (! -f "debfiles/po/templates.pot") {
+       tag "missing-templates-pot", "";
+       $missing_files = 1;
+}
+
+if (-x "/usr/bin/msgcmp" && -x "/usr/share/intltool-debian/intltool-update" ) {
+       if ($missing_files == 0) {
+               $ENV{"INTLTOOL_EXTRACT"} ||= "/usr/share/intltool-debian/intltool-extract";
+               system_env("cd debfiles/po && /usr/share/intltool-debian/intltool-update --gettext-package=test --pot");
+               system_env("/usr/bin/msgcmp --use-untranslated debfiles/po/test.pot debfiles/po/templates.pot >/dev/null 2>&1"
+                           . "&& /usr/bin/msgcmp --use-untranslated debfiles/po/templates.pot debfiles/po/test.pot >/dev/null 2>&1") == 0
+                       or tag "newer-debconf-templates";
+       }
+} else {
+       fail("either msgcmp or intltool-update not found");
+}
+
+if (! -x "/usr/bin/msgfmt" ) {
+       fail("msgfmt not found");
+}
+opendir(DEBIAN, 'debfiles/po')
+        or fail("Can't open debfiles/po directory.");
+while (defined(my $file=readdir(DEBIAN))) {
+        next unless $file =~ m/\.po$/;
+        tag "misnamed-po-file", "debian/po/$file"
+                unless ($file =~ /^[a-z]{2,3}(_[A-Z]{2})?\.po$/);
+        local ($/) = "\n\n";
+        $_ = '';
+        open(PO, '<', "debfiles/po/$file")
+                or fail("Can't open debfiles/po/$file file.");
+        while (<PO>) {
+                last if m/^msgstr/m;
+        }
+        close(PO);
+       unless ($_) {
+               tag "invalid-po-file", "debian/po/$file";
+               next;
+       }
+        s/"\n"//g;
+        my $charset = '';
+        if (m/charset=(.*?)\\n/) {
+                $charset = ($1 eq 'CHARSET' ? '' : $1);
+        }
+        tag "unknown-encoding-in-po-file", "debian/po/$file"
+                unless length($charset);
+       system_env("msgfmt -o /dev/null debfiles/po/$file 2>/dev/null") == 0
+               or tag "invalid-po-file", "debian/po/$file";
+
+       my $stats = `LANG=C msgfmt -o /dev/null --statistics debfiles/po/$file 2>&1`;
+       if (!$full_translation && $stats =~ m/^\w+ \w+ \w+\.$/) {
+               $full_translation = 1;
+       }
+}
+
+tag "no-complete-debconf-translation", "" if !$full_translation;
+
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 8
+# End:
+# vim: syntax=perl
diff --git a/checks/po-debconf.desc b/checks/po-debconf.desc
new file mode 100644 (file)
index 0000000..bd8d41a
--- /dev/null
@@ -0,0 +1,115 @@
+Check-Script: po-debconf
+Author: Denis Barbier <barbier@linuxfr.org>
+Abbrev: pd
+Type: source
+Unpack-Level: 1
+Info: This looks for common mistakes in packages using po-debconf.
+Needs-Info: debfiles
+
+Tag: not-using-po-debconf
+Severity: serious
+Certainty: certain
+Info: This package seems to be using debconf templates, but it does not
+ use po-debconf to make translations possible (<tt>debian/po</tt> doesn't
+ exist).  Debian Policy requires that all packages using debconf use a
+ gettext-based translation system.  If this package uses one other than
+ po-debconf, please report this as a lintian bug along with how to detect
+ that system.
+Ref: 3.9.1
+
+Tag: stray-translated-debconf-templates
+Severity: normal
+Certainty: certain
+Info: This package contains a file named *templates.XX or
+ *templates.XX_XX.  This was the naming convention for the translated
+ templates merged using debconf-mergetemplate.  Since the package is using
+ po-debconf, these files should be replaced by language-specific files in
+ the <tt>debian/po</tt> directory and should no longer be needed.
+
+Tag: missing-potfiles-in
+Severity: normal
+Certainty: certain
+Info: The required file <tt>POTFILES.in</tt> is missing from
+ <tt>debian/po</tt>.
+Ref: po-debconf(7)
+
+Tag: missing-file-from-potfiles-in
+Severity: normal
+Certainty: certain
+Info: A file listed in <tt>debian/po/POTFILES.in</tt> could not be found
+ in the source package.
+Ref: po-debconf(7)
+
+Tag: missing-templates-pot
+Severity: normal
+Certainty: certain
+Info: The required file <tt>templates.pot</tt> is missing from
+ <tt>debian/po</tt>.
+Ref: po-debconf(7)
+
+Tag: unknown-encoding-in-po-file
+Severity: normal
+Certainty: certain
+Info: Encoding must be declared in PO files. Otherwise, charset
+ conversions cannot be performed.
+
+Tag: invalid-po-file
+Severity: normal
+Certainty: certain
+Info: Errors were found in the listed PO file that will cause its content
+ to be discarded. Run <tt>msgfmt</tt> on the file to see the error
+ messages.
+
+Tag: misnamed-po-file
+Severity: normal
+Certainty: possible
+Info: The name of this PO file doesn't appear to be a valid language
+ code.  Any files in <tt>debian/po</tt> ending in <tt>.po</tt> will be
+ processed as translations by po2debconf for the language code equal to
+ the file name without the trailing <tt>.po</tt>.  If the file name does
+ not correctly reflect the language of the translation, the translation
+ will not be accessible to users of that language.
+ .
+ If this file isn't actually a PO file, rename it to something that
+ doesn't end in <tt>.po</tt> or move it to another directory so that
+ translation merging programs will not be confused.
+
+Tag: newer-debconf-templates
+Severity: normal
+Certainty: possible
+Info: debconf-updatepo has not been run since the last change to your
+ debconf templates.
+ .
+ You should run debconf-updatepo whenever debconf templates files are
+ changed so that translators can be warned that their files are
+ outdated.
+ .
+ This can be ensured by running debconf-updatepo in the 'clean' target
+ of <tt>debian/rules</tt>. PO files will then always be up-to-date when
+ building the source package.
+
+Tag: translated-default-field
+Severity: normal
+Certainty: possible
+Info: You should not mark as translatable "Default:" or "DefaultChoice:"
+ fields, unless explicitly needed (e.g. default country, default language,
+ etc.).  If this Default field really should be translated, you should
+ explain translators how they should translate it by using brackets.  For
+ example:
+ .
+   _Default: English[ Default language name, but not translated]
+Ref: po-debconf(7)
+
+Tag: no-complete-debconf-translation
+Severity: wishlist
+Certainty: possible
+Info: Even though this package provides debconf translation support, there 
+ are no  translations or none of the translations are complete. This may 
+ mean that translators weren't properly warned about new strings.
+ .
+ Translators may be notified of changes using podebconf-report-po, for
+ example:
+ .
+  podebconf-report-po --call --withtranslators --deadline="+10 days" \
+  --languageteam
+Ref: devref 6.5.2.2
diff --git a/checks/rules b/checks/rules
new file mode 100644 (file)
index 0000000..08dd447
--- /dev/null
@@ -0,0 +1,183 @@
+# rules -- lintian check script -*- perl -*-
+
+# Copyright (C) 2006 Russ Allbery <rra@debian.org>
+# Copyright (C) 2005 René van Bevern <rvb@pro-linux.de>
+#
+# 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 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.
+
+package Maemian::rules;
+use strict;
+use Tags;
+use Util;
+
+# The following targets are required per Policy.
+my %required = map { $_ => 1 }
+    qw(build binary binary-arch binary-indep clean);
+
+# Rules about required debhelper command ordering.  Each command is put into a
+# class and the tag is issued if they're called in the wrong order for the
+# classes.  Unknown commands won't trigger this flag.
+my %debhelper_order =
+    (dh_makeshlibs => 1,
+     dh_shlibdeps  => 2,
+     dh_installdeb => 2,
+     dh_gencontrol => 2,
+     dh_builddeb   => 3);
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+# Policy could be read as allowing debian/rules to be a symlink to some other
+# file, and in a native Debian package it could be a symlink to a file that we
+# didn't unpack.  Warn if it's a symlink (dpkg-source does as well) and skip
+# all the tests if we then can't read it.
+if (-l "debfiles/rules") {
+    tag "debian-rules-is-symlink", "";
+    return 0 unless -f "debfiles/rules";
+}
+
+#get architecture field:
+unless (-d "fields") {
+    fail("directory in lintian laboratory for $type package $pkg missing: fields");
+}
+
+my $architecture = $info->field('architecture') || '';
+
+open(RULES, '<', 'debfiles/rules') or fail("Failed opening rules: $!");
+
+# Check for required #!/usr/bin/make -f opening line.  Allow -r or -e; a
+# strict reading of Policy doesn't allow either, but they seem harmless.
+my $start = <RULES>;
+tag "debian-rules-not-a-makefile", ""
+    unless $start =~ m%^\#!\s*/usr/bin/make\s+-[re]?f[re]?\s*$%;
+
+# Scan debian/rules.  We would really like to let make do this for us, but
+# unfortunately there doesn't seem to be a way to get make to syntax-check and
+# analyze a makefile without running at least $(shell) commands.
+#
+# We skip some of the rule analysis if debian/rules includes any other files,
+# since to chase all includes we'd have to have all of its build dependencies
+# installed.
+my $includes = 0;
+my %seen;
+local $_;
+my @current_targets;
+my %rules_per_target;
+my $debhelper_group;
+while (<RULES>) {
+    next if /^\s*\#/;
+    $includes = 1 if m/^ *[s-]?include\s+/;
+
+    # Check for DH_COMPAT settings outside of any rule, which are now
+    # deprecated.  It's a bit easier structurally to do this here than in
+    # debhelper.
+    if (/^\s*(export\s+)?DH_COMPAT\s*:?=/ && keys(%seen) == 0) {
+        tag "debian-rules-sets-DH_COMPAT", "line $.";
+    }
+
+    # Check for problems that can occur anywhere in debian/rules.
+    if (/\$[\(\{]PWD[\)\}]/) {
+        tag "debian-rules-uses-pwd", "line $.";
+    }
+    if (/^\t\s*-(?:\$[\(\{]MAKE[\}\)]|make)\s.*(?:dist)?clean/ ||
+       /^\t\s*(?:\$[\(\{]MAKE[\}\)]|make)\s(?:.*\s)?-\w*i.*(?:dist)?clean/) {
+        tag "debian-rules-ignores-make-clean-error", "line $.";
+    }
+    if (/$[\(\{]DEB_BUILD_OPTS[\)\}]/) {
+        tag "debian-rules-uses-DEB_BUILD_OPTS", "line $.";
+    }
+
+    # Listing a rule as a dependency of .PHONY is sufficient to make it
+    # present for the purposes of GNU make and therefore the Policy
+    # requirement.
+    if (/^(?:[^:]+\s)?\.PHONY(?:\s[^:]+)?:(.+)/) {
+        my @targets = split (' ', $1);
+        for (@targets) {
+            $seen{$_}++ if $required{$_};
+        }
+    }
+
+    if (/^([^\s:][^:]*):/) {
+       @current_targets = split (' ', $1);
+       for (@current_targets) {
+            if (m/%/) {
+                my $pattern = quotemeta $_;
+                $pattern =~ s/\\%/.*/g;
+                for my $target (keys %required) {
+                    $seen{$target}++ if $target =~ m/$pattern/;
+                }
+            } else {
+                $seen{$_}++ if $required{$_};
+            }
+       }
+        $debhelper_group = 0;
+    } elsif (/^define /) {
+        # We don't want to think the body of the define is part of the
+        # previous rule or we'll get false positives on tags like
+        # binary-arch-rules-but-pkg-is-arch-indep.  Treat a define as the
+        # end of the current rule, although that isn't very accurate either.
+        @current_targets = ();
+    } else {
+       # If we have non-empty, non-comment lines, store them for all current
+       # targets and check whether debhelper programs are called in a
+       # reasonable order.
+       if (m/^\s+[^\#]/) {
+           foreach my $target (@current_targets) {
+               $rules_per_target{$target} ||= [];
+               push @{$rules_per_target{$target}}, $_;
+           }
+            if (m/^\s+(dh_\S+)\b/ and $debhelper_order{$1}) {
+                my $command = $1;
+                my $group = $debhelper_order{$command};
+                if ($group < $debhelper_group) {
+                    tag "debian-rules-calls-debhelper-in-odd-order",
+                        $command, "(line $.)";
+                } else {
+                    $debhelper_group = $group;
+                }
+            }
+       }
+    }
+}
+close RULES;
+
+unless ($includes) {
+    # Make sure all the required rules were seen.
+    for my $target (sort keys %required) {
+        tag "debian-rules-missing-required-target", $target
+            unless $seen{$target};
+    }
+}
+
+# Make sure we have no content for binary-arch if we are arch-indep:
+$rules_per_target{'binary-arch'} ||= [];
+if ($architecture eq "all" && scalar @{$rules_per_target{'binary-arch'}}) {
+    my $nonempty = 0;
+    foreach (@{$rules_per_target{'binary-arch'}}) {
+        # dh binary-arch is actually a no-op if there is no
+        # Architecture: any package in the control file
+        unless (m/^\s*dh\s+(?:binary-arch|\$\@)/) {
+            $nonempty = 1;
+        }
+    }
+    tag "binary-arch-rules-but-pkg-is-arch-indep" if $nonempty;
+}
+}
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround
diff --git a/checks/rules.desc b/checks/rules.desc
new file mode 100644 (file)
index 0000000..6c36658
--- /dev/null
@@ -0,0 +1,103 @@
+Check-Script: rules
+Author: Russ Allbery <rra@debian.org>
+Type: source
+Unpack-Level: 1
+Needs-Info: debfiles
+Info: Check targets and actions in debian/rules.
+Abbrev: rul
+
+Tag: debian-rules-is-symlink
+Severity: normal
+Certainty: certain
+Info: The file <tt>debian/rules</tt> is a symlink instead of a regular
+ file. This is unnecessary and makes package checking and manipulation
+ more difficult. If the rules file should be available in the source
+ package under multiple names, make <tt>debian/rules</tt> the real
+ file and the other names symlinks to it.
+ .
+ This problem may have prevented lintian from performing other checks,
+ leading to undetected changelog errors.
+
+Tag: debian-rules-not-a-makefile
+Severity: serious
+Certainty: certain
+Ref: policy 4.9
+Info: The <tt>debian/rules</tt> file for this package does not appear to
+ be a makefile or does not start with the required line.
+ <tt>debian/rules</tt> must be a valid makefile and must have
+ "<tt>#!/usr/bin/make -f</tt>" as its first line.
+
+Tag: debian-rules-missing-required-target
+Severity: serious
+Certainty: certain
+Ref: policy 4.9
+Info: The <tt>debian/rules</tt> file for this package does not provide one
+ of the required targets.  All of build, binary, binary-arch,
+ binary-indep, and clean must be provided, even if they don't do anything
+ for this package.
+
+Tag: debian-rules-uses-pwd
+Severity: normal
+Certainty: certain
+Info: The <tt>debian/rules</tt> file for this package appears to use the
+ variable $(PWD) to refer to the current directory.  This variable is not
+ set by GNU make and therefore will have whatever value it has in the
+ environment, which may not be the actual current directory.  Some ways of
+ building Debian packages (such as through sudo) will clear the PWD
+ environment variable.
+ .
+ Instead of $(PWD), use $(CURDIR), which is set by GNU make, ignores the
+ environment, and is guaranteed to always be set.
+
+Tag: debian-rules-ignores-make-clean-error
+Severity: normal
+Certainty: certain
+Info: A rule in the <tt>debian/rules</tt> file for this package calls the
+ package's clean or distclean target with a line like:
+ .
+  -$(MAKE) distclean
+ or
+  $(MAKE) -i distclean
+ .
+ The leading "-" or the option -i tells make to ignore all errors.
+ Normally this is done for packages using Autoconf since Makefile may not
+ exist.  However, this line ignores all other error messages, not just
+ the missing Makefile error.  It's better to use:
+ .
+  [ ! -f Makefile ] || $(MAKE) distclean
+ .
+ so that other error messages from the clean or distclean rule will still
+ be caught (or just remove the "-" if the package uses a static makefile).
+
+Tag: debian-rules-uses-DEB_BUILD_OPTS
+Severity: normal
+Certainty: certain
+Info: The standard environment variable for build options is
+ DEB_BUILD_OPTIONS.  Usually, referring to DEB_BUILD_OPTS is a mistake and
+ DEB_BUILD_OPTIONS was intended instead.
+
+Tag: debian-rules-sets-DH_COMPAT
+Severity: normal
+Certainty: certain
+Ref: debhelper(7)
+Info: As of debhelper version 4, the DH_COMPAT environment variable is
+ only to be used for temporarily overriding <tt>debian/compat</tt>.  Any
+ line in <tt>debian/rules</tt> that sets it globally should be deleted and
+ a separate <tt>debian/compat</tt> file created if needed.
+
+Tag: binary-arch-rules-but-pkg-is-arch-indep
+Severity: normal
+Certainty: certain
+Info: It looks like you try to run code in the binary-arch target of 
+ <tt>debian/rules</tt>, even though your package is architecture-
+ independent.
+
+Tag: debian-rules-calls-debhelper-in-odd-order
+Severity: normal
+Certainty: certain
+Info: One of the targets in the <tt>debian/rules</tt> file for this
+ package calls debhelper programs in an odd order.  Normally,
+ dh_makeshlibs should be called before dh_shlibdeps or dh_installdeb,
+ dh_shlibdeps should be called before dh_gencontrol, and all should be
+ called before dh_builddeb.  Calling them in the wrong order may cause
+ incorrect or missing package files and metadata.
diff --git a/checks/scripts b/checks/scripts
new file mode 100644 (file)
index 0000000..0379d47
--- /dev/null
@@ -0,0 +1,1165 @@
+# scripts -- lintian check script -*- perl -*-
+#
+# This is probably the right file to add a check for the use of
+# set -e in bash and sh scripts.
+#
+# Copyright (C) 1998 Richard Braakman
+# Copyright (C) 2002 Josip Rodin
+#
+# 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 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.
+
+package Maemian::scripts;
+use strict;
+
+use lib "$ENV{'MAEMIAN_ROOT'}/checks/";
+use common_data;
+use Tags;
+use Util;
+
+use Maemian::Relation;
+
+# This is a map of all known interpreters.  The key is the interpreter name
+# (the binary invoked on the #! line).  The value is an anonymous array of one
+# or two elements.  The first, mandatory argument is the path on a Debian
+# system where that interpreter would be installed.  The second, optional
+# argument is the dependency that provides that interpreter.  If the second
+# argument isn't given, the package name is assumed to be the same as the
+# interpreter name.  (Saves some typing.)
+#
+# Some interpreters list empty dependencies (as opposed to undefined ones).
+# Those interpreters should not have any dependency for one reason or another
+# (usually because they're essential packages or aren't used in a normal way).
+#
+# Do not list versioned patterns here (such as pythonX.Y, rubyX.Y, etc.).  For
+# those, see %versioned_interpreters below.
+our %interpreters =
+    (ash           => [ '/bin' ],
+     awk           => [ '/usr/bin', '' ],
+     bash          => [ '/bin', '' ],
+     bltwish       => [ '/usr/bin', 'blt' ],
+     clisp         => [ '/usr/bin' ],
+     csh           => [ '/bin', 'tcsh | csh | c-shell' ],
+     dash          => [ '/bin' ],
+     expect        => [ '/usr/bin' ],
+     expectk       => [ '/usr/bin' ],
+     fish          => [ '/usr/bin' ],
+     gawk          => [ '/usr/bin' ],
+     gbr2          => [ '/usr/bin', 'gambas2-runtime' ],
+     gbx           => [ '/usr/bin', 'gambas-runtime' ],
+     gbx2          => [ '/usr/bin', 'gambas2-runtime' ],
+     gforth        => [ '/usr/bin' ],
+     gnuplot       => [ '/usr/bin' ],
+     gosh          => [ '/usr/bin', 'gauche' ],
+     icmake        => [ '/usr/bin', 'icmake' ],
+     'install-menu' => [ '/usr/bin', '' ],
+     jed           => [ '/usr/bin' ],
+     'jed-script'   => [ '/usr/bin', 'jed | xjed' ],
+     kaptain        => [ '/usr/bin' ],
+     ksh           => [ '/bin', 'ksh | mksh | pdksh | zsh' ],
+     lefty         => [ '/usr/bin', 'graphviz' ],
+     magicfilter    => [ '/usr/sbin' ],
+     make          => [ '/usr/bin', 'make | build-essential | dpkg-dev' ],
+     mawk          => [ '/usr/bin' ],
+     mksh          => [ '/bin' ],
+     nickle        => [ '/usr/bin' ],
+     ocamlrun      => [ '/usr/bin',
+                        'ocaml-base-nox | ocaml-base | ocaml-nox | ocaml' ],
+     pagsh         => [ '/usr/bin', 'openafs-client | heimdal-clients' ],
+     parrot        => [ '/usr/bin' ],
+     perl          => [ '/usr/bin', '' ],
+     procmail      => [ '/usr/bin' ],
+     python        => [ '/usr/bin', 'python | python-minimal' ],
+     pforth        => [ '/usr/bin' ],
+     rc                    => [ '/usr/bin' ],
+     regina        => [ '/usr/bin', 'regina-rexx' ],
+     rexx          => [ '/usr/bin', 'regina-rexx' ],
+     rrdcgi        => [ '/usr/bin', 'rrdtool' ],
+     ruby          => [ '/usr/bin' ],
+     runhugs       => [ '/usr/bin', 'hugs | hugs98' ],
+     sed           => [ '/bin', '' ],
+     sh                    => [ '/bin', '' ],
+     slsh          => [ '/usr/bin' ],
+     speedy        => [ '/usr/bin', 'speedy-cgi-perl' ],
+     tcsh          => [ '/usr/bin' ],
+     tixwish       => [ '/usr/bin', 'tix' ],
+     trs           => [ '/usr/bin', 'konwert' ],
+     xjed          => [ '/usr/bin', 'xjed' ],
+     yforth        => [ '/usr/bin', 'yforth' ],
+     yorick        => [ '/usr/bin' ],
+     zsh           => [ '/bin', 'zsh | zsh-beta' ],
+    );
+
+# The more complex case of interpreters that may have a version number.
+#
+# This is a hash from the base interpreter name to a list.  The base
+# interpreter name may appear by itself or followed by some combination of
+# dashes, digits, and periods.  The values are the directory in which the
+# interpreter is found, the dependency to add for a version-less interpreter,
+# a regular expression to match versioned interpreters and extract the version
+# number, the package dependency for a versioned interpreter, and the list of
+# known versions.
+#
+# An interpreter with a version must have a dependency on the specific package
+# formed by taking the fourth element of the list and replacing $1 with the
+# version number.  An interpreter without a version is rejected if the second
+# element is undef; otherwise, the package must satisfy a dependency on the
+# disjunction of the second argument (if non-empty) and all the packages
+# formed by taking the list of known versions (the fifth element and on) and
+# replacing $1 in the fourth argument with them.
+#
+# For example:
+#
+#    lua => [ '/usr/bin', 'lua', qr/^lua([\d.]+)$/, 'lua$1', qw(40 50 5.1) ]
+#
+# says that any lua interpreter must be in /usr/bin, a package using
+# /usr/bin/lua50 must depend on lua50, and a package using just /usr/bin/lua
+# must satisfy lua | lua40 | lusa50 | lua5.1.
+#
+# The list of known versions is the largest maintenance headache here, but
+# it's only used for the unversioned dependency handling, and then only when
+# someone uses the unversioned script but depends on a specific version for
+# some reason.  So it's not a huge problem if it's a little out of date.
+our %versioned_interpreters =
+    (guile   => [ '/usr/bin', 'guile',
+                 qr/^guile-([\d.]+)$/, 'guile-$1', qw(1.6 1.8)
+               ],
+     jruby   => [ '/usr/bin', 'jruby',
+                 qr/^jruby([\d.]+)$/, 'jruby$1', qw(1.0 1.1 1.2)
+               ],
+     lua     => [ '/usr/bin', 'lua',
+                 qr/^lua([\d.]+)$/, 'lua$1', qw(40 50 5.1)
+               ],
+     octave  => [ '/usr/bin', 'octave',
+                 qr/^octave([\d.]+)$/, 'octave$1', qw(2.1 3.0 3.1)
+               ],
+     php     => [ '/usr/bin', '',
+                 qr/^php(\d+)$/, 'php$1-cli', qw(5)
+               ],
+     pike    => [ '/usr/bin', '',
+                 qr/^pike([\d.]+)$/, 'pike$1 | pike$1-core', qw(7.6)
+               ],
+     python  => [ '/usr/bin', undef,
+                 qr/^python([\d.]+)$/, 'python$1 | python$1-minimal',
+                 qw(2.4 2.5)
+               ],
+     ruby    => [ '/usr/bin', undef,
+                 qr/^ruby([\d.]+)$/, 'ruby$1', qw(1.8 1.9)
+               ],
+     scsh    => [ '/usr/bin', 'scsh',
+                 qr/^scsh-([\d.]+)$/, 'scsh-$1', qw(0.6)
+               ],
+     tclsh   => [ '/usr/bin', 'tclsh | tcl',
+                 qr/^tclsh([\d.]+)$/, 'tcl$1', qw(8.3 8.4 8.5 8.6)
+               ],
+     wish    => [ '/usr/bin', 'wish | tk',
+                 qr/^wish([\d.]+)$/, 'tk$1', qw(8.3 8.4 8.5 8.6)
+               ],
+    );
+
+# Any of the following packages can satisfy an update-inetd dependency.
+our $update_inetd
+    = join (' | ', qw(update-inetd inet-superserver openbsd-inetd
+                      inetutils-inetd rlinetd xinetd));
+
+# Appearance of one of these regexes in a maintainer script means that there
+# must be a dependency (or pre-dependency) on the given package.  The tag
+# reported is maintainer-script-needs-depends-on-%s, so be sure to update
+# scripts.desc when adding a new rule.
+our @depends_needed = (
+       [ adduser       => '\badduser\s'           ],
+       [ gconf2        => '\bgconf-schemas\s'     ],
+       [ $update_inetd => '\bupdate-inetd\s'      ],
+       [ ucf           => '\bucf\s'               ],
+       [ 'xml-core'    => '\bupdate-xmlcatalog\s' ],
+);
+
+# When detecting commands inside shell scripts, use this regex to match the
+# beginning of the command rather than checking whether the command is at the
+# beginning of a line.
+our $LEADIN = qr'(?:(?:^|[`&;(|{])\s*|(?:if|then|do|while)\s+)';
+
+our @bashism_single_quote_regexs = (
+    $LEADIN . qr'echo\s+(?:-[^e\s]+\s+)?\'[^\']*(\\[\\abcEfnrtv0])+.*?[\']',
+       # unsafe echo with backslashes
+    $LEADIN . qr'source\s+[\"\']?(?:\.\/|\/|\$|[\w.-])[^\s]+',
+       # should be '.', not 'source'
+);
+our @bashism_string_regexs = (
+    qr'\$\[\w+\]',              # arith not allowed
+    qr'\$\{\w+\:\d+(?::\d+)?\}',   # ${foo:3[:1]}
+    qr'\$\{\w+(/.+?){1,2}\}',   # ${parm/?/pat[/str]}
+    qr'\$\{\#?\w+\[[0-9\*\@]+\]\}',# bash arrays, ${name[0|*|@]}
+    qr'\$\{!\w+[\@*]\}',                # ${!prefix[*|@]}
+    qr'\$\{!\w+\}',             # ${!name}
+    qr'(\$\(|\`)\s*\<\s*\S+\s*(\)|\`)', # $(\< foo) should be $(cat foo)
+    qr'\$\{?RANDOM\}?\b',               # $RANDOM
+    qr'\$\{?(OS|MACH)TYPE\}?\b',   # $(OS|MACH)TYPE
+    qr'\$\{?HOST(TYPE|NAME)\}?\b', # $HOST(TYPE|NAME)
+    qr'\$\{?DIRSTACK\}?\b',        # $DIRSTACK
+    qr'\$\{?EUID\}?\b',            # $EUID should be "id -u"
+    qr'\$\{?UID\}?\b',          # $UID should be "id -ru"
+    qr'\$\{?SECONDS\}?\b',      # $SECONDS
+    qr'\$\{?BASH_[A-Z]+\}?\b',     # $BASH_SOMETHING
+    qr'\$\{?SHELLOPTS\}?\b',       # $SHELLOPTS
+    qr'\$\{?PIPESTATUS\}?\b',      # $PIPESTATUS
+    qr'\$\{?SHLVL\}?\b',                # $SHLVL
+    qr'<<<',                       # <<< here string
+    $LEADIN . qr'echo\s+(?:-[^e\s]+\s+)?\"[^\"]*(\\[\\abcEfnrtv0])+.*?[\"]',
+       # unsafe echo with backslashes
+);
+our @bashism_regexs = (
+    qr'(?:^|\s+)function \w+(\s|\(|\Z)',  # function is useless
+    qr'(test|-o|-a)\s*[^\s]+\s+==\s', # should be 'b = a'
+    qr'\[\s+[^\]]+\s+==\s',        # should be 'b = a'
+    qr'\s(\|\&)',                       # pipelining is not POSIX
+    qr'[^\\\$]\{(?:[^\s\\\}]*?,)+[^\\\}\s]*\}', # brace expansion
+    qr'(?:^|\s+)\w+\[\d+\]=',      # bash arrays, H[0]
+    $LEADIN . qr'read\s+(?:-[a-qs-zA-Z\d-]+)',
+       # read with option other than -r
+    $LEADIN . qr'read\s*(?:-\w+\s*)*(?:\".*?\"|[\'].*?[\'])?\s*(?:;|$)',
+       # read without variable
+    $LEADIN . qr'kill\s+-[^sl]\w*',# kill -[0-9] or -[A-Z]
+    $LEADIN . qr'trap\s+["\']?.*["\']?\s+.*[1-9]', # trap with signal numbers
+    qr'\&>',                    # cshism
+    qr'(<\&|>\&)\s*((-|\d+)[^\s;|)`&\\\\]|[^-\d\s]+)', # should be >word 2>&1
+    qr'\[\[(?!:)',              # alternative test command
+    $LEADIN . qr'select\s+\w+',    # 'select' is not POSIX
+    $LEADIN . qr'echo\s+(-n\s+)?-n?en?',  # echo -e
+    $LEADIN . qr'exec\s+-[acl]',   # exec -c/-l/-a name
+    qr'(?:^|\s+)let\s',                 # let ...
+    qr'(?<![\$\(])\(\(.*\)\)',     # '((' should be '$(('
+    qr'\$\[[^][]+\]',           # '$[' should be '$(('
+    qr'(\[|test)\s+-a',                 # test with unary -a (should be -e)
+    qr'/dev/(tcp|udp)',                 # /dev/(tcp|udp)
+    $LEADIN . qr'\w+\+=',               # should be "VAR="${VAR}foo"
+    $LEADIN . qr'suspend\s',
+    $LEADIN . qr'caller\s',
+    $LEADIN . qr'complete\s',
+    $LEADIN . qr'compgen\s',
+    $LEADIN . qr'declare\s',
+    $LEADIN . qr'typeset\s',
+    $LEADIN . qr'disown\s',
+    $LEADIN . qr'builtin\s',
+    $LEADIN . qr'set\s+-[BHT]+',   # set -[BHT]
+    $LEADIN . qr'alias\s+-p',      # alias -p
+    $LEADIN . qr'unalias\s+-a',    # unalias -a
+    $LEADIN . qr'local\s+-[a-zA-Z]+', # local -opt
+    qr'(?:^|\s+)\s*\(?\w*[^\(\w\s]+\S*?\s*\(\)\s*([\{|\(]|\Z)',
+       # function names should only contain [a-z0-9_]
+    $LEADIN . qr'(push|pop)d(\s|\Z)',   # (push|pod)d
+    $LEADIN . qr'export\s+-[^p]',  # export only takes -p as an option
+    $LEADIN . qr'ulimit(\s|\Z)',
+    $LEADIN . qr'shopt(\s|\Z)',
+    $LEADIN . qr'type\s',
+    $LEADIN . qr'time\s',
+    $LEADIN . qr'dirs(\s|\Z)',
+    qr'(?:^|\s+)[<>]\(.*?\)',      # <() process substituion
+    qr'(?:^|\s+)readonly\s+-[af]', # readonly -[af]
+    $LEADIN . qr'(sh|\$\{?SHELL\}?) -[rD]', # sh -[rD]
+    $LEADIN . qr'(sh|\$\{?SHELL\}?) --\w+', # sh --long-option
+    $LEADIN . qr'(sh|\$\{?SHELL\}?) [-+]O', # sh [-+]O
+);
+
+
+sub run {
+
+my %executable = ();
+my %suid = ();
+my %ELF = ();
+my %scripts = ();
+
+# no dependency for install-menu, because the menu package specifically
+# says not to depend on it.
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+foreach (sort keys %{$info->index}) {
+    next if $_ eq "";
+    my $index_info = $info->index->{$_};
+    my $operm = $index_info->{operm};
+    next unless ($index_info->{type} =~ m,^[-h], and ($operm & 01 or
+       $operm & 010 or $operm & 0100));
+    my $is_suid = $operm & 04000;
+    $executable{'./' . $_} = 1;
+    $suid{'./' . $_} = $is_suid;
+}
+
+for my $file (sort keys %{$info->file_info}) {
+    $ELF{'./' . $file} = 1 if $info->file_info->{$file} =~ /^[^,]*\bELF\b/o;
+}
+
+my $all_deps = '';
+for my $field (qw/suggests recommends depends pre-depends provides/) {
+    if (defined $info->field($field)) {
+        $all_deps .= ', ' if $all_deps;
+        $all_deps .= $info->field($field);
+    }
+}
+$all_deps .= ', ' if $all_deps;
+$all_deps .= $pkg;
+my $all_parsed = Maemian::Relation->new($all_deps);
+
+for my $filename (sort keys %{$info->scripts}) {
+    my $interpreter = $info->scripts->{$filename}->{interpreter};
+    my $calls_env = $info->scripts->{$filename}->{calls_env};
+    $filename = './' . $filename;
+    $scripts{$filename} = 1;
+
+    # no checks necessary at all for scripts in /usr/share/doc/
+    next if $filename =~ m,usr/share/doc/,;
+
+    my ($base) = $interpreter =~ m,([^/]*)$,;
+
+    # allow exception for .in files that have stuff like #!@PERL@
+    next if ($filename =~ m,\.in$, and $interpreter =~ m,^(\@|<\<)[A-Z_]+(\@|>\>)$,);
+
+    my $is_absolute = ($interpreter =~ m,^/, or defined $calls_env);
+
+    # Skip files that have the #! line, but are not executable and do not have
+    # an absolute path and are not in a bin/ directory (/usr/bin, /bin etc)
+    # They are probably not scripts after all.
+    next if ($filename !~ m,(bin/|etc/init\.d/), and !$executable{$filename}
+             and !$is_absolute);
+
+    if ($interpreter eq "") {
+       tag("script-without-interpreter", $filename);
+       next;
+    }
+
+    # Either they use an absolute path or they use '/usr/bin/env interp'.
+    tag("interpreter-not-absolute", $filename, "#!$interpreter")
+       unless $is_absolute;
+    tag("script-not-executable", $filename)
+       unless ($executable{$filename}
+               or $filename =~ m,^\./usr/(lib|share)/.*\.pm,
+               or $filename =~ m,^\./usr/(lib|share)/.*\.py,
+               or $filename =~ m,^\./usr/(lib|share)/ruby/.*\.rb,
+               or $filename =~ m,\.in$,
+               or $filename =~ m,\.ex$,
+               or $filename eq './etc/init.d/skeleton'
+               or $filename =~ m,^\./etc/menu-methods,
+               or $filename =~ m,^\./etc/X11/Xsession\.d,);
+
+    # Warn about csh scripts.
+    tag("csh-considered-harmful", $filename)
+        if (($base eq 'csh' or $base eq 'tcsh')
+           and $executable{$filename}
+           and $filename !~ m,^\./etc/csh/login\.d/,);
+
+    # Syntax-check most shell scripts, but don't syntax-check scripts that end
+    # in .dpatch.  bash -n doesn't stop checking at exit 0 and goes on to blow
+    # up on the patch itself.
+    if ($base =~ /^$known_shells_regex$/) {
+       if (-x $interpreter
+           and ! script_is_evil_and_wrong("unpacked/$filename")
+           and $filename !~ m,\.dpatch$,
+           # exclude some shells. zsh -n is broken, see #485885
+           and $base !~ m/^(z|t?c)sh$/) {
+
+           if (check_script_syntax($interpreter, "unpacked/$filename")) {
+               tag("shell-script-fails-syntax-check", $filename);
+           }
+       }
+    }
+
+    # Try to find the expected path of the script to check.  First check
+    # %interpreters and %versioned_interpreters.  If not found there, see if
+    # it ends in a version number and the base is found in
+    # %versioned_interpreters.
+    my $data = $interpreters{$base};
+    my $versioned = 0;
+    if (not defined $data) {
+       $data = $versioned_interpreters{$base};
+       undef $data if ($data and not defined ($data->[1]));
+       if (not defined ($data) and $base =~ /^(.*[^\d.-])-?[\d.]+$/) {
+           $data = $versioned_interpreters{$1};
+           undef $data unless ($data and $base =~ /$data->[2]/);
+       }
+       $versioned = 1 if $data;
+    }
+    if ($data) {
+       my $expected = $data->[0] . '/' . $base;
+       unless ($interpreter eq $expected or defined $calls_env) {
+           tag("wrong-path-for-interpreter",
+               "#!$interpreter != $expected", "($filename)");
+       }
+    } elsif ($interpreter =~ m,/usr/local/,) {
+       tag("interpreter-in-usr-local", $filename, "#!$interpreter");
+    } elsif ($executable{'.' . $interpreter}) {
+       # Package installs the interpreter itself, so it's probably ok.  Don't
+       # emit any tag for this.
+    } elsif ($base eq 'suidperl') {
+       tag("calls-suidperl-directly", $filename);
+    } elsif ($interpreter eq '/bin/env') {
+       tag("script-uses-bin-env", $filename);
+    } else {
+       tag("unusual-interpreter", $filename, "#!$interpreter");
+    }
+
+    # Do some additional checks on shell scripts in /etc.  This should
+    # probably be extended eventually to any script in a public directory.
+    # This also needs smarter processing of multiline quoted strings,
+    # heredocs, and so forth.  Hopefully it will do for right now.
+    if ($filename =~ m,^./etc/, and $base =~ /^$known_shells_regex$/) {
+       my ($saw_init, $saw_invoke);
+       local $.;
+       open(FH, '<', 'unpacked/' . $filename);
+       while (<FH>) {
+           next if m,^\s*$,;  # skip empty lines
+           next if m,^\s*\#,; # skip comment lines
+           $_ = remove_comments($_);
+           chomp;
+
+           # Check for running init scripts directly instead of via
+           # invoke-rc.d.  Scripts are allowed to reinvoke themselves with a
+           # different argument; some init scripts implement actions that
+           # way.  Scripts are also allowed to do this for actions other than
+           # those defined for invoke-rc.d.
+           if (m,$LEADIN/etc/init.d/(\S+)\s+[\"\']?(\S+)[\"\']?,) {
+               my ($script, $action) = ($1, $2);
+               next if "./etc/init.d/$script" eq $filename;
+               next unless $action =~ /^(force-)?(start|stop|restart|reload|status)$/;
+               $saw_init = $.;
+           }
+           if (m%^\s*invoke-rc\.d\s+%) {
+               $saw_invoke = 1;
+           }
+       }
+       close(FH);
+       if ($saw_init and not $saw_invoke) {
+           tag 'script-calls-init-script-directly', "$filename:$saw_init";
+       }
+    }
+
+    # If we found the interpreter and the script is executable, check
+    # dependencies.  This should be the last thing we do in the loop so that
+    # we can use next for an early exit and reduce the nesting.
+    next unless ($data && $executable{$filename});
+    if (!$versioned) {
+       my $depends = $data->[1];
+       if (not defined $depends) {
+           $depends = $base;
+       }
+       if ($depends && !$all_parsed->implies($depends)) {
+           if ($base =~ /^(python|ruby|(m|g)awk)$/) {
+               tag("$base-script-but-no-$base-dep", $filename);
+           } elsif ($base eq 'csh' && $filename =~ m,^\./etc/csh/login\.d/,) {
+               # Initialization files for csh.
+           } elsif ($base eq 'fish' && $filename =~ m,^\./etc/fish\.d/,) {
+               # Initialization files for fish.
+           } elsif ($base eq 'ocamlrun' && $all_deps =~ /\bocaml(-base)?(-nox)?-\d\.[\d.]+/) {
+               # ABI-versioned virtual packages for ocaml
+           } else {
+               tag('missing-dep-for-interpreter', "$base => $depends",
+                   "($filename)");
+           }
+       }
+       if ($base eq 'perl' && $suid{$filename}) {
+           tag("suid-perl-script-but-no-perl-suid-dep", $filename)
+               unless $all_parsed->implies('perl-suid');
+       }
+    } elsif ($versioned_interpreters{$base}) {
+       my @versions = @$data[4 .. @$data - 1];
+       my @depends = map {
+           my $d = $data->[3];
+           $d =~ s/\$1/$_/g;
+           $d;
+       } @versions;
+       unshift (@depends, $data->[1]) if length $data->[1];
+       my $depends = join (' | ',  @depends);
+       unless ($all_parsed->implies($depends)) {
+           if ($base eq 'php') {
+               tag('php-script-but-no-phpX-cli-dep', $filename);
+           } elsif ($base =~ /^(wish|tclsh)/) {
+               tag("$1-script-but-no-$1-dep", $filename);
+           } else {
+               tag("missing-dep-for-interpreter", "$base => $depends",
+                   "($filename)");
+           }
+       }
+    } else {
+       my ($version) = ($base =~ /$data->[2]/);
+       my $depends = $data->[3];
+       $depends =~ s/\$1/$version/g;
+       unless ($all_parsed->implies($depends)) {
+           if ($base =~ /^php/) {
+               tag('php-script-but-no-phpX-cli-dep', $filename);
+           } elsif ($base =~ /^(python|ruby)/) {
+               tag("$1-script-but-no-$1-dep", $filename);
+           } else {
+               tag("missing-dep-for-interpreter", "$base => $depends",
+                   "($filename)");
+           }
+       }
+    }
+}
+
+foreach (keys %executable) {
+    tag("executable-not-elf-or-script", $_)
+       unless ( $ELF{$_}
+                or $scripts{$_}
+                or $_ =~ m,^usr(/X11R6)?/man/,
+                or $_ =~ m/\.exe$/ # mono convention
+                );
+}
+
+open(SCRIPTS, '<', "control-scripts")
+    or fail("cannot open lintian control-scripts file: $!");
+
+# Handle control scripts.  This is an edited version of the code for
+# normal scripts above, because there were just enough differences to
+# make a shared function awkward.
+
+my %added_diversions;
+my %removed_diversions;
+my $expand_diversions = 0;
+while (<SCRIPTS>) {
+    chop;
+
+    m/^(\S*) (.*)$/ or fail("bad line in control-scripts file: $_");
+    my $interpreter = $1;
+    my $file = $2;
+    my $filename = "control/$file";
+
+    $interpreter =~ m|([^/]*)$|;
+    my $base = $1;
+
+    if ($interpreter eq "") {
+       tag("script-without-interpreter", $filename);
+       next;
+    }
+
+    tag("interpreter-not-absolute", $filename, "#!$interpreter")
+       unless ($interpreter =~ m|^/|);
+
+    if ($interpreter =~ m|/usr/local/|) {
+       tag("control-interpreter-in-usr-local", $filename, "#!$interpreter");
+    } elsif ($base eq 'sh' or $base eq 'bash' or $base eq 'perl') {
+       my $expected = $interpreters{$base}->[0] . '/' . $base;
+       tag("wrong-path-for-interpreter", "#!$interpreter != $expected",
+           "($filename)")
+           unless ($interpreter eq $expected);
+    } elsif ($file eq 'config') {
+       tag('forbidden-config-interpreter', "#!$interpreter");
+    } elsif ($file eq 'postrm') {
+       tag('forbidden-postrm-interpreter', "#!$interpreter");
+    } elsif (exists $interpreters{$base}) {
+       my $data = $interpreters{$base};
+       my $expected = $data->[0] . '/' . $base;
+       unless ($interpreter eq $expected) {
+           tag("wrong-path-for-interpreter", "#!$interpreter != $expected",
+               "($filename)")
+       }
+       tag('unusual-control-interpreter', $filename, "#!$interpreter");
+
+       # Interpreters used by preinst scripts must be in Pre-Depends.
+       # Interpreters used by postinst or prerm scripts must be in Depends.
+       unless (defined ($data->[1]) and not $data->[1]) {
+           my $depends = Maemian::Relation->new($data->[1] || $base);
+           if ($file eq 'preinst') {
+               unless ($info->relation('pre-depends')->implies($depends)) {
+                   tag('preinst-interpreter-without-predepends',
+                       "#!$interpreter")
+               }
+           } else {
+               unless ($info->relation('strong')->implies($depends)) {
+                   tag('control-interpreter-without-depends', $filename,
+                       "#!$interpreter")
+               }
+           }
+       }
+    } else {
+       tag("unknown-control-interpreter", $filename, "#!$interpreter");
+       next; # no use doing further checks if it's not a known interpreter
+    }
+
+    # perhaps we should warn about *csh even if they're somehow screwed,
+    # but that's not really important...
+    tag("csh-considered-harmful", $filename)
+       if ($base eq 'csh' or $base eq 'tcsh');
+
+    my $shellscript = $base =~ /^$known_shells_regex$/ ? 1 : 0;
+
+    # Only syntax-check scripts we can check with bash.
+    my $checkbashisms;
+    if ($shellscript) {
+       $checkbashisms = $base eq "sh" ? 1 : 0;
+       if ($base eq 'sh' or $base eq 'bash') {
+           if (check_script_syntax("/bin/bash", $filename)) {
+               tag("maintainer-shell-script-fails-syntax-check", $file);
+           }
+       }
+    }
+
+    # now scan the file contents themselves
+    open (C, '<', "$filename")
+       or fail("cannot open maintainer script $filename for reading: $!");
+
+    my %warned;
+    my ($saw_init, $saw_invoke, $saw_debconf, $saw_bange, $saw_sete, $has_code);
+    my $cat_string = "";
+
+    my $previous_line = "";
+    while (<C>) {
+       if ($. == 1 && $shellscript && m,/$base\s*.*\s-\w*e\w*\b,) {
+           $saw_bange = 1;
+       }
+
+       next if m,^\s*$,;  # skip empty lines
+       next if m,^\s*\#,; # skip comment lines
+       $_ = remove_comments($_);
+
+       # Concatenate lines containing continuation character (\) at the end
+       if ($shellscript && /\\$/) {
+           s/\\//;
+           chomp;
+           $previous_line .= $_;
+           next;
+       }
+
+       chomp;
+       $_ = $previous_line . $_;
+       $previous_line = "";
+
+       # Don't consider the standard dh-make boilerplate to be code.  This
+       # means ignoring the framework of a case statement, the labels, the
+       # echo complaining about unknown arguments, and an exit.
+       unless ($has_code
+               || m/^\s*set\s+-\w+\s*$/
+               || m/^\s*case\s+\"?\$1\"?\s+in\s*$/
+               || m/^\s*(?:[a-z|-]+|\*)\)\s*$/
+               || m/^\s*[:;]+\s*$/
+               || m/^\s*echo\s+\"[^\"]+\"(?:\s*>&2)?\s*$/
+               || m/^\s*esac\s*$/
+               || m/^\s*exit\s+\d+\s*$/) {
+           $has_code = 1;
+       }
+
+       if ($shellscript && m,${LEADIN}set\s*(\s+-(-.*|[^e]+))*\s-\w*e,) {
+           $saw_sete = 1;
+       }
+
+       if (m,[^\w]((/var)?/tmp|\$TMPDIR)/[^)\]}\s], and not m/\bmks?temp\b/ and not m/\btempfile\b/ and not m/\bmkdir\b/ and not m/\$RANDOM/) {
+           tag "possibly-insecure-handling-of-tmp-files-in-maintainer-script", "$file:$."
+               unless $warned{tmp};
+           $warned{tmp} = 1;
+       }
+       if (m/^\s*killall(?:\s|\z)/) {
+           tag "killall-is-dangerous", "$file:$." unless $warned{killall};
+           $warned{killall} = 1;
+       }
+       if (m/^\s*mknod(?:\s|\z)/ and not m/\sp\s/) {
+           tag "mknod-in-maintainer-script", "$file:$.";
+       }
+
+       # Collect information about init script invocations to catch running
+       # init scripts directly rather than through invoke-rc.d.  Since the
+       # script is allowed to run the init script directly if invoke-rc.d
+       # doesn't exist, only tag direct invocations where invoke-rc.d is
+       # never used in the same script.  Lots of false negatives, but
+       # hopefully not many false positives.
+       if (m%^\s*/etc/init\.d/(\S+)\s+[\"\']?(\S+)[\"\']?%) {
+           $saw_init = $.;
+       }
+       if (m%^\s*invoke-rc\.d\s+%) {
+           $saw_invoke = $.;
+       }
+
+       if ($shellscript) {
+           if ($cat_string ne "" and m/^\Q$cat_string\E$/) {
+               $cat_string = "";
+           }
+           my $within_another_shell = 0;
+           if (m,(?:^|\s+)(?:(?:/usr)?/bin/)?($known_shells_regex)\s+-c\s*.+,
+               and $1 ne 'sh') {
+               $within_another_shell = 1;
+           }
+           # if cat_string is set, we are in a HERE document and need not
+           # check for things
+           if ($cat_string eq "" and $checkbashisms and !$within_another_shell) {
+               my $found = 0;
+               my $match = '';
+
+               # since this test is ugly, I have to do it by itself
+               # detect source (.) trying to pass args to the command it runs
+               # The first expression weeds out '. "foo bar"'
+               if (not $found and
+                   not m/^\s*\.\s+(\"[^\"]+\"|\'[^\']+\')\s*(\&|\||\d?>|<|;|\Z)/
+                   and m/^\s*(\.\s+[^\s;\`:]+\s+([^\s;]+))/) {
+
+                   my $extra;
+                   ($match, $extra) = ($1, $2);
+                   if ($extra =~ /^(\&|\||\d?>|<)/) {
+                       # everything is ok
+                       ;
+                   } else {
+                       $found = 1;
+                   }
+               }
+
+               my $line = $_;
+
+               unless ($found) {
+                   for my $re (@bashism_single_quote_regexs) {
+                       if ($line =~ m/($re)/) {
+                           $found = 1;
+                           ($match) = m/($re)/;
+                           last;
+                       }
+                   }
+               }
+
+               # Ignore anything inside single quotes; it could be an
+               # argument to grep or the like.
+
+               # $cat_line contains the version of the line we'll check
+               # for heredoc delimiters later. Initially, remove any
+               # spaces between << and the delimiter to make the following
+               # updates to $cat_line easier.
+               my $cat_line = $line;
+               $cat_line =~ s/(<\<-?)\s+/$1/g;
+
+               # Remove single quoted strings, with the exception that we
+               # don't remove the string
+               # if the quote is immediately preceeded by a < or a -, so we
+               # can match "foo <<-?'xyz'" as a heredoc later
+               # The check is a little more greedy than we'd like, but the
+               # heredoc test itself will weed out any false positives
+               $cat_line =~ s/(^|[^<\\\"-](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
+
+               unless ($found) {
+                   # Remove "quoted quotes". They're likely to be inside
+                   # another pair of quotes; we're not interested in
+                   # them for their own sake and removing them makes finding
+                   # the limits of the outer pair far easier.
+                   $line =~ s/(^|[^\\\'\"])\"\'\"/$1/g;
+                   $line =~ s/(^|[^\\\'\"])\'\"\'/$1/g;
+
+                   $line =~ s/(^|[^\\\"](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
+                   for my $re (@bashism_string_regexs) {
+                       if ($line =~ m/($re)/) {
+                           $found = 1;
+                           ($match) = m/($re)/;
+                           last;
+                       }
+                   }
+               }
+
+               # We've checked for all the things we still want to notice in
+               # double-quoted strings, so now remove those strings as well.
+               $cat_line =~ s/(^|[^<\\\'-](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
+               unless ($found) {
+                   $line =~ s/(^|[^\\\'](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
+                   for my $re (@bashism_regexs) {
+                       if ($line =~ m/($re)/) {
+                           $found = 1;
+                           ($match) = m/($re)/;
+                           last;
+                       }
+                   }
+               }
+
+               if ($found) {
+                   tag "possible-bashism-in-maintainer-script", "$file:$. \'$match\'";
+               }
+
+               # Only look for the beginning of a heredoc here, after we've
+               # stripped out quoted material, to avoid false positives.
+               if ($cat_line =~ m/(?:^|[^<])\<\<\-?\s*(?:[\\]?(\w+)|[\'\"](.*?)[\'\"])/) {
+                   $cat_string = $1;
+                   $cat_string = $2 if not defined $cat_string;
+               }
+           }
+           if (!$cat_string) {
+               if (/^\s*start-stop-daemon\s+/ && !/\s--stop\b/) {
+                   tag 'start-stop-daemon-in-maintainer-script', "$file:$.";
+               }
+               # Don't use chown foo.bar
+               if (/(chown(\s+--?[A-Za-z-]+)*\s+[-_A-Za-z0-9]+\.[-_A-Za-z0-9]+)\s+/) {
+                   tag "deprecated-chown-usage", "$file:$. \'$1\'";
+               }
+               if (/invoke-rc.d.*\|\| exit 0/) {
+                   tag "maintainer-script-hides-init-failure", "$file:$.";
+               }
+               if (m,/usr/share/debconf/confmodule,) {
+                   $saw_debconf = 1;
+               }
+               if (m/^\s*read(?:\s|\z)/ && !$saw_debconf) {
+                   tag "read-in-maintainer-script", "$file:$.";
+               }
+               if (m,^\s*rm\s+([^>]*\s)?/dev/,) {
+                   tag "maintainer-script-removes-device-files", "$file:$.";
+               }
+               if (m,>\s*(/etc/(?:services|protocols|rpc))(\s|\Z),) {
+                   tag "maintainer-script-modifies-netbase-managed-file", "$file:$. $1";
+               }
+               if (m,^\s*(?:cp|mv)\s.*(/etc/(?:services|protocols|rpc))\s*$,) {
+                   tag "maintainer-script-modifies-netbase-managed-file", "$file:$. $1";
+               }
+               if (m,>\s*/etc/inetd\.conf(\s|\Z),) {
+                   tag "maintainer-script-modifies-inetd-conf", "$file:$."
+                       unless $info->relation('provides')->implies('inet-superserver');
+               }
+               if (m,^\s*(?:cp|mv)\s+(?:.*\s)?/etc/inetd\.conf\s*$,) {
+                   tag "maintainer-script-modifies-inetd-conf", "$file:$."
+                       unless $info->relation('provides')->implies('inet-superserver');
+               }
+               if (m,^$LEADIN(/(usr/)?s?bin/[\w.+-]+)(\s|;|$),) {
+                   tag "command-with-path-in-maintainer-script", "$file:$. $1";
+               }
+
+               # Ancient dpkg feature tests.
+               if (m/^\s*dpkg\s+--assert-support-predepends\b/) {
+                   tag "ancient-dpkg-predepends-check", "$file:$.";
+               }
+               if (m/^\s*dpkg\s+--assert-working-epoch\b/) {
+                   tag "ancient-dpkg-epoch-check", "$file:$.";
+               }
+               if (m/^dpkg\s+--assert-long-filenames\b/) {
+                   tag "ancient-dpkg-long-filenames-check", "$file:$.";
+               }
+               if (m/^dpkg\s+--assert-multi-conrep\b/) {
+                   tag "ancient-dpkg-multi-conrep-check", "$file:$.";
+               }
+           }
+       }
+       if (m,\bsuidregister\b,) {
+           tag "suidregister-used-in-maintainer-script", "$file";
+       }
+       if ($file eq 'postrm') {
+           if (m,update\-alternatives \-\-remove,) {
+               tag "update-alternatives-remove-called-in-postrm", "";
+           }
+       } else {
+           for my $rule (@depends_needed) {
+               my ($package, $regex) = @$rule;
+               if ($pkg ne $package and /$regex/ and ! $warned{$package}) {
+                   if (m,-x\s+\S*$regex, or m,(which|type)\s+$regex, or m,command\s+.*?$regex,) {
+                       $warned{$package} = 1;
+                   } else {
+                       unless ($info->relation('strong')->implies($package)) {
+                           my $shortpackage = $package;
+                           $shortpackage =~ s/[ \(].*//;
+                           tag "maintainer-script-needs-depends-on-$shortpackage", "$file";
+                           $warned{$package} = 1;
+                       }
+                   }
+               }
+           }
+       }
+       if (m,\bgconftool(-2)?(\s|\Z),) {
+           tag "gconftool-used-in-maintainer-script", "$file:$.";
+       }
+       if (m,\binstall-sgmlcatalog\b, && !(m,--remove, && ($file eq 'prerm' || $file eq 'postinst'))) {
+           tag "install-sgmlcatalog-deprecated", "$file:$.";
+       }
+        if (m,/var/lib/dpkg/status\b, && $pkg ne 'base-files' && $pkg ne 'dpkg') {
+            tag "maintainer-script-uses-dpkg-status-directly", "$file";
+        }
+       if (m,$LEADIN(?:/usr/sbin/)?dpkg-divert\s, && ! /--(?:help|list|truename|version)/) {
+           if (/--local/ or !/--package/) {
+               tag 'package-uses-local-diversion', "$file:$.";
+           } else {
+               my $mode = /--remove/ ? 'remove' : 'add';
+               my ($divert) = /dpkg-divert\s*(.*)$/;
+               $divert =~ s/\s*--(?:add|quiet|remove|rename|test|(:?admindir|divert|package)\s+\S+)//g;
+               $divert =~ s/\s+//g;
+               # Remove unpaired opening or closing parenthesis
+               while($divert =~ m/\G.*?\(.+?\)/gc) {}
+               $divert =~ s/\G(.*?)[()]/$1/;
+               pos($divert) = undef;
+               # Remove unpaired opening or closing braces
+               while($divert =~ m/\G.*?{.+?}/gc) {}
+               $divert =~ s/\G(.*?)[{}]/$1/;
+               pos($divert) = undef;
+
+               # position after the last pair of quotation marks, if any
+               while($divert =~ m/\G.*?("|').+?\1/gc) {} #"
+               # Strip anything matching and after '&&', '||', or ';'
+               # this is safe only after we are positioned after the last pair
+               # of quotation marks
+               $divert =~ s/\G.+?\K(?: && | \|\| | ;).*$//x;
+               pos($divert) = undef;
+               # Remove quotation marks, they affect:
+               # * our var to regex trick
+               # * stripping the initial slash if the path was quoted
+               $divert =~ s/["']//g; #"
+               # remove the leading / because it's not in the index hash
+               $divert =~ s,^/,,;
+
+               $divert = quotemeta($divert);
+
+               # For now just replace variables, they will later be normalised
+               $expand_diversions = 1 if $divert =~ s/\\\$\w+/.+/g;
+               $expand_diversions = 1 if $divert =~ s/\\\$\\{\w+.*?\\}/.+/g;
+               # handle $() the same way:
+               $expand_diversions = 1 if $divert =~ s/\\\$\\\(.+?\\\)/.+/g;
+
+               if ($mode eq 'add') {
+                   $added_diversions{$divert} = {'script' => $file, 'line' => $.};
+               } elsif ($mode eq 'remove') {
+                   push @{$removed_diversions{$divert}}, {'script' => $file, 'line' => $.};
+               } else {
+                   fail "Internal error: \$mode has unknown value: ".
+                       "$mode";
+               }
+           }
+       }
+    }
+
+    if ($saw_init && ! $saw_invoke) {
+       tag "maintainer-script-calls-init-script-directly", "$file:$saw_init";
+    }
+    unless ($has_code) {
+       tag "maintainer-script-empty", $file;
+    }
+    if ($shellscript && !$saw_sete) {
+       if ($saw_bange) {
+           tag 'maintainer-script-without-set-e', $file;
+       } else {
+           tag 'maintainer-script-ignores-errors', $file;
+       }
+    }
+
+    close C;
+
+}
+close(SCRIPTS);
+
+# If any of the maintainer scripts used a variable in the file or
+# diversion name normalise them all
+if ($expand_diversions) {
+    for my $divert (keys %removed_diversions, keys %added_diversions) {
+
+       # if a wider regex was found, the entries might no longer be there
+       unless (exists($removed_diversions{$divert})
+           or exists($added_diversions{$divert})) {
+           next;
+       }
+
+       my $widerrx = $divert;
+       my $wider = $widerrx;
+       $wider =~ s/\\//g;
+
+       # find the widest regex:
+       my @matches = grep {
+           my $lrx = $_;
+           my $l = $lrx;
+           $l =~ s/\\//g;
+
+           if ($wider =~ m/^$lrx$/) {
+               $widerrx = $lrx;
+               $wider = $l;
+               1;
+           } elsif ($l =~ m/^$widerrx$/) {
+               1;
+           } else {
+               0;
+           }
+       } (keys %removed_diversions, keys %added_diversions);
+
+       # replace all the occurences with the widest regex:
+       for my $k (@matches) {
+           next if ($k eq $widerrx);
+
+           if (exists($removed_diversions{$k})) {
+               $removed_diversions{$widerrx} = $removed_diversions{$k};
+               delete $removed_diversions{$k};
+           }
+           if (exists($added_diversions{$k})) {
+               $added_diversions{$widerrx} = $added_diversions{$k};
+               delete $added_diversions{$k};
+           }
+       }
+    }
+}
+
+for my $divert (keys %removed_diversions) {
+    if (exists $added_diversions{$divert}) {
+       # just mark the entry, because a --remove might
+       # happen in two branches in the script, i.e. we
+       # see it twice, which is not a bug
+       $added_diversions{$divert}{'removed'} = 1;
+    } else {
+       for my $item (@{$removed_diversions{$divert}}) {
+           my $script = $item->{'script'};
+           my $line = $item->{'line'};
+
+           next unless ($script eq 'postrm');
+
+           # Allow preinst and postinst to remove diversions the
+           # package doesn't add to clean up after previous
+           # versions of the package.
+
+           $divert = unquote($divert, $expand_diversions);
+
+           tag 'remove-of-unknown-diversion', $divert, "$script:$line";
+       }
+    }
+}
+
+for my $divert (keys %added_diversions) {
+    my $script = $added_diversions{$divert}{'script'};
+    my $line = $added_diversions{$divert}{'line'};
+
+    my $divertrx = $divert;
+    $divert = unquote($divert, $expand_diversions);
+
+    if ($expand_diversions) {
+       tag 'diversion-for-unknown-file', $divert, "$script:$line"
+           unless (grep { $_ =~ m/$divertrx/ } keys %{$info->index});
+    } else {
+       tag 'diversion-for-unknown-file', $divert, "$script:$line"
+           unless (exists $info->index->{$divert});
+    }
+
+    if (not exists $added_diversions{$divertrx}{'removed'}) {
+       tag 'orphaned-diversion', $divert, $script;
+    }
+}
+
+}
+
+# -----------------------------------
+
+# Returns non-zero if the given file is not actually a shell script,
+# just looks like one.
+sub script_is_evil_and_wrong {
+    my ($filename) = @_;
+    my $ret = 0;
+    open (IN, '<', $filename) or fail("cannot open $filename: $!");
+    my $i = 0;
+    my $var = "0";
+    my $backgrounded = 0;
+    local $_;
+    while (<IN>) {
+       chomp;
+       next if m/^#/o;
+       next if m/^$/o;
+       last if (++$i > 55);
+       if (m~
+            # the exec should either be "eval"ed or a new statement
+            (^\s*|\beval\s*[\'\"]|(;|&&|\b(then|else))\s*)
+
+            # eat anything between the exec and $0
+            exec\s*.+\s*
+
+            # optionally quoted executable name (via $0)
+            .?\$$var.?\s*
+
+            # optional "end of options" indicator
+            (--\s*)?
+
+            # Match expressions of the form '${1+$@}', '${1:+"$@"',
+            # '"${1+$@', "$@", etc where the quotes (before the dollar
+            # sign(s)) are optional and the second (or only if the $1
+            # clause is omitted) parameter may be $@ or $*.
+            #
+            # Finally the whole subexpression may be omitted for scripts
+            # which do not pass on their parameters (i.e. after re-execing
+            # they take their parameters (and potentially data) from stdin
+            .?(\${1:?\+.?)?(\$(\@|\*))?~x) {
+           $ret = 1;
+           last;
+       } elsif (/^\s*(\w+)=\$0;/) {
+           $var = $1;
+       } elsif (m~
+           # Match scripts which use "foo $0 $@ &\nexec true\n"
+           # Program name
+           \S+\s+
+
+           # As above
+           .?\$$var.?\s*
+           (--\s*)?
+           .?(\${1:?\+.?)?(\$(\@|\*))?.?\s*\&~x) {
+
+           $backgrounded = 1;
+       } elsif ($backgrounded and m~
+           # the exec should either be "eval"ed or a new statement
+           (^\s*|\beval\s*[\'\"]|(;|&&|\b(then|else))\s*)
+           exec\s+true(\s|\Z)~x) {
+
+           $ret = 1;
+           last;
+       }
+    }
+    close IN;
+    return $ret;
+}
+
+# Given an interpretor and a file, run the interpretor on that file with the
+# -n option to check syntax, discarding output and returning the exit status.
+sub check_script_syntax {
+    my ($interpreter, $script) = @_;
+    my $pid = fork;
+    if (!defined $pid) {
+       fail("cannot fork: $!");
+    } elsif ($pid == 0) {
+       open STDOUT, '>/dev/null' or fail("cannot reopen stdout: $!");
+       open STDERR, '>&STDOUT' or fail("cannot reopen stderr: $!");
+       exec $interpreter, '-n', $script
+           or fail("cannot exec $interpreter: $!");
+    } else {
+       waitpid $pid, 0;
+    }
+    return $?;
+}
+
+sub remove_comments {
+    local $_;
+
+    my $line = shift || '';
+    $_ = $line;
+
+    # Remove quoted strings so we can more easily ignore comments
+    # inside them
+    s/(^|[^\\](?:\\\\)*)\'(?:\\.|[^\\\'])+\'/$1''/g;
+    s/(^|[^\\](?:\\\\)*)\"(?:\\.|[^\\\"])+\"/$1""/g;
+    # If the remaining string contains what looks like a comment,
+    # eat it. In either case, swap the unmodified script line
+    # back in for processing (if required) and return it.
+    if (m/(?:^|[^[\\])[\s\&;\(\)](\#.*$)/) {
+       $_ = $line;
+       s/\Q$1\E//;  # eat comments
+    } else {
+       $_ = $line;
+    }
+
+    return $_;
+}
+
+sub unquote($$) {
+    my ($string, $replace_regex) = @_;
+
+    $string =~ s,\\,,g;
+    if ($replace_regex) {
+       $string =~ s,\.\+,*,g;
+    }
+
+    return $string;
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl ts=8 sw=4
diff --git a/checks/scripts.desc b/checks/scripts.desc
new file mode 100644 (file)
index 0000000..8be8cef
--- /dev/null
@@ -0,0 +1,583 @@
+Check-Script: scripts
+Author: Richard Braakman <dark@xs4all.nl>
+Abbrev: scr
+Type: binary
+Unpack-Level: 2
+Info: This script checks the #! lines of scripts in a package.
+Needs-Info: file-info, scripts
+
+Tag: script-without-interpreter
+Severity: important
+Certainty: certain
+Info: This file starts with the #! sequence that identifies scripts, but
+ it does not name an interpreter.
+
+Tag: executable-not-elf-or-script
+Severity: normal
+Certainty: certain
+Info: This executable file is not an ELF format binary, and does not start
+ with the #! sequence that marks interpreted scripts.  It might be a sh script
+ that fails to name /bin/sh as its shell.
+Ref: policy 10.4
+
+Tag: script-not-executable
+Severity: normal
+Certainty: certain
+Info: This file starts with the #! sequence that marks interpreted scripts,
+ but it is not executable.
+
+Tag: interpreter-not-absolute
+Severity: normal
+Certainty: certain
+Info: This script uses a relative path to locate its interpreter.
+ This path will be taken relative to the caller's current directory, not
+ the script's, so it is not likely to be what was intended.
+
+Tag: unusual-interpreter
+Severity: normal
+Certainty: possible
+Info: This package contains a script for an interpreter that the Maemian
+ maintainers have not heard of.  It could be a typo for a common
+ interpreter.  If not, please file a wishlist bug on lintian so that the
+ Maemian maintainers can add this interpreter to their list.
+
+Tag: script-uses-bin-env
+Severity: normal
+Certainty: certain
+Info: This script uses /bin/env as its interpreter (used to find the
+ actual interpreter on the user's path).  There is no /bin/env on Debian
+ systems; env is instead installed as /usr/bin/env.  Usually, the path to
+ env in the script should be changed.
+
+Tag: forbidden-config-interpreter
+Severity: important
+Certainty: certain
+Info: This package contains a <tt>config</tt> script for pre-configuring
+ the package.  During pre-configuration, however, only essential packages
+ are guaranteed to be installed, so you cannot use a non-essential
+ interpreter.
+
+Tag: forbidden-postrm-interpreter
+Severity: serious
+Certainty: certain
+Info: This package contains a <tt>postrm</tt> maintainer script that uses
+ an interpreter that isn't essential.  The <tt>purge</tt> action of
+ <tt>postrm</tt> can only rely on essential packages, which means the
+ interpreter used by <tt>postrm</tt> must be one of the essential ones
+ (<tt>sh</tt>, <tt>bash</tt>, or <tt>perl</tt>).
+Ref: policy 7.2
+
+Tag: unusual-control-interpreter
+Severity: minor
+Certainty: certain
+Info: This package contains a control script for an interpreter that is
+ not normally used for control scripts.  This is permissible but not
+ recommended.  It makes it harder for other developers to understand your
+ package.
+
+Tag: unknown-control-interpreter
+Severity: important
+Certainty: possible
+Info: This package contains a maintainer script that uses an interpreter
+ that the Maemian maintainers have not heard of.  This is usually a typo
+ for a common interpreter.  If not, please file a wishlist bug on lintian
+ so that the Maemian maintainers can add this interpreter to their list.
+
+Tag: interpreter-in-usr-local
+Severity: important
+Certainty: certain
+Info: This package contains a script that looks for an interpreter in a
+ directory in /usr/local.  Since Debian does not install anything in
+ /usr/local, this is the wrong place to look.
+
+Tag: control-interpreter-in-usr-local
+Severity: serious
+Certainty: certain
+Info: A control script for this package references an interpreter in a
+ directory in <tt>/usr/local</tt>.  Control scripts must use interpreters
+ provided by Debian packages, and Debian packages do not install anything
+ in <tt>/usr/local</tt>.
+
+Tag: preinst-interpreter-without-predepends
+Severity: serious
+Certainty: certain
+Info: The package contains a <tt>preinst</tt> maintainer script that uses
+ an unusual and non-essential interpreter but does not declare a
+ pre-dependency on the package that provides this interpreter.
+ .
+ <tt>preinst</tt> scripts should be written using only essential
+ interpreters to avoid additional dependency complexity.  Please do not
+ add a pre-dependency without following the policy for doing so (Policy
+ section 3.5).
+Ref: policy 7.2
+
+Tag: control-interpreter-without-depends
+Severity: serious
+Certainty: possible
+Info: The package contains a maintainer script that uses an unusual and
+ non-essential interpreter but does not declare a dependency on the
+ package that provides this interpreter.
+Ref: policy 7.2
+
+Tag: missing-dep-for-interpreter
+Severity: important
+Certainty: possible
+Info: You used an interpreter for a script that is not in an essential
+ package.  In most cases, you will need to add a Dependency on the
+ package that contains the interpreter.  If the dependency is already
+ present, please file a bug against Maemian with the details of your
+ package so that its database can be updated.
+ .
+ In some cases a weaker relationship, such as Suggests or Recommends, will
+ be more appropriate.
+
+Tag: csh-considered-harmful
+Severity: normal
+Certainty: certain
+Info: The Debian policy for scripts explicitly warns against using csh
+ and tcsh as scripting languages.
+Ref: policy 10.4
+
+Tag: suid-perl-script-but-no-perl-suid-dep
+Severity: important
+Certainty: certain
+Info: Packages that use perl scripts that are suid must depend on the
+ perl-suid package.
+ .
+ In some cases a weaker relationship, such as Suggests or Recommends, will
+ be more appropriate.
+
+Tag: wrong-path-for-interpreter
+Severity: important
+Certainty: certain
+Info: The interpreter you used is installed at another location on Debian
+ systems.
+
+Tag: gawk-script-but-no-gawk-dep
+Severity: important
+Certainty: certain
+Info: Packages that use gawk scripts must depend on the gawk package.
+ If they don't need gawk-specific features, and can just as easily work
+ with mawk, then they should be awk scripts instead.
+ .
+ In some cases a weaker relationship, such as Suggests or Recommends, will
+ be more appropriate.
+
+Tag: mawk-script-but-no-mawk-dep
+Severity: important
+Certainty: certain
+Info: Packages that use mawk scripts must depend on the mawk package.
+ If they don't need mawk-specific features, and can just as easily work
+ with gawk, then they should be awk scripts instead.
+ .
+ In some cases a weaker relationship, such as Suggests or Recommends, will
+ be more appropriate.
+
+Tag: php-script-but-no-phpX-cli-dep
+Severity: important
+Certainty: certain
+Info: Packages with PHP scripts must depend on a phpX-cli package such as
+ php5-cli.  Note that a dependency on a php-cgi package (such as php5-cgi)
+ is needlessly strict and forces the user to install a package that isn't
+ needed.
+ .
+ In some cases a weaker relationship, such as Suggests or Recommends, will
+ be more appropriate.
+ .
+ Maemian can only recognize phpX-cli dependencies for values of X that it
+ knows are available in the archive.  You will get this warning if you
+ allow, as alternatives, versions of PHP that are so old they're not
+ available in stable.  The correct fix in those cases is probably to drop
+ the old alternative.  If this package depends on a newer php-cli package
+ that Maemian doesn't know about, please file a bug against Maemian so
+ that it can be updated.
+
+Tag: python-script-but-no-python-dep
+Severity: important
+Certainty: certain
+Info: Packages with Python scripts must depend on the package python.
+ Those that have scripts executed with a versioned python package need a
+ dependency on the equivalent version of python. 
+ .
+ For example, if a script in the package uses <tt>#!/usr/bin/python</tt>,
+ the package needs a dependency on "python".  If a script uses
+ <tt>#!/usr/bin/python2.5</tt>, the package need a dependency on
+ "python2.5".
+ .
+ In some cases a weaker relationship, such as Suggests or Recommends, will
+ be more appropriate.
+
+Tag: ruby-script-but-no-ruby-dep
+Severity: important
+Certainty: certain
+Info: Packages with Ruby scripts must depend on the package ruby. Those
+ that have Ruby scripts that run under a specific version of Ruby need a
+ dependency on the equivalent version of Ruby.
+ .
+ For example, if a script in the package uses <tt>#!/usr/bin/ruby</tt>,
+ the package needs a dependency on "ruby".  If a script uses
+ <tt>#!/usr/bin/ruby1.9</tt>, then the package need a dependency on
+ "ruby1.9".
+ .
+ In some cases a weaker relationship, such as Suggests or Recommends, will
+ be more appropriate.
+
+Tag: wish-script-but-no-wish-dep
+Severity: important
+Certainty: certain
+Info: Packages that include wish scripts must depend on the virtual
+ package wish or, if they require a specific version of wish or tk, that
+ version of tk.
+ .
+ In some cases a weaker relationship, such as Suggests or Recommends, will
+ be more appropriate.
+
+Tag: tclsh-script-but-no-tclsh-dep
+Severity: important
+Certainty: certain
+Info: Packages that include tclsh scripts must depend on the virtual
+ package tclsh or, if they require a specific version of tcl, that
+ version of tcl.
+ .
+ In some cases a weaker relationship, such as Suggests or Recommends, will
+ be more appropriate.
+
+Tag: calls-suidperl-directly
+Severity: important
+Certainty: certain
+Info: Since perl version 5.8.3-3, /usr/bin/suidperl shouldn't be called
+ directly anymore (and doing so will lead to errors in most cases) but the
+ script should just use /usr/bin/perl as interpreter which will call
+ suidperl automatically if the script has the suid permission bit set.
+
+Tag: shell-script-fails-syntax-check
+Severity: important
+Certainty: certain
+Info: Running this shell script with the shell's -n option set fails,
+ which means that the script has syntax errors.
+ .
+ Run e.g. <tt>sh -n yourscript</tt> to see the errors yourself.
+
+Tag: maintainer-shell-script-fails-syntax-check
+Severity: serious
+Certainty: certain
+Info: Running this shell script with the shell's -n option set fails,
+ which means that the script has syntax errors. This will likely make
+ the package uninstallable.
+ .
+ Run e.g. <tt>sh -n yourscript</tt> to see the errors yourself.
+
+Tag: possibly-insecure-handling-of-tmp-files-in-maintainer-script
+Severity: normal
+Certainty: possible
+Info: The maintainer script seems to access a file in <tt>/tmp</tt> or
+ some other temporary directory. Since creating temporary files in a
+ world-writable directory is very dangerous, this is likely to be a
+ security bug. Use the <tt>tempfile</tt> or <tt>mktemp</tt> utilities to
+ create temporary files in these directories.
+Ref: policy 10.4
+
+Tag: killall-is-dangerous
+Severity: normal
+Certainty: possible
+Info: The maintainer script seems to call <tt>killall</tt>.  Since this
+ utility kills processes by name, it may well end up killing unrelated
+ processes.  Most uses of <tt>killall</tt> should use <tt>invoke-rc.d</tt>
+ instead.
+
+Tag: mknod-in-maintainer-script
+Severity: serious
+Certainty: certain
+Ref: policy 10.6
+Info: Maintainer scripts must not create device files directly.  They
+ should call MAKEDEV instead.
+
+Tag: start-stop-daemon-in-maintainer-script
+Severity: normal
+Certainty: certain
+Info: The maintainer script seems to call <tt>start-stop-daemon</tt>
+ directly.  Long-running daemons should be started and stopped via init
+ scripts using <tt>invoke-rc.d</tt> rather than directly in maintainer
+ scripts.
+Ref: policy 9.3.3.2
+
+Tag: maintainer-script-removes-device-files
+Severity: serious
+Certainty: certain
+Ref: policy 10.6
+Info: Maintainer scripts must not remove device files.  This is left to
+ the system administrator.
+
+Tag: read-in-maintainer-script
+Severity: normal
+Certainty: certain
+Ref: policy 3.9.1 
+Info: This maintainer script appears to use read to get information from
+ the user.  Prompting in maintainer scripts should be done by
+ communicating through a program such as debconf which conforms to the
+ Debian Configuration management specification, version 2 or higher.
+
+Tag: possible-bashism-in-maintainer-script
+Severity: normal
+Certainty: possible
+Ref: policy 10.4
+Info: This script is marked as running under <tt>/bin/sh</tt>, but it seems
+ to use a feature found in bash but not in the SUSv3 or POSIX shell
+ specification.
+ .
+ Examples:
+  '==' in a test, it should use '=' instead
+  'read' without a variable in the argument
+  'function' to define a function
+  'source' instead of '.'
+  '. command args', passing arguments to commands via 'source' is not supported
+  '{foo,bar}' instead of 'foo bar'
+  '[[ test ]]' instead of '[ test ]' (requires a Korn shell)
+  'type' instead of 'which' or 'command -v'
+
+Tag: suidregister-used-in-maintainer-script
+Severity: important
+Certainty: certain
+Info: This script calls suidregister, a long-obsolete program that has
+ been replaced by dpkg-statoverride.
+
+Tag: maintainer-script-needs-depends-on-update-inetd
+Severity: normal
+Certainty: certain
+Info: This script calls update-inetd, but the package does not depend or
+ pre-depend on inet-superserver, any of the providers of inet-superserver
+ which provide it, or update-inetd.
+ .
+ update-inetd has been moved from netbase into a separate package, so a
+ dependency on netbase should be updated to depend on "openbsd-inetd |
+ inet-superserver".
+
+Tag: maintainer-script-needs-depends-on-adduser
+Severity: normal
+Certainty: certain
+Info: This script calls adduser, but the package does not depend or
+ pre-depend on the adduser package.
+
+Tag: maintainer-script-needs-depends-on-gconf2
+Severity: normal
+Certainty: certain
+Info: This script calls gconf-schemas, which comes from the gconf2 package,
+ but does not depend or pre-depend on gconf2.  If you are using dh_gconf,
+ add a dependency on ${misc:Depends} and dh_gconf will take care of this
+ for you.
+
+Tag: maintainer-script-needs-depends-on-ucf
+Severity: normal
+Certainty: certain
+Info: This script calls ucf, but the package does not depend or pre-depend
+ on the ucf package.
+
+Tag: maintainer-script-needs-depends-on-xml-core
+Severity: normal
+Certainty: certain
+Info: This script calls update-xmlcatalog, which comes from the xml-core
+ package, but does not depend or pre-depend on xml-core.  Packages that call
+ update-xmlcatalog need to depend on xml-core.  If you are using
+ dh_installxmlcatalogs, add a dependency on ${misc:Depends} and
+ dh_installxmlcatalogs will take care of this for you.
+
+Tag: update-alternatives-remove-called-in-postrm
+Severity: normal
+Certainty: certain
+Info: <tt>update-alternatives --remove &lt;alternative&gt; foo</tt> is
+ called in the postrm.  This can be dangerous because at the time the
+ postrm is executed foo has already been deleted and update-alternatives
+ will ignore it while constructing its list of available alternatives.
+ Then, if the /etc/alternatives symlink points at foo, update-alternatives
+ won't recognize it and will mark the symlink as something site-specific.
+ As such, the symlink will no longer be updated automatically and will be
+ left dangling until <tt>update-alternatives --auto
+ &lt;alternative&gt;</tt> is run by hand.
+ .
+ <tt>update-alternatives --remove</tt> should be called in the prerm
+ instead.
+Ref: policy F, update-alternatives(8)
+
+Tag: deprecated-chown-usage
+Severity: normal
+Certainty: certain
+Info: <tt>chown user.group</tt> is called in one of the maintainer
+ scripts.  The correct syntax is <tt>chown user:group</tt>. Using "." as a
+ separator is still supported by the GNU tools, but it will fail as soon
+ as a system uses the "." in user or group names.
+Ref: chown(1)
+
+Tag: maintainer-script-hides-init-failure
+Severity: normal
+Certainty: certain
+Info: This script calls invoke-rc.d to run an init script but then, if the
+ init script fails, exits successfully (using || exit 0).  If the init
+ script fails, the maintainer script should probably fail.
+ .
+ The most likely cause of this problem is that the package was built with
+ a debhelper version suffering from Bug#337664 that inserted incorrect
+ invoke-rc.d code in the generated maintainer script. The package needs to
+ be reuploaded (could be bin-NMUd, no source changes needed).
+
+Tag: maintainer-script-calls-init-script-directly
+Severity: serious
+Certainty: certain
+Info: This script apparently runs an init script directly rather than
+ using invoke-rc.d.  The use of invoke-rc.d to invoke the /etc/init.d/*
+ initscripts instead of calling them directly is required.  Maintainer
+ scripts may call the init script directly only if invoke-rc.d is not
+ available.
+Ref: policy 9.3.3.2
+
+Tag: script-calls-init-script-directly
+Severity: normal
+Certainty: possible
+Info: This script apparently runs an init script directly rather than
+ using <tt>invoke-rc.d</tt>.  While use of <tt>invoke-rc.d</tt> is only
+ required for maintainer scripts, supporting the policy layer that it
+ implements is a good idea in any script.
+Ref: policy 9.3.3.2
+
+Tag: gconftool-used-in-maintainer-script
+Severity: normal
+Certainty: possible
+Info: This script apparently runs gconftool or gconftool-2.  It should
+ probably be calling gconf-schemas or update-gconf-defaults instead.
+
+Tag: maintainer-script-uses-dpkg-status-directly
+Severity: important
+Certainty: certain
+Info: The file /var/lib/dpkg/status is internal to dpkg, may disappear or
+ change formats, and is not always a correct and complete record of
+ installed packages while dpkg is running.  Maintainer scripts should use
+ dpkg-query instead.  For the most common case of retrieving conffile
+ information, use:
+ .
+  dpkg-query -W -f='${Conffiles}' &lt;package&gt;
+ .
+ instead.
+Ref: http://wiki.debian.org/DpkgConffileHandling
+
+Tag: maintainer-script-modifies-netbase-managed-file
+Severity: serious
+Certainty: certain
+Info: The maintainer script modifies at least one of the files
+ <tt>/etc/services</tt>, <tt>/etc/protocols</tt>, and <tt>/etc/rpc</tt>,
+ which are managed by the netbase package. Instead of doing this, please
+ file a wishlist bug against netbase to have an appropriate entry added.
+Ref: policy 11.2
+
+Tag: maintainer-script-modifies-inetd-conf
+Severity: serious
+Certainty: certain
+Info: The maintainer script modifies <tt>/etc/inetd.conf</tt> directly.
+ This file must not be modified directly; instead, use the
+ <tt>update-inetd</tt> script or the <tt>DebianNet.pm</tt> Perl module.
+Ref: policy 11.2
+
+Tag: install-sgmlcatalog-deprecated
+Severity: important
+Certainty: certain
+Info: The maintainer script apparently runs install-sgmlcatalog with flags
+ other than <tt>--quiet</tt> and <tt>--remove</tt> or in a maintainer
+ script other than postinst or prerm.  install-sgmlcatalog is deprecated
+ and should only be used in postinst or prerm to remove the entries from
+ earlier packages.  Given how long ago this transition was, consider
+ removing it entirely.
+
+Tag: maintainer-script-empty
+Severity: minor
+Certainty: certain
+Info: The maintainer script doesn't seem to contain any code other than
+ comments and boilerplate (set -e, exit statements, and the case statement
+ to parse options).  While this is harmless in most cases, it is probably
+ not what you wanted, may mean the package will leave unnecessary files
+ behind until purged, and may even lead to problems in rare situations
+ where dpkg would fail if no maintainer script was present.
+ .
+ If the package currently doesn't need to do anything in this maintainer
+ script, it shouldn't be included in the package.
+
+Tag: maintainer-script-ignores-errors
+Severity: normal
+Certainty: certain
+Ref: policy 10.4
+Info: The maintainer script doesn't seem to set the <tt>-e</tt> flag which
+ ensures that the script's execution is aborted when any executed command
+ fails.
+
+Tag: maintainer-script-without-set-e
+Severity: pedantic
+Certainty: certain
+Ref: policy 10.4
+Info: The maintainer script passes <tt>-e</tt> to the shell on the
+ <tt>#!</tt> line rather than using <tt>set -e</tt> in the body of the
+ script.  This is fine for normal operation, but if the script is run by
+ hand with <tt>sh /path/to/script</tt> (common in debugging), <tt>-e</tt>
+ will not be in effect.  It's therefore better to use <tt>set -e</tt> in
+ the body of the script.
+
+Tag: command-with-path-in-maintainer-script
+Severity: normal
+Certainty: certain
+Info: The indicated program run in a maintainer script has a prepended
+ path.  Programs called from maintainer scripts normally should not have a
+ path prepended.  dpkg ensures that the PATH is set to a reasonable value,
+ and prepending a path may prevent the local administrator from using a
+ replacement version of a command for some local reason.
+Ref: policy 6.1
+
+Tag: ancient-dpkg-predepends-check
+Severity: minor
+Certainty: certain
+Info: The package calls dpkg --assert-support-predepends in a maintainer
+ script.  This check is obsolete and has always returned true since dpkg
+ 1.1.0, released 1996-02-11.
+
+Tag: ancient-dpkg-epoch-check
+Severity: minor
+Certainty: certain
+Info: The package calls dpkg --assert-working-epoch in a maintainer
+ script.  This check is obsolete and has always returned true since dpkg
+ 1.4.0.7, released 1997-01-25.
+
+Tag: ancient-dpkg-long-filenames-check
+Severity: minor
+Certainty: certain
+Info: The package calls dpkg --assert-long-filenames in a maintainer
+ script.  This check is obsolete and has always returned true since dpkg
+ 1.4.1.17, released 1999-10-21.
+
+Tag: ancient-dpkg-multi-conrep-check
+Severity: minor
+Certainty: certain
+Info: The package calls dpkg --assert-multi-conrep in a maintainer
+ script.  This check is obsolete and has always returned true since dpkg
+ 1.4.1.19, released 1999-10-30.
+
+Tag: package-uses-local-diversion
+Severity: serious
+Certainty: certain
+Info: The maintainer script calls dpkg-divert with <tt>--local</tt> or
+ without <tt>--package</tt>.  This option is reserved for local
+ administrators and must never be used by a Debian package.
+
+Tag: diversion-for-unknown-file
+Severity: important
+Certainty: certain
+Info: The maintainer script adds a diversion for a file that is not
+ provided by this package.
+
+Tag: orphaned-diversion
+Severity: important
+Certainty: certain
+Info: A diversion was added for the file, but not removed. This means
+ your package doesn't restore the previous state after removal.
+
+Tag: remove-of-unknown-diversion
+Severity: important
+Certainty: certain
+Info: The maintainer script removes a diversion that it didn't add.  If
+ you're cleaning up unnecessary diversions from older versions of the
+ package, remove them in <tt>preinst</tt> or <tt>postinst</tt> instead of
+ waiting for <tt>postrm</tt> to do it.
diff --git a/checks/shared-libs b/checks/shared-libs
new file mode 100644 (file)
index 0000000..aae06de
--- /dev/null
@@ -0,0 +1,643 @@
+# shared-libs -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Christian Schwarz
+#
+# 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 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.
+
+package Maemian::shared_libs;
+use strict;
+
+use File::Basename;
+
+use Maemian::Data;
+use Maemian::Relation;
+use Tags;
+use Util;
+
+# Libraries that should only be used in the presence of certain capabilities
+# may be located in subdirectories of the standard ldconfig search path with
+# one of the following names.
+my %hwcap_dir = map { $_ => 1 }
+    qw(i486 i586 i686 cmov tls);
+
+# The following architectures should always have a STACK setting in shared
+# libraries to disable executable stack.  Other architectures don't always add
+# this section and therefore can't be checked.
+our %stack_arches = map { $_ => 1 }
+    qw(
+       alpha
+       amd64
+       i386
+       m68k
+       powerpc
+       s390
+       sparc
+      );
+
+our $ldconfig_dirs = Maemian::Data->new('shared-libs/ldconfig-dirs');
+
+sub run {
+
+my $file;
+my $must_call_ldconfig;
+my %SONAME;
+my %sharedobject;
+my @shlibs;
+my @words;
+
+# ---end-of-configuration-part---
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+my $objdump = $info->objdump_info;
+
+# 1st step: get info about shared libraries installed by this package
+foreach my $file (sort keys %{$objdump}) {
+    $SONAME{$file} = $objdump->{$file}->{SONAME}[0]
+       if defined $objdump->{$file}->{SONAME};
+}
+
+foreach my $file (sort keys %{$info->file_info}) {
+    next unless length $file;
+    my $fileinfo = $info->file_info->{$file};
+    if ($fileinfo =~ m/^[^,]*\bELF\b/ && $fileinfo =~ m/shared object/) {
+       $sharedobject{$file} = 1;
+    }
+}
+
+# 2nd step: read package contents
+
+for my $cur_file (sort keys %{$info->index}) {
+    # shared library?
+    my $cur_file_data = $info->index->{$cur_file};
+
+    if (exists $SONAME{$cur_file} or 
+       (defined $cur_file_data->{link} and exists $SONAME{abs_path(dirname($cur_file)."/".$cur_file_data->{link})})) {
+       # yes!!
+       my ($real_file, $real_perm);
+       if ($SONAME{$cur_file}) {
+           $real_file = $cur_file;
+           $real_perm = $cur_file_data->{operm};
+       } else {
+           $real_file = abs_path(dirname($cur_file)."/".$cur_file_data->{link});
+           $real_perm = $info->index->{$real_file}->{operm} || $cur_file_data->{operm};
+       }
+
+       # Now that we're sure this is really a shared library, report on
+       # non-PIC problems.
+        if ($cur_file eq $real_file and $objdump->{$cur_file}->{TEXTREL}) {
+            tag "shlib-with-non-pic-code", "$cur_file";
+        }
+
+       my @symbol_names = map { @{$_}[2] } @{$objdump->{$cur_file}->{SYMBOLS}};
+       if (grep /^_?exit$/, @symbol_names and !grep /^fork$/, @symbol_names) {
+           tag "shlib-calls-exit", "$cur_file";
+       }
+
+        # don't apply the permission checks to links
+        # since this only results in doubled messages
+        if ($cur_file eq $real_file) { 
+            # executable?
+            if ($real_perm & 0100 or $real_perm & 010 or $real_perm & 01) {
+                # yes.  But if the library has an INTERP section, it's designed
+                # to do something useful when executed, so don't report an error.
+                tag "shlib-with-executable-bit", $cur_file, sprintf("%04o", $real_perm)
+                    unless $objdump->{$real_file}->{INTERP};
+            } elsif ($real_perm != 0644) {
+                # bad permissions
+                tag "shlib-with-bad-permissions", $cur_file, sprintf("%04o", $real_perm);
+            }
+        }
+
+       # Installed in a directory controlled by the dynamic linker?  We have
+       # to strip off directories named for hardware capabilities.
+       my $dirname = dirname($cur_file);
+       my $last;
+       do {
+           $dirname =~ s%/([^/]+)$%%;
+           $last = $1;
+       } while ($last && $hwcap_dir{$last});
+       $dirname .= "/$last" if $last;
+       if ($ldconfig_dirs->known($dirname)) {
+           # yes! so postinst must call ldconfig
+           $must_call_ldconfig = $real_file;
+       }
+
+       # executable stack.  We can only warn about a missing section on some
+       # architectures.  Only warn if there's an Architecture field; if
+       # that's missing, we'll already be complaining elsewhere.
+       if (exists $objdump->{$cur_file}->{OTHER_DATA}) {
+           if (not defined $objdump->{$cur_file}->{STACK}) {
+               if (defined $info->field('architecture')) {
+                   my $arch = $info->field('architecture');
+                   tag "shlib-without-PT_GNU_STACK-section", $cur_file
+                       if $stack_arches{$arch};
+               }
+           } elsif ($objdump->{$cur_file}->{STACK} ne "rw-") {
+               tag "shlib-with-executable-stack", $cur_file;
+           }
+       }
+    } elsif (exists $objdump->{$cur_file}->{OTHER_DATA}
+            && $ldconfig_dirs->known(dirname($cur_file))
+            && exists $sharedobject{$cur_file}) {
+       tag "sharedobject-in-library-directory-missing-soname", "$cur_file";
+    } elsif ($cur_file =~ m/\.la$/ and not defined($cur_file_data->{link})) {
+       local $_;
+       open(LAFILE, "< unpacked/$cur_file")
+           or fail("Could not open unpacked/$cur_file for reading!");
+       while(<LAFILE>) {
+           next unless (m/^libdir='(.+?)'$/);
+           my $actual = $1;
+           $actual =~ s,/+$,,;
+           my ($expected) = ("/$cur_file" =~ m,^(.+)/[^/]+$,);
+
+           # python-central is a special case since the libraries are moved
+           # at install time.
+           next if ($actual =~ m,^/usr/lib/python[\d.]+/site-packages,
+                    and $expected =~ m,^/usr/share/pyshared,);
+           tag "incorrect-libdir-in-la-file", $cur_file, "$actual != $expected"
+               unless($expected eq $actual);
+           last;
+       }
+       close(LAFILE);
+    }
+}
+
+close(IN);
+
+# 3rd step: check if shlib symlinks are present and in correct order
+for my $shlib_file (keys %SONAME) {
+    # file found?
+    if (not exists $info->index->{$shlib_file}) {
+       fail("shlib $shlib_file not found in package (should not happen!)");
+    }
+
+    my ($dir, $shlib_name) = $shlib_file =~ m,(.*)/([^/]+)$,;
+
+    # not a public shared library, skip it
+    next unless $ldconfig_dirs->known($dir);
+
+    # symlink found?
+    my $link_file = "$dir/$SONAME{$shlib_file}";
+    if (not exists $info->index->{$link_file}) {
+       tag "ldconfig-symlink-missing-for-shlib", "$link_file $shlib_file $SONAME{$shlib_file}";
+    } else {
+       # $link_file really another file?
+       if ($link_file eq $shlib_file) {
+           # the library file uses its SONAME, this is ok...
+       } else {
+           # $link_file really a symlink?
+           if (exists $info->index->{$link_file}->{link}) {
+               # yes.
+
+               # $link_file pointing to correct file?
+               if ($info->index->{$link_file}->{link} eq $shlib_name) {
+                   # ok.
+               } else {
+                   tag "ldconfig-symlink-referencing-wrong-file",
+                       "$link_file -> " . $info->index->{$link_file}->{link} . " instead of $shlib_name";
+               }
+           } else {
+               tag "ldconfig-symlink-is-not-a-symlink", "$shlib_file $link_file";
+           }
+
+           # symlink after shlib?
+           #if ($info->index->{$link_file} < $info->index->{$shlib_file}) {
+           #    tag "ldconfig-symlink-before-shlib-in-deb", "$link_file";
+           #}
+       }
+    }
+
+    # determine shlib link name (w/o version)
+    $link_file =~ s/\.so.*$/.so/o;
+
+    # -dev package?
+    if ($pkg =~ m/\-dev$/o) {
+       # yes!!
+
+       # need shlib symlink
+       if (not exists $info->index->{$link_file}) {
+           tag "dev-pkg-without-shlib-symlink", "$shlib_file $link_file";
+       }
+    } else {
+       # no.
+
+       # shlib symlink may not exist.
+       # if shlib doesn't _have_ a version, then $link_file and $shlib_file will
+       # be equal, and it's not a development link, so don't complain.
+       if (exists $info->index->{$link_file} and $link_file ne $shlib_file) {
+           tag "non-dev-pkg-with-shlib-symlink", "$shlib_file $link_file";
+       }
+    }
+}
+
+# 4th step: check shlibs control file
+my $version;
+if (defined $info->field('version')) {
+    $version = $info->field('version');
+}
+my $provides = $pkg . "( = $version)";
+if (defined $info->field('provides')) {
+    $provides .= ", " . $info->field('provides');
+}
+$provides = Maemian::Relation->new($provides);
+
+my %shlibs_control;
+my %symbols_control;
+
+# Libraries with no version information can't be represented by the shlibs
+# format (but can be represented by symbols).  We want to warn about them if
+# they appear in public directories.  If they're in private directories,
+# assume they're plugins or private libraries and are safe.
+my %unversioned_shlibs;
+for (keys %SONAME) {
+    my $soname = format_soname($SONAME{$_});
+    if ($soname !~ / /) {
+       $unversioned_shlibs{$_} = 1;
+       tag 'shlib-without-versioned-soname', $_, $soname
+           if $ldconfig_dirs->known(dirname($_));
+    }
+}
+@shlibs = grep { !$unversioned_shlibs{$_} } keys %SONAME;
+
+if ($#shlibs == -1) {
+    # no shared libraries included in package, thus shlibs control file should
+    # not be present
+    if (-f 'control/shlibs') {
+       tag "pkg-has-shlibs-control-file-but-no-actual-shared-libs", "";
+    }
+} else {
+    # shared libraries included, thus shlibs control file has to exist
+    if (not -f 'control/shlibs') {
+       if ($type ne 'udeb') {
+           for my $shlib (@shlibs) {
+               # skip it if it's not a public shared library
+               next unless $ldconfig_dirs->known(dirname($shlib));
+               tag "no-shlibs-control-file", "$shlib";
+           }
+       }
+    } else {
+       my %shlibs_control_used;
+       my @shlibs_depends;
+       open(SHLIBS, '<', 'control/shlibs')
+           or fail("cannot open control/shlibs for reading: $!");
+       while (<SHLIBS>) {
+           chop;
+           next if m/^\s*$/ or /^#/;
+
+           # We exclude udebs from the checks for correct shared library
+           # dependencies, since packages may contain dependencies on
+           # other udeb packages.
+           my $udeb="";
+           $udeb = "udeb: " if s/^udeb:\s+//o;
+           @words = split(/\s+/o,$_);
+           my $shlibs_string = $udeb.$words[0].' '.$words[1];
+           if ($shlibs_control{$shlibs_string}) {
+               tag "duplicate-entry-in-shlibs-control-file", $shlibs_string;
+           } else {
+               $shlibs_control{$shlibs_string} = 1;
+               push (@shlibs_depends, join (' ', @words[2 .. $#words]))
+                   unless $udeb;
+           }
+       }
+       close(SHLIBS);
+       my $shlib_name;
+       for my $shlib (@shlibs) {
+           $shlib_name = $SONAME{$shlib};
+           $shlib_name = format_soname($shlib_name);
+           $shlibs_control_used{$shlib_name} = 1;
+           $shlibs_control_used{"udeb: ".$shlib_name} = 1;
+           unless (exists $shlibs_control{$shlib_name}) {
+               # skip it if it's not a public shared library
+               next unless $ldconfig_dirs->known(dirname($shlib));
+               # no!!
+               tag "shlib-missing-in-control-file", $shlib_name, 'for', $shlib;
+           }
+       }
+       for $shlib_name (keys %shlibs_control) {
+           tag "unused-shlib-entry-in-control-file", $shlib_name
+               unless $shlibs_control_used{$shlib_name};
+       }
+
+       # Check that all of the packages listed as dependencies in the shlibs
+       # file are satisfied by the current package or its Provides.
+       # Normally, packages should only declare dependencies in their shlibs
+       # that they themselves can satisfy.
+       #
+       # Deduplicate the list of dependencies before warning so that we don't
+       # dupliate warnings.
+       my %seen;
+       @shlibs_depends = grep { !$seen{$_}++ } @shlibs_depends;
+       for my $depend (@shlibs_depends) {
+           unless ($provides->implies($depend)) {
+               tag "shlibs-declares-dependency-on-other-package", $depend;
+           }
+       }
+    }
+}
+
+# 5th step: check symbols control file.  Add back in the unversioned shared
+# libraries, since they can still have symbols files.
+if ($#shlibs == -1 and not %unversioned_shlibs) {
+    # no shared libraries included in package, thus symbols control file should
+    # not be present
+    if (-f 'control/symbols') {
+        tag "pkg-has-symbols-control-file-but-no-shared-libs", "";
+    }
+} elsif (not -f 'control/symbols') {
+    if ($type ne 'udeb') {
+       for my $shlib (@shlibs, keys %unversioned_shlibs) {
+           # skip it if it's not a public shared library
+           next unless $ldconfig_dirs->known(dirname($shlib));
+           tag "no-symbols-control-file", "$shlib";
+       }
+    }
+} elsif (open(IN, '<', 'control/symbols')) {
+    my $version_wo_rev = $version;
+    $version_wo_rev =~ s/^(.+)-([^-]+)$/$1/;
+    my ($full_version_count, $full_version_sym) = (0, undef);
+    my ($debian_revision_count, $debian_revision_sym) = (0, undef);
+    my ($soname, $dep_package, $dep);
+    my %symbols_control_used;
+    my @symbols_depends;
+    my $dep_templates = 0;
+    my $meta_info_seen = 0;
+    my $warned = 0;
+    my $symbol_count = 0;
+
+    while (<IN>) {
+       chomp;
+       next if m/^\s*$/ or /^#/;
+
+       if (m/^([^\s|*]\S+)\s\S+\s*(?:\(\S+\s+\S+\)|\#MINVER\#)?/) {
+           # soname, main dependency template
+
+           $soname = $1;
+           s/^\Q$soname\E\s*//;
+           $soname = format_soname($soname);
+
+           if ($symbols_control{$soname}) {
+               tag "duplicate-entry-in-symbols-control-file", $soname;
+           } else {
+               $symbols_control{$soname} = 1;
+               $warned = 0;
+
+               foreach my $part (split /\s*,\s*/) {
+                   foreach my $subpart (split /\s*\|\s*/, $part) {
+                       $subpart =~ m,^(\S+)\s*(\(\S+\s+\S+\)|#MINVER#)?$,;
+                       ($dep_package, $dep) = ($1, $2 || '');
+                       if (defined $dep_package) {
+                           push @symbols_depends, $dep_package . ' ' . $dep;
+                       } else {
+                           tag "syntax-error-in-symbols-file", $.
+                               unless $warned;
+                           $warned = 1;
+                       }
+                   }
+               }
+           }
+
+           $dep_templates = 0;
+           $meta_info_seen = 0;
+           $symbol_count = 0;
+       } elsif (m/^\|\s+\S+\s*(?:\(\S+\s+\S+\)|#MINVER#)?/) {
+           # alternative dependency template
+
+           $warned = 0;
+
+           if ($meta_info_seen or not defined $soname) {
+               tag "syntax-error-in-symbols-file", $.;
+               $warned = 1;
+           }
+
+           s/^\|\s*//;
+
+           foreach my $part (split /\s*,\s*/) {
+               foreach my $subpart (split /\s*\|\s*/, $part) {
+                   $subpart =~ m,^(\S+)\s*(\(\S+\s+\S+\)|#MINVER#)?$,;
+                   ($dep_package, $dep) = ($1, $2 || '');
+                   if (defined $dep_package) {
+                       push @symbols_depends, $dep_package . ' ' . $dep;
+                   } else {
+                       tag "syntax-error-in-symbols-file", $. unless $warned;
+                       $warned = 1;
+                   }
+               }
+           }
+
+           $dep_templates++ unless $warned;
+       } elsif (m/^\*\s(\S+):\s\S+/) {
+           # meta-information
+
+           # This should probably be in a hash, but there's
+           # only one supported value currently
+           tag "unknown-meta-field-in-symbols-file", "$1, line $."
+               unless $1 eq 'Build-Depends-Package';
+           tag "syntax-error-in-symbols-file", $.
+               unless defined $soname and $symbol_count == 0;
+
+           $meta_info_seen = 1;
+       } elsif (m/^\s+(\S+)\s(\S+)(?:\s(\S+(?:\s\S+)?))?$/) {
+           # Symbol definition
+
+           tag "syntax-error-in-symbols-file", $.
+               unless defined $soname;
+
+           $symbol_count++;
+           my ($sym, $v, $dep_order) = ($1, $2, $3);
+           $dep_order ||= '';
+
+           if (($v eq $version) and ($version =~ /-/)) {
+               $full_version_sym ||= $sym;
+               $full_version_count++;
+           } elsif (($v =~ /-/) and (not $v =~ /~$/) and ($v ne $version_wo_rev)) {
+               $debian_revision_sym ||= $sym;
+               $debian_revision_count++;
+           }
+
+           if (length $dep_order) {
+               if ($dep_order !~ /^\d+$/ or $dep_order > $dep_templates) {
+                   tag "invalid-template-id-in-symbols-file", $.;
+               }
+           }
+       } else {
+           # Unparseable line
+
+           tag "syntax-error-in-symbols-file", $.;
+       }
+    }
+    close IN;
+    if ($full_version_count) {
+       $full_version_count--;
+       my $others = '';
+       if ($full_version_count > 0) {
+           $others = " and $full_version_count others";
+       }
+       tag "symbols-file-contains-current-version-with-debian-revision",
+           "on symbol $full_version_sym$others";
+    }
+    if ($debian_revision_count) {
+       $debian_revision_count--;
+       my $others = '';
+       if ($debian_revision_count > 0) {
+           $others = " and $debian_revision_count others";
+       }
+       tag "symbols-file-contains-debian-revision",
+           "on symbol $debian_revision_sym$others";
+    }
+    my $shlib_name;
+    for my $shlib (@shlibs, keys %unversioned_shlibs) {
+       $shlib_name = $SONAME{$shlib};
+       $shlib_name = format_soname($shlib_name);
+       $symbols_control_used{$shlib_name} = 1;
+       $symbols_control_used{"udeb: ".$shlib_name} = 1;
+       unless (exists $symbols_control{$shlib_name}) {
+           # skip it if it's not a public shared library
+           next unless $ldconfig_dirs->known(dirname($shlib));
+           tag "shlib-missing-in-symbols-control-file", $shlib_name, 'for', $shlib;
+       }
+    }
+    for $shlib_name (keys %symbols_control) {
+       tag "unused-shlib-entry-in-symbols-control-file", $shlib_name
+           unless $symbols_control_used{$shlib_name};
+    }
+
+    # Check that all of the packages listed as dependencies in the symbols
+    # file are satisfied by the current package or its Provides.
+    # Normally, packages should only declare dependencies in their symbols
+    # files that they themselves can satisfy.
+    #
+    # Deduplicate the list of dependencies before warning so that we don't
+    # dupliate warnings.
+    my %seen;
+    @symbols_depends = grep { !$seen{$_}++ } @symbols_depends;
+    for my $depend (@symbols_depends) {
+       unless ($provides->implies($depend)) {
+           tag "symbols-declares-dependency-on-other-package", $depend;
+       }
+    } 
+}
+
+# Compare the contents of the shlibs and symbols control files, but exclude
+# from this check shared libraries whose SONAMEs has no version.  Those can
+# only be represented in symbols files and aren't expected in shlibs files.
+if (keys %shlibs_control and keys %symbols_control) {
+    for my $key (keys %symbols_control) {
+       unless (exists $shlibs_control{$key} or $key !~ / /) {
+           tag "symbols-declared-but-not-shlib", $key;
+       }
+    }
+}
+
+# 6th step: check pre- and post- control files
+if (-f 'control/preinst') {
+    local $_ = slurp_entire_file('control/preinst');
+    if (/^[^\#]*\bldconfig\b/m) {
+       tag "preinst-calls-ldconfig", ""
+    }
+}
+
+my $we_call_postinst=0;
+if (-f 'control/postinst') {
+    local $_ = slurp_entire_file('control/postinst');
+
+    # Decide if we call ldconfig
+    if (/^[^\#]*\bldconfig\b/m) {
+       $we_call_postinst=1;
+    }
+}
+
+if ($type eq 'udeb') {
+    tag "udeb-postinst-must-not-call-ldconfig"
+       if $we_call_postinst;
+} else {
+    tag "postinst-has-useless-call-to-ldconfig", ""
+       if $we_call_postinst and not $must_call_ldconfig;
+    tag "postinst-must-call-ldconfig", "$must_call_ldconfig"
+       if not $we_call_postinst and $must_call_ldconfig;
+}
+
+if (-f 'control/prerm') {
+    local $_ = slurp_entire_file('control/prerm');
+    if (/^[^\#]*\bldconfig\b/m) {
+       tag "prerm-calls-ldconfig", "";
+    }
+}
+
+if (-f 'control/postrm') {
+    local $_ = slurp_entire_file('control/postrm');
+
+    # Decide if we call ldconfig
+    if (/^[^\#]*\bldconfig\b/m) {
+       tag "postrm-has-useless-call-to-ldconfig", ""
+           unless $must_call_ldconfig;
+    } else {
+       tag "postrm-should-call-ldconfig", "$must_call_ldconfig"
+           if $must_call_ldconfig;
+    }
+
+    # Decide if we do it safely
+    s/\bldconfig\b/BldconfigB/g;
+    s/[ \t]//g;
+    # this one matches code from debhelper
+    s/^if\["\$1"=.?remove.?\];?\n*then\n*BldconfigB//gm;
+    # variations...
+    s/^if\[.?remove.?="\$1"\];?\n*then\n*BldconfigB//gm;
+    s/^\["\$1"=.?remove.?\]\&&BldconfigB//gm;
+    s/^\[.?remove.?="\$1"\]&&BldconfigB//gm;
+    s/remove(?:\|[^)]+)*\).*?BldconfigB.*?(;;|esac)//s;
+
+    if (/^[^\#]*BldconfigB/m) {
+        tag "postrm-unsafe-ldconfig", "";
+    }
+}
+
+}
+
+# make /tmp/baz/baz.txt from /tmp/foo/../bar/../baz/baz.txt
+sub abs_path {
+    my $path = shift;
+    while($path =~ s!/[^/]*/\.\./!/!g){1};
+    return $path;
+}
+
+# Extract the library name and the version from an SONAME and return them
+# separated by a space.  This code should match the split_soname function in
+# dpkg-shlibdeps.
+sub format_soname {
+    my $soname = shift;
+
+    # libfoo.so.X.X
+    if ($soname =~ /^(.*)\.so\.(.*)$/) {
+       $soname = "$1 $2";
+    # libfoo-X.X.so
+    } elsif ($soname =~ /^(.*)-(\d.*)\.so$/) {
+       $soname = "$1 $2";
+    }
+
+    return $soname
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 ts=8
diff --git a/checks/shared-libs.desc b/checks/shared-libs.desc
new file mode 100644 (file)
index 0000000..01fecd0
--- /dev/null
@@ -0,0 +1,412 @@
+Check-Script: shared-libs
+Author: Christian Schwarz <schwarz@debian.org>
+Abbrev: shl
+Type: binary, udeb
+Unpack-Level: 2
+Needs-Info: file-info, objdump-info
+Info: This script checks if a binary package conforms to shared library policy.
+
+Tag: shlib-with-executable-bit
+Severity: important
+Certainty: certain
+Info: Shared libraries should be mode 0644.
+Ref: policy 8.1
+
+Tag: shlib-with-bad-permissions
+Severity: normal
+Certainty: certain
+Info: Shared libraries should be mode 0644.
+Ref: policy 8.1
+
+Tag: shlib-with-non-pic-code
+Severity: serious
+Certainty: possible
+Ref: policy 10.2
+Info: The listed shared libraries contain object code that was compiled 
+ without -fPIC. All object code in shared libraries should be recompiled
+ separately from the static libraries with the -fPIC option. 
+ .
+ Another common mistake that causes this problem is linking with 
+ <tt>gcc -Wl,-shared</tt> instead of <tt>gcc -shared</tt>.
+ .
+ In some cases, exceptions to this rule are warranted. If this is such a
+ case, follow the procedure outlined in Policy and then please document
+ the exception by adding a lintian override to this package.
+ .
+ To check whether a shared library has this problem, run <tt>readelf
+ -d</tt> on the shared library.  If a tag of type TEXTREL is present, the
+ shared library contains non-PIC code.
+
+Tag: shlib-without-versioned-soname
+Severity: normal
+Certainty: possible
+Ref: policy 10.2, policy 8.6
+Info: The listed shared library in a public library directory has an
+ SONAME that does not contain any versioning information, either after the
+ <tt>.so</tt> or before it and set off by a hyphen.  It cannot therefore
+ be represented in the shlibs system, and if linked by binaries its
+ interface cannot safely change.  There is no backward-compatible way to
+ migrate programs linked against it to a new ABI.
+ .
+ Normally, this means the shared library is a private library for a
+ particular application and is not meant for general use.  Policy
+ recommends that such libraries be installed in a subdirectory of
+ <tt>/usr/lib</tt> rather than in a public shared library directory.
+ .
+ To view the SONAME of a shared library, run <tt>readelf -d</tt> on the
+ shared library and look for the tag of type SONAME.
+ .
+ There are some special stub libraries or special-purpose shared objects
+ for which an ABI version is not meaningful.  If this is one of those
+ cases, please add an override.
+
+Tag: ldconfig-symlink-missing-for-shlib
+Severity: important
+Certainty: certain
+Info: The package should not only include the shared library itself, but also
+ the symbolic link which ldconfig would produce. (This is necessary, so 
+ that the link gets removed by dpkg automatically when the package
+ gets removed.)  If the symlink is in the package, check that the SONAME of the
+ library matches the info in the shlibs file.
+Ref: policy 8.1
+
+Tag: ldconfig-symlink-before-shlib-in-deb
+Severity: important
+Certainty: certain
+Info: In the package contents list, the shared library has to come before
+ any symbolic links referencing the shared library.
+Ref: policy 8.1
+
+Tag: dev-pkg-without-shlib-symlink
+Severity: normal
+Certainty: certain
+Info: A "-dev" package is supposed to install a "libsomething.so" symbolic
+ link referencing the corresponding shared library. Notice how the link name
+ doesn't include the version number -- this is because such a link is used
+ by the linker when other programs are built against this shared library.
+Ref: policy 8.4
+
+Tag: non-dev-pkg-with-shlib-symlink
+Severity: normal
+Certainty: possible
+Info: Although this package is not a "-dev" package, it installs a
+ "libsomething.so" symbolic link referencing the corresponding shared
+ library. When the link doesn't include the version number, it is used by
+ the linker when other programs are built against this shared library.
+ .
+ Shared libraries are supposed to place such symbolic links in their
+ respective "-dev" packages, so it is a bug to include it with the main
+ library package.
+ .
+ However, if this is a small package which includes the runtime and the
+ development libraries, this is not a bug. In the latter case, please
+ override this warning.
+Ref: policy 8.4
+
+Tag: preinst-calls-ldconfig
+Severity: normal
+Certainty: certain
+Info: The preinst script calls ldconfig.  Calls to ldconfig should only be
+ in postinst and postrm scripts.
+Ref: policy 8.1.1
+
+Tag: prerm-calls-ldconfig
+Severity: normal
+Certainty: certain
+Info: The prerm script calls ldconfig.  Calls to ldconfig should only
+ be in postinst and postrm scripts.
+Ref: policy 8.1.1
+
+Tag: postrm-unsafe-ldconfig
+Severity: normal
+Certainty: certain
+Info: The postrm script calls ldconfig unsafely.  The postrm
+ must only call ldconfig when given the argument "remove".
+Ref: policy 8.1.1
+
+Tag: no-shlibs-control-file
+Severity: serious
+Certainty: possible
+Info: Although the package includes a shared library, the package does not
+ have a shlibs control file. If this is intentional, please override this
+ error.
+Ref: policy 8.6
+
+Tag: pkg-has-shlibs-control-file-but-no-actual-shared-libs
+Severity: important
+Certainty: certain
+Info: Although the package does not include any shared libraries, it does
+ have a shlibs control file. If you did include a shared library, check that
+ the SONAME of the library is set and that it matches the contents of the
+ shlibs file.
+ .
+ SONAMEs are set with something like <tt>gcc -Wl,-soname,libfoo.so.0</tt>,
+ where 0 is the major version of the library. If your package uses libtool,
+ then libtool invoked with the right options should be doing this.
+
+Tag: duplicate-entry-in-shlibs-control-file
+Severity: important
+Certainty: certain
+Info: The shlibs control file contains a duplicate entry.
+
+Tag: shlib-missing-in-control-file
+Severity: important
+Certainty: possible
+Info: The package contains a shared library that is not listed in the
+ shlibs control file. If this is intentional, please override this error.
+Ref: policy 8.6
+
+Tag: unused-shlib-entry-in-control-file
+Severity: normal
+Certainty: certain
+Info: The shlibs control file contains an entry for a shared library that
+ is not installed by this package.
+Ref: policy 8.6
+
+Tag: shlibs-declares-dependency-on-other-package
+Severity: normal
+Certainty: possible
+Info: This package declares in its shlibs control file either a dependency
+ on some other package not listed in the Provides of this package or on a
+ version of this package that the package version doesn't satisfy.
+ .
+ Packages should normally only list in their shlibs control file the
+ shared libraries included in that package, and therefore the dependencies
+ listed there should normally be satisfied by either the package itself or
+ one of its Provides.
+ .
+ In unusual circumstances where it's necessary to declare more complex
+ dependencies in the shlibs control file, please add a lintian override
+ for this warning.
+Ref: policy 8.6
+
+Tag: ldconfig-symlink-referencing-wrong-file
+Severity: important
+Certainty: certain
+Info: The symbolic link references the wrong file. (It should reference
+ the shared library.)
+Ref: policy 8.1
+
+Tag: ldconfig-symlink-is-not-a-symlink
+Severity: important
+Certainty: certain
+Info: The package installs a file with the name, ldconfig would use for
+ the symbolic link to reference the shared library.
+Ref: policy 8.1
+
+Tag: postinst-has-useless-call-to-ldconfig
+Severity: minor
+Certainty: certain
+Info: The postinst script calls ldconfig even though no shared libraries are
+ installed in a directory controlled by the dynamic library loader.
+Ref: policy 8.1.1
+
+Tag: udeb-postinst-must-not-call-ldconfig
+Severity: important
+Certainty: certain
+Info: The postinst script calls ldconfig, which is an error in udebs.
+ ldconfig is not available and not needed in debian-installer
+
+Tag: postrm-has-useless-call-to-ldconfig
+Severity: minor
+Certainty: certain
+Info: The postrm script calls ldconfig even though no shared libraries are
+ installed in a directory controlled by the dynamic library loader.
+Ref: policy 8.1.1
+
+Tag: postinst-must-call-ldconfig
+Severity: serious
+Certainty: certain
+Info: The package installs shared libraries in a directory controlled by
+ the dynamic library loader. Therefore, the package must call "ldconfig" in
+ its postinst script.
+Ref: policy 8.1.1
+
+Tag: postrm-should-call-ldconfig
+Severity: important
+Certainty: certain
+Info: The package installs shared libraries in a directory controlled by
+ the dynamic library loader. Therefore, the package should call "ldconfig"
+ in its postrm script.
+Ref: policy 8.1.1
+
+Tag: sharedobject-in-library-directory-missing-soname
+Severity: important
+Certainty: possible
+Info: A shared object was identified in a library directory (a directory
+ in the standard linker path) which doesn't have a SONAME.  This is
+ usually an error.
+ .
+ SONAMEs are set with something like <tt>gcc -Wl,-soname,libfoo.so.0</tt>,
+ where 0 is the major version of the library. If your package uses libtool,
+ then libtool invoked with the right options should be doing this.
+ .
+ To view the SONAME of a shared library, run <tt>readelf -d</tt> on the
+ shared library and look for the tag of type SONAME.
+
+Tag: shlib-without-PT_GNU_STACK-section
+Severity: important
+Certainty: certain
+Info: The listed shared libraries lacks a PT_GNU_STACK section. This forces
+ the dynamic linker to make the stack executable.
+ .
+ The shared lib is linked either with a non-GNU linker or a linker which is
+ very old. This problem can be fixed with a rebuild.
+ .
+ To see whether a shared library has this section, run <tt>readelf -l</tt>
+ on it and look for a program header of type GNU_STACK.
+
+Tag: shlib-with-executable-stack
+Severity: normal
+Certainty: possible
+Info: The listed shared libraries declares the stack as executable.
+ .
+ Executable stack is usually an error as it is only needed if the code
+ contains GCC trampolines or similar constructs which uses code on the
+ stack. One possible source for false positives are object files built
+ from assembler files which don't define a proper .note.GNU-stack
+ section.
+ .
+ To see the permissions on the stack, run <tt>readelf -l</tt> on the
+ shared library and look for the program header of type GNU_STACK.  In the
+ flag column, there should not be an E flag set.
+
+Tag: symbols-file-contains-current-version-with-debian-revision
+Severity: important
+Certainty: certain
+Info: Debian revisions should be stripped from versions in symbols files.
+ Not doing so leads to dependencies unsatisfiable by backports (1.0-1~bpo
+ &lt;&lt; 1.0-1 while 1.0-1~bpo &gt;= 1.0).  If the debian revision can't
+ be stripped because the symbol really appeared between two specific
+ Debian revisions, you should postfix the version with a single "~"
+ (example: 1.0-3~ if the symbol appeared in 1.0-3).
+ .
+ This problem normally means that the symbols were added automatically by
+ dpkg-gensymbols.  dpkg-gensymbols uses the full version number for the
+ dependency associated to any new symbol that it detects.  The maintainer
+ must update the <tt>debian/&lt;package&gt;.symbols</tt> file by adding
+ the new symbols with the corresponding upstream version.
+
+Tag: symbols-file-contains-debian-revision
+Severity: normal
+Certainty: certain
+Info: Debian revisions should be stripped from versions in symbols files.
+ Not doing so leads to dependencies unsatisfiable by backports (1.0-1~bpo
+ &lt;&lt; 1.0-1 while 1.0-1~bpo &gt;= 1.0).  If the debian revision can't
+ be stripped because the symbol really appeared between two specific
+ Debian revisions, you should postfix the version with a single "~"
+ (example: 1.0-3~ if the symbol appeared in 1.0-3).
+Ref: dpkg-gensymbols(1), http://wiki.debian.org/UsingSymbolsFiles
+
+Tag: syntax-error-in-symbols-file
+Severity: important
+Certainty: certain
+Info: The symbols file contains an entry that does not follow the syntax
+ rules for symbols files.
+ .
+ This may be due to the entry appearing out of sequence.
+Ref: deb-symbols(5)
+
+Tag: duplicate-entry-in-symbols-control-file
+Severity: important
+Certainty: certain
+Info: The symbols control file contains a duplicate entry.
+
+Tag: no-symbols-control-file
+Severity: wishlist
+Certainty: certain
+Info: Although the package includes a shared library, the package does not
+ have a symbols control file.
+ .
+ dpkg can use symbols files in order to generate more accurate library
+ dependencies for applications, based on the symbols from the library that
+ are actually used by the application.
+Ref: dpkg-gensymbols(1), http://wiki.debian.org/UsingSymbolsFiles
+
+Tag: pkg-has-symbols-control-file-but-no-shared-libs
+Severity: important
+Certainty: certain
+Info: Although the package does not include any shared libraries, it does
+ have a symbols control file. If you did include a shared library, check that
+ the SONAME of the library is set and that it matches the contents of the
+ symbols file.
+ .
+ SONAMEs are set with something like <tt>gcc -Wl,-soname,libfoo.so.0</tt>,
+ where 0 is the major version of the library. If your package uses libtool,
+ then libtool invoked with the right options should be doing this.
+
+Tag: shlib-missing-in-symbols-control-file
+Severity: normal
+Certainty: possible
+Info: The package contains a shared library that is not listed in the
+ symbols control file. This may not be a problem if, for example,
+ the library is a C++ library.
+
+Tag: unused-shlib-entry-in-symbols-control-file
+Severity: normal
+Certainty: certain
+Info: The symbols control file contains an entry for a shared library that
+ is not installed by this package.
+
+Tag: symbols-declares-dependency-on-other-package
+Severity: normal
+Certainty: possible
+Info: This package declares in its symbols control file a dependency on
+ some other package (and not one listed in the Provides of this package).
+ .
+ Packages should normally only list in their symbols control file the
+ shared libraries included in that package, and therefore the dependencies
+ listed there should normally be satisfied by either the package itself or
+ one of its Provides.
+ .
+ In unusual circumstances where it's necessary to declare more complex
+ dependencies in the symbols control file, please add a lintian override
+ for this warning.
+Ref: policy 8.6
+
+Tag: invalid-template-id-in-symbols-file
+Severity: important
+Certainty: certain
+Info: The symbol definition refers to an alternative dependency template
+ which is not defined for the library containing the symbol.
+ .
+ The first alternative dependency template for a library the id number
+ of 1, with the ids of subsequent alternative templates increasing in
+ sequence.
+
+Tag: unknown-meta-field-in-symbols-file
+Severity: important
+Certainty: certain
+Info: The symbols control file contains an unknown meta-information field.
+ .
+ A list of currently supported fields may be found in deb-control(5).
+Ref: deb-control(5)
+
+Tag: symbols-declared-but-not-shlib
+Severity: important
+Certainty: certain
+Info: The symbols control file contains dependency and symbol information
+ for a shared library which is not listed in the shlibs control file.
+
+Tag: shlib-calls-exit
+Severity: wishlist
+Certainty: possible
+Experimental: yes
+Info: The listed shared library calls the C library exit() or _exit()
+ functions.
+ .
+ In the case of an error, the library should instead return an appropriate
+ error code to the calling program which can then determine how to handle
+ the error, including performing any required clean-up.
+ .
+ In most cases, removing the call should be discussed with upstream,
+ particularly as it may produce an ABI change.
+
+Tag: incorrect-libdir-in-la-file
+Severity: important
+Certainty: possible
+Info: The given .la file points to a libdir other than the path where it is
+ installed.  This can be caused by resetting <tt>prefix</tt> at make install
+ time instead of using <tt>DESTDIR</tt>.  The incorrect path will cause
+ packages linking to this library using libtool to build incorrectly (adding
+ incorrect paths to RPATH, for example).
diff --git a/checks/standards-version b/checks/standards-version
new file mode 100644 (file)
index 0000000..44c69ff
--- /dev/null
@@ -0,0 +1,155 @@
+# standards-version -- lintian check script -*- perl -*-
+
+# Copyright (C) 1998 Christian Schwarz and Richard Braakman
+# Copyright (C) 2008-2009 Russ Allbery
+#
+# 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 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.
+
+package Maemian::standards_version;
+use strict;
+
+use POSIX qw(strftime);
+
+use Maemian::Data;
+use Tags;
+use Util;
+
+our $STANDARDS = Maemian::Data->new('standards-version/release-dates', '\s+');
+
+# In addition to the normal Maemian::Data structure, we also want a list of
+# all standards and their release dates so that we can check things like the
+# release date of the standard released after the one a package declared.  Do
+# that by pulling all data out of the Maemian::Data structure and sorting it
+# by release date.  We can also use this to get the current standards version.
+our @STANDARDS = sort { $b->[1] <=> $a->[1] }
+    map { [ $_, $STANDARDS->value($_) ] } $STANDARDS->all;
+our $CURRENT   = $STANDARDS[0][0];
+our @CURRENT   = split(/\./, $CURRENT);
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+# udebs aren't required to conform to policy, so they don't need
+# Standards-Version. (If they have it, though, it should be valid.)
+my $version = $info->field('standards-version');
+my $pkgs = $info->binaries;
+my $all_udeb = 1;
+foreach my $bin_type (values %$pkgs) {
+    if ($bin_type ne 'udeb') {
+        $all_udeb = 0;
+        last;
+    }
+}
+if (not defined $version) {
+    tag 'no-standards-version-field' unless $all_udeb;
+    return 0;
+}
+
+# Check basic syntax and strip off the fourth digit.  People are allowed to
+# include the fourth digit if they want, but it indicates a non-normative
+# change in Policy and is therefore meaningless in the Standards-Version
+# field.
+unless ($version =~ m/^\s*(\d+\.\d+\.\d+)(?:\.\d+)?\s*$/) {
+    tag 'invalid-standards-version', $version;
+    return 0;
+}
+my $stdver = $1;
+my ($major, $minor, $patch) = $stdver =~ m/^(\d+)\.(\d+)\.(\d+)/;
+
+# To do some date checking, we have to get the package date from the changelog
+# file.  If we can't find the changelog file, assume that the package was
+# released today, since that activates the most tags.
+my $changes = $info->changelog;
+my $pkgdate;
+if (defined $changes) {
+    my ($entry) = $changes->data;
+    $pkgdate = ($entry && $entry->Timestamp) ? $entry->Timestamp : time;
+} else {
+    $pkgdate = time;
+}
+
+# Check for packages dated prior to the date of release of the standards
+# version with which they claim to comply.
+if ($STANDARDS->known($stdver) && $STANDARDS->value($stdver) > $pkgdate) {
+    my $package = strftime('%Y-%m-%d', gmtime $pkgdate);
+    my $release = strftime('%Y-%m-%d', gmtime $STANDARDS->value($stdver));
+    tag 'timewarp-standards-version', "($package < $release)";
+}
+
+my $tag = "$version (current is $CURRENT)";
+if (not $STANDARDS->known($stdver)) {
+    # Unknown standards version.  Perhaps newer?
+    if (   ($major > $CURRENT[0])
+        or ($major == $CURRENT[0] and $minor > $CURRENT[1])
+        or ($major == $CURRENT[0] and $minor == $CURRENT[1]
+            and $patch > $CURRENT[2])) {
+        tag 'newer-standards-version', $tag;
+    } else {
+        tag 'invalid-standards-version', $version;
+    }
+} elsif ($stdver eq $CURRENT) {
+    # Current standard.  Nothing more to check.
+    return 0;
+} else {
+    # Otherwise, we need to see if the standard that this package declares is
+    # both new enough to not be ancient and was the current standard at the
+    # time the package was uploaded.
+    #
+    # A given standards version is considered obsolete if the version
+    # following it has been out for at least two years (so the current version
+    # is never obsolete).
+    my $obsdate = time;
+    for my $index (0 .. $#STANDARDS) {
+        if ($STANDARDS[$index][0] eq $stdver) {
+            $obsdate = $STANDARDS[$index - 1][1] if $index > 0;
+            last;
+        }
+    }
+    if ($obsdate + (60 * 60 * 24 * 365 * 2) < time) {
+        tag 'ancient-standards-version', $tag;
+    } else {
+        # We have to get the package date from the changelog file.  If we
+        # can't find the changelog file, always issue the tag.
+        my $changes = $info->changelog;
+        if (not defined $changes) {
+            tag 'out-of-date-standards-version', $tag;
+            return 0;
+        }
+        my ($entry) = $changes->data;
+        my $timestamp = ($entry && $entry->Timestamp) ? $entry->Timestamp : 0;
+        for my $standard (@STANDARDS) {
+            last if $standard->[0] eq $stdver;
+            if ($standard->[1] < $timestamp) {
+                tag 'out-of-date-standards-version', $tag;
+                last;
+            }
+        }
+    }
+}
+
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround
diff --git a/checks/standards-version.desc b/checks/standards-version.desc
new file mode 100644 (file)
index 0000000..3cb9c4b
--- /dev/null
@@ -0,0 +1,74 @@
+Check-Script: standards-version
+Author: Christian Schwarz <schwarz@debian.org>
+Abbrev: std
+Type: source
+Unpack-Level: 1
+Info: This script checks if a source package contains a valid
+ Standards-Version field.
+Needs-Info: debfiles, source-control-file
+
+Tag: no-standards-version-field
+Severity: important
+Certainty: certain
+Ref: policy 5.6.11
+Info: The source package does not have a Standards-Version control field.
+ Please update your package to latest Policy and set this control field
+ appropriately.
+
+Tag: invalid-standards-version
+Severity: important
+Certainty: certain
+Info: The source package refers to a Standards-Version which never
+ existed.  Please update your package to latest Policy and set this
+ control field appropriately.
+
+Tag: newer-standards-version
+Severity: normal
+Certainty: certain
+Info: The source package refers to a Standards-Version which is
+ newer than the highest one lintian is programmed to check.  If the source
+ package is correct, then please upgrade lintian to the newest version.
+ (If there is no newer lintian version, then please bug &maint; to make
+ one.)
+
+Tag: ancient-standards-version
+Severity: normal
+Certainty: certain
+Info: The source package refers to a Standards-Version that has been
+ obsolete for more than two years.  Please update your package to latest
+ Policy and set this control field appropriately.
+ .
+ If the package is already compliant with the current standards, you don't
+ have to re-upload the package just to adjust the Standards-Version
+ control field.  However, please remember to update this field next time
+ you upload the package.
+ .
+ See <tt>/usr/share/doc/debian-policy/upgrading-checklist.txt.gz</tt> in
+ the debian-policy package for a summary of changes in newer versions of
+ Policy.
+
+Tag: out-of-date-standards-version
+Severity: normal
+Certainty: certain
+Info: The source package refers to a Standards-Version older than the one
+ that was current at the time the package was created (according to the
+ timestamp of the latest <tt>debian/changelog</tt> entry).  Please
+ consider updating the package to current Policy and setting this control
+ field appropriately.
+ .
+ If the package is already compliant with the current standards, you don't
+ have to re-upload the package just to adjust the Standards-Version
+ control field.  However, please remember to update this field next time
+ you upload the package.
+ .
+ See <tt>/usr/share/doc/debian-policy/upgrading-checklist.txt.gz</tt> in
+ the debian-policy package for a summary of changes in newer versions of
+ Policy.
+
+Tag: timewarp-standards-version
+Severity: normal
+Certainty: certain
+Info: The source package refers to a Standards-Version that was released
+ after the date of the most recent <tt>debian/changelog</tt> entry.
+ Perhaps you forgot to update the timestamp in <tt>debian/changelog</tt>
+ before building the package?
diff --git a/checks/version-substvars b/checks/version-substvars
new file mode 100644 (file)
index 0000000..edb85cd
--- /dev/null
@@ -0,0 +1,104 @@
+# version-substvars -- lintian check script -*- perl -*-
+#
+# Copyright (C) 2006 Adeodato Simó
+#
+# 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 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.
+
+# SUMMARY
+# =======
+#
+# What breaks
+# -----------
+#
+# (b1) any -> any (= ${source:Version})                 -> use b:V
+# (b2) any -> all (= ${Source-Version}) [or b:V] -> use s:V
+# (b3) all -> any (= ${either-of-them})                 -> use (>= ${s:V}),
+#                                                  optionally (<< ${s:V}.1~)
+#
+# Always warn on ${Source-Version} even if it doesn't break since the substvar
+# is now considered deprecated.
+
+package Maemian::version_substvars;
+use strict;
+
+use Util;
+use Tags;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+my $binpkgs = $info->binaries;
+
+my @dep_fields = qw(depends pre-depends recommends suggests conflicts replaces);
+
+foreach (keys %$binpkgs) {
+       my ($pkg1, $pkg1_is_any, $pkg2, $pkg2_is_any, $substvar_strips_binNMU);
+
+       $pkg1 = $_;
+       $pkg1_is_any = ($info->binary_field($pkg1, 'architecture') ne 'all');
+
+       foreach my $field (@dep_fields) {
+               next unless $info->binary_field($pkg1, $field);
+               if ($info->binary_field($pkg1, $field) =~ m/\${Source-Version}/) {
+                       tag "substvar-source-version-is-deprecated", $pkg1;
+               }
+       }
+
+       foreach (split(m/,/, $info->binary_field($pkg1, 'pre-depends').", ".
+                      $info->binary_field($pkg1, 'depends'))) {
+               next unless m/(\S+)\s*\(\s*=\s*\${((?:Source-|source:|binary:)Version)}/x;
+
+               $pkg2 = $1;
+               $substvar_strips_binNMU = ($2 eq 'source:Version');
+
+               # We can't test dependencies on packages whose names are
+               # formed via substvars expanded during the build.  Assume
+               # those maintainers know what they're doing.
+               if (not $info->binary_field($pkg2, 'architecture')) {
+                       tag "version-substvar-for-external-package", "$pkg1 -> $pkg2"
+                               unless ($pkg2 =~ /\$\{\S+\}/);
+                       next;
+               }
+               $pkg2_is_any = ($info->binary_field($pkg2, 'architecture') !~ m/^all$/);
+
+               if ($pkg1_is_any) {
+                       if ($pkg2_is_any and $substvar_strips_binNMU) {
+                               # (b1) any -> any (= ${source:Version})
+                               tag "not-binnmuable-any-depends-any", "$pkg1 -> $pkg2";
+                       } elsif (not $pkg2_is_any and not $substvar_strips_binNMU) {
+                               # (b2) any -> all (= ${Source-Version}) [or b:V]
+                               tag "not-binnmuable-any-depends-all", "$pkg1 -> $pkg2";
+                       }
+               } elsif ($pkg2_is_any) {
+                       # (b3) all -> any (= ${either-of-them})
+                       tag "not-binnmuable-all-depends-any", "$pkg1 -> $pkg2";
+               }
+       }
+}
+
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 8
+# End:
+# vim: syntax=perl sw=4 ts=4 noet shiftround
diff --git a/checks/version-substvars.desc b/checks/version-substvars.desc
new file mode 100644 (file)
index 0000000..652c368
--- /dev/null
@@ -0,0 +1,56 @@
+Check-Script: version-substvars
+Author: Adeodato Simó <dato@net.com.org.es>
+Abbrev: v-s
+Type: source
+Unpack-Level: 1
+Needs-Info: debfiles, source-control-file
+Info: This script checks for correct use of the various *Version
+ substvars, e.g. deprecated substvars, or usage that can cause
+ un-binNMUability
+
+Tag: not-binnmuable-any-depends-any
+Severity: important
+Certainty: certain
+Info: The package is not safely binNMUable because an arch:any package
+ depends on another arch:any package with a (= ${source:Version})
+ relationship. Please use (= ${binary:Version}) instead.
+
+Tag: not-binnmuable-any-depends-all
+Severity: important
+Certainty: certain
+Info: The package is not safely binNMUable because an arch:any package
+ depends on an arch:all package with a (= ${Source-Version}) or
+ (= ${binary:Version}) relationship. Please use (= ${source:Version})
+ instead.
+
+Tag: not-binnmuable-all-depends-any
+Severity: important
+Certainty: certain
+Info: The package is not safely binNMUable because an arch:all package
+ depends on an arch:any package with a strict (= ${Source-Version}), or
+ similar, relationship.
+ .
+ It is not possible for arch:all packages to depend so strictly on
+ arch:any packages while having the package binNMUable, so please use
+ one of these, whichever is more appropriate:
+ .
+   Depends: arch_any (&gt;= ${source:Version})
+   Depends: arch_any (&gt;= ${source:Version}),
+    arch_any (&lt;&lt; ${source:Version}.1~)
+
+Tag: version-substvar-for-external-package
+Severity: important
+Certainty: certain
+Info: The first package has a dependency on the second package that uses
+ (= ${binary:Version}), (= ${source:Version}), or (= ${Source-Version}),
+ but the second package is not built from this source package.  Usually
+ this means there is a mistake in the package name in this dependency.
+
+Tag: substvar-source-version-is-deprecated
+Severity: normal
+Certainty: certain
+Info: The package uses the now deprecated ${Source-Version} substvar,
+ which has misleading semantics.  Please switch to ${binary:Version} or
+ ${source:Version} as appropriate (introduced in dpkg 1.13.19, released
+ with etch).  Support for ${Source-Version} may be removed from dpkg-dev
+ in the future.
diff --git a/checks/watch-file b/checks/watch-file
new file mode 100644 (file)
index 0000000..6c21225
--- /dev/null
@@ -0,0 +1,176 @@
+# watch-file -- lintian check script -*- perl -*-
+#
+# Copyright (C) 2008 Patrick Schoenfeld
+# Copyright (C) 2008 Russ Allbery
+# Copyright (C) 2008 Raphael Geissert
+#
+# 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 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.
+
+package Maemian::watch_file;
+use strict;
+
+use Maemian::Collect;
+use Tags;
+
+sub run {
+
+my $pkg = shift;
+my $type = shift;
+my $info = shift;
+
+if (! -f "debfiles/watch") {
+    tag 'debian-watch-file-is-missing' unless ($info->native);
+    return;
+}
+
+# Perform the other checks even if it is a native package
+tag 'debian-watch-file-in-native-package' if ($info->native);
+
+# Check if the Debian version contains anything that resembles a repackaged
+# source package sign, for fine grained version mangling check
+my $version = $info->field('version');
+my $repack;
+if ($version =~ /(dfsg|debian|ds)/) {
+    $repack = $1;
+}
+
+# Gather information from the watch file and look for problems we can
+# diagnose on the first time through.
+open(WATCH, '<', 'debfiles/watch') or fail("cannot open watch file: $!");
+local $_;
+my ($watchver, $mangle, $dmangle, $nonempty, %dversions);
+while (<WATCH>) {
+    next if /^\s*\#/;
+    next if /^\s*$/;
+    s/^\s*//;
+
+  CHOMP:
+    chomp;
+    if (s/(?<!\\)\\$//) {
+        # This is caught by uscan.
+        last if eof(WATCH);
+        $_ .= <WATCH>;
+        goto CHOMP;
+    }
+
+    if (/^version\s*=\s*(\d+)(\s|\Z)/) {
+        if (defined $watchver) {
+            tag 'debian-watch-file-declares-multiple-versions', "line $.";
+        }
+        $watchver = $1;
+        if ($watchver ne '2' and $watchver ne '3') {
+            tag 'debian-watch-file-unknown-version', $watchver;
+        }
+    } else {
+        $nonempty = 1;
+
+        unless (defined($watchver)) {
+            tag 'debian-watch-file-missing-version';
+            $watchver = 1;
+        }
+        # Version 1 watch files are too broken to try checking them.
+        next if ($watchver == 1);
+
+        my ($opts, @opts);
+        if (s/^opt(?:ion)?s=\"([^\"]+)\"\s+// || s/^opt(?:ion)?s=(\S+)\s+//) {
+            $opts = $1;
+            @opts = split(',', $opts);
+            if (defined $repack) {
+                for (@opts) {
+                    $mangle = 1 if /^[ud]?versionmangle\s*=.*($repack)/;
+                    $dmangle = 1 if /^dversionmangle\s*=.*($repack)/;
+                }
+            }
+        }
+        if (m%qa\.debian\.org/watch/sf\.php\?%) {
+            tag 'debian-watch-file-uses-deprecated-sf-redirector-method',
+                "line $.";
+        }
+
+        if (m%(https?|ftp)://((.+\.)?dl|(pr)?downloads?|ftp\d?|upload)\.(sourceforge|sf)\.net%
+            or m%https?://(www\.)?(sourceforge|sf)\.net/project/showfiles\.php%
+            or m%https?://(www\.)?(sourceforge|sf)\.net/projects/.+/files%) {
+            tag 'debian-watch-file-should-use-sf-redirector', "line $.";
+        }
+
+        # This bit is as-is from uscan.pl:
+        my ($base, $filepattern, $lastversion, $action) = split ' ', $_, 4;
+        if ($base =~ s%/([^/]*\([^/]*\)[^/]*)$%/%) {
+            # Last component of $base has a pair of parentheses, so no
+            # separate filepattern field; we remove the filepattern from the
+            # end of $base and rescan the rest of the line
+            $filepattern = $1;
+            (undef, $lastversion, $action) = split ' ', $_, 3;
+        }
+        push @{$dversions{$lastversion}}, $. if (defined($lastversion));
+    }
+}
+close WATCH;
+
+# If the version of the package contains dfsg, assume that it needs to be
+# mangled to get reasonable matches with upstream.
+if ($nonempty and $repack and not $mangle) {
+    tag 'debian-watch-file-should-mangle-version';
+}
+
+if ($repack and $mangle and not $dmangle) {
+    tag 'debian-watch-file-should-dversionmangle-not-uversionmangle';
+}
+
+my $changes = $info->changelog;
+if (defined $changes and %dversions) {
+    my $data = $changes->data;
+    my %changelog_versions;
+    my $count = 1;
+    for my $entry (@{$data}) {
+        my $uversion = $entry->Version;
+        $uversion =~ s/-[^-]+$//; # revision
+        $uversion =~ s/^\d+://; # epoch
+        $changelog_versions{'orig'}{$entry->Version} = $count;
+
+        # Preserve the first value here to correctly detect old versions.
+        $changelog_versions{'mangled'}{$uversion} = $count
+            unless (exists($changelog_versions{'mangled'}{$uversion}));
+        $count++;
+    }
+
+    while (my ($dversion, $lines) = each %dversions) {
+        next if (!defined($dversion) || $dversion eq 'debian');
+        local $" = ', ';
+        if (!$info->native && exists($changelog_versions{'orig'}{$dversion})) {
+            tag 'debian-watch-file-specifies-wrong-upstream-version',
+                "$dversion: @{$lines}";
+            next;
+        }
+        if (exists($changelog_versions{'mangled'}{$dversion})
+            && $changelog_versions{'mangled'}{$dversion} != 1) {
+            tag 'debian-watch-file-specifies-old-upstream-version',
+                "$dversion: @{$lines}";
+            next;
+        }
+    }
+}
+
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround
diff --git a/checks/watch-file.desc b/checks/watch-file.desc
new file mode 100644 (file)
index 0000000..00241aa
--- /dev/null
@@ -0,0 +1,129 @@
+Check-Script: watch-file
+Author: Patrick Schoenfeld <schoenfeld@in-medisa-res.com>
+Abbrev: watch
+Type: source
+Unpack-Level: 1
+Needs-Info: debfiles
+Info: Check debian/watch files in source packages.
+
+Tag: debian-watch-file-is-missing
+Severity: wishlist
+Certainty: certain
+Ref: policy 4.11, uscan(1)
+Info: This source package is not Debian-native but it does not have a
+ <tt>debian/watch</tt> file.  This file is used for automatic detection of
+ new upstream versions by the Debian External Health Status project and
+ other project infrastructure.  If this package is maintained upstream,
+ please consider adding a <tt>debian/watch</tt> file to detect new
+ releases.
+ .
+ If the package is not maintained upstream or if upstream uses a
+ distribution mechanism that cannot be meaningfully monitored by uscan
+ and the Debian External Health Status project, please consider adding a
+ <tt>debian/watch</tt> file containing only comments documenting the
+ situation.
+
+Tag: debian-watch-file-declares-multiple-versions
+Severity: normal
+Certainty: certain
+Ref: uscan(1)
+Info: The <tt>debian/watch</tt> file in this package contains multiple
+ lines starting with <tt>version=</tt>.  There should be only one version
+ declaration in a watch file, on the first non-comment line of the file.
+
+Tag: debian-watch-file-unknown-version
+Severity: normal
+Certainty: certain
+Ref: uscan(1)
+Info: The <tt>version=</tt> line in the <tt>debian/watch</tt> file in this
+ package declares an unknown version.  The currently known watch file
+ versions are 2 and 3.
+
+Tag: debian-watch-file-missing-version
+Severity: normal
+Certainty: certain
+Ref: uscan(1)
+Info: The <tt>debian/watch</tt> file in this package doesn't start a
+ <tt>version=</tt> line.  The first non-comment line of
+ <tt>debian/watch</tt> should be a <tt>version=</tt> declaration.  This
+ may mean that this is an old version one watch file that should be
+ updated to the current version.
+
+Tag: debian-watch-file-should-mangle-version
+Severity: normal
+Certainty: certain
+Ref: uscan(1), http://wiki.debian.org/DEHS
+Info: The version of this package contains <tt>dfsg</tt>, <tt>ds</tt>,
+ or <tt>debian</tt>, which normally indicates that the upstream source
+ has been repackaged to comply with the Debian Free Software Guidelines
+ (or similar reason), but there is no version mangling in the
+ <tt>debian/watch</tt> file.  Since the <tt>dfsg</tt> string is not
+ part of the upstream version, the <tt>debian/watch</tt> file should
+ use the dversionmangle option to remove the <tt>dfsg</tt> before
+ version number comparison.
+
+Tag: debian-watch-file-should-dversionmangle-not-uversionmangle
+Severity: wishlist
+Certainty: certain
+Ref: http://wiki.debian.org/DEHS
+Info: The version of this package contains <tt>dfsg</tt>, <tt>ds</tt>,
+ or <tt>debian</tt>, but a misleading upstream version mangling occurs in
+ the <tt>debian/watch</tt> file.  Since the <tt>dfsg</tt> string is not
+ part of the upstream version and its addition is Debian-specific, the
+ the <tt>debian/watch</tt> file should use the dversionmangle option to
+ remove, instead of adding in uversionmangle, the <tt>dfsg</tt> before
+ comparing version numbers.
+
+Tag: debian-watch-file-in-native-package
+Severity: normal
+Certainty: certain
+Ref: http://wiki.debian.org/DEHS
+Info: The package ships a watch file although it is a Debian native
+ package.  DEHS does not process watch files in native packages based on
+ the reasoning that native packages do not have upstreams to check for new
+ releases.
+
+Tag: debian-watch-file-uses-deprecated-sf-redirector-method
+Severity: normal
+Certainty: certain
+Info: The watch file seems to be passing arguments to the redirector
+ other than a path. Calling the SourceForge redirector with parameters like
+ <tt>project</tt> prevents uscan from generating working URIs to the files
+ and thus has been deprecated and is no longer supported by the redirector.
+
+Tag: debian-watch-file-should-use-sf-redirector
+Severity: normal
+Certainty: certain
+Ref: uscan(1)
+Info: The watch file specifies a SourceForge download server directly.
+ This is not recommended; SourceForge changes their download servers
+ periodically, requiring watch files to be modified every time.  Instead,
+ use the qa.debian.org redirector by using the magic URL:
+ .
+   http://sf.net/&lt;project&gt;/&lt;tar-name&gt;-(.+)\.tar\.gz
+ .
+ replacing <tt>&lt;project&gt;</tt> with the name of the SourceForge
+ project and <tt>&lt;tar-name&gt;</tt> with the name of the tarball
+ distributed within that project.
+
+Tag: debian-watch-file-specifies-wrong-upstream-version
+Severity: normal
+Certainty: certain
+Ref: uscan(1)
+Info: The watch file specifies an upstream version which exactly matches
+ the version of a <tt>debian/changelog</tt> entry, this is not a
+ native package, and no version mangling is being done.  The version
+ field in a watch file should specify the expected upstream version, not
+ the version of the Debian package.  Any epochs and Debian revisions
+ should be removed first or mangled away.
+
+Tag: debian-watch-file-specifies-old-upstream-version
+Severity: normal
+Certainty: certain
+Info: The watch file specifies an upstream version number which matches
+ the upstream portion of an old <tt>debian/changelog</tt> entry, and the
+ current <tt>debian/changelog</tt> entry specifies a newer upstream
+ version.  The version number in the watch file is very likely to be
+ incorrect and probably should be replaced with the current expected
+ upstream version.  Otherwise, DEHS and similar projects will think the
+ package is out of date even when it may not be.
diff --git a/collection/changelog-file b/collection/changelog-file
new file mode 100755 (executable)
index 0000000..a642d4a
--- /dev/null
@@ -0,0 +1,110 @@
+#!/usr/bin/perl -w
+# changelog-file -- maemian collector script
+
+# Copyright (C) 1998 Richard Braakman
+#
+# 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 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.
+
+use strict;
+
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Util;
+
+($#ARGV == 1) or fail("syntax: changelog-file <pkg> <type>");
+my $pkg = shift;
+my $type = shift;
+
+-f "fields/package" or fail("changelog-file invoked in wrong directory");
+
+unlink("changelog");
+
+# Pick the first of these files that exists.
+my @changelogs = ("unpacked/usr/share/doc/$pkg/changelog.Debian.gz",
+              "unpacked/usr/share/doc/$pkg/changelog.Debian",
+              "unpacked/usr/share/doc/$pkg/changelog.debian.gz",
+              "unpacked/usr/share/doc/$pkg/changelog.debian",
+              "unpacked/usr/share/doc/$pkg/changelog.gz",
+              "unpacked/usr/share/doc/$pkg/changelog",
+              "unpacked/usr/doc/$pkg/changelog.Debian.gz",
+              "unpacked/usr/doc/$pkg/changelog.Debian",
+              "unpacked/usr/doc/$pkg/changelog.debian.gz",
+              "unpacked/usr/doc/$pkg/changelog.debian",
+              "unpacked/usr/doc/$pkg/changelog.gz",
+              "unpacked/usr/doc/$pkg/changelog");
+
+my $chl;
+
+for (@changelogs) {
+    if (-l $_ || -f $_) {
+       $chl = $_;
+       last;
+    }
+}
+
+# If the changelog file we found was a symlink, we have to be careful.  It
+# could be a symlink to some file outside of the laboratory and we don't want
+# to end up reading that file by mistake.  Relative links within the same
+# directory or to a subdirectory we accept; anything else is replaced by an
+# intentinally broken symlink so that checks can do the right thing.
+if (defined ($chl) && -l $chl) {
+    my $link = readlink $chl or fail("cannot readlink $chl: $!");
+    if ($link =~ /\.\./ || ($link =~ m%/% && $link !~ m%^[^/]+(/+[^/]+)*\z%)) {
+       symlink('file-is-in-another-package', 'changelog')
+           or fail("cannot create changelog symlink: $!");
+       undef $chl;
+    } elsif (! -f $chl) {
+        undef $chl;
+    }
+}
+
+# If the changelog was a broken symlink, it will be undefined and we'll now
+# treat it the same as if we didn't find a changelog and do nothing.  If it
+# was a symlink, copy the file, since otherwise the relative symlinks are
+# going to break things.
+if (not defined $chl) {
+    # no changelog found
+} elsif ($chl =~ /\.gz$/) {
+    gunzip_file($chl, 'changelog');
+} elsif (-f $chl && -l $chl) {
+    local $_;
+    open (CHL, '<', $chl) or fail("cannot open $chl: $!");
+    open (COPY, '>', 'changelog') or fail("cannot create changelog: $!");
+    print COPY while <CHL>;
+    close CHL;
+    close COPY;
+} else {
+    link($chl, "changelog")
+       or fail("cannot link $chl to changelog: $!");
+}
+
+# Extract NEWS.Debian files as well, with similar precautious.  Ignore any
+# symlinks to other packages here; in that case, we just won't check the file.
+unlink('NEWS.Debian');
+my $news = "unpacked/usr/share/doc/$pkg/NEWS.Debian.gz";
+if (-f $news) {
+    if (-l $news) {
+        my $link = readlink $news or fail("cannot readlink $chl: $!");
+        if ($link =~ /\.\./ || ($link =~ m%/% && $link !~ m%^[^/]+(/+[^/]+)*\z%)) {
+            undef $news;
+        } elsif (! -f $news) {
+            undef $news;
+        }
+    }
+    if ($news) {
+       gunzip_file($news, "NEWS.Debian");
+    }
+}
diff --git a/collection/changelog-file.desc b/collection/changelog-file.desc
new file mode 100644 (file)
index 0000000..1dff706
--- /dev/null
@@ -0,0 +1,9 @@
+Collector-Script: changelog-file
+Author: Richard Braakman <dark@xs4all.nl>
+Info: This script copies the <tt>changelog</tt> file and
+ <tt>NEWS.Debian</tt> file (if any) of a package into the maemian
+ directory.
+Type: binary
+Unpack-Level: 2
+Version: 1
+Order: 1
diff --git a/collection/copyright-file b/collection/copyright-file
new file mode 100755 (executable)
index 0000000..c6d2ee1
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/perl -w
+# copyright-file -- maemian collector script
+
+# Copyright (C) 1998 Richard Braakman
+# 
+# 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 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.
+
+use strict;
+
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Util;
+
+use File::Copy qw(copy);
+
+($#ARGV == 1) or fail("syntax: copyright-file <pkg> <type>");
+my $pkg = shift;
+my $type = shift;
+
+-f "fields/package" or fail("copyright-file invoked in wrong directory");
+
+unlink("copyright");
+
+my $file1 = "unpacked/usr/share/doc/$pkg/copyright";
+my $file2 = "unpacked/usr/doc/$pkg/copyright";
+
+my $file;
+if (-f $file1 ) { $file = $file1; }
+else { $file = $file2; }
+
+# If copyright was a symlink, we need to make a copy of it.  Just hardlinking
+# to the symlink may leave a relative symlink into a directory we can't
+# unpack.  Be careful about what symlinks we allow, though.
+if (-l $file) {
+    my $link = readlink($file) or fail("cannot readlink $file: $!");
+    if ($link =~ /\.\./ || ($link =~ m%/% && $link !~ m%^[^/]+(/+[^/]+)*\z%)) {
+        touch_file("copyright");
+    } else {
+        copy($file, "copyright") or fail("cannot copy $file: $!");
+    }
+} elsif (-f $file) {
+    link($file, "copyright")
+       or fail("cannot link $file to copyright: $!");
+} elsif (-f "$file.gz") {
+    gunzip_file($file, 'copyright');
+} else {
+    # no copyright file found
+    touch_file('copyright');
+}
diff --git a/collection/copyright-file.desc b/collection/copyright-file.desc
new file mode 100644 (file)
index 0000000..7a7aaad
--- /dev/null
@@ -0,0 +1,8 @@
+Collector-Script: copyright-file
+Author: Richard Braakman <dark@xs4all.nl>
+Info: This script copies the "copyright" file of a package into the
+ maemian directory.
+Type: binary
+Unpack-Level: 2
+Version: 1
+Order: 1
diff --git a/collection/debfiles b/collection/debfiles
new file mode 100755 (executable)
index 0000000..8ea7247
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/perl -w
+# debfiles -- maemian collector script
+
+# Copyright (C) 1999 by Joey Hess
+# 
+# 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 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.
+
+use strict;
+
+($#ARGV == 1) or fail("syntax: debfiles <pkg> <type>");
+my $pkg = shift;
+my $type = shift;
+
+-e "unpacked" or fail("debfiles invoked in wrong directory");
+
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Util;
+
+if (-e "debfiles") {
+    delete_dir('debfiles')
+       or fail("cannot rm old debfiles directory");
+}
+
+mkdir('debfiles', 0777) or fail("cannot mkdir debfiles: $!");
+
+# Don't copy the whole directory, just all files in it.
+opendir(DEBIAN, 'unpacked/debian')
+       or fail("cannot open unpacked/debian/ directory: $!");
+while (my $file=readdir(DEBIAN)) {
+       next if -d $file;
+       copy_dir("unpacked/debian/$file", 'debfiles/')
+               or fail("cannot copy unpacked/debian/$file: $!");
+}
+closedir(DEBIAN);
diff --git a/collection/debfiles.desc b/collection/debfiles.desc
new file mode 100644 (file)
index 0000000..316b542
--- /dev/null
@@ -0,0 +1,8 @@
+Collector-Script: debfiles
+Author: Joey Hess <joeyh@debian.org>
+Info: This script collects files shipped in the source of the
+ package.
+Type: source
+Unpack-Level: 2
+Version: 1
+Order: 1
diff --git a/collection/debian-readme b/collection/debian-readme
new file mode 100755 (executable)
index 0000000..9ed8191
--- /dev/null
@@ -0,0 +1,61 @@
+#!/usr/bin/perl -w
+# debian-readme -- maemian collector script
+
+# Copyright (C) 1998 Richard Braakman
+# 
+# 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 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.
+
+use strict;
+
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Util;
+
+($#ARGV == 1) or fail("syntax: debian-readme <pkg> <type>");
+my $pkg = shift;
+my $type = shift;
+
+-f "fields/package" or fail("debian-readme invoked in wrong directory");
+
+unlink("README.Debian");
+
+# Pick the first of these files that exists.
+my @readmes = ("unpacked/usr/share/doc/$pkg/README.Debian.gz",
+           "unpacked/usr/share/doc/$pkg/README.Debian",
+           "unpacked/usr/share/doc/$pkg/README.debian.gz",
+           "unpacked/usr/share/doc/$pkg/README.debian",
+           "unpacked/usr/doc/$pkg/README.Debian.gz",
+           "unpacked/usr/doc/$pkg/README.Debian",
+           "unpacked/usr/doc/$pkg/README.debian.gz",
+           "unpacked/usr/doc/$pkg/README.debian");
+
+my $file;
+for (@readmes) {
+    if (-f $_) {
+       $file = $_;
+       last;
+    }
+}
+
+if (not defined $file) {
+    # no README found
+    touch_file("README.Debian");
+} elsif ($file =~ m/\.gz$/) {
+    gunzip_file($file, "README.Debian");
+} else {
+    link($file, "README.Debian")
+       or fail("cannot link $file to README.Debian: $!");
+}
diff --git a/collection/debian-readme.desc b/collection/debian-readme.desc
new file mode 100644 (file)
index 0000000..a4d15dd
--- /dev/null
@@ -0,0 +1,7 @@
+Collector-Script: debian-readme
+Author: Richard Braakman <dark@xs4all.nl>
+Info: This script copies the 'README.Debian' file of a package into the maemian directory.
+Type: binary
+Unpack-Level: 2
+Version: 1
+Order: 1
diff --git a/collection/diffstat b/collection/diffstat
new file mode 100755 (executable)
index 0000000..2baea5b
--- /dev/null
@@ -0,0 +1,68 @@
+#!/usr/bin/perl -w
+# diffstat -- maemian collection script for source packages
+
+# Copyright (C) 1998 Richard Braakman
+# 
+# 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 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.
+
+# This could be written more easily in shell script, but I'm trying
+# to keep everything as perl to cut down on the number of processes
+# that need to be started in a maemian scan.  Eventually all the
+# perl code will be perl modules, so only one perl interpreter
+# need be started.
+
+use strict;
+
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Util;
+
+($#ARGV == 1) or fail("syntax: diffstat <pkg>");
+my $pkg = shift;
+
+-f "fields/version" or fail("diffstat invoked in wrong directory");
+
+open (V, '<', "fields/version") or fail("cannot open fields/version: $!");
+my $ver = <V>; chomp $ver;
+close V;
+
+unlink('debian-patch');
+
+$ver =~ s/^\d://; #Remove epoch for this
+
+my $diff_file = "${pkg}_${ver}.diff.gz";
+unless (-f $diff_file) {
+# we have to write an empty file so that the checks don't crap out. <sigh>
+  touch_file('diffstat');
+  exit 0;
+}
+
+gunzip_file($diff_file, "debian-patch");
+
+open (STAT, '>', "diffstat") or fail("cannot open scripts output file: $!");
+# diffstat is noisy on stderr if its stdout is not a tty.
+# Shut it up by redirecting stderr to /dev/null.
+open STDERR, ">/dev/null";
+open (DIFF, '-|', qw/diffstat -p1 debian-patch/)
+  or fail("cannot open pipe to diffstat on debian-patch: $!");
+# Copy all except last line to the STAT file
+my $previous;
+while (<DIFF>) {
+    print STAT $previous if $previous;
+    $previous = $_;
+}
+close DIFF or fail("cannot close pipe to diffstat on debian-patch: $!");
+close STAT or fail("error writing diffstat file: $!");
diff --git a/collection/diffstat.desc b/collection/diffstat.desc
new file mode 100644 (file)
index 0000000..d250dbe
--- /dev/null
@@ -0,0 +1,8 @@
+Collector-Script: diffstat
+Author: Richard Braakman <dark@xs4all.nl>
+Info: This script extracts the Debian diff of a source package, and runs
+ diffstat on it, leaving the result in the diffstat output file
+Type: source
+Unpack-Level: 1
+Version: 1
+Order: 1
diff --git a/collection/doc-base-files b/collection/doc-base-files
new file mode 100755 (executable)
index 0000000..e4c461a
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/perl -w
+# doc-base-files -- maemian collector script
+
+# Copyright (C) 1998 Richard Braakman
+# Copyright (C) 2001 Josip Rodin
+# 
+# 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 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.
+
+use strict;
+
+($#ARGV == 1) or fail("syntax: doc-base-files <pkg> <type>");
+my $pkg = shift;
+my $type = shift;
+
+-f "fields/package" or fail("doc-base-files invoked in wrong directory");
+
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Util;
+
+if (-e "doc-base") {
+    delete_dir("doc-base")
+       or fail("cannot rm old doc-base directory");
+}
+
+if (-d "unpacked/usr/share/doc-base") {
+    copy_dir('unpacked/usr/share/doc-base', 'doc-base')
+       or fail("cannot copy directory unpacked/usr/share/doc-base");
+} else {
+    # no doc-base directory
+    mkdir("doc-base", 0777) or fail("cannot mkdir doc-base: $!");
+}
diff --git a/collection/doc-base-files.desc b/collection/doc-base-files.desc
new file mode 100644 (file)
index 0000000..01e5b10
--- /dev/null
@@ -0,0 +1,8 @@
+Collector-Script: doc-base-files
+Author: Josip Rodin <jrodin@jagor.srce.hr>
+Info: This script copies the contents of /usr/share/doc-base into the
+ maemian doc-base/ directory.
+Type: binary
+Unpack-Level: 2
+Version: 1
+Order: 1
diff --git a/collection/file-info b/collection/file-info
new file mode 100755 (executable)
index 0000000..1b939ab
--- /dev/null
@@ -0,0 +1,62 @@
+#!/usr/bin/perl -w
+# file-info -- maemian collection script
+
+# Copyright (C) 1998 Richard Braakman
+#
+# 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 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.
+
+use strict;
+
+use FileHandle;
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Util;
+use Maemian::Command qw(spawn reap);
+
+($#ARGV == 1) or fail("syntax: file-info <pkg> <type>");
+my $pkg = shift;
+my $type = shift;
+
+-f "fields/source" or fail("file-info invoked in wrong directory");
+
+unlink("file-info");
+chdir("unpacked")
+    or fail("cannot chdir to unpacked directory: $!");
+
+# We ignore failures from file because sometimes file returns a non-zero exit
+# status when it can't parse a file.  So far, the resulting output still
+# appears to be usable (although will contain "ERROR" strings, which Maemian
+# doesn't care about), and the only problem was the exit status.
+my %opts = ( pipe_in => FileHandle->new,
+            out => '../file-info',
+            fail => 'never' );
+spawn(\%opts, ['xargs', '-0r', 'file', '--']);
+$opts{pipe_in}->blocking(1);
+open(INDEX, '<', "../index")
+    or fail("cannot open index file: $!");
+while (<INDEX>) {
+    chomp;
+    $_ = (split(" ", $_, 6))[5];
+    s/ link to .*//;
+    s/ -> .*//;
+    s/(\G|[^\\](?:\\\\)*)\\(\d{3})/"$1" . chr(oct $2)/ge;
+    s/\\\\/\\/;
+    printf {$opts{pipe_in}} "%s\0", $_;
+}
+close(INDEX);
+
+close $opts{pipe_in};
+reap(\%opts);
diff --git a/collection/file-info.desc b/collection/file-info.desc
new file mode 100644 (file)
index 0000000..08e513e
--- /dev/null
@@ -0,0 +1,7 @@
+Collector-Script: file-info
+Author: Richard Braakman <dark@xs4all.nl>
+Info: This script runs the "file" command over all files of any kind of package.
+Type: binary, udeb, source
+Unpack-Level: 2
+Version: 1
+Order: 1
diff --git a/collection/init.d b/collection/init.d
new file mode 100755 (executable)
index 0000000..9a814bb
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/perl -w
+# init.d -- maemian collector script
+
+# Copyright (C) 1998 Richard Braakman
+# 
+# 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 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.
+
+use strict;
+
+($#ARGV == 1) or fail("syntax: init.d <pkg> <type>");
+my $pkg = shift;
+my $type = shift;
+
+-f "fields/package" or fail("init.d invoked in wrong directory");
+
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Util;
+
+if (-e "init.d") {
+    delete_dir("init.d")
+       or fail("cannot rm old init.d directory");
+}
+
+if (-d "unpacked/etc/init.d") {
+    copy_dir('unpacked/etc/init.d', 'init.d')
+       or fail("cannot copy init.d directory");
+} else {
+    # no etc/init.d
+    mkdir("init.d", 0777) or fail("cannot mkdir init.d: $!");
+}
diff --git a/collection/init.d.desc b/collection/init.d.desc
new file mode 100644 (file)
index 0000000..c186238
--- /dev/null
@@ -0,0 +1,8 @@
+Collector-Script: init.d
+Author: Richard Braakman <dark@xs4all.nl>
+Info: This script copies the "etc/init.d" directory into the maemian
+ directory.
+Type: binary
+Unpack-Level: 2
+Version: 1
+Order: 1
diff --git a/collection/md5sums b/collection/md5sums
new file mode 100755 (executable)
index 0000000..9740c45
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/perl -w
+# md5sums -- maemian collection script
+
+# Copyright (C) 1998 Richard Braakman
+# 
+# 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 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.
+
+use strict;
+
+use FileHandle;
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Maemian::Command qw(spawn reap);
+use Util;
+
+($#ARGV == 1) or fail("syntax: md5sums <pkg> <type>");
+my $pkg = shift;
+my $type = shift;
+
+-f "fields/package" or fail("md5sums invoked in wrong directory");
+
+unlink("md5sums");
+chdir("unpacked")
+    or fail("cannot chdir to unpacked directory: $!");
+
+my %opts = ( pipe_in => FileHandle->new,
+            out => '../md5sums',
+            fail => 'error' );
+spawn(\%opts, ['xargs', '-0r', 'md5sum'] );
+$opts{pipe_in}->blocking(1);
+open(INDEX, '<', "../index")
+    or fail("cannot open index file: $!");
+while (<INDEX>) {
+    next unless m/^-/;
+    chop;
+    $_ = (split(" ", $_, 6))[5];
+    s/ link to .*//;
+    s/\\(\d+)/chr(oct($1))/eg;
+    s/\\\\/\\/g;
+    printf {$opts{pipe_in}} "%s\0", $_;
+}
+close(INDEX);
+
+close $opts{pipe_in};
+reap(\%opts);
+
diff --git a/collection/md5sums.desc b/collection/md5sums.desc
new file mode 100644 (file)
index 0000000..4e5e52f
--- /dev/null
@@ -0,0 +1,7 @@
+Collector-Script: md5sums
+Author: Richard Braakman <dark@xs4all.nl>
+Info: This script runs the "md5sums" over all files in a binary package.
+Type: binary, udeb
+Unpack-Level: 2
+Version: 1
+Order: 1
diff --git a/collection/menu-files b/collection/menu-files
new file mode 100755 (executable)
index 0000000..e220602
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/perl -w
+# menu-files -- maemian collector script
+
+# Copyright (C) 1998 Richard Braakman
+# 
+# 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 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.
+
+use strict;
+
+($#ARGV == 1) or fail("syntax: menu-files <pkg> <type>");
+my $pkg = shift;
+my $type = shift;
+
+-f "fields/package" or fail("menu-files invoked in wrong directory");
+
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Util;
+
+if (-e "menu") {
+    delete_dir('menu')
+       or fail("cannot rm old menu directory");
+}
+
+mkdir("menu", 0777) or fail("cannot mkdir menu: $!");
+
+if (-d "unpacked/usr/lib/menu") {    
+     copy_dir('unpacked/usr/lib/menu', 'menu/lib')
+       or fail("cannot copy unpacked/usr/lib/menu/ directory");
+} else {
+    # no menu directory
+    mkdir("menu/lib", 0777) or fail("cannot mkdir menu/lib: $!");
+}
+
+if (-d "unpacked/usr/share/menu") {    
+    copy_dir('unpacked/usr/share/menu', 'menu/share')
+       or fail("cannot copy unpacked/usr/share/menu directory");
+} else {
+    # no menu directory
+    mkdir("menu/share", 0777) or fail("cannot mkdir menu/share: $!");
+}
diff --git a/collection/menu-files.desc b/collection/menu-files.desc
new file mode 100644 (file)
index 0000000..a1ab6d1
--- /dev/null
@@ -0,0 +1,7 @@
+Collector-Script: menu-files
+Author: Richard Braakman <dark@xs4all.nl>
+Info: This script copies the contents of /usr/lib/menu into the maemian menu/ directory.
+Type: binary
+Unpack-Level: 2
+Version: 1
+Order: 1
diff --git a/collection/objdump-info b/collection/objdump-info
new file mode 100755 (executable)
index 0000000..54eb8e4
--- /dev/null
@@ -0,0 +1,240 @@
+#!/usr/bin/perl -w
+# objdump-info -- maemian collection script
+
+# The original shell script version of this script is
+# Copyright (C) 1998 Christian Schwarz
+# 
+# This version, including support for etch's binutils, is
+# Copyright (C) 2008 Adam D. Barratt
+# 
+# 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 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.
+
+use strict;
+
+# Sanity check
+unless (-f "fields/package") {
+    print STDERR "error: collection script called in wrong directory!\n";
+    exit 2;
+}
+
+my $failed = 0;
+
+open (FILES, '<', "file-info")
+    or fail("cannot open file-info: $!");
+
+open (OUT, '>', "objdump-info")
+    or fail("cannot open objdump-info: $!");
+
+open(PIPE, '-|', "dpkg-query -W -f='\${Version}\n' binutils")
+    or fail("unable to run dpkg-query: $!");
+my $binutils_version = <PIPE>;
+chomp $binutils_version;
+close PIPE;
+
+chdir ("unpacked")
+    or fail ("unable to chdir to unpacked: $!\n");
+
+while (<FILES>) {
+    if (m/^(.+?):\s.*ELF/) {
+       my $bin = $1;
+
+       print OUT "-- $bin\n";
+
+       system("head $bin | grep -q 'packed.*with.*UPX'");
+       print OUT "objdump: $bin: Packed with UPX" if $? == 0;
+
+       if (open(PIPE, '-|', "readelf -l $bin 2>&1")) {
+           local $/;
+           local $_ = <PIPE>;
+           print OUT $_;
+           close PIPE;
+       }
+
+       system("objdump -T $bin >/dev/null 2>&1");
+       if ($? == 0) {
+           # Seems happy so slurp the full output
+           if (open(PIPE, '-|', "objdump --headers --private-headers -T $bin 2>&1")) {
+               local $/;
+               local $_ = <PIPE>;
+               print OUT $_;
+               close PIPE;
+           }
+       } else {
+           $failed = 1;
+           my $invalidop = 0;
+           my $objdumpout = '';
+           if (open(PIPE, '-|', "objdump --headers --private-headers -T $bin 2>&1")) {
+               while(<PIPE>) {
+                   $objdumpout .= $_;
+                   if (m/Invalid operation$/) {
+                       $invalidop = 1;
+                       $failed = 0;
+                   } elsif (m/File format not recognized$/) {
+                       $failed = 0;
+                   } elsif (m/File truncated$/) {
+                       $failed = 0;
+                   }
+               }
+               close PIPE;
+           }
+
+           last if $failed;
+
+           if ($invalidop or $binutils_version !~ m/^2\.17/) {
+               # If we're using a binutils newer than etch's then either
+               # "invalid operation" or "file format not recognized"
+               # are simply passed through to the checks scripts
+               # which handle the output themselves
+               #
+               # If objdump returned "invalid operation" and we are
+               # using etch's binutils then the readelf code will tend
+               # to produce false positives so we just return the
+               # objdump output and let the scripts handle it
+
+               print OUT $objdumpout;
+           } elsif (system("readelf -l $bin 2>&1 | grep -q 'Error: Not an ELF file'") == 0) {
+               print OUT "objdump: $bin: File format not recognized\n";
+           } else {
+               # We're using etch's binutils so attempt to build an output
+               # file in the expected format without using objdump; we lose
+               # some data but none that our later checks actually use
+
+               my @sections;
+               my @symbol_versions;
+
+               if (open(PIPE, '-|', "readelf -W -l -t -d -V $bin")) {
+                   my $section = '';
+                   my %program_headers;
+
+                   while(<PIPE>) {
+                       chomp;
+                       if (m/^Program Headers:/) {
+                           $section = 'PH';
+                           print OUT "$_\n";
+                       } elsif (m/^Section Headers:/) {
+                           $section = 'SH';
+                           print OUT "$_\n";
+                       } elsif (m/^Dynamic section at offset .*:/) {
+                           $section = 'DS';
+                           print OUT "$_\n";
+                       } elsif (m/^Version symbols section /) {
+                           $section = 'VS';
+                       } elsif (m/^\s*$/) {
+                           $section = '';
+                       } elsif (m/^\s*(\S+)\s*(?:(?:\S+\s+){4})\S+\s(...)/
+                             and $section eq 'PH') {
+                           my ($header, $flags) = ($1, $2);
+                           $header =~ s/^GNU_//g;
+                           next if $header eq 'Type';
+
+                           my $newflags = '';
+                           $newflags .= ($flags =~ m/R/) ? 'r' : '-';
+                           $newflags .= ($flags =~ m/W/) ? 'w' : '-';
+                           $newflags .= ($flags =~ m/E/) ? 'x' : '-';
+
+                           $program_headers{$header} = $newflags;
+
+                           print OUT "  $header off 0x0 X 0x0 X 0x0\n  flags $newflags\n";
+                       } elsif (m/^\s*\[(\d+)\]\s*(\S+)(?:\s|\Z)/
+                             and $section eq 'SH') {
+                           $sections[$1] = $2;
+                       } elsif (m/^\s*0x(?:[0-9A-F]+)\s+\((.*?)\)\s+(\S.*)\Z/i
+                             and $section eq 'DS') {
+                           my ($type, $value) = ($1, $2);
+
+                           $value =~ s/^(?:Shared library|Library soname): \[(.*)\]/$1/;
+                           print OUT "  $type   $value\n";
+                       } elsif (m/^\s*[0-9A-F]+:\s+(\S+)\s*(?:\((\S+)\))?(\s|\Z)/i
+                             and $section eq 'VS') {
+                           while (m/([0-9A-F]+h?)\s*(?:\((\S+)\))?(\s|\Z)/gci) {
+                               my ($vernum, $verstring) = ($1, $2);
+                               $verstring ||= '';
+                               if ($vernum =~ m/h$/) {
+                                   $verstring = "($verstring)";
+                               }
+                               push @symbol_versions, $verstring;
+                           }
+                       } elsif (m/^There is no dynamic section in this file/
+                             and exists $program_headers{DYNAMIC}) {
+                           # The headers declare a dynamic section but it's
+                           # empty. Generate the same error as objdump,
+                           # the checks scripts special-case the string.
+                           print OUT "\n\nobjdump: $bin: Invalid operation\n";
+                       }
+                   }
+                   close PIPE;
+               }
+
+               if (open(PIPE, '-|', "readelf -W -s -D $bin")) {
+                   print OUT "DYNAMIC SYMBOL TABLE:\n";
+
+                   while(<PIPE>) {
+                       last if m/^Symbol table of/;
+
+                       if (m/^\s*(\d+)\s+\d+:\s*[0-9a-f]+\s+\d+\s+(?:(?:\S+\s+){3})(\S+)\s+(.*)\Z/) {
+                           my ($symnum, $seg, $sym, $ver) = ($1, $2, $3, '');
+
+                           if ($sym =~ m/^(.*)@(.*)$/) {
+                               $sym = $1;
+                               $ver = $2;
+                           } elsif (@symbol_versions == 0) {
+                               # No versioned symbols...
+                               $ver = '';
+                           } else {
+                               $ver = $symbol_versions[$symnum];
+
+                               if ($ver eq '*local*' or $ver eq '*global*') {
+                                   if ($seg eq 'UND') {
+                                       $ver = '   ';
+                                   } else {
+                                       $ver = 'Base';
+                                   }
+                               } elsif ($ver eq '()') {
+                                   $ver = '(Base)';
+                               }
+                           }
+
+                           if ($seg =~ m/^\d+$/ and defined $sections[$seg]) {
+                               $seg = $sections[$seg];
+                           }
+
+                           print OUT "00      XX $seg  000000  $ver  $sym\n";
+                       }
+                   }
+
+                   close PIPE;
+               }
+           }
+       }
+    }
+}
+
+close FILES;
+close OUT;
+
+exit $failed;
+
+sub fail {
+    if ($_[0]) {
+        print STDERR "internal error: $_[0]\n";
+    } elsif ($!) {
+        print STDERR "internal error: $!\n";
+    } else {
+        print STDERR "internal error.\n";
+    }
+    exit 1;
+}
diff --git a/collection/objdump-info.desc b/collection/objdump-info.desc
new file mode 100644 (file)
index 0000000..c0b7321
--- /dev/null
@@ -0,0 +1,9 @@
+Collector-Script: objdump-info
+Author: Christian Schwarz <schwarz@debian.org>
+Info: This script runs "objdump" over all binaries and object files of a
+ binary package.
+Type: binary, udeb
+Unpack-Level: 2
+Version: 1
+Order: 2
+Needs-Info: file-info
diff --git a/collection/override-file b/collection/override-file
new file mode 100755 (executable)
index 0000000..23b3284
--- /dev/null
@@ -0,0 +1,56 @@
+#!/usr/bin/perl -w
+# override-file -- maemian collector script
+
+# Copyright (C) 1999 by Darren Benham
+# Derived from debian-readme by Richard Braakman
+# 
+# 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 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.
+
+use strict;
+
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Util;
+
+($#ARGV == 1) or fail("syntax: override-file <pkg> <type>");
+my $pkg = shift;
+my $type = shift;
+
+-d "unpacked" or fail("override-file invoked in wrong directory");
+
+unlink("override");
+
+# Pick the first of these files that exists.
+my $file;
+if ($type eq 'source') {
+    $file = "unpacked/debian/source.maemian-overrides";
+} else {
+    $file = "unpacked/usr/share/maemian/overrides/$pkg";
+}
+
+if ( ! -f $file ) 
+{      if ( -f $file . ".gz" ) { $file = $file . ".gz"; }
+       else { $file = ''; }
+}
+
+if ( $file eq '' ) {
+    # no override found
+} elsif ($file =~ /\.gz$/) {
+    gunzip_file($file, "override");
+} else {
+    link($file, "override")
+       or fail("cannot link $file to override: $!");
+}
diff --git a/collection/override-file.desc b/collection/override-file.desc
new file mode 100644 (file)
index 0000000..cdca520
--- /dev/null
@@ -0,0 +1,8 @@
+Collector-Script: override-file
+Author: Darren Benham <gecko@debian.org>
+Info: This script copies the "override" file of a package into the maemian
+ directory.
+Type: binary, udeb, source
+Unpack-Level: 2
+Version: 1
+Order: 1
diff --git a/collection/scripts b/collection/scripts
new file mode 100755 (executable)
index 0000000..fc904bd
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/perl -w
+# scripts -- maemian collection script
+
+# Copyright (C) 1998 Richard Braakman
+# 
+# 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 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.
+
+use strict;
+
+open(SCRIPTS, '>', "scripts") or fail("cannot open scripts output file: $!");
+open(INDEX, '<', "index") or fail("cannot open index file: $!");
+
+my $file;
+my $magic;
+my $scriptpath;
+
+while (<INDEX>) {
+    next unless /^-/;   # skip non-files
+    chop;
+
+    # Extract the filename field from the tar-like file index.
+    # Note that the split is done with an explicit limit so that filenames
+    # with embedded spaces are handled correctly.
+    $file = (split(' ', $_, 6))[5];
+    $file =~ s/ link to .*//;    # cut off info about hard links
+    # This used to call fail() instead of next.  However, the check to
+    # see if all files in the index can be opened should be done elsewhere.
+    open(FILE, '<', "unpacked/$file") or next;
+    if (read(FILE, $magic, 2) and $magic eq '#!' and not eof(FILE)) {
+       $scriptpath = <FILE>;
+       chomp($scriptpath);
+       next if ($scriptpath =~ m/^\#!/); # skip lincity data files
+                                         # #!#!#!
+       my $copy_path = $scriptpath;
+       $scriptpath =~ s/^\s+//; # remove leading whitespace
+       $scriptpath =~ s/^\#.*//; # remove comments
+       if ($scriptpath eq '') {
+           print SCRIPTS "$copy_path $file\n";
+       } else {
+       # This used to have (\S+) rather than (\S*), but that went wrong
+       # with scripts that start with an empty #! line.
+           my $env = '';
+           if ($scriptpath =~ s,^/usr/bin/env\s+,,) {
+               $env = 'env ';
+           }
+            $scriptpath =~ s/^(\S*).*/$1/s;
+           print SCRIPTS $env . "$scriptpath $file\n";
+       }
+    }
+    close(FILE);
+}
+close(INDEX);
+close(SCRIPTS);
+
+open(SCRIPTS, '>', "control-scripts")
+    or fail("cannot open control-scripts output file: $!");
+
+opendir(CONTROL, "control")
+    or fail("cannot read control directory: $!");
+
+for $file (readdir CONTROL) {
+    next unless -f "control/$file";
+    open(FILE, '<', "control/$file") or fail("cannot open control/$file: $!");
+    if (read(FILE, $magic, 2) and $magic eq '#!') {
+       $scriptpath = <FILE>;
+       $scriptpath =~ s/^\s*(\S*).*/$1/s;
+       print SCRIPTS "$scriptpath $file\n"
+    }
+    close(FILE);
+}
+closedir(CONTROL);
+close(SCRIPTS);
+
+exit 0;
+
+# -----------------------------------
+
+sub fail {
+    if ($_[0]) {
+        print STDERR "internal error: $_[0]\n";
+    } elsif ($!) {
+        print STDERR "internal error: $!\n";
+    } else {
+        print STDERR "internal error.\n";
+    }
+    exit 1;
+}
diff --git a/collection/scripts.desc b/collection/scripts.desc
new file mode 100644 (file)
index 0000000..3469335
--- /dev/null
@@ -0,0 +1,11 @@
+Collector-Script: scripts
+Author: Richard Braakman <dark@xs4all.nl>
+Info: This script scans a binary package for scripts that start with #! and
+ lists their filenames together with the interpreter named by their first line.
+  The format is: scriptpath filename
+ Note that the filename might contain spaces, but the scriptpath will not,
+ because linux only looks at the first word when executing a script.
+Type: binary, udeb
+Unpack-Level: 2
+Version: 1
+Order: 1
diff --git a/collection/source-control-file b/collection/source-control-file
new file mode 100755 (executable)
index 0000000..6f68244
--- /dev/null
@@ -0,0 +1,53 @@
+#!/usr/bin/perl -w
+# source-control-file -- maemian collector script
+
+# Copyright (C) 2004 Frank Lichtenheld
+# 
+# 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 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.
+
+use strict;
+use warnings;
+
+($#ARGV == 1) or fail("syntax: source-control-file <pkg> <type>");
+my $pkg = shift;
+my $type = shift;
+
+-f "debfiles/control" or fail("control invoked in wrong directory");
+
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Util;
+
+my @control_data = read_dpkg_control("debfiles/control");
+shift @control_data; # we don't need the source information
+
+delete_dir('control');
+mkdir "control", 0777  or fail( "can't create dir control: $!" );
+
+foreach (@control_data) {
+    my $pkg_name = $_->{'package'};
+    fail("no package line found in control file of $pkg $type")
+        if !$pkg_name;
+    mkdir "control/$pkg_name", 0777
+        or fail( "can't create dir control/$pkg_name: $!" );
+    for my $field (keys %$_) {
+        my $field_file = "control/$pkg_name/$field";
+        open (F, '>', "$field_file")
+            or fail("cannot open file $field_file for writing: $!");
+        print F $_->{$field},"\n";
+        close F;
+    }
+}
diff --git a/collection/source-control-file.desc b/collection/source-control-file.desc
new file mode 100644 (file)
index 0000000..a8127d2
--- /dev/null
@@ -0,0 +1,8 @@
+Collector-Script: source-control-file
+Author: Frank Lichtenheld <djpig@debian.org>
+Info: Collects information about binary packages from debian/control in source packages
+Type: source
+Unpack-Level: 1
+Version: 1
+Order: 2
+Needs-Info: debfiles
diff --git a/collection/strings b/collection/strings
new file mode 100755 (executable)
index 0000000..4583c97
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/sh -e
+# strings -- maemian collection script
+
+# Copyright (C) 2009 Raphael Geissert <atomo64@gmail.com>
+#
+# 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 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.
+
+[ $# -eq 2 ] || {
+    echo "Syntax: strings <pkg> <type>"
+    exit 1
+}
+
+[ ! -f elf-index ] || rm -f elf-index
+exec >elf-index
+
+for bin in $(sed -rn 's/:\s+\bELF\b.+$//g;T;p' file-info); do
+    echo "$bin"
+    case $bin in
+      /usr/lib/debug/*)
+        ;;
+      *)
+        mkdir -p "strings/$(dirname "$bin")"
+        strings "unpacked/$bin" > "strings/$bin"
+        ;;
+    esac
+done
diff --git a/collection/strings.desc b/collection/strings.desc
new file mode 100644 (file)
index 0000000..12f36d9
--- /dev/null
@@ -0,0 +1,9 @@
+Collector-Script: strings
+Author: Raphael Geissert <atomo64@gmail.com>
+Info: This script runs the "strings" command over all files of a binary
+ package.
+Type: binary, udeb
+Unpack-Level: 2
+Version: 1
+Order: 2
+Needs-Info: file-info
diff --git a/data/README b/data/README
new file mode 100644 (file)
index 0000000..b1e7ba9
--- /dev/null
@@ -0,0 +1,12 @@
+This directory contains files loaded by the Lintian::Data module,
+specifically lists of keywords used in various Lintian checks.  For all
+files in this directory, blank lines are ignored, as are lines beginning
+with #.
+
+For each list of keywords, please include in a comment the origin of the
+list, any information about how to resynchronize the list with that
+origin, and any special exceptions or caveats.
+
+Files should generally be organized into subdirectory by check or by
+general class of lists (for example, all lists related to doc-base files
+should go into a doc-base subdirectory).
diff --git a/data/binaries/multiarch b/data/binaries/multiarch
new file mode 100644 (file)
index 0000000..0efa0fd
--- /dev/null
@@ -0,0 +1,18 @@
+# Known multiarch directories.  Binaries under one of these directories
+# under /lib or /usr/lib are permitted in Architecture: all packages since
+# the package may be for multiarch support in a related architecture.
+#
+# See Bug#469301 and Bug#464796 for more details.
+
+# Regular Debian ports.
+arm-linux-gnu
+arm-linux-gnueabi
+i486-linux-gnu
+hppa-linux-gnu
+m68k-linux-gnu
+mips-linux-gnu
+mipsel-linux-gnu
+powerpc-linux-gnu
+s390-linux-gnu
+sparc-linux-gnu
+x86_64-linux-gnu
diff --git a/data/changelog-file/ubuntu-dists b/data/changelog-file/ubuntu-dists
new file mode 100644 (file)
index 0000000..5c7602d
--- /dev/null
@@ -0,0 +1,9 @@
+# A list of Ubuntu distributions, used to suppress some checks for Ubuntu
+# packages and to validate Ubuntu distributions.
+
+dapper
+gutsy
+hardy
+intrepid
+jaunty
+karmic
diff --git a/data/debhelper/dh_commands b/data/debhelper/dh_commands
new file mode 100644 (file)
index 0000000..658324f
--- /dev/null
@@ -0,0 +1,105 @@
+dh_auto_build=debhelper
+dh_auto_build_nant=cli-common-dev
+dh_auto_clean=debhelper
+dh_auto_clean_nant=cli-common-dev
+dh_auto_configure=debhelper
+dh_auto_install=debhelper
+dh_auto_test=debhelper
+dh_bash-completion=bash-completion
+dh_bugfiles=debhelper
+dh_builddeb=debhelper
+dh_buildinfo=dh-buildinfo
+dh_clean=debhelper
+dh_clideps=cli-common-dev
+dh_clifixperms=cli-common-dev
+dh_cligacpolicy=cli-common-dev
+dh_clistrip=cli-common-dev
+dh_compress=debhelper
+dh_consoledata=dh-consoledata
+dh_desktop=debhelper
+dh_fixperms=debhelper
+dh_gconf=debhelper
+dh_gencontrol=debhelper
+dh_gnustep=gnustep-make
+dh_gstscancodecs=libgstreamer0.10-dev
+dh_gtkmodules=libgtk2.0-dev
+dh_haskell=haskell-devscripts
+dh_haskell_build=haskell-devscripts
+dh_haskell_buildinst=haskell-devscripts
+dh_haskell_clean=haskell-devscripts
+dh_haskell_configure=haskell-devscripts
+dh_haskell_depends=haskell-devscripts
+dh_haskell_install=haskell-devscripts
+dh_haskell_prep=haskell-devscripts
+dh_haskell_shlibdeps=haskell-devscripts
+dh_icons=debhelper
+dh_install=debhelper
+dh_installcatalogs=debhelper
+dh_installchangelogs=debhelper
+dh_installcligac=cli-common-dev
+dh_installcron=debhelper
+dh_installdeb=debhelper
+dh_installdebconf=debhelper
+dh_installdefoma=defoma
+dh_installdirs=debhelper
+dh_installdocs=debhelper
+dh_installemacsen=debhelper
+dh_installexamples=debhelper
+dh_installifupdown=debhelper
+dh_installinfo=debhelper
+dh_installinit=debhelper
+dh_installkpatches=dh-kpatches
+dh_installlisting=desktop-profiles
+dh_installlogcheck=debhelper
+dh_installlogrotate=debhelper
+dh_installman=debhelper
+dh_installmanpages=debhelper
+dh_installmenu=debhelper
+dh_installmime=debhelper
+dh_installmodules=debhelper
+dh_installpam=debhelper
+dh_installppp=debhelper
+dh_installtex=tex-common
+dh_installudev=debhelper
+dh_installwm=debhelper
+dh_installxfonts=debhelper
+dh_installxmlcatalogs=xml-core
+dh_installxsp=mono-xsp-base
+dh_installyorick=yorick-dev
+dh_installzope=zope-debhelper
+dh_installzopeinstance=zope-debhelper
+dh_javadoc=gjdoc
+dh_link=debhelper
+dh_lintian=debhelper
+dh_lisp=dh-lisp
+dh_listpackages=debhelper
+dh_make=dh-make
+dh_makeclilibs=cli-common-dev
+dh_makeshlibs=debhelper
+dh_md5sums=debhelper
+dh_metainit=dh-metainit
+dh_movefiles=debhelper
+dh_nativejava=libgcj-common
+dh_ocaml=dh-ocaml
+dh_pangomodules=libpango1.0-dev
+dh_perl=debhelper
+dh_pidgin=pidgin-dev
+dh_prep=debhelper
+dh_pycentral=python-central
+dh_pysupport=python-support
+dh_python=debhelper
+dh_quilt_patch=quilt
+dh_quilt_unpatch=quilt
+dh_rdoc=ruby-pkg-tools
+dh_sameversiondep=pkg-kde-tools
+dh_scrollkeeper=debhelper
+dh_shlibdeps=debhelper
+dh_strip=debhelper
+dh_suidregister=debhelper
+dh_testdir=debhelper
+dh_testroot=debhelper
+dh_testversion=debhelper
+dh_undocumented=debhelper
+dh_upx=upx-ucl
+dh_usrlocal=debhelper
+dh_xine=libxine-dev
diff --git a/data/debhelper/dh_packages b/data/debhelper/dh_packages
new file mode 100644 (file)
index 0000000..a05df0e
--- /dev/null
@@ -0,0 +1,32 @@
+bash-completion
+cli-common-dev
+debhelper
+defoma
+desktop-profiles
+dh-buildinfo
+dh-consoledata
+dh-kpatches
+dh-lisp
+dh-make
+dh-metainit
+dh-ocaml
+gjdoc
+gnustep-make
+haskell-devscripts
+libgcj-common
+libgstreamer0.10-dev
+libgtk2.0-dev
+libpango1.0-dev
+libxine-dev
+mono-xsp-base
+pidgin-dev
+pkg-kde-tools
+python-central
+python-support
+quilt
+ruby-pkg-tools
+tex-common
+upx-ucl
+xml-core
+yorick-dev
+zope-debhelper
diff --git a/data/debhelper/filename-config-files b/data/debhelper/filename-config-files
new file mode 100644 (file)
index 0000000..ff58d02
--- /dev/null
@@ -0,0 +1,13 @@
+# This is a list of known debhelper config files which consist of a list
+# of filenames.  It's used for tests that look for problems in debhelper
+# processing of file names, such as using glob characters that aren't
+# guaranteed to work.
+
+dirs
+docs
+examples
+info
+install
+manpages
+sgmlcatalogs
+wm
diff --git a/data/debhelper/maint_commands b/data/debhelper/maint_commands
new file mode 100644 (file)
index 0000000..6a9789b
--- /dev/null
@@ -0,0 +1,35 @@
+dh_cligacpolicy
+dh_consoledata
+dh_gconf
+dh_haskell_prep
+dh_icons
+dh_installcatalogs
+dh_installcligac
+dh_installdebconf
+dh_installdefoma
+dh_installemacsen
+dh_installinfo
+dh_installinit
+dh_installlisting
+dh_installmenu
+dh_installmime
+dh_installmodules
+dh_installtex
+dh_installudev
+dh_installwm
+dh_installxfonts
+dh_installxmlcatalogs
+dh_installxsp
+dh_installzope
+dh_installzopeinstance
+dh_lisp
+dh_makeshlibs
+dh_metainit
+dh_nativejava
+dh_ocaml
+dh_pycentral
+dh_pysupport
+dh_python
+dh_scrollkeeper
+dh_suidregister
+dh_usrlocal
diff --git a/data/debhelper/miscDepends_commands b/data/debhelper/miscDepends_commands
new file mode 100644 (file)
index 0000000..0afb23c
--- /dev/null
@@ -0,0 +1,12 @@
+dh_gconf
+dh_gtkmodules
+dh_installcatalogs
+dh_installdebconf
+dh_installlisting
+dh_installtex
+dh_installxfonts
+dh_installxmlcatalogs
+dh_lisp
+dh_nativejava
+dh_pangomodules
+dh_pidgin
diff --git a/data/doc-base/sections b/data/doc-base/sections
new file mode 100644 (file)
index 0000000..41ef102
--- /dev/null
@@ -0,0 +1,74 @@
+# Taken from /usr/share/doc-base/data/sections.list.
+#
+# Last synchronized with doc-base 0.8.16 (2008-05-14).
+
+Accessibility
+Amateur Radio
+Data Management
+Debian
+Editors
+Education
+Emulators
+File Management
+Games/Action
+Games/Adventure
+Games/Blocks
+Games/Board
+Games/Card
+Games/Puzzles
+Games/Simulation
+Games/Strategy
+Games/Tools
+Games/Toys
+Graphics
+Help
+Help/Books
+Help/FAQ
+Help/HOWTO
+Help/RFC
+Help/Standards
+Mobile Devices
+Network/Communication
+Network/File Transfer
+Network/Monitoring
+Network/Web Browsing
+Network/Web News
+Office
+Programming
+Programming/C
+Programming/C++
+Programming/Java
+Programming/OCaml
+Programming/Perl
+Programming/Python
+Programming/Ruby
+Project Management
+Science/Astronomy
+Science/Biology
+Science/Chemistry
+Science/Data Analysis
+Science/Electronics
+Science/Engineering
+Science/Geoscience
+Science/Mathematics
+Science/Medicine
+Science/Physics
+Science/Social
+Screen
+Screen/Saving
+Screen/Locking
+Shells
+Sound
+System/Administration
+System/Hardware
+System/Language Environment
+System/Monitoring
+System/Package Management
+System/Security
+Terminal Emulators
+Text
+TV and Radio
+Viewers
+Video
+Web Development
+Window Managers
diff --git a/data/fields/architectures b/data/fields/architectures
new file mode 100644 (file)
index 0000000..822c3a4
--- /dev/null
@@ -0,0 +1,187 @@
+# List of known architectures as provided by dpkg-architecture
+# Last updated: 2009-03-12
+
+all
+alpha
+amd64
+any
+arm
+armeb
+armel
+darwin-alpha
+darwin-amd64
+darwin-arm
+darwin-armeb
+darwin-hppa
+darwin-i386
+darwin-ia64
+darwin-m32r
+darwin-m68k
+darwin-mips
+darwin-mipsel
+darwin-powerpc
+darwin-ppc64
+darwin-s390
+darwin-s390x
+darwin-sh3
+darwin-sh3eb
+darwin-sh4
+darwin-sh4eb
+darwin-sparc
+freebsd-alpha
+freebsd-amd64
+freebsd-arm
+freebsd-armeb
+freebsd-hppa
+freebsd-i386
+freebsd-ia64
+freebsd-m32r
+freebsd-m68k
+freebsd-mips
+freebsd-mipsel
+freebsd-powerpc
+freebsd-ppc64
+freebsd-s390
+freebsd-s390x
+freebsd-sh3
+freebsd-sh3eb
+freebsd-sh4
+freebsd-sh4eb
+freebsd-sparc
+hppa
+hurd-alpha
+hurd-amd64
+hurd-arm
+hurd-armeb
+hurd-hppa
+hurd-i386
+hurd-ia64
+hurd-m32r
+hurd-m68k
+hurd-mips
+hurd-mipsel
+hurd-powerpc
+hurd-ppc64
+hurd-s390
+hurd-s390x
+hurd-sh3
+hurd-sh3eb
+hurd-sh4
+hurd-sh4eb
+hurd-sparc
+i386
+ia64
+kfreebsd-alpha
+kfreebsd-amd64
+kfreebsd-arm
+kfreebsd-armeb
+kfreebsd-hppa
+kfreebsd-i386
+kfreebsd-ia64
+kfreebsd-m32r
+kfreebsd-m68k
+kfreebsd-mips
+kfreebsd-mipsel
+kfreebsd-powerpc
+kfreebsd-ppc64
+kfreebsd-s390
+kfreebsd-s390x
+kfreebsd-sh3
+kfreebsd-sh3eb
+kfreebsd-sh4
+kfreebsd-sh4eb
+kfreebsd-sparc
+knetbsd-alpha
+knetbsd-amd64
+knetbsd-arm
+knetbsd-armeb
+knetbsd-hppa
+knetbsd-i386
+knetbsd-ia64
+knetbsd-m32r
+knetbsd-m68k
+knetbsd-mips
+knetbsd-mipsel
+knetbsd-powerpc
+knetbsd-ppc64
+knetbsd-s390
+knetbsd-s390x
+knetbsd-sh3
+knetbsd-sh3eb
+knetbsd-sh4
+knetbsd-sh4eb
+knetbsd-sparc
+lpia
+m32r
+m68k
+mips
+mipsel
+netbsd-alpha
+netbsd-amd64
+netbsd-arm
+netbsd-armeb
+netbsd-hppa
+netbsd-i386
+netbsd-ia64
+netbsd-m32r
+netbsd-m68k
+netbsd-mips
+netbsd-mipsel
+netbsd-powerpc
+netbsd-ppc64
+netbsd-s390
+netbsd-s390x
+netbsd-sh3
+netbsd-sh3eb
+netbsd-sh4
+netbsd-sh4eb
+netbsd-sparc
+openbsd-alpha
+openbsd-amd64
+openbsd-arm
+openbsd-armeb
+openbsd-hppa
+openbsd-i386
+openbsd-ia64
+openbsd-m32r
+openbsd-m68k
+openbsd-mips
+openbsd-mipsel
+openbsd-powerpc
+openbsd-ppc64
+openbsd-s390
+openbsd-s390x
+openbsd-sh3
+openbsd-sh3eb
+openbsd-sh4
+openbsd-sh4eb
+openbsd-sparc
+powerpc
+ppc64
+s390
+s390x
+sh3
+sh3eb
+sh4
+sh4eb
+solaris-alpha
+solaris-amd64
+solaris-arm
+solaris-armeb
+solaris-hppa
+solaris-i386
+solaris-ia64
+solaris-m32r
+solaris-m68k
+solaris-mips
+solaris-mipsel
+solaris-powerpc
+solaris-ppc64
+solaris-s390
+solaris-s390x
+solaris-sh3
+solaris-sh3eb
+solaris-sh4
+solaris-sh4eb
+solaris-sparc
+sparc
diff --git a/data/fields/obsolete-packages b/data/fields/obsolete-packages
new file mode 100644 (file)
index 0000000..9726cf0
--- /dev/null
@@ -0,0 +1,58 @@
+# Known obsolete packages.  Not all packages are added to this list, only
+# ones for which it's helpful for Lintian to warn about, such as
+# significant transitions or transitional packages that we're trying to
+# remove from the archive.
+#
+# Each list of packages should be tagged with the last Debian release in
+# which the package appeared so that we can remove long-obsolete entries
+# that are no longer worth checking for.
+
+# Last seen in sarge.
+xlibs-dev
+
+# Last seen in etch.
+debmake
+gcc-2.95
+
+# Last seen in lenny.
+cdrecord
+cupsys
+cupsys-bsd
+cupsys-client
+cupsys-common
+foomatic-data
+gaim
+gnomemeeting
+gs
+gs-aladdin
+gs-esp
+gs-gpl
+kernel-headers
+kernel-image
+kernel-image-2.4
+kernel-image-2.6
+kernel-source
+lambdamoo-core
+lambdamoo-server
+libcupsys2
+libcupsys2-dev
+libglu1-xorg
+libglu1-xorg-dev
+libmime-perl
+libungif4-dev
+libxerces28
+libxerces28-dev
+mkisofs
+netcdfg-dev
+pcmcia-cs
+python-pyopenssl
+ssh-krb5
+tetex-base
+tetex-bin
+tetex-extra
+x-dev
+xbase-clients
+xlibmesa-gl
+xlibmesa-gl-dev
+xlibmesa-glu
+xutils
diff --git a/data/fields/perl-provides b/data/fields/perl-provides
new file mode 100644 (file)
index 0000000..2c1a4dc
--- /dev/null
@@ -0,0 +1,29 @@
+# virtual packages provided by the Perl core packages that also have a
+# separate binary package available
+#
+# the listed version is the one included in the Perl core
+#
+# regenerate by running
+#   debian/rules refresh-perl-provides
+# in the lintian source tree
+#
+# last updated for PERL_VERSION=5.010000
+libtime-piece-perl 1.12
+libsys-syslog-perl 0.22
+libio-compress-zlib-perl 2.008
+libio-compress-base-perl 2.008
+libdigest-sha-perl 5.45
+libcompress-zlib-perl 2.008
+libcompress-raw-zlib-perl 2.008
+libtest-simple-perl 0.72
+libtest-harness-perl 2.64
+libpod-simple-perl 3.05
+libpod-escapes-perl 1.04
+libmodule-pluggable-perl 3.6
+libmodule-load-conditional-perl 0.22
+libmodule-corelist-perl 2.12
+libmodule-build-perl 0.2808.01
+libio-zlib-perl 1.07
+libfile-temp-perl 0.18
+libextutils-parsexs-perl 2.18.02
+libextutils-cbuilder-perl 0.21
diff --git a/data/fields/virtual-packages b/data/fields/virtual-packages
new file mode 100644 (file)
index 0000000..608fefb
--- /dev/null
@@ -0,0 +1,283 @@
+# The list of virtual packages in Debian that are provided by two or more
+# packages.
+#
+# Packages that should be listed but are not found by this script can be
+# listed in a special comment in this file.  They will then be preserved when
+# the list is regenerated.  Such packages must be listed in a comment line
+# staring with "Keep:".  Multiple packages can be specified in the same line,
+# separated by comma and/or white space. Multiple "Keep: " lines can be used
+# as well.
+#
+# Last updated: 2009-05-15
+
+
+aide-binary
+alsaplayer-interface
+alsaplayer-output
+apache2-mpm
+aptitude-doc
+aspell-dictionary
+aspell6a-dictionary
+atl2-modules
+audio-mixer
+aufs-modules
+automaken
+awk
+bacula-director
+bacula-sd-tools
+batman-adv-modules
+batmand-gateway-modules
+bochs-gui
+bogofilter-db
+boom-wad
+c++-compiler
+c++abi2-dev
+c-compiler
+c-shell
+chasen-dic
+childsplay-alphabet-sounds
+cl-sql-backend
+console-utilities
+cyrus21-clients
+cyrus21-imapd
+cyrus21-pop3d
+cyrus22-imapd
+cyrus22-pop3d
+debconf-2.0
+dict-client
+dictd-dictionary
+djvu-viewer
+docbook-xsl-doc
+drbd-module-source
+drbd-utils
+drbd8-modules
+dssi-plugin
+dyndns-client
+editor
+emacsen
+epic4-script
+erlang-abi-13.a
+exim4-localscanapi-1.0
+exim4-localscanapi-1.1
+festival-voice
+finger-server
+firebird-server
+fortran95-compiler
+fortune-cookie-db
+freeciv
+freeciv-client
+fsck-backend
+ftp-server
+fwbuilder-backend
+gcompris-sound
+gforge-mta
+gforge-plugin-scm
+ggz-core-client
+gift-client
+gift-plugin
+gimp-help
+gnome-www-browser
+gnustep-back0.16-alt
+gstreamer0.10-audiosink
+gstreamer0.10-audiosource
+gstreamer0.10-videosink
+gstreamer0.10-videosource
+guile
+gvim
+httpd
+httpd-cgi
+hunspell-dictionary
+hunspell-dictionary-de
+hunspell-dictionary-en
+ident-server
+ike-server
+imap-client
+imap-server
+imaze
+inet-superserver
+inews
+info-browser
+irc
+ircd
+ispell-dictionary
+itcl-doc
+itk-doc
+java-compiler
+java-runtime
+java-runtime-headless
+java-sdk
+java-virtual-machine
+java1-runtime
+java1-runtime-headless
+java2-runtime
+java2-runtime-headless
+java2-sdk
+java5-runtime
+java5-runtime-headless
+java5-sdk
+kde-l10n
+koffice-i18n
+ladspa-host
+ladspa-plugin
+libatlas-3gf.so
+libatlas.so.3gf
+libbatteries-camlp4-dev
+libblas-3gf.so
+libblas.so.3gf
+libcurl-dev
+libcurl-ssl-dev
+libcyrus-imap-perl
+libcyrus-imap-perl21
+libdb++-dev
+libdb-java
+libdb-java-dev
+libdeps-renderer
+libdspam7-drv
+libedac
+libfcgi-ruby
+libfilesystem-ruby
+libgd-dev
+libgd2
+libgdchart
+libgdchart-gd2-dev
+libgettext-ruby
+libggi-target
+libgl-dev
+libgl1
+libguile-dev
+libhdf5-1.6.6-0
+libhdf5-dev
+libhk-classes-driver
+libhtml-wikiconverter-dialect
+libimage-size-ruby
+liblapack-3gf.so
+liblapack.so.3gf
+libmpich-dev
+libneon-dev
+libsoqt-dev
+libstdc++-dev
+libstlport-dev
+libswfdec-dev
+licq-plugin
+linux-headers
+linux-headers-2.6
+linux-image
+linux-image-2.6
+linux-initramfs-tool
+linux-kernel-log-daemon
+linux-latest-modules-2.6.29-2-486
+linux-latest-modules-2.6.29-2-686
+linux-latest-modules-2.6.29-2-686-bigmem
+linux-latest-modules-2.6.29-2-amd64
+lisp-compiler
+loop-aes-modules
+lsb-qt4-ia32
+lsb-qt4-noarch
+lua
+lzma-modules
+mail-reader
+mail-transport-agent
+man-browser
+mnogosearch
+monodoc-viewer
+mp3-decoder
+mpd-client
+mpipython
+myspell-dictionary
+myspell-dictionary-de
+myspell-dictionary-en
+myspell-dictionary-fr
+myspell-dictionary-pt
+nethack
+netkit-inetd
+netpbm-dev
+news-reader
+news-transport-system
+nfs-server
+nilfs2-modules
+objc++-compiler
+objc-compiler
+open
+openoffice.org-help-3.1
+openoffice.org-hyphenation
+openoffice.org-l10n-3.1
+openoffice.org-spellcheck-de-at
+openoffice.org-spellcheck-de-ch
+openoffice.org-spellcheck-de-de
+openoffice.org-spellcheck-fi
+openoffice.org2-thesaurus
+openoffice.org2-thesaurus-ru
+pascal-compiler
+paw-binary
+pcsc-ifd-handler
+pdf-viewer
+pdns-backend
+pgdocs
+phonon-backend
+phpapi-20060613+lfs
+pinentry
+pinentry-x11
+ping
+pop3-server
+postscript-preview
+postscript-viewer
+radius-server
+ratbox-services
+readline-editor
+root-db-client
+root-file-server
+root-fitter
+skkserv
+snort-rules
+speakup-modules
+sqlrelay-api
+sqlrelay-connection-daemon
+ssh-server
+sword-comm
+sword-dict
+sword-frontend
+sword-text
+system-log-daemon
+tcldoc
+tclsh
+telepathy-connection-manager
+telnet-client
+telnet-server
+tesseract-ocr-language
+time-daemon
+tipptrainer-data
+tkdoc
+tp-smapi-modules
+ttf-japanese-gothic
+ttf-japanese-mincho
+ups-monitor
+usplash-theme
+vim-perl
+vim-python
+vim-ruby
+vim-tcl
+virtualbox-ose-guest-modules
+virtualbox-ose-modules
+vnc-server
+vnc-viewer
+wims-extra
+wish
+wordlist
+www-browser
+wx-doc
+wx-i18n
+x-audio-mixer
+x-display-manager
+x-session-manager
+x-terminal-emulator
+x-window-manager
+xen-hypervisor
+xen-hypervisor-3
+xen-hypervisor-3.2-1
+xmp-player
+xserver
+xserver-xorg-input-4
+xserver-xorg-video-2
+xserver-xorg-video-5
+zcode-interpreter
+zope
diff --git a/lib/Checker.pm b/lib/Checker.pm
new file mode 100644 (file)
index 0000000..b65c266
--- /dev/null
@@ -0,0 +1,59 @@
+# Checker -- Perl checker functions for lintian
+
+# Copyright (C) 2004 Jeroen van Wolffelaar
+#
+# 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 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.
+
+package Checker;
+use strict;
+no strict 'refs';
+
+# Quiet "Name "main::MAEMIAN_ROOT" used only once"
+# The variable comes from 'lintian'
+() = $main::MAEMIAN_ROOT;
+my $MAEMIAN_ROOT = $main::MAEMIAN_ROOT;
+my $debug = $::debug;
+
+sub runcheck {
+       my ($pkg, $type, $info, $name) = @_;
+
+       # Will be set to 2 if error is encountered
+       my $return = 0;
+
+       print "N: Running check: $name ...\n" if $debug;
+
+       # require has an anti-require-twice cache
+       require "$MAEMIAN_ROOT/checks/$name";
+
+       $name =~ s/[-.]/_/g;
+       eval { &{'Maemian::'.$name.'::run'}($pkg, $type, $info) };
+       if ( $@ ) {
+           print STDERR $@;
+           print STDERR "internal error: cannot run $name check on package $pkg\n";
+           $return = 2;
+       }
+
+       return $return;
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: t
+# cperl-indent-level: 8
+# End:
+# vim: ts=4 sw=4 noet
diff --git a/lib/Maemian/Collect.pm b/lib/Maemian/Collect.pm
new file mode 100644 (file)
index 0000000..89077b2
--- /dev/null
@@ -0,0 +1,155 @@
+# Maemian::Collect -- interface to package data collection
+
+# Copyright (C) 2009 Jeremiah C. Foster
+# Based on the work of Russ Allbery
+#
+# 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 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, see <http://www.gnu.org/licenses/>.
+
+package Maemian::Collect;
+use strict;
+
+# Take the package name and type, initialize an appropriate collect object
+# based on the package type, and return it.  Returns undef for an unknown
+# package type.
+sub new {
+    my ($class, $pkg, $type) = @_;
+    my $object;
+    if ($type eq 'source') {
+        require Maemian::Collect::Source;
+        $object = Maemian::Collect::Source->new ($pkg);
+    } elsif ($type eq 'binary' or $type eq 'udeb') {
+        require Maemian::Collect::Binary;
+        $object = Maemian::Collect::Binary->new ($pkg);
+    } else {
+        return;
+    }
+    $object->{name} = $pkg;
+    $object->{type} = $type;
+    return $object;
+}
+
+# Return the package name.
+# sub name Needs-Info <>
+sub name {
+    my ($self) = @_;
+    return $self->{name};
+}
+
+# Return the package type.
+# sub type Needs-Info <>
+sub type {
+    my ($self) = @_;
+    return $self->{type};
+}
+
+# Return the value of the specified control field of the package, or undef if
+# that field wasn't present in the control file for the package.  For source
+# packages, this is the *.dsc file; for binary packages, this is the control
+# file in the control section of the package.
+# sub field Needs-Info <>
+sub field {
+    my ($self, $field) = @_;
+    return $self->{field}{$field} if exists $self->{field}{$field};
+    if (open(FIELD, '<', "fields/$field")) {
+        local $/;
+        my $value = <FIELD>;
+        close FIELD;
+        $value =~ s/\n\z//;
+        $self->{field}{$field} = $value;
+    } else {
+        $self->{field}{$field} = undef;
+    }
+    return $self->{field}{$field};
+}
+
+=head1 NAME
+
+Maemian::Collect - Maemian interface to package data collection
+
+=head1 SYNOPSIS
+
+    my ($name, $type) = ('foobar', 'udeb');
+    my $collect = Maemian::Collect->new($name, $type);
+    $name = $collect->name;
+    $type = $collect->type;
+
+=head1 DESCRIPTION
+
+Maemian::Collect provides the shared interface to package data used by
+source, binary, and udeb packages.  It creates an object of the
+appropriate type and provides common functions used by the collection
+interface to all three types of packages.
+
+This module is in its infancy.  Most of Maemian still reads all data from
+files in the laboratory whenever that data is needed and generates that
+data via collect scripts.  The goal is to eventually access all data via
+this module and its subclasses so that the module can cache data where
+appropriate and possibly retire collect scripts in favor of caching that
+data in memory.
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item new(PACKAGE, TYPE)
+
+Creates a new object appropriate to the package type.  Currently, the only
+TYPE supported is C<source>, which creates a new Maemian::Collect::Source
+object and returns it.  TYPE can be retrieved later with the type()
+method.  Returns undef an unknown TYPE.
+
+PACKAGE is the name of the package and is stored in the collect object.
+It can be retrieved with the name() method.
+
+=back
+
+=head1 INSTANCE METHODS
+
+In addition to the instance methods documented here, see the documentation
+of Maemian::Collect::Source for instance methods specific to source
+packages.
+
+=over 4
+
+=item field(FIELD)
+
+Returns the value of the control field FIELD in the control file for the
+package.  For a source package, this is the *.dsc file; for a binary
+package, this is the control file in the control section of the package.
+The value will be read from the F<fields/> subdirectory of the current
+directory if it hasn't previously been requested and cached in memory so
+that subsequent requests for the same field can be answered without file
+accesses.
+
+=item name()
+
+Returns the name of the package.
+
+=item type()
+
+Returns the type of the package.
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Russ Allbery <rra@debian.org> for Maemian.
+
+=head1 SEE ALSO
+
+maemian(1), Maemian::Collect::Source(3)
+
+=cut
+
+1;
diff --git a/lib/Maemian/Collect/Binary.pm b/lib/Maemian/Collect/Binary.pm
new file mode 100644 (file)
index 0000000..a68ac65
--- /dev/null
@@ -0,0 +1,454 @@
+# Maemian::Collect::Binary -- interface to binary package data collection
+
+# Copyright (C) 2008, 2009 Russ Allbery
+# Copyright (C) 2008 Frank Lichtenheld
+#
+# 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 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, see <http://www.gnu.org/licenses/>.
+
+package Maemian::Collect::Binary;
+
+use strict;
+use warnings;
+use base 'Maemian::Collect';
+
+use Maemian::Relation;
+use Carp qw(croak);
+use Parse::DebianChangelog;
+
+use Util;
+
+# Initialize a new binary package collect object.  Takes the package name,
+# which is currently unused.
+sub new {
+    my ($class, $pkg) = @_;
+    my $self = {};
+    bless($self, $class);
+    return $self;
+}
+
+# Returns whether the package is a native package according to
+# its version number
+# sub native Needs-Info <>
+sub native {
+    my ($self) = @_;
+    return $self->{native} if exists $self->{native};
+    my $version = $self->field('version');
+    $self->{native} = ($version !~ m/-/);
+    return $self->{native};
+}
+
+# Get the changelog file of a binary package as a Parse::DebianChangelog
+# object.  Returns undef if the changelog file couldn't be found.
+sub changelog {
+    my ($self) = @_;
+    return $self->{changelog} if exists $self->{changelog};
+    # sub changelog Needs-Info changelog-file
+    if (-l 'changelog' || ! -f 'changelog') {
+        $self->{changelog} = undef;
+    } else {
+        my %opts = (infile => 'changelog', quiet => 1);
+        $self->{changelog} = Parse::DebianChangelog->init(\%opts);
+    }
+    return $self->{changelog};
+}
+
+# Returns the information from the indices
+# FIXME: should maybe return an object
+# sub index Needs-Info <>
+sub index {
+    my ($self) = @_;
+    return $self->{index} if exists $self->{index};
+
+    my (%idx, %dir_counts);
+    open my $idx, '<', "index"
+        or fail("cannot open index file index: $!");
+    open my $num_idx, '<', "index-owner-id"
+        or fail("cannot open index file index-owner-id: $!");
+    while (<$idx>) {
+        chomp;
+
+        my (%file, $perm, $owner, $name);
+        ($perm,$owner,$file{size},$file{date},$file{time},$name) =
+            split(' ', $_, 6);
+        $file{operm} = perm2oct($perm);
+        $file{type} = substr $perm, 0, 1;
+
+        my $numeric = <$num_idx>;
+        chomp $numeric;
+        fail("cannot read index file index-owner-id") unless defined $numeric;
+        my ($owner_id, $name_chk) = (split(' ', $numeric, 6))[1, 5];
+        fail("mismatching contents of index files: $name $name_chk")
+            if $name ne $name_chk;
+
+        ($file{owner}, $file{group}) = split '/', $owner, 2;
+        ($file{uid}, $file{gid}) = split '/', $owner_id, 2;
+
+        $name =~ s,^\./,,;
+        if ($name =~ s/ link to (.*)//) {
+            $file{type} = 'h';
+            $file{link} = $1;
+            $file{link} =~ s,^\./,,;
+        } elsif ($file{type} eq 'l') {
+            ($name, $file{link}) = split ' -> ', $name, 2;
+        }
+        $file{name} = $name;
+
+        # count directory contents:
+        $dir_counts{$name} ||= 0 if $file{type} eq 'd';
+        $dir_counts{$1} = ($dir_counts{$1} || 0) + 1
+            if $name =~ m,^(.+/)[^/]+/?$,;
+
+        $idx{$name} = \%file;
+    }
+    foreach my $file (keys %idx) {
+        if ($dir_counts{$idx{$file}->{name}}) {
+            $idx{$file}->{count} = $dir_counts{$idx{$file}->{name}};
+        }
+    }
+    $self->{index} = \%idx;
+
+    return $self->{index};
+}
+
+# Returns the information from collect/file-info
+sub file_info {
+    my ($self) = @_;
+    return $self->{file_info} if exists $self->{file_info};
+
+    my %file_info;
+    # sub file_info Needs-Info file-info
+    open(my $idx, '<', "file-info")
+        or fail("cannot open file-info: $!");
+    while (<$idx>) {
+        chomp;
+
+        m/^(.+?):\s+(.*)$/o
+            or fail("an error in the file pkg is preventing lintian from checking this package: $_");
+        my ($file, $info) = ($1,$2);
+
+        $file =~ s,^\./,,o;
+        $file =~ s,/+$,,o;
+
+        $file_info{$file} = $info;
+    }
+    close $idx;
+    $self->{file_info} = \%file_info;
+
+    return $self->{file_info};
+}
+
+sub scripts {
+    my ($self) = @_;
+    return $self->{scripts} if exists $self->{scripts};
+
+    my %scripts;
+    # sub scripts Needs-Info scripts
+    open(SCRIPTS, '<', "scripts")
+       or fail("cannot open scripts file: $!");
+    while (<SCRIPTS>) {
+       chomp;
+       my (%file, $name);
+
+       m/^(env )?(\S*) (.*)$/o
+           or fail("bad line in scripts file: $_");
+       ($file{calls_env}, $file{interpreter}, $name) = ($1, $2, $3);
+
+       $name =~ s,^\./,,o;
+       $name =~ s,/+$,,o;
+       $file{name} = $name;
+       $scripts{$name} = \%file;
+    }
+    close SCRIPTS;
+    $self->{scripts} = \%scripts;
+
+    return $self->{scripts};
+}
+
+
+# Returns the information from collect/objdump-info
+sub objdump_info {
+    my ($self) = @_;
+    return $self->{objdump_info} if exists $self->{objdump_info};
+
+    my %objdump_info;
+    my ($dynsyms, $file);
+    # sub objdump_info Needs-Info objdump-info
+    open(my $idx, '<', "objdump-info")
+        or fail("cannot open objdump-info: $!");
+    while (<$idx>) {
+        chomp;
+
+        next if m/^\s*$/o;
+
+        if (m,^-- \./(\S+)\s*$,o) {
+            if ($file) {
+                $objdump_info{$file->{name}} = $file;
+            }
+            $file = { name => $1 };
+            $dynsyms = 0;
+        } elsif ($dynsyms) {
+            # The .*? near the end is added because a number of optional fields
+            # might be printed.  The symbol name should be the last word.
+            if (m/^[0-9a-fA-F]+.{6}\w\w?\s+(\S+)\s+[0-9a-zA-Z]+\s+(\S+)\s+(\S+)$/){
+                my ($foo, $sec, $sym) = ($1, $2, $3);
+                push @{$file->{SYMBOLS}}, [ $foo, $sec, $sym ];
+
+               if ($foo eq '.text' and $sec eq 'Base' and $sym eq 'caml_main') {
+                   $file->{OCAML} = 1;
+               }
+            }
+        } else {
+            if (m/^\s*NEEDED\s*(\S+)/o) {
+                push @{$file->{NEEDED}}, $1;
+            } elsif (m/^\s*RPATH\s*(\S+)/o) {
+                foreach (split m/:/, $1) {
+                    $file->{RPATH}{$_}++;
+                }
+            } elsif (m/^\s*SONAME\s*(\S+)/o) {
+                push @{$file->{SONAME}}, $1;
+            } elsif (m/^\s*\d+\s+\.comment\s+/o) {
+                $file->{COMMENT_SECTION} = 1;
+            } elsif (m/^\s*\d+\s+\.note\s+/o) {
+                $file->{NOTE_SECTION} = 1;
+            } elsif (m/^DYNAMIC SYMBOL TABLE:/) {
+                $dynsyms = 1;
+            } elsif (m/^objdump: (.*?): File format not recognized$/) {
+                push @{$file->{NOTES}}, "File format not recognized";
+            } elsif (m/^objdump: (.*?): File truncated$/) {
+                push @{$file->{NOTES}}, "File truncated";
+            } elsif (m/^objdump: \.(.*?): Packed with UPX$/) {
+                push @{$file->{NOTES}}, "Packed with UPX";
+            } elsif (m/objdump: \.(.*?): Invalid operation$/) {
+                # Don't anchor this regex since it can be interspersed with other
+                # output and hence not on the beginning of a line.
+                push @{$file->{NOTES}}, "Invalid operation";
+            } elsif (m/CXXABI/) {
+                $file->{CXXABI} = 1;
+            } elsif (m%Requesting program interpreter:\s+/lib/klibc-\S+\.so%) {
+                $file->{KLIBC} = 1;
+           } elsif (m/^\s*TEXTREL\s/o) {
+               $file->{TEXTREL} = 1;
+           } elsif (m/^\s*INTERP\s/) {
+               $file->{INTERP} = 1;
+           } elsif (m/^\s*STACK\s/) {
+               $file->{STACK} = 0;
+           } else {
+               if (defined $file->{STACK} and $file->{STACK} eq 0) {
+                   m/\sflags\s+(\S+)/o;
+                   $file->{STACK} = $1;
+               } else {
+                   $file->{OTHER_DATA} = 1;
+               }
+           }
+       }
+    }
+    if ($file) {
+        $objdump_info{$file->{name}} = $file;
+    }
+    $self->{objdump_info} = \%objdump_info;
+
+    return $self->{objdump_info};
+}
+
+# Return a Maemian::Relation object for the given relationship field.  In
+# addition to all the normal relationship fields, the following special
+# field names are supported: all (pre-depends, depends, recommends, and
+# suggests), strong (pre-depends and depends), and weak (recommends and
+# suggests).
+# sub relation Needs-Info <>
+sub relation {
+    my ($self, $field) = @_;
+    $field = lc $field;
+    return $self->{relation}->{$field} if exists $self->{relation}->{$field};
+
+    my %special = (all    => [ qw(pre-depends depends recommends suggests) ],
+                   strong => [ qw(pre-depends depends) ],
+                   weak   => [ qw(recommends suggests) ]);
+    my $result;
+    if ($special{$field}) {
+        my $merged;
+        for my $f (@{ $special{$field} }) {
+            my $value = $self->field($f);
+            $merged .= ', ' if (defined($merged) and defined($value));
+            $merged .= $value if defined($value);
+        }
+        $result = $merged;
+    } else {
+        my %known = map { $_ => 1 }
+            qw(pre-depends depends recommends suggests enhances breaks
+               conflicts provides replaces);
+        croak("unknown relation field $field") unless $known{$field};
+        my $value = $self->field($field);
+        $result = $value if defined($value);
+    }
+    $self->{relation}->{$field} = Maemian::Relation->new($result);
+    return $self->{relation}->{$field};
+}
+
+=head1 NAME
+
+Maemian::Collect::Binary - Maemian interface to binary package data collection
+
+=head1 SYNOPSIS
+
+    my ($name, $type) = ('foobar', 'binary');
+    my $collect = Maemian::Collect->new($name, $type);
+    if ($collect->native) {
+        print "Package is native\n";
+    }
+
+=head1 DESCRIPTION
+
+Maemian::Collect::Binary provides an interface to package data for binary
+packages.  It implements data collection methods specific to binary
+packages.
+
+This module is in its infancy.  Most of Maemian still reads all data from
+files in the laboratory whenever that data is needed and generates that
+data via collect scripts.  The goal is to eventually access all data about
+source packages via this module so that the module can cache data where
+appropriate and possibly retire collect scripts in favor of caching that
+data in memory.
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item new(PACKAGE)
+
+Creates a new Maemian::Collect::Binary object.  Currently, PACKAGE is
+ignored.  Normally, this method should not be called directly, only via
+the Maemian::Collect constructor.
+
+=back
+
+=head1 INSTANCE METHODS
+
+In addition to the instance methods listed below, all instance methods
+documented in the Maemian::Collect module are also available.
+
+=over 4
+
+=item changelog()
+
+Returns the changelog of the binary package as a Parse::DebianChangelog
+object, or undef if the changelog doesn't exist.  The changelog-file
+collection script must have been run to create the changelog file, which
+this method expects to find in F<changelog>.
+
+=item native()
+
+Returns true if the binary package is native and false otherwise.
+Nativeness will be judged by its version number.
+
+=item index()
+
+Returns a reference to an array of hash references with content
+information about the binary package.  Each hash may have the
+following keys:
+
+=over 4
+
+=item name
+
+Name of the index entry without leading slash.
+
+=item owner
+
+=item group
+
+=item uid
+
+=item gid
+
+The former two are in string form and may depend on the local system,
+the latter two are the original numerical values as saved by tar.
+
+=item date
+
+Format "YYYY-MM-DD".
+
+=item time
+
+Format "hh:mm".
+
+=item type
+
+Entry type as one character.
+
+=item operm
+
+Entry permissions as octal number.
+
+=item size
+
+Entry size in bytes.  Note that tar(1) lists the size of directories as
+0 (so this is what you will get) contrary to what ls(1) does.
+
+=item link
+
+If the entry is either a hardlink or symlink, contains the target of the
+link.
+
+=item count
+
+If the entry is a directory, contains the number of other entries this
+directory contains.
+
+=back
+
+=item relation(FIELD)
+
+Returns a Maemian::Relation object for the specified FIELD, which should
+be one of the possible relationship fields of a Debian package or one of
+the following special values:
+
+=over 4
+
+=item all
+
+The concatenation of Pre-Depends, Depends, Recommends, and Suggests.
+
+=item strong
+
+The concatenation of Pre-Depends and Depends.
+
+=item weak
+
+The concatenation of Recommends and Suggests.
+
+=back
+
+If FIELD isn't present in the package, the returned Maemian::Relation
+object will be empty (always satisfied and implies nothing).
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Frank Lichtenheld <djpig@debian.org> for Maemian.
+
+=head1 SEE ALSO
+
+lintian(1), Maemian::Collect(3), Maemian::Relation(3)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround
diff --git a/lib/Maemian/Collect/Source.pm b/lib/Maemian/Collect/Source.pm
new file mode 100644 (file)
index 0000000..954de4d
--- /dev/null
@@ -0,0 +1,379 @@
+# -*- perl -*-
+# Maemian::Collect::Source -- interface to source package data collection
+
+# Copyright (C) 2008 Russ Allbery
+# Copyright (C) 2009 Raphael Geissert
+#
+# 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 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, see <http://www.gnu.org/licenses/>.
+
+package Maemian::Collect::Source;
+
+use strict;
+use warnings;
+use base 'Maemian::Collect';
+
+use Maemian::Relation;
+use Parse::DebianChangelog;
+
+use Util;
+
+our @ISA = qw(Maemian::Collect);
+
+# Initialize a new source package collect object.  Takes the package name,
+# which is currently unused.
+sub new {
+    my ($class, $pkg) = @_;
+    my $self = {};
+    bless($self, $class);
+    return $self;
+}
+
+# Get the changelog file of a source package as a Parse::DebianChangelog
+# object.  Returns undef if the changelog file couldn't be found.
+# sub changelog Needs-Info <>
+sub changelog {
+    my ($self) = @_;
+    return $self->{changelog} if exists $self->{changelog};
+    if (-l 'debfiles/changelog' || ! -f 'debfiles/changelog') {
+        $self->{changelog} = undef;
+    } else {
+        my %opts = (infile => 'debfiles/changelog', quiet => 1);
+        $self->{changelog} = Parse::DebianChangelog->init(\%opts);
+    }
+    return $self->{changelog};
+}
+
+# Returns whether the package is a native package.  For everything except
+# format 3.0 (quilt) packages, we base this on whether we have a Debian
+# *.diff.gz file.  3.0 (quilt) packages are always non-native.  Returns true
+# if the package is native and false otherwise.
+# sub native Needs-Info <>
+sub native {
+    my ($self) = @_;
+    return $self->{native} if exists $self->{native};
+    my $format = $self->field('format');
+    if ($format =~ /^\s*2\.0\s*$/ or $format =~ /^\s*3\.0\s+\(quilt\)\s*$/) {
+        $self->{native} = 0;
+    } else {
+        my $version = $self->field('version');
+        $version =~ s/^\d+://;
+        my $name = $self->{name};
+        $self->{native} = (-f "${name}_${version}.diff.gz" ? 0 : 1);
+    }
+    return $self->{native};
+}
+
+# Returns a hash of binaries to the package type, assuming a type of deb
+# unless the package type field is present.
+sub binaries {
+    my ($self) = @_;
+    return $self->{binaries} if exists $self->{binaries};
+    my %binaries;
+    # sub binaries Needs-Info source-control-file
+    opendir(BINPKGS, 'control') or fail("can't open control directory: $!");
+    for my $package (readdir BINPKGS) {
+        next if $package =~ /^\.\.?$/;
+        my $type = $self->binary_field($package, 'xc-package-type') || 'deb';
+        $binaries{$package} = lc $type;
+    }
+    closedir BINPKGS;
+    $self->{binaries} = \%binaries;
+    return $self->{binaries};
+}
+
+# Returns the value of a control field for a binary package or the empty
+# string if that control field isn't present.  This does not implement
+# inheritance from the settings in the source stanza.
+sub binary_field {
+    my ($self, $package, $field) = @_;
+    return $self->{binary_field}{$package}{$field}
+        if exists $self->{binary_field}{$package}{$field};
+    my $value = '';
+    # sub binary_field Needs-Info source-control-file
+    if (-f "control/$package/$field") {
+        $value = slurp_entire_file("control/$package/$field");
+        chomp $value;
+    }
+    $self->{binary_field}{$package}{$field} = $value;
+    return $self->{binary_field}{$package}{$field};
+}
+
+# Return a Maemian::Relation object for the given relationship field in a
+# binary package.  In addition to all the normal relationship fields, the
+# following special field names are supported:  all (pre-depends, depends,
+# recommends, and suggests), strong (pre-depends and depends), and weak
+# (recommends and suggests).
+sub binary_relation {
+    my ($self, $package, $field) = @_;
+    $field = lc $field;
+    return $self->{binary_relation}->{$package}->{$field}
+        if exists $self->{binary_relation}->{$package}->{$field};
+
+    my %special = (all    => [ qw(pre-depends depends recommends suggests) ],
+                   strong => [ qw(pre-depends depends) ],
+                   weak   => [ qw(recommends suggests) ]);
+    my $result;
+    if ($special{$field}) {
+        my $merged;
+        for my $f (@{ $special{$field} }) {
+           # sub binary_relation Needs-Info :binary_field
+            my $value = $self->binary_field($f);
+            $merged .= ', ' if (defined($merged) and defined($value));
+            $merged .= $value if defined($value);
+        }
+        $result = $merged;
+    } else {
+        my %known = map { $_ => 1 }
+            qw(pre-depends depends recommends suggests enhances breaks
+               conflicts provides replaces);
+        croak("unknown relation field $field") unless $known{$field};
+        my $value = $self->binary_field($field);
+        $result = $value if defined($value);
+    }
+    $result = Maemian::Relation->new($result);
+    $self->{binary_relation}->{$package}->{$field} = $result;
+    return $self->{binary_relation}->{$field};
+}
+
+# Returns the information from collect/file-info.
+sub file_info {
+    my ($self) = @_;
+    return $self->{file_info} if exists $self->{file_info};
+
+    my %file_info;
+    # sub file_info Needs-Info file-info
+    open(my $idx, '<', "file-info") or fail("cannot open file-info: $!");
+    while (<$idx>) {
+        chomp;
+        m/^(.+?):\s+(.*)$/o or fail("cannot parse file output: $_");
+        my ($file, $info) = ($1,$2);
+        $file =~ s,^\./,,o;
+        $file =~ s,/+$,,o;
+        $file_info{$file} = $info;
+    }
+    close $idx;
+    $self->{file_info} = \%file_info;
+    return $self->{file_info};
+}
+
+# Return a Maemian::Relation object for the given build relationship
+# field.  In addition to all the normal build relationship fields, the
+# following special field names are supported:  build-depends-all
+# (build-depends and build-depends-indep) and build-conflicts-all
+# (build-conflicts and build-conflicts-indep).
+# sub relation Needs-Info <>
+sub relation {
+    my ($self, $field) = @_;
+    $field = lc $field;
+    return $self->{relation}->{$field} if exists $self->{relation}->{$field};
+
+    my $result;
+    if ($field =~ /^build-(depends|conflicts)-all$/) {
+        my $type = $1;
+        my $merged;
+        for my $f ("build-$type", "build-$type-indep") {
+            my $value = $self->field($f);
+            $merged .= ', ' if (defined($merged) and defined($value));
+            $merged .= $value if defined($value);
+        }
+        $result = $merged;
+    } elsif ($field =~ /^build-(depends|conflicts)(-indep)?$/) {
+        my $value = $self->field($field);
+        $result = $value if defined($value);
+    } else {
+        croak("unknown relation field $field");
+    }
+    $self->{relation}->{$field} = Maemian::Relation->new($result);
+    return $self->{relation}->{$field};
+}
+
+# Similar to relation(), return a Maemian::Relation object for the given build
+# relationship field, but ignore architecture restrictions.  It supports the
+# same special field names.
+# sub relation_noarch Needs-Info <>
+sub relation_noarch {
+    my ($self, $field) = @_;
+    $field = lc $field;
+    return $self->{relation_noarch}->{$field}
+        if exists $self->{relation_noarch}->{$field};
+
+    my $result;
+    if ($field =~ /^build-(depends|conflicts)-all$/) {
+        my $type = $1;
+        my $merged;
+        for my $f ("build-$type", "build-$type-indep") {
+            my $value = $self->field($f);
+            $merged .= ', ' if (defined($merged) and defined($value));
+            $merged .= $value if defined($value);
+        }
+        $result = $merged;
+    } elsif ($field =~ /^build-(depends|conflicts)(-indep)?$/) {
+        my $value = $self->field($field);
+        $result = $value if defined($value);
+    } else {
+        croak("unknown relation field $field");
+    }
+    $self->{relation_noarch}->{$field}
+        = Maemian::Relation->new_noarch($result);
+    return $self->{relation_noarch}->{$field};
+}
+
+=head1 NAME
+
+Maemian::Collect::Source - Maemian interface to source package data collection
+
+=head1 SYNOPSIS
+
+    my ($name, $type) = ('foobar', 'source');
+    my $collect = Maemian::Collect->new($name, $type);
+    if ($collect->native) {
+        print "Package is native\n";
+    }
+
+=head1 DESCRIPTION
+
+Maemian::Collect::Source provides an interface to package data for source
+packages.  It implements data collection methods specific to source
+packages.
+
+This module is in its infancy.  Most of Maemian still reads all data from
+files in the laboratory whenever that data is needed and generates that
+data via collect scripts.  The goal is to eventually access all data about
+source packages via this module so that the module can cache data where
+appropriate and possibly retire collect scripts in favor of caching that
+data in memory.
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item new(PACKAGE)
+
+Creates a new Maemian::Collect::Source object.  Currently, PACKAGE is
+ignored.  Normally, this method should not be called directly, only via
+the Maemian::Collect constructor.
+
+=back
+
+=head1 INSTANCE METHODS
+
+In addition to the instance methods listed below, all instance methods
+documented in the Maemian::Collect module are also available.
+
+=over 4
+
+=item binaries()
+
+Returns a hash reference with the binary package names as keys and the
+Package-Type as value (which should be either C<deb> or C<udeb>
+currently).  The source-control-file collection script must have been run
+to parse the F<debian/control> file and put the fields in the F<control>
+directory in the lab.
+
+=item binary_field(PACKAGE, FIELD)
+
+Returns the content of the field FIELD for the binary package PACKAGE in
+the F<debian/control> file, or an empty string if that field isn't set.
+Inheritance of field values from the source section of the control file is
+not implemented.  Only the literal value of the field is returned.
+
+The source-control-file collection script must have been run to parse the
+F<debian/control> file and put the fields in the F<control> directory in
+the lab.
+
+=item binary_relation(PACKAGE, FIELD)
+
+Returns a Maemian::Relation object for the specified FIELD in the binary
+package PACKAGE in the F<debian/control> file.  FIELD should be one of the
+possible relationship fields of a Debian package or one of the following
+special values:
+
+=over 4
+
+=item all
+
+The concatenation of Pre-Depends, Depends, Recommends, and Suggests.
+
+=item strong
+
+The concatenation of Pre-Depends and Depends.
+
+=item weak
+
+The concatenation of Recommends and Suggests.
+
+=back
+
+If FIELD isn't present in the package, the returned Maemian::Relation
+object will be empty (always satisfied and implies nothing).
+
+Any substvars in F<debian/control> will be represented in the returned
+relation as packages named after the substvar.
+
+=item changelog()
+
+Returns the changelog of the source package as a Parse::DebianChangelog
+object, or undef if the changelog is a symlink or doesn't exist.  The
+debfiles collection script must have been run to create the changelog
+file, which this method expects to find in F<debfiles/changelog>.
+
+=item native()
+
+Returns true if the source package is native and false otherwise.
+
+=item relation(FIELD)
+
+Returns a Maemian::Relation object for the given build relationship field
+FIELD.  In addition to the normal build relationship fields, the
+following special field names are supported:
+
+=over 4
+
+=item build-depends-all
+
+The concatenation of Build-Depends and Build-Depends-Indep.
+
+=item build-conflicts-all
+
+The concatenation of Build-Conflicts and Build-Conflicts-Indep.
+
+=back
+
+If FIELD isn't present in the package, the returned Maemian::Relation
+object will be empty (always satisfied and implies nothing).
+
+=item relation_noarch(FIELD)
+
+The same as relation(), but ignores architecture restrictions in the
+FIELD field.
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Russ Allbery <rra@debian.org> for Maemian.
+
+=head1 SEE ALSO
+
+lintian(1), Maemian::Collect(3), Maemian::Relation(3)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround
index 053fb70..46c361d 100644 (file)
@@ -33,11 +33,11 @@ use IPC::Run qw(run harness kill_kill);
 
 =head1 NAME
 
-Lintian::Command - Utilities to execute other commands from lintian code
+Maemian::Command - Utilities to execute other commands from lintian code
 
 =head1 SYNOPSIS
 
-    use Lintian::Command qw(spawn);
+    use Maemian::Command qw(spawn);
 
     # simplest possible call
     my $success = spawn({}, ['command']);
@@ -59,10 +59,10 @@ Lintian::Command - Utilities to execute other commands from lintian code
 
 =head1 DESCRIPTION
 
-Lintian::Command is a thin wrapper around IPC::Run, that catches exception
+Maemian::Command is a thin wrapper around IPC::Run, that catches exception
 and implements a useful default behaviour for input and output redirection.
 
-Lintian::Command provides a function spawn() which is a wrapper
+Maemian::Command provides a function spawn() which is a wrapper
 around IPC::Run::run() resp. IPC::Run::start() (depending on whether a
 pipe is requested).  To wait for finished child processes, it also
 provides the reap() function as a wrapper around IPC::Run::finish().
@@ -315,12 +315,12 @@ __END__
 
 =head1 EXPORTS
 
-Lintian::Command exports nothing by default, but you can export the
+Maemian::Command exports nothing by default, but you can export the
 spawn() and reap() functions.
 
 =head1 AUTHOR
 
-Originally written by Frank Lichtenheld <djpig@debian.org> for Lintian.
+Originally written by Frank Lichtenheld <djpig@debian.org> for Maemian.
 
 =head1 SEE ALSO
 
index e529069..a61548a 100644 (file)
@@ -1,5 +1,5 @@
 # -*- perl -*-
-# Lintian::Data -- interface to query lists of keywords
+# Maemian::Data -- interface to query lists of keywords
 
 # Copyright (C) 2008 Russ Allbery
 #
@@ -16,7 +16,7 @@
 # You should have received a copy of the GNU General Public License along with
 # this program.  If not, see <http://www.gnu.org/licenses/>.
 
-package Lintian::Data;
+package Maemian::Data;
 use strict;
 
 use Carp qw(croak);
@@ -32,7 +32,7 @@ use Carp qw(croak);
         my ($class, $type, $separator) = @_;
         croak('no data type specified') unless $type;
         unless (exists $data{$type}) {
-            my $dir = $ENV{LINTIAN_ROOT} . '/data';
+            my $dir = $ENV{MAEMIAN_ROOT} . '/data';
             open(LIST, '<', "$dir/$type")
                 or croak("unknown data type $type");
             local ($_, $.);
@@ -78,15 +78,15 @@ sub value {
 
 =head1 NAME
 
-Lintian::Data - Lintian interface to query lists of keywords
+Maemian::Data - Maemian interface to query lists of keywords
 
 =head1 SYNOPSIS
 
-    my $list = Lintian::Data->new('type');
+    my $list = Maemian::Data->new('type');
     if ($list->known($keyword)) {
         # do something ...
     }
-    my $hash = Lintian::Data->new('another-type', '\s+');
+    my $hash = Maemian::Data->new('another-type', '\s+');
     if ($list->value($keyword) > 1) {
         # do something ...
     }
@@ -94,9 +94,9 @@ Lintian::Data - Lintian interface to query lists of keywords
 
 =head1 DESCRIPTION
 
-Lintian::Data provides a way of loading a list of keywords or key/value
-pairs from a file in the Lintian root and then querying that list.
-The lists are stored in the F<data> directory of the Lintian root and
+Maemian::Data provides a way of loading a list of keywords or key/value
+pairs from a file in the Maemian root and then querying that list.
+The lists are stored in the F<data> directory of the Maemian root and
 consist of one keyword or key/value pair per line.  Blank lines and
 lines beginning with C<#> are ignored.  Leading and trailing whitespace
 is stripped.
@@ -115,7 +115,7 @@ easily editable files.
 
 =item new(TYPE [,SEPARATOR])
 
-Creates a new Lintian::Data object for the given TYPE.  TYPE is a partial
+Creates a new Maemian::Data object for the given TYPE.  TYPE is a partial
 path relative to the F<data> directory and should correspond to a file in
 that directory.  The contents of that file will be loaded into memory and
 returned as part of the newly created object.  On error, new() throws an
@@ -143,12 +143,12 @@ the number of keywords.
 =item known(KEYWORD)
 
 Returns true if KEYWORD was listed in the data file represented by this
-Lintian::Data instance and false otherwise.
+Maemian::Data instance and false otherwise.
 
 =item value(KEYWORD)
 
 Returns the value attached to KEYWORD if it was listed in the data
-file represented by this Lintian::Data instance and the undefined value
+file represented by this Maemian::Data instance and the undefined value
 otherwise. If SEPARATOR was not given, the value will '1'.
 
 =back
@@ -164,7 +164,7 @@ new() was called without a TYPE argument.
 =item unknown data type %s
 
 The TYPE argument to new() did not correspond to a file in the F<data>
-directory of the Lintian root.
+directory of the Maemian root.
 
 =back
 
@@ -172,7 +172,7 @@ directory of the Lintian root.
 
 =over 4
 
-=item LINTIAN_ROOT/data
+=item MAEMIAN_ROOT/data
 
 The files loaded by this module must be located in this directory.
 Relative paths containing a C</> are permitted, so files may be organized
@@ -184,10 +184,10 @@ in subdirectories in this directory.
 
 =over 4
 
-=item LINTIAN_ROOT
+=item MAEMIAN_ROOT
 
-This variable must be set to Lintian's root directory (normally
-F</usr/share/lintian> when Lintian is installed as a Debian package).  The
+This variable must be set to Maemian's root directory (normally
+F</usr/share/lintian> when Maemian is installed as a Debian package).  The
 B<lintian> program normally takes care of doing this.  This module doesn't
 care about the contents of this directory other than expecting the F<data>
 subdirectory of this directory to contain its files.
@@ -196,7 +196,7 @@ subdirectory of this directory to contain its files.
 
 =head1 AUTHOR
 
-Originally written by Russ Allbery <rra@debian.org> for Lintian.
+Originally written by Russ Allbery <rra@debian.org> for Maemian.
 
 =head1 SEE ALSO
 
index 2d65d9e..af40f10 100644 (file)
@@ -39,23 +39,23 @@ BEGIN {
 
 =head1 NAME
 
-Lintian::Output - Lintian messaging handling
+Maemian::Output - Maemian messaging handling
 
 =head1 SYNOPSIS
 
     # non-OO
-    use Lintian::Output qw(:messages)
+    use Maemian::Output qw(:messages)
 
-    $Lintian::Output::GLOBAL->verbose(1);
+    $Maemian::Output::GLOBAL->verbose(1);
 
     msg("Something interesting");
     v_msg("Something less interesting");
     debug_msg(3, "Something very specfific");
 
     # OO
-    use Lintian::Output;
+    use Maemian::Output;
 
-    my $out = new Lintian::Output;
+    my $out = new Maemian::Output;
 
     $out->quiet(1);
     $out->msg("Something interesting");
@@ -64,12 +64,12 @@ Lintian::Output - Lintian messaging handling
 
 =head1 DESCRIPTION
 
-Lintian::Output is used for all interaction between lintian and the user.
+Maemian::Output is used for all interaction between lintian and the user.
 It is designed to be easily extendable via subclassing.
 
-To simplify usage in the most common cases, many Lintian::Output methods
+To simplify usage in the most common cases, many Maemian::Output methods
 can be used as class methods and will therefor automatically use the object
-$Lintian::Output::GLOBAL unless their first argument C<isa('Lintian::Output')>.
+$Maemian::Output::GLOBAL unless their first argument C<isa('Maemian::Output')>.
 
 =cut
 
@@ -80,7 +80,7 @@ use Tags ();
 
 =head1 ACCESSORS
 
-The following fields define the behaviours of Lintian::Output.
+The following fields define the behaviours of Maemian::Output.
 
 =over 4
 
@@ -154,7 +154,7 @@ sub new {
 =head1 CLASS/INSTANCE METHODS
 
 These methods can be used both with and without an object.  If no object
-is given, they will fall back to the $Lintian::Output::GLOBAL object.
+is given, they will fall back to the $Maemian::Output::GLOBAL object.
 
 =over 4
 
@@ -483,7 +483,7 @@ Exports all the methods in L<CLASS METHODS>
 
 =head1 AUTHOR
 
-Originally written by Frank Lichtenheld <djpig@debian.org> for Lintian.
+Originally written by Frank Lichtenheld <djpig@debian.org> for Maemian.
 Modified by Jeremiah C. Foster <jeremiah@maemo.org> for maemian.
 
 =head1 SEE ALSO
diff --git a/lib/Maemian/Relation.pm b/lib/Maemian/Relation.pm
new file mode 100644 (file)
index 0000000..5dc866b
--- /dev/null
@@ -0,0 +1,631 @@
+# -*- perl -*-
+# Maemian::Relation -- operations on dependencies and relationships
+
+# Copyright (C) 1998 Christian Schwarz and Richard Braakman
+# Copyright (C) 2004-2009 Russ Allbery <rra@debian.org>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 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, see <http://www.gnu.org/licenses/>.
+
+package Maemian::Relation;
+
+use strict;
+use warnings;
+
+use Maemian::Relation::Version;
+
+=head1 NAME
+
+Maemian::Relation - Maemian operations on dependencies and relationships
+
+=head1 SYNOPSIS
+
+    my $depends = Maemian::Relation->new('foo | bar, baz');
+    print "yes\n" if $depends->implies('baz');
+    print "no\n" if $depends->implies('foo');
+
+=head1 DESCRIPTION
+
+This module provides functions for parsing and evaluating package
+relationship fields such as Depends and Recommends for binary packages and
+Build-Depends for source packages.  It parses a relationship into an
+internal format and can then answer questions such as "does this
+dependency require that a given package be installed" or "is this
+relationship a superset of another relationship."
+
+A dependency line is viewed as a predicate formula.  The comma separator
+means "and", and the alternatives separator means "or".  A bare package
+name is the predicate "a package of this name is available".  A package
+name with a version clause is the predicate "a package of this name that
+satisfies this version clause is available."  Architecture restrictions,
+as specified in Policy for build dependencies, are supported and also
+checked in the implication logic unless the new_noarch() constructor is
+used.  With that constructor, architecture restrictions are ignored.
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item new(RELATION)
+
+Creates a new Maemian::Relation object corresponding to the parsed
+relationship RELATION.  This object can then be used to ask questions
+about that relationship.  RELATION may be C<undef> or the empty string, in
+which case the returned Maemian::Relation object is empty (always
+satisfied).
+
+=cut
+
+# The internal parser which converts a single package element of a
+# relationship into the parsed form used for later processing.  We permit
+# substvars to be used as package names so that we can use these routines with
+# the unparsed debian/control file.
+sub parse_element {
+    my ($class, $element) = @_;
+    $element =~ /
+        ^\s*                            # skip leading whitespace
+        (                               # package name or substvar (1)
+         [a-zA-Z0-9][a-zA-Z0-9+.-]+     #   package name
+         |                              #   or
+         \$\{[a-zA-Z0-9:-]+\}           #   substvar
+        )                               # end of package name or substvar
+        (?:                             # start of optional version
+         \s* \(                         # open parenthesis for version part
+         \s* (<<|<=|=|>=|>>|<|>)        # relation part (2)
+         \s* (.*?)                      # version (3)
+         \s* \)                         # closing parenthesis
+        )?                              # end of optional version
+        (?:                             # start of optional architecture
+         \s* \[                         # open bracket for architecture
+         \s* (.*?)                      # architectures (4)
+         \s* \]                         # closing bracket
+        )?                              # end of optional architecture
+    /x;
+
+    # If there's no version, we don't need to do any further processing.
+    # Otherwise, convert the legacy < and > relations to the current ones.
+    return ['PRED', $1, undef, undef, $4] if not defined $2;
+    my $two = $2;
+    if ($two eq '<') {
+        $two = '<<';
+    } elsif ($two eq '>') {
+        $two = '>>';
+    }
+    return ['PRED', $1, $two, $3, $4];
+}
+
+
+# Create a new Maemian::Relation object, parsing the argument into our
+# internal format.
+sub new {
+    my ($class, $relation) = @_;
+    $relation = '' unless defined($relation);
+    my @result;
+    for my $element (split(/\s*,\s*/, $relation)) {
+        next if $element =~ /^$/;
+        my @alternatives;
+        for my $alternative (split(/\s*\|\s*/, $element)) {
+            push(@alternatives, $class->parse_element($alternative));
+        }
+        if (@alternatives == 1) {
+            push(@result, @alternatives);
+        } else {
+            push(@result, ['OR', @alternatives]);
+        }
+    }
+    my $self;
+    if (@result == 1) {
+        $self = $result[0];
+    } else {
+        $self = ['AND', @result];
+    }
+    bless($self, $class);
+    return $self;
+}
+
+=item new_noarch(RELATION)
+
+Creates a new Maemian::Relation object corresponding to the parsed
+relationship RELATION, ignoring architecture restrictions.  This should be
+used in cases where we only care if a dependency is present in some cases
+and we don't want to require that the architectures match (such as when
+checking for proper build dependencies, since if there are architecture
+constraints the maintainer is doing something beyond Maemian's ability to
+analyze).  RELATION may be C<undef> or the empty string, in which case the
+returned Maemian::Relation object is empty (always satisfied).
+
+=cut
+
+sub new_noarch {
+    my ($class, $relation) = @_;
+    $relation = '' unless defined($relation);
+    $relation =~ s/\[[^\]]*\]//g;
+    return $class->new($relation);
+}
+
+=back
+
+=head1 INSTANCE METHODS
+
+=over 4
+
+=item duplicates()
+
+Returns a list of duplicated elements within the relation object.  Each
+element of the returned list will be a reference to an anonymous array
+holding a set of relations considered duplicates of each other.  Two
+relations are considered duplicates if one implies the other, meaning that
+if one relationship is satisfied, the other is necessarily satisfied.
+This relationship does not have to be commutative: the opposite
+implication may not hold.
+
+=cut
+
+sub duplicates {
+    my ($self) = @_;
+
+    # There are no duplicates unless the top-level relationship is AND.
+    if ($self->[0] ne 'AND') {
+        return ();
+    }
+
+    # The logic here is a bit complex in order to merge sets of duplicate
+    # dependencies.  We want foo (<< 2), foo (>> 1), foo (= 1.5) to end up as
+    # one set of duplicates, even though the first doesn't imply the second.
+    #
+    # $dups holds a hash, where the key is the earliest dependency in a set
+    # and the value is a hash whose keys are the other dependencies in the
+    # set.  $seen holds a map from package names to the duplicate sets that
+    # they're part of, if they're not the earliest package in a set.  If
+    # either of the dependencies in a duplicate pair were already seen, add
+    # the missing one of the pair to the existing set rather than creating a
+    # new one.
+    my (%dups, %seen);
+    for (my $i = 1; $i < @$self; $i++) {
+        for (my $j = $i + 1; $j < @$self; $j++) {
+            my $forward = $self->implies_array($self->[$i], $self->[$j]);
+            my $reverse = $self->implies_array($self->[$j], $self->[$i]);
+            if ($forward or $reverse) {
+                my $first = unparse($self->[$i]);
+                my $second = unparse($self->[$j]);
+                if ($seen{$first}) {
+                    $dups{$seen{$first}}->{$second} = $j;
+                    $seen{$second} = $seen{$first};
+                } elsif ($seen{$second}) {
+                    $dups{$seen{$second}}->{$first} = $i;
+                    $seen{$first} = $seen{$second};
+                } else {
+                    $dups{$first} ||= {};
+                    $dups{$first}->{$second} = $j;
+                    $seen{$second} = $first;
+                }
+            }
+        }
+    }
+
+    # The sort maintains the original order in which we encountered the
+    # dependencies, just in case that helps the user find the problems,
+    # despite the fact we're using a hash.
+    return map {
+        [ $_,
+          sort { $dups{$_}->{$a} <=> $dups{$_}->{$b} } keys %{ $dups{$_} }
+        ]
+    } keys %dups;
+}
+
+=item implies(RELATION)
+
+Returns true if the relationship implies RELATION, meaning that if the
+Maemian::Relation object is satisfied, RELATION will always be satisfied.
+RELATION may be either a string or another Maemian::Relation object.
+
+By default, architecture restrictions are honored in RELATION if it is a
+string.  If architecture restrictions should be ignored in RELATION,
+create a Maemian::Relation object with new_noarch() and pass that in as
+RELATION instead of the string.
+
+=cut
+
+# This internal function does the heavily lifting of comparing two
+# elements.
+#
+# Takes two elements and returns true iff the second can be deduced from the
+# first.  If the second is falsified by the first (in other words, if p
+# actually implies not q), return 0.  Otherwise, return undef.  The 0 return
+# is used by implies_element_inverse.
+sub implies_element {
+    my ($self, $p, $q) = @_;
+
+    # If the names don't match, there is no relationship between them.
+    $$p[1] = '' unless defined $$p[1];
+    $$q[1] = '' unless defined $$q[1];
+    return undef if $$p[1] ne $$q[1];
+
+    # If the names match, then the only difference is in the architecture or
+    # version clauses.  First, check architecture.  The architectures for p
+    # must be a superset of the architectures for q.
+    my @p_arches = split(' ', defined($$p[4]) ? $$p[4] : '');
+    my @q_arches = split(' ', defined($$q[4]) ? $$q[4] : '');
+    if (@p_arches || @q_arches) {
+        my $p_arch_neg = @p_arches && $p_arches[0] =~ /^!/;
+        my $q_arch_neg = @q_arches && $q_arches[0] =~ /^!/;
+
+        # If p has no arches, it is a superset of q and we should fall through
+        # to the version check.
+        if (not @p_arches) {
+            # nothing
+        }
+
+        # If q has no arches, it is a superset of p and there are no useful
+        # implications.
+        elsif (not @q_arches) {
+            return undef;
+        }
+
+        # Both have arches.  If neither are negated, we know nothing useful
+        # unless q is a subset of p.
+        elsif (not $p_arch_neg and not $q_arch_neg) {
+            my %p_arches = map { $_ => 1 } @p_arches;
+            my $subset = 1;
+            for my $arch (@q_arches) {
+                $subset = 0 unless $p_arches{$arch};
+            }
+            return undef unless $subset;
+        }
+
+        # If both are negated, we know nothing useful unless p is a subset of
+        # q (and therefore has fewer things excluded, and therefore is more
+        # general).
+        elsif ($p_arch_neg and $q_arch_neg) {
+            my %q_arches = map { $_ => 1 } @q_arches;
+            my $subset = 1;
+            for my $arch (@p_arches) {
+                $subset = 0 unless $q_arches{$arch};
+            }
+            return undef unless $subset;
+        }
+
+        # If q is negated and p isn't, we'd need to know the full list of
+        # arches to know if there's any relationship, so bail.
+        elsif (not $p_arch_neg and $q_arch_neg) {
+            return undef;
+        }
+
+        # If p is negated and q isn't, q is a subset of p iff none of the
+        # negated arches in p are present in q.
+        elsif ($p_arch_neg and not $q_arch_neg) {
+            my %q_arches = map { $_ => 1 } @q_arches;
+            my $subset = 1;
+            for my $arch (@p_arches) {
+                $subset = 0 if $q_arches{substr($arch, 1)};
+            }
+            return undef unless $subset;
+        }
+    }
+
+    # Now, down to version.  The implication is true if p's clause is stronger
+    # than q's, or is equivalent.
+
+    # If q has no version clause, then p's clause is always stronger.
+    return 1 if not defined $$q[2];
+
+    # If q does have a version clause, then p must also have one to have any
+    # useful relationship.
+    return undef if not defined $$p[2];
+
+    # q wants an exact version, so p must provide that exact version.  p
+    # disproves q if q's version is outside the range enforced by p.
+    if ($$q[2] eq '=') {
+        if ($$p[2] eq '<<') {
+            return versions_lte($$p[3], $$q[3]) ? 0 : undef;
+        } elsif ($$p[2] eq '<=') {
+            return versions_lt($$p[3], $$q[3]) ? 0 : undef;
+        } elsif ($$p[2] eq '>>') {
+            return versions_gte($$p[3], $$q[3]) ? 0 : undef;
+        } elsif ($$p[2] eq '>=') {
+            return versions_gt($$p[3], $$q[3]) ? 0 : undef;
+        } elsif ($$p[2] eq '=') {
+            return versions_equal($$p[3], $$q[3]);
+        }
+    }
+
+    # A greater than clause may disprove a less than clause.  Otherwise, if
+    # p's clause is <<, <=, or =, the version must be <= q's to imply q.
+    if ($$q[2] eq '<=') {
+        if ($$p[2] eq '>>') {
+            return versions_gte($$p[3], $$q[3]) ? 0 : undef;
+        } elsif ($$p[2] eq '>=') {
+            return versions_gt($$p[3], $$q[3]) ? 0 : undef;
+        } elsif ($$p[2] eq '=') {
+            return versions_lte($$p[3], $$q[3]);
+        } else {
+            return versions_lte($$p[3], $$q[3]) ? 1 : undef;
+        }
+    }
+
+    # Similar, but << is stronger than <= so p's version must be << q's
+    # version if the p relation is <= or =.
+    if ($$q[2] eq '<<') {
+        if ($$p[2] eq '>>' or $$p[2] eq '>=') {
+            return versions_gte($$p[3], $$p[3]) ? 0 : undef;
+        } elsif ($$p[2] eq '<<') {
+            return versions_lte($$p[3], $$q[3]);
+        } elsif ($$p[2] eq '=') {
+            return versions_lt($$p[3], $$q[3]);
+        } else {
+            return versions_lt($$p[3], $$q[3]) ? 1 : undef;
+        }
+    }
+
+    # Same logic as above, only inverted.
+    if ($$q[2] eq '>=') {
+        if ($$p[2] eq '<<') {
+            return versions_lte($$p[3], $$q[3]) ? 0 : undef;
+        } elsif ($$p[2] eq '<=') {
+            return versions_lt($$p[3], $$q[3]) ? 0 : undef;
+        } elsif ($$p[2] eq '=') {
+            return versions_gte($$p[3], $$q[3]);
+        } else {
+            return versions_gte($$p[3], $$q[3]) ? 1 : undef;
+        }
+    }
+    if ($$q[2] eq '>>') {
+        if ($$p[2] eq '<<' or $$p[2] eq '<=') {
+            return versions_lte($$p[3], $$q[3]) ? 0 : undef;
+        } elsif ($$p[2] eq '>>') {
+            return versions_gte($$p[3], $$q[3]);
+        } elsif ($$p[2] eq '=') {
+            return versions_gt($$p[3], $$q[3]);
+        } else {
+            return versions_gt($$p[3], $$q[3]) ? 1 : undef;
+        }
+    }
+
+    return undef;
+}
+
+# This internal function does the heavy of AND, OR, and NOT logic.  It expects
+# two references to arrays instead of an object and a relation.
+sub implies_array {
+    my ($self, $p, $q) = @_;
+    my $i;
+    if ($q->[0] eq 'PRED') {
+        if ($p->[0] eq 'PRED') {
+            return $self->implies_element($p, $q);
+        } elsif ($p->[0] eq 'AND') {
+            $i = 1;
+            while ($i < @$p) {
+                return 1 if $self->implies_array($p->[$i++], $q);
+            }
+            return 0;
+        } elsif ($p->[0] eq 'OR') {
+            $i = 1;
+            while ($i < @$p) {
+                return 0 if not $self->implies_array($p->[$i++], $q);
+            }
+            return 1;
+        } elsif ($p->[0] eq 'NOT') {
+            return $self->implies_array_inverse($p->[1], $q);
+        }
+    } elsif ($q->[0] eq 'AND') {
+        # Each of q's clauses must be deduced from p.
+        $i = 1;
+        while ($i < @$q) {
+            return 0 if not $self->implies_array($p, $q->[$i++]);
+        }
+        return 1;
+    } elsif ($q->[0] eq 'OR') {
+        # If p is something other than OR, p needs to satisfy one of the
+        # clauses of q.  If p is an AND clause, q is satisfied if any of the
+        # clauses of p satisfy it.
+        #
+        # The interesting case is OR.  In this case, do an OR to OR comparison
+        # to determine if q's clause is a superset of p's clause as follows:
+        # take each branch of p and see if it satisfies a branch of q.  If
+        # each branch of p satisfies some branch of q, return 1.  Otherwise,
+        # return 0.
+        #
+        # Simple logic that requires that p satisfy at least one of the
+        # clauses of q considered in isolation will miss that a|b satisfies
+        # a|b|c, since a|b doesn't satisfy any of a, b, or c in isolation.
+        if ($p->[0] eq 'PRED') {
+            $i = 1;
+            while ($i < @$q) {
+                return 1 if $self->implies_array($p, $q->[$i++]);
+            }
+            return 0;
+        } elsif ($p->[0] eq 'AND') {
+            $i = 1;
+            while ($i < @$p) {
+                return 1 if $self->implies_array($p->[$i++], $q);
+            }
+            return 0;
+        } elsif ($p->[0] eq 'OR') {
+            for ($i = 1; $i < @$p; $i++) {
+                my $j = 1;
+                my $satisfies = 0;
+                while ($j < @$q) {
+                    if ($self->implies_array($p->[$i], $q->[$j++])) {
+                        $satisfies = 1;
+                        last;
+                    }
+                }
+                return 0 unless $satisfies;
+            }
+            return 1;
+        } elsif ($p->[0] eq 'NOT') {
+            return $self->implies_array_inverse($p->[1], $q);
+        }
+    } elsif ($q->[0] eq 'NOT') {
+        if ($p->[0] eq 'NOT') {
+            return $self->implies_array($q->[1], $p->[1]);
+        }
+        return $self->implies_array_inverse($p, $q->[1]);
+    }
+}
+
+# The public interface.
+sub implies {
+    my ($self, $relation) = @_;
+    if (ref($relation) ne 'Maemian::Relation') {
+        $relation = Maemian::Relation->new($relation);
+    }
+    return $self->implies_array($self, $relation);
+}
+
+=item implies_inverse(RELATION)
+
+Returns true if the relationship implies that RELATION is certainly false,
+meaning that if the Maemian::Relation object is satisfied, RELATION cannot
+be satisfied.  RELATION may be either a string or another
+Maemian::Relation object.
+
+As with implies(), by default, architecture restrictions are honored in
+RELATION if it is a string.  If architecture restrictions should be
+ignored in RELATION, create a Maemian::Relation object with new_noarch()
+and pass that in as RELATION instead of the string.
+
+=cut
+
+# This internal function does the heavy lifting of inverse implication between
+# two elements.  Takes two elements and returns true iff the falsehood of
+# the second can be deduced from the truth of the first.  In other words, p
+# implies not q, or resstated, q implies not p.  (Since if a implies b, not b
+# implies not a.)  Due to the return value of implies_element(), we can let it
+# do most of the work.
+sub implies_element_inverse {
+    my ($self, $p, $q) = @_;
+    my $result = $self->implies_element($q, $p);
+
+    return not $result if defined $result;
+    return undef;
+}
+
+# This internal function does the heavily lifting for AND, OR, and NOT
+# handling for inverse implications.  It takes two references to arrays and
+# returns true iff the falsehood of the second can be deduced from the truth
+# of the first.
+sub implies_array_inverse {
+    my ($self, $p, $q) = @_;
+    my $i;
+    if ($$q[0] eq 'PRED') {
+        if ($$p[0] eq 'PRED') {
+            return $self->implies_element_inverse($p, $q);
+        } elsif ($$p[0] eq 'AND') {
+            # q's falsehood can be deduced from any of p's clauses
+            $i = 1;
+            while ($i < @$p) {
+                return 1 if $self->implies_array_inverse($$p[$i++], $q);
+            }
+            return 0;
+        } elsif ($$p[0] eq 'OR') {
+            # q's falsehood must be deduced from each of p's clauses
+            $i = 1;
+            while ($i < @$p) {
+                return 0 if not $self->implies_array_inverse($$p[$i++], $q);
+            }
+            return 1;
+        } elsif ($$p[0] eq 'NOT') {
+            return $self->implies_array($q, $$p[1]);
+        }
+    } elsif ($$q[0] eq 'AND') {
+        # Any of q's clauses must be falsified by p.
+        $i = 1;
+        while ($i < @$q) {
+            return 1 if $self->implies_array_inverse($p, $$q[$i++]);
+        }
+        return 0;
+    } elsif ($$q[0] eq 'OR') {
+        # Each of q's clauses must be falsified by p.
+        $i = 1;
+        while ($i < @$q) {
+            return 0 if not $self->implies_array_inverse($p, $$q[$i++]);
+        }
+        return 1;
+    } elsif ($$q[0] eq 'NOT') {
+        return $self->implies_array($p, $$q[1]);
+    }
+}
+
+# The public interface.
+sub implies_inverse {
+    my ($self, $relation) = @_;
+    if (ref($relation) ne 'Maemian::Relation') {
+        $relation = Maemian::Relation->new($relation);
+    }
+    return $self->implies_array_inverse($self, $relation);
+}
+
+=item unparse()
+
+Returns the textual form of a relationship.  This converts the internal
+form back into the textual representation and returns that, not the
+original argument, so the spacing is standardized.  Returns undef on
+internal faliures (such as an object in an unexpected format).
+
+=cut
+
+# The second argument isn't part of the public API.  It's a partial relation
+# that's not a blessed object and is used by unparse() internally so that it
+# can recurse.
+#
+# We also support a NOT predicate.  This currently isn't ever generated by a
+# regular relation, but it may someday be useful.
+sub unparse {
+    my ($self, $partial) = @_;
+    my $relation = defined($partial) ? $partial : $self;
+    if ($relation->[0] eq 'PRED') {
+        my $text = $relation->[1];
+        if (defined $relation->[2]) {
+            $text .= " ($relation->[2] $relation->[3])";
+        }
+        if (defined $relation->[4]) {
+            $text .= " [$relation->[4]]";
+        }
+        return $text;
+    } elsif ($relation->[0] eq 'AND' || $relation->[0] eq 'OR') {
+        my $seperator = ($relation->[0] eq 'AND') ? ', ' : ' | ';
+        my $text = '';
+        for my $element (@$relation) {
+            $text .= $seperator if $text;
+            my $result = $self->unparse($element);
+            return unless defined($result);
+            $text .= $result;
+        }
+        return $text;
+    } elsif ($relation->[0] eq 'NOT') {
+        return '! ' . $self->unparse($relation->[1]);
+    } else {
+        return;
+    }
+}
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Russ Allbery <rra@debian.org> for Maemian.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 ts=8
diff --git a/lib/Maemian/Relation/Version.pm b/lib/Maemian/Relation/Version.pm
new file mode 100644 (file)
index 0000000..31f50cd
--- /dev/null
@@ -0,0 +1,183 @@
+# -*- perl -*-
+# Maemian::Relation::Version -- comparison operators on Debian versions
+
+# Copyright (C) 1998 Christian Schwarz and Richard Braakman
+# Copyright (C) 2004-2009 Russ Allbery <rra@debian.org>
+# Copyright (C) 2009 Adam D. Barratt <adam@adam-barratt.org.uk>
+#
+# 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 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, see <http://www.gnu.org/licenses/>.
+
+package Maemian::Relation::Version;
+
+use strict;
+use warnings;
+
+use Carp qw(croak);
+
+use base 'Exporter';
+BEGIN {
+    our @EXPORT = qw(versions_equal versions_lte versions_gte versions_lt
+                     versions_gt versions_compare);
+}
+
+use AptPkg::Config '$_config';
+my $versioning = $_config->system->versioning;
+
+=head1 NAME
+
+Maemian::Relation::Version - Comparison operators on Debian versions
+
+=head1 SYNOPSIS
+
+    print "yes\n" if versions_equal('1.0', '1.00');
+    print "yes\n" if versions_gte('1.1', '1.0');
+    print "no\n" if versions_lte('1.1', '1.0');
+    print "yes\n" if versions_gt('1.1', '1.0');
+    print "no\n" if versions_lt('1.1', '1.1');
+    print "yes\n" if versions_compare('1.1', '<=', '1.1');
+
+=head1 DESCRIPTION
+
+This module provides five functions for comparing version numbers.  The
+underlying implementation uses C<libapt-pkg-perl> to ensure that
+the results match what dpkg will expect.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item versions_equal(A, B)
+
+Returns true if A is equal to B (C<=>) and false otherwise.
+
+=cut
+
+sub versions_equal {
+    my ($p, $q) = @_;
+    my $result;
+
+    return 1 if $p eq $q;
+
+    $result = $versioning->compare($p, $q);
+    
+    return ($result == 0);
+}
+
+=item versions_lte(A, B)
+
+Returns true if A is less than or equal (C<< <= >>) to B and false
+otherwise.
+
+=cut
+
+sub versions_lte {
+    my ($p, $q) = @_;
+    my $result;
+
+    return 1 if $p eq $q;
+
+    $result = $versioning->compare($p, $q);
+
+    return ($result <= 0);
+}
+
+=item versions_gte(A, B)
+
+Returns true if A is greater than or equal (C<< >= >>) to B and false
+otherwise.
+
+=cut
+
+sub versions_gte {
+    my ($p, $q) = @_;
+    my $result;
+
+    return 1 if $p eq $q;
+
+    $result = $versioning->compare($p, $q);
+
+    return ($result >= 0);
+}
+
+=item versions_lt(A, B)
+
+Returns true if A is less than (C<<< << >>>) B and false otherwise.
+
+=cut
+
+sub versions_lt {
+    my ($p, $q) = @_;
+    my $result;
+
+    return 0 if $p eq $q;
+
+    $result = $versioning->compare($p, $q);
+
+    return ($result < 0);
+}
+
+=item versions_gt(A, B)
+
+Returns true if A is greater than (C<<< >> >>>) B and false otherwise.
+
+=cut
+
+sub versions_gt {
+    my ($p, $q) = @_;
+    my $result;
+
+    return 0 if $p eq $q;
+
+    $result = $versioning->compare($p, $q);
+
+    return ($result > 0);
+}
+
+=item versions_compare(A, OP, B)
+
+Returns true if A OP B, where OP is one of C<=>, C<< <= >>, C<< >= >>,
+C<<< << >>>, or C<<< >> >>>, and false otherwise.
+
+=cut
+
+sub versions_compare {
+    my ($p, $op, $q) = @_;
+    if    ($op eq  '=') { return versions_equal($p, $q) }
+    elsif ($op eq '<=') { return versions_lte  ($p, $q) }
+    elsif ($op eq '>=') { return versions_gte  ($p, $q) }
+    elsif ($op eq '<<') { return versions_lt   ($p, $q) }
+    elsif ($op eq '>>') { return versions_gt   ($p, $q) }
+    else { croak("unknown operator $op") }
+}
+
+=back
+
+=head1 AUTHOR
+
+Originally written by Russ Allbery <rra@debian.org> for Maemian and adapted
+to use libapt-pkg-perl by Adam D. Barratt <adam@adam-barratt-org.uk>.
+
+=head1 SEE ALSO
+
+lintian(1)
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround
index 3fe5899..48abecb 100644 (file)
@@ -16,7 +16,7 @@
 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
 # MA 02110-1301, USA.
 
-package Lintian::Schedule;
+package Maemian::Schedule;
 
 use strict;
 use warnings;
index fc173a6..8763c2d 100644 (file)
@@ -1,5 +1,5 @@
 # -*- perl -*-
-# Lintian::Tag::Info -- interface to tag metadata
+# Maemian::Tag::Info -- interface to tag metadata
 
 # Copyright (C) 1998 Christian Schwarz and Richard Braakman
 # Copyright (C) 2009 Russ Allbery
@@ -45,11 +45,11 @@ our %MANUALS;
 
 =head1 NAME
 
-Lintian::Tag::Info - Lintian interface to tag metadata
+Maemian::Tag::Info - Maemian interface to tag metadata
 
 =head1 SYNOPSIS
 
-    my $tag = Lintian::Tag::Info->new('some-tag');
+    my $tag = Maemian::Tag::Info->new('some-tag');
     print "Tag info is:\n";
     print $tag_info->description('text', '   ');
     print "\nTag info in HTML is:\n";
@@ -68,14 +68,14 @@ used to retrieve other metadata about tags.
 
 =item new(TAG)
 
-Creates a new Lintian::Tag::Info object for the given TAG.  Returns undef
+Creates a new Maemian::Tag::Info object for the given TAG.  Returns undef
 if the tag is unknown and throws an exception if there is a parse error
 reading the check description files or if TAG is not specified.
 
 The first time this method is called, all tag metadata will be loaded into
 a memory cache.  This information will be used to satisfy all subsequent
-Lintian::Tag::Info object creation, avoiding multiple file reads.  This
-however means that a running Lintian process will not notice changes to
+Maemian::Tag::Info object creation, avoiding multiple file reads.  This
+however means that a running Maemian process will not notice changes to
 tag metadata on disk.
 
 =cut
@@ -83,7 +83,7 @@ tag metadata on disk.
 # Load all tag data into the %INFO hash.  Called by new() if %INFO is
 # empty and hence called the first time new() is called.
 sub _load_tag_data {
-    my $root = $ENV{LINTIAN_ROOT} || '/usr/share/lintian';
+    my $root = $ENV{MAEMIAN_ROOT} || '/usr/share/lintian';
     for my $desc (<$root/checks/*.desc>) {
         debug_msg(2, "Reading checker description file $desc ...");
         my ($header, @tags) = read_dpkg_control($desc);
@@ -138,11 +138,11 @@ formatted output.
 
 # Load manual reference data into %MANUALS.  This information doesn't have
 # a single unique key and has multiple data values per key, so we don't
-# try to use the Lintian::Data interface.  Instead, we read a file
-# delimited by double colons.  We do use a path similar to Lintian::Data
+# try to use the Maemian::Data interface.  Instead, we read a file
+# delimited by double colons.  We do use a path similar to Maemian::Data
 # to keep such files in the same general location.
 sub _load_manual_data {
-    my $root = $ENV{LINTIAN_ROOT} || '/usr/share/lintian';
+    my $root = $ENV{MAEMIAN_ROOT} || '/usr/share/lintian';
     open(REFS, '<', "$root/data/output/manual-references")
         or fail("can't open $root/data/output/manual-references: $!");
     local $_;
@@ -158,7 +158,7 @@ sub _load_manual_data {
     close REFS;
 }
 
-# Format a reference to a manual in the HTML that Lintian uses internally
+# Format a reference to a manual in the HTML that Maemian uses internally
 # for tag descriptions and return the result.  Takes the name of the
 # manual and the name of the section.  Returns an empty string if the
 # argument isn't a known manual.
@@ -248,7 +248,7 @@ sub description {
     if ($self->{experimental}) {
         push(@text, '',
              'This tag is marked experimental, which means that the code that'
-             . ' generates it is not as well-tested as the rest of Lintian'
+             . ' generates it is not as well-tested as the rest of Maemian'
              . ' and might still give surprising results.  Feel free to'
              . ' ignore experimental tags that do not seem to make sense,'
              . ' though of course bug reports are always welcomed.');
@@ -272,7 +272,7 @@ The following exceptions may be thrown:
 
 =item no tag specified
 
-The Lintian::Tag::Info::new constructor was called without passing a tag
+The Maemian::Tag::Info::new constructor was called without passing a tag
 as an argument.
 
 =item unknown output format %s
@@ -288,7 +288,7 @@ The following fatal internal errors may be reported:
 
 =item can't open %s: %s
 
-The specified file, which should be part of the standard Lintian data
+The specified file, which should be part of the standard Maemian data
 files, could not be opened.  The file may be missing or have the wrong
 permissions.
 
@@ -309,12 +309,12 @@ field.
 
 =over 4
 
-=item LINTIAN_ROOT/checks/*.desc
+=item MAEMIAN_ROOT/checks/*.desc
 
 The tag description files, from which tag metadata is read.  All files
 matching this shell glob expression will be read looking for tag data.
 
-=item LINTIAN_ROOT/data/output/manual-references
+=item MAEMIAN_ROOT/data/output/manual-references
 
 Information about manual references.  Each non-comment, non-empty line of
 this file contains four fields separated by C<::>.  The first field is the
@@ -328,9 +328,9 @@ is the URL.  The URL is optional.
 
 =over 4
 
-=item LINTIAN_ROOT
+=item MAEMIAN_ROOT
 
-This variable specifies Lintian's root directory.  It defaults to
+This variable specifies Maemian's root directory.  It defaults to
 F</usr/share/lintian> if not set.  The B<lintian> program normally takes
 care of setting it.
 
@@ -338,7 +338,7 @@ care of setting it.
 
 =head1 AUTHOR
 
-Originally written by Russ Allbery <rra@debian.org> for Lintian.
+Originally written by Russ Allbery <rra@debian.org> for Maemian.
 
 =head1 SEE ALSO
 
index 587cc87..4e11e91 100644 (file)
@@ -1,5 +1,5 @@
 # Hey emacs! This is a -*- Perl -*- script!
-# Read_pkglists -- Perl utility functions to read Lintian's package lists
+# Read_pkglists -- Perl utility functions to read Maemian's package lists
 
 # Copyright (C) 1998 Christian Schwarz
 #
@@ -28,9 +28,9 @@ use vars qw($BINLIST_FORMAT $SRCLIST_FORMAT $UDEBLIST_FORMAT %source_info %binar
 
 # these banner lines have to be changed with every incompatible change of the
 # binary and source list file formats
-$BINLIST_FORMAT = "Lintian's list of binary packages in the archive--V3";
-$SRCLIST_FORMAT = "Lintian's list of source packages in the archive--V3";
-$UDEBLIST_FORMAT = "Lintian's list of udeb packages in the archive--V2";
+$BINLIST_FORMAT = "Maemian's list of binary packages in the archive--V3";
+$SRCLIST_FORMAT = "Maemian's list of source packages in the archive--V3";
+$UDEBLIST_FORMAT = "Maemian's list of udeb packages in the archive--V2";
 
 %source_info = ();
 %binary_info = ();
diff --git a/lib/Spelling.pm b/lib/Spelling.pm
new file mode 100644 (file)
index 0000000..250c18f
--- /dev/null
@@ -0,0 +1,625 @@
+# -*- perl -*-
+# Spelling -- check for common spelling errors
+
+# Copyright (C) 1998 Richard Braakman
+#
+# 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 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.
+
+package Spelling;
+use strict;
+use Tags;
+
+use Exporter;
+our @ISA = qw(Exporter);
+our @EXPORT = qw(spelling_check spelling_check_picky);
+
+# All spelling errors that have been observed "in the wild" in package
+# descriptions are added here, on the grounds that if they occurred once they
+# are more likely to occur again.
+
+# Misspellings of "compatibility", "separate", and "similar" are particularly
+# common.
+
+# Be careful with corrections that involve punctuation, since the check is a
+# bit rough with punctuation.  For example, I had to delete the correction of
+# "builtin" to "built-in".
+
+our %CORRECTIONS = qw(
+                      abandonning abandoning
+                      abigious ambiguous
+                      abitrate arbitrate
+                      abov above
+                      absolut absolute
+                      accelleration acceleration
+                      accesing accessing
+                      accesnt accent
+                      accessable accessible
+                      accessable accessible
+                      accidentaly accidentally
+                      accidentually accidentally
+                      accomodate accommodate
+                      accomodate accommodate
+                      accomodates accommodates
+                      accout account
+                      acess access
+                      acording according
+                      acumulating accumulating
+                      addional additional
+                      additionaly additionally
+                      adress address
+                      adresses addresses
+                      adviced advised
+                      afecting affecting
+                      albumns albums
+                      alegorical allegorical
+                      algorith algorithm
+                      algorithmical algorithmically
+                      allpication application
+                      alows allows
+                      als also
+                      altough although
+                      ambigious ambiguous
+                      amoung among
+                      amout amount
+                      analysator analyzer
+                      ang and
+                      annoucement announcement
+                      appearence appearance
+                      appropiate appropriate
+                      appropriatly appropriately
+                      aquired acquired
+                      aquired acquired
+                      architechture architecture
+                      arguement argument
+                      arguements arguments
+                      aritmetic arithmetic
+                      arne't aren't
+                      arraival arrival
+                      artifical artificial
+                      artillary artillery
+                      assigment assignment
+                      assigments assignments
+                      assistent assistant
+                      asuming assuming
+                      atomatically automatically
+                      attemps attempts
+                      attruibutes attributes
+                      authentification authentication
+                      automaticly automatically
+                      automatize automate
+                      automatized automated
+                      automatizes automates
+                      auxilliary auxiliary
+                      avaiable available
+                      availabled available
+                      availablity availability
+                      availale available
+                      availavility availability
+                      availble available
+                      availble available
+                      availiable available
+                      avaliable available
+                      avaliable available
+                      backgroud background
+                      baloons balloons
+                      batery battery
+                      becomming becoming
+                      becuase because
+                      begining beginning
+                      calender calendar
+                      cancelation cancellation
+                      cancelation cancellation
+                      capabilites capabilities
+                      capatibilities capabilities
+                      cariage carriage
+                      challanges challenges
+                      changable changeable
+                      charachters characters
+                      charcter character
+                      choosen chosen
+                      colorfull colorful
+                      comand command
+                      comit commit
+                      commerical commercial
+                      comminucation communication
+                      commiting committing
+                      committ commit
+                      commoditiy commodity
+                      compability compatibility
+                      compatability compatibility
+                      compatable compatible
+                      compatibiliy compatibility
+                      compatibilty compatibility
+                      compleatly completely
+                      complient compliant
+                      compres compress
+                      compresion compression
+                      connectinos connections
+                      consistancy consistency
+                      containes contains
+                      containts contains
+                      contence contents
+                      continous continuous
+                      continueing continuing
+                      contraints constraints
+                      convertor converter
+                      convinient convenient
+                      corected corrected
+                      cryptocraphic cryptographic
+                      deamon daemon
+                      debain Debian
+                      debians Debian's
+                      decompres decompress
+                      definate definite
+                      definately definitely
+                      delemiter delimiter
+                      dependancies dependencies
+                      dependancy dependency
+                      dependant dependent
+                      detabase database
+                      developement development
+                      developement development
+                      developped developed
+                      deveolpment development
+                      devided divided
+                      dictionnary dictionary
+                      diplay display
+                      disapeared disappeared
+                      dispertion dispersion
+                      dissapears disappears
+                      docuentation documentation
+                      documantation documentation
+                      documentaion documentation
+                      dont don't
+                      easilly easily
+                      ecspecially especially
+                      edditable editable
+                      editting editing
+                      eletronic electronic
+                      enchanced enhanced
+                      encorporating incorporating
+                      endianness endianess
+                      enhaced enhanced
+                      enlightnment enlightenment
+                      enterily entirely
+                      enviroiment environment
+                      enviroment environment
+                      environement environment
+                      excecutable executable
+                      exceded exceeded
+                      excellant excellent
+                      exlcude exclude
+                      explicitely explicitly
+                      expresion expression
+                      exprimental experimental
+                      extention extension
+                      failuer failure
+                      familar familiar
+                      fatser faster
+                      fetaures features
+                      forse force
+                      fortan fortran
+                      forwardig forwarding
+                      framwork framework
+                      fuction function
+                      fuctions functions
+                      functionaly functionally
+                      functionnality functionality
+                      functonality functionality
+                      futhermore furthermore
+                      generiously generously
+                      grahical graphical
+                      grahpical graphical
+                      grapic graphic
+                      guage gauge
+                      halfs halves
+                      heirarchically hierarchically
+                      helpfull helpful
+                      hierachy hierarchy
+                      hierarchie hierarchy
+                      howver however
+                      implemantation implementation
+                      incomming incoming
+                      incompatabilities incompatibilities
+                      incompatable incompatible
+                      inconsistant inconsistent
+                      indendation indentation
+                      indended intended
+                      independant independent
+                      informatiom information
+                      infromation information
+                      initalize initialize
+                      initators initiators
+                      initializiation initialization
+                      inofficial unofficial
+                      integreated integrated
+                      integrety integrity
+                      integrey integrity
+                      intendet intended
+                      interchangable interchangeable
+                      intermittant intermittent
+                      interupted interrupted
+                      jave java
+                      langage language
+                      langauage language
+                      langugage language
+                      lauch launch
+                      lenght length
+                      lesstiff lesstif
+                      libaries libraries
+                      libary library
+                      libraris libraries
+                      licenceing licencing
+                      loggging logging
+                      loggin login
+                      logile logfile
+                      machinary machinery
+                      maintainance maintenance
+                      maintainence maintenance
+                      makeing making
+                      managable manageable
+                      manoeuvering maneuvering
+                      mathimatical mathematical
+                      mathimatic mathematic
+                      mathimatics mathematics
+                      ment meant
+                      messsages messages
+                      microprocesspr microprocessor
+                      milliseonds milliseconds
+                      miscelleneous miscellaneous
+                      misformed malformed
+                      mispelled misspelled
+                      mmnemonic mnemonic
+                      modulues modules
+                      monochorome monochrome
+                      monochromo monochrome
+                      monocrome monochrome
+                      mroe more
+                      multidimensionnal multidimensional
+                      navagating navigating
+                      nead need
+                      neccesary necessary
+                      neccessary necessary
+                      neccessary necessary
+                      necesary necessary
+                      negotation negotiation
+                      nescessary necessary
+                      nessessary necessary
+                      noticable noticeable
+                      notications notifications
+                      o'caml OCaml
+                      omitt omit
+                      ommitted omitted
+                      optionnal optional
+                      optmizations optimizations
+                      orientatied orientated
+                      orientied oriented
+                      overaall overall
+                      pacakge package
+                      pachage package
+                      packacge package
+                      packege package
+                      packge package
+                      pakage package
+                      paramameters parameters
+                      parameterize parametrize
+                      paramter parameter
+                      paramters parameters
+                      particularily particularly
+                      pased passed
+                      peprocessor preprocessor
+                      perfoming performing
+                      permissons permissions
+                      persistant persistent
+                      plattform platform
+                      ploting plotting
+                      posible possible
+                      postgressql PostgreSQL
+                      powerfull powerful
+                      preceeded preceded
+                      preceeding preceding
+                      precission precision
+                      prefered preferred
+                      prefferably preferably
+                      prepaired prepared
+                      primative primitive
+                      princliple principle
+                      priorty priority
+                      proccesors processors
+                      proces process
+                      processessing processing
+                      processpr processor
+                      processsing processing
+                      progams programs
+                      programers programmers
+                      programm program
+                      programms programs
+                      promps prompts
+                      pronnounced pronounced
+                      prononciation pronunciation
+                      pronouce pronounce
+                      pronunce pronounce
+                      propery property
+                      prosess process
+                      protable portable
+                      protcol protocol
+                      protecion protection
+                      protocoll protocol
+                      psychadelic psychedelic
+                      quering querying
+                      recieved received
+                      recieved received
+                      recieve receive
+                      recieve receive
+                      reciever receiver
+                      recognizeable recognizable
+                      recommanded recommended
+                      redircet redirect
+                      redirectrion redirection
+                      reenabled re-enabled
+                      reenable re-enable
+                      reencode re-encode
+                      refence reference
+                      registerd registered
+                      registraration registration
+                      regulamentations regulations
+                      remoote remote
+                      removeable removable
+                      repectively respectively
+                      replacments replacements
+                      requiere require
+                      requred required
+                      resizeable resizable
+                      ressize resize
+                      ressource resource
+                      retransmited retransmitted
+                      runnning running
+                      safly safely
+                      savable saveable
+                      searchs searches
+                      secund second
+                      separatly separately
+                      sepcify specify
+                      seperated separated
+                      seperated separated
+                      seperately separately
+                      seperate separate
+                      seperate separate
+                      seperatly separately
+                      seperator separator
+                      sequencial sequential
+                      serveral several
+                      setts sets
+                      similiar similar
+                      simliar similar
+                      speach speech
+                      speciefied specified
+                      specifed specified
+                      specificaton specification
+                      specifing specifying
+                      speficied specified
+                      speling spelling
+                      splitted split
+                      staically statically
+                      standart standard
+                      staticly statically
+                      subdirectoires subdirectories
+                      succesfully successfully
+                      succesful successful
+                      superceded superseded
+                      superflous superfluous
+                      superseeded superseded
+                      suplied supplied
+                      suport support
+                      suppored supported
+                      supportin supporting
+                      suppoted supported
+                      suppported supported
+                      suppport support
+                      suspicously suspiciously
+                      synax syntax
+                      synchonized synchronized
+                      syncronize synchronize
+                      syncronize synchronize
+                      syncronizing synchronizing
+                      syncronus synchronous
+                      syste system
+                      sythesis synthesis
+                      taht that
+                      throught through
+                      transfering transferring
+                      trasmission transmission
+                      treshold threshold
+                      trigerring triggering
+                      unexecpted unexpected
+                      unfortunatelly unfortunately
+                      unknonw unknown
+                      unuseful useless
+                      useable usable
+                      usefull useful
+                      usera users
+                      usetnet Usenet
+                      utilites utilities
+                      utillities utilities
+                      utilties utilities
+                      utiltity utility
+                      utitlty utility
+                      variantions variations
+                      varient variant
+                      verbse verbose
+                      verisons versions
+                      verison version
+                      verson version
+                      vicefersa vice-versa
+                      wheter whether
+                      wierd weird
+                      xwindows X
+                      yur your
+                     );
+
+# The format above doesn't allow spaces.
+$CORRECTIONS{'alot'} = 'a lot';
+
+# Picky corrections, applied before lowercasing the word.  These are only
+# applied to things known to be entirely English text, such as package
+# descriptions, and should not be applied to files that may contain
+# configuration fragments or more informal files such as debian/copyright.
+our %CORRECTIONS_CASE = qw(
+                           apache Apache
+                           api API
+                           Api API
+                           D-BUS D-Bus
+                           d-bus D-Bus
+                           dbus D-Bus
+                           debian Debian
+                           english English
+                           french French
+                           EMacs Emacs
+                           Gconf GConf
+                           gconf GConf
+                           german German
+                           Gnome GNOME
+                           gnome GNOME
+                           Gnome-VFS GnomeVFS
+                           Gnome-Vfs GnomeVFS
+                           GnomeVfs GnomeVFS
+                           gnome-vfs GnomeVFS
+                           gnomevfs GnomeVFS
+                           gnu GNU
+                           Gnu GNU
+                           Gobject GObject
+                           gobject GObject
+                           Gstreamer GStreamer
+                           gstreamer GStreamer
+                           GTK GTK+
+                           gtk+ GTK+
+                           Http HTTP
+                           kde KDE
+                           meta-package metapackage
+                           MYSQL MySQL
+                           Mysql MySQL
+                           mysql MySQL
+                           linux Linux
+                           Latex LaTeX
+                           latex LaTeX
+                           OCAML OCaml
+                           Ocaml OCaml
+                           ocaml OCaml
+                           OpenLdap OpenLDAP
+                           Openldap OpenLDAP
+                           openldap OpenLDAP
+                           Postgresql PostgreSQL
+                           postgresql PostgreSQL
+                           python Python
+                           russian Russian
+                           SkoleLinux Skolelinux
+                           skolelinux Skolelinux
+                           SLang S-Lang
+                           S-lang S-Lang
+                           s-lang S-Lang
+                           spanish Spanish
+                           subversion Subversion
+                           TCL Tcl
+                           tcl Tcl
+                           TEX TeX
+                           Tex TeX
+                           TeTeX teTeX
+                           Tetex teTeX
+                           tetex teTeX
+                           TK Tk
+                           tk Tk
+                           Xemacs XEmacs
+                           XEMacs XEmacs
+                           XFCE Xfce
+                           XFce Xfce
+                           xfce Xfce
+                          );
+
+# The format above doesn't allow spaces.
+$CORRECTIONS_CASE{'Debian-Edu'} = 'Debian Edu';
+$CORRECTIONS_CASE{'debian-edu'} = 'Debian Edu';
+$CORRECTIONS_CASE{'TeXLive'} = 'TeX Live';
+$CORRECTIONS_CASE{'TeX-Live'} = 'TeX Live';
+$CORRECTIONS_CASE{'TeXlive'} = 'TeX Live';
+$CORRECTIONS_CASE{'TeX-live'} = 'TeX Live';
+$CORRECTIONS_CASE{'texlive'} = 'TeX Live';
+$CORRECTIONS_CASE{'tex-live'} = 'TeX Live';
+
+# -----------------------------------
+
+sub _tag {
+    my @args = grep { defined($_) } @_;
+    tag(@args);
+}
+
+# Check spelling of $text and report the tag $tag if we find anything.
+# $filename, if included, is given as the first argument to the tag.  If it's
+# not defined, it will be omitted.
+sub spelling_check {
+    my ($tag, $text, $filename) = @_;
+    return unless $text;
+
+    $text = lc $text;
+    $text =~ s/[.,;:?!()[\]]//g;
+
+    for my $word (split(/\s+/, $text)) {
+        if (exists $CORRECTIONS{$word}) {
+            _tag($tag, $filename, $word, $CORRECTIONS{$word});
+        }
+    }
+
+    # Special case for correcting a multi-word string.
+    if ($text =~ m,debian/gnu\s+linux,) {
+        _tag($tag, $filename, "Debian/GNU Linux", "Debian GNU/Linux");
+    }
+}
+
+# Check spelling of $text against pickier corrections, such as common
+# capitalization mistakes.  This check is separate from spelling_check since
+# it isn't appropriate for some files (such as changelog).  Takes $text to
+# check spelling in and $tag to report if we find anything.  $filename, if
+# included, is given as the first argument to the tag.  If it's not defined,
+# it will be omitted.
+sub spelling_check_picky {
+    my ($tag, $text, $filename) = @_;
+
+    # Check this first in case it's contained in square brackets and
+    # removed below.
+    if ($text =~ m,meta\s+package,) {
+        _tag($tag, $filename, "meta package", "metapackage");
+    }
+
+    # Exclude text enclosed in square brackets as it could be a package list
+    # or similar which may legitimately contain lower-cased versions of
+    # the words.
+    $text =~ s/\[.+?\]//sg;
+    for my $word (split(/\s+/, $text)) {
+        $word =~ s/^\(|[).,?!:;]+$//g;
+        if (exists $CORRECTIONS_CASE{$word}) {
+            _tag($tag, $filename, $word, $CORRECTIONS_CASE{$word});
+            next;
+        }
+    }
+}
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround
index 4a9024d..aa2bc5e 100644 (file)
@@ -1,4 +1,4 @@
-# Tags -- Perl tags functions for lintian
+# Tags -- Perl tags functions for maemian
 
 # Copyright (C) 1998-2004 Various authors
 # Copyright (C) 2005 Frank Lichtenheld <frank@lichtenheld.de>
@@ -129,11 +129,11 @@ sub select_pkg {
     }
 
     if ($current) {
-       $Lintian::Output::GLOBAL->print_end_pkg($info{$current});
+       $Maemian::Output::GLOBAL->print_end_pkg($info{$current});
     }
     $current = $file;
     if ($file !~ /.changes$/) {
-       $Lintian::Output::GLOBAL->print_start_pkg($info{$current});
+       $Maemian::Output::GLOBAL->print_start_pkg($info{$current});
     }
     return 1;
 }
@@ -141,7 +141,7 @@ sub select_pkg {
 # only delete the value of 'current' without deleting any stored information
 sub reset_pkg {
     if ($current) {
-       $Lintian::Output::GLOBAL->print_end_pkg($info{$current});
+       $Maemian::Output::GLOBAL->print_end_pkg($info{$current});
     }
     undef $current;
     return 1;
@@ -362,7 +362,7 @@ sub tag {
     return 0 unless
        ! keys %only_issue_tags or exists $only_issue_tags{$tag};
 
-    # Clean up @information and collapse it to a string.  Lintian code doesn't
+    # Clean up @information and collapse it to a string.  Maemian code doesn't
     # treat the distinction between extra arguments to tag() as significant,
     # so we may as well take care of this up front.
     @information = grep { defined($_) and $_ ne '' }
@@ -382,15 +382,9 @@ sub tag {
 
     return 1 if skip_print( $tag_info );
 
-    $Lintian::Output::GLOBAL->print_tag( $info{$current}, $tag_info,
+    $Maemian::Output::GLOBAL->print_tag( $info{$current}, $tag_info,
                                         $information );
     return 1;
 }
 
 1;
-
-# Local Variables:
-# indent-tabs-mode: t
-# cperl-indent-level: 4
-# End:
-# vim: ts=4 sw=4 noet
index 96e745a..f1aef91 100644 (file)
@@ -121,7 +121,7 @@ sub dtml_to_html {
 
     my $pre=0;
     for $_ (@_) {
-       s,\&maint\;,<a href=\"mailto:lintian-maint\@debian.org\">Lintian maintainer</a>,o; # "
+       s,\&maint\;,<a href=\"mailto:lintian-maint\@debian.org\">Maemian maintainer</a>,o; # "
        s,\&debdev\;,<a href=\"mailto:debian-devel\@lists.debian.org\">debian-devel</a>,o; # "
 
        # empty line?
@@ -157,7 +157,7 @@ sub dtml_to_html {
 
 sub dtml_to_text {
     for $_ (@_) {
-       # substitute Lintian &tags;
+       # substitute Maemian &tags;
        s,&maint;,lintian-maint\@debian.org,go;
        s,&debdev;,debian-devel\@lists.debian.org,go;
 
index 1b7cb26..47de540 100644 (file)
@@ -1,5 +1,4 @@
-# Hey emacs! This is a -*- Perl -*- script!
-# Util -- Perl utility functions for lintian
+# Util -- Perl utility functions for maemian
 
 # Copyright (C) 1998 Christian Schwarz
 #
@@ -314,9 +313,3 @@ sub fail {
 }
 
 1;
-
-# Local Variables:
-# indent-tabs-mode: t
-# cperl-indent-level: 4
-# End:
-# vim: syntax=perl sw=4 ts=8
diff --git a/maemian b/maemian
index 867dccd..81f7ff4 100755 (executable)
--- a/maemian
+++ b/maemian
@@ -113,9 +113,6 @@ our $MAEMIAN_UNPACK_LEVEL = undef;
 our $MAEMIAN_ARCH = undef;
 our $MAEMIAN_SECTION = undef;
 our $MAEMIAN_AREA = undef;
-# }}}
-
-# {{{ Setup Code
 
 #turn off file buffering
 $| = 1;
@@ -1061,22 +1058,20 @@ if (not $check_everything and not $packages_file and not $schedule->count) {
     v_msg("No packages selected.");
     exit $exit_code;
 }
-# }}}
 
-# {{{ A lone subroutine
-#----------------------------------------------------------------------------
 #  Check to make sure there are packages to check.
 sub set_value {
     my ($f,$target,$field,$source,$required) = @_;
+    use YAML;
+#    print map { Dump($_) } @_;
     if ($required and not $source->{$field}) {
-       fail("description file $f does not define required tag $field");
+      print Dump($f)."\n";
+      fail("description file $f does not define required tag $field");
     }
     $target->{$field} = $source->{$field};
     delete $source->{$field};
 }
-# }}}
 
-# {{{ Load information about collector scripts
 opendir(COLLDIR, "$MAEMIAN_ROOT/collection")
     or fail("cannot read directory $MAEMIAN_ROOT/collection");
 
diff --git a/reporting/checkout-release b/reporting/checkout-release
new file mode 100755 (executable)
index 0000000..95b0342
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+# Copyright 2008 Frank Lichtenheld <djpig@debian.org>, if at all copyrightable
+# Copyright 2009 Russ Allbery <rra@debian.org>
+
+# Helper script for updating lintian.debian.org to the latest lintian release
+# Call with the release number as only argument, e.g.
+#
+#   $ cd /org/lintian.debian.org && ./root/reporting/checkout-release 1.25.0
+
+set -e
+
+if [ $# -ne 1 ]; then
+       echo "Usage: checkout-release <version-number>"
+       exit 2
+fi
+release=$1
+
+set -x
+
+cd root
+git fetch
+git merge "$release"
+perl -i -pe "s/(LINTIAN_VERSION = )\S+/$1\"$release\";/" root/frontend/lintian
+debian/rules build
diff --git a/reporting/config b/reporting/config
new file mode 100644 (file)
index 0000000..adf652f
--- /dev/null
@@ -0,0 +1,30 @@
+# Hey emacs! This is a -*- Perl -*- script!
+# config -- configuration file for Lintian reporting harness
+
+$HOME = "/org/lintian.debian.org";
+$LINTIAN_ARCHIVEDIR = "/org/ftp.debian.org/ftp";
+$LINTIAN_DIST = "sid";
+$LINTIAN_AREA = "main";
+$LINTIAN_ARCH = "i386";
+
+$LINTIAN_ROOT = "$HOME/root";
+$LINTIAN_LAB = "$HOME/laboratory";
+$LOG_DIR = "$HOME/logs";
+$HTML_DIR = "$HOME/www";
+$HTML_TMP_DIR = "$HTML_DIR.new";
+$LINTIAN_CFG = "$LINTIAN_ROOT/reporting/lintian-dummy.cfg";  # this config file has to be empty!
+$LINTIAN_UNPACK_LEVEL = "";
+$LINTIAN_BIN_DIR = "$HOME/bin";
+$LINTIAN_GPG_CHECK = 0;
+
+$log_file = "$LOG_DIR/harness.log";
+$changes_file = "$LOG_DIR/setup-lab.log";
+$list_file = "$LOG_DIR/changed-packages.list";
+$lintian_log = "$LOG_DIR/lintian.log";
+$old_lintian_log = "$LOG_DIR/lintian.log.old";
+$lintian_cmd = "$LINTIAN_ROOT/frontend/lintian";
+$html_reports_cmd = "$LINTIAN_ROOT/reporting/html_reports";
+$html_reports_log = "$LOG_DIR/html_reports.log";
+$statistics_file = "$LOG_DIR/statistics";
+
+1;
diff --git a/reporting/harness b/reporting/harness
new file mode 100755 (executable)
index 0000000..06324b0
--- /dev/null
@@ -0,0 +1,314 @@
+#!/usr/bin/perl
+#
+# Lintian reporting harness -- Create and maintain Lintian reports automatically
+#
+# 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.
+
+use strict;
+use Getopt::Std;
+
+use vars qw($opt_c $opt_f $opt_i $opt_r);
+unless (getopts('cfir')) {
+  print <<END;
+Lintian reporting harness
+Create and maintain Lintian reports automatically
+
+Usage: harness [ -i | -c [-f] ] [ -r ]
+
+Options:
+  -c   clean mode, erase everything and start from scratch
+  -f   full mode, blithely overwrite lintian.log
+  -i   incremental mode, use old lintian.log data, process changes only
+  -r   generate HTML reports only
+
+Incremental mode is the default if you have a lintian.log;
+otherwise, it's full.
+
+Report bugs to <lintian-maint\@debian.org>.
+END
+#'# for cperl-mode
+  exit;
+}
+
+die "Can't use both incremental and full/clean." if ($opt_i && ($opt_f || $opt_c));
+$opt_f = 1 if $opt_c;
+die "Can't use other modes with reports only." if ($opt_r && ($opt_i || $opt_f || $opt_c));
+
+# read configuration
+require './config';
+use vars qw($LINTIAN_ROOT $LINTIAN_LAB $LINTIAN_ARCHIVEDIR $LINTIAN_DIST
+            $LINTIAN_SECTION $LINTIAN_ARCH $LINTIAN_UNPACK_LEVEL $LINTIAN_CFG
+            $lintian_cmd $html_reports_cmd
+            $log_file $lintian_log $old_lintian_log
+            $changes_file $list_file $html_reports_log
+            $LOG_DIR $statistics_file
+            $HTML_DIR $HTML_TMP_DIR $LINTIAN_BIN_DIR $LINTIAN_GPG_CHECK
+            $LINTIAN_AREA);
+
+# import perl libraries
+unshift @INC, "$LINTIAN_ROOT/lib";
+require Read_pkglists;
+use vars qw(%binary_info %source_info %udeb_info); # from the above
+require Util;
+
+# turn file buffering off
+$| = 1;
+
+# rotate log files
+system("savelog $log_file $changes_file $list_file $html_reports_log >/dev/null") == 0
+    or Die("cannot rotate log files");
+
+# create new log file
+open(LOG, '>', $log_file)
+    or Die("cannot open log file $log_file for writing: $!");
+
+system("mkdir -p -m 775 $LINTIAN_BIN_DIR") == 0 || die "$!";
+
+# export Lintian's configuration
+$ENV{'LINTIAN_ROOT'} = $LINTIAN_ROOT;
+$ENV{'LINTIAN_CFG'} = $LINTIAN_CFG;
+$ENV{'LINTIAN_LAB'} = $LINTIAN_LAB;
+$ENV{'LINTIAN_ARCHIVEDIR'} = $LINTIAN_ARCHIVEDIR;
+$ENV{'LINTIAN_DIST'} = $LINTIAN_DIST;
+$ENV{'LINTIAN_UNPACK_LEVEL'} = $LINTIAN_UNPACK_LEVEL;
+# LINTIAN_SECTION is deprecated in favour of LINTIAN_AREA
+if (defined $LINTIAN_SECTION) {
+    print STDERR "warning: LINTIAN_SECTION has been deprecated in favour of LINTIAN_AREA.\n";
+    if (defined $LINTIAN_AREA) {
+        print STDERR "Using LINTIAN_AREA as both were defined.\n";
+    } else {
+        print STDERR "Both are currently accepted, but LINTIAN_SECTION may be removed\n";
+        print STDERR "in a future Lintian release.\n";
+        $LINTIAN_AREA = $LINTIAN_SECTION;
+    }
+}
+$ENV{'LINTIAN_AREA'} = $LINTIAN_AREA;
+$ENV{'LINTIAN_ARCH'} = $LINTIAN_ARCH;
+$ENV{'PATH'} = $LINTIAN_BIN_DIR . ':' . $ENV{'PATH'};
+
+if ($LINTIAN_GPG_CHECK) {
+  if (-l $LINTIAN_BIN_DIR . '/gpg') {
+    unlink($LINTIAN_BIN_DIR . '/gpg');
+  } else {
+    rename($LINTIAN_BIN_DIR . '/gpg', $LINTIAN_BIN_DIR . '/gpg.bkp');
+  }
+} else {
+  symlink '/bin/true', $LINTIAN_BIN_DIR . '/gpg'
+    unless(-f $LINTIAN_BIN_DIR . '/gpg');
+}
+
+if ($opt_c) { # purge the old packages
+  system("rm -rf $LINTIAN_LAB/binary") == 0 || die "$!";
+  system("mkdir -m 2775 $LINTIAN_LAB/binary") == 0 || die "$!";
+  system("rm -rf $LINTIAN_LAB/udeb") == 0 || die "$!";
+  system("mkdir -m 2775 $LINTIAN_LAB/udeb") == 0 || die "$!";
+  system("rm -rf $LINTIAN_LAB/source") == 0 || die "$!";
+  system("mkdir -m 2775 $LINTIAN_LAB/source") == 0 || die "$!";
+  system("rm -f $LINTIAN_LAB/info/*") == 0 || die "$!";
+}
+
+unless ($opt_r) {
+  # make lintian update its packages files and save output
+  run("$lintian_cmd -v --setup-lab >$changes_file")
+      or Die("cannot run lintian --setup-lab");
+  Log("");
+}
+
+unless ($opt_f || $opt_c) {
+  unless ($opt_r) {
+    if (-f $lintian_log) {
+      $opt_i = 1;
+    } else {
+      $opt_f = 1;
+    }
+  }
+}
+
+if ($opt_f) { # check all packages
+  Log("Running Lintian over all packages...");
+  my $cmd = "$lintian_cmd -I -E --pedantic -v -a --show-overrides -U changelog-file >$lintian_log 2>&1";
+  Log("Executing $cmd");
+  my $res = (system($cmd) >> 8);
+  (($res == 0) or ($res == 1))
+    or Log("warning: executing lintian returned $res");
+  Log("");
+}
+
+if ($opt_i) { # process changes only
+
+    die "Old Lintian log file $lintian_log not found!\n" unless -f $lintian_log;
+
+    my $pkgfile;
+    my %skip_binary;
+    my %skip_udeb;
+    my %skip_source;
+
+    # read binary packages files
+    $pkgfile = "$LINTIAN_LAB/info/binary-packages";
+    (-f $pkgfile) or Die("cannot find list of binary packages $pkgfile");
+    read_bin_list($pkgfile);
+
+    # read udeb packages files
+    $pkgfile = "$LINTIAN_LAB/info/udeb-packages";
+    (-f $pkgfile) or Die("cannot find list of udeb packages $pkgfile");
+    read_udeb_list($pkgfile);
+
+    # read source packages files
+    $pkgfile = "$LINTIAN_LAB/info/source-packages";
+    (-f $pkgfile) or Die("cannot find list of source packages $pkgfile");
+    read_src_list($pkgfile);
+
+    # process changes file and create list of packages to process
+    Log("Reading changes file...");
+    open(IN, '<', $changes_file)
+       or Die("cannot open changes file $changes_file for reading: $!");
+    open(OUT, '>', $list_file)
+       or Die("cannot open list file $list_file for writing: $!");
+    while (<IN>) {
+       chop;
+
+       if (/^N: Listed (changed|new) (binary|udeb|source) package (\S+) (\S+)/o) {
+           my ($type,$binsrc,$pkg,$ver) = ($1,$2,$3,$4);
+
+           Log("$type $binsrc package $pkg $ver");
+
+           if ($binsrc eq 'binary') {
+               my $data = $binary_info{$pkg};
+               $data or Die("cannot find binary package $pkg in binary-packages file");
+               print OUT "b $binary_info{$pkg}->{'package'} $binary_info{$pkg}->{'version'} $LINTIAN_ARCHIVEDIR/$binary_info{$pkg}->{'file'}\n";
+               $skip_binary{$pkg} = 1;
+           } elsif ($binsrc eq 'udeb') {
+               my $data = $udeb_info{$pkg};
+               $data or Die("cannot find udeb package $pkg in udeb-packages file");
+               print OUT "u $udeb_info{$pkg}->{'package'} $udeb_info{$pkg}->{'version'} $LINTIAN_ARCHIVEDIR/$udeb_info{$pkg}->{'file'}\n";
+               $skip_udeb{$pkg} = 1;
+           } else {
+               my $data = $source_info{$pkg};
+               $data or Die("cannot find source package $pkg in source-packages file");
+               print OUT "s $source_info{$pkg}->{'source'} $source_info{$pkg}->{'version'} $LINTIAN_ARCHIVEDIR/$source_info{$pkg}->{'file'}\n";
+               $skip_source{$pkg} = 1;
+           }
+       } elsif (/^N: Removed (binary|udeb|source) package (\S+)/o) {
+           my ($binsrc,$pkg) = ($1,$2);
+
+           Log("removed $binsrc package $pkg");
+           run("rm -r -- \"$LINTIAN_LAB/$binsrc/$pkg\"")
+               or Log("could not remove $binsrc package $pkg");
+           if ($binsrc eq 'binary') {
+               $skip_binary{$pkg} = 1;
+           } elsif ($binsrc eq 'udeb') {
+               $skip_udeb{$pkg} = 1;
+           } else {
+               $skip_source{$pkg} = 1;
+           }
+       } elsif (/^N/o) {
+           # ignore other notes
+       } else {
+           Log("skipping changes line: $_");
+       }
+    }
+    close(OUT);
+    close(IN);
+    Log("");
+
+    # update lintian.log
+    Log("Updating lintian.log...");
+    rename $lintian_log, $old_lintian_log;
+    open(IN, '<', $old_lintian_log)
+       or Die("cannot open old lintian.log $old_lintian_log for reading: $!");
+    open(OUT, '>', $lintian_log)
+       or Die("cannot open lintian.log $lintian_log for writing: $!");
+    my $copy_mode = 1;
+    while (<IN>) {
+       if (/^N: Processing (binary|udeb|source) package (\S+)/o) {
+           my ($type,$pkg) = ($1,$2);
+
+           if ($type eq 'binary') {
+               $copy_mode = not exists $skip_binary{$pkg};
+           } elsif ($type eq 'udeb') {
+               $copy_mode = not exists $skip_udeb{$pkg};
+           } else {
+               $copy_mode = not exists $skip_source{$pkg};
+           }
+       }
+
+       if ($copy_mode) {
+           print OUT $_;
+       }
+    }
+    print OUT "N: ---end-of-old-lintian-log-file---\n";
+    close(OUT);
+    close(IN);
+    Log("");
+
+    # run Lintian over the newly introduced or changed packages
+    Log("Running Lintian over newly introduced and changed packages...");
+    my $cmd = "$lintian_cmd -I -E --pedantic -v --show-overrides -p $list_file -U changelog-file >>$lintian_log 2>&1";
+    Log("Executing $cmd");
+    my $res = (system($cmd) >> 8);
+    (($res == 0) or ($res == 1))
+        or Log("warning: executing lintian returned $res");
+    Log("");
+}
+
+# create html reports
+Log("Creating HTML reports...");
+run("$html_reports_cmd $lintian_log >$html_reports_log 2>&1")
+    or Log("warning: executing $html_reports_cmd returned $?");
+Log("");
+
+# rotate the statistics file updated by $html_reports_cmd
+if (-f $statistics_file) {
+  system("cp $statistics_file $LOG_DIR/stats/statistics-`date +%Y%m%d`") == 0
+    or Log("warning: couldn't rotate the statistics file");
+}
+
+#Log("Creating depcheck pages...");
+#run("$LINTIAN_ROOT/depcheck/deppages.pl >>$html_reports_log")
+#    or Log("warning: executing deppages.pl returned $?");
+#Log("");
+
+# install new html directory
+Log("Installing HTML reports...");
+system("rm -rf $HTML_DIR") == 0
+    or Die("error removing $HTML_DIR");
+# a tiny bit of race right here
+rename($HTML_TMP_DIR,$HTML_DIR)
+    or Die("error renaming $HTML_TMP_DIR into $HTML_DIR");
+Log("");
+
+# ready!!! :-)
+Log("All done.");
+exit 0;
+
+# -------------------------------
+
+sub Log {
+    print LOG $_[0],"\n";
+}
+
+sub run {
+    Log("Executing $_[0]");
+    return (system($_[0]) == 0);
+}
+
+sub Die {
+    Log("fatal error: $_[0]");
+    exit 1;
+}
diff --git a/reporting/html_reports b/reporting/html_reports
new file mode 100755 (executable)
index 0000000..b8d4730
--- /dev/null
@@ -0,0 +1,604 @@
+#!/usr/bin/perl -w
+#
+# Lintian HTML reporting tool -- Create Lintian web reports
+#
+# Copyright (C) 1998 Christian Schwarz and Richard Braakman
+# Copyright (C) 2007 Russ Allbery
+#
+# 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.
+
+use strict;
+
+use File::Copy qw(copy);
+use URI::Escape;
+use Text::Template ();
+
+# ------------------------------
+# Global variables and configuration
+
+# Maximum number of identical tags per package to display.  Any remaining tags
+# will be compressed into a "... reported %d more times" line.
+our $MAX_TAGS = 8;
+
+# These have no default and must be set in the configuration file.
+# FIXME: $statistics_file should be in all caps as well.
+our ($LINTIAN_ROOT, $LINTIAN_LAB, $LINTIAN_ARCHIVEDIR, $LINTIAN_DIST,
+     $LINTIAN_SECTION, $LINTIAN_ARCH, $HTML_TMP_DIR, $statistics_file,
+     $LINTIAN_AREA);
+
+# Read the configuration.
+require './config';
+
+if (defined $LINTIAN_SECTION and not defined $LINTIAN_AREA) {
+    $LINTIAN_AREA = $LINTIAN_SECTION;
+}
+
+# The path to the mirror timestamp.
+our $LINTIAN_TIMESTAMP
+    = "$LINTIAN_ARCHIVEDIR/project/trace/ftp-master.debian.org";
+
+# FIXME: At least the lab should be a parameter to Read_pkglists rather
+# than an environment variable.
+$ENV{'LINTIAN_LAB'} = $LINTIAN_LAB;
+$ENV{'LINTIAN_ROOT'} = $LINTIAN_ROOT;
+
+# Import Lintian Perl libraries.
+use lib "$ENV{LINTIAN_ROOT}/lib";
+use Lintian::Tag::Info ();
+use Read_pkglists;
+use Text_utils;
+use Util;
+
+# Global variables from Read_pkglists.  Ugh.
+# FIXME: Read_pkglists should return this information instead.
+our (%binary_info, %source_info, %udeb_info, %bin_src_ref);
+
+# Get additional tag information.
+our %tag_extra = ();
+
+opendir(CHECKDIR, "$LINTIAN_ROOT/checks")
+    or fail("cannot read directory $LINTIAN_ROOT/checks");
+
+for my $check (readdir CHECKDIR) {
+    next unless $check =~ /\.desc$/;
+    my @tags = read_dpkg_control("$LINTIAN_ROOT/checks/$check");
+    shift(@tags);
+    foreach my $tag (@tags) {
+        next unless $tag->{severity} and $tag->{certainty};
+        my $name = $tag->{tag};
+        $tag_extra{$name}{severity} = $tag->{severity};
+        $tag_extra{$name}{certainty} = $tag->{certainty};
+    }
+}
+
+closedir(CHECKDIR);
+
+# Set the Lintian version, current timestamp, and archive timestamp.
+our $LINTIAN_VERSION = `$LINTIAN_ROOT/frontend/lintian --print-version`;
+our $timestamp = `date -u --rfc-822`;
+our $mirror_timestamp = slurp_entire_file($LINTIAN_TIMESTAMP);
+chomp ($LINTIAN_VERSION, $timestamp);
+$mirror_timestamp =~ s/\n.*//s;
+
+
+# ------------------------------
+# Initialize templates
+
+# The path to our templates.
+our $TEMPLATES = "$LINTIAN_ROOT/reporting/templates";
+
+# This only has to be done once, so do it at the start and then reuse the same
+# templates throughout.
+our %templates;
+for my $template (qw/head foot clean index maintainer maintainers packages tag
+                     tags tags-severity/) {
+    my %options = (TYPE => 'FILE', SOURCE => "$TEMPLATES/$template.tmpl");
+    $templates{$template} = Text::Template->new (%options)
+        or die "cannot load template $template: $Text::Template::ERROR\n";
+}
+
+
+# ------------------------------
+# Main routine
+
+# Read the package lists.
+#
+# FIXME: get_bin_src_ref runs read_src_list unconditionally so we can't call
+# it directly, which is confusing.
+read_bin_list;
+read_udeb_list;
+get_bin_src_ref;
+
+# Create output directories.
+mkdir($HTML_TMP_DIR, 0777)
+    or die "cannot create output directory $HTML_TMP_DIR: $!\n";
+mkdir("$HTML_TMP_DIR/full", 0777)
+    or die "cannot create output directory $HTML_TMP_DIR/full: $!\n";
+mkdir("$HTML_TMP_DIR/maintainer", 0777)
+    or die "cannot create output directory $HTML_TMP_DIR/maintainer: $!\n";
+mkdir("$HTML_TMP_DIR/tags", 0777)
+    or die "cannot create output directory $HTML_TMP_DIR/tags: $!\n";
+symlink(".", "$HTML_TMP_DIR/reports")
+    or die "cannot create symlink $HTML_TMP_DIR/reports: $!\n";
+symlink("$LINTIAN_ROOT/doc/lintian.html", "$HTML_TMP_DIR/manual")
+    or die "cannot create symlink $HTML_TMP_DIR/manual: $!\n";
+if ($ARGV[0]) {
+    symlink($ARGV[0], "$HTML_TMP_DIR/lintian.log")
+        or die "cannot create symlink $HTML_TMP_DIR/lintian.log: $!\n";
+}
+copy("$LINTIAN_ROOT/reporting/lintian.css", "$HTML_TMP_DIR/lintian.css")
+    or die "cannot copy lintian.css to $HTML_TMP_DIR: $!\n";
+for my $image (qw/ico.png l.png logo-small.png/) {
+    copy("$LINTIAN_ROOT/reporting/images/$image", "$HTML_TMP_DIR/$image")
+        or die "cannot copy images/$image to $HTML_TMP_DIR: $!\n";
+}
+
+# This variable will accumulate statistics.  For tags: errors, warnings,
+# experimental, overridden, and info are the keys holding the count of tags of
+# that sort.  For packages: binary, udeb, and source are the number of
+# packages of each type with Lintian errors or warnings.  For maintainers:
+# maintainers is the number of maintainers with Lintian errors or warnings.
+my %statistics;
+
+# %by_maint holds a hash of maintainer names to packages and tags.  Each
+# maintainer is a key.  The value is a hash of package names to hashes.  Each
+# package hash is in turn a hash of versions to an anonymous array of hashes,
+# with each hash having keys code, package, type, tag, severity, certainty,
+# extra, and xref.  xref gets the partial URL of the maintainer page for that
+# source package.
+#
+# In other words, the lintian output line:
+#
+#     W: gnubg source: substvar-source-version-is-deprecated gnubg-data
+#
+# for gnubg 0.15~20061120-1 maintained by Russ Allbery <rra@debian.org> is
+# turned into the following structure:
+#
+# { 'gnubg' => {
+#       '0.15~20061120-1' => [
+#           { code      => 'W',
+#             package   => 'gnubg',
+#             type      => 'source',
+#             tag       => 'substvar-source-version-is-deprecated',
+#             severity  => 'normal',
+#             certainty => 'certain',
+#             extra     => 'gnubg-data'
+#             xref      => 'rra@debian.org.html#gnubg' } ] } }
+#
+# and then stored under the key 'Russ Allbery <rra@debian.org>'
+#
+# %by_uploader holds the same thing except for packages for which the person
+# is only an uploader.
+#
+# %by_tag is a hash of tag names to an anonymous array of tag information
+# hashes just like the inside-most data structure above.
+my (%by_maint, %by_uploader, %by_tag);
+
+# We take a lintian log file on either standard input or as the first
+# argument.  This log file contains all the tags lintian found, plus N: tags
+# with informational messages.  Ignore all the N: tags and load everything
+# else into the hashes we use for all web page generation.
+#
+# We keep track of a hash from maintainer page URLs to maintainer values so
+# that we don't have two maintainers who map to the same page and overwrite
+# each other's pages.  If we find two maintainers who map to the same URL,
+# just assume that the second maintainer is the same as the first (but warn
+# about it).
+my (%seen, %saw_maintainer);
+while (<>) {
+    chomp;
+    next unless m/^([EWIXO]): (\S+)(?: (\S+))?: (\S+)(?:\s+(.*))?/;
+    my ($code, $package, $type, $tag, $extra) = ($1, $2, $3, $4, $5);
+    $type = 'binary' unless (defined $type);
+    next unless ($type eq 'source' || $type eq 'binary' || $type eq 'udeb');
+
+    # Update statistics.
+    my $key = {
+        E => 'errors',
+        W => 'warnings',
+        I => 'info',
+        X => 'experimental',
+        O => 'overridden'
+    }->{$code};
+    $statistics{$key}++;
+    unless ($seen{"$package $type"}) {
+        $statistics{"$type-packages"}++;
+        $seen{"$package $type"} = 1;
+    }
+
+    # Determine the source package for this package and warn if there appears
+    # to be no source package in the archive.  Determine the maintainer and
+    # version.  Work around a missing source package by pulling information
+    # from a binary package or udeb of the same name if there is any.
+    my ($source, $version, $source_version, $maintainer, $uploaders);
+    if ($type eq 'source') {
+        $source = $package;
+        if (exists $source_info{$source}) {
+            $version = $source_info{$source}->{version};
+            $maintainer = $source_info{$source}->{maintainer};
+            $uploaders = $source_info{$source}->{uploaders};
+        } else {
+            warn "source package $package not listed!\n";
+        }
+    } else {
+        $source = $bin_src_ref{$package};
+        if ($source and exists $source_info{$source}) {
+            $maintainer = $source_info{$source}->{maintainer};
+            $uploaders = $source_info{$source}->{uploaders};
+        } else {
+            warn "source for package $package not found!\n";
+            $source = $package;
+            if ($type eq 'binary') {
+                $maintainer = $binary_info{$package}->{maintainer};
+            } elsif ($type eq 'udeb') {
+                $maintainer = $udeb_info{$package}->{maintainer};
+            }
+        }
+        if ($type eq 'binary') {
+            $version = $binary_info{$package}->{version};
+            $source_version = $binary_info{$package}->{'source-version'};
+        } elsif ($type eq 'udeb') {
+            $version = $udeb_info{$package}->{version};
+            $source_version = $udeb_info{$package}->{'source-version'};
+        }
+    }
+    $maintainer ||= '(unknown)';
+    $version ||= 'unknown';
+    $source_version ||= $version;
+
+    # Check if we've seen the URL for this maintainer before and, if so, map
+    # them to the same person as the previous one.
+    $maintainer = map_maintainer ($maintainer);
+    $saw_maintainer{$maintainer} = 1;
+
+    # Update maintainer statistics.
+    $statistics{maintainers}++ unless defined $by_maint{$maintainer};
+
+    # Sanitize, just out of paranoia.
+    $source =~ s/[^a-zA-Z0-9.+-]/_/g;
+    $version =~ s/[^a-zA-Z0-9.+:~-]/_/g;
+
+    # Add the tag information to our hashes.  Share the data between the
+    # hashes to save space (which means we can't later do destructive tricks
+    # with it).
+    my $info = {
+        code      => html_quote ($code),
+        package   => html_quote ($package),
+        version   => html_quote ($version),
+        type      => html_quote ($type),
+        tag       => html_quote ($tag),
+        severity  => html_quote ($tag_extra{$tag}{severity}),
+        certainty => html_quote ($tag_extra{$tag}{certainty}),
+        extra     => html_quote ($extra),
+        xref      => maintainer_url ($maintainer) . "#$source"
+    };
+    $by_maint{$maintainer}{$source}{$source_version} ||= [];
+    push(@{ $by_maint{$maintainer}{$source}{$source_version} }, $info);
+    $by_tag{$tag} ||= [];
+    push(@{ $by_tag{$tag} }, $info);
+
+    # If the package had uploaders listed, also add the information to
+    # %by_uploaders (still sharing the data between hashes).
+    if ($uploaders) {
+        my @uploaders = split (/\s*,\s*/, $uploaders);
+        for (@uploaders) {
+            my $uploader = map_maintainer ($_);
+            next if $uploader eq $maintainer;
+            $saw_maintainer{$uploader} = 1;
+            $by_uploader{$uploader}{$source}{$source_version} ||= [];
+            push(@{ $by_uploader{$uploader}{$source}{$source_version} }, $info);
+        }
+    }
+}
+
+# Build a hash of all maintainers, not just those with Lintian tags.  We use
+# this later to generate stub pages for maintainers whose packages are all
+# Lintian-clean.
+my %clean;
+for my $source (keys %source_info) {
+    my $maintainer = $source_info{$source}->{maintainer};
+    my $id = maintainer_url ($maintainer);
+    $clean{$id} = $maintainer;
+}
+
+# Now, walk through the tags by source package (sorted by maintainer).  Output
+# a summary page of errors and warnings for each maintainer, output a full
+# page that includes info, experimental, and overriden tags, and assemble the
+# maintainer index and the QA package list as we go.
+my (%qa, %maintainers, %packages);
+my @maintainers;
+{
+    my %seen;
+    @maintainers =
+        sort grep { !$seen{$_}++ } keys (%by_maint), keys (%by_uploader);
+}
+for my $maintainer (@maintainers) {
+    my $id = maintainer_url ($maintainer);
+    delete $clean{$id};
+
+    # For each of this maintainer's packages, add statistical information
+    # about warnings and errors to the QA list and build the packages hash
+    # used for the package index.  We only do this for the maintainer
+    # packages, not the uploader packages, to avoid double-counting.
+    for my $source (keys %{ $by_maint{$maintainer} }) {
+        my ($errors, $warnings) = (0, 0);
+        for my $version (keys %{ $by_maint{$maintainer}{$source} }) {
+            my $tags = $by_maint{$maintainer}{$source}{$version};
+            for my $tag (@$tags) {
+                $errors++ if $tag->{code} eq 'E';
+                $warnings++ if $tag->{code} eq 'W';
+                $packages{$tag->{package}} = $tag->{xref};
+            }
+        }
+        $qa{$source} = [ $errors, $warnings ];
+    }
+
+    # Determine if the maintainer's page is clean.  Check all packages for
+    # which they're either maintainer or uploader and set $error_clean if
+    # they have no errors or warnings.
+    my $error_clean = 1;
+    for my $source (keys %{ $by_maint{$maintainer} },
+                    keys %{ $by_uploader{$maintainer} }) {
+        my $versions = $by_maint{$maintainer}{$source}
+            || $by_uploader{$maintainer}{$source};
+        for my $version (keys %$versions) {
+            my $tags = $versions->{$version};
+            for my $tag (@$tags) {
+                $error_clean = 0 if ($tag->{code} eq 'E');
+                $error_clean = 0 if ($tag->{code} eq 'W');
+            }
+        }
+    }
+
+    # Determine the parts of the maintainer and the file name for the
+    # maintainer page.
+    my ($name, $email) = ($maintainer =~ /^(.*) <([^>]+)>/);
+    $name = 'Unknown Maintainer' unless $name;
+    $email = 'unknown' unless $email;
+    my $regular = "maintainer/$id";
+    my $full = "full/$id";
+
+    # Create the regular maintainer page (only errors and warnings) and the
+    # full maintainer page (all tags, including overrides and info tags).
+    print "Generating page for $id\n";
+    my %data = (
+        email      => html_quote (uri_escape ($email)),
+        errors     => 1,
+        id         => $id,
+        maintainer => html_quote ($maintainer),
+        name       => html_quote ($name),
+        packages   => $by_maint{$maintainer},
+        uploads    => $by_uploader{$maintainer},
+    );
+    my $template;
+    if ($error_clean) {
+        $template = $templates{clean};
+    } else {
+        $template = $templates{maintainer};
+    }
+    output_template ($regular, $template, \%data);
+    $template = $templates{maintainer};
+    $data{errors} = 0;
+    output_template ($full, $template, \%data);
+
+    # Add this maintainer to the hash of maintainer to URL mappings.
+    $maintainers{$maintainer} = $id;
+}
+
+# Write out the maintainer index.
+my %data = (
+    maintainers => \%maintainers,
+);
+output_template ('maintainers.html', $templates{maintainers}, \%data);
+
+# Write out the QA package list.
+open (QA, '>', "$HTML_TMP_DIR/qa-list.txt")
+    or die "cannot create qa-list.txt: $!\n";
+for my $source (sort keys %qa) {
+    print QA "$source $qa{$source}[0] $qa{$source}[1]\n";
+}
+close QA or die "cannot write to qa-list: $!\n";
+
+# Now, generate stub pages for every maintainer who has only clean packages.
+for my $id (keys %clean) {
+    my $maintainer = $clean{$id};
+    my ($name, $email) = ($maintainer =~ /^(.*) <([^>]+)>/);
+    $email = 'unknown' unless $email;
+    my %data = (
+        email      => html_quote (uri_escape ($email)),
+        maintainer => html_quote ($maintainer),
+        name       => html_quote ($name),
+    );
+    print "Generating clean page for $id\n";
+    output_template ("maintainer/$id", $templates{clean}, \%data);
+    output_template ("full/$id", $templates{clean}, \%data);
+}
+
+# Create the pages for each tag.  Each page shows the extended description for
+# the tag and all the packages for which that tag was issued.
+for my $tag (sort keys %by_tag) {
+    my $info = Lintian::Tag::Info->new($tag);
+    my $description;
+    if ($info) {
+        $description = $info->description('html', '    ');
+    } else {
+        $description = "    <p>Can't find description of tag $tag.</p>";
+    }
+    my $code = 'O';
+    foreach (@{$by_tag{$tag}}) {
+        if ($_->{code} ne 'O') {
+            $code = $_->{code};
+            last;
+        }
+    }
+    my %data = (
+        description => $description,
+        tag         => html_quote ($tag),
+        code        => $code,
+        tags        => $by_tag{$tag},
+    );
+    output_template ("tags/$tag.html", $templates{tag}, \%data);
+}
+
+# Create the general tag indices.
+%data = (
+    tags      => \%by_tag,
+);
+output_template ('tags.html', $templates{tags}, \%data);
+output_template ('tags-severity.html', $templates{'tags-severity'}, \%data);
+
+# Generate the package lists.  These are huge, so we break them into four
+# separate pages.
+#
+# FIXME: Does anyone actually use these pages?  They're basically unreadable.
+my %list;
+$list{'0-9, A-F'} = [];
+$list{'G-L'}      = [];
+$list{'M-R'}      = [];
+$list{'S-Z'}      = [];
+for my $package (sort keys %packages) {
+    my $first = uc substr($package, 0, 1);
+    if    ($first le 'F') { push(@{ $list{'0-9, A-F'} }, $package) }
+    elsif ($first le 'L') { push(@{ $list{'G-L'} },      $package) }
+    elsif ($first le 'R') { push(@{ $list{'M-R'} },      $package) }
+    else                  { push(@{ $list{'S-Z'} },      $package) }
+}
+%data = (
+    packages  => \%packages,
+);
+my $i = 1;
+for my $area (sort keys %list) {
+    $data{area} = $area;
+    $data{list} = $list{$area};
+    output_template ("packages_$i.html", $templates{packages}, \%data);
+    $i++;
+}
+
+# Finally, we can start creating the index page.  First, read in the old
+# statistics file so that we can calculate deltas for all of our statistics.
+my $old_statistics;
+if (-f $statistics_file) {
+    ($old_statistics) = read_dpkg_control($statistics_file);
+}
+my %delta;
+my @attrs = qw(maintainers source-packages binary-packages udeb-packages
+               errors warnings info experimental overridden);
+for my $attr (@attrs) {
+    my $old = $old_statistics->{$attr} || 0;
+    $statistics{$attr} ||= 0;
+    $delta{$attr} = sprintf("%d (%+d)", $statistics{$attr},
+                            $statistics{$attr} - $old);
+}
+
+# Update the statistics file.
+open (STATS, '>', $statistics_file)
+    or die "cannot open $statistics_file for writing: $!\n";
+print STATS "last-updated: $timestamp\n";
+print STATS "mirror-timestamp: $mirror_timestamp\n";
+for my $attr (@attrs) {
+    print STATS "$attr: $statistics{$attr}\n";
+}
+print STATS "lintian-version: $LINTIAN_VERSION\n";
+close STATS or die "cannot write to $statistics_file: $!\n";
+
+# Create the main page.
+%data = (
+    architecture => $LINTIAN_ARCH,
+    delta        => \%delta,
+    dist         => $LINTIAN_DIST,
+    mirror       => $mirror_timestamp,
+    previous     => $old_statistics->{'last-updated'},
+    area         => $LINTIAN_AREA,
+);
+output_template ('index.html', $templates{index}, \%data);
+exit 0;
+
+# ------------------------------
+# Utility functions
+
+# Determine the file name for the maintainer page given a maintainer.  It
+# should be <email>.html where <email> is their email address with all
+# characters other than a-z A-Z 0-9 - _ . @ = + replaced with _.  Don't change
+# this without coordinating with QA.
+sub maintainer_url {
+    my ($maintainer) = @_;
+    my ($email) = ($maintainer =~ /<([^>]+)>/);
+    my ($regular, $full);
+    if ($email) {
+        my $id = $email;
+        $id =~ tr/a-zA-Z0-9_.@=+-/_/c;
+        return "$id.html";
+    } else {
+        return 'unsorted.html';
+    }
+}
+
+# Deduplicate maintainers.  Maintains a cache of the maintainers we've seen
+# with a given e-mail address, issues a warning if two maintainers have the
+# same e-mail address, and returns the maintainer string that we should use
+# (which is whatever maintainer we saw first with that e-mail).
+{
+    my (%urlmap, %warned);
+    sub map_maintainer {
+        my ($maintainer) = @_;
+        my $url = maintainer_url ($maintainer);
+        if ($urlmap{$url} && $urlmap{$url} ne $maintainer) {
+            warn "$maintainer has the same page as $urlmap{$url}\n"
+                unless ($warned{$maintainer}
+                        || lc ($maintainer) eq lc ($urlmap{$url})
+                        || $maintainer =~ /\@lists\.(alioth\.)?debian\.org>/);
+            $warned{$maintainer}++;
+            $maintainer = $urlmap{$url};
+        } else {
+            $urlmap{$url} = $maintainer;
+        }
+        return $maintainer;
+    }
+}
+
+# Quote special characters for HTML output.
+sub html_quote {
+    my ($text) = @_;
+    $text ||= '';
+    $text =~ s/&/\&amp;/g;
+    $text =~ s/</\&lt;/g;
+    $text =~ s/>/\&gt;/g;
+    return $text;
+}
+
+# Given a file name, a template, and a data hash, fill out the template with
+# that data hash and output the results to the file.
+sub output_template {
+    my ($file, $template, $data) = @_;
+    $data->{version} ||= $LINTIAN_VERSION;
+    $data->{timestamp} ||= $timestamp;
+    $data->{head} ||= sub { $templates{head}->fill_in (HASH => { page_title => $_[0],
+                                                                 path_prefix => '../' x ($_[1]||0),
+                                                                 %$data }) };
+    $data->{foot} ||= sub { $templates{foot}->fill_in (HASH => $data) };
+    open (OUTPUT, '>', "$HTML_TMP_DIR/$file")
+        or die "creating $HTML_TMP_DIR/$file falied: $!\n";
+    $template->fill_in (OUTPUT => \*OUTPUT, HASH => $data)
+        or die "filling out $file failed: $Text::Template::ERROR\n";
+    close OUTPUT;
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround
diff --git a/reporting/lintian-dummy.cfg b/reporting/lintian-dummy.cfg
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/reporting/lintian.css b/reporting/lintian.css
new file mode 100644 (file)
index 0000000..552e7b7
--- /dev/null
@@ -0,0 +1,373 @@
+/* lintian.css -- Style sheet for lintian.debian.org pages. */
+
+/*
+ # Table of Contents:
+ #
+ # 1. General styles (links, lists, titles, tables...)
+ # 2. Header
+ # 3. Navigation
+ # 4. Main content
+ # 5. Footer
+ # 6. Other
+ */
+
+/*
+ # Order:
+ #
+ # example {
+ #   display
+ #   position
+ #   width
+ #   height
+ #   margin
+ #   padding
+ #   background
+ #   color
+ #   font
+ #   text
+ #   line-height
+ #   border
+ # }
+ */
+
+
+/*
+ * 1. General styles
+ */
+
+body {
+    margin: 0;
+    padding: 0;
+    color: #222;
+    background-color: white;
+    font-family: sans-serif;
+}
+
+h1, h2 {
+    font-family: "Junicode", "FreeSerif", serif;
+}
+
+h2 {
+    margin-bottom: 14px;
+    padding-bottom: 6px;
+    border-bottom: 2px solid #AAA;
+}
+
+h3 {
+    margin: 5px 0 5px 10px;
+    color: #444;
+    font-size: 1.0em;
+}
+
+p {
+    line-height: 1.4em;
+}
+
+a {
+    color: #3252B2;
+}
+
+ul {
+    margin: 0;
+}
+
+li {
+    color: #333;
+}
+
+hr {
+    display: none;
+}
+
+
+/*
+ * 2. Header title
+ */
+
+#header {
+    margin: 0 0 28px 0;
+    padding: 5px 20px;
+    /* TODO: Needs full path or url... */
+    background: #F3F3F3 url('logo-small.png') no-repeat right;
+    border-bottom: 1px solid #AAA;
+}
+
+#header p {
+    float: left;
+    margin: 5px 0;
+    color: #444;
+    font-size: 1.0em;
+    font-family: sans-serif;
+    font-weight: bold;
+}
+
+#header p a {
+    color: #444;
+    text-decoration: none;
+}
+
+
+/*
+ * 3. Navigation bar
+ */
+
+#nav {
+    float: right;
+    margin: 5px 55px 5px 0;
+    padding: 0;
+}
+
+#nav li {
+    display: inline;
+    margin-left: 5px;
+}
+
+#nav a {
+    padding: 3px 5px;
+    color: #333;
+    font-size: 0.9em;
+    text-decoration: none;
+}
+
+#nav a:hover {
+    background-color: #FBFBFB;
+    border-bottom: 2px solid #D70751;
+}
+
+
+/*
+ * 4. Main content
+ */
+
+#main {
+    margin: 0 25px;
+    font-size: 0.9em;
+    line-height: 1.4em;
+}
+
+/* Front page */
+
+#logo {
+    text-align: center;
+}
+
+#index h2, #stats h2 {
+    margin: 1.4em 0 0.4em 0;
+    border: none;
+}
+
+#info {
+    margin: 25px 0;
+    padding: 20px;
+    background: #EFF4F8 url(l.png) no-repeat left;
+    border: 1px solid #DFE4E8;
+}
+
+#info p {
+    margin-left: 130px;
+}
+
+#stats table {
+    border-collapse: collapse;
+}
+
+#stats table tr td {
+    padding: 3px 5px;
+    background-color: #FDFDFD;
+    border: 1px solid #CCC;
+}
+
+#stats td span {
+    margin: 0 3px 0 0;
+    padding: 1px 3px;
+    font-family: monospace;
+}
+
+/* Maintainer reports */
+
+#summary {
+    font-size: 0.9em;
+}
+
+#summary p {
+    margin-top: 5px;
+    font-size: 0.95em;
+}
+
+#summary ul {
+    padding-left: 20px;
+}
+
+ul.report {
+    padding-left: 20px;
+    padding-bottom: 1em;
+}
+
+ul.tag {
+    padding-bottom: 1em;
+}
+
+ul.report li {
+    padding-left: 5px;
+    padding-bottom: 6px;
+    list-style: none;
+}
+
+ul.tag li {
+    list-style: square;
+}
+
+li span {
+    margin: 0 3px 0 0;
+    padding: 1px 3px;
+    font-family: monospace;
+}
+
+h1 span {
+    padding: 1px 5px;
+    font-family: monospace;
+    font-size: 0.85em;
+    font-weight: normal;
+}
+
+ul.extra {
+    margin-bottom: 0;
+    padding-bottom: 0;
+}
+
+ul.report li ul.extra li {
+    padding-left: 0;
+    padding-bottom: 1px;
+    color: #444;
+    list-style: square;
+}
+
+div.source-header {
+    width: 100%;
+    margin: 1.4em 0 14px 0;
+    padding-bottom: 6px;
+    border-bottom: 2px solid #AAA;
+}
+
+div.source-header p, div.source-header h2 {
+    display: inline;
+    border: none;
+}
+
+.info-links {
+    margin-top: 0;
+}
+
+.info-links:before {
+    content: "– ";
+}
+
+/* Tag type */
+
+h2.tag {
+    margin: 5px 0;
+    padding: 0;
+    color: #444;
+    font-family: sans-serif;
+    font-size: 1.0em;
+    font-weight: normal;
+    border: none;
+}
+
+h2.tag a {
+    font-weight: bold;
+}
+
+h2.tag span.type-O {
+    margin: 0 3px 0 0;
+    padding: 2px 4px;
+    color: #555;
+    background: #EEE;
+    font-family: monospace;
+    font-size: 1.1em;
+    font-weight: bold;
+    border: 1px solid #DDD;
+}
+
+/* E/W/I colors */
+
+span.type-I {
+    color: #111;
+    background-color: #C7EA3C;
+}
+
+span.type-W {
+    color: #111;
+    background-color: #FFEB44;
+}
+
+span.type-E {
+    color: #111;
+    background-color: #FF6700;
+}
+
+span.type-X {
+    color: #111;
+    background-color: #EE99EE;
+}
+
+span.type-O {
+    color: #111;
+    background-color: #DDD;
+}
+
+li.type-O {
+    color: #444;
+}
+
+blockquote {
+    padding: 6px 16px;
+    background-color: #EEE;
+    border: 1px solid #DDD;
+}
+
+blockquote.type-I {
+    background-color: #DFA;
+    border: 1px solid #C7EA3C;
+}
+
+blockquote.type-W {
+    background-color: #FFD;
+    border: 1px solid #FFEB44;
+}
+
+blockquote.type-E {
+    background-color: #FE9;
+    border: 1px solid #FF6700;
+}
+
+blockquote.type-X {
+    background-color: #FECCFE;
+    border: 1px solid #DE66DE;
+}
+
+
+/*
+ * 5. Footer
+ */
+
+#footer {
+    margin: 20px 20px;
+    padding: 10px 0 0 0;
+    font-size: 0.85em;
+    border-top: 1px solid #AAA;
+}
+
+#footer p {
+    margin: 0;
+    padding: 0;
+}
+
+
+/*
+ * 6. Other
+ */
+
+div.clear {
+    clear: both;
+}
diff --git a/unpack/list-binpkg b/unpack/list-binpkg
new file mode 100755 (executable)
index 0000000..73d1cec
--- /dev/null
@@ -0,0 +1,204 @@
+#!/usr/bin/perl -w
+
+# list-binpkg -- maemian helper script
+
+# Copyright (C) 1998 Christian Schwarz
+# 
+# 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 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.
+
+use strict;
+
+# turn file buffering off:
+$| = 1;
+
+# parse command line options
+if ($#ARGV == -1) {
+    print "list-binpkg [-v] <output-list-file>\n";
+    print "options:\n";
+    print "   -v  verbose\n";
+    exit 0;
+}
+
+my $verbose = 0;
+my $output_file = undef;
+
+while (my $arg = shift) {
+    if ($arg =~ s,^-,,o) {
+       if ($arg eq 'v') {
+           $verbose = 1;
+       } else {
+           print STDERR "error: unknown command line argument: $arg\n";
+           exit 1;
+       }
+    } else {
+       if ($output_file) {
+           print STDERR "error: too many command line arguments: $arg\n";
+           exit 1;
+       }
+       $output_file = $arg;
+    }
+}
+
+unless ($output_file) {
+    print STDERR "error: no output file specified\n";
+    exit 1;
+}
+
+# import perl libraries
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Read_pkglists;
+use vars qw(%binary_info $BINLIST_FORMAT); # from the above
+use Util;
+
+# get variables out of environment
+my $MAEMIAN_ARCHIVEDIR = $ENV{'MAEMIAN_ARCHIVEDIR'};
+my $MAEMIAN_DIST = $ENV{'MAEMIAN_DIST'};
+my $MAEMIAN_ARCH = $ENV{'MAEMIAN_ARCH'};
+my $MAEMIAN_AREA = $ENV{'MAEMIAN_AREA'};
+my $MAEMIAN_LAB = $ENV{'MAEMIAN_LAB'};
+
+# read old list file (this command does nothing if the file does not exist)
+read_bin_list($output_file,1);
+
+my %pkgfile;
+# map filenames to package names
+for my $pkg (keys %binary_info) {
+    $pkgfile{$binary_info{$pkg}->{'file'}} = $pkg;
+}
+
+# open output file
+open(OUT, '>', $output_file)
+    or fail("cannot open list file $output_file for writing: $!");
+print OUT "$BINLIST_FORMAT\n";
+
+# parse Packages file to get list of packages
+my $packages = "$MAEMIAN_ARCHIVEDIR/dists/$MAEMIAN_DIST/$MAEMIAN_AREA/".
+               "binary-$MAEMIAN_ARCH/Packages";
+if (-e $packages) {
+       print "N: Parsing $packages ...\n" if $verbose;
+       open(IN, '<', $packages) or fail("cannot open Packages file $packages: $!");
+} elsif (-e "$packages.gz") {
+       print "N: Parsing $packages.gz ...\n" if $verbose;
+       open (IN, '-|', 'gzip', '-dc', "$packages.gz")
+           or fail("cannot open Packages file $packages.gz: $!");
+} else {
+       fail("No packages file $packages");
+}
+
+my $line;
+my %packages;
+my $total = 0;
+
+while (!eof(IN)) {
+    do { $line = <IN> } until ($line =~ m/^Architecture: (.*)$/m);
+    my $arch = $1;
+    do { $line = <IN> } until ($line =~ m/^Filename: (.*)$/m);
+    my $deb_file = $1;
+    do { $line = <IN> } until ($line =~ m/^\s*$/m);
+
+    my @stat;
+    # get timestamp...
+    unless (@stat = stat "$MAEMIAN_ARCHIVEDIR/$deb_file") {
+       print "E: general: cannot stat $MAEMIAN_ARCHIVEDIR/$deb_file\n";
+       next;
+    }
+    my $timestamp = $stat[9];
+    my ($status, $pkg, $data);
+
+    # was package already included in last list?
+    if (exists $pkgfile{$deb_file}) {
+       # yes!
+       $pkg = $pkgfile{$deb_file};
+       $data = $binary_info{$pkg};
+
+       # file changed since last run?
+       if ($timestamp == $data->{'timestamp'}) {
+           # no.
+           $status = 'unchanged';
+       } else {
+           $status = 'changed';
+           delete $binary_info{$pkg};
+       }
+    } else {
+       # new package, get info
+       $status = 'new';
+    }
+
+    if (($status eq 'new') or ($status eq 'changed')) {
+       $data = &safe_get_deb_info($deb_file);
+       next if not defined $data;
+       $pkg = $data->{'package'};
+    }
+
+    # check for duplicates
+    if (exists $packages{$pkg}) {
+       print "E: general: duplicate-binary-package $pkg\n";
+       next;
+    }
+
+    unless (exists $data->{'source-version'}) {
+       if ($data->{'source'} =~ /^([-+\.\w]+)\s+\((.+)\)$/) {
+           $data->{'source'} = $1;
+           $data->{'source-version'} = $2;
+       } else {
+           $data->{'source-version'} = $data->{'version'};
+       }
+    }
+
+    # write entry to output file
+    print OUT join(';',
+                  $pkg,
+                  $data->{'version'},
+                  $data->{'source'},
+                  $data->{'source-version'},
+                  $deb_file,
+                  $timestamp,
+                  ),"\n";
+    printf "N: Listed %s binary package %s %s\n",$status,$pkg,$data->{'version'} if $verbose;
+
+    # remove record from hash
+    delete $binary_info{$pkg} if $status eq 'unchanged';
+    $packages{$pkg} = 1;
+    $total++;
+}
+close(IN) or fail("cannot close input pipe: $!");
+close(OUT) or fail("cannot close output pipe: $!");
+
+if ($verbose) {
+    # all packages that are still included in %binary_info have disappeared from the archive...
+    for my $pkg (sort keys %binary_info) {
+       print "N: Removed binary package $pkg from list\n";
+    }
+    printf "N: Listed %d binary packages\n",$total;
+}
+
+exit 0;
+
+sub safe_get_deb_info {
+    # use eval when calling get_deb_info, since we don't want to `die' just
+    # because of a single broken package
+    my $data;
+    eval { $data = get_deb_info("$MAEMIAN_ARCHIVEDIR/$_[0]"); };
+    if ($@) {
+       # error!
+       print STDERR "$@\n";
+       print "E: general: bad-binary-package $_[0]\n";
+       return undef;
+    }
+    $data->{'source'} or ($data->{'source'} = $data->{'package'});
+    return $data;
+}
diff --git a/unpack/list-srcpkg b/unpack/list-srcpkg
new file mode 100755 (executable)
index 0000000..1227af1
--- /dev/null
@@ -0,0 +1,196 @@
+#!/usr/bin/perl -w
+# list-srcpkg -- maemian helper script
+
+# Copyright (C) 1998 Christian Schwarz
+# 
+# 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 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.
+
+use strict;
+
+# turn file buffering off:
+$| = 1;
+
+# parse command line options
+if ($#ARGV == -1) {
+  print "list-srcpkg [-v] <output-list-file>\n";
+  print "options:\n";
+  print "   -v  verbose\n";
+  exit 0;
+}
+
+my $verbose = 0;
+my $output_file = undef;
+
+while (my $arg = shift) {
+  if ($arg =~ s,^-,,o) {
+    if ($arg eq 'v') {
+      $verbose = 1;
+    } else {
+      print STDERR "error: unknown command line argument: $arg\n";
+      exit 1;
+    }
+  } else {
+    if ($output_file) {
+      print STDERR "error: too many command line arguments: $arg\n";
+      exit 1;
+    }
+    $output_file = $arg;
+  }
+}
+unless ($output_file) {
+  print STDERR "error: no output file specified\n";
+  exit 1;
+}
+
+# import perl libraries
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Read_pkglists;
+use vars qw(%source_info $SRCLIST_FORMAT); # from the above
+use Util;
+
+# get variables out of environment
+my $MAEMIAN_ARCHIVEDIR = $ENV{'MAEMIAN_ARCHIVEDIR'};
+my $MAEMIAN_DIST = $ENV{'MAEMIAN_DIST'};
+my $MAEMIAN_LAB = $ENV{'MAEMIAN_LAB'};
+my $MAEMIAN_AREA = $ENV{'MAEMIAN_AREA'};
+
+# read old list file (this command does nothing if the file does not exist)
+read_src_list($output_file,1);
+
+my %pkgfile;
+# map filenames to package names
+for my $pkg (keys %source_info) {
+  $pkgfile{$source_info{$pkg}->{'file'}} = $pkg;
+}
+
+# open output file
+open(OUT, '>', $output_file) or fail("cannot open list file $output_file for writing: $!");
+print OUT "$SRCLIST_FORMAT\n";
+
+# parse Sources.gz to get list of packages
+my $sources = "$MAEMIAN_ARCHIVEDIR/dists/$MAEMIAN_DIST/$MAEMIAN_AREA/source/Sources.gz";
+print "N: Parsing $sources ...\n" if $verbose;
+open(IN, '-|', 'zcat', $sources) or fail("Cannot open input pipe from zcat $sources: $!");
+
+my $line;
+my %packages;
+my $total = 0;
+
+while (!eof(IN)) {
+  do { $line = <IN> } until ($line =~ m/^Directory: (.*)$/m);
+  my $pkg_dir = $1;
+  do { $line = <IN> } until ($line =~ m/^ [0-9a-f]{32} [0-9]+ (.+\.dsc)$/m);
+  my $dsc_file = "$pkg_dir/$1";
+  do { $line = <IN> } until ($line =~ m/^\s*$/m);
+
+  my @stat;
+  # get timestamp...
+  unless (@stat = stat "$MAEMIAN_ARCHIVEDIR/$dsc_file") {
+    warn "E: general: cannot stat file $MAEMIAN_ARCHIVEDIR/$dsc_file: $!\n";
+    next;
+  }
+  my $timestamp = $stat[9];
+
+  my ($status,$pkg,$data);
+
+  # was package already included in last list?
+  if (exists $pkgfile{$dsc_file}) {
+    # yes!
+    $pkg = $pkgfile{$dsc_file};
+    $data = $source_info{$pkg};
+
+    # file changed since last run?
+    if ($timestamp == $data->{'timestamp'}) {
+      # no.
+      $status = 'unchanged';
+    } else {
+      $status = 'changed';
+      delete $source_info{$pkg};
+    }
+  } else {
+    # new package, get info
+    $status = 'new';
+  }
+
+  if (($status eq 'new') or ($status eq 'changed')) {
+    # use eval when calling get_dsc_info, since we don't want to `die' just
+    # because of a single broken package
+    eval { $data = get_dsc_info("$MAEMIAN_ARCHIVEDIR/$dsc_file"); };
+    if ($@) {
+      # error!
+      print STDERR "$@\n";
+      print "E: general: bad-source-package $dsc_file\n";
+      next;
+    }
+    my @f = (); 
+    for my $fs (split(/\n/,$data->{'files'})) {
+      next if $fs =~ /^\s*$/o;
+      my @t = split(/\s+/o,$fs);
+      push(@f,$t[2]);
+    }
+    $data->{'files'} = join(',',@f);
+    $data->{'standards-version'} ||= "";
+    $pkg = $data->{'source'};
+  }
+
+  # check for duplicates
+  if (exists $packages{$pkg}) {
+    print "E: general: duplicate-source-package $pkg\n";
+    next;
+  }
+
+  # write entry to output file
+  for (qw/version maintainer uploaders architecture standards-version binary files/) {
+    $data->{$_} =~ tr/;\n/_ / if $data->{$_};
+  }
+  print OUT join(';',
+                $pkg,
+                $data->{'version'},
+                $data->{'maintainer'},
+                 $data->{'uploaders'} || '',
+                 $data->{'architecture'},
+                 $data->{'standards-version'},
+                 $data->{'binary'},
+                 $data->{'files'},
+                 $dsc_file,
+                $timestamp,
+                ),"\n";
+  printf "N: Listed %s source package %s %s\n",$status,$pkg,$data->{'version'} if $verbose;
+
+  # remove record from hash
+  delete $source_info{$pkg} if $status eq 'unchanged';
+  $packages{$pkg} = 1;
+  $total++;
+}
+close(IN) or fail("cannot close input pipe: $!");
+close(OUT) or fail("cannot close output pipe: $!");
+
+if ($verbose) {
+  # all packages that are still included in %source_info have disappeared from the archive...
+  for my $pkg (sort keys %source_info) {
+    print "N: Removed source package $pkg from list\n";
+  }
+  printf "N: Listed %d source packages\n",$total;
+}
+
+exit 0;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 2
+# End:
+# vim: syntax=perl sw=2 sts=2 ts=2 et shiftround
diff --git a/unpack/list-udebpkg b/unpack/list-udebpkg
new file mode 100755 (executable)
index 0000000..d71a1e7
--- /dev/null
@@ -0,0 +1,204 @@
+#!/usr/bin/perl -w
+# list-udebpkg -- maemian helper script
+
+# Copyright (C) 1998 Christian Schwarz
+# Copyright (C) 2004 Frank Lichtenheld
+# 
+# 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 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.
+
+use strict;
+
+# turn file buffering off:
+$| = 1;
+
+# parse command line options
+if ($#ARGV == -1) {
+    print "list-udebpkg [-v] <output-list-file>\n";
+    print "options:\n";
+    print "   -v  verbose\n";
+    exit 0;
+}
+
+my $verbose = 0;
+my $output_file = undef;
+
+while (my $arg = shift) {
+    if ($arg =~ s,^-,,o) {
+       if ($arg eq 'v') {
+           $verbose = 1;
+       } else {
+           print STDERR "error: unknown command line argument: $arg\n";
+           exit 1;
+       }
+    } else {
+       if ($output_file) {
+           print STDERR "error: too many command line arguments: $arg\n";
+           exit 1;
+       }
+       $output_file = $arg;
+    }
+}
+
+unless ($output_file) {
+    print STDERR "error: no output file specified\n";
+    exit 1;
+}
+
+# import perl libraries
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Read_pkglists;
+use vars qw(%udeb_info $UDEBLIST_FORMAT); # from the above
+use Util;
+
+# get variables out of environment
+my $MAEMIAN_ARCHIVEDIR = $ENV{'MAEMIAN_ARCHIVEDIR'};
+my $MAEMIAN_DIST = $ENV{'MAEMIAN_DIST'};
+my $MAEMIAN_ARCH = $ENV{'MAEMIAN_ARCH'};
+my $MAEMIAN_AREA = $ENV{'MAEMIAN_AREA'};
+my $MAEMIAN_LAB = $ENV{'MAEMIAN_LAB'};
+
+# read old list file (this command does nothing if the file does not exist)
+read_udeb_list($output_file,1);
+
+my %pkgfile;
+# map filenames to package names
+for my $pkg (keys %udeb_info) {
+    $pkgfile{$udeb_info{$pkg}->{'file'}} = $pkg;
+}
+
+# open output file
+open(OUT, '>', $output_file)
+    or fail("cannot open list file $output_file for writing: $!");
+print OUT "$UDEBLIST_FORMAT\n";
+
+# parse Packages file to get list of packages
+my $packages = "$MAEMIAN_ARCHIVEDIR/dists/$MAEMIAN_DIST/$MAEMIAN_AREA/".
+               "debian-installer/binary-$MAEMIAN_ARCH/Packages";
+if (-e $packages) {
+    print "N: Parsing $packages ...\n" if $verbose;
+    open(IN, '<', $packages) or fail("cannot open Packages file $packages: $!");
+} elsif (-e "$packages.gz") {
+    print "N: Parsing $packages.gz ...\n" if $verbose;
+    open(IN, '-|', 'gzip', '-dc', "$packages.gz")
+        or fail("cannot open Packages file $packages.gz: $!");
+} else {
+    fail("No packages file $packages");
+}
+
+my $line;
+my %packages;
+my $total = 0;
+
+while (!eof(IN)) {
+    do { $line = <IN> } until ($line =~ m/^Architecture: (.*)$/m);
+    my $arch = $1;
+    do { $line = <IN> } until ($line =~ m/^Filename: (.*)$/m);
+    my $deb_file = $1;
+    do { $line = <IN> } until ($line =~ m/^\s*$/m);
+
+    my @stat;
+    # get timestamp...
+    unless (@stat = stat "$MAEMIAN_ARCHIVEDIR/$deb_file") {
+       print "E: general: cannot stat $MAEMIAN_ARCHIVEDIR/$deb_file\n";
+       next;
+    }
+    my $timestamp = $stat[9];
+    my ($status, $pkg, $data);
+
+    # was package already included in last list?
+    if (exists $pkgfile{$deb_file}) {
+       # yes!
+       $pkg = $pkgfile{$deb_file};
+       $data = $udeb_info{$pkg};
+
+       # file changed since last run?
+       if ($timestamp == $data->{'timestamp'}) {
+           # no.
+           $status = 'unchanged';
+       } else {
+           $status = 'changed';
+           delete $udeb_info{$pkg};
+       }
+    } else {
+       # new package, get info
+       $status = 'new';
+    }
+
+    if (($status eq 'new') or ($status eq 'changed')) {
+       $data = &safe_get_deb_info($deb_file);
+       next if not defined $data;
+       $pkg = $data->{'package'};
+    }
+
+    # check for duplicates
+    if (exists $packages{$pkg}) {
+       print "E: general: duplicate-udeb-package $pkg\n";
+       next;
+    }
+
+    unless (exists $data->{'source-version'}) {
+       if ($data->{'source'} =~ /^([-+\.\w]+)\s+\((.+)\)$/) {
+           $data->{'source'} = $1;
+           $data->{'source-version'} = $2;
+       } else {
+           $data->{'source-version'} = $data->{'version'};
+       }
+    }
+
+    # write entry to output file
+    print OUT join(';',
+                  $pkg,
+                  $data->{'version'},
+                  $data->{'source'},
+                  $data->{'source-version'},
+                  $deb_file,
+                  $timestamp,
+                  ),"\n";
+    printf "N: Listed %s udeb package %s %s\n",$status,$pkg,$data->{'version'} if $verbose;
+
+    # remove record from hash
+    delete $udeb_info{$pkg} if $status eq 'unchanged';
+    $packages{$pkg} = 1;
+    $total++;
+}
+close(IN) or fail("cannot close input pipe: $!");
+close(OUT) or fail("cannot close output pipe: $!");
+
+if ($verbose) {
+    # all packages that are still included in %udeb_info have disappeared from the archive...
+    for my $pkg (sort keys %udeb_info) {
+       print "N: Removed udeb package $pkg from list\n";
+    }
+    printf "N: Listed %d udeb packages\n",$total;
+}
+
+exit 0;
+
+sub safe_get_deb_info {
+    # use eval when calling get_deb_info, since we don't want to `die' just
+    # because of a single broken package
+    my $data;
+    eval { $data = get_deb_info("$MAEMIAN_ARCHIVEDIR/$_[0]"); };
+    if ($@) {
+       # error!
+       print STDERR "$@\n";
+       print "E: general: bad-udeb-package $_[0]\n";
+       return undef;
+    }
+    $data->{'source'} or ($data->{'source'} = $data->{'package'});
+    return $data;
+}
diff --git a/unpack/unpack-binpkg-l1 b/unpack/unpack-binpkg-l1
new file mode 100755 (executable)
index 0000000..e42a042
--- /dev/null
@@ -0,0 +1,126 @@
+#!/usr/bin/perl
+# unpack-binpkg-l1 -- maemian unpack script (binary packages level 1)
+#
+# syntax: unpack-binpkg-l1 <base-dir> <deb-file>
+#
+# Note that <deb-file> must be specified with absolute path.
+
+# Copyright (C) 1998 Christian Schwarz
+#
+# 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 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.
+
+use strict;
+use vars qw($verbose);
+
+($#ARGV == 1) or die "syntax: unpack-binpkg-l1 <base-dir> <deb-file>";
+my $base_dir = shift;
+my $file = shift;
+
+# import perl libraries
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Util;
+use Maemian::Command qw(spawn reap);
+
+# stat $file
+(my @stat = stat $file) or fail("$file: cannot stat: $!");
+
+my (@jobs, $job);
+
+# create directory in lab
+print "N: Creating directory $base_dir ...\n" if $verbose;
+mkdir("$base_dir", 0777) or fail("mkdir $base_dir: $!");
+mkdir("$base_dir/control", 0777) or fail("mkdir $base_dir/control: $!");
+mkdir("$base_dir/fields", 0777) or fail("mkdir $base_dir/fields: $!");
+symlink($file,"$base_dir/deb") or fail("symlink: $!");
+
+# The following calls use knowledge of the .deb format for speed
+
+# (replaces dpkg-deb -e)
+# extract control files' tarball
+spawn({ fail => 'error', out => "$base_dir/control.tar" },
+      ['ar', 'p', $file, 'control.tar.gz'],
+      '|', ['gzip', '-dc']);
+
+$job = { fail => 'error', err => "$base_dir/control-errors" };
+push @jobs, $job;
+# extract the tarball's contents
+spawn($job,
+      ["tar", "xf", "$base_dir/control.tar", "-C", "$base_dir/control", '&']);
+
+$job = { fail => 'error',
+         out  => "$base_dir/control-index",
+         err  => "$base_dir/control-index-errors" };
+push @jobs, $job;
+# create index of control.tar.gz
+spawn($job,
+      ["tar", "tvf", "$base_dir/control.tar"],
+      '|', ["sort", "-k", "6"], '&');
+
+reap(@jobs);
+undef @jobs;
+# clean up control.tar
+unlink("$base_dir/control.tar") or fail();
+
+# fix permissions
+spawn({ fail => 'error' },
+      ["chmod", "-R", "u+rX,o-w", "$base_dir/control"]);
+
+$job = { fail => 'error',
+         out  => "$base_dir/index",
+         err  => "$base_dir/index-errors" };
+push @jobs, $job;
+# (replaces dpkg-deb -c)
+# create index file for package
+spawn($job,
+      ["dpkg-deb", "--fsys-tarfile", $file ],
+      '|', ["tar", "tfv", "-"],
+      '|', ["sed", "-e", "s/^h/-/"],
+      '|', ["sort", "-k", "6"], '&');
+
+$job = { fail => 'error',
+         out  => "$base_dir/index-owner-id",
+         err  => '/dev/null' };
+push @jobs, $job;
+# (replaces dpkg-deb -c)
+# create index file for package with owner IDs instead of names
+spawn($job,
+      ["dpkg-deb", "--fsys-tarfile", $file],
+      '|', ["tar", "--numeric-owner", "-tvf", "-"],
+      '|', ["sed", "-e", "s/^h/-/"],
+      '|', ["sort", "-k", "6"], '&');
+
+# get package control information
+my $data = (read_dpkg_control("$base_dir/control/control"))[0];
+$data->{'source'} or ($data->{'source'} = $data->{'package'});
+
+# create control field files
+for my $field (keys %$data) {
+    my $field_file = "$base_dir/fields/$field";
+    open(F, '>', $field_file) or fail("cannot open file $field_file for writing: $!");
+    print F $data->{$field},"\n";
+    close(F);
+}
+
+# create symlink to source package
+$data->{'source'} =~ s/\s*\(.*\)\s*$//;
+symlink("../../source/$data->{'source'}","$base_dir/source")
+    or fail("symlink: $!");
+
+reap(@jobs);
+undef @jobs;
+
+exit 0;
diff --git a/unpack/unpack-binpkg-l2 b/unpack/unpack-binpkg-l2
new file mode 100755 (executable)
index 0000000..2f07b4a
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+# unpack-binpkg-l2 -- maemian unpack script (binary packages level 2)
+#
+# syntax: unpack-binpkg-l <base-dir>
+
+# Copyright (C) 1998 Christian Schwarz and Richard Braakman
+#
+# 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 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.
+
+use strict;
+use vars qw($verbose);
+
+($#ARGV == 0) or fail("syntax: unpack-binpkg-l2 <base-dir>");
+my $base_dir = shift;
+
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Maemian::Command qw(spawn);
+use Util;
+
+print "N: Unpacking binary packages in directory $base_dir ...\n" if $verbose;
+mkdir("$base_dir/unpacked", 0777) or fail();
+
+# avoid using dpkg-deb -x; this pipeline is far faster.  I got a factor 2
+# improvement on large debs, and factor 1.5 on small debs.  I heard
+# it's because dpkg-deb syncs while writing.  -- Richard
+
+spawn({ fail => 'error', err => "$base_dir/unpacked-errors" },
+      ['dpkg-deb', '--fsys-tarfile', "$base_dir/deb"],
+      '|', ['tar', 'xf', '-', '-C', "$base_dir/unpacked"]);
+
+# fix permissions
+spawn({ fail => 'error' },
+      ['chmod', '-R', 'u+rwX,go-w', "$base_dir/unpacked"]);
diff --git a/unpack/unpack-srcpkg-l1 b/unpack/unpack-srcpkg-l1
new file mode 100755 (executable)
index 0000000..b10776a
--- /dev/null
@@ -0,0 +1,178 @@
+#!/usr/bin/perl
+# unpack-srcpkg-l1 -- maemian unpack script (source packages level 1)
+#
+# syntax: unpack-srcpkg-l1 <base-dir> <dsc-file>
+#
+# Note, that <dsc-file> must be specified with absolute path.
+
+# Copyright (C) 1998 Christian Schwarz
+# Copyright (C) 2009 Raphael Geissert
+# Copyright (C) 2009 Russ Allbery
+#
+# 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 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.
+
+use strict;
+use vars qw($verbose);
+
+($#ARGV == 1) or die "syntax: unpack-srcpkg-l1 <base-dir> <dsc-file>";
+my $base_dir = shift;
+my $file = shift;
+
+# import perl libraries
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Util;
+
+use File::Spec;
+use Maemian::Command qw(spawn reap);
+
+# stat $file
+(my @stat = stat $file) or fail("$file: cannot stat: $!");
+
+# get package control information
+my $data = get_dsc_info($file);
+
+# create directory in lab
+print "N: Creating directory $base_dir ...\n" if $verbose;
+mkdir("$base_dir", 0777) or fail("mkdir $base_dir: $!");
+mkdir("$base_dir/fields", 0777) or fail("mkdir $base_dir/fields: $!");
+
+# create control field files
+for my $field (keys %$data) {
+    my $field_file = "$base_dir/fields/$field";
+    open(F, '>', $field_file)
+        or fail("cannot open file $field_file for writing: $!");
+    print F $data->{$field},"\n";
+    close(F);
+}
+
+# Install symbolic links to source package files.  Version handling is based
+# on Dpkg::Version::parseversion.
+my (undef, $dir, $name) = File::Spec->splitpath($file);
+my $version = $data->{'version'};
+if ($version =~ /:/) {
+    $version =~ s/^(?:\d+):(.+)/$1/ or fail("bad version number '$version'");
+}
+my $baserev = $data->{'source'} . '_' . $version;
+$version =~ s/(.+)-(.*)$/$1/;
+my $base = $data->{'source'} . '_' . $version;
+symlink($file,"$base_dir/dsc") or fail("cannot symlink dsc file: $!");
+my $tarball;
+for my $fs (split(/\n/,$data->{'files'})) {
+    $fs =~ s/^\s*//;
+    next if $fs =~ /^$/o;
+    my @t = split(/\s+/o,$fs);
+    if ($t[2] =~ /^(\Q$base\E\.orig|\Q$baserev\E)\.tar\.(gz|bz2|lzma)$/) {
+        $tarball = $t[2];
+    }
+    symlink("$dir/$t[2]", "$base_dir/$t[2]")
+        or fail("cannot symlink file $t[2]: $!");
+}
+if (!$tarball) {
+    fail("could not find the source tarball");
+}
+
+# Collect a list of the files in the source package.  tar currently doesn't
+# automatically recognize LZMA, so we need to add the option where it's
+# needed.  Change hard link status (h) to regular files and remove a leading
+# ./ prefix on filenames while we're reading the tar output.  We intentionally
+# don't parallelize this job because we need to use the output below.
+my @tar_options = ('-tvf');
+if ($tarball =~ /\.lzma\z/) {
+    unshift(@tar_options, '--lzma');
+}
+my @index;
+my $last = '';
+my $collect = sub {
+    my @lines = map { split "\n" } @_;
+    if ($last ne '') {
+        $lines[0] = $last . $lines[0];
+    }
+    if ($_[-1] !~ /\n\z/) {
+        $last = pop @lines;
+    } else {
+        $last = '';
+    }
+    for my $line (@lines) {
+        $line =~ s/^h/-/;
+        if ($line and $line !~ m,^(?:\S+\s+){5}\./$,) {
+            push(@index, $line . "\n");
+        }
+    }
+};
+spawn({ fail => 'never', out => $collect, err => "$base_dir/index-errors" },
+      ["tar", @tar_options, "$base_dir/$tarball"]);
+if ($last) {
+    fail("tar output doesn't end in a newline");
+}
+
+# We now need to see if all files in the tarball have a common prefix.  If so,
+# we're going to strip that prefix off each file name.  We also remove lines
+# that consist solely of the prefix.
+my $prefix;
+for my $line (@index) {
+    my ($file) = ($line =~ /^(?:\S+\s+){5}(.*)/);
+    $file =~ s,^\./+,,;
+    my ($dir) = ($file =~ m,^([^/]+),);
+    if (defined($dir) and $dir eq $file and not $line =~ /^d/) {
+        $prefix = '';
+    } elsif (defined $dir) {
+        if (not defined $prefix) {
+            $prefix = $dir;
+        } elsif ($dir ne $prefix) {
+            $prefix = '';
+        }
+    } else {
+        $prefix = '';
+    }
+}
+if ($prefix) {
+    @index = map {
+        s,^((?:\S+\s+){5})(?:\./+)?\Q$prefix\E(?:/+|\Z),$1,;
+        if (/^(?:\S+\s+){5}\S+/) {
+            $_;
+        } else {
+            ();
+        }
+    } @index;
+    open(PREFIX, '>', "$base_dir/source-prefix")
+        or fail("cannot create $base_dir/source-prefix: $!");
+    print PREFIX "$prefix\n";
+    close PREFIX;
+}
+
+# Now that we have the file names we want, write them out sorted to the index
+# file.
+my $job = { fail => 'error', out => "$base_dir/index" };
+spawn($job, sub { print @index }, '|', ['sort', '-k', '6'], '&');
+
+# Create symbolic links to binary packages
+mkdir("$base_dir/binary", 0777) or fail("mkdir $base_dir/binary: $!");
+for my $bin (split(/,\s+/o,$data->{'binary'})) {
+    symlink("../../../binary/$bin", "$base_dir/binary/$bin")
+        or fail("cannot symlink binary package $bin: $!");
+}
+
+# Wait for all jobs to finish.
+reap($job);
+
+exit 0;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround
diff --git a/unpack/unpack-srcpkg-l2 b/unpack/unpack-srcpkg-l2
new file mode 100755 (executable)
index 0000000..65ec574
--- /dev/null
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+# unpack-srcpkg-l2 -- maemian unpack script (source packages level 2)
+#
+# syntax: unpack-srcpkg-l2 <base-dir>
+
+# Copyright (C) 1998 Christian Schwarz and Richard Braakman
+#
+# 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 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.
+
+use strict;
+use vars qw($verbose);
+
+use lib "$ENV{'MAEMIAN_ROOT'}/lib";
+use Maemian::Command qw(spawn);
+use Util;
+
+($#ARGV == 0) or fail("syntax: unpack-srcpkg-l2 <base-dir>");
+my $base_dir = shift;
+
+print "N: Unpacking source package in directory $base_dir ...\n" if $verbose;
+chdir($base_dir);
+
+# Ignore STDOUT of the child process because older versions of dpkg-source
+# print things out even with -q.
+spawn({ fail => 'error', out => '/dev/null', err => 'unpacked-errors' },
+      ['dpkg-source', '-q', '-x', 'dsc', 'unpacked']);
+
+# fix permissions
+spawn({ fail => 'error' },
+      ['chmod', '-R', 'u+rwX,o+rX,o-w', 'unpacked']);
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 ts=4 et shiftround