diff options
Diffstat (limited to 'tools/LibraryReport')
-rwxr-xr-x | tools/LibraryReport/LibraryReport.tcl | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/tools/LibraryReport/LibraryReport.tcl b/tools/LibraryReport/LibraryReport.tcl new file mode 100755 index 0000000..e1bc8bd --- /dev/null +++ b/tools/LibraryReport/LibraryReport.tcl @@ -0,0 +1,234 @@ +#!/bin/sh +# tcl magic \ +exec tclsh $0 $* +################################################################################ +# +# LibraryReport; produce a list of shared libraries on the system, and a list of +# all executables that use them. +# +################################################################################ +# +# Stage 1 looks for shared libraries; the output of 'ldconfig -r' is examined +# for hints as to where to look for libraries (but not trusted as a complete +# list). +# +# These libraries each get an entry in the global 'Libs()' array. +# +# Stage 2 walks the entire system directory heirachy looking for executable +# files, applies 'ldd' to them and attempts to determine which libraries are +# used. The path of the executable is then added to the 'Libs()' array +# for each library used. +# +# Stage 3 reports on the day's findings. +# +################################################################################ +# +# $Id$ +# + +######################################################################################### +# findLibs +# +# Ask ldconfig where it thinks libraries are to be found. Go look for them, and +# add an element to 'Libs' for everything that looks like a library. +# +proc findLibs {} { + + global Libs stats verbose; + + # Older ldconfigs return a junk value when asked for a report + if {[catch {set liblist [exec ldconfig -r]} err]} { # get ldconfig output + puts stderr "ldconfig returned nonzero, persevering."; + set liblist $err; # there's junk in this + } + + # remove hintsfile name, convert to list + set liblist [lrange [split $liblist "\n"] 1 end]; + + set libdirs ""; # no directories yet + foreach line $liblist { + # parse ldconfig output + if {[scan $line "%s => %s" junk libname] == 2} { + # find directory name + set libdir [file dirname $libname]; + # have we got this one already? + if {[lsearch -exact $libdirs $libdir] == -1} { + lappend libdirs $libdir; + } + } else { + puts stderr "Unparseable ldconfig output line :"; + puts stderr $line; + } + } + + # libdirs is now a list of directories that we might find libraries in + foreach dir $libdirs { + # get the names of anything that looks like a library + set libnames [glob -nocomplain "$dir/lib*.so.*"] + foreach lib $libnames { + set Libs($lib) ""; # add it to our list + if {$verbose} {puts "+ $lib";} + } + } + set stats(libs) [llength [array names Libs]]; +} + +################################################################################ +# findLibUsers +# +# Look in the directory (dir) for executables. If we find any, call +# examineExecutable to see if it uses any shared libraries. Call ourselves +# on any directories we find. +# +# Note that the use of "*" as a glob pattern means we miss directories and +# executables starting with '.'. This is a Feature. +# +proc findLibUsers {dir} { + + global stats verbose; + + if {[catch { + set ents [glob -nocomplain "$dir/*"]; + } msg]} { + if {$msg == ""} { + set msg "permission denied"; + } + puts stderr "Can't search under '$dir' : $msg"; + return ; + } + + if {$verbose} {puts "===>> $dir";} + incr stats(dirs); + + # files? + foreach f $ents { + # executable? + if {[file executable $f]} { + # really a file? + if {[file isfile $f]} { + incr stats(files); + examineExecutable $f; + } + } + } + # subdirs? + foreach f $ents { + # maybe a directory with more files? + # don't use 'file isdirectory' because that follows symlinks + if {[catch {set type [file type $f]}]} { + continue ; # may not be able to stat + } + if {$type == "directory"} { + findLibUsers $f; + } + } +} + +################################################################################ +# examineExecutable +# +# Look at (fname) and see if ldd thinks it references any shared libraries. +# If it does, update Libs with the information. +# +proc examineExecutable {fname} { + + global Libs stats verbose; + + # ask Mr. Ldd. + if {[catch {set result [exec ldd $fname]} msg]} { + return ; # not dynamic + } + + if {$verbose} {puts -nonewline "$fname : ";} + incr stats(execs); + + # For a non-shared executable, we get a single-line error message. + # For a shared executable, we get a heading line, so in either case + # we can discard the first line and any subsequent lines are libraries + # that are required. + set llist [lrange [split $result "\n"] 1 end]; + set uses ""; + + foreach line $llist { + if {[scan $line "%s => %s %s" junk1 lib junk2] == 3} { + lappend Libs($lib) $fname; + lappend uses $lib; + } else { + puts stderr "Unparseable ldd putput line :"; + puts stderr $line; + } + } + if {$verbose} {puts "$uses";} +} + +################################################################################ +# emitLibDetails +# +# Emit a listing of libraries and the executables that use them. +# +proc emitLibDetails {} { + + global Libs; + + # divide into used/unused + set used ""; + set unused ""; + foreach lib [array names Libs] { + if {$Libs($lib) == ""} { + lappend unused $lib; + } else { + lappend used $lib; + } + } + + # emit used list + puts "== Current Shared Libraries =================================================="; + foreach lib [lsort $used] { + # sort executable names + set users [lsort $Libs($lib)]; + puts [format "%-30s %s" $lib $users]; + } + # emit unused + puts "== Stale Shared Libraries ===================================================="; + foreach lib [lsort $unused] { + # sort executable names + set users [lsort $Libs($lib)]; + puts [format "%-30s %s" $lib $users]; + } +} + +################################################################################ +# Run the whole shebang +# +proc main {} { + + global stats verbose argv; + + set verbose 0; + foreach arg $argv { + switch -- $arg { + -v { + set verbose 1; + } + default { + puts stderr "Unknown option '$arg'"; + exit ; + } + } + } + + set stats(libs) 0; + set stats(dirs) 0; + set stats(files) 0; + set stats(execs) 0 + + findLibs; + findLibUsers "/"; + emitLibDetails; + + puts [format "Searched %d directories, %d executables (%d dynamic) for %d libraries" \ + $stats(dirs) $stats(files) $stats(execs) $stats(libs)]; +} + +################################################################################ +main; |