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