| /* |
| * dpkg - main program for package management |
| * trigproc.c - trigger processing |
| * |
| * Copyright © 2007 Canonical Ltd |
| * written by Ian Jackson <ian@chiark.greenend.org.uk> |
| * |
| * This is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, 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 this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <config.h> |
| #include <compat.h> |
| |
| #include <sys/fcntl.h> |
| #include <sys/stat.h> |
| |
| #include <assert.h> |
| #include <stdlib.h> |
| |
| #include <dpkg/i18n.h> |
| #include <dpkg/dpkg.h> |
| #include <dpkg/dpkg-db.h> |
| #include <dpkg/pkg-queue.h> |
| #include <dpkg/triglib.h> |
| |
| #include "main.h" |
| #include "filesdb.h" |
| |
| /* |
| * Trigger processing algorithms: |
| * |
| * |
| * There is a separate queue (‘deferred trigproc list’) for triggers |
| * ‘relevant’ to what we just did; when we find something triggered ‘now’ |
| * we add it to that queue (unless --no-triggers). |
| * |
| * |
| * We want to prefer configuring packages where possible to doing |
| * trigger processing, but we want to prefer trigger processing to |
| * cycle-breaking and dependency forcing. This is achieved as |
| * follows: |
| * |
| * Each time during configure processing where a package D is blocked by |
| * only (ie Depends unsatisfied but would be satisfied by) a t-awaiter W |
| * we make a note of (one of) W's t-pending, T. (Only the last such T.) |
| * (If --no-triggers and nonempty argument list and package isn't in |
| * argument list then we don't do this.) |
| * |
| * Each time in packages.c where we increment dependtry, we instead see |
| * if we have encountered such a t-pending T. If we do, we trigproc T |
| * instead of incrementing dependtry and this counts as having done |
| * something so we reset sincenothing. |
| * |
| * |
| * For --triggers-only and --configure, we go through each thing in the |
| * argument queue (the add_to_queue queue) and check what its state is |
| * and if appropriate we trigproc it. If we didn't have a queue (or had |
| * just --pending) we search all triggers-pending packages and add them |
| * to the deferred trigproc list. |
| * |
| * |
| * Before quitting from most operations, we trigproc each package in the |
| * deferred trigproc list. This may (if not --no-triggers) of course add |
| * new things to the deferred trigproc list. |
| * |
| * |
| * Note that ‘we trigproc T’ must involve trigger cycle detection and |
| * also automatic setting of t-awaiters to t-pending or installed. In |
| * particular, we do cycle detection even for trigger processing in the |
| * configure dependtry loop (and it is OK to do it for explicitly |
| * specified packages from the command line arguments; duplicates are |
| * removed by packages.c:process_queue). |
| */ |
| |
| /*========== Deferred trigger queue. ==========*/ |
| |
| static struct pkg_queue deferred = PKG_QUEUE_INIT; |
| |
| 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 = pkg_queue_push(&deferred, pend); |
| debug(dbg_triggers, "trigproc_enqueue_deferred pend=%s", pend->name); |
| } |
| |
| void |
| trigproc_run_deferred(void) |
| { |
| jmp_buf ejbuf; |
| |
| debug(dbg_triggers, "trigproc_run_deferred"); |
| while (!pkg_queue_is_empty(&deferred)) { |
| struct pkginfo *pkg; |
| |
| pkg = pkg_queue_pop(&deferred); |
| if (!pkg) |
| continue; |
| |
| if (setjmp(ejbuf)) { |
| pop_error_context(ehflag_bombout); |
| continue; |
| } |
| push_error_context_jump(&ejbuf, print_error_perpackage, |
| pkg->name); |
| |
| pkg->clientdata->trigprocdeferred = NULL; |
| trigproc(pkg); |
| |
| pop_error_context(ehflag_normaltidy); |
| } |
| } |
| |
| /* |
| * Called by modstatdb_note. |
| */ |
| 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 bool tortoise_advance; |
| static struct trigcyclenode *tortoise, *hare; |
| |
| void |
| trigproc_reset_cycle(void) |
| { |
| tortoise_advance = false; |
| 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 = pkg_db_iter_new(); |
| while ((pkg = pkg_db_iter_next(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; |
| } |
| pkg_db_iter_free(it); |
| 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_get_progname()); |
| 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; |
| } |
| |
| /* |
| * 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) |
| { |
| 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); |
| |
| varbuf_reset(&namesarg); |
| for (tp = pkg->trigpend_head; tp; tp = tp->next) { |
| varbuf_add_char(&namesarg, ' '); |
| varbuf_add_str(&namesarg, tp->name); |
| } |
| varbuf_end_str(&namesarg); |
| |
| /* Setting the status to half-configured |
| * causes modstatdb_note to clear pending triggers. */ |
| pkg->status = stat_halfconfigured; |
| modstatdb_note(pkg); |
| |
| if (!f_noact) { |
| 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, |
| enum trig_options opts) |
| { |
| 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, |
| enum trig_options opts) |
| { |
| struct pkginfo *pend = user; |
| |
| trig_cicb_interest_add(trig, pend, opts); |
| transitional_interest_callback_ro(trig, user, opts); |
| } |
| |
| /* |
| * cstatus might be msdbrw_readonly 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. |
| */ |
| static void |
| trig_transitional_activate(enum modstatdb_rw cstatus) |
| { |
| struct pkgiterator *it; |
| struct pkginfo *pkg; |
| |
| it = pkg_db_iter_new(); |
| while ((pkg = pkg_db_iter_next(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); |
| /* Ensure we're not creating incoherent data that can't |
| * be written down. This should never happen in theory but |
| * can happen if you restore an old status file that is |
| * not in sync with the infodb files. */ |
| if (pkg->status < stat_triggersawaited) |
| continue; |
| pkg->status = pkg->trigaw.head ? stat_triggersawaited : |
| pkg->trigpend_head ? stat_triggerspending : |
| stat_installed; |
| } |
| pkg_db_iter_free(it); |
| |
| if (cstatus >= msdbrw_write) { |
| modstatdb_checkpoint(); |
| trig_file_interests_save(); |
| } |
| } |
| |
| /*========== Hook setup. ==========*/ |
| |
| static struct filenamenode * |
| th_proper_nn_find(const char *name, bool nonew) |
| { |
| return findnamenode(name, nonew ? fnn_nonew : 0); |
| } |
| |
| TRIGHOOKS_DEFINE_NAMENODE_ACCESSORS |
| |
| static const struct trig_hooks trig_our_hooks = { |
| .enqueue_deferred = trigproc_enqueue_deferred, |
| .transitional_activate = trig_transitional_activate, |
| .namenode_find = th_proper_nn_find, |
| .namenode_interested = th_nn_interested, |
| .namenode_name = th_nn_name, |
| }; |
| |
| void |
| trigproc_install_hooks(void) |
| { |
| trig_override_hooks(&trig_our_hooks); |
| } |