# Copyright (c) 2006-2008 - OpenSLX GmbH
#
# This program is free software distributed under the GPL version 2.
# See http://openslx.org/COPYING
#
# If you have any feedback please consult http://openslx.org/feedback and
# send your suggestions, praise, or complaints to feedback@openslx.org
#
# General information about OpenSLX can be found at http://openslx.org/
# -----------------------------------------------------------------------------
# LibScanner.pm
# - module that recursively scans a given binary for library dependencies
# -----------------------------------------------------------------------------
package OpenSLX::LibScanner;
use strict;
use warnings;
use File::Find;
use File::Path;
use OpenSLX::Basics;
use OpenSLX::Utils;
################################################################################
### interface methods
################################################################################
sub new
{
my $class = shift;
my $params = shift || {};
checkParams($params, {
'root-path' => '!',
'verbose' => '?',
} );
my $self = {
rootPath => $params->{'root-path'},
verbose => $params->{'verbose'} || 0,
};
return bless $self, $class;
}
sub determineRequiredLibs
{
my $self = shift;
my @binaries = @_;
$self->{filesToDo} = [];
$self->{libs} = [];
$self->{libInfo} = {};
$self->_fetchLoaderConfig();
foreach my $binary (@binaries) {
if (substr($binary, 0, 1) ne '/') {
# force relative paths relative to $rootPath:
$binary = "$self->{rootPath}/$binary";
}
if (!-e $binary) {
warn _tr("$0: unable to find file '%s', skipping it\n", $binary);
next;
}
push @{$self->{filesToDo}}, $binary;
}
foreach my $file (@{$self->{filesToDo}}) {
$self->_addLibsForBinary($file);
}
return @{$self->{libs}};
}
sub _fetchLoaderConfig
{
my $self = shift;
my @libFolders;
if (!-e "$self->{rootPath}/etc") {
die _tr("'%s'-folder not found, maybe wrong root-path?\n",
"$self->{rootPath}/etc");
}
$self->_fetchLoaderConfigFile("$self->{rootPath}/etc/ld.so.conf");
# add "trusted" folders /lib and /usr/lib if not already in place:
if (!grep { m[^$self->{rootPath}/lib$] } @libFolders) {
push @libFolders, "$self->{rootPath}/lib";
}
if (!grep { m[^$self->{rootPath}/usr/lib$] } @libFolders) {
push @libFolders, "$self->{rootPath}/usr/lib";
}
# add lib32-folders for 64-bit Debians, as they do not
# refer those in ld.so.conf (which I find strange...)
if (-e '/lib32' && !grep { m[^$self->{rootPath}/lib32$] } @libFolders) {
push @libFolders, "$self->{rootPath}/lib32";
}
if (-e '/usr/lib32'
&& !grep { m[^$self->{rootPath}/usr/lib32$] } @libFolders)
{
push @libFolders, "$self->{rootPath}/usr/lib32";
}
$self->{libFolders} = \@libFolders;
return;
}
sub _fetchLoaderConfigFile
{
my $self = shift;
my $ldConfFile = shift;
return unless -e $ldConfFile;
my $ldconfFH;
if (!open($ldconfFH, '<', $ldConfFile)) {
warn(_tr("unable to open file '%s' (%s)", $ldConfFile, $!));
return;
}
while (<$ldconfFH>) {
chomp;
if (m{^\s*include\s+(.+?)\s*$}i) {
my @incFiles = glob("$self->{rootPath}$1");
foreach my $incFile (@incFiles) {
if ($incFile) {
$self->_fetchLoaderConfigFile($incFile);
}
}
next;
}
if (m{\S+}i) {
s[=.+][];
# remove any lib-type specifications (e.g. '=libc5')
push @{$self->{libFolders}}, "$self->{rootPath}$_";
}
}
close $ldconfFH
or die(_tr("unable to close file '%s' (%s)", $ldConfFile, $!));
return;
}
sub _addLibsForBinary
{
my $self = shift;
my $binary = shift;
# first do some checks:
warn _tr("analyzing '%s'...\n", $binary) if $self->{verbose};
my $fileInfo = `file --dereference --brief --mime $binary 2>/dev/null`;
if ($?) {
die _tr("unable to fetch file info for '%s', giving up!\n", $binary);
}
chomp $fileInfo;
warn _tr("\tinfo is: '%s'...\n", $fileInfo) if $self->{verbose};
if ($fileInfo !~ m[^application/(x-executable|x-shared)]i) {
# ignore anything that's not an executable or a shared library
warn _tr(
"%s: ignored, as it isn't an executable or a shared library\n",
$binary
);
next;
}
# fetch file info again, this time without '--mime' in order to get the architecture
# bitwidth:
$fileInfo = `file --dereference --brief $binary 2>/dev/null`;
if ($?) {
die _tr("unable to fetch file info for '%s', giving up!\n", $binary);
}
chomp $fileInfo;
warn _tr("\tinfo is: '%s'...\n", $fileInfo) if $self->{verbose};
my $bitwidth = ($fileInfo =~ m[64-bit]i) ? 64 : 32;
# determine whether binary is 32- or 64-bit platform
# now find out about needed libs, we first try objdump...
warn _tr("\ttrying objdump...\n") if $self->{verbose};
my $res = `objdump -p $binary 2>/dev/null`;
if (!$?) {
# find out if rpath is set for binary:
my $rpath;
if ($res =~ m[^\s*RPATH\s*(\S+)]im) {
$rpath = $1;
warn _tr("\trpath='%s'\n", $rpath) if $self->{verbose};
}
while ($res =~ m[^\s*NEEDED\s*(.+?)\s*$]gm) {
$self->_addLib($1, $bitwidth, $rpath);
}
} else {
# ...objdump failed, so we try readelf instead:
warn _tr("\ttrying readelf...\n") if $self->{verbose};
$res = `readelf -d $binary 2>/dev/null`;
if ($?) {
die _tr(
"neither objdump nor readelf seems to be installed, giving up!\n"
);
}
# find out if rpath is set for binary:
my $rpath;
if ($res =~ m{Library\s*rpath:\s*\[([^\]]+)}im) {
$rpath = $1;
warn _tr("\trpath='%s'\n", $rpath) if $self->{verbose};
}
while ($res =~ m{\(NEEDED\)[^\[]+\[(.+?)\]\s*$}gm) {
$self->_addLib($1, $bitwidth, $rpath);
}
}
return;
}
sub _addLib
{
my $self = shift;
my $lib = shift;
my $bitwidth = shift;
my $rpath = shift;
if (!exists $self->{libInfo}->{$lib}) {
my $libPath;
my @folders = @{$self->{libFolders}};
if (defined $rpath) {
# add rpath if given (explicit paths set during link stage)
push @folders, split ':', $rpath;
}
foreach my $folder (@folders) {
if (-e "$folder/$lib") {
# have library matching name, now check if the platform is ok, too:
my $libFileInfo =
`file --dereference --brief $folder/$lib 2>/dev/null`;
if ($?) {
die _tr("unable to fetch file info for '%s', giving up!\n",
$folder / $lib);
}
my $libBitwidth = ($libFileInfo =~ m[64-bit]i) ? 64 : 32;
if ($bitwidth != $libBitwidth) {
vlog(
0,
_tr(
'%s has wrong bitwidth (%s instead of %s)',
"$folder/$lib", $libBitwidth, $bitwidth
)
) if $self->{verbose};
next;
}
$libPath = "$folder/$lib";
last;
}
}
if (!defined $libPath) {
die _tr("unable to find lib %s!\n", $lib);
}
print "found $libPath\n" if $self->{verbose};
push @{$self->{libs}}, $libPath;
$self->{libInfo}->{$lib} = 1;
push @{$self->{filesToDo}}, $libPath;
}
return;
}
1;