818 lines
36 KiB
Text
818 lines
36 KiB
Text
Triggers
|
||
========
|
||
|
||
Status: recommendation, stable
|
||
|
||
Introduction
|
||
------------
|
||
|
||
A dpkg trigger is a facility that allows events caused by one package
|
||
but of interest to another package to be recorded and aggregated, and
|
||
processed later by the interested package. This feature simplifies
|
||
various registration and system-update tasks and reduces duplication
|
||
of processing.
|
||
|
||
(NB: Triggers are intended for events that occur during package
|
||
installation, not events that occur in general operation.)
|
||
|
||
|
||
Concepts
|
||
--------
|
||
|
||
Each trigger is named, and at any time zero or more packages may be
|
||
interested in it.
|
||
|
||
We currently envisage three 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.
|
||
* Future kinds of special triggers, which are activated by magic code
|
||
in dpkg itself. Currently none are defined besides file triggers.
|
||
|
||
A trigger is always activated by a particular package.
|
||
|
||
Trigger names contain only printing 7-bit ASCII characters (no
|
||
whitespace). Each trigger kind has a distinct subset of the trigger
|
||
name space so that the kind can be determined from the name. After we
|
||
run out of straightforward syntaxes, we will use <kind>:<details>.
|
||
|
||
When a trigger is activated, it becomes pending for every package
|
||
which is interested in the trigger at that time. Each package has a
|
||
list of zero or more pending triggers. Repeated activation of the
|
||
same trigger has no additional effect. Note that in general a trigger
|
||
will not be processed immediately when it is activated; processing is
|
||
deferred until it is convenient (as described below).
|
||
|
||
At a trigger activation, the interested packages(s) are added to the
|
||
triggering package's list of triggers-awaited packages (unless the
|
||
trigger has been configured to not require it); the triggering
|
||
package is said to await the trigger processing.
|
||
|
||
A package which has pending triggers, or which awaits triggers, is not
|
||
considered properly installed. There are two new dpkg status values,
|
||
‘triggers-pending’ and ‘triggers-awaited’, which lie between
|
||
‘config-failed’ and ‘installed’.
|
||
|
||
|
||
Details - Overview table
|
||
------------------------
|
||
|
||
Status Pending Awaited Satisfies Remedy
|
||
triggers triggers Depends
|
||
|
||
unpacked never maybe No postinst configure
|
||
c.-failed never maybe No postinst configure (when requested)
|
||
t.-awaited yes always No postinst triggered + fix awaited pkg(s)
|
||
t.-awaited no always No fix awaited package(s)
|
||
t.-pending always never Yes postinst triggered
|
||
installed never never Yes n/a
|
||
|
||
Packages in t-awaited and t-pending demand satisfaction of their
|
||
dependencies just like packages in installed.
|
||
|
||
|
||
Details - triggering package
|
||
----------------------------
|
||
|
||
When a package <T> activates a trigger in which a package <I> is
|
||
interested, <I> is added to the list of packages whose trigger
|
||
processing is awaited by <T>. Zero or more packages <I> may be added as a
|
||
result of any particular trigger activation, depending on how many
|
||
packages were interested. (If <T> chooses, explicit trigger activation
|
||
using dpkg-trigger of <I> by <T> need not make <T> become triggers-awaited
|
||
in this way.)
|
||
|
||
A package which awaits trigger processing but would otherwise be
|
||
‘installed’ or ‘triggers-pending’ is considered to be in state
|
||
‘triggers-awaited’. Packages in ‘triggers-awaited’ do not satisfy
|
||
Depends dependencies.
|
||
|
||
Every triggered package <I> in <T>'s list of awaited packages either has a
|
||
nonempty list of pending triggers, or is in ‘config-failed’ or worse.
|
||
When <I> enters ‘installed’ (or ‘config-files’ or ‘not-installed’), the
|
||
entry in <T>'s list of awaited packages is removed so that <T> may, if it
|
||
no longer awaits any packages, become ‘installed’ or ‘triggers-pending’.
|
||
|
||
Packages in ‘config-files’ or ‘not-installed’ do not await triggers.
|
||
|
||
|
||
Details - triggered package
|
||
---------------------------
|
||
|
||
When one of the triggers in which a package is interested is
|
||
activated, the triggered package has the trigger added to its list of
|
||
pending triggers. Packages with a nonempty list of pending triggers
|
||
which would otherwise be in state ‘installed’ are in state
|
||
‘triggers-pending’ instead, so if the package was previously
|
||
‘installed’ it becomes ‘triggers-pending’.
|
||
|
||
If a package has nonempty lists both of pending and awaited triggers,
|
||
then it is in ‘triggers-awaited’. Nevertheless efforts will still be
|
||
made to process its triggers so as to make the list of pending
|
||
triggers empty.
|
||
|
||
To restore a package in state ‘triggers-pending’ to ‘installed’, or to
|
||
process pending triggers of a package with both pending and awaited
|
||
triggers, dpkg will run the postinst script as:
|
||
|
||
postinst triggered "<trigger-name> <trigger-name> ..."
|
||
|
||
by passing a space-separated list of <trigger-name>s as the second argument.
|
||
|
||
This will be attempted for each relevant package at the end of each
|
||
dpkg run; so, normally, in the same dpkg run as the event which made
|
||
the package go to ‘triggers-pending’. This leaves packages in
|
||
reasonable states by default.
|
||
|
||
If the “postinst triggered” run fails the package goes to
|
||
‘config-failed’, so that the trigger processing will not be attempted
|
||
again until explicitly requested.
|
||
|
||
|
||
│
|
||
v
|
||
┌────────────┐
|
||
│ unpacked │
|
||
└─────┬──────┘
|
||
│
|
||
│
|
||
(automatic)│ ┌───────────────┐
|
||
│ │ config-failed │
|
||
│ └─────┬─────────┘
|
||
│ │ ^
|
||
│ │ │
|
||
├──────<─────┘ │ ┌──────────────────────────────────┐
|
||
│ (user request) │ │ triggers-pending │
|
||
postinst │ │ │ or │
|
||
"configure" │ │ │ triggers-awaited w/ some pending │
|
||
│ │ └────────────┬─────────────────────┘
|
||
│ │ │ ^
|
||
├────────>───────┤ postinst │ │
|
||
│ error │ "triggered" │ │
|
||
│ │ (automatic) │ │
|
||
│ │ │ │ trigger(s)
|
||
│ │ │ │ activated
|
||
│ └────────<─────────┤ │
|
||
│ error │ │
|
||
│ │ │
|
||
v v │
|
||
┌──────────────────────────────────────────────┴────┐
|
||
│ installed or triggers-awaited w/ none pending │
|
||
└───────────────────────────────────────────────────┘
|
||
|
||
Packages in ‘config-failed’ or worse are never considered to have
|
||
lists of pending triggers. A package whose postinst is being run
|
||
can however acquire pending triggers during that run (ie, a package
|
||
can trigger itself).
|
||
|
||
This means that if a triggering package <T> awaits trigger processing by
|
||
an interested package <I>, and <I> goes to ‘config-failed’ or worse (eg,
|
||
during unpack for upgrade), then when <I> is reconfigured (goes to
|
||
‘installed’) or removed, <T> will no longer await processing by <I>, so
|
||
that <T> may automatically go from ‘triggers-awaited’ to ‘installed’.
|
||
|
||
Or to put it another way, triggered actions are considered irrelevant
|
||
if the interested package <I> is not configured. When <I>'s postinst is
|
||
called with ‘configure’, it must do whatever actions are necessary to
|
||
deal with any trigger activations which might have occurred while it
|
||
was not configured, just as if the package was being configured for
|
||
the first time.
|
||
|
||
Trigger processing should be idempotent. The list of triggers being
|
||
processed is provided to the postinst only so that it can optimize
|
||
away redundant processing.
|
||
|
||
In that case, where an interested package has more than one trigger
|
||
and wants to process them differently, the list of triggers can
|
||
be examined in a shell script like this:
|
||
case " $2 " in
|
||
*" trigger-name-a "*) process-trigger-a ;;
|
||
esac
|
||
Generally each trigger name should be tested for separately, as the
|
||
postinst will often be called for several triggers at once.
|
||
|
||
Note that if a package both activates triggers in other packages, and
|
||
is interested in triggers of its own, its postinst may run for trigger
|
||
processing before the postinst(s) of the package(s) it has triggered.
|
||
|
||
|
||
Timing guarantees, races, etc.
|
||
------------------------------
|
||
|
||
Activating a trigger will not have any immediate effect, although
|
||
putative resulting status changes will show up in dpkg --status etc.
|
||
(Putative because the actual status changes may depend on the state of
|
||
trigger interests when dpkg processes the trigger activation into
|
||
the status database, rather than that when dpkg --status is run.)
|
||
|
||
A package is only guaranteed to become notified of a trigger
|
||
activation if it is continuously interested in the trigger, and never
|
||
in ‘config-failed’ or worse, during the period from when the trigger
|
||
is activated until dpkg runs the package postinst (either due to
|
||
--configure --pending, or at the end of the relevant run, as described
|
||
above). Subsequent to activation and before notification, the
|
||
interested package will not be considered in state ‘installed’, so
|
||
long as the package remains interested, and the triggering package
|
||
will not be considered ‘installed’.
|
||
|
||
If the package is not in state ‘installed’, ‘triggers-pending’ or
|
||
‘triggers-awaited’ then pending triggers are not accumulated.
|
||
However, if such a package (between ‘half-installed’ and
|
||
‘config-failed’ inclusive) declares some trigger interests then the
|
||
triggering packages *will* await their configuration (which implies
|
||
completion of any necessary trigger processing) or removal.
|
||
|
||
It is not defined in what order triggers will run. dpkg will make
|
||
some effort to minimize redundant work in the case where many packages
|
||
have postinst trigger processing activating another package's triggers
|
||
(for example, by processing triggers in fifo order during a single
|
||
dpkg run). Cycles in the triggering graph are prohibited and will
|
||
eventually, perhaps after some looping, be detected by dpkg and cause
|
||
trigger processing to fail; when this happens one of the packages
|
||
involved will be put in state ‘config-failed’ so that the trigger loop
|
||
will not be reattempted. See “Cycle detection” below.
|
||
|
||
|
||
Explicit triggers
|
||
-----------------
|
||
|
||
Explicit triggers have names with the same syntax as package names,
|
||
*but* should *not* normally be named identically to a package.
|
||
|
||
When choosing an explicit trigger name it is usually good to include a
|
||
relevant package name or some other useful identifier to help make the
|
||
trigger name unique. On the other hand, explicit triggers should
|
||
generally not be renamed just because the interested or triggering
|
||
packages' names change.
|
||
|
||
Explicit trigger names form part of the interface between packages.
|
||
Therefore in case of wider use of any trigger the name and purpose
|
||
should be discussed in the usual way and documented in the appropriate
|
||
packaging guidelines (eg, in the distribution policy).
|
||
|
||
|
||
File triggers
|
||
-------------
|
||
|
||
File triggers have names of the form
|
||
/path/to/directory/or/file
|
||
and are activated when the specified filesystem object, or any object
|
||
under the specified subdirectory, is created, updated or deleted by
|
||
dpkg during package unpack or removal. The pathname must be absolute.
|
||
|
||
File triggers should not generally be used without mutual consent.
|
||
The use of a file trigger, and the name of the trigger used, should be
|
||
stated in the distribution policy, so that a package which creates a
|
||
relevant file in a maintainer script can activate the trigger explicitly.
|
||
|
||
File triggers must definitely not be used as an escalation tool in
|
||
disagreements between different packages as to the desired contents of
|
||
the filesystem. Trigger activation due to a particular file should
|
||
not generally modify that file again.
|
||
|
||
Configuration files (whether dpkg-handled conffiles or not), or any
|
||
other files which are modified at times other than package management,
|
||
should not rely on file triggers detecting all modifications; dpkg
|
||
triggers are not a general mechanism for filesystem monitoring.
|
||
|
||
If there are or might be directory symlinks which result in packages
|
||
referring to files by different names, then to be sure of activation
|
||
all of the paths which might be included in packages should be listed.
|
||
The path specified by the interested package is matched against the
|
||
path included in the triggering package, not against the truename of
|
||
the file as installed. Only textually identical filenames (or
|
||
filenames where the interest is a directory prefix of the installed
|
||
file) are guaranteed to match.
|
||
|
||
A file trigger is guaranteed to be activated before the file in
|
||
question is modified by dpkg; on the other hand, a file trigger might
|
||
be activated even though no file was actually modified. Changes made
|
||
by dpkg to the link count of a file, or to solely the inode number
|
||
(ie, if dpkg atomically replaces it with another identical file), are
|
||
not guaranteed to cause trigger activation.
|
||
|
||
Because of the restriction on trigger names, it is not possible to
|
||
declare a file trigger for a directory whose name contains whitespace,
|
||
i18n characters, etc. Such a trigger should not be necessary.
|
||
|
||
|
||
Package declarations regarding triggers
|
||
---------------------------------------
|
||
|
||
See deb-triggers(5).
|
||
|
||
Support future extension of the trigger name syntax with additional
|
||
dpkg-generated triggers is as follows: a package which is interested
|
||
in any unsupported trigger kinds cannot be configured (since such a
|
||
package cannot be guaranteed to have these triggers properly activated
|
||
by dpkg). Therefore no package can be interested in any unsupported
|
||
trigger kinds and they can be freely activated (both by ‘activate’ and
|
||
by dpkg-trigger). dpkg-deb will be changed to warn about unrecognized
|
||
trigger names syntaxes and unrecognized trigger control directives.
|
||
|
||
|
||
New command line interfaces to dpkg tools
|
||
-----------------------------------------
|
||
|
||
See dpkg(1).
|
||
|
||
Here is a summary of the behaviors:
|
||
|
||
Command line Trigproc Trigproc Configure
|
||
these any triggered
|
||
----------------------+---------------+---------------+-----------------
|
||
--unpack no usually[1] none
|
||
--remove n/a usually[1] none
|
||
--install n/a usually[1] these
|
||
--configure -a any needed usually[1] any needed
|
||
--configure <some> if needed usually[1] must, or trigproc
|
||
--triggers-only -a any needed usually[1] none
|
||
--triggers-only <some> must usually not[1] none
|
||
|
||
[1] can be specified explicitly by --triggers or --no-triggers
|
||
|
||
|
||
See dpkg-trigger(1).
|
||
|
||
A trigger may be activated explicitly with:
|
||
dpkg-trigger [--by-package <package>] <name-of-trigger>
|
||
dpkg-trigger --no-await <name-of-trigger>
|
||
|
||
There will be no output to stdout, and none to stderr unless
|
||
dpkg-trigger is unable to make a record of the trigger activation.
|
||
|
||
NB that in the case of a file trigger the name of the trigger is
|
||
needed, not the name of a file which would match the trigger.
|
||
|
||
|
||
apt and aptitude
|
||
----------------
|
||
|
||
These must be taught about the new ‘triggers-awaited’ and
|
||
‘triggers-pending’ states. Packages in these states should be treated
|
||
roughly like those in ‘unpacked’: the remedy is to run dpkg
|
||
--configure.
|
||
|
||
Normally apt and aptitude will not see packages in ‘triggers-pending’
|
||
since dpkg will generally attempt to run the triggers thus leaving the
|
||
package in ‘config-failed’ or ‘installed’.
|
||
|
||
Note that automatic package management tools which call dpkg (like apt
|
||
and aptitude) should not attempt to configure individual packages in
|
||
state ‘triggers-pending’ (or indeed ‘triggers-awaited’) with dpkg
|
||
--triggers-only <package>... or dpkg --no-triggers --configure <package>...,
|
||
or similar approaches. This might defeat dpkg's trigger cycle detection.
|
||
|
||
A package management tool which will run dpkg --configure --pending at
|
||
the end may use --no-triggers on its other dpkg runs. This would be
|
||
more efficient as it allows more aggressive deferral (and hence more
|
||
unification) of trigger processing.
|
||
|
||
|
||
Error handling
|
||
--------------
|
||
|
||
Packages should be written so that they DO NOT BREAK just because
|
||
their pending triggers have not yet been run. It is allowed for the
|
||
functionality relating to the unprocessed trigger to fail (ie, the
|
||
package which is awaiting the trigger processing may be broken), but
|
||
the remainder of the interested package must work normally.
|
||
|
||
For example, a package which uses file triggers to register addons
|
||
must cope with (a) an addon being dropped into the filesystem but not
|
||
yet registered and (b) an addon being removed but not yet
|
||
deregistered. In both of these cases the package's main functionality
|
||
must continue to work normally; failure of the addon in question is
|
||
expected, warning messages are tolerable, but complete failure of the
|
||
whole package, or failures of other addons, are not acceptable.
|
||
|
||
dpkg cannot ensure that triggers are run in a timely enough manner for
|
||
pathological error behaviors to be tolerable.
|
||
|
||
|
||
Where a trigger script finds bad data provided by a triggering
|
||
package, it should generally report to stderr the problem with the bad
|
||
data and exit nonzero, leaving the interested package in config-failed
|
||
and the triggering package in triggers-awaited and thus signalling the
|
||
problem to the user.
|
||
|
||
Alternatively, in some situations it may be more desirable to allow
|
||
the interested package to be configured even though it can only
|
||
provide partial service. In this case clear information will have to
|
||
be given in appropriate places about the missing functionality, and a
|
||
record should be made of the cause of the errors. This option is
|
||
recommended for situations where the coupling between the interested
|
||
and triggering package is particularly loose; an example of such a
|
||
loose coupling would be Python modules.
|
||
|
||
|
||
|
||
WORKED EXAMPLE - SCROLLKEEPER
|
||
=============================
|
||
|
||
Currently, every Gnome program which comes with some help installs the
|
||
help files in /usr/share/gnome/help and then in the postinst runs
|
||
scrollkeeper-update. scrollkeeper-update reads, parses and rewrites
|
||
some large xml files in /var/lib/scrollkeeper; currently this
|
||
occurs at every relevant package installation, upgrade or removal.
|
||
|
||
When triggers are available, this will work as follows:
|
||
|
||
* gnome-foobar will ship its «omf» file in /usr/share/omf as
|
||
normal, but will not contain any special machinery to invoke
|
||
scrollkeeper.
|
||
|
||
* scrollkeeper will in its triggers control file say:
|
||
interest /usr/share/omf
|
||
and in its postinst say:
|
||
scrollkeeper-update-now -q
|
||
|
||
dpkg will arrange that this is run once at the end of each run
|
||
where any documentation was updated.
|
||
|
||
Note that it is not necessary to execute this only on particular
|
||
postinst "$1" values; however, at the time of writing, scrollkeeper
|
||
does this:
|
||
|
||
if [ "$1" = "configure" ]; then
|
||
printf "Rebuilding the database. This may take some time.\n"
|
||
scrollkeeper-rebuilddb -q
|
||
fi
|
||
|
||
and to retain this behavior, something along the following lines
|
||
would be sensible:
|
||
|
||
if [ "$1" = "configure" ]; then
|
||
printf "Rebuilding the database. This may take some time.\n"
|
||
scrollkeeper-rebuilddb -q
|
||
else
|
||
printf "Updating GNOME help database.\n"
|
||
scrollkeeper-update-now -q
|
||
fi
|
||
|
||
* dh_scrollkeeper will only adjust the DTD declarations and no longer
|
||
edit maintainer scripts.
|
||
|
||
|
||
Full implementation of the transition plan defined below, for
|
||
scrollkeeper, goes like this:
|
||
|
||
1. Update scrollkeeper:
|
||
- Add a ‘triggers’ control archive file containing
|
||
interest /usr/share/omf
|
||
- Make the postinst modifications as described above.
|
||
- Rename scrollkeeper-update to scrollkeeper-update-now
|
||
- Provide a new wrapper script as scrollkeeper-update:
|
||
#!/bin/sh
|
||
set -e
|
||
if type dpkg-trigger >/dev/null 2>&1 && \
|
||
dpkg-trigger /usr/share/omf; then
|
||
exit 0
|
||
fi
|
||
exec scrollkeeper-update-now "$@"
|
||
|
||
2. In gnome-policy chapter 2, “Use of scrollkeeper”,
|
||
- delete the requirement that the package must depend on
|
||
scrollkeeper
|
||
- delete the requirement that the package must invoke
|
||
scrollkeeper in the postinst and postrm
|
||
- instead say:
|
||
|
||
OMF files should be installed under /usr/share/omf in the
|
||
usual way. A dpkg trigger is used to arrange to update the
|
||
scrollkeeper documentation index automatically and no special
|
||
care need be taken in packages which supply OMFs.
|
||
|
||
If an OMF file is placed, modified or removed other than as
|
||
a file installed in the ordinary way by dpkg, the dpkg file
|
||
trigger «/usr/share/omf» should be activated; see the dpkg
|
||
triggers specification for details.
|
||
|
||
Existing packages which Depend on scrollkeeper (>= 3.8)
|
||
because of dh_scrollkeeper or explicit calls to
|
||
scrollkeeper-update should be modified not to Depend on
|
||
scrollkeeper.
|
||
|
||
3. Update debhelper's dh_scrollkeeper not to edit maintainer
|
||
scripts. One of dh_scrollkeeper or lintian should be changed to
|
||
issue a warning for packages with scrollkeeper (>= 3.8) in the
|
||
Depends control file line.
|
||
|
||
4. Remove the spurious dependencies on scrollkeeper, at our leisure.
|
||
As a bonus, after this is complete it will be possible to remove
|
||
scrollkeeper while keeping all of the documentation-supplying
|
||
gnome packages installed.
|
||
|
||
5. If there are any packages which do by hand what dh_scrollkeeper
|
||
does, change them not to call scrollkeeper-update and drop
|
||
their dependency on scrollkeeper.
|
||
|
||
This is not 100% in keeping with the full transition plan defined
|
||
below: if a new gnome package is used with an old scrollkeeper, there
|
||
is some possibility that the help will not properly be available.
|
||
|
||
Unfortunately, dh_scrollkeeper doesn't generate the scrollkeeper
|
||
dependency in the control file, which makes it excessively hard to get
|
||
the dependency up to date. The bad consequences of the inaccurate
|
||
dependencies are less severe than the contortions which would be
|
||
required to deal with the problem.
|
||
|
||
|
||
TRANSITION PLAN
|
||
===============
|
||
|
||
|
||
Old dpkg to new dpkg
|
||
--------------------
|
||
|
||
The first time a trigger-supporting dpkg is run on any system, it will
|
||
activate all triggers in which anyone is interested, immediately.
|
||
|
||
These trigger activations will not be processed in the same dpkg run,
|
||
to avoid unexpectedly processing triggers while attempting an
|
||
unrelated operation. dpkg --configure --pending (and not other dpkg
|
||
operations) will run the triggers, and the dpkg postinst will warn the
|
||
user about the need to run it (if this deferred triggers condition
|
||
exists). (Any triggers activated or reactivated *after* this
|
||
mass-activation will be processed in the normal way.)
|
||
|
||
To use this correctly:
|
||
* Packages which are interested in triggers, or which want to
|
||
explicitly activate triggers, should Depend on the
|
||
triggers-supporting version of dpkg.
|
||
* Update instructions and tools should arrange to run
|
||
dpkg --configure --pending
|
||
after the install; this will process the pending triggers.
|
||
|
||
dpkg's prerm will check for attempts to downgrade while triggers are
|
||
pending and refuse. (Since the new dpkg would be installed but then
|
||
refuse to read the status file.) In case this is necessary a separate
|
||
tool will be provided which will:
|
||
* Put all packages with any pending triggers into state
|
||
‘config-failed’ and remove the list of pending triggers.
|
||
* Remove the list of awaited triggers from every package. This
|
||
may cause packages to go from ‘triggers-awaited’ to ‘installed’
|
||
which is not 100% accurate but the best that can be done.
|
||
* Remove /var/lib/dpkg/triggers (to put the situation to that which
|
||
we would have seen if the trigger-supporting dpkg had never been
|
||
installed).
|
||
|
||
|
||
Higher-level programs
|
||
---------------------
|
||
|
||
The new dpkg will declare versioned Conflicts against apt and aptitude
|
||
and other critical package management tools which will be broken by
|
||
the new Status field values. Therefore, the new higher-level tools
|
||
will have to be deployed first.
|
||
|
||
The new dpkg will declare versioned Breaks against any known
|
||
noncritical package management tools which will be broken by the new
|
||
Status field value.
|
||
|
||
|
||
Transition hints for existing packages
|
||
--------------------------------------
|
||
|
||
When a central (consumer) package defines a directory where other leaf
|
||
(producer) packages may place files and/or directories, and currently
|
||
the producer packages are required to run an «update-consumer» script
|
||
in their postinst:
|
||
1. In the relevant policy, define a trigger name which is the name of
|
||
the directory where the individual files are placed by producer
|
||
packages.
|
||
2. Update the consumer package:
|
||
* Declare an interest in the trigger.
|
||
* Edit «update-consumer» so that if it is called without --real
|
||
it does the following:
|
||
if type dpkg-trigger >/dev/null 2>&1 && \
|
||
dpkg-trigger name-of-trigger; then
|
||
exit 0
|
||
fi
|
||
If this fails to cause «update-consumer» to exit, it should do
|
||
its normal update processing. Alternatively, if it is more
|
||
convenient, «update-consumer» could be renamed and supplanted with
|
||
a wrapper script which conditionally runs the real
|
||
«update-consumer».
|
||
* In the postinst, arrange for the new ‘triggered’ invocation to
|
||
run «update-consumer --real». The consumer package's postinst
|
||
will already run «update-consumer» during configuration, and this
|
||
should be retained and supplemented with the --real option (or
|
||
changed to call the real script rather than the wrapper).
|
||
3. Update the producer packages:
|
||
* In the postinst, remove the call to «update-consumer».
|
||
* Change the dependency on consumer to be versioned, specifying a
|
||
trigger-interested consumer.
|
||
This can be done at our leisure. Ideally for loosely coupled
|
||
packages this would be done only in the release after the one
|
||
containing the triggers-interested consumer, to facilitate partial
|
||
upgrades and backports.
|
||
4. After all producer packages have been updated according to step 3,
|
||
«update-consumer» has become an interface internal to the consumer
|
||
and need no longer be kept stable. If un-updated producers are
|
||
still of interest, incompatible changes to «update-consumer» imply
|
||
a versioned Breaks against the old producers.
|
||
(See also “Transition plan”, below.)
|
||
|
||
If there are several consumer packages all of which are interested in
|
||
the features provided by producer packages, the current arrangements
|
||
usually involve an additional central switchboard package (eg,
|
||
emacsen-common). In this case:
|
||
|
||
-- NOTE - this part of the transition plan is still a proof of
|
||
concept and we might yet improve on it
|
||
|
||
1. Define the trigger name.
|
||
2. Update the switchboard to have any new functionality needed by the
|
||
consumers in step 3 (2nd bullet).
|
||
3. Update the consumer packages:
|
||
* Declare an interest in the trigger.
|
||
* In the postinst, arrange for the new ‘trigger’ invocation to run
|
||
the compilation/registration process. This may involve scanning
|
||
for new or removed producers, and may involve new common
|
||
functionality from the switchboard (in which case a versioned
|
||
Depends is needed).
|
||
* The old interface allowing the switchboard to run
|
||
compilation/registration should be preserved, including
|
||
calls to the switchboard to register this consumer.
|
||
4. When all consumers have been updated, update the switchboard:
|
||
* Make the registration scripts called by producers try to
|
||
activate the trigger and if that succeeds quit without
|
||
doing any work (as for bullet 2 in the simple case above).
|
||
* Versioned Breaks, against the old (pre-step-3) consumers.
|
||
5. After the switchboard has been updated, producers can be updated:
|
||
* Remove the calls to the switchboard registration/compilation
|
||
functions.
|
||
* Change the dependency on the switchboard to a versioned one,
|
||
specifying the one which Breaks old consumers. Alternatively,
|
||
it may be the case that the switchboard is no longer needed (or
|
||
not needed for this producer), in which case the dependency on
|
||
the switchboard can be removed in favour of an appropriate
|
||
versioned Breaks (probably, identical to that in the new
|
||
switchboard).
|
||
6. After all the producers have been updated, the cruft in the
|
||
consumers can go away:
|
||
* Remove the calls to the switchboard's registration system.
|
||
* Versioned Breaks against old switchboards, or versioned Depends
|
||
on new switchboards, depending on whether the switchboard is
|
||
still needed for other common functionality.
|
||
7. After all of the producers and consumers have been updated, the
|
||
cruft in the switchboard can go away:
|
||
* Remove the switchboard's registration system (but not obviously
|
||
the common functionality from step 3, discussed above).
|
||
* Versioned Breaks against pre-step-6 consumers and pre-step-5
|
||
producers.
|
||
|
||
|
||
DISCUSSION
|
||
==========
|
||
|
||
The activation of a trigger does not record details of the activating
|
||
event. For example, file triggers do not inform the package of the
|
||
filename. In the future this might be added as an additional feature,
|
||
but there are some problems with this.
|
||
|
||
|
||
Broken producer packages, and error reporting
|
||
---------------------------------------------
|
||
|
||
Often trigger processing will involve a central package registering,
|
||
compiling or generally parsing some data provided by a leaf package.
|
||
|
||
If the central package finds problems with the leaf package data it is
|
||
usually more correct for only the individual leaf package to be
|
||
recorded as not properly installed. There is not currently any way to
|
||
do this and there are no plans to provide one.
|
||
|
||
The naive approach of giving the postinst a list of the triggering
|
||
packages does not work because this information is not recorded in the
|
||
right way (it might suffer from lacunae); enhancing the bookkeeping
|
||
for this to work would be possible but it is far better simply to make
|
||
the system more idempotent. See above for the recommended approach.
|
||
|
||
|
||
|
||
|
||
INTERNALS
|
||
=========
|
||
|
||
On-disk state
|
||
-------------
|
||
|
||
A single file /var/lib/dpkg/triggers/File lists all of the filename
|
||
trigger interests in the form
|
||
/path/to/directory/or/file package
|
||
|
||
For each explicit trigger in which any package is interested,
|
||
a file /var/lib/dpkg/triggers/<name-of-trigger> is a list of
|
||
the interested packages, one per line.
|
||
|
||
These interest files are not updated to remove a package just because
|
||
a state change causes it not to be interested in any triggers any more
|
||
- they are updated when we remove or unpack.
|
||
|
||
For each package which has pending triggers, the status file contains
|
||
a Triggers-Pending field which contains the space-separated names of
|
||
the pending triggers. For each package which awaits triggers the
|
||
status file contains a Triggers-Awaited field which contains the
|
||
*package* names of the packages whose trigger processing is awaited.
|
||
See “Details - Overview table” above for the invariants which relate
|
||
Triggers-Pending, Triggers-Awaited, and Status.
|
||
|
||
During dpkg's execution, /var/lib/dpkg/triggers/Unincorp is a list of
|
||
the triggers which have been requested by dpkg-trigger but not yet
|
||
incorporated in the status file. Each line is a trigger name followed
|
||
by one or more triggering package names. The triggering package name
|
||
"-" is used to indicate one or more package(s) which did not need to
|
||
await the trigger.
|
||
|
||
/var/lib/dpkg/triggers/Lock is the fcntl lockfile for the trigger
|
||
system. Processes hang onto this lock only briefly: dpkg-trigger
|
||
to add new activations, or dpkg to incorporate activations (and
|
||
perhaps when it updates interests). Therefore this lock is always
|
||
acquired with F_GETLKW so as to serialize rather than fail on
|
||
contention.
|
||
|
||
|
||
Processing
|
||
----------
|
||
|
||
dpkg-trigger updates triggers/Unincorp, and does not read or write the
|
||
status file or take out the dpkg status lock. dpkg (and dpkg-query)
|
||
reads triggers/Unincorp after reading /var/lib/dpkg/status, and after
|
||
running a maintainer script. If the status database is opened for
|
||
writing then the data from Unincorp is moved to updates as
|
||
Triggers-Pending and Triggers-Awaited entries and corresponding Status
|
||
changes.
|
||
|
||
This means that dpkg is guaranteed to reincorporate pending trigger
|
||
information into the status file only 1. when a maintainer script has
|
||
finished, or 2. when dpkg starts up with a view to performing some
|
||
operation.
|
||
|
||
When a package is unpacked or removed, its triggers control file will
|
||
be parsed and /var/lib/dpkg/triggers/* updated accordingly.
|
||
|
||
Triggers are run as part of configuration. dpkg will try to first
|
||
configure all packages which do not depend on packages which are
|
||
awaiting triggers, and then run triggers one package at a time in the
|
||
hope of making useful progress. (This will involve a new ‘dependtry’
|
||
level in configure.c's algorithm.) The only constraint on the
|
||
ordering of postinsts is only the normal Depends constraint, so the
|
||
usual Depends cycle breaking will function properly. See “Cycle
|
||
detection” below regarding cycles in the “A triggers B” relation.
|
||
|
||
|
||
Processing - Transitional
|
||
-------------------------
|
||
|
||
The case where a triggers-supporting dpkg is run for the first time is
|
||
detected by the absence of /var/lib/dpkg/triggers/Unincorp. When the
|
||
triggers-supporting dpkg starts up without this it will set each
|
||
package's list of pending triggers equal to its interests (obviously
|
||
only for packages which are in ‘installed’ or ‘triggers-pending’).
|
||
This may result in a package going from ‘installed’ to
|
||
‘triggers-pending’ but it will not create the directory at this time.
|
||
Packages marked as triggers-pending in this way will not be scheduled
|
||
for trigger processing in this dpkg run.
|
||
|
||
dpkg will also at this time create /var/lib/dpkg/triggers if
|
||
necessary, triggers/File, triggers/Unincorp, and the per-trigger
|
||
package lists in /var/lib/dpkg/triggers/<trigger-name>, so that future
|
||
trigger activations will be processed properly.
|
||
|
||
Only dpkg may create /var/lib/dpkg/triggers and only when it is
|
||
holding the overall dpkg status lock.
|
||
|
||
dpkg and/or dpkg-deb will be made to reject packages containing
|
||
Triggers-Pending and Triggers-Awaited control file fields, to prevent
|
||
accidents.
|
||
|
||
|
||
Cycle detection
|
||
---------------
|
||
|
||
In addition to dependency cycles, triggers raise the possibility of
|
||
mutually triggering packages - a cycle detectable only dynamically,
|
||
which we will call a “trigger cycle”.
|
||
|
||
Trigger cycles are detected using the usual hare-and-tortoise
|
||
approach. Each time after dpkg runs a postinst for triggers, dpkg
|
||
records the set of pending triggers (ie, the set of activated <pending
|
||
package, trigger name> tuples). If the hare set is a superset of the
|
||
tortoise set, a cycle has been found.
|
||
|
||
For guaranteed termination, it would be sufficient to declare a cycle
|
||
only when the two sets are identical, but because of the requirement
|
||
to make progress we can cut this short. Formally, there is supposed
|
||
to be a complete ordering of pending trigger sets satisfying the
|
||
condition that any set of pending triggers is (strictly) greater than
|
||
all its (strict) subsets. Trigger processing is supposed to
|
||
monotonically decrease the set in this ordering. (The set elements
|
||
are <package, trigger name> tuples.)
|
||
|
||
(See “Processing” above for discussion of dependency cycles.)
|