updating RPM directory

Klaus Steden klaus.steden at thomson.net
Wed Aug 3 17:43:42 UTC 2005


> I think I've asked before, but can't seem to find that email.
> 
> Does anyone have a script to update an existing directory with newer 
> rpms from a given folder?
> 
> For example, I have an RPM folder with customizations, and I want to 
> update the RPMS from a directory that has update 1 plus newer erratas?
> Does anyone have such a script, before I try to write one?
> 
Try this. I can't claim ownership or authorship of it - someone on this list
wrote it, but I've been using it for the past two months. It's a life-saver.

Klaus
-------------- next part --------------
#!/usr/bin/python
#
#    update_release.py 2.0 - merge rpm updates and third party rpms
#                            into a RedHat distro and check for
#                            dependencies/conflicts problems
#    Copyright (C) 2003 - Zouhir Hafidi (Zouhir.Hafidi at agat.univ-lille1.fr)
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#    Usage (some examples):
#    - get help:
#    update_release.py -h
#    - run in test mode and use current dir as a base dir
#    update_release.py -n
#    update_release.py -n -v
#    - run in test mode and use /export/install/redhat/8.0 as a base dir
#    update_release.py -n -d /export/install/redhat/8.0
#    update_release.py -n -v -d /export/install/redhat/8.0
#    - use current dir as a base dir. The installation dir and updates dir
#    are respectively by default updated_release/RedHat/RPMS/ and updates/
#    update_release.py
#    update_release.py -v
#    - use /export/install/redhat/8.0 as a base dir. The installation dir and 
#    updates dir are respectively by default updated_release/RedHat/RPMS/ and updates/
#    update_release.py -d /export/install/redhat/8.0
#    update_release.py -v -d /export/install/redhat/8.0
#    - use /export/install/redhat/8.0 as a base dir and only check for 
#    dependencies/conflicts under updated_release/RedHat/RPMS
#    update_release.py -c -d /export/install/redhat/8.0
#    update_release.py -c -v -d /export/install/redhat/8.0
#    - use /export/install/redhat/8.0 as a base dir and only check for 
#    dependencies/conflicts under some/relative/dir
#    update_release.py -c -d /export/install/redhat/8.0 -i some/relative/dir
#    update_release.py -c -v -d /export/install/redhat/8.0 -i some/relative/dir


import os
import sys
import getopt
import commands
import rpm
import string
import shutil


def usage():
	print "Usage: %s [-h] | [[-n] [-v] [-d dir] [-i dir] [-u dir] [-b branch] [-c] [-H]]" % sys.argv[0]
	print "  -h|--help                print this message"
	print "  -n|--dryrun              dry run mode (show a trace of what would be done)"
	print "  -v|--verbose             print more information when running"
	print "  -d dir|--basedir=dir     base dir (current dir by default)"
	print "  -i dir|--installdir=dir  installation dir (updated_release/RedHat/RPMS/ by default)"
	print "  -u dir|--updatesdir=dir  updates dir (updates/ by default)"
	print "  -b branch|--branch=vers  RedHat version (i.e. Fedora, RedHat)"
	print "  -c|--checkonly           check for dependencies/conflicts in the installation dir"
	print "  -H|--nohdlist            don't regenerate hdlists"


# Description: 
#	construct a dependency string (this function comes from anaconda)
# Argument(s):
#	- rpm name
#	- rpm version
#	- dependency flags
# Return value(s):
#	- dependency string
def formatRequire (name, version, flags):
	string = name

	if flags:
		if flags & (rpm.RPMSENSE_LESS | rpm.RPMSENSE_GREATER | rpm.RPMSENSE_EQUAL):
			string = string + " "
		if flags & rpm.RPMSENSE_LESS:
			string = string + "<"
		if flags & rpm.RPMSENSE_GREATER:
			string = string + ">"
		if flags & rpm.RPMSENSE_EQUAL:
			string = string + "="
			string = string + " %s" % version
	return string


# Description: 
#	construct a dictionary containing the headers of a set of rpms.
#	If a same rpm exists with different versions, then take into
#	account only the recent one. Source rpms are not processed.
# Argument(s):
#	- a list of rpms filenames
# Return value(s):
#	- a dictionary of the form:
#	{(rpm_name, rpm_arch):(rpm_header, rpm_filename), ...}
#	- a list containing source rpms and old rpms
def getRpmsHeaders(rpms):
	headers = {}
	bad_rpms = []
	ts = rpm.TransactionSet("/", ~(rpm._RPMVSF_NOSIGNATURES))
	ts.closeDB()
	for rpm_file in rpms:
		fdno = os.open(rpm_file, os.O_RDONLY)
		header = ts.hdrFromFdno(fdno)
		os.close(fdno)

 		if string.find(rpm_file, "debuginfo") >= 0:
			bad_rpms.append(rpm_file)
			if verbose_flag:
				print " skip %s which is a debug package" % os.path.basename(rpm_file)

		elif header[rpm.RPMTAG_SOURCEPACKAGE]:
			bad_rpms.append(rpm_file)
			if verbose_flag:
				print " skip %s which is a source package" % os.path.basename(rpm_file)
		else:
			key = (header[rpm.RPMTAG_NAME], header[rpm.RPMTAG_ARCH])
			if headers.has_key(key):
				cmp = rpm.versionCompare(headers[key][0], header)
				if not cmp in [-1, 1]:
					print " rpm.versionCompare() returned error code %d while comparing %s and %s" % (cmp, os.path.basename(headers[key][1]), os.path.basename(rpm_file))
					sys.exit(cmp) 
				if cmp == -1:
					bad_rpms.append(headers[key][1])
					if verbose_flag:
						print " use %s instead of %s" % (os.path.basename(rpm_file), os.path.basename(headers[key][1]))
					headers[key] = (header, rpm_file)
				else:
					bad_rpms.append(rpm_file)
					if verbose_flag:
						print " skip %s which is older than %s" % (os.path.basename(rpm_file), os.path.basename(headers[key][1]))
			else:
				headers[key] = (header, rpm_file) 

	return (headers, bad_rpms)


# Description: 
#	delete unwanted entries from a list of rpms filenames
# Argument(s):
#	- list of rpms filenames
#	- list of entries to delete
# Return value(s):
#	None
def skipRpms(rpms_list, skip_list):
	for item in skip_list:
		del rpms_list[rpms_list.index(item)]


# Description: 
#	check if a given set of rpms put together doesn't have any
#	dependencies nor conflicts problems
# Argument(s):
#	- a dictionary of the form:
#	{(rpm_name, rpm_arch):(rpm_header, rpm_filename), ...}
# Return value(s):
#	- a list of errors if any
def checkDepsAndConflicts(headers):
	ts = rpm.TransactionSet("/", ~(rpm._RPMVSF_NOSIGNATURES))
	ts.closeDB()
	for key in headers.keys():
		ts.addInstall(headers[key][0], key, 'i')
	return ts.check()


def main():
	# Processing command line arguments
	try:
		opts, args = getopt.getopt(sys.argv[1:], 'hnvd:i:u:b:cHG', \
		["help", "dryrun", "verbose", "basedir=", "installdir=", \
		"updatesdir=", "branch=", "checkonly", "nohdlist", "genhdlist"])
	except getopt.GetoptError:
		usage()
		sys.exit(1)

	if args != []:
		usage()
		sys.exit(1)

	global verbose_flag
	verbose_flag = 0
	dryrun_flag = 0
	base_dir = os.getcwd()
#	updated_release_dir = "updated_release/RedHat/RPMS"
	updated_release_dir = "Fedora/RPMS"
	updates_dir = "updates"
	branch = "Fedora"
	checkonly_flag = 0
	nohdlist_flag = 0
	genhdlist_flag = 0
	
	for o, a in opts:
		if o in ("-h", "--help"):
			usage()
			sys.exit()
		if o in ("-n", "--dryrun"):
			dryrun_flag = 1
		if o in ("-v", "--verbose"):
			verbose_flag = 1
		if o in ("-d", "--basedir"):
			base_dir = a
		if o in ("-i", "--installdir"):
			updated_release_dir = a
		if o in ("-u", "--updatesdir"):
			updates_dir = a
		if o in ("-b", "--branch"):
			branch = a
		if o in ("-c", "--checkonly"):
			checkonly_flag = 1
		if o in ("-H", "--nohdlist"):
			nohdlist_flag = 1
		if o in ("-G", "--genhdlist"):
			genhdlist_flag = 1

	# Some initializations
	updated_release_dir = os.path.join(base_dir, updated_release_dir)
	updates_dir = os.path.join(base_dir, updates_dir)
	old_rpms_dir = os.path.join(base_dir, "old_rpms")
	regenerate_hdlist = 0
	if dryrun_flag:
		verbose_flag = 1
	
	# The updated release dir must exist
	if not os.path.isdir(updated_release_dir):
		print "can't access %s, nothing to do" % updated_release_dir
		sys.exit(2)

	if verbose_flag:
		print "processing rpms under %s ..." % updated_release_dir

	# Get all rpm names under updated_release_dir
	exit_status, output = commands.getstatusoutput("find %s -name '*\.rpm' | grep -v '.src.rpm'" % updated_release_dir)
	if exit_status != 0:
		sys.exit(3)
	updated_release_rpms = output.split()

	# Get headers of all rpms under updated_release_dir
	updated_release_headers, bad_rpms = getRpmsHeaders(updated_release_rpms)
	if bad_rpms != []:
		print " ERROR: the following rpms are either source rpms or older rpms. Please remove them:"
		for rpm_file in bad_rpms:
			print " %s" % rpm_file
		sys.exit(4) 

	# Before merging, check if the current updated release dir contains
	# any dependencies or conflicts problems and exit if so
	errors = checkDepsAndConflicts(updated_release_headers)
	if errors:
		print "ERROR: the installation tree already contains conflicts and/or dependencies problems"
		for ((name, version, release), (reqname, reqversion), \
			flags, suggest, sense) in errors:
			if sense==rpm.RPMDEP_SENSE_REQUIRES:
				print " depcheck: package %s needs %s" % ( name, formatRequire(reqname, reqversion, flags))
			elif sense==rpm.RPMDEP_SENSE_CONFLICTS:
				print " depcheck: package %s conflicts with %s" % (name, reqname)
		sys.exit(4)

	if verbose_flag:
		print "done"

	if checkonly_flag:
		if errors:
			print "dependencies/conflicts problems exist in %s" % updated_release_dir
		else:
			print "no dependencies/conflicts problems in %s" % updated_release_dir
		sys.exit()


	# The updates dir must exist
	if not os.path.isdir(updates_dir):
		print "can't access %s, nothing to do" % updates_dir
		sys.exit(2)

	# If necessary, create the dir to put old rpms into
	if not os.path.isdir(old_rpms_dir):
		os.mkdir(old_rpms_dir)

	if verbose_flag:
		print "processing rpms under %s ..." % updates_dir

	# Get all rpm names under updates_dir
	exit_status, output = commands.getstatusoutput("find %s -follow -name '*\.rpm' | grep -v '.src.rpm'" % updates_dir)
	if exit_status != 0:
		sys.exit(3)
	updates_rpms = output.split()

	# Get headers of all rpms under updates_dir
	updates_headers, bad_rpms = getRpmsHeaders(updates_rpms)

	# Ignore unwanted rpms
	skipRpms(updates_rpms, bad_rpms)
	if bad_rpms != []:
		print " WARNING: the following rpms are either source rpms or older rpms. I will ignore them:"
		for rpm_file in bad_rpms:
			print " %s" % rpm_file

	if verbose_flag:
		print "done"

	# Take the entries in the updates_headers dictionary and merge them
	# with the entries in the updated_release_headers dictionary:
	#	- for every entry in the updates_headers dictionary
	#	- if a corresponding entry with an old version exists
	#	  in the updated_release_headers dictionary then drop it
	#	  in the old_rpms_headers dictionary and replace it with
	#	  the new one
	#	- if there is no corresponding entry with an old version
	#	  then simply add the new one in the updated_release_headers 
	#	  dictionary
	old_rpms_headers = {}
	for key in updates_headers.keys():
		if updated_release_headers.has_key(key):
			cmp = rpm.versionCompare(updated_release_headers[key][0], updates_headers[key][0])
			if cmp == 1: 
				if verbose_flag:
					print "skip %s which is older than %s" % (os.path.basename(updates_headers[key][1]), os.path.basename(updated_release_headers[key][1]))
			elif cmp == 0:
				if verbose_flag:
					print "skip %s which is the same as %s" % (os.path.basename(updates_headers[key][1]), os.path.basename(updated_release_headers[key][1]))
			elif cmp == -1:
				if verbose_flag:
					print "exchange %s with %s" % (os.path.basename(updated_release_headers[key][1]), os.path.basename(updates_headers[key][1]))
				old_rpms_headers[key] = updated_release_headers[key]
				updated_release_headers[key] = updates_headers[key]
			else:
				print " rpm.versionCompare() returned error code %d while comparing %s and %s" % (cmp, os.path.basename(updated_release_headers[key][1]), os.path.basename(updates_headers[key][1]))
				sys.exit(cmp)
		else:
			updated_release_headers[key] = updates_headers[key]
			if verbose_flag:
				print "add %s" % os.path.basename(updates_headers[key][1])

	# At this point, the merging process is done and the updated_release_headers
        # dictionary should be up to date. Before applying the changes to the updated
	# release dir, we must make sure that there's no dependencies nor conflicts
	# problems. If so, we do a "backtracking" on the headers which make problems
	# until a state without any dependencies/conflicts is reached.
	while 1:
		deps_and_conflicts = 0

		errors = checkDepsAndConflicts(updated_release_headers)
		if errors:
			# Construct a list containing the names of the rpms that
			# make problems. If a same rpm exists for different 
			# architectures, then all of them will not be used.
			# NOTE: we don't know how to discard only problematic rpms
			# since the architecture is not reported by the check()
			# method (is there a fix ?)
			if verbose_flag:
				print "merging will result in the following conflicts and/or dependencies problems:"
			name_list = []
			for ((name, version, release), (reqname, reqversion), \
				flags, suggest, sense) in errors:
				if sense==rpm.RPMDEP_SENSE_REQUIRES:
					if verbose_flag:
						print " depcheck: package %s needs %s" % ( name, formatRequire(reqname, reqversion, flags))
					deps_and_conflicts = 1
					if not name in name_list:
						name_list.append(name)
				elif sense==rpm.RPMDEP_SENSE_CONFLICTS:
					if verbose_flag:
						print " depcheck: package %s conflicts with %s" % (name, reqname)
					deps_and_conflicts = 1
					if not name in name_list:
						name_list.append(name)

			# Discard entries with dependencies/conflicts problems in
			# the updated_release_headers dictionary
			if verbose_flag:
				print "backtracking:"
			for (name, arch) in updated_release_headers.keys():
				if name in name_list :
					if os.path.dirname(updated_release_headers[(name, arch)][1]) == updated_release_dir:
						print "CRITICAL ERROR: attempt to discard %s" % updated_release_headers[(name, arch)][1]
						sys.exit(4)
					if verbose_flag:
						print " discard %s" % updated_release_headers[(name, arch)][1]
					del updated_release_headers[(name, arch)]

			# Put back those entries which have been replaced by recent
			# entries which resulted in dependencies/conflicts problems
			for (name, arch) in old_rpms_headers.keys():
				if name in name_list :
					if verbose_flag:
						print " reuse %s" % old_rpms_headers[(name, arch)][1]
					updated_release_headers[(name, arch)] = old_rpms_headers[(name, arch)]
					del old_rpms_headers[(name, arch)]

		# The infinite loop *should* be broken after some iterations
		if deps_and_conflicts == 0:
			break

	# At this point, the updated_release_headers dictionary contains
	# entries with the rpms filenames that should be in the updated
	# release dir so that we have no dependencies/conflicts problems.
	# The old_rpms_headers dictionary contains entries with the rpms 
	# filenames that should be moved from the updated release dir to
	# the old rpms dir. Just reflect these changes on the directories
	# themselves
	for key in updated_release_headers.keys():
		if os.path.dirname(updated_release_headers[key][1]) == updated_release_dir:
			continue
		regenerate_hdlist = 1
		if not dryrun_flag:
#			os.link(updated_release_headers[key][1], \
			shutil.copy2(updated_release_headers[key][1], \
			os.path.join(updated_release_dir, os.path.basename(updated_release_headers[key][1])))
		else:
			print "should",
		if key in old_rpms_headers.keys():
			print "update %s with %s" % (os.path.basename(old_rpms_headers[key][1]), \
			os.path.basename(updated_release_headers[key][1]))
			if not dryrun_flag:
				os.rename(old_rpms_headers[key][1], \
				os.path.join(old_rpms_dir, os.path.basename(old_rpms_headers[key][1])))
		else:
			print "add %s" % os.path.basename(updated_release_headers[key][1])

	# Regenerate hdlists if necessary
	if genhdlist_flag or ((not nohdlist_flag) and regenerate_hdlist):
		if not dryrun_flag:
			os.system("/usr/lib/anaconda-runtime/genhdlist --withnumbers --productpath %s %s/../.." % (branch, updated_release_dir) )
			os.system("PYTHONPATH=/usr/lib/anaconda /usr/lib/anaconda-runtime/pkgorder %s/../.. i386 %s > pkgorder" % (updated_release_dir, branch) )
			os.system("/usr/lib/anaconda-runtime/genhdlist --withnumbers --productpath %s --fileorder pkgorder %s/../.." % (branch, updated_release_dir) )
		else:
			if verbose_flag:
				print "should",
		if verbose_flag:
			print "regenerate hdlist"


if __name__ == "__main__":
	main()
	


More information about the Kickstart-list mailing list