/* 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 <malloc.h>
#include "apk_state.h"
#include "apk_database.h"
static int apk_state_commit_deps(struct apk_state *state,
struct apk_database *db,
struct apk_dependency_array *deps);
struct apk_state *apk_state_new(struct apk_database *db)
{
struct apk_state *state;
int num_bytes;
num_bytes = sizeof(struct apk_state) + (db->pkg_id * 2 + 7) / 8;
state = (struct apk_state*) calloc(1, num_bytes);
state->refs = 1;
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;
free(state);
}
static void apk_state_set(struct apk_state *state, int pos, int val)
{
int byte = pos / 4, offs = pos % 4;
state->bitarray[byte] &= ~(0x3 << (offs * 2));
state->bitarray[byte] |= (val & 0x3) << (offs * 2);
}
static int apk_state_get(struct apk_state *state, int pos)
{
int byte = pos / 4, offs = pos % 4;
if (state == NULL)
return APK_STATE_NOT_CONSIDERED;
return (state->bitarray[byte] >> (offs * 2)) & 0x3;
}
static int apk_state_commit_pkg(struct apk_state *state,
struct apk_database *db,
struct apk_name *name,
struct apk_package *oldpkg,
struct apk_package *newpkg)
{
const char *msg = NULL;
int r, upgrade = 0;
if (newpkg != NULL) {
r = apk_state_commit_deps(state, db, newpkg->depends);
if (r != 0)
return r;
}
if (oldpkg == NULL) {
apk_message("Installing %s (%s)",
name->name, newpkg->version);
} else if (newpkg == NULL) {
apk_message("Purging %s (%s)",
name->name, oldpkg->version);
} else {
r = apk_version_compare(APK_BLOB_STR(newpkg->version),
APK_BLOB_STR(oldpkg->version));
switch (r) {
case APK_VERSION_LESS:
msg = "Downgrading";
upgrade = 1;
break;
case APK_VERSION_EQUAL:
msg = "Re-installing";
break;
case APK_VERSION_GREATER:
msg = "Upgrading";
upgrade = 1;
break;
}
apk_message("%s %s (%s -> %s)",
msg, name->name, oldpkg->version, newpkg->version);
}
return apk_db_install_pkg(db, oldpkg, newpkg);
}
static int apk_state_commit_name(struct apk_state *state,
struct apk_database *db,
struct apk_name *name)
{
struct apk_package *oldpkg = NULL, *newpkg = NULL;
int i;
for (i = 0; i < name->pkgs->num; i++) {
if (apk_pkg_get_state(name->pkgs->item[i]) == APK_STATE_INSTALL)
oldpkg = name->pkgs->item[i];
if (apk_state_get(state, name->pkgs->item[i]->id) == APK_STATE_INSTALL)
newpkg = name->pkgs->item[i];
}
if (oldpkg == NULL && newpkg == NULL)
return 0;
/* No reinstallations for now */
if (newpkg == oldpkg)
return 0;
return apk_state_commit_pkg(state, db, name, oldpkg, newpkg);
}
static int apk_state_commit_deps(struct apk_state *state,
struct apk_database *db,
struct apk_dependency_array *deps)
{
int r, i;
if (deps == NULL)
return 0;
for (i = 0; i < deps->num; i++) {
r = apk_state_commit_name(state, db, deps->item[i].name);
if (r != 0)
return r;
}
return 0;
}
int apk_state_commit(struct apk_state *state,
struct apk_database *db)
{
struct apk_package *pkg;
struct hlist_node *c, *n;
int r;
/* Check all dependencies */
r = apk_state_commit_deps(state, db, db->world);
if (r != 0)
return r;
/* And purge all installed packages that were not considered */
hlist_for_each_entry_safe(pkg, c, n, &db->installed.packages, installed_pkgs_list)
apk_state_commit_name(state, db, pkg->name);
return 0;
}
int apk_state_satisfy_name(struct apk_state *state,
struct apk_name *name)
{
struct apk_package *preferred = NULL;
int i;
int upgrading = 1;
/* Is something already installed? Or figure out the preferred
* package. */
for (i = 0; i < name->pkgs->num; i++) {
if (apk_state_get(state, name->pkgs->item[i]->id) ==
APK_STATE_INSTALL)
return 0;
if (preferred == NULL) {
preferred = name->pkgs->item[i];
continue;
}
if (upgrading) {
if (apk_version_compare(APK_BLOB_STR(name->pkgs->item[i]->version),
APK_BLOB_STR(preferred->version)) ==
APK_VERSION_GREATER) {
preferred = name->pkgs->item[i];
continue;
}
} else {
if (apk_pkg_get_state(name->pkgs->item[i]) ==
APK_STATE_INSTALL) {
preferred = name->pkgs->item[i];
continue;
}
}
}
/* Mark conflicting names as no install */
for (i = 0; i < name->pkgs->num; i++) {
if (name->pkgs->item[i] != preferred)
apk_state_set(state, name->pkgs->item[i]->id,
APK_STATE_NO_INSTALL);
}
return apk_state_pkg_install(state, preferred);
}
int apk_state_satisfy_deps(struct apk_state *state,
struct apk_dependency_array *deps)
{
struct apk_name *name;
int r, i;
if (deps == NULL)
return 0;
for (i = 0; i < deps->num; i++) {
name = deps->item[i].name;
if (name->pkgs == NULL) {
apk_error("No providers for '%s'", name->name);
return -1;
}
r = apk_state_satisfy_name(state, name);
if (r != 0)
return r;
}
return 0;
}
void apk_state_pkg_set(struct apk_state *state,
struct apk_package *pkg)
{
apk_state_set(state, pkg->id, APK_STATE_INSTALL);
}
int apk_state_pkg_install(struct apk_state *state,
struct apk_package *pkg)
{
switch (apk_state_get(state, pkg->id)) {
case APK_STATE_INSTALL:
return 0;
case APK_STATE_NO_INSTALL:
return -1;
}
apk_state_set(state, pkg->id, APK_STATE_INSTALL);
if (pkg->depends == NULL)
return 0;
return apk_state_satisfy_deps(state, pkg->depends);
}
int apk_state_pkg_is_installed(struct apk_state *state,
struct apk_package *pkg)
{
return apk_state_get(state, pkg->id);
}