1 # shared-libs -- lintian check script -*- perl -*-
3 # Copyright (C) 1998 Christian Schwarz
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, you can find it on the World Wide
17 # Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free
18 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
21 package Lintian::shared_libs;
33 # Libraries that should only be used in the presence of certain capabilities
34 # may be located in subdirectories of the standard ldconfig search path with
35 # one of the following names.
36 my %hwcap_dir = map { $_ => 1 }
37 qw( i486 i586 i686 cmov tls );
39 # The following architectures should always have a STACK setting in shared
40 # libraries to disable executable stack. Other architectures don't always add
41 # this section and therefore can't be checked.
42 my %stack_arches = map { $_ => 1 }
52 my $ldconfig_dirs = Lintian::Data->new('shared-libs/ldconfig-dirs');
54 my $must_call_ldconfig;
55 my $postrm = "control/postrm";
56 my $postinst = "control/postinst";
57 my $preinst = "control/preinst";
58 my $prerm = "control/prerm";
59 my $shlibs_control_file = "control/shlibs";
60 my $symbols_control_file = "control/symbols";
72 # ---end-of-configuration-part---
77 # 1st step: get info about shared libraries installed by this package
78 open(IN, '<', "objdump-info")
79 or fail("cannot find objdump-info for $type package $pkg");
86 if (m/^-- (\S+)\s*$/o) {
89 } elsif (m/^\s*SONAME\s+(\S+)/o) {
91 } elsif (m/^\s*TEXTREL\s/o) {
93 } elsif (m/^\s*INTERP\s/) {
95 } elsif (m/^\s*STACK\s/) {
98 if (defined $STACK{$file} and $STACK{$file} eq 0) {
103 $objsomething{$file} = 1;
109 open (IN, '<', "file-info")
110 or fail("cannot find file-info for $type package $pkg");
113 # with file names containing colons and spaces, we're fucked.
114 m/^(?:\.\/)?(.+?):\s+(.*)$/o or fail("unrecognized file(1) output: $_");
115 my ($file,$info) = ($1,$2);
116 if ($info =~ m/^[^,]*\bELF\b/ && $info =~ m/shared object/) {
117 $sharedobject{$file} = 1;
122 # 2nd step: read package contents
124 open(IN, '<', "index") or fail("cannot open index file index: $!");
127 @words = split(/\s+/o, $_, 6);
128 my $perm = $words[0];
129 my $cur_file = $words[5];
130 $cur_file =~ s,^(\./),,;
131 $cur_file =~ s/ link to .*//;
133 if ($perm =~ m/^l/) {
135 ($cur_file, $link) = split(' -> ', $cur_file);
136 $link_info{$cur_file} = $link;
138 $index_info{$cur_file} = 1;
139 $files{$cur_file} = $perm;
143 my ($cur_file, $perm) = ($_, $files{$_});
146 if (exists $SONAME{$cur_file} or
147 ($link_info{$cur_file} and exists $SONAME{abs_path(dirname($cur_file)."/".$link_info{$cur_file})})) {
150 my ($real_file, $real_perm);
151 if ($SONAME{$cur_file}) {
152 $real_file = $cur_file;
155 $real_file = abs_path(dirname($cur_file)."/".$link_info{$cur_file});
156 $real_perm = $files{$real_file} || $perm;
159 # Now that we're sure this is really a shared library, report on
161 if ($cur_file eq $real_file and $TEXTREL{$cur_file}) {
162 tag "shlib-with-non-pic-code", "$cur_file";
165 # don't apply the permission checks to links
166 # since this only results in doubled messages
167 if ($cur_file eq $real_file) {
169 if ($real_perm =~ m/x/) {
170 # yes. But if the library has an INTERP section, it's designed
171 # to do something useful when executed, so don't report an error.
172 tag "shlib-with-executable-bit", $cur_file, sprintf("%04o",perm2oct($real_perm))
173 unless $INTERP{$real_file};
174 } elsif ($real_perm ne '-rw-r--r--') {
176 tag "shlib-with-bad-permissions", $cur_file, sprintf("%04o",perm2oct($real_perm));
180 # Installed in a directory controlled by the dynamic linker? We have
181 # to strip off directories named for hardware capabilities.
182 my $dirname = dirname($cur_file);
185 $dirname =~ s%/([^/]+)$%%;
187 } while ($last && $hwcap_dir{$last});
188 $dirname .= "/$last" if $last;
189 if ($ldconfig_dirs->known($dirname)) {
190 # yes! so postinst must call ldconfig
191 $must_call_ldconfig = $real_file;
194 # executable stack. We can only warn about a missing section on some
195 # architectures. Only warn if there's an Architecture field; if
196 # that's missing, we'll already be complaining elsewhere.
197 if (exists $objsomething{$cur_file}) {
198 if (not defined $STACK{$cur_file}) {
199 if (open(FH, '<', "fields/architecture")) {
203 tag "shlib-without-PT_GNU_STACK-section", $cur_file
204 if $stack_arches{$arch};
206 } elsif ($STACK{$cur_file} ne "rw-") {
207 tag "shlib-with-executable-stack", $cur_file;
210 } elsif (exists $objsomething{$cur_file}
211 && $ldconfig_dirs->known(dirname($cur_file))
212 && exists $sharedobject{$cur_file}) {
213 tag "sharedobject-in-library-directory-missing-soname", "$cur_file";
219 # 3rd step: check if shlib symlinks are present and in correct order
220 for my $shlib_file (keys %SONAME) {
222 if (not exists $index_info{$shlib_file}) {
223 fail("shlib $shlib_file not found in package (should not happen!)");
226 my ($dir, $shlib_name) = $shlib_file =~ m,(.*)/([^/]+)$,;
228 # not a public shared library, skip it
229 next unless $ldconfig_dirs->known($dir);
232 my $link_file = "$dir/$SONAME{$shlib_file}";
233 if (not exists $index_info{$link_file}) {
234 tag "ldconfig-symlink-missing-for-shlib", "$link_file $shlib_file $SONAME{$shlib_file}";
236 # $link_file really another file?
237 if ($link_file eq $shlib_file) {
238 # the library file uses its SONAME, this is ok...
240 # $link_file really a symlink?
241 if (exists $link_info{$link_file}) {
244 # $link_file pointing to correct file?
245 if ($link_info{$link_file} eq $shlib_name) {
248 tag "ldconfig-symlink-referencing-wrong-file", "$link_file -> $link_info{$link_file} instead of $shlib_name";
251 tag "ldconfig-symlink-is-not-a-symlink", "$shlib_file $link_file";
254 # symlink after shlib?
255 if ($index_info{$link_file} < $index_info{$shlib_file}) {
256 tag "ldconfig-symlink-before-shlib-in-deb", "$link_file";
261 # determine shlib link name (w/o version)
262 $link_file =~ s/\.so.*$/.so/o;
265 if ($pkg =~ m/\-dev$/o) {
269 if (not exists $index_info{$link_file}) {
270 tag "dev-pkg-without-shlib-symlink", "$shlib_file $link_file";
275 # shlib symlink may not exist.
276 # if shlib doesn't _have_ a version, then $link_file and $shlib_file will
277 # be equal, and it's not a development link, so don't complain.
278 if (exists $index_info{$link_file} and $link_file ne $shlib_file) {
279 tag "non-dev-pkg-with-shlib-symlink", "$shlib_file $link_file";
284 # 4th step: check shlibs control file
286 if (open (VERSION, '<', 'fields/version')) {
287 $version = <VERSION>;
291 my $provides = $pkg . "( = $version)";
292 if (open (PROVIDES, '<', 'fields/provides')) {
293 my $line = <PROVIDES>;
296 $provides .= ", $line";
298 $provides = Dep::parse($provides);
303 @shlibs = grep { !m,^lib/libnss_[^.]+\.so(\.[0-9]+)$, } keys %SONAME;
304 if ($#shlibs == -1) {
305 # no shared libraries included in package, thus shlibs control file should
307 if (-f $shlibs_control_file) {
308 tag "pkg-has-shlibs-control-file-but-no-actual-shared-libs", "";
311 # shared libraries included, thus shlibs control file has to exist
312 if (not -f $shlibs_control_file) {
313 if ($type ne 'udeb') {
314 for my $shlib (@shlibs) {
315 # skip it if it's not a public shared library
316 next unless $ldconfig_dirs->known(dirname($shlib));
317 tag "no-shlibs-control-file", "$shlib";
321 my %shlibs_control_used;
323 open(SHLIBS, '<', $shlibs_control_file)
324 or fail("cannot open shlibs control file $shlibs_control_file for reading: $!");
327 next if m/^\s*$/ or /^#/;
329 # We exclude udebs from the checks for correct shared library
330 # dependencies, since packages may contain dependencies on
331 # other udeb packages.
333 $udeb = "udeb: " if s/^udeb:\s+//o;
334 @words = split(/\s+/o,$_);
335 my $shlibs_string = $udeb.$words[0].' '.$words[1];
336 if ($shlibs_control{$shlibs_string}) {
337 tag "duplicate-entry-in-shlibs-control-file", $shlibs_string;
339 $shlibs_control{$shlibs_string} = 1;
340 push (@shlibs_depends, join (' ', @words[2 .. $#words]))
346 for my $shlib (@shlibs) {
347 $shlib_name = $SONAME{$shlib};
348 $shlib_name = format_soname($shlib_name);
349 $shlibs_control_used{$shlib_name} = 1;
350 $shlibs_control_used{"udeb: ".$shlib_name} = 1;
351 unless (exists $shlibs_control{$shlib_name}) {
352 # skip it if it's not a public shared library
353 next unless $ldconfig_dirs->known(dirname($shlib));
355 tag "shlib-missing-in-control-file", $shlib_name, 'for', $shlib;
358 for $shlib_name (keys %shlibs_control) {
359 tag "unused-shlib-entry-in-control-file", $shlib_name
360 unless $shlibs_control_used{$shlib_name};
363 # Check that all of the packages listed as dependencies in the shlibs
364 # file are satisfied by the current package or its Provides.
365 # Normally, packages should only declare dependencies in their shlibs
366 # that they themselves can satisfy.
368 # Deduplicate the list of dependencies before warning so that we don't
371 @shlibs_depends = grep { !$seen{$_}++ } @shlibs_depends;
372 for my $depend (@shlibs_depends) {
373 unless (Dep::implies($provides, Dep::parse($depend))) {
374 tag "shlibs-declares-dependency-on-other-package", $depend;
380 # 5th step: check symbols control file
381 if ($#shlibs == -1) {
382 # no shared libraries included in package, thus symbols control file should
384 if (-f $symbols_control_file) {
385 tag "pkg-has-symbols-control-file-but-no-shared-libs", "";
387 } elsif (not -f $symbols_control_file) {
388 if ($type ne 'udeb') {
389 for my $shlib (@shlibs) {
390 # skip it if it's not a public shared library
391 next unless $ldconfig_dirs->known(dirname($shlib));
392 tag "no-symbols-control-file", "$shlib";
395 } elsif (open(IN, '<', $symbols_control_file)) {
396 my $version_wo_rev = $version;
397 $version_wo_rev =~ s/^(.+)-([^-]+)$/$1/;
398 my ($full_version_count, $full_version_sym) = (0, undef);
399 my ($debian_revision_count, $debian_revision_sym) = (0, undef);
400 my ($soname, $dep_package, $dep);
401 my %symbols_control_used;
403 my $dep_templates = 0;
404 my $meta_info_seen = 0;
408 next if m/^\s*$/ or /^#/;
410 if (m/^([^\s|*]\S+)\s(\S+)(?:\s(\S+))?/) {
411 # soname, main dependency template
413 ($soname, $dep_package, $dep) = ($1, $2, $3);
415 $soname = format_soname($soname);
417 if ($symbols_control{$soname}) {
418 tag "duplicate-entry-in-symbols-control-file", $soname;
420 $symbols_control{$soname} = 1;
421 push @symbols_depends, $dep_package . ' ' . $dep;
426 } elsif (m/^\|\s+(\S+)\s(\S+(\s\S+)?)$/) {
427 # alternative dependency template
429 if ($meta_info_seen or not defined $soname) {
430 tag "syntax-error-in-symbols-file", $.;
433 ($dep_package, $dep) = ($1, $2);
434 push @symbols_depends, $dep_package . ' ' . $dep;
436 } elsif (m/^\*\s(\S+):\s(\S+)/) {
439 # This should probably be in a hash, but there's
440 # only one supported value currently
441 tag "unknown-meta-field-in-symbols-file", "$1, line $."
442 unless $1 eq 'Build-Depends-Package';
443 tag "syntax-error-in-symbols-file", $.
444 if $dep_templates > 0;
447 } elsif (m/^\s+(\S+)\s(\S+)(?:\s(\S+(\s\S+)?))?$/) {
450 tag "syntax-error-in-symbols-file", $.
451 unless defined $soname;
453 my ($sym, $v, $dep_order) = ($1, $2, $3);
456 if (($v eq $version) and ($version =~ /-/)) {
457 $full_version_sym ||= $sym;
458 $full_version_count++;
460 if (($v =~ /-/) and (not $v =~ /~$/) and ($v ne $version_wo_rev)) {
461 $debian_revision_sym ||= $sym;
462 $debian_revision_count++;
465 if (length $dep_order) {
466 if ($dep_order !~ /^\d+$/ or $dep_order > $dep_templates) {
467 tag "invalid-template-id-in-symbols-file", $.;
473 tag "syntax-error-in-symbols-file", $.;
477 if ($full_version_count) {
478 $full_version_count--;
480 if ($full_version_count > 0) {
481 $others = " and $full_version_count others";
483 tag "symbols-file-contains-current-version-with-debian-revision",
484 "on symbol $full_version_sym$others";
486 if ($debian_revision_count) {
487 $debian_revision_count--;
489 if ($debian_revision_count > 0) {
490 $others = " and $debian_revision_count others";
492 tag "symbols-file-contains-debian-revision",
493 "on symbol $debian_revision_sym$others";
496 for my $shlib (@shlibs) {
497 $shlib_name = $SONAME{$shlib};
498 $shlib_name = format_soname($shlib_name);
499 $symbols_control_used{$shlib_name} = 1;
500 $symbols_control_used{"udeb: ".$shlib_name} = 1;
501 unless (exists $symbols_control{$shlib_name}) {
502 # skip it if it's not a public shared library
503 next unless $ldconfig_dirs->known(dirname($shlib));
504 tag "shlib-missing-in-symbols-control-file", $shlib_name, 'for', $shlib;
507 for $shlib_name (keys %symbols_control) {
508 tag "unused-shlib-entry-in-symbols-control-file", $shlib_name
509 unless $symbols_control_used{$shlib_name};
512 # Check that all of the packages listed as dependencies in the symbols
513 # file are satisfied by the current package or its Provides.
514 # Normally, packages should only declare dependencies in their symbols
515 # files that they themselves can satisfy.
517 # Deduplicate the list of dependencies before warning so that we don't
520 @symbols_depends = grep { !$seen{$_}++ } @symbols_depends;
521 for my $depend (@symbols_depends) {
522 unless (Dep::implies($provides, Dep::parse($depend))) {
523 tag "symbols-declares-dependency-on-other-package", $depend;
528 # Compare the contents of the shlibs and symbols control files
529 if (keys %shlibs_control and keys %symbols_control) {
530 for my $key (keys %symbols_control) {
531 unless (exists $shlibs_control{$key}) {
532 tag "symbols-declared-but-not-shlib", $key;
537 # 6th step: check pre- and post- control files
539 local $_ = slurp_entire_file($preinst);
540 if (/^[^\#]*\bldconfig\b/m) {
541 tag "preinst-calls-ldconfig", ""
545 my $we_call_postinst=0;
547 local $_ = slurp_entire_file($postinst);
549 # Decide if we call ldconfig
550 if (/^[^\#]*\bldconfig\b/m) {
555 if ($type eq 'udeb') {
556 tag "udeb-postinst-must-not-call-ldconfig"
557 if $we_call_postinst;
559 tag "postinst-has-useless-call-to-ldconfig", ""
560 if $we_call_postinst and not $must_call_ldconfig;
561 tag "postinst-must-call-ldconfig", "$must_call_ldconfig"
562 if not $we_call_postinst and $must_call_ldconfig;
566 local $_ = slurp_entire_file($prerm);
567 if (/^[^\#]*\bldconfig\b/m) {
568 tag "prerm-calls-ldconfig", "";
573 local $_ = slurp_entire_file($postrm);
575 # Decide if we call ldconfig
576 if (/^[^\#]*\bldconfig\b/m) {
577 tag "postrm-has-useless-call-to-ldconfig", ""
578 unless $must_call_ldconfig;
580 tag "postrm-should-call-ldconfig", "$must_call_ldconfig"
581 if $must_call_ldconfig;
584 # Decide if we do it safely
585 s/\bldconfig\b/BldconfigB/g;
587 # this one matches code from debhelper
588 s/^if\["\$1"=.?remove.?\];?\n*then\n*BldconfigB//gm;
590 s/^if\[.?remove.?="\$1"\];?\n*then\n*BldconfigB//gm;
591 s/^\["\$1"=.?remove.?\]\&&BldconfigB//gm;
592 s/^\[.?remove.?="\$1"\]&&BldconfigB//gm;
593 s/remove(?:\|[^)]+)*\).*?BldconfigB.*?(;;|esac)//s;
595 if (/^[^\#]*BldconfigB/m) {
596 tag "postrm-unsafe-ldconfig", "";
602 # make /tmp/baz/baz.txt from /tmp/foo/../bar/../baz/baz.txt
605 while($path =~ s!/[^/]*/\.\./!/!g){1};
613 if ($soname =~ m/(.+)\.so\.(.*)$/) {
616 } elsif ($soname =~ m/(.+)\-(\w[\w\.]*)\.so$/) {
626 # indent-tabs-mode: t
627 # cperl-indent-level: 4
629 # vim: syntax=perl sw=4 ts=8