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