PackagingAndMaintainerScripts

This page is based on the MOTU/School/MaintainerScripts session presented by CesareTirabassi on 28/07/2008.

This document describes the basics of Debian package management, and goes on to cover maintainer scripts in more depth.

What is a Debian binary package?

Debian binary packge files ( .deb files ) are simply archive files. They can be examined and extracted using the ar utility.

For example, after downloading a copy of ucf for Hardy:

wget http://archive.ubuntu.com/ubuntu/pool/main/u/ucf/ucf_3.005_all.deb

this ucf binary package file can be examined by doing

ar -t ucf_3.005_all.deb

which displays a list of the files inside the archive: debian-binary control.tar.gz data.tar.gz

  • debian-binary is simply an ASCII file containing the version of the Debian package format (now 2.0).

  • data.tar.gz is a gzipped tar archive containing the files to be installed.

  • control.tar.gz is another gzipped tar archive containing the control files

Control files will be discussed further later in this document.

For more details about ar, see its man page.

What is dpkg ?

Most people interested in packaging already know what dpkg is. dpkg is the main package management program in Debian. It operates mostly on a single package at a time, and some might disagree that it really *manages* packages.

More specificically, dpkg can be used to install, unpack, configure, remove or purge packages. It can also be used to extract or list the content of a package.

Used alone, dpkg will not attempt to satisfy dependencies, for that you have to use a more advanced tool, like apt (which uses dpkg as a backend). If you want to be a good package maintainer, it is important to understand how dpkg works when it installs, upgrades or removes packages.

Dealing with a corrupted dpkg

If somehow dpkg itself becomes corrupted and unusable, knowing what .deb files are can help. It is possible to download the dpkg .deb, extract the files from it using ar, then untar the data.tar.gz file that results starting at the root directory.

Removing and purging packages with dpkg

The command man dpkg will provide basic information. For more exhaustive information, see the Debian policy, chapter 6m which is online at http://www.debian.org/doc/debian-policy/ch-maintainerscripts.html

When dpkg is requested to remove a package, the following actions will happen:

  • first, it will run the package's prerm script. The prerm script is one of the maintainer scripts, which will be described later in detail.

  • Then, dpkg will remove all the files that were installed for that package, except its configuration files.

  • Next, it will run the postrm script, which is another maintainer script.

Note: When purging a package, the same actions are taken, and then as a last step it will also remove the configuration files.

Upgrading packages with dpkg

Another more complex example of dpkg in action is what happens during an upgrade:

  1. First of all, dpkg will extract the control files of the new package (the contents of control.tar.gz)

  2. It will then execute the prerm script of the old package.

  3. After that, dpkg will run the preinst script from the new package. Again, preinst is a maintainer script.

  4. Next, dpkg will remove the old files and unpack the new files (from data.tar.gz).

  5. It will then execute the postrm script of the old package.

  6. Then it will unpack the configuration files.
  7. And finally, dpkg will run the new postinst script.

You don't need to memorize all these steps, a man dpkg will refresh your memory when you need it.

As the script names indicate, prerm is (generally) called before a removal; postinst is (generally) called after an installation, and so on.

An Overview of Maintainer Script Usage

Many preinst scripts stop services for packages which are being upgraded.

Many postinst start or restart services once a new package has been installed or upgraded. Sometimes postinst is used to finish a configuration, asking for user input. This should preferably be done via debconf. Note: The use of debconf will not be covered here. See http://www.fifi.org/doc/debconf-doc/tutorial.html for a good tutorial.

prerm scripts are typically used to stop services for a package, before removing the package.

postrm scripts typically modify links or other files associated with the package, and/or remove files created by the package.

Adding Maintainer Scripts to a Package

When you want to add a maintainer script in your package, as for every packaging file you add it in the debian directory. For instance the prerm script for the binary <foo> will be called <foo>.prerm and so on. If there is only one binary package, you may omit the <foo>.

All these scripts will be installed in the binary package by dpkg-deb.

Another good link to see how maintainer scripts interact is http://wiki.debian.org/MaintainerScripts

Configuration Files Explained

Configuration files are all files which affect the operation of a program, or provide site- or host-specific information, or otherwise customise the behaviour of a program.

Some examples are:

  • /etc/apt/sources.list

  • /etc/X11/xorg.conf

  • /etc/network/interfaces

Policy dictates that any configuration files created or used by your package must reside in /etc . All the examples above follow that rule.

For any installed <package>, you will find a list of its configuration files in /var/lib/dpkg/info/<package>.conffiles

For instance:

  • /var/lib/dpkg/info/x11-common.conffiles

  • /var/lib/dpkg/info/udev.conffiles

Configuration files are special, from a packaging point of view, because by default they are not removed on package removal or overwritten on package upgrade.

Control Files Explained

Control files are all the files in a debian package necessary for its installation and configuration

Some examples of control files which you may see:

  • conffiles (a list of all configuration files, as noted earlier)

  • config (debconf config script)

  • templates (debconf templates)

  • md5sums (md5sums of all files to be installed)

  • shlibs (contain the mapping from shared libraries to the necessary dependency information)

  • triggers (triggers list)

  • control (binary control file)

Be careful to distinguish between the control file (singular) and all of the many control files (plural).

and then of course there are also

  • all the maintainer scripts: postinst, postrm, preinst, prerm

All these are in control.tar.gz, as described previously.

They are all unpacked into /var/lib/dpkg/info/ .

There, you will also find a <package>.list file, which will lists ALL files belonging to <package> . Strictly speaking, <package>.list is not a control file.

dpkg-deb

There is a nice utility that can be used to work with control files: dpkg-deb . See man dpkg-deb for fuller details.

Using dpkg as a frontend to dpkg-deb

Also dpkg can be used since it can also act as a front end to dpkg-deb .

With dpkg-deb you can:

  • Print a summary of the content of a package as well as its control file
  • print the content of control files
  • print control file information: this is the binary control file

Example dpkg-deb commands

Some example dpkg-deb (and also dpkg ) commands are:

  • dpkg-deb -e <package> : Extracts the control information files into a subdir of the current dir named DEBIAN

  • dpkg-deb -I <package> : Print a summary of the content as well as the control file

  • dpkg-deb -I <package> <control file> : Print content of one of the <control file>s

  • dpkg-deb -f <package> : Print control file information

Worked example dpkg-deb commands using the ucf package

Returning to the example package ucf_3.005_all.deb :

dpkg -e ucf_3.005_all.deb     # Or you can use dpkg-deb -e ucf_3.005_all.deb

should extract the package, and create a DEBIAN subdirectory.

In this case, it contains conffiles  control  md5sums  postinst  postrm  preinst  templates

This DEBIAN directory is simply the unpacked contents of control.tar.gz again.

Examining the content of the control file, DEBIAN/control , it includes a slightly mixed bag of things from debian/control and more. Incidentally, its content is displayed by the command aptitude show <[ackage> .

More info about the control file can be found in the deb-control man page (part of the dpkg-dev package.

As a second specific example dpkg-deb command:

dpkg-deb -I ucf_3.005_all.deb

outputs the control file and a summary of all the control files, with some info about them, like size, number of lines, and if it is an executable what kind of executable it is.

As a third and final such example, we can see a list of the ucf configuration files are using

dpkg-deb -I ucf_3.005_all.deb conffiles

Note that configuration files are not always necessary. As you can see for ucf there is only one.

Maintainer Scripts In Depth

OK, we are finally coming to the key point of this document.

As we have seen, maintainer scripts are part of a package which the package management system will run for you when your package is installed, upgraded or removed. They are normally shell (/bin/sh) scripts but they can also be other shebang scripts. It is normally preferable to use /bin/sh, otherwise you have to depends on bash/perl/python,etc. Also, POSIX shell or Bash are preferred to Perl/Python etc. since they enable debhelper to easily add bits to the scripts.

Note that in Ubuntu (and now also Debian) /bin/sh points to dash, so be careful, if you use bash-specfic coding in a /bin/sh shell script, it will fail.

A first example maintainer script: postrm from ucf

We can look at the postrm script for ucf by executing:

dpkg-deb -I ucf_3.005_all.deb postrm

This looks like a fairly complex script. Some of the reasons why are:

  • Policy requires that maintainer scripts must be idempotent -- this means that you need to make sure nothing bad will happen if the script is called twice where it would usually be called once.
  • Standard input and output may be redirected (e.g. into pipes) for logging purposes, so don't rely on them being a tty.
  • All prompting or interactive configuration should be kept to a minimum. When it is necessary, you should use the debconf package for the interface. Remember that prompting in any case can only be in the configure stage of the postinst script.

Keep the maintainer scripts as simple as possible, and don't assume that $PATH will allow you to use all commands (so use absolute and complete paths).

Error handling in maintainer scripts

Note the set -e at the top: this will make the script abort if any command returns an error value. This is very important, because we don't want maintainer scripts to continue if there is any error, and possibly cripple the user's machine. However, it is also important that we don't uninstalled or unconfigured stuff around which may cripple the user's package management system!

As we will see, there are ways to recover errors, but the most important way is to CHECK beforehand for things that can go wrong and abort GRACEFULLY if possible. You can use the usual test constructs, for example:

if [ <test> ]; then
print >&2 "Error. Please fix me."
exit 0;
fi

Note the use of exit 0, which allows dpkg to finish its job, and at the same time we alert the user that something "strange" happened.

The postinst script from another example package: trousers

wget http://archive.ubuntu.com/ubuntu/pool/universe/t/trousers/trousers_0.3.1-4_amd64.deb

Now lets check the postinst script

Now, lets for a moment forget the first part of the script, we will see later what all these actions are

In the second part, after the comment # Automatically added by dh_installinit you will see that it will call another script, which, in effect IS a maintainer script even though it will not be part of the control files

In this particular case, its a configuration file, which makes it tricky since it might be difficult to remove it if it fails

This is an init script, ie. a script used to launch a daemon at boot-up or stop it at shutdown

It is standard practice to use this script as well to launch or stop the daemon during package upgrade/removal

Now, lets check this init script. How could we do it?

[16:05] <tarvid> dpkg-deb -I trousers_0.3.1-4_amd64.deb postinst|less tarvid: that won't show the init script though, remember, its not a control file

[16:06] <Xk2c> norsetto: $ dpkg -x trousers_0.3.1-4_amd64.deb

And you will find it as etc/init.d/trousers [16:07] <Xk2c> vim etc/init.d/trousers

As you can see this script does something very nice, before starting the daemon it checks if something which is necessary for the daemon to be successfull is there

If it isn't, it doesn't bail off, and hell breaks loose, it will print an error message and exit gracefully. So, the user is informed and his system is not left in a broken state.

<Dabian> It checks if the binary exists and is excuteable by the current user? Dabian: yes, but the check I mean is about about the existance of the dev file, which means the tpm module is loaded into memory <Xk2c> Dabian: lines 26 to 31

Look also at the stop action

The daemon is stopped with the --oknodo option, this makes it return exit status 0 instead of 1 in case of errors

Could we improve on that? For instance, catch obvious problems before they happen?

  • <Xk2c> norsetto: if the daemon ist running is checked already? <Xk2c> with the "--pidfile /var/run/$NAME.pid " ?

yes, but what again if /dev/tpm doesn't exist? Even better (and the package has later been patched like this) would be to have the same check that we have for the start action

Back to the postrm script in ucf

As you can see there are a number of actions in there. All the possible actions are:

  • configure, abort-deconfigure, deconfigure-in-favour, install, abort-install, upgrade, abort-upgrade, failed-upgrade, remove, abort-remove, remove in-favour, purge, disappear

These are all coming from dpkg.

Some of these actions are pretty obscure, and it is likely that you will never need to use them.

As you can see, the maintainer for ucf used some template (I guess coming from an old version of dh-make) where all the possible actions for a postrm script are already there, with comments which explain what they do.

You have to know that when dpkg calls the maintainer scripts, its calls are of the type: script action [package|version] Each action will depend on what dpkg is trying to do

  • Is it trying to remove the package?
  • Is it trying to purge the package?
  • Is it trying to recover from a failed remove?

What dpkg will do depends on the request from the user and the status of the system

For instance for postrm, dpkg could call it as follows: <postrm> remove: dpkg will call this after a removal <postrm> purge: dpkg will call this after a purge <old-postrm> upgrade <new-version>: dpkg will call this after removing an old package during an upgrade and an important one <new-postrm> failed-upgrade <old-version>: dpkg will call this if the postrm of the old package during upgrade failed The other cases are really very special or corner cases and we will skip them for this lecture Now, lets see an example for an upgrade There are some nice diagrams that could help you to visualise this in http://wiki.debian.org/MaintainerScripts geser already pointed these out to you

Can you see the upgrade diagram in this page?

The nominal steps are as we explained briefly above (prerm->preinst->postrm->postinst), but if something goes wrong things get hairy, for instance:

First dpkg will call prerm-from-old-package upgrade new-version

If the script runs but exits with a non-zero exit status, dpkg will attempt:

prerm-from-new-package failed-upgrade old-version

Do you see this in the diagram?

If this works, the upgrade continues. If this does not work, it will continue with: postinst-from-old-package abort-upgrade new-version If this works, then the old-version is "Installed", if not, the old version is in a "Failed-Config" state. upgrade is really a complex example, but I wanted to show you what attempts dpkg will do in case something goes wrong you can make a clever use of these in your maintainer scripts, to ensure that dpkg will eventually be able ti install, or eventually to remove faulty packages

Another example, configuration:

postinst configure most-recently-configured-version

That's it, no attempt is made to recover errors! If the configuration fails, the package is in a "Failed Config" state, and an error message is generated, so, be careful with that

You can find these and all other possible dpkg calls in the debian policy, chapter 6

Writing Maintainer Scripts

Usually one will not manually write these scripts, debhelper scripts will do that for us.

For instance if our package installs icons and we want to update the Freedesktop icon caches, you will just add a dh_icons call in debian/rules, this will create appropriate maintainer scripts that will call update-icon-caches .

If you have several debhelper scriplets, all the fragments from each will be collated in a single maintainer script.

If you need to have a mixed script, ie. a part manually written and another generated by debhelper, you can use the #DEBHELPER# token During build, the token will be replaced by whatever "functions" debhelper needs to generate

We have seen and example of this for trousers

In that case there was a fragment that was inserted in the postinst by dh_installinit . You can check it by looking at trousers.postinst in the debian dir of the trousers source package.

Testing and Debugging Maintainer Scripts

Even though you will rarely need to write maintainer scripts manually, it is VERY IMPORTANT to be able to debug failures coming from maintainer scripts. This is because these may cause packages to be left in an non-desirable state, and possibly screw up the whole package management. You will see many bugs of this kind in the LP bug tracker.

It is also important to check an init script provided by upstream before installing it. Since it will be a maintainer script we need to make sure that it doesn't fail, and if it does so, it does so gracefully. If an init script fails, it may fail one of the maintainer scripts and leave a user's system in a mess. On top of that, it is a conffile, so, not removed on upgrade.

Using dh_installinit to recover from broken maintainer scripts

We have seen this for trousers, dh_installinit is the debhelper scriplet that installs init scripts into package build directories

dh_installinit also automatically generates the postinst, postrm and prerm commands needed to set up the symlinks in /etc/rc*.d/ and to start and stop the init scripts

there is a nice option that this debhelper function provides: you can override the failure action by using the option --error-handler=function when calling dh_installinit in debian/rules In this case, the named shell function will be run if the init script fails

This function should be provided in the prerm and postinst scripts, before the #DEBHELPER# token

This will simply modify the snippets inserted by dh_installinit with something like "/etc/init.d/init_script action || function" instead of "/etc/init.d/init_script action || exit $?"

so if "init_script action" fails it will execute funtion instead of exiting with an error <> 0 and failing the maintainer script

For instance a simple --error-handler=true will just make the removal continue if the initscript fails stopping the daemon and you have a new version to install that fix that very bug! Without this, you will have to ask the user to manually modify the init script himself

Another thing to be careful is that you cannot assume in your maintainer scripts that conffiles from another package are available Furthermore, you should not assume that a conffile being present means that some functionality are available, remember that conffiles are only removed on purge

An Example Maintainer Script bug: bug 248150

I stumbled against a problem of the first kind in a recent bug (bug 248150)

Launchpad bug 248150 in fcalendar "package r-cran-fcalendar 220.10063-1 failed to install/upgrade: " [Undecided,Confirmed] https://launchpad.net/bugs/248150

there is a very nice explanation by supermaster steve langasek in the debian bug which should enligthen you (it did it for me ;-))

do you see what the problem is ?

to tell you in a (hopefull) nutshell, the postrm of the old packages was calling a binary which relied on a conffile being present problem is, that that conffile was moved from one location to another, and a symlink installed to point to the new location now, when the package is upgraded, if the new package providing the binary is not yet installed, everything goes fine however, if the new package is installed, since it will not yet be configured, the symlink will be left dangling, and that binary will fail and fail 40+ other packages with it ...

Don't be worried if you don't get it immediately, its not that simple Smile :-) that was just to show you what kind of considerations one would have to make when doing maintainer scripts and what pain they can cause to debug

A Second example Maintainer script bug: bug 250088

Launchpad bug 250088 in psad "can't remove psad package" [Undecided,Fix released] https://launchpad.net/bugs/250088

to understand this, download psad (the buggy version) and check what the prerm script does

wget http://archive.ubuntu.com/ubuntu/pool/universe/p/psad/psad_2.1-1_amd64.deb

dpkg -I psad_2.1-1_amd64.deb prerm

do you see where it fails?

[16:54] <huats> the line find /var/run/psad/ -type f -exec rm -f {} \;

yes, do you see why it fails?

[16:55] <huats> should test first the existence of /var/run/psad

indeed, lets not forget that in ubuntu /var/run is mounted as tmpfs

[16:55] <huats> since if it does not exists, find will raise an error indeed, and fail the removal, leaving the user machine in a mess (recoverable, but still a mess)

in this particular case I also believe that this will need to be done in the preinst and/or postinst (creating /var/run/psad if not existing)

Another example

bug 251696

[17:00] <ubottu> Launchpad bug 251696 in pygopherd "package pygopherd 2.0.17 failed to install/upgrade: subprocess post-installation script returned error exit status 1 (dup-of: 238755)" [Undecided,Incomplete] https://launchpad.net/bugs/251696

[17:00] <ubottu> Launchpad bug 238755 in shadow "'Account has expired' message when adding a new user" [Undecided,Confirmed] https://launchpad.net/bugs/238755

do you see whats happening with this?

17:04] <MikeMc> 'passwd -l' is broken? there are two problems actually, but one is not lethal (even though is silly)

the issue is definetively in the chsh call

you can all see that in the script

just immediately after resuming normal error checking he is calling "chsh -s /bin/sh gopher" that will fail if your account is locked, and fail everything else

[17:09] <Xk2c_> yeah passwd -l is broken Sad :(

Smile :-) somebody call that a feature Wink ;-)

[17:10] <Xk2c_> norsetto: if used at the right place but an expired rootaccount causes headaches

MOTU/School/PackagingAndMaintainerScripts (last edited 2010-11-01 10:42:42 by klabs)