Commit 459ff178 authored by Chris Lamb's avatar Chris Lamb 💬

Add support for passing .buildinfo files to Lintian. (Closes: #853274)

parent f2145e0e
......@@ -4,8 +4,8 @@ Lintian - Static Debian package analysis tool
Lintian is a static analysis tool for finding many bugs, policy
violations and other issues in Debian based packages. It can process
binary Debian packages (.deb), micro/installer packages (.udeb),
Debian source packages (.dsc) and (to a limited degree) the "changes"
files.
Debian source packages (.dsc) and (to a limited degree) the "buildinfo"
and "changes" files.
Running Lintian
......
......@@ -1508,17 +1508,15 @@ sub setup_work_pool {
for my $arg (@ARGV) {
# file?
if (-f $arg) {
if ($arg =~ m/\.(?:u?deb|dsc|changes)$/o){
if ($arg =~ m/\.(?:u?deb|dsc|changes|buildinfo)$/o){
eval {$pool->add_file($arg);};
if ($@) {
print STDERR "Skipping $arg: $@";
$exit_code = 2;
}
} else {
fatal_error(
join(q{ },
"bad package file name $arg",
'(neither .deb, .udeb, .changes or .dsc file)'));
fatal_error("bad package file name $arg (neither .deb, "
. '.udeb, .changes .dsc or .buildinfo file)');
}
} else {
# parameter is a package name--so look it up
......
......@@ -43,6 +43,10 @@ lintian (2.5.73) UNRELEASED; urgency=medium
+ [CL] Detect "backports" (and "backport") as overly generic Python
module names. (Closes: #888559)
* lib/Lintian/*:
+ [CL] Add support for passing .buildinfo files to Lintian.
(Closes: #853274)
-- Chris Lamb <lamby@debian.org> Fri, 26 Jan 2018 16:33:51 +1100
lintian (2.5.72) unstable; urgency=medium
......
......@@ -87,6 +87,9 @@ sub new {
} elsif ($type eq 'binary' or $type eq 'udeb') {
require Lintian::Collect::Binary;
$object = Lintian::Collect::Binary->new($pkg);
} elsif ($type eq 'buildinfo') {
require Lintian::Collect::Buildinfo;
$object = Lintian::Collect::Buildinfo->new($pkg);
} elsif ($type eq 'changes') {
require Lintian::Collect::Changes;
$object = Lintian::Collect::Changes->new($pkg);
......
# -*- perl -*-
# Lintian::Collect::Buildinfo -- interface to .buildinfo file data collection
# Copyright (C) 2018 Chris Lamb
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
package Lintian::Collect::Buildinfo;
use strict;
use warnings;
use parent 'Lintian::Collect';
use Lintian::Util qw(strip);
=head1 NAME
Lintian::Collect::Buildinfo - Lintian interface to .buildinfo file data collection
=head1 SYNOPSIS
my ($name, $type) = ('foobar_1.2_i386.buildinfo', 'changes');
my $collect = Lintian::Collect->new($name, $type);
my $files = $collect->files;
foreach my $file (keys %{$files}) {
my $size = $files->{$file}{size};
print "File $file has size $size\n";
}
=head1 DESCRIPTION
Lintian::Collect::Buildinfo provides an interface to data for .buildinfo
files. It implements data collection methods specific to .buildinfo
files.
=head1 CLASS METHODS
=over 4
=item new (PACKAGE)
Creates a new Lintian::Collect::Buildinfo object. Currently, PACKAGE is
ignored. Normally, this method should not be called directly, only via
the L<Lintian::Collect> constructor.
=cut
# Initialize a new .buildinfo file collect object. Takes the package name,
# which is currently unused.
sub new {
my ($class, $pkg) = @_;
my $self = {};
bless($self, $class);
return $self;
}
=back
=head1 INSTANCE METHODS
In addition to the instance methods listed below, all instance methods
documented in the L<Lintian::Collect> module are also available.
=over 4
=item files
Returns a reference to a hash containing information about files listed
in the .buildinfo file. Each hash may have the following keys:
=over 4
=item name
Name of the file.
=item size
The size of the file in bytes.
=item section
The archive section to which the file belongs.
=item priority
The priority of the file.
=item checksums
A hash with the keys being checksum algorithms and the values themselves being
hashes containing
=over 4
=item sum
The result of applying the given algorithm to the file.
=item filesize
The size of the file as given in the .buildinfo section relating to the given
checksum.
=back
=back
Needs-Info requirements for using I<files>: L<Lintian::Collect/field ([FIELD[, DEFAULT]])>
=cut
sub files {
my ($self) = @_;
return $self->{files} if exists $self->{files};
my %files;
my $file_list = $self->field('files') || '';
local $_;
for (split /\n/, $file_list) {
strip;
next if $_ eq '';
my ($md5sum,$size,$section,$priority,$file) = split(/\s+/o, $_);
next if $file =~ m,/,;
$files{$file}{checksums}{md5} = {
'sum' => $md5sum,
'filesize' => $size,
};
$files{$file}{name} = $file;
$files{$file}{size} = $size;
$files{$file}{section} = $section;
$files{$file}{priority} = $priority;
}
foreach my $alg (qw(sha1 sha256)) {
my $list = $self->field("checksums-$alg") || '';
for (split /\n/, $list) {
strip;
next if $_ eq '';
my ($checksum, $size, $file) = split(/\s+/o, $_);
next if $file =~ m,/,;
$files{$file}{checksums}{$alg} = {
'sum' => $checksum,
'filesize' => $size
};
}
}
$self->{files} = \%files;
return $self->{files};
}
=back
=head1 AUTHOR
Originally written by Adam D. Barratt <adsb@debian.org> for Lintian.
=head1 SEE ALSO
lintian(1), L<Lintian::Collect>
=cut
1;
# Local Variables:
# indent-tabs-mode: nil
# cperl-indent-level: 4
# End:
# vim: syntax=perl sw=4 sts=4 sr et
......@@ -44,6 +44,7 @@ use constant {
# A private table of supported types.
my %SUPPORTED_TYPES = (
'binary' => 1,
'buildinfo' => 1,
'changes' => 1,
'source' => 1,
'udeb' => 1,
......
......@@ -77,6 +77,8 @@ use constant BINLIST_FORMAT =>
q{Lintian's list of binary packages in the archive--V5};
use constant SRCLIST_FORMAT =>
q{Lintian's list of source packages in the archive--V5};
use constant BLDLIST_FORMAT =>
q{Lintian's list of buildinfo packages in the archive--V1};
use constant CHGLIST_FORMAT =>
q{Lintian's list of changes packages in the archive--V1};
......@@ -95,6 +97,9 @@ my @BIN_FILE_FIELDS = (
'architecture','file','timestamp','area',
);
# buildinfo packages lists
my @BLD_FILE_FIELDS = ('source','version','architecture','file','timestamp',);
# changes packages lists
my @CHG_FILE_FIELDS = ('source','version','architecture','file','timestamp',);
......@@ -107,6 +112,8 @@ my @GROUP_QUERY = ('source','version','identifier',);
my @BIN_QUERY = ('package','version','architecture',);
my @BLD_QUERY = ('source','version','architecture',);
my @CHG_QUERY = ('source','version','architecture',);
my %TYPE2INFO = (
......@@ -120,6 +127,11 @@ my %TYPE2INFO = (
'file-header' => BINLIST_FORMAT,
'query-fields' => \@BIN_QUERY
},
'buildinfo' => {
'file-fields' => \@BLD_FILE_FIELDS,
'file-header' => BLDLIST_FORMAT,
'query-fields' => \@BLD_QUERY
},
'changes' => {
'file-fields' => \@CHG_FILE_FIELDS,
'file-header' => CHGLIST_FORMAT,
......
......@@ -16,7 +16,7 @@
# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
# MA 02110-1301, USA.
## Represents something Lintian can process (e.g. a deb, dsc or a changes)
## Represents something Lintian can process (e.g. a deb, dsc, buildinfo or a changes)
package Lintian::Processable::Package;
use parent qw(Lintian::Processable Class::Accessor::Fast);
......@@ -144,13 +144,13 @@ sub new {
$self->{pkg_src} = $pkg_name; # it is own source pkg
$self->{pkg_src_version} = $pkg_version;
$self->{'extra-fields'} = $dinfo;
} elsif ($pkg_type eq 'changes'){
} elsif ($pkg_type eq 'buildinfo' or $pkg_type eq 'changes'){
my $cinfo = get_dsc_info($pkg_path)
or croak "$pkg_path is not a valid changes file";
or croak "$pkg_path is not a valid $pkg_type file";
my $pkg_version = $cinfo->{version};
my $pkg_name = $cinfo->{source}//'';
unless ($pkg_name) {
$pkg_name = _derive_name($pkg_path, 'changes')
$pkg_name = _derive_name($pkg_path, $pkg_type)
or croak "Cannot determine the name of $pkg_path";
}
$self->{pkg_name} = $pkg_name;
......
......@@ -45,16 +45,17 @@ Lintian::ProcessableGroup -- A group of objects that Lintian can process
Instances of this perl class are sets of
L<processables|Lintian::Processable>. It allows at most one source
and one changes package per set, but multiple binary packages
and one changes or buildinfo package per set, but multiple binary packages
(provided that the binary is not already in the set).
=head1 METHODS
=over 4
=item Lintian::ProcessableGroup->new ([LAB[, CHANGES]])
=item Lintian::ProcessableGroup->new ([LAB[, FILE]])
Creates a group and optionally add all processables from CHANGES.
Creates a group and optionally add all processables from .changes
or .buildinfo file FILE.
If the LAB parameter is given, all processables added to this group
will be stored as a L<lab entry|Lintian::Lab::Entry> from LAB.
......@@ -62,11 +63,12 @@ will be stored as a L<lab entry|Lintian::Lab::Entry> from LAB.
=cut
sub new {
my ($class, $lab, $changes) = @_;
my ($class, $lab, $file) = @_;
my $self = {'lab' => $lab,};
bless $self, $class;
$self->_init_group_from_changes($changes)
if defined $changes;
if (defined $file and $file =~ m/\.(buildinfo|changes)$/) {
$self->_init_group_from_file($1, $file);
}
return $self;
}
......@@ -81,42 +83,43 @@ sub _lab_proc {
}
# Internal initialization sub
# populates $self from a changes file.
sub _init_group_from_changes {
my ($self, $changes) = @_;
my ($cinfo, $cdir);
internal_error("$changes does not exist") unless -e $changes;
$cinfo = get_dsc_info($changes)
or internal_error("$changes is not a valid changes file");
$self->add_new_processable($changes, 'changes');
$cdir = $changes;
if ($changes =~ m,^/+[^/]++$,o){
# populates $self from a buildinfo or changes file.
sub _init_group_from_file {
my ($self, $type, $filename) = @_;
my ($info, $dir);
internal_error("$filename does not exist") unless -e $filename;
$info = get_dsc_info($filename)
or internal_error("$filename is not a valid $type file");
$self->add_new_processable($filename, $type);
$dir = $filename;
if ($filename =~ m,^/+[^/]++$,o){
# it is "/files.changes?"
# - In case you were wondering, we were told not to ask :)
# See #624149
$cdir = '/';
$dir = '/';
} else {
# it is "<something>/files.changes"
$cdir =~ s,(.+)/[^/]+$,$1,;
$dir =~ s,(.+)/[^/]+$,$1,;
}
for my $line (split(/\n/o, $cinfo->{'files'}//'')) {
my $key = $type eq 'buildinfo' ? 'checksums-sha256' : 'files';
for my $line (split(/\n/o, $info->{$key}//'')) {
my ($file);
next unless defined $line;
strip($line);
next if $line eq '';
# Ignore files that may lead to path traversal issues.
# We do not need (in order) md5sum, size, section or priority
# We do not need (eg.) md5sum, size, section or priority
# - just the file name please.
(undef, undef, undef, undef, $file) = split(/\s+/o, $line);
$file = (split(/\s+/o, $line))[-1];
# If the field is malformed, $file may be undefined; we also
# skip it, if it contains a "/" since that is most likely a
# traversal attempt
next if !$file || $file =~ m,/,o;
if (not -f "$cdir/$file") {
print STDERR "$cdir/$file does not exist, exiting\n";
if (not -f "$dir/$file") {
print STDERR "$dir/$file does not exist, exiting\n";
exit 2;
}
......@@ -125,7 +128,7 @@ sub _init_group_from_changes {
next;
}
$self->add_new_processable("$cdir/$file");
$self->add_new_processable("$dir/$file");
}
return 1;
......@@ -165,10 +168,10 @@ sub add_processable{
my ($self, $processable) = @_;
my $pkg_type = $processable->pkg_type;
if ($pkg_type eq 'changes'){
internal_error('Cannot add another changes file')
if (exists $self->{changes});
$self->{changes} = $self->_lab_proc($processable);
if ($pkg_type eq 'changes' or $pkg_type eq 'buildinfo'){
internal_error("Cannot add another $pkg_type file")
if (exists $self->{$pkg_type});
$self->{$pkg_type} = $self->_lab_proc($processable);
} elsif ($pkg_type eq 'source'){
internal_error('Cannot add another source package')
if (exists $self->{source});
......@@ -264,6 +267,20 @@ sub get_source_processable {
return $self->{source};
}
=item $group->get_buildinfo_processable
Returns the processable identified as the "buildinfo" processable (e.g.
the buildinfo file).
If $group does not have a buildinfo processable, this method returns C<undef>.
=cut
sub get_buildinfo_processable {
my ($self) = @_;
return $self->{buildinfo};
}
=item $group->get_changes_processable
Returns the processable identified as the "changes" processable (e.g.
......
......@@ -42,6 +42,7 @@ Lintian::ProcessablePool -- Pool of processables
$pool->add_file('foo.changes');
$pool->add_file('bar.dsc');
$pool->add_file('baz.deb');
$pool->add_file('qux.buildinfo');
foreach my $gname ($pool->get_group_names){
my $group = $pool->get_group($gname);
process($gname, $group);
......@@ -81,11 +82,12 @@ processables from the same source package (if any).
sub add_file {
my ($self, $file) = @_;
if ($file =~ m/\.changes$/o){
if ($file =~ m/\.(buildinfo|changes)$/o){
my $type = $1;
croak "$file does not exist" unless -f $file;
my $pkg_path = Cwd::abs_path($file);
croak "Cannot resolve $file: $!" unless $pkg_path;
return $self->_add_changes_file($pkg_path);
return $self->_add_file($type, $pkg_path);
}
my $proc = Lintian::Processable::Package->new($file);
......@@ -186,11 +188,16 @@ sub empty{
#### Internal subs ####
sub _add_changes_file{
my ($self, $pkg_path) = @_;
sub _add_file{
my ($self, $type, $pkg_path) = @_;
my $group = Lintian::ProcessableGroup->new($self->{'lab'}, $pkg_path);
my $cproc = $group->get_changes_processable;
my $gid = $self->_get_group_id($cproc);
my $proc;
if ($type eq 'buildinfo') {
$proc = $group->get_buildinfo_processable;
} elsif ($type eq 'changes') {
$proc = $group->get_changes_processable;
}
my $gid = $self->_get_group_id($proc);
my $ogroup = $self->{groups}{$gid};
if (defined($ogroup)){
# Group already exists...
......@@ -198,8 +205,13 @@ sub _add_changes_file{
# Merge architectures/packages ...
# Accept all new
if (!defined($ogroup->get_changes_processable)) {
$ogroup->add_processable($cproc);
if ($type eq 'buildinfo'
&& !defined($ogroup->get_buildinfo_processable)) {
$ogroup->add_processable($proc);
$added = 1;
}elsif ($type eq 'changes'
&& !defined($ogroup->get_changes_processable)) {
$ogroup->add_processable($proc);
$added = 1;
}
......
......@@ -60,7 +60,7 @@ sub do_tests {
opendir(my $dirfd, "$DATADIR/changes");
foreach my $pkgbase (readdir $dirfd) {
next unless $pkgbase =~ m/\.(?:changes|u?deb|dsc)$/;
next unless $pkgbase =~ m/\.(?:buildinfo|changes|u?deb|dsc)$/;
my $path = "$DATADIR/changes/$pkgbase";
my $proc = Lintian::Processable::Package->new($path);
my $entry = $LAB_A->get_package($proc);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment