head 1.1; branch 1.1.1; access ; symbols MAXIMUM_RPM_1_0:1.1.1.1 VENDOR:1.1.1; locks ; strict; comment @# @; 1.1 date 2001.08.28.12.07.09; author rse; state Exp; branches 1.1.1.1; next ; 1.1.1.1 date 2001.08.28.12.07.09; author rse; state Exp; branches ; next ; desc @@ 1.1 log @Initial revision @ text @
While RPM makes building packages as easy as possible, some of the default design decisions might not work well in a particular situation. Here are two situations where RPM's method of package building may cause problems:
Either of these situations can be resolved by directing RPM to build, install, and package the software in a different area on your build system. It requires a bit of additional effort to accomplish this, but taken a step at a time, it is not difficult. Basically, the process can be summed up by addressing the following steps:
The methods discussed here are not required in every situation. For example, a system administrator developing a package on a production system may only need to add support for a build root. On the other hand, a student wishing to build a package on a university system will need to get around the lack of root access by implementing every method described here.
Part of the process of packaging software with RPM is to actually build the software and install it on the build system. The installation of software can only be accomplished by someone with root access, so a non-privileged user will certainly need to handle RPM's installation phase differently. There are times, however, when even a person with root access will not want RPM to copy new files into the system's directories. As mentioned above, the reasons might be due to the fact that the software being packaged is already in use on the build system. Another reason might be as mundane as not having enough free space available to perform the install into the default directories.
Whatever the reason, RPM provides the ability to direct a given package to install into an alternate root. This alternate root is known as a ``build root''. Several requirements must be met in order for a build root to be utilized:
The first part is easy. It entails adding the following line to the spec file:
BuildRoot: <root>
Of course, you would replace ``<root>'' with the name of the directory in which you'd like the software to install.1 If, for example, you specify a build root of /tmp/foo, and the software you're packaging installs a file bar in /usr/bin, you'll find bar residing in /tmp/foo/usr/bin after the build.
A note for you non-root package builders: make sure you can actually write to the build root you specify! Those of you with root access should also make sure you choose your build root carefully. For an assortment of reasons, it's not a good idea to declare a build root of ``/''! We'll get into the reasons why shortly.
The final requirement for adding build root support is to make sure the software's installation method can support installing into an alternate root. The difficulty in meeting this requirement can range from dead simple to nearly impossible. There are probably as many different ways of approaching this as there are packages to build. But in general, some variant of the following approach is used:
Here's an example of how these components work together to utilize a build root. First, there's the definition of the build root in the spec file:
BuildRoot: /tmp/cdplayer
This line defines the build root as being /tmp/cdplayer. All the files installed by this software will be placed under the cdplayer directory. Next is the spec file's %install section:
%install make ROOT="$RPM_BUILD_ROOT" install
Since the software we're packaging uses make to perform the actual install, we simply define the environment variable ROOT to be the path defined by RPM_BUILD_ROOT. So far, so good. Things really start to get interesting in the software's Makefile, though:
install: cdp cdp.1.Z # chmod 755 cdp # cp cdp /usr/local/bin install -m 755 -o 0 -g 0 -d $(ROOT)/usr/local/bin/ install -m 755 -o 0 -g 0 cdp $(ROOT)/usr/local/bin/cdp # ln -s /usr/local/bin/cdp /usr/local/bin/cdplay ln -s ./cdp $(ROOT)/usr/local/bin/cdplay # cp cdp.1 /usr/local/man/man1 install -m 755 -o 0 -g 0 -d $(ROOT)/usr/local/man/man1/ install -m 755 -o 0 -g 0 cdp.1 $(ROOT)/usr/local/man/man1/cdp.1
In the example above, the commented lines were the original ones. The uncommented lines perform the same function, but also support installation in the root specified by the environment variable ROOT.
One point worth noting is that the Makefile now takes extra pains to make sure the proper directory structure exists before installing any files. This is often necessary, as build roots are deleted, in most cases, after the software has been packaged. This is why install is used with the -d option -- to make sure the necessary directories have been created.
Let's see how it works:
# rpm -ba cdplayer-1.0.spec
* Package: cdplayer Executing: %prep + cd /usr/src/redhat/BUILD ... + exit 0 Executing: %build + cd /usr/src/redhat/BUILD + cd cdplayer-1.0 ... + exit 0 + umask 022 Executing: %install + cd /usr/src/redhat/BUILD + cd cdplayer-1.0 + make ROOT=/tmp/cdplayer install install -m 755 -o 0 -g 0 -d /tmp/cdplayer/usr/local/bin/ install -m 755 -o 0 -g 0 cdp /tmp/cdplayer/usr/local/bin/cdp ln -s ./cdp /tmp/cdplayer/usr/local/bin/cdplay install -m 755 -o 0 -g 0 -d /tmp/cdplayer/usr/local/man/man1/ install -m 755 -o 0 -g 0 cdp.1 /tmp/cdplayer/usr/local/man/man1/cdp.1 + exit 0 Executing: special doc + cd /usr/src/redhat/BUILD + cd cdplayer-1.0 + DOCDIR=/tmp/cdplayer//usr/doc/cdplayer-1.0-1 + rm -rf /tmp/cdplayer//usr/doc/cdplayer-1.0-1 + mkdir -p /tmp/cdplayer//usr/doc/cdplayer-1.0-1 + cp -ar README /tmp/cdplayer//usr/doc/cdplayer-1.0-1 + exit 0 Binary Packaging: cdplayer-1.0-1 Finding dependencies... Requires (2): libc.so.5 libncurses.so.2.0 usr/doc/cdplayer-1.0-1 usr/doc/cdplayer-1.0-1/README usr/local/bin/cdp usr/local/bin/cdplay usr/local/man/man1/cdp.1 93 blocks Generating signature: 0 Wrote: /usr/src/redhat/RPMS/i386/cdplayer-1.0-1.i386.rpm + umask 022 + echo Executing: %clean Executing: %clean + cd /usr/src/redhat/BUILD + cd cdplayer-1.0 + exit 0 Source Packaging: cdplayer-1.0-1 cdplayer-1.0.spec cdplayer-1.0.tgz 82 blocks Generating signature: 0 Wrote: /usr/src/redhat/SRPMS/cdplayer-1.0-1.src.rpm #
Looking over the output from the %install section, we first see that the RPM_BUILD_ROOT environment variable in the make install command, has been replaced with the path specified earlier in the spec file on the BuildRoot: line. The ROOT environment variable used in the makefile now has the appropriate value, as can be seen in the various install commands that follow.
Note, also, that we use install's -d option to ensure that every directory in the path exists before we actually install the software. Unfortunately, we can't do this and install the file in one command.
Looking at the section labeled Executing: special doc, we find that RPM is doing something similar for us. It starts by making sure there is no pre-existing documentation directory. Next, RPM creates the documentation directory and copies files into it.
The remainder of this example is identical to that of a package being built without a build root being specified. However, although the output is identical, there is one crucial difference. When the binary package is created, instead of simply using each line in the %files list verbatim, RPM prepends the build root path first. If this wasn't done, RPM would attempt to find the files, relative to the system's root directory, and would, of course, fail. Because of the automatic prepending of the build root, it's important to not include the build root path in any %files list entry. Otherwise, the files would not be found by RPM, and the build would fail.
Although RPM has to go through a bit of extra effort to locate the files to be packaged, the resulting binary package is indistinguishable from the same package created without using a build root.
Once the necessary modifications have been made to support a build root, it's necessary for the package builder to keep some issues in mind. The first is that the build root specified in the spec file can be overridden. RPM will set the build root (and therefore, the value of $RPM_BUILD_ROOT) to one of the following values:
Because of this, it's important that the spec file and the makefile be written in such a way that no assumptions about the build root are made. The main issue is that the build root must not be hard-coded anywhere. Always use the RPM_BUILD_ROOT environment variable!
Another issue to keep in mind is cleaning up after the build. Once software builds and is packaged successfully, it's probably no longer necessary to leave the build root in place. Therefore, it's a good idea to include the necessary commands in the spec file's %clean section. Here's an example:
%clean rm -rf $RPM_BUILD_ROOT
Since RPM executes the %clean section after the binary package has been created, it's the perfect place to delete the build root tree. In the example above, that's exactly what we're doing. We're also doing the right thing by using the RPM_BUILD_ROOT, instead of a hard-coded path.
The last issue to keep in mind revolves around the %clean section we just created. At the start of the chapter, we mentioned that it's not a good idea to define a build root of ``/''. The %clean section is why: If the build root was set to ``/'', the %clean section would blow away your root filesystem! Keep in mind that this can bite you, even if the package's spec file doesn't specify ``/'' as a build root. It's possible to use the - -buildroot option to specify a dangerous build root, too:
# rpm -ba --buildroot / cdplayer-1.0.spec
But for all the possible hazards using build roots can pose for the careless, it's the only way to prevent a build from disrupting the operation of certain packages on the build system. And for the person wanting to build packages without root access, it's the first of three steps necessary to accomplish the task. The next step is to direct RPM to build the software in a directory other than RPM's default one.
While RPM's build root requires a certain amount of spec file and make file tweaking in order to get it working properly, directing RPM to perform the build in a different directory is a snap. The hardest part is to create the directories RPM will use during the build process.
RPM's build area consists of five directories in the top-level:
The description of the RPMS directory above, is missing one key point. Since the binary package files are specific to an architecture, the directory actually contains one or more subdirectories, one for each architecture. It is in these subdirectories that RPM will write the binary package files.
Let's start by creating the directories. We can even do it with one command:
% pwd
/home/ed
% mkdir mybuild\
? mybuild/BUILD\
? mybuild/RPMS\
? mybuild/RPMS/i386\
? mybuild/SOURCES\
? mybuild/SPECS\
? mybuild/SRPMS
%
That's all there is to it. You may have noticed that we created a subdirectory to RPMS called i386 -- This is the architecture-specific subdirectory for Intel x86-based systems, which is our example build system.
The next step in getting RPM to use a different build area is telling RPM where the new build area is. And it's almost as easy as creating the build area itself.
All that's required to get RPM to start using the new build area is to define an alternate value for topdir in an rpmrc file. For the non-root user, this means putting the following line in a file called .rpmrc, located in your home directory:
topdir: <path>
By replacing ``<path>'' with the path to the new build area's top-level directory, RPM will attempt to use it the next time a build is performed. Using our newly created build area as an example, we'll set topdir to /home/ed/mybuild:
topdir: /home/ed/mybuild
That's all there is to it. Now it's time to try a build.
In the following example, a non-root user attempts to build the cdplayer package in a personal build area. If the user has modified rpmrc file entries to change the default build area, the command used to start the build is just like the one used by a root user. Otherwise, the - -buildroot option will need to be used:
% cd /home/ed/mybuild/SPECS
% rpm -ba - -buildroot /home/ed/mybuildroot cdplayer-1.0.spec
* Package: cdplayer + umask 022 Executing: %prep + cd /home/ed/mybuild/BUILD + cd /home/ed/mybuild/BUILD + rm -rf cdplayer-1.0 + gzip -dc /home/ed/mybuild/SOURCES/cdplayer-1.0.tgz + tar -xvvf - drwxrwxr-x root/users 0 Aug 20 20:58 1996 cdplayer-1.0/ -rw-r--r-- root/users 17982 Nov 10 01:10 1995 cdplayer-1.0/COPYING ... + cd /home/ed/mybuild/BUILD/cdplayer-1.0 + chmod -R a+rX,g-w,o-w . + exit 0 Executing: %build + cd /home/ed/mybuild/BUILD + cd cdplayer-1.0 + make gcc -Wall -O2 -c -I/usr/include/ncurses cdp.c ... Executing: %install + cd /home/ed/mybuild/BUILD + make ROOT=/home/ed/mybuildroot/cdplayer install install -m 755 -o 0 -g 0 -d /home/ed/mybuildroot/cdplayer/usr/local/bin/ install: /home/ed/mybuildroot/cdplayer: Operation not permitted install: /home/ed/mybuildroot/cdplayer/usr: Operation not permitted install: /home/ed/mybuildroot/cdplayer/usr/local: Operation not permitted install: /home/ed/mybuildroot/cdplayer/usr/local/bin: Operation not permitted install: /home/ed/mybuildroot/cdplayer/usr/local/bin/: Operation not permitted make: *** [install] Error 1 Bad exit status %
Things started off pretty well -- The %prep section of the spec file unpacked the sources into the new build area, as did the %build section. The build was proceeding normally in the user-specified build area, and root access was not required. In the %install section, however, things started to fall apart. What happened?
Take a look at that install command. The two options, ``-o 0'' and ``-g 0'', dictate that the directories to be created in the build root are to be owned by the root account. Since the user performing this build did not have root access, the install failed, and rightly so.
OK, let's remove the offending options and see where that gets us. Here's the install section of the make file after our modifications:
install: cdp cdp.1.Z install -m 755 -d $(ROOT)/usr/local/bin/ install -m 755 cdp $(ROOT)/usr/local/bin/cdp rm -f $(ROOT)/usr/local/bin/cdplay ln -s ./cdp $(ROOT)/usr/local/bin/cdplay install -m 755 -d $(ROOT)/usr/local/man/man1/ install -m 755 cdp.1 $(ROOT)/usr/local/man/man1/cdp.1
We'll spare you from having to read through another build, but this time it completed successfully. Now, let's put our sysadmin hat on and install the newly built package:
# rpm -ivh cdplayer-1.0-1.i386.rpm
cdplayer ##################################################
#
Well, that was easy enough. Let's take a look at some of the files and make sure everything looks OK. We know there are some files installed in /usr/local/bin, so let's check those:
# ls -al /usr/local/bin
-rwxr-xr-x 1 ed ed 40739 Sep 13 20:16 cdp*
lrwxrwxrwx 1 ed ed 47 Sep 13 20:34 cdplay -> ./cdp*
#
Looks pretty good... Wait a minute! What's up with the owner and group? The answer is simple: User ed ran the build, which executed the make file, which ran install, which created the files. Since ed created the files, they are owned by him.
This brings up an interesting point. Software must be installed with very specific file ownership and permissions. But a non-root user can't create files that are owned by anyone other than his or herself. What is a non-root user to do?
In cases where the package builder cannot create the files to be packaged with the proper ownership and permissions, the %attr macro can be used to make things right.
The %attr macro has the following format:
%attr(<mode>, <user>, <group>) file
There are a couple other wrinkles to using the %attr macro. If a particular file attribute doesn't need to be specified, that attribute can be replaced with a dash ``-'' and %attr will not change it. Say, for instance, that a package's files are installed with the permissions correctly set, as they almost always are. Instead of having to go to the trouble of entering the permissions for each and every file, each file can have the same %attr:
%attr(-, root, root)
This works for user and group specifications, as well.
The other wrinkle is that, although we've been showing the three file attributes separated by commas, in reality they could be separated by spaces as well. Whichever delimiter you choose, it pays to be consistent throughout a spec file.
Let's fix up cdplayer with a liberal sprinkling of %attrs. Here's what the %files list looks like after we've had our way with it:
%files %attr(-, root, root) %doc README %attr(4755, root, root) /usr/local/bin/cdp %attr(-, root, root) /usr/local/bin/cdplay %attr(-, root, rot) /usr/local/man/man1/cdp.1
A couple points are worth noting here. The line for README shows that multiple macros can be used on a line -- in this case, one to set file attributes, and one to mark the file as being documentation. The %attr for /usr/local/bin/cdp declares the file to be setuid root. If it sends a shiver down your spine to know that anybody can create a package that will run setuid root when installed on your system -- Good! Just because RPM makes it easy to install software doesn't mean that you should blindly install every package you find.
A single RPM command can quickly point out areas of potential problems and should be issued on any package file whose creators you don't trust:
% rpm -qlvp ../RPMS/i386/cdplayer-1.0-1.i386.rpm
drwxr-xr-x- root root 1024 Sep 13 20:16 /usr/doc/cdplayer-1.0-1 -rw-r--r--- root root 1085 Nov 10 01:10 /usr/doc/cdplayer-1.0-1/README -rwsr-xr-x- root root 40739 Sep 13 21:32 /usr/local/bin/cdp lrwxrwxrwx- root root 47 Sep 13 21:32 /usr/local/bin/cdplay -> ./cdp -rwxr-xr-x- root rot 4550 Sep 13 21:32 /usr/local/man/man1/cdp.1 %
Sure enough -- there's that setuid root file. In this case we trust the package builder, so let's install it:
# rpm -ivh cdplayer-1.0-1.i386.rpm
cdplayer ##################################################
group rot does not exist - using root
#
What's this about group ``rot''? Looking back at the rpm -qlvp output, it looks like /usr/local/man/man1/cdp.1 has a bogus group. Looking back even further, it's there in the %attr for that file. Must have been a typo. We could pretend that RPM used advanced artificial intelligence technology to come to the same conclusion as we did and made the appropriate change, but in reality, RPM simply used the only group identifier it could count on -- root. RPM will do the same thing if it can't resolve a user specification.
Let's look at some of the files the package installed, including that worrisome setuid root file:
# ls /usr/local/bin
total 558 -rwsr-xr-x 1 root root 40739 Sep 13 21:32 cdp* lrwxrwxrwx 1 root root 47 Sep 13 21:36 cdplay -> ./cdp* #
RPM did just what it was supposed to -- It gave the files the attributes specified by the %attr macros.
At the start of this section, we mentioned that the %attr macro wouldn't accept numeric uids or gids, and we promised to explain why. The reason is simply that, even if a package requires a certain user or group to own the package's files, the user may not have the same uid/gid from system to system. There -- wasn't that simple?
In the next chapter, we'll discuss how to make your packaged software safe against modification by unscrupulous people. The name of the game is Pretty Good Privacy, and you'll see how signing packages with PGP is easier than you think!