From 10440009b68f59eeed4cb1b56547e3cf356aa540 Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Sun, 30 Mar 2008 09:47:15 +0300 Subject: [PATCH] Implement triggers support Closes: #17243, #68981, #215374, #217622, #248693, #308285 --- ChangeLog | 161 ++++++++ Makefile.am | 1 + configure.ac | 1 + debian/changelog | 2 + debian/control | 5 +- debian/dpkg.install | 1 + doc/triggers.txt | 895 ++++++++++++++++++++++++++++++++++++++++++ dselect/helpmsgs.cc | 13 +- dselect/pkgdepcon.cc | 8 +- dselect/pkgdisplay.cc | 4 +- dselect/pkglist.cc | 4 + lib/.gitignore | 1 + lib/Makefile.am | 3 + lib/database.c | 3 + lib/dbmodify.c | 35 ++ lib/dlist.h | 66 ++++ lib/dpkg-db.h | 131 ++++++- lib/dpkg.h | 11 +- lib/dump.c | 84 +++- lib/fields.c | 93 +++++ lib/parse.c | 34 ++ lib/parsedump.h | 2 + lib/parsehelp.c | 2 + lib/trigdeferred.l | 202 ++++++++++ lib/triglib.c | 755 +++++++++++++++++++++++++++++++++++ po/POTFILES.in | 4 + src/Makefile.am | 11 +- src/archives.c | 17 +- src/configure.c | 13 + src/depcon.c | 11 +- src/enquiry.c | 14 + src/filesdb.c | 5 + src/filesdb.h | 2 + src/help.c | 30 +- src/main.c | 12 + src/main.h | 31 ++ src/packages.c | 122 +++++- src/processarc.c | 33 +- src/query.c | 4 +- src/remove.c | 9 +- src/trigcmd.c | 230 +++++++++++ src/trigproc.c | 393 +++++++++++++++++++ 42 files changed, 3416 insertions(+), 42 deletions(-) create mode 100644 doc/triggers.txt create mode 100644 lib/dlist.h create mode 100644 lib/trigdeferred.l create mode 100644 lib/triglib.c create mode 100644 src/trigcmd.c create mode 100644 src/trigproc.c diff --git a/ChangeLog b/ChangeLog index 6aec5ce3..04709543 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,164 @@ +2008-03-30 Ian Jackson , + Guillem Jover + + * 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 . + (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 * src/packages.c (deppossi_ok_found): Refactor returning code. diff --git a/Makefile.am b/Makefile.am index 6f31b2b4..77988c70 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,6 +26,7 @@ EXTRA_DIST = \ README.api \ README.feature-removal-schedule \ README.translators \ + doc/triggers.txt \ debian/archtable \ debian/changelog \ debian/compat \ diff --git a/configure.ac b/configure.ac index beb38fc8..2e1c5780 100644 --- a/configure.ac +++ b/configure.ac @@ -53,6 +53,7 @@ AC_SUBST(admindir) # Checks for programs. AC_PROG_CC AC_PROG_CXX +AC_PROG_LEX AC_PROG_RANLIB DPKG_PROG_PERL diff --git a/debian/changelog b/debian/changelog index 278aa4c2..a708866c 100644 --- a/debian/changelog +++ b/debian/changelog @@ -38,6 +38,8 @@ dpkg (1.14.17) UNRELEASED; urgency=low dpkg test suite can be skept if desired. * Improve log and status-fd output by printing more status change updates and actions. Thanks to Ian Jackson. + * Implement triggers support. Thanks to Ian Jackson. + Closes: #17243, #68981, #215374, #217622, #248693, #308285 [ Raphael Hertzog ] * Add a warning displayed by dpkg-genchanges if the current version is diff --git a/debian/control b/debian/control index 8393bf44..6decc6d3 100644 --- a/debian/control +++ b/debian/control @@ -11,7 +11,7 @@ Vcs-Browser: http://git.debian.org/?p=dpkg/dpkg.git Vcs-Git: git://git.debian.org/git/dpkg/dpkg.git Standards-Version: 3.7.3 Build-Depends: debhelper (>= 4.1.81), pkg-config, po4a (>= 0.23), - libncursesw5-dev, zlib1g-dev (>= 1:1.1.3-19.1), libbz2-dev, + libncursesw5-dev, zlib1g-dev (>= 1:1.1.3-19.1), libbz2-dev, flex, libselinux1-dev (>= 1.28-4) [!hurd-i386 !kfreebsd-i386 !kfreebsd-amd64], libtimedate-perl, libio-string-perl @@ -20,7 +20,8 @@ Architecture: any Essential: yes Pre-Depends: ${shlibs:Depends}, coreutils (>= 5.93-1) Conflicts: sysvinit (<< 2.82-1), dpkg-iasearch (<< 0.11), dpkg-static, - dpkg-dev (<< 1.14.6), dpkg-dev (= 1.14.13), dpkg-dev (= 1.14.14) + dpkg-dev (<< 1.14.6), dpkg-dev (= 1.14.13), dpkg-dev (= 1.14.14), + apt (<< 0.7.7), aptitude (<< 0.4.7-1) Replaces: dpkg-doc-ja, dpkg-static, manpages-de (<= 0.4-3), manpages-pl (<= 20051117-1) Suggests: apt, lzma diff --git a/debian/dpkg.install b/debian/dpkg.install index 2f546183..c209845a 100644 --- a/debian/dpkg.install +++ b/debian/dpkg.install @@ -7,6 +7,7 @@ usr/bin/dpkg usr/bin/dpkg-deb usr/bin/dpkg-query usr/bin/dpkg-split +usr/bin/dpkg-trigger usr/lib/dpkg/mksplit usr/sbin usr/share/dpkg diff --git a/doc/triggers.txt b/doc/triggers.txt new file mode 100644 index 00000000..0fc8a21a --- /dev/null +++ b/doc/triggers.txt @@ -0,0 +1,895 @@ +TRIGGERS +======== + +Introduction +------------ + +A dpkg trigger is a facility that allows events caused by one package +but of interest to another package to be recorded and aggregated, and +processed later by the interested package. This feature simplifies +various registration and system-update tasks and reduces duplication +of processing. + +(NB: Triggers are intended for events that occur during package +installation, not events that occur in general operation.) + + +Concepts +-------- + +Each trigger is named, and at any time zero or more packages may be +interested in it. + +We currently envisage three kinds of triggers: + * Explicit triggers. These can be activated by any program + by running dpkg-trigger (at any time, but ideally from a maintainer + script). + * File triggers. These are activated automatically by dpkg + when a matching file is installed, upgraded or removed as part + of a package. They may also be explicitly activated by running + dpkg-trigger. + * Future kinds of special triggers, which are activated by magic code + in dpkg itself. Currently none are defined besides file triggers. + +A trigger is always activated by a particular package. + +Trigger names contain only printing 7-bit ascii characters (no +whitespace). Each trigger kind has a distinct subset of the trigger +name space so that the kind can be determined from the name. After we +run out of straightforward syntaxes, we will use :
. + +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 " ..." + +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 + + 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 + + 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 or + --triggers-only 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 if needed usually[1] must, or trigproc + --triggers-only -a any needed usually[1] none + --triggers-only 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 ] + dpkg-trigger --no-await + +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 ... or dpkg --suppress-triggers --configure +..., 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/ 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/, 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 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 tuples.) + +(See `Processing' above for discussion of dependency cycles.) + +-- + + diff --git a/dselect/helpmsgs.cc b/dselect/helpmsgs.cc index 9401467a..8a110f7d 100644 --- a/dselect/helpmsgs.cc +++ b/dselect/helpmsgs.cc @@ -125,12 +125,13 @@ four columns for its current status on the system and mark. In terse mode (use\ \ Error flag: Space - no error (but package may be in broken state - see below)\n\ `R' - serious error during installation, needs reinstallation;\n\ - Installed state: Space - not installed;\n\ - `*' - installed;\n\ - `-' - not installed but config files remain;\n\ - packages in { `U' - unpacked but not yet configured;\n\ - these states { `C' - half-configured (an error happened);\n\ - are broken { `I' - half-installed (an error happened).\n\ + Installed state: Space - not installed;\n\ + `*' - installed;\n\ + `-' - not installed but config files remain;\n\ + packages in these { `U' - unpacked but not yet configured;\n\ + states are not { `C' - half-configured (an error happened);\n\ + (quite) properly { `I' - half-installed (an error happened).\n\ + installed { `W',`t' - triggers are awaited resp. pending.\n\ Old mark: what was requested for this package before presenting this list;\n\ Mark: what is requested for this package:\n\ `*': marked for installation or upgrade;\n\ diff --git a/dselect/pkgdepcon.cc b/dselect/pkgdepcon.cc index b0d7a334..eea4f836 100644 --- a/dselect/pkgdepcon.cc +++ b/dselect/pkgdepcon.cc @@ -39,7 +39,9 @@ int packagelist::useavailable(pkginfo *pkg) { if (pkg->clientdata && pkg->clientdata->selected == pkginfo::want_install && informative(pkg,&pkg->available) && - (pkg->status != pkginfo::stat_installed || + (!(pkg->status == pkginfo::stat_installed || + pkg->status == pkginfo::stat_triggersawaited || + pkg->status == pkginfo::stat_triggerspending) || versioncompare(&pkg->available.version,&pkg->installed.version) > 0)) return 1; else @@ -411,7 +413,9 @@ int packagelist::deppossatisfied(deppossi *possi, perpackagestate **fixbyupgrade if (useavailable(provider->up->up)) return 1; if (fixbyupgrade && !*fixbyupgrade && - (provider->up->up->status != pkginfo::stat_installed || + (!(provider->up->up->status == pkginfo::stat_installed || + provider->up->up->status == pkginfo::stat_triggerspending || + provider->up->up->status == pkginfo::stat_triggersawaited) || versioncompare(&provider->up->up->available.version, &provider->up->up->installed.version) > 1)) *fixbyupgrade= provider->up->up->clientdata; diff --git a/dselect/pkgdisplay.cc b/dselect/pkgdisplay.cc index 8d662194..6e9e5fce 100644 --- a/dselect/pkgdisplay.cc +++ b/dselect/pkgdisplay.cc @@ -54,6 +54,8 @@ const char N_("half installed"), N_("unpacked (not set up)"), N_("failed config"), + N_("awaiting trigger processing"), + N_("triggered"), N_("installed"), 0 }, @@ -89,7 +91,7 @@ const char N_("bUG"), N_("?") }; -const char statuschars[] = " -IUC*"; +const char statuschars[] = " -IUCWt*"; const char eflagchars[]= " R?#"; const char wantchars[]= "n*=-_"; diff --git a/dselect/pkglist.cc b/dselect/pkglist.cc index f4eefdc1..cba3c5ad 100644 --- a/dselect/pkglist.cc +++ b/dselect/pkglist.cc @@ -182,6 +182,8 @@ void packagelist::ensurestatsortinfo() { case pkginfo::stat_unpacked: case pkginfo::stat_halfconfigured: case pkginfo::stat_halfinstalled: + case pkginfo::stat_triggersawaited: + case pkginfo::stat_triggerspending: table[index]->ssavail= ssa_broken; break; case pkginfo::stat_notinstalled: @@ -227,6 +229,8 @@ void packagelist::ensurestatsortinfo() { case pkginfo::stat_unpacked: case pkginfo::stat_halfconfigured: case pkginfo::stat_halfinstalled: + case pkginfo::stat_triggersawaited: + case pkginfo::stat_triggerspending: table[index]->ssstate= sss_broken; break; case pkginfo::stat_notinstalled: diff --git a/lib/.gitignore b/lib/.gitignore index 83fafd57..b29a4354 100644 --- a/lib/.gitignore +++ b/lib/.gitignore @@ -3,3 +3,4 @@ .deps Makefile Makefile.in +trigdeferred.c diff --git a/lib/Makefile.am b/lib/Makefile.am index 12401a8f..e1140414 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -15,6 +15,7 @@ libdpkg_a_SOURCES = \ dpkg-def.h \ dpkg.h \ dpkg-db.h \ + dlist.h \ cleanup.c \ compat.c \ compression.c \ @@ -36,6 +37,8 @@ libdpkg_a_SOURCES = \ parsedump.h \ showpkg.c \ tarfn.c tarfn.h \ + triglib.c \ + trigdeferred.l \ utils.c \ varbuf.c \ vercmp.c diff --git a/lib/database.c b/lib/database.c index 398af9ee..d5afefa5 100644 --- a/lib/database.c +++ b/lib/database.c @@ -72,6 +72,9 @@ void blankpackage(struct pkginfo *pigp) { pigp->available.valid= 0; pigp->clientdata= NULL; pigp->color= white; + pigp->trigaw.head = pigp->trigaw.tail = NULL; + pigp->othertrigaw_head = NULL; + pigp->trigpend_head = NULL; blankpackageperfile(&pigp->installed); blankpackageperfile(&pigp->available); } diff --git a/lib/dbmodify.c b/lib/dbmodify.c index 1a865a26..5c5a4311 100644 --- a/lib/dbmodify.c +++ b/lib/dbmodify.c @@ -40,6 +40,7 @@ #include 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 index 00000000..d5adef7b --- /dev/null +++ b/lib/dlist.h @@ -0,0 +1,66 @@ +/* + * dlist.h - macros for handling doubly linked lists + * + * Copyright (C) 1997-1999 Ian Jackson + * + * 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 diff --git a/lib/dpkg-db.h b/lib/dpkg-db.h index cb92f9d6..04ea77d6 100644 --- a/lib/dpkg-db.h +++ b/lib/dpkg-db.h @@ -107,6 +107,25 @@ struct pkginfoperfile { /* pif */ struct arbitraryfield *arbs; }; +struct trigpend { + /* Node indicates that parent's Triggers-Pending mentions name. */ + /* NB that these nodes do double duty: after they're removed from + * a package's trigpend list, references may be preserved by the + * trigger cycle checker (see trigproc.c). + */ + struct trigpend *next; + char *name; +}; + +struct trigaw { + /* Node indicates that aw's Triggers-Awaited mentions pend. */ + struct pkginfo *aw, *pend; + struct trigaw *nextsamepend; + struct { + struct trigaw *next, *back; + } sameaw; +}; + struct perpackagestate; /* dselect and dpkg have different versions of this */ struct pkginfo { /* pig */ @@ -131,6 +150,8 @@ struct pkginfo { /* pig */ stat_halfinstalled, stat_unpacked, stat_halfconfigured, + stat_triggersawaited, + stat_triggerspending, stat_installed } status; enum pkgpriority { @@ -146,6 +167,15 @@ struct pkginfo { /* pig */ struct pkginfoperfile available; struct perpackagestate *clientdata; enum { white, gray, black } color; /* used during cycle detection */ + + struct { + /* ->aw == this */ + struct trigaw *head, *tail; + } trigaw; + + /* ->pend == this, non-NULL for us when Triggers-Pending. */ + struct trigaw *othertrigaw_head; + struct trigpend *trigpend_head; }; /*** from lock.c ***/ @@ -172,10 +202,109 @@ void modstatdb_note_ifwrite(struct pkginfo *pkg); void modstatdb_checkpoint(void); void modstatdb_shutdown(void); -extern char *statusfile, *availablefile; /* initialised by modstatdb_init */ +/* Initialised by modstatdb_init. */ +extern char *statusfile, *availablefile; +extern char *triggersdir, *triggersfilefile, *triggersnewfilefile; const char *pkgadminfile(struct pkginfo *pkg, const char *whichfile); +/*** from trigdeferred.l ***/ + +enum trigdef_updateflags { + tduf_nolockok = 001, + tduf_write = 002, + tduf_nolock = 003, + /* Should not be set unless _write is. */ + tduf_writeifempty = 010, + tduf_writeifenoent = 020, +}; + +struct trigdefmeths { + void (*trig_begin)(const char *trig); + void (*package)(const char *awname); + void (*trig_end)(void); +}; + +extern const struct trigdefmeths *trigdef; +extern FILE *trig_new_deferred; + +/* Return values: + * -1 Lock ENOENT with O_CREAT (directory does not exist) + * -2 Unincorp empty, tduf_writeifempty unset + * -3 Unincorp ENOENT, tduf_writeifenoent unset + * 1 Unincorp ENOENT, tduf_writeifenoent set } caller must call + * 2 ok } trigdef_update_done! + */ +int trigdef_update_start(enum trigdef_updateflags uf, const char *admindir); + +int trigdef_yylex(void); +void trigdef_process_done(void); + +/*** hooks for more sophisticated processing in dpkg proper ***/ + +/* We do things like this so we can get most of the trigger tracking + * in dpkg-query, dselect, and so on, but avoid the transitional + * processing and deferred trigproc queue management other than when + * we're actually doing real package management work. */ + +struct trigfileint { + struct pkginfo *pkg; + struct filenamenode *fnn; + struct trigfileint *samefile_next; + struct { + struct trigfileint *next, *back; + } inoverall; +}; + +struct trig_hooks { + /* The first two are normally NULL. + * If non-NULL, we're dpkg proper and we might need to invent trigger + * activations as the first run of a triggers-supporting dpkg. + */ + void (*enqueue_deferred)(struct pkginfo *pend); + void (*transitional_activate)(enum modstatdb_rw cstatus); + + struct filenamenode *(*namenode_find)(const char *filename, int nonew); + struct trigfileint **(*namenode_interested)(struct filenamenode *fnn); + + /* Returns a pointer from nfmalloc. */ + const char *(*namenode_name)(struct filenamenode *fnn); +}; + +extern struct trig_hooks trigh; + +#define TRIGHOOKS_DEFINE_NAMENODE_ACCESSORS \ + static struct trigfileint **th_nn_interested(struct filenamenode *fnn) \ + { return &fnn->trig_interested; } \ + static const char *th_nn_name(struct filenamenode *fnn) \ + { return fnn->name; } + +/*** from triglib.c ***/ + +void trig_file_activate_byname(const char *trig, struct pkginfo *aw); +void trig_file_activate(struct filenamenode *trig, struct pkginfo *aw); + +int trig_note_pend_core(struct pkginfo *pend, char *trig /*not copied!*/); +int trig_note_pend(struct pkginfo *pend, char *trig /*not copied!*/); +int trig_note_aw(struct pkginfo *pend, struct pkginfo *aw); +void trig_clear_awaiters(struct pkginfo *notpend); + +void trig_file_interests_ensure(void); +void trig_file_interests_save(void); + +void trig_cicb_interest_delete(const char *trig, void *user); +void trig_cicb_interest_add(const char *trig, void *user); +typedef void trig_parse_cicb(const char *trig, void *user); +void trig_parse_ci(const char *file, trig_parse_cicb *interest, + trig_parse_cicb *activate, void *user); + +/* Called by process_archive. */ +void trig_cicb_statuschange_activate(const char *trig, void *user); + +void trig_incorporate(enum modstatdb_rw cstatus, const char *admindir); + +const char *illegal_triggername(const char *p); + /*** from database.c ***/ struct pkginfo *findpackage(const char *name); diff --git a/lib/dpkg.h b/lib/dpkg.h index b3dd47a7..ba6066c0 100644 --- a/lib/dpkg.h +++ b/lib/dpkg.h @@ -84,6 +84,7 @@ #define PRERMFILE "prerm" #define POSTRMFILE "postrm" #define LISTFILE "list" +#define TRIGGERSCIFILE "triggers" #define STATUSFILE "status" #define AVAILFILE "available" @@ -95,12 +96,19 @@ #define UPDATESDIR "updates/" #define INFODIR "info/" #define PARTSDIR "parts/" +#define TRIGGERSDIR "triggers/" +#define TRIGGERSFILEFILE "File" +#define TRIGGERSDEFERREDFILE "Unincorp" +#define TRIGGERSLOCKFILE "Lock" #define CONTROLDIRTMP "tmp.ci/" #define IMPORTANTTMP "tmp.i" #define REASSEMBLETMP "reassemble" DEBEXT #define IMPORTANTMAXLEN 10 #define IMPORTANTFMT "%04d" /* change => also change lib/database.c:cleanup_updates */ -#define MAXUPDATES 50 +#define MAXUPDATES 250 + +#define MAINTSCRIPTPKGENVVAR "DPKG_MAINTSCRIPT_PACKAGE" +#define MAINTSCRIPTDPKGENVVAR "DPKG_RUNNING_VERSION" #define LOCALLIBDIR "/usr/local/lib/dpkg" #define METHODSDIR "methods" @@ -122,6 +130,7 @@ #define PKGSCRIPTMAXARGS 10 #define MD5HASHLEN 32 +#define MAXTRIGDIRECTIVE 256 #define CONFFOPTCELLS /* int conffoptcells[2] {* 1= user edited *} \ [2] {* 1= distributor edited *} = */ \ diff --git a/lib/dump.c b/lib/dump.c index 562734b5..a1fa0914 100644 --- a/lib/dump.c +++ b/lib/dump.c @@ -64,7 +64,11 @@ void w_configversion(struct varbuf *vb, enum fwriteflags flags, const struct fieldinfo *fip) { if (pifp != &pigp->installed) return; if (!informativeversion(&pigp->configversion)) return; - if (pigp->status == stat_installed || pigp->status == stat_notinstalled) return; + if (pigp->status == stat_installed || + pigp->status == stat_notinstalled || + pigp->status == stat_triggerspending || + pigp->status == stat_triggersawaited) + return; if (flags&fw_printheader) varbufaddstr(vb,"Config-Version: "); varbufversion(vb,&pigp->configversion,vdew_nonambig); @@ -161,7 +165,37 @@ void w_status(struct varbuf *vb, if (pifp != &pigp->installed) return; assert(pigp->want <= want_purge); assert(pigp->eflag <= eflagv_reinstreq); /* hold and hold-reinstreq NOT allowed */ - assert(pigp->status <= stat_installed); + +#define PEND pigp->trigpend_head +#define AW pigp->trigaw.head + switch (pigp->status) { + case stat_notinstalled: + case stat_configfiles: + assert(!PEND); + assert(!AW); + break; + case stat_halfinstalled: + case stat_unpacked: + case stat_halfconfigured: + assert(!PEND); + break; + case stat_triggersawaited: + assert(AW); + break; + case stat_triggerspending: + assert(PEND); + assert(!AW); + break; + case stat_installed: + assert(!PEND); + assert(!AW); + break; + default: + abort(); + } +#undef PEND +#undef AW + if (flags&fw_printheader) varbufaddstr(vb,"Status: "); varbufaddstr(vb,wantinfos[pigp->want].name); varbufaddc(vb,' '); @@ -239,6 +273,52 @@ void w_conffiles(struct varbuf *vb, varbufaddc(vb,'\n'); } +void +w_trigpend(struct varbuf *vb, + const struct pkginfo *pigp, const struct pkginfoperfile *pifp, + enum fwriteflags flags, const struct fieldinfo *fip) +{ + struct trigpend *tp; + + if (!pifp->valid || pifp == &pigp->available || !pigp->trigpend_head) + return; + + assert(pigp->status >= stat_triggersawaited && + pigp->status <= stat_triggerspending); + + if (flags & fw_printheader) + varbufaddstr(vb, "Triggers-Pending:"); + for (tp = pigp->trigpend_head; tp; tp = tp->next) { + varbufaddc(vb, ' '); + varbufaddstr(vb, tp->name); + } + if (flags & fw_printheader) + varbufaddc(vb, '\n'); +} + +void +w_trigaw(struct varbuf *vb, + const struct pkginfo *pigp, const struct pkginfoperfile *pifp, + enum fwriteflags flags, const struct fieldinfo *fip) +{ + struct trigaw *ta; + + if (!pifp->valid || pifp == &pigp->available || !pigp->trigaw.head) + return; + + assert(pigp->status > stat_configfiles && + pigp->status <= stat_triggersawaited); + + if (flags & fw_printheader) + varbufaddstr(vb, "Triggers-Awaited:"); + for (ta = pigp->trigaw.head; ta; ta = ta->sameaw.next) { + varbufaddc(vb, ' '); + varbufaddstr(vb, ta->pend->name); + } + if (flags & fw_printheader) + varbufaddc(vb, '\n'); +} + void varbufrecord(struct varbuf *vb, const struct pkginfo *pigp, const struct pkginfoperfile *pifp) { const struct fieldinfo *fip; diff --git a/lib/fields.c b/lib/fields.c index b5dfae75..76d1020c 100644 --- a/lib/fields.c +++ b/lib/fields.c @@ -453,3 +453,96 @@ void f_dependency(struct pkginfo *pigp, struct pkginfoperfile *pifp, } } +static const char * +scan_word(const char **valp) +{ + static char *buf; + static int avail; + + int l; + const char *p, *start, *end; + + p = *valp; + for (;;) { + if (!*p) { + *valp = p; + return NULL; + } + if (cisspace(*p)) { + p++; + continue; + } + start = p; + break; + } + for (;;) { + if (*p && !cisspace(*p)) { + p++; + continue; + } + end = p; + break; + } + l = end - start; + if (l >= avail) { + avail = l * 2 + 4; + buf = m_realloc(buf, avail); + } + memcpy(buf, start, l); + buf[l] = 0; + *valp = p; + + return buf; +} + +void +f_trigpend(struct pkginfo *pend, struct pkginfoperfile *pifp, + enum parsedbflags flags, + const char *filename, int lno, FILE *warnto, int *warncount, + const char *value, const struct fieldinfo *fip) +{ + const char *word, *emsg; + + if (flags & pdb_rejectstatus) + parseerr(NULL, filename, lno, warnto, warncount, pend, 0, + _("value for `triggers-pending' field not allowed in this context")); + + while ((word = scan_word(&value))) { + emsg = illegal_triggername(word); + if (emsg) + parseerr(NULL, filename, lno, warnto, warncount, pend, 0, + _("illegal pending trigger name `%.255s': %s"), word, emsg); + + if (!trig_note_pend_core(pend, nfstrsave(word))) + parseerr(NULL, filename, lno, warnto, warncount, pend, 0, + _("duplicate pending trigger `%.255s'"), word); + } +} + +void +f_trigaw(struct pkginfo *aw, struct pkginfoperfile *pifp, + enum parsedbflags flags, + const char *filename, int lno, FILE *warnto, int *warncount, + const char *value, const struct fieldinfo *fip) +{ + const char *word, *emsg; + struct pkginfo *pend; + + if (flags & pdb_rejectstatus) + parseerr(NULL, filename, lno, warnto, warncount, aw, 0, + _("value for `triggers-awaited' field not allowed in this context")); + + while ((word = scan_word(&value))) { + emsg = illegal_packagename(word, NULL); + if (emsg) + parseerr(NULL, filename, lno, warnto, warncount, aw, 0, + _("illegal package name in awaited trigger `%.255s': %s"), + word, emsg); + pend = findpackage(word); + + if (!trig_note_aw(pend, aw)) + parseerr(NULL, filename, lno, warnto, warncount, aw, 0, + _("duplicate awaited trigger package `%.255s'"), word); + } +} + diff --git a/lib/parse.c b/lib/parse.c index 3de8a614..d562566b 100644 --- a/lib/parse.c +++ b/lib/parse.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -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; } diff --git a/lib/parsedump.h b/lib/parsedump.h index 2f7bb769..43470f2d 100644 --- a/lib/parsedump.h +++ b/lib/parsedump.h @@ -38,6 +38,7 @@ typedef void freadfunction(struct pkginfo *pigp, struct pkginfoperfile *pifp, freadfunction f_name, f_charfield, f_priority, f_section, f_status, f_filecharf; freadfunction f_boolean, f_dependency, f_conffiles, f_version, f_revision; freadfunction f_configversion; +freadfunction f_trigpend, f_trigaw; enum fwriteflags { fw_printheader = 001 /* print field header and trailing newline */ @@ -49,6 +50,7 @@ typedef void fwritefunction(struct varbuf*, fwritefunction w_name, w_charfield, w_priority, w_section, w_status, w_configversion; fwritefunction w_version, w_null, w_booleandefno, w_dependency, w_conffiles; fwritefunction w_filecharf; +fwritefunction w_trigpend, w_trigaw; struct fieldinfo { const char *name; diff --git a/lib/parsehelp.c b/lib/parsehelp.c index f602fdea..4c823a94 100644 --- a/lib/parsehelp.c +++ b/lib/parsehelp.c @@ -87,6 +87,8 @@ const struct namevalue statusinfos[]= { /* Note ! These must be in order ! */ { "half-installed", stat_halfinstalled, 14 }, { "unpacked", stat_unpacked, 8 }, { "half-configured", stat_halfconfigured, 15, }, + { "triggers-awaited", stat_triggersawaited, 16 }, + { "triggers-pending", stat_triggerspending, 16 }, { "installed", stat_installed, 9 }, /* These are additional entries for reading only, in any order ... */ { "postinst-failed", stat_halfconfigured, 15 }, /* fixme: backwards compat., remove */ diff --git a/lib/trigdeferred.l b/lib/trigdeferred.l new file mode 100644 index 00000000..a7bcb117 --- /dev/null +++ b/lib/trigdeferred.l @@ -0,0 +1,202 @@ +/* + * dpkg - main program for package management + * trigdeferred.l - parsing of triggers/Deferred + * + * Copyright (C) 2007 Canonical Ltd + * written by Ian Jackson + * + * 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 + +#include +#include + +#include +#include + +static struct varbuf fn, newfn; + +%} + +%% + +[ \t\n] /* whitespace */ +#.*\n /* comments */ +[\x21-\x7e]+ { + trigdef->trig_begin(trigdef_yytext); + BEGIN(midline); + } + +[ \t] /* whitespace */ +[-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); + } +\n|#.*\n { + trigdef->trig_end(); + BEGIN(0); + } +<> { + 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 index 00000000..32f18f1c --- /dev/null +++ b/lib/triglib.c @@ -0,0 +1,755 @@ +/* + * libdpkg - Debian packaging suite library routines + * triglib.c - trigger handling + * + * Copyright (C) 2007 Canonical Ltd + * Written by Ian Jackson + * + * 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 + +#include +#include +#include + +#include +#include + +#include +#include +#include + +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 +}; + diff --git a/po/POTFILES.in b/po/POTFILES.in index 26d15913..5ad5573b 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -17,6 +17,8 @@ lib/parsehelp.c lib/showcright.c lib/showpkg.c lib/tarfn.c +lib/trigdeferred.c +lib/triglib.c lib/utils.c lib/varbuf.c lib/vercmp.c @@ -35,6 +37,8 @@ src/processarc.c src/query.c src/remove.c src/select.c +src/trigcmd.c +src/trigproc.c src/update.c dpkg-deb/build.c diff --git a/src/Makefile.am b/src/Makefile.am index 80e61e76..a1377e75 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,7 +8,7 @@ INCLUDES = \ -I$(top_srcdir)/lib -bin_PROGRAMS = dpkg dpkg-query +bin_PROGRAMS = dpkg dpkg-query dpkg-trigger dpkg_SOURCES = \ archives.c archives.h \ @@ -24,6 +24,7 @@ dpkg_SOURCES = \ processarc.c \ remove.c \ select.c \ + trigproc.c \ update.c dpkg_LDADD = \ @@ -40,6 +41,14 @@ dpkg_query_LDADD = \ $(LIBINTL) \ ../lib/libdpkg.a +dpkg_trigger_SOURCES = \ + trigcmd.c + +dpkg_trigger_LDADD = \ + ../libcompat/libcompat.a \ + $(LIBINTL) \ + ../lib/libdpkg.a + install-data-local: $(mkdir_p) $(DESTDIR)$(admindir)/alternatives $(mkdir_p) $(DESTDIR)$(admindir)/info diff --git a/src/archives.c b/src/archives.c index cfd471f2..023678fe 100644 --- a/src/archives.c +++ b/src/archives.c @@ -980,7 +980,9 @@ void check_conflict(struct dependency *dep, struct pkginfo *pkg, fixbyrm->clientdata->istobe= itb_remove; fprintf(stderr, _("dpkg: considering removing %s in favour of %s ...\n"), fixbyrm->name, pkg->name); - if (fixbyrm->status != stat_installed) { + if (!(fixbyrm->status == stat_installed || + fixbyrm->status == stat_triggerspending || + fixbyrm->status == stat_triggersawaited)) { fprintf(stderr, _("%s is not properly installed - ignoring any dependencies on it.\n"), fixbyrm->name); @@ -1081,6 +1083,8 @@ void archivefiles(const char *const *argv) { const char **arglist; char *p; + trigproc_install_hooks(); + modstatdb_init(admindir, f_noact ? msdbrw_readonly : cipaction->arg == act_avail ? msdbrw_write @@ -1198,6 +1202,7 @@ void archivefiles(const char *const *argv) { switch (cipaction->arg) { case act_install: case act_configure: + case act_triggers: case act_remove: case act_purge: process_queue(); @@ -1208,6 +1213,7 @@ void archivefiles(const char *const *argv) { internerr("unknown action"); } + trigproc_run_deferred(); modstatdb_shutdown(); } @@ -1238,15 +1244,18 @@ int wanttoinstall(struct pkginfo *pkg, const struct versionrevision *ver, int sa } } - if (pkg->status != stat_installed) return 1; + if (!(pkg->status == stat_installed || + pkg->status == stat_triggersawaited || + pkg->status == stat_triggerspending)) + return 1; if (!ver) return -1; r= versioncompare(ver,&pkg->installed.version); if (r > 0) { return 1; } else if (r == 0) { - if (f_skipsame && /* same version fully installed ? */ - pkg->status == stat_installed && !(pkg->eflag &= eflagf_reinstreq)) { + /* Same version fully installed. */ + if (f_skipsame) { if (saywhy) fprintf(stderr, _("Version %.250s of %.250s already installed, " "skipping.\n"), versiondescribe(&pkg->installed.version, vdew_nonambig), diff --git a/src/configure.c b/src/configure.c index 71087ef4..cde2c688 100644 --- a/src/configure.c +++ b/src/configure.c @@ -107,6 +107,15 @@ void deferred_configure(struct pkginfo *pkg) { add_to_queue(pkg); return; } + + trigproc_reset_cycle(); + /* At this point removal from the queue is confirmed. This + * represents irreversible progress wrt trigger cycles. Only + * packages in stat_unpacked are automatically added to the + * configuration queue, and during configuration and trigger + * processing new packages can't enter into unpacked. + */ + ok = breakses_ok(pkg, &aemsgs) ? ok : 0; if (ok == 0) { sincenothing= 0; @@ -134,6 +143,8 @@ void deferred_configure(struct pkginfo *pkg) { versiondescribe(&pkg->installed.version, vdew_nonambig)); log_action("configure", pkg); + trig_activate_packageprocessing(pkg); + if (f_noact) { pkg->status= stat_installed; pkg->clientdata->istobe= itb_normal; @@ -237,6 +248,7 @@ void deferred_configure(struct pkginfo *pkg) { varbufaddstr(&cdr,DPKGDISTEXT); varbufaddc(&cdr,0); strcpy(cdr2rest,DPKGNEWEXT); + trig_file_activate_byname(conff->name, pkg); if (rename(cdr2.buf,cdr.buf)) fprintf(stderr, _("dpkg: %s: warning - failed to rename `%.250s' to `%.250s': %s\n"), @@ -272,6 +284,7 @@ void deferred_configure(struct pkginfo *pkg) { printf(_("Installing new version of config file %s ...\n"),conff->name); case cfo_newconff: strcpy(cdr2rest,DPKGNEWEXT); + trig_file_activate_byname(conff->name, pkg); if (rename(cdr2.buf,cdr.buf)) ohshite(_("unable to install `%.250s' as `%.250s'"),cdr2.buf,cdr.buf); break; diff --git a/src/depcon.c b/src/depcon.c index 3014ae86..42d7b44c 100644 --- a/src/depcon.c +++ b/src/depcon.c @@ -227,9 +227,11 @@ int depisok(struct dependency *dep, struct varbuf *whynot, case itb_remove: case itb_deconfigure: return 1; case itb_normal: - /* Only `installed' packages can be make dependency problems */ + /* Only installed packages can be make dependency problems */ switch (dep->up->status) { case stat_installed: + case stat_triggerspending: + case stat_triggersawaited: break; case stat_notinstalled: case stat_configfiles: case stat_halfinstalled: case stat_halfconfigured: case stat_unpacked: @@ -277,6 +279,7 @@ int depisok(struct dependency *dep, struct varbuf *whynot, case itb_normal: case itb_preinstall: switch (possi->ed->status) { case stat_installed: + case stat_triggerspending: if (versionsatisfied(&possi->ed->installed,possi)) return 1; sprintf(linebuf,_(" %.250s is installed, but is version %.250s.\n"), possi->ed->name, @@ -291,6 +294,7 @@ int depisok(struct dependency *dep, struct varbuf *whynot, break; case stat_unpacked: case stat_halfconfigured: + case stat_triggersawaited: if (allowunconfigd) { if (!informativeversion(&possi->ed->configversion)) { sprintf(linebuf, _(" %.250s is unpacked, but has never been configured.\n"), @@ -311,6 +315,7 @@ int depisok(struct dependency *dep, struct varbuf *whynot, return 1; } } + /* Fall through. */ default: sprintf(linebuf, _(" %.250s is %s.\n"), possi->ed->name, gettext(statusstrings[possi->ed->status])); @@ -421,6 +426,8 @@ int depisok(struct dependency *dep, struct varbuf *whynot, case stat_halfconfigured: if (dep->type == dep_breaks) break; /* no problem */ case stat_installed: + case stat_triggerspending: + case stat_triggersawaited: if (!versionsatisfied(&possi->ed->installed, possi)) break; sprintf(linebuf, _(" %.250s (version %.250s) is present and %s.\n"), possi->ed->name, @@ -484,6 +491,8 @@ int depisok(struct dependency *dep, struct varbuf *whynot, case stat_halfconfigured: if (dep->type == dep_breaks) break; /* no problem */ case stat_installed: + case stat_triggerspending: + case stat_triggersawaited: sprintf(linebuf, _(" %.250s provides %.250s and is present and %s.\n"), provider->up->up->name, possi->ed->name, diff --git a/src/enquiry.c b/src/enquiry.c index 7e29606b..266bb16d 100644 --- a/src/enquiry.c +++ b/src/enquiry.c @@ -96,6 +96,16 @@ static const struct badstatinfo badstatinfos[]= { N_("The following packages are only half installed, due to problems during\n" "installation. The installation can probably be completed by retrying it;\n" "the packages can be removed using dselect or dpkg --remove:\n") + }, { + bsyn_status, stat_triggersawaited, + N_("The following packages are awaiting processesing of triggers that they\n" + "have activated in other packages. This processing can be requested using\n" + "dselect or dpkg --configure --pending (or dpkg --triggers-only):\n") + }, { + bsyn_status, stat_triggerspending, + N_("The following packages have been triggered, but the trigger processesing\n" + "has not yet been done. Trigger processing can be requested using\n" + "dselect or dpkg --configure --pending (or dpkg --triggers-only):\n") }, { NULL } @@ -150,6 +160,8 @@ static int yettobeunpacked(struct pkginfo *pkg, const char **thissect) { switch (pkg->status) { case stat_unpacked: case stat_installed: case stat_halfconfigured: + case stat_triggerspending: + case stat_triggersawaited: return 0; case stat_notinstalled: case stat_halfinstalled: case stat_configfiles: if (thissect) @@ -254,8 +266,10 @@ static void assertversion(const char *const *argv, pkg= findpackage("dpkg"); switch (pkg->status) { case stat_installed: + case stat_triggerspending: break; case stat_unpacked: case stat_halfconfigured: case stat_halfinstalled: + case stat_triggersawaited: if (versionsatisfied3(&pkg->configversion,verrev_buf,dvr_laterequal)) break; printf(_("Version of dpkg with working epoch support not yet configured.\n" diff --git a/src/filesdb.c b/src/filesdb.c index 46b3cbff..97fe7673 100644 --- a/src/filesdb.c +++ b/src/filesdb.c @@ -55,6 +55,7 @@ ensure_package_clientdata(struct pkginfo *pkg) pkg->clientdata->istobe = itb_normal; pkg->clientdata->fileslistvalid = 0; pkg->clientdata->files = NULL; + pkg->clientdata->trigprocdeferred = NULL; } void note_must_reread_files_inpackage(struct pkginfo *pkg) { @@ -583,6 +584,9 @@ struct filenamenode *findnamenode(const char *name, enum fnnflags flags) { } if (*pointerp) return *pointerp; + if (flags & fnn_nonew) + return NULL; + newnode= nfmalloc(sizeof(struct filenamenode)); newnode->packages = NULL; if((flags & fnn_nocopy) && name > orig_name && name[-1] == '/') @@ -597,6 +601,7 @@ struct filenamenode *findnamenode(const char *name, enum fnnflags flags) { newnode->divert = NULL; newnode->statoverride = NULL; newnode->filestat = NULL; + newnode->trig_interested = NULL; *pointerp= newnode; nfiles++; diff --git a/src/filesdb.h b/src/filesdb.h index c4ed8477..32933387 100644 --- a/src/filesdb.h +++ b/src/filesdb.h @@ -50,6 +50,7 @@ struct pkginfo; enum fnnflags { fnn_nocopy= 000001, /* do not need to copy filename */ + fnn_nonew = 000002, /* findnamenode may return NULL */ }; struct filenamenode { @@ -72,6 +73,7 @@ struct filenamenode { } flags; /* Set to zero when a new node is created. */ const char *oldhash; /* valid iff this namenode is in the newconffiles list */ struct stat *filestat; + struct trigfileint *trig_interested; }; struct fileinlist { diff --git a/src/help.c b/src/help.c index 8e00a2df..81e9c516 100644 --- a/src/help.c +++ b/src/help.c @@ -43,13 +43,18 @@ const char *const statusstrings[]= { N_("broken due to failed removal or installation"), N_("unpacked but not configured"), N_("broken due to postinst failure"), + N_("awaiting trigger processing by another package"), + N_("triggered"), N_("installed") }; struct filenamenode *namenodetouse(struct filenamenode *namenode, struct pkginfo *pkg) { struct filenamenode *r; - if (!namenode->divert) return namenode; + if (!namenode->divert) { + r = namenode; + goto found; + } debug(dbg_eachfile,"namenodetouse namenode=`%s' pkg=%s", namenode->name,pkg->name); @@ -64,6 +69,9 @@ struct filenamenode *namenodetouse(struct filenamenode *namenode, struct pkginfo namenode->divert->camefrom ? namenode->divert->camefrom->name : "", namenode->divert->pkg ? namenode->divert->pkg->name : "", 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); } diff --git a/src/main.c b/src/main.c index 5fac1465..00b05440 100644 --- a/src/main.c +++ b/src/main.c @@ -67,6 +67,7 @@ usage(void) " --unpack <.deb file name> ... | -R|--recursive ...\n" " -A|--record-avail <.deb file name> ... | -R|--recursive ...\n" " --configure ... | -a|--pending\n" +" --triggers-only ... | -a|--pending\n" " -r|--remove ... | -a|--pending\n" " -P|--purge ... | -a|--pending\n" " --get-selections [ ...] 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(); diff --git a/src/main.h b/src/main.h index add265d6..3713be00 100644 --- a/src/main.h +++ b/src/main.h @@ -41,6 +41,9 @@ struct perpackagestate { int fileslistvalid; struct fileinlist *files; int replacingfilesandsaid; + + /* Non-NULL iff in trigproc.c:deferred. */ + struct pkginqueue *trigprocdeferred; }; struct packageinlist { @@ -55,6 +58,7 @@ struct pkginqueue { }; enum action { act_unset, act_install, act_unpack, act_avail, act_configure, + act_triggers, act_remove, act_purge, act_listpackages, act_avreplace, act_avmerge, act_unpackchk, act_status, act_searchfiles, act_audit, act_listfiles, act_assertpredep, act_printarch, act_predeppackage, act_cmpversions, @@ -89,6 +93,7 @@ extern const char *const statusstrings[]; extern const struct cmdinfo *cipaction; extern int f_pending, f_recursive, f_alsoselect, f_skipsame, f_noact; extern int f_autodeconf, f_largemem, f_nodebsig; +extern int f_triggers; extern unsigned long f_debug; extern int fc_downgrade, fc_configureany, fc_hold, fc_removereinstreq, fc_overwrite; extern int fc_removeessential, fc_conflicts, fc_depends, fc_dependsversion; @@ -202,6 +207,10 @@ int chmodsafe_unlink(const char *pathname, const char **failed); int chmodsafe_unlink_statted(const char *pathname, const struct stat *stab, const char **failed); void checkpath(void); + +/* NB side effect! This not only computes the appropriate filename + * to use including thinking about any diversions, but also activates + * any file triggers. */ struct filenamenode *namenodetouse(struct filenamenode*, struct pkginfo*); /* all ...'s are const char*'s ... */ @@ -214,7 +223,13 @@ int maintainer_script_alternative(struct pkginfo *pkg, const char *scriptname, const char *description, const char *cidir, char *cidirrest, const char *ifok, const char *iffallback); + +/* Callers wanting to run the postinst use these two as they want to postpone + * trigger incorporation until after updating the package status. The effect + * is that a package can trigger itself. */ int maintainer_script_postinst(struct pkginfo *pkg, ...); +void post_postinst_tasks_core(struct pkginfo *pkg); + void post_postinst_tasks(struct pkginfo *pkg, enum pkgstatus new_status); void clear_istobes(void); @@ -232,12 +247,28 @@ enum debugflags { dbg_depcondetail= 00400, dbg_veryverbose= 01000, dbg_stupidlyverbose= 02000, + dbg_triggers = 010000, + dbg_triggersdetail = 020000, + dbg_triggersstupid = 040000, }; void debug(int which, const char *fmt, ...) PRINTFFORMAT(2,3); void check_libver(void); void log_action(const char *action, struct pkginfo *pkg); +/* from trigproc.c */ + +void trigproc_install_hooks(void); +void trigproc_run_deferred(void); +void trigproc_reset_cycle(void); + +/* Does cycle checking. Doesn't mind if pkg has no triggers + * pending - in that case does nothing but fix up any stale awaiters. */ +void trigproc(struct pkginfo *pkg); + +/* Called by modstatdb_note. */ +void trig_activate_packageprocessing(struct pkginfo *pkg); + /* from depcon.c */ int depisok(struct dependency *dep, struct varbuf *whynot, diff --git a/src/packages.c b/src/packages.c index 8f1ebeee..8d22ae40 100644 --- a/src/packages.c +++ b/src/packages.c @@ -40,6 +40,7 @@ #include "filesdb.h" #include "main.h" +static struct pkginfo *progress_bytrigproc; static PKGQUEUE_DEF_INIT(queue); int sincenothing = 0, dependtry = 0; @@ -89,6 +90,8 @@ void packages(const char *const *argv) { const char *thisarg; size_t l; + trigproc_install_hooks(); + modstatdb_init(admindir, f_noact ? msdbrw_readonly : fc_nonroot ? msdbrw_write @@ -105,7 +108,15 @@ void packages(const char *const *argv) { while ((pkg = iterpkgnext(it)) != NULL) { switch (cipaction->arg) { case act_configure: - if (pkg->status != stat_unpacked && pkg->status != stat_halfconfigured) + if (!(pkg->status == stat_unpacked || + pkg->status == stat_halfconfigured || + pkg->trigpend_head)) + continue; + if (pkg->want != want_install) + continue; + break; + case act_triggers: + if (!pkg->trigpend_head) continue; if (pkg->want != want_install) continue; @@ -147,6 +158,7 @@ void packages(const char *const *argv) { ensure_diversions(); process_queue(); + trigproc_run_deferred(); modstatdb_shutdown(); } @@ -154,12 +166,14 @@ void packages(const char *const *argv) { void process_queue(void) { struct pkginqueue *removeent, *rundown; struct pkginfo *volatile pkg; + enum action action_todo; jmp_buf ejbuf; enum istobes istobe= itb_normal; clear_istobes(); switch (cipaction->arg) { + case act_triggers: case act_configure: case act_install: istobe= itb_installnew; break; case act_remove: case act_purge: istobe= itb_remove; break; default: internerr("unknown action for queue start"); @@ -169,6 +183,7 @@ void process_queue(void) { if (rundown->pkg->clientdata->istobe == istobe) { /* Erase the queue entry - this is a second copy! */ switch (cipaction->arg) { + case act_triggers: case act_configure: case act_remove: case act_purge: printf(_("Package %s listed more than once, only processing once.\n"), rundown->pkg->name); @@ -193,6 +208,20 @@ void process_queue(void) { if (!pkg) continue; /* duplicate, which we removed earlier */ + action_todo = cipaction->arg; + + if (sincenothing++ > queue.length * 2 + 2) { + if (progress_bytrigproc && progress_bytrigproc->trigpend_head) { + add_to_queue(pkg); + pkg = progress_bytrigproc; + action_todo = act_configure; + } else { + dependtry++; + sincenothing = 0; + assert(dependtry <= 4); + } + } + assert(pkg->status <= stat_installed); if (setjmp(ejbuf)) { @@ -203,17 +232,24 @@ void process_queue(void) { continue; } push_error_handler(&ejbuf,print_error_perpackage,pkg->name); - if (sincenothing++ > queue.length * 2 + 2) { - dependtry++; sincenothing= 0; - assert(dependtry <= 4); - } - switch (cipaction->arg) { + switch (action_todo) { + case act_triggers: + if (!pkg->trigpend_head) + ohshit(_("package %.250s is not ready for trigger processing\n" + " (current status `%.250s' with no pending triggers)"), + pkg->name, statusinfos[pkg->status].name); + /* Fall through. */ case act_install: /* Don't try to configure pkgs that we've just disappeared. */ if (pkg->status == stat_notinstalled) break; + /* Fall through. */ case act_configure: - deferred_configure(pkg); + /* Do whatever is most needed. */ + if (pkg->trigpend_head) + trigproc(pkg); + else + deferred_configure(pkg); break; case act_remove: case act_purge: deferred_remove(pkg); @@ -266,10 +302,21 @@ void process_queue(void) { * and breaking. */ +/* Return values: + * 0: cannot be satisfied. + * 1: defer: may be satisfied later, when other packages are better or + * at higher dependtry due to --force + * will set *fixbytrig to package whose trigger processing would help + * if applicable (and leave it alone otherwise) + * 2: not satisfied but forcing + * (*interestingwarnings >= 0 on exit? caller is to print oemsgs) + * 3: satisfied now + */ static int deppossi_ok_found(struct pkginfo *possdependee, struct pkginfo *requiredby, struct pkginfo *removing, struct pkginfo *providing, + struct pkginfo **fixbytrig, int *matched, struct deppossi *checkversion, int *interestingwarnings, @@ -299,6 +346,8 @@ static int deppossi_ok_found(struct pkginfo *possdependee, switch (possdependee->status) { case stat_unpacked: case stat_halfconfigured: + case stat_triggersawaited: + case stat_triggerspending: case stat_installed: assert(possdependee->installed.valid); if (checkversion && !versionsatisfied(&possdependee->installed,checkversion)) { @@ -312,15 +361,51 @@ static int deppossi_ok_found(struct pkginfo *possdependee, (*interestingwarnings)++; return thisf; } - if (possdependee->status == stat_installed) { + if (possdependee->status == stat_installed || + possdependee->status == stat_triggerspending) { debug(dbg_depcondetail," is installed, ok and found"); return 3; } + if (possdependee->status == stat_triggersawaited) { + assert(possdependee->trigaw.head); + if (removing || !(f_triggers || + possdependee->clientdata->istobe == itb_installnew)) { + if (providing) { + varbufprintf(oemsgs, + _(" Package %s which provides %s awaits trigger processing.\n"), + possdependee->name, providing->name); + } else { + varbufprintf(oemsgs, + _(" Package %s awaits trigger processing.\n"), + possdependee->name); + } + debug(dbg_depcondetail, " triggers-awaited, no fixbytrig"); + goto unsuitable; + } + /* We don't check the status of trigaw.head->pend here, just in case + * we get into the pathological situation where Triggers-Awaited but + * the named package doesn't actually have any pending triggers. In + * that case we queue the non-pending package for trigger processing + * anyway, and that trigger processing will be a noop except for + * sorting out all of the packages which name it in T-Awaited. + * + * (This situation can only arise if modstatdb_note success in + * clearing the triggers-pending status of the pending package + * but then fails to go on to update the awaiters.) + */ + *fixbytrig = possdependee->trigaw.head->pend; + debug(dbg_depcondetail, + " triggers-awaited, fixbytrig `%s', returning 1", + (*fixbytrig)->name); + return 1; + } if (possdependee->clientdata && possdependee->clientdata->istobe == itb_installnew) { debug(dbg_depcondetail," unpacked/halfconfigured, defer"); return 1; - } else if (!removing && fc_configureany && !skip_due_to_hold(possdependee)) { + } else if (!removing && fc_configureany && + !skip_due_to_hold(possdependee) && + !(possdependee->status == stat_halfconfigured)) { fprintf(stderr, _("dpkg: also configuring `%s' (required by `%s')\n"), possdependee->name, requiredby->name); @@ -338,6 +423,7 @@ static int deppossi_ok_found(struct pkginfo *possdependee, debug(dbg_depcondetail, " not configured/able"); goto unsuitable; } + default: if (providing) { varbufprintf(oemsgs, @@ -437,10 +523,11 @@ int breakses_ok(struct pkginfo *pkg, struct varbuf *aemsgs) { int dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing, struct varbuf *aemsgs) { - int ok, matched, found, thisf, interestingwarnings; + int ok, matched, found, thisf, interestingwarnings, anycannotfixbytrig; struct varbuf oemsgs; struct dependency *dep; struct deppossi *possi, *provider; + struct pkginfo *possfixbytrig, *canfixbytrig; varbufinit(&oemsgs); interestingwarnings= 0; @@ -449,11 +536,14 @@ int dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing, pkg->name, removing ? removing->name : ""); 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); diff --git a/src/processarc.c b/src/processarc.c index d47a3f48..1096e1ab 100644 --- a/src/processarc.c +++ b/src/processarc.c @@ -282,7 +282,8 @@ void process_archive(const char *filename) { ensure_allinstfiles_available(); filesdbinit(); - + trig_file_interests_ensure(); + if (pkg->status != stat_notinstalled && pkg->status != stat_configfiles) { printf(_("Preparing to replace %s %s (using %s) ...\n"), pkg->name, @@ -299,9 +300,15 @@ void process_archive(const char *filename) { return; } - /* OK, we're going ahead. First we read the conffiles, and copy the - * hashes across. + /* + * OK, we're going ahead. */ + + trig_activate_packageprocessing(pkg); + strcpy(cidirrest, TRIGGERSCIFILE); + trig_parse_ci(cidir, NULL, trig_cicb_statuschange_activate, pkg); + + /* Read the conffiles, and copy the hashes across. */ newconffiles = NULL; newconffileslastp = &newconffiles; push_cleanup(cu_fileslist, ~0, NULL, 0, 0); @@ -387,7 +394,10 @@ void process_archive(const char *filename) { debug(dbg_general,"process_archive oldversionstatus=%s", statusstrings[oldversionstatus]); - if (oldversionstatus == stat_halfconfigured || oldversionstatus == stat_installed) { + if (oldversionstatus == stat_halfconfigured || + oldversionstatus == stat_triggersawaited || + oldversionstatus == stat_triggerspending || + oldversionstatus == stat_installed) { pkg->eflag |= eflagf_reinstreq; pkg->status= stat_halfconfigured; modstatdb_note(pkg); @@ -408,6 +418,7 @@ void process_archive(const char *filename) { else printf(_("De-configuring %s ...\n"), deconpil->pkg->name); + trig_activate_packageprocessing(deconpil->pkg); deconpil->pkg->status= stat_halfconfigured; modstatdb_note(deconpil->pkg); @@ -439,7 +450,10 @@ void process_archive(const char *filename) { for (i = 0 ; i < cflict_index; i++) { if (!(conflictor[i]->status == stat_halfconfigured || + conflictor[i]->status == stat_triggersawaited || + conflictor[i]->status == stat_triggerspending || conflictor[i]->status == stat_installed)) continue; + trig_activate_packageprocessing(conflictor[i]); conflictor[i]->status= stat_halfconfigured; modstatdb_note(conflictor[i]); push_cleanup(cu_prerminfavour, ~ehflag_normaltidy, NULL, 0, @@ -738,6 +752,16 @@ void process_archive(const char *filename) { */ write_filelist_except(pkg,newfileslist,0); + /* Trigger interests may have changed. + * Firstly we go through the old list of interests deleting them. + * Then we go through the new list adding them. + */ + strcpy(cidirrest, TRIGGERSCIFILE); + trig_parse_ci(pkgadminfile(pkg, TRIGGERSCIFILE), + trig_cicb_interest_delete, NULL, pkg); + trig_parse_ci(cidir, trig_cicb_interest_add, NULL, pkg); + trig_file_interests_save(); + /* We also install the new maintainer scripts, and any other * cruft that may have come along with the package. First * we go through the existing scripts replacing or removing @@ -985,6 +1009,7 @@ void process_archive(const char *filename) { * run maintainer scripts and things, as we can't back out. But * what can we do ? It has to be run this late. */ + trig_activate_packageprocessing(otherpkg); maintainer_script_installed(otherpkg, POSTRMFILE, "post-removal script (for disappearance)", "disappear", pkg->name, diff --git a/src/query.c b/src/query.c index 631ee58c..dd6d25a0 100644 --- a/src/query.c +++ b/src/query.c @@ -124,7 +124,7 @@ static void list1package(struct pkginfo *pkg, int *head, if (!*head) { fputs(_("\ Desired=Unknown/Install/Remove/Purge/Hold\n\ -| Status=Not/Installed/Config-files/Unpacked/Failed-config/Half-installed\n\ +| Status=Not/Inst/Cfg-files/Unpacked/Failed-cfg/Half-inst/trig-aWait/Trig-pend\n\ |/ Err?=(none)/Hold/Reinst-required/X=both-problems (Status,Err: uppercase=bad)\n"), stdout); printf(format,'|','|','/', _("Name"), _("Version"), 40, _("Description")); printf("+++-"); /* status */ @@ -138,7 +138,7 @@ Desired=Unknown/Install/Remove/Purge/Hold\n\ limiteddescription(pkg,dw,&pdesc,&l); printf(format, "uihrp"[pkg->want], - "ncHUFi"[pkg->status], + "ncHUFWti"[pkg->status], " R?#"[pkg->eflag], pkg->name, versiondescribe(&pkg->installed.version, vdew_nonambig), diff --git a/src/remove.c b/src/remove.c index e9851702..1e92d623 100644 --- a/src/remove.c +++ b/src/remove.c @@ -66,7 +66,10 @@ static void checkforremoval(struct pkginfo *pkgtoremove, if (possi->up->type != dep_depends && possi->up->type != dep_predepends) continue; depender= possi->up->up; debug(dbg_depcon,"checking depending package `%s'",depender->name); - if (depender->status != stat_installed) continue; + if (!(depender->status == stat_installed || + depender->status == stat_triggerspending || + depender->status == stat_triggersawaited)) + continue; if (ignore_depends(depender)) { debug(dbg_depcon,"ignoring depending package `%s'\n",depender->name); continue; @@ -163,7 +166,8 @@ void deferred_remove(struct pkginfo *pkg) { printf(_("Removing %s ...\n"),pkg->name); log_action("remove", pkg); - if (pkg->status == stat_halfconfigured || pkg->status == stat_installed) { + trig_activate_packageprocessing(pkg); + if (pkg->status >= stat_halfconfigured) { static enum pkgstatus oldpkgstatus; oldpkgstatus= pkg->status; @@ -404,6 +408,7 @@ static void removal_bulk_remove_configfiles(struct pkginfo *pkg) { printf(_("Purging configuration files for %s ...\n"),pkg->name); log_action("purge", pkg); + trig_activate_packageprocessing(pkg); ensure_packagefiles_available(pkg); /* We may have modified this above. */ /* We're about to remove the configuration, so remove the note diff --git a/src/trigcmd.c b/src/trigcmd.c new file mode 100644 index 00000000..94fdf068 --- /dev/null +++ b/src/trigcmd.c @@ -0,0 +1,230 @@ +/* + * dpkg-trigger - trigger management utility + * + * Copyright (C) 2007 Canonical Ltd. + * Written by Ian Jackson + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 [ ...] \n" +" %s [ ...] \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= Use instead of %s.\n" +" --by-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 index 00000000..b327f5d3 --- /dev/null +++ b/src/trigproc.c @@ -0,0 +1,393 @@ +/* + * dpkg - main program for package management + * trigproc.c - trigger processing + * + * Copyright (C) 2007 Canonical Ltd + * written by Ian Jackson + * + * 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 + +#include +#include +#include + +#include +#include + +#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; +} + -- 2.39.5