Home | History | Annotate | Download | only in depcheck
      1 #!/usr/bin/perl
      2 #
      3 # CDDL HEADER START
      4 #
      5 # The contents of this file are subject to the terms of the
      6 # Common Development and Distribution License, Version 1.0 only
      7 # (the "License").  You may not use this file except in compliance
      8 # with the License.
      9 #
     10 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
     11 # or http://www.opensolaris.org/os/licensing.
     12 # See the License for the specific language governing permissions
     13 # and limitations under the License.
     14 #
     15 # When distributing Covered Code, include this CDDL HEADER in each
     16 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     17 # If applicable, add the following below this CDDL HEADER, with the
     18 # fields enclosed by brackets "[]" replaced with your own identifying
     19 # information: Portions Copyright [yyyy] [name of copyright owner]
     20 #
     21 # CDDL HEADER END
     22 #
     23 #
     24 # Copyright (c) 2000 by Sun Microsystems, Inc.
     25 # All rights reserved.
     26 #
     27 
     28 # ident	"%Z%%M%	%I%	%E% SMI"
     29 
     30 #
     31 # check for perl5 -- we use things unavailable in perl4
     32 #
     33 
     34 die "Sorry, this program requires perl version 5.000 or up. You have $]. Stopping" if $] < 5.000;
     35 
     36 $DBM_DIR_CHARACTERIZATION = "directory for the dbm databases";
     37 
     38 $Usage =
     39 "Usage: get_depend_info 
     40   -dbdir <$DBM_DIR_CHARACTERIZATION>  [ -s ] [ -cons ] [ -root directory ]
     41   [ -f ] [ -p ] [ -pkg SUNWxxx ] [ filename ]
     42   [-h for help]\n";
     43 
     44 $Help =
     45 "This program statically analyzes executable files and their
     46 symbolic links using /usr/bin/ldd and /usr/bin/strings.  It
     47 can accept filename(s) or packages as the list of files to be
     48 analyzed.  By default, the program will report the file
     49 dependencies and which packages those dependencies came from.
     50 There is one required argument:
     51 
     52         -dbdir  <dir>			the $DBM_DIR_CHARACTERIZATION
     53 
     54 The optional argument -h produces this message instead of any processing.
     55 The optional argument -cons tells the tool to be conservative and not to
     56 run /usr/bin/strings.
     57 The optional argument -root allows you to specify a new root (useful for
     58 doing analysis on build trees).
     59 The optional argument -pkg allows you to specify a package name.
     60 The optional argument -f only outputs the filename of the dependencies
     61 The optional argument -p only outputs the packanames of the dependencies
     62 
     63 The optional argument -s ONLY outputs symbolic links for files or packages.
     64 No ldd or strings analysis is done.
     65 
     66 Some Examples:
     67 get_depend_info -dbdir ./DBM /usr/bin/ls
     68 get_depend_info -dbdir ./DBM /usr/bin/*
     69 get_depend_info -dbdir ./DBM -pkg SUNWnisu
     70 get_depend_info -f -dbdir ./DBM -pkg SUNWnisu
     71 get_depend_info -s -dbdir ./DBM /usr/bin/*
     72 get_depend_info -s -dbdir ./DBM -pkg SUNWnisu
     73 
     74 
     75 NOTE: Run make_pkg_db to create the database directory for get_depend_info
     76 ";
     77 
     78 #
     79 # process arguments
     80 #
     81 
     82 @PkgList = "";
     83 $PackageOnly = false;
     84 $FileOnly = false;
     85 $Verbose = true;
     86 $Silent = false;
     87 $SymLink = false;
     88 $NoStrings = false;
     89 $Root = "";
     90 
     91 while (@ARGV) {
     92     $arg = shift (@ARGV);
     93     if ($arg eq "-h") {
     94         print "$Help\n$Usage";
     95         exit 0;
     96     } elsif ($arg eq "-dbdir") {
     97         $DBDir = shift(@ARGV) unless ($ARGV[0] =~ /^-/);
     98     } elsif ($arg eq "-s") {
     99 	$SymLink = true;
    100 	$Silent = true;
    101     } elsif ($arg eq "-p") {
    102 	$PackageOnly = true;
    103 	$Verbose = false;
    104     } elsif ($arg eq "-f") {
    105 	$FileOnly = true;
    106 	$Verbose = false;
    107     } elsif ($arg eq "-cons") {
    108 	$NoStrings = true;
    109     } elsif ($arg eq "-pkg") {
    110         $PKGName = shift(@ARGV) unless ($ARGV[0] =~ /^-/);
    111     } elsif ($arg eq "-root") {
    112         $Root = shift(@ARGV) unless ($ARGV[0] =~ /^-/);
    113     }else {
    114 	push(@filelist, $arg);
    115     }
    116 }
    117 
    118 if (!$DBDir) {
    119     print STDERR "Required argument -dbdir missing. \n$Usage";
    120     exit 1;
    121 }
    122 
    123 if ($PKGName) {
    124     # For a given pkg definition directory, this subroutine will
    125     # go through the proto files and look for executable files.
    126     # It will then put all the executable files into @filelist
    127     &HandlePackageName($PKGName);
    128 
    129     if ($PackageOnly eq true) {
    130         $Silent = true;
    131     }
    132 }
    133 
    134 &OpenDBs;
    135 
    136 $Silent = true if (($Verbose eq false) && ($PackageOnly eq false)
    137 			&& ($FileOnly eq false));
    138 
    139 foreach $entry (@filelist) {
    140 
    141 	print("\n\nAnalyzing $Root$entry:\n") unless ($Silent eq true);
    142 
    143         # make sure file exists
    144 	if (!(-r $entry)) {
    145     		print STDERR "Could not open file $entry\n";
    146     		next;
    147 	}
    148 
    149 
    150 	$file = $FTYPE{$entry};
    151 	$pkgs = $PKGS{$entry};
    152 	$abslink = $ABSLINK{$entry};
    153 
    154 	if ($file eq "d") {
    155 		print("Input file is a directory\n") unless ($Silent eq true);
    156     		next;
    157 	}
    158 
    159 	# destfile represents the actual file we are interested in!
    160 	if ($abslink =~ /\w/) {
    161 		$destfile = $abslink;
    162 
    163 		if (($FTYPE{$entry} eq "s") && ($SymLink eq true)) {
    164 			print("$entry is linked to $destfile:$PKGS{$destfile}\n");
    165 		}
    166 	}
    167 	else {
    168 		$destfile = $entry;
    169 	}
    170 
    171 	# if the -s flag is set, tell 'em about sym links and go to the next file
    172 	next if ($SymLink eq true);
    173 
    174 	$mode = $MODE{$destfile};
    175 
    176         # Handle the case where the user resets $ROOT
    177 	$destfile = "$Root$destfile" if ($Root =~ /\w/);
    178 	$filecmd = `/usr/bin/file $destfile 2>&1`;
    179 
    180 	# make sure we are dealing with an executable
    181 	if (($mode !~ /(.)(.*)7/) && ($mode !~ /(.)(.*)5/) && ($mode !~ /(.)(.*)3/) && ($mode !~ /(.)(.*)1/)){
    182 		print("Input file is not an executable\n");
    183 		next;
    184 	}
    185 
    186         # Kernel modules are handled separately
    187 	if ($destfile =~ /\/kernel\//) {
    188 		&HandleKernelMod($destfile, $FTYPE{$entry});
    189 		&OutputPackageList if (($PackageOnly eq true) && !($PKGName));
    190 		next;
    191 	}
    192 
    193 	# take care of scripts
    194 	if (($filecmd =~ /script/) || ($filecmd =~ /text/)) {
    195 		&HandleScripts($destfile);
    196 		&OutputPackageList if (($PackageOnly eq true) && !($PKGName));
    197 		next;
    198 	}
    199 
    200 	# Its not a script, not a kernel module, so its get to be a binary
    201 	&HandleBinaries($destfile);
    202 	&OutputPackageList if (($PackageOnly eq true) && !($PKGName));
    203 }
    204 
    205 if (($PKGName) && ($SymLink eq false)) {
    206 	print ("\n\nPackage dependencies for $PKGName:\n");
    207 	&OutputPackageList;
    208 }
    209 
    210 &CloseDBs;
    211 
    212 #===========================END OF MAIN====================================
    213 
    214 sub GetLddInfo {		# return multi-line string of ldd info for File
    215 local ($FileID, $FileType) = @_;
    216 
    217     $outstring = "* Not a File\n";
    218     return ($outstring) if $FileType =~ /[Mlsdc]/;	# ldd results not useful here
    219 
    220     #
    221     # use map file to see if this is a file that gives a known bad ldd return
    222     #
    223 
    224 #    if ($Unsup{$FileID} == 1) {
    225 #	$outstring = "* unsupported or unknown file type, per map file";
    226 #	return ($outstring);
    227 #    }
    228 #    $err = "";
    229 #    $string = `/usr/bin/ldd $FileID 2>&1`;
    230 #    if ($?) {	# if some error (don't just get wait status here)
    231 #	$errnum = 0 + $!;
    232 #	$err = "==$?==$errnum==";
    233 #	if (($err eq "==256==29==") || ($err eq "==256==0==")) {
    234 #	    $err = "*";			# these are normal ldd returns
    235 #	} else {
    236 #	    die "Unexpected ldd return $? $!";
    237 #	}
    238 #	$string =~ s/\/usr\/bin\/ldd:[^\0]*://g;	# trim up error line
    239 #    } elsif ($string =~ s/warning:.*://) {	# other normal ldd returns
    240 #	$err = "*";
    241 #    }
    242 
    243     $outstring = "";
    244     $string = `/usr/bin/ldd $FileID 2>&1`;
    245     # on a non-zero ldd, return nothing
    246     return ($outstring) if ($?);
    247 
    248 
    249     $outstring = "";
    250     @infolines = split(/\s*\n\s*/, $string);
    251     foreach $line (@infolines) {
    252 	$line =~ s/^\s+//;			# trim leading ws
    253 	next unless $line;			# skip if blank
    254 	@words = split(/\s/, $line);
    255 	$filename = $words[0];
    256 	$outstring .= "$filename\n";
    257     }
    258     return ($outstring);
    259 }
    260 
    261 sub CloseDBs {
    262 	# close the dbs
    263 	dbmclose(FTYPE);
    264 	dbmclose(MODE);
    265 	dbmclose(PKGS);
    266 	dbmclose(ABSLINK);
    267 	dbmclose(PKGNAMES);
    268 }
    269 
    270 sub OpenDBs {
    271 	# open the databases for read-only
    272 	dbmopen(%FTYPE, "$DBDir/FTYPE", 0664) ||
    273 		die"Cannot open dbm db $DBDir/FTYPE\n";
    274 
    275 	dbmopen(%MODE, "$DBDir/MODE", 0664) ||
    276 		die"Cannot open dbm db $DBDir/MODE\n";
    277 
    278 	dbmopen(%PKGS, "$DBDir/PKGS", 0664) ||
    279 		die"Cannot open dbm db $DBDir/PKGS\n";
    280 
    281 	dbmopen(%ABSLINK, "$DBDir/ABSLINK", 0664) ||
    282 		die"Cannot open dbm db $DBDir/ABSLINK \n";
    283 
    284 	dbmopen(%PKGNAMES, "$DBDir/PKGNAMES", 0644) ||
    285 		die"Cannot open dbm db $DBDir/PKGNAMES\n";
    286 }
    287 
    288 sub HandleKernelMod {
    289 local ($entry, $ftype) = @_;
    290 
    291 	# search for the magic right, starting from the right (ie. end of path)
    292 	$index = rindex($entry, "kernel");
    293 	# rindex() returns where the "kernel" began, add 6 to get
    294 	# "{some path}/kernel"
    295 	$index += 6;
    296 	# OK, now pull out the absolute path
    297 	$KernelPath = substr($entry, 0, $index);
    298 
    299 	# There are two ways to figure out the dependencies.
    300 	# First, we check to see if /usr/bin/ldd will tell us.
    301 	# If ldd fails, then we need to look at the output of /usr/bin/strings
    302 
    303 	$LddInfo = "";
    304 	$LddInfo = &GetLddInfo($entry, $ftype);
    305 
    306 	if ($LddInfo =~ /\w/) {
    307 		@list = "";
    308 		@list = split(/\n/, $LddInfo);
    309 		foreach $file (@list) {
    310 			$found = 0;
    311 
    312 			# first, check to see if there is a module relative to
    313 			# this file
    314 			if ($FTYPE{"$KernelPath/$file"} =~ /\w/){
    315 				&Output("$KernelPath/$file");
    316 				$found++;
    317 			}
    318 
    319 			# Haven't found it yet, check /kernel
    320 			if (($FTYPE{"/kernel/$file"} =~ /\w/) && ($found == 0)){
    321 				&Output("/kernel/$file");
    322 				$found++;
    323 			}
    324 
    325 			# Haven't found it yet, check /usr/kernel
    326 			if (($FTYPE{"/usr/kernel/$file"} =~ /\w/) && ($found == 0)){
    327 				&Output("/usr/kernel/$file");
    328 				$found++;
    329 			}
    330 
    331 			if ($found == 0) {
    332 				print("Could not resolve $file\n");
    333 			}
    334 		}
    335 		return;
    336 	}
    337 
    338 	# the ldd failed, so now let's look at the string output
    339 	$string = "";
    340 	@infolines = "";
    341 	@outlines = "";
    342 
    343     	$string = `/usr/bin/strings $entry 2>&1`;
    344     	@infolines = split(/\s*\n\s*/, $string);
    345 
    346 	foreach $line (@infolines) {
    347 		if ($line =~ /\//){
    348 			push (@outlines,$line);
    349 		}
    350     	}
    351 
    352 	foreach $line (@outlines) {
    353 		@words = split(/\s/, $line);
    354 		foreach $word (@words) {
    355 			$found = 0;
    356 
    357 			# first, check to see if there is a module relative to
    358 			# this file
    359 			if ($FTYPE{"$KernelPath/$word"} =~ /\w/){
    360 				&Output("$KernelPath/$word");
    361 				$found++;
    362 			}
    363 
    364 			# Haven't found it yet, check /kernel
    365 			if (($FTYPE{"/kernel/$word"} =~ /\w/) && ($found == 0)){
    366 				&Output("/kernel/$word");
    367 				$found++;
    368 			}
    369 
    370 			# Haven't found it yet, check /usr/kernel
    371 			if (($FTYPE{"/usr/kernel/$word"} =~ /\w/) && ($found == 0)){
    372 				&Output("/usr/kernel/$word");
    373 				$found++;
    374 			}
    375 		}
    376 	}
    377 }
    378 
    379 sub GetStringsInfo {		# return multi-line string of ldd info for File
    380 local ($FileID, $FileType) = @_;
    381 
    382     $outstring = "* Not a File\n";
    383     return ($outstring) if $FileType =~ /[Mlsdc]/;	# ldd results not useful here
    384     return ($outstring) if ($NoStrings eq true);	# we are running in conservative mode
    385 
    386     # use map file to see if this is a file that gives a known bad ldd return
    387     if ($Unsup{$FileID} == 1) {
    388 	$outstring = "* unsupported or unknown file type, per map file";
    389 	return ($outstring);
    390     }
    391     $err = "";
    392     $string = "";
    393     $string = `/usr/bin/strings $FileID 2>&1`;
    394 
    395     $outstring = "";
    396     @infolines = "";
    397     @outlines = "";
    398     @infolines = split(/\s*\n\s*/, $string);
    399 
    400     foreach $line (@infolines) {
    401 	if (($line =~ /\//) && !($line =~ /%/) && !($line =~ m%/$%)){
    402 		push (@outlines,$line);
    403         }
    404     }
    405     @outlines = sort(@outlines);
    406 
    407     foreach $word (@outlines) {
    408 	if ($lastword ne $word) {
    409 		$outstring .= $word; $outstring .= "\n";
    410 	}
    411 	$lastword = $word;
    412     }
    413     return ($outstring);
    414 }
    415 
    416 sub HandleScripts {
    417 local ($filename) = @_;
    418 	open(SCRIPT, $filename);
    419 
    420 	undef @output;
    421 	while (<SCRIPT>) {
    422 		s/^\s+//;		# trim leading ws
    423 		s/=/ /g;		# get rid of all =
    424 		s/\`/ /g;		# get rid of all `
    425 		next if ($_ =~ /^#/);	# strip out obvious comments
    426 		next unless $_;		# skip if blank
    427 
    428 		$line = $_;
    429 		@words = split(/\s/, $line);
    430 		foreach $word (@words) {
    431 			if (($PKGS{$word} =~ /\w/) && ($FTYPE{$word} ne "d")) {
    432 				push(@output, $word);
    433 			}
    434 		}
    435 	}
    436 
    437 	@output = sort(@output);
    438 	$count = 0;
    439 
    440 	# make sure we don't output dupes
    441 	foreach $file (@output) {
    442 		if ($count == 0) {
    443 			&Output($file);
    444 		}
    445 
    446 		if (($count > 0) && ($output[$count] ne $output[$count-1])) {
    447 			&Output($file);
    448 		}
    449 		$count++;
    450 	}
    451 
    452 	# remember to play nice
    453 	close(SCRIPT);
    454 }
    455 
    456 sub HandleBinaries {
    457 local ($filename) = @_;
    458 	$LddInfo = &GetLddInfo($destfile, $FTYPE{$filename});
    459 	$StringInfo = &GetStringsInfo($destfile, $FTYPE{$filename});
    460 
    461 	# Parse the ldd output.
    462 	# Libs can be found in /kernel or /usr/lib
    463 	@list = split(/\n/, $LddInfo);
    464 	foreach $file (@list) {
    465 		$found = 0;
    466 		if ($FTYPE{"/kernel/$file"} =~ /\w/){
    467 			&Output("/kernel/$file");
    468 			$found++;
    469 		}
    470 
    471 		if ($FTYPE{"/usr/lib/$file"} =~ /\w/){
    472 			&Output("/usr/lib/$file");
    473 			$found++;
    474 		}
    475 
    476 		if ($found == 0) {
    477 			print("Could not resolve $file\n");
    478 		}
    479 	}
    480 
    481 	# For the strings output, we parse it to see if we can match it to
    482 	# any files distributed in a package.
    483 	@list = split(/\n/, $StringInfo);
    484 	foreach $file (@list) {
    485 		if (($FTYPE{$file} =~ /\w/) && ($FTYPE{$file} ne "d")) {
    486 			&Output($file);
    487 		}
    488 	}
    489 }
    490 
    491 sub Output {
    492 local($filename) = @_;
    493 	
    494         # If they want a package listing, a unique sorted list
    495         # will be outputted later.  Here we simply push elements onto
    496 	# this list.
    497 	if ($PKGName) {
    498 		push(@PkgList, "$PKGS{$filename}\n");
    499 	}
    500 
    501 	if ($Verbose eq true) {
    502 		print("$filename:$PKGS{$filename}\n");
    503 		return;
    504 	}
    505 
    506         # If they want a package listing, a unique sorted list
    507         # will be outputted later.  Here we simply push elements onto
    508 	# this list.
    509 	if ($PackageOnly eq true) {
    510 		push(@PkgList, "$PKGS{$filename}\n");
    511 		return;
    512 	}
    513 
    514 	if ($FileOnly eq true) {
    515 		print("$filename\n");
    516 		return;
    517 	}
    518 }
    519 
    520 sub HandlePackageName {
    521 local($pkg) = @_;
    522 	$pkgchk = `/usr/sbin/pkgchk -l $pkg | grep Pathname | sed 's/Pathname: //'`;
    523 
    524 	@files = split(/\n/, $pkgchk);
    525 	foreach $file (@files) {
    526 		push(@filelist, $file);
    527 	}
    528 }
    529 
    530 sub OutputPackageList {
    531 local($filename) = @_;
    532 	# If the user specified a package list, here we sort
    533 	# the list and make sure we don't output dupes.
    534 	$lastpkg = "";
    535 	@outPkgs = sort(@PkgList);
    536 
    537 	foreach $pkg (@outPkgs) {
    538 		$pkg =~ s/\s*$//;   # trim extra space off the end
    539 
    540 		# make sure this entry isn't a dupe before
    541 		# printing it
    542 		if ($lastpkg ne $pkg) {
    543 			print("P $pkg\t$PKGNAMES{$pkg}\n");
    544 		}
    545 
    546 		$lastpkg = $pkg;
    547 	}
    548 
    549 	# reset the list for the next entry
    550 	@PkgList = "";
    551 }
    552 
    553