#!/bin/sh
# tcl magic \
exec tclsh $0 $*
################################################################################
# Copyright (C) 1997
# Michael Smith. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the author nor the names of any co-contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY Michael Smith AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL Michael Smith OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
################################################################################
#
# 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.
#
################################################################################
#
# $FreeBSD$
#
#########################################################################################
# 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 type [file type $lib]; # what is it?
switch $type {
file { # looks like a library
# may have already been referenced by a symlink
if {![info exists Libs($lib)]} {
set Libs($lib) ""; # add it to our list
if {$verbose} {puts "+ $lib";}
}
}
link { # symlink; probably to another library
# If the readlink fails, the symlink is stale
if {[catch {set ldest [file readlink $lib]}]} {
puts stderr "Symbolic link points to nothing : $lib";
} else {
# may have already been referenced by another symlink
if {![info exists Libs($lib)]} {
set Libs($lib) ""; # add it to our list
if {$verbose} {puts "+ $lib";}
}
# list the symlink as a consumer of this library
lappend Libs($ldest) "($lib)";
if {$verbose} {puts "-> $ldest";}
}
}
}
}
}
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} {
if {$lib == "not"} { # "not found" error
set mlname [string range $junk1 2 end];
puts stderr "$fname : library '$mlname' not known.";
} else {
lappend Libs($lib) $fname;
lappend uses $lib;
}
} else {
puts stderr "Unparseable ldd output 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;