Maintainer scripts
Table of Contents
Introduction
This document is meant to clarify the interaction between maintainer scripts and dpkg, and examines the state changes for a package when a user interacts with the packaging system. The dynamic interactions between the packaging system and the package's maintainer scripts are described formally using UML diagrams1. This document does not attempt to describe what the maintainer scripts can or can not do, concentrating instead mostly one the packaging system interface. It also provides a call graph of the maintainer script.
This document is meant to be informative, not normative, at this point, and is presented here mostly since the maintainer scripts interaction section of policy is one of the more opaque segments. However, it also is trying to formally define the packaging system interface formally, and is meant to become normative at some point in the future, once it has buy in from the interested parties and has been checked for correctness.
This document was inspired by Margarita Manterola's excellent treatise on maintainer script invocations and return values2.
Package States and Flags
dpkg uses the following package states internally about all known packages
- Not-Installed state : The package is not installed on the system.
- Config-Files state : Only the configuration files of the package exist on the system3.
- Half-Installed state : The installation of the package has been started, but not completed for some reason.
- Unpacked state : The package is unpacked, but not configured.
- Half-Configured state : The package is unpacked and configuration has been started, but not yet completed for some reason.
- Triggers-Awaited state : The package awaits trigger processing by another package4.
- Triggers-Pending state : Another package has activated a trigger that this package had earlier expressed an interest in, and now some work has to be done.
- Fully Installed state : The package has been unpacked and configured correctly, and is deemed installed.
Apart from these states, dpkg may also flag a package on a system. Currently, the only flag available is:
-
reinst-required flag : A package marked reinst-required is broken and
requires reinstallation. These packages cannot be removed, unless
forced with option
--force-remove-reinstreq
.
Common Use Cases
To start with, we consider the most common use cases related to a user's interaction with the packaging system:
- Install: Starting with a package which is not installed on the system, the user asks the packaging system to install a package. If all goes well, the end result is that the package is installed on the system.
- Remove: Starting with a package that is currently installed on the system, the user asks the packaging system to remove it. If all goes well, only configuration files shall remain on the system at the end.
- Purge: Starting with the state when only configuration files remain on the system, the user asks the package system to remove even the configuration files. If all goes well, the package will be fully un-installed.
- Starting with a package installed on the system, the user asks the system to fully uninstall the package. This is not an independent use case, it is just the Remove followed by a Purge, and we do not consider it separately below.
- Reinstall: Starting with just the configuration files remaining on the system, the user asks the packaging system to install the package again (potentially newer version of the package).
- Upgrade: Starting with a version of the package installed on the system, the user asks the packaging system to install a newer version of the package. (A variation is the case where the old and the new version of the package are the same, but it follows the same process).
It is interesting to note that there are only three states a package may be in when the user initiates the action (Not-Installed state, Config-Files state, and Fully Installed state). These states are also successful terminal state, and there are only three failure terminal states (Half-Installed state, Config-Files state, and Unpacked state).
In the treatment below, we do not discuss details of failure modes, we just detail what happens when a maintainer script encounters an error. We also do not go into the details of conflicts handling during upgrades and installations, since that clutters up the diagram to the point of uselessness. In lieu of that. we present a standalone section explaining the handling of conflicting packages at the end.
Install a previously unknown package
This is the case where a package is not currently installed on the system, and the user asks the packaging system to install version V1. Installing packages is the most basic, and the most critical, feature of a packaging system. This is the bread and butter of a package management system.
Installation consists of the following steps:
- Extract the control files of the new package.
- If another version of the same package was installed before the new installation, execute prerm script of the old package.
- Run preinst script, if provided by the package.
- Unpack the new files, and at the same time back up the old files, so that if something goes wrong, they can be restored. No configuration is done at this point.
- If another version of the same package was installed before the new installation, execute the postrm script of the old package. Note that this script is executed after the preinst script of the new package, because new files are written at the same time old files are removed.
-
Configure the package. Configuring consists of the following steps
- Unpack the conffiles, and at the same time back up the old conffiles, so that they can be restored if something goes wrong.
- Run postinst script, if provided by the package.
State diagram
The state diagram reflects the simplicity of the operation: when the user asks for a package to be installed, it may either be not installed at all, end up in the Half-Installed state (with the option of being installed later at user request), face a configuration failure (Config-Files state), or end up being properly installed (Installed state). Only one of the four terminal stages is a success condition.
State transition diagram for package installation
Collaboration diagram
The collaboration diagram reveals that dpkg calls the maintainer scripts preinst and postinst during normal operations, and the postrm during error unwind. Indeed, the postinst may be called twice, as we shall see in the activity diagram below.
Collaboration diagram for package installation
Activity diagram
The green ellipses are the start states a package may be in, the blue labels represent user actions. The maintainer scripts are represented by decision boxes, since usually how the process flows depends on whether the maintainer scripts succeeds or fails. Terminal states are octagons. The colors green and red denote success and failure, respectively.
For the most part, this is a straightforward operation, were it not for the fact that dpkg has to deal with conflicting packages and their dependents, and also the whole trigger activation process in the late installation processing. Remove the conflict handling, and triggers, the activity diagram is as simple as the collaboration diagram leaves us to believe. The deconfiguration and removal of conflicting packages and their dependent packages is detailed later in this document.
Activity diagram for package installation
Remove and Purge an installed package
This is the reverse operation from above: starting with a package in the Fully Installed state, remove the package, and then, if asked explicitly, purge the configuration files as well. This too is in the ball park of a bread-and-butter operation for a packaging system.
If purge is not called, it may avoid having to reconfigure the package if it is reinstalled later. purge removes everything, including conffiles 5. Removing of a package consists of the following steps:
- Run prerm script
- Remove the installed files
- Run postrm script
State diagram
This is a two stage operation. Staring from an Fully Installed state, remove moves the package to the Config-Files state, and from there purge moves it to the Not-Installed state. So, for these two operations, we have two success terminal states, and four failure mode states.
State transition diagram for package removal and purge
Collaboration diagram
In the non failure mode, this is a simple operation. dpkg calls the prerm, postrm, and purge. The postinst scripts are only called during error-unwind, usually the postinst of the package being removed is called, unless prerm failed because of a conflicting package, in which case the postinst of the conflicting package is called.
Collaboration diagram for package removal
Activity Diagram
The only complication in this process happens if the prerm fails, because the actions taken differ based on whether the prerm failed due to a conflicting package. Interestingly, this activity diagram involves all three states in which user actions take place (Fully Installed state, Config-Files state, and Not-Installed state).
Activity diagram for package removal and purge
Reinstall a package
This is the case where configuration files from a previous installation still exist on the system, and the user asks the packaging system to install a (potentially different) version of the package. To most users, this case is mostly indistinguishable from a straight install, and mostly looks the same on analysis.
State Diagram
This is mostly identical to the case of installation, though failure of the preinst script, the terminal failure state is the Config-Files state, instead of Not-Installed state 6.
State transition diagram for package reinstallation
Collaboration Diagram
The collaboration diagram is identical to that of a straight installation. Indeed, apart from the early stages the old configurations files exist (until the new package is unpacked), the interactions between the packaging system and the maintainer scripts is unchanged.
Collaboration diagram for package reinstallation
As with the collaboration diagram, the activity diagram is indistinguishable from that of a straight install,
Activity Diagram
Activity diagram for package reinstallation
Upgrade a package to a new version
This is, perhaps unsurprisingly, the most complex operation of the ones we have so far considered. The sequence of activity in the trigger handling sequence is affected by external events (trigger activation and processing), though a package postinst script may activate a new trigger, and participates in handling any pending action requests in other packages it has expressed an interest in.
State Diagram
One of ways the complexity of the operation is expressed is that the number of failure mode terminal states is five, with only a single success terminal state. This is the only state transition that may end with a package version being abandoned in the Unpacked state.
State transition diagram for package upgrades
Collaboration diagram
In the normal operation mode, only the old prerm, the new preinst, the old postrm, and the new postinst are called, as can be logically expected. However, the various failure modes and error unwind procedures add a further five maintainer scripts into the mix; this operation, thus, may involve no less than nine different maintainer scripts, some called more than once.
Collaboration diagram for package upgrade
Activity Diagram
This is the most complex activity diagram we have seen so far, and the flurry of activity related to triggers around the post-installation stage adds to the complexity of the process. The iterative and asynchronous nature of the whole triggering process makes depiction of the process in a UML activity diagram cumbersome. Had we expanded the deconfiguration and removal of conflicting packages and their dependents into this activity diagram it would have been incomprehensible.
Activity diagram for package upgrades
Handling Conflicts
Here we present a short explanation of what happens when a package is being installed or upgraded, and how conflicting packages and their dependencies are handled. This happens in two stages: first, we deconfigure any conflicting packages, or packages impacted by breaks.
Deconfigure packages
Before the package that is going to be installed or upgraded has its files unpacked, we deconfigure any packages that are in conflict (since conflicting packages can not have files on disk at the same time. We also deconfigure all packges that depend on the conflicting packages, and mark all these for removal.
State Diagram
This state diagram refers to the packages that conflict with the package which is being installed, and the packages that depend on the conflicting packages. It is a relatively simple transition; normally, the package is deconfigured, and if that fails, the package and/or one of its dependent packages may end up in the Half-Installed state.
State diagram for package deconfiguration
Collaboration Diagram
During deconfiguration of a package, normally only the prerm is called, and if it fails, the postinst is called to unwind the error. Since all conflicting packages, as well as their dependents, are deconfigured, the collaboration diagram becomes clear.
Collaboration diagram for package deconfiguration
Activity Diagram
The activity diagram is essentially two identical sections, the top half details deconfiguring conflicting packages, and the bottom half deals with any packages that depend on the conflicting packages. It looks more complicated than it is because of the loops. All this happens before the package being installed is unpacked.
Also, while deconfiguring packages, the deconfigured package is marked as needing to be removed once the unpack phase finishes without error.
Activity diagram for package deconfiguration
Remove Conflicting Packages
After the package to be installed has been unpacked, we remove any packages we have previously deconfigured and marked for removal. The operation is identical to the simple removal (no purge activity) detailed above, and the diagrams are the same, and are not being reproduced here. The only interesting thing here is that we loop over all the packages marked as deconfigured, and remove each in turn.
A note on dpkg triggers
The dpkg trigger facility allows events caused by the installation of one package (the triggering package) but of interest to another package be recorded and aggregated, and processed later (after all packages being installed in the current run have had a chance to trigger events, either explicitly or implicitly) by the interested packages. This feature simplifies various registration and system-update tasks and reduces duplication of processing.
At this point, there are only two kinds of triggers:
- Explicit Triggers: These can be activated by any program by running dpkg-trigger at any time, but ideally from a maintainer script.
- File Triggers: These are activated automatically by dpkg when a matching file is installed, upgraded or removed as part of a package. They may also be explicitly activated by running dpkg-trigger.
All triggers are named, and the names may consist only of printing 7-bit ascii characters.
Packages may express an interest in a trigger; this means that when any event activates the trigger, the package would like to perform some action7.
All trigger activations are associated with the package that caused the activation; an imply that the package installation will not be complete until all the interested and correctly installed packages have performed whatever action they intended to perform when they expressed an interest in the trigger. When a package is waiting for other packages to perform whatever actions they wanted to perform, the state it is in is called Triggers-Awaited state (Waiting-For-Triggered-Actions, really). The list of actions pending is whittled down as the interested packages perform the work, or fail (the interested packages leave the Triggers-Pending state state at that point, and no longer block the installation of this package).
When a trigger is activated, all the packages that expressed an interest in performing some work enter a Triggers-Pending state (should be called Need-to-do-some-work state). At some point, the package's postinst will be called with the list of events for which action is pending in order to let the package do the work.
Conclusion
In conclusion, here is the full activity diagram encompassing the above use cases. While this is certainly complex, it is still fairly easy to see how the use cases flow from the initial states (the green ellipses), through the decision points (the blue diamonds of the maintainer script execution), to the octagonal terminal states.
Overall Activity Diagram
Footnotes:
1 The sequence diagrams are not created since the error unwind steps are hard to describe in a single sequence diagram, and the information is already present in the activity diagrams that are present.
2 Why not just use Marga's document as a basis? Well, this document has a different slant than Marga's documents: Firstly, it tries to be more complete, as befits something that is in policy, as opposed to the crystal clarity of the document Marga created (it tries to be as clear as it can, but does not elide conflicting packages, deconfiguration, and triggers). Secondly, this aims to become a normative, formal definition of the packaging system interface at a future time. Also, this document tries to take a more holistic view of state transitions of a package in the package management system, and does not have a maintainer script centric treatment (the script centric approach is certainly useful to the people developing the scripts). Finally, this document tries to hew closer to formal UML diagrams.
3 Conffiles are configuration files that are listed in the
DEBIAN/conffiles
control file
4 Usually this means that the installation of the package has activated some triggers, either explicitly in the postinst, or implicitly by installing some file, and at least one other package has expressed an interest in doing some work when the trigger has been activated.
5 Some configuration files might be unknown to dpkg because they are created and handled separately through the maintainer scripts. In this case, dpkg won't remove them by itself, but the package's postrm script (which is called by dpkg), has to take care of their removal during purge.
6 In other words, the old configuration files are not ever removed: either they are replaced with the new package's configuration files, or they are untouched
7 Think of actions like updating the manual page indices, which need only happen once at the end of the current installation run, instead of at the end of the installation of every package that has manual pages.
Date: <2009-10-16 Fri>
HTML generated by org-mode 6.31trans in emacs 23