xref: /titanic_50/usr/src/tools/depcheck/get_depend_info (revision 7ff178cd8db129d385d3177eb20744d3b6efc59b)
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
34die "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
46symbolic links using /usr/bin/ldd and /usr/bin/strings.  It
47can accept filename(s) or packages as the list of files to be
48analyzed.  By default, the program will report the file
49dependencies and which packages those dependencies came from.
50There is one required argument:
51
52        -dbdir  <dir>			the $DBM_DIR_CHARACTERIZATION
53
54The optional argument -h produces this message instead of any processing.
55The optional argument -cons tells the tool to be conservative and not to
56run /usr/bin/strings.
57The optional argument -root allows you to specify a new root (useful for
58doing analysis on build trees).
59The optional argument -pkg allows you to specify a package name.
60The optional argument -f only outputs the filename of the dependencies
61The optional argument -p only outputs the packanames of the dependencies
62
63The optional argument -s ONLY outputs symbolic links for files or packages.
64No ldd or strings analysis is done.
65
66Some Examples:
67get_depend_info -dbdir ./DBM /usr/bin/ls
68get_depend_info -dbdir ./DBM /usr/bin/*
69get_depend_info -dbdir ./DBM -pkg SUNWnisu
70get_depend_info -f -dbdir ./DBM -pkg SUNWnisu
71get_depend_info -s -dbdir ./DBM /usr/bin/*
72get_depend_info -s -dbdir ./DBM -pkg SUNWnisu
73
74
75NOTE: 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
91while (@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
118if (!$DBDir) {
119    print STDERR "Required argument -dbdir missing. \n$Usage";
120    exit 1;
121}
122
123if ($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
139foreach $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
205if (($PKGName) && ($SymLink eq false)) {
206	print ("\n\nPackage dependencies for $PKGName:\n");
207	&OutputPackageList;
208}
209
210&CloseDBs;
211
212#===========================END OF MAIN====================================
213
214sub GetLddInfo {		# return multi-line string of ldd info for File
215local ($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
261sub CloseDBs {
262	# close the dbs
263	dbmclose(FTYPE);
264	dbmclose(MODE);
265	dbmclose(PKGS);
266	dbmclose(ABSLINK);
267	dbmclose(PKGNAMES);
268}
269
270sub 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
288sub HandleKernelMod {
289local ($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
379sub GetStringsInfo {		# return multi-line string of ldd info for File
380local ($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
416sub HandleScripts {
417local ($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
456sub HandleBinaries {
457local ($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
491sub Output {
492local($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
520sub HandlePackageName {
521local($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
530sub OutputPackageList {
531local($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