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