xref: /dflybsd-src/tools/LibraryReport/LibraryReport.tcl (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
1*86d7f5d3SJohn Marino#!/bin/sh
2*86d7f5d3SJohn Marino# tcl magic \
3*86d7f5d3SJohn Marinoexec tclsh $0 $*
4*86d7f5d3SJohn Marino################################################################################
5*86d7f5d3SJohn Marino# Copyright (C) 1997
6*86d7f5d3SJohn Marino#      Michael Smith.  All rights reserved.
7*86d7f5d3SJohn Marino#
8*86d7f5d3SJohn Marino# Redistribution and use in source and binary forms, with or without
9*86d7f5d3SJohn Marino# modification, are permitted provided that the following conditions
10*86d7f5d3SJohn Marino# are met:
11*86d7f5d3SJohn Marino# 1. Redistributions of source code must retain the above copyright
12*86d7f5d3SJohn Marino#    notice, this list of conditions and the following disclaimer.
13*86d7f5d3SJohn Marino# 2. Redistributions in binary form must reproduce the above copyright
14*86d7f5d3SJohn Marino#    notice, this list of conditions and the following disclaimer in the
15*86d7f5d3SJohn Marino#    documentation and/or other materials provided with the distribution.
16*86d7f5d3SJohn Marino# 3. Neither the name of the author nor the names of any co-contributors
17*86d7f5d3SJohn Marino#    may be used to endorse or promote products derived from this software
18*86d7f5d3SJohn Marino#    without specific prior written permission.
19*86d7f5d3SJohn Marino#
20*86d7f5d3SJohn Marino# THIS SOFTWARE IS PROVIDED BY Michael Smith AND CONTRIBUTORS ``AS IS'' AND
21*86d7f5d3SJohn Marino# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22*86d7f5d3SJohn Marino# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23*86d7f5d3SJohn Marino# ARE DISCLAIMED.  IN NO EVENT SHALL Michael Smith OR CONTRIBUTORS BE LIABLE
24*86d7f5d3SJohn Marino# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25*86d7f5d3SJohn Marino# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26*86d7f5d3SJohn Marino# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27*86d7f5d3SJohn Marino# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28*86d7f5d3SJohn Marino# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29*86d7f5d3SJohn Marino# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30*86d7f5d3SJohn Marino# SUCH DAMAGE.
31*86d7f5d3SJohn Marino################################################################################
32*86d7f5d3SJohn Marino#
33*86d7f5d3SJohn Marino# LibraryReport; produce a list of shared libraries on the system, and a list of
34*86d7f5d3SJohn Marino# all executables that use them.
35*86d7f5d3SJohn Marino#
36*86d7f5d3SJohn Marino################################################################################
37*86d7f5d3SJohn Marino#
38*86d7f5d3SJohn Marino# Stage 1 looks for shared libraries; the output of 'ldconfig -r' is examined
39*86d7f5d3SJohn Marino# for hints as to where to look for libraries (but not trusted as a complete
40*86d7f5d3SJohn Marino# list).
41*86d7f5d3SJohn Marino#
42*86d7f5d3SJohn Marino# These libraries each get an entry in the global 'Libs()' array.
43*86d7f5d3SJohn Marino#
44*86d7f5d3SJohn Marino# Stage 2 walks the entire system directory heirachy looking for executable
45*86d7f5d3SJohn Marino# files, applies 'ldd' to them and attempts to determine which libraries are
46*86d7f5d3SJohn Marino# used.  The path of the executable is then added to the 'Libs()' array
47*86d7f5d3SJohn Marino# for each library used.
48*86d7f5d3SJohn Marino#
49*86d7f5d3SJohn Marino# Stage 3 reports on the day's findings.
50*86d7f5d3SJohn Marino#
51*86d7f5d3SJohn Marino################################################################################
52*86d7f5d3SJohn Marino#
53*86d7f5d3SJohn Marino# $FreeBSD: src/tools/LibraryReport/LibraryReport.tcl,v 1.5 1999/08/28 00:54:21 peter Exp $
54*86d7f5d3SJohn Marino# $DragonFly: src/tools/LibraryReport/LibraryReport.tcl,v 1.2 2003/06/17 04:29:11 dillon Exp $
55*86d7f5d3SJohn Marino#
56*86d7f5d3SJohn Marino
57*86d7f5d3SJohn Marino#########################################################################################
58*86d7f5d3SJohn Marino# findLibs
59*86d7f5d3SJohn Marino#
60*86d7f5d3SJohn Marino# Ask ldconfig where it thinks libraries are to be found.  Go look for them, and
61*86d7f5d3SJohn Marino# add an element to 'Libs' for everything that looks like a library.
62*86d7f5d3SJohn Marino#
63*86d7f5d3SJohn Marinoproc findLibs {} {
64*86d7f5d3SJohn Marino
65*86d7f5d3SJohn Marino    global Libs stats verbose;
66*86d7f5d3SJohn Marino
67*86d7f5d3SJohn Marino    # Older ldconfigs return a junk value when asked for a report
68*86d7f5d3SJohn Marino    if {[catch {set liblist [exec ldconfig -r]} err]} {	# get ldconfig output
69*86d7f5d3SJohn Marino	puts stderr "ldconfig returned nonzero, persevering.";
70*86d7f5d3SJohn Marino	set liblist $err;				# there's junk in this
71*86d7f5d3SJohn Marino    }
72*86d7f5d3SJohn Marino
73*86d7f5d3SJohn Marino    # remove hintsfile name, convert to list
74*86d7f5d3SJohn Marino    set liblist [lrange [split $liblist "\n"] 1 end];
75*86d7f5d3SJohn Marino
76*86d7f5d3SJohn Marino    set libdirs "";				# no directories yet
77*86d7f5d3SJohn Marino    foreach line $liblist {
78*86d7f5d3SJohn Marino	# parse ldconfig output
79*86d7f5d3SJohn Marino	if {[scan $line "%s => %s" junk libname] == 2} {
80*86d7f5d3SJohn Marino	    # find directory name
81*86d7f5d3SJohn Marino	    set libdir [file dirname $libname];
82*86d7f5d3SJohn Marino	    # have we got this one already?
83*86d7f5d3SJohn Marino	    if {[lsearch -exact $libdirs $libdir] == -1} {
84*86d7f5d3SJohn Marino		lappend libdirs $libdir;
85*86d7f5d3SJohn Marino	    }
86*86d7f5d3SJohn Marino	} else {
87*86d7f5d3SJohn Marino	    puts stderr "Unparseable ldconfig output line :";
88*86d7f5d3SJohn Marino	    puts stderr $line;
89*86d7f5d3SJohn Marino	}
90*86d7f5d3SJohn Marino    }
91*86d7f5d3SJohn Marino
92*86d7f5d3SJohn Marino    # libdirs is now a list of directories that we might find libraries in
93*86d7f5d3SJohn Marino    foreach dir $libdirs {
94*86d7f5d3SJohn Marino	# get the names of anything that looks like a library
95*86d7f5d3SJohn Marino	set libnames [glob -nocomplain "$dir/lib*.so.*"]
96*86d7f5d3SJohn Marino	foreach lib $libnames {
97*86d7f5d3SJohn Marino	    set type [file type $lib];			# what is it?
98*86d7f5d3SJohn Marino	    switch $type {
99*86d7f5d3SJohn Marino		file {		# looks like a library
100*86d7f5d3SJohn Marino		    # may have already been referenced by a symlink
101*86d7f5d3SJohn Marino		    if {![info exists Libs($lib)]} {
102*86d7f5d3SJohn Marino			set Libs($lib) "";		# add it to our list
103*86d7f5d3SJohn Marino			if {$verbose} {puts "+ $lib";}
104*86d7f5d3SJohn Marino		    }
105*86d7f5d3SJohn Marino		}
106*86d7f5d3SJohn Marino		link {		# symlink; probably to another library
107*86d7f5d3SJohn Marino		    # If the readlink fails, the symlink is stale
108*86d7f5d3SJohn Marino		    if {[catch {set ldest [file readlink $lib]}]} {
109*86d7f5d3SJohn Marino			puts stderr "Symbolic link points to nothing : $lib";
110*86d7f5d3SJohn Marino		    } else {
111*86d7f5d3SJohn Marino			# may have already been referenced by another symlink
112*86d7f5d3SJohn Marino			if {![info exists Libs($lib)]} {
113*86d7f5d3SJohn Marino			    set Libs($lib) "";		# add it to our list
114*86d7f5d3SJohn Marino			    if {$verbose} {puts "+ $lib";}
115*86d7f5d3SJohn Marino			}
116*86d7f5d3SJohn Marino			# list the symlink as a consumer of this library
117*86d7f5d3SJohn Marino			lappend Libs($ldest) "($lib)";
118*86d7f5d3SJohn Marino			if {$verbose} {puts "-> $ldest";}
119*86d7f5d3SJohn Marino		    }
120*86d7f5d3SJohn Marino		}
121*86d7f5d3SJohn Marino	    }
122*86d7f5d3SJohn Marino	}
123*86d7f5d3SJohn Marino    }
124*86d7f5d3SJohn Marino    set stats(libs) [llength [array names Libs]];
125*86d7f5d3SJohn Marino}
126*86d7f5d3SJohn Marino
127*86d7f5d3SJohn Marino################################################################################
128*86d7f5d3SJohn Marino# findLibUsers
129*86d7f5d3SJohn Marino#
130*86d7f5d3SJohn Marino# Look in the directory (dir) for executables.  If we find any, call
131*86d7f5d3SJohn Marino# examineExecutable to see if it uses any shared libraries.  Call ourselves
132*86d7f5d3SJohn Marino# on any directories we find.
133*86d7f5d3SJohn Marino#
134*86d7f5d3SJohn Marino# Note that the use of "*" as a glob pattern means we miss directories and
135*86d7f5d3SJohn Marino# executables starting with '.'.  This is a Feature.
136*86d7f5d3SJohn Marino#
137*86d7f5d3SJohn Marinoproc findLibUsers {dir} {
138*86d7f5d3SJohn Marino
139*86d7f5d3SJohn Marino    global stats verbose;
140*86d7f5d3SJohn Marino
141*86d7f5d3SJohn Marino    if {[catch {
142*86d7f5d3SJohn Marino	set ents [glob -nocomplain "$dir/*"];
143*86d7f5d3SJohn Marino    } msg]} {
144*86d7f5d3SJohn Marino	if {$msg == ""} {
145*86d7f5d3SJohn Marino	    set msg "permission denied";
146*86d7f5d3SJohn Marino	}
147*86d7f5d3SJohn Marino	puts stderr "Can't search under '$dir' : $msg";
148*86d7f5d3SJohn Marino	return ;
149*86d7f5d3SJohn Marino    }
150*86d7f5d3SJohn Marino
151*86d7f5d3SJohn Marino    if {$verbose} {puts "===>> $dir";}
152*86d7f5d3SJohn Marino    incr stats(dirs);
153*86d7f5d3SJohn Marino
154*86d7f5d3SJohn Marino    # files?
155*86d7f5d3SJohn Marino    foreach f $ents {
156*86d7f5d3SJohn Marino	# executable?
157*86d7f5d3SJohn Marino	if {[file executable $f]} {
158*86d7f5d3SJohn Marino	    # really a file?
159*86d7f5d3SJohn Marino	    if {[file isfile $f]} {
160*86d7f5d3SJohn Marino		incr stats(files);
161*86d7f5d3SJohn Marino		examineExecutable $f;
162*86d7f5d3SJohn Marino	    }
163*86d7f5d3SJohn Marino	}
164*86d7f5d3SJohn Marino    }
165*86d7f5d3SJohn Marino    # subdirs?
166*86d7f5d3SJohn Marino    foreach f $ents {
167*86d7f5d3SJohn Marino	# maybe a directory with more files?
168*86d7f5d3SJohn Marino	# don't use 'file isdirectory' because that follows symlinks
169*86d7f5d3SJohn Marino	if {[catch {set type [file type $f]}]} {
170*86d7f5d3SJohn Marino	    continue ;		# may not be able to stat
171*86d7f5d3SJohn Marino	}
172*86d7f5d3SJohn Marino	if {$type == "directory"} {
173*86d7f5d3SJohn Marino	    findLibUsers $f;
174*86d7f5d3SJohn Marino	}
175*86d7f5d3SJohn Marino    }
176*86d7f5d3SJohn Marino}
177*86d7f5d3SJohn Marino
178*86d7f5d3SJohn Marino################################################################################
179*86d7f5d3SJohn Marino# examineExecutable
180*86d7f5d3SJohn Marino#
181*86d7f5d3SJohn Marino# Look at (fname) and see if ldd thinks it references any shared libraries.
182*86d7f5d3SJohn Marino# If it does, update Libs with the information.
183*86d7f5d3SJohn Marino#
184*86d7f5d3SJohn Marinoproc examineExecutable {fname} {
185*86d7f5d3SJohn Marino
186*86d7f5d3SJohn Marino    global Libs stats verbose;
187*86d7f5d3SJohn Marino
188*86d7f5d3SJohn Marino    # ask Mr. Ldd.
189*86d7f5d3SJohn Marino    if {[catch {set result [exec ldd $fname]} msg]} {
190*86d7f5d3SJohn Marino	return ;	# not dynamic
191*86d7f5d3SJohn Marino    }
192*86d7f5d3SJohn Marino
193*86d7f5d3SJohn Marino    if {$verbose} {puts -nonewline "$fname : ";}
194*86d7f5d3SJohn Marino    incr stats(execs);
195*86d7f5d3SJohn Marino
196*86d7f5d3SJohn Marino    # For a non-shared executable, we get a single-line error message.
197*86d7f5d3SJohn Marino    # For a shared executable, we get a heading line, so in either case
198*86d7f5d3SJohn Marino    # we can discard the first line and any subsequent lines are libraries
199*86d7f5d3SJohn Marino    # that are required.
200*86d7f5d3SJohn Marino    set llist [lrange [split $result "\n"] 1 end];
201*86d7f5d3SJohn Marino    set uses "";
202*86d7f5d3SJohn Marino
203*86d7f5d3SJohn Marino    foreach line $llist {
204*86d7f5d3SJohn Marino	if {[scan $line "%s => %s %s" junk1 lib junk2] == 3} {
205*86d7f5d3SJohn Marino	    if {$lib == "not"} {	# "not found" error
206*86d7f5d3SJohn Marino		set mlname [string range $junk1 2 end];
207*86d7f5d3SJohn Marino		puts stderr "$fname : library '$mlname' not known.";
208*86d7f5d3SJohn Marino	    } else {
209*86d7f5d3SJohn Marino		lappend Libs($lib) $fname;
210*86d7f5d3SJohn Marino		lappend uses $lib;
211*86d7f5d3SJohn Marino	    }
212*86d7f5d3SJohn Marino	} else {
213*86d7f5d3SJohn Marino	    puts stderr "Unparseable ldd output line :";
214*86d7f5d3SJohn Marino	    puts stderr $line;
215*86d7f5d3SJohn Marino	}
216*86d7f5d3SJohn Marino    }
217*86d7f5d3SJohn Marino    if {$verbose} {puts "$uses";}
218*86d7f5d3SJohn Marino}
219*86d7f5d3SJohn Marino
220*86d7f5d3SJohn Marino################################################################################
221*86d7f5d3SJohn Marino# emitLibDetails
222*86d7f5d3SJohn Marino#
223*86d7f5d3SJohn Marino# Emit a listing of libraries and the executables that use them.
224*86d7f5d3SJohn Marino#
225*86d7f5d3SJohn Marinoproc emitLibDetails {} {
226*86d7f5d3SJohn Marino
227*86d7f5d3SJohn Marino    global Libs;
228*86d7f5d3SJohn Marino
229*86d7f5d3SJohn Marino    # divide into used/unused
230*86d7f5d3SJohn Marino    set used "";
231*86d7f5d3SJohn Marino    set unused "";
232*86d7f5d3SJohn Marino    foreach lib [array names Libs] {
233*86d7f5d3SJohn Marino	if {$Libs($lib) == ""} {
234*86d7f5d3SJohn Marino	    lappend unused $lib;
235*86d7f5d3SJohn Marino	} else {
236*86d7f5d3SJohn Marino	    lappend used $lib;
237*86d7f5d3SJohn Marino	}
238*86d7f5d3SJohn Marino    }
239*86d7f5d3SJohn Marino
240*86d7f5d3SJohn Marino    # emit used list
241*86d7f5d3SJohn Marino    puts "== Current Shared Libraries ==================================================";
242*86d7f5d3SJohn Marino    foreach lib [lsort $used] {
243*86d7f5d3SJohn Marino	# sort executable names
244*86d7f5d3SJohn Marino	set users [lsort $Libs($lib)];
245*86d7f5d3SJohn Marino	puts [format "%-30s  %s" $lib $users];
246*86d7f5d3SJohn Marino    }
247*86d7f5d3SJohn Marino    # emit unused
248*86d7f5d3SJohn Marino    puts "== Stale Shared Libraries ====================================================";
249*86d7f5d3SJohn Marino    foreach lib [lsort $unused] {
250*86d7f5d3SJohn Marino	# sort executable names
251*86d7f5d3SJohn Marino	set users [lsort $Libs($lib)];
252*86d7f5d3SJohn Marino	puts [format "%-30s  %s" $lib $users];
253*86d7f5d3SJohn Marino    }
254*86d7f5d3SJohn Marino}
255*86d7f5d3SJohn Marino
256*86d7f5d3SJohn Marino################################################################################
257*86d7f5d3SJohn Marino# Run the whole shebang
258*86d7f5d3SJohn Marino#
259*86d7f5d3SJohn Marinoproc main {} {
260*86d7f5d3SJohn Marino
261*86d7f5d3SJohn Marino    global stats verbose argv;
262*86d7f5d3SJohn Marino
263*86d7f5d3SJohn Marino    set verbose 0;
264*86d7f5d3SJohn Marino    foreach arg $argv {
265*86d7f5d3SJohn Marino	switch -- $arg {
266*86d7f5d3SJohn Marino	    -v {
267*86d7f5d3SJohn Marino		set verbose 1;
268*86d7f5d3SJohn Marino	    }
269*86d7f5d3SJohn Marino	    default {
270*86d7f5d3SJohn Marino		puts stderr "Unknown option '$arg'.";
271*86d7f5d3SJohn Marino		exit ;
272*86d7f5d3SJohn Marino	    }
273*86d7f5d3SJohn Marino	}
274*86d7f5d3SJohn Marino    }
275*86d7f5d3SJohn Marino
276*86d7f5d3SJohn Marino    set stats(libs) 0;
277*86d7f5d3SJohn Marino    set stats(dirs) 0;
278*86d7f5d3SJohn Marino    set stats(files) 0;
279*86d7f5d3SJohn Marino    set stats(execs) 0
280*86d7f5d3SJohn Marino
281*86d7f5d3SJohn Marino    findLibs;
282*86d7f5d3SJohn Marino    findLibUsers "/";
283*86d7f5d3SJohn Marino    emitLibDetails;
284*86d7f5d3SJohn Marino
285*86d7f5d3SJohn Marino    puts [format "Searched %d directories, %d executables (%d dynamic) for %d libraries." \
286*86d7f5d3SJohn Marino	      $stats(dirs) $stats(files) $stats(execs) $stats(libs)];
287*86d7f5d3SJohn Marino}
288*86d7f5d3SJohn Marino
289*86d7f5d3SJohn Marino################################################################################
290*86d7f5d3SJohn Marinomain;
291