diff options
Diffstat (limited to 'database.c')
-rw-r--r-- | database.c | 2709 |
1 files changed, 2709 insertions, 0 deletions
diff --git a/database.c b/database.c new file mode 100644 index 0000000..b01c8ed --- /dev/null +++ b/database.c @@ -0,0 +1,2709 @@ +/* This file is part of "reprepro" + * Copyright (C) 2007,2008,2016 Bernhard R. Link + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1301 USA + */ +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <assert.h> +#include <limits.h> +#include <string.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <db.h> + +#include "globals.h" +#include "error.h" +#include "ignore.h" +#include "strlist.h" +#include "names.h" +#include "database.h" +#include "dirs.h" +#include "filecntl.h" +#include "files.h" +#include "filelist.h" +#include "reference.h" +#include "tracking.h" +#include "dpkgversions.h" +#include "distribution.h" +#include "database_p.h" +#include "chunks.h" + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define LIBDB_VERSION_STRING "bdb" TOSTRING(DB_VERSION_MAJOR) "." TOSTRING(DB_VERSION_MINOR) "." TOSTRING(DB_VERSION_PATCH) +#define CLEARDBT(dbt) { memset(&dbt, 0, sizeof(dbt)); } +#define SETDBT(dbt, datastr) {const char *my = datastr; memset(&dbt, 0, sizeof(dbt)); dbt.data = (void *)my; dbt.size = strlen(my) + 1;} +#define SETDBTl(dbt, datastr, datasize) {const char *my = datastr; memset(&dbt, 0, sizeof(dbt)); dbt.data = (void *)my; dbt.size = datasize;} + +static bool rdb_initialized, rdb_used, rdb_locked, rdb_verbose; +static int rdb_dircreationdepth; +static bool rdb_nopackages, rdb_readonly; +static /*@null@*/ char *rdb_version, *rdb_lastsupportedversion, + *rdb_dbversion, *rdb_lastsupporteddbversion; +static DB_ENV *rdb_env = NULL; + +struct table *rdb_checksums, *rdb_contents; +struct table *rdb_references; + +struct opened_tables { + struct opened_tables *next; + const char *name; + const char *subname; +}; + +struct opened_tables *opened_tables = NULL; + +static void database_free(void) { + if (!rdb_initialized) + return; + free(rdb_version); + rdb_version = NULL; + free(rdb_lastsupportedversion); + rdb_lastsupportedversion = NULL; + free(rdb_dbversion); + rdb_dbversion = NULL; + free(rdb_lastsupporteddbversion); + rdb_lastsupporteddbversion = NULL; + rdb_initialized = false; +} + +static inline char *dbfilename(const char *filename) { + return calc_dirconcat(global.dbdir, filename); +} + +static retvalue database_openenv(void) { + int dbret; + + dbret = db_env_create(&rdb_env, 0); + if (dbret != 0) { + fprintf(stderr, "db_env_create: %s\n", db_strerror(dbret)); + return RET_ERROR; + } + + // DB_INIT_LOCK is needed to open multiple databases in one file (e.g. for move command) + dbret = rdb_env->open(rdb_env, global.dbdir, + DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_LOCK, 0664); + if (dbret != 0) { + rdb_env->err(rdb_env, dbret, "environment open: %s", global.dbdir); + return RET_ERROR; + } + + return RET_OK; +} + +static void database_closeenv(void) { + int dbret; + + dbret = rdb_env->close(rdb_env, 0); + if (dbret != 0) { + fprintf(stderr, "Error: DB_ENV->close: %s\n", db_strerror(dbret)); + } + rdb_env = NULL; +} + +/**********************/ +/* lock file handling */ +/**********************/ + +static retvalue database_lock(size_t waitforlock) { + char *lockfile; + int fd; + retvalue r; + size_t tries = 0; + + assert (!rdb_locked); + rdb_dircreationdepth = 0; + r = dir_create_needed(global.dbdir, &rdb_dircreationdepth); + if (RET_WAS_ERROR(r)) + return r; + + lockfile = dbfilename("lockfile"); + if (FAILEDTOALLOC(lockfile)) + return RET_ERROR_OOM; + fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY, + S_IRUSR|S_IWUSR); + while (fd < 0) { + int e = errno; + if (e == EEXIST) { + if (tries < waitforlock && ! interrupted()) { + unsigned int timetosleep = 10; + if (verbose >= 0) + printf( +"Could not acquire lock: %s already exists!\nWaiting 10 seconds before trying again.\n", + lockfile); + while (timetosleep > 0) + timetosleep = sleep(timetosleep); + tries++; + fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL + |O_NOFOLLOW|O_NOCTTY, + S_IRUSR|S_IWUSR); + continue; + + } + fprintf(stderr, +"The lock file '%s' already exists. There might be another instance with the\n" +"same database dir running. To avoid locking overhead, only one process\n" +"can access the database at the same time. Do not delete the lock file unless\n" +"you are sure no other version is still running!\n", lockfile); + + } else + fprintf(stderr, +"Error %d creating lock file '%s': %s!\n", + e, lockfile, strerror(e)); + free(lockfile); + return RET_ERRNO(e); + } + // TODO: do some more locking of this file to avoid problems + // with the non-atomity of O_EXCL with nfs-filesystems... + if (close(fd) != 0) { + int e = errno; + fprintf(stderr, +"(Late) Error %d creating lock file '%s': %s!\n", + e, lockfile, strerror(e)); + (void)unlink(lockfile); + free(lockfile); + return RET_ERRNO(e); + } + free(lockfile); + rdb_locked = true; + + r = database_openenv(); + if (RET_WAS_ERROR(r)) { + (void)unlink(lockfile); + free(lockfile); + return r; + } + return RET_OK; +} + +static void releaselock(void) { + char *lockfile; + + assert (rdb_locked); + + database_closeenv(); + lockfile = dbfilename("lockfile"); + if (lockfile == NULL) + return; + if (unlink(lockfile) != 0) { + int e = errno; + fprintf(stderr, "Error %d deleting lock file '%s': %s!\n", + e, lockfile, strerror(e)); + (void)unlink(lockfile); + } + free(lockfile); + dir_remove_new(global.dbdir, rdb_dircreationdepth); + rdb_locked = false; +} + +static retvalue writeversionfile(void); + +retvalue database_close(void) { + retvalue result = RET_OK, r; + + if (rdb_references != NULL) { + r = table_close(rdb_references); + RET_UPDATE(result, r); + rdb_references = NULL; + } + if (rdb_checksums != NULL) { + r = table_close(rdb_checksums); + RET_UPDATE(result, r); + rdb_checksums = NULL; + } + if (rdb_contents != NULL) { + r = table_close(rdb_contents); + RET_UPDATE(result, r); + rdb_contents = NULL; + } + r = writeversionfile(); + RET_UPDATE(result, r); + if (rdb_locked) + releaselock(); + database_free(); + return result; +} + +static retvalue database_hasdatabasefile(const char *filename, /*@out@*/bool *exists_p) { + char *fullfilename; + + fullfilename = dbfilename(filename); + if (FAILEDTOALLOC(fullfilename)) + return RET_ERROR_OOM; + *exists_p = isregularfile(fullfilename); + free(fullfilename); + return RET_OK; +} + +enum database_type { + dbt_QUERY, + dbt_BTREE, dbt_BTREEDUP, dbt_BTREEPAIRS, dbt_BTREEVERSIONS, + dbt_HASH, + dbt_COUNT /* must be last */ +}; +static const uint32_t types[dbt_COUNT] = { + DB_UNKNOWN, + DB_BTREE, DB_BTREE, DB_BTREE, DB_BTREE, + DB_HASH +}; + +static int debianversioncompare(UNUSED(DB *db), const DBT *a, const DBT *b); +#if DB_VERSION_MAJOR >= 6 +static int paireddatacompare(UNUSED(DB *db), const DBT *a, const DBT *b, size_t *locp); +#else +static int paireddatacompare(UNUSED(DB *db), const DBT *a, const DBT *b); +#endif + +static retvalue database_opentable(const char *filename, /*@null@*/const char *subtable, enum database_type type, uint32_t flags, /*@out@*/DB **result) { + DB *table; + int dbret; + + dbret = db_create(&table, rdb_env, 0); + if (dbret != 0) { + fprintf(stderr, "db_create: %s\n", db_strerror(dbret)); + return RET_DBERR(dbret); + } + if (type == dbt_BTREEPAIRS || type == dbt_BTREEVERSIONS) { + dbret = table->set_flags(table, DB_DUPSORT); + if (dbret != 0) { + table->err(table, dbret, "db_set_flags(DB_DUPSORT):"); + (void)table->close(table, 0); + return RET_DBERR(dbret); + } + } else if (type == dbt_BTREEDUP) { + dbret = table->set_flags(table, DB_DUP); + if (dbret != 0) { + table->err(table, dbret, "db_set_flags(DB_DUP):"); + (void)table->close(table, 0); + return RET_DBERR(dbret); + } + } + if (type == dbt_BTREEPAIRS) { + dbret = table->set_dup_compare(table, paireddatacompare); + if (dbret != 0) { + table->err(table, dbret, "db_set_dup_compare:"); + (void)table->close(table, 0); + return RET_DBERR(dbret); + } + } + if (type == dbt_BTREEVERSIONS) { + dbret = table->set_dup_compare(table, debianversioncompare); + if (dbret != 0) { + table->err(table, dbret, "db_set_dup_compare:"); + (void)table->close(table, 0); + return RET_DBERR(dbret); + } + } + +#if DB_VERSION_MAJOR == 5 || DB_VERSION_MAJOR == 6 +#define DB_OPEN(database, filename, name, type, flags) \ + database->open(database, NULL, filename, name, type, flags, 0664) +#else +#if DB_VERSION_MAJOR == 4 +#define DB_OPEN(database, filename, name, type, flags) \ + database->open(database, NULL, filename, name, type, flags, 0664) +#else +#if DB_VERSION_MAJOR == 3 +#define DB_OPEN(database, filename, name, type, flags) \ + database->open(database, filename, name, type, flags, 0664) +#else +#error Unexpected DB_VERSION_MAJOR! +#endif +#endif +#endif + dbret = DB_OPEN(table, filename, subtable, types[type], flags); + if (dbret == ENOENT && !ISSET(flags, DB_CREATE)) { + (void)table->close(table, 0); + return RET_NOTHING; + } + if (dbret != 0) { + if (subtable != NULL) + table->err(table, dbret, "db_open(%s:%s)[%d]", + filename, subtable, dbret); + else + table->err(table, dbret, "db_open(%s)[%d]", + filename, dbret); + (void)table->close(table, 0); + return RET_DBERR(dbret); + } + *result = table; + return RET_OK; +} + +retvalue database_listsubtables(const char *filename, struct strlist *result) { + DB *table; + DBC *cursor; + DBT key, data; + int dbret; + retvalue ret, r; + struct strlist ids; + + r = database_opentable(filename, NULL, + dbt_QUERY, DB_RDONLY, &table); + if (!RET_IS_OK(r)) + return r; + + cursor = NULL; + if ((dbret = table->cursor(table, NULL, &cursor, 0)) != 0) { + table->err(table, dbret, "cursor(%s):", filename); + (void)table->close(table, 0); + return RET_ERROR; + } + CLEARDBT(key); + CLEARDBT(data); + + strlist_init(&ids); + + ret = RET_NOTHING; + while ((dbret=cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) { + char *identifier = strndup(key.data, key.size); + if (FAILEDTOALLOC(identifier)) { + (void)table->close(table, 0); + strlist_done(&ids); + return RET_ERROR_OOM; + } + r = strlist_add(&ids, identifier); + if (RET_WAS_ERROR(r)) { + (void)table->close(table, 0); + strlist_done(&ids); + return r; + } + ret = RET_OK; + CLEARDBT(key); + CLEARDBT(data); + } + + if (dbret != 0 && dbret != DB_NOTFOUND) { + table->err(table, dbret, "c_get(%s):", filename); + (void)table->close(table, 0); + strlist_done(&ids); + return RET_DBERR(dbret); + } + if ((dbret = cursor->c_close(cursor)) != 0) { + table->err(table, dbret, "c_close(%s):", filename); + (void)table->close(table, 0); + strlist_done(&ids); + return RET_DBERR(dbret); + } + + dbret = table->close(table, 0); + if (dbret != 0) { + table->err(table, dbret, "close(%s):", filename); + strlist_done(&ids); + return RET_DBERR(dbret); + } else { + strlist_move(result, &ids); + return ret; + } +} + +retvalue database_dropsubtable(const char *table, const char *subtable) { + char *filename; + DB *db; + int dbret; + + filename = dbfilename(table); + if (FAILEDTOALLOC(filename)) + return RET_ERROR_OOM; + + if ((dbret = db_create(&db, NULL, 0)) != 0) { + fprintf(stderr, "db_create: %s %s\n", + filename, db_strerror(dbret)); + free(filename); + return RET_DBERR(dbret); + } + dbret = db->remove(db, filename, subtable, 0); + if (dbret == ENOENT) { + free(filename); + return RET_NOTHING; + } + if (dbret != 0) { + fprintf(stderr, "Error removing '%s' from %s!\n", + subtable, filename); + free(filename); + return RET_DBERR(dbret); + } + + free(filename); + return RET_OK; +} + +static inline bool targetisdefined(const char *identifier, struct distribution *distributions) { + struct distribution *d; + struct target *t; + + for (d = distributions ; d != NULL ; d = d->next) { + for (t = d->targets; t != NULL ; t = t->next) { + if (strcmp(t->identifier, identifier) == 0) { + t->existed = true; + return true; + } + } + } + return false; +} + +static retvalue warnidentifiers(const struct strlist *identifiers, struct distribution *distributions, bool readonly) { + struct distribution *d; + struct target *t; + const char *identifier; + retvalue r; + int i; + + for (i = 0; i < identifiers->count ; i++) { + identifier = identifiers->values[i]; + + if (targetisdefined(identifier, distributions)) + continue; + + fprintf(stderr, +"Error: packages database contains unused '%s' database.\n", identifier); + if (ignored[IGN_undefinedtarget] == 0) { + (void)fputs( +"This usually means you removed some component, architecture or even\n" +"a whole distribution from conf/distributions.\n" +"In that case you most likely want to call reprepro clearvanished to get rid\n" +"of the databases belonging to those removed parts.\n" +"(Another reason to get this error is using conf/ and db/ directories\n" +" belonging to different reprepro repositories).\n", + stderr); + } + if (IGNORABLE(undefinedtarget)) { + (void)fputs( +"Ignoring as --ignore=undefinedtarget given.\n", + stderr); + ignored[IGN_undefinedtarget]++; + continue; + } + + (void)fputs( +"To ignore use --ignore=undefinedtarget.\n", + stderr); + return RET_ERROR; + } + if (readonly) + return RET_OK; + for (d = distributions ; d != NULL ; d = d->next) { + bool architecture_existed[d->architectures.count]; + bool have_old = false; + + /* check for new architectures */ + memset(architecture_existed, 0, sizeof(architecture_existed)); + + for (t = d->targets; t != NULL ; t = t->next) { + int o; + + if (!t->existed) + continue; + + o = atomlist_ofs(&d->architectures, + t->architecture); + assert (o >= 0); + if (o >= 0) { + architecture_existed[o] = true; + /* only warn about new ones if there + * is at least one old one, otherwise + * it's just a new distribution */ + have_old = true; + } + } + for (i = 0 ; have_old && i < d->architectures.count ; i++) { + architecture_t a; + + if (architecture_existed[i]) + continue; + + a = d->architectures.atoms[i]; + + fprintf(stderr, +"New architecture '%s' in '%s'. Perhaps you want to call\n" +"reprepro flood '%s' '%s'\n" +"to populate it with architecture 'all' packages from other architectures.\n", + atoms_architectures[a], d->codename, + d->codename, atoms_architectures[a]); + } + + /* create databases, so we know next time what is new */ + for (t = d->targets; t != NULL ; t = t->next) { + if (t->existed) + continue; + /* create database now, to test it can be created + * early, and to know when new architectures + * arrive in the future. */ + r = target_initpackagesdb(t, READWRITE); + if (RET_WAS_ERROR(r)) + return r; + r = target_closepackagesdb(t); + if (RET_WAS_ERROR(r)) + return r; + } + } + return RET_OK; +} + +static retvalue warnunusedtracking(const struct strlist *codenames, const struct distribution *distributions) { + const char *codename; + const struct distribution *d; + int i; + + for (i = 0; i < codenames->count ; i++) { + codename = codenames->values[i]; + + d = distributions; + while (d != NULL && strcmp(d->codename, codename) != 0) + d = d->next; + if (d != NULL && d->tracking != dt_NONE) + continue; + + fprintf(stderr, +"Error: tracking database contains unused '%s' database.\n", codename); + if (ignored[IGN_undefinedtracking] == 0) { + if (d == NULL) + (void)fputs( +"This either means you removed a distribution from the distributions config\n" +"file without calling clearvanished (or at least removealltracks), you\n" +"experienced a bug in retrack in versions < 3.0.0, you found a new bug or your\n" +"config does not belong to this database.\n", + stderr); + else + (void)fputs( +"This either means you removed the Tracking: options from this distribution without\n" +"calling removealltracks for it, or your config does not belong to this database.\n", + stderr); + } + if (IGNORABLE(undefinedtracking)) { + (void)fputs( +"Ignoring as --ignore=undefinedtracking given.\n", + stderr); + ignored[IGN_undefinedtracking]++; + continue; + } + + (void)fputs("To ignore use --ignore=undefinedtracking.\n", + stderr); + return RET_ERROR; + } + return RET_OK; +} + +static retvalue readline(/*@out@*/char **result, FILE *f, const char *versionfilename) { + char buffer[21]; + size_t l; + + if (fgets(buffer, 20, f) == NULL) { + int e = errno; + if (e == 0) { + fprintf(stderr, +"Error reading '%s': unexpected empty file\n", + versionfilename); + return RET_ERROR; + } else { + fprintf(stderr, "Error reading '%s': %s(errno is %d)\n", + versionfilename, strerror(e), e); + return RET_ERRNO(e); + } + } + l = strlen(buffer); + while (l > 0 && (buffer[l-1] == '\r' || buffer[l-1] == '\n')) { + buffer[--l] = '\0'; + } + if (l == 0) { + fprintf(stderr, "Error reading '%s': unexpected empty line.\n", + versionfilename); + return RET_ERROR; + } + *result = strdup(buffer); + if (FAILEDTOALLOC(*result)) + return RET_ERROR_OOM; + return RET_OK; +} + +static retvalue readversionfile(bool nopackagesyet) { + char *versionfilename; + FILE *f; + retvalue r; + int c; + + versionfilename = dbfilename("version"); + if (FAILEDTOALLOC(versionfilename)) + return RET_ERROR_OOM; + f = fopen(versionfilename, "r"); + if (f == NULL) { + int e = errno; + + if (e != ENOENT) { + fprintf(stderr, "Error opening '%s': %s(errno is %d)\n", + versionfilename, strerror(e), e); + free(versionfilename); + return RET_ERRNO(e); + } + free(versionfilename); + if (nopackagesyet) { + /* set to default for new packages.db files: */ + rdb_version = strdup(VERSION); + if (FAILEDTOALLOC(rdb_version)) + return RET_ERROR_OOM; + } else + rdb_version = NULL; + rdb_lastsupportedversion = NULL; + rdb_dbversion = NULL; + rdb_lastsupporteddbversion = NULL; + return RET_NOTHING; + } + /* first line is the version creating this database */ + r = readline(&rdb_version, f, versionfilename); + if (RET_WAS_ERROR(r)) { + (void)fclose(f); + free(versionfilename); + return r; + } + /* second line says which versions of reprepro will be able to cope + * with this database */ + r = readline(&rdb_lastsupportedversion, f, versionfilename); + if (RET_WAS_ERROR(r)) { + (void)fclose(f); + free(versionfilename); + return r; + } + /* next line is the version of the underlying database library */ + r = readline(&rdb_dbversion, f, versionfilename); + if (RET_WAS_ERROR(r)) { + (void)fclose(f); + free(versionfilename); + return r; + } + /* and then the minimum version of this library needed. */ + r = readline(&rdb_lastsupporteddbversion, f, versionfilename); + if (RET_WAS_ERROR(r)) { + (void)fclose(f); + free(versionfilename); + return r; + } + (void)fclose(f); + free(versionfilename); + + /* ensure we can understand it */ + + r = dpkgversions_cmp(VERSION, rdb_lastsupportedversion, &c); + if (RET_WAS_ERROR(r)) + return r; + if (c < 0) { + fprintf(stderr, +"According to %s/version this database was created with a future version\n" +"and uses features this version cannot understand. Aborting...\n", + global.dbdir); + return RET_ERROR; + } + + /* ensure it's a libdb database: */ + + if (strncmp(rdb_dbversion, "bdb", 3) != 0) { + fprintf(stderr, +"According to %s/version this database was created with a yet unsupported\n" +"database library. Aborting...\n", + global.dbdir); + return RET_ERROR; + } + if (strncmp(rdb_lastsupporteddbversion, "bdb", 3) != 0) { + fprintf(stderr, +"According to %s/version this database was created with a yet unsupported\n" +"database library. Aborting...\n", + global.dbdir); + return RET_ERROR; + } + r = dpkgversions_cmp(LIBDB_VERSION_STRING, + rdb_lastsupporteddbversion, &c); + if (RET_WAS_ERROR(r)) + return r; + if (c < 0) { + fprintf(stderr, +"According to %s/version this database was created with a future version\n" +"%s of libdb. The libdb version this binary is linked against cannot yet\n" +"handle this format. Aborting...\n", + global.dbdir, rdb_dbversion + 3); + return RET_ERROR; + } + return RET_OK; +} + +static retvalue writeversionfile(void) { + char *versionfilename, *finalversionfilename; + FILE *f; + int i, e; + + versionfilename = dbfilename("version.new"); + if (FAILEDTOALLOC(versionfilename)) + return RET_ERROR_OOM; + f = fopen(versionfilename, "w"); + if (f == NULL) { + e = errno; + fprintf(stderr, "Error creating '%s': %s(errno is %d)\n", + versionfilename, strerror(e), e); + free(versionfilename); + return RET_ERRNO(e); + } + if (rdb_version == NULL) + (void)fputs("0\n", f); + else { + (void)fputs(rdb_version, f); + (void)fputc('\n', f); + } + if (rdb_lastsupportedversion == NULL) { + (void)fputs("3.3.0\n", f); + } else { + int c; + retvalue r; + + r = dpkgversions_cmp(rdb_lastsupportedversion, + "3.3.0", &c); + if (!RET_IS_OK(r) || c < 0) + (void)fputs("3.3.0\n", f); + else { + (void)fputs(rdb_lastsupportedversion, f); + (void)fputc('\n', f); + } + } + if (rdb_dbversion == NULL) + fprintf(f, "bdb%d.%d.%d\n", DB_VERSION_MAJOR, DB_VERSION_MINOR, + DB_VERSION_PATCH); + else { + (void)fputs(rdb_dbversion, f); + (void)fputc('\n', f); + } + if (rdb_lastsupporteddbversion == NULL) + fprintf(f, "bdb%d.%d.0\n", DB_VERSION_MAJOR, DB_VERSION_MINOR); + else { + (void)fputs(rdb_lastsupporteddbversion, f); + (void)fputc('\n', f); + } + + e = ferror(f); + + if (e != 0) { + fprintf(stderr, "Error writing '%s': %s(errno is %d)\n", + versionfilename, strerror(e), e); + (void)fclose(f); + unlink(versionfilename); + free(versionfilename); + return RET_ERRNO(e); + } + if (fclose(f) != 0) { + e = errno; + fprintf(stderr, "Error writing '%s': %s(errno is %d)\n", + versionfilename, strerror(e), e); + unlink(versionfilename); + free(versionfilename); + return RET_ERRNO(e); + } + finalversionfilename = dbfilename("version"); + if (FAILEDTOALLOC(finalversionfilename)) { + unlink(versionfilename); + free(versionfilename); + return RET_ERROR_OOM; + } + + i = rename(versionfilename, finalversionfilename); + if (i != 0) { + e = errno; + fprintf(stderr, "Error %d moving '%s' to '%s': %s\n", + e, versionfilename, finalversionfilename, + strerror(e)); + (void)unlink(versionfilename); + free(versionfilename); + free(finalversionfilename); + return RET_ERRNO(e); + } + free(finalversionfilename); + free(versionfilename); + return RET_OK; +} + +static retvalue createnewdatabase(struct distribution *distributions) { + struct distribution *d; + struct target *t; + retvalue result = RET_NOTHING, r; + + for (d = distributions ; d != NULL ; d = d->next) { + for (t = d->targets ; t != NULL ; t = t->next) { + r = target_initpackagesdb(t, READWRITE); + RET_UPDATE(result, r); + if (RET_IS_OK(r)) { + r = target_closepackagesdb(t); + RET_UPDATE(result, r); + } + } + } + r = writeversionfile(); + RET_UPDATE(result, r); + return result; +} + +/* Initialize a database. + * - if not fast, make all kind of checks for consistency (TO BE IMPLEMENTED), + * - if readonly, do not create but return with RET_NOTHING + * - lock database, waiting a given amount of time if already locked + */ +retvalue database_create(struct distribution *alldistributions, bool fast, bool nopackages, bool allowunused, bool readonly, size_t waitforlock, bool verbosedb) { + retvalue r; + bool packagesfileexists, trackingfileexists, nopackagesyet; + + if (rdb_initialized || rdb_used) { + fputs("Internal Error: database initialized a 2nd time!\n", + stderr); + return RET_ERROR_INTERNAL; + } + + if (readonly && !isdir(global.dbdir)) { + if (verbose >= 0) + fprintf(stderr, +"Exiting without doing anything, as there is no database yet that could result in other actions.\n"); + return RET_NOTHING; + } + + rdb_initialized = true; + rdb_used = true; + + r = database_lock(waitforlock); + assert (r != RET_NOTHING); + if (!RET_IS_OK(r)) { + database_free(); + return r; + } + rdb_readonly = readonly; + rdb_verbose = verbosedb; + + r = database_hasdatabasefile("packages.db", &packagesfileexists); + if (RET_WAS_ERROR(r)) { + releaselock(); + database_free(); + return r; + } + r = database_hasdatabasefile("tracking.db", &trackingfileexists); + if (RET_WAS_ERROR(r)) { + releaselock(); + database_free(); + return r; + } + + nopackagesyet = !packagesfileexists && !trackingfileexists; + + r = readversionfile(nopackagesyet); + if (RET_WAS_ERROR(r)) { + releaselock(); + database_free(); + return r; + } + + if (nopackages) { + rdb_nopackages = true; + return RET_OK; + } + + if (nopackagesyet) { + // TODO: handle readonly, but only once packages files may no + // longer be generated when it is active... + + r = createnewdatabase(alldistributions); + if (RET_WAS_ERROR(r)) { + database_close(); + return r; + } + } + + /* after this point we should call database_close, + * as other stuff was handled, + * so writing the version file cannot harm (and not doing so could) */ + + if (!allowunused && !fast && packagesfileexists) { + struct strlist identifiers; + + r = database_listpackages(&identifiers); + if (RET_WAS_ERROR(r)) { + database_close(); + return r; + } + if (r == RET_NOTHING) + strlist_init(&identifiers); + r = warnidentifiers(&identifiers, + alldistributions, readonly); + if (RET_WAS_ERROR(r)) { + strlist_done(&identifiers); + database_close(); + return r; + } + strlist_done(&identifiers); + } + if (!allowunused && !fast && trackingfileexists) { + struct strlist codenames; + + r = tracking_listdistributions(&codenames); + if (RET_WAS_ERROR(r)) { + database_close(); + return r; + } + if (RET_IS_OK(r)) { + r = warnunusedtracking(&codenames, alldistributions); + if (RET_WAS_ERROR(r)) { + strlist_done(&codenames); + database_close(); + return r; + } + strlist_done(&codenames); + } + } + + return RET_OK; +} + +/**************************************************************************** + * Stuff string parts * + ****************************************************************************/ + +static const char databaseerror[] = "Internal error of the underlying BerkeleyDB database:\n"; + +/**************************************************************************** + * Stuff to handle data in tables * + **************************************************************************** + There is nothing that cannot be solved by another layer of indirection, except + too many levels of indirection. (Source forgotten) */ + +struct cursor { + DBC *cursor; + uint32_t flags; + retvalue r; +}; + +struct table { + char *name, *subname; + DB *berkeleydb; + DB *sec_berkeleydb; + bool readonly, verbose; + uint32_t flags; +}; + +static void table_printerror(struct table *table, int dbret, const char *action) { + char *error_msg; + + switch (dbret) { + case DB_MALFORMED_KEY: + error_msg = "DB_MALFORMED_KEY: Primary key does not contain the separator '|'."; + break; + case RET_ERROR_OOM: + error_msg = "RET_ERROR_OOM: Out of memory."; + break; + default: + error_msg = NULL; + break; + } + + if (error_msg == NULL) { + if (table->subname != NULL) + table->berkeleydb->err(table->berkeleydb, dbret, + "%sWithin %s subtable %s at %s", + databaseerror, table->name, table->subname, + action); + else + table->berkeleydb->err(table->berkeleydb, dbret, + "%sWithin %s at %s", + databaseerror, table->name, action); + } else { + if (table->subname != NULL) + table->berkeleydb->errx(table->berkeleydb, + "%sWithin %s subtable %s at %s: %s", + databaseerror, table->name, table->subname, + action, error_msg); + else + table->berkeleydb->errx(table->berkeleydb, + "%sWithin %s at %s: %s", + databaseerror, table->name, action, error_msg); + } +} + +static void print_opened_tables(FILE *stream) { + if (opened_tables == NULL) { + fprintf(stream, "No tables are opened.\n"); + } else { + fprintf(stream, "Opened tables:\n"); + for (struct opened_tables *iter = opened_tables; iter != NULL; iter = iter->next) { + fprintf(stream, " * %s - '%s'\n", iter->name, iter->subname); + } + } +} + +retvalue table_close(struct table *table) { + struct opened_tables *prev = NULL; + int dbret; + retvalue result = RET_OK; + + if (verbose >= 15) + fprintf(stderr, "trace: table_close(table.name=%s, table.subname=%s) called.\n", + table == NULL ? NULL : table->name, table == NULL ? NULL : table->subname); + if (table == NULL) + return RET_NOTHING; + if (table->sec_berkeleydb != NULL) { + dbret = table->sec_berkeleydb->close(table->sec_berkeleydb, 0); + if (dbret != 0) { + fprintf(stderr, "db_sec_close(%s, %s): %s\n", + table->name, table->subname, + db_strerror(dbret)); + result = RET_DBERR(dbret); + } + } + if (table->berkeleydb == NULL) { + assert (table->readonly); + dbret = 0; + } else + dbret = table->berkeleydb->close(table->berkeleydb, 0); + if (dbret != 0) { + fprintf(stderr, "db_close(%s, %s): %s\n", + table->name, table->subname, + db_strerror(dbret)); + result = RET_DBERR(dbret); + } + + for (struct opened_tables *iter = opened_tables; iter != NULL; iter = iter->next) { + if(strcmp2(iter->name, table->name) == 0 && strcmp2(iter->subname, table->subname) == 0) { + if (prev == NULL) { + opened_tables = iter->next; + } else { + prev->next = iter->next; + } + free(iter); + break; + } + prev = iter; + } + + if (verbose >= 25) + print_opened_tables(stderr); + + free(table->name); + free(table->subname); + free(table); + return result; +} + +retvalue table_getrecord(struct table *table, bool secondary, const char *key, char **data_p, size_t *datalen_p) { + int dbret; + DBT Key, Data; + DB *db; + + assert (table != NULL); + if (table->berkeleydb == NULL) { + assert (table->readonly); + return RET_NOTHING; + } + + SETDBT(Key, key); + CLEARDBT(Data); + Data.flags = DB_DBT_MALLOC; + + if (secondary) + db = table->sec_berkeleydb; + else + db = table->berkeleydb; + dbret = db->get(db, NULL, &Key, &Data, 0); + // TODO: find out what error code means out of memory... + if (dbret == DB_NOTFOUND) + return RET_NOTHING; + if (dbret != 0) { + table_printerror(table, dbret, "get"); + return RET_DBERR(dbret); + } + if (FAILEDTOALLOC(Data.data)) + return RET_ERROR_OOM; + if (Data.size <= 0 || + ((const char*)Data.data)[Data.size-1] != '\0') { + if (table->subname != NULL) + fprintf(stderr, +"Database %s(%s) returned corrupted (not null-terminated) data!\n", + table->name, table->subname); + else + fprintf(stderr, +"Database %s returned corrupted (not null-terminated) data!\n", + table->name); + free(Data.data); + return RET_ERROR; + } + *data_p = Data.data; + if (datalen_p != NULL) + *datalen_p = Data.size-1; + return RET_OK; +} + +retvalue table_getpair(struct table *table, const char *key, const char *value, /*@out@*/const char **data_p, /*@out@*/size_t *datalen_p) { + int dbret; + DBT Key, Data; + size_t valuelen = strlen(value); + + assert (table != NULL); + if (table->berkeleydb == NULL) { + assert (table->readonly); + return RET_NOTHING; + } + + SETDBT(Key, key); + SETDBTl(Data, value, valuelen + 1); + + dbret = table->berkeleydb->get(table->berkeleydb, NULL, + &Key, &Data, DB_GET_BOTH); + if (dbret == DB_NOTFOUND || dbret == DB_KEYEMPTY) + return RET_NOTHING; + if (dbret != 0) { + table_printerror(table, dbret, "get(BOTH)"); + return RET_DBERR(dbret); + } + if (FAILEDTOALLOC(Data.data)) + return RET_ERROR_OOM; + if (Data.size < valuelen + 2 || + ((const char*)Data.data)[Data.size-1] != '\0') { + if (table->subname != NULL) + fprintf(stderr, +"Database %s(%s) returned corrupted (not paired) data!", + table->name, table->subname); + else + fprintf(stderr, +"Database %s returned corrupted (not paired) data!", + table->name); + return RET_ERROR; + } + *data_p = ((const char*)Data.data) + valuelen + 1; + *datalen_p = Data.size - valuelen - 2; + return RET_OK; +} + +retvalue table_gettemprecord(struct table *table, const char *key, const char **data_p, size_t *datalen_p) { + int dbret; + DBT Key, Data; + + assert (table != NULL); + if (table->berkeleydb == NULL) { + assert (table->readonly); + return RET_NOTHING; + } + + SETDBT(Key, key); + CLEARDBT(Data); + + dbret = table->berkeleydb->get(table->berkeleydb, NULL, + &Key, &Data, 0); + // TODO: find out what error code means out of memory... + if (dbret == DB_NOTFOUND) + return RET_NOTHING; + if (dbret != 0) { + table_printerror(table, dbret, "get"); + return RET_DBERR(dbret); + } + if (FAILEDTOALLOC(Data.data)) + return RET_ERROR_OOM; + if (data_p == NULL) { + assert (datalen_p == NULL); + return RET_OK; + } + if (Data.size <= 0 || + ((const char*)Data.data)[Data.size-1] != '\0') { + if (table->subname != NULL) + fprintf(stderr, +"Database %s(%s) returned corrupted (not null-terminated) data!\n", + table->name, table->subname); + else + fprintf(stderr, +"Database %s returned corrupted (not null-terminated) data!\n", + table->name); + return RET_ERROR; + } + *data_p = Data.data; + if (datalen_p != NULL) + *datalen_p = Data.size - 1; + return RET_OK; +} + +retvalue table_checkrecord(struct table *table, const char *key, const char *data) { + int dbret; + DBT Key, Data; + DBC *cursor; + retvalue r; + + SETDBT(Key, key); + SETDBT(Data, data); + dbret = table->berkeleydb->cursor(table->berkeleydb, NULL, &cursor, 0); + if (dbret != 0) { + table_printerror(table, dbret, "cursor"); + return RET_DBERR(dbret); + } + dbret=cursor->c_get(cursor, &Key, &Data, DB_GET_BOTH); + if (dbret == 0) { + r = RET_OK; + } else if (dbret == DB_NOTFOUND) { + r = RET_NOTHING; + } else { + table_printerror(table, dbret, "c_get"); + (void)cursor->c_close(cursor); + return RET_DBERR(dbret); + } + dbret = cursor->c_close(cursor); + if (dbret != 0) { + table_printerror(table, dbret, "c_close"); + return RET_DBERR(dbret); + } + return r; +} + +retvalue table_removerecord(struct table *table, const char *key, const char *data) { + int dbret; + DBT Key, Data; + DBC *cursor; + retvalue r; + + SETDBT(Key, key); + SETDBT(Data, data); + dbret = table->berkeleydb->cursor(table->berkeleydb, NULL, &cursor, 0); + if (dbret != 0) { + table_printerror(table, dbret, "cursor"); + return RET_DBERR(dbret); + } + dbret=cursor->c_get(cursor, &Key, &Data, DB_GET_BOTH); + + if (dbret == 0) + dbret = cursor->c_del(cursor, 0); + + if (dbret == 0) { + r = RET_OK; + } else if (dbret == DB_NOTFOUND) { + r = RET_NOTHING; + } else { + table_printerror(table, dbret, "c_get"); + (void)cursor->c_close(cursor); + return RET_DBERR(dbret); + } + dbret = cursor->c_close(cursor); + if (dbret != 0) { + table_printerror(table, dbret, "c_close"); + return RET_DBERR(dbret); + } + return r; +} + +bool table_recordexists(struct table *table, const char *key) { + retvalue r; + + r = table_gettemprecord(table, key, NULL, NULL); + return RET_IS_OK(r); +} + +retvalue table_addrecord(struct table *table, const char *key, const char *data, size_t datalen, bool ignoredups) { + int dbret; + DBT Key, Data; + + assert (table != NULL); + assert (!table->readonly && table->berkeleydb != NULL); + + SETDBT(Key, key); + SETDBTl(Data, data, datalen + 1); + dbret = table->berkeleydb->put(table->berkeleydb, NULL, + &Key, &Data, ISSET(table->flags, DB_DUPSORT) ? DB_NODUPDATA : 0); + if (dbret != 0 && !(ignoredups && dbret == DB_KEYEXIST)) { + table_printerror(table, dbret, "put"); + return RET_DBERR(dbret); + } + if (table->verbose) { + if (table->subname != NULL) + printf("db: '%s' added to %s(%s).\n", + key, table->name, table->subname); + else + printf("db: '%s' added to %s.\n", + key, table->name); + } + return RET_OK; +} + +retvalue table_adduniqsizedrecord(struct table *table, const char *key, const char *data, size_t data_size, bool allowoverwrite, bool nooverwrite) { + int dbret; + DBT Key, Data; + + assert (table != NULL); + assert (!table->readonly && table->berkeleydb != NULL); + assert (data_size > 0 && data[data_size-1] == '\0'); + + SETDBT(Key, key); + SETDBTl(Data, data, data_size); + dbret = table->berkeleydb->put(table->berkeleydb, NULL, + &Key, &Data, allowoverwrite?0:DB_NOOVERWRITE); + if (nooverwrite && dbret == DB_KEYEXIST) { + /* if nooverwrite is set, do nothing and ignore: */ + return RET_NOTHING; + } + if (dbret != 0) { + table_printerror(table, dbret, "put(uniq)"); + return RET_DBERR(dbret); + } + if (table->verbose) { + if (table->subname != NULL) + printf("db: '%s' added to %s(%s).\n", + key, table->name, table->subname); + else + printf("db: '%s' added to %s.\n", + key, table->name); + } + return RET_OK; +} +retvalue table_adduniqrecord(struct table *table, const char *key, const char *data) { + if (verbose >= 15) + fprintf(stderr, "trace: table_adduniqrecord(table={name: %s, subname: %s}, key=%s) called.\n", + table->name, table->subname, key); + return table_adduniqsizedrecord(table, key, data, strlen(data)+1, + false, false); +} + +retvalue table_deleterecord(struct table *table, const char *key, bool ignoremissing) { + int dbret; + DBT Key; + + assert (table != NULL); + assert (!table->readonly && table->berkeleydb != NULL); + + SETDBT(Key, key); + dbret = table->berkeleydb->del(table->berkeleydb, NULL, &Key, 0); + if (dbret != 0) { + if (dbret == DB_NOTFOUND && ignoremissing) + return RET_NOTHING; + table_printerror(table, dbret, "del"); + if (dbret == DB_NOTFOUND) + return RET_ERROR_MISSING; + else + return RET_DBERR(dbret); + } + if (table->verbose) { + if (table->subname != NULL) + printf("db: '%s' removed from %s(%s).\n", + key, table->name, table->subname); + else + printf("db: '%s' removed from %s.\n", + key, table->name); + } + return RET_OK; +} + +retvalue table_replacerecord(struct table *table, const char *key, const char *data) { + retvalue r; + + if (verbose >= 15) + fprintf(stderr, "trace: table_replacerecord(table={name: %s, subname: %s}, key=%s) called.\n", + table->name, table->subname, key); + r = table_deleterecord(table, key, false); + if (r != RET_ERROR_MISSING && RET_WAS_ERROR(r)) + return r; + return table_adduniqrecord(table, key, data); +} + +static retvalue newcursor(struct table *table, uint32_t flags, struct cursor **cursor_p) { + DB *berkeleydb; + struct cursor *cursor; + int dbret; + + if (verbose >= 15) + fprintf(stderr, "trace: newcursor(table={name: %s, subname: %s}) called.\n", + table->name, table->subname); + + if (table->sec_berkeleydb == NULL) { + berkeleydb = table->berkeleydb; + } else { + berkeleydb = table->sec_berkeleydb; + } + + if (berkeleydb == NULL) { + assert (table->readonly); + *cursor_p = NULL; + return RET_NOTHING; + } + + cursor = zNEW(struct cursor); + if (FAILEDTOALLOC(cursor)) + return RET_ERROR_OOM; + + cursor->cursor = NULL; + cursor->flags = flags; + cursor->r = RET_OK; + dbret = berkeleydb->cursor(berkeleydb, NULL, + &cursor->cursor, 0); + if (dbret != 0) { + table_printerror(table, dbret, "cursor"); + free(cursor); + return RET_DBERR(dbret); + } + *cursor_p = cursor; + return RET_OK; +} + +retvalue table_newglobalcursor(struct table *table, bool duplicate, struct cursor **cursor_p) { + retvalue r; + + r = newcursor(table, duplicate ? DB_NEXT : DB_NEXT_NODUP, cursor_p); + if (r == RET_NOTHING) { + // table_newglobalcursor returned RET_OK when table->berkeleydb == NULL. Is that return value wanted? + r = RET_OK; + } + return r; +} + +static inline retvalue parse_data(struct table *table, DBT Key, DBT Data, /*@null@*//*@out@*/const char **key_p, /*@out@*/const char **data_p, /*@out@*/size_t *datalen_p) { + if (Key.size <= 0 || Data.size <= 0 || + ((const char*)Key.data)[Key.size-1] != '\0' || + ((const char*)Data.data)[Data.size-1] != '\0') { + if (table->subname != NULL) + fprintf(stderr, +"Database %s(%s) returned corrupted (not null-terminated) data!", + table->name, table->subname); + else + fprintf(stderr, +"Database %s returned corrupted (not null-terminated) data!", + table->name); + return RET_ERROR; + } + if (key_p != NULL) + *key_p = Key.data; + *data_p = Data.data; + if (datalen_p != NULL) + *datalen_p = Data.size - 1; + return RET_OK; +} + +static inline retvalue parse_pair(struct table *table, DBT Key, DBT Data, /*@null@*//*@out@*/const char **key_p, /*@out@*/const char **value_p, /*@out@*/const char **data_p, /*@out@*/size_t *datalen_p) { + /*@dependant@*/ const char *separator; + + if (Key.size == 0 || Data.size == 0 || + ((const char*)Key.data)[Key.size-1] != '\0' || + ((const char*)Data.data)[Data.size-1] != '\0') { + if (table->subname != NULL) + fprintf(stderr, +"Database %s(%s) returned corrupted (not null-terminated) data!", + table->name, table->subname); + else + fprintf(stderr, +"Database %s returned corrupted (not null-terminated) data!", + table->name); + return RET_ERROR; + } + separator = memchr(Data.data, '\0', Data.size-1); + if (separator == NULL) { + if (table->subname != NULL) + fprintf(stderr, +"Database %s(%s) returned corrupted data!\n", + table->name, table->subname); + else + fprintf(stderr, +"Database %s returned corrupted data!\n", + table->name); + return RET_ERROR; + } + if (key_p != NULL) + *key_p = Key.data; + *value_p = Data.data; + *data_p = separator + 1; + *datalen_p = Data.size - (separator - (const char*)Data.data) - 2; + return RET_OK; +} + +retvalue table_newduplicatecursor(struct table *table, const char *key, long long skip, struct cursor **cursor_p, const char **key_p, const char **data_p, size_t *datalen_p) { + struct cursor *cursor; + int dbret; + DBT Key, Data; + retvalue r; + + r = newcursor(table, DB_NEXT_DUP, &cursor); + if(!RET_IS_OK(r)) { + return r; + } + SETDBT(Key, key); + CLEARDBT(Data); + dbret = cursor->cursor->c_get(cursor->cursor, &Key, &Data, DB_SET); + if (dbret == DB_NOTFOUND || dbret == DB_KEYEMPTY) { + (void)cursor->cursor->c_close(cursor->cursor); + free(cursor); + return RET_NOTHING; + } + if (dbret != 0) { + table_printerror(table, dbret, "c_get(DB_SET)"); + (void)cursor->cursor->c_close(cursor->cursor); + free(cursor); + return RET_DBERR(dbret); + } + + while (skip > 0) { + CLEARDBT(Key); + CLEARDBT(Data); + + dbret = cursor->cursor->c_get(cursor->cursor, &Key, &Data, cursor->flags); + if (dbret == DB_NOTFOUND) { + (void)cursor->cursor->c_close(cursor->cursor); + free(cursor); + return RET_NOTHING; + } + if (dbret != 0) { + table_printerror(table, dbret, "c_get(DB_NEXT_DUP)"); + (void)cursor->cursor->c_close(cursor->cursor); + free(cursor); + return RET_DBERR(dbret); + } + + skip--; + } + + r = parse_data(table, Key, Data, key_p, data_p, datalen_p); + if (RET_WAS_ERROR(r)) { + (void)cursor->cursor->c_close(cursor->cursor); + free(cursor); + return r; + } + *cursor_p = cursor; + return RET_OK; +} + +retvalue table_newduplicatepairedcursor(struct table *table, const char *key, struct cursor **cursor_p, const char **value_p, const char **data_p, size_t *datalen_p) { + struct cursor *cursor; + int dbret; + DBT Key, Data; + retvalue r; + + r = newcursor(table, DB_NEXT_DUP, cursor_p); + if(!RET_IS_OK(r)) { + return r; + } + cursor = *cursor_p; + SETDBT(Key, key); + CLEARDBT(Data); + dbret = cursor->cursor->c_get(cursor->cursor, &Key, &Data, DB_SET); + if (dbret == DB_NOTFOUND || dbret == DB_KEYEMPTY) { + (void)cursor->cursor->c_close(cursor->cursor); + free(cursor); + return RET_NOTHING; + } + if (dbret != 0) { + table_printerror(table, dbret, "c_get(DB_SET)"); + (void)cursor->cursor->c_close(cursor->cursor); + free(cursor); + return RET_DBERR(dbret); + } + r = parse_pair(table, Key, Data, NULL, value_p, data_p, datalen_p); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + (void)cursor->cursor->c_close(cursor->cursor); + free(cursor); + return r; + } + + *cursor_p = cursor; + return RET_OK; +} + +retvalue table_newpairedcursor(struct table *table, const char *key, const char *value, struct cursor **cursor_p, const char **data_p, size_t *datalen_p) { + struct cursor *cursor; + int dbret; + DBT Key, Data; + retvalue r; + size_t valuelen = strlen(value); + + /* cursor_next is not allowed with this type: */ + r = newcursor(table, DB_GET_BOTH, cursor_p); + if(!RET_IS_OK(r)) { + return r; + } + cursor = *cursor_p; + SETDBT(Key, key); + SETDBTl(Data, value, valuelen + 1); + dbret = cursor->cursor->c_get(cursor->cursor, &Key, &Data, DB_GET_BOTH); + if (dbret != 0) { + if (dbret == DB_NOTFOUND || dbret == DB_KEYEMPTY) { + table_printerror(table, dbret, "c_get(DB_GET_BOTH)"); + r = RET_DBERR(dbret); + } else + r = RET_NOTHING; + (void)cursor->cursor->c_close(cursor->cursor); + free(cursor); + return r; + } + if (Data.size < valuelen + 2 || + ((const char*)Data.data)[Data.size-1] != '\0') { + if (table->subname != NULL) + fprintf(stderr, +"Database %s(%s) returned corrupted (not paired) data!", + table->name, table->subname); + else + fprintf(stderr, +"Database %s returned corrupted (not paired) data!", + table->name); + (void)cursor->cursor->c_close(cursor->cursor); + free(cursor); + return RET_ERROR; + } + if (data_p != NULL) + *data_p = ((const char*)Data.data) + valuelen + 1; + if (datalen_p != NULL) + *datalen_p = Data.size - valuelen - 2; + *cursor_p = cursor; + return RET_OK; +} + +retvalue cursor_close(struct table *table, struct cursor *cursor) { + int dbret; + retvalue r; + + if (cursor == NULL) + return RET_OK; + + r = cursor->r; + dbret = cursor->cursor->c_close(cursor->cursor); + cursor->cursor = NULL; + free(cursor); + if (dbret != 0) { + table_printerror(table, dbret, "c_close"); + RET_UPDATE(r, RET_DBERR(dbret)); + } + return r; +} + +static bool cursor_next(struct table *table, struct cursor *cursor, DBT *Key, DBT *Data) { + int dbret; + + if (cursor == NULL) + return false; + + CLEARDBT(*Key); + CLEARDBT(*Data); + + dbret = cursor->cursor->c_get(cursor->cursor, Key, Data, + cursor->flags); + if (dbret == DB_NOTFOUND) + return false; + + if (dbret != 0) { + table_printerror(table, dbret, + (cursor->flags==DB_NEXT) + ? "c_get(DB_NEXT)" + : (cursor->flags==DB_NEXT_DUP) + ? "c_get(DB_NEXT_DUP)" + : "c_get(DB_???NEXT)"); + cursor->r = RET_DBERR(dbret); + return false; + } + return true; +} + +bool cursor_nexttempdata(struct table *table, struct cursor *cursor, const char **key, const char **data, size_t *len_p) { + DBT Key, Data; + bool success; + retvalue r; + + success = cursor_next(table, cursor, &Key, &Data); + if (!success) + return false; + r = parse_data(table, Key, Data, key, data, len_p); + if (RET_WAS_ERROR(r)) { + cursor->r = r; + return false; + } + return true; +} + +bool cursor_nextpair(struct table *table, struct cursor *cursor, /*@null@*/const char **key_p, const char **value_p, const char **data_p, size_t *datalen_p) { + DBT Key, Data; + bool success; + retvalue r; + + success = cursor_next(table, cursor, &Key, &Data); + if (!success) + return false; + r = parse_pair(table, Key, Data, key_p, value_p, data_p, datalen_p); + if (RET_WAS_ERROR(r)) { + cursor->r = r; + return false; + } + return true; +} + +retvalue cursor_replace(struct table *table, struct cursor *cursor, const char *data, size_t datalen) { + DBT Key, Data; + int dbret; + + assert (cursor != NULL); + assert (!table->readonly); + + CLEARDBT(Key); + SETDBTl(Data, data, datalen + 1); + + dbret = cursor->cursor->c_put(cursor->cursor, &Key, &Data, DB_CURRENT); + + if (dbret != 0) { + table_printerror(table, dbret, "c_put(DB_CURRENT)"); + return RET_DBERR(dbret); + } + return RET_OK; +} + +retvalue cursor_delete(struct table *table, struct cursor *cursor, const char *key, const char *value) { + int dbret; + + assert (cursor != NULL); + assert (!table->readonly); + + dbret = cursor->cursor->c_del(cursor->cursor, 0); + + if (dbret != 0) { + table_printerror(table, dbret, "c_del"); + return RET_DBERR(dbret); + } + if (table->verbose) { + if (value != NULL) + if (table->subname != NULL) + printf("db: '%s' '%s' removed from %s(%s).\n", + key, value, + table->name, table->subname); + else + printf("db: '%s' '%s' removed from %s.\n", + key, value, table->name); + else + if (table->subname != NULL) + printf("db: '%s' removed from %s(%s).\n", + key, table->name, table->subname); + else + printf("db: '%s' removed from %s.\n", + key, table->name); + } + return RET_OK; +} + +static bool table_isempty(struct table *table) { + DBC *cursor; + DBT Key, Data; + int dbret; + + dbret = table->berkeleydb->cursor(table->berkeleydb, NULL, + &cursor, 0); + if (dbret != 0) { + table_printerror(table, dbret, "cursor"); + return true; + } + CLEARDBT(Key); + CLEARDBT(Data); + + dbret = cursor->c_get(cursor, &Key, &Data, DB_NEXT); + if (dbret == DB_NOTFOUND) { + (void)cursor->c_close(cursor); + return true; + } + if (dbret != 0) { + table_printerror(table, dbret, "c_get(DB_NEXT)"); + (void)cursor->c_close(cursor); + return true; + } + dbret = cursor->c_close(cursor); + if (dbret != 0) + table_printerror(table, dbret, "c_close"); + return false; +} + +retvalue database_haspackages(const char *identifier) { + struct table *packages; + retvalue r; + bool empty; + + r = database_openpackages(identifier, true, &packages); + if (RET_WAS_ERROR(r)) + return r; + empty = table_isempty(packages); + (void)table_close(packages); + return empty?RET_NOTHING:RET_OK; +} + +/**************************************************************************** + * Open the different types of tables with their needed flags: * + ****************************************************************************/ +static retvalue database_table_secondary(const char *filename, const char *subtable, enum database_type type, uint32_t flags, + const char *secondary_filename, enum database_type secondary_type, /*@out@*/struct table **table_p) { + struct table *table; + struct opened_tables *opened_table; + retvalue r; + + if (verbose >= 15) + fprintf(stderr, "trace: database_table_secondary(filename=%s, subtable=%s, type=%i, flags=%u, secondary_filename=%s, secondary_type=%i) called.\n", + filename, subtable, type, flags, secondary_filename, secondary_type); + + for (struct opened_tables *iter = opened_tables; iter != NULL; iter = iter->next) { + if(strcmp2(iter->name, filename) == 0 && strcmp2(iter->subname, subtable) == 0) { + fprintf(stderr, + "Internal Error: Trying to open table '%s' from file '%s' multiple times.\n" + "This should normally not happen (to avoid triggering bugs in the underlying BerkeleyDB)\n", + subtable, filename); + return RET_ERROR; + } + } + + table = zNEW(struct table); + if (FAILEDTOALLOC(table)) + return RET_ERROR_OOM; + /* TODO: is filename always an static constant? then we could drop the dup */ + table->name = strdup(filename); + if (FAILEDTOALLOC(table->name)) { + free(table); + return RET_ERROR_OOM; + } + if (subtable != NULL) { + table->subname = strdup(subtable); + if (FAILEDTOALLOC(table->subname)) { + free(table->name); + free(table); + return RET_ERROR_OOM; + } + } else + table->subname = NULL; + table->readonly = ISSET(flags, DB_RDONLY); + table->verbose = rdb_verbose; + table->flags = flags; + r = database_opentable(filename, subtable, type, flags, + &table->berkeleydb); + if (RET_WAS_ERROR(r)) { + free(table->subname); + free(table->name); + free(table); + return r; + } + if (r == RET_NOTHING) { + if (ISSET(flags, DB_RDONLY)) { + /* sometimes we don't want a return here, when? */ + table->berkeleydb = NULL; + r = RET_OK; + } else { + free(table->subname); + free(table->name); + free(table); + return r; + } + + } + + if (secondary_filename != NULL) { + r = database_opentable(secondary_filename, subtable, secondary_type, flags, + &table->sec_berkeleydb); + if (RET_WAS_ERROR(r)) { + table->berkeleydb->close(table->berkeleydb, 0); + free(table->subname); + free(table->name); + free(table); + return r; + } + if (r == RET_NOTHING) { + if (ISSET(flags, DB_RDONLY)) { + /* sometimes we don't want a return here, when? */ + table->sec_berkeleydb = NULL; + r = RET_OK; + } else { + table->berkeleydb->close(table->berkeleydb, 0); + free(table->subname); + free(table->name); + free(table); + return r; + } + + } + } + + opened_table = zNEW(struct opened_tables); + if (FAILEDTOALLOC(opened_table)) { + free(table->subname); + free(table->name); + free(table); + return RET_ERROR_OOM; + } + opened_table->name = table->name; + opened_table->subname = table->subname; + opened_table->next = opened_tables; + opened_tables = opened_table; + + if (verbose >= 25) + print_opened_tables(stderr); + + *table_p = table; + return r; +} + +static retvalue database_table(const char *filename, const char *subtable, enum database_type type, uint32_t flags, /*@out@*/struct table **table_p) { + return database_table_secondary(filename, subtable, type, flags, NULL, 0, table_p); +} + +retvalue database_openreferences(void) { + retvalue r; + + assert (rdb_references == NULL); + r = database_table("references.db", "references", + dbt_BTREEDUP, DB_CREATE, &rdb_references); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + rdb_references = NULL; + return r; + } else + rdb_references->verbose = false; + return RET_OK; +} + +static int debianversioncompare(UNUSED(DB *db), const DBT *a, const DBT *b) { + const char *a_version; + const char *b_version; + int versioncmp; + // There is no way to indicate an error to the caller + // Thus return -1 in case of an error + retvalue r = -1; + + if (a->size == 0 || ((char*)a->data)[a->size-1] != '\0') { + fprintf(stderr, "Database value '%.*s' empty or not NULL terminated.\n", a->size, (char*)a->data); + return r; + } + if (b->size == 0 || ((char*)b->data)[b->size-1] != '\0') { + fprintf(stderr, "Database value '%.*s' empty or not NULL terminated.\n", b->size, (char*)b->data); + return r; + } + + a_version = strchr(a->data, '|'); + if (a_version == NULL) { + fprintf(stderr, "Database value '%s' malformed. It should be 'package|version'.\n", (char*)a->data); + return r; + } + a_version++; + b_version = strchr(b->data, '|'); + if (b_version == NULL) { + fprintf(stderr, "Database value '%s' malformed. It should be 'package|version'.\n", (char*)b->data); + return r; + } + b_version++; + + r = dpkgversions_cmp(a_version, b_version, &versioncmp); + if (RET_WAS_ERROR(r)) { + fprintf(stderr, "Parse errors processing versions.\n"); + return r; + } + + return -versioncmp; +} + +/* only compare the first 0-terminated part of the data */ +static int paireddatacompare(UNUSED(DB *db), const DBT *a, const DBT *b +#if DB_VERSION_MAJOR >= 6 +#warning Berkeley DB >= 6.0 is not yet tested and highly experimental + , UNUSED(size_t *locp) + /* "The locp parameter is currently unused, and must be set to NULL or corruption can occur." + * What the ...! How am I supposed to handle that? */ +#endif +) { + if (a->size < b->size) + return strncmp(a->data, b->data, a->size); + else + return strncmp(a->data, b->data, b->size); +} + +retvalue database_opentracking(const char *codename, bool readonly, struct table **table_p) { + struct table *table; + retvalue r; + + if (rdb_nopackages) { + (void)fputs( +"Internal Error: Accessing packages database while that was not prepared!\n", + stderr); + return RET_ERROR; + } + + r = database_table("tracking.db", codename, + dbt_BTREEPAIRS, readonly?DB_RDONLY:DB_CREATE, &table); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + *table_p = table; + return RET_OK; +} + +static int get_package_name(DB *secondary, const DBT *pkey, const DBT *pdata, DBT *skey) { + const char *separator; + size_t length; + + separator = memchr(pkey->data, '|', pkey->size); + if (unlikely(separator == NULL)) { + return DB_MALFORMED_KEY; + } + + length = (size_t)separator - (size_t)pkey->data; + skey->flags = DB_DBT_APPMALLOC; + skey->data = strndup(pkey->data, length); + if (FAILEDTOALLOC(skey->data)) { + return RET_ERROR_OOM; + } + skey->size = length + 1; + return 0; +} + +static retvalue database_translate_legacy_packages(void) { + struct cursor *databases_cursor, *cursor; + struct table *legacy_databases, *legacy_table, *packages; + const char *chunk, *packagename; + char *identifier, *key, *legacy_filename, *packages_filename, *packageversion; + retvalue r, result; + int ret, e; + size_t chunk_len; + DBT Key, Data; + + if (verbose >= 15) + fprintf(stderr, "trace: database_translate_legacy_packages() called.\n"); + + if (!isdir(global.dbdir)) { + fprintf(stderr, "Cannot find directory '%s'!\n", global.dbdir); + return RET_ERROR; + } + + packages_filename = dbfilename("packages.db"); + legacy_filename = dbfilename("packages.legacy.db"); + ret = rename(packages_filename, legacy_filename); + if (ret != 0) { + e = errno; + fprintf(stderr, "error %d renaming %s to %s: %s\n", + e, packages_filename, legacy_filename, strerror(e)); + return (e != 0)?e:EINVAL; + } + if (verbose >= 15) + fprintf(stderr, "trace: Moved '%s' to '%s'.\n", packages_filename, legacy_filename); + + r = database_table("packages.legacy.db", NULL, dbt_BTREE, DB_RDONLY, &legacy_databases); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + + r = table_newglobalcursor(legacy_databases, true, &databases_cursor); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + (void)table_close(legacy_databases); + return r; + } + result = RET_NOTHING; + // Iterate over all databases inside the packages.db file. + while (cursor_next(legacy_databases, databases_cursor, &Key, &Data)) { + identifier = strndup(Key.data, Key.size); + if (FAILEDTOALLOC(identifier)) { + RET_UPDATE(result, RET_ERROR_OOM); + break; + } + if (verbose >= 15) + fprintf(stderr, "Converting table '%s' to new layout...\n", identifier); + + r = database_table("packages.legacy.db", identifier, dbt_BTREE, DB_RDONLY, &legacy_table); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + free(identifier); + RET_UPDATE(result, r); + break; + } + + r = table_newglobalcursor(legacy_table, true, &cursor); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + (void)table_close(legacy_table); + free(identifier); + RET_UPDATE(result, r); + break; + } + + r = database_openpackages(identifier, false, &packages); + free(identifier); + identifier = NULL; + if (RET_WAS_ERROR(r)) { + (void)cursor_close(legacy_databases, databases_cursor); + (void)table_close(legacy_table); + RET_UPDATE(result, r); + break; + } + + while (cursor_nexttempdata(legacy_table, cursor, &packagename, &chunk, &chunk_len)) { + r = chunk_getvalue(chunk, "Version", &packageversion); + if (!RET_IS_OK(r)) { + RET_UPDATE(result, r); + break; + } + key = package_primarykey(packagename, packageversion); + r = table_addrecord(packages, key, chunk, chunk_len, false); + free(key); + if (RET_WAS_ERROR(r)) { + RET_UPDATE(result, r); + break; + } + } + + r = table_close(packages); + RET_UPDATE(result, r); + r = cursor_close(legacy_table, cursor); + RET_UPDATE(result, r); + r = table_close(legacy_table); + RET_UPDATE(result, r); + + if (RET_WAS_ERROR(result)) { + break; + } + result = RET_OK; + } + r = cursor_close(legacy_databases, databases_cursor); + RET_ENDUPDATE(result, r); + r = table_close(legacy_databases); + RET_ENDUPDATE(result, r); + + if (RET_IS_OK(result)) { + e = deletefile(legacy_filename); + if (e != 0) { + fprintf(stderr, "Could not delete '%s'!\n" +"It can now safely be deleted and it all that is left to be done!\n", + legacy_filename); + return RET_ERRNO(e); + } + } + + return result; +} + +retvalue database_openpackages(const char *identifier, bool readonly, struct table **table_p) { + struct table *table; + retvalue r; + + if (rdb_nopackages) { + (void)fputs( +"Internal Error: Accessing packages database while that was not prepared!\n", + stderr); + return RET_ERROR; + } + + r = database_table_secondary("packages.db", identifier, + dbt_BTREE, readonly?DB_RDONLY:DB_CREATE, + "packagenames.db", dbt_BTREEVERSIONS, &table); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) + return r; + + if (table->berkeleydb != NULL && table->sec_berkeleydb == NULL) { + r = table_close(table); + if (RET_WAS_ERROR(r)) { + return r; + } + r = database_translate_legacy_packages(); + if (RET_WAS_ERROR(r)) { + return r; + } + return database_openpackages(identifier, readonly, table_p); + } + + if (table->berkeleydb != NULL && table->sec_berkeleydb != NULL) { + r = table->berkeleydb->associate(table->berkeleydb, NULL, + table->sec_berkeleydb, get_package_name, 0); + if (RET_WAS_ERROR(r)) { + return r; + } + } + + *table_p = table; + return RET_OK; +} + +/* Get a list of all identifiers having a package list */ +retvalue database_listpackages(struct strlist *identifiers) { + return database_listsubtables("packages.db", identifiers); +} + +/* drop a database */ +retvalue database_droppackages(const char *identifier) { + retvalue r; + + r = database_dropsubtable("packages.db", identifier); + if (RET_IS_OK(r)) + r = database_dropsubtable("packagenames.db", identifier); + return r; +} + +retvalue database_openfiles(void) { + retvalue r; + struct strlist identifiers; + bool oldfiles; + + assert (rdb_checksums == NULL); + assert (rdb_contents == NULL); + + r = database_listsubtables("contents.cache.db", &identifiers); + if (RET_IS_OK(r)) { + if (strlist_in(&identifiers, "filelists")) { + fprintf(stderr, +"Your %s/contents.cache.db file still contains a table of cached file lists\n" +"in the old (pre 3.0.0) format. You have to either delete that file (and lose\n" +"all caches of file lists) or run reprepro with argument translatefilelists\n" +"to translate the old caches into the new format.\n", + global.dbdir); + strlist_done(&identifiers); + return RET_ERROR; + } + strlist_done(&identifiers); + } + + r = database_table("checksums.db", "pool", + dbt_BTREE, DB_CREATE, + &rdb_checksums); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + rdb_checksums = NULL; + return r; + } + r = database_hasdatabasefile("files.db", &oldfiles); + if (RET_WAS_ERROR(r)) { + (void)table_close(rdb_checksums); + rdb_checksums = NULL; + return r; + } + if (oldfiles) { + fprintf(stderr, +"Error: database uses deprecated format.\n" +"Please run translatelegacychecksums to update to the new format first.\n"); + return RET_ERROR; + } + + // TODO: only create this file once it is actually needed... + r = database_table("contents.cache.db", "compressedfilelists", + dbt_BTREE, DB_CREATE, &rdb_contents); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + (void)table_close(rdb_checksums); + rdb_checksums = NULL; + rdb_contents = NULL; + } + return r; +} + +retvalue database_openreleasecache(const char *codename, struct table **cachedb_p) { + retvalue r; + char *oldcachefilename; + + /* Since 3.1.0 it's release.caches.db, before release.cache.db. + * The new file also contains the sha1 checksums and is extensible + * for more in the future. Thus if there is only the old variant, + * rename to the new. (So no old version by accident uses it and + * puts the additional sha1 data into the md5sum fields.) + * If both files are there, just delete both, as neither will + * be very current then. + * */ + + oldcachefilename = dbfilename("release.cache.db"); + if (FAILEDTOALLOC(oldcachefilename)) + return RET_ERROR_OOM; + if (isregularfile(oldcachefilename)) { + char *newcachefilename; + + newcachefilename = dbfilename("release.caches.db"); + if (FAILEDTOALLOC(newcachefilename)) { + free(oldcachefilename); + return RET_ERROR_OOM; + } + if (isregularfile(newcachefilename) + || rename(oldcachefilename, newcachefilename) != 0) { + fprintf(stderr, +"Deleting old-style export cache file %s!\n" +"This means that all index files (even unchanged) will be rewritten the\n" +"next time parts of their distribution are changed. This should only\n" +"happen once while migration from pre-3.1.0 to later versions.\n", + oldcachefilename); + + if (unlink(oldcachefilename) != 0) { + int e = errno; + fprintf(stderr, "Cannot delete '%s': %s!", + oldcachefilename, + strerror(e)); + free(oldcachefilename); + free(newcachefilename); + return RET_ERRNO(e); + } + (void)unlink(oldcachefilename); + } + free(newcachefilename); + } + free(oldcachefilename); + + r = database_table("release.caches.db", codename, + dbt_HASH, DB_CREATE, cachedb_p); + if (RET_IS_OK(r)) + (*cachedb_p)->verbose = false; + return r; +} + +static retvalue table_copy(struct table *oldtable, struct table *newtable) { + retvalue r; + struct cursor *cursor; + const char *filekey, *data; + size_t data_len; + + r = table_newglobalcursor(oldtable, true, &cursor); + if (!RET_IS_OK(r)) + return r; + while (cursor_nexttempdata(oldtable, cursor, &filekey, + &data, &data_len)) { + r = table_adduniqsizedrecord(newtable, filekey, + data, data_len+1, false, true); + if (RET_WAS_ERROR(r)) + return r; + } + return RET_OK; +} + +retvalue database_translate_filelists(void) { + char *dbname, *tmpdbname; + struct table *oldtable, *newtable; + struct strlist identifiers; + int ret; + retvalue r, r2; + + r = database_listsubtables("contents.cache.db", &identifiers); + if (RET_IS_OK(r)) { + if (!strlist_in(&identifiers, "filelists")) { + fprintf(stderr, +"Your %s/contents.cache.db file does not contain an old style database!\n", + global.dbdir); + strlist_done(&identifiers); + return RET_NOTHING; + } + strlist_done(&identifiers); + } + + dbname = dbfilename("contents.cache.db"); + if (FAILEDTOALLOC(dbname)) + return RET_ERROR_OOM; + tmpdbname = dbfilename("old.contents.cache.db"); + if (FAILEDTOALLOC(tmpdbname)) { + free(dbname); + return RET_ERROR_OOM; + } + ret = rename(dbname, tmpdbname); + if (ret != 0) { + int e = errno; + fprintf(stderr, "Could not rename '%s' into '%s': %s(%d)\n", + dbname, tmpdbname, strerror(e), e); + free(dbname); + free(tmpdbname); + return RET_ERRNO(e); + } + newtable = NULL; + r = database_table("contents.cache.db", "compressedfilelists", + dbt_BTREE, DB_CREATE, &newtable); + assert (r != RET_NOTHING); + oldtable = NULL; + if (RET_IS_OK(r)) { + r = database_table("old.contents.cache.db", "filelists", + dbt_BTREE, DB_RDONLY, &oldtable); + if (r == RET_NOTHING) { + fprintf(stderr, "Could not find old-style database!\n"); + r = RET_ERROR; + } + } + if (RET_IS_OK(r)) { + r = filelists_translate(oldtable, newtable); + if (r == RET_NOTHING) + r = RET_OK; + } + r2 = table_close(oldtable); + RET_ENDUPDATE(r, r2); + oldtable = NULL; + if (RET_IS_OK(r)) { + /* copy the new-style database, */ + r = database_table("old.contents.cache.db", "compressedfilelists", + dbt_BTREE, DB_RDONLY, &oldtable); + if (RET_IS_OK(r)) { + /* if there is one... */ + r = table_copy(oldtable, newtable); + r2 = table_close(oldtable); + RET_ENDUPDATE(r, r2); + } + if (r == RET_NOTHING) { + r = RET_OK; + } + } + r2 = table_close(newtable); + RET_ENDUPDATE(r, r2); + if (RET_IS_OK(r)) + (void)unlink(tmpdbname); + + if (RET_WAS_ERROR(r)) { + ret = rename(tmpdbname, dbname); + if (ret != 0) { + int e = errno; + fprintf(stderr, +"Could not rename '%s' back into '%s': %s(%d)\n", + dbname, tmpdbname, strerror(e), e); + free(tmpdbname); + free(dbname); + return RET_ERRNO(e); + } + free(tmpdbname); + free(dbname); + return r; + } + free(tmpdbname); + free(dbname); + return RET_OK; +} + +/* This is already implemented as standalone functions duplicating a bit + * of database_create and from files.c, + * because database_create is planed to error out if * there is still an old + * files.db and files.c is supposed to lose all support for it in the next + * major version */ + +static inline retvalue translate(struct table *oldmd5sums, struct table *newchecksums) { + long numold = 0, numnew = 0, numreplace = 0, numretro = 0; + struct cursor *cursor, *newcursor; + const char *filekey, *md5sum, *all; + size_t alllen; + retvalue r; + + /* first add all md5sums to checksums if not there yet */ + + r = table_newglobalcursor(oldmd5sums, true, &cursor); + if (RET_WAS_ERROR(r)) + return r; + while (cursor_nexttempdata(oldmd5sums, cursor, + &filekey, &md5sum, NULL)) { + struct checksums *n = NULL; + const char *combined; + size_t combinedlen; + + r = table_gettemprecord(newchecksums, filekey, + &all, &alllen); + if (RET_IS_OK(r)) + r = checksums_setall(&n, all, alllen); + if (RET_IS_OK(r)) { + if (checksums_matches(n, cs_md5sum, md5sum)) { + /* already there, nothing to do */ + checksums_free(n); + numnew++; + continue; + } + /* new item does not match */ + if (verbose > 0) + printf( +"Overwriting stale new-checksums entry '%s'!\n", + filekey); + numreplace++; + checksums_free(n); + n = NULL; + } + if (RET_WAS_ERROR(r)) { + (void)cursor_close(oldmd5sums, cursor); + return r; + } + /* parse and recreate, to only have sanitized strings + * in the database */ + r = checksums_parse(&n, md5sum); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + (void)cursor_close(oldmd5sums, cursor); + return r; + } + + r = checksums_getcombined(n, &combined, &combinedlen); + assert (r != RET_NOTHING); + if (!RET_IS_OK(r)) { + (void)cursor_close(oldmd5sums, cursor); + return r; + } + numold++; + r = table_adduniqsizedrecord(newchecksums, filekey, + combined, combinedlen + 1, true, false); + assert (r != RET_NOTHING); + if (!RET_IS_OK(r)) { + (void)cursor_close(oldmd5sums, cursor); + return r; + } + } + r = cursor_close(oldmd5sums, cursor); + if (RET_WAS_ERROR(r)) + return r; + + /* then delete everything from checksums that is not in md5sums */ + + r = table_newglobalcursor(oldmd5sums, true, &cursor); + if (RET_WAS_ERROR(r)) + return r; + r = table_newglobalcursor(newchecksums, true, &newcursor); + if (RET_WAS_ERROR(r)) { + cursor_close(oldmd5sums, cursor); + return r; + } + while (cursor_nexttempdata(oldmd5sums, cursor, + &filekey, &md5sum, NULL)) { + bool more; + int cmp; + const char *newfilekey, *dummy; + + do { + more = cursor_nexttempdata(newchecksums, newcursor, + &newfilekey, &dummy, NULL); + /* should have been added in the last step */ + assert (more); + cmp = strcmp(filekey, newfilekey); + /* should have been added in the last step */ + assert (cmp >= 0); + more = cmp > 0; + if (more) { + numretro++; + if (verbose > 0) + printf( +"Deleting stale new-checksums entry '%s'!\n", + newfilekey); + r = cursor_delete(newchecksums, newcursor, + newfilekey, dummy); + if (RET_WAS_ERROR(r)) { + cursor_close(oldmd5sums, cursor); + cursor_close(newchecksums, newcursor); + return r; + } + } + } while (more); + } + r = cursor_close(oldmd5sums, cursor); + if (RET_WAS_ERROR(r)) + return r; + r = cursor_close(newchecksums, newcursor); + if (RET_WAS_ERROR(r)) + return r; + if (verbose >= 0) { + printf("%ld packages were already in the new checksums.db\n", + numnew); + printf("%ld packages were added to the new checksums.db\n", + numold - numreplace); + if (numretro != 0) + printf( +"%ld were only in checksums.db and not in files.db\n" +"This should only have happened if you added them with a newer version\n" +"and then deleted them with an older version of reprepro.\n", + numretro); + if (numreplace != 0) + printf( +"%ld were different checksums.db and not in files.db\n" +"This should only have happened if you added them with a newer version\n" +"and then deleted them with an older version of reprepro and\n" +"then readded them with a old version.\n", + numreplace); + if (numretro != 0 || numreplace != 0) + printf( +"If you never run a old version after a new version,\n" +"you might want to check with check and checkpool if something went wrong.\n"); + } + return RET_OK; +} + +retvalue database_translate_legacy_checksums(bool verbosedb) { + struct table *newchecksums, *oldmd5sums; + char *fullfilename; + retvalue r; + int e; + + if (rdb_initialized || rdb_used) { + fputs("Internal Error: database initialized a 2nd time!\n", + stderr); + return RET_ERROR_INTERNAL; + } + + if (!isdir(global.dbdir)) { + fprintf(stderr, "Cannot find directory '%s'!\n", + global.dbdir); + return RET_ERROR; + } + + rdb_initialized = true; + rdb_used = true; + + r = database_lock(0); + assert (r != RET_NOTHING); + if (!RET_IS_OK(r)) { + database_free(); + return r; + } + rdb_readonly = READWRITE; + rdb_verbose = verbosedb; + + r = readversionfile(false); + if (RET_WAS_ERROR(r)) { + releaselock(); + database_free(); + return r; + } + + r = database_table("files.db", "md5sums", dbt_BTREE, 0, &oldmd5sums); + if (r == RET_NOTHING) { + fprintf(stderr, +"There is no old files.db in %s. Nothing to translate!\n", + global.dbdir); + releaselock(); + database_free(); + return RET_NOTHING; + } else if (RET_WAS_ERROR(r)) { + releaselock(); + database_free(); + return r; + } + + r = database_table("checksums.db", "pool", dbt_BTREE, DB_CREATE, + &newchecksums); + assert (r != RET_NOTHING); + if (RET_WAS_ERROR(r)) { + (void)table_close(oldmd5sums); + releaselock(); + database_free(); + return r; + } + + r = translate(oldmd5sums, newchecksums); + if (RET_WAS_ERROR(r)) { + (void)table_close(oldmd5sums); + (void)table_close(newchecksums); + releaselock(); + database_free(); + return r; + } + + (void)table_close(oldmd5sums); + r = table_close(newchecksums); + if (RET_WAS_ERROR(r)) { + releaselock(); + database_free(); + return r; + } + fullfilename = dbfilename("files.db"); + if (FAILEDTOALLOC(fullfilename)) { + releaselock(); + database_free(); + return RET_ERROR_OOM; + } + e = deletefile(fullfilename); + if (e != 0) { + fprintf(stderr, "Could not delete '%s'!\n" +"It can now safely be deleted and it all that is left to be done!\n", + fullfilename); + database_free(); + return RET_ERRNO(e); + } + r = writeversionfile(); + releaselock(); + database_free(); + return r; +} |