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