Skip to main content

skip to main content

developerWorks  >  Linux  >

Packaging software with RPM, Part 3

Running scripts at install and uninstall

developerWorks
Document options

Document options requiring JavaScript are not displayed


Rate this page

Help us improve this content


Level: Intermediate

Dan Poirier (poirier@us.ibm.com), Software engineer, IBM

01 Feb 2002

RPM is a widely-used tool for delivering software for Linux. Users can easily install an RPM-packaged product. In this article, third in a series, Dan explains how to run scripts when your package is installed or uninstalled, or when other packages are installed or uninstalled.

It's often useful to be able to execute commands when your program is installed or uninstalled on a user's machine. For example, you might need to edit a system configuration file to enable a new service, or need to define a new user to own the program that you're installing.

To get the most out of the advice and examples in this article, take a look at Part 1 and Part 2 of this series, which show you how to use RPM and how to distribute your work, respectively.

How install and uninstall scripts work

Install and uninstall scripts seem simple, but a few surprises in how they work can cause big problems.

Here are the basics. You can add any of the following four sections to your .spec file, which lists shell scripts to run at various points in the install life of your package:

%pre
Runs before the package is installed
%post
Runs after the package is installed
%preun
Runs before the package is uninstalled
%postun
Runs after the package is uninstalled

In particular, note the difference between %install and these sections. %install runs on your development machine when you are building the RPM; it should install the product on your development machine, or into a build root. These sections, on the other hand, specify what will run on the user's machine when the user is installing or uninstalling your RPM package.

Here's an example, building on the previous articles. We want to use install-info to add GNU indent's info file to the directory.


Listing 1. indent-4.spec

# Simplistic example of install scripts - do not use
Summary: GNU indent
Name: indent
Version: 2.2.6
Release: 4
Source0: %{name}-%{version}.tar.gz
License: GPL
Group: Development/Tools
BuildRoot: %{_builddir}/%{name}-root

%description
The GNU indent program reformats C code to any of a variety of
formatting standards, or you can define your own.

%prep
%setup -q

%build
./configure
make

%install
rm -rf $RPM_BUILD_ROOT
make DESTDIR=$RPM_BUILD_ROOT install

%post
if [ -x /sbin/install-info ]; then
  /sbin/install-info /usr/local/info/indent.info /usr/local/info/dir
fi

%preun
if [ -x /sbin/install-info ]; then
  /sbin/install-info --delete /usr/local/info/indent.info /usr/local/info/dir
fi

%clean
rm -rf $RPM_BUILD_ROOT

%files
%defattr(-,root,root)
/usr/local/bin/indent
%doc /usr/local/info/indent.info
%doc %attr(0444,root,root) /usr/local/man/man1/indent.1
%doc COPYING AUTHORS README NEWS

Note first that we check for the install-info tool before trying to use it. We don't want our install to fail just because we cannot provide a link to the product documentation.

But there might be cases where you would like an installation to fail. A good technique is to use the %pre script to check for installation prerequisites that are more complex than what RPM can support directly. If the prerequisites are not met, the script exits with a non-zero status, and RPM does not continue with the installation.

Notice also that we are careful to undo our install script using an uninstall script.



Back to top


Not so simple: upgrades complicate everything

Now let's turn to upgrades. The instructions in the previous section work fine if users only install and delete your package; they fail badly during upgrades.

Here's how RPM performs an upgrade:

  • Run %pre of new package
  • Install new files
  • Run %post of new package
  • Run %preun of old package
  • Delete any old files not overwritten by newer ones
  • Run %postun of old package

If we do an upgrade using our previous example, the last thing that RPM runs is the %postun script, which removes all the work we did in our install script! This is not exactly what the average RPM-using developer would have guessed. I won't try to explain the reasons for this, just what you have to do about it.

Luckily, there is a way for a script to tell if the package is being installed, deleted, or upgraded -- sort of. Each script is passed a single command-line argument, a number. This is supposed to tell the script how many copies of the package will be installed after the current package has finished installing or uninstalling.

Rather than trying to figure this out, it's probably easier to just look at the values that get passed in various cases.

Here are the actual values passed during an install:

  • Run %pre of new package (1)
  • Install new files
  • Run %post of new package (1)

Here are the values passed during an upgrade:

  • Run %pre of new package (2)
  • Install new files
  • Run %post of new package (2)
  • Run %preun of old package (1)
  • Delete any old files not overwritten by newer ones
  • Run %postun of old package (1)

Here are the values passed during a delete:

  • Run %preun of old package (0)
  • Delete files
  • Run %postun of old package (0)

You can test this yourself by adding something like the next example to your package. Then make a new package with a slightly higher release number, and you can install the first, then upgrade to the second, and finally uninstall it, to see all the possibilities. And of course, you will always want to try this several times yourself before releasing any RPM on an unsuspecting public.


Listing 2. Test order and arguments for script execution

%pre
echo This is pre for %{version}-%{release}: arg=$1

%post
echo This is post for %{version}-%{release}: arg=$1

%preun
echo This is preun for %{version}-%{release}: arg=$1

%postun
echo This is postun for %{version}-%{release}: arg=$1

Here's another example, this time coping correctly with the upgrade process:


Listing 3. indent-5.spec

Summary: GNU indent
Name: indent
Version: 2.2.6
Release: 5
Source0: %{name}-%{version}.tar.gz
License: GPL
Group: Development/Tools
BuildRoot: %{_builddir}/%{name}-root

%description
The GNU indent program reformats C code to any of a variety of
formatting standards, or you can define your own.

%prep
%setup -q

%build
./configure
make

%install
rm -rf $RPM_BUILD_ROOT
make DESTDIR=$RPM_BUILD_ROOT install

%post
if [ "$1" = "1" ] ; then  # first install
 if [ -x /sbin/install-info ]; then
   /sbin/install-info /usr/local/info/indent.info /usr/local/info/dir
 fi
fi

%preun
if [ "$1" = "0" ] ; then # last uninstall
 if [ -x /sbin/install-info ]; then
   /sbin/install-info --delete /usr/local/info/indent.info /usr/local/info/dir
 fi
fi

%clean
rm -rf $RPM_BUILD_ROOT

%files
%defattr(-,root,root)
/usr/local/bin/indent
%doc /usr/local/info/indent.info
%doc %attr(0444,root,root) /usr/local/man/man1/indent.1
%doc COPYING AUTHORS README NEWS

Now the info link will be removed only when the package is being completely deleted.



Back to top


Triggers -- running scripts when other packages are installed or uninstalled

Suppose you want to run some code in your package when other packages are installed or uninstalled. You can do it with trigger scripts.

Why would you want to do this? Usually because your package uses the services of one or more other packages, or provides services to one or more other packages.

Here's an example. Suppose you're packaging up a nifty add-on tool for the Emacs and Xemacs editors. It can work with either or both, but you need to do some minor configuration depending on which editors are installed.

At install time, you can test for Emacs and Xemacs and configure your tool to be accessible by the available editors. But what happens if the user installs Xemacs later? Your tool won't be usable in Xemacs unless the user uninstalls and reinstalls your tool. Wouldn't it be better if your package could tell RPM, "let me know if Xemacs gets installed"?

That's the idea of trigger scripts. You can add this to your .spec file:


Listing 4. Trigger example

%triggerin -- emacs
# Insert code here to run if your package is already installed,
# then emacs is installed,
# OR if emacs is already installed, then your package is installed

%triggerin -- xemacs
# Insert code here to run if your package is already installed,
# then xemacs is installed,
# OR if xemacs is already installed, then your package is installed

%triggerun -- emacs
# insert code here to run if your package is already installed,
# then emacs is uninstalled

%triggerun -- xemacs
# insert code here to run if your package is already installed,
# then xemacs is uninstalled

%postun
# Insert code here to run if your package is uninstalled

Trigger scripts are passed two arguments. The first argument is the number of instances of your package that will be installed when your trigger script has completed running. The second argument is the number of instances of the triggering package that will be installed when your trigger script has completed running.

From the triggers file in the RPM distribution, here's the complete ordering of script executions and file installs and uninstalls during an RPM upgrade:


Listing 5. Script ordering

  new-%pre	for new version of package being installed
  ...		(all new files are installed)
  new-%post	for new version of package being installed

  any-%triggerin (%triggerin from other packages set off by new install)
  new-%triggerin
  old-%triggerun
  any-%triggerun (%triggerun from other packages set off by old uninstall)

  old-%preun	for old version of package being removed
  ...		(all old files are removed)
  old-%postun	for old version of package being removed

  old-%triggerpostun
  any-%triggerpostun (%triggerpostun from other packages set off by old un
		install)



Back to top


Advanced scripting

Alternate interpreters


Ordinarily, all install-time scripts and trigger scripts are run using the /bin/sh shell program. If you prefer another scripting language, say Perl, you can tell RPM that your script should be run using another interpreter by adding -p interpreter to the script line. For example:


Listing 6. Example of alternate interpreters

%post -p /usr/bin/perl
# Perl script here

%triggerun -p /usr/bin/perl -- xemacs
# Another Perl script here

Note that this does not apply to the build-time scripts of RPM, such as %install.

RPM variables

RPM expands RPM variables in your scripts before storing them into the RPM package file, which can be useful sometimes. For example, you can define your own variables near the top of your .spec file, and then refer to them using %{variable_name} all through your .spec file -- and even in your scripts:


Listing 7. Example of RPM variables

...
%define foo_dir /usr/lib/foo
...

%install
cp install.time.message $RPM_BUILD_ROOT/%{foo_dir}

%files
%{foo_dir}/install.time.message

%post
/bin/cat %{foo_dir}/install.time.message



Back to top


Things to avoid

You might be tempted to do some things at install time that turn out to be a bad idea. For example, any attempt to interact with the user probably won't work very well. RPM is designed to allow batch installs, during which no user is necessarily present. If an RPM package stops during an install to ask a question, and no one sees the question, the install will appear to just hang forever.

Another thing you probably want to avoid is starting any services. During a complete installation, you can't be sure if everything that your program needs is there already (for example, there might not be any network yet); also, if every RPM service tried to start during a complete operating system install, the entire install would probably take significantly longer. What you can do in such cases is print a message telling the user about any required configuration or service that needs to be started. If a user is installing your RPM package manually, he or she will see it; if it's part of a larger batch install, it won't hurt anything, and the machine will almost certainly be rebooted at the end, starting your service.



Back to top


Things to remember

If your package installs an init script, you can use chkconfig to arrange for your service to be started and stopped in the appropriate run-levels. Though you can accomplish the same thing by installing the necessary symbolic links directly as part of your package, getting them right is a pesky detail that you might prefer to leave to chkconfig.

A number of services run under a specific user id for security; if yours does, you will need to create that user on the system if it doesn't already exist.

If your package installs any GNU info files, they won't be visible in the Info directory unless you use the install-info tool to add them at install time.

Before uninstalling, you should almost certainly attempt to stop any services your package might be running (but be sure the uninstall won't fail if the service isn't running).

At uninstall, of course you should reverse most changes you might have made to the system at install-time. But give a little thought to your actions; uninstalling an RPM package should not, for example, accidentally delete any user-created files. So you might be better off not trying to remove a user ID, or deleting entire directory trees.



Resources

  • Part 1 in this RPM series introduces the process for building software packages with RPM, and Part 2 explains how to build without root, patch software, and distribute RPM.



  • Some Linux distributions use Debian's APT tool for package management.



  • The RPM Web site has pointers to many useful resources. The RPM e-mail list is a good place to ask questions.



  • Maximum RPM is a book about using RPM. It has become quite outdated, but an effort is underway to update it.



  • The RPM HOWTO is also getting somewhat dated. It covers some of the same ground as this article.



  • Eric S. Raymond's Software Release Practice HOWTO document is not specific to RPM or Linux. But it does give many good tips on releasing software in a way that makes it easy for users to use and programmers to contribute fixes and improvements.



  • The Free Software Foundation is the source for GNU Indent and many other useful software packages -- including glibc and emacs.



  • Browse more Linux resources on developerWorks.



  • Browse more Open source resources on developerWorks.


About the author

Dan Poirier is an Advisory Software Engineer at IBM. He currently works in Research Triangle Park, North Carolina, on network appliances that run Linux. Contact Dan at poirier@us.ibm.com.




Rate this page


Please take a moment to complete this form to help us better serve you.



YesNoDon't know
 


 


12345
Not
useful
Extremely
useful
 


Back to top