+2008-03-30 Ian Jackson <ian@davenant.greenend.org.uk>,
+ Guillem Jover <guillem@debian.org>
+
+ * doc/triggers.txt: New file.
+ * lib/dlist.h: Likewise.
+ * lib/trigdeferred.l: Likewise.
+ * lib/triglib.c: Likewise.
+ * src/trigcmd.c: Likewise.
+ * src/trigproc.c: Likewise.
+ * configure.ac: Use AC_PROG_LEX.
+ * Makefile.am (EXTRA_DIST): Add 'doc/triggers.txt'.
+ * po/POTFILES.in: Add 'lib/trigdeferred.c', 'lib/triglib.c',
+ 'src/trigcmd.c' and 'src/trigproc.c'.
+ * dselect/helpmsgs.cc (hlp_displayexplain1): Document the new trigger
+ statuses.
+ * dselect/pkgdepcon.cc (packagelist::useavailable): Treat the trigger
+ statuses in the same way as installed status.
+ (packagelist::deppossatisfied): Likewise.
+ * dselect/pkgdisplay.cc (statusstrings): Add trigger statuses
+ descriptions.
+ (statuschars): Likewise.
+ * dselect/pkglist.cc (packagelist::ensurestatsortinfo): Mark trigger
+ status as broken.
+ * lib/.gitignore: Add 'trigdeferred.c'.
+ * lib/Makefile.am (libdpkg_a_SOURCES): Add 'dlist.h', 'triglib.c' and
+ 'trigdeferred.l'.
+ * lib/database.c (blankpackage): Initialize trigaw.head, trigaw.tail,
+ othertrigaw_head and trigpend_head members.
+ * lib/dbmodify.c (triggersdir, triggersfilefile): New variables.
+ (triggersnewfilefile): Likewise.
+ (fnis): Add 'TRIGGERSDIR', 'TRIGGERSDIR "/File"' and
+ 'TIGGERSDIR "/File.new"' entries.
+ (modstatdb_init): Incorporate triggers.
+ (modstatdb_note): Reset trigpend_head if status is not a trigger one.
+ Remove awaited triggers if status is <= than configfiles. Clear
+ awaiters.
+ * lib/dpkg-db.h (struct trigpend, struct trigaw): New types.
+ (enum trigdef_updateflags, struct trigdefmeths): Likewise.
+ (struct trigfileint, struct trig_hooks): Likewise.
+ (enum pkgstatus): Add stat_triggersawaited and stat_triggerspending.
+ (struct pkginfo): Add new trigaw, othertrigaw_head and trigpend_head
+ members.
+ (triggersdir, triggersfilefile, triggersnewfilefile): New variables.
+ (trigdef, trig_new_deferred, trigh): Likewise.
+ (trigdef_update_start, trigdef_yylex, trigdef_process_done): New
+ functions prototypes.
+ (trig_note_pend_core, trig_note_pend, trig_note_aw): Likewise.
+ (trig_clear_awaiters, trig_incorporate): Likewise.
+ (trig_file_activate_byname, trig_file_activate): Likewise.
+ (trig_file_interests_ensure, trig_file_interests_save): Likewise.
+ (trig_cicb_interest_delete, trig_cicb_interest_add): Likewise.
+ (trig_cicb_statuschange_activate, trig_parse_ci): Likewise.
+ (illegal_triggername): Likewise.
+ (trig_parse_cicb): New function typedef.
+ (TRIGHOOKS_DEFINE_NAMENODE_ACCESSORS): New macro.
+ * lib/dpkg.h (TRIGGERSCIFILE, TRIGGERSDIR, TRIGGERSFILEFILE)
+ (TRIGGERSDEFERREDFILE, TRIGGERSLOCKFILE, MAINTSCRIPTPKGENVVAR)
+ (MAINTSCRIPTDPKGENVVAR, MAXTRIGDIRECTIVE): New macros.
+ (MAXUPDATES): Set to 250.
+ * lib/dump.c (w_configversion): Do not write the field if on trigger
+ statuses either.
+ (w_status): Abort on unknown status. Assert the presence or not
+ of awaited and pending triggers depending on the status.
+ (w_trigpend, w_trigaw): New functions.
+ * lib/fields.c (scan_word, f_trigpend, f_trigaw): Likewise.
+ * lib/parse.c: Include <assert.h>.
+ (fieldinfos): Add 'Triggers-Pending' and 'Triggers-Awaited' fields.
+ (parsedb): Add new variable aw. Check for consistency between the
+ triggers lists and the status. Initialize trigpend_head and trigaw.
+ * lib/parsedump.h (f_trigpend, f_trigaw, w_trigpend, w_trigaw): New
+ function prototypes.
+ * lib/parsehelp.c (statusinfos): Add 'triggers-awaited' and
+ 'triggers-pending'.
+ * src/Makefile.am (bin_PROGRAMS): Add 'dpkg-trigger'.
+ (dpkg_SOURCES): Add 'trigproc.c'.
+ (dpkg_trigger_SOURCES, dpkg_trigger_LDADD): New variables.
+ * src/archives.c (check_conflict): Treat trigger statuses the same way
+ as installed.
+ (archivefiles): Install trigger hooks. Make act_triggers perform
+ process_queue. Do a deferred triggers process run.
+ (wanttoinstall): Treat trigger statuses the same way as installed.
+ Remove duped check about package being installed on forced
+ skip-same-version.
+ * src/configure.c (deferred_configure): Activate file triggers on
+ conffile changes.
+ * src/depcon.c (depisok): Treat trigger statuses the same way as
+ installed for the dependency and for breaks or conflicts. For depends,
+ predepends, recommends and suggests treat triggerspending as installed
+ and triggersawaited as halfconfigured.
+ * src/enquiry.c (badstatinfos): Add stat_triggerspending and
+ stat_triggersawaited entries.
+ (yettobeunpacked): Treat trigger statuses as unpacked.
+ (assertversion): Treat triggerspending the same way as installed,
+ and triggersawaited as not fully installed.
+ * src/filesdb.h (enum fnnflags): Add new fnn_nonew item.
+ (struct filenamenode): Add new trig_interested member.
+ * src/filesdb.c (ensure_package_clientdata): Initialize
+ clientdata->trigprocdeferred.
+ (findnamenode): Return NULL if flags has fnn_nonew and there is no
+ match before creating a new one. Initialize trig_interested when
+ creating a newnode.
+ * src/help.c (statusstrings): Add trigger statuses descriptions.
+ (namenodetouse): Activate a file trigger before returning.
+ (post_postinst_tasks): Initialize trigpend_head to NULL. Set status
+ to stat_triggersawaited if trigaw.head otherwise to new_status.
+ Call post_postinst_tasks_core. Move call to modstatdb_note to ...
+ (post_postinst_tasks_core): ... here. New function. Incorporate
+ triggers.
+ (post_script_tasks): Incorportate triggers.
+ (do_script): Set MAINTSCRIPTPKGENVVAR and MAINTSCRIPTDPKGENVVAR
+ environment variables.
+ * src/main.h (struct perpackagestate): Add new trigprocdeferred
+ member.
+ (enum action): Add new act_triggers item.
+ (f_triggers): New variable definition.
+ (namenodetouse): Add a comment about its new side effects.
+ (post_postinst_tasks_core): Likewise. New prototype.
+ (enum debugflags): Add dbg_triggers, dbg_triggersdetail and
+ dbg_triggersstupid items.
+ (trigproc_install_hooks, trigproc_run_deferred): New protoypes.
+ (trigproc_reset_cycle, trigproc): Likewise.
+ (trig_activate_packageprocessing): Likewise.
+ * src/main.c (usage): Document '--triggers-only' and
+ '--[no-]triggers'.
+ (f_triggers): New variable declaration
+ (setdebug): Document new triggers debug flags.
+ (cmdinfos): Add triggers-only, triggers and no-triggers entries.
+ (main): Disable f_triggers when --triggers-only has been specified.
+ * src/packages.c (progress_bytrigproc): New variable.
+ (packages): Install trigger hooks. On act_configure skip the package
+ also if trigpend_head is not valid. Handle act_triggers. Do a
+ deferred triggers process run.
+ (process_queue): New action_todo variable. Set the same value for
+ istobe as on act_triggers as for act_configure. Print the same message
+ when erasing the queue entry on act_triggers as for act_configure.
+ Add packages which can be fixed by means of processing their triggers
+ to the queue and set the action to act_configure. On act_triggers
+ ohshit if trigpend_head is NULL, otherwise fall through to
+ act_install. On act_configure call trigproc if there's pending
+ triggers or call deferred_configure.
+ (deppossi_ok_found): Add a new fixbytrig argument, fix all callers.
+ Treat trigger statuses the same way as installed. Handle dependee
+ trigger statuses.
+ (dependencies_ok): Handle packages which can be fixed by means of
+ processing their triggers.
+ * src/processarc.c (process_archive): Ensure file triggers intersts.
+ Activate trigger package processsing. Parse package triggers control
+ file before reading the conffiles. Treat trigger statuses the same
+ way as installed. Activate trigger package processing for the package
+ being deconfigured and for conflictors. Parse package control file
+ to delete the package and parse it again to re-add it and then save
+ the file triggers interests. Activate trigger package processing for
+ disappearing packages.
+ * src/query.c (list1package): Add trigger statuses to the listing
+ header and to the one letter statuses.
+ * src/remove.c (checkforremoval): Treat trigger statuses in the same
+ way as installed.
+ (deferred_remove): Activate trigger package processing. Operate on
+ packages with halfconfigured or higher status.
+ (removal_bulk_remove_configfiles): Activate trigger package processing.
+
2008-03-28 Ian Jackson <ian@davenant.greenend.org.uk>
* src/packages.c (deppossi_ok_found): Refactor returning code.
README.api \
README.feature-removal-schedule \
README.translators \
+ doc/triggers.txt \
debian/archtable \
debian/changelog \
debian/compat \
# Checks for programs.
AC_PROG_CC
AC_PROG_CXX
+AC_PROG_LEX
AC_PROG_RANLIB
DPKG_PROG_PERL
dpkg test suite can be skept if desired.
* Improve log and status-fd output by printing more status change updates
and actions. Thanks to Ian Jackson.
+ * Implement triggers support. Thanks to Ian Jackson.
+ Closes: #17243, #68981, #215374, #217622, #248693, #308285
[ Raphael Hertzog ]
* Add a warning displayed by dpkg-genchanges if the current version is
Vcs-Git: git://git.debian.org/git/dpkg/dpkg.git
Standards-Version: 3.7.3
Build-Depends: debhelper (>= 4.1.81), pkg-config, po4a (>= 0.23),
- libncursesw5-dev, zlib1g-dev (>= 1:1.1.3-19.1), libbz2-dev,
+ libncursesw5-dev, zlib1g-dev (>= 1:1.1.3-19.1), libbz2-dev, flex,
libselinux1-dev (>= 1.28-4) [!hurd-i386 !kfreebsd-i386 !kfreebsd-amd64],
libtimedate-perl, libio-string-perl
Essential: yes
Pre-Depends: ${shlibs:Depends}, coreutils (>= 5.93-1)
Conflicts: sysvinit (<< 2.82-1), dpkg-iasearch (<< 0.11), dpkg-static,
- dpkg-dev (<< 1.14.6), dpkg-dev (= 1.14.13), dpkg-dev (= 1.14.14)
+ dpkg-dev (<< 1.14.6), dpkg-dev (= 1.14.13), dpkg-dev (= 1.14.14),
+ apt (<< 0.7.7), aptitude (<< 0.4.7-1)
Replaces: dpkg-doc-ja, dpkg-static, manpages-de (<= 0.4-3),
manpages-pl (<= 20051117-1)
Suggests: apt, lzma
usr/bin/dpkg-deb
usr/bin/dpkg-query
usr/bin/dpkg-split
+usr/bin/dpkg-trigger
usr/lib/dpkg/mksplit
usr/sbin
usr/share/dpkg
--- /dev/null
+TRIGGERS
+========
+
+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; 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:
+ 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.
+
+
+ |
+ V
+ ,------------.
+ | 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. 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 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 ;;
+ 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 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
+ /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 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
+package.
+
+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 unrecognised
+trigger names syntaxes and unrecognised trigger control directives.
+
+
+New command-line interfaces to dpkg tools
+-----------------------------------------
+
+dpkg will grow new options:
+
+ --no-triggers
+ 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 needed.
+ --triggers
+ Cancels a previous --no-triggers.
+
+ --triggers-only
+ 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>
+ dpkg-trigger --no-await <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...',
+above).
+
+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.
+
+The --no-await option arranges that the calling package T (if any)
+need not await the processing of this trigger; the interested
+package(s) I will not be added to T's trigger processing awaited list
+and T's status is unchanged. T may be considered installed even
+though I may not yet have processed the trigger.
+
+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
+--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 <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.
+
+
+
+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 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
+ 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 -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
+ 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
+ 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 serialise 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.)
+
+--
+
+
\
Error flag: Space - no error (but package may be in broken state - see below)\n\
`R' - serious error during installation, needs reinstallation;\n\
- Installed state: Space - not installed;\n\
- `*' - installed;\n\
- `-' - not installed but config files remain;\n\
- packages in { `U' - unpacked but not yet configured;\n\
- these states { `C' - half-configured (an error happened);\n\
- are broken { `I' - half-installed (an error happened).\n\
+ Installed state: Space - not installed;\n\
+ `*' - installed;\n\
+ `-' - not installed but config files remain;\n\
+ packages in these { `U' - unpacked but not yet configured;\n\
+ states are not { `C' - half-configured (an error happened);\n\
+ (quite) properly { `I' - half-installed (an error happened).\n\
+ installed { `W',`t' - triggers are awaited resp. pending.\n\
Old mark: what was requested for this package before presenting this list;\n\
Mark: what is requested for this package:\n\
`*': marked for installation or upgrade;\n\
if (pkg->clientdata &&
pkg->clientdata->selected == pkginfo::want_install &&
informative(pkg,&pkg->available) &&
- (pkg->status != pkginfo::stat_installed ||
+ (!(pkg->status == pkginfo::stat_installed ||
+ pkg->status == pkginfo::stat_triggersawaited ||
+ pkg->status == pkginfo::stat_triggerspending) ||
versioncompare(&pkg->available.version,&pkg->installed.version) > 0))
return 1;
else
if (useavailable(provider->up->up))
return 1;
if (fixbyupgrade && !*fixbyupgrade &&
- (provider->up->up->status != pkginfo::stat_installed ||
+ (!(provider->up->up->status == pkginfo::stat_installed ||
+ provider->up->up->status == pkginfo::stat_triggerspending ||
+ provider->up->up->status == pkginfo::stat_triggersawaited) ||
versioncompare(&provider->up->up->available.version,
&provider->up->up->installed.version) > 1))
*fixbyupgrade= provider->up->up->clientdata;
N_("half installed"),
N_("unpacked (not set up)"),
N_("failed config"),
+ N_("awaiting trigger processing"),
+ N_("triggered"),
N_("installed"),
0 },
N_("bUG"),
N_("?") };
-const char statuschars[] = " -IUC*";
+const char statuschars[] = " -IUCWt*";
const char eflagchars[]= " R?#";
const char wantchars[]= "n*=-_";
case pkginfo::stat_unpacked:
case pkginfo::stat_halfconfigured:
case pkginfo::stat_halfinstalled:
+ case pkginfo::stat_triggersawaited:
+ case pkginfo::stat_triggerspending:
table[index]->ssavail= ssa_broken;
break;
case pkginfo::stat_notinstalled:
case pkginfo::stat_unpacked:
case pkginfo::stat_halfconfigured:
case pkginfo::stat_halfinstalled:
+ case pkginfo::stat_triggersawaited:
+ case pkginfo::stat_triggerspending:
table[index]->ssstate= sss_broken;
break;
case pkginfo::stat_notinstalled:
.deps
Makefile
Makefile.in
+trigdeferred.c
dpkg-def.h \
dpkg.h \
dpkg-db.h \
+ dlist.h \
cleanup.c \
compat.c \
compression.c \
parsedump.h \
showpkg.c \
tarfn.c tarfn.h \
+ triglib.c \
+ trigdeferred.l \
utils.c \
varbuf.c \
vercmp.c
pigp->available.valid= 0;
pigp->clientdata= NULL;
pigp->color= white;
+ pigp->trigaw.head = pigp->trigaw.tail = NULL;
+ pigp->othertrigaw_head = NULL;
+ pigp->trigpend_head = NULL;
blankpackageperfile(&pigp->installed);
blankpackageperfile(&pigp->available);
}
#include <dpkg-db.h>
char *statusfile=NULL, *availablefile=NULL;
+char *triggersdir, *triggersfilefile, *triggersnewfilefile;
static enum modstatdb_rw cstatus=-1, cflags=0;
static char *importanttmpfile=NULL;
{ STATUSFILE, &statusfile },
{ AVAILFILE, &availablefile },
{ UPDATESDIR IMPORTANTTMP, &importanttmpfile },
+ { TRIGGERSDIR, &triggersdir },
+ { TRIGGERSDIR "/File", &triggersfilefile },
+ { TRIGGERSDIR "/File.new", &triggersnewfilefile},
{ NULL, NULL }
};
uvb.buf= m_malloc(uvb.size);
}
+ trig_incorporate(cstatus, admindir);
+
return cstatus;
}
free(updatefnbuf);
}
+/* Note: If anyone wants to set some triggers-pending, they must also
+ * set status appropriately, or we will undo it. That is, it is legal
+ * to call this when pkg->status and pkg->trigpend_head disagree and
+ * in that case pkg->status takes precedence and pkg->trigpend_head
+ * will be adjusted.
+ */
void modstatdb_note(struct pkginfo *pkg) {
+ struct trigaw *ta;
+
assert(cstatus >= msdbrw_write);
onerr_abort++;
+ if (pkg->status != stat_triggerspending &&
+ pkg->status != stat_triggersawaited)
+ pkg->trigpend_head = NULL;
+
+ if (pkg->status <= stat_configfiles) {
+ for (ta = pkg->trigaw.head; ta; ta = ta->sameaw.next)
+ ta->aw = NULL;
+ pkg->trigaw.head = pkg->trigaw.tail = NULL;
+ }
+
log_message("status %s %s %s", statusinfos[pkg->status].name, pkg->name,
versiondescribe(&pkg->installed.version, vdew_nonambig));
statusfd_send("status: %s: %s", pkg->name, statusinfos[pkg->status].name);
createimptmp();
+ if (!pkg->trigpend_head && pkg->othertrigaw_head) {
+ /* Automatically remove us from other packages' Triggers-Awaited.
+ * We do this last because we want to maximise our chances of
+ * successfully recording the status of the package we were
+ * pointed at by our caller, although there is some risk of
+ * leaving us in a slightly odd situation which is cleared up
+ * by the trigger handling logic in deppossi_ok_found.
+ */
+ trig_clear_awaiters(pkg);
+ }
+
onerr_abort--;
}
--- /dev/null
+/*
+ * dlist.h - macros for handling doubly linked lists
+ *
+ * Copyright (C) 1997-1999 Ian Jackson <ian@davenant.greenend.org.uk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ADNS_DLIST_H_INCLUDED
+#define ADNS_DLIST_H_INCLUDED
+
+#define LIST_INIT(list) ((list).head = (list).tail = NULL)
+#define LINK_INIT(link) ((link).next = (link).back = NULL)
+
+#define LIST_UNLINK_PART(list, node, part) \
+ do { \
+ if ((node)->part back) \
+ (node)->part back->part next = (node)->part next; \
+ else \
+ (list).head = (node)->part next; \
+ if ((node)->part next) \
+ (node)->part next->part back = (node)->part back; \
+ else \
+ (list).tail = (node)->part back; \
+ } while (0)
+
+#define LIST_LINK_TAIL_PART(list, node, part) \
+ do { \
+ (node)->part next = NULL; \
+ (node)->part back = (list).tail; \
+ if ((list).tail) \
+ (list).tail->part next = (node); \
+ else (list).head = (node); \
+ (list).tail = (node); \
+ } while (0)
+
+
+#define LIST_CHECKNODE_PART(list, node, part) \
+ do { \
+ if ((node)->next) \
+ assert((node)->part next->part back == (node)); \
+ else \
+ assert((node) == (list).tail); \
+ if ((node)->back) \
+ assert((node)->part back->part next == (node)); \
+ else \
+ assert((node) == (list).head); \
+ } while (0)
+
+#define LIST_UNLINK(list, node) LIST_UNLINK_PART(list, node,)
+#define LIST_LINK_TAIL(list, node) LIST_LINK_TAIL_PART(list, node,)
+#define LIST_CHECKNODE(list, node) LIST_CHECKNODE_PART(list, node,)
+
+#endif
struct arbitraryfield *arbs;
};
+struct trigpend {
+ /* Node indicates that parent's Triggers-Pending mentions name. */
+ /* NB that these nodes do double duty: after they're removed from
+ * a package's trigpend list, references may be preserved by the
+ * trigger cycle checker (see trigproc.c).
+ */
+ struct trigpend *next;
+ char *name;
+};
+
+struct trigaw {
+ /* Node indicates that aw's Triggers-Awaited mentions pend. */
+ struct pkginfo *aw, *pend;
+ struct trigaw *nextsamepend;
+ struct {
+ struct trigaw *next, *back;
+ } sameaw;
+};
+
struct perpackagestate; /* dselect and dpkg have different versions of this */
struct pkginfo { /* pig */
stat_halfinstalled,
stat_unpacked,
stat_halfconfigured,
+ stat_triggersawaited,
+ stat_triggerspending,
stat_installed
} status;
enum pkgpriority {
struct pkginfoperfile available;
struct perpackagestate *clientdata;
enum { white, gray, black } color; /* used during cycle detection */
+
+ struct {
+ /* ->aw == this */
+ struct trigaw *head, *tail;
+ } trigaw;
+
+ /* ->pend == this, non-NULL for us when Triggers-Pending. */
+ struct trigaw *othertrigaw_head;
+ struct trigpend *trigpend_head;
};
/*** from lock.c ***/
void modstatdb_checkpoint(void);
void modstatdb_shutdown(void);
-extern char *statusfile, *availablefile; /* initialised by modstatdb_init */
+/* Initialised by modstatdb_init. */
+extern char *statusfile, *availablefile;
+extern char *triggersdir, *triggersfilefile, *triggersnewfilefile;
const char *pkgadminfile(struct pkginfo *pkg, const char *whichfile);
+/*** from trigdeferred.l ***/
+
+enum trigdef_updateflags {
+ tduf_nolockok = 001,
+ tduf_write = 002,
+ tduf_nolock = 003,
+ /* Should not be set unless _write is. */
+ tduf_writeifempty = 010,
+ tduf_writeifenoent = 020,
+};
+
+struct trigdefmeths {
+ void (*trig_begin)(const char *trig);
+ void (*package)(const char *awname);
+ void (*trig_end)(void);
+};
+
+extern const struct trigdefmeths *trigdef;
+extern FILE *trig_new_deferred;
+
+/* Return values:
+ * -1 Lock ENOENT with O_CREAT (directory does not exist)
+ * -2 Unincorp empty, tduf_writeifempty unset
+ * -3 Unincorp ENOENT, tduf_writeifenoent unset
+ * 1 Unincorp ENOENT, tduf_writeifenoent set } caller must call
+ * 2 ok } trigdef_update_done!
+ */
+int trigdef_update_start(enum trigdef_updateflags uf, const char *admindir);
+
+int trigdef_yylex(void);
+void trigdef_process_done(void);
+
+/*** hooks for more sophisticated processing in dpkg proper ***/
+
+/* We do things like this so we can get most of the trigger tracking
+ * in dpkg-query, dselect, and so on, but avoid the transitional
+ * processing and deferred trigproc queue management other than when
+ * we're actually doing real package management work. */
+
+struct trigfileint {
+ struct pkginfo *pkg;
+ struct filenamenode *fnn;
+ struct trigfileint *samefile_next;
+ struct {
+ struct trigfileint *next, *back;
+ } inoverall;
+};
+
+struct trig_hooks {
+ /* The first two are normally NULL.
+ * If non-NULL, we're dpkg proper and we might need to invent trigger
+ * activations as the first run of a triggers-supporting dpkg.
+ */
+ void (*enqueue_deferred)(struct pkginfo *pend);
+ void (*transitional_activate)(enum modstatdb_rw cstatus);
+
+ struct filenamenode *(*namenode_find)(const char *filename, int nonew);
+ struct trigfileint **(*namenode_interested)(struct filenamenode *fnn);
+
+ /* Returns a pointer from nfmalloc. */
+ const char *(*namenode_name)(struct filenamenode *fnn);
+};
+
+extern struct trig_hooks trigh;
+
+#define TRIGHOOKS_DEFINE_NAMENODE_ACCESSORS \
+ static struct trigfileint **th_nn_interested(struct filenamenode *fnn) \
+ { return &fnn->trig_interested; } \
+ static const char *th_nn_name(struct filenamenode *fnn) \
+ { return fnn->name; }
+
+/*** from triglib.c ***/
+
+void trig_file_activate_byname(const char *trig, struct pkginfo *aw);
+void trig_file_activate(struct filenamenode *trig, struct pkginfo *aw);
+
+int trig_note_pend_core(struct pkginfo *pend, char *trig /*not copied!*/);
+int trig_note_pend(struct pkginfo *pend, char *trig /*not copied!*/);
+int trig_note_aw(struct pkginfo *pend, struct pkginfo *aw);
+void trig_clear_awaiters(struct pkginfo *notpend);
+
+void trig_file_interests_ensure(void);
+void trig_file_interests_save(void);
+
+void trig_cicb_interest_delete(const char *trig, void *user);
+void trig_cicb_interest_add(const char *trig, void *user);
+typedef void trig_parse_cicb(const char *trig, void *user);
+void trig_parse_ci(const char *file, trig_parse_cicb *interest,
+ trig_parse_cicb *activate, void *user);
+
+/* Called by process_archive. */
+void trig_cicb_statuschange_activate(const char *trig, void *user);
+
+void trig_incorporate(enum modstatdb_rw cstatus, const char *admindir);
+
+const char *illegal_triggername(const char *p);
+
/*** from database.c ***/
struct pkginfo *findpackage(const char *name);
#define PRERMFILE "prerm"
#define POSTRMFILE "postrm"
#define LISTFILE "list"
+#define TRIGGERSCIFILE "triggers"
#define STATUSFILE "status"
#define AVAILFILE "available"
#define UPDATESDIR "updates/"
#define INFODIR "info/"
#define PARTSDIR "parts/"
+#define TRIGGERSDIR "triggers/"
+#define TRIGGERSFILEFILE "File"
+#define TRIGGERSDEFERREDFILE "Unincorp"
+#define TRIGGERSLOCKFILE "Lock"
#define CONTROLDIRTMP "tmp.ci/"
#define IMPORTANTTMP "tmp.i"
#define REASSEMBLETMP "reassemble" DEBEXT
#define IMPORTANTMAXLEN 10
#define IMPORTANTFMT "%04d" /* change => also change lib/database.c:cleanup_updates */
-#define MAXUPDATES 50
+#define MAXUPDATES 250
+
+#define MAINTSCRIPTPKGENVVAR "DPKG_MAINTSCRIPT_PACKAGE"
+#define MAINTSCRIPTDPKGENVVAR "DPKG_RUNNING_VERSION"
#define LOCALLIBDIR "/usr/local/lib/dpkg"
#define METHODSDIR "methods"
#define PKGSCRIPTMAXARGS 10
#define MD5HASHLEN 32
+#define MAXTRIGDIRECTIVE 256
#define CONFFOPTCELLS /* int conffoptcells[2] {* 1= user edited *} \
[2] {* 1= distributor edited *} = */ \
enum fwriteflags flags, const struct fieldinfo *fip) {
if (pifp != &pigp->installed) return;
if (!informativeversion(&pigp->configversion)) return;
- if (pigp->status == stat_installed || pigp->status == stat_notinstalled) return;
+ if (pigp->status == stat_installed ||
+ pigp->status == stat_notinstalled ||
+ pigp->status == stat_triggerspending ||
+ pigp->status == stat_triggersawaited)
+ return;
if (flags&fw_printheader)
varbufaddstr(vb,"Config-Version: ");
varbufversion(vb,&pigp->configversion,vdew_nonambig);
if (pifp != &pigp->installed) return;
assert(pigp->want <= want_purge);
assert(pigp->eflag <= eflagv_reinstreq); /* hold and hold-reinstreq NOT allowed */
- assert(pigp->status <= stat_installed);
+
+#define PEND pigp->trigpend_head
+#define AW pigp->trigaw.head
+ switch (pigp->status) {
+ case stat_notinstalled:
+ case stat_configfiles:
+ assert(!PEND);
+ assert(!AW);
+ break;
+ case stat_halfinstalled:
+ case stat_unpacked:
+ case stat_halfconfigured:
+ assert(!PEND);
+ break;
+ case stat_triggersawaited:
+ assert(AW);
+ break;
+ case stat_triggerspending:
+ assert(PEND);
+ assert(!AW);
+ break;
+ case stat_installed:
+ assert(!PEND);
+ assert(!AW);
+ break;
+ default:
+ abort();
+ }
+#undef PEND
+#undef AW
+
if (flags&fw_printheader)
varbufaddstr(vb,"Status: ");
varbufaddstr(vb,wantinfos[pigp->want].name); varbufaddc(vb,' ');
varbufaddc(vb,'\n');
}
+void
+w_trigpend(struct varbuf *vb,
+ const struct pkginfo *pigp, const struct pkginfoperfile *pifp,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ struct trigpend *tp;
+
+ if (!pifp->valid || pifp == &pigp->available || !pigp->trigpend_head)
+ return;
+
+ assert(pigp->status >= stat_triggersawaited &&
+ pigp->status <= stat_triggerspending);
+
+ if (flags & fw_printheader)
+ varbufaddstr(vb, "Triggers-Pending:");
+ for (tp = pigp->trigpend_head; tp; tp = tp->next) {
+ varbufaddc(vb, ' ');
+ varbufaddstr(vb, tp->name);
+ }
+ if (flags & fw_printheader)
+ varbufaddc(vb, '\n');
+}
+
+void
+w_trigaw(struct varbuf *vb,
+ const struct pkginfo *pigp, const struct pkginfoperfile *pifp,
+ enum fwriteflags flags, const struct fieldinfo *fip)
+{
+ struct trigaw *ta;
+
+ if (!pifp->valid || pifp == &pigp->available || !pigp->trigaw.head)
+ return;
+
+ assert(pigp->status > stat_configfiles &&
+ pigp->status <= stat_triggersawaited);
+
+ if (flags & fw_printheader)
+ varbufaddstr(vb, "Triggers-Awaited:");
+ for (ta = pigp->trigaw.head; ta; ta = ta->sameaw.next) {
+ varbufaddc(vb, ' ');
+ varbufaddstr(vb, ta->pend->name);
+ }
+ if (flags & fw_printheader)
+ varbufaddc(vb, '\n');
+}
+
void varbufrecord(struct varbuf *vb,
const struct pkginfo *pigp, const struct pkginfoperfile *pifp) {
const struct fieldinfo *fip;
}
}
+static const char *
+scan_word(const char **valp)
+{
+ static char *buf;
+ static int avail;
+
+ int l;
+ const char *p, *start, *end;
+
+ p = *valp;
+ for (;;) {
+ if (!*p) {
+ *valp = p;
+ return NULL;
+ }
+ if (cisspace(*p)) {
+ p++;
+ continue;
+ }
+ start = p;
+ break;
+ }
+ for (;;) {
+ if (*p && !cisspace(*p)) {
+ p++;
+ continue;
+ }
+ end = p;
+ break;
+ }
+ l = end - start;
+ if (l >= avail) {
+ avail = l * 2 + 4;
+ buf = m_realloc(buf, avail);
+ }
+ memcpy(buf, start, l);
+ buf[l] = 0;
+ *valp = p;
+
+ return buf;
+}
+
+void
+f_trigpend(struct pkginfo *pend, struct pkginfoperfile *pifp,
+ enum parsedbflags flags,
+ const char *filename, int lno, FILE *warnto, int *warncount,
+ const char *value, const struct fieldinfo *fip)
+{
+ const char *word, *emsg;
+
+ if (flags & pdb_rejectstatus)
+ parseerr(NULL, filename, lno, warnto, warncount, pend, 0,
+ _("value for `triggers-pending' field not allowed in this context"));
+
+ while ((word = scan_word(&value))) {
+ emsg = illegal_triggername(word);
+ if (emsg)
+ parseerr(NULL, filename, lno, warnto, warncount, pend, 0,
+ _("illegal pending trigger name `%.255s': %s"), word, emsg);
+
+ if (!trig_note_pend_core(pend, nfstrsave(word)))
+ parseerr(NULL, filename, lno, warnto, warncount, pend, 0,
+ _("duplicate pending trigger `%.255s'"), word);
+ }
+}
+
+void
+f_trigaw(struct pkginfo *aw, struct pkginfoperfile *pifp,
+ enum parsedbflags flags,
+ const char *filename, int lno, FILE *warnto, int *warncount,
+ const char *value, const struct fieldinfo *fip)
+{
+ const char *word, *emsg;
+ struct pkginfo *pend;
+
+ if (flags & pdb_rejectstatus)
+ parseerr(NULL, filename, lno, warnto, warncount, aw, 0,
+ _("value for `triggers-awaited' field not allowed in this context"));
+
+ while ((word = scan_word(&value))) {
+ emsg = illegal_packagename(word, NULL);
+ if (emsg)
+ parseerr(NULL, filename, lno, warnto, warncount, aw, 0,
+ _("illegal package name in awaited trigger `%.255s': %s"),
+ word, emsg);
+ pend = findpackage(word);
+
+ if (!trig_note_aw(pend, aw))
+ parseerr(NULL, filename, lno, warnto, warncount, aw, 0,
+ _("duplicate awaited trigger package `%.255s'"), word);
+ }
+}
+
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
+#include <assert.h>
#include <dpkg.h>
#include <dpkg-db.h>
{ "MD5sum", f_filecharf, w_filecharf, FILEFOFF(md5sum) },
{ "MSDOS-Filename", f_filecharf, w_filecharf, FILEFOFF(msdosname) },
{ "Description", f_charfield, w_charfield, PKGIFPOFF(description) },
+ { "Triggers-Pending", f_trigpend, w_trigpend },
+ { "Triggers-Awaited", f_trigaw, w_trigaw },
/* Note that aliases are added to the nicknames table in parsehelp.c. */
{ NULL /* sentinel - tells code that list is ended */ }
};
struct pkginfo newpig, *pigp;
struct pkginfoperfile *newpifp, *pifp;
struct arbitraryfield *arp, **larpp;
+ struct trigaw *ta;
int lno;
int pdone;
int fieldencountered[NFIELDS];
}
}
+ if (newpig.trigaw.head &&
+ (newpig.status <= stat_configfiles ||
+ newpig.status >= stat_triggerspending))
+ parseerr(NULL, filename, lno, warnto, warncount, &newpig, 0,
+ _("package has status %s but triggers are awaited"),
+ statusinfos[newpig.status].name);
+ else if (newpig.status == stat_triggersawaited && !newpig.trigaw.head)
+ parseerr(NULL, filename, lno, warnto, warncount, &newpig, 0,
+ _("package has status triggers-awaited but no triggers awaited"));
+
+ if (!(newpig.status == stat_triggerspending ||
+ newpig.status == stat_triggersawaited) &&
+ newpig.trigpend_head)
+ parseerr(NULL, filename, lno, warnto, warncount, &newpig, 0,
+ _("package has status %s but triggers are pending"),
+ statusinfos[newpig.status].name);
+ else if (newpig.status == stat_triggerspending && !newpig.trigpend_head)
+ parseerr(NULL, filename, lno, warnto, warncount, &newpig, 0,
+ _("package has status triggers-pending but no triggers pending"));
+
/* There was a bug that could make a not-installed package have
* conffiles, so we check for them here and remove them (rather than
* calling it an error, which will do at some point -- fixme).
pigp->status= newpig.status;
pigp->configversion= newpig.configversion;
pigp->files= NULL;
+
+ pigp->trigpend_head = newpig.trigpend_head;
+ pigp->trigaw = newpig.trigaw;
+ for (ta = pigp->trigaw.head; ta; ta = ta->sameaw.next) {
+ assert(ta->aw == &newpig);
+ ta->aw = pigp;
+ /* ->othertrigaw_head is updated by trig_note_aw in *(findpackage())
+ * rather than in newpig */
+ }
+
} else if (!(flags & pdb_ignorefiles)) {
pigp->files= newpig.files;
}
freadfunction f_name, f_charfield, f_priority, f_section, f_status, f_filecharf;
freadfunction f_boolean, f_dependency, f_conffiles, f_version, f_revision;
freadfunction f_configversion;
+freadfunction f_trigpend, f_trigaw;
enum fwriteflags {
fw_printheader = 001 /* print field header and trailing newline */
fwritefunction w_name, w_charfield, w_priority, w_section, w_status, w_configversion;
fwritefunction w_version, w_null, w_booleandefno, w_dependency, w_conffiles;
fwritefunction w_filecharf;
+fwritefunction w_trigpend, w_trigaw;
struct fieldinfo {
const char *name;
{ "half-installed", stat_halfinstalled, 14 },
{ "unpacked", stat_unpacked, 8 },
{ "half-configured", stat_halfconfigured, 15, },
+ { "triggers-awaited", stat_triggersawaited, 16 },
+ { "triggers-pending", stat_triggerspending, 16 },
{ "installed", stat_installed, 9 },
/* These are additional entries for reading only, in any order ... */
{ "postinst-failed", stat_halfconfigured, 15 }, /* fixme: backwards compat., remove */
--- /dev/null
+/*
+ * dpkg - main program for package management
+ * trigdeferred.l - parsing of triggers/Deferred
+ *
+ * Copyright (C) 2007 Canonical Ltd
+ * written by Ian Jackson <ian@chiark.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with dpkg; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+%option prefix="trigdef_yy"
+/* Reset the name to the default value (instead of using "trigdeferred.c")
+ * so that automake (ylwrap) can find it. */
+%option outfile="lex.yy.c"
+%option noyywrap
+%option batch
+%option nodefault
+%option perf-report
+%option warn
+%option nounput
+
+%x midline
+
+%{
+
+#include <config.h>
+
+#include <sys/stat.h>
+#include <sys/fcntl.h>
+
+#include <dpkg.h>
+#include <dpkg-db.h>
+
+static struct varbuf fn, newfn;
+
+%}
+
+%%
+
+[ \t\n] /* whitespace */
+#.*\n /* comments */
+[\x21-\x7e]+ {
+ trigdef->trig_begin(trigdef_yytext);
+ BEGIN(midline);
+ }
+
+<midline>[ \t] /* whitespace */
+<midline>[-0-9a-z][-+.0-9a-z]* {
+ if (trigdef_yytext[0] == '-' && trigdef_yytext[1])
+ ohshit(_("invalid package name `%.250s' in triggers deferred "
+ "file `%.250s'"), trigdef_yytext, fn.buf);
+ trigdef->package(trigdef_yytext);
+ }
+<midline>\n|#.*\n {
+ trigdef->trig_end();
+ BEGIN(0);
+ }
+<midline><<EOF>> {
+ ohshit(_("truncated triggers deferred file `%.250s'"), fn.buf);
+ }
+
+<*>. {
+ ohshit(_("syntax error in triggers deferred file `%.250s' at "
+ "character `%s'%s"),
+ fn.buf, yytext, YY_START == midline ? " midline": "");
+ }
+
+%%
+
+/*---------- Deferred file handling ----------*/
+
+static int lock_fd = -1;
+static FILE *old_deferred;
+
+FILE *trig_new_deferred;
+const struct trigdefmeths *trigdef;
+
+static void
+constructfn(struct varbuf *vb, const char *admindir, const char *tail)
+{
+ varbufreset(vb);
+ varbufaddstr(vb, admindir);
+ varbufaddstr(vb, "/" TRIGGERSDIR);
+ varbufaddstr(vb, tail);
+ varbufaddc(vb, 0);
+}
+
+int
+trigdef_update_start(enum trigdef_updateflags uf, const char *admindir)
+{
+ struct stat stab;
+ int r;
+
+ if (uf & tduf_write) {
+ constructfn(&fn, admindir, "Lock");
+ if (lock_fd == -1) {
+ lock_fd = open(fn.buf, O_RDWR | O_CREAT | O_TRUNC, 0600);
+ if (lock_fd == -1) {
+ if (!(errno == ENOENT && (uf & tduf_nolockok)))
+ ohshite(_("unable to open/create "
+ "triggers lockfile `%.250s'"),
+ fn.buf);
+ return -1;
+ }
+ }
+
+ lock_file(&lock_fd, fn.buf, _("unable to lock triggers area"),
+ NULL);
+ } else {
+ /* Dummy for pop_cleanups. */
+ push_cleanup(NULL, 0, NULL, 0, 0);
+ }
+
+ constructfn(&fn, admindir, TRIGGERSDEFERREDFILE);
+ r = stat(fn.buf, &stab);
+ if (r) {
+ if (errno != ENOENT)
+ ohshite(_("unable to stat triggers deferred file `%.250s'"),
+ fn.buf);
+ } else if (!stab.st_size) {
+ if (!(uf & tduf_writeifempty)) {
+ pop_cleanup(ehflag_normaltidy);
+ return -2;
+ }
+ }
+
+ if (old_deferred)
+ fclose(old_deferred);
+ old_deferred = fopen(fn.buf, "r");
+ if (!old_deferred) {
+ if (errno != ENOENT)
+ ohshite(_("unable to open triggers deferred file `%.250s'"),
+ fn.buf);
+ if (!(uf & tduf_writeifenoent)) {
+ pop_cleanup(ehflag_normaltidy);
+ return -3;
+ }
+ }
+
+ if (uf & tduf_write) {
+ constructfn(&newfn, admindir, TRIGGERSDEFERREDFILE ".new");
+ if (trig_new_deferred)
+ fclose(trig_new_deferred);
+ trig_new_deferred = fopen(newfn.buf, "w");
+ if (!trig_new_deferred)
+ ohshite(_("unable to open/create new triggers deferred file `%.250s'"),
+ newfn.buf);
+ }
+
+ if (!old_deferred)
+ return 1;
+
+ trigdef_yyrestart(old_deferred);
+ BEGIN(0);
+
+ return 2;
+}
+
+void
+trigdef_process_done(void)
+{
+ int r;
+
+ if (old_deferred) {
+ if (ferror(old_deferred))
+ ohshite(_("error reading triggers deferred file `%.250s'"),
+ fn.buf);
+ fclose(old_deferred);
+ old_deferred = NULL;
+ }
+
+ if (trig_new_deferred) {
+ if (ferror(trig_new_deferred))
+ ohshite(_("unable to write new triggers deferred "
+ "file `%.250s'"), newfn.buf);
+ r = fclose(trig_new_deferred);
+ trig_new_deferred = NULL;
+ if (r)
+ ohshite(_("unable to close new triggers deferred "
+ "file `%.250s'"), newfn.buf);
+
+ if (rename(newfn.buf, fn.buf))
+ ohshite(_("unable to install new triggers deferred "
+ "file `%.250s'"), fn.buf);
+ }
+
+ /* Unlock. */
+ pop_cleanup(ehflag_normaltidy);
+}
+
--- /dev/null
+/*
+ * libdpkg - Debian packaging suite library routines
+ * triglib.c - trigger handling
+ *
+ * Copyright (C) 2007 Canonical Ltd
+ * Written by Ian Jackson <ian@chiark.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with dpkg; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <dpkg.h>
+#include <dpkg-db.h>
+#include <dlist.h>
+
+const char *
+illegal_triggername(const char *p)
+{
+ int c;
+
+ if (!*p)
+ return _("empty trigger names are not permitted");
+
+ while ((c = *p++)) {
+ if (c <= ' ' || c >= 0177)
+ return _("trigger name contains invalid character");
+ }
+
+ return NULL;
+}
+
+/*========== recording triggers ==========*/
+
+/*---------- noting trigger activation in memory ----------*/
+
+/* Called via trig_*activate* et al from:
+ * - trig_incorporate: reading of Unincorp (explicit trigger activations)
+ * - various places: processing start (`activate' in triggers ci file)
+ * - namenodetouse: file triggers during unpack / remove
+ * - deferred_configure: file triggers during config file processing
+ *
+ * Not called from trig_transitional_activation; that runs
+ * trig_note_pend directly which means that (a) awaiters are not
+ * recorded (how would we know?) and (b) we don't enqueue them for
+ * deferred processing in this run.
+ *
+ * We add the trigger to Triggers-Pending first. This makes it
+ * harder to get into the state where Triggers-Awaited for aw lists
+ * pend but Triggers-Pending for pend is empty. (See also the
+ * comment in deppossi_ok_found regarding this situation.)
+ */
+
+/* aw might be NULL, and trig is not copied! */
+static void
+trig_record_activation(struct pkginfo *pend, struct pkginfo *aw, char *trig)
+{
+ if (pend->status < stat_triggersawaited)
+ /* Not interested then. */
+ return;
+
+ if (trig_note_pend(pend, trig))
+ modstatdb_note_ifwrite(pend);
+
+ if (trigh.enqueue_deferred)
+ trigh.enqueue_deferred(pend);
+
+ if (aw && pend->status > stat_configfiles)
+ if (trig_note_aw(pend, aw)) {
+ if (aw->status > stat_triggersawaited)
+ aw->status = stat_triggersawaited;
+ modstatdb_note_ifwrite(aw);
+ }
+}
+
+/* NB that this is also called from fields.c where *pend is a temporary! */
+int
+trig_note_pend_core(struct pkginfo *pend, char *trig /*not copied!*/)
+{
+ struct trigpend *tp;
+
+ for (tp = pend->trigpend_head; tp; tp = tp->next)
+ if (!strcmp(tp->name, trig))
+ return 0;
+
+ tp = nfmalloc(sizeof(*tp));
+ tp->name = trig;
+ tp->next = pend->trigpend_head;
+ pend->trigpend_head = tp;
+
+ return 1;
+}
+
+/* Returns: 1 for done, 0 for already noted. */
+int
+trig_note_pend(struct pkginfo *pend, char *trig /*not copied!*/)
+{
+ if (!trig_note_pend_core(pend, trig))
+ return 0;
+
+ pend->status = pend->trigaw.head ? stat_triggersawaited :
+ stat_triggerspending;
+
+ return 1;
+}
+
+/* Returns: 1 for done, 0 for already noted. */
+/* NB that this is called also from fields.c where *aw is a temporary
+ * but pend is from findpackage()! */
+int
+trig_note_aw(struct pkginfo *pend, struct pkginfo *aw)
+{
+ struct trigaw *ta;
+
+ /* We search through aw's list because that's probably shorter. */
+ for (ta = aw->trigaw.head; ta; ta = ta->sameaw.next)
+ if (ta->pend == pend)
+ return 0;
+
+ ta = nfmalloc(sizeof(*ta));
+ ta->aw = aw;
+ ta->pend = pend;
+ ta->nextsamepend = pend->othertrigaw_head;
+ pend->othertrigaw_head = ta;
+ LIST_LINK_TAIL_PART(aw->trigaw, ta, sameaw.);
+
+ return 1;
+}
+
+void
+trig_clear_awaiters(struct pkginfo *notpend)
+{
+ struct trigaw *ta;
+ struct pkginfo *aw;
+
+ assert(!notpend->trigpend_head);
+
+ ta = notpend->othertrigaw_head;
+ notpend->othertrigaw_head = NULL;
+ for (; ta; ta = ta->nextsamepend) {
+ aw = ta->aw;
+ if (!aw)
+ continue;
+ LIST_UNLINK_PART(aw->trigaw, ta, sameaw.);
+ if (!aw->trigaw.head && aw->status == stat_triggersawaited) {
+ aw->status = aw->trigpend_head ? stat_triggerspending :
+ stat_installed;
+ modstatdb_note(aw);
+ }
+ }
+}
+
+/*---------- generalised handling of trigger kinds ----------*/
+
+static const struct trigkindinfo tki_explicit, tki_file, tki_unknown;
+
+struct trigkindinfo {
+ /* Only for trig_activate_start. */
+ void (*activate_start)(void);
+
+ /* Rest are for everyone: */
+ void (*activate_awaiter)(struct pkginfo *pkg /* may be NULL */);
+ void (*activate_done)(void);
+ void (*interest_change)(const char *name, struct pkginfo *pkg, int signum);
+};
+
+#define TKI_DEFINE(kindname) \
+ static const struct trigkindinfo tki_##kindname= { \
+ trk_##kindname##_activate_start, \
+ trk_##kindname##_activate_awaiter, \
+ trk_##kindname##_activate_done, \
+ trk_##kindname##_interest_change \
+ };
+
+static const struct trigkindinfo *dtki;
+
+/* As passed into activate_start. */
+static const char *trig_activating_name;
+
+static const struct trigkindinfo *
+trig_classify_byname(const char *name)
+{
+ if (name[0] == '/') {
+ const char *slash;
+
+ slash = name;
+ while (slash) {
+ if (slash[1] == 0 || slash[1] == '/')
+ goto invalid;
+
+ slash = strchr(slash + 2, '/');
+ }
+ return &tki_file;
+ }
+
+ if (!illegal_packagename(name, NULL) && !strchr(name, '_'))
+ return &tki_explicit;
+
+invalid:
+ return &tki_unknown;
+}
+
+/* Calling sequence is:
+ * trig_activate_start(triggername)
+ * dtki->activate_awaiter(awaiting_package) } zero or more times
+ * dtki->activate_awaiter(0) } in any order
+ * dtki->activate_done(0)
+ */
+static void
+trig_activate_start(const char *name)
+{
+ dtki = trig_classify_byname(name);
+ trig_activating_name = name;
+ dtki->activate_start();
+}
+
+/*---------- unknown trigger kinds ----------*/
+
+static void
+trk_unknown_activate_start(void)
+{
+}
+
+static void
+trk_unknown_activate_awaiter(struct pkginfo *aw)
+{
+}
+
+static void
+trk_unknown_activate_done(void)
+{
+}
+
+static void
+trk_unknown_interest_change(const char *trig, struct pkginfo *pkg, int signum)
+{
+ ohshit(_("invalid or unknown syntax in trigger name `%.250s'"
+ " (in trigger interests for package `%.250s'"),
+ trig, pkg->name);
+}
+
+TKI_DEFINE(unknown);
+
+/*---------- explicit triggers ----------*/
+
+static FILE *trk_explicit_f;
+static struct varbuf trk_explicit_fn;
+static char *trk_explicit_trig;
+
+static void
+trk_explicit_activate_done(void)
+{
+ if (trk_explicit_f) {
+ fclose(trk_explicit_f);
+ trk_explicit_f = NULL;
+ }
+}
+
+static void
+trk_explicit_start(const char *trig)
+{
+ trk_explicit_activate_done();
+
+ varbufreset(&trk_explicit_fn);
+ varbufaddstr(&trk_explicit_fn, triggersdir);
+ varbufaddstr(&trk_explicit_fn, trig);
+ varbufaddc(&trk_explicit_fn, 0);
+
+ trk_explicit_f = fopen(trk_explicit_fn.buf, "r");
+ if (!trk_explicit_f) {
+ if (errno != ENOENT)
+ ohshite(_("failed to open trigger interest list file `%.250s'"),
+ trk_explicit_fn.buf);
+ }
+}
+
+static int
+trk_explicit_fgets(char *buf, size_t sz)
+{
+ return fgets_checked(buf, sz, trk_explicit_f, trk_explicit_fn.buf);
+}
+
+static void
+trk_explicit_activate_start(void)
+{
+ trk_explicit_start(trig_activating_name);
+ trk_explicit_trig = nfstrsave(trig_activating_name);
+}
+
+static void
+trk_explicit_activate_awaiter(struct pkginfo *aw)
+{
+ char buf[1024];
+ const char *emsg;
+ struct pkginfo *pend;
+
+ if (!trk_explicit_f)
+ return;
+
+ if (fseek(trk_explicit_f, 0, SEEK_SET))
+ ohshite(_("failed to rewind trigger interest file `%.250s'"),
+ trk_explicit_fn.buf);
+
+ while (trk_explicit_fgets(buf, sizeof(buf)) >= 0) {
+ if ((emsg = illegal_packagename(buf, NULL)))
+ ohshit(_("trigger interest file `%.250s' syntax error; "
+ "illegal package name `%.250s': %.250s"),
+ trk_explicit_fn.buf, buf, emsg);
+ pend = findpackage(buf);
+ trig_record_activation(pend, aw, trk_explicit_trig);
+ }
+}
+
+static void
+trk_explicit_interest_change(const char *trig, struct pkginfo *pkg, int signum)
+{
+ static struct varbuf newfn;
+ char buf[1024];
+ FILE *nf;
+
+ trk_explicit_start(trig);
+ varbufreset(&newfn);
+ varbufprintf(&newfn, "%s/%s.new", triggersdir, trig);
+ varbufaddc(&newfn, 0);
+
+ nf = fopen(newfn.buf, "w");
+ if (!nf)
+ ohshite(_("unable to create new trigger interest file `%.250s'"),
+ newfn.buf);
+ push_cleanup(cu_closefile, ~ehflag_normaltidy, NULL, 0, 1, nf);
+
+ while (trk_explicit_f && trk_explicit_fgets(buf, sizeof(buf)) >= 0) {
+ if (!strcmp(buf, pkg->name))
+ continue;
+ fprintf(nf, "%s\n", buf);
+ }
+ if (signum > 0)
+ fprintf(nf, "%s\n", pkg->name);
+ if (ferror(nf) || fclose(nf))
+ ohshite(_("unable to write new trigger interest file `%.250s'"),
+ newfn.buf);
+ pop_cleanup(ehflag_normaltidy);
+
+ if (rename(newfn.buf, trk_explicit_fn.buf))
+ ohshite(_("unable to install new trigger interest file `%.250s'"),
+ trk_explicit_fn.buf);
+}
+
+TKI_DEFINE(explicit);
+
+/*---------- file triggers ----------*/
+
+static struct {
+ struct trigfileint *head, *tail;
+} filetriggers;
+
+/*
+ * Values:
+ * -1: not read,
+ * 0: not edited
+ * 1: edited
+ */
+static int filetriggers_edited = -1;
+
+/* Called by various people with signum -1 and +1 to mean remove and add
+ * and also by trig_file_interests_ensure with signum +2 meaning add
+ * but die if already present.
+ */
+static void
+trk_file_interest_change(const char *trig, struct pkginfo *pkg, int signum)
+{
+ struct filenamenode *fnn;
+ struct trigfileint **search, *tfi;
+
+ fnn = trigh.namenode_find(trig, signum <= 0);
+ if (!fnn) {
+ assert(signum < 0);
+ return;
+ }
+
+ for (search = trigh.namenode_interested(fnn);
+ (tfi = *search);
+ search = &tfi->samefile_next)
+ if (tfi->pkg == pkg)
+ goto found;
+
+ /* not found */
+ if (signum < 0)
+ return;
+
+ tfi = nfmalloc(sizeof(*tfi));
+ tfi->pkg = pkg;
+ tfi->fnn = fnn;
+ tfi->samefile_next = *trigh.namenode_interested(fnn);
+ *trigh.namenode_interested(fnn) = tfi;
+
+ LIST_LINK_TAIL_PART(filetriggers, tfi, inoverall.);
+ goto edited;
+
+found:
+ if (signum > 1)
+ ohshit(_("duplicate file trigger interest for filename `%.250s' "
+ "and package `%.250s'"), trig, pkg->name);
+ if (signum > 0)
+ return;
+
+ /* remove it: */
+ *search = tfi->samefile_next;
+ LIST_UNLINK_PART(filetriggers, tfi, inoverall.);
+edited:
+ filetriggers_edited = 1;
+}
+
+void
+trig_file_interests_save(void)
+{
+ struct trigfileint *tfi;
+ FILE *nf;
+
+ if (filetriggers_edited <= 0)
+ return;
+
+ nf = fopen(triggersnewfilefile, "w");
+ if (!nf)
+ ohshite(_("unable to create new file triggers file `%.250s'"),
+ triggersnewfilefile);
+ push_cleanup(cu_closefile, ~ehflag_normaltidy, NULL, 0, 1, nf);
+
+ for (tfi = filetriggers.head; tfi; tfi = tfi->inoverall.next)
+ fprintf(nf, "%s %s\n", trigh.namenode_name(tfi->fnn),
+ tfi->pkg->name);
+
+ if (ferror(nf) || fclose(nf))
+ ohshite(_("unable to write new file triggers file `%.250s'"),
+ triggersnewfilefile);
+ pop_cleanup(ehflag_normaltidy);
+
+ if (rename(triggersnewfilefile, triggersfilefile))
+ ohshite(_("unable to install new file triggers file as `%.250s'"),
+ triggersfilefile);
+
+ filetriggers_edited = 0;
+}
+
+void
+trig_file_interests_ensure(void)
+{
+ FILE *f;
+ char linebuf[1024], *space;
+ struct pkginfo *pkg;
+ const char *emsg;
+
+ if (filetriggers_edited >= 0)
+ return;
+
+ f = fopen(triggersfilefile, "r");
+ if (!f) {
+ if (errno == ENOENT)
+ goto ok;
+ ohshite(_("unable to read file triggers file `%.250s'"),
+ triggersfilefile);
+ }
+
+ push_cleanup(cu_closefile, 0, NULL, 0, 1, f);
+ while (fgets_checked(linebuf, sizeof(linebuf), f, triggersfilefile) >= 0) {
+ space = strchr(linebuf, ' ');
+ if (!space || linebuf[0] != '/')
+ ohshit(_("syntax error in file triggers file `%.250s'"),
+ triggersfilefile);
+ *space++ = 0;
+ if ((emsg = illegal_packagename(space, NULL)))
+ ohshit(_("file triggers record mentions illegal "
+ "package name `%.250s' (for interest in file "
+ "`%.250s'): %.250s"), space, linebuf, emsg);
+ pkg = findpackage(space);
+ trk_file_interest_change(linebuf, pkg, +2);
+ }
+ pop_cleanup(ehflag_normaltidy);
+ok:
+ filetriggers_edited = 0;
+}
+
+static struct filenamenode *filetriggers_activating;
+
+void
+trig_file_activate_byname(const char *trig, struct pkginfo *aw)
+{
+ struct filenamenode *fnn = trigh.namenode_find(trig, 1);
+
+ if (fnn)
+ trig_file_activate(fnn, aw);
+}
+
+void
+trig_file_activate(struct filenamenode *trig, struct pkginfo *aw)
+{
+ struct trigfileint *tfi;
+
+ for (tfi = *trigh.namenode_interested(trig); tfi;
+ tfi = tfi->samefile_next)
+ trig_record_activation(tfi->pkg, aw, (char*)trigh.namenode_name(trig));
+}
+
+static void
+trk_file_activate_start(void)
+{
+ filetriggers_activating = trigh.namenode_find(trig_activating_name, 1);
+}
+
+static void
+trk_file_activate_awaiter(struct pkginfo *aw)
+{
+ if (!filetriggers_activating)
+ return;
+ trig_file_activate(filetriggers_activating, aw);
+}
+
+static void
+trk_file_activate_done(void)
+{
+}
+
+TKI_DEFINE(file);
+
+/*---------- trigger control info file ----------*/
+
+static void
+trig_cicb_interest_change(const char *trig, struct pkginfo *pkg, int signum)
+{
+ const struct trigkindinfo *tki = trig_classify_byname(trig);
+
+ assert(filetriggers_edited >= 0);
+ tki->interest_change(trig, pkg, signum);
+}
+
+void
+trig_cicb_interest_delete(const char *trig, void *user)
+{
+ trig_cicb_interest_change(trig, user, -1);
+}
+
+void
+trig_cicb_interest_add(const char *trig, void *user)
+{
+ trig_cicb_interest_change(trig, user, +1);
+}
+
+void
+trig_cicb_statuschange_activate(const char *trig, void *user)
+{
+ struct pkginfo *aw = user;
+
+ trig_activate_start(trig);
+ dtki->activate_awaiter(aw);
+ dtki->activate_done();
+}
+
+static void
+parse_ci_call(const char *file, const char *cmd, trig_parse_cicb *cb,
+ const char *trig, void *user)
+{
+ const char *emsg;
+
+ emsg = illegal_triggername(trig);
+ if (emsg)
+ ohshit(_("triggers ci file `%.250s' contains illegal trigger "
+ "syntax in trigger name `%.250s': %.250s"),
+ file, trig, emsg);
+ if (cb)
+ cb(trig, user);
+}
+
+void
+trig_parse_ci(const char *file, trig_parse_cicb *interest,
+ trig_parse_cicb *activate, void *user)
+{
+ FILE *f;
+ char linebuf[MAXTRIGDIRECTIVE], *cmd, *spc, *eol;
+ int l;
+
+ f = fopen(file, "r");
+ if (!f) {
+ if (errno == ENOENT)
+ /* No file is just like an empty one. */
+ return;
+ ohshite(_("unable to open triggers ci file `%.250s'"), file);
+ }
+ push_cleanup(cu_closefile, ~0, NULL, 0, 1, f);
+
+ while ((l = fgets_checked(linebuf, sizeof(linebuf), f, file)) >= 0) {
+ for (cmd = linebuf; cisspace(*cmd); cmd++);
+ if (*cmd == '#')
+ continue;
+ for (eol = linebuf + l; eol > cmd && cisspace(eol[-1]); eol--);
+ if (eol == cmd)
+ continue;
+ *eol = 0;
+
+ for (spc = cmd; *spc && !cisspace(*spc); spc++);
+ if (!*spc)
+ ohshit(_("triggers ci file contains unknown directive syntax"));
+ *spc++ = 0;
+ while (cisspace(*spc))
+ spc++;
+ if (!strcmp(cmd, "interest")) {
+ parse_ci_call(file, cmd, interest, spc, user);
+ } else if (!strcmp(cmd, "activate")) {
+ parse_ci_call(file, cmd, activate, spc, user);
+ } else {
+ ohshit(_("triggers ci file contains unknown directive `%.250s'"),
+ cmd);
+ }
+ }
+ pop_cleanup(ehflag_normaltidy); /* fclose */
+}
+
+/*---------- Unincorp file incorporation ----------*/
+
+static void
+tdm_incorp_trig_begin(const char *trig)
+{
+ trig_activate_start(trig);
+}
+
+static void
+tdm_incorp_package(const char *awname)
+{
+ struct pkginfo *aw = strcmp(awname, "-") ? findpackage(awname) : NULL;
+
+ dtki->activate_awaiter(aw);
+}
+
+static void
+tdm_incorp_trig_end(void)
+{
+ dtki->activate_done();
+}
+
+static const struct trigdefmeths tdm_incorp = {
+ tdm_incorp_trig_begin,
+ tdm_incorp_package,
+ tdm_incorp_trig_end
+};
+
+void
+trig_incorporate(enum modstatdb_rw cstatus, const char *admindir)
+{
+ int ur;
+ enum trigdef_updateflags tduf;
+
+ trigdef = &tdm_incorp;
+ trig_file_interests_ensure();
+
+ tduf = tduf_nolockok;
+ if (cstatus >= msdbrw_write) {
+ tduf |= tduf_write;
+ if (trigh.transitional_activate)
+ tduf |= tduf_writeifenoent;
+ }
+
+ ur = trigdef_update_start(tduf, admindir);
+ if (ur == -1 && cstatus >= msdbrw_write) {
+ if (mkdir(triggersdir, 0755)) {
+ if (errno != EEXIST)
+ ohshite(_("unable to create triggers state"
+ " directory `%.250s'"), triggersdir);
+ } else if (chown(triggersdir, 0, 0)) {
+ ohshite(_("unable to set ownership of triggers state"
+ " directory `%.250s'"), triggersdir);
+ }
+ ur = trigdef_update_start(tduf, admindir);
+ }
+ switch (ur) {
+ case -2:
+ return;
+ case -1:
+ case -3:
+ if (!trigh.transitional_activate)
+ return;
+ /* Fall through. */
+ case 1:
+ trigh.transitional_activate(cstatus);
+ break;
+ case 2:
+ /* Read and incorporate triggers. */
+ trigdef_yylex();
+ break;
+ default:
+ abort();
+ }
+
+ /* Right, that's it. New (empty) Unincopr can be installed. */
+ trigdef_process_done();
+}
+
+/*---------- default hooks ----------*/
+
+struct filenamenode {
+ struct filenamenode *next;
+ const char *name;
+ struct trigfileint *trig_interested;
+};
+
+static struct filenamenode *trigger_files;
+
+static struct filenamenode *
+th_simple_nn_find(const char *name, int nonew)
+{
+ struct filenamenode *search;
+
+ for (search = trigger_files; search; search = search->next)
+ if (!strcmp(search->name, name))
+ return search;
+ /* Not found. */
+ if (nonew)
+ return NULL;
+
+ search = nfmalloc(sizeof(*search));
+ search->name = nfstrsave(name);
+ search->trig_interested = NULL;
+ search->next = trigger_files;
+ trigger_files = search;
+
+ return search;
+}
+
+TRIGHOOKS_DEFINE_NAMENODE_ACCESSORS
+
+struct trig_hooks trigh = {
+ NULL,
+ NULL,
+ th_simple_nn_find,
+ th_nn_interested,
+ th_nn_name
+};
+
lib/showcright.c
lib/showpkg.c
lib/tarfn.c
+lib/trigdeferred.c
+lib/triglib.c
lib/utils.c
lib/varbuf.c
lib/vercmp.c
src/query.c
src/remove.c
src/select.c
+src/trigcmd.c
+src/trigproc.c
src/update.c
dpkg-deb/build.c
-I$(top_srcdir)/lib
-bin_PROGRAMS = dpkg dpkg-query
+bin_PROGRAMS = dpkg dpkg-query dpkg-trigger
dpkg_SOURCES = \
archives.c archives.h \
processarc.c \
remove.c \
select.c \
+ trigproc.c \
update.c
dpkg_LDADD = \
$(LIBINTL) \
../lib/libdpkg.a
+dpkg_trigger_SOURCES = \
+ trigcmd.c
+
+dpkg_trigger_LDADD = \
+ ../libcompat/libcompat.a \
+ $(LIBINTL) \
+ ../lib/libdpkg.a
+
install-data-local:
$(mkdir_p) $(DESTDIR)$(admindir)/alternatives
$(mkdir_p) $(DESTDIR)$(admindir)/info
fixbyrm->clientdata->istobe= itb_remove;
fprintf(stderr, _("dpkg: considering removing %s in favour of %s ...\n"),
fixbyrm->name, pkg->name);
- if (fixbyrm->status != stat_installed) {
+ if (!(fixbyrm->status == stat_installed ||
+ fixbyrm->status == stat_triggerspending ||
+ fixbyrm->status == stat_triggersawaited)) {
fprintf(stderr,
_("%s is not properly installed - ignoring any dependencies on it.\n"),
fixbyrm->name);
const char **arglist;
char *p;
+ trigproc_install_hooks();
+
modstatdb_init(admindir,
f_noact ? msdbrw_readonly
: cipaction->arg == act_avail ? msdbrw_write
switch (cipaction->arg) {
case act_install:
case act_configure:
+ case act_triggers:
case act_remove:
case act_purge:
process_queue();
internerr("unknown action");
}
+ trigproc_run_deferred();
modstatdb_shutdown();
}
}
}
- if (pkg->status != stat_installed) return 1;
+ if (!(pkg->status == stat_installed ||
+ pkg->status == stat_triggersawaited ||
+ pkg->status == stat_triggerspending))
+ return 1;
if (!ver) return -1;
r= versioncompare(ver,&pkg->installed.version);
if (r > 0) {
return 1;
} else if (r == 0) {
- if (f_skipsame && /* same version fully installed ? */
- pkg->status == stat_installed && !(pkg->eflag &= eflagf_reinstreq)) {
+ /* Same version fully installed. */
+ if (f_skipsame) {
if (saywhy) fprintf(stderr, _("Version %.250s of %.250s already installed, "
"skipping.\n"),
versiondescribe(&pkg->installed.version, vdew_nonambig),
add_to_queue(pkg);
return;
}
+
+ trigproc_reset_cycle();
+ /* At this point removal from the queue is confirmed. This
+ * represents irreversible progress wrt trigger cycles. Only
+ * packages in stat_unpacked are automatically added to the
+ * configuration queue, and during configuration and trigger
+ * processing new packages can't enter into unpacked.
+ */
+
ok = breakses_ok(pkg, &aemsgs) ? ok : 0;
if (ok == 0) {
sincenothing= 0;
versiondescribe(&pkg->installed.version, vdew_nonambig));
log_action("configure", pkg);
+ trig_activate_packageprocessing(pkg);
+
if (f_noact) {
pkg->status= stat_installed;
pkg->clientdata->istobe= itb_normal;
varbufaddstr(&cdr,DPKGDISTEXT);
varbufaddc(&cdr,0);
strcpy(cdr2rest,DPKGNEWEXT);
+ trig_file_activate_byname(conff->name, pkg);
if (rename(cdr2.buf,cdr.buf))
fprintf(stderr,
_("dpkg: %s: warning - failed to rename `%.250s' to `%.250s': %s\n"),
printf(_("Installing new version of config file %s ...\n"),conff->name);
case cfo_newconff:
strcpy(cdr2rest,DPKGNEWEXT);
+ trig_file_activate_byname(conff->name, pkg);
if (rename(cdr2.buf,cdr.buf))
ohshite(_("unable to install `%.250s' as `%.250s'"),cdr2.buf,cdr.buf);
break;
case itb_remove: case itb_deconfigure:
return 1;
case itb_normal:
- /* Only `installed' packages can be make dependency problems */
+ /* Only installed packages can be make dependency problems */
switch (dep->up->status) {
case stat_installed:
+ case stat_triggerspending:
+ case stat_triggersawaited:
break;
case stat_notinstalled: case stat_configfiles: case stat_halfinstalled:
case stat_halfconfigured: case stat_unpacked:
case itb_normal: case itb_preinstall:
switch (possi->ed->status) {
case stat_installed:
+ case stat_triggerspending:
if (versionsatisfied(&possi->ed->installed,possi)) return 1;
sprintf(linebuf,_(" %.250s is installed, but is version %.250s.\n"),
possi->ed->name,
break;
case stat_unpacked:
case stat_halfconfigured:
+ case stat_triggersawaited:
if (allowunconfigd) {
if (!informativeversion(&possi->ed->configversion)) {
sprintf(linebuf, _(" %.250s is unpacked, but has never been configured.\n"),
return 1;
}
}
+ /* Fall through. */
default:
sprintf(linebuf, _(" %.250s is %s.\n"),
possi->ed->name, gettext(statusstrings[possi->ed->status]));
case stat_halfconfigured:
if (dep->type == dep_breaks) break; /* no problem */
case stat_installed:
+ case stat_triggerspending:
+ case stat_triggersawaited:
if (!versionsatisfied(&possi->ed->installed, possi)) break;
sprintf(linebuf, _(" %.250s (version %.250s) is present and %s.\n"),
possi->ed->name,
case stat_halfconfigured:
if (dep->type == dep_breaks) break; /* no problem */
case stat_installed:
+ case stat_triggerspending:
+ case stat_triggersawaited:
sprintf(linebuf,
_(" %.250s provides %.250s and is present and %s.\n"),
provider->up->up->name, possi->ed->name,
N_("The following packages are only half installed, due to problems during\n"
"installation. The installation can probably be completed by retrying it;\n"
"the packages can be removed using dselect or dpkg --remove:\n")
+ }, {
+ bsyn_status, stat_triggersawaited,
+ N_("The following packages are awaiting processesing of triggers that they\n"
+ "have activated in other packages. This processing can be requested using\n"
+ "dselect or dpkg --configure --pending (or dpkg --triggers-only):\n")
+ }, {
+ bsyn_status, stat_triggerspending,
+ N_("The following packages have been triggered, but the trigger processesing\n"
+ "has not yet been done. Trigger processing can be requested using\n"
+ "dselect or dpkg --configure --pending (or dpkg --triggers-only):\n")
}, {
NULL
}
switch (pkg->status) {
case stat_unpacked: case stat_installed: case stat_halfconfigured:
+ case stat_triggerspending:
+ case stat_triggersawaited:
return 0;
case stat_notinstalled: case stat_halfinstalled: case stat_configfiles:
if (thissect)
pkg= findpackage("dpkg");
switch (pkg->status) {
case stat_installed:
+ case stat_triggerspending:
break;
case stat_unpacked: case stat_halfconfigured: case stat_halfinstalled:
+ case stat_triggersawaited:
if (versionsatisfied3(&pkg->configversion,verrev_buf,dvr_laterequal))
break;
printf(_("Version of dpkg with working epoch support not yet configured.\n"
pkg->clientdata->istobe = itb_normal;
pkg->clientdata->fileslistvalid = 0;
pkg->clientdata->files = NULL;
+ pkg->clientdata->trigprocdeferred = NULL;
}
void note_must_reread_files_inpackage(struct pkginfo *pkg) {
}
if (*pointerp) return *pointerp;
+ if (flags & fnn_nonew)
+ return NULL;
+
newnode= nfmalloc(sizeof(struct filenamenode));
newnode->packages = NULL;
if((flags & fnn_nocopy) && name > orig_name && name[-1] == '/')
newnode->divert = NULL;
newnode->statoverride = NULL;
newnode->filestat = NULL;
+ newnode->trig_interested = NULL;
*pointerp= newnode;
nfiles++;
enum fnnflags {
fnn_nocopy= 000001, /* do not need to copy filename */
+ fnn_nonew = 000002, /* findnamenode may return NULL */
};
struct filenamenode {
} flags; /* Set to zero when a new node is created. */
const char *oldhash; /* valid iff this namenode is in the newconffiles list */
struct stat *filestat;
+ struct trigfileint *trig_interested;
};
struct fileinlist {
N_("broken due to failed removal or installation"),
N_("unpacked but not configured"),
N_("broken due to postinst failure"),
+ N_("awaiting trigger processing by another package"),
+ N_("triggered"),
N_("installed")
};
struct filenamenode *namenodetouse(struct filenamenode *namenode, struct pkginfo *pkg) {
struct filenamenode *r;
- if (!namenode->divert) return namenode;
+ if (!namenode->divert) {
+ r = namenode;
+ goto found;
+ }
debug(dbg_eachfile,"namenodetouse namenode=`%s' pkg=%s",
namenode->name,pkg->name);
namenode->divert->camefrom ? namenode->divert->camefrom->name : "<none>",
namenode->divert->pkg ? namenode->divert->pkg->name : "<none>",
r->name);
+
+ found:
+ trig_file_activate(r, pkg);
return r;
}
void
post_postinst_tasks(struct pkginfo *pkg, enum pkgstatus new_status)
{
- pkg->status = new_status;
+ pkg->trigpend_head = NULL;
+ pkg->status = pkg->trigaw.head ? stat_triggersawaited : new_status;
+
+ post_postinst_tasks_core(pkg);
+}
+
+void
+post_postinst_tasks_core(struct pkginfo *pkg)
+{
modstatdb_note(pkg);
+
+ debug(dbg_triggersdetail, "post_postinst_tasks_core - trig_incorporate");
+ trig_incorporate(msdbrw_write, admindir);
}
static void
post_script_tasks(void)
{
ensure_diversions();
+
+ debug(dbg_triggersdetail,
+ "post_script_tasks - ensure_diversions; trig_incorporate");
+ trig_incorporate(msdbrw_write, admindir);
}
static void
narglist[r]= arglist[r];
scriptexec= preexecscript(scriptpath,(char * const *)narglist);
narglist[0]= scriptexec;
+ if (setenv(MAINTSCRIPTPKGENVVAR, pkg, 1) ||
+ setenv(MAINTSCRIPTDPKGENVVAR, PACKAGE_VERSION, 1))
+ ohshite(_("unable to setenv for maint script"));
execv(scriptexec,(char * const *)narglist);
ohshite(desc,name);
}
" --unpack <.deb file name> ... | -R|--recursive <directory> ...\n"
" -A|--record-avail <.deb file name> ... | -R|--recursive <directory> ...\n"
" --configure <package> ... | -a|--pending\n"
+" --triggers-only <package> ... | -a|--pending\n"
" -r|--remove <package> ... | -a|--pending\n"
" -P|--purge <package> ... | -a|--pending\n"
" --get-selections [<pattern> ...] Get list of selections to stdout.\n"
" -E|--skip-same-version Skip packages whose same version is installed.\n"
" -G|--refuse-downgrade Skip packages with earlier version than installed.\n"
" -B|--auto-deconfigure Install even if it would break some other package.\n"
+" --[no-]triggers Skip or force consequential trigger processing.\n"
" --no-debsig Do not try to verify package signatures.\n"
" --no-act|--dry-run|--simulate\n"
" Just say what we would do - don't do it.\n"
const struct cmdinfo *cipaction = NULL;
int f_pending=0, f_recursive=0, f_alsoselect=1, f_skipsame=0, f_noact=0;
int f_autodeconf=0, f_nodebsig=0;
+int f_triggers = 0;
unsigned long f_debug=0;
/* Change fc_overwrite to 1 to enable force-overwrite by default */
int fc_downgrade=1, fc_configureany=0, fc_hold=0, fc_removereinstreq=0, fc_overwrite=0;
" 200 conffdetail Lots of output for each configuration file\n"
" 40 depcon Dependencies and conflicts\n"
" 400 depcondetail Lots of dependencies/conflicts output\n"
+" 10000 triggers Trigger activation and processing\n"
+" 20000 triggersdetail Lots of output regarding triggers\n"
+" 40000 triggersstupid Silly amounts of output regarding triggers\n"
" 1000 veryverbose Lots of drivel about eg the dpkg/info directory\n"
" 2000 stupidlyverbose Insane amounts of drivel\n"
"\n"
ACTION( "configure", 0, act_configure, packages ),
ACTION( "remove", 'r', act_remove, packages ),
ACTION( "purge", 'P', act_purge, packages ),
+ ACTION( "triggers-only", 0, act_triggers, packages ),
ACTIONBACKEND( "listfiles", 'L', DPKGQUERY),
ACTIONBACKEND( "status", 's', DPKGQUERY),
ACTION( "get-selections", 0, act_getselections, getselections ),
/* Alias ('G') for --refuse. */
{ NULL, 'G', 0, &fc_downgrade, NULL, NULL, 0 },
{ "selected-only", 'O', 0, &f_alsoselect, NULL, NULL, 0 },
+ { "triggers", 0, 0, &f_triggers, NULL, NULL, 1 },
+ { "no-triggers", 0, 0, &f_triggers, NULL, NULL, -1 },
/* FIXME: Remove ('N') sometime. */
{ "no-also-select", 'N', 0, &f_alsoselect, NULL, NULL, 0 },
{ "skip-same-version", 'E', 0, &f_skipsame, NULL, NULL, 1 },
standard_startup(&ejbuf, argc, &argv, DPKG, 1, cmdinfos);
if (!cipaction) badusage(_("need an action option"));
+ if (!f_triggers)
+ f_triggers = (cipaction->arg == act_triggers && *argv) ? -1 : 1;
+
setvbuf(stdout, NULL, _IONBF, 0);
filesdbinit();
int fileslistvalid;
struct fileinlist *files;
int replacingfilesandsaid;
+
+ /* Non-NULL iff in trigproc.c:deferred. */
+ struct pkginqueue *trigprocdeferred;
};
struct packageinlist {
};
enum action { act_unset, act_install, act_unpack, act_avail, act_configure,
+ act_triggers,
act_remove, act_purge, act_listpackages, act_avreplace, act_avmerge,
act_unpackchk, act_status, act_searchfiles, act_audit, act_listfiles,
act_assertpredep, act_printarch, act_predeppackage, act_cmpversions,
extern const struct cmdinfo *cipaction;
extern int f_pending, f_recursive, f_alsoselect, f_skipsame, f_noact;
extern int f_autodeconf, f_largemem, f_nodebsig;
+extern int f_triggers;
extern unsigned long f_debug;
extern int fc_downgrade, fc_configureany, fc_hold, fc_removereinstreq, fc_overwrite;
extern int fc_removeessential, fc_conflicts, fc_depends, fc_dependsversion;
int chmodsafe_unlink_statted(const char *pathname, const struct stat *stab,
const char **failed);
void checkpath(void);
+
+/* NB side effect! This not only computes the appropriate filename
+ * to use including thinking about any diversions, but also activates
+ * any file triggers. */
struct filenamenode *namenodetouse(struct filenamenode*, struct pkginfo*);
/* all ...'s are const char*'s ... */
const char *scriptname, const char *description,
const char *cidir, char *cidirrest,
const char *ifok, const char *iffallback);
+
+/* Callers wanting to run the postinst use these two as they want to postpone
+ * trigger incorporation until after updating the package status. The effect
+ * is that a package can trigger itself. */
int maintainer_script_postinst(struct pkginfo *pkg, ...);
+void post_postinst_tasks_core(struct pkginfo *pkg);
+
void post_postinst_tasks(struct pkginfo *pkg, enum pkgstatus new_status);
void clear_istobes(void);
dbg_depcondetail= 00400,
dbg_veryverbose= 01000,
dbg_stupidlyverbose= 02000,
+ dbg_triggers = 010000,
+ dbg_triggersdetail = 020000,
+ dbg_triggersstupid = 040000,
};
void debug(int which, const char *fmt, ...) PRINTFFORMAT(2,3);
void check_libver(void);
void log_action(const char *action, struct pkginfo *pkg);
+/* from trigproc.c */
+
+void trigproc_install_hooks(void);
+void trigproc_run_deferred(void);
+void trigproc_reset_cycle(void);
+
+/* Does cycle checking. Doesn't mind if pkg has no triggers
+ * pending - in that case does nothing but fix up any stale awaiters. */
+void trigproc(struct pkginfo *pkg);
+
+/* Called by modstatdb_note. */
+void trig_activate_packageprocessing(struct pkginfo *pkg);
+
/* from depcon.c */
int depisok(struct dependency *dep, struct varbuf *whynot,
#include "filesdb.h"
#include "main.h"
+static struct pkginfo *progress_bytrigproc;
static PKGQUEUE_DEF_INIT(queue);
int sincenothing = 0, dependtry = 0;
const char *thisarg;
size_t l;
+ trigproc_install_hooks();
+
modstatdb_init(admindir,
f_noact ? msdbrw_readonly
: fc_nonroot ? msdbrw_write
while ((pkg = iterpkgnext(it)) != NULL) {
switch (cipaction->arg) {
case act_configure:
- if (pkg->status != stat_unpacked && pkg->status != stat_halfconfigured)
+ if (!(pkg->status == stat_unpacked ||
+ pkg->status == stat_halfconfigured ||
+ pkg->trigpend_head))
+ continue;
+ if (pkg->want != want_install)
+ continue;
+ break;
+ case act_triggers:
+ if (!pkg->trigpend_head)
continue;
if (pkg->want != want_install)
continue;
ensure_diversions();
process_queue();
+ trigproc_run_deferred();
modstatdb_shutdown();
}
void process_queue(void) {
struct pkginqueue *removeent, *rundown;
struct pkginfo *volatile pkg;
+ enum action action_todo;
jmp_buf ejbuf;
enum istobes istobe= itb_normal;
clear_istobes();
switch (cipaction->arg) {
+ case act_triggers:
case act_configure: case act_install: istobe= itb_installnew; break;
case act_remove: case act_purge: istobe= itb_remove; break;
default: internerr("unknown action for queue start");
if (rundown->pkg->clientdata->istobe == istobe) {
/* Erase the queue entry - this is a second copy! */
switch (cipaction->arg) {
+ case act_triggers:
case act_configure: case act_remove: case act_purge:
printf(_("Package %s listed more than once, only processing once.\n"),
rundown->pkg->name);
if (!pkg) continue; /* duplicate, which we removed earlier */
+ action_todo = cipaction->arg;
+
+ if (sincenothing++ > queue.length * 2 + 2) {
+ if (progress_bytrigproc && progress_bytrigproc->trigpend_head) {
+ add_to_queue(pkg);
+ pkg = progress_bytrigproc;
+ action_todo = act_configure;
+ } else {
+ dependtry++;
+ sincenothing = 0;
+ assert(dependtry <= 4);
+ }
+ }
+
assert(pkg->status <= stat_installed);
if (setjmp(ejbuf)) {
continue;
}
push_error_handler(&ejbuf,print_error_perpackage,pkg->name);
- if (sincenothing++ > queue.length * 2 + 2) {
- dependtry++; sincenothing= 0;
- assert(dependtry <= 4);
- }
- switch (cipaction->arg) {
+ switch (action_todo) {
+ case act_triggers:
+ if (!pkg->trigpend_head)
+ ohshit(_("package %.250s is not ready for trigger processing\n"
+ " (current status `%.250s' with no pending triggers)"),
+ pkg->name, statusinfos[pkg->status].name);
+ /* Fall through. */
case act_install:
/* Don't try to configure pkgs that we've just disappeared. */
if (pkg->status == stat_notinstalled)
break;
+ /* Fall through. */
case act_configure:
- deferred_configure(pkg);
+ /* Do whatever is most needed. */
+ if (pkg->trigpend_head)
+ trigproc(pkg);
+ else
+ deferred_configure(pkg);
break;
case act_remove: case act_purge:
deferred_remove(pkg);
* and breaking.
*/
+/* Return values:
+ * 0: cannot be satisfied.
+ * 1: defer: may be satisfied later, when other packages are better or
+ * at higher dependtry due to --force
+ * will set *fixbytrig to package whose trigger processing would help
+ * if applicable (and leave it alone otherwise)
+ * 2: not satisfied but forcing
+ * (*interestingwarnings >= 0 on exit? caller is to print oemsgs)
+ * 3: satisfied now
+ */
static int deppossi_ok_found(struct pkginfo *possdependee,
struct pkginfo *requiredby,
struct pkginfo *removing,
struct pkginfo *providing,
+ struct pkginfo **fixbytrig,
int *matched,
struct deppossi *checkversion,
int *interestingwarnings,
switch (possdependee->status) {
case stat_unpacked:
case stat_halfconfigured:
+ case stat_triggersawaited:
+ case stat_triggerspending:
case stat_installed:
assert(possdependee->installed.valid);
if (checkversion && !versionsatisfied(&possdependee->installed,checkversion)) {
(*interestingwarnings)++;
return thisf;
}
- if (possdependee->status == stat_installed) {
+ if (possdependee->status == stat_installed ||
+ possdependee->status == stat_triggerspending) {
debug(dbg_depcondetail," is installed, ok and found");
return 3;
}
+ if (possdependee->status == stat_triggersawaited) {
+ assert(possdependee->trigaw.head);
+ if (removing || !(f_triggers ||
+ possdependee->clientdata->istobe == itb_installnew)) {
+ if (providing) {
+ varbufprintf(oemsgs,
+ _(" Package %s which provides %s awaits trigger processing.\n"),
+ possdependee->name, providing->name);
+ } else {
+ varbufprintf(oemsgs,
+ _(" Package %s awaits trigger processing.\n"),
+ possdependee->name);
+ }
+ debug(dbg_depcondetail, " triggers-awaited, no fixbytrig");
+ goto unsuitable;
+ }
+ /* We don't check the status of trigaw.head->pend here, just in case
+ * we get into the pathological situation where Triggers-Awaited but
+ * the named package doesn't actually have any pending triggers. In
+ * that case we queue the non-pending package for trigger processing
+ * anyway, and that trigger processing will be a noop except for
+ * sorting out all of the packages which name it in T-Awaited.
+ *
+ * (This situation can only arise if modstatdb_note success in
+ * clearing the triggers-pending status of the pending package
+ * but then fails to go on to update the awaiters.)
+ */
+ *fixbytrig = possdependee->trigaw.head->pend;
+ debug(dbg_depcondetail,
+ " triggers-awaited, fixbytrig `%s', returning 1",
+ (*fixbytrig)->name);
+ return 1;
+ }
if (possdependee->clientdata &&
possdependee->clientdata->istobe == itb_installnew) {
debug(dbg_depcondetail," unpacked/halfconfigured, defer");
return 1;
- } else if (!removing && fc_configureany && !skip_due_to_hold(possdependee)) {
+ } else if (!removing && fc_configureany &&
+ !skip_due_to_hold(possdependee) &&
+ !(possdependee->status == stat_halfconfigured)) {
fprintf(stderr,
_("dpkg: also configuring `%s' (required by `%s')\n"),
possdependee->name, requiredby->name);
debug(dbg_depcondetail, " not configured/able");
goto unsuitable;
}
+
default:
if (providing) {
varbufprintf(oemsgs,
int dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
struct varbuf *aemsgs) {
- int ok, matched, found, thisf, interestingwarnings;
+ int ok, matched, found, thisf, interestingwarnings, anycannotfixbytrig;
struct varbuf oemsgs;
struct dependency *dep;
struct deppossi *possi, *provider;
+ struct pkginfo *possfixbytrig, *canfixbytrig;
varbufinit(&oemsgs);
interestingwarnings= 0;
pkg->name, removing ? removing->name : "<none>");
assert(pkg->installed.valid);
+ anycannotfixbytrig = 0;
+ canfixbytrig = NULL;
for (dep= pkg->installed.depends; dep; dep= dep->next) {
if (dep->type != dep_depends && dep->type != dep_predepends) continue;
debug(dbg_depcondetail," checking group ...");
matched= 0; varbufreset(&oemsgs);
found= 0; /* 0=none, 1=defer, 2=withwarning, 3=ok */
+ possfixbytrig = NULL;
for (possi= dep->list; found != 3 && possi; possi= possi->next) {
debug(dbg_depcondetail," checking possibility -> %s",possi->ed->name);
if (possi->cyclebreak) {
found= 3; break;
}
thisf = deppossi_ok_found(possi->ed, pkg, removing, NULL,
+ &possfixbytrig,
&matched,possi,&interestingwarnings,&oemsgs);
if (thisf > found) found= thisf;
if (found != 3 && possi->verrel == dvr_none) {
if (provider->up->type != dep_provides) continue;
debug(dbg_depcondetail," checking provider %s",provider->up->up->name);
thisf= deppossi_ok_found(provider->up->up,pkg,removing,possi->ed,
+ &possfixbytrig,
&matched, NULL, &interestingwarnings, &oemsgs);
if (thisf > found) found= thisf;
}
debug(dbg_depcondetail," found %d",found);
if (thisf > found) found= thisf;
}
- debug(dbg_depcondetail," found %d matched %d",found,matched);
+ debug(dbg_depcondetail, " found %d matched %d possfixbytrig %s",
+ found, matched, possfixbytrig ? possfixbytrig->name : "-");
if (removing && !matched) continue;
switch (found) {
case 0:
+ anycannotfixbytrig = 1;
ok= 0;
case 2:
varbufaddstr(aemsgs, " ");
}
break;
case 1:
+ if (possfixbytrig)
+ canfixbytrig = possfixbytrig;
+ else
+ anycannotfixbytrig = 1;
if (ok>1) ok= 1;
break;
case 3:
}
if (ok == 0 && (pkg->clientdata && pkg->clientdata->istobe == itb_remove))
ok= 1;
+ if (!anycannotfixbytrig && canfixbytrig)
+ progress_bytrigproc = canfixbytrig;
varbuffree(&oemsgs);
debug(dbg_depcon,"ok %d msgs >>%.*s<<", ok, (int)aemsgs->used, aemsgs->buf);
ensure_allinstfiles_available();
filesdbinit();
-
+ trig_file_interests_ensure();
+
if (pkg->status != stat_notinstalled && pkg->status != stat_configfiles) {
printf(_("Preparing to replace %s %s (using %s) ...\n"),
pkg->name,
return;
}
- /* OK, we're going ahead. First we read the conffiles, and copy the
- * hashes across.
+ /*
+ * OK, we're going ahead.
*/
+
+ trig_activate_packageprocessing(pkg);
+ strcpy(cidirrest, TRIGGERSCIFILE);
+ trig_parse_ci(cidir, NULL, trig_cicb_statuschange_activate, pkg);
+
+ /* Read the conffiles, and copy the hashes across. */
newconffiles = NULL;
newconffileslastp = &newconffiles;
push_cleanup(cu_fileslist, ~0, NULL, 0, 0);
debug(dbg_general,"process_archive oldversionstatus=%s",
statusstrings[oldversionstatus]);
- if (oldversionstatus == stat_halfconfigured || oldversionstatus == stat_installed) {
+ if (oldversionstatus == stat_halfconfigured ||
+ oldversionstatus == stat_triggersawaited ||
+ oldversionstatus == stat_triggerspending ||
+ oldversionstatus == stat_installed) {
pkg->eflag |= eflagf_reinstreq;
pkg->status= stat_halfconfigured;
modstatdb_note(pkg);
else
printf(_("De-configuring %s ...\n"), deconpil->pkg->name);
+ trig_activate_packageprocessing(deconpil->pkg);
deconpil->pkg->status= stat_halfconfigured;
modstatdb_note(deconpil->pkg);
for (i = 0 ; i < cflict_index; i++) {
if (!(conflictor[i]->status == stat_halfconfigured ||
+ conflictor[i]->status == stat_triggersawaited ||
+ conflictor[i]->status == stat_triggerspending ||
conflictor[i]->status == stat_installed)) continue;
+ trig_activate_packageprocessing(conflictor[i]);
conflictor[i]->status= stat_halfconfigured;
modstatdb_note(conflictor[i]);
push_cleanup(cu_prerminfavour, ~ehflag_normaltidy, NULL, 0,
*/
write_filelist_except(pkg,newfileslist,0);
+ /* Trigger interests may have changed.
+ * Firstly we go through the old list of interests deleting them.
+ * Then we go through the new list adding them.
+ */
+ strcpy(cidirrest, TRIGGERSCIFILE);
+ trig_parse_ci(pkgadminfile(pkg, TRIGGERSCIFILE),
+ trig_cicb_interest_delete, NULL, pkg);
+ trig_parse_ci(cidir, trig_cicb_interest_add, NULL, pkg);
+ trig_file_interests_save();
+
/* We also install the new maintainer scripts, and any other
* cruft that may have come along with the package. First
* we go through the existing scripts replacing or removing
* run maintainer scripts and things, as we can't back out. But
* what can we do ? It has to be run this late.
*/
+ trig_activate_packageprocessing(otherpkg);
maintainer_script_installed(otherpkg, POSTRMFILE,
"post-removal script (for disappearance)",
"disappear", pkg->name,
if (!*head) {
fputs(_("\
Desired=Unknown/Install/Remove/Purge/Hold\n\
-| Status=Not/Installed/Config-files/Unpacked/Failed-config/Half-installed\n\
+| Status=Not/Inst/Cfg-files/Unpacked/Failed-cfg/Half-inst/trig-aWait/Trig-pend\n\
|/ Err?=(none)/Hold/Reinst-required/X=both-problems (Status,Err: uppercase=bad)\n"), stdout);
printf(format,'|','|','/', _("Name"), _("Version"), 40, _("Description"));
printf("+++-"); /* status */
limiteddescription(pkg,dw,&pdesc,&l);
printf(format,
"uihrp"[pkg->want],
- "ncHUFi"[pkg->status],
+ "ncHUFWti"[pkg->status],
" R?#"[pkg->eflag],
pkg->name,
versiondescribe(&pkg->installed.version, vdew_nonambig),
if (possi->up->type != dep_depends && possi->up->type != dep_predepends) continue;
depender= possi->up->up;
debug(dbg_depcon,"checking depending package `%s'",depender->name);
- if (depender->status != stat_installed) continue;
+ if (!(depender->status == stat_installed ||
+ depender->status == stat_triggerspending ||
+ depender->status == stat_triggersawaited))
+ continue;
if (ignore_depends(depender)) {
debug(dbg_depcon,"ignoring depending package `%s'\n",depender->name);
continue;
printf(_("Removing %s ...\n"),pkg->name);
log_action("remove", pkg);
- if (pkg->status == stat_halfconfigured || pkg->status == stat_installed) {
+ trig_activate_packageprocessing(pkg);
+ if (pkg->status >= stat_halfconfigured) {
static enum pkgstatus oldpkgstatus;
oldpkgstatus= pkg->status;
printf(_("Purging configuration files for %s ...\n"),pkg->name);
log_action("purge", pkg);
+ trig_activate_packageprocessing(pkg);
ensure_packagefiles_available(pkg); /* We may have modified this above. */
/* We're about to remove the configuration, so remove the note
--- /dev/null
+/*
+ * dpkg-trigger - trigger management utility
+ *
+ * Copyright (C) 2007 Canonical Ltd.
+ * Written by Ian Jackson <ian@davenant.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with dpkg; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fnmatch.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/termios.h>
+#include <fcntl.h>
+
+#include <dpkg.h>
+#include <dpkg-db.h>
+#include <myopt.h>
+
+static const char *bypackage, *activate, *admindir = ADMINDIR;
+static int f_noact, f_check;
+
+static void
+noawait(const struct cmdinfo *ci, const char *value)
+{
+ bypackage = "-";
+}
+
+static const struct cmdinfo cmdinfos[] = {
+ { "admindir", 0, 1, NULL, &admindir },
+ { "by-package", 'f', 1, NULL, &bypackage },
+ { "no-await", 0, 0, NULL, &bypackage, noawait },
+ { "no-act", 0, 0, &f_noact, NULL, 0, 1 },
+ { "check-supported", 0, 0, &f_check, NULL, 0, 1 },
+ { "help", 'h', 0, NULL, NULL, helponly },
+ { "version", 0, 0, NULL, NULL, versiononly },
+ /* UK spelling */
+ { "licence", 0, 0, NULL, NULL, showcopyright },
+ /* US spelling */
+ { "license", 0, 0, NULL, NULL, showcopyright },
+ { NULL }
+};
+
+const char thisname[] = "dpkg-trigger";
+
+const char printforhelp[] = N_(
+"Type dpkg-triggers --help for help about this utility.");
+
+void
+printversion(void)
+{
+ if (printf(_("Debian %s package trigger utility.\n"), thisname) < 0)
+ werr("stdout");
+
+ if (printf(_(
+"This is free software; see the GNU General Public License version 2 or\n"
+"later for copying conditions. There is NO warranty.\n"
+"See %s --license for copyright and license details.\n"), thisname) < 0)
+ werr("stdout");
+}
+
+void
+usage(void)
+{
+ if (printf(_(
+"Usage: %s [<options> ...] <trigger-name>\n"
+" %s [<options> ...] <command>\n"
+"\n"), thisname, thisname) < 0)
+ werr ("stdout");
+
+ if (printf(_(
+"Commands:\n"
+" --check-supported Check if the running dpkg supports triggers.\n"
+"\n")) < 0)
+ werr ("stdout");
+
+ if (printf(_(
+" -h|--help Show this help message.\n"
+" --version Show the version.\n"
+" --license|--licence Show the copyright licensing terms.\n"
+"\n")) < 0)
+ werr ("stdout");
+
+ if (printf(_(
+"Options:\n"
+" --admindir=<directory> Use <directory> instead of %s.\n"
+" --by-package=<package> Override trigger awaiter (normally set\n"
+" by dpkg).\n"
+" --no-await No package needs to await the processing.\n"
+" --no-act Just test - don't actually change anything.\n"
+"\n"), admindir) < 0)
+ werr("stdout");
+}
+
+static int done_trig, ctrig;
+
+static void
+yespackage(const char *awname)
+{
+ fprintf(trig_new_deferred, " %s", awname);
+}
+
+static void
+tdm_add_trig_begin(const char *trig)
+{
+ ctrig = !strcmp(trig, activate);
+ fputs(trig, trig_new_deferred);
+ if (!ctrig || done_trig)
+ return;
+ yespackage(bypackage);
+ done_trig = 1;
+}
+
+static void
+tdm_add_package(const char *awname)
+{
+ if (ctrig && !strcmp(awname, bypackage))
+ return;
+ yespackage(awname);
+}
+
+static void
+tdm_add_trig_end(void)
+{
+ fputc('\n', trig_new_deferred);
+}
+
+static const struct trigdefmeths tdm_add = {
+ tdm_add_trig_begin,
+ tdm_add_package,
+ tdm_add_trig_end
+};
+
+static void
+do_check(void)
+{
+ int uf;
+
+ uf = trigdef_update_start(tduf_nolockok, admindir);
+ switch (uf) {
+ case -1:
+ fprintf(stderr, _("%s: triggers data directory not yet created\n"),
+ thisname);
+ exit(1);
+ case -3:
+ fprintf(stderr, _("%s: trigger records not yet in existence\n"),
+ thisname);
+ exit(1);
+ case 2:
+ case -2:
+ exit(0);
+ default:
+ abort();
+ }
+}
+
+int
+main(int argc, const char *const *argv)
+{
+ jmp_buf ejbuf;
+ int uf;
+ const char *badname;
+ enum trigdef_updateflags tduf;
+
+ standard_startup(&ejbuf, argc, &argv, NULL, 0, cmdinfos);
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ if (f_check) {
+ if (*argv)
+ badusage(_("dpkg-trigger --check-supported takes no arguments"));
+ do_check();
+ }
+
+ if (!*argv || argv[1])
+ badusage(_("dpkg-trigger takes one argument, the trigger name"));
+
+ if (!bypackage) {
+ bypackage = getenv(MAINTSCRIPTPKGENVVAR);
+ if (!bypackage)
+ ohshit(_("dpkg-trigger must be called from a maintainer script"
+ " (or with a --by-package option)"));
+ }
+ if (strcmp(bypackage, "-") &&
+ (badname = illegal_packagename(bypackage, NULL)))
+ ohshit(_("dpkg-trigger: illegal awaited package name `%.250s': %.250s"),
+ bypackage, badname);
+
+ activate = argv[0];
+ if ((badname = illegal_triggername(activate)))
+ badusage(_("dpkg-trigger: invalid trigger name `%.250s': %.250s"),
+ activate, badname);
+
+ trigdef = &tdm_add;
+ tduf = tduf_nolockok;
+ if (!f_noact)
+ tduf |= tduf_write | tduf_writeifempty;
+ uf = trigdef_update_start(tduf, admindir);
+ if (uf >= 0) {
+ trigdef_yylex();
+ if (!done_trig)
+ fprintf(trig_new_deferred, "%s %s\n",
+ activate, bypackage);
+ trigdef_process_done();
+ }
+
+ standard_shutdown(0);
+
+ return 0;
+}
+
--- /dev/null
+/*
+ * dpkg - main program for package management
+ * trigproc.c - trigger processing
+ *
+ * Copyright (C) 2007 Canonical Ltd
+ * written by Ian Jackson <ian@chiark.greenend.org.uk>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2,
+ * or (at your option) any later version.
+ *
+ * This is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with dpkg; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <config.h>
+
+#include <assert.h>
+#include <sys/stat.h>
+#include <sys/fcntl.h>
+
+#include <dpkg.h>
+#include <dpkg-db.h>
+
+#include "main.h"
+#include "filesdb.h"
+
+/*
+ * Trigger processing algorithms:
+ *
+ *
+ * There is a separate queue (`deferred trigproc list') for triggers
+ * `relevant' to what we just did; when we find something triggered `now'
+ * we add it to that queue (unless --no-triggers).
+ *
+ *
+ * We want to prefer configuring packages where possible to doing
+ * trigger processing, but we want to prefer trigger processing to
+ * cycle-breaking and dependency forcing. This is achieved as
+ * follows:
+ *
+ * Each time during configure processing where a package D is blocked by
+ * only (ie Depends unsatisfied but would be satisfied by) a t-awaiter W
+ * we make a note of (one of) W's t-pending, T. (Only the last such T.)
+ * (If --no-triggers and nonempty argument list and package isn't in
+ * argument list then we don't do this.)
+ *
+ * Each time in packages.c where we increment dependtry, we instead see
+ * if we have encountered such a t-pending T. If we do, we trigproc T
+ * instead of incrementing dependtry and this counts as having done
+ * something so we reset sincenothing.
+ *
+ *
+ * For --triggers-only and --configure, we go through each thing in the
+ * argument queue (the add_to_queue queue) and check what its state is
+ * and if appropriate we trigproc it. If we didn't have a queue (or had
+ * just --pending) we search all triggers-pending packages and add them
+ * to the deferred trigproc list.
+ *
+ *
+ * Before quitting from most operations, we trigproc each package in the
+ * deferred trigproc list. This may (if not --no-triggers) of course add
+ * new things to the deferred trigproc list.
+ *
+ *
+ * Note that `we trigproc T' must involve trigger cycle detection and
+ * also automatic setting of t-awaiters to t-pending or installed. In
+ * particular, we do cycle detection even for trigger processing in the
+ * configure dependtry loop (and it is OK to do it for explicitly
+ * specified packages from the command line arguments; duplicates are
+ * removed by packages.c:process_queue).
+ *
+ */
+
+/*========== deferred trigger queue ==========*/
+
+static PKGQUEUE_DEF_INIT(deferred);
+
+static void
+trigproc_enqueue_deferred(struct pkginfo *pend)
+{
+ if (f_triggers < 0)
+ return;
+ ensure_package_clientdata(pend);
+ if (pend->clientdata->trigprocdeferred)
+ return;
+ pend->clientdata->trigprocdeferred = add_to_some_queue(pend, &deferred);
+ debug(dbg_triggers, "trigproc_enqueue_deferred pend=%s", pend->name);
+}
+
+void
+trigproc_run_deferred(void)
+{
+ struct pkginqueue *node;
+ struct pkginfo *pkg;
+
+ debug(dbg_triggers, "trigproc_run_deferred");
+ while ((node = remove_from_some_queue(&deferred))) {
+ pkg = node->pkg;
+ free(node);
+ if (!pkg)
+ continue;
+ pkg->clientdata->trigprocdeferred = NULL;
+ trigproc(pkg);
+ }
+}
+
+void
+trig_activate_packageprocessing(struct pkginfo *pkg)
+{
+ debug(dbg_triggersdetail, "trigproc_activate_packageprocessing pkg=%s",
+ pkg->name);
+
+ trig_parse_ci(pkgadminfile(pkg, TRIGGERSCIFILE), NULL,
+ trig_cicb_statuschange_activate, pkg);
+}
+
+/*========== actual trigger processing ==========*/
+
+struct trigcyclenode {
+ struct trigcyclenode *next;
+ struct trigcycleperpkg *pkgs;
+ struct pkginfo *then_processed;
+};
+
+struct trigcycleperpkg {
+ struct trigcycleperpkg *next;
+ struct pkginfo *pkg;
+ struct trigpend *then_trigs;
+};
+
+static int tortoise_advance;
+static struct trigcyclenode *tortoise, *hare;
+
+void
+trigproc_reset_cycle(void)
+{
+ tortoise_advance = 0;
+ tortoise = hare = NULL;
+}
+
+/* Returns package we're to give up on. */
+static struct pkginfo *
+check_trigger_cycle(struct pkginfo *processing_now)
+{
+ struct trigcyclenode *tcn;
+ struct trigcycleperpkg *tcpp, *tortoise_pkg;
+ struct trigpend *hare_trig, *tortoise_trig;
+ struct pkgiterator *it;
+ struct pkginfo *pkg, *giveup;
+ const char *sep;
+
+ debug(dbg_triggers, "check_triggers_cycle pnow=%s", processing_now->name);
+
+ tcn = nfmalloc(sizeof(*tcn));
+ tcn->pkgs = NULL;
+ tcn->then_processed = processing_now;
+
+ it = iterpkgstart();
+ while ((pkg = iterpkgnext(it))) {
+ if (!pkg->trigpend_head)
+ continue;
+ tcpp = nfmalloc(sizeof(*tcpp));
+ tcpp->pkg = pkg;
+ tcpp->then_trigs = pkg->trigpend_head;
+ tcpp->next = tcn->pkgs;
+ tcn->pkgs = tcpp;
+ }
+ if (!hare) {
+ debug(dbg_triggersdetail, "check_triggers_cycle pnow=%s first",
+ processing_now->name);
+ tcn->next = NULL;
+ hare = tortoise = tcn;
+ return NULL;
+ }
+
+ tcn->next = NULL;
+ hare->next = tcn;
+ hare = tcn;
+ if (tortoise_advance)
+ tortoise = tortoise->next;
+ tortoise_advance = !tortoise_advance;
+
+ /* Now we compare hare to tortoise.
+ * We want to find a trigger pending in tortoise which is not in hare
+ * if we find such a thing we have proved that hare isn't a superset
+ * of tortoise and so that we haven't found a loop (yet).
+ */
+ for (tortoise_pkg = tortoise->pkgs;
+ tortoise_pkg;
+ tortoise_pkg = tortoise_pkg->next) {
+ debug(dbg_triggersdetail, "check_triggers_cycle pnow=%s tortoise=%s",
+ processing_now->name, tortoise_pkg->pkg->name);
+ for (tortoise_trig = tortoise_pkg->then_trigs;
+ tortoise_trig;
+ tortoise_trig = tortoise_trig->next) {
+ debug(dbg_triggersdetail,
+ "check_triggers_cycle pnow=%s tortoise=%s"
+ " tortoisetrig=%s", processing_now->name,
+ tortoise_pkg->pkg->name, tortoise_trig->name);
+ /* hare is now so we can just look up in the actual
+ * data. */
+ for (hare_trig = tortoise_pkg->pkg->trigpend_head;
+ hare_trig;
+ hare_trig = hare_trig->next) {
+ debug(dbg_triggersstupid,
+ "check_triggers_cycle pnow=%s tortoise=%s"
+ " tortoisetrig=%s haretrig=%s",
+ processing_now->name, tortoise_pkg->pkg->name,
+ tortoise_trig->name, hare_trig->name);
+ if (!strcmp(hare_trig->name, tortoise_trig->name))
+ goto found_in_hare;
+ }
+ /* Not found in hare, yay! */
+ debug(dbg_triggersdetail,
+ "check_triggers_cycle pnow=%s tortoise=%s OK",
+ processing_now->name, tortoise_pkg->pkg->name);
+ return NULL;
+ found_in_hare:;
+ }
+ }
+ /* Oh dear. hare is a superset of tortoise. We are making no progress. */
+ fprintf(stderr, _("%s: cycle found while processing triggers:\n chain of"
+ " packages whose triggers are or may be responsible:\n"),
+ DPKG);
+ sep = " ";
+ for (tcn = tortoise; tcn; tcn = tcn->next) {
+ fprintf(stderr, "%s%s", sep, tcn->then_processed->name);
+ sep = " -> ";
+ }
+ fprintf(stderr, _("\n" " packages' pending triggers which are"
+ " or may be unresolvable:\n"));
+ for (tortoise_pkg = tortoise->pkgs;
+ tortoise_pkg;
+ tortoise_pkg = tortoise_pkg->next) {
+ fprintf(stderr, " %s", tortoise_pkg->pkg->name);
+ sep = ": ";
+ for (tortoise_trig = tortoise_pkg->then_trigs;
+ tortoise_trig;
+ tortoise_trig = tortoise_trig->next) {
+ fprintf(stderr, "%s%s", sep, tortoise_trig->name);
+ }
+ fprintf(stderr, "\n");
+ }
+
+ /* We give up on the _earliest_ package involved. */
+ giveup = tortoise->pkgs->pkg;
+ debug(dbg_triggers, "check_triggers_cycle pnow=%s giveup=%p",
+ processing_now->name, giveup->name);
+ assert(giveup->status == stat_triggersawaited ||
+ giveup->status == stat_triggerspending);
+ giveup->status = stat_halfconfigured;
+ modstatdb_note(giveup);
+ print_error_perpackage(_("triggers looping, abandoned"), giveup->name);
+
+ return giveup;
+}
+
+void
+trigproc(struct pkginfo *pkg)
+{
+ static struct varbuf namesarg;
+
+ struct trigpend *tp;
+ struct pkginfo *gaveup;
+
+ debug(dbg_triggers, "trigproc %s", pkg->name);
+
+ if (pkg->clientdata->trigprocdeferred)
+ pkg->clientdata->trigprocdeferred->pkg = NULL;
+ pkg->clientdata->trigprocdeferred = NULL;
+
+ if (pkg->trigpend_head) {
+ assert(pkg->status == stat_triggerspending ||
+ pkg->status == stat_triggersawaited);
+
+ gaveup = check_trigger_cycle(pkg);
+ if (gaveup == pkg)
+ return;
+
+ printf(_("Processing triggers for %s ...\n"), pkg->name);
+ log_action("trigproc", pkg);
+
+ varbufreset(&namesarg);
+ for (tp = pkg->trigpend_head; tp; tp = tp->next) {
+ varbufaddc(&namesarg, ' ');
+ varbufaddstr(&namesarg, tp->name);
+ }
+ varbufaddc(&namesarg, 0);
+
+ pkg->status = stat_halfconfigured;
+ modstatdb_note(pkg);
+
+ sincenothing = 0;
+ maintainer_script_postinst(pkg, "triggered",
+ namesarg.buf + 1, NULL);
+
+ /* This is to cope if the package triggers itself: */
+ pkg->status = pkg->trigaw.head ? stat_triggersawaited :
+ pkg->trigpend_head ? stat_triggerspending :
+ stat_installed;
+
+ post_postinst_tasks_core(pkg);
+ } else {
+ /* In other branch is done by modstatdb_note. */
+ trig_clear_awaiters(pkg);
+ }
+}
+
+/*========== transitional global activation ==========*/
+
+static void
+transitional_interest_callback_ro(const char *trig, void *user)
+{
+ struct pkginfo *pend = user;
+
+ debug(dbg_triggersdetail,
+ "trig_transitional_interest_callback trig=%s pend=%s",
+ trig, pend->name);
+ if (pend->status >= stat_triggersawaited)
+ trig_note_pend(pend, nfstrsave(trig));
+}
+
+static void
+transitional_interest_callback(const char *trig, void *user)
+{
+ struct pkginfo *pend = user;
+
+ trig_cicb_interest_add(trig, pend);
+ transitional_interest_callback_ro(trig, user);
+}
+
+static void
+trig_transitional_activate(enum modstatdb_rw cstatus)
+{
+ /* cstatus might be _read if we're in --no-act mode, in which
+ * case we don't write out all of the interest files etc.
+ * but we do invent all of the activations for our own benefit.
+ */
+ struct pkgiterator *it;
+ struct pkginfo *pkg;
+
+ it = iterpkgstart();
+
+ while ((pkg = iterpkgnext(it))) {
+ if (pkg->status <= stat_halfinstalled)
+ continue;
+ debug(dbg_triggersdetail, "trig_transitional_activate %s %s",
+ pkg->name, statusinfos[pkg->status].name);
+ pkg->trigpend_head = NULL;
+ trig_parse_ci(pkgadminfile(pkg, TRIGGERSCIFILE),
+ cstatus >= msdbrw_write ?
+ transitional_interest_callback :
+ transitional_interest_callback_ro, NULL, pkg);
+ }
+ iterpkgend(it);
+ if (cstatus >= msdbrw_write)
+ modstatdb_checkpoint();
+
+ trig_file_interests_save();
+}
+
+/*========== hook setup ==========*/
+
+static struct filenamenode *
+th_proper_nn_find(const char *name, int nonew)
+{
+ return findnamenode(name, nonew ? fnn_nonew : 0);
+}
+
+TRIGHOOKS_DEFINE_NAMENODE_ACCESSORS
+
+static const struct trig_hooks trig_our_hooks = {
+ trigproc_enqueue_deferred,
+ trig_transitional_activate,
+ th_proper_nn_find,
+ th_nn_interested,
+ th_nn_name
+};
+
+void
+trigproc_install_hooks(void)
+{
+ trigh = trig_our_hooks;
+}
+