]> err.no Git - dpkg/commitdiff
Implement triggers support
authorIan Jackson <ian@davenant.greenend.org.uk>
Sun, 30 Mar 2008 06:47:15 +0000 (09:47 +0300)
committerGuillem Jover <guillem@debian.org>
Sun, 30 Mar 2008 09:46:43 +0000 (12:46 +0300)
Closes: #17243, #68981, #215374, #217622, #248693, #308285
42 files changed:
ChangeLog
Makefile.am
configure.ac
debian/changelog
debian/control
debian/dpkg.install
doc/triggers.txt [new file with mode: 0644]
dselect/helpmsgs.cc
dselect/pkgdepcon.cc
dselect/pkgdisplay.cc
dselect/pkglist.cc
lib/.gitignore
lib/Makefile.am
lib/database.c
lib/dbmodify.c
lib/dlist.h [new file with mode: 0644]
lib/dpkg-db.h
lib/dpkg.h
lib/dump.c
lib/fields.c
lib/parse.c
lib/parsedump.h
lib/parsehelp.c
lib/trigdeferred.l [new file with mode: 0644]
lib/triglib.c [new file with mode: 0644]
po/POTFILES.in
src/Makefile.am
src/archives.c
src/configure.c
src/depcon.c
src/enquiry.c
src/filesdb.c
src/filesdb.h
src/help.c
src/main.c
src/main.h
src/packages.c
src/processarc.c
src/query.c
src/remove.c
src/trigcmd.c [new file with mode: 0644]
src/trigproc.c [new file with mode: 0644]

index 6aec5ce30e35cc837c6a45856efb4b0c8bbaf014..04709543c6d9d58e42f650f3a8e56b9be7a07028 100644 (file)
--- a/ChangeLog
+++ b/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.
index 6f31b2b454423b01c5c5b4989e168d75be48d213..77988c7002d89849f15b9e686c7c068fc32dd6e8 100644 (file)
@@ -26,6 +26,7 @@ EXTRA_DIST = \
        README.api \
        README.feature-removal-schedule \
        README.translators \
+       doc/triggers.txt \
        debian/archtable \
        debian/changelog \
        debian/compat \
index beb38fc826b88574b907b9640041ac847d7c749a..2e1c5780c355bc885ec090c4f0f48b6dfe112ff0 100644 (file)
@@ -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
 
index 278aa4c252d2f458076a07aefcbce2bff967ff0a..a708866ce11cb7bfc8bd59ede1b50faf83221ac1 100644 (file)
@@ -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
index 8393bf44d780777fe29c49a7bf3a18b790d39f7e..6decc6d382280e96fe8d14f2402a1f607b059ab7 100644 (file)
@@ -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
index 2f546183e768e182daebe5132631796257cad68a..c209845a5528f5001a812f06c1a7bed5d32a6294 100644 (file)
@@ -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
diff --git a/doc/triggers.txt b/doc/triggers.txt
new file mode 100644 (file)
index 0000000..0fc8a21
--- /dev/null
@@ -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.)
+
+--
+
+
index 9401467ab783946fa19def60143b8ab846836f6a..8a110f7d72f2099adb82e2508f5f5c48ac4f62cd 100644 (file)
@@ -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\
index b0d7a3340410f65a78c679cea9ba5eab0dfa709c..eea4f836c134f7966a4872bb939a977ec0ebd491 100644 (file)
@@ -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;
index 8d662194960478288df647630fb0f573128fadc4..6e9e5fce5bd38d840799c62fbd97479e7723356a 100644 (file)
@@ -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*=-_";
 
index f4eefdc14a52bce31469b8a9f9d5c3ac040fe653..cba3c5ad6751f494e99ec57c8a6fee80789871b8 100644 (file)
@@ -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:
index 83fafd57cc7de944d0d83a725081f8cc32301b15..b29a4354c26764cc90dfb0f416e96b826c06e210 100644 (file)
@@ -3,3 +3,4 @@
 .deps
 Makefile
 Makefile.in
+trigdeferred.c
index 12401a8fa48744c354a67c014cffe23e8bedf94c..e114041480212a6fcd252d2d1379dd602e1f4552 100644 (file)
@@ -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
index 398af9eeb9e44d8103a5d44338349e758c2316e8..d5afefa5439b4f0e53d91a5097badc78837520cb 100644 (file)
@@ -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);
 }
index 1a865a26f950b6cb5b79c8650c228a72b5dddd32..5c5a43119f778aa1596b531ab23dab119fc397f1 100644 (file)
@@ -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--;
 }
 
diff --git a/lib/dlist.h b/lib/dlist.h
new file mode 100644 (file)
index 0000000..d5adef7
--- /dev/null
@@ -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
index cb92f9d601418e69afa48c344ec8548f0097c384..04ea77d68768221ec66b362c69a5102c32789b11 100644 (file)
@@ -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);
index b3dd47a70d92f3b274ec7a35a583bb5f182ab752..ba6066c0aa9531f2bc6d7f19cb0935c9a3aa6102 100644 (file)
@@ -84,6 +84,7 @@
 #define PRERMFILE          "prerm"
 #define POSTRMFILE         "postrm"
 #define LISTFILE           "list"
+#define TRIGGERSCIFILE     "triggers"
 
 #define STATUSFILE        "status"
 #define AVAILFILE         "available"
 #define UPDATESDIR        "updates/"
 #define INFODIR           "info/"
 #define PARTSDIR          "parts/"
+#define TRIGGERSDIR       "triggers/"
+#define TRIGGERSFILEFILE  "File"
+#define TRIGGERSDEFERREDFILE "Unincorp"
+#define TRIGGERSLOCKFILE  "Lock"
 #define CONTROLDIRTMP     "tmp.ci/"
 #define IMPORTANTTMP      "tmp.i"
 #define REASSEMBLETMP     "reassemble" DEBEXT
 #define IMPORTANTMAXLEN    10
 #define IMPORTANTFMT      "%04d" /* change => also change lib/database.c:cleanup_updates */
-#define MAXUPDATES         50
+#define MAXUPDATES         250
+
+#define MAINTSCRIPTPKGENVVAR "DPKG_MAINTSCRIPT_PACKAGE"
+#define MAINTSCRIPTDPKGENVVAR "DPKG_RUNNING_VERSION"
 
 #define LOCALLIBDIR         "/usr/local/lib/dpkg"
 #define METHODSDIR          "methods"
 
 #define PKGSCRIPTMAXARGS     10
 #define MD5HASHLEN           32
+#define MAXTRIGDIRECTIVE     256
 
 #define CONFFOPTCELLS  /* int conffoptcells[2] {* 1= user edited *}              \
                                            [2] {* 1= distributor edited *} = */  \
index 562734b5bb1abb834f8b6756a9b7b9f35d8d05d6..a1fa09142c919b66c02eafc3ed47e141f4d7dc55 100644 (file)
@@ -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;
index b5dfae75cac99d8fc50dd4c832f47fbd942d38dd..76d1020c10bbd84baf7d58a4a197f2877a4afed8 100644 (file)
@@ -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);
+  }
+}
+
index 3de8a614b5a99615b5bbd3c6609b8dd9fe022535..d562566bcc7becdcb36a63bb61e0a31c9bd99cea 100644 (file)
@@ -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;
     }
index 2f7bb769a93097803d0b892af8b631fcd42489c5..43470f2daa03502be5227ae0c7c7065bdc6323c1 100644 (file)
@@ -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;
index f602fdea0f160f05d2961090d85247a0329a98da..4c823a947e622a3edc879566c10591f4540dd390 100644 (file)
@@ -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 */
diff --git a/lib/trigdeferred.l b/lib/trigdeferred.l
new file mode 100644 (file)
index 0000000..a7bcb11
--- /dev/null
@@ -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);
+}
+
diff --git a/lib/triglib.c b/lib/triglib.c
new file mode 100644 (file)
index 0000000..32f18f1
--- /dev/null
@@ -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
+};
+
index 26d15913fc0c570bc8430a39b8e9fa6dacfc4f6b..5ad5573b246a50ef19e9713832cf1f2c0b493954 100644 (file)
@@ -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
index 80e61e76ba869323b1916841c13c7c945df466b8..a1377e756f8096cebd3de4c493ea1bb226f2703f 100644 (file)
@@ -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
index cfd471f23ef00d196f0eb6c5549a98ba40ab1810..023678fef827cc8673e4314cc3df5c6c932a8706 100644 (file)
@@ -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),
index 71087ef48ec9ccdcdae380230d2a6b4514593806..cde2c688c10125c4037952b4ecf3ef240dabf819 100644 (file)
@@ -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;
index 3014ae864cda98a0b80c8d651ee74f477caf0c4b..42d7b44c979b946b515119299b4b1b551b747432 100644 (file)
@@ -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,
index 7e29606b1f84e991a9decea73a5dee3457c584dc..266bb16d342abedc7ddce9e08dc78274958f7cd8 100644 (file)
@@ -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"
index 46b3cbff160e1a73af0972e9b1f6d5ac8d35cd96..97fe7673ee3b247014d5d2a972df49231a0383b8 100644 (file)
@@ -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++;
 
index c4ed84773dfbc7b26f46ec870e315101a44525a1..3293338717aa940cb41794e57d2a05a64256cc88 100644 (file)
@@ -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 {
index 8e00a2df73e08474ddde91153d190ae6a3c681b3..81e9c5166719185268b28e11fc54b06f7e75f785 100644 (file)
@@ -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);
   }
index 5fac146520731b697cb7028c9afc94bb8a74a429..00b05440ee337d65f585acd630d167c07a6fc8f2 100644 (file)
@@ -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();
 
index add265d62b8ce4b2d913515c71d628c7ae415c10..3713be0019b85139b2d1e18fb2066a7b5238df8d 100644 (file)
@@ -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,
index 8f1ebeee10603a351e260de38b791d1876d30bf5..8d22ae4034481f9d261cf44f8c3b9c14a4df51a8 100644 (file)
@@ -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);
index d47a3f48bcf5082dfccb14442d397be2e84b91d8..1096e1abce3f87e03d2385c331e321d465dda4b0 100644 (file)
@@ -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, 
index 631ee58ce4fc184cf4cbb06b9ba38c8251c660ee..dd6d25a0d4c6475fad565bfc49e1ea22d7f5319d 100644 (file)
@@ -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),
index e98517026c189f018debef833771e00e3d9b7e4e..1e92d623cb9ad8ac07e690486e44cdc9a0b651f6 100644 (file)
@@ -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
diff --git a/src/trigcmd.c b/src/trigcmd.c
new file mode 100644 (file)
index 0000000..94fdf06
--- /dev/null
@@ -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;
+}
+
diff --git a/src/trigproc.c b/src/trigproc.c
new file mode 100644 (file)
index 0000000..b327f5d
--- /dev/null
@@ -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;
+}
+