diff options
Diffstat (limited to 'doc/spec/triggers.txt')
-rw-r--r-- | doc/spec/triggers.txt | 818 |
1 files changed, 818 insertions, 0 deletions
diff --git a/doc/spec/triggers.txt b/doc/spec/triggers.txt new file mode 100644 index 0000000..64f4232 --- /dev/null +++ b/doc/spec/triggers.txt @@ -0,0 +1,818 @@ +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 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.) |