#!/usr/bin/perl # $OpenBSD: pkg_check-problems,v 1.14 2023/05/14 15:06:11 espie Exp $ # Copyright (c) 2004, 2010 Marc Espie # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # check all packages in the current directory, and report common directory # issues use v5.36; my ($ports1); use FindBin; BEGIN { $ports1 = $ENV{PORTSDIR} || '/usr/ports'; } use lib ("$ports1/infrastructure/lib", "$FindBin::Bin/../lib"); use File::Spec; use File::Path; use File::Basename; use OpenBSD::PkgCfl; use OpenBSD::Mtree; use OpenBSD::PlistScanner; # code for checking directories sub register_dir($d, $h) { return if defined $h->{$d}; $h->{$d} = 1; register_dir(dirname($d), $h); } package OpenBSD::PackingElement; # $self->register($o, $db, $pkgname) sub register($, $, $, $) { } # $self->known_page($h) sub known_page($, $) { } # $self_>add_extra_manpage($known, $plist) sub add_extra_manpage($, $, $) { } package OpenBSD::PackingElement::FileBase; use File::Basename; sub register($self, $o, $db, $pkgname) { # common dirs if ($o->{do_dirs}) { my $d = File::Spec->canonpath($self->fullname); main::register_dir(dirname($d), $db->{need_dirs}); } # conflicts if (defined $o->{filehash}) { my $all_conflict = $o->{filehash}; my $pkg_list = $o->{pkg_list}; my $seen = $o->{seen}; my $file = File::Spec->canonpath($self->fullname); # build one single list for each pkgnames combination if (exists $all_conflict->{$file}) { $pkg_list->{$all_conflict->{$file}}{$pkgname} ||= [@{$all_conflict->{$file}}, $pkgname ]; $all_conflict->{$file} = $pkg_list->{$all_conflict->{$file}}{$pkgname}; } elsif (exists $seen->{$file}) { $pkg_list->{$seen->{$file}}{$pkgname} ||= [ @{$seen->{$file}}, $pkgname ]; $all_conflict->{$file} = $pkg_list->{$seen->{$file}}{$pkgname}; delete $seen->{$file}; } else { $pkg_list->{$pkgname} ||= [$pkgname]; $seen->{$file} = $pkg_list->{$pkgname}; } } } package OpenBSD::PackingElement::DirlikeObject; sub register($self, $o, $db, $) { if ($o->{do_dirs}) { my $d = File::Spec->canonpath($self->fullname); $db->{dirs}{$d} = 1; } } package OpenBSD::PackingElement::Dependency; sub register($self, $o, $db, $pkgname) { $db->{deps}{$self->{def}} = 1; $o->{wanted}{$self->{def}} //= $o->{currentname}; $o->{pkgpath}{$self->{def}} = $self->{pkgpath}; push @{$o->{all_deps}{$pkgname}}, $self->{def}; } package OpenBSD::PackingElement::ExtraInfo; sub register($self, $o, $, $pkgname) { if ($self->{ftp} eq 'yes') { $o->{ftp_okay}{$pkgname} = 1; } else { $o->{ftp_okay}{$pkgname} = 0; } } package OpenBSD::PackingElement::Manpage; sub is_dest($self) { return $self->name =~ m/man\/cat[^\/]+\/[^\/]+\.0$/o; } sub dest_to_source($self) { my $v = $self->name; $v =~ s/(man\/)cat([^\/]+)(\/[^\/]+)\.0$/$1man$2$3.$2/; return $v; } sub known_page($self, $h) { $h->{File::Spec->canonpath($self->fullname)} = 1; } sub add_extra_manpage($self, $known, $plist) { if ($self->is_source) { my $dest = $self->source_to_dest; my $fullname = $self->cwd."/".$dest; my $file = File::Spec->canonpath($fullname); if (!$known->{$file}) { OpenBSD::PackingElement::Manpage->add($plist, $dest); $known->{$file} = 1; } } if ($self->is_dest) { my $src = $self->dest_to_source; my $fullname = $self->cwd."/".$src; my $file = File::Spec->canonpath($fullname); if (!$known->{$file}) { OpenBSD::PackingElement::Manpage->add($plist, $src); $known->{$file} = 1; } } } package CheckProblemsScanner; our @ISA = (qw(OpenBSD::PlistScanner)); use OpenBSD::PackageInfo; sub add_more_man($self, $plist) { my $knownman = {}; $plist->known_page($knownman); $plist->add_extra_manpage($knownman, $plist); } sub register_plist($self, $plist) { my $pkgname = $plist->pkgname; # don't register twice, which happens for ports tree scans return if $self->{got}{$pkgname}; $self->{got}{$pkgname} = 1; $self->{db}{$pkgname} = { pkgname => $pkgname, missing_deps => {}, dirs => {}, need_dirs => {}, deps => {}, problems => {} }; if ($self->{do_conflicts}) { $self->{conflicts}{$pkgname} = OpenBSD::PkgCfl->make_conflict_list($plist); } if ($self->{do_manpages}) { $self->add_more_man($plist); } $plist->register($self, $self->{db}{$pkgname}, $pkgname); } sub handle_options($self) { $self->{signature_style} = 'unsigned'; $self->SUPER::handle_options('CD', "[-CDeSv] [-d plist_dir] [-o output] [-p ports_dir] [pkgname ...]"); } sub new($class) { my $o = $class->SUPER::new('pkg_check-problem'); if ($o->ui->opt('D') && $o->ui->opt('C')) { $o->ui->usage("Won't compute anything"); } $o->{do_dirs} = !$o->ui->opt('D'); $o->{do_conflicts} = !$o->ui->opt('C'); $o->{do_manpages} = $o->ui->opt('e'); $o->{db} = {}; $o->{mtree} = { '/usr/local/lib/X11' => 1, '/usr/local/include/X11' => 1, '/usr/local/lib/X11/app-defaults' => 1 }; OpenBSD::Mtree::parse($o->{mtree}, '/', '/etc/mtree/4.4BSD.dist'); OpenBSD::Mtree::parse($o->{mtree}, '/', '/etc/mtree/BSD.x11.dist'); if ($o->{do_conflicts}) { $o->{filehash} = {}; $o->{pkg_list} = {}; $o->{conflicts} = {}; $o->{all_deps} = {}; $o->{seen} = {}; } return $o; } # for common dirs sub parent_has_dir($self, $db, $t, $dir) { for my $dep (keys %{$t->{deps}}) { if (!defined $db->{$dep}) { if (!defined $self->{missing_deps}{$dep}) { $self->ui->errsay("#1 : #2 not found", $t->{pkgname}, $dep); $self->{missing_deps}{$dep} = 1; } next; } if ($db->{$dep}->{dirs}->{$dir} || $self->parent_has_dir($db, $db->{$dep}, $dir)) { $t->{dirs}{$dir} = 1; return 1; } } return 0; } sub parent_has_problem($db, $t, $dir) { for my $dep (keys %{$t->{deps}}) { next if !defined $db->{$dep}; if ($db->{$dep}->{problems}->{$dir}) { return 1; } } return 0; } sub build_common_dirs($self) { my $db = $self->{db}; my $mtree = $self->{mtree}; my @l = keys %$db; $self->progress->next; $self->progress->for_list("Checking common dirs", \@l, sub { my $pkgname = shift; my $t = $db->{$pkgname}; for my $dir (keys(%{$t->{need_dirs}})) { return if $t->{dirs}{$dir}; return if $mtree->{$dir}; return if $self->parent_has_dir($db, $t, $dir); $t->{problems}{$dir} = 1; } }); } sub show_common_dirs($self) { my $db = $self->{db}; for my $pkgname (sort {$self->fullname($a) cmp $self->fullname($b)} keys %$db) { my @l=(); my $t = $db->{$pkgname}; for my $dir (keys %{$t->{problems}}) { next if parent_has_problem($db, $t, $dir); push(@l, $dir); } if (@l != 0) { $self->say("#1: #2", $self->fullname($pkgname), join(', ', sort @l)); } } } # for conflicts my $cache3 = {}; my $cache4 = {}; sub direct_conflict($conflicts, $pkg, $pkg2) { return $cache3->{$pkg}{$pkg2} //= $conflicts->{$pkg}->conflicts_with($pkg2); } sub has_a_conflict($conflicts, $deps, $pkg, $pkg2) { return $cache4->{$pkg}{$pkg2} //= find_a_conflict($conflicts, $deps, $pkg, $pkg2); } sub find_a_conflict($conflicts, $deps, $pkg, $pkg2) { return 0 if $pkg eq $pkg2; if (defined $conflicts->{$pkg} && direct_conflict($conflicts, $pkg, $pkg2)) { return 1; } if (defined $deps->{$pkg}) { for my $dep (@{$deps->{$pkg}}) { if (has_a_conflict($conflicts, $deps, $dep, $pkg2)) { return 1; } } } if (defined $deps->{$pkg2}) { for my $dep (@{$deps->{$pkg2}}) { if (has_a_conflict($conflicts, $deps, $pkg, $dep)) { return 1; } } } return 0; } sub compute_true_conflicts($self, $l) { my $conflicts = $self->{conflicts}; my $deps = $self->{all_deps}; # create a list of unconflicting packages. my $l2 = []; for my $pkg (@$l) { my $keepit = 0; for my $pkg2 (@$l) { next if $pkg eq $pkg2; if (!(has_a_conflict($conflicts, $deps, $pkg, $pkg2) || has_a_conflict($conflicts, $deps, $pkg2, $pkg))) { $keepit = 1; last; } } if ($keepit) { push(@$l2, $pkg); } } return $l2; } sub compute_conflicts($self) { $self->progress->set_header("Compute file problems"); my $c = {}; my $c2 = {}; my $r = {}; my $cache = {}; my $h = $self->{filehash}; my $total = scalar(keys %$h); my $i =0; while (my ($key, $l) = each %$h) { $self->progress->show(++$i, $total); if (!defined $c->{$l}) { my %s = map {($_, 1)} @$l; $c->{$l} = [sort keys %s]; $c2->{$l} = join(',', @{$c->{$l}}); } my $hv = $c2->{$l}; $l = $c->{$l}; next if @$l == 1; $cache->{$hv} //= $self->compute_true_conflicts($l); my $result = $cache->{$hv}; if (@$result != 0) { my $newkey = join(',', sort map { $self->fullname($_) } @$result); if (@$result == 1) { $newkey .= "-> was ".join(',', @$l); } push(@{$r->{$newkey}}, $key); } } $self->progress->next; return $r; } sub show_conflicts($self, $result) { for my $cfl (sort keys %$result) { $self->say("#1", $cfl); for my $f (sort @{$result->{$cfl}}) { $self->say("\t#1", $f); } } } sub check_license($self, $pkg, $k) { my @todo = ($pkg); my $done = {}; while (@todo > 0) { my $dep = shift @todo; next if $done->{$dep}; $done->{$dep} = 1; if (defined $self->{all_deps}{$dep}) { push(@todo, @{$self->{all_deps}{$dep}}); } if (defined $self->{$k}{$dep}) { return $dep if $self->{$k}{$dep} == 0; } else { return $dep; } } return undef; } sub check_licenses($self) { my $r = {}; $self->progress->set_header("Compute licenses issues"); for my $k (qw(ftp_okay)) { while (my ($pkg, $v) = each %{$self->{$k}}) { next if $v == 0; next if !$self->{current}{$pkg}; my $d = $self->check_license($pkg, $k); if (defined $d) { $r->{$k}{$pkg} = $d; } } } $self->progress->next; return $r; } sub show_licenses_issues($self, $result) { for my $k (qw(ftp_okay)) { for my $pkg (sort keys %{$result->{$k}}) { $self->say("#1 is marked as #2 but depends on #3", $pkg, $k, $result->{$k}{$pkg}); } } } sub display_results($self) { if ($self->{do_dirs}) { $self->build_common_dirs; $self->say("Common dirs:"); $self->show_common_dirs; } if ($self->{do_conflicts}) { my $result = $self->compute_conflicts; $self->say("Conflicts:"); $self->show_conflicts($result); my $r2 = $self->check_licenses; $self->say("Licenses problems:"); $self->show_licenses_issues($r2); } } package main; my $o = CheckProblemsScanner->new; $o->run;