Commit a23f6f4a authored by Timo Teräs's avatar Timo Teräs

state: rework changeset calculation algorithm

Calculate changesets directly by stabilizating the package graph instead of
recalculating the whole graph and then diffing (similar approach as seen
in 'smart' package manager). The algorithm is not complete: defferred
search space forking is missing. So you don't always get a solution on
complex graphs.

Benefits:
- usually the search state tree is smaller (less memory used)
- speed relational to changeset size, not database size (usually faster)
- touch only packages related to users request (can work on partitially
  broken state; upgrades only necessary packages, fixes #7)

Also implemented:
- command prompt to confirm operation if packages are deleted or downgraded
- requesting deletion of package suggests removal of all packages depending
  on the package being removed (you'll get list of packages that also get
  removed if you want package X removed)
- option --simulate to see what would have been done (mainly for testing)
- an untested implementation of versioned dependencies and conflicts

A lot has changed, so expect new bugs too.
parent 7cef96c3
......@@ -13,6 +13,7 @@
#include <stdio.h>
#include "apk_applet.h"
#include "apk_database.h"
#include "apk_state.h"
struct add_ctx {
unsigned int open_flags;
......@@ -27,7 +28,7 @@ static int add_parse(void *ctx, int optch, int optindex, const char *optarg)
actx->open_flags |= APK_OPENF_CREATE;
break;
case 'u':
apk_upgrade = 1;
apk_flags |= APK_UPGRADE;
break;
default:
return -1;
......@@ -39,12 +40,14 @@ static int add_main(void *ctx, int argc, char **argv)
{
struct add_ctx *actx = (struct add_ctx *) ctx;
struct apk_database db;
int i, r, ret = 1;
struct apk_state *state;
int i, r;
r = apk_db_open(&db, apk_root, actx->open_flags | APK_OPENF_WRITE);
if (r != 0)
return r;
state = apk_state_new(&db);
for (i = 0; i < argc; i++) {
struct apk_dependency dep;
......@@ -59,20 +62,29 @@ static int add_main(void *ctx, int argc, char **argv)
dep = (struct apk_dependency) {
.name = pkg->name,
.min_version = pkg->version,
.max_version = pkg->version,
.version = pkg->version,
.result_mask = APK_VERSION_EQUAL,
};
} else {
dep = (struct apk_dependency) {
.name = apk_db_get_name(&db, APK_BLOB_STR(argv[i])),
.result_mask = APK_DEPMASK_REQUIRE,
};
}
apk_deps_add(&db.world, &dep);
dep.name->flags |= APK_NAME_TOPLEVEL;
r = apk_state_lock_dependency(state, &dep);
if (r != 0) {
apk_error("Unable to install '%s'", dep.name->name);
goto err;
}
}
ret = apk_db_recalculate_and_commit(&db);
r = apk_state_commit(state, &db);
err:
apk_state_unref(state);
apk_db_close(&db);
return ret;
return r;
}
static struct option add_options[] = {
......
......@@ -23,9 +23,8 @@
const char *apk_root;
struct apk_repository_url apk_repository_list;
int apk_verbosity = 1, apk_progress = 0, apk_upgrade = 0;
int apk_clean = 0, apk_force = 0;
int apk_cwd_fd;
int apk_verbosity = 1, apk_cwd_fd;
unsigned int apk_flags = 0;
void apk_log(const char *prefix, const char *format, ...)
{
......@@ -111,16 +110,17 @@ static struct apk_repository_url *apk_repository_new(const char *url)
return r;
}
#define NUM_GENERIC_OPTS 8
#define NUM_GENERIC_OPTS 9
static struct option generic_options[32] = {
{ "root", required_argument, NULL, 'p' },
{ "repository", required_argument, NULL, 'X' },
{ "quiet", no_argument, NULL, 'q' },
{ "verbose", no_argument, NULL, 'v' },
{ "version", no_argument, NULL, 'V' },
{ "progress", no_argument, &apk_progress, 1 },
{ "clean-protected", no_argument, &apk_clean, 1 },
{ "force", no_argument, &apk_force, 1 },
{ "progress", no_argument, NULL, 0x101 },
{ "clean-protected", no_argument, NULL, 0x102 },
{ "force", no_argument, NULL, 0x103 },
{ "simulate", no_argument, NULL, 0x104 },
};
int main(int argc, char **argv)
......@@ -181,6 +181,17 @@ int main(int argc, char **argv)
break;
case 'V':
return version();
case 0x101:
apk_flags |= APK_PROGRESS;
break;
case 0x102:
apk_flags |= APK_CLEAN_PROTECTED;
break;
case 0x103:
apk_flags |= APK_FORCE;
break;
case 0x104:
apk_flags |= APK_SIMULATE;
break;
default:
if (applet == NULL || applet->parse == NULL)
......
......@@ -54,8 +54,12 @@ struct apk_db_dir_instance {
gid_t gid;
};
#define APK_NAME_TOPLEVEL 0x0001
struct apk_name {
apk_hash_node hash_node;
unsigned int id;
unsigned int flags;
char *name;
struct apk_package_array *pkgs;
struct apk_name_array *rdepends;
......@@ -68,7 +72,7 @@ struct apk_repository {
struct apk_database {
char *root;
int root_fd, lock_fd;
unsigned pkg_id, num_repos;
unsigned name_id, num_repos;
struct apk_dependency_array *world;
struct apk_string_array *protected_paths;
......@@ -109,6 +113,7 @@ struct apk_db_file *apk_db_file_query(struct apk_database *db,
#define APK_OPENF_CREATE 0x0002
int apk_db_open(struct apk_database *db, const char *root, unsigned int flags);
int apk_db_write_config(struct apk_database *db);
void apk_db_close(struct apk_database *db);
struct apk_package *apk_db_pkg_add_file(struct apk_database *db, const char *file);
......@@ -118,8 +123,6 @@ struct apk_package *apk_db_get_file_owner(struct apk_database *db, apk_blob_t fi
int apk_db_index_write(struct apk_database *db, struct apk_ostream *os);
int apk_db_add_repository(apk_database_t db, apk_blob_t repository);
int apk_db_recalculate_and_commit(struct apk_database *db);
int apk_db_install_pkg(struct apk_database *db,
struct apk_package *oldpkg,
struct apk_package *newpkg,
......
......@@ -50,8 +50,14 @@ extern csum_t bad_checksum;
#define csum_valid(buf) memcmp(buf, bad_checksum, sizeof(csum_t))
#endif
extern int apk_cwd_fd, apk_verbosity, apk_progress, apk_upgrade;
extern int apk_clean, apk_force;
extern int apk_cwd_fd, apk_verbosity;
extern unsigned int apk_flags;
#define APK_FORCE 0x0001
#define APK_SIMULATE 0x0002
#define APK_CLEAN_PROTECTED 0x0004
#define APK_PROGRESS 0x0008
#define APK_UPGRADE 0x0010
#define apk_error(args...) apk_log("ERROR: ", args);
#define apk_warning(args...) if (apk_verbosity > 0) { apk_log("WARNING: ", args); }
......
......@@ -28,6 +28,9 @@ struct apk_name;
#define APK_SCRIPT_PRE_UPGRADE 5
#define APK_SCRIPT_POST_UPGRADE 6
#define APK_PKG_NOT_INSTALLED 0
#define APK_PKG_INSTALLED 1
struct apk_script {
struct hlist_node script_list;
unsigned int type;
......@@ -35,10 +38,14 @@ struct apk_script {
char script[];
};
#define APK_DEPMASK_REQUIRE (APK_VERSION_EQUAL|APK_VERSION_LESS|\
APK_VERSION_GREATER)
#define APK_DEPMASK_CONFLICT (0)
struct apk_dependency {
struct apk_name *name;
char *min_version;
char *max_version;
int result_mask;
char *version;
};
APK_ARRAY(apk_dependency_array, struct apk_dependency);
......@@ -46,7 +53,7 @@ struct apk_package {
apk_hash_node hash_node;
csum_t csum;
unsigned id, repos;
unsigned repos;
struct apk_name *name;
char *version;
char *url, *description, *license;
......
......@@ -14,43 +14,17 @@
#include "apk_database.h"
#define APK_STATE_NOT_CONSIDERED 0
#define APK_STATE_INSTALL 1
#define APK_STATE_NO_INSTALL 2
struct apk_change {
struct list_head change_list;
struct apk_package *oldpkg;
struct apk_package *newpkg;
};
struct apk_state {
int refs;
struct list_head change_list_head;
unsigned char bitarray[];
};
struct apk_deferred_state {
unsigned int preference;
struct apk_package *deferred_install;
/* struct apk_pkg_name_queue *install_queue; */
struct apk_state *state;
};
struct apk_state;
struct apk_state *apk_state_new(struct apk_database *db);
struct apk_state *apk_state_dup(struct apk_state *state);
void apk_state_unref(struct apk_state *state);
int apk_state_commit(struct apk_state *state, struct apk_database *db);
int apk_state_satisfy_deps(struct apk_state *state,
struct apk_dependency_array *deps);
int apk_state_purge_unneeded(struct apk_state *state,
struct apk_database *db);
int apk_state_pkg_install(struct apk_state *state,
struct apk_package *pkg);
int apk_state_pkg_is_installed(struct apk_state *state,
struct apk_package *pkg);
int apk_state_lock_dependency(struct apk_state *state,
struct apk_dependency *dep);
int apk_state_lock_name(struct apk_state *state,
struct apk_name *name,
struct apk_package *newpkg);
#endif
......@@ -14,11 +14,9 @@
#include "apk_blob.h"
#define APK_VERSION_LESS -1
#define APK_VERSION_EQUAL 0
#define APK_VERSION_GREATER 1
#define APK_VERSION_RESULT_MASK(r) (1 << ((r)+1))
#define APK_VERSION_EQUAL 1
#define APK_VERSION_LESS 2
#define APK_VERSION_GREATER 4
int apk_version_validate(apk_blob_t ver);
int apk_version_compare(apk_blob_t a, apk_blob_t b);
......
......@@ -154,6 +154,7 @@ struct apk_name *apk_db_get_name(struct apk_database *db, apk_blob_t name)
return NULL;
pn->name = apk_blob_cstr(name);
pn->id = db->name_id++;
apk_hash_insert(&db->available.names, pn);
return pn;
......@@ -355,7 +356,6 @@ static struct apk_package *apk_db_pkg_add(struct apk_database *db, struct apk_pa
idb = apk_hash_get(&db->available.packages, APK_BLOB_BUF(pkg->csum));
if (idb == NULL) {
idb = pkg;
pkg->id = db->pkg_id++;
apk_hash_insert(&db->available.packages, pkg);
*apk_package_array_add(&pkg->name->pkgs) = pkg;
apk_db_pkg_rdepends(db, pkg);
......@@ -393,7 +393,7 @@ static int apk_db_index_read(struct apk_database *db, struct apk_istream *is, in
if (repo != -1)
pkg->repos |= BIT(repo);
else
apk_pkg_set_state(db, pkg, APK_STATE_INSTALL);
apk_pkg_set_state(db, pkg, APK_PKG_INSTALLED);
if (apk_db_pkg_add(db, pkg) != pkg && repo == -1) {
apk_error("Installed database load failed");
......@@ -573,6 +573,7 @@ static int apk_db_read_state(struct apk_database *db)
{
struct apk_istream *is;
apk_blob_t blob;
int i;
/* Read:
* 1. installed repository
......@@ -590,6 +591,9 @@ static int apk_db_read_state(struct apk_database *db)
apk_deps_parse(db, &db->world, blob);
free(blob.ptr);
for (i = 0; i < db->world->num; i++)
db->world->item[i].name->flags |= APK_NAME_TOPLEVEL;
is = apk_istream_from_file("var/lib/apk/installed");
if (is != NULL) {
apk_db_index_read(db, is, -1);
......@@ -742,7 +746,7 @@ struct write_ctx {
int fd;
};
static int apk_db_write_config(struct apk_database *db)
int apk_db_write_config(struct apk_database *db)
{
struct apk_ostream *os;
......@@ -919,39 +923,6 @@ int apk_db_add_repository(apk_database_t _db, apk_blob_t repository)
return 0;
}
int apk_db_recalculate_and_commit(struct apk_database *db)
{
struct apk_state *state;
int r;
state = apk_state_new(db);
r = apk_state_satisfy_deps(state, db->world);
if (r == 0) {
r = apk_state_purge_unneeded(state, db);
if (r != 0) {
apk_error("Failed to clean up state");
return r;
}
r = apk_state_commit(state, db);
if (r != 0) {
apk_error("Failed to commit changes");
return r;
}
apk_db_write_config(db);
apk_message("OK: %d packages, %d dirs, %d files",
db->installed.stats.packages,
db->installed.stats.dirs,
db->installed.stats.files);
} else {
apk_error("Failed to build installation graph");
}
apk_state_unref(state);
return r;
}
static void extract_cb(void *_ctx, size_t progress)
{
struct install_ctx *ctx = (struct install_ctx *) _ctx;
......@@ -1069,7 +1040,7 @@ static int apk_db_install_archive_entry(void *_ctx,
if (file->diri != diri) {
opkg = file->diri->pkg;
if (opkg->name != pkg->name) {
if (!apk_force) {
if (!(apk_flags & APK_FORCE)) {
apk_error("%s: Trying to overwrite %s "
"owned by %s.\n",
pkg->name->name, ae->name,
......@@ -1093,7 +1064,7 @@ static int apk_db_install_archive_entry(void *_ctx,
(memcmp(file->csum, fi.csum, sizeof(csum_t)) != 0 ||
!csum_valid(file->csum))) {
/* Protected file. Extract to separate place */
if (!apk_clean) {
if (!(apk_flags & APK_CLEAN_PROTECTED)) {
snprintf(alt_name, sizeof(alt_name),
"%s/%s.apk-new",
diri->dir->dirname, file->filename);
......@@ -1160,7 +1131,7 @@ static void apk_db_purge_pkg(struct apk_database *db,
__hlist_del(dc, &pkg->owned_dirs.first);
apk_db_diri_free(db, diri);
}
apk_pkg_set_state(db, pkg, APK_STATE_NO_INSTALL);
apk_pkg_set_state(db, pkg, APK_PKG_NOT_INSTALLED);
}
int apk_db_install_pkg(struct apk_database *db,
......@@ -1229,7 +1200,7 @@ int apk_db_install_pkg(struct apk_database *db,
bs->close(bs, csum, NULL);
apk_pkg_set_state(db, newpkg, APK_STATE_INSTALL);
apk_pkg_set_state(db, newpkg, APK_PKG_INSTALLED);
if (memcmp(csum, newpkg->csum, sizeof(csum)) != 0)
apk_warning("%s-%s: checksum does not match",
......
......@@ -11,12 +11,15 @@
#include <stdio.h>
#include "apk_applet.h"
#include "apk_state.h"
#include "apk_database.h"
static int del_main(void *ctx, int argc, char **argv)
{
struct apk_database db;
int i, j;
struct apk_state *state;
struct apk_name *name;
int i, j, r;
if (apk_db_open(&db, apk_root, APK_OPENF_WRITE) < 0)
return -1;
......@@ -24,7 +27,13 @@ static int del_main(void *ctx, int argc, char **argv)
if (db.world == NULL)
goto out;
state = apk_state_new(&db);
for (i = 0; i < argc; i++) {
struct apk_dependency dep;
name = apk_db_get_name(&db, APK_BLOB_STR(argv[i]));
/* Remove from world, so we get proper changeset */
for (j = 0; j < db.world->num; j++) {
if (strcmp(db.world->item[j].name->name,
argv[i]) == 0) {
......@@ -34,13 +43,26 @@ static int del_main(void *ctx, int argc, char **argv)
apk_dependency_array_resize(db.world, db.world->num-1);
}
}
}
name->flags &= ~APK_NAME_TOPLEVEL;
apk_db_recalculate_and_commit(&db);
dep = (struct apk_dependency) {
.name = name,
.result_mask = APK_DEPMASK_CONFLICT,
};
r = apk_state_lock_dependency(state, &dep);
if (r != 0) {
apk_error("Unable to remove '%s'", name->name);
goto err;
}
}
r = apk_state_commit(state, &db);
err:
apk_state_unref(state);
out:
apk_db_close(&db);
return 0;
return r;
}
static struct apk_applet apk_del = {
......
......@@ -50,7 +50,7 @@ static int info_exists(struct info_ctx *ctx, struct apk_database *db,
return 1;
for (j = 0; j < name->pkgs->num; j++) {
if (apk_pkg_get_state(name->pkgs->item[j]) == APK_STATE_INSTALL)
if (apk_pkg_get_state(name->pkgs->item[j]) == APK_PKG_INSTALLED)
break;
}
if (j >= name->pkgs->num)
......@@ -76,6 +76,7 @@ static int info_who_owns(struct info_ctx *ctx, struct apk_database *db,
if (apk_verbosity < 1) {
dep = (struct apk_dependency) {
.name = pkg->name,
.result_mask = APK_DEPMASK_REQUIRE,
};
apk_deps_add(&deps, &dep);
} else {
......@@ -107,7 +108,7 @@ static int info_package(struct info_ctx *ctx, struct apk_database *db,
}
for (j = 0; j < name->pkgs->num; j++) {
struct apk_package *pkg = name->pkgs->item[j];
if (apk_pkg_get_state(pkg) == APK_STATE_INSTALL)
if (apk_pkg_get_state(pkg) == APK_PKG_INSTALLED)
ctx->subaction(pkg);
}
}
......@@ -172,7 +173,7 @@ static void info_print_required_by(struct apk_package *pkg)
for (j = 0; j < name0->pkgs->num; j++) {
struct apk_package *pkg0 = name0->pkgs->item[j];
if (apk_pkg_get_state(pkg0) != APK_STATE_INSTALL ||
if (apk_pkg_get_state(pkg0) != APK_PKG_INSTALLED ||
pkg0->depends == NULL)
continue;
for (k = 0; k < pkg0->depends->num; k++) {
......
......@@ -100,10 +100,46 @@ static int parse_depend(void *ctx, apk_blob_t blob)
struct parse_depend_ctx *pctx = (struct parse_depend_ctx *) ctx;
struct apk_dependency *dep;
struct apk_name *name;
apk_blob_t bname, bop, bver = APK_BLOB_NULL;
int mask = APK_VERSION_LESS | APK_VERSION_EQUAL | APK_VERSION_GREATER;
if (blob.len == 0)
return 0;
/* [!]name[<,<=,=,>=,>]ver */
if (blob.ptr[0] == '!') {
mask = 0;
blob.ptr++;
blob.len--;
}
if (apk_blob_cspn(blob, "<>=", &bname, &bop)) {
int i;
if (mask == 0)
return -1;
if (!apk_blob_spn(bop, "<>=", &bop, &bver))
return -1;
for (i = 0; i < blob.len; i++) {
switch (blob.ptr[i]) {
case '<':
mask |= APK_VERSION_LESS;
break;
case '>':
mask |= APK_VERSION_GREATER;
break;
case '=':
mask |= APK_VERSION_EQUAL;
break;
}
}
if ((mask & (APK_VERSION_LESS|APK_VERSION_GREATER))
== (APK_VERSION_LESS|APK_VERSION_GREATER))
return -1;
if (!apk_version_validate(bver))
return -1;
}
name = apk_db_get_name(pctx->db, blob);
if (name == NULL)
return -1;
......@@ -114,6 +150,8 @@ static int parse_depend(void *ctx, apk_blob_t blob)
*dep = (struct apk_dependency){
.name = name,
.version = APK_BLOB_IS_NULL(bver) ? NULL : apk_blob_cstr(bver),
.result_mask = mask,
};
return 0;
......@@ -131,6 +169,24 @@ void apk_deps_parse(struct apk_database *db,
apk_blob_for_each_segment(blob, " ", parse_depend, &ctx);
}
static const char *mask2str(int mask)
{
switch (mask) {
case APK_VERSION_LESS:
return "<";
case APK_VERSION_LESS|APK_VERSION_EQUAL:
return "<=";
case APK_VERSION_EQUAL:
return "=";
case APK_VERSION_GREATER|APK_VERSION_EQUAL:
return ">=";
case APK_VERSION_GREATER:
return ">";
default:
return "?";
}
}
int apk_deps_format(char *buf, int size,
struct apk_dependency_array *depends)
{
......@@ -142,9 +198,25 @@ int apk_deps_format(char *buf, int size,
for (i = 0; i < depends->num; i++) {
if (i && n < size)
buf[n++] = ' ';
n += snprintf(&buf[n], size-n,
"%s",
depends->item[i].name->name);
switch (depends->item[i].result_mask) {
case APK_DEPMASK_CONFLICT:
n += snprintf(&buf[n], size-n,
"!%s",
depends->item[i].name->name);
break;
case APK_DEPMASK_REQUIRE:
n += snprintf(&buf[n], size-n,
"%s",
depends->item[i].name->name);
break;
default:
n += snprintf(&buf[n], size-n,
"%s%s%s",
depends->item[i].name->name,
mask2str(depends->item[i].result_mask),
depends->item[i].version);
break;
}
}
return n;
}
......@@ -430,21 +502,21 @@ void apk_pkg_free(struct apk_package *pkg)
int apk_pkg_get_state(struct apk_package *pkg)
{
if (list_hashed(&pkg->installed_pkgs_list))
return APK_STATE_INSTALL;
return APK_STATE_NO_INSTALL;
return APK_PKG_INSTALLED;
return APK_PKG_NOT_INSTALLED;
}
void apk_pkg_set_state(struct apk_database *db, struct apk_package *pkg, int state)
{
switch (state) {
case APK_STATE_INSTALL:
case APK_PKG_INSTALLED:
if (!list_hashed(&pkg->installed_pkgs_list)) {
db->installed.stats.packages++;
list_add_tail(&pkg->installed_pkgs_list,
&db->installed.packages);
}
break;
case APK_STATE_NO_INSTALL:
case APK_PKG_NOT_INSTALLED:
if (list_hashed(&pkg->installed_pkgs_list)) {
db->installed.stats.packages--;
list_del(&pkg->installed_pkgs_list);
......
This diff is collapsed.
......@@ -143,6 +143,12 @@ int apk_version_compare(apk_blob_t a, apk_blob_t b)
int at = TOKEN_DIGIT, bt = TOKEN_DIGIT;
int av = 0, bv = 0;
if (APK_BLOB_IS_NULL(a) || APK_BLOB_IS_NULL(b)) {
if (APK_BLOB_IS_NULL(a) && APK_BLOB_IS_NULL(b))
return APK_VERSION_EQUAL;
return APK_VERSION_EQUAL | APK_VERSION_GREATER | APK_VERSION_LESS;
}
while (at == bt && at != TOKEN_END && at != TOKEN_INVALID && av == bv) {
av = get_token(&at, &a);
bv = get_token(&bt, &b);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment