summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorNarayan Desai <desai@mcs.anl.gov>2007-04-05 19:17:10 +0000
committerNarayan Desai <desai@mcs.anl.gov>2007-04-05 19:17:10 +0000
commitca0148e3ee939b0eb84e61ba40c5fbdc9b81afc2 (patch)
treef61918c3bd582b2122a16969facf6e38fb713bba /src
parent62f2da3e6c4f26225e418336572a4ef48968021e (diff)
downloadbcfg2-ca0148e3ee939b0eb84e61ba40c5fbdc9b81afc2.tar.gz
bcfg2-ca0148e3ee939b0eb84e61ba40c5fbdc9b81afc2.tar.bz2
bcfg2-ca0148e3ee939b0eb84e61ba40c5fbdc9b81afc2.zip
Add in verify-only version of the RPMng driver (Resolves Ticket #394)
git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@3013 ce84e21b-d406-0410-9b95-82705330c041
Diffstat (limited to 'src')
-rw-r--r--src/lib/Client/Tools/RPMng.py534
-rw-r--r--src/lib/Client/Tools/__init__.py9
-rwxr-xr-xsrc/lib/Client/Tools/rpmtools.py1061
3 files changed, 1600 insertions, 4 deletions
diff --git a/src/lib/Client/Tools/RPMng.py b/src/lib/Client/Tools/RPMng.py
new file mode 100644
index 000000000..dcda1bed7
--- /dev/null
+++ b/src/lib/Client/Tools/RPMng.py
@@ -0,0 +1,534 @@
+'''Bcfg2 Support for RPMS'''
+
+__revision__ = '$Revision$'
+
+import Bcfg2.Client.Tools, time, rpmtools, sys
+
+class RPMng(Bcfg2.Client.Tools.PkgTool):
+ '''Support for RPM packages'''
+ __name__ = 'RPMng'
+ __execs__ = ['/bin/rpm', '/var/lib/rpm']
+ __handles__ = [('Package', 'rpm')]
+
+ __req__ = {'Package': ['name', 'version']}
+ __ireq__ = {'Package': ['name', 'version', 'url']}
+
+ __new_req__ = {'Package': ['name'], 'Instance': ['version', 'release', 'arch']}
+ __new_ireq__ = {'Package': ['name', 'uri'], \
+ 'Instance': ['simplefile', 'version', 'release', 'arch']}
+
+ __gpg_req__ = {'Package': ['name'], 'Instance': ['version', 'release']}
+ __gpg_ireq__ = {'Package': ['name'], 'Instance': ['version', 'release']}
+
+ conflicts = ['RPM']
+
+ pkgtype = 'rpm'
+ pkgtool = ("rpm --oldpackage --replacepkgs --quiet -U %s", ("%s", ["url"]))
+
+ # This is mostly the default list from YUM on Centos 4. Check these are
+ # still correct.
+ # ***** Should probably put in bcfg2.config somewhere. *****
+ installOnlyPkgs = ['kernel', 'kernel-bigmem', 'kernel-enterprise', 'kernel-smp',
+ 'kernel-modules', 'kernel-debug', 'kernel-unsupported',
+ 'kernel-source', 'kernel-devel', 'kernel-default',
+ 'kernel-largesmp-devel', 'kernel-largesmp', 'gpg-pubkey']
+
+ def __init__(self, logger, setup, config, states):
+ Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config, states)
+
+ self.instance_status = {}
+
+ def RefreshPackages(self):
+ '''
+ Creates self.installed{} which is a dict of installed packages.
+
+ The dict items are lists of nevra dicts. This loosely matches the
+ config from the server and what rpmtools uses to specify pacakges.
+
+ e.g.
+
+ self.installed['foo'] = [ {'name':'foo', 'epoch':None,
+ 'version':'1', 'release':2,
+ 'arch':'i386'},
+ {'name':'foo', 'epoch':None,
+ 'version':'1', 'release':2,
+ 'arch':'x86_64'} ]
+ '''
+ self.installed = {}
+ refresh_ts = rpmtools.rpmtransactionset()
+ for nevra in rpmtools.rpmpackagelist(refresh_ts):
+ self.installed.setdefault(nevra['name'], []).append(nevra)
+ if self.setup['debug']:
+ print "The following package instances are installed:"
+ for name, instances in self.installed.iteritems():
+ self.logger.info(" " + name)
+ for inst in instances:
+ self.logger.info(" %s:%s-%s.%s" % \
+ (inst.get('epoch', None), inst.get('version', None),
+ inst.get('release', None), inst.get('arch', None)))
+ refresh_ts.closeDB()
+
+ def VerifyPackage(self, entry, modlist):
+ '''
+ Verify Package status for entry.
+ Compares the 'version' info against self.installed{} and does
+ an rpm level package verify.
+
+ Code for the old/new style Package Entries has been kept separate,
+ even though it has meant some code duplication, so that the old style
+ code can be easily removed at a later date.
+ '''
+
+ instances = entry.findall('Instance')
+ if not instances:
+ # We have an old style no Instance entry.
+ if entry.get('release', None) == None:
+ version, release = entry.get('version').split('-')
+ entry.set('version', version)
+ entry.set('release', release)
+ instances = [ entry ]
+
+ vp_ts = rpmtools.rpmtransactionset()
+
+ self.logger.info("Verifying package instances for %s" % entry.get('name'))
+ package_fail = False
+ qtext_versions = ''
+
+ if self.installed.has_key(entry.get('name')):
+ # There is at least one instance installed.
+ if entry.get('name') in self.installOnlyPkgs:
+ # Packages that should only be installed or removed.
+ # e.g. kernels.
+ self.logger.info(" Install only package.")
+ for inst in instances:
+ self.instance_status.setdefault(inst, {})['installed'] = False
+ self.instance_status[inst]['version_fail'] = False
+ if inst.tag == 'Package' and len(self.installed[entry.get('name')]) > 1:
+ self.logger.error("WARNING: Multiple instances of package %s are installed." % (entry.get('name')))
+ for pkg in self.installed[entry.get('name')]:
+ if inst.tag == 'Package':
+ # We have an old style Package entry that does not
+ # have an epoch or an arch, so scrub them from
+ # installed{}.
+ pkg.pop('arch', None)
+ pkg.pop('epoch', None)
+ if inst.get('epoch', None) != None:
+ epoch = int(inst.get('epoch'))
+ else:
+ epoch = None
+ if epoch == pkg.get('epoch') and \
+ inst.get('version') == pkg.get('version') and \
+ inst.get('release') == pkg.get('release') and \
+ inst.get('arch', None) == pkg.get('arch', None):
+ self.logger.info(" %s:%s-%s.%s" % \
+ (inst.get('epoch', None), inst.get('version'),
+ inst.get('release'), inst.get('arch', None)))
+ self.logger.debug(" verify_flags = %s" % \
+ (inst.get('verify_flags', [])))
+ self.instance_status[inst]['installed'] = True
+ self.instance_status[inst]['verify'] = \
+ rpmtools.rpm_verify( vp_ts, pkg, \
+ inst.get('verify_flags', '').split(','))
+
+ if self.instance_status[inst]['installed'] == False:
+ package_fail = True
+ self.logger.info(" Package %s %s:%s-%s.%s not installed." % \
+ (entry.get('name'),
+ inst.get('epoch', None), inst.get('version'),
+ inst.get('release'), inst.get('arch', None)))
+
+ qtext_versions = qtext_versions + '%s:%s-%s.%s ' % \
+ (inst.get('epoch', ''), inst.get('version', ''),\
+ inst.get('release', ''), inst.get('arch', ''))
+
+ entry.set('current_exists', 'false')
+ else:
+ # Normal Packages that can be upgraded.
+ for inst in instances:
+ self.instance_status.setdefault(inst, {})['installed'] = False
+ self.instance_status[inst]['version_fail'] = False
+
+ # Only installed packages with the same architecture are
+ # relevant.
+ if inst.get('arch', None) == None:
+ arch_match = self.installed[entry.get('name')]
+ else:
+ arch_match = [pkg for pkg in self.installed[entry.get('name')] \
+ if pkg.get('arch', None) == inst.get('arch', None)]
+
+ if len(arch_match) > 1:
+ self.logger.error("Multiple instances of package %s installed with the same achitecture." % \
+ (entry.get('name')))
+ elif len(arch_match) == 1:
+ # There is only one installed like there should be.
+ # Check that it is the right version.
+ for pkg in arch_match:
+ if inst.tag == 'Package':
+ # We have an old style Package entry that does not
+ # have an epoch or an arch, so scrub them from
+ # installed{}.
+ pkg.pop('arch', None)
+ pkg.pop('epoch', None)
+ if inst.get('epoch', None) != None:
+ epoch = int(inst.get('epoch'))
+ else:
+ epoch = None
+ if epoch == pkg.get('epoch', None) and \
+ inst.get('version') == pkg.get('version') and \
+ inst.get('release') == pkg.get('release'):
+ self.logger.info(" %s:%s-%s.%s" % \
+ (inst.get('epoch', None), inst.get('version', ''), \
+ inst.get('release', ''), inst.get('arch', None)))
+ self.logger.debug(" verify_flags = %s" % \
+ (inst.get('verify_flags', [])))
+ self.instance_status[inst]['installed'] = True
+ self.instance_status[inst]['verify'] = \
+ rpmtools.rpm_verify( vp_ts, pkg,\
+ inst.get('verify_flags', '').split(','))
+ else:
+ package_fail = True
+ self.instance_status[inst]['version_fail'] = True
+ self.logger.info(" Wrong version installed. Want %s:%s-%s.%s, but have %s:%s-%s.%s" % \
+ (inst.get('epoch', None), inst.get('version'), \
+ inst.get('release'), inst.get('arch', None), \
+ pkg.get('epoch'), pkg.get('version'), \
+ pkg.get('release'), pkg.get('arch')))
+
+ qtext_versions = qtext_versions + \
+ '(%s:%s-%s.%s -> %s:%s-%s.%s) ' % \
+ (pkg.get('epoch', ''), pkg.get('version'), \
+ pkg.get('release'), pkg.get('arch', ''), \
+ inst.get('epoch', ''), inst.get('version'), \
+ inst.get('release'), inst.get('arch', ''))
+ elif len(arch_match) == 0:
+ # This instance is not installed.
+ self.instance_status[inst]['installed'] = False
+
+ self.logger.info(" %s:%s-%s.%s is not installed." % \
+ (inst.get('epoch', None), inst.get('version'), \
+ inst.get('release'), inst.get('arch', None)))
+
+ qtext_versions = qtext_versions + '%s:%s-%s.%s ' % \
+ (inst.get('epoch', ''), inst.get('version'), \
+ inst.get('release'), inst.get('arch', ''))
+
+ entry.set('current_exists', 'false')
+
+ for inst in instances:
+ instance_fail = False
+ # Dump the rpm verify results.
+ #****Write something to format this nicely.*****
+ if self.setup['debug'] and self.instance_status[inst].get('verify', None):
+ self.logger.debug(self.instance_status[inst]['verify'])
+
+ # Check the rpm verify results.
+ if self.instance_status[inst].get('verify', None):
+ if len(self.instance_status[inst].get('verify')) > 1:
+ self.logger.info("WARNING: Verification of more than one package instance.")
+
+ self.instance_status[inst]['verify_fail'] = False
+
+ for result in self.instance_status[inst]['verify']:
+
+ # Check header results
+ if result.get('hdr', []):
+ package_fail = True
+ instance_fail = True
+ self.instance_status[inst]['verify_fail'] = True
+
+ # Check dependency results
+ if result.get('deps', []):
+ package_fail = True
+ instance_fail = True
+ self.instance_status[inst]['verify_fail'] = True
+
+ # Check the rpm verify file results against the modlist
+ # and per Instance Ignores.
+ for file_result in result.get('files', []):
+ if file_result[-1] not in modlist and \
+ file_result[-1] not in \
+ [ignore.get('name') for ignore in inst.findall('Ignore')]:
+ package_fail = True
+ instance_fail = True
+ self.instance_status[inst]['verify_fail'] = True
+ else:
+ self.logger.info(" Modlist/Ignore match: %s" % \
+ (file_result[-1]))
+
+ if instance_fail == True:
+ self.logger.info("*** Instance %s:%s-%s.%s failed RPM verification ***" % \
+ (inst.get('epoch', None), inst.get('version'), \
+ inst.get('release'), inst.get('arch', None)))
+
+ qtext_versions = qtext_versions + '%s:%s-%s.%s ' % \
+ (inst.get('epoch', ''), inst.get('version'), \
+ inst.get('release'), inst.get('arch', ''))
+
+ if self.instance_status[inst]['installed'] == False or \
+ self.instance_status[inst]['version_fail'] == True:
+ package_fail = True
+
+ if package_fail == True:
+ self.logger.info(" Package %s failed verification." % \
+ (entry.get('name')))
+ qtext = 'Upgrade/downgrade Package %s instance(s) - %s (y/N) ' % \
+ (entry.get('name'), qtext_versions)
+ entry.set('qtext', qtext)
+ entry.set('current_version', "%s:%s-%s.%s" % \
+ (inst.get('epoch', None), inst.get('version'),
+ inst.get('release'), inst.get('arch', None)))
+ return False
+
+ else:
+ # There are no Instances of this package installed.
+ self.logger.debug("Package %s has no instances installed" % (entry.get('name')))
+ entry.set('current_exists', 'false')
+ for inst in instances:
+ qtext_versions = qtext_versions + '%s:%s-%s.%s ' % \
+ (inst.get('epoch', None), inst.get('version'),
+ inst.get('release'), inst.get('arch', None))
+ self.instance_status.setdefault(inst, {})['installed'] = False
+
+ entry.set('qtext', "Install Package %s Instance(s) %s? (y/N) " % \
+ (entry.get('name'), qtext_versions))
+
+ return False
+ return True
+
+ def RemovePackages(self, packages):
+ '''Remove specified entries'''
+ #pkgnames = [pkg.get('name') for pkg in packages]
+ #if len(pkgnames) > 0:
+ # self.logger.info("Removing packages: %s" % pkgnames)
+ # if self.cmd.run("rpm --quiet -e --allmatches %s" % " ".join(pkgnames))[0] == 0:
+ # self.modified += packages
+ # else:
+ # for pkg in packages:
+ # if self.cmd.run("rpm --quiet -e --allmatches %s" % \
+ # pkg.get('name'))[0] == 0:
+ # self.modified += pkg
+ #
+ # self.RefreshPackages()
+ # self.extra = self.FindExtraPackages()
+ print "The following package instances would have been deleted:"
+ for pkg in packages:
+ print " %s:" % (pkg.get('name'))
+ for inst in pkg:
+ print " %s:%s-%s.%s" % (inst.get('epoch', None), inst.get('version'), \
+ inst.get('release'), inst.get('arch', None))
+
+ def Install(self, packages):
+ '''
+ '''
+ self.logger.info('''The following packages have something wrong with them and RPM.Install()
+ will try and do something to fix them if appropriate:''')
+
+ for pkg in packages:
+ instances = pkg.findall('Instance')
+ if not instances:
+ instances = [ pkg ]
+ for inst in instances:
+ if self.instance_status[inst].get('installed', False) == False or \
+ self.instance_status[inst].get('version_fail', False) == True or \
+ self.instance_status[inst].get('verify_fail', False) == True:
+ print "%s: %s:%s-%s.%s installed = %s, Version_fail = %s, verify_fail = %s" % \
+ (pkg.get('name'),inst.get('epoch', None), inst.get('version'), \
+ inst.get('release'), inst.get('arch', None), \
+ self.instance_status[inst].get('installed', None),\
+ self.instance_status[inst].get('version_fail', None),\
+ self.instance_status[inst].get('verify_fail', None))
+
+ def canInstall(self, entry):
+ '''
+ test if entry has enough information to be installed
+ '''
+ if not self.handlesEntry(entry):
+ return False
+
+ instances = entry.findall('Instance')
+
+ if not instances:
+ # Old non Instance format.
+ if [attr for attr in self.__ireq__[entry.tag] if attr not in entry.attrib]:
+ self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
+ % (entry.tag, entry.get('name')))
+ return False
+ else:
+ if entry.get('name') == 'gpg-pubkey':
+ # gpg-pubkey packages aren't really pacakges, so we have to do
+ # something a little different.
+ # Check that the Package Level has what we need for verification.
+ if [attr for attr in self.__gpg_ireq__[entry.tag] if attr not in entry.attrib]:
+ self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
+ % (entry.tag, entry.get('name')))
+ return False
+ # Check that the Instance Level has what we need for verification.
+ for inst in instances:
+ if [attr for attr in self.__gpg_ireq__[inst.tag] if attr not in inst.attrib]:
+ self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
+ % (inst.tag, inst.get('name')))
+ return False
+ else:
+ # New format with Instances.
+ # Check that the Package Level has what we need for verification.
+ if [attr for attr in self.__new_ireq__[entry.tag] if attr not in entry.attrib]:
+ self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
+ % (entry.tag, entry.get('name')))
+ return False
+ # Check that the Instance Level has what we need for verification.
+ for inst in instances:
+ if [attr for attr in self.__new_ireq__[inst.tag] if attr not in inst.attrib]:
+ self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
+ % (inst.tag, inst.get('name')))
+ return False
+ return True
+
+ def canVerify(self, entry):
+ '''
+ Test if entry has enough information to be verified.
+
+ Three types of entries are checked.
+ Old style Package
+ New style Package with Instances
+ pgp-pubkey packages
+ '''
+ if not self.handlesEntry(entry):
+ return False
+
+ instances = entry.findall('Instance')
+
+ if not instances:
+ # Old non Instance format.
+ if [attr for attr in self.__req__[entry.tag] if attr not in entry.attrib]:
+ self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
+ % (entry.tag, entry.get('name')))
+ return False
+ else:
+ if entry.get('name') == 'gpg-pubkey':
+ # gpg-pubkey packages aren't really pacakges, so we have to do
+ # something a little different.
+ # Check that the Package Level has what we need for verification.
+ if [attr for attr in self.__gpg_req__[entry.tag] if attr not in entry.attrib]:
+ self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
+ % (entry.tag, entry.get('name')))
+ return False
+ # Check that the Instance Level has what we need for verification.
+ for inst in instances:
+ if [attr for attr in self.__gpg_req__[inst.tag] if attr not in inst.attrib]:
+ self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
+ % (inst.tag, inst.get('name')))
+ return False
+ else:
+ # New format with Instances.
+ # Check that the Package Level has what we need for verification.
+ if [attr for attr in self.__new_req__[entry.tag] if attr not in entry.attrib]:
+ self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
+ % (entry.tag, entry.get('name')))
+ return False
+ # Check that the Instance Level has what we need for verification.
+ for inst in instances:
+ if [attr for attr in self.__new_req__[inst.tag] if attr not in inst.attrib]:
+ self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
+ % (inst.tag, inst.get('name')))
+ return False
+ return True
+
+ def FindExtraPackages(self):
+ '''
+ Find extra packages
+ '''
+ extra_packages = []
+ packages = {}
+ for entry in self.getSupportedEntries():
+ packages[entry.get('name')] = entry
+
+ for name, pkg_list in self.installed.iteritems():
+ extra_entry = Bcfg2.Client.XML.Element('Package', name=name, type=self.pkgtype)
+ if packages.get(name, None) != None:
+ # There is supposed to be at least one instance installed.
+ instances = packages[name].findall('Instance')
+ if not instances:
+ instances = [ packages[name] ]
+ if name in self.installOnlyPkgs:
+ for installed_inst in pkg_list:
+ not_found = True
+ for inst in instances:
+ if inst.get('epoch', None) != None:
+ epoch = int(inst.get('epoch'))
+ else:
+ epoch = None
+ if epoch == installed_inst.get('epoch') and \
+ inst.get('version') == installed_inst.get('version') and \
+ inst.get('release') == installed_inst.get('release') and \
+ inst.get('arch', None) == installed_inst.get('arch'):
+ not_found = False
+ break
+ if not_found == True:
+ # Extra package.
+ self.logger.info("Extra InstallOnlyPackage %s %s:%s-%s.%s." % \
+ (name, installed_inst.get('epoch'), \
+ installed_inst.get('version'), \
+ installed_inst.get('release'), \
+ installed_inst.get('arch')))
+ if inst.tag == 'Package':
+ Bcfg2.Client.XML.SubElement(extra_entry, \
+ 'Instance',
+ version = installed_inst.get('version'), \
+ release = installed_inst.get('release'))
+ else:
+ Bcfg2.Client.XML.SubElement(extra_entry, \
+ 'Instance',
+ epoch = str(installed_inst.get('epoch')),\
+ version = installed_inst.get('version'), \
+ release = installed_inst.get('release'), \
+ arch = installed_inst.get('arch', ''))
+ else:
+ # Normal package, only check arch.
+ for installed_inst in pkg_list:
+ not_found = True
+ for inst in instances:
+ if installed_inst.get('arch') == inst.get('arch'):
+ not_found = False
+ break
+ if not_found:
+ self.logger.info("Extra Normal Package Instance %s %s:%s-%s.%s." % \
+ (name, installed_inst.get('epoch'), \
+ installed_inst.get('version'), \
+ installed_inst.get('release'), \
+ installed_inst.get('arch')))
+ if inst.tag == 'Package':
+ Bcfg2.Client.XML.SubElement(extra_entry, \
+ 'Instance',
+ version = installed_inst.get('version'), \
+ release = installed_inst.get('release'))
+ else:
+ Bcfg2.Client.XML.SubElement(extra_entry, \
+ 'Instance',
+ epoch = str(installed_inst.get('epoch')),\
+ version = installed_inst.get('version'), \
+ release = installed_inst.get('release'), \
+ arch = installed_inst.get('arch', ''))
+ else:
+ # Extra package.
+ self.logger.info("No instances of Package %s should be installed." % (name))
+ for installed_inst in pkg_list:
+ if inst.tag == 'Package':
+ Bcfg2.Client.XML.SubElement(extra_entry, \
+ 'Instance',
+ version = installed_inst.get('version'), \
+ release = installed_inst.get('release'))
+ else:
+ Bcfg2.Client.XML.SubElement(extra_entry, \
+ 'Instance',
+ epoch = str(installed_inst.get('epoch')),\
+ version = installed_inst.get('version'), \
+ release = installed_inst.get('release'), \
+ arch = installed_inst.get('arch', ''))
+ if len(extra_entry) > 0:
+ extra_packages.append(extra_entry)
+ else:
+ del extra_entry
+ return extra_packages
+
+
+
diff --git a/src/lib/Client/Tools/__init__.py b/src/lib/Client/Tools/__init__.py
index fd998f271..e761f9d16 100644
--- a/src/lib/Client/Tools/__init__.py
+++ b/src/lib/Client/Tools/__init__.py
@@ -1,11 +1,12 @@
'''This contains all Bcfg2 Tool modules'''
__revision__ = '$Revision$'
-__all__ = ["Action", "APT", "Blast", "Chkconfig", "DebInit", "Encap", "launchd",
- "Portage", "POSIX", "RPM", "RcUpdate", "SMF", "SYSV", "Yum"]
+__all__ = ["Action", "APT", "Blast", "Chkconfig", "DebInit", "Encap",
+ "launchd", "Portage", "POSIX", "RPM", "RPMng", 'rpmtools',
+ "RcUpdate", "SMF", "SYSV", "Yum"]
-drivers = __all__[:]
-default = drivers[:]
+drivers = [item for item in __all__ if item not in ['rpmtools']]
+default = [item for item in drivers if item not in ['RPMng']]
import os, popen2, stat, sys, Bcfg2.Client.XML, time
diff --git a/src/lib/Client/Tools/rpmtools.py b/src/lib/Client/Tools/rpmtools.py
new file mode 100755
index 000000000..41a22cccc
--- /dev/null
+++ b/src/lib/Client/Tools/rpmtools.py
@@ -0,0 +1,1061 @@
+#!/usr/bin/python
+"""
+ Module that uses rpm-python to implement the following rpm
+ functionality for the bcfg2 RPM and YUM client drivers:
+
+ rpm -qa
+ rpm --verify
+ rpm --erase
+
+ The code closely follows the rpm C code.
+
+ The code was written to be used in the bcfg2 RPM/YUM drivers.
+
+ Some command line options have been provided to assist with
+ testing and development, but the output isn't pretty and looks
+ nothing like rpm output.
+
+ Run 'rpmtools' -h for the options.
+
+"""
+__revision__ = '0.3'
+
+import rpm, optparse, pwd, grp
+import sys, os, md5, stat
+
+# Determine what prelink tools we have available.
+# The isprelink module is a python extension that examines the ELF headers
+# to see if the file has been prelinked. If it is not present a lot of files
+# are unnecessarily run through the prelink command.
+try:
+ from isprelink import *
+ isprelink_imported = True
+except ImportError:
+ isprelink_imported = False
+ #print '*********************** isprelink not loaded ***********************'
+
+# If the prelink command is installed on the system then we need to do
+# prelink -y on files.
+if os.access('/usr/sbin/prelink', os.X_OK):
+ prelink_exists = True
+else:
+ prelink_exists = False
+
+# If we don't have isprelink then we will use the prelink configuration file to
+# filter what we have to put through prelink -y.
+import re
+blacklist = []
+whitelist = []
+try:
+ f = open('/etc/prelink.conf', mode='r')
+ for line in f:
+ if line.startswith('#'):
+ continue
+ option, pattern = line.split()
+ if pattern.startswith('*.'):
+ pattern = pattern.replace('*.','\.')
+ pattern += '$'
+ elif pattern.startswith('/'):
+ pattern = '^' + pattern
+ if option == '-b':
+ blacklist.append(pattern)
+ elif option == '-l':
+ whitelist.append(pattern)
+ f.close()
+except IOError:
+ pass
+
+blacklist_re = re.compile('|'.join(blacklist))
+whitelist_re = re.compile('|'.join(whitelist))
+
+# Flags that are not defined in rpm-python.
+# They are defined in lib/rpmcli.h
+# Bit(s) for verifyFile() attributes.
+#
+RPMVERIFY_NONE = 0 # /*!< */
+RPMVERIFY_MD5 = 1 # 1 << 0 # /*!< from %verify(md5) */
+RPMVERIFY_FILESIZE = 2 # 1 << 1 # /*!< from %verify(size) */
+RPMVERIFY_LINKTO = 4 # 1 << 2 # /*!< from %verify(link) */
+RPMVERIFY_USER = 8 # 1 << 3 # /*!< from %verify(user) */
+RPMVERIFY_GROUP = 16 # 1 << 4 # /*!< from %verify(group) */
+RPMVERIFY_MTIME = 32 # 1 << 5 # /*!< from %verify(mtime) */
+RPMVERIFY_MODE = 64 # 1 << 6 # /*!< from %verify(mode) */
+RPMVERIFY_RDEV = 128 # 1 << 7 # /*!< from %verify(rdev) */
+RPMVERIFY_CONTEXTS = 32768 # (1 << 15) # /*!< from --nocontexts */
+RPMVERIFY_READLINKFAIL = 268435456 # (1 << 28) # /*!< readlink failed */
+RPMVERIFY_READFAIL = 536870912 # (1 << 29) # /*!< file read failed */
+RPMVERIFY_LSTATFAIL = 1073741824 # (1 << 30) # /*!< lstat failed */
+RPMVERIFY_LGETFILECONFAIL = 2147483648 # (1 << 31) # /*!< lgetfilecon failed */
+
+RPMVERIFY_FAILURES = \
+ (RPMVERIFY_LSTATFAIL|RPMVERIFY_READFAIL|RPMVERIFY_READLINKFAIL| \
+ RPMVERIFY_LGETFILECONFAIL)
+
+# Bit(s) to control rpm_verify() operation.
+#
+VERIFY_DEFAULT = 0, # /*!< */
+VERIFY_MD5 = 1 << 0 # /*!< from --nomd5 */
+VERIFY_SIZE = 1 << 1 # /*!< from --nosize */
+VERIFY_LINKTO = 1 << 2 # /*!< from --nolinkto */
+VERIFY_USER = 1 << 3 # /*!< from --nouser */
+VERIFY_GROUP = 1 << 4 # /*!< from --nogroup */
+VERIFY_MTIME = 1 << 5 # /*!< from --nomtime */
+VERIFY_MODE = 1 << 6 # /*!< from --nomode */
+VERIFY_RDEV = 1 << 7 # /*!< from --nodev */
+# /* bits 8-14 unused, reserved for rpmVerifyAttrs */
+VERIFY_CONTEXTS = 1 << 15 # /*!< verify: from --nocontexts */
+VERIFY_FILES = 1 << 16 # /*!< verify: from --nofiles */
+VERIFY_DEPS = 1 << 17 # /*!< verify: from --nodeps */
+VERIFY_SCRIPT = 1 << 18 # /*!< verify: from --noscripts */
+VERIFY_DIGEST = 1 << 19 # /*!< verify: from --nodigest */
+VERIFY_SIGNATURE = 1 << 20 # /*!< verify: from --nosignature */
+VERIFY_PATCHES = 1 << 21 # /*!< verify: from --nopatches */
+VERIFY_HDRCHK = 1 << 22 # /*!< verify: from --nohdrchk */
+VERIFY_FOR_LIST = 1 << 23 # /*!< query: from --list */
+VERIFY_FOR_STATE = 1 << 24 # /*!< query: from --state */
+VERIFY_FOR_DOCS = 1 << 25 # /*!< query: from --docfiles */
+VERIFY_FOR_CONFIG = 1 << 26 # /*!< query: from --configfiles */
+VERIFY_FOR_DUMPFILES = 1 << 27 # /*!< query: from --dump */
+# /* bits 28-31 used in rpmVerifyAttrs */
+
+# Comes from C cource. lib/rpmcli.h
+VERIFY_ATTRS = \
+ ( VERIFY_MD5 | VERIFY_SIZE | VERIFY_LINKTO | VERIFY_USER | VERIFY_GROUP | \
+ VERIFY_MTIME | VERIFY_MODE | VERIFY_RDEV | VERIFY_CONTEXTS )
+
+VERIFY_ALL = \
+ ( VERIFY_ATTRS | VERIFY_FILES | VERIFY_DEPS | VERIFY_SCRIPT | VERIFY_DIGEST |\
+ VERIFY_SIGNATURE | VERIFY_HDRCHK )
+
+
+# Some masks for what checks to NOT do on these file types.
+# The C code actiually resets these up for every file.
+DIR_FLAGS = ~(RPMVERIFY_MD5 | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | \
+ RPMVERIFY_LINKTO)
+
+# These file types all have the same mask, but hopefully this will make the
+# code more readable.
+FIFO_FLAGS = CHR_FLAGS = BLK_FLAGS = GHOST_FLAGS = DIR_FLAGS
+
+LINK_FLAGS = ~(RPMVERIFY_MD5 | RPMVERIFY_FILESIZE | RPMVERIFY_MTIME | \
+ RPMVERIFY_MODE | RPMVERIFY_USER | RPMVERIFY_GROUP)
+
+REG_FLAGS = ~(RPMVERIFY_LINKTO)
+
+
+def s_isdev(mode):
+ """
+ Check to see if a file is a device.
+
+ """
+ return stat.S_ISBLK(mode) | stat.S_ISCHR(mode)
+
+def rpmpackagelist(rts):
+ """
+ Equivalent of rpm -qa. Intended for RefreshPackages() in the RPM Driver.
+ Requires rpmtransactionset() to be run first to get a ts.
+ Returns a list of pkgspec dicts.
+
+ e.g. [ {'name':'foo', 'epoch':'20', 'version':'1.2', 'release':'5', 'arch':'x86_64' },
+ {'name':'bar', 'epoch':'10', 'version':'5.2', 'release':'2', 'arch':'x86_64' } ]
+
+ """
+ return [ { 'name':header[rpm.RPMTAG_NAME],
+ 'epoch':header[rpm.RPMTAG_EPOCH],
+ 'version':header[rpm.RPMTAG_VERSION],
+ 'release':header[rpm.RPMTAG_RELEASE],
+ 'arch':header[rpm.RPMTAG_ARCH] }
+ for header in rts.dbMatch()]
+
+def getindexbykeyword(index_ts, **kwargs):
+ """
+ Return list of indexs from the rpmdb matching keywords
+ ex: getHeadersByKeyword(name='foo', version='1', release='1')
+
+ """
+ lst = []
+ name = kwargs.get('name')
+ if name:
+ index_mi = index_ts.dbMatch(rpm.RPMTAG_NAME, name)
+ else:
+ index_mi = index_ts.dbMatch()
+
+ # This is wrong! Yes its an issue, but you can't just ignore it.
+ # FIX THIS!!!! This is how it is in YUM.
+ if kwargs.has_key('epoch'):
+ del(kwargs['epoch']) # epochs don't work here for None/0/'0' reasons
+
+ keywords = len(kwargs.keys())
+ for hdr in index_mi:
+ match = 0
+ for keyword in kwargs.keys():
+ if hdr[keyword] == kwargs[keyword]:
+ match += 1
+ if match == keywords:
+ lst.append(index_mi.instance())
+ del index_mi
+ return lst
+
+def getheadersbykeyword(header_ts, **kwargs):
+ """
+ Borrowed parts of this from from Yum. Need to fix it though.
+ Epoch is not handled right.
+
+ Return list of headers from the rpmdb matching keywords
+ ex: getHeadersByKeyword(name='foo', version='1', release='1')
+
+ """
+ lst = []
+ name = kwargs.get('name')
+ if name:
+ header_mi = header_ts.dbMatch(rpm.RPMTAG_NAME, name)
+ else:
+ header_mi = header_ts.dbMatch()
+
+ # This is wrong! Yes its an issue, but you can't just ignore it.
+ # FIX THIS!!!! This is how it is in YUM.
+ # Fixed, I hope. MJTB 20070127.
+ if kwargs.has_key('epoch') and kwargs['epoch'] != None:
+ kwargs['epoch'] = int(kwargs['epoch'])
+ #del(kwargs['epoch']) # epochs don't work here for None/0/'0' reasons
+
+ keywords = len(kwargs.keys())
+ for hdr in header_mi:
+ match = 0
+ for keyword in kwargs.keys():
+ if hdr[keyword] == kwargs[keyword]:
+ match += 1
+ if match == keywords:
+ lst.append(hdr)
+ del header_mi
+ return lst
+
+def prelink_md5_check(filename):
+ """
+ Checks if a file is prelinked. If it is run it through prelink -y
+ to get the unprelinked md5 and file size.
+
+ Return 0 if the file was not prelinked, otherwise return the file size.
+ Always return the md5.
+
+ """
+ prelink = False
+ try:
+ plf = open(filename,"rb")
+ except IOError:
+ return False, 0
+
+ if prelink_exists:
+ if isprelink_imported:
+ plfd = plf.fileno()
+ if isprelink(plfd):
+ plf.close()
+ cmd = '/usr/sbin/prelink -y %s 2> /dev/null' \
+ % (re.escape(filename))
+ plf = os.popen(cmd, 'rb')
+ prelink = True
+ elif whitelist_re.search(filename) and not blacklist_re.search(filename):
+ plf.close()
+ cmd = '/usr/sbin/prelink -y %s 2> /dev/null' \
+ % (re.escape(filename))
+ plf = os.popen(cmd, 'rb')
+ prelink = True
+
+ fsize = 0
+ chksum = md5.new()
+ while 1:
+ data = plf.read()
+ if not data:
+ break
+ fsize += len(data)
+ chksum.update(data)
+ plf.close()
+ file_md5 = chksum.hexdigest()
+ if prelink:
+ return file_md5, fsize
+ else:
+ return file_md5, 0
+
+def prelink_size_check(filename):
+ """
+ This check is only done if the prelink_md5_check() is not done first.
+
+ Checks if a file is prelinked. If it is run it through prelink -y
+ to get the unprelinked file size.
+
+ Return 0 if the file was not prelinked, otherwise return the file size.
+
+ """
+ fsize = 0
+ try:
+ plf = open(filename,"rb")
+ except IOError:
+ return False
+
+ if prelink_exists:
+ if isprelink_imported:
+ plfd = plf.fileno()
+ if isprelink(plfd):
+ plf.close()
+ cmd = '/usr/sbin/prelink -y %s 2> /dev/null' \
+ % (re.escape(filename))
+ plf = os.popen(cmd, 'rb')
+
+ while 1:
+ data = plf.read()
+ if not data:
+ break
+ fsize += len(data)
+
+ elif whitelist_re.search(filename) and not blacklist_re.search(filename):
+ # print "***** Warning isprelink extension failed to import ******"
+ plf.close()
+ cmd = '/usr/sbin/prelink -y %s 2> /dev/null' \
+ % (re.escape(filename))
+ plf = os.popen(cmd, 'rb')
+
+ while 1:
+ data = plf.read()
+ if not data:
+ break
+ fsize += len(data)
+
+ plf.close()
+
+ return fsize
+
+def debug_verify_flags(vflags):
+ """
+ Decodes the verify flags bits.
+ """
+ if vflags & RPMVERIFY_MD5:
+ print 'RPMVERIFY_MD5'
+ if vflags & RPMVERIFY_FILESIZE:
+ print 'RPMVERIFY_FILESIZE'
+ if vflags & RPMVERIFY_LINKTO:
+ print 'RPMVERIFY_LINKTO'
+ if vflags & RPMVERIFY_USER:
+ print 'RPMVERIFY_USER'
+ if vflags & RPMVERIFY_GROUP:
+ print 'RPMVERIFY_GROUP'
+ if vflags & RPMVERIFY_MTIME:
+ print 'RPMVERIFY_MTIME'
+ if vflags & RPMVERIFY_MODE:
+ print 'RPMVERIFY_MODE'
+ if vflags & RPMVERIFY_RDEV:
+ print 'RPMVERIFY_RDEV'
+ if vflags & RPMVERIFY_CONTEXTS:
+ print 'RPMVERIFY_CONTEXTS'
+ if vflags & RPMVERIFY_READLINKFAIL:
+ print 'RPMVERIFY_READLINKFAIL'
+ if vflags & RPMVERIFY_READFAIL:
+ print 'RPMVERIFY_READFAIL'
+ if vflags & RPMVERIFY_LSTATFAIL:
+ print 'RPMVERIFY_LSTATFAIL'
+ if vflags & RPMVERIFY_LGETFILECONFAIL:
+ print 'RPMVERIFY_LGETFILECONFAIL'
+
+def debug_file_flags(fflags):
+ """
+ Decodes the file flags bits.
+ """
+ if fflags & rpm.RPMFILE_CONFIG:
+ print 'rpm.RPMFILE_CONFIG'
+
+ if fflags & rpm.RPMFILE_DOC:
+ print 'rpm.RPMFILE_DOC'
+
+ if fflags & rpm.RPMFILE_ICON:
+ print 'rpm.RPMFILE_ICON'
+
+ if fflags & rpm.RPMFILE_MISSINGOK:
+ print 'rpm.RPMFILE_MISSINGOK'
+
+ if fflags & rpm.RPMFILE_NOREPLACE:
+ print 'rpm.RPMFILE_NOREPLACE'
+
+ if fflags & rpm.RPMFILE_GHOST:
+ print 'rpm.RPMFILE_GHOST'
+
+ if fflags & rpm.RPMFILE_LICENSE:
+ print 'rpm.RPMFILE_LICENSE'
+
+ if fflags & rpm.RPMFILE_README:
+ print 'rpm.RPMFILE_README'
+
+ if fflags & rpm.RPMFILE_EXCLUDE:
+ print 'rpm.RPMFILE_EXLUDE'
+
+ if fflags & rpm.RPMFILE_UNPATCHED:
+ print 'rpm.RPMFILE_UNPATCHED'
+
+ if fflags & rpm.RPMFILE_PUBKEY:
+ print 'rpm.RPMFILE_PUBKEY'
+
+def rpm_verify_file(fileinfo, rpmlinktos, omitmask):
+ """
+ Verify all the files in a package.
+
+ Returns a list of error flags, the file type and file name. The list
+ entries are strings that are the same as the labels for the bitwise
+ flags used in the C code.
+
+ """
+ (fname, fsize, fmode, fmtime, fflags, frdev, finode, fnlink, fstate, \
+ vflags, fuser, fgroup, fmd5) = fileinfo
+
+ # 1. rpmtsRootDir stuff. What does it do and where to I get it from?
+
+ file_results = []
+ flags = vflags
+
+ # Check to see if the file was installed - if not pretend all is ok.
+ # This is what the rpm C code does!
+ if fstate != rpm.RPMFILE_STATE_NORMAL:
+ return file_results
+
+ # Get the installed files stats
+ try:
+ lstat = os.lstat(fname)
+ except OSError:
+ if not (fflags & (rpm.RPMFILE_MISSINGOK|rpm.RPMFILE_GHOST)):
+ file_results.append('RPMVERIFY_LSTATFAIL')
+ #file_results.append(fname)
+ return file_results
+
+ # 5. Contexts? SELinux stuff?
+
+ # Setup what checks to do. This is straight out of the C code.
+ if stat.S_ISDIR(lstat.st_mode):
+ flags &= DIR_FLAGS
+ elif stat.S_ISLNK(lstat.st_mode):
+ flags &= LINK_FLAGS
+ elif stat.S_ISFIFO(lstat.st_mode):
+ flags &= FIFO_FLAGS
+ elif stat.S_ISCHR(lstat.st_mode):
+ flags &= CHR_FLAGS
+ elif stat.S_ISBLK(lstat.st_mode):
+ flags &= BLK_FLAGS
+ else:
+ flags &= REG_FLAGS
+
+ if (fflags & rpm.RPMFILE_GHOST):
+ flags &= GHOST_FLAGS
+
+ flags &= ~(omitmask | RPMVERIFY_FAILURES)
+
+ # 8. SELinux stuff.
+
+ prelink_size = 0
+ if flags & RPMVERIFY_MD5:
+ prelink_md5, prelink_size = prelink_md5_check(fname)
+ if prelink_md5 == False:
+ file_results.append('RPMVERIFY_MD5')
+ file_results.append('RPMVERIFY_READFAIL')
+ elif prelink_md5 != fmd5:
+ file_results.append('RPMVERIFY_MD5')
+
+ if flags & RPMVERIFY_LINKTO:
+ linkto = os.readlink(fname)
+ if not linkto:
+ file_results.append('RPMVERIFY_READLINKFAIL')
+ file_results.append('RPMVERIFY_LINKTO')
+ else:
+ if len(rpmlinktos) == 0 or linkto != rpmlinktos:
+ file_results.append('RPMVERIFY_LINKTO')
+
+ if flags & RPMVERIFY_FILESIZE:
+ if not (flags & RPMVERIFY_MD5): # prelink check hasn't been done.
+ prelink_size = prelink_size_check(fname)
+ if (prelink_size != 0): # This is a prelinked file.
+ if (prelink_size != fsize):
+ file_results.append('RPMVERIFY_FILESIZE')
+ elif lstat.st_size != fsize: # It wasn't a prelinked file.
+ file_results.append('RPMVERIFY_FILESIZE')
+
+ if flags & RPMVERIFY_MODE:
+ metamode = fmode
+ filemode = lstat.st_mode
+
+ # Comparing the type of %ghost files is meaningless, but perms are ok.
+ if fflags & rpm.RPMFILE_GHOST:
+ metamode &= ~0xf000
+ filemode &= ~0xf000
+
+ if (stat.S_IFMT(metamode) != stat.S_IFMT(filemode)) or \
+ (stat.S_IMODE(metamode) != stat.S_IMODE(filemode)):
+ file_results.append('RPMVERIFY_MODE')
+
+ if flags & RPMVERIFY_RDEV:
+ if (stat.S_ISCHR(fmode) != stat.S_ISCHR(lstat.st_mode) or
+ stat.S_ISBLK(fmode) != stat.S_ISBLK(lstat.st_mode)):
+ file_results.append('RPMVERIFY_RDEV')
+ elif (s_isdev(fmode) & s_isdev(lstat.st_mode)):
+ st_rdev = lstat.st_rdev
+ if frdev != st_rdev:
+ file_results.append('RPMVERIFY_RDEV')
+
+ if flags & RPMVERIFY_MTIME:
+ if lstat.st_mtime != fmtime:
+ file_results.append('RPMVERIFY_MTIME')
+
+ if flags & RPMVERIFY_USER:
+ user = pwd.getpwuid(lstat.st_uid)[0]
+ if not user or not fuser or (user != fuser):
+ file_results.append('RPMVERIFY_USER')
+
+ if flags & RPMVERIFY_GROUP:
+ group = grp.getgrgid(lstat.st_gid)[0]
+ if not group or not fgroup or (group != fgroup):
+ file_results.append('RPMVERIFY_GROUP')
+
+ return file_results
+
+def rpm_verify_dependencies(header):
+ """
+ Check package dependencies. Header is an rpm.hdr.
+
+ Don't like opening another ts to do this, but
+ it was the only way I could find of clearing the ts
+ out.
+
+ Have asked on the rpm-maint list on how to do
+ this the right way (28 Feb 2007).
+
+ ts.check() returns:
+
+ ((name, version, release), (reqname, reqversion), \
+ flags, suggest, sense)
+
+ """
+ _ts1 = rpmtransactionset()
+ _ts1.addInstall(header, 'Dep Check', 'i')
+ dep_errors = _ts1.check()
+ _ts1.closeDB()
+ return dep_errors
+
+def rpm_verify_package(vp_ts, header, verify_options):
+ """
+ Verify a single package specified by header. Header is an rpm.hdr.
+
+ If errors are found it returns a dictionary of errors.
+
+ """
+ # Set some transaction level flags.
+ vsflags = 0
+ if 'nodigest' in verify_options:
+ vsflags |= rpm._RPMVSF_NODIGESTS
+ if 'nosifnature' in verify_options:
+ vsflags |= rpm._RPMVSF_NOSIGNATURES
+ ovsflags = vp_ts.setVSFlags(vsflags)
+
+ # Map from the Python options to the rpm bitwise flags.
+ omitmask = 0
+
+ if 'nolinkto' in verify_options:
+ omitmask |= VERIFY_LINKTO
+ if 'nomd5' in verify_options:
+ omitmask |= VERIFY_MD5
+ if 'nosize' in verify_options:
+ omitmask |= VERIFY_SIZE
+ if 'nouser' in verify_options:
+ omitmask |= VERIFY_USER
+ if 'nogroup' in verify_options:
+ omitmask |= VERIFY_GROUP
+ if 'nomtime' in verify_options:
+ omitmask |= VERIFY_MTIME
+ if 'nomode' in verify_options:
+ omitmask |= VERIFY_MODE
+ if 'nordev' in verify_options:
+ omitmask |= VERIFY_RDEV
+
+ omitmask = ((~omitmask & VERIFY_ATTRS) ^ VERIFY_ATTRS)
+ #print 'omitmask =', omitmask
+
+ package_results = {}
+
+ # Check Signatures and Digests.
+ # No idea what this might return. Need to break something to see.
+ # Setting the vsflags above determines what gets checked in the header.
+ hdr_stat = vp_ts.hdrCheck(header.unload())
+ if hdr_stat:
+ package_results['hdr'] = hdr_stat
+
+ # Check Package Depencies.
+ if 'nodeps' not in verify_options:
+ dep_stat = rpm_verify_dependencies(header)
+ if dep_stat:
+ package_results['deps'] = dep_stat
+
+ # Check all the package files.
+ if 'nofiles' not in verify_options:
+ vp_fi = header.fiFromHeader()
+ for fileinfo in vp_fi:
+ # Do not bother doing anything with ghost files.
+ # This is what RPM does.
+ if fileinfo[4] & rpm.RPMFILE_GHOST:
+ continue
+
+ # This is only needed because of an inconsistency in the
+ # rpm.fi interface.
+ linktos = vp_fi.FLink()
+
+ file_stat = rpm_verify_file(fileinfo, linktos, omitmask)
+
+ #if len(file_stat) > 0 or options.verbose:
+ if len(file_stat) > 0:
+ fflags = fileinfo[4]
+ if fflags & rpm.RPMFILE_CONFIG:
+ file_stat.append('c')
+ elif fflags & rpm.RPMFILE_DOC:
+ file_stat.append('d')
+ elif fflags & rpm.RPMFILE_GHOST:
+ file_stat.append('g')
+ elif fflags & rpm.RPMFILE_LICENSE:
+ file_stat.append('l')
+ elif fflags & rpm.RPMFILE_PUBKEY:
+ file_stat.append('P')
+ elif fflags & rpm.RPMFILE_README:
+ file_stat.append('r')
+ else:
+ file_stat.append(' ')
+
+ file_stat.append(fileinfo[0])
+ package_results.setdefault('files', []).append(file_stat)
+
+ # Run the verify script if there is one.
+ # Do we want this?
+ #if 'noscripts' not in verify_options:
+ # script_stat = rpmVerifyscript()
+ # if script_stat:
+ # package_results['script'] = script_stat
+
+ # If there have been any errors, add the package nevra to the result.
+ if len(package_results) > 0:
+ package_results.setdefault('nevra', ( header[rpm.RPMTAG_NAME], \
+ header[rpm.RPMTAG_EPOCH], \
+ header[rpm.RPMTAG_VERSION], \
+ header[rpm.RPMTAG_RELEASE], \
+ header[rpm.RPMTAG_ARCH] ) )
+ else:
+ package_results = None
+
+ # Put things back the way we found them.
+ vsflags = vp_ts.setVSFlags(ovsflags)
+
+ return package_results
+
+def rpm_verify(verify_ts, verify_pkgspec, verify_options=[]):
+ """
+ Requires rpmtransactionset() to be run first to get a ts.
+
+ pkgspec is a dict specifying the package
+ e.g.:
+ For a single package
+ { name='foo', epoch='20', version='1', release='1', arch='x86_64'}
+
+ For all packages
+ {}
+
+ Or any combination of keywords to select one or more packages to verify.
+
+ options is a list of 'rpm --verify' options. Default is to check everything.
+ e.g.:
+ [ 'nodeps', 'nodigest', 'nofiles', 'noscripts', 'nosignature',
+ 'nolinkto' 'nomd5', 'nosize', 'nouser', 'nogroup', 'nomtime',
+ 'nomode', 'nordev' ]
+
+ Returns a list. One list entry per package. Each list entry is a
+ dictionary. Dict keys are 'files', 'deps', 'nevra' and 'hdr'.
+ Entries only get added for the failures. If nothing failed, None is
+ returned.
+
+ Its all a bit messy and probably needs reviewing.
+
+ [ { 'hdr': [???],
+ 'deps: [((name, version, release), (reqname, reqversion),
+ flags, suggest, sense), .... ]
+ 'files': [ ['filename1', 'RPMVERIFY_GROUP', 'RPMVERIFY_USER' ],
+ ['filename2', 'RPMVERFIY_LSTATFAIL']]
+ 'nevra': ['name1', 'epoch1', 'version1', 'release1', 'arch1'] }
+ { 'hdr': [???],
+ 'deps: [((name, version, release), (reqname, reqversion),
+ flags, suggest, sense), .... ]
+ 'files': [ ['filename', 'RPMVERIFY_GROUP', 'RPMVERIFY_USER" ],
+ ['filename2', 'RPMVERFIY_LSTATFAIL']]
+ 'nevra': ['name2', 'epoch2', 'version2', 'release2', 'arch2'] } ]
+
+ """
+ verify_results = []
+ headers = getheadersbykeyword(verify_ts, **verify_pkgspec)
+ for header in headers:
+ result = rpm_verify_package(verify_ts, header, verify_options)
+ if result:
+ verify_results.append(result)
+
+ return verify_results
+
+def rpmtransactionset():
+ """
+ A simple wrapper for rpm.TransactionSet() to keep everthiing together.
+ Might use it to set some ts level flags later.
+
+ """
+ ts = rpm.TransactionSet()
+ return ts
+
+class Rpmtscallback(object):
+ """
+ Callback for ts.run(). Used for adding, upgrading and removing packages.
+ Starting with all possible reasons codes, but bcfg2 will probably only
+ make use of a few of them.
+
+ Mostly just printing stuff at the moment to understand how the callback
+ is used.
+
+ """
+ def __init__(self):
+ self.fdnos = {}
+
+ def callback(self, reason, amount, total, key, client_data):
+ """
+ Generic rpmts call back.
+ """
+ if reason == rpm.RPMCALLBACK_INST_OPEN_FILE:
+ print 'rpm.RPMCALLBACK_INST_OPEN_FILE'
+ elif reason == rpm.RPMCALLBACK_INST_CLOSE_FILE:
+ print 'rpm.RPMCALLBACK_INST_CLOSE_FILE'
+ elif reason == rpm.RPMCALLBACK_INST_START:
+ print 'rpm.RPMCALLBACK_INST_START'
+ elif reason == rpm.RPMCALLBACK_TRANS_PROGRESS or \
+ reason == rpm.RPMCALLBACK_INST_PROGRESS:
+ print 'rpm.RPMCALLBACK_TRANS_PROGRESS or \
+ rpm.RPMCALLBACK_INST_PROGRESS'
+ elif reason == rpm.RPMCALLBACK_TRANS_START:
+ print 'rpm.RPMCALLBACK_TRANS_START'
+ elif reason == rpm.RPMCALLBACK_TRANS_STOP:
+ print 'rpm.RPMCALLBACK_TRANS_STOP'
+ elif reason == rpm.RPMCALLBACK_REPACKAGE_START:
+ print 'rpm.RPMCALLBACK_REPACKAGE_START'
+ elif reason == rpm.RPMCALLBACK_REPACKAGE_PROGRESS:
+ print 'rpm.RPMCALLBACK_REPACKAGE_PROGRESS'
+ elif reason == rpm.RPMCALLBACK_REPACKAGE_STOP:
+ print 'rpm.RPMCALLBACK_REPACKAGE_STOP'
+ elif reason == rpm.RPMCALLBACK_UNINST_PROGRESS:
+ print 'rpm.RPMCALLBACK_UNINST_PROGRESS'
+ elif reason == rpm.RPMCALLBACK_UNINST_START:
+ print 'rpm.RPMCALLBACK_UNINST_START'
+ elif reason == rpm.RPMCALLBACK_UNINST_STOP:
+ print 'rpm.RPMCALLBACK_UNINST_STOP'
+ print '***Package ', key, ' deleted ***'
+ # How do we get at this?
+ # RPM.modified += key
+ elif reason == rpm.RPMCALLBACK_UNPACK_ERROR:
+ print 'rpm.RPMCALLBACK_UNPACK_ERROR'
+ elif reason == rpm.RPMCALLBACK_CPIO_ERROR:
+ print 'rpm.RPMCALLBACK_CPIO_ERROR'
+ elif reason == rpm.RPMCALLBACK_UNKNOWN:
+ print 'rpm.RPMCALLBACK_UNKNOWN'
+ else:
+ print 'ERROR - Fell through callBack'
+
+ print reason, amount, total, key, client_data
+
+def rpm_erase(erase_pkgspec, erase_flags):
+ """
+ pkgspecs is a list of pkgspec dicts specifying packages
+ e.g.:
+ For a single package
+ { name='foo', epoch='20', version='1', release='1', arch='x86_64'}
+
+ """
+ erase_ts_flags = 0
+ if 'noscripts' in erase_flags:
+ erase_ts_flags |= rpm.RPMTRANS_FLAG_NOSCRIPTS
+ if 'notriggers' in erase_flags:
+ erase_ts_flags |= rpm.RPMTRANS_FLAG_NOTRIGGERS
+ if 'repackage' in erase_flags:
+ erase_ts_flags |= rpm.RPMTRANS_FLAG_REPACKAGE
+
+ erase_ts = rpmtransactionset()
+ erase_ts.setFlags(erase_ts_flags)
+
+ idx_list = getindexbykeyword(erase_ts, **erase_pkgspec)
+ if len(idx_list) > 1 and not 'allmatches' in erase_flags:
+ print 'ERROR - Multiple package match for erase'
+ else:
+ for idx in idx_list:
+ erase_ts.addErase(idx)
+
+ if 'nodeps' not in erase_flags:
+ erase_problems = erase_ts.check()
+
+ if erase_problems:
+ print 'ERROR - Dependency failures on package erase'
+ print erase_problems
+ else:
+ erase_ts.order()
+ erase_callback = Rpmtscallback()
+ erase_ts.run(erase_callback.callback, 'Erase')
+
+ erase_ts.closeDB()
+ return erase_problems
+
+def display_verify_file(file_results):
+ '''
+ Display file results similar to rpm --verify.
+ '''
+ filename = file_results[-1]
+ filetype = file_results[-2]
+
+ result_string = ''
+
+ if 'RPMVERIFY_LSTATFAIL' in file_results:
+ result_string = 'missing '
+ else:
+ if 'RPMVERIFY_FILESIZE' in file_results:
+ result_string = result_string + 'S'
+ else:
+ result_string = result_string + '.'
+
+ if 'RPMVERIFY_MODE' in file_results:
+ result_string = result_string + 'M'
+ else:
+ result_string = result_string + '.'
+
+ if 'RPMVERIFY_MD5' in file_results:
+ if 'RPMVERIFY_READFAIL' in file_results:
+ result_string = result_string + '?'
+ else:
+ result_string = result_string + '5'
+ else:
+ result_string = result_string + '.'
+
+ if 'RPMVERIFY_RDEV' in file_results:
+ result_string = result_string + 'D'
+ else:
+ result_string = result_string + '.'
+
+ if 'RPMVERIFY_LINKTO' in file_results:
+ if 'RPMVERIFY_READLINKFAIL' in file_results:
+ result_string = result_string + '?'
+ else:
+ result_string = result_string + 'L'
+ else:
+ result_string = result_string + '.'
+
+ if 'RPMVERIFY_USER' in file_results:
+ result_string = result_string + 'U'
+ else:
+ result_string = result_string + '.'
+
+ if 'RPMVERIFY_GROUP' in file_results:
+ result_string = result_string + 'G'
+ else:
+ result_string = result_string + '.'
+
+ if 'RPMVERIFY_MTIME' in file_results:
+ result_string = result_string + 'T'
+ else:
+ result_string = result_string + '.'
+
+ print result_string + ' ' + filetype + ' ' + filename
+ sys.stdout.flush()
+
+#===============================================================================
+# Some options and output to assit with developenment and testing.
+# These are not intended for normal use.
+if __name__ == "__main__":
+
+ p = optparse.OptionParser()
+
+ p.add_option('--name', action='store', \
+ default=None, \
+ help='''Package name to verify.
+
+ ******************************************
+ NOT SPECIFYING A NAME MEANS 'ALL' PACKAGES.
+ ******************************************
+
+ The specified operation will be carried out on all
+ instances of packages that match the package specification
+ (name, epoch, version, release, arch).''')
+
+ p.add_option('--epoch', action='store', \
+ default=None, \
+ help='''Package epoch.''')
+
+ p.add_option('--version', action='store', \
+ default=None, \
+ help='''Package version.''')
+
+ p.add_option('--release', action='store', \
+ default=None, \
+ help='''Package release.''')
+
+ p.add_option('--arch', action='store', \
+ default=None, \
+ help='''Package arch.''')
+
+ p.add_option('--erase', '-e', action='store_true', \
+ default=None, \
+ help='''****************************************************
+ REMOVE PACKAGES. THERE ARE NO WARNINGS. MULTIPLE
+ PACKAGES WILL BE REMOVED IF A FULL PACKAGE SPEC IS NOT
+ GIVEN. E.G. IF JUST A NAME IS GIVEN ALL INSTALLED
+ INSTANCES OF THAT PACKAGE WILL BE REMOVED PROVIDED
+ DEPENDENCY CHECKS PASS. IF JUST AN EPOCH IS GIVEN
+ ALL PACKAGE INSTANCES WITH THAT EPOCH WILL BE REMOVED.
+ ****************************************************''')
+
+ p.add_option('--list', '-l', action='store_true', \
+ help='''List package identity info. rpm -qa ish equivalent
+ intended for use in RefreshPackages().''')
+
+ p.add_option('--verify', action='store_true', \
+ help='''Verify Package(s). Output is only produced after all
+ packages has been verified. Be patient.''')
+
+ p.add_option('--verbose', '-v', action='store_true', \
+ help='''Verbose output for --verify option. Output is the
+ same as rpm -v --verify.''')
+
+ p.add_option('--nodeps', action='store_true', \
+ default=False, \
+ help='Do not do dependency testing.')
+
+ p.add_option('--nodigest', action='store_true', \
+ help='Do not check package digests.')
+
+ p.add_option('--nofiles', action='store_true', \
+ help='Do not do file checks.')
+
+ p.add_option('--noscripts', action='store_true', \
+ help='Do not run verification scripts.')
+
+ p.add_option('--nosignature', action='store_true', \
+ help='Do not do package signature verification.')
+
+ p.add_option('--nolinkto', action='store_true', \
+ help='Do not do symlink tests.')
+
+ p.add_option('--nomd5', action='store_true', \
+ help='''Do not do MD5 checksums on files. Note that this does
+ not work for prelink files yet.''')
+
+ p.add_option('--nosize', action='store_true', \
+ help='''Do not do file size tests. Note that this does not work
+ for prelink files yet.''')
+
+ p.add_option('--nouser', action='store_true', \
+ help='Do not check file user ownership.')
+
+ p.add_option('--nogroup', action='store_true', \
+ help='Do not check file group ownership.')
+
+ p.add_option('--nomtime', action='store_true', \
+ help='Do not check file modification times.')
+
+ p.add_option('--nomode', action='store_true', \
+ help='Do not check file modes (permissions).')
+
+ p.add_option('--nordev', action='store_true', \
+ help='Do not check device node.')
+
+ p.add_option('--notriggers', action='store_true', \
+ help='Do not do not generate triggers on erase.')
+
+ p.add_option('--repackage', action='store_true', \
+ help='''Do repackage on erase.i Packages are put
+ in /var/spool/repackage.''')
+
+ p.add_option('--allmatches', action='store_true', \
+ help='''Remove all package instances that match the
+ pkgspec.
+
+ ***************************************************
+ NO WARNINGS ARE GIVEN. IF THERE IS NO PACKAGE SPEC
+ THAT MEANS ALL PACKAGES!!!!
+ ***************************************************''')
+
+ options, arguments = p.parse_args()
+
+ pkgspec = {}
+ rpm_options = []
+
+ if options.nodeps:
+ rpm_options.append('nodeps')
+
+ if options.nodigest:
+ rpm_options.append('nodigest')
+
+ if options.nofiles:
+ rpm_options.append('nofiles')
+
+ if options.noscripts:
+ rpm_options.append('noscripts')
+
+ if options.nosignature:
+ rpm_options.append('nosignature')
+
+ if options.nolinkto:
+ rpm_options.append('nolinkto')
+
+ if options.nomd5:
+ rpm_options.append('nomd5')
+
+ if options.nosize:
+ rpm_options.append('nosize')
+
+ if options.nouser:
+ rpm_options.append('nouser')
+
+ if options.nogroup:
+ rpm_options.append('nogroup')
+
+ if options.nomtime:
+ rpm_options.append('nomtime')
+
+ if options.nomode:
+ rpm_options.append('nomode')
+
+ if options.nordev:
+ rpm_options.append('nordev')
+
+ if options.repackage:
+ rpm_options.append('repackage')
+
+ if options.allmatches:
+ rpm_options.append('allmatches')
+
+ main_ts = rpmtransactionset()
+
+ cmdline_pkgspec = {}
+ if options.name != 'all':
+ if options.name:
+ cmdline_pkgspec['name'] = str(options.name)
+ if options.epoch:
+ cmdline_pkgspec['epoch'] = str(options.epoch)
+ if options.version:
+ cmdline_pkgspec['version'] = str(options.version)
+ if options.release:
+ cmdline_pkgspec['release'] = str(options.release)
+ if options.arch:
+ cmdline_pkgspec['arch'] = str(options.arch)
+
+ if options.verify:
+ results = rpm_verify(main_ts, cmdline_pkgspec, rpm_options)
+ for r in results:
+ files = r.get('files', '')
+ for f in files:
+ display_verify_file(f)
+
+ elif options.list:
+ for p in rpmpackagelist(main_ts):
+ print p
+
+ elif options.erase:
+ if options.name:
+ rpm_erase(cmdline_pkgspec, rpm_options)
+ else:
+ print 'You must specify the "--name" option'