xref: /freebsd/tools/LibraryReport/LibraryReport.tcl (revision 61afd5bb22d787b0641523e7b9b95c964d669bd5)
1#!/bin/sh
2# tcl magic \
3exec tclsh $0 $*
4################################################################################
5#
6# LibraryReport; produce a list of shared libraries on the system, and a list of
7# all executables that use them.
8#
9################################################################################
10#
11# Stage 1 looks for shared libraries; the output of 'ldconfig -r' is examined
12# for hints as to where to look for libraries (but not trusted as a complete
13# list).
14#
15# These libraries each get an entry in the global 'Libs()' array.
16#
17# Stage 2 walks the entire system directory heirachy looking for executable
18# files, applies 'ldd' to them and attempts to determine which libraries are
19# used.  The path of the executable is then added to the 'Libs()' array
20# for each library used.
21#
22# Stage 3 reports on the day's findings.
23#
24################################################################################
25#
26# $Id$
27#
28
29#########################################################################################
30# findLibs
31#
32# Ask ldconfig where it thinks libraries are to be found.  Go look for them, and
33# add an element to 'Libs' for everything that looks like a library.
34#
35proc findLibs {} {
36
37    global Libs stats verbose;
38
39    # Older ldconfigs return a junk value when asked for a report
40    if {[catch {set liblist [exec ldconfig -r]} err]} {	# get ldconfig output
41	puts stderr "ldconfig returned nonzero, persevering.";
42	set liblist $err;				# there's junk in this
43    }
44
45    # remove hintsfile name, convert to list
46    set liblist [lrange [split $liblist "\n"] 1 end];
47
48    set libdirs "";				# no directories yet
49    foreach line $liblist {
50	# parse ldconfig output
51	if {[scan $line "%s => %s" junk libname] == 2} {
52	    # find directory name
53	    set libdir [file dirname $libname];
54	    # have we got this one already?
55	    if {[lsearch -exact $libdirs $libdir] == -1} {
56		lappend libdirs $libdir;
57	    }
58	} else {
59	    puts stderr "Unparseable ldconfig output line :";
60	    puts stderr $line;
61	}
62    }
63
64    # libdirs is now a list of directories that we might find libraries in
65    foreach dir $libdirs {
66	# get the names of anything that looks like a library
67	set libnames [glob -nocomplain "$dir/lib*.so.*"]
68	foreach lib $libnames {
69	    set Libs($lib) "";			# add it to our list
70	    if {$verbose} {puts "+ $lib";}
71	}
72    }
73    set stats(libs) [llength [array names Libs]];
74}
75
76################################################################################
77# findLibUsers
78#
79# Look in the directory (dir) for executables.  If we find any, call
80# examineExecutable to see if it uses any shared libraries.  Call ourselves
81# on any directories we find.
82#
83# Note that the use of "*" as a glob pattern means we miss directories and
84# executables starting with '.'.  This is a Feature.
85#
86proc findLibUsers {dir} {
87
88    global stats verbose;
89
90    if {[catch {
91	set ents [glob -nocomplain "$dir/*"];
92    } msg]} {
93	if {$msg == ""} {
94	    set msg "permission denied";
95	}
96	puts stderr "Can't search under '$dir' : $msg";
97	return ;
98    }
99
100    if {$verbose} {puts "===>> $dir";}
101    incr stats(dirs);
102
103    # files?
104    foreach f $ents {
105	# executable?
106	if {[file executable $f]} {
107	    # really a file?
108	    if {[file isfile $f]} {
109		incr stats(files);
110		examineExecutable $f;
111	    }
112	}
113    }
114    # subdirs?
115    foreach f $ents {
116	# maybe a directory with more files?
117	# don't use 'file isdirectory' because that follows symlinks
118	if {[catch {set type [file type $f]}]} {
119	    continue ;		# may not be able to stat
120	}
121	if {$type == "directory"} {
122	    findLibUsers $f;
123	}
124    }
125}
126
127################################################################################
128# examineExecutable
129#
130# Look at (fname) and see if ldd thinks it references any shared libraries.
131# If it does, update Libs with the information.
132#
133proc examineExecutable {fname} {
134
135    global Libs stats verbose;
136
137    # ask Mr. Ldd.
138    if {[catch {set result [exec ldd $fname]} msg]} {
139	return ;	# not dynamic
140    }
141
142    if {$verbose} {puts -nonewline "$fname : ";}
143    incr stats(execs);
144
145    # For a non-shared executable, we get a single-line error message.
146    # For a shared executable, we get a heading line, so in either case
147    # we can discard the first line and any subsequent lines are libraries
148    # that are required.
149    set llist [lrange [split $result "\n"] 1 end];
150    set uses "";
151
152    foreach line $llist {
153	if {[scan $line "%s => %s %s" junk1 lib junk2] == 3} {
154	    lappend Libs($lib) $fname;
155	    lappend uses $lib;
156	} else {
157	    puts stderr "Unparseable ldd putput line :";
158	    puts stderr $line;
159	}
160    }
161    if {$verbose} {puts "$uses";}
162}
163
164################################################################################
165# emitLibDetails
166#
167# Emit a listing of libraries and the executables that use them.
168#
169proc emitLibDetails {} {
170
171    global Libs;
172
173    # divide into used/unused
174    set used "";
175    set unused "";
176    foreach lib [array names Libs] {
177	if {$Libs($lib) == ""} {
178	    lappend unused $lib;
179	} else {
180	    lappend used $lib;
181	}
182    }
183
184    # emit used list
185    puts "== Current Shared Libraries ==================================================";
186    foreach lib [lsort $used] {
187	# sort executable names
188	set users [lsort $Libs($lib)];
189	puts [format "%-30s  %s" $lib $users];
190    }
191    # emit unused
192    puts "== Stale Shared Libraries ====================================================";
193    foreach lib [lsort $unused] {
194	# sort executable names
195	set users [lsort $Libs($lib)];
196	puts [format "%-30s  %s" $lib $users];
197    }
198}
199
200################################################################################
201# Run the whole shebang
202#
203proc main {} {
204
205    global stats verbose argv;
206
207    set verbose 0;
208    foreach arg $argv {
209	switch -- $arg {
210	    -v {
211		set verbose 1;
212	    }
213	    default {
214		puts stderr "Unknown option '$arg'";
215		exit ;
216	    }
217	}
218    }
219
220    set stats(libs) 0;
221    set stats(dirs) 0;
222    set stats(files) 0;
223    set stats(execs) 0
224
225    findLibs;
226    findLibUsers "/";
227    emitLibDetails;
228
229    puts [format "Searched %d directories, %d executables (%d dynamic) for %d libraries" \
230	      $stats(dirs) $stats(files) $stats(execs) $stats(libs)];
231}
232
233################################################################################
234main;
235