LibraryReport.tcl 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. #!/bin/sh
  2. # tcl magic \
  3. exec 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. # findLibs
  57. #
  58. # Ask ldconfig where it thinks libraries are to be found. Go look for them, and
  59. # add an element to 'Libs' for everything that looks like a library.
  60. #
  61. proc findLibs {} {
  62. global Libs stats verbose;
  63. # Older ldconfigs return a junk value when asked for a report
  64. if {[catch {set liblist [exec ldconfig -r]} err]} { # get ldconfig output
  65. puts stderr "ldconfig returned nonzero, persevering.";
  66. set liblist $err; # there's junk in this
  67. }
  68. # remove hintsfile name, convert to list
  69. set liblist [lrange [split $liblist "\n"] 1 end];
  70. set libdirs ""; # no directories yet
  71. foreach line $liblist {
  72. # parse ldconfig output
  73. if {[scan $line "%s => %s" junk libname] == 2} {
  74. # find directory name
  75. set libdir [file dirname $libname];
  76. # have we got this one already?
  77. if {[lsearch -exact $libdirs $libdir] == -1} {
  78. lappend libdirs $libdir;
  79. }
  80. } else {
  81. puts stderr "Unparseable ldconfig output line :";
  82. puts stderr $line;
  83. }
  84. }
  85. # libdirs is now a list of directories that we might find libraries in
  86. foreach dir $libdirs {
  87. # get the names of anything that looks like a library
  88. set libnames [glob -nocomplain "$dir/lib*.so.*"]
  89. foreach lib $libnames {
  90. set type [file type $lib]; # what is it?
  91. switch $type {
  92. file { # looks like a library
  93. # may have already been referenced by a symlink
  94. if {![info exists Libs($lib)]} {
  95. set Libs($lib) ""; # add it to our list
  96. if {$verbose} {puts "+ $lib";}
  97. }
  98. }
  99. link { # symlink; probably to another library
  100. # If the readlink fails, the symlink is stale
  101. if {[catch {set ldest [file readlink $lib]}]} {
  102. puts stderr "Symbolic link points to nothing : $lib";
  103. } else {
  104. # may have already been referenced by another symlink
  105. if {![info exists Libs($lib)]} {
  106. set Libs($lib) ""; # add it to our list
  107. if {$verbose} {puts "+ $lib";}
  108. }
  109. # list the symlink as a consumer of this library
  110. lappend Libs($ldest) "($lib)";
  111. if {$verbose} {puts "-> $ldest";}
  112. }
  113. }
  114. }
  115. }
  116. }
  117. set stats(libs) [llength [array names Libs]];
  118. }
  119. ################################################################################
  120. # findLibUsers
  121. #
  122. # Look in the directory (dir) for executables. If we find any, call
  123. # examineExecutable to see if it uses any shared libraries. Call ourselves
  124. # on any directories we find.
  125. #
  126. # Note that the use of "*" as a glob pattern means we miss directories and
  127. # executables starting with '.'. This is a Feature.
  128. #
  129. proc findLibUsers {dir} {
  130. global stats verbose;
  131. if {[catch {
  132. set ents [glob -nocomplain "$dir/*"];
  133. } msg]} {
  134. if {$msg == ""} {
  135. set msg "permission denied";
  136. }
  137. puts stderr "Can't search under '$dir' : $msg";
  138. return ;
  139. }
  140. if {$verbose} {puts "===>> $dir";}
  141. incr stats(dirs);
  142. # files?
  143. foreach f $ents {
  144. # executable?
  145. if {[file executable $f]} {
  146. # really a file?
  147. if {[file isfile $f]} {
  148. incr stats(files);
  149. examineExecutable $f;
  150. }
  151. }
  152. }
  153. # subdirs?
  154. foreach f $ents {
  155. # maybe a directory with more files?
  156. # don't use 'file isdirectory' because that follows symlinks
  157. if {[catch {set type [file type $f]}]} {
  158. continue ; # may not be able to stat
  159. }
  160. if {$type == "directory"} {
  161. findLibUsers $f;
  162. }
  163. }
  164. }
  165. ################################################################################
  166. # examineExecutable
  167. #
  168. # Look at (fname) and see if ldd thinks it references any shared libraries.
  169. # If it does, update Libs with the information.
  170. #
  171. proc examineExecutable {fname} {
  172. global Libs stats verbose;
  173. # ask Mr. Ldd.
  174. if {[catch {set result [exec ldd $fname]} msg]} {
  175. return ; # not dynamic
  176. }
  177. if {$verbose} {puts -nonewline "$fname : ";}
  178. incr stats(execs);
  179. # For a non-shared executable, we get a single-line error message.
  180. # For a shared executable, we get a heading line, so in either case
  181. # we can discard the first line and any subsequent lines are libraries
  182. # that are required.
  183. set llist [lrange [split $result "\n"] 1 end];
  184. set uses "";
  185. foreach line $llist {
  186. if {[scan $line "%s => %s %s" junk1 lib junk2] == 3} {
  187. if {$lib == "not"} { # "not found" error
  188. set mlname [string range $junk1 2 end];
  189. puts stderr "$fname : library '$mlname' not known.";
  190. } else {
  191. lappend Libs($lib) $fname;
  192. lappend uses $lib;
  193. }
  194. } else {
  195. puts stderr "Unparseable ldd output line :";
  196. puts stderr $line;
  197. }
  198. }
  199. if {$verbose} {puts "$uses";}
  200. }
  201. ################################################################################
  202. # emitLibDetails
  203. #
  204. # Emit a listing of libraries and the executables that use them.
  205. #
  206. proc emitLibDetails {} {
  207. global Libs;
  208. # divide into used/unused
  209. set used "";
  210. set unused "";
  211. foreach lib [array names Libs] {
  212. if {$Libs($lib) == ""} {
  213. lappend unused $lib;
  214. } else {
  215. lappend used $lib;
  216. }
  217. }
  218. # emit used list
  219. puts "== Current Shared Libraries ==================================================";
  220. foreach lib [lsort $used] {
  221. # sort executable names
  222. set users [lsort $Libs($lib)];
  223. puts [format "%-30s %s" $lib $users];
  224. }
  225. # emit unused
  226. puts "== Stale Shared Libraries ====================================================";
  227. foreach lib [lsort $unused] {
  228. # sort executable names
  229. set users [lsort $Libs($lib)];
  230. puts [format "%-30s %s" $lib $users];
  231. }
  232. }
  233. ################################################################################
  234. # Run the whole shebang
  235. #
  236. proc main {} {
  237. global stats verbose argv;
  238. set verbose 0;
  239. foreach arg $argv {
  240. switch -- $arg {
  241. -v {
  242. set verbose 1;
  243. }
  244. default {
  245. puts stderr "Unknown option '$arg'.";
  246. exit ;
  247. }
  248. }
  249. }
  250. set stats(libs) 0;
  251. set stats(dirs) 0;
  252. set stats(files) 0;
  253. set stats(execs) 0
  254. findLibs;
  255. findLibUsers "/";
  256. emitLibDetails;
  257. puts [format "Searched %d directories, %d executables (%d dynamic) for %d libraries." \
  258. $stats(dirs) $stats(files) $stats(execs) $stats(libs)];
  259. }
  260. ################################################################################
  261. main;