[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

dpkg-triggers implementation status, revised spec as implemented


I have completed what I think is the implementation of the dpkg
triggers feature.  It's had some rudimentary testing and it seems to
work for me.  I'm going to upload it to Ubuntu this evening - only to
Ubuntu right now because I'm prioritising meeting the hard deadline of
Ubuntu's feature freeze, tomorrow.

I haven't had time to play with git yet - I've been too busy coding -
but as the patch (against dpkg 1.13.24ubuntu6) is 3781 lines long (net
change: 1799 more lines added than removed) I think it is essential
for me to get it into a proper revision control system.  It's an
intrusive patch and if anyone else has done anything substantial I'll
have to do some merging.

For the avoidance of any doubt, aside from any other reasons, getting
this patch into Debian's dpkg is important for Ubuntu because we want
to make this feature available so that Debian developers start using
it in their packages.  So I will be doing that real soon now.

As in any project of this kind, I found myself improving on the
specification during implementation and testing.  The revised spec is
below for your information.  (What happened to the doc/ subdirectory
in the dpkg source?)




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.)


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
 * 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
 * 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; 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 an 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.

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

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:
   postinst triggered "<trigger-name> <trigger-name> ..."

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 explictly requested.

       |  unpacked  |
  (automatic)|     ,----------.
             |     | config-  |
             |     |  failed  |
             |     `----------'
             |       |      ^
             |       |      |
             |,---<--'      |	    ,------------------------------.
             |  (user       |	    |    triggers-pending          |
    postinst |   request)   |	    |            or                |
 "configure" |              |       | t.-awaited with some pending |
             |              |       `------------------------------'
             |              |                  |    ^
             |`----->------'|       	       |    |
             |    error     |        postinst  |    |
             |              |      "triggered" |    | trigger(s)
             |              |      (automatic) |    |  activated
             |              |                  |    |
             |              `-----<-----------'|    |
             |                  error          |    |
             |                                 |    |
             V                                 V    |
       |   installed  or  t.-awaited with none pending    |

Packages in `config-failed' or worse are never considered to have
lists of pending triggers.

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 occured 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 optimise
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 can
be examined in a shell script like this:
   case " $3 " in
   *" trigger-name-a "*)  process-trigger-a ;;
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 minimise 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 policy).

File triggers

File triggers have names of the form
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 policy, so that a package which creates a relevant file in a
maintainer script can activate the trigger explictly.

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

A package declares its relationship to some trigger(s) by including a
`triggers' file in its control archive (ie, DEBIAN/triggers during
package creation).  This file contains directives, one per line.
Leading and trailing whitespace and everything after the first # on
any line will be trimmed, and empty lines will be ignored.

The trigger control directives currently supported are:

 interest <trigger-name>

    Specifies that the package is interested in the named trigger.
    All triggers in which a package is interested must be listed using
    this directive in the triggers control file.

 activate <trigger-name>

    Arranges that changes to this package's state will activate the
    specified trigger.  The trigger will be activated at the start of
    the following operations: unpack, configure, remove (including for
    the benefit of a conflicting package), purge and deconfigure.

    If this package disappears during the unpacking of another package
    the trigger will be activated when the disappearance is noted
    towards the end of the unpack.  Trigger processing, and transition
    from triggers-awaited to installed, does not cause activations.
    In the case of unpack, triggers mentioned in both the old and new
    versions of the package will be activated.

Unknown directives are an error which will prevent installation of the

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 to warn about
unrecognised trigger names syntaxes and unrecognised trigger control

New command-line interfaces to dpkg tools

dpkg will grow new options:

      Do not run any triggers in this run (activations will still be
      recorded).  If used with dpkg --configure <some package> or
      --triggers-only <some package> then the named package
      postinst will still be run even if only a triggers run is
      Cancels a previous --no-triggers.

      Processes only triggers.  All pending triggers will be
      processed.  If package names are supplied only those packages'
      triggers will be processed, exactly once each where necessary.

Use of --no-triggers or --triggers-only may leave packages in the
improper `triggers-awaited' and `triggers-pending' states.  This can
be fixed later by running:
   dpkg --configure --pending

Here is a summary of the behaviours:

 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

A trigger may be activated explicitly with:
   dpkg-trigger [--by-package <package>] <name-of-trigger>

This can be used by maintainer scripts in complex and conditional
situations where the file triggers, or the declarative `activate'
triggers control file directive, are insufficiently rich.  It can also
be used for testing and by system administrators (but note that the
triggers won't actually be run by dpkg-trigger - see `Timing...',

The --by-package option should not normally be necessary.  dpkg will
be modified to set an environment variable DPKG_MAINTSCRIPT_PACKAGE in
the environment of maintainer scripts, naming the package to which the
script belongs, and this will be used by default.

If a postinst would like to know whether the running dpkg supports
triggers, it can ask
   dpkg-trigger --check-supported
which will exit 0 if a triggers-capable dpkg has run, or 1 with an
error message to stderr if not.  Normally, however, it is better just
to activate the desired trigger with `dpkg-trigger'.  See Transition
Plan, below.

The --verbose and --query options will show which packages were
interested and what the current activation state is, on stdout in
human- and machine-readable (untranslated) format.  Without any
options there will be no output to stdout, and none to stderr unless
dpkg-trigger is unable to make a record of the trigger activation.
With --query no trigger is activated.

Unrecognised trigger name syntaxes are an error for dpkg-trigger.

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

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 <package>... or dpkg --suppress-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 --suppress-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 behaviours 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.


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 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

   and to retain this behaviour, 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
         printf "Updating GNOME help database.\n"
         scrollkeeper-update-now -q

 * 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 -e
	   if type dpkg-trigger >/dev/null 2>&1 && \
	      dpkg-trigger /usr/share/omf; then
		 exit 0
	   exec scrollkeeper-update-now "$@"
 2. In gnome-policy chapter 2, `Use of scrollkeeper',
     - delete the requirement that the package must depend on
     - 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
         an 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

 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.


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

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
 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
      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
    * 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
    * 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
 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


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.


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/Deferred 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.

/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 serialise rather than fail on


dpkg-trigger updates triggers/Deferred, and does not read or write the
status file or take out the dpkg status lock.  dpkg (and dpkg-query)
reads triggers/Deferred after reading /var/lib/dpkg/status, and after
running a maintainer script.  If the status database is opened for
writing then the data from Deferred is moved to updates as
Triggers-Pending and Triggers-Awaited entries and corresponding Status

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

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/Deferred.  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/Deferred, 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

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.)


Reply to: