ccd23a4
#!/usr/bin/env python
ccd23a4
ccd23a4
import exceptions
ff8b7eb
import getopt
ff8b7eb
import os
ff8b7eb
import re
ccd23a4
import rpm
ccd23a4
import select
ccd23a4
import subprocess
ff8b7eb
import sys
ff8b7eb
ff8b7eb
#------------------------------------------------------------------------------
ff8b7eb
ff8b7eb
def get_rlms(root):
ff8b7eb
    rlm_re = re.compile(r'^rlm_')
ff8b7eb
    version_re = re.compile(r'-[0-9.]+\.so$')
ff8b7eb
    names = os.listdir(root)
ff8b7eb
    names = [x for x in names if rlm_re.search(x)]
ff8b7eb
    names = [x for x in names if not version_re.search(x)]
ff8b7eb
    names.sort()
ff8b7eb
    return names
ccd23a4
ccd23a4
#------------------------------------------------------------------------------
ccd23a4
ccd23a4
debug = False
ccd23a4
verbose = False
ccd23a4
ccd23a4
exclude_rpms = ['glibc']
ccd23a4
ff8b7eb
build = '2.0.2-1.fc8'
ff8b7eb
root_template = '/var/tmp/freeradius-%s-root-jdennis/usr/lib/freeradius'
b281c60
libdirs = ['/lib','/usr/lib']
ccd23a4
ccd23a4
#------------------------------------------------------------------------------
ccd23a4
ccd23a4
def get_rpm_nvr_from_header(hdr):
ccd23a4
    'Given an RPM header return the package NVR as a string'
ccd23a4
    name    = hdr['name']
ccd23a4
    version = hdr['version']
ccd23a4
    release = hdr['release']
ccd23a4
ccd23a4
    return "%s-%s-%s" % (name, version, release)
ccd23a4
ccd23a4
def get_rpm_hdr_by_file_path(path):
ccd23a4
    if path is None:
ccd23a4
        return None
ccd23a4
ccd23a4
    hdr = None
ccd23a4
    try:
ccd23a4
        ts = rpm.ts()
ccd23a4
        mi = ts.dbMatch(rpm.RPMTAG_BASENAMES, path)
ccd23a4
        for hdr in mi: break
ccd23a4
    except Exception, e:
ccd23a4
        print >> sys.stderr, "failed to retrieve rpm hdr for %s, %s" %(path, e)
ccd23a4
        hdr = None
ccd23a4
    return hdr
ccd23a4
ccd23a4
def get_rpm_nvr_by_file_path(path):
ccd23a4
    if path is None:
ccd23a4
        return None
ccd23a4
ccd23a4
    hdr = get_rpm_hdr_by_file_path(path)
ccd23a4
    if not hdr:
ccd23a4
        print >> sys.stderr, "failed to retrieve rpm info for %s" %(path)
ccd23a4
    nvr = get_rpm_nvr_from_header(hdr)
ccd23a4
    return nvr
ccd23a4
ccd23a4
def get_rpm_name_by_file_path(path):
ccd23a4
    if path is None:
ccd23a4
        return None
ccd23a4
ccd23a4
    hdr = get_rpm_hdr_by_file_path(path)
ccd23a4
    if not hdr:
ccd23a4
        print >> sys.stderr, "failed to retrieve rpm info for %s" %(path)
ccd23a4
    name = hdr['name']
ccd23a4
    return name
ccd23a4
ccd23a4
#------------------------------------------------------------------------------
ccd23a4
ccd23a4
class CmdError(exceptions.Exception):
ccd23a4
    def __init__(self, errno, msg):
ccd23a4
        self.errno = errno
ccd23a4
        self.msg = msg
ccd23a4
ccd23a4
ccd23a4
class Command:
ccd23a4
    def __init__(self, cmd):
ccd23a4
        self.cmd = cmd
ccd23a4
        self.sub_process = None
ccd23a4
        self.bufsize = 1024
ccd23a4
        self.stdout_buf = ''
ccd23a4
        self.stderr_buf = ''
ccd23a4
        self.stdout_lines = []
ccd23a4
        self.stderr_lines = []
ccd23a4
ccd23a4
    def run(self, stdout_callback=None, stderr_callback=None):
ccd23a4
        self.sub_process = subprocess.Popen(self.cmd, \
ccd23a4
            stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, \
ccd23a4
            close_fds=True, shell=True)
ccd23a4
        self.stdout = self.sub_process.stdout
ccd23a4
        self.stderr = self.sub_process.stderr
ccd23a4
ccd23a4
        read_watch = [self.stdout, self.stderr]
ccd23a4
        while read_watch:
ccd23a4
            readable = select.select(read_watch, [], [])[0]
ccd23a4
            for fd in readable:
ccd23a4
                if fd == self.stdout:
ccd23a4
                    data = os.read(fd.fileno(), self.bufsize)
ccd23a4
                    if not data:
ccd23a4
                        read_watch.remove(fd)
ccd23a4
                    else:
ccd23a4
                        self.stdout_buf += data
ccd23a4
                    for line in self.burst_lines('stdout_buf'):
ccd23a4
                        if stdout_callback: stdout_callback(line)
ccd23a4
                        self.stdout_lines.append(line)
ccd23a4
                if fd == self.stderr:
ccd23a4
                    data = os.read(fd.fileno(), self.bufsize)
ccd23a4
                    if not data:
ccd23a4
                        read_watch.remove(fd)
ccd23a4
                    else:
ccd23a4
                        self.stderr_buf += data
ccd23a4
                    for line in self.burst_lines('stderr_buf'):
ccd23a4
                        if stdout_callback: stderr_callback(line)
ccd23a4
                        self.stderr_lines.append(line)
ccd23a4
ccd23a4
        self.returncode = self.sub_process.wait()
ccd23a4
        if self.returncode:
ccd23a4
            raise CmdError(self.returncode, "cmd \"%s\"\nreturned status %d\n%s" % (self.cmd, self.returncode, ''.join(self.stderr_lines)))
ccd23a4
ccd23a4
        return self.returncode
ccd23a4
ccd23a4
    def burst_lines(self, what):
ccd23a4
        buf = getattr(self, what)
ccd23a4
        start = 0
ccd23a4
        end = buf.find('\n', start)
ccd23a4
        while end >= 0:
ccd23a4
            end += 1                # include newline
ccd23a4
            line = buf[start:end]
ccd23a4
            yield line
ccd23a4
            start = end
ccd23a4
            end = buf.find('\n', start)
ccd23a4
        buf = buf[start:]
ccd23a4
        setattr(self, what, buf)
ccd23a4
b281c60
b281c60
#------------------------------------------------------------------------------
b281c60
b281c60
def get_so_requires(path):
b281c60
    requires = {}
b281c60
    cmd = 'ldd %s' % (path)
b281c60
    so_re = re.compile(r'^\s*(\S+)\s+=>\s+(\S+)')
b281c60
b281c60
    c = Command(cmd)
b281c60
    status = c.run()
b281c60
b281c60
    for line in c.stdout_lines:
b281c60
        line = line.strip()
b281c60
        match = so_re.search(line)
b281c60
        if match:
b281c60
            so_name = match.group(1)
b281c60
            if match.group(2).startswith('/'):
b281c60
                so_path = match.group(2)
b281c60
            else:
b281c60
                so_path = None
b281c60
b281c60
            requires[so_name] = so_path
b281c60
    return requires
b281c60
b281c60
def get_so_needed(path):
b281c60
    needed = []
b281c60
    cmd = 'readelf -d %s' % (path)
b281c60
    so_re = re.compile(r'\(NEEDED\)\s+Shared library:\s+\[([^\]]+)\]')
b281c60
b281c60
    c = Command(cmd)
b281c60
    status = c.run()
b281c60
b281c60
    for line in c.stdout_lines:
b281c60
        line = line.strip()
b281c60
        match = so_re.search(line)
b281c60
        if match:
b281c60
            so_name = match.group(1)
b281c60
            needed.append(so_name)
b281c60
    return needed
b281c60
b281c60
def format_size(size):
b281c60
    if size > 1000000000:
b281c60
        return '%.1f GB' % (size/1000000000.0)
b281c60
    if size > 1000000:
b281c60
        return '%.1f MB' % (size/1000000.0)
b281c60
    if size > 1000:
b281c60
        return '%.1f KB' % (size/1000.0)
b281c60
    return '%d' % (size)
ccd23a4
#------------------------------------------------------------------------------
ccd23a4
ccd23a4
class RPM_Prop:
ccd23a4
    def __init__(self, path=None, name=None):
ccd23a4
        self.name = name
ccd23a4
        self.paths = {}
b281c60
        self.rpm_hdr = None
b281c60
        self.used_by = {}
ccd23a4
        if path:
ccd23a4
            self.register_path(path)
b281c60
            if not self.rpm_hdr:
b281c60
                self.rpm_hdr = get_rpm_hdr_by_file_path(path)
b281c60
            if self.rpm_hdr:
b281c60
                if not self.name:
b281c60
                    self.name = self.rpm_hdr[rpm.RPMTAG_NAME]
b281c60
                self.size = self.rpm_hdr[rpm.RPMTAG_SIZE]
ccd23a4
ccd23a4
    def __str__(self):
ccd23a4
        return "name=%s paths=%s" % (self.name, ','.join(self.paths.keys()))
ccd23a4
ccd23a4
    def register_path(self, path, name=None):
ccd23a4
        if debug: print "%s.register_path: path=%s" % (self.__class__.__name__, path)
ccd23a4
        return self.paths.setdefault(path, path)
ccd23a4
ccd23a4
class RPM_Collection:
ccd23a4
    def __init__(self):
ccd23a4
        self.names = {}
ccd23a4
        self.paths = {}
ccd23a4
ccd23a4
    def __str__(self):
ccd23a4
        text = ''
ccd23a4
        names = self.get_names()
ccd23a4
        for name in names:
ccd23a4
            text += "%s: %s\n" % (name, self.names[name])
ccd23a4
        return text
ccd23a4
ccd23a4
    def register_path(self, path):
ccd23a4
        if debug: print "%s.register_path: path=%s" % (self.__class__.__name__, path)
ff8b7eb
        rpm_prop = self.paths.get(path)
ff8b7eb
        if not rpm_prop:
ff8b7eb
            rpm_prop = self.paths.setdefault(path, RPM_Prop(path=path))
ccd23a4
        self.names.setdefault(rpm_prop.name, rpm_prop)
ccd23a4
        return rpm_prop
ccd23a4
ccd23a4
    def get_names(self):
ccd23a4
        names = self.names.keys()
ccd23a4
        names.sort()
ccd23a4
        return names
ccd23a4
b281c60
    def get_name(self, name):
b281c60
        return self.names.get(name)
b281c60
ccd23a4
class SO_File:
ccd23a4
    def __init__(self, name=None, path=None):
ccd23a4
        self.name = name
ccd23a4
        self.path = path
ccd23a4
        self.rpm = None
ccd23a4
ccd23a4
    def __str__(self):
ccd23a4
        if self.rpm:
ccd23a4
            rpm_name = self.rpm.name
ccd23a4
        else:
ccd23a4
            rpm_name = None
ccd23a4
        return "name=%s rpm=%s" % (self.name, rpm_name)
ccd23a4
ccd23a4
class SO_Collection:
ccd23a4
    def __init__(self):
ccd23a4
        self.names = {}
ccd23a4
        self.paths = {}
ccd23a4
ccd23a4
    def __str__(self):
ccd23a4
        text = ''
ccd23a4
        names = self.get_names()
ccd23a4
        for name in names:
ccd23a4
            text += "%s: %s\n" % (name, self.names[name])
ccd23a4
        return text
ccd23a4
ccd23a4
    def register_path(self, path, name=None):
ccd23a4
        if debug: print "%s.register_path: path=%s" % (self.__class__.__name__, path)
ff8b7eb
        so_prop = self.paths.get(path)
ff8b7eb
        if not so_prop:
ff8b7eb
            so_prop = self.paths.setdefault(path, SO_File(name, path=path))
ccd23a4
        self.names.setdefault(name, so_prop)
ccd23a4
        return so_prop
ccd23a4
ccd23a4
    def get_names(self):
ccd23a4
        names = self.names.keys()
ccd23a4
        names.sort()
ccd23a4
        return names
ccd23a4
ccd23a4
class LoadableModule:
ccd23a4
    def __init__(self, path, name=None):
ccd23a4
        if name is None:
ccd23a4
            name = os.path.basename(path)
ccd23a4
        self.name = name
ccd23a4
        self.path = path
ff8b7eb
        self.rpm_names = {}
ccd23a4
        self.sos = SO_Collection()
b281c60
        self.get_so_requires()
ccd23a4
ccd23a4
    def __str__(self):
ccd23a4
        text = '%s\n' % (self.name)
ff8b7eb
        text += "    RPM's: %s\n" % (','.join(self.get_rpm_names()))
ccd23a4
        text += "    SO's: %s\n" % (','.join(self.sos.get_names()))
ccd23a4
        return text
ccd23a4
b281c60
    def get_so_requires(self):
b281c60
        requires = get_so_requires(self.path)
b281c60
        needed = get_so_needed(self.path)
b281c60
        #print "%s requires=%s" % (self.name, requires)
b281c60
        #print "%s needed=%s" % (self.name, needed)
b281c60
b281c60
        for so_name, so_path in requires.items():
b281c60
            if so_name not in needed: continue
b281c60
            if so_path:
b281c60
                so_prop = self.sos.register_path(so_path, so_name)
b281c60
                rpm_prop = rpms.register_path(so_prop.path)
b281c60
                rpm_prop.used_by[self.name] = 1
b281c60
                self.rpm_names.setdefault(rpm_prop.name, rpm_prop.name)
b281c60
                so_prop.rpm = rpm_prop
b281c60
            else:
b281c60
                so_prop = None
b281c60
            if verbose: print "found so='%s' %s" % (so_name, so_prop)
ccd23a4
ccd23a4
    def register_so(self, so):
ccd23a4
        if debug: print "%s.register_so: so=%s" % (self.__class__.__name__, so)
ccd23a4
        self.sos.setdefault(so, so)
ccd23a4
        self.names.setdefault(so.name, so)
ccd23a4
        return so
ccd23a4
ff8b7eb
    def get_rpm_names(self):
ff8b7eb
        rpm_names = self.rpm_names.keys()
ff8b7eb
        rpm_names.sort()
ff8b7eb
        return rpm_names
ff8b7eb
ccd23a4
    def get_sos(self):
ccd23a4
        sos = self.sos.keys()
ccd23a4
        sos.sort(lambda a,b: cmp(a.name, b.name))
ccd23a4
        return sos
ccd23a4
ccd23a4
#------------------------------------------------------------------------------
ccd23a4
ccd23a4
#------------------------------------------------------------------------------
ccd23a4
ff8b7eb
opts, args = getopt.getopt(sys.argv[1:], "b:v", ['build=','verbose'])
ff8b7eb
for o, a in opts:
ff8b7eb
    if o in ['-b', '--build']:
ff8b7eb
        build = a
ff8b7eb
    elif o in ['-v', '--verbose']:
ff8b7eb
        verbose = True
ff8b7eb
    else:
ff8b7eb
        print >> sys.stderr, "Unknown arg: %s" % o
ff8b7eb
        sys.exit(1)
ff8b7eb
ff8b7eb
root = root_template % build
ff8b7eb
modules = get_rlms(root)
ff8b7eb
module_paths = [os.path.join(root,x) for x in modules]
ff8b7eb
rpms = RPM_Collection()
ff8b7eb
ccd23a4
lms = []
ccd23a4
for module_path in module_paths[:]:
ccd23a4
    lm = LoadableModule(module_path)
ccd23a4
    lms.append(lm)
ccd23a4
ccd23a4
ff8b7eb
print "RLM Modules(%s): %s\n" % (len(modules), ','.join(modules))
ff8b7eb
ccd23a4
for lm in lms:
ff8b7eb
    rpm_names = [x for x in lm.get_rpm_names() if x not in exclude_rpms]
ff8b7eb
    if rpm_names:
ccd23a4
        print lm.name
ff8b7eb
        print '    %s' % (','.join(rpm_names))
ccd23a4
ccd23a4
print "--------------"
ccd23a4
b281c60
rpm_props = [x for x in rpms.names.values() if len(x.used_by) and x.name not in exclude_rpms]
b281c60
rpm_props.sort(lambda a,b: cmp(a.name, b.name))
b281c60
for rpm_prop in rpm_props:
b281c60
    used_by = rpm_prop.used_by.keys()
b281c60
    used_by.sort()
b281c60
    print "%s: %s" % (rpm_prop.name, ','.join(used_by))
b281c60
b281c60
print "--------------"
b281c60
b281c60
rpm_props.sort(lambda a,b: cmp(a.size, b.size))
b281c60
for rpm_prop in rpm_props:
b281c60
    print '%10s %s' % (format_size(rpm_prop.size), rpm_prop.name)
b281c60
b281c60
b281c60
print "--------------"
b281c60
ccd23a4
for lm in lms:
ccd23a4
    print lm