Product SiteDocumentation Site

15.5. Comparing an RPM File to an Installed Package

You can pull together the RPM file and database discussions, shown previously, to create a number of RPM programs. A useful utility that shows the RPM C library compares a package file against installed packages, reporting whether the package in the RPM file represents a newer or older package than what was already installed.
Listing 16-4 shows such a program.
Listing 16-4: vercompare.c
/* Compares a package file with an installed package,
telling which one is newer.
Usage:
vercompare pkg_files+
Compile as
cc -I/usr/include/rpm -o vercompare vercompare.c -lrpm -lrpmdb -lrpmio -lpopt
*/
#include <stdlib.h>
#include <rpmcli.h>
#include <rpmdb.h>
#include <rpmds.h>
#include <rpmts.h>
/* Set up a table of options using standard RPM options. */
static struct poptOption optionsTable[] = {
{ NULL, '\0', POPT_ARG_INCLUDE_TABLE, rpmcliAllPoptTable, 0,
"Common options for all rpm modes and executables:",
NULL },
POPT_AUTOALIAS
POPT_AUTOHELP
POPT_TABLEEND
};
int main(int argc, char * argv[])
{
poptContext context;
const char ** fnp;
rpmdbMatchIterator iter;
Header file_header, installed_header;
rpmts ts;
rpmds dependency_set;
FD_t fd;
rpmRC rpmrc;
int rc;
context = rpmcliInit(argc, argv, optionsTable);
if (context == NULL) {
exit(EXIT_FAILURE);
}
ts = rpmtsCreate();
for (fnp = poptGetArgs(context); fnp && *fnp; fnp++) {
/* Read package header, continuing to next arg on failure. */
fd = Fopen(*fnp, "r.ufdio");
if (fd == NULL || Ferror(fd)) {
rpmError(RPMERR_OPEN, "open of %s failed: %s\n", *fnp,
Fstrerror(fd));
if (fd) {
Fclose(fd);
}
continue;
}
rpmrc = rpmReadPackageFile(ts, fd, *fnp, &file_header);
Fclose(fd);
if (rpmrc != RPMRC_OK) {
rpmError(RPMERR_OPEN, "%s cannot be read\n", *fnp);
continue;
}
/* Generate "name <= epoch:version-release" depset for package */
dependency_set = rpmdsThis(file_header, RPMTAG_REQUIRENAME,
(RPMSENSE_EQUAL|RPMSENSE_LESS));
rc = -1; /* assume no package is installed. */
/* Search all installed packages with same name. */
iter = rpmtsInitIterator(ts, RPMTAG_NAME, rpmdsN(dependency_set), 0);
while ((installed_header = rpmdbNextIterator(iter)) != NULL) {
/* Is the installed package newer than the file? */
rc = rpmdsNVRMatchesDep(installed_header, dependency_set, 1);
switch (rc) {
case 1:
if ( rpmIsVerbose() )
fprintf(stderr, "installed package is older (or same) as %s\n",
*fnp);
break;
case 0:
if ( rpmIsVerbose() )
fprintf(stderr, "installed package is newer than %s\n",
*fnp);
break;
}
}
/* Clean up. */
iter = rpmdbFreeIterator(iter);
dependency_set = rpmdsFree(dependency_set);
if (rc < 0 && rpmIsVerbose() )
fprintf(stderr, "no package is installed %s\n", *fnp);
}
ts = rpmtsFree(ts);
context = rpmcliFini(context);
return rc;
}
The vercompare.c program shows reading in RPM package files as well as querying the RPM database. It introduces transaction sets, used extensively in the RPM API, and also dependency sets. You can use this program as a guide for making your own RPM programs.
When you run the vercompare.c program, pass the names of one or more RPM files. The vercompare.c program will extract the package name from the files, and then query the RPM database for matching packages. For each matching package, vercompare.c checks whether the installed package is newer than the RPM file, or at the same version or older. For example, if you have installed version 1.17-1 of the jikes package (a Java compiler), you can compare the installed version against RPM files. If you have a package that has a newer version, you should see output like the following:
$ ./vercompare -v jikes-1.18-1.i386.rpm
installed package is older (or same) as jikes-1.18-1.i386.rpm
Note that the output is relative to the installed package.
If you compare against a file that has an older version of the package, you will see results like the following:
$ ./vercompare -v jikes-1.14-1-glibc-2.2.i386.rpm
installed package is newer than jikes-1.14-1-glibc-2.2.i386.rpm
And, if you compare to an RPM file that holds the same package, you will see output as follows:
$ ./vercompare -v jikes-1.17-glibc2.2-1.i386.rpm
installed package is older (or same) as jikes-1.17-glibc2.2-1.i386.rpm
You can change this aspect of the test by changing the flags passed to rpmdsThis.
Note
The vercompare.c program prints out nothing unless there is an error. Instead, it sets the program exit status based on the package version comparison. You can use this with automated tools, such as make, that check the exit status.
If you want output from the program, pass the –v, verbose, option to the command, as shown in the previous examples.
The RPM cli or command-line interface functions, such as rpmcliInit, are based on the command-line options expected by the rpm and rpmbuild commands. You can use these functions to provide a high level of abstraction onto the RPM system. For example, to run the query options just like the rpm command, call rpmcliQuery.
int rpmcliQuery(rpmts transaction_set,
QVA_t qva,
const char **argv);
Set the QVA_t variable to point at the global variable rpmQVKArgs, which is set up from the global option table for the query mode, rpmQueryPoptTable. Pass rpmcliQuery a set of file names or package names. You can get these names in the given format by calling poptGetArgs, introduced previously.
To support the query options, you need the rpm query entries in your poptOption table. To get these options, add the following entry:
{ NULL, '\0', POPT_ARG_INCLUDE_TABLE, rpmQueryPoptTable, 0,
"Query options (with -q or --query):",
NULL },
With the rpmQueryPoptTable options, you can make a program that works like the rpm --query command using just the following code:
poptContext context;

QVA_t qva = &rpmQVKArgs;

rpmts ts;

int ec;

context = rpmcliInit(argc, argv, optionsTable);

if (context == NULL) {

/* Display error and exit... */

}

ts = rpmtsCreate();

if (qva->qva_mode == 'q') {

/* Make sure there's something to do. */

if (qva->qva_source != RPMQV_ALL && !poptPeekArg(context)) {

fprintf(stderr, "no arguments given for --query");

exit(EXIT_FAILURE);

}

ec = rpmcliQuery(ts, qva, (const char **) poptGetArgs(context));

}

ts = rpmtsFree(ts);

context = rpmcliFini(context);


This code supports all the query options just like the rpm command. That's both good and bad. If you wanted everything exactly like the rpm command, chances are you could use the rpm command as is. But if you need to add RPM query support into your programs, this is probably the easiest way to do it.
With a small additional set of code, you can add support for all the --verify options to your program. You need to include the --verify command-line option definitions, which come from the global rpmVerifyPoptTable table:
/* Add in --verify options. */
{ NULL, '\0', POPT_ARG_INCLUDE_TABLE, rpmVerifyPoptTable, 0,
"Verify options (with -V or --verify):",
NULL },
You can then check for the verify mode, and support the options, with code like the following:
if (qva->qva_mode == 'V') {
rpmVerifyFlags verifyFlags = VERIFY_ALL;
/* Verify flags are negated from query flags. */
verifyFlags &= ~qva->qva_flags;
qva->qva_flags = (rpmQueryFlags) verifyFlags;
/* Make sure there's something to do. */
if (qva->qva_source != RPMQV_ALL && !poptPeekArg(context)) {
fprintf(stderr, "no arguments given for --verify");
exit(EXIT_FAILURE);
}
ec = rpmcliVerify(ts, qva, (const char **)
poptGetArgs(context));
}
The workhorse function in this code is rpmcliVerify, a high-level function that performs all the --verify work done by the rpm command.
int rpmcliVerify(rpmts transaction_set,
QVA_t qva,
const char **argv);
Again, set the QVA_t variable to point at the global variable rpmQVKArgs, which is set up from the global option table for the query mode, rpmQueryPoptTable.
Putting this all together, Listing 16-5 shows a program that performs the same as the rpm command for the --query and --verify options.
Listing 16-5: rpmq.c
 /*

rpm --query and --verify modes in standalone program.

Compile as

cc -I/usr/include/rpm -o rpmq rpmq.c -lrpm -lrpmdb -lrpmio -lpopt

See option usage by invoking

./rpmq --help

*/ 

#include <stdlib.h>
#include <rpmcli.h>
#include <rpmdb.h>
#include <rpmds.h>
#include <rpmts.h>

/* Set up a table of options. */
static struct poptOption optionsTable[] = {
	{ NULL, '\0', POPT_ARG_INCLUDE_TABLE, rpmcliAllPoptTable, 0,

	"Common options for all rpm modes and executables:",

	NULL },

	{ NULL, '\0', POPT_ARG_INCLUDE_TABLE, rpmQueryPoptTable, 0,

	"Query options (with -q or --query):",

	NULL },

/* Add in --verify options. */

	{ NULL, '\0', POPT_ARG_INCLUDE_TABLE, rpmVerifyPoptTable, 0,

	"Verify options (with -V or --verify):",

	NULL },

	POPT_AUTOALIAS
	POPT_AUTOHELP
	POPT_TABLEEND
};

int main(int argc, char * argv[]) {
	poptContext context;
	QVA_t qva = &rpmQVKArgs;
	rpmts ts;
	int ec;

	context = rpmcliInit(argc, argv, optionsTable);

	if (context == NULL) {
		poptPrintUsage(context, stderr, 0);
		exit(EXIT_FAILURE);
	}

	ts = rpmtsCreate();

	/* Check for query mode. */
	if (qva->qva_mode == 'q') {
		/* Make sure there's something to do. */
		if (qva->qva_source != RPMQV_ALL && !poptPeekArg(context)) {
			fprintf(stderr, "no arguments given for --query");
			exit(EXIT_FAILURE);
		}

		ec = rpmcliQuery(ts, qva, (const char **) poptGetArgs(context));
	}
	/* Check for verify mode. */
	else if (qva->qva_mode == 'V') {
		rpmVerifyFlags verifyFlags = VERIFY_ALL;

		/* Verify flags are negated from query flags. */
		verifyFlags &= ~qva->qva_flags;
		qva->qva_flags = (rpmQueryFlags) verifyFlags;

		/* Make sure there's something to do. */
		if (qva->qva_source != RPMQV_ALL && !poptPeekArg(context)) {
			fprintf(stderr, "no arguments given for --verify");
			exit(EXIT_FAILURE);
		}

		ec = rpmcliVerify(ts, qva, (const char **) poptGetArgs(context));

	} else {
		poptPrintUsage(context, stderr, 0);
		exit(EXIT_FAILURE);
	}

	ts = rpmtsFree(ts);
	context = rpmcliFini(context);
	return ec;
} 

There is not a lot of code in rpmq.c, as this program is mostly calling the high-level functions for the rpm command-line interface.
When you run the rpmq program, it performs the same tasks as the rpm command with the --query (or -q) and --verify (or -V) command-line options.
For example, rpmq supports query formats:
$ ./rpmq -q --qf "%{NAME} %{INSTALLTID:date}\n" jikes
jikes Fri 25 Oct 2002 06:49:38 PM CDT