974 lines
23 KiB
C
974 lines
23 KiB
C
/* state.c - Alpine Package Keeper (APK)
|
|
*
|
|
* Copyright (C) 2005-2008 Natanael Copa <n@tanael.org>
|
|
* Copyright (C) 2008 Timo Teräs <timo.teras@iki.fi>
|
|
* All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published
|
|
* by the Free Software Foundation. See http://www.gnu.org/ for details.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <malloc.h>
|
|
|
|
#include "apk_state.h"
|
|
#include "apk_database.h"
|
|
#include "apk_print.h"
|
|
|
|
struct apk_name_choices {
|
|
unsigned short refs, num;
|
|
struct apk_package *pkgs[];
|
|
};
|
|
|
|
#if 0
|
|
struct apk_deferred_state {
|
|
unsigned int preference;
|
|
struct apk_state *state;
|
|
};
|
|
#endif
|
|
|
|
int apk_state_prune_dependency(struct apk_state *state,
|
|
struct apk_dependency *dep);
|
|
|
|
#define APK_NS_LOCKED 0x00000001
|
|
#define APK_NS_PENDING 0x00000002
|
|
#define APK_NS_ERROR 0x00000004
|
|
|
|
static void apk_state_record_conflict(struct apk_state *state,
|
|
struct apk_package *pkg)
|
|
{
|
|
struct apk_name *name = pkg->name;
|
|
|
|
state->name[name->id] = (void*) (((intptr_t) pkg) | APK_NS_ERROR | APK_NS_LOCKED);
|
|
*apk_package_array_add(&state->conflicts) = pkg;
|
|
}
|
|
|
|
static int inline ns_locked(apk_name_state_t name)
|
|
{
|
|
if (((intptr_t) name) & APK_NS_LOCKED)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static int inline ns_pending(apk_name_state_t name)
|
|
{
|
|
if (((intptr_t) name) & APK_NS_PENDING)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static int inline ns_error(apk_name_state_t name)
|
|
{
|
|
if (((intptr_t) name) & APK_NS_ERROR)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static int ns_empty(apk_name_state_t name)
|
|
{
|
|
return name == NULL;
|
|
}
|
|
|
|
static apk_name_state_t ns_from_pkg(struct apk_package *pkg)
|
|
{
|
|
return (apk_name_state_t) (((intptr_t) pkg) | APK_NS_LOCKED | APK_NS_PENDING);
|
|
}
|
|
|
|
static apk_name_state_t ns_from_pkg_non_pending(struct apk_package *pkg)
|
|
{
|
|
return (apk_name_state_t) (((intptr_t) pkg) | APK_NS_LOCKED);
|
|
}
|
|
|
|
static struct apk_package *ns_to_pkg(apk_name_state_t name)
|
|
{
|
|
return (struct apk_package *)
|
|
(((intptr_t) name) & ~(APK_NS_LOCKED | APK_NS_PENDING | APK_NS_ERROR));
|
|
}
|
|
|
|
static apk_name_state_t ns_from_choices(struct apk_name_choices *nc)
|
|
{
|
|
if (nc == NULL)
|
|
return ns_from_pkg(NULL);
|
|
return (apk_name_state_t) nc;
|
|
}
|
|
|
|
static struct apk_name_choices *ns_to_choices(apk_name_state_t name)
|
|
{
|
|
return (struct apk_name_choices *) name;
|
|
}
|
|
|
|
static struct apk_name_choices *name_choices_new(struct apk_database *db,
|
|
struct apk_name *name)
|
|
{
|
|
struct apk_name_choices *nc;
|
|
int i, j;
|
|
|
|
if (name->pkgs->num == 0)
|
|
return NULL;
|
|
|
|
nc = malloc(sizeof(struct apk_name_choices) +
|
|
name->pkgs->num * sizeof(struct apk_package *));
|
|
if (nc == NULL)
|
|
return NULL;
|
|
|
|
nc->refs = 1;
|
|
nc->num = name->pkgs->num;
|
|
memcpy(nc->pkgs, name->pkgs->item,
|
|
name->pkgs->num * sizeof(struct apk_package *));
|
|
|
|
if (name->flags & APK_NAME_TOPLEVEL_OVERRIDE)
|
|
return nc;
|
|
|
|
/* Check for global dependencies */
|
|
for (i = 0; i < db->world->num; i++) {
|
|
struct apk_dependency *dep = &db->world->item[i];
|
|
|
|
if (dep->name != name)
|
|
continue;
|
|
|
|
for (j = 0; j < nc->num; ) {
|
|
if (apk_version_compare_blob(*nc->pkgs[j]->version, *dep->version)
|
|
& dep->result_mask) {
|
|
j++;
|
|
} else {
|
|
nc->pkgs[j] = nc->pkgs[nc->num - 1];
|
|
nc->num--;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nc;
|
|
}
|
|
|
|
static void name_choices_unref(struct apk_name_choices *nc)
|
|
{
|
|
if (--nc->refs == 0)
|
|
free(nc);
|
|
}
|
|
|
|
static struct apk_name_choices *name_choices_writable(struct apk_name_choices *nc)
|
|
{
|
|
struct apk_name_choices *n;
|
|
|
|
if (nc->refs == 1)
|
|
return nc;
|
|
|
|
n = malloc(sizeof(struct apk_name_choices) +
|
|
nc->num * sizeof(struct apk_package *));
|
|
if (n == NULL)
|
|
return NULL;
|
|
|
|
n->refs = 1;
|
|
n->num = nc->num;
|
|
memcpy(n->pkgs, nc->pkgs, nc->num * sizeof(struct apk_package *));
|
|
name_choices_unref(nc);
|
|
|
|
return n;
|
|
}
|
|
|
|
static void ns_free(apk_name_state_t name)
|
|
{
|
|
if (!ns_empty(name) && !ns_locked(name))
|
|
name_choices_unref(ns_to_choices(name));
|
|
}
|
|
|
|
static inline int apk_state_pkg_available(struct apk_state *state,
|
|
struct apk_package *pkg)
|
|
{
|
|
if (pkg->ipkg != NULL)
|
|
return TRUE;
|
|
if (pkg->installed_size == 0)
|
|
return TRUE;
|
|
if (pkg->filename != NULL)
|
|
return TRUE;
|
|
if (apk_db_select_repo(state->db, pkg) != NULL)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
struct apk_state *apk_state_new(struct apk_database *db)
|
|
{
|
|
struct apk_state *state;
|
|
int num_bytes;
|
|
|
|
num_bytes = sizeof(struct apk_state) + db->name_id * sizeof(char *);
|
|
state = (struct apk_state*) calloc(1, num_bytes);
|
|
state->refs = 1;
|
|
state->num_names = db->name_id;
|
|
state->db = db;
|
|
state->print_ok = 1;
|
|
list_init(&state->change_list_head);
|
|
|
|
apk_name_array_init(&state->missing);
|
|
apk_package_array_init(&state->conflicts);
|
|
|
|
return state;
|
|
}
|
|
|
|
struct apk_state *apk_state_dup(struct apk_state *state)
|
|
{
|
|
state->refs++;
|
|
return state;
|
|
}
|
|
|
|
void apk_state_unref(struct apk_state *state)
|
|
{
|
|
if (--state->refs > 0)
|
|
return;
|
|
|
|
apk_package_array_free(&state->conflicts);
|
|
apk_name_array_free(&state->missing);
|
|
free(state);
|
|
}
|
|
|
|
static int apk_state_add_change(struct apk_state *state,
|
|
struct apk_package *oldpkg,
|
|
struct apk_package *newpkg)
|
|
{
|
|
struct apk_change *change;
|
|
|
|
change = (struct apk_change *) malloc(sizeof(struct apk_change));
|
|
if (change == NULL)
|
|
return -1;
|
|
|
|
list_init(&change->change_list);
|
|
list_add_tail(&change->change_list, &state->change_list_head);
|
|
state->num_changes++;
|
|
change->oldpkg = oldpkg;
|
|
change->newpkg = newpkg;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* returns:
|
|
* -1 error
|
|
* 0 locked entry matches and is ok
|
|
* +n this many candidates on apk_name_choices for the name
|
|
*/
|
|
int apk_state_prune_dependency(struct apk_state *state,
|
|
struct apk_dependency *dep)
|
|
{
|
|
struct apk_name *name = dep->name;
|
|
struct apk_name_choices *c;
|
|
int i;
|
|
|
|
if (name->id >= state->num_names)
|
|
return -1;
|
|
|
|
if (ns_empty(state->name[name->id])) {
|
|
if (dep->result_mask == APK_DEPMASK_CONFLICT) {
|
|
state->name[name->id] = ns_from_pkg(NULL);
|
|
return 1;
|
|
}
|
|
|
|
/* This name has not been visited yet.
|
|
* Construct list of candidates. */
|
|
state->name[name->id] = ns_from_choices(name_choices_new(state->db, name));
|
|
}
|
|
|
|
if (ns_locked(state->name[name->id])) {
|
|
/* Locked: check that selected package provides
|
|
* requested version. */
|
|
struct apk_package *pkg = ns_to_pkg(state->name[name->id]);
|
|
|
|
/* Locked to not-installed / remove? */
|
|
if (ns_error(state->name[name->id])) {
|
|
return -1;
|
|
} else if (pkg == NULL) {
|
|
if (dep->result_mask != APK_DEPMASK_CONFLICT) {
|
|
if (ns_pending(state->name[name->id])) {
|
|
state->name[name->id] = ns_from_pkg_non_pending(NULL);
|
|
*apk_name_array_add(&state->missing) = name;
|
|
}
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (!(apk_version_compare_blob(*pkg->version, *dep->version)
|
|
& dep->result_mask))
|
|
return -1;
|
|
}
|
|
|
|
if (ns_pending(state->name[name->id]))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Multiple candidates: prune incompatible versions. */
|
|
c = ns_to_choices(state->name[name->id]);
|
|
i = 0;
|
|
while (i < c->num) {
|
|
if (apk_version_compare_blob(*c->pkgs[i]->version, *dep->version)
|
|
& dep->result_mask) {
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
c = name_choices_writable(c);
|
|
c->pkgs[i] = c->pkgs[c->num - 1];
|
|
c->num--;
|
|
}
|
|
if (c->num == 1 && apk_state_pkg_available(state, c->pkgs[0])) {
|
|
struct apk_package *pkg = c->pkgs[0];
|
|
name_choices_unref(c);
|
|
state->name[name->id] = ns_from_pkg(pkg);
|
|
return 1;
|
|
}
|
|
if (c->num <= 1) {
|
|
name_choices_unref(c);
|
|
state->name[name->id] = ns_from_pkg(NULL);
|
|
*apk_name_array_add(&state->missing) = name;
|
|
return -1;
|
|
}
|
|
|
|
state->name[name->id] = ns_from_choices(c);
|
|
return c->num;
|
|
}
|
|
|
|
int apk_state_lock_dependency(struct apk_state *state,
|
|
struct apk_dependency *dep)
|
|
{
|
|
struct apk_name *name = dep->name;
|
|
struct apk_name_choices *c;
|
|
struct apk_package *installed = NULL, *latest = NULL, *use;
|
|
int i, r;
|
|
|
|
r = apk_state_prune_dependency(state, dep);
|
|
if (r <= 0)
|
|
return r;
|
|
|
|
if (ns_pending(state->name[name->id]))
|
|
return apk_state_lock_name(state, name, ns_to_pkg(state->name[name->id]));
|
|
|
|
c = ns_to_choices(state->name[name->id]);
|
|
#if 1
|
|
/* Get latest and installed packages */
|
|
for (i = 0; i < c->num; i++) {
|
|
struct apk_package *pkg = c->pkgs[i];
|
|
|
|
if (pkg->ipkg != NULL)
|
|
installed = pkg;
|
|
else if (!apk_state_pkg_available(state, pkg))
|
|
continue;
|
|
|
|
if (latest == NULL) {
|
|
latest = pkg;
|
|
continue;
|
|
}
|
|
|
|
if ((apk_flags & APK_PREFER_AVAILABLE) ||
|
|
(name->flags & APK_NAME_REINSTALL)) {
|
|
if (latest->repos != 0 && pkg->repos == 0)
|
|
continue;
|
|
|
|
if (latest->repos == 0 && pkg->repos != 0) {
|
|
latest = pkg;
|
|
continue;
|
|
}
|
|
|
|
/* Otherwise both are not available, or both are
|
|
* available and we just compare the versions then */
|
|
}
|
|
|
|
if (apk_pkg_version_compare(pkg, latest) == APK_VERSION_GREATER)
|
|
latest = pkg;
|
|
}
|
|
|
|
/* Choose the best looking candidate.
|
|
* FIXME: We should instead try all alternatives. */
|
|
if (apk_flags & APK_UPGRADE) {
|
|
use = latest;
|
|
} else {
|
|
if (installed != NULL &&
|
|
(installed->repos != 0 ||
|
|
!(name->flags & APK_NAME_REINSTALL)))
|
|
use = installed;
|
|
else
|
|
use = latest;
|
|
}
|
|
if (use == NULL)
|
|
return -1;
|
|
|
|
return apk_state_lock_name(state, name, use);
|
|
#else
|
|
/* If any of the choices is installed, we are good. Otherwise,
|
|
* the caller needs to install this dependency. */
|
|
for (i = 0; i < c->num; i++)
|
|
if (apk_pkg_get_state(c->pkgs[i]) == APK_PKG_INSTALLED)
|
|
return 0;
|
|
|
|
/* Queue for deferred solution. */
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static int apk_state_fix_package(struct apk_state *state,
|
|
struct apk_package *pkg)
|
|
{
|
|
int i, r, ret = 0;
|
|
|
|
if (pkg == NULL)
|
|
return 0;
|
|
|
|
for (i = 0; i < pkg->depends->num; i++) {
|
|
r = apk_state_lock_dependency(state,
|
|
&pkg->depends->item[i]);
|
|
if (r != 0)
|
|
ret = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int call_if_dependency_broke(struct apk_state *state,
|
|
struct apk_package *pkg,
|
|
struct apk_name *dep_name,
|
|
int (*cb)(struct apk_state *state,
|
|
struct apk_package *pkg,
|
|
struct apk_dependency *dep,
|
|
void *ctx),
|
|
void *ctx)
|
|
{
|
|
struct apk_package *dep_pkg;
|
|
int k;
|
|
|
|
dep_pkg = ns_to_pkg(state->name[dep_name->id]);
|
|
for (k = 0; k < pkg->depends->num; k++) {
|
|
struct apk_dependency *dep = &pkg->depends->item[k];
|
|
if (dep->name != dep_name)
|
|
continue;
|
|
if (dep_pkg == NULL &&
|
|
dep->result_mask == APK_DEPMASK_CONFLICT)
|
|
continue;
|
|
if (dep_pkg != NULL &&
|
|
(apk_version_compare_blob(*dep_pkg->version, *dep->version)
|
|
& dep->result_mask))
|
|
continue;
|
|
return cb(state, pkg, dep, ctx);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int for_each_broken_reverse_depency(struct apk_state *state,
|
|
struct apk_name *name,
|
|
int (*cb)(struct apk_state *state,
|
|
struct apk_package *pkg,
|
|
struct apk_dependency *dep,
|
|
void *ctx),
|
|
void *ctx)
|
|
{
|
|
struct apk_package *pkg0;
|
|
int i, j, r;
|
|
|
|
for (i = 0; i < name->rdepends->num; i++) {
|
|
struct apk_name *name0 = name->rdepends->item[i];
|
|
|
|
if (ns_locked(state->name[name0->id])) {
|
|
pkg0 = ns_to_pkg(state->name[name0->id]);
|
|
if (pkg0 == NULL)
|
|
continue;
|
|
r = call_if_dependency_broke(state, pkg0, name,
|
|
cb, ctx);
|
|
if (r != 0)
|
|
return r;
|
|
} else if (!ns_empty(state->name[name0->id])) {
|
|
struct apk_name_choices *ns =
|
|
ns_to_choices(state->name[name0->id]);
|
|
|
|
for (j = 0; j < ns->num; j++) {
|
|
if (ns->pkgs[j]->ipkg == NULL)
|
|
continue;
|
|
r = call_if_dependency_broke(state,
|
|
ns->pkgs[j],
|
|
name, cb, ctx);
|
|
if (r != 0)
|
|
return r;
|
|
break;
|
|
}
|
|
} else {
|
|
for (j = 0; j < name0->pkgs->num; j++) {
|
|
pkg0 = name0->pkgs->item[j];
|
|
|
|
if (pkg0->ipkg == NULL)
|
|
continue;
|
|
|
|
r = call_if_dependency_broke(state,
|
|
name0->pkgs->item[j],
|
|
name, cb, ctx);
|
|
if (r != 0)
|
|
return r;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int delete_broken_package(struct apk_state *state,
|
|
struct apk_package *pkg,
|
|
struct apk_dependency *dep,
|
|
void *ctx)
|
|
{
|
|
return apk_state_lock_name(state, pkg->name, NULL);
|
|
}
|
|
|
|
static int reinstall_broken_package(struct apk_state *state,
|
|
struct apk_package *pkg,
|
|
struct apk_dependency *dep,
|
|
void *ctx)
|
|
|
|
{
|
|
struct apk_dependency dep0 = {
|
|
.name = pkg->name,
|
|
.version = apk_blob_atomize(APK_BLOB_NULL),
|
|
.result_mask = APK_DEPMASK_REQUIRE,
|
|
};
|
|
return apk_state_lock_dependency(state, &dep0);
|
|
}
|
|
|
|
int apk_state_lock_name(struct apk_state *state,
|
|
struct apk_name *name,
|
|
struct apk_package *newpkg)
|
|
{
|
|
struct apk_package *oldpkg = NULL;
|
|
int i, r;
|
|
|
|
if (name->id >= state->num_names)
|
|
return -1;
|
|
|
|
ns_free(state->name[name->id]);
|
|
state->name[name->id] = ns_from_pkg_non_pending(newpkg);
|
|
|
|
for (i = 0; i < name->pkgs->num; i++) {
|
|
struct apk_package *pkg = name->pkgs->item[i];
|
|
if (name->pkgs->item[i]->name == name &&
|
|
pkg->ipkg != NULL)
|
|
oldpkg = pkg;
|
|
}
|
|
|
|
/* First we need to make sure the dependants of the old package
|
|
* still have their dependencies ok. */
|
|
if (oldpkg != NULL) {
|
|
r = for_each_broken_reverse_depency(state, name,
|
|
newpkg == NULL ?
|
|
delete_broken_package :
|
|
reinstall_broken_package,
|
|
NULL);
|
|
if (r != 0) {
|
|
apk_state_record_conflict(state, newpkg);
|
|
return r;
|
|
}
|
|
}
|
|
|
|
/* Check that all other dependencies hold for the new package. */
|
|
r = apk_state_fix_package(state, newpkg);
|
|
if (r != 0) {
|
|
apk_state_record_conflict(state, newpkg);
|
|
return r;
|
|
}
|
|
|
|
/* If the chosen package is installed, all is done here */
|
|
if (oldpkg == newpkg &&
|
|
(newpkg == NULL ||
|
|
!(newpkg->name->flags & APK_NAME_REINSTALL)))
|
|
return 0;
|
|
|
|
/* Track change */
|
|
r = apk_state_add_change(state, oldpkg, newpkg);
|
|
if (r != 0)
|
|
return r;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void apk_print_change(struct apk_database *db,
|
|
struct apk_package *oldpkg,
|
|
struct apk_package *newpkg,
|
|
int num, int total)
|
|
{
|
|
const char *msg = NULL;
|
|
int r;
|
|
struct apk_name *name;
|
|
char status[64];
|
|
|
|
snprintf(status, sizeof(status), "(%i/%i)", num, total);
|
|
status[sizeof(status) - 1] = '\0';
|
|
|
|
if (oldpkg != NULL)
|
|
name = oldpkg->name;
|
|
else
|
|
name = newpkg->name;
|
|
|
|
if (oldpkg == NULL) {
|
|
apk_message("%s Installing %s (" BLOB_FMT ")",
|
|
status, name->name,
|
|
BLOB_PRINTF(*newpkg->version));
|
|
} else if (newpkg == NULL) {
|
|
apk_message("%s Purging %s (" BLOB_FMT ")",
|
|
status, name->name,
|
|
BLOB_PRINTF(*oldpkg->version));
|
|
} else {
|
|
r = apk_pkg_version_compare(newpkg, oldpkg);
|
|
switch (r) {
|
|
case APK_VERSION_LESS:
|
|
msg = "Downgrading";
|
|
break;
|
|
case APK_VERSION_EQUAL:
|
|
if (newpkg == oldpkg) {
|
|
msg = "Re-installing";
|
|
break;
|
|
}
|
|
/* fallthrough - equal version, but different
|
|
* package is counted as upgrade */
|
|
case APK_VERSION_GREATER:
|
|
msg = "Upgrading";
|
|
break;
|
|
}
|
|
apk_message("%s %s %s (" BLOB_FMT " -> " BLOB_FMT ")",
|
|
status, msg, name->name,
|
|
BLOB_PRINTF(*oldpkg->version),
|
|
BLOB_PRINTF(*newpkg->version));
|
|
}
|
|
}
|
|
|
|
struct apk_stats {
|
|
unsigned int bytes;
|
|
unsigned int packages;
|
|
};
|
|
|
|
static void apk_count_change(struct apk_change *change, struct apk_stats *stats)
|
|
{
|
|
if (change->newpkg != NULL) {
|
|
stats->bytes += change->newpkg->installed_size;
|
|
stats->packages ++;
|
|
}
|
|
if (change->oldpkg != NULL)
|
|
stats->packages ++;
|
|
}
|
|
|
|
static inline void apk_draw_progress(int percent, int last)
|
|
{
|
|
char tmp[128];
|
|
char reset[128];
|
|
int i;
|
|
|
|
snprintf(tmp, sizeof(tmp), "-[ ]- %3i%%", percent);
|
|
for (i = 0; (i < (percent/5)) && (i < (sizeof(tmp)-2)); i++)
|
|
tmp[2+i] = '#';
|
|
memset(reset, '\b', strlen(tmp));
|
|
fwrite(tmp, strlen(tmp), 1, stderr);
|
|
if (!last)
|
|
fwrite(reset, strlen(tmp), 1, stderr);
|
|
else if (apk_verbosity > 0)
|
|
fwrite("\n", 1, 1, stderr);
|
|
fflush(stderr);
|
|
}
|
|
|
|
struct progress {
|
|
struct apk_stats done;
|
|
struct apk_stats total;
|
|
struct apk_package *pkg;
|
|
size_t count;
|
|
};
|
|
|
|
static void progress_cb(void *ctx, size_t progress)
|
|
{
|
|
struct progress *prog = (struct progress *) ctx;
|
|
size_t partial = 0, count;
|
|
|
|
if (prog->pkg != NULL)
|
|
partial = muldiv(progress, prog->pkg->installed_size, APK_PROGRESS_SCALE);
|
|
|
|
count = muldiv(100, prog->done.bytes + prog->done.packages + partial,
|
|
prog->total.bytes + prog->total.packages);
|
|
|
|
if (prog->count != count)
|
|
apk_draw_progress(count, 0);
|
|
prog->count = count;
|
|
}
|
|
|
|
static int dump_packages(struct apk_state *state,
|
|
int (*cmp)(struct apk_change *change),
|
|
const char *msg)
|
|
{
|
|
struct apk_change *change;
|
|
struct apk_name *name;
|
|
struct apk_indent indent = { 0, 2 };
|
|
char tmp[256];
|
|
int match = 0, i;
|
|
|
|
list_for_each_entry(change, &state->change_list_head, change_list) {
|
|
if (!cmp(change))
|
|
continue;
|
|
if (match == 0)
|
|
printf("%s:\n ", msg);
|
|
if (change->newpkg != NULL)
|
|
name = change->newpkg->name;
|
|
else
|
|
name = change->oldpkg->name;
|
|
|
|
i = snprintf(tmp, sizeof(tmp), "%s%s", name->name,
|
|
(name->flags & APK_NAME_TOPLEVEL) ? "*" : "");
|
|
apk_print_indented(&indent, APK_BLOB_PTR_LEN(tmp, i));
|
|
match++;
|
|
}
|
|
if (match)
|
|
printf("\n");
|
|
return match;
|
|
}
|
|
|
|
static int cmp_remove(struct apk_change *change)
|
|
{
|
|
return change->newpkg == NULL;
|
|
}
|
|
|
|
static int cmp_new(struct apk_change *change)
|
|
{
|
|
return change->oldpkg == NULL;
|
|
}
|
|
|
|
static int cmp_downgrade(struct apk_change *change)
|
|
{
|
|
if (change->newpkg == NULL || change->oldpkg == NULL)
|
|
return 0;
|
|
if (apk_pkg_version_compare(change->newpkg, change->oldpkg)
|
|
& APK_VERSION_LESS)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int cmp_upgrade(struct apk_change *change)
|
|
{
|
|
if (change->newpkg == NULL || change->oldpkg == NULL)
|
|
return 0;
|
|
|
|
/* Count swapping package as upgrade too - this can happen if
|
|
* same package version is used after it was rebuilt against
|
|
* newer libraries. Basically, different (and probably newer)
|
|
* package, but equal version number. */
|
|
if ((apk_pkg_version_compare(change->newpkg, change->oldpkg) &
|
|
(APK_VERSION_GREATER | APK_VERSION_EQUAL)) &&
|
|
(change->newpkg != change->oldpkg))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fail_if_something_broke(struct apk_state *state,
|
|
struct apk_package *pkg,
|
|
struct apk_dependency *dep,
|
|
void *ctx)
|
|
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static int apk_state_autoclean(struct apk_state *state,
|
|
struct apk_package *pkg)
|
|
{
|
|
apk_name_state_t oldns;
|
|
int i, r;
|
|
|
|
for (i = 0; i < pkg->depends->num; i++) {
|
|
struct apk_name *n = pkg->depends->item[i].name;
|
|
|
|
if (ns_locked(state->name[n->id]))
|
|
continue;
|
|
if (n->flags & APK_NAME_TOPLEVEL)
|
|
continue;
|
|
|
|
oldns = state->name[n->id];
|
|
state->name[n->id] = ns_from_pkg(NULL);
|
|
r = for_each_broken_reverse_depency(state, n,
|
|
fail_if_something_broke,
|
|
NULL);
|
|
state->name[n->id] = oldns;
|
|
|
|
if (r == 0) {
|
|
r = apk_state_lock_name(state, n, NULL);
|
|
if (r != 0)
|
|
return r;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct error_state {
|
|
struct apk_indent indent;
|
|
struct apk_package *prevpkg;
|
|
};
|
|
|
|
static int print_dep(struct apk_state *state,
|
|
struct apk_package *pkg,
|
|
struct apk_dependency *dep,
|
|
void *ctx)
|
|
{
|
|
struct error_state *es = (struct error_state *) ctx;
|
|
apk_blob_t blob;
|
|
char buf[256];
|
|
int len;
|
|
|
|
if (pkg != es->prevpkg) {
|
|
printf("\n");
|
|
es->indent.x = 0;
|
|
len = snprintf(buf, sizeof(buf), PKG_VER_FMT ":",
|
|
PKG_VER_PRINTF(pkg));
|
|
apk_print_indented(&es->indent, APK_BLOB_PTR_LEN(buf, len));
|
|
es->prevpkg = pkg;
|
|
}
|
|
|
|
blob = APK_BLOB_BUF(buf);
|
|
apk_blob_push_dep(&blob, dep);
|
|
blob = apk_blob_pushed(APK_BLOB_BUF(buf), blob);
|
|
apk_print_indented(&es->indent, blob);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void apk_state_print_errors(struct apk_state *state)
|
|
{
|
|
struct apk_package *pkg;
|
|
struct error_state es;
|
|
int i, j, r;
|
|
|
|
for (i = 0; i < state->conflicts->num; i++) {
|
|
if (i == 0)
|
|
apk_error("Unable to satisfy all dependencies:");
|
|
|
|
es.prevpkg = pkg = state->conflicts->item[i];
|
|
es.indent.x =
|
|
printf(" " PKG_VER_FMT ":", PKG_VER_PRINTF(pkg));
|
|
es.indent.indent = es.indent.x + 1;
|
|
for (j = 0; j < pkg->depends->num; j++) {
|
|
r = apk_state_lock_dependency(state,
|
|
&pkg->depends->item[j]);
|
|
if (r != 0)
|
|
print_dep(state, pkg, &pkg->depends->item[j], &es);
|
|
}
|
|
|
|
/* Print conflicting reverse deps */
|
|
for_each_broken_reverse_depency(state, pkg->name,
|
|
print_dep, &es);
|
|
printf("\n");
|
|
}
|
|
|
|
for (i = 0; i < state->missing->num; i++) {
|
|
struct apk_name *name = state->missing->item[i];
|
|
if (i == 0) {
|
|
apk_error("Missing packages:");
|
|
es.indent.x = 0;
|
|
es.indent.indent = 2;
|
|
}
|
|
apk_print_indented(&es.indent, APK_BLOB_STR(name->name));
|
|
}
|
|
if (i != 0)
|
|
printf("\n");
|
|
}
|
|
|
|
int apk_state_commit(struct apk_state *state,
|
|
struct apk_database *db)
|
|
{
|
|
struct progress prog;
|
|
struct apk_change *change;
|
|
int n = 0, r = 0, size_diff = 0, toplevel = FALSE, deleteonly = TRUE;
|
|
|
|
/* Count what needs to be done */
|
|
memset(&prog, 0, sizeof(prog));
|
|
list_for_each_entry(change, &state->change_list_head, change_list) {
|
|
if (change->newpkg == NULL) {
|
|
if (change->oldpkg->name->flags & APK_NAME_TOPLEVEL)
|
|
toplevel = TRUE;
|
|
} else
|
|
deleteonly = FALSE;
|
|
if (change->oldpkg != NULL)
|
|
apk_state_autoclean(state, change->oldpkg);
|
|
apk_count_change(change, &prog.total);
|
|
if (change->newpkg)
|
|
size_diff += change->newpkg->installed_size;
|
|
if (change->oldpkg)
|
|
size_diff -= change->oldpkg->installed_size;
|
|
}
|
|
size_diff /= 1024;
|
|
|
|
if (toplevel &&
|
|
(apk_flags & (APK_INTERACTIVE | APK_RECURSIVE_DELETE)) == 0) {
|
|
if (!deleteonly)
|
|
return -1;
|
|
|
|
dump_packages(state, cmp_remove,
|
|
"The top-level dependencies have been updated "
|
|
"but the following packages are not removed");
|
|
goto update_state;
|
|
}
|
|
|
|
if (apk_verbosity > 1 || (apk_flags & APK_INTERACTIVE)) {
|
|
r = dump_packages(state, cmp_remove,
|
|
"The following packages will be REMOVED");
|
|
r += dump_packages(state, cmp_downgrade,
|
|
"The following packages will be DOWNGRADED");
|
|
if (r || (apk_flags & APK_INTERACTIVE) || apk_verbosity > 2) {
|
|
dump_packages(state, cmp_new,
|
|
"The following NEW packages will be installed");
|
|
dump_packages(state, cmp_upgrade,
|
|
"The following packages will be upgraded");
|
|
printf("%d kB of %s\n", abs(size_diff),
|
|
(size_diff < 0) ?
|
|
"disk space will be freed" :
|
|
"additional disk space will be used");
|
|
}
|
|
if (apk_flags & APK_INTERACTIVE) {
|
|
printf("Do you want to continue [Y/n]? ");
|
|
fflush(stdout);
|
|
r = fgetc(stdin);
|
|
if (r != 'y' && r != 'Y' && r != '\n')
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Go through changes */
|
|
r = 0;
|
|
n = 0;
|
|
list_for_each_entry(change, &state->change_list_head, change_list) {
|
|
n++;
|
|
apk_print_change(db, change->oldpkg, change->newpkg, n, state->num_changes);
|
|
prog.pkg = change->newpkg;
|
|
|
|
if (!(apk_flags & APK_SIMULATE)) {
|
|
r = apk_db_install_pkg(db,
|
|
change->oldpkg, change->newpkg,
|
|
(apk_flags & APK_PROGRESS) ? progress_cb : NULL,
|
|
&prog);
|
|
if (r != 0)
|
|
break;
|
|
|
|
if (change->oldpkg != NULL &&
|
|
change->newpkg == NULL &&
|
|
change->oldpkg->name->flags & APK_NAME_TOPLEVEL) {
|
|
change->oldpkg->name->flags &= ~APK_NAME_TOPLEVEL;
|
|
apk_deps_del(&db->world, change->oldpkg->name);
|
|
}
|
|
}
|
|
|
|
apk_count_change(change, &prog.done);
|
|
}
|
|
if (apk_flags & APK_PROGRESS)
|
|
apk_draw_progress(100, 1);
|
|
|
|
update_state:
|
|
apk_db_run_triggers(db);
|
|
apk_db_write_config(db);
|
|
|
|
if (r == 0 && state->print_ok)
|
|
apk_message("OK: %d packages, %d dirs, %d files",
|
|
db->installed.stats.packages,
|
|
db->installed.stats.dirs,
|
|
db->installed.stats.files);
|
|
|
|
return r;
|
|
}
|