diff options
Diffstat (limited to 'lib/Lintian/Reporting')
-rw-r--r-- | lib/Lintian/Reporting/ResourceManager.pm | 233 | ||||
-rw-r--r-- | lib/Lintian/Reporting/Util.pm | 217 |
2 files changed, 450 insertions, 0 deletions
diff --git a/lib/Lintian/Reporting/ResourceManager.pm b/lib/Lintian/Reporting/ResourceManager.pm new file mode 100644 index 0000000..171b6b7 --- /dev/null +++ b/lib/Lintian/Reporting/ResourceManager.pm @@ -0,0 +1,233 @@ +# Copyright (C) 2014 Niels Thykier <niels@thykier.net> +# +# 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 https://www.gnu.org/copyleft/gpl.html, or write to the Free +# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, +# MA 02110-1301, USA. + +# A simple resource manager for html_reports +package Lintian::Reporting::ResourceManager; + +use v5.20; +use warnings; +use utf8; + +use Carp qw(croak); +use Const::Fast; +use File::Basename qw(basename); +use File::Copy qw(copy); +use Unicode::UTF8 qw(encode_utf8); + +use Lintian::Util qw(get_file_digest); + +const my $SPACE => q{ }; +const my $SLASH => q{/}; +const my $EQUALS => q{=}; + +const my $BASE64_UNIT => 4; +const my $WIDELY_READABLE_FOLDER => oct(755); + +=head1 NAME + +Lintian::Reporting::ResourceManager -- A simple resource manager for html_reports + +=head1 SYNOPSIS + + use Lintian::Reporting::ResourceManager; + + my $resMan = Lintian::Reporting::ResourceManager->new( + 'html_dir' => 'path/to/HTML-root', + ); + # Copy the resource + $resMan->install_resource('path/to/my-image.png', { install_method => 'copy'} ); + # Move the resource + $resMan->install_resource('path/to/generated-styles.css'); + print encode_utf8('Image: ' . $resMan->resource_url('my-image.png'), "\n"); + print encode_utf8('CSS: ' . $resMan->resource_url('generated-styles.css'), "\n"); + +=head1 DESCRIPTION + +A simple resource manager for Lintian's reporting tool, +B<html_reports>. + + +=head1 CLASS METHODS + +=over 4 + +=item new(TYPE, OPTS) + +Instantiates a new resource manager. + +OPTS is a key-value list, which must contain the key "html_dir" set to +the root of the HTML path. It is beneath this path that all resources +will be installed + +=cut + +sub new { + my ($class, %opts) = @_; + my $self = {%opts,}; + croak encode_utf8('Missing required parameter html_dir (or it is undef)') + if not defined $opts{'html_dir'}; + $self->{'_resource_cache'} = {}; + $self->{'_resource_integrity'} = {}; + return bless($self, $class); +} + +=back + +=head1 INSTANCE METHODS + +=over 4 + +=item install_resource(RESOURCE[, OPT]) + +Installs RESOURCE into the html root. The resource may be renamed +(based on content etc.). + +Note that the basename of RESOURCE must be unique between all +resources installed. See L</resource_url(RESOURCE_NAME)>. + +If OPT is given, it must be a hashref with 0 or more of the following +keys (and values). + +=over 4 + +=item install_method + +Can be "copy" or "move" (default). If set to "move", the original file +will be renamed into its new location. Otherwise, a copy is done and +the original file is left in place. + +=item source_file + +By default, the path denoted by RESOURCE is both the resource name and +the source file. This option can be used to install a given file as +RESOURCE regardless of the basename of the source file. + +If this is passed, RESOURCE must be a basename (i.e. without any +slashes). + +=back + +=cut + +sub install_resource { + my ($self, $resource_name, $opt) = @_; + my $resource_root = $self->{'html_dir'} . '/resources'; + my $method = 'move'; + my ($basename, $install_name, $resource, $digest, $b64digest); + $method = $opt->{'install_method'} + if $opt && exists($opt->{'install_method'}); + if ($opt && exists($opt->{'source_file'})) { + $basename = $resource_name; + $resource = $opt->{'source_file'}; + + if ($basename =~ m{ / }msx) { + + croak encode_utf8( + join($SPACE, + qq(Resource "${resource_name}" must not contain "/"), + 'when source_file is given') + ); + } + } else { + $basename = basename($resource_name); + $resource = $resource_name; + } + $digest = get_file_digest('sha256', $resource); + $install_name = $digest->clone->hexdigest; + $b64digest = $digest->b64digest; + + while (length($b64digest) % $BASE64_UNIT) { + $b64digest .= $EQUALS; + } + + croak encode_utf8("Resource name ${basename} already in use") + if defined($self->{'_resource_cache'}{$basename}); + if ($basename =~ m/^.+(\.[^\.]+)$/xsm) { + my $ext = $1; + $install_name .= $ext; + } + + if (!-d $resource_root) { + mkdir($resource_root, $WIDELY_READABLE_FOLDER) + or die encode_utf8("Cannot mkdir $resource_root"); + } + + my $target_file = "$resource_root/$install_name"; + if ($method eq 'move') { + rename($resource, $target_file) + or die encode_utf8("Cannot rename $resource to $target_file"); + + } elsif ($method eq 'copy') { + copy($resource, $target_file) + or croak encode_utf8("Cannot copy $resource to $target_file: $!"); + } else { + croak encode_utf8( + join($SPACE, + "Unknown install method ${method}", + '- please use "move" or "copy"') + ); + } + $self->{'_resource_cache'}{$basename} = $target_file; + $self->{'_resource_integrity'}{$basename} = "sha256-${b64digest}"; + return; +} + +=item resource_url(RESOURCE_NAME) + +Returns the path (relative to the HTML root) to a resource installed +via L</install_resource(RESOURCE)>, where RESOURCE_NAME is the +basename of the path given to install_resource. + +=cut + +sub resource_url { + my ($self, $resource_name) = @_; + croak encode_utf8("Unknown resource $resource_name") + if not defined($self->{'_resource_cache'}{$resource_name}); + return $self->{'_resource_cache'}{$resource_name}; +} + +=item resource_integrity_value(RESOURCE_NAME) + +Return a string that is valid in the "integrity" field of a C<< <link> +>> HTML tag. (See https://www.w3.org/TR/SRI/) + +=cut + +sub resource_integrity_value { + my ($self, $resource_name) = @_; + croak encode_utf8("Unknown resource $resource_name") + if not defined($self->{'_resource_integrity'}{$resource_name}); + return $self->{'_resource_integrity'}{$resource_name}; +} + +=back + +=head1 AUTHOR + +Originally written by Niels Thykier <niels@thykier.net> for Lintian. + +=cut + +1; + +# Local Variables: +# indent-tabs-mode: nil +# cperl-indent-level: 4 +# End: +# vim: syntax=perl sw=4 sts=4 sr et diff --git a/lib/Lintian/Reporting/Util.pm b/lib/Lintian/Reporting/Util.pm new file mode 100644 index 0000000..ecf34cf --- /dev/null +++ b/lib/Lintian/Reporting/Util.pm @@ -0,0 +1,217 @@ +# Hey emacs! This is a -*- Perl -*- script! +# Lintian::Reporting::Util -- Perl utility functions for lintian's reporting framework + +# 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 https://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 Lintian::Reporting::Util; + +=head1 NAME + +Lintian::Reporting::Util - Lintian utility functions + +=head1 SYNOPSIS + + use Lintian::Reporting::Util qw(load_state_cache find_backlog); + + my $cache = load_state_cache('path/to/state-dir'); + my @backlog = find_backlog('2.12', $cache); + +=head1 DESCRIPTION + +This module contains a number of utility subs that are nice to have +for the reporting framework, but on their own did not warrant their +own module. + +Most subs are imported only on request. + +=head1 FUNCTIONS + +=over 4 + +=cut + +use v5.20; +use warnings; +use utf8; + +use Const::Fast; +use Exporter qw(import); +use File::Temp qw(tempfile); +use List::Util qw(shuffle); +use Path::Tiny; +use Syntax::Keyword::Try; +use Unicode::UTF8 qw(encode_utf8); +use YAML::XS (); + +use Lintian::Relation::Version qw(versions_equal versions_comparator); + +our @EXPORT_OK = ( + qw( + load_state_cache + save_state_cache + find_backlog + ) +); + +const my $WIDELY_READABLE => oct(644); + +=item load_state_cache(STATE_DIR) + +[Reporting tools only] Load the state cache from STATE_DIR. + +=cut + +sub load_state_cache { + my ($state_dir) = @_; + my $state_file = "$state_dir/state-cache"; + my $state = {}; + + return $state + unless -e $state_file; + + my $yaml = path($state_file)->slurp; + + try { + $state = YAML::XS::Load($yaml); + + } catch { + # Not sure what Load does in case of issues; perldoc YAML says + # very little about it. Based on YAML::Error, I guess it will + # write stuff to STDERR and use die/croak, but it remains a + # guess. + die encode_utf8( + "$state_file was invalid; please fix or remove it.\n$@"); + } + + $state //= {}; + + if (ref($state) ne 'HASH') { + die encode_utf8("$state_file was invalid; please fix or remove it."); + } + return $state; +} + +=item save_state_cache(STATE_DIR, STATE) + +[Reporting tools only] Save the STATE cache to STATE_DIR. + +=cut + +sub save_state_cache { + my ($state_dir, $state) = @_; + my $state_file = "$state_dir/state-cache"; + my ($tmp_fd, $tmp_path); + + ($tmp_fd, $tmp_path) = tempfile('state-cache-XXXXXX', DIR => $state_dir); + ## TODO: Should tmp_fd be binmode'd as we use YAML::XS? + + # atomic replacement of the state file; not a substitute for + # proper locking, but it will at least ensure that the file + # is in a consistent state. + try { + print {$tmp_fd} encode_utf8(YAML::XS::Dump($state)); + + close($tmp_fd) or die encode_utf8("close $tmp_path: $!"); + + # There is no secret in this. Set it to 0644, so it does not + # require sudo access on lintian.d.o to read the file. + chmod($WIDELY_READABLE, $tmp_path); + + rename($tmp_path, $state_file) + or die encode_utf8("rename $tmp_path -> $state_file: $!"); + + } catch { + my $err = $@; + if (-e $tmp_path) { + # Ignore error as we have a more important one + unlink($tmp_path) + or warn encode_utf8("Cannot unlink $tmp_path"); + } + die encode_utf8($err); + + # perlcritic 1.140-1 requires the semicolon on the next line + }; + + return 1; +} + +=item find_backlog(LINTIAN_VERSION, STATE) + +[Reporting tools only] Given the current lintian version and the +harness state, return a list of group ids that are part of the +backlog. The list is sorted based on what version of Lintian +processed the package. + +Note the result is by design not deterministic to reduce the +risk of all large packages being in the same run (e.g. like +gcc-5 + gcc-5-cross + gcc-6 + gcc-6-cross). + +=cut + +sub find_backlog { + my ($lintian_version, $state) = @_; + my (@backlog, %by_version, @low_priority); + for my $group_id (keys(%{$state->{'groups'}})) { + my $last_version = '0'; + my $group_data = $state->{'groups'}{$group_id}; + my $is_out_of_date; + # Does this group repeatedly fail with the current version + # of lintian? + if ( exists($group_data->{'processing-errors'}) + and $group_data->{'processing-errors'} > 2 + and exists($group_data->{'last-error-by'}) + and $group_data->{'last-error-by'} ne $lintian_version) { + # To avoid possible "starvation", we will give lower priority + # to packages that repeatedly fail. They will be retried as + # the backlog is cleared. + push(@low_priority, $group_id); + next; + } + if (exists($group_data->{'out-of-date'})) { + $is_out_of_date = $group_data->{'out-of-date'}; + } + if (exists($group_data->{'last-processed-by'})) { + $last_version = $group_data->{'last-processed-by'}; + } + $is_out_of_date = 1 + if not versions_equal($last_version, $lintian_version); + push(@{$by_version{$last_version}}, $group_id) if $is_out_of_date; + } + for my $v (sort(versions_comparator keys(%by_version))) { + push(@backlog, shuffle(@{$by_version{$v}})); + } + push(@backlog, shuffle(@low_priority)) if @low_priority; + return @backlog; +} + +=back + +=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 sr et |