No subject

Wed Jan 10 20:24:04 GMT 2007


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.

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 whose status is `triggered' or `triggers-failed'
    into state `config-failed'.
  * 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 value.  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.
    * Declare a versioned dependency on a triggers-supporting dpkg.
    * In the postinst, arrange for the new `trigger' invocation to run
      update-consumer.  (The consumer package's postinst will alread
      run update-consumer during configuration, and this should be
 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.
 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:
 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:
    * Versioned dependency on triggers-supporting dpkg.
    * Make the registration scripts called by producers be no-ops.
    * 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.

Error handling and the broken-package and broken-file triggers

Often trigger processing by 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 the leaf package to be recorded as not
properly installed, than the central package.

There is not currently any way to do this.  There are no plans to
implement an answer to this problem at this stage, but we imagine
something like:

A pair of special triggers, defined by dpkg:
dpkg is interested in these, and the effect is to put the package in
question from installed to a new state `broken' (which is like
`installed' from the point of view of maintainer script processing).

This feature probably depends on the ability to record some additional
information with a trigger activation, so that when a package is
declared broken in this way it's possible to tell who declared it
broken and why.


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.

For each package which has pending triggers, the status
field contains an Pending-Triggers field which contains the
space-separated names of the pending triggers.  This field
is present iff the package is in the `triggered' or
`triggers-failed' state.  (But see below.)

During dpkg's running /var/lib/dpkg/triggers/Deferred is a list of
the trigger names which have been requested by dpkg-trigger but not
yet incorporated in the status file.

/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 update 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
Pending-Triggers entries and corresponding Status changes.

This means that the dpkg is only guaranteed to reincorporates 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.

The case where a triggers-supporting dpkg is run for the first time is
detected by the absence of /var/lib/dpkg/triggers/File.  Each time the
triggers-supporting dpkg starts up without this file, it will set each
`installed' or `triggered' package to `installed' with a list of
triggers equal to its interests - but it will not create File at this

When --configure --pending is run, indicating that the triggers system
should now be enabled, File and the per-trigger package lists in
/var/lib/dpkg/triggers/<trigger-name>, will be created from the
`triggers' control files of already-installed packages.

dpkg and/or dpkg-deb will be made to reject packages containing a
Pending-Triggers control file field, to prevent accidents.

Cycle detection

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
<package,triggername> 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.)


More information about the ubuntu-devel mailing list