Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
Rework conflicts detection logic.
Vsevolod Stakhov committed 12 years ago
commit ddb79696a4051a2300ba8d9fb1eb359f8524155d
parent 887ffd6
6 files changed +263 -124
modified libpkg/Makefile
@@ -102,6 +102,7 @@ LIBPKGSRCS= ${PC} \
		pkg_add.c \
		pkg_attributes.c \
		pkg_config.c \
+
		pkg_conflicts.c \
		pkg_create.c \
		pkg_cudf.c \
		pkg_delete.c \
modified libpkg/pkg.h.in
@@ -481,6 +481,10 @@ typedef enum {
	 * $PKGDB_DIR/local.sqlite or any of the repo database bits
	 */
	EPKG_INSECURE,
+
	/**
+
	 * A conflict between packages found
+
	 */
+
	EPKG_CONFLICT
} pkg_error_t;

/**
added libpkg/pkg_conflicts.c
@@ -0,0 +1,176 @@
+
/*-
+
 * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
+
 * All rights reserved.
+
 *
+
 * Redistribution and use in source and binary forms, with or without
+
 * modification, are permitted provided that the following conditions
+
 * are met:
+
 * 1. Redistributions of source code must retain the above copyright
+
 *    notice, this list of conditions and the following disclaimer
+
 *    in this position and unchanged.
+
 * 2. Redistributions in binary form must reproduce the above copyright
+
 *    notice, this list of conditions and the following disclaimer in the
+
 *    documentation and/or other materials provided with the distribution.
+
 *
+
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+
 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
 */
+

+
#include <sys/param.h>
+
#include <sys/types.h>
+
#include <stdbool.h>
+
#include <stdlib.h>
+
#include <string.h>
+

+
#include "pkg.h"
+
#include "private/event.h"
+
#include "private/pkg.h"
+
#include "private/pkgdb.h"
+

+
struct pkg_conflict_chain {
+
	struct pkg_job_request *req;
+
	struct pkg_conflict_chain *next;
+
};
+

+
static int
+
pkg_conflicts_chain_cmp_cb(struct pkg_conflict_chain *a, struct pkg_conflict_chain *b)
+
{
+
	const char *vera, *verb;
+

+
	pkg_get(a->req->pkg, PKG_VERSION, &vera);
+
	pkg_get(b->req->pkg, PKG_VERSION, &verb);
+

+
	/* Inverse sort to get the maximum version as the first element */
+
	return (pkg_version_cmp(verb, vera));
+
}
+

+
static int
+
pkg_conflicts_request_resolve_chain(struct pkg *req, struct pkg_conflict_chain *chain)
+
{
+
	struct pkg_conflict_chain *elt, *selected = NULL;
+
	const char *name, *origin, *slash_pos;
+

+
	pkg_get(req, PKG_NAME, &name);
+
	/*
+
	 * First of all prefer pure origins, where the last element of
+
	 * an origin is pkg name
+
	 */
+
	LL_FOREACH(chain, elt) {
+
		pkg_get(elt->req->pkg, PKG_ORIGIN, &origin);
+
		slash_pos = strrchr(origin, '/');
+
		if (slash_pos != NULL) {
+
			if (strcmp(slash_pos + 1, name) == 0) {
+
				selected = elt;
+
				break;
+
			}
+
		}
+
	}
+

+
	if (selected == NULL) {
+
		/* XXX: add manual selection here */
+
		/* Sort list by version of package */
+
		LL_SORT(chain, pkg_conflicts_chain_cmp_cb);
+
		selected = chain;
+
	}
+

+
	/* Disable conflicts from a request */
+
	LL_FOREACH(chain, elt) {
+
		if (elt != selected)
+
			elt->req->skip = true;
+
	}
+

+
	return (EPKG_OK);
+
}
+

+
int
+
pkg_conflicts_request_resolve(struct pkg_jobs *j)
+
{
+
	struct pkg_job_request *req, *rtmp, *found;
+
	struct pkg_conflict *c, *ctmp;
+
	struct pkg_conflict_chain *chain, *elt;
+

+
	HASH_ITER(hh, j->request_add, req, rtmp) {
+
		chain = NULL;
+
		HASH_ITER(hh, req->pkg->conflicts, c, ctmp) {
+
			HASH_FIND_STR(j->request_add, pkg_conflict_origin(c), found);
+
			if (found && !found->skip) {
+
				elt = calloc(1, sizeof(struct pkg_conflict_chain));
+
				if (elt == NULL) {
+
					pkg_emit_errno("resolve_request_conflicts", "calloc: struct pkg_conflict_chain");
+
					return (EPKG_FATAL);
+
				}
+
				elt->req = found;
+
				LL_PREPEND(chain, elt);
+
			}
+
			if (chain != NULL) {
+
				/* We need to handle conflict chain here */
+
				if (pkg_conflicts_request_resolve_chain(req->pkg, chain) != EPKG_OK) {
+
					LL_FREE(chain, pkg_conflict_chain, free);
+
					return (EPKG_FATAL);
+
				}
+
				LL_FREE(chain, pkg_conflict_chain, free);
+
			}
+
		}
+
	}
+

+
	return (EPKG_OK);
+
}
+

+
void
+
pkg_conflicts_register(struct pkg *p1, struct pkg *p2)
+
{
+
	struct pkg_conflict *c1, *c2;
+
	const char *o1, *o2;
+

+
	pkg_get(p1, PKG_ORIGIN, &o1);
+
	pkg_get(p2, PKG_ORIGIN, &o2);
+

+
	pkg_conflict_new(&c1);
+
	pkg_conflict_new(&c2);
+
	if (c1 != NULL && c2 != NULL) {
+
		sbuf_set(&c1->origin, o2);
+
		sbuf_set(&c2->origin, o1);
+
		pkg_debug(2, "registering conflict between %s and %s", o1, o2);
+
		HASH_ADD_KEYPTR(hh, p1->conflicts, pkg_conflict_origin(c1), sbuf_size(c1->origin), c1);
+
		HASH_ADD_KEYPTR(hh, p2->conflicts, pkg_conflict_origin(c2), sbuf_size(c2->origin), c2);
+
	}
+
}
+

+

+
static void
+
pkg_conflicts_add_from_pkgdb(const char *o1, const char *o2, void *ud)
+
{
+
	struct pkg_jobs *j = (struct pkg_jobs *)ud;
+
	struct pkg_job_universe_item *u1, *u2;
+

+
	HASH_FIND_STR(j->universe, o1, u1);
+
	HASH_FIND_STR(j->universe, o2, u2);
+

+
	if (u1 == NULL || u2 == NULL) {
+
		pkg_emit_error("conflicts: cannot register a conflict between non-existing packages");
+
		return;
+
	}
+

+
	pkg_conflicts_register(u1->pkg, u2->pkg);
+
}
+

+
int
+
pkg_conflicts_append_pkg(struct pkg *p, struct pkg_jobs *j)
+
{
+
	/* Now we can get conflicts only from pkgdb */
+
	return (pkgdb_integrity_append(j->db, p, pkg_conflicts_add_from_pkgdb, j));
+
}
+

+
int
+
pkg_conflicts_integrity_check(struct pkg_jobs *j)
+
{
+
	return (pkgdb_integrity_check(j->db, pkg_conflicts_add_from_pkgdb, j));
+
}
modified libpkg/pkg_jobs.c
@@ -52,6 +52,7 @@ static int pkg_jobs_fetch(struct pkg_jobs *j);
static bool newer_than_local_pkg(struct pkg_jobs *j, struct pkg *rp, bool force);
static bool pkg_need_upgrade(struct pkg *rp, struct pkg *lp, bool recursive);
static bool new_pkg_version(struct pkg_jobs *j);
+
static int pkg_jobs_check_conflicts(struct pkg_jobs *j);

int
pkg_jobs_new(struct pkg_jobs **j, pkg_jobs_t t, struct pkgdb *db)
@@ -399,95 +400,6 @@ pkg_jobs_add_universe(struct pkg_jobs *j, struct pkg *pkg, int priority, bool re
	return (EPKG_OK);
}

-
struct pkg_conflict_chain {
-
	struct pkg_job_request *req;
-
	struct pkg_conflict_chain *next;
-
};
-

-
static int
-
conflict_chain_cmp_cb(struct pkg_conflict_chain *a, struct pkg_conflict_chain *b)
-
{
-
	const char *vera, *verb;
-

-
	pkg_get(a->req->pkg, PKG_VERSION, &vera);
-
	pkg_get(b->req->pkg, PKG_VERSION, &verb);
-

-
	/* Inverse sort to get the maximum version as the first element */
-
	return (pkg_version_cmp(verb, vera));
-
}
-

-
static int
-
resolve_request_conflicts_chain(struct pkg *req, struct pkg_conflict_chain *chain)
-
{
-
	struct pkg_conflict_chain *elt, *selected = NULL;
-
	const char *name, *origin, *slash_pos;
-

-
	pkg_get(req, PKG_NAME, &name);
-
	/*
-
	 * First of all prefer pure origins, where the last element of
-
	 * an origin is pkg name
-
	 */
-
	LL_FOREACH(chain, elt) {
-
		pkg_get(elt->req->pkg, PKG_ORIGIN, &origin);
-
		slash_pos = strrchr(origin, '/');
-
		if (slash_pos != NULL) {
-
			if (strcmp(slash_pos + 1, name) == 0) {
-
				selected = elt;
-
				break;
-
			}
-
		}
-
	}
-

-
	if (selected == NULL) {
-
		/* XXX: add manual selection here */
-
		/* Sort list by version of package */
-
		LL_SORT(chain, conflict_chain_cmp_cb);
-
		selected = chain;
-
	}
-

-
	/* Disable conflicts from a request */
-
	LL_FOREACH(chain, elt) {
-
		if (elt != selected)
-
			elt->req->skip = true;
-
	}
-

-
	return (EPKG_OK);
-
}
-

-
static int
-
resolve_request_conflicts(struct pkg_jobs *j)
-
{
-
	struct pkg_job_request *req, *rtmp, *found;
-
	struct pkg_conflict *c, *ctmp;
-
	struct pkg_conflict_chain *chain, *elt;
-

-
	HASH_ITER(hh, j->request_add, req, rtmp) {
-
		chain = NULL;
-
		HASH_ITER(hh, req->pkg->conflicts, c, ctmp) {
-
			HASH_FIND_STR(j->request_add, pkg_conflict_origin(c), found);
-
			if (found && !found->skip) {
-
				elt = calloc(1, sizeof(struct pkg_conflict_chain));
-
				if (elt == NULL) {
-
					pkg_emit_errno("resolve_request_conflicts", "calloc: struct pkg_conflict_chain");
-
					return (EPKG_FATAL);
-
				}
-
				elt->req = found;
-
				LL_PREPEND(chain, elt);
-
			}
-
			if (chain != NULL) {
-
				/* We need to handle conflict chain here */
-
				if (resolve_request_conflicts_chain (req->pkg, chain) != EPKG_OK) {
-
					LL_FREE(chain, pkg_conflict_chain, free);
-
					return (EPKG_FATAL);
-
				}
-
				LL_FREE(chain, pkg_conflict_chain, free);
-
			}
-
		}
-
	}
-

-
	return (EPKG_OK);
-
}
-

static int
jobs_solve_deinstall(struct pkg_jobs *j)
{
@@ -1038,7 +950,7 @@ jobs_solve_install(struct pkg_jobs *j)
		}
	}

-
	if (resolve_request_conflicts(j) != EPKG_OK) {
+
	if (pkg_conflicts_request_resolve(j) != EPKG_OK) {
		pkg_emit_error("Cannot resolve conflicts in a request");
		return (EPKG_FATAL);
	}
@@ -1465,8 +1377,20 @@ pkg_jobs_apply(struct pkg_jobs *j)
	case PKG_JOBS_INSTALL:
		pkg_plugins_hook_run(PKG_PLUGIN_HOOK_PRE_INSTALL, j, j->db);
		rc = pkg_jobs_fetch(j);
-
		if (rc == EPKG_OK)
-
			rc = pkg_jobs_install(j);
+
		if (rc == EPKG_OK) {
+
			rc = pkg_jobs_check_conflicts(j);
+
			if (rc == EPKG_OK) {
+
				rc = pkg_jobs_install(j);
+
			}
+
			else if (rc == EPKG_CONFLICT) {
+
				j->solved = false;
+
				rc = pkg_jobs_solve(j);
+
				if (rc == EPKG_OK) {
+
					/* XXX: is it safe to do this recursion */
+
					return (pkg_jobs_apply(j));
+
				}
+
			}
+
		}
		pkg_plugins_hook_run(PKG_PLUGIN_HOOK_POST_INSTALL, j, j->db);
		break;
	case PKG_JOBS_DEINSTALL:
@@ -1502,16 +1426,12 @@ pkg_jobs_fetch(struct pkg_jobs *j)
{
	struct pkg *p = NULL;
	struct pkg_solved *ps;
-
	struct pkg *pkg = NULL;
	struct statfs fs;
	struct stat st;
-
	char path[MAXPATHLEN];
	int64_t dlsize = 0;
	const char *cachedir = NULL;
	const char *repopath = NULL;
	char cachedpath[MAXPATHLEN];
-
	int ret = EPKG_OK;
-
	struct pkg_manifest_key *keys = NULL;
	
	if (pkg_config_string(PKG_CONFIG_CACHEDIR, &cachedir) != EPKG_OK)
		return (EPKG_FATAL);
@@ -1559,7 +1479,23 @@ pkg_jobs_fetch(struct pkg_jobs *j)
			return (EPKG_FATAL);
	}

-
	/* integrity checking */
+
	return (EPKG_OK);
+
}
+

+
static int
+
pkg_jobs_check_conflicts(struct pkg_jobs *j)
+
{
+
	struct pkg *p = NULL;
+
	struct pkg_solved *ps;
+
	struct pkg_manifest_key *keys = NULL;
+
	struct pkg *pkg = NULL;
+
	const char *cachedir = NULL;
+
	char path[MAXPATHLEN];
+
	int ret = EPKG_OK, res;
+

+
	if (pkg_config_string(PKG_CONFIG_CACHEDIR, &cachedir) != EPKG_OK)
+
		return (EPKG_FATAL);
+

	pkg_emit_integritycheck_begin();

	pkg_manifest_keys_new(&keys);
@@ -1569,21 +1505,26 @@ pkg_jobs_fetch(struct pkg_jobs *j)

		pkg_get(p, PKG_REPOPATH, &pkgrepopath);
		snprintf(path, sizeof(path), "%s/%s", cachedir,
-
		    pkgrepopath);
+
				pkgrepopath);
		if (pkg_open(&pkg, path, keys, 0) != EPKG_OK)
			return (EPKG_FATAL);

-
		if (pkgdb_integrity_append(j->db, pkg) != EPKG_OK)
-
			ret = EPKG_FATAL;
+
		if ((res = pkg_conflicts_append_pkg(pkg, j)) != EPKG_OK) {
+
			ret = res;
+
			if (ret == EPKG_FATAL)
+
				break;
+
		}
	}
	pkg_manifest_keys_free(keys);

	pkg_free(pkg);

-
	if (pkgdb_integrity_check(j->db) != EPKG_OK || ret != EPKG_OK)
-
		return (EPKG_FATAL);
+
	if (ret != EPKG_FATAL) {
+
		if ((ret = pkg_conflicts_integrity_check(j)) != EPKG_OK)
+
			return (ret);
+
	}

	pkg_emit_integritycheck_finished();

-
	return (EPKG_OK);
+
	return (ret);
}
modified libpkg/pkgdb.c
@@ -3774,13 +3774,14 @@ pkgdb_search(struct pkgdb *db, const char *pattern, match_t match,
}

int
-
pkgdb_integrity_append(struct pkgdb *db, struct pkg *p)
+
pkgdb_integrity_append(struct pkgdb *db, struct pkg *p,
+
		conflict_func_cb cb, void *cbdata)
{
	int		 ret = EPKG_OK;
	sqlite3_stmt	*stmt = NULL;
	sqlite3_stmt	*stmt_conflicts = NULL;
	struct pkg_file	*file = NULL;
-

+
	const char *porigin;

	const char	 sql[] = ""
		"INSERT INTO integritycheck (name, origin, version, path)"
@@ -3804,6 +3805,7 @@ pkgdb_integrity_append(struct pkgdb *db, struct pkg *p)
		return (EPKG_FATAL);
	}

+
	pkg_get(p, PKG_ORIGIN, &porigin);

	while (pkg_files(p, &file) == EPKG_OK) {
		const char	*name, *origin, *version;
@@ -3831,17 +3833,14 @@ pkgdb_integrity_append(struct pkgdb *db, struct pkg *p)
			    -1, SQLITE_STATIC);
			cur = conflicts_list;
			while (sqlite3_step(stmt_conflicts) != SQLITE_DONE) {
-
				if (cur == NULL) {
-
					cur = calloc(1, sizeof (struct pkg_event_conflict));
-
					conflicts_list = cur;
-
				}
-
				else {
-
					cur->next = calloc(1, sizeof (struct pkg_event_conflict));
-
					cur = cur->next;
-
				}
+
				cur = calloc(1, sizeof (struct pkg_event_conflict));
				cur->name = strdup(sqlite3_column_text(stmt_conflicts, 0));
				cur->origin = strdup(sqlite3_column_text(stmt_conflicts, 1));
				cur->version = strdup(sqlite3_column_text(stmt_conflicts, 2));
+
				LL_PREPEND(conflicts_list, cur);
+

+
				if (cb != NULL)
+
					cb (porigin, cur->origin, cbdata);
			}
			sqlite3_finalize(stmt_conflicts);
			pkg_emit_integritycheck_conflict(name, version, origin, pkg_path, conflicts_list);
@@ -3854,7 +3853,7 @@ pkgdb_integrity_append(struct pkgdb *db, struct pkg *p)
				free(conflicts_list);
				conflicts_list = cur;
			}
-
			ret = EPKG_FATAL;
+
			ret = EPKG_CONFLICT;
		}
		sqlite3_reset(stmt);
	}
@@ -3864,21 +3863,22 @@ pkgdb_integrity_append(struct pkgdb *db, struct pkg *p)
}

int
-
pkgdb_integrity_check(struct pkgdb *db)
+
pkgdb_integrity_check(struct pkgdb *db, conflict_func_cb cb, void *cbdata)
{
	int		 ret, retcode = EPKG_OK;
	sqlite3_stmt	*stmt;
	sqlite3_stmt	*stmt_conflicts;
	struct sbuf	*conflictmsg = NULL;
+
	struct sbuf *origin;

	assert (db != NULL);

	const char	 sql_local_conflict[] = ""
-
		"SELECT p.name, p.version FROM packages AS p, files AS f "
+
		"SELECT p.name, p.version, p.origin FROM packages AS p, files AS f "
		"WHERE p.id = f.package_id AND f.path = ?1;";

	const char	 sql_conflicts[] = ""
-
		"SELECT name, version FROM integritycheck WHERE path = ?1;";
+
		"SELECT name, version, origin FROM integritycheck WHERE path = ?1;";

	if (sqlite3_prepare_v2(db->sqlite,
		"SELECT path, COUNT(path) FROM ("
@@ -3893,16 +3893,19 @@ pkgdb_integrity_check(struct pkgdb *db)
	}

	conflictmsg = sbuf_new_auto();
+
	origin = sbuf_new_auto();

	while (sqlite3_step(stmt) != SQLITE_DONE) {
		sbuf_clear(conflictmsg);
+
		sbuf_clear(origin);

-
			pkg_debug(4, "Pkgdb: running '%s'", sql_local_conflict);
+
		pkg_debug(4, "Pkgdb: running '%s'", sql_local_conflict);
		ret = sqlite3_prepare_v2(db->sqlite, sql_local_conflict, -1,
		    &stmt_conflicts, NULL);
		if (ret != SQLITE_OK) {
			ERROR_SQLITE(db->sqlite);
			sqlite3_finalize(stmt);
+
			sbuf_delete(origin);
			sbuf_delete(conflictmsg);
			return (EPKG_FATAL);
		}
@@ -3917,7 +3920,7 @@ pkgdb_integrity_check(struct pkgdb *db)
		    sqlite3_column_text(stmt_conflicts, 0),
		    sqlite3_column_text(stmt_conflicts, 1),
		    sqlite3_column_text(stmt, 0));
-

+
		sbuf_cpy(origin, sqlite3_column_text(stmt_conflicts, 2));
		sqlite3_finalize(stmt_conflicts);

		pkg_debug(4, "Pkgdb: running '%s'", sql_conflicts);
@@ -3927,9 +3930,12 @@ pkgdb_integrity_check(struct pkgdb *db)
			ERROR_SQLITE(db->sqlite);
			sqlite3_finalize(stmt);
			sbuf_delete(conflictmsg);
+
			sbuf_delete(origin);
			return (EPKG_FATAL);
		}

+
		sbuf_finish(origin);
+

		sqlite3_bind_text(stmt_conflicts, 1,
		    sqlite3_column_text(stmt, 0), -1, SQLITE_STATIC);

@@ -3937,15 +3943,20 @@ pkgdb_integrity_check(struct pkgdb *db)
			sbuf_printf(conflictmsg, "\t- %s-%s\n",
			    sqlite3_column_text(stmt_conflicts, 0),
			    sqlite3_column_text(stmt_conflicts, 1));
+
			if (cb != NULL)
+
				cb (sbuf_data(origin), sqlite3_column_text(stmt_conflicts, 2), cbdata);
		}
-
		sqlite3_finalize(stmt_conflicts);
+

		sbuf_finish(conflictmsg);
-
		pkg_emit_error("%s", sbuf_get(conflictmsg));
-
		retcode = EPKG_FATAL;
+
		sqlite3_finalize(stmt_conflicts);
+

+
		//pkg_emit_error("%s", sbuf_get(conflictmsg));
+
		retcode = EPKG_CONFLICT;
	}

	sqlite3_finalize(stmt);
	sbuf_delete(conflictmsg);
+
	sbuf_delete(origin);

/*	sql_exec(db->sqlite, "DROP TABLE IF EXISTS integritycheck");*/

modified libpkg/private/pkg.h
@@ -439,8 +439,14 @@ int pkg_delete_dirs(struct pkgdb *db, struct pkg *pkg, bool force);

int pkgdb_is_dir_used(struct pkgdb *db, const char *dir, int64_t *res);

-
int pkgdb_integrity_append(struct pkgdb *db, struct pkg *p);
-
int pkgdb_integrity_check(struct pkgdb *db);
+
int pkg_conflicts_request_resolve(struct pkg_jobs *j);
+
int pkg_conflicts_append_pkg(struct pkg *p, struct pkg_jobs *j);
+
int pkg_conflicts_integrity_check(struct pkg_jobs *j);
+

+
typedef void (*conflict_func_cb)(const char *, const char *, void *);
+
int pkgdb_integrity_append(struct pkgdb *db, struct pkg *p,
+
		conflict_func_cb cb, void *cbdata);
+
int pkgdb_integrity_check(struct pkgdb *db, conflict_func_cb cb, void *cbdata);
struct pkgdb_it *pkgdb_integrity_conflict_local(struct pkgdb *db,
						const char *origin);