include("site.inc"); $template = new Page; $template->initCommon(); $template->displayHeader(); ?>
With the RPM system, you have a lot of choices. You can install or upgrade packages with the rpm command. You can install or upgrade packages with special programs you write using the C API. And you can install or upgrade packages using the Python API. If you are writing a special program to install or upgrade packages, the Python API makes this task much easier. As with the C API, most of your work needs to be part of a transaction set.
To install or upgrade a package, you need to create a transaction set, build up the transaction with packages, which are stored as transaction elements within the transaction set, check for unresolved dependencies, reorder the transaction set based on the dependencies, and then run the transaction set. Running the transaction set installs or upgrades the packages. The following sections cover these steps.
Package installs and upgrades need to be performed within the context of a transaction set. To install or upgrade a set of packages, you need to call addInstall with the package headers to install or upgrade. The basic syntax follows:
ts.addInstall(header, key_data, mode)
When you call addInstall, you pass the header object along with arbitrary callback key data and a mode flag. The mode flag should be 'i' to install a package, 'u' to upgrade a package, or 'a' as a special code to make a package available for transaction checks but not install or upgrade the package. The 'a' flag is rarely used. In most cases, you should use 'u', just as in most cases, you should install packages with rpm –U instead of rpm –i.
The key_data parameter will get passed to the transaction set run callback, covered in the “Running the Transaction” section later in this chapter.
Note
To remove packages instead of install or upgrade, call addErase instead of addInstall:
ts.addErase(package_name)
To set up a package to be upgraded or installed, you can use code like the following:
h = readRpmHeader( ts, sys.argv[1] )
ts.addInstall(h, sys.argv[1], 'u')
This example expects a package file name on the command line (accessed with sys.argv[1]), and reads in the package header using the readRpmHeader function introduced previously.
The call to addInstall adds the header object (and the associated RPM package file) for an upgrade with the 'u' mode flag. The name of the package file, from sys.argv[1], is passed as the arbitrary data for the transaction set run callback function.
Transaction sets are made up of transaction elements. A transaction element makes up one part of a transaction and holds one package per operation (install or remove) in each transaction set. That is, there is one transaction element per package per operation in the transaction set. You can iterate over a transaction set to get each transaction element. Once you have a transaction element, you can call methods on each element to check entries in the header as well as get dependency sets for the package.
Table 17-4 lists the informational methods you can call on a transaction element. Most of the methods listed in Table 17-4 return a single value.
Table 17-4 Informational methods on transaction sets
Method | Returns |
A | Returns package architecture |
E | Returns package epoch |
O | Returns package operating system |
R | Returns package release number |
V | Returns package version |
N | Returns package name |
NEVR | Returns package name-epoch-version-release |
DS | Returns the package dependency set for a given tag |
FI | Returns the file info set for the package |
For more complex checking, the DS method returns the package dependency set for a given tag:
ds = te.DS(tag_name)
Pass one of 'Providename', 'Requirename', 'Obsoletename', or 'Conflictname' for the tag name. For example:
ds = te.DS('Requirename')
The FI method returns the file info set for the package:
fi = te.FI(tag_name)
For the FI method, you must pass a tag name of 'Basenames'.
As an example, Listing 17-6 shows how to iterate through a transaction set to get transaction elements.
Listing 17-6: te.py
#!/usr/bin/python
# Adds all package files on command line to a transaction
# and prints out the transaction elements.
# Usage:
# python te.py rpm_file1.rpm rpm_file2.rpm ...
#
import rpm, os, sys
def readRpmHeader(ts, filename):
""" Read an rpm header. """
fd = os.open(filename, os.O_RDONLY)
h = ts.hdrFromFdno(fd)
os.close(fd)
return h
ts = rpm.TransactionSet()
# Set to not verify DSA signatures.
ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
for filename in sys.argv[1:]:
h = readRpmHeader(ts, filename)
print "Installing %s-%s-%s" % (h['name'], h['version'], h['release'])
ts.addInstall(h, filename, 'i')
print "This will install:"
for te in ts:
print "%s-%s-%s" % (te.N(), te.V(), te.R() )
ts.check()
ts.order()
print "This will install:"
for te in ts:
print "%s-%s-%s" % (te.N(), te.V(), te.R() )
The te.py script sets up a transaction and then prints out the elements, never completing the transaction. The purpose here is just to show what is in the transaction. The second set of printed output shows the results of the check and order methods, covered in the following section.
After you have called addInstall or addErase for each of the packages you want to install, upgrade, or remove, you need to call two methods to verify the transaction set and order all the elements properly. These two methods are check and order.
The check method checks the dependencies in a transaction set.
unresolved_dependencies = ts.check()
It returns None if all dependencies are resolved, or a complex tuple for each unresolved dependency. In general, if the check method returns anything but None, you cannot perform the transaction.
On a dependency failure, check returns a complex tuple of the dependency information in the following format:
((N,V,R), (reqN, reqV), needsFlags, suggestedPackage, sense)
The first element is a tuple of the name, version, and release of the package you are trying to install. The next tuple holds the required name and required version or conflicting name and version. The version will be None if the dependency is a shared library or other file.
The needs flags tell you about the requirement or conflict. The value is a bitmask that can contain the following bit settings: rpm.RPMSENSE_EQUAL, rpm.RPMSENSE_GREATER, and rpm.RPMSENSE_LESS. This tells you if the dependency is for a version of a package greater than 4.1, for example.
The suggested package names a package that solves the dependency. The packages considered are those for which you call addInstall with a flag of 'a'. This value will be None if there is no known package to solve this dependency.
You can tell whether the dependency is a conflict or a requirement based on the sense value, one of rpm.RPMSENSE_CONFLICTS or rpm.RPMSENSE_REQUIRES.
For example, the following tuple shows a required package:
(('eruby-devel', '0.9.8', '2'), ('eruby-libs', '0.9.8'), 8, None, 0)
The following tuple shows a required shared library:
(('jpilot', '0.97', '1'), ('libpisock.so.3', None), 0, None, 0)
Note
This tuple format will likely change in future versions of RPM. This example shows the format in RPM 4.1. With each RPM release, check the online documentation on the Python API to look for changes.
You can pass an optional callback function to the call to check. This callback gets called for each unresolved dependency in the transaction set. You can use this callback to try to automatically bring in required packages, for example.
The basic syntax for the transaction check callback is:
def checkCallback(ts, TagN, N, EVR, Flags):
# Do something…
You can use a check callback to automatically bring in packages that are required into a transaction set. You can bring in packages from the Red Hat RPM database package, which contains a database of all Red Hat packages, the rpmdb-redhat package. You can open the database from this package by using the trick described previously for opening transactions to more than one RPM database at a time. Simply set the _dbpath macro to "/usr/lib/rpmdb/i386-redhat-linux/redhat", or the location of your rpmdb-redhat database, and create a transaction set. Your check callback can then search this extra database and add packages from that database into the current, real RPM database.
Your check callback can also attempt to find package files to resolve dependencies, from a disk directory or network archive for example. The following code shows a stub check callback that you can fill in to try to resolve dependencies. This callback sets up a format for finding unresolved packages in another RPM database, or elsewhere. You need to fill in the skeleton with the algorithm you want to actually resolve the dependencies.
def checkCallback(ts, TagN, N, EVR, Flags):
if TagN == rpm.RPMTAG_REQUIRENAME:
prev = ""
Nh = None
if N[0] == '/':
dbitag = 'basenames'
else:
dbitag = 'providename'
# What do you need to do.
if EVR:
print "Must find package [", N, "-", EVR, "]"
else:
print "Must find file [", N, "]"
if resolved:
# ts.addIntall(h, h, 'i')
return -1
return 1
Depending on the values passed to the callback, your code must either find a package itself or a package that provides a given file or capability to resolve the dependency. If you have another RPM database to look at, such as the rpmdb-redhat database, you can use dbMatch to find the necessary packages in that database. If, however, you are working with a directory of RPM files, you need to build up file names from the package name, version, and release.
You can add packages to a transaction set in any order. The order method reorders the transaction set to ensure that packages get installed or removed in the right order. The order method orders by a topological sort using the dependencies relations between objects with dependency comparisons.
Note
You must call check prior to order.
After setting up the transaction set, perform the transaction by calling run. You need to provide two parameters:
ts.run(callback, client_data)
The callback parameter must be a Python function. The client_data is any data you want to pass to the callback. There may be more than one package in the transaction set, so this data should not be specific to a particular package.
Warning
You must not pass None as the client_data or you will get a Python error.
The callback you pass to the run method on a transaction set is essential. Your callback must work properly, or the transaction will fail. You must provide a callback.
Your callback will get called a number of times, mostly as a means to report progress. If you are writing a graphical user interface, for example, you can use the progress callbacks to update a visual progress meter.
The basic syntax for the transaction set run callback is:
def runCallback(reason, amount, total, key, client_data):
# Do your stuff...
The key is the data you provided in the call to the addInstall method. The client_data is the data you passed to the run method.
Each time your callback is called, the transaction set will provide a reason flag. Table 17-5 lists the values for the reason parameter.
Table 17-5 Transaction set run callback reason values
Value | Reason |
rpm.RPMCALLBACK_UNKNOWN | Unknown problem |
rpm.RPMCALLBACK_INST_PROGRESS | Progress for installation |
rpm.RPMCALLBACK_INST_START | Start of installation |
rpm.RPMCALLBACK_INST_OPEN_FILE | Callback should open package file |
rpm.RPMCALLBACK_INST_CLOSE_FILE | Callback should close package file |
rpm.RPMCALLBACK_TRANS_PROGRESS | Transaction progress |
rpm.RPMCALLBACK_TRANS_START | Transaction start |
rpm.RPMCALLBACK_TRANS_STOP | Transaction stop |
rpm.RPMCALLBACK_UNINST_PROGRESS | Uninstallation progress |
rpm.RPMCALLBACK_UNINST_START | Uninstallation start |
rpm.RPMCALLBACK_UNINST_STOP | Uninstallation stop |
rpm.RPMCALLBACK_REPACKAGE_PROGRESS | Repackaging progress |
rpm.RPMCALLBACK_REPACKAGE_START | Repackaging start |
rpm.RPMCALLBACK_REPACKAGE_STOP | Repackaging stop |
rpm.RPMCALLBACK_UNPACK_ERROR | Error unpacking package file |
rpm.RPMCALLBACK_CPIO_ERROR | cpio error getting package payload |
Your callback must handle at least two cases: a reason value of rpm.RPMCALLBACK_INST_OPEN_FILE and rpm.RPMCALLBACK_INST_CLOSE_FILE.
With the reason of rpm.RPMCALLBACK_INST_OPEN_FILE, you must open the RPM package file and return a file descriptor for the file. You need to keep this file descriptor in a global-scope or otherwise-accessible variable, because with the reason of rpm.RPMCALLBACK_INST_CLOSE_FILE, you must close this file.
The following code shows a valid sample callback for upgrading and installing packages.
# Global file descriptor for the callback.
rpmtsCallback_fd = None
def runCallback(reason, amount, total, key, client_data):
global rpmtsCallback_fd
if reason == rpm.RPMCALLBACK_INST_OPEN_FILE:
print "Opening file. ", reason, amount, total, key, client_data
rpmtsCallback_fd = os.open(client_data, os.O_RDONLY)
return rpmtsCallback_fd
elif reason == rpm.RPMCALLBACK_INST_START:
print "Closing file. ", reason, amount, total, key, client_data
os.close(rpmtsCallback_fd)
This callback assumes that the call to addInstall passed client data of the package file name. This callback ignores the client_data passed to the run method, but this is a perfect slot for passing an object. You can use this, for example, to avoid having a global variable for the file descriptor.
Listing 17-7 shows a simple Python script to upgrade or install a package.
Listing 17-7: rpmupgrade.py
#!/usr/bin/python
# Upgrades packages passed on the command line.
# Usage:
# python rpmupgrade.py rpm_file1.rpm rpm_file2.rpm ...
#
import rpm, os, sys
# Global file descriptor for the callback.
rpmtsCallback_fd = None
def runCallback(reason, amount, total, key, client_data):
global rpmtsCallback_fd
if reason == rpm.RPMCALLBACK_INST_OPEN_FILE:
print "Opening file. ", reason, amount, total, key, client_data
rpmtsCallback_fd = os.open(key, os.O_RDONLY)
return rpmtsCallback_fd
elif reason == rpm.RPMCALLBACK_INST_START:
print "Closing file. ", reason, amount, total, key, client_data
os.close(rpmtsCallback_fd)
def checkCallback(ts, TagN, N, EVR, Flags):
if TagN == rpm.RPMTAG_REQUIRENAME:
prev = ""
Nh = None
if N[0] == '/':
dbitag = 'basenames'
else:
dbitag = 'providename'
# What do you need to do.
if EVR:
print "Must find package [", N, "-", EVR, "]"
else:
print "Must find file [", N, "]"
if resolved:
# ts.addIntall(h, h, 'i')
return -1
return 1
def readRpmHeader(ts, filename):
""" Read an rpm header. """
fd = os.open(filename, os.O_RDONLY)
h = ts.hdrFromFdno(fd)
os.close(fd)
return h
ts = rpm.TransactionSet()
# Set to not verify DSA signatures.
ts.setVSFlags(-1)
for filename in sys.argv[1:]:
h = readRpmHeader(ts, filename)
print "Upgrading %s-%s-%s" % (h['name'], h['version'], h['release'])
ts.addInstall(h, filename, 'u')
unresolved_dependencies = ts.check(checkCallback)
if not unresolved_dependencies:
ts.order()
print "This upgrade will install:"
for te in ts:
print "%s-%s-%s" % (te.N(), te.V(), te.R())
print "Running transaction (final step)..."
ts.run(runCallback, 1)
else:
print "Error: Unresolved dependencies, transaction failed."
print unresolved_dependencies
This script expects the name of an RPM package file on the command line, and attempts to upgrade the package. (This will also install new packages.)
When you run the rpmupgrade.py script, you should see output like the following:
# rpm -q jikes
jikes-1.17-1
# python rpmupgrade.py jikes-1.18-1.i386.rpm
Upgrading jikes-1.18-1
This upgrade will install:
jikes-1.18-1
jikes-1.17-1
Running transaction (final step)...
Opening file. 4 0 0 jikes-1.18-1.i386.rpm 1
Closing file. 2 0 2854204 jikes-1.18-1.i386.rpm 1
# rpm -q jikes
jikes-1.18-1
This example shows that the package was upgraded after running the rpmupgrade.py script. Note that with an upgrade, the original package, jikes-1.17-1 in this case, is also added to the transaction set. With an install, this is not the case. That’s because the original package is removed as part of the transaction.
If you run this script as a non-root user, you will likely see an error like the following:
$ python rpmupgrade.py jikes-1.18-1.i386.rpm
Upgrading jikes-1.18-1
This upgrade will install:
jikes-1.18-1
jikes-1.17-1
Running transaction (final step)...
error: cannot get exclusive lock on /var/lib/rpm/Packages
error: cannot open Packages index using db3 - Operation not permitted (1)
error: cannot open Packages database in /var/lib/rpm
If a package has a dependency on a file such as a shared library, you will see output like the following:
# python rpmupgrade.py jikes-1.17-glibc2.2-1.i386.rpm jpilot-0_97-1_i386.rpm
Upgrading jikes-1.17-1
Upgrading jpilot-0.97-1
Must find file [ libpisock.so.3 ]
Error: Unresolved dependencies, transaction failed.
(('jpilot', '0.97', '1'), ('libpisock.so.3', None), 0, None, 0)
If a package has a dependency on another package, you will see output like the following:
# python rpmupgrade.py eruby-devel-0.9.8-2.i386.rpm
Upgrading eruby-devel-0.9.8-2
Must find package [ eruby-libs - 0.9.8 ]
Error: Unresolved dependencies, transaction failed.
(('eruby-devel', '0.9.8', '2'), ('eruby-libs', '0.9.8'), 8, None, 0)