121183Smsmith#!/bin/sh
221183Smsmith# tcl magic \
321183Smsmithexec tclsh $0 $*
421183Smsmith################################################################################
521359Smsmith# Copyright (C) 1997
621359Smsmith#      Michael Smith.  All rights reserved.
721183Smsmith#
821359Smsmith# Redistribution and use in source and binary forms, with or without
921359Smsmith# modification, are permitted provided that the following conditions
1021359Smsmith# are met:
1121359Smsmith# 1. Redistributions of source code must retain the above copyright
1221359Smsmith#    notice, this list of conditions and the following disclaimer.
1321359Smsmith# 2. Redistributions in binary form must reproduce the above copyright
1421359Smsmith#    notice, this list of conditions and the following disclaimer in the
1521359Smsmith#    documentation and/or other materials provided with the distribution.
1621359Smsmith# 3. Neither the name of the author nor the names of any co-contributors
1721359Smsmith#    may be used to endorse or promote products derived from this software
1821359Smsmith#    without specific prior written permission.
1921359Smsmith#
2021359Smsmith# THIS SOFTWARE IS PROVIDED BY Michael Smith AND CONTRIBUTORS ``AS IS'' AND
2121359Smsmith# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2221359Smsmith# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2321359Smsmith# ARE DISCLAIMED.  IN NO EVENT SHALL Michael Smith OR CONTRIBUTORS BE LIABLE
2421359Smsmith# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2521359Smsmith# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2621359Smsmith# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2721359Smsmith# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2821359Smsmith# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2921359Smsmith# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3021359Smsmith# SUCH DAMAGE.
3121359Smsmith################################################################################
3221359Smsmith#
3321183Smsmith# LibraryReport; produce a list of shared libraries on the system, and a list of
3421183Smsmith# all executables that use them.
3521183Smsmith#
3621183Smsmith################################################################################
3721183Smsmith#
3821183Smsmith# Stage 1 looks for shared libraries; the output of 'ldconfig -r' is examined
3921183Smsmith# for hints as to where to look for libraries (but not trusted as a complete
4021183Smsmith# list).
4121183Smsmith#
4221183Smsmith# These libraries each get an entry in the global 'Libs()' array.
4321183Smsmith#
4421183Smsmith# Stage 2 walks the entire system directory heirachy looking for executable
4521183Smsmith# files, applies 'ldd' to them and attempts to determine which libraries are
4621183Smsmith# used.  The path of the executable is then added to the 'Libs()' array
4721183Smsmith# for each library used.
4821183Smsmith#
4921183Smsmith# Stage 3 reports on the day's findings.
5021183Smsmith#
5121183Smsmith################################################################################
5221183Smsmith#
5350477Speter# $FreeBSD$
5421183Smsmith#
5521183Smsmith
5621183Smsmith#########################################################################################
5721183Smsmith# findLibs
5821183Smsmith#
5921183Smsmith# Ask ldconfig where it thinks libraries are to be found.  Go look for them, and
6021183Smsmith# add an element to 'Libs' for everything that looks like a library.
6121183Smsmith#
6221183Smsmithproc findLibs {} {
6321183Smsmith
6421183Smsmith    global Libs stats verbose;
6521183Smsmith
6621183Smsmith    # Older ldconfigs return a junk value when asked for a report
6721183Smsmith    if {[catch {set liblist [exec ldconfig -r]} err]} {	# get ldconfig output
6821183Smsmith	puts stderr "ldconfig returned nonzero, persevering.";
6921183Smsmith	set liblist $err;				# there's junk in this
7021183Smsmith    }
7121183Smsmith
7221183Smsmith    # remove hintsfile name, convert to list
7321183Smsmith    set liblist [lrange [split $liblist "\n"] 1 end];
7421183Smsmith
7521183Smsmith    set libdirs "";				# no directories yet
7621183Smsmith    foreach line $liblist {
7721183Smsmith	# parse ldconfig output
7821183Smsmith	if {[scan $line "%s => %s" junk libname] == 2} {
7921183Smsmith	    # find directory name
8021183Smsmith	    set libdir [file dirname $libname];
8121183Smsmith	    # have we got this one already?
8221183Smsmith	    if {[lsearch -exact $libdirs $libdir] == -1} {
8321183Smsmith		lappend libdirs $libdir;
8421183Smsmith	    }
8521183Smsmith	} else {
8621183Smsmith	    puts stderr "Unparseable ldconfig output line :";
8721183Smsmith	    puts stderr $line;
8821183Smsmith	}
8921183Smsmith    }
9021183Smsmith
9121183Smsmith    # libdirs is now a list of directories that we might find libraries in
9221183Smsmith    foreach dir $libdirs {
9321183Smsmith	# get the names of anything that looks like a library
9421183Smsmith	set libnames [glob -nocomplain "$dir/lib*.so.*"]
9521183Smsmith	foreach lib $libnames {
9621359Smsmith	    set type [file type $lib];			# what is it?
9721359Smsmith	    switch $type {
9821359Smsmith		file {		# looks like a library
9921359Smsmith		    # may have already been referenced by a symlink
10021359Smsmith		    if {![info exists Libs($lib)]} {
10121359Smsmith			set Libs($lib) "";		# add it to our list
10221359Smsmith			if {$verbose} {puts "+ $lib";}
10321359Smsmith		    }
10421359Smsmith		}
10521359Smsmith		link {		# symlink; probably to another library
10621359Smsmith		    # If the readlink fails, the symlink is stale
10721359Smsmith		    if {[catch {set ldest [file readlink $lib]}]} {
10821359Smsmith			puts stderr "Symbolic link points to nothing : $lib";
10921359Smsmith		    } else {
11021359Smsmith			# may have already been referenced by another symlink
11121359Smsmith			if {![info exists Libs($lib)]} {
11221359Smsmith			    set Libs($lib) "";		# add it to our list
11321359Smsmith			    if {$verbose} {puts "+ $lib";}
11421359Smsmith			}
11521359Smsmith			# list the symlink as a consumer of this library
11621359Smsmith			lappend Libs($ldest) "($lib)";
11721359Smsmith			if {$verbose} {puts "-> $ldest";}
11821359Smsmith		    }
11921359Smsmith		}
12021359Smsmith	    }
12121183Smsmith	}
12221183Smsmith    }
12321183Smsmith    set stats(libs) [llength [array names Libs]];
12421183Smsmith}
12521183Smsmith
12621183Smsmith################################################################################
12721183Smsmith# findLibUsers
12821183Smsmith#
12921183Smsmith# Look in the directory (dir) for executables.  If we find any, call
13021183Smsmith# examineExecutable to see if it uses any shared libraries.  Call ourselves
13121183Smsmith# on any directories we find.
13221183Smsmith#
13321183Smsmith# Note that the use of "*" as a glob pattern means we miss directories and
13421183Smsmith# executables starting with '.'.  This is a Feature.
13521183Smsmith#
13621183Smsmithproc findLibUsers {dir} {
13721183Smsmith
13821183Smsmith    global stats verbose;
13921183Smsmith
14021183Smsmith    if {[catch {
14121183Smsmith	set ents [glob -nocomplain "$dir/*"];
14221183Smsmith    } msg]} {
14321183Smsmith	if {$msg == ""} {
14421183Smsmith	    set msg "permission denied";
14521183Smsmith	}
14621183Smsmith	puts stderr "Can't search under '$dir' : $msg";
14721183Smsmith	return ;
14821183Smsmith    }
14921183Smsmith
15021183Smsmith    if {$verbose} {puts "===>> $dir";}
15121183Smsmith    incr stats(dirs);
15221183Smsmith
15321183Smsmith    # files?
15421183Smsmith    foreach f $ents {
15521183Smsmith	# executable?
15621183Smsmith	if {[file executable $f]} {
15721183Smsmith	    # really a file?
15821183Smsmith	    if {[file isfile $f]} {
15921183Smsmith		incr stats(files);
16021183Smsmith		examineExecutable $f;
16121183Smsmith	    }
16221183Smsmith	}
16321183Smsmith    }
16421183Smsmith    # subdirs?
16521183Smsmith    foreach f $ents {
16621183Smsmith	# maybe a directory with more files?
16721183Smsmith	# don't use 'file isdirectory' because that follows symlinks
16821183Smsmith	if {[catch {set type [file type $f]}]} {
16921183Smsmith	    continue ;		# may not be able to stat
17021183Smsmith	}
17121183Smsmith	if {$type == "directory"} {
17221183Smsmith	    findLibUsers $f;
17321183Smsmith	}
17421183Smsmith    }
17521183Smsmith}
17621183Smsmith
17721183Smsmith################################################################################
17821183Smsmith# examineExecutable
17921183Smsmith#
18021183Smsmith# Look at (fname) and see if ldd thinks it references any shared libraries.
18121183Smsmith# If it does, update Libs with the information.
18221183Smsmith#
18321183Smsmithproc examineExecutable {fname} {
18421183Smsmith
18521183Smsmith    global Libs stats verbose;
18621183Smsmith
18721183Smsmith    # ask Mr. Ldd.
18821183Smsmith    if {[catch {set result [exec ldd $fname]} msg]} {
18921183Smsmith	return ;	# not dynamic
19021183Smsmith    }
19121183Smsmith
19221183Smsmith    if {$verbose} {puts -nonewline "$fname : ";}
19321183Smsmith    incr stats(execs);
19421183Smsmith
19521183Smsmith    # For a non-shared executable, we get a single-line error message.
19621183Smsmith    # For a shared executable, we get a heading line, so in either case
19721183Smsmith    # we can discard the first line and any subsequent lines are libraries
19821183Smsmith    # that are required.
19921183Smsmith    set llist [lrange [split $result "\n"] 1 end];
20021183Smsmith    set uses "";
20121183Smsmith
20221183Smsmith    foreach line $llist {
20321183Smsmith	if {[scan $line "%s => %s %s" junk1 lib junk2] == 3} {
20421359Smsmith	    if {$lib == "not"} {	# "not found" error
20521359Smsmith		set mlname [string range $junk1 2 end];
20621359Smsmith		puts stderr "$fname : library '$mlname' not known.";
20721359Smsmith	    } else {
20821359Smsmith		lappend Libs($lib) $fname;
20921359Smsmith		lappend uses $lib;
21021359Smsmith	    }
21121183Smsmith	} else {
21221359Smsmith	    puts stderr "Unparseable ldd output line :";
21321183Smsmith	    puts stderr $line;
21421183Smsmith	}
21521183Smsmith    }
21621183Smsmith    if {$verbose} {puts "$uses";}
21721183Smsmith}
21821183Smsmith
21921183Smsmith################################################################################
22021183Smsmith# emitLibDetails
22121183Smsmith#
22221183Smsmith# Emit a listing of libraries and the executables that use them.
22321183Smsmith#
22421183Smsmithproc emitLibDetails {} {
22521183Smsmith
22621183Smsmith    global Libs;
22721183Smsmith
22821183Smsmith    # divide into used/unused
22921183Smsmith    set used "";
23021183Smsmith    set unused "";
23121183Smsmith    foreach lib [array names Libs] {
23221183Smsmith	if {$Libs($lib) == ""} {
23321183Smsmith	    lappend unused $lib;
23421183Smsmith	} else {
23521183Smsmith	    lappend used $lib;
23621183Smsmith	}
23721183Smsmith    }
23821183Smsmith
23921183Smsmith    # emit used list
24021183Smsmith    puts "== Current Shared Libraries ==================================================";
24121183Smsmith    foreach lib [lsort $used] {
24221183Smsmith	# sort executable names
24321183Smsmith	set users [lsort $Libs($lib)];
24421183Smsmith	puts [format "%-30s  %s" $lib $users];
24521183Smsmith    }
24621183Smsmith    # emit unused
24721183Smsmith    puts "== Stale Shared Libraries ====================================================";
24821183Smsmith    foreach lib [lsort $unused] {
24921183Smsmith	# sort executable names
25021183Smsmith	set users [lsort $Libs($lib)];
25121183Smsmith	puts [format "%-30s  %s" $lib $users];
25221183Smsmith    }
25321183Smsmith}
25421183Smsmith
25521183Smsmith################################################################################
25621183Smsmith# Run the whole shebang
25721183Smsmith#
25821183Smsmithproc main {} {
25921183Smsmith
26021183Smsmith    global stats verbose argv;
26121183Smsmith
26221183Smsmith    set verbose 0;
26321183Smsmith    foreach arg $argv {
26421183Smsmith	switch -- $arg {
26521183Smsmith	    -v {
26621183Smsmith		set verbose 1;
26721183Smsmith	    }
26821183Smsmith	    default {
26921359Smsmith		puts stderr "Unknown option '$arg'.";
27021183Smsmith		exit ;
27121183Smsmith	    }
27221183Smsmith	}
27321183Smsmith    }
27421183Smsmith
27521183Smsmith    set stats(libs) 0;
27621183Smsmith    set stats(dirs) 0;
27721183Smsmith    set stats(files) 0;
27821183Smsmith    set stats(execs) 0
27921183Smsmith
28021183Smsmith    findLibs;
28121183Smsmith    findLibUsers "/";
28221183Smsmith    emitLibDetails;
28321183Smsmith
28421359Smsmith    puts [format "Searched %d directories, %d executables (%d dynamic) for %d libraries." \
28521183Smsmith	      $stats(dirs) $stats(files) $stats(execs) $stats(libs)];
28621183Smsmith}
28721183Smsmith
28821183Smsmith################################################################################
28921183Smsmithmain;
290