#!/usr/bin/env perl
use strict;
use IPC::Open2;

if ($ARGV[0] eq '--version') {
	print "scandeps version 1.0\n";
	print "Copright (c) 2005 Hongli Lai\n";
	print "Licensed under the GNU General Public License v2.\n";
	exit;
} elsif ($ARGV[0] eq '--help') {
	print "Usage: scandeps [FILES...]\n";
	print "This script scans ELF binaries' library dependancies and prints a nice report\n" .
		"of the dependancies.\n";
	exit;
}


# First, generate a list of binaries. Either scan user-specified files,
# or scan all files in the current directory (including subfolders).
my @files;

if (@ARGV > 0) {
	# Scan user-specified files
	@files = @ARGV;
} else {
	# Scan directory recursively
	@files = scandir(".");
}

# Now get the dependancies for all binaries
# %fileDeps: hash which contains a list of dependancies. The filename is the key.
# %depFiles: hash which contains a list of files. The dependancy is the key.
my (%fileDeps, %depFiles);
getdeps(\%fileDeps, \%depFiles, \@files);

print "Common dependancies:\n";
foreach my $dep (keys %depFiles) {
	if (@{$depFiles{$dep}} == @files) {
		print " $dep\n";
	}
}
print "\n";

print "All dependancies:\n";
my @keys = keys %depFiles;
@keys = sort { @{$depFiles{$b}} - @{$depFiles{$a}} } @keys;
foreach my $dep (@keys) {
	my $num = scalar(@{$depFiles{$dep}});
	my $word = ($num > 1) ? "files" : "file";
	printf " %-40s (used by %3d $word)\n", $dep, $num;
}

print "\n";
print "Dependancies and associated binaries:\n";
foreach my $dep (@keys) {
	print " $dep:\n";
	if (@{$depFiles{$dep}} == @files) {
		print "    All files depend on this library.\n";
	} else {
		foreach my $file (@{$depFiles{$dep}}) {
			print "    $file\n";
		}
	}
	print "\n";
}


##################################

sub fatal {
	print STDERR $_[0];
	exit 1;
}

sub isELF {
	my ($f, $data);

	if (!open($f, $_[0])) {
		print STDERR "Warning: cannot open file '$_[0]' for reading.\n";
		return 0;
	}

	binmode $f;
	sysread $f, $data, 4;
	close $f;
	return $data eq "\177ELF";
}

sub scandir {
	my $d;

	if (!opendir($d, $_[0])) {
		print STDERR "Warning: cannot open directory '$_[0]' for reading.\n";
		return;
	}

	my @files;
	foreach my $file (readdir($d)) {
		next if ($file eq "." || $file eq "..");

		$file = "$_[0]/$file";
		if (-d $file) {
			# Recurse into subdirectory
			push @files, scandir("$file");

		} elsif (-f $file && -x $file && isELF($file)) {
			push @files, $file;
		}
	}
	closedir($d);
	return @files;
}

sub getdeps {
	my ($fileDeps, $depFiles, $files) = @_;
	my ($r, $w);
	if (!open2($r, $w, 'objdump', '-p', @{$files})) {
		fatal("Cannot communicate with objdump.\n");
	}
	close $w;

	my $currentFile;
	foreach my $line (<$r>) {
		if ($line =~ /^(.+):[ \t]+file format /) {
			$currentFile = $1;
			$currentFile =~ s/^.\///;

		} elsif ($line =~ /NEEDED[ \t]+(.+)$/) {
			$fileDeps->{$currentFile} = [] if (!exists $fileDeps->{$currentFile});
			push @{$fileDeps->{$currentFile}}, $1;

			$depFiles->{$1} = [] if (!exists $depFiles->{$1});
			push @{$depFiles->{$1}}, $currentFile;
		}
	}
	close $r;
}
