include("site.inc"); $template = new Page; $template->initCommon(); $template->displayHeader(); ?>
In addition to providing query routines for RPM files, you can also access the RPM database with the RPM2 package.
To access the RPM database, your Perl script must first open the database.
Open the RPM database with a call to open_rpm_db on the RPM2 object. For example:
my $rpm_db = RPM2->open_rpm_db();
You can also specify the directory where the RPM database resides. This is most useful for accessing a database in a non-standard location. For example:
my $rpm_db = RPM2->open_rpm_db( "-path" => "/var/lib/rpm" );
Note
The -path is normally used as a Perl bareword but is shown here as a string.
Once you have an RPM database object, you can call one of the find subroutines to find packages in most of the same ways as supported by the rpm –q command.
The find_by_name subroutine finds a package or packages by name. It returns a Perl list of the entries found. For example, if you installed more than one version of a package, find_by_name would return a list of all the packages at the different versions.
Similar to find_by_name, find_by_name_iter returns an iterator to iterate over the packages that match the query. The iterator approach is usually more efficient.
Iterators are important in the RPM2 package because they provide a more efficient interface to potentially large sets of packages, and because iterators more closely match the underlying C API. Furthermore, iterators are very easy to use. Simply call the next subroutine to move ahead to the next element, that is, the next package.
For example:
my $pkg_iter = $rpm_db->find_by_name_iter( "kernel" );
while (my $pkg = $pkg_iter->next() ) {
# Do something ...
}
Listing 18-3 shows a script that acts much like the rpm –q command, without any other command-line options.
Listing 18-3: rpmname.pl
#!/usr/bin/perl
#
# Queries RPM database for given package.
# Usage:
# rpmname.pl package_name
#
use strict;
use RPM2;
my $rpm_db = RPM2->open_rpm_db( "−path" => "/var/lib/rpm" );
my $pkg_iter = $rpm_db->find_by_name_iter( $ARGV[0] );
while (my $pkg = $pkg_iter->next() ) {
print $pkg->tag("NAME"), "-", $pkg->tag("VERSION"), "\n";
}
$rpm_db->close_rpm_db();
When you run this script, you need to pass the name of a package to query. For example:
$ ./rpmname.pl kernel
kernel-2.4.18
The find_by_name_iter subroutine finds a package by its name. The RPM2 module also supports a number of other query routines, listed in Table 18-1.
Table 18-1 RPM2 module query routines
Routine | Usage |
find_all() | Returns a list with all the packages in the database |
find_all_iter() | Returns an iterator over all the packages in the database |
find_by_file($filename) | Finds all packages that own the given file, returning a list |
find_by_file_iter($filename) | Finds all packages that own the given file, returning an iterator |
find_by_name($package_name) | Finds all packages with the given name, returning a list |
find_by_name_iter($package_name) | Finds all packages with the given name, returning an iterator |
find_by_provides($capability) | Finds all packages that provide the given capability, returning a list |
find_by_provides_iter($capability) | Finds all packages that provide the given capability, returning an iterator |
find_by_requires($capability) | Finds all packages that require the given capability, returning a list |
find_by_requires_iter($capability) | Finds all packages that require the given capability, returning an iterator |
To verify the find routines, you can try the following script and compare the results with the rpm command. Listing 18-4 shows the script that finds what package provides a capability and also which packages require the capability.
Listing 18-4: rpmprovides.pl
#!/usr/bin/perl
#
# Queries RPM database for given package,
# listing what it provides and what other
# packages require the capability.
#
# Usage:
# rpmprovides.pl package_name
#
use strict;
use RPM2;
my $rpm_db = RPM2->open_rpm_db();
my $pkg_iter = $rpm_db->find_by_provides_iter( $ARGV[0] );
print "Provides: ", $ARGV[0], "\n";
while (my $pkg = $pkg_iter->next() ) {
print "\t", $pkg->as_nvre(), "\n";
}
# Now, what packages require this capability.
my $pkg_iter2 = $rpm_db->find_by_requires_iter( $ARGV[0] );
print "Requires: ", $ARGV[0], "\n";
while (my $pkg2 = $pkg_iter2->next() ) {
print "\t", $pkg2->as_nvre(), "\n";
}
$rpm_db->close_rpm_db();
When you run this script with the name of a capability, you'll see output like the following:
$ ./rpmprovides.pl httpd
Provides: httpd
httpd-2.0.40-8
Requires: httpd
mod_perl-1.99_05-3
5:redhat-config-httpd-1.0.1-13
mod_python-3.0.0-10
1:mod_ssl-2.0.40-8
Note
The 5: in 5:redhat-config-httpd-1.0.1-13 and 1: in 1:mod_ssl-2.0.40-8 represent the EPOCH tag value.
To verify this script, run the rpm -q command to see if you get the same packages listed. For example:
$ rpm -q --whatprovides httpd
httpd-2.0.40-8
$ rpm -q --whatrequires httpd
mod_perl-1.99_05-3
redhat-config-httpd-1.0.1-13
mod_python-3.0.0-10
mod_ssl-2.0.40-8
In both cases, you see the same packages listed. You can use this technique to verify your scripts.
Note
The find_by_provides_iter subroutine requires the name of a package, such as bash. You cannot pass a file name, such as /bin/bash, to get the name of the package that provides this capability (a file, really).
The tag, as_nvre, and is_source_package subroutines that worked on header objects read from RPM files, shown previously, also work with package entries returned from the RPM database.
For example, Listing 18-5 shows a script, rpminfo.pl, that prints out descriptive information about a given package.
Listing 18-5: rpminfo.pl
#!/usr/bin/perl
#
# Queries RPM database for given package and prints info.
# Usage:
# rpminfo.pl package_name
#
use strict;
use RPM2;
my $rpm_db = RPM2->open_rpm_db( "-path" => "/var/lib/rpm" );
my $pkg_iter = $rpm_db->find_by_name_iter( $ARGV[0] );
while (my $pkg = $pkg_iter->next() ) {
printInfo( $pkg );
}
$rpm_db->close_rpm_db();
# Prints info on one package.
sub printInfo {
my($pkg) = shift;
print $pkg->as_nvre(), ", ", $pkg->tag("ARCH"), ", ",
$pkg->tag("OS"), ", ", $pkg->tag("PLATFORM"), "\n";
print $pkg->tag("SUMMARY"), "\n";
print "Group: ", $pkg->tag("GROUP"), "\n";
print $pkg->tag("DESCRIPTION"), "\n";
print "Vendor: ", $pkg->tag("VENDOR"), ", ", $pkg->tag("URL"), "\n";
print "Size: ", $pkg->tag("SIZE"), "\n";
}
When you run this script, you’ll see output like the following:
$ ./rpminfo.pl XFree86
XFree86-4.2.0-72, i386, linux, i386-redhat-linux-gnu
The basic fonts, programs and docs for an X workstation.
Group: User Interface/X
XFree86 is an open source implementation of the X Window System. It
provides the basic low level functionality which full fledged
graphical user interfaces (GUIs) such as GNOME and KDE are designed
upon.
Vendor: Red Hat, Inc., http://www.xfree86.org
Size: 30552239
The installed date is a number value representing the number of seconds since the start of the UNIX epoch, January 1, 1970, which predates the start of the Linux epoch by about 20 years. So, when you get the value of the INSTALLTIME tag, you’ll see a meaningless number.
To make sense of this number, pass the value to the Perl localtime function. Listing 18-6 shows an example of this.
Listing 18-6: rpmdate.pl
#!/usr/bin/perl
#
# Queries RPM database for given package,
# prints out name, vendor, and date installed.
# Usage:
# rpmdate.pl package_name
#
use strict;
use RPM2;
my $rpm_db = RPM2->open_rpm_db();
my $pkg_iter = $rpm_db->find_by_name_iter( $ARGV[0] );
while (my $pkg = $pkg_iter->next() ) {
printDate( $pkg );
}
$rpm_db->close_rpm_db();
# Prints installation data for one package.
sub printDate {
my($pkg) = shift;
my $date = localtime( $pkg->tag("INSTALLTIME") );
printf("%-20s %-17s %s\n", $pkg->as_nvre(), $pkg->tag("VENDOR"), $date);
}
Note
The printf function in this script can do something the rpm command cannot do. Even with the --queryformat option, you cannot group multiple items and then set the size; with Perl, you can. Simply assign the multiple values to a string, or use the handy as_nvre subroutine, which gathers up to four tags together into one string.
When you pass the name of a package to this script, you’ll see the date the package was installed. For example:
$ ./rpmdate.pl kernel
kernel-2.4.18-14 Red Hat, Inc. Sat Oct 5 12:29:58 2002
Not only is the date stored in a format that adds complication to your script. A number of tags are string arrays, not scalar strings. This means you may see output that is all mashed together.
To help deal with this, the following subroutine takes in an array of strings and returns a string that is built using a passed-in delimiter:
sub arrayToString {
my($sep) = shift;
my(@array) = @_;
my($str);
$str = $array[0];
for ( $i = 1; $i < $#array; $i++ )
{
$str = $str . $sep . $array[$i];
}
return $str;
}
Note
Show your Perl expertise and earn extra points by implementing the arrayToString subroutine as a single Perl statement that uses the join function.
The following list shows the tags that are an array of strings:
*BASENAMES
*CHANGELOGNAME
*CHANGELOGTEXT
*DIRNAMES
*FILEGROUPNAME
*FILELANGS
*FILELINKTOS
*FILEMD5S
*FILEUSERNAME
*OLDFILENAMES
*PROVIDENAME
*PROVIDEVERSION
*REQUIRENAME
*REQUIREVERSION
Cross Reference
Chapter 5 covers more on these tags.
The files subroutine provides a list of all the files in a package. Listing 18-7 shows how to access this list.
Listing 18-7: rpmfiles.pl
#!/usr/bin/perl
#
# Queries RPM database for given package,
# prints out the files in the package.
# Usage:
# rpmfiles.pl package_name
#
use strict;
use RPM2;
my $rpm_db = RPM2->open_rpm_db();
my $pkg_iter = $rpm_db->find_by_name_iter( $ARGV[0] );
while (my $pkg = $pkg_iter->next() ) {
printFiles( $pkg );
}
$rpm_db->close_rpm_db();
# Prints installation data for one package.
sub printFiles {
my($pkg) = shift;
my $files = arrayToString("\n", $pkg->files() );
print "Files:\n", $files, "\n";
}
sub arrayToString {
my($sep) = shift;
my(@array) = @_;
my($str);
$str = $array[0];
for ( my $i = 1; $i < $#array; $i++ )
{
$str = $str . $sep . $array[$i];
}
return $str;
}
When you run this script, you’ll see output like the following:
$ ./rpmfiles.pl jikes
Files:
/usr/bin/jikes
/usr/doc/jikes-1.17/license.htm
The RPM2 module overrides the spaceship operator, <=>, to perform version comparisons between packages. The script in Listing 18-8 shows how to compare all local RPM files against the newest installed version of the same package, if the package is installed.
Listing 18-8: rpmver.pl
#!/usr/bin/perl -w
#
# Compare versions of all *.rpm files against the
# latest packages installed (if installed)
#
# Usage:
# rpmver.pl
# This script looks for all *.rpm files.
#
use strict;
use RPM2;
my $rpm_db = RPM2->open_rpm_db();
for my $filename (<*.rpm>) {
my $h = RPM2->open_package( $filename );
# Ensure we compare against the newest
# package of the given name.
my ($installed) =
sort { $b <=> $a } $rpm_db->find_by_name($h->name);
if (not $installed) {
printf "Package %s not installed.\n", $h->as_nvre;
} else {
my ($result) = ($h <=> $installed);
if ($result < 0) {
printf "Installed package %s newer than file %s\n",
$installed->as_nvre,
$h->as_nvre;
} else {
printf "File %s newer than installed package %s\n",
$h->as_nvre,
$installed->as_nvre;
}
}
}
The sort { $a <=> $b } in front of the find_by_name call sorts all the packages of that name by the version number, so that the comparison is performed against the newest installed version of the package. The ($h <=> $installed) compares the header from the RPM file on disk against the newest installed version of the package.
When you run this script, you’ll see output like the following, depending on which RPM files you have in the local directory:
$ perl rpmver.pl
Package acroread-4.0-0 not installed.
Package canvas-7.0b2.0-1 not installed.
Installed package jikes-1.18-1 newer than file jikes-1.14-1
Installed package SDL-1.2.4-5 newer than file SDL-0.9.9-4
Package ted-2.8-1 not installed.