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 ################################################################################
56 #########################################################################################
59 # Ask ldconfig where it thinks libraries are to be found. Go look for them, and
60 # add an element to 'Libs' for everything that looks like a library.
64 global Libs stats verbose;
66 # Older ldconfigs return a junk value when asked for a report
67 if {[catch {set liblist [exec ldconfig -r]} err]} { # get ldconfig output
68 puts stderr "ldconfig returned nonzero, persevering.";
69 set liblist $err; # there's junk in this
72 # remove hintsfile name, convert to list
73 set liblist [lrange [split $liblist "\n"] 1 end];
75 set libdirs ""; # no directories yet
76 foreach line $liblist {
77 # parse ldconfig output
78 if {[scan $line "%s => %s" junk libname] == 2} {
80 set libdir [file dirname $libname];
81 # have we got this one already?
82 if {[lsearch -exact $libdirs $libdir] == -1} {
83 lappend libdirs $libdir;
86 puts stderr "Unparseable ldconfig output line :";
91 # libdirs is now a list of directories that we might find libraries in
92 foreach dir $libdirs {
93 # get the names of anything that looks like a library
94 set libnames [glob -nocomplain "$dir/lib*.so.*"]
95 foreach lib $libnames {
96 set type [file type $lib]; # what is it?
98 file { # looks like a library
99 # may have already been referenced by a symlink
100 if {![info exists Libs($lib)]} {
101 set Libs($lib) ""; # add it to our list
102 if {$verbose} {puts "+ $lib";}
105 link { # symlink; probably to another library
106 # If the readlink fails, the symlink is stale
107 if {[catch {set ldest [file readlink $lib]}]} {
108 puts stderr "Symbolic link points to nothing : $lib";
110 # may have already been referenced by another symlink
111 if {![info exists Libs($lib)]} {
112 set Libs($lib) ""; # add it to our list
113 if {$verbose} {puts "+ $lib";}
115 # list the symlink as a consumer of this library
116 lappend Libs($ldest) "($lib)";
117 if {$verbose} {puts "-> $ldest";}
123 set stats(libs) [llength [array names Libs]];
126 ################################################################################
129 # Look in the directory (dir) for executables. If we find any, call
130 # examineExecutable to see if it uses any shared libraries. Call ourselves
131 # on any directories we find.
133 # Note that the use of "*" as a glob pattern means we miss directories and
134 # executables starting with '.'. This is a Feature.
136 proc findLibUsers {dir} {
138 global stats verbose;
141 set ents [glob -nocomplain "$dir/*"];
144 set msg "permission denied";
146 puts stderr "Can't search under '$dir' : $msg";
150 if {$verbose} {puts "===>> $dir";}
156 if {[file executable $f]} {
158 if {[file isfile $f]} {
160 examineExecutable $f;
166 # maybe a directory with more files?
167 # don't use 'file isdirectory' because that follows symlinks
168 if {[catch {set type [file type $f]}]} {
169 continue ; # may not be able to stat
171 if {$type == "directory"} {
177 ################################################################################
180 # Look at (fname) and see if ldd thinks it references any shared libraries.
181 # If it does, update Libs with the information.
183 proc examineExecutable {fname} {
185 global Libs stats verbose;
188 if {[catch {set result [exec ldd $fname]} msg]} {
189 return ; # not dynamic
192 if {$verbose} {puts -nonewline "$fname : ";}
195 # For a non-shared executable, we get a single-line error message.
196 # For a shared executable, we get a heading line, so in either case
197 # we can discard the first line and any subsequent lines are libraries
199 set llist [lrange [split $result "\n"] 1 end];
202 foreach line $llist {
203 if {[scan $line "%s => %s %s" junk1 lib junk2] == 3} {
204 if {$lib == "not"} { # "not found" error
205 set mlname [string range $junk1 2 end];
206 puts stderr "$fname : library '$mlname' not known.";
208 lappend Libs($lib) $fname;
212 puts stderr "Unparseable ldd output line :";
216 if {$verbose} {puts "$uses";}
219 ################################################################################
222 # Emit a listing of libraries and the executables that use them.
224 proc emitLibDetails {} {
228 # divide into used/unused
231 foreach lib [array names Libs] {
232 if {$Libs($lib) == ""} {
240 puts "== Current Shared Libraries ==================================================";
241 foreach lib [lsort $used] {
242 # sort executable names
243 set users [lsort $Libs($lib)];
244 puts [format "%-30s %s" $lib $users];
247 puts "== Stale Shared Libraries ====================================================";
248 foreach lib [lsort $unused] {
249 # sort executable names
250 set users [lsort $Libs($lib)];
251 puts [format "%-30s %s" $lib $users];
255 ################################################################################
256 # Run the whole shebang
260 global stats verbose argv;
269 puts stderr "Unknown option '$arg'.";
284 puts [format "Searched %d directories, %d executables (%d dynamic) for %d libraries." \
285 $stats(dirs) $stats(files) $stats(execs) $stats(libs)];
288 ################################################################################