check-problems 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. #!/usr/bin/perl
  2. # $OpenBSD: check-problems,v 1.4 2016/09/14 15:02:41 espie Exp $
  3. # Copyright (c) 2004, 2010 Marc Espie <espie@openbsd.org>
  4. #
  5. # Permission to use, copy, modify, and distribute this software for any
  6. # purpose with or without fee is hereby granted, provided that the above
  7. # copyright notice and this permission notice appear in all copies.
  8. #
  9. # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  10. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  12. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  15. # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. # check all packages in the current directory, and report common directory
  17. # issues
  18. use strict;
  19. use warnings;
  20. my ($ports1);
  21. use FindBin;
  22. BEGIN {
  23. $ports1 = $ENV{PORTSDIR} || '/usr/ports';
  24. }
  25. use lib ("$ports1/infrastructure/lib", "$FindBin::Bin/../lib");
  26. use File::Spec;
  27. use File::Path;
  28. use File::Basename;
  29. use OpenBSD::PkgCfl;
  30. use OpenBSD::Mtree;
  31. use OpenBSD::PlistScanner;
  32. # code for checking directories
  33. sub register_dir
  34. {
  35. my ($d, $h) = @_;
  36. return if defined $h->{$d};
  37. $h->{$d} = 1;
  38. register_dir(dirname($d), $h);
  39. }
  40. package OpenBSD::PackingElement;
  41. sub check_common_dirs
  42. {
  43. }
  44. package OpenBSD::PackingElement::FileBase;
  45. use File::Basename;
  46. sub check_common_dirs
  47. {
  48. my ($item, $t) = @_;
  49. my $d = File::Spec->canonpath($item->fullname);
  50. main::register_dir(dirname($d), $t->{need_dirs});
  51. }
  52. package OpenBSD::PackingElement::DirlikeObject;
  53. sub check_common_dirs
  54. {
  55. my ($item, $t) = @_;
  56. my $d = File::Spec->canonpath($item->fullname);
  57. $t->{dirs}->{$d} = 1;
  58. }
  59. package OpenBSD::PackingElement::Dependency;
  60. sub check_common_dirs
  61. {
  62. my ($item, $t, $o) = @_;
  63. $t->{deps}{$item->{def}} = 1;
  64. $o->{wanted}{$item->{def}} //= $o->{currentname};
  65. }
  66. # code for checking conflicts
  67. package OpenBSD::PackingElement;
  68. sub register
  69. {
  70. }
  71. sub known_page
  72. {
  73. }
  74. sub add_extra_manpage
  75. {
  76. }
  77. package OpenBSD::PackingElement::FileBase;
  78. my $pkg_list = {};
  79. my $seen = {};
  80. sub register
  81. {
  82. my ($self, $o, $pkgname) = @_;
  83. my $all_conflict = $o->{filehash};
  84. my $file = File::Spec->canonpath($self->fullname);
  85. # build one single list for each pkgnames combination
  86. if (exists $all_conflict->{$file}) {
  87. $pkg_list->{$all_conflict->{$file}}{$pkgname} ||=
  88. [@{$all_conflict->{$file}}, $pkgname ];
  89. $all_conflict->{$file} =
  90. $pkg_list->{$all_conflict->{$file}}{$pkgname};
  91. } elsif (exists $seen->{$file}) {
  92. $pkg_list->{$seen->{$file}}{$pkgname} ||=
  93. [ @{$seen->{$file}}, $pkgname ];
  94. $all_conflict->{$file} =
  95. $pkg_list->{$seen->{$file}}{$pkgname};
  96. delete $seen->{$file};
  97. } else {
  98. $pkg_list->{$pkgname} ||= [$pkgname];
  99. $seen->{$file} = $pkg_list->{$pkgname};
  100. }
  101. }
  102. package OpenBSD::PackingElement::Dependency;
  103. sub register
  104. {
  105. my ($self, $o, $pkgname) = @_;
  106. $o->{wanted}{$self->{def}} //= $o->{currentname};
  107. push @{$o->{all_deps}{$pkgname}}, $self->{def};
  108. }
  109. package OpenBSD::PackingElement::Manpage;
  110. sub is_dest
  111. {
  112. my $self = shift;
  113. return $self->name =~ m/man\/cat[^\/]+\/[^\/]+\.0$/o;
  114. }
  115. sub dest_to_source
  116. {
  117. my $self = shift;
  118. my $v = $self->name;
  119. $v =~ s/(man\/)cat([^\/]+)(\/[^\/]+)\.0$/$1man$2$3.$2/;
  120. return $v;
  121. }
  122. sub known_page
  123. {
  124. my ($self, $h) = @_;
  125. $h->{File::Spec->canonpath($self->fullname)} = 1;
  126. }
  127. sub add_extra_manpage
  128. {
  129. my ($self, $known, $plist) = @_;
  130. if ($self->is_source) {
  131. my $dest = $self->source_to_dest;
  132. my $fullname = $self->cwd."/".$dest;
  133. my $file = File::Spec->canonpath($fullname);
  134. if (!$known->{$file}) {
  135. OpenBSD::PackingElement::Manpage->add($plist, $dest);
  136. $known->{$file} = 1;
  137. }
  138. }
  139. if ($self->is_dest) {
  140. my $src = $self->dest_to_source;
  141. my $fullname = $self->cwd."/".$src;
  142. my $file = File::Spec->canonpath($fullname);
  143. if (!$known->{$file}) {
  144. OpenBSD::PackingElement::Manpage->add($plist, $src);
  145. $known->{$file} = 1;
  146. }
  147. }
  148. }
  149. package CheckProblemsScanner;
  150. our @ISA = (qw(OpenBSD::PlistScanner));
  151. use OpenBSD::PackageInfo;
  152. sub add_more_man
  153. {
  154. my ($self, $plist) = @_;
  155. my $knownman = {};
  156. $plist->known_page($knownman);
  157. $plist->add_extra_manpage($knownman, $plist);
  158. }
  159. sub register_plist
  160. {
  161. my ($self, $plist) = @_;
  162. my $pkgname = $plist->pkgname;
  163. $self->{got}{$pkgname} = 1;
  164. if ($self->{do_dirs}) {
  165. $self->{db}{$pkgname} //= {
  166. pkgname => $pkgname,
  167. missing_deps => {},
  168. dirs => {},
  169. need_dirs => {},
  170. deps => {},
  171. problems => {}
  172. };
  173. $plist->check_common_dirs($self->{db}{$pkgname}, $self);
  174. }
  175. if ($self->{do_conflicts}) {
  176. $self->{conflicts}{$pkgname} =
  177. OpenBSD::PkgCfl->make_conflict_list($plist);
  178. if ($self->ui->opt('e')) {
  179. $self->add_more_man($plist);
  180. }
  181. $plist->register($self, $pkgname);
  182. }
  183. }
  184. sub handle_options
  185. {
  186. my $self = shift;
  187. $self->{signature_style} = 'unsigned';
  188. $self->SUPER::handle_options('CD',
  189. "[-CDeSv] [-d plist_dir] [-o output] [-p ports_dir] [pkgname ...]");
  190. }
  191. sub new
  192. {
  193. my ($class) = @_;
  194. my $o = $class->SUPER::new('check-problems');
  195. if ($o->ui->opt('D') && $o->ui->opt('C')) {
  196. $o->ui->usage("Won't compute anything");
  197. }
  198. $o->{do_dirs} = !$o->ui->opt('D');
  199. $o->{do_conflicts} = !$o->ui->opt('C');
  200. if ($o->{do_dirs}) {
  201. $o->{db} = {};
  202. $o->{mtree} = {
  203. '/usr/local/lib/X11' => 1,
  204. '/usr/local/include/X11' => 1,
  205. '/usr/local/lib/X11/app-defaults' => 1
  206. };
  207. OpenBSD::Mtree::parse($o->{mtree}, '/',
  208. '/etc/mtree/4.4BSD.dist');
  209. OpenBSD::Mtree::parse($o->{mtree}, '/',
  210. '/etc/mtree/BSD.x11.dist');
  211. }
  212. if ($o->{do_conflicts}) {
  213. $o->{filehash} = {};
  214. $o->{conflicts} = {};
  215. $o->{all_deps} = {};
  216. }
  217. return $o;
  218. }
  219. # for common dirs
  220. sub parent_has_dir
  221. {
  222. my ($self, $db, $t, $dir) = @_;
  223. for my $dep (keys %{$t->{deps}}) {
  224. if (!defined $db->{$dep}) {
  225. if (!defined $self->{missing_deps}{$dep}) {
  226. $self->ui->errsay("#1 : #2 not found", $t->{pkgname},
  227. $dep);
  228. $self->{missing_deps}{$dep} = 1;
  229. }
  230. next;
  231. }
  232. if ($db->{$dep}->{dirs}->{$dir} ||
  233. $self->parent_has_dir($db, $db->{$dep}, $dir)) {
  234. $t->{dirs}{$dir} = 1;
  235. return 1;
  236. }
  237. }
  238. return 0;
  239. }
  240. sub parent_has_problem
  241. {
  242. my ($db, $t, $dir) = @_;
  243. for my $dep (keys %{$t->{deps}}) {
  244. next if !defined $db->{$dep};
  245. if ($db->{$dep}->{problems}->{$dir}) {
  246. return 1;
  247. }
  248. }
  249. return 0;
  250. }
  251. sub build_common_dirs
  252. {
  253. my ($self) = @_;
  254. my $db = $self->{db};
  255. my $mtree = $self->{mtree};
  256. my @l = keys %$db;
  257. $self->progress->for_list("Checking common dirs", \@l,
  258. sub {
  259. my $pkgname = shift;
  260. my $t = $db->{$pkgname};
  261. for my $dir (keys(%{$t->{need_dirs}})) {
  262. return if $t->{dirs}{$dir};
  263. return if $mtree->{$dir};
  264. return if $self->parent_has_dir($db, $t, $dir);
  265. $t->{problems}{$dir} = 1;
  266. }
  267. });
  268. $self->progress->next;
  269. }
  270. sub show_common_dirs
  271. {
  272. my ($self) = @_;
  273. my $db = $self->{db};
  274. for my $pkgname (sort {$self->fullname($a) cmp $self->fullname($b)}
  275. keys %$db) {
  276. my @l=();
  277. my $t = $db->{$pkgname};
  278. for my $dir (keys %{$t->{problems}}) {
  279. next if parent_has_problem($db, $t, $dir);
  280. push(@l, $dir);
  281. }
  282. if (@l != 0) {
  283. $self->say("#1: #2", $self->fullname($pkgname),
  284. join(', ', sort @l));
  285. }
  286. }
  287. }
  288. # for conflicts
  289. my $cache3 = {};
  290. my $cache4 = {};
  291. sub direct_conflict
  292. {
  293. my ($conflicts, $pkg, $pkg2) = @_;
  294. return $cache3->{$pkg}{$pkg2} //= $conflicts->{$pkg}->conflicts_with($pkg2);
  295. }
  296. sub has_a_conflict
  297. {
  298. my ($conflicts, $deps, $pkg, $pkg2) = @_;
  299. return $cache4->{$pkg}{$pkg2} //= find_a_conflict($conflicts, $deps, $pkg, $pkg2);
  300. }
  301. sub find_a_conflict
  302. {
  303. my ($conflicts, $deps, $pkg, $pkg2) = @_;
  304. return 0 if $pkg eq $pkg2;
  305. if (defined $conflicts->{$pkg} &&
  306. direct_conflict($conflicts, $pkg, $pkg2)) {
  307. return 1;
  308. }
  309. if (defined $deps->{$pkg}) {
  310. for my $dep (@{$deps->{$pkg}}) {
  311. if (has_a_conflict($conflicts, $deps, $dep, $pkg2)) {
  312. return 1;
  313. }
  314. }
  315. }
  316. if (defined $deps->{$pkg2}) {
  317. for my $dep (@{$deps->{$pkg2}}) {
  318. if (has_a_conflict($conflicts, $deps, $pkg, $dep)) {
  319. return 1;
  320. }
  321. }
  322. }
  323. return 0;
  324. }
  325. sub compute_true_conflicts
  326. {
  327. my ($self, $l) = @_;
  328. my $conflicts = $self->{conflicts};
  329. my $deps = $self->{all_deps};
  330. # create a list of unconflicting packages.
  331. my $l2 = [];
  332. for my $pkg (@$l) {
  333. my $keepit = 0;
  334. for my $pkg2 (@$l) {
  335. next if $pkg eq $pkg2;
  336. if (!(has_a_conflict($conflicts, $deps, $pkg, $pkg2) ||
  337. has_a_conflict($conflicts, $deps, $pkg2, $pkg))) {
  338. $keepit = 1;
  339. last;
  340. }
  341. }
  342. if ($keepit) {
  343. push(@$l2, $pkg);
  344. }
  345. }
  346. return $l2;
  347. }
  348. sub compute_conflicts
  349. {
  350. my ($self) = @_;
  351. $self->progress->set_header("Compute file problems");
  352. my $c = {};
  353. my $c2 = {};
  354. my $r = {};
  355. my $cache = {};
  356. my $h = $self->{filehash};
  357. my $total = scalar(keys %$h);
  358. my $i =0;
  359. while (my ($key, $l) = each %$h) {
  360. $self->progress->show(++$i, $total);
  361. if (!defined $c->{$l}) {
  362. my %s = map {($_, 1)} @$l;
  363. $c->{$l} = [sort keys %s];
  364. $c2->{$l} = join(',', @{$c->{$l}});
  365. }
  366. my $hv = $c2->{$l};
  367. $l = $c->{$l};
  368. next if @$l == 1;
  369. $cache->{$hv} //= $self->compute_true_conflicts($l);
  370. my $result = $cache->{$hv};
  371. if (@$result != 0) {
  372. my $newkey = join(',',
  373. sort map { $self->fullname($_) } @$result);
  374. if (@$result == 1) {
  375. $newkey.="-> was ".join(',', @$l);
  376. }
  377. push(@{$r->{$newkey}}, $key);
  378. }
  379. }
  380. $self->progress->next;
  381. return $r;
  382. }
  383. sub show_conflicts
  384. {
  385. my ($self, $result) = @_;
  386. for my $cfl (sort keys %$result) {
  387. $self->say("#1", $cfl);
  388. for my $f (sort @{$result->{$cfl}}) {
  389. $self->say("\t#1", $f);
  390. }
  391. }
  392. }
  393. sub display_results
  394. {
  395. my $self = shift;
  396. if ($self->{do_dirs}) {
  397. $self->build_common_dirs;
  398. $self->say("Common dirs:");
  399. $self->show_common_dirs;
  400. }
  401. if ($self->{do_conflicts}) {
  402. my $result = $self->compute_conflicts;
  403. $self->say("Conflicts:");
  404. $self->show_conflicts($result);
  405. }
  406. }
  407. package main;
  408. my $o = CheckProblemsScanner->new;
  409. $o->run;