From 038b672061919296b68b83a1ccead9c31b650c6c Mon Sep 17 00:00:00 2001
From: Timo Teräs <timo.teras@iki.fi>
Date: Tue, 1 Jun 2010 11:49:32 +0300
Subject: state: improve error messages from dependency failures

Print more information why installation changeset calculation failed.
Fixes #187.
---
 src/add.c         |  26 +++++-----
 src/apk_blob.h    |   1 +
 src/apk_package.h |   2 +
 src/apk_state.h   |   2 +
 src/blob.c        |   8 +++
 src/del.c         |  11 ++--
 src/fix.c         |  15 +++---
 src/package.c     |  59 +++++++++++----------
 src/state.c       | 149 +++++++++++++++++++++++++++++++++++++++++++++---------
 src/upgrade.c     |  12 ++---
 10 files changed, 197 insertions(+), 88 deletions(-)

(limited to 'src')

diff --git a/src/add.c b/src/add.c
index 17935c8..91570fe 100644
--- a/src/add.c
+++ b/src/add.c
@@ -67,12 +67,12 @@ static int add_main(void *ctx, struct apk_database *db, int argc, char **argv)
 
 	if (actx->virtpkg) {
 		if (non_repository_check(db))
-			goto err;
+			return -1;
 
 		virtpkg = apk_pkg_new();
 		if (virtpkg == NULL) {
 			apk_error("Failed to allocate virtual meta package");
-			goto err;
+			return -1;
 		}
 		virtpkg->name = apk_db_get_name(db, APK_BLOB_STR(actx->virtpkg));
 		apk_blob_checksum(APK_BLOB_STR(virtpkg->name->name),
@@ -96,7 +96,7 @@ static int add_main(void *ctx, struct apk_database *db, int argc, char **argv)
 			struct apk_sign_ctx sctx;
 
 			if (non_repository_check(db))
-				goto err;
+				return -1;
 
 			apk_sign_ctx_init(&sctx, APK_SIGN_VERIFY_AND_GENERATE,
 					  NULL, db->keys_fd);
@@ -104,14 +104,13 @@ static int add_main(void *ctx, struct apk_database *db, int argc, char **argv)
 			apk_sign_ctx_free(&sctx);
 			if (r != 0) {
 				apk_error("%s: %s", argv[i], apk_error_str(r));
-				goto err;
-
+				return -1;
 			}
 			apk_dep_from_pkg(&dep, db, pkg);
 		} else {
 			r = apk_dep_from_blob(&dep, db, APK_BLOB_STR(argv[i]));
 			if (r != 0)
-				goto err;
+				return -1;
 		}
 
 		if (virtpkg)
@@ -126,7 +125,7 @@ static int add_main(void *ctx, struct apk_database *db, int argc, char **argv)
 
 	state = apk_state_new(db);
 	if (state == NULL)
-		goto err;
+		return -1;
 
 	for (i = 0; i < num_deps; i++) {
 		r = apk_state_lock_dependency(state, &deps[i]);
@@ -134,16 +133,15 @@ static int add_main(void *ctx, struct apk_database *db, int argc, char **argv)
 			apk_deps_add(&db->world, &deps[i]);
 			deps[i].name->flags |= APK_NAME_TOPLEVEL;
 		} else {
-			apk_error("Unable to install '%s': %d",
-				  deps[i].name->name, r);
 			errors++;
 		}
 	}
-	if (errors && !(apk_flags & APK_FORCE))
-		goto err;
-
-	r = apk_state_commit(state, db);
-err:
+	if (errors && !(apk_flags & APK_FORCE)) {
+		apk_state_print_errors(state);
+		r = -1;
+	} else {
+		r = apk_state_commit(state, db);
+	}
 	if (state != NULL)
 		apk_state_unref(state);
 	return r;
diff --git a/src/apk_blob.h b/src/apk_blob.h
index 5a5e381..8ef95c6 100644
--- a/src/apk_blob.h
+++ b/src/apk_blob.h
@@ -74,6 +74,7 @@ int apk_blob_spn(apk_blob_t blob, const char *accept, apk_blob_t *l, apk_blob_t
 int apk_blob_cspn(apk_blob_t blob, const char *reject, apk_blob_t *l, apk_blob_t *r);
 int apk_blob_split(apk_blob_t blob, apk_blob_t split, apk_blob_t *l, apk_blob_t *r);
 int apk_blob_rsplit(apk_blob_t blob, char split, apk_blob_t *l, apk_blob_t *r);
+apk_blob_t apk_blob_pushed(apk_blob_t buffer, apk_blob_t left);
 unsigned long apk_blob_hash_seed(apk_blob_t, unsigned long seed);
 unsigned long apk_blob_hash(apk_blob_t str);
 int apk_blob_compare(apk_blob_t a, apk_blob_t b);
diff --git a/src/apk_package.h b/src/apk_package.h
index dbd469c..7901ddb 100644
--- a/src/apk_package.h
+++ b/src/apk_package.h
@@ -112,6 +112,8 @@ int apk_dep_from_blob(struct apk_dependency *dep, struct apk_database *db,
 		      apk_blob_t blob);
 void apk_dep_from_pkg(struct apk_dependency *dep, struct apk_database *db,
 		      struct apk_package *pkg);
+void apk_blob_push_dep(apk_blob_t *to, struct apk_dependency *dep);
+
 int apk_deps_add(struct apk_dependency_array **depends,
 		 struct apk_dependency *dep);
 void apk_deps_del(struct apk_dependency_array **deps,
diff --git a/src/apk_state.h b/src/apk_state.h
index 72c02b9..a862cdf 100644
--- a/src/apk_state.h
+++ b/src/apk_state.h
@@ -26,6 +26,7 @@ struct apk_state {
 	unsigned int refs, num_names;
 	struct apk_database *db;
 	struct list_head change_list_head;
+	struct apk_package_array *conflicts;
 	apk_name_state_t name[];
 };
 
@@ -33,6 +34,7 @@ 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);
 
+void apk_state_print_errors(struct apk_state *state);
 int apk_state_commit(struct apk_state *state, struct apk_database *db);
 int apk_state_lock_dependency(struct apk_state *state,
 			      struct apk_dependency *dep);
diff --git a/src/blob.c b/src/blob.c
index 0b6efbf..83bcf80 100644
--- a/src/blob.c
+++ b/src/blob.c
@@ -98,6 +98,14 @@ int apk_blob_split(apk_blob_t blob, apk_blob_t split, apk_blob_t *l, apk_blob_t
 	}
 }
 
+apk_blob_t apk_blob_pushed(apk_blob_t buffer, apk_blob_t left)
+{
+	if (buffer.ptr + buffer.len != left.ptr + left.len)
+		return APK_BLOB_NULL;
+
+	return APK_BLOB_PTR_LEN(buffer.ptr, left.ptr - buffer.ptr);
+}
+
 unsigned long apk_blob_hash_seed(apk_blob_t blob, unsigned long seed)
 {
 	unsigned long hash = seed;
diff --git a/src/del.c b/src/del.c
index 03b720e..c88bc81 100644
--- a/src/del.c
+++ b/src/del.c
@@ -56,13 +56,12 @@ static int del_main(void *ctx, struct apk_database *db, int argc, char **argv)
 			.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_lock_dependency(state, &dep);
 	}
-	r = apk_state_commit(state, db);
+	if (r == 0)
+		r = apk_state_commit(state, db);
+	else
+		apk_state_print_errors(state);
 err:
 	if (state != NULL)
 		apk_state_unref(state);
diff --git a/src/fix.c b/src/fix.c
index 5c2ff54..f84fbc8 100644
--- a/src/fix.c
+++ b/src/fix.c
@@ -84,14 +84,13 @@ static int fix_main(void *pctx, struct apk_database *db, int argc, char **argv)
 			name->flags |= APK_NAME_REINSTALL;
 	}
 
-	for (i = 0; i < argc; i++) {
-		r = apk_state_lock_dependency(state, &deps[i]);
-		if (r != 0) {
-			if (!(apk_flags & APK_FORCE))
-				goto err;
-		}
-	}
-	r = apk_state_commit(state, db);
+	for (i = 0; i < argc; i++)
+		r |= apk_state_lock_dependency(state, &deps[i]);
+
+	if (r == 0 || (apk_flags & APK_FORCE))
+		r = apk_state_commit(state, db);
+	else
+		apk_state_print_errors(state);
 err:
 	if (r != 0 && i < argc)
 		apk_error("Error while processing '%s'", argv[i]);
diff --git a/src/package.c b/src/package.c
index b97c412..b608fb8 100644
--- a/src/package.c
+++ b/src/package.c
@@ -286,43 +286,41 @@ void apk_deps_parse(struct apk_database *db,
 	apk_blob_for_each_segment(blob, " ", parse_depend, &ctx);
 }
 
+void apk_blob_push_dep(apk_blob_t *to, struct apk_dependency *dep)
+{
+	if (dep->result_mask == APK_DEPMASK_CONFLICT)
+		apk_blob_push_blob(to, APK_BLOB_PTR_LEN("!", 1));
+
+	apk_blob_push_blob(to, APK_BLOB_STR(dep->name->name));
+
+	if (dep->result_mask != APK_DEPMASK_CONFLICT &&
+	    dep->result_mask != APK_DEPMASK_REQUIRE) {
+		apk_blob_push_blob(to, APK_BLOB_STR(apk_version_op_string(dep->result_mask)));
+		apk_blob_push_blob(to, APK_BLOB_STR(dep->version));
+	}
+}
+
 int apk_deps_write(struct apk_dependency_array *deps, struct apk_ostream *os)
 {
-	int i, r, n = 0;
+	apk_blob_t blob;
+	char tmp[256];
+	int i, n = 0;
 
 	if (deps == NULL)
 		return 0;
 
 	for (i = 0; i < deps->num; i++) {
-		if (i) {
-			if (os->write(os, " ", 1) != 1)
-				return -1;
-			n += 1;
-		}
-
-		if (deps->item[i].result_mask == APK_DEPMASK_CONFLICT) {
-			if (os->write(os, "!", 1) != 1)
-				return -1;
-			n += 1;
-		}
+		blob = APK_BLOB_BUF(tmp);
+		if (i)
+			apk_blob_push_blob(&blob, APK_BLOB_PTR_LEN(" ", 1));
+		apk_blob_push_dep(&blob, &deps->item[i]);
+
+		blob = apk_blob_pushed(APK_BLOB_BUF(tmp), blob);
+		if (APK_BLOB_IS_NULL(blob) || 
+		    os->write(os, blob.ptr, blob.len) != blob.len)
+			return -1;
 
-		r = apk_ostream_write_string(os, deps->item[i].name->name);
-		if (r < 0)
-			return r;
-		n += r;
-
-		if (deps->item[i].result_mask != APK_DEPMASK_CONFLICT &&
-		    deps->item[i].result_mask != APK_DEPMASK_REQUIRE) {
-			r = apk_ostream_write_string(os, apk_version_op_string(deps->item[i].result_mask));
-			if (r < 0)
-				return r;
-			n += r;
-
-			r = apk_ostream_write_string(os, deps->item[i].version);
-			if (r < 0)
-				return r;
-			n += r;
-		}
+		n += blob.len;
 	}
 
 	return n;
@@ -967,7 +965,8 @@ int apk_pkg_write_index_entry(struct apk_package *info,
 	if (APK_BLOB_IS_NULL(bbuf))
 		return -1;
 
-	if (os->write(os, buf, bbuf.ptr - buf) != bbuf.ptr - buf)
+	bbuf = apk_blob_pushed(APK_BLOB_BUF(buf), bbuf);
+	if (os->write(os, bbuf.ptr, bbuf.len) != bbuf.len)
 		return -1;
 
 	if (info->depends != NULL) {
diff --git a/src/state.c b/src/state.c
index f0561b6..33cabb8 100644
--- a/src/state.c
+++ b/src/state.c
@@ -34,6 +34,16 @@ int apk_state_prune_dependency(struct apk_state *state,
 
 #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)state->name[name->id]) | APK_NS_ERROR);
+	*apk_package_array_add(&state->conflicts) = pkg;
+}
 
 static int inline ns_locked(apk_name_state_t name)
 {
@@ -49,6 +59,13 @@ static int inline ns_pending(apk_name_state_t name)
 	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;
@@ -67,7 +84,7 @@ static apk_name_state_t ns_from_pkg_non_pending(struct apk_package *pkg)
 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));
+		(((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)
@@ -247,7 +264,9 @@ int apk_state_prune_dependency(struct apk_state *state,
 		struct apk_package *pkg = ns_to_pkg(state->name[name->id]);
 
 		/* Locked to not-installed / remove? */
-		if (pkg == NULL) {
+		if (ns_error(state->name[name->id])) {
+			return -1;
+		} else if (pkg == NULL) {
 			if (dep->result_mask != APK_DEPMASK_CONFLICT)
 				return -1;
 		} else {
@@ -258,6 +277,7 @@ int apk_state_prune_dependency(struct apk_state *state,
 
 		if (ns_pending(state->name[name->id]))
 			return 1;
+
 		return 0;
 	}
 
@@ -370,22 +390,29 @@ int apk_state_lock_dependency(struct apk_state *state,
 static int apk_state_fix_package(struct apk_state *state,
 				 struct apk_package *pkg)
 {
-	int i, r;
+	int i, r, ret = 0;
+
+	if (pkg == NULL || pkg->depends == NULL)
+		return 0;
 
 	for (i = 0; i < pkg->depends->num; i++) {
 		r = apk_state_lock_dependency(state,
 					      &pkg->depends->item[i]);
 		if (r != 0)
-			return -1;
+			ret = -1;
 	}
-	return 0;
+
+	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_package *pkg,
+					      struct apk_dependency *dep,
+					      void *ctx),
+				    void *ctx)
 {
 	struct apk_package *dep_pkg;
 	int k;
@@ -405,7 +432,7 @@ static int call_if_dependency_broke(struct apk_state *state,
 		    (apk_version_compare(dep_pkg->version, dep->version)
 		     & dep->result_mask))
 			continue;
-		return cb(state, pkg);
+		return cb(state, pkg, dep, ctx);
 	}
 
 	return 0;
@@ -414,7 +441,10 @@ static int call_if_dependency_broke(struct apk_state *state,
 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_package *pkg,
+						     struct apk_dependency *dep,
+						     void *ctx),
+					   void *ctx)
 {
 	struct apk_package *pkg0;
 	int i, j, r;
@@ -429,7 +459,8 @@ static int for_each_broken_reverse_depency(struct apk_state *state,
 			pkg0 = ns_to_pkg(state->name[name0->id]);
 			if (pkg0 == NULL)
 				continue;
-			r = call_if_dependency_broke(state, pkg0, name, cb);
+			r = call_if_dependency_broke(state, pkg0, name,
+						     cb, ctx);
 			if (r != 0)
 				return r;
 		} else if (!ns_empty(state->name[name0->id])) {
@@ -441,7 +472,7 @@ static int for_each_broken_reverse_depency(struct apk_state *state,
 					continue;
 				r = call_if_dependency_broke(state,
 							     ns->pkgs[j],
-							     name, cb);
+							     name, cb, ctx);
 				if (r != 0)
 					return r;
 				break;
@@ -455,7 +486,7 @@ static int for_each_broken_reverse_depency(struct apk_state *state,
 
 				r = call_if_dependency_broke(state,
 							     name0->pkgs->item[j],
-							     name, cb);
+							     name, cb, ctx);
 				if (r != 0)
 					return r;
 				break;
@@ -467,20 +498,24 @@ static int for_each_broken_reverse_depency(struct apk_state *state,
 }
 
 static int delete_broken_package(struct apk_state *state,
-				 struct apk_package *pkg)
+				 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_package *pkg,
+				    struct apk_dependency *dep,
+				    void *ctx)
 
 {
-	struct apk_dependency dep = {
+	struct apk_dependency dep0 = {
 		.name = pkg->name,
 		.result_mask = APK_DEPMASK_REQUIRE,
 	};
-	return apk_state_lock_dependency(state, &dep);
+	return apk_state_lock_dependency(state, &dep0);
 }
 
 int apk_state_lock_name(struct apk_state *state,
@@ -512,16 +547,19 @@ int apk_state_lock_name(struct apk_state *state,
 		r = for_each_broken_reverse_depency(state, name,
 						    newpkg == NULL ?
 						    delete_broken_package :
-						    reinstall_broken_package);
-		if (r != 0)
+						    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. */
-	if (newpkg != NULL && newpkg->depends != NULL) {
-		r = apk_state_fix_package(state, newpkg);
-		if (r != 0)
-			return r;
+	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 */
@@ -709,7 +747,9 @@ static int cmp_upgrade(struct apk_change *change)
 }
 
 static int fail_if_something_broke(struct apk_state *state,
-				   struct apk_package *pkg)
+				   struct apk_package *pkg,
+				   struct apk_dependency *dep,
+				   void *ctx)
 
 {
 	return 1;
@@ -735,7 +775,8 @@ static int apk_state_autoclean(struct apk_state *state,
 		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);
+						    fail_if_something_broke,
+						    NULL);
 		state->name[n->id] = oldns;
 
 		if (r == 0) {
@@ -747,6 +788,68 @@ static int apk_state_autoclean(struct apk_state *state,
 	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), "%s-%s:",
+			       pkg->name->name, pkg->version);
+		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;
+
+	if (state->conflicts == NULL)
+		return;
+
+	apk_error("Unable to satisfy all dependencies:");
+	for (i = 0; i < state->conflicts->num; i++) {
+		es.prevpkg = pkg = state->conflicts->item[i];
+		es.indent.x = es.indent.indent =
+			printf("  %s-%s:",
+				pkg->name->name, pkg->version);
+
+		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");
+	}
+}
+
 int apk_state_commit(struct apk_state *state,
 		     struct apk_database *db)
 {
diff --git a/src/upgrade.c b/src/upgrade.c
index 879e14c..611f677 100644
--- a/src/upgrade.c
+++ b/src/upgrade.c
@@ -46,14 +46,12 @@ static int upgrade_main(void *ctx, struct apk_database *db, int argc, char **arg
 			dep->result_mask = APK_VERSION_EQUAL | APK_VERSION_LESS | APK_VERSION_GREATER;
 			dep->version = NULL;
 		}
-		r = apk_state_lock_dependency(state, dep);
-		if (r != 0) {
-			apk_error("Unable to upgrade '%s'",
-				  dep->name->name);
-			goto err;
-		}
+		r |= apk_state_lock_dependency(state, dep);
 	}
-	r = apk_state_commit(state, db);
+	if (r == 0)
+		r = apk_state_commit(state, db);
+	else
+		apk_state_print_errors(state);
 err:
 	if (state != NULL)
 		apk_state_unref(state);
-- 
cgit v1.2.3-70-g09d2