summaryrefslogblamecommitdiffstats
path: root/initramfs/OpenSLX/LibScanner.pm
blob: c5cef34a2d32214e172f7d42ccef5f07d9bd3794 (plain) (tree)





































































































































































































































































                                                                                                   
# 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;