Product SiteDocumentation Site

Chapter 15. Programming RPM with C

15.1. Programming with the C Library
15.1.1. Setting Up a C Programming Environment
15.1.2. Setting Up the RPM Programming Environment
15.1.3. Using the RPM Library
15.1.4. Compiling and Linking RPM Programs
15.1.5. Getting information on your RPM environment
15.2. The Power of popt
15.2.1. Popt aliases
15.2.2. Programming with popt
15.2.3. Handling Errors
15.2.4. Running a popt example
15.2.5. Handling rpm command-line options
15.3. Working with RPM Files
15.3.1. Opening RPM files
15.3.2. Reading the RPM lead and signature
15.3.3. Reading header information
15.3.4. A shortcut to header information
15.3.5. Closing RPM files
15.4. Programming with the RPM Database
15.4.1. Database iterators
15.4.2. Dependency Sets
15.5. Comparing an RPM File to an Installed Package
15.6. Where to Go from Here
15.7. Summary
This chapter covers:
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 7, RPM Management Software 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
Chapter 26, Linux Text Editors and Development Tools 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 16, Programming RPM with Python 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 4, Using the RPM Database 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 20, Customizing RPM Behavior 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 20, Customizing RPM Behavior 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