#!/usr/bin/env python
'''
== Infos ==
Print (1) packages used by a binary, and (2) the list of installed patches
related to these packages. If you have a binary that works with Solaris 10 update N, but doesn't with Solaris 10 update N-2, run this script on both platform and it will help you to find the patches you're looking for.
(1) is retrieved:
* By using pldd(pid) on the process you want to trace to get a list of loaded
shared library
* By retrieving in the main /var/sadm/install/contents database
the list of package related to these shared libraries
(2) is retrieved by parsing the output of the showrev -p command, given as
input of this script
Requires Python 2.3 (Set module usage)
== Usage ==
Use the -h / --help switch for options.
# give it a pid
$ pldd2pkg.py -p `pgrep dtexec`
# give it a file (offline)
$ pldd 732 > /tmp/foobar
$ pldd2pkg.py -l /tmp/foobar
Written by Benjamin Sergeant: bsergean@gmail.com
'''
import sys
from optparse import OptionParser
from sets import Set
from commands import getoutput
from cStringIO import StringIO
def uniq(alist): # Fastest order preserving
''' Helper from comments in http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560 '''
set = {}
return [set.setdefault(e,e) for e in alist if e not in set]
def package_info(p):
'''
$ pkginfo SUNWglrt
application SUNWglrt Sun OpenGL for Solaris Runtime Libraries
'''
output = getoutput('pkginfo %s' % p)
return (' ').join(output.split()[2:])
def showrev_minus_p():
print 'Get a list of patches installed on the system with `showrev -p`'
return getoutput('showrev -p')
def pldd(pid):
return getoutput('pldd %s' % pid)
def main(pldd_fo):
# Read list of libraries from pldd output
# First name is name of binary
binaries = pldd_fo.read().splitlines()
shared_libraries = binaries[1:]
executable = binaries[0]
# Create a map between files and SVR4 packages
map_files_package = {}
map_files_package_lines = open('/var/sadm/install/contents').read().splitlines()
for l in map_files_package_lines:
tokens = l.split()
# we are only interested in regular files
if tokens[1] == 'f':
fn, pkg = tokens[0], tokens[-1]
map_files_package[fn] = pkg
# Output
print executable
for f in shared_libraries:
if f in map_files_package:
print '\t', f, ' -> ', map_files_package[f]
else:
print '\t', f, ' -> ', '(Not found)'
print
print 'package used:'
pkg_used = [map_files_package[f] for f in shared_libraries if f in map_files_package]
pkg_used = uniq(pkg_used)
pkg_used_set = Set(pkg_used)
for p in pkg_used:
print '\t', p, ' -> ', package_info(p)
print
# Create a map between patch and SVR4 packages
map_patch_package = {}
showrev_fo = StringIO(showrev_minus_p())
map_patch_package_lines = showrev_fo.read().splitlines()
for l in map_patch_package_lines:
# Patch: 108806-17 Obsoletes: Requires: Incompatibles: Packages: SUNWqfed, SUNWqfedu
patch = l.split()[1]
tokens = l.split(':')
packages = [p.strip() for p in tokens[5].split(',')]
packages_set = Set(packages)
if packages_set & pkg_used_set:
map_patch_package[patch] = packages
# Output used patch / per patch
print 'patch related to package used:'
for k, v in map_patch_package.iteritems():
print '\t', k, ' -> ', (', ').join(v)
print
# Output used patch / per package
print 'patch related to package used:'
for p in pkg_used:
print '\t', p, ' -> ', package_info(p)
patches = [k for k, v in map_patch_package.iteritems() if p in v]
patches.sort()
for p in patches:
print '\t\t', p
print
if __name__ == "__main__":
# parse args
parser = OptionParser(usage = "usage: %prog <options>")
parser.add_option("-p", "--pid", dest="pid", default='',
help="The pid of the process to analyse")
parser.add_option("-l", "--pldd-file", dest="pldd_fn", default='',
help="offline: The file captured output of the pldd pid command")
options, args = parser.parse_args()
if options.pldd_fn:
pldd_fo = open(options.pldd_fn)
elif options.pid:
pldd_fo = StringIO(pldd(options.pid))
main(pldd_fo)