Radish alpha
H
rad:z3QDZAW2FAfuLvihrhiyDC9fAD8G9
HardenedBSD Package Manager
Radicle
Git
Move all remote repo manipulations to pkgdb_repo.
Vsevolod Stakhov committed 13 years ago
commit 4186881c5ebc3a84c232f6871bd6aff32431c3fe
parent 716d8ca
6 files changed +943 -775
modified libpkg/Makefile
@@ -32,6 +32,7 @@ SRCS= ${PC} \
		pkg_status.c \
		pkg_version.c \
		pkgdb.c \
+
		pkgdb_repo.c \
		rcscripts.c \
		rsa.c \
		scripts.c \
modified libpkg/pkg_repo.c
@@ -48,128 +48,6 @@
#include "private/repodb.h"
#include "private/thd_repo.h"

-
/* The package repo schema major revision */
-
#define REPO_SCHEMA_MAJOR 2
-

-
/* The package repo schema minor revision.
-
   Minor schema changes don't prevent older pkgng
-
   versions accessing the repo. */
-
#define REPO_SCHEMA_MINOR 4
-

-
#define REPO_SCHEMA_VERSION (REPO_SCHEMA_MAJOR * 1000 + REPO_SCHEMA_MINOR)
-

-
typedef enum _sql_prstmt_index {
-
	PKG = 0,
-
	DEPS,
-
	CAT1,
-
	CAT2,
-
	LIC1,
-
	LIC2,
-
	OPTS,
-
	SHLIB1,
-
	SHLIB_REQD,
-
	SHLIB_PROV,
-
	ABSTRACT1,
-
	ABSTRACT2,
-
	EXISTS,
-
	VERSION,
-
	DELETE,
-
	PRSTMT_LAST,
-
} sql_prstmt_index;
-

-
static sql_prstmt sql_prepared_statements[PRSTMT_LAST] = {
-
	[PKG] = {
-
		NULL,
-
		"INSERT INTO packages ("
-
		"origin, name, version, comment, desc, arch, maintainer, www, "
-
		"prefix, pkgsize, flatsize, licenselogic, cksum, path"
-
		")"
-
		"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)",
-
		"TTTTTTTTTIIITT",
-
	},
-
	[DEPS] = {
-
		NULL,
-
		"INSERT INTO deps (origin, name, version, package_id) "
-
		"VALUES (?1, ?2, ?3, ?4)",
-
		"TTTI",
-
	},
-
	[CAT1] = {
-
		NULL,
-
		"INSERT OR IGNORE INTO categories(name) VALUES(?1)",
-
		"T",
-
	},
-
	[CAT2] = {
-
		NULL,
-
		"INSERT OR ROLLBACK INTO pkg_categories(package_id, category_id) "
-
		"VALUES (?1, (SELECT id FROM categories WHERE name = ?2))",
-
		"IT",
-
	},
-
	[LIC1] = {
-
		NULL,
-
		"INSERT OR IGNORE INTO licenses(name) VALUES(?1)",
-
		"T",
-
	},
-
	[LIC2] = {
-
		NULL,
-
		"INSERT OR ROLLBACK INTO pkg_licenses(package_id, license_id) "
-
		"VALUES (?1, (SELECT id FROM licenses WHERE name = ?2))",
-
		"IT",
-
	},
-
	[OPTS] = {
-
		NULL,
-
		"INSERT OR ROLLBACK INTO options (option, value, package_id) "
-
		"VALUES (?1, ?2, ?3)",
-
		"TTI",
-
	},
-
	[SHLIB1] = {
-
		NULL,
-
		"INSERT OR IGNORE INTO shlibs(name) VALUES(?1)",
-
		"T",
-
	},
-
	[SHLIB_REQD] = {
-
		NULL,
-
		"INSERT OR ROLLBACK INTO pkg_shlibs_required(package_id, shlib_id) "
-
		"VALUES (?1, (SELECT id FROM shlibs WHERE name = ?2))",
-
		"IT",
-
	},
-
	[SHLIB_PROV] = {
-
		NULL,
-
		"INSERT OR ROLLBACK INTO pkg_shlibs_provided(package_id, shlib_id) "
-
		"VALUES (?1, (SELECT id FROM shlibs WHERE name = ?2))",
-
		"IT",
-
	},
-
	[EXISTS] = {
-
		NULL,
-
		"SELECT count(*) FROM packages WHERE cksum=?1",
-
		"T",
-
	},
-
	[ABSTRACT1] = {
-
		NULL,
-
		"INSERT OR IGNORE INTO abstract(abstract) "
-
		"VALUES (?1)",
-
		"T",
-
	},
-
	[ABSTRACT2] = {
-
		NULL,
-
		"INSERT OR ROLLBACK INTO pkg_abstract(package_id, key_id, value_id) "
-
		"VALUES (?1,"
-
		" (SELECT abstract_id FROM abstract WHERE abstract=?2),"
-
		" (SELECT abstract_id FROM abstract WHERE abstract=?3))",
-
		"ITT",
-
	},
-
	[VERSION] = {
-
		NULL,
-
		"SELECT version FROM packages WHERE origin=?1",
-
		"T",
-
	},
-
	[DELETE] = {
-
		NULL,
-
		"DELETE FROM packages WHERE origin=?1",
-
		"T",
-
	}
-
	/* PRSTMT_LAST */
-
};
-

int
pkg_repo_fetch(struct pkg *pkg)
{
@@ -264,272 +142,6 @@ pkg_repo_fetch(struct pkg *pkg)
}

static void
-
file_exists(sqlite3_context *ctx, int argc, __unused sqlite3_value **argv)
-
{
-
	char	 fpath[MAXPATHLEN];
-
	sqlite3	*db = sqlite3_context_db_handle(ctx);
-
	char	*path = dirname(sqlite3_db_filename(db, "main"));
-
	char	 cksum[SHA256_DIGEST_LENGTH * 2 +1];
-

-
	if (argc != 2) {
-
		sqlite3_result_error(ctx, "file_exists needs two argument", -1);
-
		return;
-
	}
-

-
	snprintf(fpath, MAXPATHLEN, "%s/%s", path, sqlite3_value_text(argv[0]));
-

-
	if (access(fpath, F_OK) == 0) {
-
		sha256_file(fpath, cksum);
-
		if (strcmp(cksum, sqlite3_value_text(argv[1])) == 0)
-
			sqlite3_result_int(ctx, 1);
-
		else
-
			sqlite3_result_int(ctx, 0);
-
	} else {
-
		sqlite3_result_int(ctx, 0);
-
	}
-
}
-

-
static int
-
get_repo_user_version(sqlite3 *sqlite, const char *database, int *reposcver)
-
{
-
	sqlite3_stmt *stmt;
-
	int retcode;
-
	char sql[BUFSIZ];
-
	const char *fmt = "PRAGMA %Q.user_version";
-

-
	assert(database != NULL);
-

-
	sqlite3_snprintf(sizeof(sql), sql, fmt, database);
-

-
	if (sqlite3_prepare_v2(sqlite, sql, -1, &stmt, NULL) != SQLITE_OK) {
-
		ERROR_SQLITE(sqlite);
-
		return (EPKG_FATAL);
-
	}
-

-
	if (sqlite3_step(stmt) == SQLITE_ROW) {
-
		*reposcver = sqlite3_column_int(stmt, 0);
-
		retcode = EPKG_OK;
-
	} else {
-
		*reposcver = -1;
-
		retcode = EPKG_FATAL;
-
	}
-
	sqlite3_finalize(stmt);
-
	return (retcode);
-
}
-

-
static int
-
set_repo_user_version(sqlite3 *sqlite, const char *database, int reposcver)
-
{
-
	int		 retcode = EPKG_OK;
-
	char		 sql[BUFSIZ];
-
	char		*errmsg;
-
	const char	*fmt = "PRAGMA %Q.user_version = %d;" ;
-

-
	assert(database != NULL);
-

-
	sqlite3_snprintf(sizeof(sql), sql, fmt, database, reposcver);
-

-
	if (sqlite3_exec(sqlite, sql, NULL, NULL, &errmsg) != SQLITE_OK) {
-
		pkg_emit_error("sqlite: %s", errmsg);
-
		sqlite3_free(errmsg);
-
		retcode = EPKG_FATAL;
-
	}
-
	return (retcode);
-
}
-

-
static int
-
initialize_repo(const char *repodb, bool force, sqlite3 **sqlite)
-
{
-
	bool incremental = false;
-
	bool db_not_open;
-
	int reposcver;
-
	int retcode = EPKG_OK;
-

-
	if (access(repodb, F_OK) == 0)
-
		incremental = true;
-

-
	sqlite3_initialize();
-
	db_not_open = true;
-
	while (db_not_open) {
-
		if (sqlite3_open(repodb, sqlite) != SQLITE_OK) {
-
			sqlite3_shutdown();
-
			return (EPKG_FATAL);
-
		}
-

-
		db_not_open = false;
-

-
		/* If the schema is too old, or we're forcing a full
-
		   update, then we cannot do an incremental update.
-
		   Delete the existing repo, and promote this to a
-
		   full update */
-
		if (!incremental)
-
			continue;
-
		retcode = get_repo_user_version(*sqlite, "main", &reposcver);
-
		if (retcode != EPKG_OK)
-
			return (EPKG_FATAL);
-
		if (force || reposcver != REPO_SCHEMA_VERSION) {
-
			if (reposcver != REPO_SCHEMA_VERSION)
-
				pkg_emit_error("updating repo schema version "
-
				     "from %d to %d", reposcver,
-
				     REPO_SCHEMA_VERSION);
-
			sqlite3_close(*sqlite);
-
			unlink(repodb);
-
			incremental = false;
-
			db_not_open = true;
-
		}
-
	}
-

-
	sqlite3_create_function(*sqlite, "file_exists", 2, SQLITE_ANY, NULL,
-
	    file_exists, NULL, NULL);
-

-
	retcode = sql_exec(*sqlite, "PRAGMA synchronous=off");
-
	if (retcode != EPKG_OK)
-
		return (retcode);
-

-
	retcode = sql_exec(*sqlite, "PRAGMA journal_mode=memory");
-
	if (retcode != EPKG_OK)
-
		return (retcode);
-

-
	retcode = sql_exec(*sqlite, "PRAGMA foreign_keys=on");
-
	if (retcode != EPKG_OK)
-
		return (retcode);
-

-
	if (!incremental) {
-
		retcode = sql_exec(*sqlite, initsql, REPO_SCHEMA_VERSION);
-
		if (retcode != EPKG_OK)
-
			return (retcode);
-
	}
-

-
	retcode = pkgdb_transaction_begin(*sqlite, NULL);
-
	if (retcode != EPKG_OK)
-
		return (retcode);
-

-
	/* remove anything that is no longer in the repository. */
-
	if (incremental) {
-
		const char *obsolete[] = {
-
			"packages WHERE NOT FILE_EXISTS(path, cksum)",
-
			"categories WHERE id NOT IN "
-
				"(SELECT category_id FROM pkg_categories)",
-
			"licenses WHERE id NOT IN "
-
				"(SELECT license_id FROM pkg_licenses)",
-
			"shlibs WHERE id NOT IN "
-
				"(SELECT shlib_id FROM pkg_shlibs_required)"
-
			        "AND id NOT IN "
-
				"(SELECT shlib_id FROM pkg_shlibs_provided)"
-
		};
-
		size_t num_objs = sizeof(obsolete) / sizeof(*obsolete);
-
		for (size_t obj = 0; obj < num_objs; obj++)
-
			sql_exec(*sqlite, "DELETE FROM %s;", obsolete[obj]);
-
	}
-

-
	return (EPKG_OK);
-
}
-

-
static int
-
initialize_prepared_statements(sqlite3 *sqlite)
-
{
-
	sql_prstmt_index i, last;
-
	int ret;
-

-
	last = PRSTMT_LAST;
-

-
	for (i = 0; i < last; i++)
-
	{
-
		ret = sqlite3_prepare_v2(sqlite, SQL(i), -1, &STMT(i), NULL);
-
		if (ret != SQLITE_OK) {
-
			ERROR_SQLITE(sqlite);
-
			return (EPKG_FATAL);
-
		}
-
	}
-
	return (EPKG_OK);
-
}
-

-
static int
-
run_prepared_statement(sql_prstmt_index s, ...)
-
{
-
	int retcode;	/* Returns SQLITE error code */
-
	va_list ap;
-
	sqlite3_stmt *stmt;
-
	int i;
-
	const char *argtypes;
-

-
	stmt = STMT(s);
-
	argtypes = sql_prepared_statements[s].argtypes;
-

-
	sqlite3_reset(stmt);
-

-
	va_start(ap, s);
-

-
	for (i = 0; argtypes[i] != '\0'; i++)
-
	{
-
		switch (argtypes[i]) {
-
		case 'T':
-
			sqlite3_bind_text(stmt, i + 1, va_arg(ap, const char*),
-
			    -1, SQLITE_STATIC);
-
			break;
-
		case 'I':
-
			sqlite3_bind_int64(stmt, i + 1, va_arg(ap, int64_t));
-
			break;
-
		}
-
	}
-

-
	va_end(ap);
-

-
	retcode = sqlite3_step(stmt);
-

-
	return (retcode);
-
}
-

-
static void
-
finalize_prepared_statements(void)
-
{
-
	sql_prstmt_index i, last;
-

-
	last = PRSTMT_LAST;
-

-
	for (i = 0; i < last; i++)
-
	{
-
		if (STMT(i) != NULL) {
-
			sqlite3_finalize(STMT(i));
-
			STMT(i) = NULL;
-
		}
-
	}
-
	return;
-
}
-

-
static int
-
maybe_delete_conflicting(const char *origin, const char *version,
-
			 const char *pkg_path)
-
{
-
	int ret = EPKG_FATAL;
-
	const char *oversion;
-

-
	if (run_prepared_statement(VERSION, origin) != SQLITE_ROW)
-
		return (EPKG_FATAL); /* sqlite error */
-
	oversion = sqlite3_column_text(STMT(VERSION), 0);
-
	switch(pkg_version_cmp(oversion, version)) {
-
	case -1:
-
		pkg_emit_error("duplicate package origin: replacing older "
-
			       "version %s in repo with package %s for "
-
			       "origin %s", oversion, pkg_path, origin);
-

-
		if (run_prepared_statement(DELETE, origin) != SQLITE_DONE)
-
			return (EPKG_FATAL); /* sqlite error */
-

-
		ret = EPKG_OK;	/* conflict cleared */
-
		break;
-
	case 0:
-
	case 1:
-
		pkg_emit_error("duplicate package origin: package %s is not "
-
			       "newer than version %s already in repo for "
-
			       "origin %s", pkg_path, oversion, origin);
-
		ret = EPKG_END;	/* keep what is already in the repo */
-
		break;
-
	}
-
	return (ret);	
-
}
-

-
static void
pack_extract(const char *pack, const char *dbname, const char *dbpath)
{
	struct archive *a = NULL;
@@ -557,7 +169,6 @@ pack_extract(const char *pack, const char *dbname, const char *dbpath)
	}

	archive_read_free(a);
-

}

struct digest_list_entry {
@@ -583,19 +194,10 @@ pkg_create_repo(char *path, bool force, void (progress)(struct pkg *pkg, void *d
	size_t len;
	pthread_t *tids = NULL;
	struct digest_list_entry *dlist = NULL, *cur_dig, *dtmp;
-

-
	struct pkg_dep *dep = NULL;
-
	struct pkg_category *category = NULL;
-
	struct pkg_license *license = NULL;
-
	struct pkg_option *option = NULL;
-
	struct pkg_shlib *shlib = NULL;
-

	sqlite3 *sqlite = NULL;

-
	int64_t package_id;
	char *errmsg = NULL;
	int retcode = EPKG_OK;
-
	int ret;

	char *repopath[2];
	char repodb[MAXPATHLEN + 1];
@@ -644,10 +246,7 @@ pkg_create_repo(char *path, bool force, void (progress)(struct pkg *pkg, void *d

	pack_extract(repopack, repo_db_file, repodb);

-
	if ((retcode = initialize_repo(repodb, force, &sqlite)) != EPKG_OK)
-
		goto cleanup;
-

-
	if ((retcode = initialize_prepared_statements(sqlite)) != EPKG_OK)
+
	if ((retcode = pkgdb_repo_init(repodb, force, &sqlite)) != EPKG_OK)
		goto cleanup;

	thd_data.root_path = path;
@@ -670,11 +269,8 @@ pkg_create_repo(char *path, bool force, void (progress)(struct pkg *pkg, void *d

	for (;;) {
		struct pkg_result *r;
+
		const char *origin;

-
		const char *name, *version, *origin, *comment, *desc;
-
		const char *arch, *maintainer, *www, *prefix, *sum, *rpath;
-
		int64_t flatsize, pkgsize;
-
		lic_t licenselogic;
		long manifest_pos, files_pos;

		pthread_mutex_lock(&thd_data.results_m);
@@ -701,11 +297,11 @@ pkg_create_repo(char *path, bool force, void (progress)(struct pkg *pkg, void *d
		/* do not add if package if already in repodb
		   (possibly at a different pkg_path) */

-
		if (run_prepared_statement(EXISTS, r->cksum) != SQLITE_ROW) {
-
			ERROR_SQLITE(sqlite);
+
		retcode = pkgdb_repo_cksum_exists(sqlite, r->cksum);
+
		if (retcode == EPKG_FATAL) {
			goto cleanup;
		}
-
		if (sqlite3_column_int(STMT(EXISTS), 0) > 0) {
+
		else if (retcode == EPKG_OK) {
			continue;
		}

@@ -717,13 +313,8 @@ pkg_create_repo(char *path, bool force, void (progress)(struct pkg *pkg, void *d
		pkg_emit_manifest_file(r->pkg, psyml, true, &manifest_digest);
		pkg_emit_filelist(r->pkg, fsyml);

-
		pkg_get(r->pkg, PKG_ORIGIN, &origin, PKG_NAME, &name,
-
		    PKG_VERSION, &version, PKG_COMMENT, &comment,
-
		    PKG_DESC, &desc, PKG_ARCH, &arch,
-
		    PKG_MAINTAINER, &maintainer, PKG_WWW, &www,
-
		    PKG_PREFIX, &prefix, PKG_FLATSIZE, &flatsize,
-
		    PKG_LICENSE_LOGIC, &licenselogic, PKG_CKSUM, &sum,
-
		    PKG_NEW_PKGSIZE, &pkgsize, PKG_REPOPATH, &rpath);
+
		pkg_get(r->pkg, PKG_ORIGIN, &origin);
+

		cur_dig = malloc(sizeof (struct digest_list_entry));
		cur_dig->origin = strdup(origin);
		cur_dig->digest = manifest_digest;
@@ -731,132 +322,25 @@ pkg_create_repo(char *path, bool force, void (progress)(struct pkg *pkg, void *d
		cur_dig->files_pos = files_pos;
		LL_PREPEND(dlist, cur_dig);

-
	try_again:
-
		if ((ret = run_prepared_statement(PKG, origin, name, version,
-
		    comment, desc, arch, maintainer, www, prefix,
-
		    pkgsize, flatsize, (int64_t)licenselogic, sum,
-
		    rpath)) != SQLITE_DONE) {
-
			if (ret == SQLITE_CONSTRAINT) {
-
				switch(maybe_delete_conflicting(origin,
-
				    version, r->path)) {
-
				case EPKG_FATAL: /* sqlite error */
-
					ERROR_SQLITE(sqlite);
-
					retcode = EPKG_FATAL;
-
					goto cleanup;
-
					break;
-
				case EPKG_END: /* repo already has newer */
-
					continue;
-
					break;
-
				default: /* conflict cleared, try again */
-
					goto try_again;
-
					break;
-
				}
-
			} else {
-
				ERROR_SQLITE(sqlite);
-
				retcode = EPKG_FATAL;
-
				goto cleanup;
-
			}
-
		}
-

-
		package_id = sqlite3_last_insert_rowid(sqlite);
-

-
		dep = NULL;
-
		while (pkg_deps(r->pkg, &dep) == EPKG_OK) {
-
			if (run_prepared_statement(DEPS,
-
			    pkg_dep_origin(dep),
-
			    pkg_dep_name(dep),
-
			    pkg_dep_version(dep),
-
			    package_id) != SQLITE_DONE) {
-
				ERROR_SQLITE(sqlite);
-
				retcode = EPKG_FATAL;
-
				goto cleanup;
-
			}
-
		}
-

-
		category = NULL;
-
		while (pkg_categories(r->pkg, &category) == EPKG_OK) {
-
			const char *cat_name = pkg_category_name(category);
-

-
			ret = run_prepared_statement(CAT1, cat_name);
-
			if (ret == SQLITE_DONE)
-
			    ret = run_prepared_statement(CAT2, package_id,
-
			        cat_name);
-
			if (ret != SQLITE_DONE)
-
			{
-
				ERROR_SQLITE(sqlite);
-
				retcode = EPKG_FATAL;
-
				goto cleanup;
-
			}
-
		}
-

-
		license = NULL;
-
		while (pkg_licenses(r->pkg, &license) == EPKG_OK) {
-
			const char *lic_name = pkg_license_name(license);
-

-
			ret = run_prepared_statement(LIC1, lic_name);
-
			if (ret == SQLITE_DONE)
-
				ret = run_prepared_statement(LIC2, package_id,
-
				    lic_name);
-
			if (ret != SQLITE_DONE) {
-
				ERROR_SQLITE(sqlite);
-
				retcode = EPKG_FATAL;
-
				goto cleanup;
-
			}
-
		}
-
		option = NULL;
-
		while (pkg_options(r->pkg, &option) == EPKG_OK) {
-
			if (run_prepared_statement(OPTS,
-
			    pkg_option_opt(option),
-
			    pkg_option_value(option),
-
			    package_id) != SQLITE_DONE) {
-
				ERROR_SQLITE(sqlite);
-
				retcode = EPKG_FATAL;
-
				goto cleanup;
-
			}
-
		}
-

-
		shlib = NULL;
-
		while (pkg_shlibs_required(r->pkg, &shlib) == EPKG_OK) {
-
			const char *shlib_name = pkg_shlib_name(shlib);
-

-
			ret = run_prepared_statement(SHLIB1, shlib_name);
-
			if (ret == SQLITE_DONE)
-
			    ret = run_prepared_statement(SHLIB_REQD, package_id,
-
			        shlib_name);
-
			if (ret != SQLITE_DONE) {
-
				ERROR_SQLITE(sqlite);
-
				retcode = EPKG_FATAL;
-
				goto cleanup;
-
			}
+
		retcode = pkgdb_repo_add_package(r->pkg, r->path, sqlite);
+
		if (retcode == EPKG_END) {
+
			continue;
		}
-

-
		shlib = NULL;
-
		while (pkg_shlibs_provided(r->pkg, &shlib) == EPKG_OK) {
-
			const char *shlib_name = pkg_shlib_name(shlib);
-

-
			ret = run_prepared_statement(SHLIB1, shlib_name);
-
			if (ret == SQLITE_DONE)
-
			    ret = run_prepared_statement(SHLIB_PROV, package_id,
-
			        shlib_name);
-
			if (ret != SQLITE_DONE) {
-
				ERROR_SQLITE(sqlite);
-
				retcode = EPKG_FATAL;
-
				goto cleanup;
-
			}
+
		else if (retcode != EPKG_OK) {
+
			goto cleanup;
		}

		pkg_free(r->pkg);
		free(r);
-

	}

-
	if (pkgdb_transaction_commit(sqlite, NULL) != SQLITE_OK) {
-
		retcode = EPKG_FATAL;
-
		goto cleanup;
-
	}
	/* Now sort all digests */
	LL_SORT(dlist, digest_sort_compare_func);
cleanup:
+
	if (pkgdb_repo_close(sqlite, retcode == EPKG_OK) != EPKG_OK) {
+
		retcode = EPKG_FATAL;
+
		goto cleanup;
+
	}
	LL_FOREACH_SAFE(dlist, cur_dig, dtmp) {
		if (retcode == EPKG_OK) {
			fprintf(mandigests, "%s:%s:%ld:%ld\n", cur_dig->origin,
@@ -883,8 +367,6 @@ cleanup:
	if (fts != NULL)
		fts_close(fts);

-
	finalize_prepared_statements();
-

	if (fsyml != NULL)
		fclose(fsyml);

@@ -1076,242 +558,3 @@ pkg_finish_repo(char *path, pem_password_cb *password_cb, char *rsa_key_path)

	return (EPKG_OK);
}
-

-
/* We want to replace some arbitrary number of instances of the placeholder
-
   %Q in the SQL with the name of the database. */
-
static int
-
substitute_into_sql(char *sqlbuf, size_t buflen, const char *fmt,
-
		    const char *replacement)
-
{
-
	char	*f;
-
	char	*f0;
-
	char	*tofree;
-
	char	*quoted;
-
	size_t	 len;
-
	int	 ret = EPKG_OK;
-

-
	tofree = f = strdup(fmt);
-
	if (tofree == NULL)
-
		return (EPKG_FATAL); /* out of memory */
-

-
	quoted = sqlite3_mprintf("%Q", replacement);
-
	if (quoted == NULL) {
-
		free(tofree);
-
		return (EPKG_FATAL); /* out of memory */
-
	}
-

-
	sqlbuf[0] = '\0';
-

-
	while ((f0 = strsep(&f, "%")) != NULL) {
-
		len = strlcat(sqlbuf, f0, buflen);
-
		if (len >= buflen) {
-
			/* Overflowed the buffer */
-
			ret = EPKG_FATAL;
-
			break;
-
		}
-

-
		if (f == NULL)
-
			break;	/* done */
-

-
		if (f[0] == 'Q') {
-
			len = strlcat(sqlbuf, quoted, buflen);
-
			f++;	/* Jump the Q */
-
		} else {
-
			len = strlcat(sqlbuf, "%", buflen);
-
		}
-

-
		if (len >= buflen) {
-
			/* Overflowed the buffer */
-
			ret = EPKG_FATAL;
-
			break;
-
		}
-
	}
-
	
-
	free(tofree);
-
	sqlite3_free(quoted); 
-

-
	return (ret);
-
}
-

-

-
static int
-
apply_repo_change(struct pkgdb *db, const char *database,
-
		  const struct repo_changes *repo_changes, const char *updown,
-
		  int version, int *next_version)
-
{
-
	const struct repo_changes	*change;
-
	bool			 found = false;
-
	int			 ret = EPKG_OK;
-
	char			 sql[BUFSIZ];
-
	char			*errmsg;
-
	
-
	for (change = repo_changes; change->version != -1; change++) {
-
		if (change->version == version) {
-
			found = true;
-
			break;
-
		}
-
	}
-
	if (!found) {
-
		pkg_emit_error("Failed to %s \"%s\" repo schema "
-
			" version %d (target version %d) "
-
			"-- change not found", updown, database, version,
-
			REPO_SCHEMA_VERSION);
-
		return (EPKG_FATAL);
-
	}
-

-
	/* substitute the repo database name */
-
	ret = substitute_into_sql(sql, sizeof(sql), change->sql, database);
-

-
	/* begin transaction */
-
	if (ret == EPKG_OK)
-
		ret = pkgdb_transaction_begin(db->sqlite, NULL);
-

-
	/* apply change */
-
	if (ret == EPKG_OK) {
-
		ret = sqlite3_exec(db->sqlite, sql, NULL, NULL, &errmsg);
-
		if (ret != SQLITE_OK) {
-
			pkg_emit_error("sqlite: %s", errmsg);
-
			sqlite3_free(errmsg);
-
			ret = EPKG_FATAL;
-
		}
-
	}
-
	
-
	/* update repo user_version */
-
	if (ret == EPKG_OK) {
-
		*next_version = change->next_version;
-
		ret = set_repo_user_version(db->sqlite, database, *next_version);
-
	}
-

-
	/* commit or rollback */
-
	if (ret == EPKG_OK)
-
		ret = pkgdb_transaction_commit(db->sqlite, NULL);
-
	else
-
		pkgdb_transaction_rollback(db->sqlite, NULL);
-

-
	if (ret == EPKG_OK) {
-
		pkg_emit_notice("Repo \"%s\" %s schema %d to %d: %s",
-
				database, updown, version,
-
				change->next_version, change->message);
-
	}
-

-
	return (ret);
-
}
-

-
static int
-
upgrade_repo_schema(struct pkgdb *db, const char *database, int current_version)
-
{
-
	int version;
-
	int next_version;
-
	int ret = EPKG_OK;
-

-
	for (version = current_version;
-
	     version < REPO_SCHEMA_VERSION;
-
	     version = next_version)  {
-
		ret = apply_repo_change(db, database, repo_upgrades,
-
					"upgrade", version, &next_version);
-
		if (ret != EPKG_OK)
-
			break;
-
	}
-
	return (ret);
-
}
-

-
static int
-
downgrade_repo_schema(struct pkgdb *db, const char *database, int current_version)
-
{
-
	int version;
-
	int next_version;
-
	int ret = EPKG_OK;
-

-
	for (version = current_version;
-
	     version > REPO_SCHEMA_VERSION;
-
	     version = next_version)  {
-
		ret = apply_repo_change(db, database, repo_downgrades,
-
					"downgrade", version, &next_version);
-
		if (ret != EPKG_OK)
-
			break;
-
	}
-
	return (ret);
-
}
-

-
int
-
pkg_check_repo_version(struct pkgdb *db, const char *database)
-
{
-
	int reposcver;
-
	int repomajor;
-
	int ret;
-

-
	assert(db != NULL);
-
	assert(database != NULL);
-

-
	if ((ret = get_repo_user_version(db->sqlite, database, &reposcver))
-
	    != EPKG_OK)
-
		return (ret);	/* sqlite error */
-

-
	/*
-
	 * If the local pkgng uses a repo schema behind that used to
-
	 * create the repo, we may still be able use it for reading
-
	 * (ie pkg install), but pkg repo can't do an incremental
-
	 * update unless the actual schema matches the compiled in
-
	 * schema version.
-
	 *
-
	 * Use a major - minor version schema: as the user_version
-
	 * PRAGMA takes an integer version, encode this as MAJOR *
-
	 * 1000 + MINOR.
-
	 *
-
	 * So long as the major versions are the same, the local pkgng
-
	 * should be compatible with any repo created by a more recent
-
	 * pkgng, although it may need some modification of the repo
-
	 * schema
-
	 */
-

-
	/* --- Temporary ---- Grandfather in the old repo schema
-
	   version so this patch doesn't immediately invalidate all
-
	   the repos out there */
-

-
	if (reposcver == 2)
-
		reposcver = 2000;
-
	if (reposcver == 3)
-
		reposcver = 2001;
-

-
	repomajor = reposcver / 1000;
-

-
	if (repomajor < REPO_SCHEMA_MAJOR) {
-
		pkg_emit_error("Repo %s (schema version %d) is too old - "
-
		    "need at least schema %d", database, reposcver,
-
		    REPO_SCHEMA_MAJOR * 1000);
-
		return (EPKG_REPOSCHEMA);
-
	}
-

-
	if (repomajor > REPO_SCHEMA_MAJOR) {
-
		pkg_emit_error("Repo %s (schema version %d) is too new - "
-
		    "we can accept at most schema %d", database, reposcver,
-
		    ((REPO_SCHEMA_MAJOR + 1) * 1000) - 1);
-
		return (EPKG_REPOSCHEMA);
-
	}
-

-
	/* This is a repo schema version we can work with */
-

-
	ret = EPKG_OK;
-

-
	if (reposcver < REPO_SCHEMA_VERSION) {
-
		if (sqlite3_db_readonly(db->sqlite, database)) {
-
			pkg_emit_error("Repo %s needs schema upgrade from "
-
			"%d to %d but it is opened readonly", database,
-
			       reposcver, REPO_SCHEMA_VERSION
-
			);
-
			ret = EPKG_FATAL;
-
		} else
-
			ret = upgrade_repo_schema(db, database, reposcver);
-
	} else if (reposcver > REPO_SCHEMA_VERSION) {
-
		if (sqlite3_db_readonly(db->sqlite, database)) {
-
			pkg_emit_error("Repo %s needs schema downgrade from "
-
			"%d to %d but it is opened readonly", database,
-
			       reposcver, REPO_SCHEMA_VERSION
-
			);
-
			ret = EPKG_FATAL;
-
		} else
-
			ret = downgrade_repo_schema(db, database, reposcver);
-
	}
-

-
	return (ret);
-
}
modified libpkg/pkgdb.c
@@ -697,7 +697,7 @@ pkgdb_open_multirepos(const char *dbdir, struct pkgdb *db)
			return (EPKG_FATAL);
		}

-
		switch (pkg_check_repo_version(db, repo_name)) {
+
		switch (pkgdb_repo_check_version(db, repo_name)) {
		case EPKG_FATAL:
			pkgdb_close(db);
			return (EPKG_FATAL);
@@ -1051,7 +1051,7 @@ pkgdb_open(struct pkgdb **db_p, pkgdb_t type)
				return (EPKG_FATAL);
			}

-
			ret = pkg_check_repo_version(db, "remote");
+
			ret = pkgdb_repo_check_version(db, "remote");
			if (ret != EPKG_OK) {
				pkgdb_close(db);
				return (ret);
added libpkg/pkgdb_repo.c
@@ -0,0 +1,847 @@
+
/*-
+
 * Copyright (c) 2013 Vsevolod Stakhov <vsevolod@FreeBSD.org>
+
 * Copyright (c) 2011-2012 Baptiste Daroussin <bapt@FreeBSD.org>
+
 * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org>
+
 * Copyright (c) 2011 Will Andrews <will@FreeBSD.org>
+
 * Copyright (c) 2011 Philippe Pepiot <phil@philpep.org>
+
 * Copyright (c) 2011-2012 Marin Atanasov Nikolov <dnaeon@gmail.com>
+
 * Copyright (c) 2012-2013 Matthew Seaman <matthew@FreeBSD.org>
+
 * Copyright (c) 2012 Bryan Drewery <bryan@shatow.net>
+
 * Copyright (c) 2013 Gerald Pfeifer <gerald@pfeifer.com>
+
 * 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 <assert.h>
+
#include <errno.h>
+
#include <regex.h>
+
#include <grp.h>
+
#include <libutil.h>
+
#include <stdlib.h>
+
#include <stdio.h>
+
#include <stdbool.h>
+
#include <string.h>
+
#include <unistd.h>
+
#include <libgen.h>
+

+
#include <sqlite3.h>
+

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

+
/* The package repo schema major revision */
+
#define REPO_SCHEMA_MAJOR 2
+

+
/* The package repo schema minor revision.
+
   Minor schema changes don't prevent older pkgng
+
   versions accessing the repo. */
+
#define REPO_SCHEMA_MINOR 4
+

+
#define REPO_SCHEMA_VERSION (REPO_SCHEMA_MAJOR * 1000 + REPO_SCHEMA_MINOR)
+

+
typedef enum _sql_prstmt_index {
+
	PKG = 0,
+
	DEPS,
+
	CAT1,
+
	CAT2,
+
	LIC1,
+
	LIC2,
+
	OPTS,
+
	SHLIB1,
+
	SHLIB_REQD,
+
	SHLIB_PROV,
+
	ABSTRACT1,
+
	ABSTRACT2,
+
	EXISTS,
+
	VERSION,
+
	DELETE,
+
	PRSTMT_LAST,
+
} sql_prstmt_index;
+

+
static sql_prstmt sql_prepared_statements[PRSTMT_LAST] = {
+
	[PKG] = {
+
		NULL,
+
		"INSERT INTO packages ("
+
		"origin, name, version, comment, desc, arch, maintainer, www, "
+
		"prefix, pkgsize, flatsize, licenselogic, cksum, path"
+
		")"
+
		"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)",
+
		"TTTTTTTTTIIITT",
+
	},
+
	[DEPS] = {
+
		NULL,
+
		"INSERT INTO deps (origin, name, version, package_id) "
+
		"VALUES (?1, ?2, ?3, ?4)",
+
		"TTTI",
+
	},
+
	[CAT1] = {
+
		NULL,
+
		"INSERT OR IGNORE INTO categories(name) VALUES(?1)",
+
		"T",
+
	},
+
	[CAT2] = {
+
		NULL,
+
		"INSERT OR ROLLBACK INTO pkg_categories(package_id, category_id) "
+
		"VALUES (?1, (SELECT id FROM categories WHERE name = ?2))",
+
		"IT",
+
	},
+
	[LIC1] = {
+
		NULL,
+
		"INSERT OR IGNORE INTO licenses(name) VALUES(?1)",
+
		"T",
+
	},
+
	[LIC2] = {
+
		NULL,
+
		"INSERT OR ROLLBACK INTO pkg_licenses(package_id, license_id) "
+
		"VALUES (?1, (SELECT id FROM licenses WHERE name = ?2))",
+
		"IT",
+
	},
+
	[OPTS] = {
+
		NULL,
+
		"INSERT OR ROLLBACK INTO options (option, value, package_id) "
+
		"VALUES (?1, ?2, ?3)",
+
		"TTI",
+
	},
+
	[SHLIB1] = {
+
		NULL,
+
		"INSERT OR IGNORE INTO shlibs(name) VALUES(?1)",
+
		"T",
+
	},
+
	[SHLIB_REQD] = {
+
		NULL,
+
		"INSERT OR ROLLBACK INTO pkg_shlibs_required(package_id, shlib_id) "
+
		"VALUES (?1, (SELECT id FROM shlibs WHERE name = ?2))",
+
		"IT",
+
	},
+
	[SHLIB_PROV] = {
+
		NULL,
+
		"INSERT OR ROLLBACK INTO pkg_shlibs_provided(package_id, shlib_id) "
+
		"VALUES (?1, (SELECT id FROM shlibs WHERE name = ?2))",
+
		"IT",
+
	},
+
	[EXISTS] = {
+
		NULL,
+
		"SELECT count(*) FROM packages WHERE cksum=?1",
+
		"T",
+
	},
+
	[ABSTRACT1] = {
+
		NULL,
+
		"INSERT OR IGNORE INTO abstract(abstract) "
+
		"VALUES (?1)",
+
		"T",
+
	},
+
	[ABSTRACT2] = {
+
		NULL,
+
		"INSERT OR ROLLBACK INTO pkg_abstract(package_id, key_id, value_id) "
+
		"VALUES (?1,"
+
		" (SELECT abstract_id FROM abstract WHERE abstract=?2),"
+
		" (SELECT abstract_id FROM abstract WHERE abstract=?3))",
+
		"ITT",
+
	},
+
	[VERSION] = {
+
		NULL,
+
		"SELECT version FROM packages WHERE origin=?1",
+
		"T",
+
	},
+
	[DELETE] = {
+
		NULL,
+
		"DELETE FROM packages WHERE origin=?1",
+
		"T",
+
	}
+
	/* PRSTMT_LAST */
+
};
+

+
static void
+
file_exists(sqlite3_context *ctx, int argc, sqlite3_value **argv)
+
{
+
	char	 fpath[MAXPATHLEN];
+
	sqlite3	*db = sqlite3_context_db_handle(ctx);
+
	char	*path = dirname(sqlite3_db_filename(db, "main"));
+
	char	 cksum[SHA256_DIGEST_LENGTH * 2 +1];
+

+
	if (argc != 2) {
+
		sqlite3_result_error(ctx, "file_exists needs two argument", -1);
+
		return;
+
	}
+

+
	snprintf(fpath, MAXPATHLEN, "%s/%s", path, sqlite3_value_text(argv[0]));
+

+
	if (access(fpath, R_OK) == 0) {
+
		sha256_file(fpath, cksum);
+
		if (strcmp(cksum, sqlite3_value_text(argv[1])) == 0)
+
			sqlite3_result_int(ctx, 1);
+
		else
+
			sqlite3_result_int(ctx, 0);
+
	} else {
+
		sqlite3_result_int(ctx, 0);
+
	}
+
}
+

+
static int
+
get_repo_user_version(sqlite3 *sqlite, const char *database, int *reposcver)
+
{
+
	sqlite3_stmt *stmt;
+
	int retcode;
+
	char sql[BUFSIZ];
+
	const char *fmt = "PRAGMA %Q.user_version";
+

+
	assert(database != NULL);
+

+
	sqlite3_snprintf(sizeof(sql), sql, fmt, database);
+

+
	if (sqlite3_prepare_v2(sqlite, sql, -1, &stmt, NULL) != SQLITE_OK) {
+
		ERROR_SQLITE(sqlite);
+
		return (EPKG_FATAL);
+
	}
+

+
	if (sqlite3_step(stmt) == SQLITE_ROW) {
+
		*reposcver = sqlite3_column_int(stmt, 0);
+
		retcode = EPKG_OK;
+
	} else {
+
		*reposcver = -1;
+
		retcode = EPKG_FATAL;
+
	}
+
	sqlite3_finalize(stmt);
+
	return (retcode);
+
}
+

+
static int
+
initialize_prepared_statements(sqlite3 *sqlite)
+
{
+
	sql_prstmt_index i, last;
+
	int ret;
+

+
	last = PRSTMT_LAST;
+

+
	for (i = 0; i < last; i++)
+
	{
+
		ret = sqlite3_prepare_v2(sqlite, SQL(i), -1, &STMT(i), NULL);
+
		if (ret != SQLITE_OK) {
+
			ERROR_SQLITE(sqlite);
+
			return (EPKG_FATAL);
+
		}
+
	}
+
	return (EPKG_OK);
+
}
+

+
static int
+
run_prepared_statement(sql_prstmt_index s, ...)
+
{
+
	int retcode;	/* Returns SQLITE error code */
+
	va_list ap;
+
	sqlite3_stmt *stmt;
+
	int i;
+
	const char *argtypes;
+

+
	stmt = STMT(s);
+
	argtypes = sql_prepared_statements[s].argtypes;
+

+
	sqlite3_reset(stmt);
+

+
	va_start(ap, s);
+

+
	for (i = 0; argtypes[i] != '\0'; i++)
+
	{
+
		switch (argtypes[i]) {
+
		case 'T':
+
			sqlite3_bind_text(stmt, i + 1, va_arg(ap, const char*),
+
			    -1, SQLITE_STATIC);
+
			break;
+
		case 'I':
+
			sqlite3_bind_int64(stmt, i + 1, va_arg(ap, int64_t));
+
			break;
+
		}
+
	}
+

+
	va_end(ap);
+

+
	retcode = sqlite3_step(stmt);
+

+
	return (retcode);
+
}
+

+
static void
+
finalize_prepared_statements(void)
+
{
+
	sql_prstmt_index i, last;
+

+
	last = PRSTMT_LAST;
+

+
	for (i = 0; i < last; i++)
+
	{
+
		if (STMT(i) != NULL) {
+
			sqlite3_finalize(STMT(i));
+
			STMT(i) = NULL;
+
		}
+
	}
+
	return;
+
}
+

+
int
+
pkgdb_repo_init(const char *repodb, bool force, sqlite3 **sqlite)
+
{
+
	bool incremental = false;
+
	bool db_not_open;
+
	int reposcver;
+
	int retcode = EPKG_OK;
+

+
	if (access(repodb, R_OK) == 0)
+
		incremental = true;
+

+
	sqlite3_initialize();
+
	db_not_open = true;
+
	while (db_not_open) {
+
		if (sqlite3_open(repodb, sqlite) != SQLITE_OK) {
+
			sqlite3_shutdown();
+
			return (EPKG_FATAL);
+
		}
+

+
		db_not_open = false;
+

+
		/* If the schema is too old, or we're forcing a full
+
		   update, then we cannot do an incremental update.
+
		   Delete the existing repo, and promote this to a
+
		   full update */
+
		if (!incremental)
+
			continue;
+
		retcode = get_repo_user_version(*sqlite, "main", &reposcver);
+
		if (retcode != EPKG_OK)
+
			return (EPKG_FATAL);
+
		if (force || reposcver != REPO_SCHEMA_VERSION) {
+
			if (reposcver != REPO_SCHEMA_VERSION)
+
				pkg_emit_error("re-creating repo to upgrade schema version "
+
				     "from %d to %d", reposcver,
+
				     REPO_SCHEMA_VERSION);
+
			sqlite3_close(*sqlite);
+
			unlink(repodb);
+
			incremental = false;
+
			db_not_open = true;
+
		}
+
	}
+

+
	sqlite3_create_function(*sqlite, "file_exists", 2, SQLITE_ANY, NULL,
+
	    file_exists, NULL, NULL);
+

+
	retcode = sql_exec(*sqlite, "PRAGMA synchronous=off");
+
	if (retcode != EPKG_OK)
+
		return (retcode);
+

+
	retcode = sql_exec(*sqlite, "PRAGMA journal_mode=memory");
+
	if (retcode != EPKG_OK)
+
		return (retcode);
+

+
	retcode = sql_exec(*sqlite, "PRAGMA foreign_keys=on");
+
	if (retcode != EPKG_OK)
+
		return (retcode);
+

+
	if (!incremental) {
+
		retcode = sql_exec(*sqlite, initsql, REPO_SCHEMA_VERSION);
+
		if (retcode != EPKG_OK)
+
			return (retcode);
+
	}
+

+
	retcode = initialize_prepared_statements(*sqlite);
+
	if (retcode != EPKG_OK)
+
		return (retcode);
+

+
	retcode = pkgdb_transaction_begin(*sqlite, NULL);
+
	if (retcode != EPKG_OK)
+
		return (retcode);
+

+
	/* remove anything that is no longer in the repository. */
+
	if (incremental) {
+
		const char *obsolete[] = {
+
			"packages WHERE NOT FILE_EXISTS(path, cksum)",
+
			"categories WHERE id NOT IN "
+
				"(SELECT category_id FROM pkg_categories)",
+
			"licenses WHERE id NOT IN "
+
				"(SELECT license_id FROM pkg_licenses)",
+
			"shlibs WHERE id NOT IN "
+
				"(SELECT shlib_id FROM pkg_shlibs_required)"
+
			        "AND id NOT IN "
+
				"(SELECT shlib_id FROM pkg_shlibs_provided)"
+
		};
+
		size_t num_objs = sizeof(obsolete) / sizeof(*obsolete);
+
		for (size_t obj = 0; obj < num_objs; obj++)
+
			sql_exec(*sqlite, "DELETE FROM %s;", obsolete[obj]);
+
	}
+

+
	return (EPKG_OK);
+
}
+

+
int
+
pkgdb_repo_close(sqlite3 *sqlite, bool commit)
+
{
+
	int retcode = EPKG_OK;
+

+
	if (commit) {
+
		if (pkgdb_transaction_commit(sqlite, NULL) != SQLITE_OK)
+
			retcode = EPKG_FATAL;
+
	}
+
	else {
+
		if (pkgdb_transaction_rollback(sqlite, NULL) != SQLITE_OK)
+
				retcode = EPKG_FATAL;
+
	}
+
	finalize_prepared_statements();
+

+
	return (retcode);
+
}
+

+
static int
+
maybe_delete_conflicting(const char *origin, const char *version,
+
			 const char *pkg_path)
+
{
+
	int ret = EPKG_FATAL;
+
	const char *oversion;
+

+
	if (run_prepared_statement(VERSION, origin) != SQLITE_ROW)
+
		return (EPKG_FATAL); /* sqlite error */
+
	oversion = sqlite3_column_text(STMT(VERSION), 0);
+
	switch(pkg_version_cmp(oversion, version)) {
+
	case -1:
+
		pkg_emit_error("duplicate package origin: replacing older "
+
			       "version %s in repo with package %s for "
+
			       "origin %s", oversion, pkg_path, origin);
+

+
		if (run_prepared_statement(DELETE, origin) != SQLITE_DONE)
+
			return (EPKG_FATAL); /* sqlite error */
+

+
		ret = EPKG_OK;	/* conflict cleared */
+
		break;
+
	case 0:
+
	case 1:
+
		pkg_emit_error("duplicate package origin: package %s is not "
+
			       "newer than version %s already in repo for "
+
			       "origin %s", pkg_path, oversion, origin);
+
		ret = EPKG_END;	/* keep what is already in the repo */
+
		break;
+
	}
+
	return (ret);
+
}
+

+
int
+
pkgdb_repo_cksum_exists(sqlite3 *sqlite, const char *cksum)
+
{
+
	if (run_prepared_statement(EXISTS, cksum) != SQLITE_ROW) {
+
		ERROR_SQLITE(sqlite);
+
		return (EPKG_FATAL);
+
	}
+
	if (sqlite3_column_int(STMT(EXISTS), 0) > 0) {
+
		return (EPKG_OK);
+
	}
+
	return (EPKG_END);
+
}
+

+
int
+
pkgdb_repo_add_package(struct pkg *pkg, const char *pkg_path, sqlite3 *sqlite)
+
{
+
	const char *name, *version, *origin, *comment, *desc;
+
	const char *arch, *maintainer, *www, *prefix, *sum, *rpath;
+
	int64_t flatsize, pkgsize;
+
	lic_t licenselogic;
+
	int ret;
+
	struct pkg_dep *dep = NULL;
+
	struct pkg_category *category = NULL;
+
	struct pkg_license *license = NULL;
+
	struct pkg_option *option = NULL;
+
	struct pkg_shlib *shlib = NULL;
+
	int64_t package_id;
+

+
	pkg_get(pkg, PKG_ORIGIN, &origin, PKG_NAME, &name,
+
			    PKG_VERSION, &version, PKG_COMMENT, &comment,
+
			    PKG_DESC, &desc, PKG_ARCH, &arch,
+
			    PKG_MAINTAINER, &maintainer, PKG_WWW, &www,
+
			    PKG_PREFIX, &prefix, PKG_FLATSIZE, &flatsize,
+
			    PKG_LICENSE_LOGIC, &licenselogic, PKG_CKSUM, &sum,
+
			    PKG_NEW_PKGSIZE, &pkgsize, PKG_REPOPATH, &rpath);
+

+
try_again:
+
	if ((ret = run_prepared_statement(PKG, origin, name, version,
+
			comment, desc, arch, maintainer, www, prefix,
+
			pkgsize, flatsize, (int64_t)licenselogic, sum,
+
			rpath)) != SQLITE_DONE) {
+
		if (ret == SQLITE_CONSTRAINT) {
+
			switch(maybe_delete_conflicting(origin,
+
					version, pkg_path)) {
+
			case EPKG_FATAL: /* sqlite error */
+
				ERROR_SQLITE(sqlite);
+
				return (EPKG_FATAL);
+
				break;
+
			case EPKG_END: /* repo already has newer */
+
				return (EPKG_END);
+
				break;
+
			default: /* conflict cleared, try again */
+
				goto try_again;
+
				break;
+
			}
+
		} else {
+
			ERROR_SQLITE(sqlite);
+
			return (EPKG_FATAL);
+
		}
+
	}
+
	package_id = sqlite3_last_insert_rowid(sqlite);
+

+
	dep = NULL;
+
	while (pkg_deps(pkg, &dep) == EPKG_OK) {
+
		if (run_prepared_statement(DEPS,
+
				pkg_dep_origin(dep),
+
				pkg_dep_name(dep),
+
				pkg_dep_version(dep),
+
				package_id) != SQLITE_DONE) {
+
			ERROR_SQLITE(sqlite);
+
			return (EPKG_FATAL);
+
		}
+
	}
+

+
	category = NULL;
+
	while (pkg_categories(pkg, &category) == EPKG_OK) {
+
		const char *cat_name = pkg_category_name(category);
+

+
		ret = run_prepared_statement(CAT1, cat_name);
+
		if (ret == SQLITE_DONE)
+
			ret = run_prepared_statement(CAT2, package_id,
+
					cat_name);
+
		if (ret != SQLITE_DONE)
+
		{
+
			ERROR_SQLITE(sqlite);
+
			return (EPKG_FATAL);
+
		}
+
	}
+

+
	license = NULL;
+
	while (pkg_licenses(pkg, &license) == EPKG_OK) {
+
		const char *lic_name = pkg_license_name(license);
+

+
		ret = run_prepared_statement(LIC1, lic_name);
+
		if (ret == SQLITE_DONE)
+
			ret = run_prepared_statement(LIC2, package_id,
+
					lic_name);
+
		if (ret != SQLITE_DONE) {
+
			ERROR_SQLITE(sqlite);
+
			return (EPKG_FATAL);
+
		}
+
	}
+
	option = NULL;
+
	while (pkg_options(pkg, &option) == EPKG_OK) {
+
		if (run_prepared_statement(OPTS,
+
				pkg_option_opt(option),
+
				pkg_option_value(option),
+
				package_id) != SQLITE_DONE) {
+
			ERROR_SQLITE(sqlite);
+
			return (EPKG_FATAL);
+
		}
+
	}
+

+
	shlib = NULL;
+
	while (pkg_shlibs_required(pkg, &shlib) == EPKG_OK) {
+
		const char *shlib_name = pkg_shlib_name(shlib);
+

+
		ret = run_prepared_statement(SHLIB1, shlib_name);
+
		if (ret == SQLITE_DONE)
+
			ret = run_prepared_statement(SHLIB_REQD, package_id,
+
					shlib_name);
+
		if (ret != SQLITE_DONE) {
+
			ERROR_SQLITE(sqlite);
+
			return (EPKG_FATAL);
+
		}
+
	}
+

+
	shlib = NULL;
+
	while (pkg_shlibs_provided(pkg, &shlib) == EPKG_OK) {
+
		const char *shlib_name = pkg_shlib_name(shlib);
+

+
		ret = run_prepared_statement(SHLIB1, shlib_name);
+
		if (ret == SQLITE_DONE)
+
			ret = run_prepared_statement(SHLIB_PROV, package_id,
+
					shlib_name);
+
		if (ret != SQLITE_DONE) {
+
			ERROR_SQLITE(sqlite);
+
			return (EPKG_FATAL);
+
		}
+
	}
+

+
	return (EPKG_OK);
+
}
+

+
/* We want to replace some arbitrary number of instances of the placeholder
+
   %Q in the SQL with the name of the database. */
+
static int
+
substitute_into_sql(char *sqlbuf, size_t buflen, const char *fmt,
+
		    const char *replacement)
+
{
+
	char	*f;
+
	char	*f0;
+
	char	*tofree;
+
	char	*quoted;
+
	size_t	 len;
+
	int	 ret = EPKG_OK;
+

+
	tofree = f = strdup(fmt);
+
	if (tofree == NULL)
+
		return (EPKG_FATAL); /* out of memory */
+

+
	quoted = sqlite3_mprintf("%Q", replacement);
+
	if (quoted == NULL) {
+
		free(tofree);
+
		return (EPKG_FATAL); /* out of memory */
+
	}
+

+
	sqlbuf[0] = '\0';
+

+
	while ((f0 = strsep(&f, "%")) != NULL) {
+
		len = strlcat(sqlbuf, f0, buflen);
+
		if (len >= buflen) {
+
			/* Overflowed the buffer */
+
			ret = EPKG_FATAL;
+
			break;
+
		}
+

+
		if (f == NULL)
+
			break;	/* done */
+

+
		if (f[0] == 'Q') {
+
			len = strlcat(sqlbuf, quoted, buflen);
+
			f++;	/* Jump the Q */
+
		} else {
+
			len = strlcat(sqlbuf, "%", buflen);
+
		}
+

+
		if (len >= buflen) {
+
			/* Overflowed the buffer */
+
			ret = EPKG_FATAL;
+
			break;
+
		}
+
	}
+

+
	free(tofree);
+
	sqlite3_free(quoted);
+

+
	return (ret);
+
}
+

+
static int
+
set_repo_user_version(sqlite3 *sqlite, const char *database, int reposcver)
+
{
+
	int		 retcode = EPKG_OK;
+
	char		 sql[BUFSIZ];
+
	char		*errmsg;
+
	const char	*fmt = "PRAGMA %Q.user_version = %d;" ;
+

+
	assert(database != NULL);
+

+
	sqlite3_snprintf(sizeof(sql), sql, fmt, database, reposcver);
+

+
	if (sqlite3_exec(sqlite, sql, NULL, NULL, &errmsg) != SQLITE_OK) {
+
		pkg_emit_error("sqlite: %s", errmsg);
+
		sqlite3_free(errmsg);
+
		retcode = EPKG_FATAL;
+
	}
+
	return (retcode);
+
}
+

+
static int
+
apply_repo_change(struct pkgdb *db, const char *database,
+
		  const struct repo_changes *repo_changes, const char *updown,
+
		  int version, int *next_version)
+
{
+
	const struct repo_changes	*change;
+
	bool			 found = false;
+
	int			 ret = EPKG_OK;
+
	char			 sql[BUFSIZ];
+
	char			*errmsg;
+

+
	for (change = repo_changes; change->version != -1; change++) {
+
		if (change->version == version) {
+
			found = true;
+
			break;
+
		}
+
	}
+
	if (!found) {
+
		pkg_emit_error("Failed to %s \"%s\" repo schema "
+
			" version %d (target version %d) "
+
			"-- change not found", updown, database, version,
+
			REPO_SCHEMA_VERSION);
+
		return (EPKG_FATAL);
+
	}
+

+
	/* substitute the repo database name */
+
	ret = substitute_into_sql(sql, sizeof(sql), change->sql, database);
+

+
	/* begin transaction */
+
	if (ret == EPKG_OK)
+
		ret = pkgdb_transaction_begin(db->sqlite, NULL);
+

+
	/* apply change */
+
	if (ret == EPKG_OK) {
+
		ret = sqlite3_exec(db->sqlite, sql, NULL, NULL, &errmsg);
+
		if (ret != SQLITE_OK) {
+
			pkg_emit_error("sqlite: %s", errmsg);
+
			sqlite3_free(errmsg);
+
			ret = EPKG_FATAL;
+
		}
+
	}
+

+
	/* update repo user_version */
+
	if (ret == EPKG_OK) {
+
		*next_version = change->next_version;
+
		ret = set_repo_user_version(db->sqlite, database, *next_version);
+
	}
+

+
	/* commit or rollback */
+
	if (ret == EPKG_OK)
+
		ret = pkgdb_transaction_commit(db->sqlite, NULL);
+
	else
+
		pkgdb_transaction_rollback(db->sqlite, NULL);
+

+
	if (ret == EPKG_OK) {
+
		pkg_emit_notice("Repo \"%s\" %s schema %d to %d: %s",
+
				database, updown, version,
+
				change->next_version, change->message);
+
	}
+

+
	return (ret);
+
}
+

+
static int
+
upgrade_repo_schema(struct pkgdb *db, const char *database, int current_version)
+
{
+
	int version;
+
	int next_version;
+
	int ret = EPKG_OK;
+

+
	for (version = current_version;
+
	     version < REPO_SCHEMA_VERSION;
+
	     version = next_version)  {
+
		ret = apply_repo_change(db, database, repo_upgrades,
+
					"upgrade", version, &next_version);
+
		if (ret != EPKG_OK)
+
			break;
+
	}
+
	return (ret);
+
}
+

+
static int
+
downgrade_repo_schema(struct pkgdb *db, const char *database, int current_version)
+
{
+
	int version;
+
	int next_version;
+
	int ret = EPKG_OK;
+

+
	for (version = current_version;
+
	     version > REPO_SCHEMA_VERSION;
+
	     version = next_version)  {
+
		ret = apply_repo_change(db, database, repo_downgrades,
+
					"downgrade", version, &next_version);
+
		if (ret != EPKG_OK)
+
			break;
+
	}
+
	return (ret);
+
}
+

+
int
+
pkgdb_repo_check_version(struct pkgdb *db, const char *database)
+
{
+
	int reposcver;
+
	int repomajor;
+
	int ret;
+

+
	assert(db != NULL);
+
	assert(database != NULL);
+

+
	if ((ret = get_repo_user_version(db->sqlite, database, &reposcver))
+
	    != EPKG_OK)
+
		return (ret);	/* sqlite error */
+

+
	/*
+
	 * If the local pkgng uses a repo schema behind that used to
+
	 * create the repo, we may still be able use it for reading
+
	 * (ie pkg install), but pkg repo can't do an incremental
+
	 * update unless the actual schema matches the compiled in
+
	 * schema version.
+
	 *
+
	 * Use a major - minor version schema: as the user_version
+
	 * PRAGMA takes an integer version, encode this as MAJOR *
+
	 * 1000 + MINOR.
+
	 *
+
	 * So long as the major versions are the same, the local pkgng
+
	 * should be compatible with any repo created by a more recent
+
	 * pkgng, although it may need some modification of the repo
+
	 * schema
+
	 */
+

+
	/* --- Temporary ---- Grandfather in the old repo schema
+
	   version so this patch doesn't immediately invalidate all
+
	   the repos out there */
+

+
	if (reposcver == 2)
+
		reposcver = 2000;
+
	if (reposcver == 3)
+
		reposcver = 2001;
+

+
	repomajor = reposcver / 1000;
+

+
	if (repomajor < REPO_SCHEMA_MAJOR) {
+
		pkg_emit_error("Repo %s (schema version %d) is too old - "
+
		    "need at least schema %d", database, reposcver,
+
		    REPO_SCHEMA_MAJOR * 1000);
+
		return (EPKG_REPOSCHEMA);
+
	}
+

+
	if (repomajor > REPO_SCHEMA_MAJOR) {
+
		pkg_emit_error("Repo %s (schema version %d) is too new - "
+
		    "we can accept at most schema %d", database, reposcver,
+
		    ((REPO_SCHEMA_MAJOR + 1) * 1000) - 1);
+
		return (EPKG_REPOSCHEMA);
+
	}
+

+
	/* This is a repo schema version we can work with */
+

+
	ret = EPKG_OK;
+

+
	if (reposcver < REPO_SCHEMA_VERSION) {
+
		if (sqlite3_db_readonly(db->sqlite, database)) {
+
			pkg_emit_error("Repo %s needs schema upgrade from "
+
			"%d to %d but it is opened readonly", database,
+
			       reposcver, REPO_SCHEMA_VERSION
+
			);
+
			ret = EPKG_FATAL;
+
		} else
+
			ret = upgrade_repo_schema(db, database, reposcver);
+
	} else if (reposcver > REPO_SCHEMA_VERSION) {
+
		if (sqlite3_db_readonly(db->sqlite, database)) {
+
			pkg_emit_error("Repo %s needs schema downgrade from "
+
			"%d to %d but it is opened readonly", database,
+
			       reposcver, REPO_SCHEMA_VERSION
+
			);
+
			ret = EPKG_FATAL;
+
		} else
+
			ret = downgrade_repo_schema(db, database, reposcver);
+
	}
+

+
	return (ret);
+
}
modified libpkg/private/pkgdb.h
@@ -66,4 +66,48 @@ int pkgdb_obtain_lock(struct pkgdb *db);
int pkgdb_release_lock(struct pkgdb *db);

void pkgshell_open(const char **r);
+

+
/**
+
 * Init repodb for specified path
+
 * @param repodb path of repodb
+
 * @param force create repository if not exists
+
 * @param sqlite destination db pointer
+
 * @return EPKG_OK if succeed
+
 */
+
int pkgdb_repo_init(const char *repodb, bool force, sqlite3 **sqlite);
+

+
/**
+
 * Close repodb and commit/rollback transaction started
+
 * @param sqlite sqlite pointer
+
 * @param commit commit transaction if true, rollback otherwise
+
 * @return EPKG_OK if succeed
+
 */
+
int pkgdb_repo_close(sqlite3 *sqlite, bool commit);
+

+
/**
+
 * Check whether a package with the cehcksum specified exists in pkg_repo
+
 * @param sqlite sqlite pointer
+
 * @param cksum sha256 printed checksum
+
 * @return EPKG_OK if checksum exists, EPKG_END if not and EPKG_FATAL if error occurred
+
 */
+
int pkgdb_repo_cksum_exists(sqlite3 *sqlite, const char *cksum);
+

+
/**
+
 * Add a package to pkg_repo
+
 * @param pkg package structure
+
 * @param pkg_path path triggered package addition
+
 * @param sqlite sqlite pointer
+
 * @return EPKG_OK if package added, EPKG_END if package already exists and is newer than
+
 * inserted one, EPKG_FATAL if error occurred
+
 */
+
int pkgdb_repo_add_package(struct pkg *pkg, const char *pkg_path, sqlite3 *sqlite);
+

+
/**
+
 * Upgrade repo db version if required
+
 * @param db package database object
+
 * @param database name of database
+
 * @return
+
 */
+
int pkgdb_repo_check_version(struct pkgdb *db, const char *database);
+

#endif
modified libpkg/update.c
@@ -291,6 +291,39 @@ pkg_update_full(const char *repofile, const char *name, const char *packagesite,

	return (rc);
}
+
#if 0
+
static int
+
pkg_update_incremental(const char *repofile, const char *name, const char *packagesite, time_t *mtime)
+
{
+
	FILE *fmanifest = NULL, *fdigests = NULL;
+
	int fd_manifest, fd_digests;
+
	struct pkgdb *db = NULL;
+
	int rc = EPKG_FATAL;
+

+
	if (pkgdb_open(&db, PKGDB_REMOTE) != EPKG_OK)
+
		goto cleanup;
+

+
	/* Initialize the remote remote */
+
	if (pkgdb_remote_init(db, name) != EPKG_OK)
+
		goto cleanup;
+

+
	if ((fd_digests = repo_fetch_remote_tmp(packagesite, repo_digests_archive, "txz", mtime, &rc)) == -1) {
+
		goto cleanup;
+
	}
+
	if ((fd_manifest = repo_fetch_remote_tmp(packagesite, repo_packagesite_archive, "txz", mtime, &rc)) == -1) {
+
		goto cleanup;
+
	}
+

+
	rc = EPKG_OK;
+
cleanup:
+
	if (fmanifest)
+
		fclose(fmanifest);
+
	if (fdigests)
+
		fclose(fdigests);
+

+
	return (rc);
+
}
+
#endif

int
pkg_update(const char *name, const char *packagesite, bool force)