From 1960326d487467271f731ff6a62830404a4947af Mon Sep 17 00:00:00 2001 From: Jeremiah Foster Date: Thu, 4 Jun 2009 02:05:45 +0300 Subject: [PATCH] Added lots more modules from lintian. Maemian appears to work. --- checks/binaries | 363 +++++++++++ checks/binaries.desc | 249 ++++++++ checks/changelog-file | 409 ++++++++++++ checks/changelog-file.desc | 316 +++++++++ checks/common_data.pm | 43 ++ checks/conffiles | 69 ++ checks/conffiles.desc | 38 ++ checks/control-file | 246 +++++++ checks/control-file.desc | 157 +++++ checks/control-files | 117 ++++ checks/control-files.desc | 38 ++ checks/copyright-file | 335 ++++++++++ checks/copyright-file.desc | 367 +++++++++++ checks/cruft | 352 ++++++++++ checks/cruft.desc | 401 ++++++++++++ checks/deb-format | 134 ++++ checks/deb-format.desc | 39 ++ checks/debconf | 555 ++++++++++++++++ checks/debconf.desc | 355 +++++++++++ checks/debhelper | 322 ++++++++++ checks/debhelper.desc | 179 ++++++ checks/debian-readme | 65 ++ checks/debian-readme.desc | 42 ++ checks/description | 187 ++++++ checks/description.desc | 171 +++++ checks/etcfiles | 64 ++ checks/etcfiles.desc | 12 + checks/fields | 1119 ++++++++++++++++++++++++++++++++ checks/fields.desc | 986 ++++++++++++++++++++++++++++ checks/files | 1088 +++++++++++++++++++++++++++++++ checks/files.desc | 979 ++++++++++++++++++++++++++++ checks/huge-usr-share | 63 ++ checks/huge-usr-share.desc | 19 + checks/infofiles | 206 ++++++ checks/infofiles.desc | 82 +++ checks/init.d | 269 ++++++++ checks/init.d.desc | 201 ++++++ checks/manpages | 356 +++++++++++ checks/manpages.desc | 277 ++++++++ checks/md5sums | 122 ++++ checks/md5sums.desc | 64 ++ checks/menu-format | 915 ++++++++++++++++++++++++++ checks/menu-format.desc | 325 ++++++++++ checks/menus | 691 ++++++++++++++++++++ checks/menus.desc | 353 ++++++++++ checks/nmu | 144 +++++ checks/nmu.desc | 90 +++ checks/patch-systems | 212 +++++++ checks/patch-systems.desc | 139 ++++ checks/po-debconf | 177 ++++++ checks/po-debconf.desc | 115 ++++ checks/rules | 183 ++++++ checks/rules.desc | 103 +++ checks/scripts | 1165 ++++++++++++++++++++++++++++++++++ checks/scripts.desc | 583 +++++++++++++++++ checks/shared-libs | 643 +++++++++++++++++++ checks/shared-libs.desc | 412 ++++++++++++ checks/standards-version | 155 +++++ checks/standards-version.desc | 74 +++ checks/version-substvars | 104 +++ checks/version-substvars.desc | 56 ++ checks/watch-file | 176 +++++ checks/watch-file.desc | 129 ++++ collection/changelog-file | 110 ++++ collection/changelog-file.desc | 9 + collection/copyright-file | 62 ++ collection/copyright-file.desc | 8 + collection/debfiles | 48 ++ collection/debfiles.desc | 8 + collection/debian-readme | 61 ++ collection/debian-readme.desc | 7 + collection/diffstat | 68 ++ collection/diffstat.desc | 8 + collection/doc-base-files | 45 ++ collection/doc-base-files.desc | 8 + collection/file-info | 62 ++ collection/file-info.desc | 7 + collection/init.d | 44 ++ collection/init.d.desc | 8 + collection/md5sums | 59 ++ collection/md5sums.desc | 7 + collection/menu-files | 54 ++ collection/menu-files.desc | 7 + collection/objdump-info | 240 +++++++ collection/objdump-info.desc | 9 + collection/override-file | 56 ++ collection/override-file.desc | 8 + collection/scripts | 101 +++ collection/scripts.desc | 11 + collection/source-control-file | 53 ++ collection/source-control-file.desc | 8 + collection/strings | 40 ++ collection/strings.desc | 9 + data/README | 12 + data/binaries/multiarch | 18 + data/changelog-file/ubuntu-dists | 9 + data/debhelper/dh_commands | 105 +++ data/debhelper/dh_packages | 32 + data/debhelper/filename-config-files | 13 + data/debhelper/maint_commands | 35 + data/debhelper/miscDepends_commands | 12 + data/doc-base/sections | 74 +++ data/fields/architectures | 187 ++++++ data/fields/obsolete-packages | 58 ++ data/fields/perl-provides | 29 + data/fields/virtual-packages | 283 +++++++++ lib/Checker.pm | 59 ++ lib/Maemian/Collect.pm | 155 +++++ lib/Maemian/Collect/Binary.pm | 454 +++++++++++++ lib/Maemian/Collect/Source.pm | 379 +++++++++++ lib/Maemian/Command.pm | 12 +- lib/Maemian/Data.pm | 36 +- lib/Maemian/Output.pm | 22 +- lib/Maemian/Relation.pm | 631 ++++++++++++++++++ lib/Maemian/Relation/Version.pm | 183 ++++++ lib/Maemian/Schedule.pm | 2 +- lib/Maemian/Tag/Info.pm | 38 +- lib/Read_pkglists.pm | 8 +- lib/Spelling.pm | 625 ++++++++++++++++++ lib/Tags.pm | 18 +- lib/Text_utils.pm | 4 +- lib/Util.pm | 9 +- maemian | 13 +- reporting/checkout-release | 24 + reporting/config | 30 + reporting/harness | 314 +++++++++ reporting/html_reports | 604 ++++++++++++++++++ reporting/lintian.css | 373 +++++++++++ unpack/list-binpkg | 204 ++++++ unpack/list-srcpkg | 196 ++++++ unpack/list-udebpkg | 204 ++++++ unpack/unpack-binpkg-l1 | 126 ++++ unpack/unpack-binpkg-l2 | 47 ++ unpack/unpack-srcpkg-l1 | 178 ++++++ unpack/unpack-srcpkg-l2 | 50 ++ 135 files changed, 25400 insertions(+), 90 deletions(-) create mode 100644 checks/binaries create mode 100644 checks/binaries.desc create mode 100644 checks/changelog-file create mode 100644 checks/changelog-file.desc create mode 100644 checks/common_data.pm create mode 100644 checks/conffiles create mode 100644 checks/conffiles.desc create mode 100644 checks/control-file create mode 100644 checks/control-file.desc create mode 100644 checks/control-files create mode 100644 checks/control-files.desc create mode 100644 checks/copyright-file create mode 100644 checks/copyright-file.desc create mode 100644 checks/cruft create mode 100644 checks/cruft.desc create mode 100644 checks/deb-format create mode 100644 checks/deb-format.desc create mode 100644 checks/debconf create mode 100644 checks/debconf.desc create mode 100644 checks/debhelper create mode 100644 checks/debhelper.desc create mode 100644 checks/debian-readme create mode 100644 checks/debian-readme.desc create mode 100644 checks/description create mode 100644 checks/description.desc create mode 100644 checks/etcfiles create mode 100644 checks/etcfiles.desc create mode 100644 checks/fields create mode 100644 checks/fields.desc create mode 100644 checks/files create mode 100644 checks/files.desc create mode 100644 checks/huge-usr-share create mode 100644 checks/huge-usr-share.desc create mode 100644 checks/infofiles create mode 100644 checks/infofiles.desc create mode 100644 checks/init.d create mode 100644 checks/init.d.desc create mode 100644 checks/manpages create mode 100644 checks/manpages.desc create mode 100644 checks/md5sums create mode 100644 checks/md5sums.desc create mode 100644 checks/menu-format create mode 100644 checks/menu-format.desc create mode 100644 checks/menus create mode 100644 checks/menus.desc create mode 100644 checks/nmu create mode 100644 checks/nmu.desc create mode 100644 checks/patch-systems create mode 100644 checks/patch-systems.desc create mode 100644 checks/po-debconf create mode 100644 checks/po-debconf.desc create mode 100644 checks/rules create mode 100644 checks/rules.desc create mode 100644 checks/scripts create mode 100644 checks/scripts.desc create mode 100644 checks/shared-libs create mode 100644 checks/shared-libs.desc create mode 100644 checks/standards-version create mode 100644 checks/standards-version.desc create mode 100644 checks/version-substvars create mode 100644 checks/version-substvars.desc create mode 100644 checks/watch-file create mode 100644 checks/watch-file.desc create mode 100755 collection/changelog-file create mode 100644 collection/changelog-file.desc create mode 100755 collection/copyright-file create mode 100644 collection/copyright-file.desc create mode 100755 collection/debfiles create mode 100644 collection/debfiles.desc create mode 100755 collection/debian-readme create mode 100644 collection/debian-readme.desc create mode 100755 collection/diffstat create mode 100644 collection/diffstat.desc create mode 100755 collection/doc-base-files create mode 100644 collection/doc-base-files.desc create mode 100755 collection/file-info create mode 100644 collection/file-info.desc create mode 100755 collection/init.d create mode 100644 collection/init.d.desc create mode 100755 collection/md5sums create mode 100644 collection/md5sums.desc create mode 100755 collection/menu-files create mode 100644 collection/menu-files.desc create mode 100755 collection/objdump-info create mode 100644 collection/objdump-info.desc create mode 100755 collection/override-file create mode 100644 collection/override-file.desc create mode 100755 collection/scripts create mode 100644 collection/scripts.desc create mode 100755 collection/source-control-file create mode 100644 collection/source-control-file.desc create mode 100755 collection/strings create mode 100644 collection/strings.desc create mode 100644 data/README create mode 100644 data/binaries/multiarch create mode 100644 data/changelog-file/ubuntu-dists create mode 100644 data/debhelper/dh_commands create mode 100644 data/debhelper/dh_packages create mode 100644 data/debhelper/filename-config-files create mode 100644 data/debhelper/maint_commands create mode 100644 data/debhelper/miscDepends_commands create mode 100644 data/doc-base/sections create mode 100644 data/fields/architectures create mode 100644 data/fields/obsolete-packages create mode 100644 data/fields/perl-provides create mode 100644 data/fields/virtual-packages create mode 100644 lib/Checker.pm create mode 100644 lib/Maemian/Collect.pm create mode 100644 lib/Maemian/Collect/Binary.pm create mode 100644 lib/Maemian/Collect/Source.pm create mode 100644 lib/Maemian/Relation.pm create mode 100644 lib/Maemian/Relation/Version.pm create mode 100644 lib/Spelling.pm create mode 100755 reporting/checkout-release create mode 100644 reporting/config create mode 100755 reporting/harness create mode 100755 reporting/html_reports create mode 100644 reporting/lintian-dummy.cfg create mode 100644 reporting/lintian.css create mode 100755 unpack/list-binpkg create mode 100755 unpack/list-srcpkg create mode 100755 unpack/list-udebpkg create mode 100755 unpack/unpack-binpkg-l1 create mode 100755 unpack/unpack-binpkg-l2 create mode 100755 unpack/unpack-srcpkg-l1 create mode 100755 unpack/unpack-srcpkg-l2 diff --git a/checks/binaries b/checks/binaries new file mode 100644 index 0000000..383c6d0 --- /dev/null +++ b/checks/binaries @@ -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 index 0000000..f82c230 --- /dev/null +++ b/checks/binaries.desc @@ -0,0 +1,249 @@ +Check-Script: binaries +Author: Christian Schwarz +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 + --strip-unneeded option. + +Tag: library-in-debug-or-profile-should-not-be-stripped +Severity: important +Certainty: certain +Info: Libraries in .../lib/debug or in + .../lib/profile 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 + /usr/lib/package. Libraries used by binaries in other + packages should be placed in /lib or /usr/lib 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 -Wl,--rpath or -R 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 + --remove-section=.comment --remove-section=.note to remove the + .note and .comment sections. + . + dh_strip will do this automatically for you, but + install -s 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 dpkg-shlibdeps is missing from the + package's debian/rules 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 "ldd + foo.so" 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 /usr/share + 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 /etc. + 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 debian/control. + . + 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 debian/control 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 /usr/lib/perl5, + 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 debian/rules or a missing + ${perl:Depends} substitution variable in the Depends line in + debian/control. + +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 /usr/lib/debug mirroring the main file system should contain + only debugging information generated by objcopy + --only-keep-debug. Binaries or shared objects built with extra + debugging should be installed directly in /usr/lib/debug 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 /usr/lib/debug. 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: + . + strings <binary> | grep -i <word> + . + You can often find the word in the source code by running: + . + grep -r '\b<original-word>\b' <source-tree> + +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 index 0000000..251053d --- /dev/null +++ b/checks/changelog-file @@ -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 () { + + 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 index 0000000..20346c3 --- /dev/null +++ b/checks/changelog-file.desc @@ -0,0 +1,316 @@ +Check-Script: changelog-file +Author: Christian Schwarz +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/<foo> 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/pkg/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/pkg + 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/pkg/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/pkg/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 + /usr/share/doc/pkg/changelog.gz. + . + 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/pkg/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/pkg/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/pkg + directory) has to install a Debian changelog file + /usr/share/doc/pkg/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/pkg 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/pkg 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/pkg + directory) must install a Debian changelog file in + /usr/share/doc/pkg/changelog.Debian.gz + . + A common error is to name the Debian changelog like an upstream changelog + (/usr/share/doc/pkg/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 (<..@unknown>) + 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 > 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 > 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 index 0000000..3aa5884 --- /dev/null +++ b/checks/common_data.pm @@ -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 index 0000000..d3f9851 --- /dev/null +++ b/checks/conffiles @@ -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 () { + 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 index 0000000..3792bef --- /dev/null +++ b/checks/conffiles.desc @@ -0,0 +1,38 @@ +Check-Script: conffiles +Author: Christian Schwarz +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 /usr may not be marked as conffiles, since + /usr 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 /etc + 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 debian/conffiles 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 debian/conffiles 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 index 0000000..aa8ab0b --- /dev/null +++ b/checks/control-file @@ -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 () { + 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 index 0000000..3f75157 --- /dev/null +++ b/checks/control-file.desc @@ -0,0 +1,157 @@ +Check-Script: control-file +Author: Marc 'HE' Brockschmidt +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 > 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 debian/control 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 debian/control, 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 debian/control 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 + debian/control 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 debian/control 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 debian/control 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 debian/control 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 + debian/control 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 debian/control file has a substvar + (something of the form ${Variable}) 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 debian/control 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 debian/control stanza. diff --git a/checks/control-files b/checks/control-files new file mode 100644 index 0000000..b370912 --- /dev/null +++ b/checks/control-files @@ -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 () { + 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; + +} # + +1; + +# vim: syntax=perl sw=4 ts=8 diff --git a/checks/control-files.desc b/checks/control-files.desc new file mode 100644 index 0000000..c09d9c9 --- /dev/null +++ b/checks/control-files.desc @@ -0,0 +1,38 @@ +Check-Script: control-files +Author: Christian Schwarz +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 index 0000000..154fa7b --- /dev/null +++ b/checks/copyright-file @@ -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,\, or m/\/) { + 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'; + } + } +} + +} # + +# ----------------------------------- + +# 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 index 0000000..d45374a --- /dev/null +++ b/checks/copyright-file.desc @@ -0,0 +1,367 @@ +Check-Script: copyright-file +Author: Christian Schwarz +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/<foo>/copyright file or must have a symlink + /usr/share/doc/<foo> -> <bar>, where <bar> 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/pkg/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/pkg/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/pkg/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/pkg/copyright contains the + complete text of the GPL v2 or v3. It should refer to the file + /usr/share/common-licenses/GPL-2 or GPL-3 instead. +Ref: policy 12.5 + +Tag: copyright-file-contains-full-gfdl-license +Severity: important +Certainty: certain +Info: The copyright file /usr/share/doc/pkg/copyright contains the + complete text of the GFDL v1.2. It should refer to the file + /usr/share/common-licenses/GFDL-1.2 instead. +Ref: policy 12.5 + +Tag: copyright-file-contains-full-apache-2-license +Severity: important +Certainty: certain +Info: The copyright file /usr/share/doc/pkg/copyright contains the + complete text of the Apache 2.0 license. It should refer to the file + /usr/share/common-licenses/Apache-2.0 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/pkg1 -> + pkg2, then pkg1 has to depend on pkg2 with the same + version as pkg1. + . + 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/pkg1 + directory within pkg1 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/pkg1 -> + pkg2, then pkg1 and pkg2 must both come from the same + source package. + . + It's suggested that you include a real /usr/share/doc/pkg1 directory + within pkg1 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/pkg1 -> pkg2 + in your package. This means that pkg1 and pkg2 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/pkg file. Instead, + you should place the copyright file in /usr/share/doc/pkg/copyright. +Ref: policy 12.5 + +Tag: old-fsf-address-in-copyright-file +Severity: normal +Certainty: certain +Info: The /usr/share/doc/pkg/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/pkg/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/pkg/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/pkg symbolic link is pointing to a directory + outside of /usr/share/doc. +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 + /usr/share/common-licenses, 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 + /usr/share/common-licenses 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 /usr/share/common-licenses 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 /usr/share/common-licenses 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 + /usr/share/common-licenses 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 > 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 dh_make. + (C) is not considered as a valid way to express the copyright + ownership. The word Copyright or the © symbol should be used + instead or in addition to (C). + +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 + /usr/share/common-licenses 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 debian/copyright file should + refer to /usr/share/common-licenses/GPL-2, not /GPL. + +Tag: copyright-refers-to-versionless-license-file +Severity: normal +Certainty: possible +Info: The copyright file refers to the versionless symlink in + /usr/share/common-licenses 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. + debian/copyright 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 debian/copyright file should refer to + /usr/share/common-licenses/GPL-2, not /GPL. + +Tag: copyright-refers-to-nonexistent-license-file +Severity: normal +Certainty: certain +Info: The copyright file refers to a license in + /usr/share/common-licenses that doesn't exist. Usually this is + a typo, such as accidentally omitting the - between the license + name and the version number. diff --git a/checks/cruft b/checks/cruft new file mode 100644 index 0000000..52eab01 --- /dev/null +++ b/checks/cruft @@ -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 () { + 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; + } +} + +} # + +# ----------------------------------- + +# 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 () { + 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 () { + 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 () { + 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 index 0000000..7892bff --- /dev/null +++ b/checks/cruft.desc @@ -0,0 +1,401 @@ +Check-Script: cruft +Author: Sean 'Shaleh' Perry +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 debian/files 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 + debian/rules. + +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 + -i to dpkg-buildpackage 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 -i to dpkg-buildpackage 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 -i to dpkg-buildpackage 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 ,, (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 -i to dpkg-buildpackage 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 ,, (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 -i to dpkg-buildpackage 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 -i to dpkg-buildpackage 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 -i to dpkg-buildpackage 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 + svn-commit(.NNN).tmp, 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 svn-commit(.NNN).tmp, + 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 + svk-commitNNN.tmp, 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 svk-commitNNN.tmp, + 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 + .arch-inventory 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 .arch-inventory 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 .htgags + 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 .htgags 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 .#file.version 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 .#file.version 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 svn resolved 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 patch 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 + ~ or of the form .xxx.swp, which is normally either an + Emacs or vim backup file or a backup file created by programs such as + autoheader or debconf-updatepo. 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 debian 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 + debian 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 debian/rules 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 index 0000000..bb337de --- /dev/null +++ b/checks/deb-format @@ -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 . + +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 () { + 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 index 0000000..dd0ad04 --- /dev/null +++ b/checks/deb-format.desc @@ -0,0 +1,39 @@ +Check-Script: deb-format +Author: Russ Allbery +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: + debian-binary, control.tar.gz, and + data.tar.gz or data.tar.bz2 in exactly that order. The + debian-binary 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 + control.tar.gz 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 index 0000000..3ec145e --- /dev/null +++ b/checks/debconf @@ -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 () { + 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 () { + 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 = ; + unless ($fl && $fl =~ /^\#!/) { + close IN; + next; + } + + while () { + s/#.*//; # Not perfect for Perl, but should be OK + next unless m/\S/; + while (s%\\$%%) { + my $next = ; + 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 () { + 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; +} + +} # + +# ----------------------------------- + +# 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 index 0000000..d4763f9 --- /dev/null +++ b/checks/debconf.desc @@ -0,0 +1,355 @@ +Check-Script: debconf +Author: Colin Watson +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 db_input. Typically, they should restrict themselves + to db_get 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 /usr/share/debconf/confmodule, while + Perl scripts should use Debconf::Client::ConfModule. +Ref: debconf-devel(7) + +Tag: postrm-does-not-purge-debconf +Severity: normal +Certainty: certain +Info: Packages using debconf should call db_purge 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 config + 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. db_input low start_$service_at_boot) + . + - 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 index 0000000..fa46f28 --- /dev/null +++ b/checks/debhelper @@ -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 () { + 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 () { + 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 () { + next if /^\s*$/; + next if (/^\#/ and $level >= 5); + if (m/(?; + 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 index 0000000..b8805e8 --- /dev/null +++ b/checks/debhelper.desc @@ -0,0 +1,179 @@ +Check-Script: debhelper +Author: Joey Hess +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 debian/rules 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 debian/rules 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 debian/rules 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 debian/compat file and in the debian/rules + 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) + debian/compat or by setting and exporting DH_COMPAT in + debian/rules. 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 debian/pycompat. 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 ~ 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 {foo,bar}) to specify files. This happens to work due + to an accident of implementation but is not a supported feature. Only + ?, *, and [...] 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 + debian/compat is not a number. + +Tag: debhelper-compatibility-level-not-a-number +Severity: important +Certainty: possible +Info: The debhelper compatibility level specified in debian/rules + is not a number. If you're using make functions or other more complex + methods to generate the compatibility level, write the output into + debian/compat 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 index 0000000..672ffd4 --- /dev/null +++ b/checks/debian-readme @@ -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 () { + 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', + ''); +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 index 0000000..b33672b --- /dev/null +++ b/checks/debian-readme.desc @@ -0,0 +1,42 @@ +Check-Script: debian-readme +Author: Richard Braakman +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 + <possible notes regarding this package - if none, delete this file> + . + 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 (<..@unknown>) + 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 index 0000000..b2bc12a --- /dev/null +++ b/checks/description @@ -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/(?= 80) { + tag "description-too-long", ""; + } + if ($synopsis =~ m/^missing\s*$/i) { + tag "description-is-debmake-template", "" unless $template++; + } elsif ($synopsis =~ m//) { + 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: /) { + 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 index 0000000..c1a3245 --- /dev/null +++ b/checks/description.desc @@ -0,0 +1,171 @@ +Check-Script: description +Author: Christian Schwarz +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 foo 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 + debian/control. 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 index 0000000..27b470f --- /dev/null +++ b/checks/etcfiles @@ -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 () { + 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 index 0000000..b9eaaab --- /dev/null +++ b/checks/etcfiles.desc @@ -0,0 +1,12 @@ +Check-Script: etcfiles +Author: Sean 'Shaleh' Perry +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 /etc 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 index 0000000..571d664 --- /dev/null +++ b/checks/fields @@ -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 () { + 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 index 0000000..48dca29 --- /dev/null +++ b/checks/fields.desc @@ -0,0 +1,986 @@ +Check-Script: fields +Author: Marc 'HE' Brockschmidt +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 Format: 1.0 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 (< and >). 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 (< and >). 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 + <debian-qa@lists.debian.org> in the Maintainer field. + . + The correct Maintainer field for orphaned packages is + Debian QA Group <packages@qa.debian.org>. +Ref: devref 5.9.4 + +Tag: wrong-debian-qa-group-name +Severity: important +Certainty: certain +Info: Orphaned packages should have "Debian QA Group + <packages@qa.debian.org>" 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 < and >. + 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 Homepage field. If the + package has an upstream home page that contains useful information or + resources for the end user, consider adding a Homepage control + field to debian/control. +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 "<" and ">" mean "<=" and ">=", not "<<" + and ">>" 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 + debian/rules 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 debian/rules. + . + 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 + debian/rules 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 debian/rules but doesn't + build-depend on python or python-dev. dh_python requires + /usr/bin/python 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 (>= 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 (>= 1.2)" would + be sufficient. If there was an earlier -0.X version of libfoo that would + not satisfy the dependency, use "libfoo (>= 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, pkga (= ${binary:Version}) | pkgb (= + ${binary:Version) 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 + +b 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 < 1.2+dfsg1 < 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, >= X.Y + >= X.Y + >= A.B, << 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 Uploaders 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 Uploaders + 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 (>= 5.10.0) | libmodule-build-perl (>= 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 (>= 5.10.0) | libmodule-build-perl (>= 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 index 0000000..5a1eed3 --- /dev/null +++ b/checks/files @@ -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,(? '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,(? 'php-db' ], + [ qr,(? '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,(? 'php-net-imap' ], + [ qr,SMTP\.php$, => 'php-net-smtp' ], + [ qr,(? 'php-net-socket' ], + [ qr,IPv4\.php$, => 'php-net-ipv4' ], + [ qr,(? '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 () { + 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 () { + 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 index 0000000..7f18c8f --- /dev/null +++ b/checks/files.desc @@ -0,0 +1,979 @@ +Check-Script: files +Author: Christian Schwarz +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 (>= + 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 (>= 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 (>= + 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 (>= 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 (>= 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 /etc/gconf/schemas + directory. No package should do this; this directory is reserved for + local overrides. Instead, schemas should be installed into + /usr/share/gconf/schemas. + +Tag: package-installs-into-etc-rc.d +Severity: serious +Certainty: certain +Info: The package installs files into the /etc/rc.d or + /etc/rc?.d 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 /etc/rc.boot 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 /etc/init.d directory should have + mode 0755. + +Tag: file-directly-in-usr-share +Severity: serious +Certainty: certain +Info: Packages should not install files directly in /usr/share, + i.e., without a subdirectory. + . + You should either create a subdirectory /usr/share/... for your + package or place the file in /usr/share/misc. + +Tag: file-in-usr-local +Severity: serious +Certainty: certain +Info: The package installs a file in /usr/local/... 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 /usr/share/man or + /usr/X11R6/man 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 /usr/local/... which is + not allowed. + . + If you want to provide an empty directory in /usr/local 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 + /usr/local 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 special file (e.g., a device file). + This is forbidden by current policy. If your program needs this device, + you should create it by calling makedev 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 + /usr/doc/examples directory. The new location for examples + is /usr/share/doc/pkg/examples. +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 /usr/share/doc should have mode + 0644. If the executable is an example, it should go in + /usr/share/doc/pkg/examples. + +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 + /usr/share/doc/pkg/examples 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.<time-interval> 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 /etc/cron.d 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 /etc/emacs* 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 /usr/lib. + According to the Filesystem Hierarchy Standard, architecture-independent + files need to be placed within /usr/share instead. + +Tag: file-directly-in-usr-share-doc +Severity: serious +Certainty: certain +Info: Documentation files have to be installed in + /usr/share/doc/pkg. +Ref: policy 12.3 + +Tag: bad-owner-for-doc-file +Severity: important +Certainty: certain +Info: Documentation files should be owned by root/root. + +Tag: dir-or-file-in-var-lock +Severity: serious +Certainty: possible +Info: /var/lock 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: /var/run 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 /var/www. + This is not one of the /var 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 /var/www + 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 /tmp or /var/tmp. + +Tag: dir-or-file-in-mnt +Severity: important +Certainty: certain +Info: Packages should not install into /mnt. 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 /opt, 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 /srv. The + specification of /srv 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 + /srv 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 /srv, 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 + http://www.python.org/doc/essays/packages.html, third-party + python packages should install their files in + /usr/lib/python1.5/site-packages. All other directories in + /usr/lib/python1.5 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 Thumbs.db or + Thumbs.db.gz, 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 .DS_Store or + .DS_Store.gz, 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 + ._, 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 perllocal.pod. 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 + debian/copyright 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 /bin. +Ref: fhs binessentialusercommandbinaries + +Tag: subdir-in-usr-bin +Severity: serious +Certainty: certain +Info: The Filesystem Hierarchy Standard forbids the installation of new + directories in /usr/bin other than /usr/bin/mh. +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 /usr hierarchy". This package contains + a directory in /usr 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 /var. 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 debian/rules: + . + find debian/pkg -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/package, 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 /etc 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 usr/share/doc/something/examples/examples + 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 + /usr/share/perl5, not /usr/lib/perl5 + 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 + /usr/share/applications, not /usr/share/gnome/apps. + +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 .sh or + .pl 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 /usr/lib/sgml. This was + the old location for SGML catalogs and similar flies. All those files + should now go into /usr/share/sgml. + +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 /usr/games. Binaries of games must be installed + in /usr/games. + +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 /bin or /usr/bin/. 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 /usr/games, 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 (/usr/share/devhelp/books and + /usr/share/gtk-doc/html) 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 /usr/share/doc), create a symlink in + /usr/share/gtk-doc/html 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 /usr/lib/debug, + 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 + /usr/share/linda/overrides. 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 ttf- or otf-.) 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 .gz, 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 index 0000000..c4c5ca4 --- /dev/null +++ b/checks/huge-usr-share @@ -0,0 +1,63 @@ +# huge-usr-share -- 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::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 index 0000000..44e3a7c --- /dev/null +++ b/checks/huge-usr-share.desc @@ -0,0 +1,19 @@ +Check-Script: huge-usr-share +Author: Jeroen van Wolffelaar +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 index 0000000..d447dec --- /dev/null +++ b/checks/infofiles @@ -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 () { + $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 = ; + $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 () { + 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 index 0000000..d5a90f2 --- /dev/null +++ b/checks/infofiles.desc @@ -0,0 +1,82 @@ +Check-Script: infofiles +Author: Josip Rodin +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 gzip -9). +Ref: policy 12.2 + +Tag: info-document-not-compressed-with-gzip +Severity: important +Certainty: certain +Info: Info documents should be compressed with gzip -9. +Ref: policy 12.2 + +Tag: info-document-not-compressed-with-max-compression +Severity: important +Certainty: certain +Info: Info documents should be compressed with gzip -9. +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 + --section option is passed to install-info in the + package postinst maintainer script. install-info 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 --section option to install-info in + the postinst maintainer script, although in this case you will + need to write the postinst 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 install-info command. Usually, + this command should be called from the prerm maintainer script. + +Tag: preinst-calls-install-info +Severity: important +Certainty: certain +Ref: policy 12.2 +Info: The preinst script calls the install-info command. Usually, + this command should be called from the postinst 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 install-info command, but the + documents installed are not then removed by the prerm maintainer + script. diff --git a/checks/init.d b/checks/init.d new file mode 100644 index 0000000..f1d7e6b --- /dev/null +++ b/checks/init.d @@ -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 () { + 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 () { + 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 () { + 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 () { + 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 () { + 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 = )) { + 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 = )) { + 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 index 0000000..6f6f12a --- /dev/null +++ b/checks/init.d.desc @@ -0,0 +1,201 @@ +Check-Script: init.d +Author: Christian Schwarz +Abbrev: ini +Type: binary +Needs-Info: init.d +Unpack-Level: 1 + +Tag: duplicate-updaterc.d-calls-in-postinst +Severity: important +Certainty: certain +Info: The postinst script calls update-rc.d several + times for the same /etc/init.d script. + +Tag: output-of-updaterc.d-not-redirected-to-dev-null +Severity: wishlist +Certainty: certain +Info: The output messages of the update-rc.d command should be + redirected to /dev/null because it is currently very chatty + per default. + +Tag: preinst-calls-updaterc.d +Severity: important +Certainty: certain +Info: The preinst package calls update-rc.d. Instead, + you should call it in the postinst script. +Ref: policy 9.3.3.1 + +Tag: duplicate-updaterc.d-calls-in-postrm +Severity: important +Certainty: certain +Info: The postrm script calls update-rc.d several + times for the same /etc/init.d script. + +Tag: prerm-calls-updaterc.d +Severity: important +Certainty: certain +Info: The prerm package calls update-rc.d. Instead, + you should call it in the postrm script. +Ref: policy 9.3.3.1 + +Tag: postrm-does-not-call-updaterc.d-for-init.d-script +Severity: important +Certainty: certain +Info: An /etc/init.d script which has been registered in the + postinst script is not de-registered in the + postrm script. +Ref: policy 9.3.3.1 + +Tag: postrm-contains-additional-updaterc.d-calls +Severity: important +Certainty: certain +Info: The postrm de-registers an /etc/init.d script which + has not been registered in the postinst 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 /etc/rc?.d 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: /etc/init.d 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 /etc/init.d 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 /etc/init.d script is registered in the + postinst 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 /etc/init.d script which is + not registered in the postinst 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 /etc/init.d script has more than one LSB keyword + section. These sections start with ### BEGIN INIT INFO and end + with ### END INIT INFO. 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 /etc/init.d script has an LSB keyword section starting + with ### BEGIN INIT INFO but either has no matching ### END + INIT INFO 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 #. + +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 + /etc/init.d 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 + /etc/init.d script but isn't one of the known LSB keywords and + doesn't begin with X-. 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 /etc/init.d + script doesn't match the required formatting of that section. Note that + keyword settings must start with #, 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 # 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 /etc/init.d script does not have an LSB keyword + section (or the ### BEGIN INIT INFO 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 /etc/init.d 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 /etc/init.d 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 /etc/init.d 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 /etc/init.d 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 + /etc/init.d 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 /etc/init.d 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 index 0000000..ea3c010 --- /dev/null +++ b/checks/manpages @@ -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 () { 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 = ; + $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 ; + 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 () { + # 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\'\"\`\(\[] + (? 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 index 0000000..be132dd --- /dev/null +++ b/checks/manpages.desc @@ -0,0 +1,277 @@ +Check-Script: manpages +Author: Christian Schwarz +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 + "../man[237]/undocumented.[237].gz" for manual pages in + /usr/share/man or + "../../../share/man/man[237]/undocumented.[237].gz" for manual + pages in /usr/X11R6/man. + +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 debmake or dh_make, or the + /usr/share/doc/man-db/examples directory. + If the package provides --help output, you might want to use + the help2man utility to generate a simple manual page. +Ref: policy 12.1 + +Tag: binary-without-manpage +Severity: normal +Certainty: possible +Info: Each binary in /usr/bin, /usr/sbin, /bin, + /sbin or /usr/games 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 + /usr/share/man/ or /usr/share/man/locale. + 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 + "section[program].gz". +Ref: policy 12.1 + +Tag: manpage-not-compressed +Severity: important +Certainty: certain +Info: Manual pages have to be installed compressed (using "gzip -9"). +Ref: policy 12.1 + +Tag: x11-games-should-be-in-usr-games +Severity: important +Certainty: certain +Info: Since X11 games should be installed in /usr/games (and + not in /usr/X11R6/bin) and the game's manual pages should be + installed in /usr/share/man/man6, the directory + /usr/X11R6/man/man6 should be empty. +Ref: policy 11.11 + +Tag: manpage-not-compressed-with-gzip +Severity: important +Certainty: certain +Info: Manual pages should be compressed with gzip -9. +Ref: policy 12.1 + +Tag: manpage-not-compressed-with-max-compression +Severity: important +Certainty: certain +Info: Manual pages should be compressed with gzip -9. +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 man's --warnings option to enable groff + warnings that catch common mistakes, such as putting . or + ' 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 (\&) 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 >/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 /usr/X11R6/bin + should be installed below /usr/X11R6/man. + . + Note that normally only packages that are part of X itself and those that + are using some arcane Imakefiles should actually install binaries into + /usr/X11R6/bin. +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 /usr/X11R6/bin + should not be installed below /usr/X11R6/man, but below + /usr/share/man. + . + Note that moving a binary into /usr/X11R6/bin 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. + . + .so man3/boo.1.gz + +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 .TH 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&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 very 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 /usr/bin, /usr/sbin, /bin, + /sbin or /usr/games 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 index 0000000..50c5819 --- /dev/null +++ b/checks/md5sums @@ -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 () { + 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 () { + 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 () { + 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 index 0000000..15c0151 --- /dev/null +++ b/checks/md5sums.desc @@ -0,0 +1,64 @@ +Check-Script: md5sums +Author: Christian Schwarz +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 + dh_md5sums at the end of your binary-indep or binary-arch + target, right before dh_builddeb. + +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 + debian/tmp/ directory is touched after dh_md5sums or + debstd 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 + debian/tmp/ directory is touched after dh_md5sums or + debstd is run. + . + If all the files in the DEBIAN/ 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 + debian/tmp/ directory is touched after dh_md5sums or + debstd is run. diff --git a/checks/menu-format b/checks/menu-format new file mode 100644 index 0000000..4e5d40f --- /dev/null +++ b/checks/menu-format @@ -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 { $_=; } while defined && (m/^\s* \#/ || m/^\s*$/); + while () { + 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 = ); + 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 = ) or goto parse_error; } + until ($line =~ /\/\*\s*XPM\s*\*\//); + + $parse = "size line"; + do { defined ($line = ) 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 = )) { + 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 index 0000000..416123f --- /dev/null +++ b/checks/menu-format.desc @@ -0,0 +1,325 @@ +Check-Script: menu-format +Author: Joey Hess +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. install-menu 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 /usr/share/pixmaps, 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 su-to-root -X, 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 X-. 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 index 0000000..ca93923 --- /dev/null +++ b/checks/menus @@ -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 () { + 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 () { + 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 = ; + $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 () { + # 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 index 0000000..9deb981 --- /dev/null +++ b/checks/menus.desc @@ -0,0 +1,353 @@ +Check-Script: menus +Author: Christian Schwarz +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 update-menus command without + checking for existence first. (The menu 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 wm-menu-config command without + checking for existence first. (The menu 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 install-docs command without + checking for existence first. (The doc-base 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/<your-package> + 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 update-menus command. Usually, + this command should be called from the postinst maintainer script. + +Tag: preinst-calls-installdocs +Severity: important +Certainty: certain +Info: The preinst script calls the install-docs command. Usually, + this command should be called from the postinst maintainer script. + +Tag: prerm-calls-updatemenus +Severity: important +Certainty: certain +Info: The prerm script calls the update-menus command. Usually, + this command should be called from the postrm maintainer script. + +Tag: postrm-calls-installdocs +Severity: important +Certainty: certain +Info: The postrm script calls the install-docs command. Usually, + this command should be called from the prerm 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 /usr/share/doc-base may not be marked as executables. + +Tag: postinst-does-not-call-updatemenus +Severity: important +Certainty: certain +Info: Since the package installs a file in /etc/menu-methods, + /usr/share/menu, or /usr/lib/menu, the package should + probably call the update-menus command in it's postinst + 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 /etc/menu-methods, + /usr/share/menu, or /usr/lib/menu, the package should + probably call the update-menus command in it's postrm + 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 postinst script calls the update-menus program + though no file is installed in /etc/menu-methods, + /usr/share/menu, or /usr/lib/menu. + +Tag: postrm-has-useless-call-to-update-menus +Severity: minor +Certainty: certain +Info: The postrm script calls the update-menus program + though no file is installed in /etc/menu-methods, + /usr/share/menu, or /usr/lib/menu. + +Tag: postinst-has-useless-call-to-install-docs +Severity: minor +Certainty: certain +Info: Explicitly calling install-docs in postinst is no + longer required since doc-base file processing is handled by triggers. + If the install-docs 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 install-docs in prerm is no + longer required since doc-base file processing is handled by triggers. + If the install-docs 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 /usr/lib/menu/menu, which is + already in use by the menu 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 /usr/share/doc-base should only contain links to + files in the /usr/share/doc 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 > 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 index 0000000..d1b47ff --- /dev/null +++ b/checks/nmu @@ -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 index 0000000..7f96052 --- /dev/null +++ b/checks/nmu.desc @@ -0,0 +1,90 @@ +Check-Script: nmu +Author: Jeroen van Wolffelaar +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 debian/changelog 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 debian/changelog 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 index 0000000..d14d7a0 --- /dev/null +++ b/checks/patch-systems @@ -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() { + 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() { + 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 + $_ .= 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 () { + #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() { + 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 () { + # 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 () { + 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 () { + 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 index 0000000..336cbd4 --- /dev/null +++ b/checks/patch-systems.desc @@ -0,0 +1,139 @@ +Check-Script: patch-systems +Author: Marc Brockschmidt +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 quilt header -e + 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 + debian 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 + debian 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 debian 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 debian + directory are stored as patches. diff --git a/checks/po-debconf b/checks/po-debconf new file mode 100644 index 0000000..56c80fb --- /dev/null +++ b/checks/po-debconf @@ -0,0 +1,177 @@ +# po-debconf -- lintian check script -*- perl -*- + +# Copyright (C) 2002-2004 by Denis Barbier +# +# 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 () { + 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 () { + 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 () { + 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 () { + 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 index 0000000..bd8d41a --- /dev/null +++ b/checks/po-debconf.desc @@ -0,0 +1,115 @@ +Check-Script: po-debconf +Author: Denis Barbier +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 (debian/po 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 debian/po directory and should no longer be needed. + +Tag: missing-potfiles-in +Severity: normal +Certainty: certain +Info: The required file POTFILES.in is missing from + debian/po. +Ref: po-debconf(7) + +Tag: missing-file-from-potfiles-in +Severity: normal +Certainty: certain +Info: A file listed in debian/po/POTFILES.in could not be found + in the source package. +Ref: po-debconf(7) + +Tag: missing-templates-pot +Severity: normal +Certainty: certain +Info: The required file templates.pot is missing from + debian/po. +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 msgfmt 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 debian/po ending in .po will be + processed as translations by po2debconf for the language code equal to + the file name without the trailing .po. 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 .po 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 debian/rules. 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 index 0000000..08dd447 --- /dev/null +++ b/checks/rules @@ -0,0 +1,183 @@ +# rules -- lintian check script -*- perl -*- + +# Copyright (C) 2006 Russ Allbery +# Copyright (C) 2005 René van Bevern +# +# 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 = ; +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 () { + 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 index 0000000..6c36658 --- /dev/null +++ b/checks/rules.desc @@ -0,0 +1,103 @@ +Check-Script: rules +Author: Russ Allbery +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 debian/rules 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 debian/rules 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 debian/rules file for this package does not appear to + be a makefile or does not start with the required line. + debian/rules must be a valid makefile and must have + "#!/usr/bin/make -f" as its first line. + +Tag: debian-rules-missing-required-target +Severity: serious +Certainty: certain +Ref: policy 4.9 +Info: The debian/rules 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 debian/rules 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 debian/rules 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 debian/compat. Any + line in debian/rules that sets it globally should be deleted and + a separate debian/compat 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 + debian/rules, 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 debian/rules 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 index 0000000..0379d47 --- /dev/null +++ b/checks/scripts @@ -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'(?]\(.*?\)', # <() 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 () { + 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 () { + 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 () { + 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 () { + 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 index 0000000..8be8cef --- /dev/null +++ b/checks/scripts.desc @@ -0,0 +1,583 @@ +Check-Script: scripts +Author: Richard Braakman +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 config 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 postrm maintainer script that uses + an interpreter that isn't essential. The purge action of + postrm can only rely on essential packages, which means the + interpreter used by postrm must be one of the essential ones + (sh, bash, or perl). +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 /usr/local. Control scripts must use interpreters + provided by Debian packages, and Debian packages do not install anything + in /usr/local. + +Tag: preinst-interpreter-without-predepends +Severity: serious +Certainty: certain +Info: The package contains a preinst maintainer script that uses + an unusual and non-essential interpreter but does not declare a + pre-dependency on the package that provides this interpreter. + . + preinst 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 #!/usr/bin/python, + the package needs a dependency on "python". If a script uses + #!/usr/bin/python2.5, 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 #!/usr/bin/ruby, + the package needs a dependency on "ruby". If a script uses + #!/usr/bin/ruby1.9, 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. sh -n yourscript 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. sh -n yourscript 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 /tmp 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 tempfile or mktemp 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 killall. Since this + utility kills processes by name, it may well end up killing unrelated + processes. Most uses of killall should use invoke-rc.d + 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 start-stop-daemon + directly. Long-running daemons should be started and stopped via init + scripts using invoke-rc.d 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 /bin/sh, 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: update-alternatives --remove <alternative> foo 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 update-alternatives --auto + <alternative> is run by hand. + . + update-alternatives --remove should be called in the prerm + instead. +Ref: policy F, update-alternatives(8) + +Tag: deprecated-chown-usage +Severity: normal +Certainty: certain +Info: chown user.group is called in one of the maintainer + scripts. The correct syntax is chown user:group. 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 invoke-rc.d. While use of invoke-rc.d 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}' <package> + . + 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 + /etc/services, /etc/protocols, and /etc/rpc, + 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 /etc/inetd.conf directly. + This file must not be modified directly; instead, use the + update-inetd script or the DebianNet.pm 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 --quiet and --remove 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 -e 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 -e to the shell on the + #! line rather than using set -e in the body of the + script. This is fine for normal operation, but if the script is run by + hand with sh /path/to/script (common in debugging), -e + will not be in effect. It's therefore better to use set -e 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 --local or + without --package. 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 preinst or postinst instead of + waiting for postrm to do it. diff --git a/checks/shared-libs b/checks/shared-libs new file mode 100644 index 0000000..aae06de --- /dev/null +++ b/checks/shared-libs @@ -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() { + 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 () { + 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 () { + 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 index 0000000..01fecd0 --- /dev/null +++ b/checks/shared-libs.desc @@ -0,0 +1,412 @@ +Check-Script: shared-libs +Author: Christian Schwarz +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 + gcc -Wl,-shared instead of gcc -shared. + . + 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 readelf + -d 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 + .so 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 + /usr/lib rather than in a public shared library directory. + . + To view the SONAME of a shared library, run readelf -d 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 gcc -Wl,-soname,libfoo.so.0, + 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 gcc -Wl,-soname,libfoo.so.0, + 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 readelf -d 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 readelf -l + 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 readelf -l 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 + << 1.0-1 while 1.0-1~bpo >= 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 debian/<package>.symbols 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 + << 1.0-1 while 1.0-1~bpo >= 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 gcc -Wl,-soname,libfoo.so.0, + 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 prefix at make install + time instead of using DESTDIR. 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 index 0000000..44c69ff --- /dev/null +++ b/checks/standards-version @@ -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 index 0000000..3cb9c4b --- /dev/null +++ b/checks/standards-version.desc @@ -0,0 +1,74 @@ +Check-Script: standards-version +Author: Christian Schwarz +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 /usr/share/doc/debian-policy/upgrading-checklist.txt.gz 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 debian/changelog 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 /usr/share/doc/debian-policy/upgrading-checklist.txt.gz 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 debian/changelog entry. + Perhaps you forgot to update the timestamp in debian/changelog + before building the package? diff --git a/checks/version-substvars b/checks/version-substvars new file mode 100644 index 0000000..edb85cd --- /dev/null +++ b/checks/version-substvars @@ -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 index 0000000..652c368 --- /dev/null +++ b/checks/version-substvars.desc @@ -0,0 +1,56 @@ +Check-Script: version-substvars +Author: Adeodato Simó +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 (>= ${source:Version}) + Depends: arch_any (>= ${source:Version}), + arch_any (<< ${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 index 0000000..6c21225 --- /dev/null +++ b/checks/watch-file @@ -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 () { + next if /^\s*\#/; + next if /^\s*$/; + s/^\s*//; + + CHOMP: + chomp; + if (s/(?; + 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 index 0000000..00241aa --- /dev/null +++ b/checks/watch-file.desc @@ -0,0 +1,129 @@ +Check-Script: watch-file +Author: Patrick Schoenfeld +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 + debian/watch 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 debian/watch 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 + debian/watch file containing only comments documenting the + situation. + +Tag: debian-watch-file-declares-multiple-versions +Severity: normal +Certainty: certain +Ref: uscan(1) +Info: The debian/watch file in this package contains multiple + lines starting with version=. 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 version= line in the debian/watch 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 debian/watch file in this package doesn't start a + version= line. The first non-comment line of + debian/watch should be a version= 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 dfsg, ds, + or debian, 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 + debian/watch file. Since the dfsg string is not + part of the upstream version, the debian/watch file should + use the dversionmangle option to remove the dfsg 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 dfsg, ds, + or debian, but a misleading upstream version mangling occurs in + the debian/watch file. Since the dfsg string is not + part of the upstream version and its addition is Debian-specific, the + the debian/watch file should use the dversionmangle option to + remove, instead of adding in uversionmangle, the dfsg 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 + project 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/<project>/<tar-name>-(.+)\.tar\.gz + . + replacing <project> with the name of the SourceForge + project and <tar-name> 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 debian/changelog 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 debian/changelog entry, and the + current debian/changelog 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 index 0000000..a642d4a --- /dev/null +++ b/collection/changelog-file @@ -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 "); +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 ; + 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 index 0000000..1dff706 --- /dev/null +++ b/collection/changelog-file.desc @@ -0,0 +1,9 @@ +Collector-Script: changelog-file +Author: Richard Braakman +Info: This script copies the changelog file and + NEWS.Debian 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 index 0000000..c6d2ee1 --- /dev/null +++ b/collection/copyright-file @@ -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 "); +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 index 0000000..7a7aaad --- /dev/null +++ b/collection/copyright-file.desc @@ -0,0 +1,8 @@ +Collector-Script: copyright-file +Author: Richard Braakman +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 index 0000000..8ea7247 --- /dev/null +++ b/collection/debfiles @@ -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 "); +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 index 0000000..316b542 --- /dev/null +++ b/collection/debfiles.desc @@ -0,0 +1,8 @@ +Collector-Script: debfiles +Author: Joey Hess +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 index 0000000..9ed8191 --- /dev/null +++ b/collection/debian-readme @@ -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 "); +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 index 0000000..a4d15dd --- /dev/null +++ b/collection/debian-readme.desc @@ -0,0 +1,7 @@ +Collector-Script: debian-readme +Author: Richard Braakman +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 index 0000000..2baea5b --- /dev/null +++ b/collection/diffstat @@ -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 "); +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 = ; 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. + 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 () { + 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 index 0000000..d250dbe --- /dev/null +++ b/collection/diffstat.desc @@ -0,0 +1,8 @@ +Collector-Script: diffstat +Author: Richard Braakman +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 index 0000000..e4c461a --- /dev/null +++ b/collection/doc-base-files @@ -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 "); +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 index 0000000..01e5b10 --- /dev/null +++ b/collection/doc-base-files.desc @@ -0,0 +1,8 @@ +Collector-Script: doc-base-files +Author: Josip Rodin +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 index 0000000..1b939ab --- /dev/null +++ b/collection/file-info @@ -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 "); +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 () { + 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 index 0000000..08e513e --- /dev/null +++ b/collection/file-info.desc @@ -0,0 +1,7 @@ +Collector-Script: file-info +Author: Richard Braakman +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 index 0000000..9a814bb --- /dev/null +++ b/collection/init.d @@ -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 "); +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 index 0000000..c186238 --- /dev/null +++ b/collection/init.d.desc @@ -0,0 +1,8 @@ +Collector-Script: init.d +Author: Richard Braakman +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 index 0000000..9740c45 --- /dev/null +++ b/collection/md5sums @@ -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 "); +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 () { + 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 index 0000000..4e5e52f --- /dev/null +++ b/collection/md5sums.desc @@ -0,0 +1,7 @@ +Collector-Script: md5sums +Author: Richard Braakman +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 index 0000000..e220602 --- /dev/null +++ b/collection/menu-files @@ -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 "); +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 index 0000000..a1ab6d1 --- /dev/null +++ b/collection/menu-files.desc @@ -0,0 +1,7 @@ +Collector-Script: menu-files +Author: Richard Braakman +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 index 0000000..54eb8e4 --- /dev/null +++ b/collection/objdump-info @@ -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 = ; +chomp $binutils_version; +close PIPE; + +chdir ("unpacked") + or fail ("unable to chdir to unpacked: $!\n"); + +while () { + 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 $_ = ; + 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 $_ = ; + print OUT $_; + close PIPE; + } + } else { + $failed = 1; + my $invalidop = 0; + my $objdumpout = ''; + if (open(PIPE, '-|', "objdump --headers --private-headers -T $bin 2>&1")) { + while() { + $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() { + 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() { + 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 index 0000000..c0b7321 --- /dev/null +++ b/collection/objdump-info.desc @@ -0,0 +1,9 @@ +Collector-Script: objdump-info +Author: Christian Schwarz +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 index 0000000..23b3284 --- /dev/null +++ b/collection/override-file @@ -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 "); +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 index 0000000..cdca520 --- /dev/null +++ b/collection/override-file.desc @@ -0,0 +1,8 @@ +Collector-Script: override-file +Author: Darren Benham +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 index 0000000..fc904bd --- /dev/null +++ b/collection/scripts @@ -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 () { + 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 = ; + 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 = ; + $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 index 0000000..3469335 --- /dev/null +++ b/collection/scripts.desc @@ -0,0 +1,11 @@ +Collector-Script: scripts +Author: Richard Braakman +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 index 0000000..6f68244 --- /dev/null +++ b/collection/source-control-file @@ -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 "); +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 index 0000000..a8127d2 --- /dev/null +++ b/collection/source-control-file.desc @@ -0,0 +1,8 @@ +Collector-Script: source-control-file +Author: Frank Lichtenheld +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 index 0000000..4583c97 --- /dev/null +++ b/collection/strings @@ -0,0 +1,40 @@ +#!/bin/sh -e +# strings -- maemian collection script + +# 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, 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 " + 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 index 0000000..12f36d9 --- /dev/null +++ b/collection/strings.desc @@ -0,0 +1,9 @@ +Collector-Script: strings +Author: Raphael Geissert +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 index 0000000..b1e7ba9 --- /dev/null +++ b/data/README @@ -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 index 0000000..0efa0fd --- /dev/null +++ b/data/binaries/multiarch @@ -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 index 0000000..5c7602d --- /dev/null +++ b/data/changelog-file/ubuntu-dists @@ -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 index 0000000..658324f --- /dev/null +++ b/data/debhelper/dh_commands @@ -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 index 0000000..a05df0e --- /dev/null +++ b/data/debhelper/dh_packages @@ -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 index 0000000..ff58d02 --- /dev/null +++ b/data/debhelper/filename-config-files @@ -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 index 0000000..6a9789b --- /dev/null +++ b/data/debhelper/maint_commands @@ -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 index 0000000..0afb23c --- /dev/null +++ b/data/debhelper/miscDepends_commands @@ -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 index 0000000..41ef102 --- /dev/null +++ b/data/doc-base/sections @@ -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 index 0000000..822c3a4 --- /dev/null +++ b/data/fields/architectures @@ -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 index 0000000..9726cf0 --- /dev/null +++ b/data/fields/obsolete-packages @@ -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 index 0000000..2c1a4dc --- /dev/null +++ b/data/fields/perl-provides @@ -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 index 0000000..608fefb --- /dev/null +++ b/data/fields/virtual-packages @@ -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 index 0000000..b65c266 --- /dev/null +++ b/lib/Checker.pm @@ -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 index 0000000..89077b2 --- /dev/null +++ b/lib/Maemian/Collect.pm @@ -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 . + +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 = ; + 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, 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 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 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 index 0000000..a68ac65 --- /dev/null +++ b/lib/Maemian/Collect/Binary.pm @@ -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 . + +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 () { + 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. + +=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 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 index 0000000..954de4d --- /dev/null +++ b/lib/Maemian/Collect/Source.pm @@ -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 . + +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 or C +currently). The source-control-file collection script must have been run +to parse the F file and put the fields in the F +directory in the lab. + +=item binary_field(PACKAGE, FIELD) + +Returns the content of the field FIELD for the binary package PACKAGE in +the F 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 file and put the fields in the F 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 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 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. + +=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 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/Command.pm b/lib/Maemian/Command.pm index 053fb70..46c361d 100644 --- a/lib/Maemian/Command.pm +++ b/lib/Maemian/Command.pm @@ -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 for Lintian. +Originally written by Frank Lichtenheld for Maemian. =head1 SEE ALSO diff --git a/lib/Maemian/Data.pm b/lib/Maemian/Data.pm index e529069..a61548a 100644 --- a/lib/Maemian/Data.pm +++ b/lib/Maemian/Data.pm @@ -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 . -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 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 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 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 -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 when Lintian is installed as a Debian package). The +This variable must be set to Maemian's root directory (normally +F when Maemian is installed as a Debian package). The B program normally takes care of doing this. This module doesn't care about the contents of this directory other than expecting the F 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 for Lintian. +Originally written by Russ Allbery for Maemian. =head1 SEE ALSO diff --git a/lib/Maemian/Output.pm b/lib/Maemian/Output.pm index 2d65d9e..af40f10 100644 --- a/lib/Maemian/Output.pm +++ b/lib/Maemian/Output.pm @@ -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. +$Maemian::Output::GLOBAL unless their first argument C. =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 =head1 AUTHOR -Originally written by Frank Lichtenheld for Lintian. +Originally written by Frank Lichtenheld for Maemian. Modified by Jeremiah C. Foster for maemian. =head1 SEE ALSO diff --git a/lib/Maemian/Relation.pm b/lib/Maemian/Relation.pm new file mode 100644 index 0000000..5dc866b --- /dev/null +++ b/lib/Maemian/Relation.pm @@ -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 +# +# 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 . + +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 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 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 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 index 0000000..31f50cd --- /dev/null +++ b/lib/Maemian/Relation/Version.pm @@ -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 +# Copyright (C) 2009 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, see . + +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 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 for Maemian and adapted +to use libapt-pkg-perl by Adam D. Barratt . + +=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 diff --git a/lib/Maemian/Schedule.pm b/lib/Maemian/Schedule.pm index 3fe5899..48abecb 100644 --- a/lib/Maemian/Schedule.pm +++ b/lib/Maemian/Schedule.pm @@ -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; diff --git a/lib/Maemian/Tag/Info.pm b/lib/Maemian/Tag/Info.pm index fc173a6..8763c2d 100644 --- a/lib/Maemian/Tag/Info.pm +++ b/lib/Maemian/Tag/Info.pm @@ -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 if not set. The B program normally takes care of setting it. @@ -338,7 +338,7 @@ care of setting it. =head1 AUTHOR -Originally written by Russ Allbery for Lintian. +Originally written by Russ Allbery for Maemian. =head1 SEE ALSO diff --git a/lib/Read_pkglists.pm b/lib/Read_pkglists.pm index 587cc87..4e11e91 100644 --- a/lib/Read_pkglists.pm +++ b/lib/Read_pkglists.pm @@ -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 index 0000000..250c18f --- /dev/null +++ b/lib/Spelling.pm @@ -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 diff --git a/lib/Tags.pm b/lib/Tags.pm index 4a9024d..aa2bc5e 100644 --- a/lib/Tags.pm +++ b/lib/Tags.pm @@ -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 @@ -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 diff --git a/lib/Text_utils.pm b/lib/Text_utils.pm index 96e745a..f1aef91 100644 --- a/lib/Text_utils.pm +++ b/lib/Text_utils.pm @@ -121,7 +121,7 @@ sub dtml_to_html { my $pre=0; for $_ (@_) { - s,\&maint\;,Lintian maintainer,o; # " + s,\&maint\;,Maemian maintainer,o; # " s,\&debdev\;,debian-devel,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; diff --git a/lib/Util.pm b/lib/Util.pm index 1b7cb26..47de540 100644 --- a/lib/Util.pm +++ b/lib/Util.pm @@ -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 --- 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 index 0000000..95b0342 --- /dev/null +++ b/reporting/checkout-release @@ -0,0 +1,24 @@ +#!/bin/sh +# Copyright 2008 Frank Lichtenheld , if at all copyrightable +# Copyright 2009 Russ Allbery + +# 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 " + 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 index 0000000..adf652f --- /dev/null +++ b/reporting/config @@ -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 index 0000000..06324b0 --- /dev/null +++ b/reporting/harness @@ -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 +#'# 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 () { + 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 () { + 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 index 0000000..b8d4730 --- /dev/null +++ b/reporting/html_reports @@ -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 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 ' +# +# %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 = "

Can't find description of tag $tag.

"; + } + 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 .html where 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/&/\&/g; + $text =~ s//\>/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 index 0000000..e69de29 diff --git a/reporting/lintian.css b/reporting/lintian.css new file mode 100644 index 0000000..552e7b7 --- /dev/null +++ b/reporting/lintian.css @@ -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 index 0000000..73d1cec --- /dev/null +++ b/unpack/list-binpkg @@ -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] \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 = } until ($line =~ m/^Architecture: (.*)$/m); + my $arch = $1; + do { $line = } until ($line =~ m/^Filename: (.*)$/m); + my $deb_file = $1; + do { $line = } 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 index 0000000..1227af1 --- /dev/null +++ b/unpack/list-srcpkg @@ -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] \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 = } until ($line =~ m/^Directory: (.*)$/m); + my $pkg_dir = $1; + do { $line = } until ($line =~ m/^ [0-9a-f]{32} [0-9]+ (.+\.dsc)$/m); + my $dsc_file = "$pkg_dir/$1"; + do { $line = } 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 index 0000000..d71a1e7 --- /dev/null +++ b/unpack/list-udebpkg @@ -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] \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 = } until ($line =~ m/^Architecture: (.*)$/m); + my $arch = $1; + do { $line = } until ($line =~ m/^Filename: (.*)$/m); + my $deb_file = $1; + do { $line = } 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 index 0000000..e42a042 --- /dev/null +++ b/unpack/unpack-binpkg-l1 @@ -0,0 +1,126 @@ +#!/usr/bin/perl +# unpack-binpkg-l1 -- maemian unpack script (binary packages level 1) +# +# syntax: unpack-binpkg-l1 +# +# Note that 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 "; +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 index 0000000..2f07b4a --- /dev/null +++ b/unpack/unpack-binpkg-l2 @@ -0,0 +1,47 @@ +#!/usr/bin/perl +# unpack-binpkg-l2 -- maemian unpack script (binary packages level 2) +# +# syntax: unpack-binpkg-l + +# 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 "); +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 index 0000000..b10776a --- /dev/null +++ b/unpack/unpack-srcpkg-l1 @@ -0,0 +1,178 @@ +#!/usr/bin/perl +# unpack-srcpkg-l1 -- maemian unpack script (source packages level 1) +# +# syntax: unpack-srcpkg-l1 +# +# Note, that 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 "; +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 index 0000000..65ec574 --- /dev/null +++ b/unpack/unpack-srcpkg-l2 @@ -0,0 +1,50 @@ +#!/usr/bin/perl +# unpack-srcpkg-l2 -- maemian unpack script (source packages level 2) +# +# syntax: unpack-srcpkg-l2 + +# 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 "); +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 -- 1.7.9.5