check-lib-depends 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. #!/usr/bin/perl
  2. # $OpenBSD: check-lib-depends,v 1.40 2017/04/11 16:02:15 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. use strict;
  17. use warnings;
  18. my $ports1;
  19. use FindBin;
  20. BEGIN {
  21. $ports1 = $ENV{PORTSDIR} || '/usr/ports';
  22. }
  23. use lib ("$ports1/infrastructure/lib", "$FindBin::Bin/../lib");
  24. use File::Spec;
  25. use OpenBSD::PackingList;
  26. use OpenBSD::SharedLibs;
  27. use OpenBSD::LibSpec;
  28. use OpenBSD::Temp;
  29. use OpenBSD::AddCreateDelete;
  30. use OpenBSD::Getopt;
  31. use OpenBSD::FileSource;
  32. use OpenBSD::BinaryScan;
  33. use OpenBSD::Recorder;
  34. use OpenBSD::Issue;
  35. package Logger;
  36. sub new
  37. {
  38. my ($class, $dir) = @_;
  39. require File::Path;
  40. File::Path::make_path($dir);
  41. bless {dir => $dir}, $class;
  42. }
  43. sub log
  44. {
  45. my ($self, $name) = @_;
  46. $name =~ s/^\/*//;
  47. $name =~ s/\//./g;
  48. return "$self->{dir}/$name";
  49. }
  50. sub open
  51. {
  52. my ($self, $name) = @_;
  53. open my $fh, '>>', $self->log($name);
  54. return $fh;
  55. }
  56. package MyFile;
  57. our @ISA = qw(OpenBSD::PackingElement::FileBase);
  58. sub fullname
  59. {
  60. my $self = shift;
  61. return $self->{name};
  62. }
  63. package OpenBSD::PackingElement;
  64. sub scan_binaries_for_libs
  65. {
  66. }
  67. sub find_libs
  68. {
  69. }
  70. sub register_libs
  71. {
  72. }
  73. sub depwalk
  74. {
  75. }
  76. sub find_binaries
  77. {
  78. }
  79. sub find_perl
  80. {
  81. }
  82. package OpenBSD::PackingElement::Wantlib;
  83. sub register_libs
  84. {
  85. my ($item, $t) = @_;
  86. my $name = $item->{name};
  87. $name =~ s/^(.*\/)?(.*)\.(\d+)\.\d+$/$2.$3/;
  88. $t->{$name} = 1;
  89. }
  90. package OpenBSD::PackingElement::Lib;
  91. sub register_libs
  92. {
  93. my ($item, $t) = @_;
  94. if ($item->fullname =~ m/^(.*\/)?lib(.*)\.so\.(\d+)\.\d+$/) {
  95. $t->{"$2.$3"} = 2;
  96. }
  97. }
  98. package OpenBSD::PackingElement::FileBase;
  99. sub find_libs
  100. {
  101. my ($item, $dest, $dump) = @_;
  102. my $fullname = $item->fullname;
  103. for my $lib ($dump->libraries($fullname)) {
  104. $dest->record($lib, $fullname);
  105. }
  106. }
  107. sub scan_binaries_for_libs
  108. {
  109. my ($item, $state) = @_;
  110. if (my $fullname = $item->is_binary) {
  111. $state->{scanner}->retrieve_and_scan_binary($item, $fullname);
  112. if ($item->is_perl_so) {
  113. $state->{scanner}->record_libs($fullname,
  114. $state->perllibs);
  115. }
  116. } else {
  117. $state->{scanner}->dont_scan($item);
  118. }
  119. }
  120. sub is_binary
  121. {
  122. my $item = shift;
  123. my $fullname = File::Spec->canonpath($item->fullname);
  124. my $linux_bin = 0;
  125. if ($fullname =~ m,^/usr/local/emul/(?:redhat|fedora)/,) {
  126. $linux_bin = 1;
  127. }
  128. if ($linux_bin || $item->{symlink} || $item->{link}) {
  129. return 0;
  130. } else {
  131. return $fullname;
  132. }
  133. }
  134. sub is_perl_so
  135. {
  136. my $item = shift;
  137. my $fullname = File::Spec->canonpath($item->fullname);
  138. if ($fullname =~ m,/libdata/perl5/.*\.so$,) {
  139. return $fullname;
  140. } else {
  141. return 0;
  142. }
  143. }
  144. sub find_binaries
  145. {
  146. my ($item, $h) = @_;
  147. if ($item->is_binary) {
  148. $h->{$item->name} = $item;
  149. }
  150. }
  151. sub find_perl
  152. {
  153. my ($item, $state) = @_;
  154. if (my $fullname = $item->is_perl_so) {
  155. $state->{scanner}->record_libs($fullname, $state->perllibs);
  156. }
  157. }
  158. package OpenBSD::PackingElement::Dependency;
  159. sub depwalk
  160. {
  161. my ($self, $h) = @_;
  162. $h->{$self->{def}} = $self->{pkgpath};
  163. }
  164. package CheckLibDepends::State;
  165. our @ISA = qw(OpenBSD::AddCreateDelete::State);
  166. sub handle_options
  167. {
  168. my $state = shift;
  169. $state->{opt}{i} = 0;
  170. $state->{opt}{S} = sub {
  171. $state->{subst}->parse_option(shift);
  172. };
  173. $state->SUPER::handle_options('oid:D:fB:qS:s:O:',
  174. '[-fiomqx] [-B destdir] [-d pkgrepo] [-O dest] [-S var=value] [-s source]');
  175. $state->{destdir} = $state->opt('B');
  176. if ($state->opt('O')) {
  177. open $state->{dest}, '>', $state->opt('O') or
  178. $state->fatal("Can't write to #1: #2",
  179. $state->opt('O'), $!);
  180. }
  181. $state->{source} = $state->opt('s');
  182. $state->{full} = $state->opt('f');
  183. $state->{repository} = $state->opt('d');
  184. $state->{stdin} = $state->opt('i');
  185. if ($state->opt('o')) {
  186. $state->{scanner} = OpenBSD::BinaryScan::Ldd->new($state);
  187. } else {
  188. $state->{scanner} = OpenBSD::BinaryScan::Objdump->new($state);
  189. }
  190. $state->{quiet} = $state->opt('q');
  191. if ($state->opt('D')) {
  192. $state->{logger} = Logger->new($state->opt('D'));
  193. }
  194. }
  195. sub init
  196. {
  197. my $self = shift;
  198. $self->{errors} = 0;
  199. $self->SUPER::init(@_);
  200. }
  201. sub context
  202. {
  203. my ($self, $pkgname) = @_;
  204. $self->{context} = $pkgname;
  205. }
  206. sub error
  207. {
  208. my $state = shift;
  209. $state->{errors}++;
  210. $state->say_with_context(@_);
  211. }
  212. sub say_with_context
  213. {
  214. my $state = shift;
  215. if ($state->{context}) {
  216. $state->say("\n#1:", $state->{context});
  217. undef $state->{context};
  218. }
  219. $state->say(@_);
  220. }
  221. sub set_context
  222. {
  223. my ($state, $plist) = @_;
  224. my $pkgname = $plist->pkgname;
  225. if ($plist->fullpkgpath) {
  226. $state->context($pkgname."(".$plist->fullpkgpath.")");
  227. } else {
  228. $state->context($pkgname);
  229. }
  230. }
  231. sub perllibs
  232. {
  233. my $state = shift;
  234. if (!defined $state->{perllibs}) {
  235. OpenBSD::SharedLibs::add_libs_from_system('/', $state);
  236. eval {
  237. my $perl = OpenBSD::SharedLibs->find_best('perl');
  238. my $c = OpenBSD::SharedLibs->find_best('c');
  239. if (!defined $perl || !defined $c) {
  240. $state->fatal("can't find system perl and c");
  241. }
  242. $state->{perllibs} = ["perl.".$perl->major, "c.".$c->major];
  243. };
  244. if ($@) {
  245. $state->fatal("please upgrade pkg_add first");
  246. }
  247. }
  248. return @{$state->{perllibs}};
  249. }
  250. package CheckLibDepends;
  251. use OpenBSD::PackageInfo;
  252. use File::Path;
  253. use File::Find;
  254. my $dependencies = {};
  255. sub register_dependencies
  256. {
  257. my $plist = shift;
  258. my $pkgname = $plist->pkgname;
  259. my $h = {};
  260. $dependencies->{$pkgname} = $h;
  261. $plist->depwalk($h);
  262. }
  263. sub get_plist
  264. {
  265. my ($self, $state, $pkgname, $pkgpath) = @_;
  266. # try physical package
  267. if (defined $state->{repository}) {
  268. my $location = "$state->{repository}/$pkgname.tgz";
  269. my $true_package = $state->repo->find($location);
  270. if ($true_package) {
  271. my $dir = $true_package->info;
  272. if (-d $dir) {
  273. my $plist = OpenBSD::PackingList->fromfile($dir.CONTENTS);
  274. $true_package->close;
  275. rmtree($dir);
  276. return $plist;
  277. }
  278. }
  279. }
  280. my $cachefile;
  281. if (exists $ENV{_DEPENDS_CACHE}) {
  282. $cachefile = "$ENV{_DEPENDS_CACHE}/$pkgname";
  283. }
  284. # check the cache
  285. if (defined $cachefile &&
  286. open my $fh, '<', "$ENV{_DEPENDS_CACHE}/$pkgname") {
  287. my $plist = OpenBSD::PackingList->read($fh);
  288. return $plist;
  289. }
  290. # or ask the ports tree directly
  291. my $portsdir = $ENV{PORTSDIR} || "/usr/ports";
  292. my ($make, @extra) = split(/\s+/, $ENV{MAKE} || "make");
  293. my $pid = open(my $fh, "-|");
  294. if ($pid) {
  295. my $plist = OpenBSD::PackingList->read($fh);
  296. close $fh;
  297. waitpid $pid, 0;
  298. if (defined $cachefile && !-f $cachefile) {
  299. $plist->tofile($cachefile);
  300. }
  301. return $plist;
  302. } else {
  303. chdir($portsdir);
  304. my %myenv = (
  305. SUBDIR => $pkgpath,
  306. FULLPATH => "Yes",
  307. ECHO_MSG => ':'
  308. );
  309. if (exists $ENV{_DEPENDS_CACHE}) {
  310. $myenv{_DEPENDS_CACHE} = $ENV{_DEPENDS_CACHE};
  311. }
  312. %ENV = %myenv;
  313. exec { $make }
  314. ($make, @extra, 'print-plist-libs-with-depends',
  315. 'wantlib_args=no-wantlib-args');
  316. exit 1;
  317. }
  318. }
  319. sub handle_dependency
  320. {
  321. my ($self, $state, $pkgname, $pkgpath) = @_;
  322. my $plist = $self->get_plist($state, $pkgname, $pkgpath);
  323. if (!defined $plist || !defined $plist->pkgname) {
  324. $state->errsay("Error: can't solve dependency for #1(#2)",
  325. $pkgname, $pkgpath);
  326. return;
  327. }
  328. if ($plist->pkgname ne $pkgname) {
  329. delete $dependencies->{$pkgname};
  330. for my $p (keys %$dependencies) {
  331. if ($dependencies->{$p}->{$pkgname}) {
  332. $dependencies->{$p}->{$plist->pkgname} =
  333. $dependencies->{$p}->{$pkgname};
  334. delete $dependencies->{$p}->{$pkgname};
  335. }
  336. }
  337. }
  338. register_dependencies($plist);
  339. OpenBSD::SharedLibs::add_libs_from_plist($plist, $state);
  340. return $plist->pkgname;
  341. }
  342. sub lookup_library
  343. {
  344. my ($dir, $spec) = @_;
  345. my $libspec = OpenBSD::LibSpec->from_string($spec);
  346. my $r = OpenBSD::SharedLibs::lookup_libspec($dir, $libspec);
  347. if (!defined $r) {
  348. return ();
  349. } else {
  350. return map {$_->origin} @$r;
  351. }
  352. }
  353. sub report_lib_issue
  354. {
  355. my ($self, $state, $plist, $lib, $binary) = @_;
  356. OpenBSD::SharedLibs::add_libs_from_system('/', $state);
  357. my $libspec = "$lib.0";
  358. my $want = $lib;
  359. $want =~ s/\.\d+$//;
  360. for my $dir (qw(/usr /usr/X11R6)) {
  361. my @r = lookup_library($dir, $libspec);
  362. if (grep { $_ eq 'system' } @r) {
  363. return OpenBSD::Issue::SystemLib->new($lib, $binary);
  364. }
  365. }
  366. while (my ($p, $pkgpath) = each %{$dependencies->{$plist->pkgname}}) {
  367. next if defined $dependencies->{$p};
  368. $self->handle_dependency($state, $p, $pkgpath);
  369. }
  370. my @r = lookup_library('/usr/local', $libspec);
  371. if (@r > 0) {
  372. for my $p (@r) {
  373. if (defined $dependencies->{$plist->pkgname}->{$p}) {
  374. return OpenBSD::Issue::DirectDependency->new($lib, $binary, $p);
  375. }
  376. }
  377. }
  378. # okay, let's walk for WANTLIB
  379. my @todo = %{$dependencies->{$plist->pkgname}};
  380. my $done = {};
  381. while (@todo >= 2) {
  382. my $path = pop @todo;
  383. my $dep = pop @todo;
  384. next if $done->{$dep};
  385. $done->{$dep} = 1;
  386. $dep = $self->handle_dependency($state, $dep, $path)
  387. unless defined $dependencies->{$dep};
  388. next if !defined $dep;
  389. $done->{$dep} = 1;
  390. push(@todo, %{$dependencies->{$dep}});
  391. }
  392. @r = lookup_library(OpenBSD::Paths->localbase, $libspec);
  393. for my $p (@r) {
  394. if (defined $done->{$p}) {
  395. return OpenBSD::Issue::IndirectDependency->new($lib, $binary, $p);
  396. }
  397. }
  398. return OpenBSD::Issue::NotReachable->new($lib,, $binary, @r);
  399. }
  400. sub has_all_libs
  401. {
  402. my ($self, $libs, $list) = @_;
  403. for my $l (@$list) {
  404. if (!defined $libs->{$l}) {
  405. return 0;
  406. }
  407. }
  408. return 1;
  409. }
  410. sub backsubst
  411. {
  412. my ($self, $h, $state) = @_;
  413. my $doit = {};
  414. # try backsubsting each list
  415. while (my ($k, $v) = each %{$state->{subst}->hash}) {
  416. my @l = split(/\s+/, $v);
  417. if ($self->has_all_libs($h, \@l)) {
  418. $doit->{$k} = \@l;
  419. }
  420. }
  421. while (my ($k, $list) = each %$doit) {
  422. for my $l (@$list) {
  423. delete $h->{$l};
  424. }
  425. $h->{'${'.$k.'}'} = 1;
  426. }
  427. }
  428. sub print_list
  429. {
  430. my ($self, $state, $head, $h) = @_;
  431. $self->backsubst($h, $state);
  432. my $line = "";
  433. for my $k (sort keys %$h) {
  434. next if $k eq 'c++abi';
  435. $k =~ s/^(std)?c\+\+$/\${LIBCXX}/;
  436. if (length $line > 50) {
  437. $state->say_with_context("#1#2", $head, $line);
  438. $line = "";
  439. }
  440. $line .= ' '.$k;
  441. }
  442. if ($line ne '') {
  443. $state->say_with_context("#1#2", $head, $line);
  444. }
  445. }
  446. sub scan_package
  447. {
  448. my ($self, $state, $plist, $source) = @_;
  449. $state->{scanner}->set_source($source);
  450. $plist->scan_binaries_for_libs($state);
  451. $state->{scanner}->finish_scanning;
  452. }
  453. sub scan_true_package
  454. {
  455. my ($self, $state, $plist, $source) = @_;
  456. $state->{scanner}->set_source($source);
  457. my $h = {};
  458. $plist->find_binaries($h);
  459. $plist->find_perl($state);
  460. while (my $o = $source->next) {
  461. my $item = $h->{$o->name};
  462. if (defined $item) {
  463. delete $h->{$o->name};
  464. $state->{scanner}->finish_retrieve_and_scan(
  465. $item, $o);
  466. }
  467. }
  468. if (keys %$h != 0) {
  469. $state->fatal("Not all files accounted for");
  470. }
  471. $state->{scanner}->finish_scanning;
  472. }
  473. sub analyze
  474. {
  475. my ($self, $state, $plist) = @_;
  476. my $pkgname = $plist->pkgname;
  477. my $needed_libs = $state->{full} ? OpenBSD::AllRecorder->new :
  478. OpenBSD::SimpleRecorder->new;
  479. my $has_libs = {};
  480. $plist->find_libs($needed_libs, $state->{dump});
  481. $plist->register_libs($has_libs);
  482. if (!defined $dependencies->{$pkgname}) {
  483. register_dependencies($plist);
  484. OpenBSD::SharedLibs::add_libs_from_plist($plist, $state);
  485. }
  486. my $r = { wantlib => {}, libdepends => {}, wantlib2 => {} };
  487. for my $lib (sort $needed_libs->libs) {
  488. my $fullname = $needed_libs->binary($lib);
  489. if (!defined $has_libs->{$lib}) {
  490. my $issue = $self->report_lib_issue($state, $plist,
  491. $lib, $fullname);
  492. $state->error("#1", $issue->message);
  493. $issue->record_wantlib($r->{wantlib});
  494. } elsif ($has_libs->{$lib} == 1) {
  495. my $issue = $self->report_lib_issue($state, $plist,
  496. $lib, $fullname);
  497. if ($issue->not_reachable) {
  498. $state->error("#1", $issue->not_reachable);
  499. }
  500. }
  501. $has_libs->{$lib} = 2;
  502. }
  503. my $extra = {};
  504. for my $k (keys %$has_libs) {
  505. my $v = $has_libs->{$k};
  506. next if $v == 2;
  507. $extra->{$k} = 1;
  508. }
  509. unless ($state->{quiet} && keys %{$r->{wantlib}} == 0) {
  510. $self->print_list($state, "Extra: ", $extra);
  511. }
  512. $self->print_list($state, "WANTLIB +=", $r->{wantlib});
  513. if ($state->{full}) {
  514. $needed_libs->dump(\*STDOUT);
  515. }
  516. }
  517. sub do_pkg
  518. {
  519. my ($self, $state, $pkgname) = @_;
  520. my $true_package = $state->repo->find($pkgname);
  521. return 0 unless $true_package;
  522. my $dir = $true_package->info;
  523. # twice read
  524. return 0 unless -d $dir;
  525. my $plist = OpenBSD::PackingList->fromfile($dir.CONTENTS);
  526. $state->set_context($plist);
  527. my $temp = OpenBSD::Temp->dir;
  528. $state->{dump} = OpenBSD::DumpRecorder->new;
  529. $self->scan_true_package($state, $plist,
  530. OpenBSD::PkgFileSource->new($true_package, $temp));
  531. $self->analyze($state, $plist);
  532. $true_package->close;
  533. $true_package->wipe_info;
  534. $plist->forget;
  535. if ($state->{dest}) {
  536. $state->{dump}->dump($state->{dest});
  537. }
  538. return 1;
  539. }
  540. sub do_plist
  541. {
  542. my ($self, $state) = @_;
  543. my $plist = OpenBSD::PackingList->read(\*STDIN);
  544. if (!defined $plist->{name}) {
  545. $state->error("Error reading plist");
  546. return;
  547. } else {
  548. $state->set_context($plist);
  549. $self->analyze($state, $plist);
  550. }
  551. }
  552. sub scan_directory
  553. {
  554. my ($self, $state, $fs) = @_;
  555. my $source = OpenBSD::FsFileSource->new($fs);
  556. $state->{scanner}->set_source($source);
  557. find({
  558. wanted => sub {
  559. return if -l $_;
  560. return unless -f _;
  561. my $name = $_;
  562. $name =~ s/^\Q$fs\E/\//;
  563. # XXX hack FileBase object;
  564. my $i = bless {name => $name}, "MyFile";
  565. $i->scan_binaries_for_libs($state);
  566. },
  567. no_chdir => 1 }, $fs);
  568. $state->{scanner}->finish_scanning;
  569. }
  570. sub main
  571. {
  572. my $self = shift;
  573. my $state = CheckLibDepends::State->new('check-lib-depends');
  574. $state->{signature_style} = 'unsigned';
  575. $state->handle_options;
  576. my $need_package = 0;
  577. # find files if we can
  578. if ($state->{source}) {
  579. $state->{dump} = OpenBSD::DumpRecorder->new;
  580. $state->{dump}->retrieve($state, $state->{source});
  581. } elsif ($state->{destdir}) {
  582. $state->{dump} = OpenBSD::DumpRecorder->new;
  583. $self->scan_directory($state, $state->{destdir});
  584. if ($state->{dest}) {
  585. $state->{dump}->dump($state->{dest});
  586. }
  587. } else {
  588. $need_package = 1;
  589. }
  590. if ($state->{stdin}) {
  591. if ($need_package) {
  592. $state->fatal("no source for actual files given");
  593. }
  594. $self->do_plist($state);
  595. } elsif (@ARGV != 0) {
  596. $state->progress->for_list("Scanning", \@ARGV,
  597. sub {
  598. $self->do_pkg($state, shift);
  599. });
  600. }
  601. exit($state->{errors} ? 1 : 0);
  602. }
  603. # XXX wrap line to avoid converting this to RCS keyword
  604. $OpenBSD::Temp::tempbase =
  605. $ENV{'TMPDIR'} || "/tmp";
  606. __PACKAGE__->main;