Chapter 16

Programming RPM with C

Copyright (c) 2005 by Eric Foster-Johnson. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/).

In This Chapter

*	Using the RPM C library

*	Setting up a development environment

*	Programming with the RPM C library

*	The power of popt for command-line argument processing

*	Comparing package files to installed packages

The RPM C library allows you to perform all the operations of the rpm command from within your own C or C++ programs.

The reason is simple: The rpm command was created using the RPM libraries. These same libraries are available for you to use in your own programs.

The rpm command itself is quick and, for the most part, simple. So, why would you want to write RPM programs?

There are many reasons, some of which are listed here:

*	Speed: If you need to perform a task on many RPM files such as verifying a large set of files, then performing the task from one program will be a lot faster than launching the rpm command for each file.

*	Custom options: If you need to do something the rpm command doesn't offer, or doesn't make easy, then you may want to write your own program.

*	Convenience: If you need to make many packages quickly, with custom options, your best bet may be to create a program suited for your tasks. Before doing this, though, be sure to look into whether writing a shell script will handle your task adequately. You'll find writing RPM shell scripts goes much faster than writing whole programs.

*	Installation programs: The Windows world has standardized on graphical installation programs such as InstallShield or InstallAnywhere. The RPM system, on the other hand, has focused on automated installation with the rpm command. You can combine the best of both worlds by writing a graphical installation program on top of the RPM system.

*	Integration with environments: You may want to better integrate RPM with a Linux desktop environment such as GNOME or KDE.

*	Working with other languages: This book covers programming RPM with C, the core language for the library, as well as the Python and Perl scripting languages. You can use the RPM library, though, to help bind with other languages such as Tcl, Ruby, or even C# (especially one of the C# implementations for Linux).

This chapter and the next cover RPM programming. This chapter covers the RPM C programming library, which provides low-level access to RPM functionality. The next chapter covers the RPM Python programming library, which provides a much higher-level of abstraction. If you are attempting to write a complex RPM program, your best bet is to try the Python API first. Even so, there is a lot you can do with the RPM C library.

Programming with the C Library

RPM C programs are C programs that call on functions in the RPM library, often called rpmlib. To use the rpmlib, you need to set up a C programming environment and install the rpm-devel package.

Setting Up a C Programming Environment

At the very least, you’ll need a C compiler, gcc, and a text editor. The easiest way to get the C compiler is to install the packages grouped under Software Development with the Red Hat package management tool.

Cross Reference

See Chapter 8 for more on the Red Hat package management tool.

The gcc package requires a number of capabilities. Make sure you install all the necessary packages. Just about every Linux distribution includes gcc and everything you need to develop C programs, so this should not be a problem.

For text editors, you can use the vi or emacs text editors, or any of a number of graphical editors such as gedit.

Cross Reference

Appendix F covers Linux text editors and development tools.

Once you have a C programming environment set up, you next need to get the RPM library for an RPM development environment.

Setting Up the RPM Programming Environment

To program with the RPM library, you need to install the rpm-devel package. You must have a version of rpm-devel that matches your version of the rpm package. If you have Red Hat Linux, your installation CDs will also have the version of the RPM development package that corresponds to your system.

Your program should link against the same libraries that are used by the rpm command itself in order to insure compatibility, so make sure that the version of the rpm-devel package matches the rpm package itself. In most cases, the best bet is to use the RPM programs and libraries that come with your version of Linux.

Cross Reference

You can also download the rpm packages from ftp://ftp.rpm.org/pub/rpm/dist/. This site includes versions of the RPM libraries going back to 1996, ancient history in terms of Linux.

The package you need is rpm-devel. If you installed Red Hat Linux 8.0, the package is rpm-devel-4.1-1.06. This package includes header files, documentation, and libraries.

Using the RPM Library

All C programs using the RPM library need to include the file rpmlib.h, which defines the core data structures, constants, and functions. One thing you’ll quickly note is that the RPM C library accesses RPM data at a very low level. This is one reason why many developers are moving to Python for their RPM programs, since the Python RPM API presents a higher level of abstraction.

Cross Reference

Chapter 17 covers programming RPM with Python.

In addition to rpmlib.h, the header file rpmcli.h defines a high-level API based on the command-line options to the rpm command. (The cli in rpmcli stands for command-line interface.) Table 16-1 lists other important RPM header files that make up the major subsystems of the RPM system.

Table 16-1 RPM sub-system header files

File	Defines
rpmdb.h	RPM database access
rpmio.h	RPM input/output routines
popt.h	Command-line option processing

In addition, a number of header files define the major data objects in the RPM system and the functions that operate on these data objects. Table 16-2 lists these header files.

Table 16-2 RPM data object header files

File	Defines
rpmts.h	Transaction sets
rpmte.h	Transaction elements (packages)
rpmds.h	Dependency sets
rpmfi.h	File information
header.h	Package headers

All the RPM include files are located in /usr/include/rpm on most versions of Linux.

Note

You can use the rpm command and the queries introduced in Chapter 5 to determine exactly where the header files are located. Simply execute the following command:

$ rpm –ql rpm-devel

Examine the output of this command for include files.

Compiling and Linking RPM Programs

RPM programs using the rpmlib C API are the same as C programs everywhere. You need to include the proper header files that define the API calls you need, and link with the right set of libraries.

Include Files

The rpm include files are located in /usr/include/rpm, so you should add this directory to the set of directories that the C compiler looks in for include files with the –I command-line option. For example:

$ gcc –I/usr/include/rpm –c rpm1.c

Note

This also means that you can install the rpm header files in other directories as needed, and just change the –I command-line option.

To help debug problems, you probably want to add the -Wall (output all warnings) and -g (compile with debugging information). For example:

$ gcc -Wall -g –I/usr/include/rpm –c rpm1.c

Libraries

The main rpm library is librpm.a, or a shared version of this same library. To do most anything with RPM programming, you need to link in the following libraries, as listed in Table 16-3.

Table 16-3 Required rpm libraries

Library	Usage
rpm	Main RPM library
rpmdb	RPM database library
rpmio	RPM input/output
popt	Command-line option parsing library

If you are creating RPMs from your C programs, you also need to link in the rpmbuild library. To compile and link a simple RPM program, you need a command like the following:

gcc -I/usr/include/rpm -o program program.c –lrpmbuild \

-lrpm -lrpmdb -lrpmio –lpopt

On some versions of Linux or on other operating systems, you’ll likely need to link a set of helper libraries, as shown following:

gcc -I/usr/include/rpm -o program program.c –lrpmbuild \

-lrpm -lrpmdb -lrpmio –lpopt -lelf -lbz2 -lz

If you have installed the rpm libraries in a non-standard directory, you need to use the –L option to specify where else to look for libraries. For example:

gcc -I/usr/include/rpm -o program program.c –L/opt/lib/rpm \

-lrpmbuild -lrpm -lrpmdb -lrpmio –lpopt -lelf -lbz2 -lz

The -L option tells the cc compiler to look in the /opt/lib/rpm directory as well as in the standard locations such as /usr/lib.

Note

Starting with RPM 4.2, you should just need to link in the rpm library. The other libraries will get pulled in automatically if needed.

Getting information on your RPM environment

A large part of the RPM system lies in system-specific configuration, including the platform you are running on, compatible platforms, and locations of various files. The RPM rc and macro systems support hundreds of options tuned to the specifics of your system, and any customizations you have configured.

Cross Reference

Chapter 21 covers customizing RPM.

Your C programs need to access these RPM system settings to ensure that all data values are properly set up for your system architecture and installation. So, to start an RPM C program, you need to read in all the configuration files. To do this, call rpmReadConfigFiles.

int rpmReadConfigFiles(const char *files, const char *target);

The files parameter holds a colon-delimited list of files that make up your system’s configuration. The target parameter holds the target platform. You can pass NULL for both these parameters to use the RPM defaults, which is generally what you want.

The rpmReadConfigFiles function returns a 0 on success, or –1 on errors.

Once you have read in the configuration files, you can access values in the configuration, or print it out.

Printing the Configuration

To print out the configuration, call rpmShowRC.

int rpmShowRC(FILE* output);

Pass in an output file to print the configuration to, such as stdout. For example:

rpmShowRC( stdout );

The rpmShowRC function always returns 0.

To control some of the output from rpmShowRC, and other RPM library functions, you can set the logging verbosity level by calling rpmSetVerbosity:

void rpmSetVerbosity(int level);

For example:

rpmSetVerbosity(RPMMESS_NORMAL);

Table 16-4 lists the verbosity levels from rpmio/rpmmessages.h going from least output to more output.

Table 16-4 Output verbosity levels

Level	Usage
RPMMESS_FATALERROR	Only critical error conditions and above
RPMMESS_ERROR	Only error conditions and above
RPMMESS_WARNING	Only warning conditions and above
RPMMESS_QUIET	Same as RPMMESS_WARNING
RPMMESS_NORMAL	Only significant messages
RPMMESS_VERBOSE	Verbose informational messages
RPMMESS_DEBUG	Debugging messages, and everything above

You can put together a simple RPM program such as the one shown in Listing 16-1.

Listing 16-1: rpm1.c

/* Show the rpmrc settings. */

#include <stdio.h>

#include <stdlib.h>

#include <rpmlib.h>

int main(int argc, char * argv[]) {

int status = rpmReadConfigFiles( (const char*) NULL,

(const char*) NULL);

if (status != 0) {

printf("Error reading RC files.\n");

exit(-1);

} else {

printf("Read RC OK\n");

}

rpmSetVerbosity(RPMMESS_NORMAL);

rpmShowRC( stdout );

exit(0);

}

Compile this program with a command like the following:

$ cc -I/usr/include/rpm -o rpm1 rpm1.c -lrpm -lrpmdb -lrpmio –lpopt

When you run this program, you should see the contents of your configuration printed to the screen.

Expanding the Value of Macros

With all the rc and macro configuration files, the RPM system has a lot of values, usually called macros, that you can use to refer to settings. The term macro is used because the values can be more than simple strings. You can have one macro refer to the value of other macros, for example. The basic macro syntax is:

%name_of_macro

For example:

%_target

Note

Most of the internal RPM macros start with an underscore, _.

You can expand a macro with the rpm --eval command:

$ rpm --eval %_target

i386-linux

You can also refer to a macro using the following syntax:

%{name_of_macro}

For example:

%{_target}

This syntax makes it easier to include a macro in combinations with other text and other macros, since it clearly delineates the macro name.

Cross Reference

Chapter 21 covers macros in depth. In your C programs, your code will likely need to expand the value of macros to place data in the proper directories, determine the platform architecture, and so on.

Expanding Macros in Your Code

You can use rpmExpand to determine the value of system macros from within your C programs.

The rpmExpand function can expand the values of one or more macros, returning the expanded value. You can pass a variable number of parameters to rpmExpand, and you must terminate the list with a NULL:

char* rpmExpand (const char *arg,...);

You need to free the data returned by rpmExpand by calling free.

The program in Listing 16-2 takes the first command-line argument to your program (after the program name) and expands that argument as a macro.

Listing 16-2: rpmexpand.c

/* Show some macro settings. */

#include <stdio.h>

#include <stdlib.h>

#include <rpmlib.h>

#include <rpmmacro.h>

int main(int argc, char * argv[]) {

int status = rpmReadConfigFiles( (const char*) NULL,

(const char*) NULL);

if (status != 0) {

printf("Error reading RC files.\n");

exit(-1);

}

char* value = rpmExpand(argv[1], (const char*) NULL);

printf("Value of macro is [%s]\n", value);

exit(0);

}

Compile and link this program as shown previously.

When you run this program, pass the name of a macro to expand. For example:

$ ./rpmexpand %_target

Value of macro is [i386-linux]

You can pass multiple macros together, as shown following:

$ ./rpmexpand %_builddir/%_target

Value of macro is [/usr/src/redhat/BUILD/i386-linux]

You can verify this program with the rpm --eval command, introduced previously:

$ rpm --eval %_builddir/%_target

/usr/src/redhat/BUILD/i386-linux

The Power of popt

Popt provides a powerful command-line processing library, allowing the rpm command to handle a variety of options in a very flexible way. You can use popt alone as a library in its own right, or use it combined with the rpm library to handle command-line options like those of the rpm command.

At its most basic, popt processes the command-line arguments to a C program, traditionally called argc and argv, into an option table that describes and contains all the option values.

The main advantage popt has over simpler libraries such as getopt lies in the ability to handle complex arguments and to define aliases. The rpm command supports three different behaviors for the –i option, depending on the context (install a package, get information on a package as part of a query, and perform the install stage of a source RPM, as part of rpmbuild).

The popt library supports both traditional UNIX short options such as –U and the longer options common for GNU programs, especially on Linux, such as --upgrade. For the popt library, you can define both short and long variants for each option. In addition, command-line options may be individual flags, such as –v for verbose, or options that expect one or more data values as arguments, such as –f, which requires a file name.

Popt aliases

One of the most powerful features of popt is the ability to define aliases. A popt alias allows you to define one command-line option as an alias for a set of options. As its simplest, the rpm command-line options --upgrade and –U refer to the same action. You could define one as an alias for the other.

With rpm, the file /usr/lib/rpm/rpmpopt-4.1 (for RPM version 4.1) defines over 400 lines of popt aliases to configure the rpm command-line options. For example:

Rpm alias –requires --qf \

"[%{REQUIRENAME} %{REQUIREFLAGS:depflags} %{REQUIREVERSION}\n]" \

--POPTdesc=$"list capabilities required by package(s)"

This example defines rpm --requires as really a query using the --qf or --queryformat options covered in Chapter 5.

Cross Reference

See Chapter 21 for more on defining popt aliases.

Programming with popt

To use popt in your programs, you need to fill in a table of options and then call poptGetContext. The poptGetContext function parses the command-line options and returns a poptContext, an opaque data type that you need to pass as a parameter to a number of popt functions. The poptContext holds the state of your command-line processing. This allows you to call the popt library with multiple sets of arguments. Each set will have an associate poptContext to keep all the data separate.

The basic poptGetContext function signature follows:

poptContext poptGetContext (const char * name,

int argc,

const char ** argv,

const struct poptOption * options,

int flags );

All the popt functions require the popt.h include file:

#include <popt.h>

The flags should be a bitmask of any options you require, including those listed in Table 16-5.

Table 16-5 Flags for poptGetContext

Flag	Meaning
POPT_CONTEXT_NO_EXEC	Ignore executable expansions
POPT_CONTEXT_KEEP_FIRST	Treat argv[0], the command name, as an option
POPT_CONTEXT_POSIXMEHARDER	Do not allow options to follow arguments

When done with a poptContext, you should free it by calling poptFreeContext:

poptContext poptFreeContext(poptContext context);

The call to poptFreeContext frees up the memory allocated for the context.

Note

You can also fill in a poptContext from settings in a file with poptReadConfigFile:

int poptReadConfigFile(poptContext context,

const char * file_name);

Filling in the Options Table

You need to pass in a table that defines all the possible options. This table is an array of structures, where each structure defines one option. The format for a single option follows:

struct poptOption {

const char * longName;

char shortName;

int argInfo;

void * arg;

int val;

const char * descrip;

const char * argDescrip;

};

Going through this structure, the longName defines the long version of the option, such as "upgrade" for --upgrade. The shortName defines the short, one-character option, such as 'U' for an option of -U. You can place a null character, '\0', to specify no short option. With the rpm command, the --rebuilddb option has only a long name and not a short name, for example.

Note

The longName is not preceded by the double minus sign. Similarly, the shortName is not preceded by the single minus sign.

The descrip field holds a short description of the option and the argDescrip field holds a description of the types of values it expects, or NULL if this option expects no values.

The argInfo field holds a flag that tells the popt library how to treat the option. At the very least, you need to define the type of the option. You can also define special processing flags. Table 16-6 lists the argument types in the options table.

Table 16-6 Popt option table argInfo argument types

Type	Value	Meaning
POPT_ARG_NONE	0	No argument data, just the option such as -v
POPT_ARG_STRING	1	arg treated as string
POPT_ARG_INT	2	arg treated as int
POPT_ARG_LONG	3	arg treated as long
POPT_ARG_INCLUDE_TABLE	4	arg points to a table
POPT_ARG_CALLBACK	5	arg points to a callback function
POPT_ARG_INTL_DOMAIN	6	sets translation domain
POPT_ARG_VAL	7	use value of val field for arg
POPT_ARG_FLOAT	8	arg treated as float
POPT_ARG_DOUBLE	9	arg treated as double

Use these constants, from the include file popt.h, in place of the actual numbers.

Depending on the type you define in the argInfo field, popt will interpret the generic pointer field, arg, in different ways. Using a pointer allows the popt library to automatically update your program variables based on the command-line option settings.

Note

You can pass NULL for the arg field. In this case, the popt library will not set any values for you.

The POPT_ARG_NONE type indicates that this option has no argument. For example, the -v verbose option has no data. On the other hand, the POPT_ARG_STRING type indicates that the user should provide a string. For example, the -f option to the rpm command is expected to include a string argument, the name of the file to look up.

Note

If the argInfo argument type is POPT_ARG_NONE, the popt library will set arg to 1 if the option is present on the command line. You should pass a pointer to an int if you want this set for you.

Popt Callbacks

The POPT_ARG_CALLBACK type indicates that the arg field holds a function pointer to a callback function of the following type:

typedef void (*poptCallbackType) (poptContext con,

enum poptCallbackReason reason,

const struct poptOption * opt,

const char * arg,

const void * data);

The callback reason will be one of the following enum values:

enum poptCallbackReason {

POPT_CALLBACK_REASON_PRE = 0,

POPT_CALLBACK_REASON_POST = 1,

POPT_CALLBACK_REASON_OPTION = 2

};

The data field holds the value of the descrip field in the poptOption entry. You can cheat and stuff a pointer to arbitrary data into this field.

The callback function is most useful if you are using nested option tables. You can place your processing code for the nested options into a callback.

Special Option Table Flags

In addition to the types in Table 16-6, you can also define special bit flags that define extra processing information for each option. Combine these bit flags with the type values using a logical OR operation:

*	The POPT_ARGFLAG_ONEDASH flag allows the longName to be used with one or two dashes, such as -upgrade or --upgrade.

*	For bitmask options, the POPT_ARGFLAG_OR, POPT_ARGFLAG_NOR, POPT_ARGFLAG_AND, POPT_ARGFLAG_NAND, and POPT_ARGFLAG_XOR type flags tell the popt library to apply the given operation, OR, NOR, AND, NAND, or XOR, to the value if set. The POPT_ARGFLAG_NOT flag tells the popt library to negate the value first.

*	You can also use the macros POPT_BIT_SET to set a bit and POPT_BIT_CLR to clear a bit.

*	The POPT_ARGFLAG_OPTIONAL flag indicates that the argument value is optional.

*	The POPT_ARGFLAG_DOC_HIDDEN flag tells popt to hide this option when displaying the help documentation. In other words, this is an internal option.

*	The rarely used POPT_ARGFLAG_STRIP flag tells popt to consume an option and ignore it. This option is rarely used.

*	The POPT_ARGFLAG_SHOW_DEFAULT flag tells popt to show the initial value of the argument for this option as a default when displaying a help message.

Magic Options

With RPM programs, developers usually round out the option table with three special options: POPT_AUTOALIAS, POPT_AUTOHELP, and POPT_TABLEEND. The POPT_AUTOALIAS option sets up a table of aliases:

#define POPT_AUTOALIAS { NULL, '\0', POPT_ARG_INCLUDE_TABLE, poptAliasOptions, \

0, "Options implemented via popt alias/exec:", NULL },

This option refers to the table, poptAliasOptions. You can use the POPT_ARG_INCLUDE_TABLE argInfo type to include another table of options. These options get filled in from popt aliases. In addition, within RPM programs, another table, rpmcliAllPoptTable, holds a set of options common to all RPM programs.

The POPT_AUTOHELP option supports standard help options. The POPT_AUTOHELP macro adds in automatic support for -?, --help, and --usage options.

#define POPT_AUTOHELP { NULL, '\0', POPT_ARG_INCLUDE_TABLE, poptHelpOptions, \

0, "Help options:", NULL },

The POPT_TABLEEND option defines an empty option to mark the end of the table. You must include an empty option to end the table, and POPT_TABLEEND makes this easy.

#define POPT_TABLEEND { NULL, '\0', 0, 0, 0, NULL, NULL }

Note

The code in Listing 16-3, in the "Running a Popt Example" section later in this chapter, shows a full option table.

Parsing the Command-Line Options

Once you have set up a poptGetContext, you need to iterate over all the command-line parameters. To do this, call poptGetNextOpt:

int poptGetNextOpt(poptContext context);

If an error occurs, poptGetNextOpt returns a negative error code. If the context is at the end of the options, poptGetNextOpt returns –1. Table 16-7 lists the error codes:

Table 16-7 Error codes from poptGetNextOpt

Code	Meaning
POPT_ERROR_NOARG	Option requires an argument, but it is missing
POPT_ERROR_BADOPT	Argument could not be parsed
POPT_ERROR_OPTSTOODEEP	Aliases are nested too deeply
POPT_ERROR_BADQUOTE	Start and end quotation marks don't match
POPT_ERROR_BADNUMBER	Argument could not be converted to a number
POPT_ERROR_OVERFLOW	Argument number was too big or too small
POPT_ERROR_ERRNO	A system call returned an error in errno

Walking Through the Command-Line Options

In normal circumstances, poptGetNextOpt parses all the options and returns –1. If your needs are simple, you can use the pointers to the variables passed in the options table, described previously. If you need some special processing for options not handled by popt, that is, options of type POPT_ARG_NONE, then poptGetNextOpt returns the single-character option.

In this case, you can call poptGetNextOpt in a while loop. For example:

while ((option = poptGetNextOpt(context) ) {

/* Do something... */

}

Inside your while loop, you can call poptGetOptArg to get the value of the argument:

char * poptGetOptArg(poptContext context);

You can restart the processing of the options by calling poptResetContext:

void poptResetContext(poptContext context);

The popt system is just looking for arguments that start with a dash, -. In most command-line applications, you may have a number of extra arguments at the end, such as a list of file names. The popt library doesn’t process these, but can provide them to you.

Call poptGetArg to return the next extra argument:

char * poptGetArg(poptContext context);

Keep calling this function until it returns NULL.

Call poptPeekArg to look at the next argument but not mark it as being processed:

char * poptPeekArg(poptContext context);

Or, you can get the whole list of extra arguments by calling poptGetArgs:

char ** poptGetArgs(poptContext context);

Handling Errors

Inside your while loop processing the command-line arguments, you can call poptBadOption to get the option that was bad, and poptStrerror to look up the error message associated with the error.

For poptBadOption, you need to pass in the context, and a bitmask of flags. Normally, pass 0 for no flags or POPT_BADOPTION_NOALIAS, which tells popt to return the actual option, not a value defined in an alias. This makes poptBadOption return the option closest to, if not exactly the same as, what the user entered, which makes for better error reporting.

The poptBadOption function signature follows:

char * poptBadOption(poptContext context, int flags);

Pass the error number returned by poptGetOptArg to poptStrerror to get the standard error message for that option:

const char * poptStrerror(const int error_code);

You can combine these and print out an error with code like the following:

fprintf( stderr, "Error with option [%s]\n %s",

poptBadOption(context, POPT_BADOPTION_NOALIAS),

poptStrerror(error_code);

To print out a usage message, call poptPrintUsage:

void poptPrintUsage(poptContext context,

FILE *output,

int flags);

This function prints out the usage help information, which is a useful function when the user has called a program with incomplete or wrong options.

Running a popt example

Pulling this all together, you can use the popt1.c program, in Listing 16-3, as an example for using popt to process command-line options.

Listing 16-3: popt1.c

/* Processes command-line options. */

#include <stdio.h>

#include <stdlib.h>

#include <popt.h>

/* Data values for the options. */

static int intVal = 55;

static int print = 0;

static char* stringVal;

void callback(poptContext context,

enum poptCallbackReason reason,

const struct poptOption * option,

const char * arg,

const void * data)

{

switch(reason)

{

case POPT_CALLBACK_REASON_PRE:

printf("\t Callback in pre setting\n"); break;

case POPT_CALLBACK_REASON_POST:

printf("\t Callback in post setting\n"); break;

case POPT_CALLBACK_REASON_OPTION:

printf("\t Callback in option setting\n"); break;

}

}

/* Set up a table of options. */

static struct poptOption optionsTable[] = {

{ (const) "int", (char) 'i', POPT_ARG_INT, (void*) &intVal, 0,

(const) "follow with an integer value", (const) "2, 4, 8, or 16" },

{ "callback", '\0', POPT_ARG_CALLBACK|POPT_ARGFLAG_DOC_HIDDEN,

&callback, 0, NULL, NULL },

{ (const) "file", (char) 'f', POPT_ARG_STRING, (void*) &stringVal, 0,

(const) "follow with a file name", NULL },

{ (const) "print", (char) 'p', POPT_ARG_NONE, &print, 0,

(const) "send output to the printer", NULL },

POPT_AUTOALIAS

POPT_AUTOHELP

POPT_TABLEEND

};

int main(int argc, char *argv[]) {

poptContext context = poptGetContext(

(const char*) "popt1",

argc,

argv,

(const struct poptOption* ) &optionsTable,

0);

int option = poptGetNextOpt(context);

printf("option = %d\n", option);

/* Print out option values. */

printf("After processing, options have values:\n");

printf("\t intVal holds %d\n", intVal);

printf("\t print flag holds %d\n", print);

printf("\t stringVal holds [%s]\n", stringVal);

poptFreeContext(context);

exit(0);

}

This example defines a callback but otherwise uses the simplest case for processing the command-line options. This program lets the popt library simply set the values into the option table. In most cases, you should avoid more complex command-line processing.

To compile popt programs, you just need the popt library. For example:

gcc -I/usr/include/rpm -o popt1 popt1.c -lpopt

When you run this program, try out the different options. For example, when you set all the options, you’ll see output like the following:

$ ./popt1 -i 42 --print -f filename1

Callback in option setting

Callback in option setting

Callback in post setting

option = -1

After processing, options have values:

intVal holds 42

print flag holds 1

stringVal holds [filename1]

This command used two short options and one long. You can mix and match short and long options, as shown following:

$ ./popt1 --int 42 -p --file filename1

Callback in option setting

Callback in option setting

Callback in post setting

option = -1

After processing, options have values:

intVal holds 42

print flag holds 1

stringVal holds [filename1]

This example used a short option for print, -p, and long options for the other two options. The popt library also provides handy help and usage messages, using the option table macro POPT_AUTOALIAS. To get a help message, use --help or -?:

$ ./popt1 --help

Usage: popt1 [OPTION...]

-i, --int=2, 4, 8, or 16 follow with an integer value

-f, --file=STRING follow with a file name

-p, --print send output to the printer

Options implemented via popt alias/exec:

Help options:

-?, --help Show this help message

--usage Display brief usage message

Notice how the help descriptions from the options table are used.

Note

With some shells, especially the tcsh shell, you need to wrap a -? In single quotes. For example:

$ ./popt1 '-?'

The usage message is shorter, and you also get it for free:

$ ./popt1 --usage

Usage: popt1 [-i|--int 2, 4, 8, or 16] [-f|--file STRING] [-p|--print]

[-?|--help] [--usage]

All in all, the popt library provides a handy library for processing command-line options and aliases, covered in Chapter 21.

Handling rpm command-line options

The RPM C library makes extensive use of popt for processing command-line arguments. Functions that set up the RPM library, such as rpmcliInit, which sets up the RPM command-line environment, require a table of poptOption entries that define the command-line options for your program.

To create a simple program that handles the standard rpm command-line options, set up the following options table:

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

};

Then, initialize your program with a call to rpmcliInit:

poptContext rpmcliInit(int argc, char *const argv[],

struct poptOption * optionsTable);

When you call rpmcliInit, it will set up all the variables for the standard rpm command-line options.

For example, to see if the verbose flag is turned on, call rpmIsVerbose:

int rpmIsVerbose();

When you are done with a program that called rpmcliInit, call rpmcliFini to clean up the global data:

poptContext rpmcliFini(poptContext context);

The call to rpmcliFini returns NULL.

Working with RPM Files

The RPM C library provides functions to read RPM files as well as query the RPM database. Going beyond querying, you can perform all the tasks that the rpm and rpmbuild commands do, since both these commands are written in C using the RPM library. That said, some tasks are much easier than other tasks. If you are writing a complex package installation program, or a program that keeps various systems up to date with regards to package versions, you may want to look at the Python RPM API instead of the C RPM library.

Cross Reference

Chapter 17 covers the Python RPM API.

Opening RPM files

When working with an RPM file from within a program, the first thing you need to do is open the file. Use Fopen:

FD_t Fopen(const char * path,

const char * fmode);

Fopen works like the standard C function fopen(3).

Note

The reason the RPM library wraps the input/output C library functions is to ensure portability to other operating systems. This is a fairly common technique.

Reading the RPM lead and signature

Once you have opened an RPM file, you can start to read header information, which is the most interesting information to most RPM programs. (You may also want to read the files in the RPM payload, for example.) Before you can start reading the header, though, you must read forward in the RPM file past the lead and signature.

Cross Reference

Chapter 3 introduces the lead and signature.

Even if your programs don’t want to examine the lead or signature, you must read past to position the file offset properly for reading the header information. To read past the lead, call readLead:

int readLead(FD_t fd, struct rpmlead *lead);

The readLead function returns 0 on success or 1 on an error. It fills in an rpmlead struct:

struct rpmlead {

unsigned char magic[4];

unsigned char major;

unsigned char minor;

short type;

short archnum;

char name[66];

short osnum;

short signature_type;

char reserved[16];

};

To read past the signature, call rpmReadSignature:

rpmRC rpmReadSignature(FD_t fd,

Header * header,

sigType sig_type);

The return code is one of the values listed in Table 16-8.

Table 16-8 Return codes from rpmReadSignature

Code
RPMRC_OK
RPMRC_BADMAGIC
RPMRC_FAIL
RPMRC_BADSIZE
RPMRC_SHORTREAD

You can do more with the signature than merely reading past it, of course. Look in the online RPM documentation for more on verifying signatures.

After reading the signature, you can start to read the general header entries.

Reading header information

The header information includes the package name, version, pre- and post-installation scripts, and so on. To read in the RPM header, call headerRead. If successful, headerRead returns a Header object. You can then read data values from the Header.

Header headerRead(FD_t fd,

enum hMagic magicp);

Note

When working with the RPM database, you will also use Header objects.

The trickiest thing about calling headerRead is that you must pass a special magic number flag. This value must be HEADER_MAGIC_YES if the header has a set of magic numbers, and HEADER_MAGIC_NO if not. If you guess incorrectly, headerRead will return an error. To get around, this, you can compare the major number in the lead. For example:

Header header = headerRead(fd, (lead.major >= 3) ?

HEADER_MAGIC_YES : HEADER_MAGIC_NO);

Note

This snippet is one of the gems you'll find when you browse the RPM source code. Use the source.

To read values from the Header, call headerGetEntry. To call headerGetEntry, you pass in a Header and a tag ID. You get back the type of the tag, a pointer to the tag values, and a count of the number of values stored under this tag.

int headerGetEntry(Header header,

int_32 tag,

hTYP_t type,

void **pointer,

hCNT_t data_size);

The call to headerGetEntry returns a 1 on success, or a 0 on failure. On success, the pointer will point at the retrieved data, with the type parameter set to one of the following enum values:

enum rpmTagType_e {

RPM_NULL_TYPE = 0,

RPM_CHAR_TYPE = 1,

RPM_INT8_TYPE = 2,

RPM_INT16_TYPE = 3,

RPM_INT32_TYPE = 4,

RPM_STRING_TYPE = 6,

RPM_BIN_TYPE = 7,

RPM_STRING_ARRAY_TYPE = 8,

RPM_I18NSTRING_TYPE

}

Note

If the type is RPM_STRING_ARRAY_TYPE or RPM_BIN_TYPE, you must free the pointer. Call headerFreeData to free the data:

void* headerFreeData(const void *pointer,

rpmTagType type);

You need to pass in the data pointer and the type flag. You can safely call headerFreeData for all types. The function will do nothing if the type is not set up to require freeing.

When you call headerGetEntry, you must identify the tag you want from the header. This tag is an identifier for the --queryformat tags introduced in Chapter 5. The file rpmlib.h lists the various tags, such as RPMTAG_NAME, RPMTAG_VERSION, and RPMTAG_RELEASE.

The following function shows how to read a string entry from a Header:

/* Function to read a string header entry. */

char* readHeaderString(Header header, int_32 tag_id) {

int_32 type;

void* pointer;

int_32 data_size;

int header_status = headerGetEntry(header,

tag_id,

&type,

&pointer,

&data_size);

if (header_status) {

if (type == RPM_STRING_TYPE) {

return pointer;

}

}

return NULL;

}

Pass the Header object and the ID of the tag to read. For example:

char* name = readHeaderString(header, RPMTAG_NAME);

char* version = readHeaderString(header, RPMTAG_VERSION);

char* release = readHeaderString(header, RPMTAG_RELEASE);

To just get the name, version, and release number, you can call the utility function headerNVR, which has the following function signature:

int headerNVR(Header header,

const char **nameptr,

const char **versionptr,

const char **releaseptr);

When you are through with a header, free it by calling headerFree:

Header headerFree(Header header);

The call to headerFree returns NULL, so you can use the call to set the original pointer to to NULL to prevent accidental reuse. For example:

header = headerFree(header);

A shortcut to header information

You can read in a Header using the shortcut utility method rpmReadPackageFile:

int rpmReadPackageFile(rpmts ts,

FD_t fd,

const char *filename,

Header *header);

You need to pass a transaction set to rpmReadPackageFile and an open file. The filename is just used for reporting errors. On success, rpmReadPackageFile fills in a Header object from the package file. The return value is 0 for success.

To get the necessary transaction set, you need to create one with rpmtsCreate, covered in the "Programming with the RPM Database" section, following.

Note

In most cases, you should call rpmReadPackageFile in place of readLead, rpmReadSignature, and headerRead, since rpmReadPackageFile also verifies the package integrity.

Closing RPM files

When you’re done with an RPM file, close it with Fclose:

int Fclose(FD_t fd);

Fclose acts much like the standard C function fclose(3). The FD_t is an RPM data type that is very similar to a FILE pointer.

The RPM I/O subsystem, defined with rpmio.h, includes functions that mimic (and in most cases wrap) the ANSI C stdio functions. These include: Fopen, Fclose, Fread, Fwrite, Ferror, Fflush, Fileno, and Fseek.

These functions wrap the ANSI C stdio functions to add new features. The Fopen function, for example, supports HTTP or FTP URLs in the place of a file name, so long as you append ".ufdio" to the mode.

Programming with the RPM Database

Many functions in rpmlib require a transaction set. In particular, accessing the rpm database is quite easy using a transaction set.

Create a transaction set by calling rpmtsCreate:

rpmts rpmtsCreate(void);

RPM uses transaction sets to bracket operations on the RPM database. As the RPM API evolves, transaction sets will become more and more important. Transaction sets also help in that the RPM library will automatically open the RPM database as needed.

When you are done with a transaction set, call rpmtsFree:

rpmts rpmtsFree(rpmts ts);

The call to rpmtsFree returns NULL.

Database iterators

Once you have a transaction set, you can iterate over the installed packages in the RPM database by creating an iterator. To do this, call rpmtsInitIterator:

rpmdbMatchIterator rpmtsInitIterator(const rpmts ts,

rpmTag rpmtag,

const void *keypointer,

size_t keylen);

You need to specify which tag to iterate by, which in most cases will be the package name, RPMTAG_NAME, introduced previously With the RPMTAG_NAME tag, you need to pass the name of a package to look for in the keypointer parameter. (The keypointer varies based on the tag you pass.)

For string data, you can pass 0 for the keylen parameter. For example, this call to rpmtsInitIterator looks for all packages named sendmail.

rpmdbMatchIterator iter;

iter = rpmtsInitIterator(ts, RPMTAG_NAME, "sendmail", 0);

The rpmdbMatchIterator allows you to iterate through a number of packages, in this case, all the packages that match a given name. After calling rpmtsInitIterator, the next step is to call rpmdbNextIterator:

Header rpmdbNextIterator(rpmdbMatchIterator iter);

This function returns the next package Header object in the iterator. The Header will be NULL if there are no more packages in the iterator.

If the Header is not NULL, you can get entries from it, as shown previously. You can use a while loop to go through all the matching packages. For example:

while ( (installed_header = rpmdbNextIterator(iter) ) != NULL) {

/* Do something... */

}

Note

In future versions of the RPM library, rpmtsNextIterator, will replace rpmdbNextIterator.

You do not need to free the Header returned by rpmdbNextIterator. Also, the next call to rpmdbNextIterator will reset the Header.

You can customize how an iterator works by adding a pattern to the iterator with rpmdbSetIteratorRE:

int rpmdbSetIteratorRE(rpmdbMatchIterator iter,

rpmTag tag,

rpmMireMode mode,

const char * pattern);

Calling rpmdbSetIteratorRE modifies the passed-in iterator to use the given pattern as a further test on the given tag. The mode parameter names the type of pattern used, which can be one of those listed in Table 16-9.

Table 16-9 Types of patterns for rpmdbSetIteratorRE

Type	Meaning
RPMMIRE_DEFAULT	Same as regular expressions but with \., .*, and ^..$ added.
RPMMIRE_GLOB	Glob-style patterns using fnmatch.
RPMMIRE_REGEX	Regular expressions using regcomp.
RPMMIRE_STRCMP	String comparisons using strcmp.

Cross Reference

For more on these patterns, see the online manual pages for fnmatch(3), glob(7), regcomp(3), regex(7), and strcmp(3).

Free the iterator when done with rpmdbFreeIterator:

rpmdbMatchIterator rpmdbFreeIterator(rpmdbMatchIterator iter);

The call to rpmdbFreeIterator returns NULL.

Dependency Sets

To compare package versions, create a dependency set. The rpm command, for example, uses dependency sets to compare package versions.

Note

You could compare the version numbers directly, calling headerGetEntry to get the version and release tags, converting these strings to numbers and then comparing, but this would cause problems. The custom comparison is not as exact as the code in this section, especially since many packages have version numbers that are not true numbers, such as 1.12.4, with one too many decimal points. This makes the comparisons harder. In addition, there is more than just the version number to take into account. You need to deal with the Epoch value, as well as the release, too.

To handle all the complicated logic of comparing versions, you can use the code in this section, or call rpmvercmp. Do not try to compare version numbers with custom code.

To create a dependency set for a given package Header, call rpmdsThis. Calling rpmdsThis creates a dependency set that holds a triple of the package name, the Epoch/Version/Release information, and the flags.

rpmds rpmdsThis(Header header,

rpmTag tagID,

int_32 Flags);

For comparing packages, you can pass RPMTAG_REQUIRENAME for the tagID. The actual tagID here is ignored for the version check. What you do need, though, are flags to check whether another package is less than or equal to the Epoch/Version/Release information in this dependency set. For this task, pass the following bit flags:

(RPMSENSE_EQUAL|RPMSENSE_LESS)

Once you have a dependency set, you can use the handy function rpmdsNVRMatchesDep to compare the NVR, or Name, Version, Release entries in the header of one package against the data in the dependency set.

int rpmdsNVRMatchesDep(const Header header,

const rpmds dependency_set,

int nopromote);

After checking the dependencies, rpmdsNVRMatchesDep returns 1 if the dependency overlaps, or 0 otherwise. In terms of comparing packages, 1 means that the package file is as old or older than the installed package, and 0 means that the package already installed is newer. Pass 1 to prevent promoting the Epoch value in the packages during the comparison.

The actual comparison is controlled by the call that creates the dependency set, especially the flags. Thus, passing flags of (RPMSENSE_EQUAL|RPMSENSE_LESS) to rpmdsThis set up the test as a less than or equal test.

Note

The RPM C API documentation marks rpmdsNVRMatchesDep as deprecated, to be replaced in the future.

You can also call rpmVersionCompare to compare the versions of two packages:

int rpmVersionCompare(Header header1, Header header2);

The return value is -1 if the header1 represents an older version than header2, 0 if the two headers represent the same version, and 1 if header1 represents a newer version than header2.

To get the name of the package from a dependency set, call rpmdsN:

const char* rpmdsN(const rpmds dependency_set);

You can use rpmdsN to get the name when calling rpmtsInitIterator if you are working with dependency sets when searching the RPM database.

Free a dependency set when done by calling rpmdsFree:

rpmds rpmdsFree(rpmds dependency_set);

As with other free functions, rpmdsFree returns NULL.

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

Where to Go from Here

There is a lot more you can do with the RPM library; you're limited only by your imagination. The best way to get started is to follow the examples in this chapter and then try out some RPM programs on your own. After working with the RPM library for a while, you can delve into other RPM topics.

The RPM Web site, at www.rpm.org, has most of the available documentation on the RPM system. This site also includes official RPM released software.

One of the best ways to help find out about how to perform RPM tasks is to look at the source code for the rpm program itself. For this, download the rpm-src source RPM, too. To see the rpm command-line interface functions in action, look especially at tools/rpmcache.c and tools/rpmgraph.c, two relatively short RPM files that show how to take advantage of a number of short cuts. The source code for the Python and Perl bindings can also provide extra hints about the purposes of the RPM API calls.

The RPM Web site also has a cross-referenced set of HTML pages on the RPM programming API. The pages for version 4.1 of RPM are available at www.rpm.org/rpmapi-4.1/. A good starting page is www.rpm.org/rpmapi-4.1/modules.html, which lists a number of modules within the overall RPM library. This extra level of organization can help you locate the functions you need.

Summary

Everything you can do with RPM you can program in C. That’s because the source code for the entire RPM system is available. In addition, the rpm and rpmbuild programs make use of a published API, called rpmlib, to access RPM functionality. You can use this library yourself.

The popt library, short for parse options, provides a lot of handy utilities for parsing very complex command-line options. You can use popt inside your own programs, even if you don’t use the rest of the RPM functionality.

Most RPM programs start up by calling rpmcliInit, which sets up RPM variables for the large set of command-line options supported by most RPM commands.

Call rpmReadPackageFile to read in the Header object from a package file. You can also get Header objects for the packages installed in a system by initializing an iterator to iterate over a set of packages that meet a certain criteria.

This chapter covers a fairly low level of access to RPM functionality. The next chapter, on Python programming, shows a higher level of abstraction for working with RPM.

Chapter 16

Chris Ricker

2003-01-25T19:52:00

Eric Foster-Johnson