4 ################################################################################
6 # Michael Smith. All rights reserved.
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions
11 # 1. Redistributions of source code must retain the above copyright
12 # notice, this list of conditions and the following disclaimer.
13 # 2. Redistributions in binary form must reproduce the above copyright
14 # notice, this list of conditions and the following disclaimer in the
15 # documentation and/or other materials provided with the distribution.
16 # 3. Neither the name of the author nor the names of any co-contributors
17 # may be used to endorse or promote products derived from this software
18 # without specific prior written permission.
20 # THIS SOFTWARE IS PROVIDED BY Michael Smith AND CONTRIBUTORS ``AS IS'' AND
21 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 # ARE DISCLAIMED. IN NO EVENT SHALL Michael Smith OR CONTRIBUTORS BE LIABLE
24 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 ################################################################################
33 # LibraryReport; produce a list of shared libraries on the system, and a list of
34 # all executables that use them.
36 ################################################################################
38 # Stage 1 looks for shared libraries; the output of 'ldconfig -r' is examined
39 # for hints as to where to look for libraries (but not trusted as a complete
42 # These libraries each get an entry in the global 'Libs()' array.
44 # Stage 2 walks the entire system directory heirachy looking for executable
45 # files, applies 'ldd' to them and attempts to determine which libraries are
46 # used. The path of the executable is then added to the 'Libs()' array
47 # for each library used.
49 # Stage 3 reports on the day's findings.
51 ################################################################################
53 # $FreeBSD: src/tools/LibraryReport/LibraryReport.tcl,v 1.5 1999/08/28 00:54:21 peter Exp $
54 # $DragonFly: src/tools/LibraryReport/LibraryReport.tcl,v 1.2 2003/06/17 04:29:11 dillon Exp $
57 #########################################################################################
60 # Ask ldconfig where it thinks libraries are to be found. Go look for them, and
61 # add an element to 'Libs' for everything that looks like a library.
65 global Libs stats verbose
;
67 # Older ldconfigs return a junk value when asked for a report
68 if {[catch
{set liblist
[exec ldconfig
-r]} err
]} { # get ldconfig output
69 puts stderr
"ldconfig returned nonzero, persevering.";
70 set liblist
$err; # there's junk in this
73 # remove hintsfile name, convert to list
74 set liblist
[lrange
[split $liblist "\n"] 1 end
];
76 set libdirs
""; # no directories yet
77 foreach line
$liblist {
78 # parse ldconfig output
79 if {[scan
$line "%s => %s" junk libname
] == 2} {
81 set libdir
[file dirname $libname];
82 # have we got this one already?
83 if {[lsearch
-exact $libdirs $libdir] == -1} {
84 lappend libdirs
$libdir;
87 puts stderr
"Unparseable ldconfig output line :";
92 # libdirs is now a list of directories that we might find libraries in
93 foreach dir
$libdirs {
94 # get the names of anything that looks like a library
95 set libnames
[glob
-nocomplain "$dir/lib*.so.*"]
96 foreach lib
$libnames {
97 set type [file type $lib]; # what is it?
99 file { # looks like a library
100 # may have already been referenced by a symlink
101 if {![info exists Libs
($lib)]} {
102 set Libs
($lib) ""; # add it to our list
103 if {$verbose} {puts
"+ $lib";}
106 link
{ # symlink; probably to another library
107 # If the readlink fails, the symlink is stale
108 if {[catch
{set ldest
[file readlink
$lib]}]} {
109 puts stderr
"Symbolic link points to nothing : $lib";
111 # may have already been referenced by another symlink
112 if {![info exists Libs
($lib)]} {
113 set Libs
($lib) ""; # add it to our list
114 if {$verbose} {puts
"+ $lib";}
116 # list the symlink as a consumer of this library
117 lappend Libs
($ldest) "($lib)";
118 if {$verbose} {puts
"-> $ldest";}
124 set stats
(libs
) [llength
[array names Libs
]];
127 ################################################################################
130 # Look in the directory (dir) for executables. If we find any, call
131 # examineExecutable to see if it uses any shared libraries. Call ourselves
132 # on any directories we find.
134 # Note that the use of "*" as a glob pattern means we miss directories and
135 # executables starting with '.'. This is a Feature.
137 proc findLibUsers
{dir
} {
139 global stats verbose
;
142 set ents
[glob
-nocomplain "$dir/*"];
145 set msg
"permission denied";
147 puts stderr
"Can't search under '$dir' : $msg";
151 if {$verbose} {puts
"===>> $dir";}
157 if {[file executable
$f]} {
159 if {[file isfile
$f]} {
161 examineExecutable
$f;
167 # maybe a directory with more files?
168 # don't use 'file isdirectory' because that follows symlinks
169 if {[catch
{set type [file type $f]}]} {
170 continue ; # may not be able to stat
172 if {$type == "directory"} {
178 ################################################################################
181 # Look at (fname) and see if ldd thinks it references any shared libraries.
182 # If it does, update Libs with the information.
184 proc examineExecutable
{fname
} {
186 global Libs stats verbose
;
189 if {[catch
{set result
[exec ldd
$fname]} msg
]} {
190 return ; # not dynamic
193 if {$verbose} {puts
-nonewline "$fname : ";}
196 # For a non-shared executable, we get a single-line error message.
197 # For a shared executable, we get a heading line, so in either case
198 # we can discard the first line and any subsequent lines are libraries
200 set llist
[lrange
[split $result "\n"] 1 end
];
203 foreach line
$llist {
204 if {[scan
$line "%s => %s %s" junk1 lib junk2
] == 3} {
205 if {$lib == "not"} { # "not found" error
206 set mlname
[string range
$junk1 2 end
];
207 puts stderr
"$fname : library '$mlname' not known.";
209 lappend Libs
($lib) $fname;
213 puts stderr
"Unparseable ldd output line :";
217 if {$verbose} {puts
"$uses";}
220 ################################################################################
223 # Emit a listing of libraries and the executables that use them.
225 proc emitLibDetails
{} {
229 # divide into used/unused
232 foreach lib
[array names Libs
] {
233 if {$Libs($lib) == ""} {
241 puts
"== Current Shared Libraries ==================================================";
242 foreach lib
[lsort
$used] {
243 # sort executable names
244 set users
[lsort
$Libs($lib)];
245 puts
[format
"%-30s %s" $lib $users];
248 puts
"== Stale Shared Libraries ====================================================";
249 foreach lib
[lsort
$unused] {
250 # sort executable names
251 set users
[lsort
$Libs($lib)];
252 puts
[format
"%-30s %s" $lib $users];
256 ################################################################################
257 # Run the whole shebang
261 global stats verbose argv
;
270 puts stderr
"Unknown option '$arg'.";
285 puts
[format
"Searched %d directories, %d executables (%d dynamic) for %d libraries." \
286 $stats(dirs) $stats(files
) $stats(execs
) $stats(libs
)];
289 ################################################################################