#!/usr/perl5/bin/perl -w # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # Copyright 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # # This utility program reads the contents file to extract Solaris ELF # libraries, and then runs pvs(1) on them to find the library versioning # information (if any). This info is printed to stdout in an index file # format. # require 5.005; use strict; use locale; use POSIX qw(locale_h); use Sun::Solaris::Utils qw(textdomain gettext); use File::Basename; use vars qw( @liblist %symlink %inode_hash %fileoutput %didlib ); setlocale(LC_ALL, ""); textdomain(TEXT_DOMAIN); # parameters for what types of libraries to list out: my $must_be_versioned = 0; my $must_be_public = 0; # paths to skip outright. my @skip_list = qw( /etc /usr/perl5 ); my $path_skip = join('|', @skip_list); $path_skip = qr/^($path_skip)/; # find library names: # # We have to use pkgchk -l output (even though it is much slower than # parsing /var/sadm/install/contents ourselves) because the contents # file will go away or change incompatibly at some point. # my $old = $ENV{'LC_ALL'}; $ENV{'LC_ALL'} = 'C'; my $contents_fh = do { local *FH; *FH }; open($contents_fh, "/usr/sbin/pkgchk -l|") || die "$!\n"; if (defined($old)) { $ENV{'LC_ALL'} = $old; } else { delete($ENV{'LC_ALL'}); } my $pathname = ''; my $type = ''; my $link = ''; my $pkgs = ''; my $status = ''; my $inpkgs = 0; while (<$contents_fh>) { next if (/^Ex/); chomp; if (/^Pathname:\s*/i) { $pathname = $'; $type = ''; $link = ''; $status = ''; $pkgs = ''; $inpkgs = 0; next; } elsif (/^Type:\s*/i) { $type = $'; next; } elsif (/^Source of link:\s*/i) { $link = $'; next; } elsif (/^Referenced by/i) { $inpkgs = 1; } elsif (/^Current status:\s*/i) { $status = $'; $inpkgs = 0; next; } elsif (/^\s*$/) { next unless ($pathname =~ m,\.so,); next unless ($pathname =~ m,/lib,); next unless ($pathname =~ m,/lib[^/]*\.so\b,); next unless ($type =~ /regular file|symbolic link/i); next unless ($status =~ /^\s*installed\s*$/); $pathname = trim($pathname); $link = trim($link); filter($pathname, $link, $pkgs); } if ($inpkgs) { $pkgs .= $_ . ' '; } } close($contents_fh); # run pvs(1) on the libraries found: my $batch = 30; # batch size to use (running in batches is faster). my @list = (); for (my $i = 1; $i <= scalar(@liblist); $i++) { push(@list, $liblist[$i-1]); if ($i % $batch == 0) { do_pvs(@list) if (@list); @list = (); } } do_pvs(@list) if (@list); # finish any remainder. exit 0; # # Take a pkgchk -l entry and decide if it corresponds to a Solaris # library. If so, save it in the list @liblist, and record info in # %symlink & %inode_hash associative arrays as appropriate. # sub filter { my ($path, $link, $pkgs) = @_; # consider only SUNW packages: return unless ($pkgs =~ /\bSUNW\S+/); my $basename; $basename = basename($path); if ($link ne '') { # include developer build-time symlinks: return unless ($basename =~ /^lib.*\.so[\.\d]*$/); } else { return unless ($basename =~ /^lib.*\.so\.[\.\d]+$/); } return if ($path =~ /$path_skip/); return unless (-f $path); # inode is used to identify what file a symlink point to: my $inode; $inode = (stat($path))[1]; return unless (defined($inode)); if ($link ne '') { # record info about symlinks: if (exists($symlink{$inode})) { $symlink{$inode} .= ":" . $path; } else { $symlink{$inode} = ":" . $path; } } else { # ordinary file case: $inode_hash{$path} = $inode; push(@liblist, $path); } } # # Run pvs(1) on a list of libraries. More than one is done at a time to # speed things up. # # Extracts the version information and passes it to the output() routine # for final processing. # sub do_pvs { my (@list) = @_; my (%list, $paths, $path, $cnt); # # record info about the library paths and construct the list of # files for the pvs command line. # $cnt = 0; $paths = ''; foreach $path (@list) { $list{$path} = 1; $paths .= ' ' if ($paths ne ''); # # $path should never have single quote in it in # all normal usage. Make sure this is so: # next if ($path =~ /'/); # # quote the filename in case it has meta-characters # (which should never happen in all normal usage) # $paths .= "'$path'"; $cnt++; } return if ($cnt == 0); # set locale to C for running command, since we interpret the output: my $old = $ENV{'LC_ALL'}; $ENV{'LC_ALL'} = 'C'; # get the file(1) output for each item: my $file_fh = do { local *FH; *FH }; open($file_fh, "/usr/bin/file $paths 2>&1 |") || die "$!\n"; my ($file, $out); while (<$file_fh>) { ($file, $out) = split(/:/, $_, 2); if ($list{$file} && $out =~ /\bELF\b/) { $fileoutput{$file} = $out; } } close($file_fh); # # in the case of only 1 item, we place it on the command line # twice to induce pvs(1) to indicate which file it is reporting # on. # if ($cnt == 1) { $paths .= " $paths"; } # # $paths are entries from /var/sadm/install/contents and # so should not contain spaces or meta characters: # my $pvs_fh = do { local *FH; *FH }; open($pvs_fh, "/usr/bin/pvs -dn $paths 2>&1 |") || die "$!\n"; # reset LC_ALL, if there was any: if (defined($old)) { $ENV{'LC_ALL'} = $old; } else { delete($ENV{'LC_ALL'}); } my ($pub, $pri, $obs, $evo, $vers, $new_path); undef($path); # initialize strings used below for appending info to: $pub = ''; $pri = ''; $obs = ''; $evo = ''; while (<$pvs_fh>) { $_ =~ s/\s*$//; if (m,^([^:]+):$,) { # a new pvs file header, e.g. "/usr/lib/libc.so.1:" if ($list{$1}) { $new_path = $1; # output the previous one and reset accumulators: if (defined($path)) { output($path, $pub, $pri, $obs, $evo); $pub = ''; $pri = ''; $obs = ''; $evo = ''; } $path = $new_path; next; # done with pvs header case } } # extract SUNW version head end: $vers = trim($_); $vers =~ s/;//g; # handle the various non-standard cases in Solaris libraries: if ($vers =~ /^(SUNW.*private|SUNW_XIL_GPI)/i) { $pri .= $vers . ":"; } elsif ($vers =~ /^(SUNW_\d|SYSVABI|SISCD)/) { $pub .= $vers . ":"; } elsif ($vers =~ /^(SUNW\.\d|SUNW_XIL)/) { $pub .= $vers . ":"; } elsif ($vers =~ /^SUNWobsolete/) { $obs .= $vers . ":"; } elsif ($vers =~ /^SUNWevolving/) { $evo .= $vers . ":"; } else { next; } } close($pvs_fh); # output the last one (if any): if (defined($path)) { output($path, $pub, $pri, $obs, $evo); } } # # Take the raw library versioning information and process it into index # file format and then print it out. # sub output { my ($path, $pub, $pri, $obs, $evo) = @_; return if ($didlib{$path}); # skip repeating a library # trim off any trailing separators: $pub =~ s/:$//; $pri =~ s/:$//; $obs =~ s/:$//; $evo =~ s/:$//; # work out the type of library: my $type; my $defn; my $n; if ($pri && ! $pub && ! $obs && ! $evo) { $type = 'INTERNAL'; $defn = 'NO_PUBLIC_SYMS'; } elsif ($obs) { $type = 'OBSOLETE'; $defn = $obs; } elsif ($pub) { $type = 'PUBLIC'; $defn = $pub; if ($defn =~ /:/) { $defn =~ s/:/,/g; $defn = "PUBLIC=$defn"; } } elsif ($evo) { $type = 'EVOLVING'; $defn = $evo; } elsif (! $pri && ! $pub && ! $obs && ! $evo) { $type = 'UNVERSIONED'; $defn = '-'; } else { return; } # return if instructed to skip either of these cases: if ($must_be_versioned && $type eq 'UNVERSIONED') { return; } if ($must_be_public && $type eq 'INTERNAL') { return; } # prepare the output line, including any symlink information: my $inode = $inode_hash{$path}; my $links; if ($inode && exists($symlink{$inode})) { $links = "${path}$symlink{$inode}"; } else { $links = "$path"; } # count the total number of references: my (@n) = split(/:/, $links); $n = scalar(@n); # determine the abi to which the library file belongs: my ($fout, $abi); $abi = 'unknown'; $fout = $fileoutput{$path}; if ($fout =~ /\bSPARCV9\b/) { $abi = 'sparcv9'; } elsif ($fout =~ /\bSPARC/) { $abi = 'sparc'; } elsif ($fout =~ /\bAMD64\b/ || $fout =~ /\bELF\s+64-bit\s+LSB\b/) { $abi = 'amd64'; } elsif ($fout =~ /\b80386\b/) { $abi = 'i386'; } print STDOUT "$abi|$path|$defn|$n|$links\n"; # record that we did this library so we do not process it a second time. $didlib{$path} = 1; } # # Remove leading and trailing spaces. # sub trim { my ($x) = @_; $x =~ s/^\s*//; $x =~ s/\s*$//; return $x; }