LibraryReport is a Tcl script that attempts to report on the status of
shared libraries in the system. It locates libraries using the output of 'ldconfig -r' to find directories containing libraries, and then performs a recursive descent of the entire filesystem heirachy looking for dynamically linked executables and recording the libraries that they use. After scanning, a report is produced listing used libraries and the executable(s) that use them, and seperately listing unused libraries.
This commit is contained in:
commit
9bd58b13fc
234
tools/LibraryReport/LibraryReport.tcl
Executable file
234
tools/LibraryReport/LibraryReport.tcl
Executable file
@ -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;
|
Loading…
Reference in New Issue
Block a user