Ian Jackson лет назад: 18
Родитель
Сommit
10440009b6
42 измененных файлов с 3416 добавлено и 42 удалено
  1. 161 0
      ChangeLog
  2. 1 0
      Makefile.am
  3. 1 0
      configure.ac
  4. 2 0
      debian/changelog
  5. 3 2
      debian/control
  6. 1 0
      debian/dpkg.install
  7. 895 0
      doc/triggers.txt
  8. 7 6
      dselect/helpmsgs.cc
  9. 6 2
      dselect/pkgdepcon.cc
  10. 3 1
      dselect/pkgdisplay.cc
  11. 4 0
      dselect/pkglist.cc
  12. 1 0
      lib/.gitignore
  13. 3 0
      lib/Makefile.am
  14. 3 0
      lib/database.c
  15. 35 0
      lib/dbmodify.c
  16. 66 0
      lib/dlist.h
  17. 130 1
      lib/dpkg-db.h
  18. 10 1
      lib/dpkg.h
  19. 82 2
      lib/dump.c
  20. 93 0
      lib/fields.c
  21. 34 0
      lib/parse.c
  22. 2 0
      lib/parsedump.h
  23. 2 0
      lib/parsehelp.c
  24. 202 0
      lib/trigdeferred.l
  25. 755 0
      lib/triglib.c
  26. 4 0
      po/POTFILES.in
  27. 10 1
      src/Makefile.am
  28. 13 4
      src/archives.c
  29. 13 0
      src/configure.c
  30. 10 1
      src/depcon.c
  31. 14 0
      src/enquiry.c
  32. 5 0
      src/filesdb.c
  33. 2 0
      src/filesdb.h
  34. 28 2
      src/help.c
  35. 12 0
      src/main.c
  36. 31 0
      src/main.h
  37. 111 11
      src/packages.c
  38. 29 4
      src/processarc.c
  39. 2 2
      src/query.c
  40. 7 2
      src/remove.c
  41. 230 0
      src/trigcmd.c
  42. 393 0
      src/trigproc.c

+ 161 - 0
ChangeLog

@@ -1,3 +1,164 @@
+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.

+ 1 - 0
Makefile.am

@@ -26,6 +26,7 @@ EXTRA_DIST = \
 	README.api \
 	README.feature-removal-schedule \
 	README.translators \
+	doc/triggers.txt \
 	debian/archtable \
 	debian/changelog \
 	debian/compat \

+ 1 - 0
configure.ac

@@ -53,6 +53,7 @@ AC_SUBST(admindir)
 # Checks for programs.
 AC_PROG_CC
 AC_PROG_CXX
+AC_PROG_LEX
 AC_PROG_RANLIB
 DPKG_PROG_PERL
 

+ 2 - 0
debian/changelog

@@ -38,6 +38,8 @@ dpkg (1.14.17) UNRELEASED; urgency=low
     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

+ 3 - 2
debian/control

@@ -11,7 +11,7 @@ Vcs-Browser: http://git.debian.org/?p=dpkg/dpkg.git
 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
 
@@ -20,7 +20,8 @@ Architecture: any
 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

+ 1 - 0
debian/dpkg.install

@@ -7,6 +7,7 @@ usr/bin/dpkg
 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

+ 895 - 0
doc/triggers.txt

@@ -0,0 +1,895 @@
+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.)
+
+--
+
+

+ 7 - 6
dselect/helpmsgs.cc

@@ -125,12 +125,13 @@ four columns for its current status on the system and mark.  In terse mode (use\
 \
  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\

+ 6 - 2
dselect/pkgdepcon.cc

@@ -39,7 +39,9 @@ int packagelist::useavailable(pkginfo *pkg) {
   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
@@ -411,7 +413,9 @@ int packagelist::deppossatisfied(deppossi *possi, perpackagestate **fixbyupgrade
       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;

+ 3 - 1
dselect/pkgdisplay.cc

@@ -54,6 +54,8 @@ const char
 			    N_("half installed"),
 			    N_("unpacked (not set up)"),
 			    N_("failed config"),
+			    N_("awaiting trigger processing"),
+			    N_("triggered"),
 			    N_("installed"),
 			    0 },
 
@@ -89,7 +91,7 @@ const char
 			       N_("bUG"),
 			       N_("?") };
 
-const char statuschars[] = " -IUC*";
+const char statuschars[] = " -IUCWt*";
 const char eflagchars[]=     " R?#";
 const char wantchars[]=     "n*=-_";
 

+ 4 - 0
dselect/pkglist.cc

@@ -182,6 +182,8 @@ void packagelist::ensurestatsortinfo() {
       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:
@@ -227,6 +229,8 @@ void packagelist::ensurestatsortinfo() {
       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:

+ 1 - 0
lib/.gitignore

@@ -3,3 +3,4 @@
 .deps
 Makefile
 Makefile.in
+trigdeferred.c

+ 3 - 0
lib/Makefile.am

@@ -15,6 +15,7 @@ libdpkg_a_SOURCES = \
 	dpkg-def.h \
 	dpkg.h \
 	dpkg-db.h \
+	dlist.h \
 	cleanup.c \
 	compat.c \
 	compression.c \
@@ -36,6 +37,8 @@ libdpkg_a_SOURCES = \
 	parsedump.h \
 	showpkg.c \
 	tarfn.c tarfn.h \
+	triglib.c \
+	trigdeferred.l \
 	utils.c \
 	varbuf.c \
 	vercmp.c

+ 3 - 0
lib/database.c

@@ -72,6 +72,9 @@ void blankpackage(struct pkginfo *pigp) {
   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);
 }

+ 35 - 0
lib/dbmodify.c

@@ -40,6 +40,7 @@
 #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;
@@ -129,6 +130,9 @@ static const struct fni {
   {   STATUSFILE,                 &statusfile         },
   {   AVAILFILE,                  &availablefile      },
   {   UPDATESDIR IMPORTANTTMP,    &importanttmpfile   },
+  {   TRIGGERSDIR,                &triggersdir        },
+  {   TRIGGERSDIR "/File",        &triggersfilefile   },
+  {   TRIGGERSDIR "/File.new",    &triggersnewfilefile},
   {   NULL, NULL                                      }
 };
 
@@ -192,6 +196,8 @@ enum modstatdb_rw modstatdb_init(const char *adir, enum modstatdb_rw readwritere
     uvb.buf= m_malloc(uvb.size);
   }
 
+  trig_incorporate(cstatus, admindir);
+
   return cstatus;
 }
 
@@ -234,11 +240,29 @@ void modstatdb_shutdown(void) {
   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);
@@ -269,6 +293,17 @@ void modstatdb_note(struct pkginfo *pkg) {
 
   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--;
 }
 

+ 66 - 0
lib/dlist.h

@@ -0,0 +1,66 @@
+/*
+ * 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

+ 130 - 1
lib/dpkg-db.h

@@ -107,6 +107,25 @@ struct pkginfoperfile { /* pif */
   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 */
@@ -131,6 +150,8 @@ struct pkginfo { /* pig */
     stat_halfinstalled,
     stat_unpacked,
     stat_halfconfigured,
+    stat_triggersawaited,
+    stat_triggerspending,
     stat_installed
   } status;
   enum pkgpriority {
@@ -146,6 +167,15 @@ struct pkginfo { /* pig */
   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 ***/
@@ -172,10 +202,109 @@ void modstatdb_note_ifwrite(struct pkginfo *pkg);
 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);

+ 10 - 1
lib/dpkg.h

@@ -84,6 +84,7 @@
 #define PRERMFILE          "prerm"
 #define POSTRMFILE         "postrm"
 #define LISTFILE           "list"
+#define TRIGGERSCIFILE     "triggers"
 
 #define STATUSFILE        "status"
 #define AVAILFILE         "available"
@@ -95,12 +96,19 @@
 #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"
@@ -122,6 +130,7 @@
 
 #define PKGSCRIPTMAXARGS     10
 #define MD5HASHLEN           32
+#define MAXTRIGDIRECTIVE     256
 
 #define CONFFOPTCELLS  /* int conffoptcells[2] {* 1= user edited *}              \
                                            [2] {* 1= distributor edited *} = */  \

+ 82 - 2
lib/dump.c

@@ -64,7 +64,11 @@ void w_configversion(struct varbuf *vb,
                      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);
@@ -161,7 +165,37 @@ void w_status(struct varbuf *vb,
   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,' ');
@@ -239,6 +273,52 @@ void w_conffiles(struct varbuf *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;

+ 93 - 0
lib/fields.c

@@ -453,3 +453,96 @@ void f_dependency(struct pkginfo *pigp, struct pkginfoperfile *pifp,
   }
 }
 
+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);
+  }
+}
+

+ 34 - 0
lib/parse.c

@@ -30,6 +30,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <assert.h>
 
 #include <dpkg.h>
 #include <dpkg-db.h>
@@ -70,6 +71,8 @@ const struct fieldinfo fieldinfos[]= {
   { "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 */                               }
 };
@@ -86,6 +89,7 @@ int parsedb(const char *filename, enum parsedbflags flags,
   struct pkginfo newpig, *pigp;
   struct pkginfoperfile *newpifp, *pifp;
   struct arbitraryfield *arp, **larpp;
+  struct trigaw *ta;
   int lno;
   int pdone;
   int fieldencountered[NFIELDS];
@@ -260,6 +264,26 @@ int parsedb(const char *filename, enum parsedbflags flags,
       }
     }
 
+    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).
@@ -307,6 +331,16 @@ int parsedb(const char *filename, enum parsedbflags flags,
       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;
     }

+ 2 - 0
lib/parsedump.h

@@ -38,6 +38,7 @@ typedef void freadfunction(struct pkginfo *pigp, struct pkginfoperfile *pifp,
 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 */
@@ -49,6 +50,7 @@ typedef void fwritefunction(struct varbuf*,
 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;

+ 2 - 0
lib/parsehelp.c

@@ -87,6 +87,8 @@ const struct namevalue statusinfos[]= {  /* Note !  These must be in order ! */
   { "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 */

+ 202 - 0
lib/trigdeferred.l

@@ -0,0 +1,202 @@
+/*
+ * 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);
+}
+

+ 755 - 0
lib/triglib.c

@@ -0,0 +1,755 @@
+/*
+ * 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
+};
+

+ 4 - 0
po/POTFILES.in

@@ -17,6 +17,8 @@ lib/parsehelp.c
 lib/showcright.c
 lib/showpkg.c
 lib/tarfn.c
+lib/trigdeferred.c
+lib/triglib.c
 lib/utils.c
 lib/varbuf.c
 lib/vercmp.c
@@ -35,6 +37,8 @@ src/processarc.c
 src/query.c
 src/remove.c
 src/select.c
+src/trigcmd.c
+src/trigproc.c
 src/update.c
 
 dpkg-deb/build.c

+ 10 - 1
src/Makefile.am

@@ -8,7 +8,7 @@ INCLUDES = \
 	-I$(top_srcdir)/lib
 
 
-bin_PROGRAMS = dpkg dpkg-query
+bin_PROGRAMS = dpkg dpkg-query dpkg-trigger
 
 dpkg_SOURCES = \
 	archives.c archives.h \
@@ -24,6 +24,7 @@ dpkg_SOURCES = \
 	processarc.c \
 	remove.c \
 	select.c \
+	trigproc.c \
 	update.c
 
 dpkg_LDADD = \
@@ -40,6 +41,14 @@ dpkg_query_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

+ 13 - 4
src/archives.c

@@ -980,7 +980,9 @@ void check_conflict(struct dependency *dep, struct pkginfo *pkg,
       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);
@@ -1081,6 +1083,8 @@ void archivefiles(const char *const *argv) {
   const char **arglist;
   char *p;
 
+  trigproc_install_hooks();
+
   modstatdb_init(admindir,
                  f_noact ?                     msdbrw_readonly
                : cipaction->arg == act_avail ? msdbrw_write
@@ -1198,6 +1202,7 @@ void archivefiles(const char *const *argv) {
   switch (cipaction->arg) {
   case act_install:
   case act_configure:
+  case act_triggers:
   case act_remove:
   case act_purge:
     process_queue();
@@ -1208,6 +1213,7 @@ void archivefiles(const char *const *argv) {
     internerr("unknown action");
   }
 
+  trigproc_run_deferred();
   modstatdb_shutdown();
 }
 
@@ -1238,15 +1244,18 @@ int wanttoinstall(struct pkginfo *pkg, const struct versionrevision *ver, int sa
     }
   }
 
-  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),

+ 13 - 0
src/configure.c

@@ -107,6 +107,15 @@ void deferred_configure(struct pkginfo *pkg) {
 		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;
@@ -134,6 +143,8 @@ void deferred_configure(struct pkginfo *pkg) {
 	       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;
@@ -237,6 +248,7 @@ void deferred_configure(struct pkginfo *pkg) {
 					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"),
@@ -272,6 +284,7 @@ void deferred_configure(struct pkginfo *pkg) {
 					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;

+ 10 - 1
src/depcon.c

@@ -227,9 +227,11 @@ int depisok(struct dependency *dep, struct varbuf *whynot,
   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:
@@ -277,6 +279,7 @@ int depisok(struct dependency *dep, struct varbuf *whynot,
       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,
@@ -291,6 +294,7 @@ int depisok(struct dependency *dep, struct varbuf *whynot,
           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"),
@@ -311,6 +315,7 @@ int depisok(struct dependency *dep, struct varbuf *whynot,
               return 1;
             }
           }
+          /* Fall through. */
         default:
           sprintf(linebuf, _("  %.250s is %s.\n"),
                   possi->ed->name, gettext(statusstrings[possi->ed->status]));
@@ -421,6 +426,8 @@ int depisok(struct dependency *dep, struct varbuf *whynot,
         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,
@@ -484,6 +491,8 @@ int depisok(struct dependency *dep, struct varbuf *whynot,
           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,

+ 14 - 0
src/enquiry.c

@@ -96,6 +96,16 @@ static const struct badstatinfo badstatinfos[]= {
     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
   }
@@ -150,6 +160,8 @@ static int yettobeunpacked(struct pkginfo *pkg, const char **thissect) {
 
   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)
@@ -254,8 +266,10 @@ static void assertversion(const char *const *argv,
   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"

+ 5 - 0
src/filesdb.c

@@ -55,6 +55,7 @@ ensure_package_clientdata(struct pkginfo *pkg)
   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) {
@@ -583,6 +584,9 @@ struct filenamenode *findnamenode(const char *name, enum fnnflags flags) {
   }
   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] == '/')
@@ -597,6 +601,7 @@ struct filenamenode *findnamenode(const char *name, enum fnnflags flags) {
   newnode->divert = NULL;
   newnode->statoverride = NULL;
   newnode->filestat = NULL;
+  newnode->trig_interested = NULL;
   *pointerp= newnode;
   nfiles++;
 

+ 2 - 0
src/filesdb.h

@@ -50,6 +50,7 @@ struct pkginfo;
 
 enum fnnflags {
     fnn_nocopy=                 000001, /* do not need to copy filename */
+    fnn_nonew =                 000002, /* findnamenode may return NULL */
 };
 
 struct filenamenode {
@@ -72,6 +73,7 @@ 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 {

+ 28 - 2
src/help.c

@@ -43,13 +43,18 @@ const char *const statusstrings[]= {
   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);
@@ -64,6 +69,9 @@ struct filenamenode *namenodetouse(struct filenamenode *namenode, struct pkginfo
         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;
 }
 
@@ -220,14 +228,29 @@ static void script_catchsignals(void) {
 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
@@ -257,6 +280,9 @@ static int do_script(const char *pkg, const char *scriptname, const char *script
       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);
   }

+ 12 - 0
src/main.c

@@ -67,6 +67,7 @@ usage(void)
 "  --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"
@@ -113,6 +114,7 @@ usage(void)
 "  -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"
@@ -154,6 +156,7 @@ const char printforhelp[]= 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;
@@ -224,6 +227,9 @@ static void setdebug(const struct cmdinfo *cpi, const char *value) {
 "    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"
@@ -391,6 +397,7 @@ static const struct cmdinfo cmdinfos[]= {
   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   ),
@@ -428,6 +435,8 @@ static const struct cmdinfo cmdinfos[]= {
   /* 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 },
@@ -615,6 +624,9 @@ int main(int argc, const char *const *argv) {
   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();
 

+ 31 - 0
src/main.h

@@ -41,6 +41,9 @@ struct perpackagestate {
   int fileslistvalid;
   struct fileinlist *files;
   int replacingfilesandsaid;
+
+  /* Non-NULL iff in trigproc.c:deferred. */
+  struct pkginqueue *trigprocdeferred;
 };
 
 struct packageinlist {
@@ -55,6 +58,7 @@ struct pkginqueue {
 };
 
 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,
@@ -89,6 +93,7 @@ extern const char *const statusstrings[];
 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;
@@ -202,6 +207,10 @@ int chmodsafe_unlink(const char *pathname, const char **failed);
 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 ... */
@@ -214,7 +223,13 @@ int maintainer_script_alternative(struct pkginfo *pkg,
                                   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);
@@ -232,12 +247,28 @@ enum debugflags {
   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,

+ 111 - 11
src/packages.c

@@ -40,6 +40,7 @@
 #include "filesdb.h"
 #include "main.h"
 
+static struct pkginfo *progress_bytrigproc;
 static PKGQUEUE_DEF_INIT(queue);
 
 int sincenothing = 0, dependtry = 0;
@@ -89,6 +90,8 @@ void packages(const char *const *argv) {
   const char *thisarg;
   size_t l;
   
+  trigproc_install_hooks();
+
   modstatdb_init(admindir,
                  f_noact ?    msdbrw_readonly
                : fc_nonroot ? msdbrw_write
@@ -105,7 +108,15 @@ void packages(const char *const *argv) {
     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;
@@ -147,6 +158,7 @@ void packages(const char *const *argv) {
   ensure_diversions();
 
   process_queue();
+  trigproc_run_deferred();
 
   modstatdb_shutdown();
 }
@@ -154,12 +166,14 @@ void packages(const char *const *argv) {
 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");
@@ -169,6 +183,7 @@ void process_queue(void) {
     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);
@@ -193,6 +208,20 @@ void process_queue(void) {
     
     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)) {
@@ -203,17 +232,24 @@ void process_queue(void) {
       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);
@@ -266,10 +302,21 @@ void process_queue(void) {
  * 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,
@@ -299,6 +346,8 @@ static int deppossi_ok_found(struct pkginfo *possdependee,
   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)) {
@@ -312,15 +361,51 @@ static int deppossi_ok_found(struct pkginfo *possdependee,
       (*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);
@@ -338,6 +423,7 @@ static int deppossi_ok_found(struct pkginfo *possdependee,
       debug(dbg_depcondetail, "      not configured/able");
       goto unsuitable;
     }
+
   default:
     if (providing) {
       varbufprintf(oemsgs,
@@ -437,10 +523,11 @@ int breakses_ok(struct pkginfo *pkg, struct varbuf *aemsgs) {
 
 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;
@@ -449,11 +536,14 @@ int dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
         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) {
@@ -461,6 +551,7 @@ int dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
         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) {
@@ -471,6 +562,7 @@ int dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
             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;
           }
@@ -479,10 +571,12 @@ int dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
       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, " ");
@@ -501,6 +595,10 @@ int dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
       }
       break;
     case 1:
+      if (possfixbytrig)
+        canfixbytrig = possfixbytrig;
+      else
+        anycannotfixbytrig = 1;
       if (ok>1) ok= 1;
       break;
     case 3:
@@ -511,6 +609,8 @@ int dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
   }
   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);

+ 29 - 4
src/processarc.c

@@ -282,7 +282,8 @@ void process_archive(const char *filename) {
   
   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,
@@ -299,9 +300,15 @@ void process_archive(const char *filename) {
     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);
@@ -387,7 +394,10 @@ void process_archive(const char *filename) {
   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);
@@ -408,6 +418,7 @@ void process_archive(const char *filename) {
     else
       printf(_("De-configuring %s ...\n"), deconpil->pkg->name);
 
+    trig_activate_packageprocessing(deconpil->pkg);
     deconpil->pkg->status= stat_halfconfigured;
     modstatdb_note(deconpil->pkg);
 
@@ -439,7 +450,10 @@ void process_archive(const char *filename) {
 
   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,
@@ -738,6 +752,16 @@ void process_archive(const char *filename) {
    */
   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
@@ -985,6 +1009,7 @@ void process_archive(const char *filename) {
      * 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, 

+ 2 - 2
src/query.c

@@ -124,7 +124,7 @@ static void list1package(struct pkginfo *pkg, int *head,
   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 */
@@ -138,7 +138,7 @@ Desired=Unknown/Install/Remove/Purge/Hold\n\
   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),

+ 7 - 2
src/remove.c

@@ -66,7 +66,10 @@ static void checkforremoval(struct pkginfo *pkgtoremove,
     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;
@@ -163,7 +166,8 @@ void deferred_remove(struct pkginfo *pkg) {
     
   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;
@@ -404,6 +408,7 @@ static void removal_bulk_remove_configfiles(struct pkginfo *pkg) {
 
     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

+ 230 - 0
src/trigcmd.c

@@ -0,0 +1,230 @@
+/*
+ * 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;
+}
+

+ 393 - 0
src/trigproc.c

@@ -0,0 +1,393 @@
+/*
+ * 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;
+}
+