1#!/bin/sh 2# tcl magic \ 3exec tclsh $0 $* 4################################################################################ 5# Copyright (C) 1997 6# Michael Smith. All rights reserved. 7# 8# Redistribution and use in source and binary forms, with or without 9# modification, are permitted provided that the following conditions 10# are met: 11# 1. Redistributions of source code must retain the above copyright 12# notice, this list of conditions and the following disclaimer. 13# 2. Redistributions in binary form must reproduce the above copyright 14# notice, this list of conditions and the following disclaimer in the 15# documentation and/or other materials provided with the distribution. 16# 3. Neither the name of the author nor the names of any co-contributors 17# may be used to endorse or promote products derived from this software 18# without specific prior written permission. 19# 20# THIS SOFTWARE IS PROVIDED BY Michael Smith AND CONTRIBUTORS ``AS IS'' AND 21# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23# ARE DISCLAIMED. IN NO EVENT SHALL Michael Smith OR CONTRIBUTORS BE LIABLE 24# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30# SUCH DAMAGE. 31################################################################################ 32# 33# LibraryReport; produce a list of shared libraries on the system, and a list of 34# all executables that use them. 35# 36################################################################################ 37# 38# Stage 1 looks for shared libraries; the output of 'ldconfig -r' is examined 39# for hints as to where to look for libraries (but not trusted as a complete 40# list). 41# 42# These libraries each get an entry in the global 'Libs()' array. 43# 44# Stage 2 walks the entire system directory heirachy looking for executable 45# files, applies 'ldd' to them and attempts to determine which libraries are 46# used. The path of the executable is then added to the 'Libs()' array 47# for each library used. 48# 49# Stage 3 reports on the day's findings. 50# 51################################################################################ 52# 53# $FreeBSD$ 54# 55 56######################################################################################### 57# findLibs 58# 59# Ask ldconfig where it thinks libraries are to be found. Go look for them, and 60# add an element to 'Libs' for everything that looks like a library. 61# 62proc findLibs {} { 63 64 global Libs stats verbose; 65 66 # Older ldconfigs return a junk value when asked for a report 67 if {[catch {set liblist [exec ldconfig -r]} err]} { # get ldconfig output 68 puts stderr "ldconfig returned nonzero, persevering."; 69 set liblist $err; # there's junk in this 70 } 71 72 # remove hintsfile name, convert to list 73 set liblist [lrange [split $liblist "\n"] 1 end]; 74 75 set libdirs ""; # no directories yet 76 foreach line $liblist { 77 # parse ldconfig output 78 if {[scan $line "%s => %s" junk libname] == 2} { 79 # find directory name 80 set libdir [file dirname $libname]; 81 # have we got this one already? 82 if {[lsearch -exact $libdirs $libdir] == -1} { 83 lappend libdirs $libdir; 84 } 85 } else { 86 puts stderr "Unparseable ldconfig output line :"; 87 puts stderr $line; 88 } 89 } 90 91 # libdirs is now a list of directories that we might find libraries in 92 foreach dir $libdirs { 93 # get the names of anything that looks like a library 94 set libnames [glob -nocomplain "$dir/lib*.so.*"] 95 foreach lib $libnames { 96 set type [file type $lib]; # what is it? 97 switch $type { 98 file { # looks like a library 99 # may have already been referenced by a symlink 100 if {![info exists Libs($lib)]} { 101 set Libs($lib) ""; # add it to our list 102 if {$verbose} {puts "+ $lib";} 103 } 104 } 105 link { # symlink; probably to another library 106 # If the readlink fails, the symlink is stale 107 if {[catch {set ldest [file readlink $lib]}]} { 108 puts stderr "Symbolic link points to nothing : $lib"; 109 } else { 110 # may have already been referenced by another symlink 111 if {![info exists Libs($lib)]} { 112 set Libs($lib) ""; # add it to our list 113 if {$verbose} {puts "+ $lib";} 114 } 115 # list the symlink as a consumer of this library 116 lappend Libs($ldest) "($lib)"; 117 if {$verbose} {puts "-> $ldest";} 118 } 119 } 120 } 121 } 122 } 123 set stats(libs) [llength [array names Libs]]; 124} 125 126################################################################################ 127# findLibUsers 128# 129# Look in the directory (dir) for executables. If we find any, call 130# examineExecutable to see if it uses any shared libraries. Call ourselves 131# on any directories we find. 132# 133# Note that the use of "*" as a glob pattern means we miss directories and 134# executables starting with '.'. This is a Feature. 135# 136proc findLibUsers {dir} { 137 138 global stats verbose; 139 140 if {[catch { 141 set ents [glob -nocomplain "$dir/*"]; 142 } msg]} { 143 if {$msg == ""} { 144 set msg "permission denied"; 145 } 146 puts stderr "Can't search under '$dir' : $msg"; 147 return ; 148 } 149 150 if {$verbose} {puts "===>> $dir";} 151 incr stats(dirs); 152 153 # files? 154 foreach f $ents { 155 # executable? 156 if {[file executable $f]} { 157 # really a file? 158 if {[file isfile $f]} { 159 incr stats(files); 160 examineExecutable $f; 161 } 162 } 163 } 164 # subdirs? 165 foreach f $ents { 166 # maybe a directory with more files? 167 # don't use 'file isdirectory' because that follows symlinks 168 if {[catch {set type [file type $f]}]} { 169 continue ; # may not be able to stat 170 } 171 if {$type == "directory"} { 172 findLibUsers $f; 173 } 174 } 175} 176 177################################################################################ 178# examineExecutable 179# 180# Look at (fname) and see if ldd thinks it references any shared libraries. 181# If it does, update Libs with the information. 182# 183proc examineExecutable {fname} { 184 185 global Libs stats verbose; 186 187 # ask Mr. Ldd. 188 if {[catch {set result [exec ldd $fname]} msg]} { 189 return ; # not dynamic 190 } 191 192 if {$verbose} {puts -nonewline "$fname : ";} 193 incr stats(execs); 194 195 # For a non-shared executable, we get a single-line error message. 196 # For a shared executable, we get a heading line, so in either case 197 # we can discard the first line and any subsequent lines are libraries 198 # that are required. 199 set llist [lrange [split $result "\n"] 1 end]; 200 set uses ""; 201 202 foreach line $llist { 203 if {[scan $line "%s => %s %s" junk1 lib junk2] == 3} { 204 if {$lib == "not"} { # "not found" error 205 set mlname [string range $junk1 2 end]; 206 puts stderr "$fname : library '$mlname' not known."; 207 } else { 208 lappend Libs($lib) $fname; 209 lappend uses $lib; 210 } 211 } else { 212 puts stderr "Unparseable ldd output line :"; 213 puts stderr $line; 214 } 215 } 216 if {$verbose} {puts "$uses";} 217} 218 219################################################################################ 220# emitLibDetails 221# 222# Emit a listing of libraries and the executables that use them. 223# 224proc emitLibDetails {} { 225 226 global Libs; 227 228 # divide into used/unused 229 set used ""; 230 set unused ""; 231 foreach lib [array names Libs] { 232 if {$Libs($lib) == ""} { 233 lappend unused $lib; 234 } else { 235 lappend used $lib; 236 } 237 } 238 239 # emit used list 240 puts "== Current Shared Libraries =================================================="; 241 foreach lib [lsort $used] { 242 # sort executable names 243 set users [lsort $Libs($lib)]; 244 puts [format "%-30s %s" $lib $users]; 245 } 246 # emit unused 247 puts "== Stale Shared Libraries ===================================================="; 248 foreach lib [lsort $unused] { 249 # sort executable names 250 set users [lsort $Libs($lib)]; 251 puts [format "%-30s %s" $lib $users]; 252 } 253} 254 255################################################################################ 256# Run the whole shebang 257# 258proc main {} { 259 260 global stats verbose argv; 261 262 set verbose 0; 263 foreach arg $argv { 264 switch -- $arg { 265 -v { 266 set verbose 1; 267 } 268 default { 269 puts stderr "Unknown option '$arg'."; 270 exit ; 271 } 272 } 273 } 274 275 set stats(libs) 0; 276 set stats(dirs) 0; 277 set stats(files) 0; 278 set stats(execs) 0 279 280 findLibs; 281 findLibUsers "/"; 282 emitLibDetails; 283 284 puts [format "Searched %d directories, %d executables (%d dynamic) for %d libraries." \ 285 $stats(dirs) $stats(files) $stats(execs) $stats(libs)]; 286} 287 288################################################################################ 289main; 290