xref: /freebsd/tools/LibraryReport/LibraryReport.tcl (revision d0b2dbfa0ecf2bbc9709efc5e20baf8e4b44bbbf)
11b56bb4cSMike Smith#!/bin/sh
21b56bb4cSMike Smith# tcl magic \
31b56bb4cSMike Smithexec tclsh $0 $*
41b56bb4cSMike Smith################################################################################
5fa3da92aSMike Smith# Copyright (C) 1997
6fa3da92aSMike Smith#      Michael Smith.  All rights reserved.
7fa3da92aSMike Smith#
8fa3da92aSMike Smith# Redistribution and use in source and binary forms, with or without
9fa3da92aSMike Smith# modification, are permitted provided that the following conditions
10fa3da92aSMike Smith# are met:
11fa3da92aSMike Smith# 1. Redistributions of source code must retain the above copyright
12fa3da92aSMike Smith#    notice, this list of conditions and the following disclaimer.
13fa3da92aSMike Smith# 2. Redistributions in binary form must reproduce the above copyright
14fa3da92aSMike Smith#    notice, this list of conditions and the following disclaimer in the
15fa3da92aSMike Smith#    documentation and/or other materials provided with the distribution.
16fa3da92aSMike Smith# 3. Neither the name of the author nor the names of any co-contributors
17fa3da92aSMike Smith#    may be used to endorse or promote products derived from this software
18fa3da92aSMike Smith#    without specific prior written permission.
19fa3da92aSMike Smith#
20fa3da92aSMike Smith# THIS SOFTWARE IS PROVIDED BY Michael Smith AND CONTRIBUTORS ``AS IS'' AND
21fa3da92aSMike Smith# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22fa3da92aSMike Smith# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23fa3da92aSMike Smith# ARE DISCLAIMED.  IN NO EVENT SHALL Michael Smith OR CONTRIBUTORS BE LIABLE
24fa3da92aSMike Smith# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25fa3da92aSMike Smith# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26fa3da92aSMike Smith# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27fa3da92aSMike Smith# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28fa3da92aSMike Smith# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29fa3da92aSMike Smith# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30fa3da92aSMike Smith# SUCH DAMAGE.
31fa3da92aSMike Smith################################################################################
321b56bb4cSMike Smith#
331b56bb4cSMike Smith# LibraryReport; produce a list of shared libraries on the system, and a list of
341b56bb4cSMike Smith# all executables that use them.
351b56bb4cSMike Smith#
361b56bb4cSMike Smith################################################################################
371b56bb4cSMike Smith#
381b56bb4cSMike Smith# Stage 1 looks for shared libraries; the output of 'ldconfig -r' is examined
391b56bb4cSMike Smith# for hints as to where to look for libraries (but not trusted as a complete
401b56bb4cSMike Smith# list).
411b56bb4cSMike Smith#
421b56bb4cSMike Smith# These libraries each get an entry in the global 'Libs()' array.
431b56bb4cSMike Smith#
441b56bb4cSMike Smith# Stage 2 walks the entire system directory heirachy looking for executable
451b56bb4cSMike Smith# files, applies 'ldd' to them and attempts to determine which libraries are
461b56bb4cSMike Smith# used.  The path of the executable is then added to the 'Libs()' array
471b56bb4cSMike Smith# for each library used.
481b56bb4cSMike Smith#
491b56bb4cSMike Smith# Stage 3 reports on the day's findings.
501b56bb4cSMike Smith#
511b56bb4cSMike Smith################################################################################
521b56bb4cSMike Smith#
531b56bb4cSMike Smith#
541b56bb4cSMike Smith
551b56bb4cSMike Smith#########################################################################################
561b56bb4cSMike Smith# findLibs
571b56bb4cSMike Smith#
581b56bb4cSMike Smith# Ask ldconfig where it thinks libraries are to be found.  Go look for them, and
591b56bb4cSMike Smith# add an element to 'Libs' for everything that looks like a library.
601b56bb4cSMike Smith#
611b56bb4cSMike Smithproc findLibs {} {
621b56bb4cSMike Smith
631b56bb4cSMike Smith    global Libs stats verbose;
641b56bb4cSMike Smith
651b56bb4cSMike Smith    # Older ldconfigs return a junk value when asked for a report
661b56bb4cSMike Smith    if {[catch {set liblist [exec ldconfig -r]} err]} {	# get ldconfig output
671b56bb4cSMike Smith	puts stderr "ldconfig returned nonzero, persevering.";
681b56bb4cSMike Smith	set liblist $err;				# there's junk in this
691b56bb4cSMike Smith    }
701b56bb4cSMike Smith
711b56bb4cSMike Smith    # remove hintsfile name, convert to list
721b56bb4cSMike Smith    set liblist [lrange [split $liblist "\n"] 1 end];
731b56bb4cSMike Smith
741b56bb4cSMike Smith    set libdirs "";				# no directories yet
751b56bb4cSMike Smith    foreach line $liblist {
761b56bb4cSMike Smith	# parse ldconfig output
771b56bb4cSMike Smith	if {[scan $line "%s => %s" junk libname] == 2} {
781b56bb4cSMike Smith	    # find directory name
791b56bb4cSMike Smith	    set libdir [file dirname $libname];
801b56bb4cSMike Smith	    # have we got this one already?
811b56bb4cSMike Smith	    if {[lsearch -exact $libdirs $libdir] == -1} {
821b56bb4cSMike Smith		lappend libdirs $libdir;
831b56bb4cSMike Smith	    }
841b56bb4cSMike Smith	} else {
851b56bb4cSMike Smith	    puts stderr "Unparseable ldconfig output line :";
861b56bb4cSMike Smith	    puts stderr $line;
871b56bb4cSMike Smith	}
881b56bb4cSMike Smith    }
891b56bb4cSMike Smith
901b56bb4cSMike Smith    # libdirs is now a list of directories that we might find libraries in
911b56bb4cSMike Smith    foreach dir $libdirs {
921b56bb4cSMike Smith	# get the names of anything that looks like a library
931b56bb4cSMike Smith	set libnames [glob -nocomplain "$dir/lib*.so.*"]
941b56bb4cSMike Smith	foreach lib $libnames {
95fa3da92aSMike Smith	    set type [file type $lib];			# what is it?
96fa3da92aSMike Smith	    switch $type {
97fa3da92aSMike Smith		file {		# looks like a library
98fa3da92aSMike Smith		    # may have already been referenced by a symlink
99fa3da92aSMike Smith		    if {![info exists Libs($lib)]} {
1001b56bb4cSMike Smith			set Libs($lib) "";		# add it to our list
1011b56bb4cSMike Smith			if {$verbose} {puts "+ $lib";}
1021b56bb4cSMike Smith		    }
1031b56bb4cSMike Smith		}
104fa3da92aSMike Smith		link {		# symlink; probably to another library
105fa3da92aSMike Smith		    # If the readlink fails, the symlink is stale
106fa3da92aSMike Smith		    if {[catch {set ldest [file readlink $lib]}]} {
107fa3da92aSMike Smith			puts stderr "Symbolic link points to nothing : $lib";
108fa3da92aSMike Smith		    } else {
109fa3da92aSMike Smith			# may have already been referenced by another symlink
110fa3da92aSMike Smith			if {![info exists Libs($lib)]} {
111fa3da92aSMike Smith			    set Libs($lib) "";		# add it to our list
112fa3da92aSMike Smith			    if {$verbose} {puts "+ $lib";}
113fa3da92aSMike Smith			}
114fa3da92aSMike Smith			# list the symlink as a consumer of this library
115fa3da92aSMike Smith			lappend Libs($ldest) "($lib)";
116fa3da92aSMike Smith			if {$verbose} {puts "-> $ldest";}
117fa3da92aSMike Smith		    }
118fa3da92aSMike Smith		}
119fa3da92aSMike Smith	    }
120fa3da92aSMike Smith	}
121fa3da92aSMike Smith    }
1221b56bb4cSMike Smith    set stats(libs) [llength [array names Libs]];
1231b56bb4cSMike Smith}
1241b56bb4cSMike Smith
1251b56bb4cSMike Smith################################################################################
1261b56bb4cSMike Smith# findLibUsers
1271b56bb4cSMike Smith#
1281b56bb4cSMike Smith# Look in the directory (dir) for executables.  If we find any, call
1291b56bb4cSMike Smith# examineExecutable to see if it uses any shared libraries.  Call ourselves
1301b56bb4cSMike Smith# on any directories we find.
1311b56bb4cSMike Smith#
1321b56bb4cSMike Smith# Note that the use of "*" as a glob pattern means we miss directories and
1331b56bb4cSMike Smith# executables starting with '.'.  This is a Feature.
1341b56bb4cSMike Smith#
1351b56bb4cSMike Smithproc findLibUsers {dir} {
1361b56bb4cSMike Smith
1371b56bb4cSMike Smith    global stats verbose;
1381b56bb4cSMike Smith
1391b56bb4cSMike Smith    if {[catch {
1401b56bb4cSMike Smith	set ents [glob -nocomplain "$dir/*"];
1411b56bb4cSMike Smith    } msg]} {
1421b56bb4cSMike Smith	if {$msg == ""} {
1431b56bb4cSMike Smith	    set msg "permission denied";
1441b56bb4cSMike Smith	}
1451b56bb4cSMike Smith	puts stderr "Can't search under '$dir' : $msg";
1461b56bb4cSMike Smith	return ;
1471b56bb4cSMike Smith    }
1481b56bb4cSMike Smith
1491b56bb4cSMike Smith    if {$verbose} {puts "===>> $dir";}
1501b56bb4cSMike Smith    incr stats(dirs);
1511b56bb4cSMike Smith
1521b56bb4cSMike Smith    # files?
1531b56bb4cSMike Smith    foreach f $ents {
1541b56bb4cSMike Smith	# executable?
1551b56bb4cSMike Smith	if {[file executable $f]} {
1561b56bb4cSMike Smith	    # really a file?
1571b56bb4cSMike Smith	    if {[file isfile $f]} {
1581b56bb4cSMike Smith		incr stats(files);
1591b56bb4cSMike Smith		examineExecutable $f;
1601b56bb4cSMike Smith	    }
1611b56bb4cSMike Smith	}
1621b56bb4cSMike Smith    }
1631b56bb4cSMike Smith    # subdirs?
1641b56bb4cSMike Smith    foreach f $ents {
1651b56bb4cSMike Smith	# maybe a directory with more files?
1661b56bb4cSMike Smith	# don't use 'file isdirectory' because that follows symlinks
1671b56bb4cSMike Smith	if {[catch {set type [file type $f]}]} {
1681b56bb4cSMike Smith	    continue ;		# may not be able to stat
1691b56bb4cSMike Smith	}
1701b56bb4cSMike Smith	if {$type == "directory"} {
1711b56bb4cSMike Smith	    findLibUsers $f;
1721b56bb4cSMike Smith	}
1731b56bb4cSMike Smith    }
1741b56bb4cSMike Smith}
1751b56bb4cSMike Smith
1761b56bb4cSMike Smith################################################################################
1771b56bb4cSMike Smith# examineExecutable
1781b56bb4cSMike Smith#
1791b56bb4cSMike Smith# Look at (fname) and see if ldd thinks it references any shared libraries.
1801b56bb4cSMike Smith# If it does, update Libs with the information.
1811b56bb4cSMike Smith#
1821b56bb4cSMike Smithproc examineExecutable {fname} {
1831b56bb4cSMike Smith
1841b56bb4cSMike Smith    global Libs stats verbose;
1851b56bb4cSMike Smith
1861b56bb4cSMike Smith    # ask Mr. Ldd.
1871b56bb4cSMike Smith    if {[catch {set result [exec ldd $fname]} msg]} {
1881b56bb4cSMike Smith	return ;	# not dynamic
1891b56bb4cSMike Smith    }
1901b56bb4cSMike Smith
1911b56bb4cSMike Smith    if {$verbose} {puts -nonewline "$fname : ";}
1921b56bb4cSMike Smith    incr stats(execs);
1931b56bb4cSMike Smith
1941b56bb4cSMike Smith    # For a non-shared executable, we get a single-line error message.
1951b56bb4cSMike Smith    # For a shared executable, we get a heading line, so in either case
1961b56bb4cSMike Smith    # we can discard the first line and any subsequent lines are libraries
1971b56bb4cSMike Smith    # that are required.
1981b56bb4cSMike Smith    set llist [lrange [split $result "\n"] 1 end];
1991b56bb4cSMike Smith    set uses "";
2001b56bb4cSMike Smith
2011b56bb4cSMike Smith    foreach line $llist {
2021b56bb4cSMike Smith	if {[scan $line "%s => %s %s" junk1 lib junk2] == 3} {
203fa3da92aSMike Smith	    if {$lib == "not"} {	# "not found" error
204fa3da92aSMike Smith		set mlname [string range $junk1 2 end];
205fa3da92aSMike Smith		puts stderr "$fname : library '$mlname' not known.";
206fa3da92aSMike Smith	    } else {
2071b56bb4cSMike Smith		lappend Libs($lib) $fname;
2081b56bb4cSMike Smith		lappend uses $lib;
209fa3da92aSMike Smith	    }
2101b56bb4cSMike Smith	} else {
211fa3da92aSMike Smith	    puts stderr "Unparseable ldd output line :";
2121b56bb4cSMike Smith	    puts stderr $line;
2131b56bb4cSMike Smith	}
2141b56bb4cSMike Smith    }
2151b56bb4cSMike Smith    if {$verbose} {puts "$uses";}
2161b56bb4cSMike Smith}
2171b56bb4cSMike Smith
2181b56bb4cSMike Smith################################################################################
2191b56bb4cSMike Smith# emitLibDetails
2201b56bb4cSMike Smith#
2211b56bb4cSMike Smith# Emit a listing of libraries and the executables that use them.
2221b56bb4cSMike Smith#
2231b56bb4cSMike Smithproc emitLibDetails {} {
2241b56bb4cSMike Smith
2251b56bb4cSMike Smith    global Libs;
2261b56bb4cSMike Smith
2271b56bb4cSMike Smith    # divide into used/unused
2281b56bb4cSMike Smith    set used "";
2291b56bb4cSMike Smith    set unused "";
2301b56bb4cSMike Smith    foreach lib [array names Libs] {
2311b56bb4cSMike Smith	if {$Libs($lib) == ""} {
2321b56bb4cSMike Smith	    lappend unused $lib;
2331b56bb4cSMike Smith	} else {
2341b56bb4cSMike Smith	    lappend used $lib;
2351b56bb4cSMike Smith	}
2361b56bb4cSMike Smith    }
2371b56bb4cSMike Smith
2381b56bb4cSMike Smith    # emit used list
2391b56bb4cSMike Smith    puts "== Current Shared Libraries ==================================================";
2401b56bb4cSMike Smith    foreach lib [lsort $used] {
2411b56bb4cSMike Smith	# sort executable names
2421b56bb4cSMike Smith	set users [lsort $Libs($lib)];
2431b56bb4cSMike Smith	puts [format "%-30s  %s" $lib $users];
2441b56bb4cSMike Smith    }
2451b56bb4cSMike Smith    # emit unused
2461b56bb4cSMike Smith    puts "== Stale Shared Libraries ====================================================";
2471b56bb4cSMike Smith    foreach lib [lsort $unused] {
2481b56bb4cSMike Smith	# sort executable names
2491b56bb4cSMike Smith	set users [lsort $Libs($lib)];
2501b56bb4cSMike Smith	puts [format "%-30s  %s" $lib $users];
2511b56bb4cSMike Smith    }
2521b56bb4cSMike Smith}
2531b56bb4cSMike Smith
2541b56bb4cSMike Smith################################################################################
2551b56bb4cSMike Smith# Run the whole shebang
2561b56bb4cSMike Smith#
2571b56bb4cSMike Smithproc main {} {
2581b56bb4cSMike Smith
2591b56bb4cSMike Smith    global stats verbose argv;
2601b56bb4cSMike Smith
2611b56bb4cSMike Smith    set verbose 0;
2621b56bb4cSMike Smith    foreach arg $argv {
2631b56bb4cSMike Smith	switch -- $arg {
2641b56bb4cSMike Smith	    -v {
2651b56bb4cSMike Smith		set verbose 1;
2661b56bb4cSMike Smith	    }
2671b56bb4cSMike Smith	    default {
268fa3da92aSMike Smith		puts stderr "Unknown option '$arg'.";
2691b56bb4cSMike Smith		exit ;
2701b56bb4cSMike Smith	    }
2711b56bb4cSMike Smith	}
2721b56bb4cSMike Smith    }
2731b56bb4cSMike Smith
2741b56bb4cSMike Smith    set stats(libs) 0;
2751b56bb4cSMike Smith    set stats(dirs) 0;
2761b56bb4cSMike Smith    set stats(files) 0;
2771b56bb4cSMike Smith    set stats(execs) 0
2781b56bb4cSMike Smith
2791b56bb4cSMike Smith    findLibs;
2801b56bb4cSMike Smith    findLibUsers "/";
2811b56bb4cSMike Smith    emitLibDetails;
2821b56bb4cSMike Smith
283fa3da92aSMike Smith    puts [format "Searched %d directories, %d executables (%d dynamic) for %d libraries." \
2841b56bb4cSMike Smith	      $stats(dirs) $stats(files) $stats(execs) $stats(libs)];
2851b56bb4cSMike Smith}
2861b56bb4cSMike Smith
2871b56bb4cSMike Smith################################################################################
2881b56bb4cSMike Smithmain;
289