aboutsummaryrefslogblamecommitdiff
path: root/tools/LibraryReport/LibraryReport.tcl
blob: fbf63896c71d170acb2b687abbb14e8696589a0f (plain) (tree)
1
2
3
4



                                                                                


























                                                                                




















                                                                                
           









































                                                                                         
























                                                                             


















































































                                                                                






                                                                    
                
                                                        























































                                                                                          
                                                     













                       
                                                                                           




                                                                                
#!/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;