blob: b12947427a84b83f89745b4e5df0c3adea0678e0 [file] [log] [blame]
/*
* dpkg - main program for package management
* packages.c - common to actions that process packages
*
* Copyright © 1994,1995 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/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <dpkg/i18n.h>
#include <dpkg/dpkg.h>
#include <dpkg/dpkg-db.h>
#include <dpkg/pkg-list.h>
#include <dpkg/pkg-queue.h>
#include <dpkg/options.h>
#include "filesdb.h"
#include "main.h"
static struct pkginfo *progress_bytrigproc;
static struct pkg_queue queue = PKG_QUEUE_INIT;
int sincenothing = 0, dependtry = 0;
void
add_to_queue(struct pkginfo *pkg)
{
pkg_queue_push(&queue, pkg);
}
static void
enqueue_pending(void)
{
struct pkgiterator *it;
struct pkginfo *pkg;
it = pkg_db_iter_new();
while ((pkg = pkg_db_iter_next(it)) != NULL) {
switch (cipaction->arg_int) {
case act_configure:
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;
break;
case act_remove:
case act_purge:
if (pkg->want != want_purge) {
if (pkg->want != want_deinstall)
continue;
if (pkg->status == stat_configfiles)
continue;
}
if (pkg->status == stat_notinstalled)
continue;
break;
default:
internerr("unknown action '%d'", cipaction->arg_int);
}
add_to_queue(pkg);
}
pkg_db_iter_free(it);
}
static void
enqueue_specified(const char *const *argv)
{
const char *thisarg;
while ((thisarg = *argv++) != NULL) {
struct pkginfo *pkg;
pkg = pkg_db_find(thisarg);
if (pkg->status == stat_notinstalled) {
size_t l = strlen(pkg->name);
const char *extension = pkg->name + l - sizeof(DEBEXT) + 1;
if (l >= sizeof(DEBEXT) && strcmp(extension, DEBEXT) == 0)
badusage(_("you must specify packages by their own names,"
" not by quoting the names of the files they come in"));
}
add_to_queue(pkg);
}
}
int
packages(const char *const *argv)
{
trigproc_install_hooks();
modstatdb_open(f_noact ? msdbrw_readonly :
fc_nonroot ? msdbrw_write :
msdbrw_needsuperuser);
checkpath();
log_message("startup packages %s", cipaction->olong);
if (f_pending) {
if (*argv)
badusage(_("--%s --pending does not take any non-option arguments"),cipaction->olong);
enqueue_pending();
} else {
if (!*argv)
badusage(_("--%s needs at least one package name argument"), cipaction->olong);
enqueue_specified(argv);
}
ensure_diversions();
process_queue();
trigproc_run_deferred();
modstatdb_shutdown();
return 0;
}
void process_queue(void) {
struct pkg_list *rundown;
struct pkginfo *volatile pkg;
volatile enum action action_todo;
jmp_buf ejbuf;
enum istobes istobe= itb_normal;
if (abort_processing)
return;
clear_istobes();
switch (cipaction->arg_int) {
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 '%d'", cipaction->arg_int);
}
for (rundown = queue.head; rundown; rundown = rundown->next) {
ensure_package_clientdata(rundown->pkg);
if (rundown->pkg->clientdata->istobe == istobe) {
/* Erase the queue entry - this is a second copy! */
switch (cipaction->arg_int) {
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);
break;
case act_install:
printf(_("More than one copy of package %s has been unpacked\n"
" in this run ! Only configuring it once.\n"),
rundown->pkg->name);
break;
default:
internerr("unknown action '%d'", cipaction->arg_int);
}
rundown->pkg = NULL;
} else {
rundown->pkg->clientdata->istobe= istobe;
}
}
while (!pkg_queue_is_empty(&queue)) {
pkg = pkg_queue_pop(&queue);
if (!pkg)
continue; /* Duplicate, which we removed earlier. */
action_todo = cipaction->arg_int;
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)) {
/* Give up on it from the point of view of other packages, i.e. reset
* istobe. */
pkg->clientdata->istobe= itb_normal;
pop_error_context(ehflag_bombout);
if (abort_processing)
return;
continue;
}
push_error_context_jump(&ejbuf, print_error_perpackage, pkg->name);
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:
/* 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);
break;
default:
internerr("unknown action '%d'", cipaction->arg_int);
}
m_output(stdout, _("<standard output>"));
m_output(stderr, _("<standard error>"));
pop_error_context(ehflag_normaltidy);
}
assert(!queue.length);
}
/*** Dependency processing - common to --configure and --remove. ***/
/*
* The algorithm for deciding what to configure or remove first is as
* follows:
*
* Loop through all packages doing a ‘try 1’ until we've been round and
* nothing has been done, then do ‘try 2’ and ‘try 3’ likewise.
*
* When configuring, in each try we check to see whether all
* dependencies of this package are done. If so we do it. If some of
* the dependencies aren't done yet but will be later we defer the
* package, otherwise it is an error.
*
* When removing, in each try we check to see whether there are any
* packages that would have dependencies missing if we removed this
* one. If not we remove it now. If some of these packages are
* themselves scheduled for removal we defer the package until they
* have been done.
*
* The criteria for satisfying a dependency vary with the various
* tries. In try 1 we treat the dependencies as absolute. In try 2 we
* check break any cycles in the dependency graph involving the package
* we are trying to process before trying to process the package
* normally. In try 3 (which should only be reached if
* --force-depends-version is set) we ignore version number clauses in
* Depends lines. In try 4 (only reached if --force-depends is set) we
* say "ok" regardless.
*
* If we are configuring and one of the packages we depend on is
* awaiting configuration but wasn't specified in the argument list we
* will add it to the argument list if --configure-any is specified.
* In this case we note this as having "done something" so that we
* don't needlessly escalate to higher levels of dependency checking
* and breaking.
*/
enum found_status {
found_none = 0,
found_defer = 1,
found_forced = 2,
found_ok = 3,
};
/*
* 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 enum found_status
deppossi_ok_found(struct pkginfo *possdependee, struct pkginfo *requiredby,
struct pkginfo *removing, struct pkginfo *providing,
struct pkginfo **fixbytrig,
bool *matched, struct deppossi *checkversion,
int *interestingwarnings, struct varbuf *oemsgs)
{
enum found_status thisf;
if (ignore_depends(possdependee)) {
debug(dbg_depcondetail," ignoring depended package so ok and found");
return found_ok;
}
thisf = found_none;
if (possdependee == removing) {
if (providing) {
varbuf_printf(oemsgs,
_(" Package %s which provides %s is to be removed.\n"),
possdependee->name, providing->name);
} else {
varbuf_printf(oemsgs, _(" Package %s is to be removed.\n"),
possdependee->name);
}
*matched = true;
if (fc_depends)
thisf = (dependtry >= 4) ? found_forced : found_defer;
debug(dbg_depcondetail," removing possdependee, returning %d",thisf);
return thisf;
}
switch (possdependee->status) {
case stat_unpacked:
case stat_halfconfigured:
case stat_triggersawaited:
case stat_triggerspending:
case stat_installed:
if (checkversion && !versionsatisfied(&possdependee->installed,checkversion)) {
varbuf_printf(oemsgs, _(" Version of %s on system is %s.\n"),
possdependee->name,
versiondescribe(&possdependee->installed.version,
vdew_nonambig));
assert(checkversion->verrel != dvr_none);
if (fc_depends || fc_dependsversion)
thisf = (dependtry >= 3) ? found_forced : found_defer;
debug(dbg_depcondetail," bad version, returning %d",thisf);
(*interestingwarnings)++;
return thisf;
}
if (possdependee->status == stat_installed ||
possdependee->status == stat_triggerspending) {
debug(dbg_depcondetail," is installed, ok and found");
return found_ok;
}
if (possdependee->status == stat_triggersawaited) {
assert(possdependee->trigaw.head);
if (removing || !(f_triggers ||
possdependee->clientdata->istobe == itb_installnew)) {
if (providing) {
varbuf_printf(oemsgs,
_(" Package %s which provides %s awaits trigger processing.\n"),
possdependee->name, providing->name);
} else {
varbuf_printf(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 Triggers-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', defer",
(*fixbytrig)->name);
return found_defer;
}
if (possdependee->clientdata &&
possdependee->clientdata->istobe == itb_installnew) {
debug(dbg_depcondetail," unpacked/halfconfigured, defer");
return found_defer;
} 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);
add_to_queue(possdependee);
sincenothing = 0;
return found_defer;
} else {
if (providing) {
varbuf_printf(oemsgs,
_(" Package %s which provides %s is not configured yet.\n"),
possdependee->name, providing->name);
} else {
varbuf_printf(oemsgs, _(" Package %s is not configured yet.\n"),
possdependee->name);
}
debug(dbg_depcondetail, " not configured/able");
goto unsuitable;
}
default:
if (providing) {
varbuf_printf(oemsgs,
_(" Package %s which provides %s is not installed.\n"),
possdependee->name, providing->name);
} else {
varbuf_printf(oemsgs, _(" Package %s is not installed.\n"),
possdependee->name);
}
debug(dbg_depcondetail, " not installed");
goto unsuitable;
}
unsuitable:
if (fc_depends)
thisf = (dependtry >= 4) ? found_forced : found_defer;
debug(dbg_depcondetail, " returning %d", thisf);
(*interestingwarnings)++;
return thisf;
}
static void
breaks_check_one(struct varbuf *aemsgs, enum dep_check *ok,
struct deppossi *breaks, struct pkginfo *broken,
struct pkginfo *breaker, struct pkginfo *virtbroken)
{
struct varbuf depmsg = VARBUF_INIT;
debug(dbg_depcondetail, " checking breaker %s virtbroken %s",
breaker->name, virtbroken ? virtbroken->name : "<none>");
if (breaker->status == stat_notinstalled ||
breaker->status == stat_configfiles) return;
if (broken == breaker) return;
if (!versionsatisfied(&broken->installed, breaks)) return;
if (ignore_depends(breaker)) return;
if (virtbroken && ignore_depends(virtbroken)) return;
varbufdependency(&depmsg, breaks->up);
varbuf_end_str(&depmsg);
varbuf_printf(aemsgs, _(" %s (%s) breaks %s and is %s.\n"), breaker->name,
versiondescribe(&breaker->installed.version, vdew_nonambig),
depmsg.buf, gettext(statusstrings[breaker->status]));
varbuf_destroy(&depmsg);
if (virtbroken) {
varbuf_printf(aemsgs, _(" %s (%s) provides %s.\n"), broken->name,
versiondescribe(&broken->installed.version, vdew_nonambig),
virtbroken->name);
} else if (breaks->verrel != dvr_none) {
varbuf_printf(aemsgs, _(" Version of %s to be configured is %s.\n"),
broken->name,
versiondescribe(&broken->installed.version, vdew_nonambig));
if (fc_dependsversion) return;
}
if (force_breaks(breaks)) return;
*ok = dep_check_halt;
}
static void
breaks_check_target(struct varbuf *aemsgs, enum dep_check *ok,
struct pkginfo *broken, struct pkginfo *target,
struct pkginfo *virtbroken)
{
struct deppossi *possi;
for (possi = target->installed.depended; possi; possi = possi->rev_next) {
if (possi->up->type != dep_breaks) continue;
if (virtbroken && possi->verrel != dvr_none) continue;
breaks_check_one(aemsgs, ok, possi, broken, possi->up->up, virtbroken);
}
}
enum dep_check
breakses_ok(struct pkginfo *pkg, struct varbuf *aemsgs)
{
struct dependency *dep;
struct pkginfo *virtbroken;
enum dep_check ok = dep_check_ok;
debug(dbg_depcon, " checking Breaks");
breaks_check_target(aemsgs, &ok, pkg, pkg, NULL);
for (dep= pkg->installed.depends; dep; dep= dep->next) {
if (dep->type != dep_provides) continue;
virtbroken= dep->list->ed;
debug(dbg_depcondetail, " checking virtbroken %s", virtbroken->name);
breaks_check_target(aemsgs, &ok, pkg, virtbroken, virtbroken);
}
return ok;
}
/*
* Checks [Pre]-Depends only.
*/
enum dep_check
dependencies_ok(struct pkginfo *pkg, struct pkginfo *removing,
struct varbuf *aemsgs)
{
/* Valid values: 2 = ok, 1 = defer, 0 = halt. */
enum dep_check ok;
/* Valid values: 0 = none, 1 = defer, 2 = withwarning, 3 = ok. */
enum found_status found, thisf;
int interestingwarnings;
bool matched, anycannotfixbytrig;
struct varbuf oemsgs = VARBUF_INIT;
struct dependency *dep;
struct deppossi *possi, *provider;
struct pkginfo *possfixbytrig, *canfixbytrig;
interestingwarnings= 0;
ok = dep_check_ok;
debug(dbg_depcon,"checking dependencies of %s (- %s)",
pkg->name, removing ? removing->name : "<none>");
anycannotfixbytrig = false;
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 = false;
varbuf_reset(&oemsgs);
found = found_none;
possfixbytrig = NULL;
for (possi = dep->list; found != found_ok && possi; possi = possi->next) {
debug(dbg_depcondetail," checking possibility -> %s",possi->ed->name);
if (possi->cyclebreak) {
debug(dbg_depcondetail," break cycle so ok and found");
found = found_ok;
break;
}
thisf = deppossi_ok_found(possi->ed, pkg, removing, NULL,
&possfixbytrig,
&matched,possi,&interestingwarnings,&oemsgs);
if (thisf > found) found= thisf;
if (found != found_ok && possi->verrel == dvr_none) {
for (provider = possi->ed->installed.depended;
found != found_ok && provider;
provider = provider->rev_next) {
if (provider->up->type != dep_provides)
continue;
debug(dbg_depcondetail, " checking provider %s",
provider->up->up->name);
thisf = deppossi_ok_found(provider->up->up, pkg, removing, possi->ed,
&possfixbytrig, &matched, NULL,
&interestingwarnings, &oemsgs);
if (thisf > found)
found = thisf;
}
}
debug(dbg_depcondetail," found %d",found);
if (thisf > found) found= thisf;
}
debug(dbg_depcondetail, " found %d matched %d possfixbytrig %s",
found, matched, possfixbytrig ? possfixbytrig->name : "-");
if (removing && !matched) continue;
switch (found) {
case found_none:
anycannotfixbytrig = true;
ok = dep_check_halt;
case found_forced:
varbuf_add_str(aemsgs, " ");
varbuf_add_str(aemsgs, pkg->name);
varbuf_add_str(aemsgs, _(" depends on "));
varbufdependency(aemsgs, dep);
if (interestingwarnings) {
/* Don't print the line about the package to be removed if
* that's the only line. */
varbuf_end_str(&oemsgs);
varbuf_add_str(aemsgs, _("; however:\n"));
varbuf_add_str(aemsgs, oemsgs.buf);
} else {
varbuf_add_str(aemsgs, ".\n");
}
break;
case found_defer:
if (possfixbytrig)
canfixbytrig = possfixbytrig;
else
anycannotfixbytrig = true;
if (ok > dep_check_defer)
ok = dep_check_defer;
break;
case found_ok:
break;
default:
internerr("unknown value for found '%d'", found);
}
}
if (ok == dep_check_halt &&
(pkg->clientdata && pkg->clientdata->istobe == itb_remove))
ok = dep_check_defer;
if (!anycannotfixbytrig && canfixbytrig)
progress_bytrigproc = canfixbytrig;
varbuf_destroy(&oemsgs);
debug(dbg_depcon,"ok %d msgs >>%.*s<<", ok, (int)aemsgs->used, aemsgs->buf);
return ok;
}