summaryrefslogtreecommitdiff
path: root/lib/backend
diff options
context:
space:
mode:
Diffstat (limited to 'lib/backend')
-rw-r--r--lib/backend/db3.c994
-rw-r--r--lib/backend/dbconfig.c2
-rw-r--r--lib/backend/dbi.c184
-rw-r--r--lib/backend/dbi.h202
-rw-r--r--lib/backend/dbiset.c214
-rw-r--r--lib/backend/dbiset.h130
-rw-r--r--lib/backend/lmdb.c939
-rw-r--r--lib/backend/ndb/glue.c492
-rw-r--r--lib/backend/ndb/rpmidx.c1280
-rw-r--r--lib/backend/ndb/rpmidx.h18
-rw-r--r--lib/backend/ndb/rpmpkg.c1312
-rw-r--r--lib/backend/ndb/rpmpkg.h20
-rw-r--r--lib/backend/ndb/rpmxdb.c1221
-rw-r--r--lib/backend/ndb/rpmxdb.h27
14 files changed, 6793 insertions, 242 deletions
diff --git a/lib/backend/db3.c b/lib/backend/db3.c
index a55a608cf..771018467 100644
--- a/lib/backend/db3.c
+++ b/lib/backend/db3.c
@@ -8,6 +8,9 @@ static int _debug = 1; /* XXX if < 0 debugging, > 0 unusual error returns */
#include <errno.h>
#include <sys/wait.h>
+#include <popt.h>
+#include <db.h>
+#include <signal.h>
#include <rpm/rpmtypes.h>
#include <rpm/rpmmacro.h>
@@ -22,19 +25,80 @@ static const char * _errpfx = "rpmdb";
struct dbiCursor_s {
dbiIndex dbi;
+ const void *key;
+ unsigned int keylen;
+ int flags;
DBC *cursor;
};
+static struct dbiConfig_s staticdbicfg;
+static struct dbConfig_s staticcfg;
+
+/** \ingroup dbi
+ */
+static const struct poptOption rdbOptions[] = {
+ /* Environment options */
+
+ { "cdb", 0,POPT_BIT_SET, &staticcfg.db_eflags, DB_INIT_CDB,
+ NULL, NULL },
+ { "lock", 0,POPT_BIT_SET, &staticcfg.db_eflags, DB_INIT_LOCK,
+ NULL, NULL },
+ { "log", 0,POPT_BIT_SET, &staticcfg.db_eflags, DB_INIT_LOG,
+ NULL, NULL },
+ { "txn", 0,POPT_BIT_SET, &staticcfg.db_eflags, DB_INIT_TXN,
+ NULL, NULL },
+ { "recover", 0,POPT_BIT_SET, &staticcfg.db_eflags, DB_RECOVER,
+ NULL, NULL },
+ { "recover_fatal", 0,POPT_BIT_SET, &staticcfg.db_eflags, DB_RECOVER_FATAL,
+ NULL, NULL },
+ { "lockdown", 0,POPT_BIT_SET, &staticcfg.db_eflags, DB_LOCKDOWN,
+ NULL, NULL },
+ { "private", 0,POPT_BIT_SET, &staticcfg.db_eflags, DB_PRIVATE,
+ NULL, NULL },
+
+ { "deadlock", 0,POPT_BIT_SET, &staticcfg.db_verbose, DB_VERB_DEADLOCK,
+ NULL, NULL },
+ { "recovery", 0,POPT_BIT_SET, &staticcfg.db_verbose, DB_VERB_RECOVERY,
+ NULL, NULL },
+ { "waitsfor", 0,POPT_BIT_SET, &staticcfg.db_verbose, DB_VERB_WAITSFOR,
+ NULL, NULL },
+ { "verbose", 0,POPT_ARG_VAL, &staticcfg.db_verbose, -1,
+ NULL, NULL },
+
+ { "cachesize", 0,POPT_ARG_INT, &staticcfg.db_cachesize, 0,
+ NULL, NULL },
+ { "mmapsize", 0,POPT_ARG_INT, &staticcfg.db_mmapsize, 0,
+ NULL, NULL },
+ { "mp_mmapsize", 0,POPT_ARG_INT, &staticcfg.db_mmapsize, 0,
+ NULL, NULL },
+ { "mp_size", 0,POPT_ARG_INT, &staticcfg.db_cachesize, 0,
+ NULL, NULL },
+
+ { "nofsync", 0,POPT_ARG_NONE, &staticcfg.db_no_fsync, 0,
+ NULL, NULL },
+
+ /* Per-dbi options */
+ { "nommap", 0,POPT_BIT_SET, &staticdbicfg.dbi_oflags, DB_NOMMAP,
+ NULL, NULL },
+
+ { "nodbsync", 0,POPT_ARG_NONE, &staticdbicfg.dbi_no_dbsync, 0,
+ NULL, NULL },
+ { "lockdbfd", 0,POPT_ARG_NONE, &staticdbicfg.dbi_lockdbfd, 0,
+ NULL, NULL },
+
+ POPT_TABLEEND
+};
+
+
static int dbapi_err(rpmdb rdb, const char * msg, int error, int printit)
{
if (printit && error) {
- int db_api = rdb->db_ver;
if (msg)
- rpmlog(RPMLOG_ERR, _("db%d error(%d) from %s: %s\n"),
- db_api, error, msg, db_strerror(error));
+ rpmlog(RPMLOG_ERR, _("%s error(%d) from %s: %s\n"),
+ rdb->db_descr, error, msg, db_strerror(error));
else
- rpmlog(RPMLOG_ERR, _("db%d error(%d): %s\n"),
- db_api, error, db_strerror(error));
+ rpmlog(RPMLOG_ERR, _("%s error(%d): %s\n"),
+ rdb->db_descr, error, db_strerror(error));
}
return error;
}
@@ -49,6 +113,11 @@ static void errlog(const DB_ENV * env, const char *errpfx, const char *msg)
rpmlog(RPMLOG_ERR, "%s: %s\n", errpfx, msg);
}
+static void warnlog(const DB_ENV *env, const char *msg)
+{
+ rpmlog(RPMLOG_WARNING, "%s: %s\n", _errpfx, msg);
+}
+
static uint32_t db_envflags(DB * db)
{
DB_ENV * env = db->get_env(db);
@@ -57,10 +126,42 @@ static uint32_t db_envflags(DB * db)
return eflags;
}
+/*
+ * Try to acquire db environment open/close serialization lock.
+ * Return the open, locked fd on success, -1 on failure.
+ */
+static int serialize_env(const char *dbhome)
+{
+ char *lock_path = rstrscat(NULL, dbhome, "/.dbenv.lock", NULL);
+ mode_t oldmask = umask(022);
+ int fd = open(lock_path, (O_RDWR|O_CREAT), 0644);
+ umask(oldmask);
+
+ if (fd >= 0) {
+ int rc;
+ struct flock info;
+ memset(&info, 0, sizeof(info));
+ info.l_type = F_WRLCK;
+ info.l_whence = SEEK_SET;
+ do {
+ rc = fcntl(fd, F_SETLKW, &info);
+ } while (rc == -1 && errno == EINTR);
+
+ if (rc == -1) {
+ close(fd);
+ fd = -1;
+ }
+ }
+
+ free(lock_path);
+ return fd;
+}
+
static int db_fini(rpmdb rdb, const char * dbhome)
{
DB_ENV * dbenv = rdb->db_dbenv;
int rc;
+ int lockfd = -1;
uint32_t eflags = 0;
if (dbenv == NULL)
@@ -72,6 +173,9 @@ static int db_fini(rpmdb rdb, const char * dbhome)
}
(void) dbenv->get_open_flags(dbenv, &eflags);
+ if (!(eflags & DB_PRIVATE))
+ lockfd = serialize_env(dbhome);
+
rc = dbenv->close(dbenv, 0);
rc = dbapi_err(rdb, "dbenv->close", rc, _debug);
@@ -89,6 +193,10 @@ static int db_fini(rpmdb rdb, const char * dbhome)
rpmlog(RPMLOG_DEBUG, "removed db environment %s\n", dbhome);
}
+
+ if (lockfd >= 0)
+ close(lockfd);
+
return rc;
}
@@ -117,18 +225,200 @@ static int isalive(DB_ENV *dbenv, pid_t pid, db_threadid_t tid, uint32_t flags)
return alive;
}
+
+static void dbConfigure(rpmDbiTagVal rpmtag, struct dbConfig_s *cfg, struct dbiConfig_s *dbicfg)
+{
+ char *dbOpts;
+
+ dbOpts = rpmExpand("%{_dbi_config_", rpmTagGetName(rpmtag), "}", NULL);
+
+ if (!(dbOpts && *dbOpts && *dbOpts != '%')) {
+ dbOpts = _free(dbOpts);
+ dbOpts = rpmExpand("%{_dbi_config}", NULL);
+ if (!(dbOpts && *dbOpts && *dbOpts != '%')) {
+ dbOpts = _free(dbOpts);
+ }
+ }
+
+ /* Parse the options for the database element(s). */
+ if (dbOpts && *dbOpts && *dbOpts != '%') {
+ char *o, *oe;
+ char *p, *pe;
+
+ memset(&staticdbicfg, 0, sizeof(staticdbicfg));
+/*=========*/
+ for (o = dbOpts; o && *o; o = oe) {
+ const struct poptOption *opt;
+ const char * tok;
+ unsigned int argInfo;
+
+ /* Skip leading white space. */
+ while (*o && risspace(*o))
+ o++;
+
+ /* Find and terminate next key=value pair. Save next start point. */
+ for (oe = o; oe && *oe; oe++) {
+ if (risspace(*oe))
+ break;
+ if (oe[0] == ':' && !(oe[1] == '/' && oe[2] == '/'))
+ break;
+ }
+ if (oe && *oe)
+ *oe++ = '\0';
+ if (*o == '\0')
+ continue;
+
+ /* Separate key from value, save value start (if any). */
+ for (pe = o; pe && *pe && *pe != '='; pe++)
+ {};
+ p = (pe ? *pe++ = '\0', pe : NULL);
+
+ /* Skip over negation at start of token. */
+ for (tok = o; *tok == '!'; tok++)
+ {};
+
+ /* Find key in option table. */
+ for (opt = rdbOptions; opt->longName != NULL; opt++) {
+ if (!rstreq(tok, opt->longName))
+ continue;
+ break;
+ }
+ if (opt->longName == NULL) {
+ rpmlog(RPMLOG_ERR,
+ _("unrecognized db option: \"%s\" ignored.\n"), o);
+ continue;
+ }
+
+ /* Toggle the flags for negated tokens, if necessary. */
+ argInfo = opt->argInfo;
+ if (argInfo == POPT_BIT_SET && *o == '!' && ((tok - o) % 2))
+ argInfo = POPT_BIT_CLR;
+
+ /* Save value in template as appropriate. */
+ switch (argInfo & POPT_ARG_MASK) {
+
+ case POPT_ARG_NONE:
+ (void) poptSaveInt((int *)opt->arg, argInfo, 1L);
+ break;
+ case POPT_ARG_VAL:
+ (void) poptSaveInt((int *)opt->arg, argInfo, (long)opt->val);
+ break;
+ case POPT_ARG_STRING:
+ { char ** t = opt->arg;
+ if (t) {
+/* FIX: opt->arg annotation in popt.h */
+ *t = _free(*t);
+ *t = xstrdup( (p ? p : "") );
+ }
+ } break;
+
+ case POPT_ARG_INT:
+ case POPT_ARG_LONG:
+ { long aLong = strtol(p, &pe, 0);
+ if (pe) {
+ if (!rstrncasecmp(pe, "Mb", 2))
+ aLong *= 1024 * 1024;
+ else if (!rstrncasecmp(pe, "Kb", 2))
+ aLong *= 1024;
+ else if (*pe != '\0') {
+ rpmlog(RPMLOG_ERR,
+ _("%s has invalid numeric value, skipped\n"),
+ opt->longName);
+ continue;
+ }
+ }
+
+ if ((argInfo & POPT_ARG_MASK) == POPT_ARG_LONG) {
+ if (aLong == LONG_MIN || aLong == LONG_MAX) {
+ rpmlog(RPMLOG_ERR,
+ _("%s has too large or too small long value, skipped\n"),
+ opt->longName);
+ continue;
+ }
+ (void) poptSaveLong((long *)opt->arg, argInfo, aLong);
+ break;
+ } else {
+ if (aLong > INT_MAX || aLong < INT_MIN) {
+ rpmlog(RPMLOG_ERR,
+ _("%s has too large or too small integer value, skipped\n"),
+ opt->longName);
+ continue;
+ }
+ (void) poptSaveInt((int *)opt->arg, argInfo, aLong);
+ }
+ } break;
+ default:
+ break;
+ }
+ }
+/*=========*/
+ }
+
+ dbOpts = _free(dbOpts);
+ if (cfg) {
+ *cfg = staticcfg; /* structure assignment */
+ /* Throw in some defaults if configuration didn't set any */
+ if (!cfg->db_mmapsize)
+ cfg->db_mmapsize = 16 * 1024 * 1024;
+ if (!cfg->db_cachesize)
+ cfg->db_cachesize = 8 * 1024 * 1024;
+ }
+ if (dbicfg) {
+ *dbicfg = staticdbicfg;
+ }
+}
+
+static char * prDbiOpenFlags(int dbflags, int print_dbenv_flags)
+{
+ ARGV_t flags = NULL;
+ const struct poptOption *opt;
+ char *buf;
+
+ for (opt = rdbOptions; opt->longName != NULL; opt++) {
+ if (opt->argInfo != POPT_BIT_SET)
+ continue;
+ if (print_dbenv_flags) {
+ if (!(opt->arg == &staticcfg.db_eflags))
+ continue;
+ } else {
+ if (!(opt->arg == &staticdbicfg.dbi_oflags))
+ continue;
+ }
+ if ((dbflags & opt->val) != opt->val)
+ continue;
+ argvAdd(&flags, opt->longName);
+ dbflags &= ~opt->val;
+ }
+ if (dbflags) {
+ char *df = NULL;
+ rasprintf(&df, "0x%x", (unsigned)dbflags);
+ argvAdd(&flags, df);
+ free(df);
+ }
+ buf = argvJoin(flags, ":");
+ argvFree(flags);
+
+ return buf ? buf : xstrdup("(none)");
+}
+
static int db_init(rpmdb rdb, const char * dbhome)
{
DB_ENV *dbenv = NULL;
int rc, xx;
int retry_open = 2;
+ int lockfd = -1;
+ int rdonly = ((rdb->db_mode & O_ACCMODE) == O_RDONLY);
struct dbConfig_s * cfg = &rdb->cfg;
/* This is our setup, thou shall not have other setups before us */
- uint32_t eflags = (DB_CREATE|DB_INIT_MPOOL|DB_INIT_CDB);
+ uint32_t eflags = (DB_CREATE|DB_INIT_MPOOL|DB_INIT_CDB|DB_PRIVATE);
if (rdb->db_dbenv != NULL) {
rdb->db_opens++;
return 0;
+ } else {
+ /* On first call, set backend description to something... */
+ free(rdb->db_descr);
+ rasprintf(&rdb->db_descr, "db%u", DB_VERSION_MAJOR);
}
/*
@@ -150,6 +440,7 @@ static int db_init(rpmdb rdb, const char * dbhome)
dbenv->set_alloc(dbenv, rmalloc, rrealloc, NULL);
dbenv->set_errcall(dbenv, NULL);
dbenv->set_errpfx(dbenv, _errpfx);
+ dbenv->set_msgcall(dbenv, warnlog);
/*
* These enable automatic stale lock removal.
@@ -176,6 +467,24 @@ static int db_init(rpmdb rdb, const char * dbhome)
}
/*
+ * Serialize shared environment open (and clock) via fcntl() lock.
+ * Otherwise we can end up calling dbenv->failchk() while another
+ * process is joining the environment, leading to transient
+ * DB_RUNRECOVER errors. Also prevents races wrt removing the
+ * environment (eg chrooted operation). Silently fall back to
+ * private environment on failure to allow non-privileged queries
+ * to "work", broken as it might be.
+ */
+ if (!(eflags & DB_PRIVATE)) {
+ lockfd = serialize_env(dbhome);
+ if (lockfd < 0 && rdonly) {
+ eflags |= DB_PRIVATE;
+ retry_open--;
+ rpmlog(RPMLOG_DEBUG, "serialize failed, using private dbenv\n");
+ }
+ }
+
+ /*
* Actually open the environment. Fall back to private environment
* if we dont have permission to join/create shared environment or
* system doesn't support it..
@@ -186,7 +495,10 @@ static int db_init(rpmdb rdb, const char * dbhome)
free(fstr);
rc = (dbenv->open)(dbenv, dbhome, eflags, rdb->db_perms);
- if ((rc == EACCES || rc == EROFS || rc == EINVAL) && errno == rc) {
+ if (rc == EINVAL && errno == rc) {
+ eflags |= DB_PRIVATE;
+ retry_open--;
+ } else if (rdonly && (rc == EACCES || rc == EROFS || rc == DB_VERSION_MISMATCH)) {
eflags |= DB_PRIVATE;
retry_open--;
} else {
@@ -208,6 +520,8 @@ static int db_init(rpmdb rdb, const char * dbhome)
rdb->db_dbenv = dbenv;
rdb->db_opens = 1;
+ if (lockfd >= 0)
+ close(lockfd);
return 0;
errxit:
@@ -216,10 +530,12 @@ errxit:
xx = dbenv->close(dbenv, 0);
xx = dbapi_err(rdb, "dbenv->close", xx, _debug);
}
+ if (lockfd >= 0)
+ close(lockfd);
return rc;
}
-void dbSetFSync(void *dbenv, int enable)
+static void db3_dbSetFSync(rpmdb rdb, int enable)
{
#ifdef HAVE_FDATASYNC
db_env_set_func_fsync(enable ? fdatasync : fsync_disable);
@@ -228,19 +544,24 @@ void dbSetFSync(void *dbenv, int enable)
#endif
}
-int dbiSync(dbiIndex dbi, unsigned int flags)
+static int db3_Ctrl(rpmdb rdb, dbCtrlOp ctrl)
+{
+ return 0;
+}
+
+static int dbiSync(dbiIndex dbi, unsigned int flags)
{
DB * db = dbi->dbi_db;
int rc = 0;
- if (db != NULL && !dbi->dbi_no_dbsync) {
+ if (db != NULL && !dbi->cfg.dbi_no_dbsync) {
rc = db->sync(db, flags);
rc = cvtdberr(dbi, "db->sync", rc, _debug);
}
return rc;
}
-dbiCursor dbiCursorInit(dbiIndex dbi, unsigned int flags)
+static dbiCursor db3_dbiCursorInit(dbiIndex dbi, unsigned int flags)
{
dbiCursor dbc = NULL;
@@ -248,33 +569,52 @@ dbiCursor dbiCursorInit(dbiIndex dbi, unsigned int flags)
DB * db = dbi->dbi_db;
DBC * cursor;
int cflags;
- int rc;
+ int rc = 0;
uint32_t eflags = db_envflags(db);
/* DB_WRITECURSOR requires CDB and writable db */
- if ((flags & DB_WRITECURSOR) &&
- (eflags & DB_INIT_CDB) && !(dbi->dbi_oflags & DB_RDONLY))
+ if ((flags & DBC_WRITE) &&
+ (eflags & DB_INIT_CDB) && !(dbi->dbi_flags & DBI_RDONLY))
{
cflags = DB_WRITECURSOR;
} else
cflags = 0;
- rc = db->cursor(db, NULL, &cursor, cflags);
- rc = cvtdberr(dbi, "db->cursor", rc, _debug);
+ /*
+ * Check for stale locks which could block writes "forever".
+ * XXX: Should we also do this on reads? Reads are less likely
+ * to get blocked so it seems excessive...
+ * XXX: On DB_RUNRECOVER, we should abort everything. Now
+ * we'll just fail to open a cursor again and again and again.
+ */
+ if (cflags & DB_WRITECURSOR) {
+ DB_ENV *dbenv = db->get_env(db);
+ rc = dbenv->failchk(dbenv, 0);
+ rc = cvtdberr(dbi, "dbenv->failchk", rc, _debug);
+ }
+
+ if (rc == 0) {
+ rc = db->cursor(db, NULL, &cursor, cflags);
+ rc = cvtdberr(dbi, "db->cursor", rc, _debug);
+ }
if (rc == 0) {
dbc = xcalloc(1, sizeof(*dbc));
dbc->cursor = cursor;
dbc->dbi = dbi;
+ dbc->flags = flags;
}
}
return dbc;
}
-dbiCursor dbiCursorFree(dbiCursor dbc)
+static dbiCursor db3_dbiCursorFree(dbiIndex dbi, dbiCursor dbc)
{
if (dbc) {
+ /* Automatically sync on write-cursor close */
+ if (dbc->flags & DBC_WRITE)
+ dbiSync(dbc->dbi, 0);
DBC * cursor = dbc->cursor;
int rc = cursor->c_close(cursor);
cvtdberr(dbc->dbi, "dbcursor->c_close", rc, _debug);
@@ -283,7 +623,7 @@ dbiCursor dbiCursorFree(dbiCursor dbc)
return NULL;
}
-int dbiCursorPut(dbiCursor dbc, DBT * key, DBT * data, unsigned int flags)
+static int dbiCursorPut(dbiCursor dbc, DBT * key, DBT * data, unsigned int flags)
{
int rc = EINVAL;
int sane = (key->data != NULL && key->size > 0 &&
@@ -302,7 +642,7 @@ int dbiCursorPut(dbiCursor dbc, DBT * key, DBT * data, unsigned int flags)
return rc;
}
-int dbiCursorGet(dbiCursor dbc, DBT * key, DBT * data, unsigned int flags)
+static int dbiCursorGet(dbiCursor dbc, DBT * key, DBT * data, unsigned int flags)
{
int rc = EINVAL;
int sane = ((flags == DB_NEXT) || (key->data != NULL && key->size > 0));
@@ -319,12 +659,21 @@ int dbiCursorGet(dbiCursor dbc, DBT * key, DBT * data, unsigned int flags)
_printit = (rc == DB_NOTFOUND ? 0 : _debug);
rc = cvtdberr(dbc->dbi, "dbcursor->c_get", rc, _printit);
+ /* Remember the last key fetched */
+ if (rc == 0) {
+ dbc->key = key->data;
+ dbc->keylen = key->size;
+ } else {
+ dbc->key = NULL;
+ dbc->keylen = 0;
+ }
+
rpmswExit(&rdb->db_getops, data->size);
}
return rc;
}
-int dbiCursorDel(dbiCursor dbc, DBT * key, DBT * data, unsigned int flags)
+static int dbiCursorDel(dbiCursor dbc, DBT * key, DBT * data, unsigned int flags)
{
int rc = EINVAL;
int sane = (key->data != NULL && key->size > 0);
@@ -350,23 +699,7 @@ int dbiCursorDel(dbiCursor dbc, DBT * key, DBT * data, unsigned int flags)
return rc;
}
-unsigned int dbiCursorCount(dbiCursor dbc)
-{
- db_recno_t count = 0;
- if (dbc) {
- DBC * cursor = dbc->cursor;
- int rc = cursor->c_count(cursor, &count, 0);
- cvtdberr(dbc->dbi, "dbcursor->c_count", rc, _debug);
- }
- return count;
-}
-
-dbiIndex dbiCursorIndex(dbiCursor dbc)
-{
- return (dbc != NULL) ? dbc->dbi : NULL;
-}
-
-int dbiByteSwapped(dbiIndex dbi)
+static int dbiByteSwapped(dbiIndex dbi)
{
DB * db = dbi->dbi_db;
int rc = 0;
@@ -383,32 +716,7 @@ int dbiByteSwapped(dbiIndex dbi)
return rc;
}
-dbiIndexType dbiType(dbiIndex dbi)
-{
- return dbi->dbi_type;
-}
-
-int dbiFlags(dbiIndex dbi)
-{
- DB *db = dbi->dbi_db;
- int flags = DBI_NONE;
- uint32_t oflags = 0;
-
- if (db && db->get_open_flags(db, &oflags) == 0) {
- if (oflags & DB_CREATE)
- flags |= DBI_CREATED;
- if (oflags & DB_RDONLY)
- flags |= DBI_RDONLY;
- }
- return flags;
-}
-
-const char * dbiName(dbiIndex dbi)
-{
- return dbi->dbi_file;
-}
-
-int dbiVerify(dbiIndex dbi, unsigned int flags)
+static int db3_dbiVerify(dbiIndex dbi, unsigned int flags)
{
int rc = 0;
@@ -426,9 +734,7 @@ int dbiVerify(dbiIndex dbi, unsigned int flags)
return rc;
}
-static int _lockdbfd = 0;
-
-int dbiClose(dbiIndex dbi, unsigned int flags)
+static int db3_dbiClose(dbiIndex dbi, unsigned int flags)
{
rpmdb rdb = dbi->dbi_rpmdb;
const char * dbhome = rpmdbHome(rdb);
@@ -445,8 +751,6 @@ int dbiClose(dbiIndex dbi, unsigned int flags)
rpmlog(RPMLOG_DEBUG, "closed db index %s/%s\n",
dbhome, dbi->dbi_file);
- if (dbi->dbi_lockdbfd && _lockdbfd)
- _lockdbfd--;
}
db_fini(rdb, dbhome ? dbhome : "");
@@ -486,7 +790,6 @@ static int dbiFlock(dbiIndex dbi, int mode)
rc = 1;
} else {
const char *dbhome = rpmdbHome(dbi->dbi_rpmdb);
- int tries;
struct flock l;
memset(&l, 0, sizeof(l));
l.l_whence = 0;
@@ -496,44 +799,26 @@ static int dbiFlock(dbiIndex dbi, int mode)
? F_RDLCK : F_WRLCK;
l.l_pid = 0;
- for (tries = 0; ; tries++) {
- rc = fcntl(fdno, F_SETLK, (void *) &l);
- if (rc) {
- uint32_t eflags = db_envflags(db);
- /* Warning iff using non-private CDB locking. */
- rc = (((eflags & DB_INIT_CDB) && !(eflags & DB_PRIVATE)) ? 0 : 1);
- if (errno == EAGAIN && rc) {
- struct timespec ts;
- if (tries == 0)
- rpmlog(RPMLOG_WARNING,
- _("waiting for %s lock on %s/%s\n"),
- ((mode & O_ACCMODE) == O_RDONLY)
- ? _("shared") : _("exclusive"),
- dbhome, dbi->dbi_file);
- ts.tv_sec = (time_t)0;
- ts.tv_nsec = 100000000; /* .1 seconds */
- if (tries < 10*60*3) { /* 3 minutes */
- nanosleep(&ts, (struct timespec *)0);
- continue;
- }
- }
- rpmlog( (rc ? RPMLOG_ERR : RPMLOG_WARNING),
- _("cannot get %s lock on %s/%s\n"),
- ((mode & O_ACCMODE) == O_RDONLY)
- ? _("shared") : _("exclusive"),
- dbhome, dbi->dbi_file);
- } else {
- rpmlog(RPMLOG_DEBUG,
- "locked db index %s/%s\n",
- dbhome, dbi->dbi_file);
- }
- break;
+ rc = fcntl(fdno, F_SETLK, (void *) &l);
+ if (rc) {
+ uint32_t eflags = db_envflags(db);
+ /* Warning iff using non-private CDB locking. */
+ rc = (((eflags & DB_INIT_CDB) && !(eflags & DB_PRIVATE)) ? 0 : 1);
+ rpmlog( (rc ? RPMLOG_ERR : RPMLOG_WARNING),
+ _("cannot get %s lock on %s/%s\n"),
+ ((mode & O_ACCMODE) == O_RDONLY)
+ ? _("shared") : _("exclusive"),
+ dbhome, dbi->dbi_file);
+ } else {
+ rpmlog(RPMLOG_DEBUG,
+ "locked db index %s/%s\n",
+ dbhome, dbi->dbi_file);
}
}
return rc;
}
-int dbiOpen(rpmdb rdb, rpmDbiTagVal rpmtag, dbiIndex * dbip, int flags)
+static int db3_dbiOpen(rpmdb rdb, rpmDbiTagVal rpmtag, dbiIndex * dbip, int flags)
{
const char *dbhome = rpmdbHome(rdb);
dbiIndex dbi = NULL;
@@ -544,22 +829,25 @@ int dbiOpen(rpmdb rdb, rpmDbiTagVal rpmtag, dbiIndex * dbip, int flags)
DB * db = NULL;
DBTYPE dbtype = DB_UNKNOWN;
uint32_t oflags;
+ static int _lockdbfd = 0;
if (dbip)
*dbip = NULL;
- /*
- * Parse db configuration parameters.
- */
if ((dbi = dbiNew(rdb, rpmtag)) == NULL)
return 1;
- oflags = dbi->dbi_oflags;
+ /*
+ * Parse db configuration parameters.
+ */
+ dbConfigure(rpmtag, rdb->db_dbenv == NULL ? &rdb->cfg : NULL, &dbi->cfg);
/*
* Map open mode flags onto configured database/environment flags.
*/
- if ((rdb->db_mode & O_ACCMODE) == O_RDONLY) oflags |= DB_RDONLY;
+ oflags = dbi->cfg.dbi_oflags;
+ if ((rdb->db_mode & O_ACCMODE) == O_RDONLY)
+ oflags |= DB_RDONLY;
rc = db_init(rdb, dbhome);
@@ -587,7 +875,7 @@ int dbiOpen(rpmdb rdb, rpmDbiTagVal rpmtag, dbiIndex * dbip, int flags)
if (rc == ENOENT) {
oflags |= DB_CREATE;
oflags &= ~DB_RDONLY;
- dbtype = (dbiType(dbi) == DBI_PRIMARY) ? DB_HASH : DB_BTREE;
+ dbtype = (rpmtag == RPMDBI_PACKAGES) ? DB_HASH : DB_BTREE;
retry_open--;
} else {
retry_open = 0;
@@ -615,12 +903,14 @@ int dbiOpen(rpmdb rdb, rpmDbiTagVal rpmtag, dbiIndex * dbip, int flags)
}
dbi->dbi_db = db;
- dbi->dbi_oflags = oflags;
- if (verifyonly)
- dbi->dbi_lockdbfd = 0; /* disable locking in verify mode */
+ dbi->dbi_flags = 0;
+ if (oflags & DB_CREATE)
+ dbi->dbi_flags |= DBI_CREATED;
+ if (oflags & DB_RDONLY)
+ dbi->dbi_flags |= DBI_RDONLY;
- if (rc == 0 && dbi->dbi_lockdbfd && _lockdbfd++ == 0) {
+ if (!verifyonly && rc == 0 && dbi->cfg.dbi_lockdbfd && _lockdbfd++ == 0) {
rc = dbiFlock(dbi, rdb->db_mode);
}
@@ -633,58 +923,456 @@ int dbiOpen(rpmdb rdb, rpmDbiTagVal rpmtag, dbiIndex * dbip, int flags)
return rc;
}
-int dbiSuspendDBLock(dbiIndex dbi, unsigned int flags)
+/**
+ * Convert retrieved data to index set.
+ * @param dbi index database handle
+ * @param data retrieved data
+ * @retval setp (malloc'ed) index set
+ * @return 0 on success
+ */
+static int dbt2set(dbiIndex dbi, DBT * data, dbiIndexSet * setp)
{
- struct flock l;
- int rc = 0;
- int fdno = -1;
+ int _dbbyteswapped = dbiByteSwapped(dbi);
+ const char * sdbir;
+ dbiIndexSet set;
+ unsigned int i;
- if (!dbi->dbi_lockdbfd)
- return 0;
- if (!(dbi->dbi_rpmdb->db_mode & (O_RDWR|O_WRONLY)))
+ if (dbi == NULL || data == NULL || setp == NULL)
+ return -1;
+
+ if ((sdbir = data->data) == NULL) {
+ *setp = NULL;
return 0;
- if (_lockdbfd == 0)
+ }
+
+ set = dbiIndexSetNew(data->size / (2 * sizeof(int32_t)));
+ set->count = data->size / (2 * sizeof(int32_t));
+
+ for (i = 0; i < set->count; i++) {
+ union _dbswap hdrNum, tagNum;
+
+ memcpy(&hdrNum.ui, sdbir, sizeof(hdrNum.ui));
+ sdbir += sizeof(hdrNum.ui);
+ memcpy(&tagNum.ui, sdbir, sizeof(tagNum.ui));
+ sdbir += sizeof(tagNum.ui);
+ if (_dbbyteswapped) {
+ _DBSWAP(hdrNum);
+ _DBSWAP(tagNum);
+ }
+ set->recs[i].hdrNum = hdrNum.ui;
+ set->recs[i].tagNum = tagNum.ui;
+ }
+ *setp = set;
+ return 0;
+}
+
+/**
+ * Convert index set to database representation.
+ * @param dbi index database handle
+ * @param data retrieved data
+ * @param set index set
+ * @return 0 on success
+ */
+static int set2dbt(dbiIndex dbi, DBT * data, dbiIndexSet set)
+{
+ int _dbbyteswapped = dbiByteSwapped(dbi);
+ char * tdbir;
+ unsigned int i;
+
+ if (dbi == NULL || data == NULL || set == NULL)
+ return -1;
+
+ data->size = set->count * (2 * sizeof(int32_t));
+ if (data->size == 0) {
+ data->data = NULL;
return 0;
- if (!(dbi->dbi_db->fd(dbi->dbi_db, &fdno) == 0 && fdno >= 0))
- return 1;
- memset(&l, 0, sizeof(l));
- l.l_whence = 0;
- l.l_start = 0;
- l.l_len = 0;
- l.l_type = F_RDLCK;
- rc = fcntl(fdno, F_SETLK, (void *)&l);
+ }
+ tdbir = data->data = xmalloc(data->size);
+
+ for (i = 0; i < set->count; i++) {
+ union _dbswap hdrNum, tagNum;
+
+ memset(&hdrNum, 0, sizeof(hdrNum));
+ memset(&tagNum, 0, sizeof(tagNum));
+ hdrNum.ui = set->recs[i].hdrNum;
+ tagNum.ui = set->recs[i].tagNum;
+ if (_dbbyteswapped) {
+ _DBSWAP(hdrNum);
+ _DBSWAP(tagNum);
+ }
+ memcpy(tdbir, &hdrNum.ui, sizeof(hdrNum.ui));
+ tdbir += sizeof(hdrNum.ui);
+ memcpy(tdbir, &tagNum.ui, sizeof(tagNum.ui));
+ tdbir += sizeof(tagNum.ui);
+ }
+ return 0;
+}
+
+static rpmRC db3_idxdbGet(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen,
+ dbiIndexSet *set, int searchType)
+{
+ rpmRC rc = RPMRC_FAIL; /* assume failure */
+ if (dbi != NULL && dbc != NULL && set != NULL) {
+ int cflags = DB_NEXT;
+ int dbrc;
+ DBT data, key;
+ memset(&data, 0, sizeof(data));
+ memset(&key, 0, sizeof(key));
+
+ if (keyp) {
+ if (keylen == 0) { /* XXX "/" fixup */
+ keyp = "";
+ keylen = 1;
+ }
+ key.data = (void *) keyp; /* discards const */
+ key.size = keylen;
+ cflags = searchType == DBC_PREFIX_SEARCH ? DB_SET_RANGE : DB_SET;
+ }
+
+ for (;;) {
+ dbiIndexSet newset = NULL;
+ dbrc = dbiCursorGet(dbc, &key, &data, cflags);
+ if (dbrc != 0)
+ break;
+ if (searchType == DBC_PREFIX_SEARCH &&
+ (key.size < keylen || memcmp(key.data, keyp, keylen) != 0))
+ break;
+ dbt2set(dbi, &data, &newset);
+ if (*set == NULL) {
+ *set = newset;
+ } else {
+ dbiIndexSetAppendSet(*set, newset, 0);
+ dbiIndexSetFree(newset);
+ }
+ if (searchType != DBC_PREFIX_SEARCH)
+ break;
+ key.data = NULL;
+ key.size = 0;
+ cflags = DB_NEXT;
+ }
+
+ /* fixup result status for prefix search */
+ if (searchType == DBC_PREFIX_SEARCH) {
+ if (dbrc == DB_NOTFOUND && *set != NULL && (*set)->count > 0)
+ dbrc = 0;
+ else if (dbrc == 0 && (*set == NULL || (*set)->count == 0))
+ dbrc = DB_NOTFOUND;
+ }
+
+ if (dbrc == 0) {
+ rc = RPMRC_OK;
+ } else if (dbrc == DB_NOTFOUND) {
+ rc = RPMRC_NOTFOUND;
+ } else {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index: %s\n"),
+ dbrc, keyp ? keyp : "???", dbiName(dbi), db_strerror(dbrc));
+ }
+ }
+ return rc;
+}
+
+/* Update secondary index. NULL set deletes the key */
+static rpmRC updateIndex(dbiCursor dbc, const char *keyp, unsigned int keylen,
+ dbiIndexSet set)
+{
+ rpmRC rc = RPMRC_FAIL;
+
+ if (dbc && keyp) {
+ dbiIndex dbi = dbc->dbi;
+ int dbrc;
+ DBT data, key;
+ memset(&key, 0, sizeof(data));
+ memset(&data, 0, sizeof(data));
+
+ key.data = (void *) keyp; /* discards const */
+ key.size = keylen;
+
+ if (set)
+ set2dbt(dbi, &data, set);
+
+ if (dbiIndexSetCount(set) > 0) {
+ dbrc = dbiCursorPut(dbc, &key, &data, DB_KEYLAST);
+ if (dbrc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) storing record \"%s\" into %s\n"),
+ dbrc, (char*)key.data, dbiName(dbi));
+ }
+ free(data.data);
+ } else {
+ dbrc = dbiCursorDel(dbc, &key, &data, 0);
+ if (dbrc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) removing record \"%s\" from %s\n"),
+ dbrc, (char*)key.data, dbiName(dbi));
+ }
+ }
+
+ if (dbrc == 0)
+ rc = RPMRC_OK;
+ }
+
+ return rc;
+}
+
+static rpmRC db3_idxdbPut(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen,
+ dbiIndexItem rec)
+{
+ dbiIndexSet set = NULL;
+ rpmRC rc;
+
+ if (keyp && keylen == 0) { /* XXX "/" fixup */
+ keyp = "";
+ keylen++;
+ }
+ rc = idxdbGet(dbi, dbc, keyp, keylen, &set, DBC_NORMAL_SEARCH);
+
+ /* Not found means a new key and is not an error. */
+ if (rc && rc != RPMRC_NOTFOUND)
+ return rc;
+
+ if (set == NULL)
+ set = dbiIndexSetNew(1);
+ dbiIndexSetAppend(set, rec, 1, 0);
+
+ rc = updateIndex(dbc, keyp, keylen, set);
+
+ dbiIndexSetFree(set);
+ return rc;
+}
+
+static rpmRC db3_idxdbDel(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen,
+ dbiIndexItem rec)
+{
+ dbiIndexSet set = NULL;
+ rpmRC rc;
+
+ if (keyp && keylen == 0) { /* XXX "/" fixup */
+ keyp = "";
+ keylen++;
+ }
+ rc = idxdbGet(dbi, dbc, keyp, keylen, &set, DBC_NORMAL_SEARCH);
if (rc)
- rpmlog(RPMLOG_WARNING, _("could not suspend database lock\n"));
+ return rc;
+
+ if (dbiIndexSetPrune(set, rec, 1, 1)) {
+ /* Nothing was pruned. XXX: Can this actually happen? */
+ rc = RPMRC_OK;
+ } else {
+ /* If there's data left, update data. Otherwise delete the key. */
+ if (dbiIndexSetCount(set) > 0) {
+ rc = updateIndex(dbc, keyp, keylen, set);
+ } else {
+ rc = updateIndex(dbc, keyp, keylen, NULL);
+ }
+ };
+ dbiIndexSetFree(set);
+
return rc;
}
-int dbiResumeDBLock(dbiIndex dbi, unsigned int flags)
+static const void * db3_idxdbKey(dbiIndex dbi, dbiCursor dbc, unsigned int *keylen)
{
- struct flock l;
+ const void *key = NULL;
+ if (dbc) {
+ key = dbc->key;
+ if (key && keylen)
+ *keylen = dbc->keylen;
+ }
+ return key;
+}
+
+
+/* Update primary Packages index. NULL hdr means remove */
+static rpmRC updatePackages(dbiCursor dbc, unsigned int hdrNum, DBT *hdr)
+{
+ union _dbswap mi_offset;
int rc = 0;
- int tries;
- int fdno = -1;
+ DBT key;
- if (!dbi->dbi_lockdbfd)
- return 0;
- if (!(dbi->dbi_rpmdb->db_mode & (O_RDWR|O_WRONLY)))
- return 0;
- if (_lockdbfd == 0)
- return 0;
- if (!(dbi->dbi_db->fd(dbi->dbi_db, &fdno) == 0 && fdno >= 0))
- return 1;
- for (tries = 0; tries < 2; tries++) {
- memset(&l, 0, sizeof(l));
- l.l_whence = 0;
- l.l_start = 0;
- l.l_len = 0;
- l.l_type = F_WRLCK;
- rc = fcntl(fdno, tries ? F_SETLKW : F_SETLK, (void *)&l);
- if (!rc)
- break;
- if (tries == 0)
- rpmlog(RPMLOG_WARNING, _("waiting to reestablish exclusive database lock\n"));
+ if (dbc == NULL || hdrNum == 0)
+ return RPMRC_FAIL;
+
+ memset(&key, 0, sizeof(key));
+
+ mi_offset.ui = hdrNum;
+ if (dbiByteSwapped(dbc->dbi) == 1)
+ _DBSWAP(mi_offset);
+ key.data = (void *) &mi_offset;
+ key.size = sizeof(mi_offset.ui);
+
+ if (hdr) {
+ rc = dbiCursorPut(dbc, &key, hdr, DB_KEYLAST);
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) adding header #%d record\n"), rc, hdrNum);
+ }
+ } else {
+ DBT data;
+
+ memset(&data, 0, sizeof(data));
+ rc = dbiCursorGet(dbc, &key, &data, DB_SET);
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) removing header #%d record\n"), rc, hdrNum);
+ } else
+ rc = dbiCursorDel(dbc, &key, &data, 0);
}
- return rc;
+
+ return rc == 0 ? RPMRC_OK : RPMRC_FAIL;
}
+/* Get current header instance number or try to allocate a new one */
+static unsigned int pkgInstance(dbiIndex dbi, int alloc)
+{
+ unsigned int hdrNum = 0;
+
+ if (dbi != NULL && dbi->dbi_type == DBI_PRIMARY) {
+ dbiCursor dbc;
+ DBT key, data;
+ unsigned int firstkey = 0;
+ union _dbswap mi_offset;
+ int ret;
+
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+
+ dbc = dbiCursorInit(dbi, alloc ? DBC_WRITE : 0);
+
+ /* Key 0 holds the current largest instance, fetch it */
+ key.data = &firstkey;
+ key.size = sizeof(firstkey);
+ ret = dbiCursorGet(dbc, &key, &data, DB_SET);
+
+ if (ret == 0 && data.data) {
+ memcpy(&mi_offset, data.data, sizeof(mi_offset.ui));
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(mi_offset);
+ hdrNum = mi_offset.ui;
+ }
+
+ if (alloc) {
+ /* Rather complicated "increment by one", bswapping as needed */
+ ++hdrNum;
+ mi_offset.ui = hdrNum;
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(mi_offset);
+ if (ret == 0 && data.data) {
+ memcpy(data.data, &mi_offset, sizeof(mi_offset.ui));
+ } else {
+ data.data = &mi_offset;
+ data.size = sizeof(mi_offset.ui);
+ }
+
+ /* Unless we manage to insert the new instance number, we failed */
+ ret = dbiCursorPut(dbc, &key, &data, DB_KEYLAST);
+ if (ret) {
+ hdrNum = 0;
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) allocating new package instance\n"), ret);
+ }
+ }
+ dbiCursorFree(dbi, dbc);
+ }
+
+ return hdrNum;
+}
+
+static rpmRC db3_pkgdbPut(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum,
+ unsigned char *hdrBlob, unsigned int hdrLen)
+{
+ DBT hdr;
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.data = hdrBlob;
+ hdr.size = hdrLen;
+ return updatePackages(dbc, hdrNum, &hdr);
+}
+
+static rpmRC db3_pkgdbDel(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum)
+{
+ return updatePackages(dbc, hdrNum, NULL);
+}
+
+static rpmRC db3_pkgdbGet(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum,
+ unsigned char **hdrBlob, unsigned int *hdrLen)
+{
+ DBT key, data;
+ union _dbswap mi_offset;
+ int rc;
+
+ if (dbc == NULL)
+ return RPMRC_FAIL;
+
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+
+ if (hdrNum) {
+ mi_offset.ui = hdrNum;
+ if (dbiByteSwapped(dbc->dbi) == 1)
+ _DBSWAP(mi_offset);
+ key.data = (void *) &mi_offset;
+ key.size = sizeof(mi_offset.ui);
+ }
+
+#if !defined(_USE_COPY_LOAD)
+ data.flags |= DB_DBT_MALLOC;
+#endif
+ rc = dbiCursorGet(dbc, &key, &data, hdrNum ? DB_SET : DB_NEXT);
+ if (rc == 0) {
+ if (hdrBlob)
+ *hdrBlob = data.data;
+ if (hdrLen)
+ *hdrLen = data.size;
+ return RPMRC_OK;
+ } else if (rc == DB_NOTFOUND)
+ return RPMRC_NOTFOUND;
+ else
+ return RPMRC_FAIL;
+}
+
+static unsigned int db3_pkgdbKey(dbiIndex dbi, dbiCursor dbc)
+{
+ union _dbswap mi_offset;
+
+ if (dbc == NULL || dbc->key == NULL)
+ return 0;
+ memcpy(&mi_offset, dbc->key, sizeof(mi_offset.ui));
+ if (dbiByteSwapped(dbc->dbi) == 1)
+ _DBSWAP(mi_offset);
+ return mi_offset.ui;
+}
+
+static rpmRC db3_pkgdbNew(dbiIndex dbi, dbiCursor dbc, unsigned int *hdrNum)
+{
+ unsigned int num;
+ if (dbc == NULL)
+ return RPMRC_FAIL;
+ num = pkgInstance(dbc->dbi, 1);
+ if (!num)
+ return RPMRC_FAIL;
+ *hdrNum = num;
+ return RPMRC_OK;
+}
+
+struct rpmdbOps_s db3_dbops = {
+ .open = db3_dbiOpen,
+ .close = db3_dbiClose,
+ .verify = db3_dbiVerify,
+
+ .setFSync = db3_dbSetFSync,
+ .ctrl = db3_Ctrl,
+
+ .cursorInit = db3_dbiCursorInit,
+ .cursorFree = db3_dbiCursorFree,
+
+ .pkgdbGet = db3_pkgdbGet,
+ .pkgdbPut = db3_pkgdbPut,
+ .pkgdbDel = db3_pkgdbDel,
+ .pkgdbNew = db3_pkgdbNew,
+ .pkgdbKey = db3_pkgdbKey,
+
+ .idxdbGet = db3_idxdbGet,
+ .idxdbPut = db3_idxdbPut,
+ .idxdbDel = db3_idxdbDel,
+ .idxdbKey = db3_idxdbKey
+};
diff --git a/lib/backend/dbconfig.c b/lib/backend/dbconfig.c
index 446be13f9..fe7c01e81 100644
--- a/lib/backend/dbconfig.c
+++ b/lib/backend/dbconfig.c
@@ -71,10 +71,8 @@ static const struct poptOption rdbOptions[] = {
NULL, NULL },
{ "lockdbfd", 0,POPT_ARG_NONE, &staticdbi.dbi_lockdbfd, 0,
NULL, NULL },
-#ifndef WITH_EXTERNAL_DB
{ "nofsync", 0,POPT_BIT_SET, &staticdbi.dbi_oflags, DB_NOFSYNC,
NULL, NULL },
-#endif
POPT_TABLEEND
};
diff --git a/lib/backend/dbi.c b/lib/backend/dbi.c
new file mode 100644
index 000000000..e99a5f2b2
--- /dev/null
+++ b/lib/backend/dbi.c
@@ -0,0 +1,184 @@
+/** \ingroup rpmdb
+ * \file lib/dbi.c
+ */
+
+#include "system.h"
+
+#include <stdlib.h>
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmlog.h>
+#include "lib/rpmdb_internal.h"
+#include "debug.h"
+
+
+dbiIndex dbiFree(dbiIndex dbi)
+{
+ if (dbi) {
+ free(dbi);
+ }
+ return NULL;
+}
+
+dbiIndex dbiNew(rpmdb rdb, rpmDbiTagVal rpmtag)
+{
+ dbiIndex dbi = xcalloc(1, sizeof(*dbi));
+ /* FIX: figger lib/dbi refcounts */
+ dbi->dbi_rpmdb = rdb;
+ dbi->dbi_file = rpmTagGetName(rpmtag);
+ dbi->dbi_type = (rpmtag == RPMDBI_PACKAGES) ? DBI_PRIMARY : DBI_SECONDARY;
+ dbi->dbi_byteswapped = -1; /* -1 unknown, 0 native order, 1 alien order */
+ return dbi;
+}
+
+static void
+dbDetectBackend(rpmdb rdb)
+{
+ const char *dbhome = rpmdbHome(rdb);
+ char *db_backend = rpmExpand("%{?_db_backend}", NULL);
+ char *path = NULL;
+
+#if defined(WITH_LMDB)
+ if (!strcmp(db_backend, "lmdb")) {
+ rdb->db_ops = &lmdb_dbops;
+ } else
+#endif
+#ifdef ENABLE_NDB
+ if (!strcmp(db_backend, "ndb")) {
+ rdb->db_ops = &ndb_dbops;
+ } else
+#endif
+ {
+ rdb->db_ops = &db3_dbops;
+ if (*db_backend == '\0') {
+ free(db_backend);
+ db_backend = xstrdup("bdb");
+ }
+ }
+
+#if defined(WITH_LMDB)
+ path = rstrscat(NULL, dbhome, "/data.mdb", NULL);
+ if (access(path, F_OK) == 0 && rdb->db_ops != &lmdb_dbops) {
+ rdb->db_ops = &lmdb_dbops;
+ rpmlog(RPMLOG_WARNING, _("Found LMDB data.mdb database while attempting %s backend: using lmdb backend.\n"), db_backend);
+ }
+ free(path);
+#endif
+
+#ifdef ENABLE_NDB
+ path = rstrscat(NULL, dbhome, "/Packages.db", NULL);
+ if (access(path, F_OK) == 0 && rdb->db_ops != &ndb_dbops) {
+ rdb->db_ops = &ndb_dbops;
+ rpmlog(RPMLOG_WARNING, _("Found NDB Packages.db database while attempting %s backend: using ndb backend.\n"), db_backend);
+ }
+ free(path);
+#endif
+
+ path = rstrscat(NULL, dbhome, "/Packages", NULL);
+ if (access(path, F_OK) == 0 && rdb->db_ops != &db3_dbops) {
+ rdb->db_ops = &db3_dbops;
+ rpmlog(RPMLOG_WARNING, _("Found BDB Packages database while attempting %s backend: using bdb backend.\n"), db_backend);
+ }
+ free(path);
+
+ if (db_backend)
+ free(db_backend);
+}
+
+const char * dbiName(dbiIndex dbi)
+{
+ return dbi->dbi_file;
+}
+
+int dbiFlags(dbiIndex dbi)
+{
+ return dbi->dbi_flags;
+}
+
+void dbSetFSync(rpmdb rdb, int enable)
+{
+ if (!rdb->db_ops)
+ dbDetectBackend(rdb);
+ rdb->db_ops->setFSync(rdb, enable);
+}
+
+int dbCtrl(rpmdb rdb, dbCtrlOp ctrl)
+{
+ if (!rdb->db_ops)
+ dbDetectBackend(rdb);
+ return rdb->db_ops->ctrl(rdb, ctrl);
+}
+
+int dbiOpen(rpmdb rdb, rpmDbiTagVal rpmtag, dbiIndex * dbip, int flags)
+{
+ if (!rdb->db_ops)
+ dbDetectBackend(rdb);
+ return rdb->db_ops->open(rdb, rpmtag, dbip, flags);
+}
+
+int dbiClose(dbiIndex dbi, unsigned int flags)
+{
+ return dbi ? dbi->dbi_rpmdb->db_ops->close(dbi, flags) : 0;
+}
+
+int dbiVerify(dbiIndex dbi, unsigned int flags)
+{
+ return dbi->dbi_rpmdb->db_ops->verify(dbi, flags);
+}
+
+dbiCursor dbiCursorInit(dbiIndex dbi, unsigned int flags)
+{
+ return dbi->dbi_rpmdb->db_ops->cursorInit(dbi, flags);
+}
+
+dbiCursor dbiCursorFree(dbiIndex dbi, dbiCursor dbc)
+{
+ return dbi->dbi_rpmdb->db_ops->cursorFree(dbi, dbc);
+}
+
+rpmRC pkgdbPut(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum, unsigned char *hdrBlob, unsigned int hdrLen)
+{
+ return dbi->dbi_rpmdb->db_ops->pkgdbPut(dbi, dbc, hdrNum, hdrBlob, hdrLen);
+}
+
+rpmRC pkgdbDel(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum)
+{
+ return dbi->dbi_rpmdb->db_ops->pkgdbDel(dbi, dbc, hdrNum);
+}
+
+rpmRC pkgdbGet(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum, unsigned char **hdrBlob, unsigned int *hdrLen)
+{
+ return dbi->dbi_rpmdb->db_ops->pkgdbGet(dbi, dbc, hdrNum, hdrBlob, hdrLen);
+}
+
+rpmRC pkgdbNew(dbiIndex dbi, dbiCursor dbc, unsigned int *hdrNum)
+{
+ return dbi->dbi_rpmdb->db_ops->pkgdbNew(dbi, dbc, hdrNum);
+}
+
+unsigned int pkgdbKey(dbiIndex dbi, dbiCursor dbc)
+{
+ return dbi->dbi_rpmdb->db_ops->pkgdbKey(dbi, dbc);
+}
+
+rpmRC idxdbGet(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen, dbiIndexSet *set, int curFlags)
+{
+ return dbi->dbi_rpmdb->db_ops->idxdbGet(dbi, dbc, keyp, keylen, set, curFlags);
+}
+
+rpmRC idxdbPut(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen, dbiIndexItem rec)
+{
+ return dbi->dbi_rpmdb->db_ops->idxdbPut(dbi, dbc, keyp, keylen, rec);
+}
+
+rpmRC idxdbDel(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen, dbiIndexItem rec)
+{
+ return dbi->dbi_rpmdb->db_ops->idxdbDel(dbi, dbc, keyp, keylen, rec);
+}
+
+const void * idxdbKey(dbiIndex dbi, dbiCursor dbc, unsigned int *keylen)
+{
+ return dbi->dbi_rpmdb->db_ops->idxdbKey(dbi, dbc, keylen);
+}
+
diff --git a/lib/backend/dbi.h b/lib/backend/dbi.h
index 3d848a8a0..1833da263 100644
--- a/lib/backend/dbi.h
+++ b/lib/backend/dbi.h
@@ -1,12 +1,25 @@
#ifndef _DBI_H
#define _DBI_H
+#include "dbiset.h"
+
+/* XXX: make this backend-specific, eliminate or something... */
+#define _USE_COPY_LOAD
+
enum rpmdbFlags {
RPMDB_FLAG_JUSTCHECK = (1 << 0),
RPMDB_FLAG_REBUILD = (1 << 1),
RPMDB_FLAG_VERIFYONLY = (1 << 2),
};
+typedef enum dbCtrlOp_e {
+ DB_CTRL_LOCK_RO = 1,
+ DB_CTRL_UNLOCK_RO = 2,
+ DB_CTRL_LOCK_RW = 3,
+ DB_CTRL_UNLOCK_RW = 4,
+ DB_CTRL_INDEXSYNC = 5
+} dbCtrlOp;
+
typedef struct dbiIndex_s * dbiIndex;
typedef struct dbiCursor_s * dbiCursor;
@@ -15,8 +28,17 @@ struct dbConfig_s {
int db_cachesize; /*!< (128Kb) */
int db_verbose;
int db_no_fsync; /*!< no-op fsync for db */
+ int db_eflags; /*!< obsolete */
+};
+
+struct dbiConfig_s {
+ int dbi_oflags; /*!< open flags */
+ int dbi_no_dbsync; /*!< don't call dbiSync */
+ int dbi_lockdbfd; /*!< do fcntl lock on db fd */
};
+struct rpmdbOps_s;
+
/** \ingroup rpmdb
* Describes the collection of index databases used by rpm.
*/
@@ -27,16 +49,20 @@ struct rpmdb_s {
int db_flags;
int db_mode; /*!< open mode */
int db_perms; /*!< open permissions */
- int db_ver; /*!< Berkeley DB version */
+ char * db_descr; /*!< db backend description (for error msgs) */
struct dbChk_s * db_checked;/*!< headerCheck()'ed package instances */
rpmdb db_next;
int db_opens;
+ dbiIndex db_pkgs; /*!< Package db */
+ const rpmDbiTag * db_tags;
int db_ndbi; /*!< No. of tag indices. */
- dbiIndex * _dbi; /*!< Tag indices. */
+ dbiIndex * db_indexes; /*!< Tag indices. */
int db_buildindex; /*!< Index rebuild indicator */
+ struct rpmdbOps_s * db_ops; /*!< backend ops */
+
/* dbenv and related parameters */
- void * db_dbenv; /*!< Berkeley DB_ENV handle. */
+ void * db_dbenv; /*!< Backend private handle */
struct dbConfig_s cfg;
int db_remove_env;
@@ -59,24 +85,44 @@ enum dbiFlags_e {
DBI_RDONLY = (1 << 1),
};
+enum dbcFlags_e {
+ DBC_READ = 0,
+ DBC_WRITE = (1 << 0),
+};
+
+enum dbcSearchType_e {
+ DBC_NORMAL_SEARCH = 0,
+ DBC_PREFIX_SEARCH = (1 << 0),
+};
+
/** \ingroup dbi
* Describes an index database (implemented on Berkeley db functionality).
*/
struct dbiIndex_s {
+ rpmdb dbi_rpmdb; /*!< the parent rpm database */
+ dbiIndexType dbi_type; /*! Type of dbi (primary / index) */
const char * dbi_file; /*!< file component of path */
-
- int dbi_oflags; /*!< db->open flags */
- int dbi_permit_dups; /*!< permit duplicate entries? */
- int dbi_no_dbsync; /*!< don't call dbiSync */
- int dbi_lockdbfd; /*!< do fcntl lock on db fd */
+ int dbi_flags;
int dbi_byteswapped;
- rpmdb dbi_rpmdb; /*!< the parent rpm database */
- dbiIndexType dbi_type; /*! Type of dbi (primary / index) */
+ struct dbiConfig_s cfg;
+
+ void * dbi_db; /*!< Backend private handle */
+};
- DB * dbi_db; /*!< Berkeley DB * handle */
+union _dbswap {
+ unsigned int ui;
+ unsigned char uc[4];
};
+#define _DBSWAP(_a) \
+\
+ { unsigned char _b, *_c = (_a).uc; \
+ _b = _c[3]; _c[3] = _c[0]; _c[0] = _b; \
+ _b = _c[2]; _c[2] = _c[1]; _c[1] = _b; \
+\
+ }
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -102,7 +148,10 @@ int dbiResumeDBLock(dbiIndex dbi, unsigned int flags);
RPM_GNUC_INTERNAL
/* Globally enable/disable fsync in the backend */
-void dbSetFSync(void *dbenv, int enable);
+void dbSetFSync(rpmdb rdb, int enable);
+
+RPM_GNUC_INTERNAL
+int dbCtrl(rpmdb rdb, dbCtrlOp ctrl);
/** \ingroup dbi
* Return new configured index database handle instance.
@@ -122,15 +171,6 @@ RPM_GNUC_INTERNAL
dbiIndex dbiFree( dbiIndex dbi);
/** \ingroup dbi
- * Format dbi open flags for debugging print.
- * @param dbflags db open flags
- * @param print_dbenv_flags format db env flags instead?
- * @return formatted flags (malloced)
- */
-RPM_GNUC_INTERNAL
-char * prDbiOpenFlags(int dbflags, int print_dbenv_flags);
-
-/** \ingroup dbi
* Actually open the database of the index.
* @param db rpm database
* @param rpmtag database index tag
@@ -151,15 +191,6 @@ RPM_GNUC_INTERNAL
int dbiClose(dbiIndex dbi, unsigned int flags);
/** \ingroup dbi
- * Flush pending operations to disk.
- * @param dbi index database handle
- * @param flags (unused)
- * @return 0 on success
- */
-RPM_GNUC_INTERNAL
-int dbiSync (dbiIndex dbi, unsigned int flags);
-
-/** \ingroup dbi
* Verify (and close) index database.
* @param dbi index database handle
* @param flags (unused)
@@ -169,22 +200,6 @@ RPM_GNUC_INTERNAL
int dbiVerify(dbiIndex dbi, unsigned int flags);
/** \ingroup dbi
- * Is database byte swapped?
- * @param dbi index database handle
- * @return 0 same order, 1 swapped order
- */
-RPM_GNUC_INTERNAL
-int dbiByteSwapped(dbiIndex dbi);
-
-/** \ingroup dbi
- * Type of dbi (primary data / index)
- * @param dbi index database handle
- * @return type of dbi
- */
-RPM_GNUC_INTERNAL
-dbiIndexType dbiType(dbiIndex dbi);
-
-/** \ingroup dbi
* Retrieve index control flags (new/existing, read-only etc)
* @param dbi index database handle
* @return dbi control flags
@@ -203,7 +218,7 @@ const char * dbiName(dbiIndex dbi);
/** \ingroup dbi
* Open a database cursor.
* @param dbi index database handle
- * @param flags DB_WRITECURSOR if writing, or 0
+ * @param flags DBC_WRITE if writing, or 0 (DBC_READ) for reading
* @return database cursor handle
*/
RPM_GNUC_INTERNAL
@@ -215,56 +230,69 @@ dbiCursor dbiCursorInit(dbiIndex dbi, unsigned int flags);
* @return NULL always
*/
RPM_GNUC_INTERNAL
-dbiCursor dbiCursorFree(dbiCursor dbc);
+dbiCursor dbiCursorFree(dbiIndex dbi, dbiCursor dbc);
-/** \ingroup dbi
- * Store (key,data) pair in index database.
- * @param dbcursor database cursor handle
- * @param key store key value/length/flags
- * @param data store data value/length/flags
- * @param flags flags
- * @return 0 on success
- */
+
+RPM_GNUC_INTERNAL
+rpmRC pkgdbPut(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum,
+ unsigned char *hdrBlob, unsigned int hdrLen);
+RPM_GNUC_INTERNAL
+rpmRC pkgdbDel(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum);
RPM_GNUC_INTERNAL
-int dbiCursorPut(dbiCursor dbc, DBT * key, DBT * data, unsigned int flags);
+rpmRC pkgdbGet(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum,
+ unsigned char **hdrBlob, unsigned int *hdrLen);
+RPM_GNUC_INTERNAL
+rpmRC pkgdbNew(dbiIndex dbi, dbiCursor dbc, unsigned int *hdrNum);
+RPM_GNUC_INTERNAL
+unsigned int pkgdbKey(dbiIndex dbi, dbiCursor dbc);
-/** \ingroup dbi
- * Retrieve (key,data) pair from index database.
- * @param dbc database cursor handle
- * @param key retrieve key value/length/flags
- * @param data retrieve data value/length/flags
- * @param flags flags
- * @return 0 on success
- */
RPM_GNUC_INTERNAL
-int dbiCursorGet(dbiCursor dbc, DBT * key, DBT * data, unsigned int flags);
+rpmRC idxdbGet(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen,
+ dbiIndexSet *set, int curFlags);
+RPM_GNUC_INTERNAL
+rpmRC idxdbPut(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen,
+ dbiIndexItem rec);
+RPM_GNUC_INTERNAL
+rpmRC idxdbDel(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen,
+ dbiIndexItem rec);
+RPM_GNUC_INTERNAL
+const void * idxdbKey(dbiIndex dbi, dbiCursor dbc, unsigned int *keylen);
+
+struct rpmdbOps_s {
+ int (*open)(rpmdb rdb, rpmDbiTagVal rpmtag, dbiIndex * dbip, int flags);
+ int (*close)(dbiIndex dbi, unsigned int flags);
+ int (*verify)(dbiIndex dbi, unsigned int flags);
+ void (*setFSync)(rpmdb rdb, int enable);
+ int (*ctrl)(rpmdb rdb, dbCtrlOp ctrl);
+
+ dbiCursor (*cursorInit)(dbiIndex dbi, unsigned int flags);
+ dbiCursor (*cursorFree)(dbiIndex dbi, dbiCursor dbc);
+
+ rpmRC (*pkgdbGet)(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum, unsigned char **hdrBlob, unsigned int *hdrLen);
+ rpmRC (*pkgdbPut)(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum, unsigned char *hdrBlob, unsigned int hdrLen);
+ rpmRC (*pkgdbDel)(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum);
+ rpmRC (*pkgdbNew)(dbiIndex dbi, dbiCursor dbc, unsigned int *hdrNum);
+ unsigned int (*pkgdbKey)(dbiIndex dbi, dbiCursor dbc);
+
+ rpmRC (*idxdbGet)(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen, dbiIndexSet *set, int curFlags);
+ rpmRC (*idxdbPut)(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen, dbiIndexItem rec);
+ rpmRC (*idxdbDel)(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen, dbiIndexItem rec);
+ const void * (*idxdbKey)(dbiIndex dbi, dbiCursor dbc, unsigned int *keylen);
+};
-/** \ingroup dbi
- * Delete (key,data) pair(s) from index database.
- * @param dbc database cursor handle
- * @param key delete key value/length/flags
- * @param data delete data value/length/flags
- * @param flags flags
- * @return 0 on success
- */
RPM_GNUC_INTERNAL
-int dbiCursorDel(dbiCursor dbc, DBT * key, DBT * data, unsigned int flags);
+extern struct rpmdbOps_s db3_dbops;
-/** \ingroup dbi
- * Retrieve count of (possible) duplicate items.
- * @param dbcursor database cursor
- * @return number of duplicates
- */
+#ifdef ENABLE_NDB
RPM_GNUC_INTERNAL
-unsigned int dbiCursorCount(dbiCursor dbc);
+extern struct rpmdbOps_s ndb_dbops;
+#endif
-/** \ingroup dbi
- * Retrieve underlying index database handle.
- * @param dbcursor database cursor
- * @return index database handle
- */
+#if defined(WITH_LMDB)
RPM_GNUC_INTERNAL
-dbiIndex dbiCursorIndex(dbiCursor dbc);
+extern struct rpmdbOps_s lmdb_dbops;
+#endif
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/backend/dbiset.c b/lib/backend/dbiset.c
new file mode 100644
index 000000000..8fb922ef7
--- /dev/null
+++ b/lib/backend/dbiset.c
@@ -0,0 +1,214 @@
+#include "system.h"
+#include <string.h>
+#include <stdlib.h>
+#include "dbiset.h"
+#include "debug.h"
+
+dbiIndexSet dbiIndexSetNew(unsigned int sizehint)
+{
+ dbiIndexSet set = xcalloc(1, sizeof(*set));
+ if (sizehint > 0)
+ dbiIndexSetGrow(set, sizehint);
+ return set;
+}
+
+/*
+ * Ensure sufficient memory for nrecs of new records in dbiIndexSet.
+ * Allocate in power of two sizes to avoid memory fragmentation, so
+ * realloc is not always needed.
+ */
+void dbiIndexSetGrow(dbiIndexSet set, unsigned int nrecs)
+{
+ size_t need = (set->count + nrecs) * sizeof(*(set->recs));
+ size_t alloced = set->alloced ? set->alloced : 1 << 4;
+
+ while (alloced < need)
+ alloced <<= 1;
+
+ if (alloced != set->alloced) {
+ set->recs = xrealloc(set->recs, alloced);
+ set->alloced = alloced;
+ }
+}
+
+static int hdrNumCmp(const void * one, const void * two)
+{
+ const struct dbiIndexItem_s *a = one, *b = two;
+ if (a->hdrNum - b->hdrNum != 0)
+ return a->hdrNum - b->hdrNum;
+ return a->tagNum - b->tagNum;
+}
+
+void dbiIndexSetSort(dbiIndexSet set)
+{
+ /*
+ * mergesort is much (~10x with lots of identical basenames) faster
+ * than pure quicksort, but glibc uses msort_with_tmp() on stack.
+ */
+ if (set && set->recs && set->count > 1) {
+#if HAVE_MERGESORT
+ mergesort(set->recs, set->count, sizeof(*set->recs), hdrNumCmp);
+#else
+ qsort(set->recs, set->count, sizeof(*set->recs), hdrNumCmp);
+#endif
+ }
+}
+
+void dbiIndexSetUniq(dbiIndexSet set, int sorted)
+{
+ unsigned int from;
+ unsigned int to = 0;
+ unsigned int num = set->count;
+
+ if (set->count < 2)
+ return;
+
+ if (!sorted)
+ dbiIndexSetSort(set);
+
+ for (from = 0; from < num; from++) {
+ if (from > 0 && set->recs[from - 1].hdrNum == set->recs[from].hdrNum) {
+ set->count--;
+ continue;
+ }
+ if (from != to)
+ set->recs[to] = set->recs[from]; /* structure assignment */
+ to++;
+ }
+}
+
+int dbiIndexSetAppend(dbiIndexSet set, dbiIndexItem recs,
+ unsigned int nrecs, int sortset)
+{
+ if (set == NULL || recs == NULL)
+ return 1;
+
+ if (nrecs) {
+ dbiIndexSetGrow(set, nrecs);
+ memcpy(set->recs + set->count, recs, nrecs * sizeof(*(set->recs)));
+ set->count += nrecs;
+ }
+
+ if (sortset && set->count > 1)
+ qsort(set->recs, set->count, sizeof(*(set->recs)), hdrNumCmp);
+
+ return 0;
+}
+
+int dbiIndexSetAppendSet(dbiIndexSet set, dbiIndexSet oset, int sortset)
+{
+ if (oset == NULL)
+ return 1;
+ return dbiIndexSetAppend(set, oset->recs, oset->count, sortset);
+}
+
+int dbiIndexSetAppendOne(dbiIndexSet set, unsigned int hdrNum,
+ unsigned int tagNum, int sortset)
+{
+ if (set == NULL)
+ return 1;
+ dbiIndexSetGrow(set, 1);
+
+ set->recs[set->count].hdrNum = hdrNum;
+ set->recs[set->count].tagNum = tagNum;
+ set->count += 1;
+
+ if (sortset && set->count > 1)
+ qsort(set->recs, set->count, sizeof(*(set->recs)), hdrNumCmp);
+
+ return 0;
+}
+
+int dbiIndexSetPrune(dbiIndexSet set, dbiIndexItem recs,
+ unsigned int nrecs, int sorted)
+{
+ unsigned int from;
+ unsigned int to = 0;
+ unsigned int num = set->count;
+ unsigned int numCopied = 0;
+ size_t recsize = sizeof(*recs);
+
+ if (num == 0 || nrecs == 0)
+ return 1;
+
+ if (nrecs > 1 && !sorted)
+ qsort(recs, nrecs, recsize, hdrNumCmp);
+
+ for (from = 0; from < num; from++) {
+ if (bsearch(&set->recs[from], recs, nrecs, recsize, hdrNumCmp)) {
+ set->count--;
+ continue;
+ }
+ if (from != to)
+ set->recs[to] = set->recs[from]; /* structure assignment */
+ to++;
+ numCopied++;
+ }
+ return (numCopied == num);
+}
+
+int dbiIndexSetPruneSet(dbiIndexSet set, dbiIndexSet oset, int sortset)
+{
+ if (oset == NULL)
+ return 1;
+ return dbiIndexSetPrune(set, oset->recs, oset->count, sortset);
+}
+
+int dbiIndexSetFilter(dbiIndexSet set, dbiIndexItem recs,
+ unsigned int nrecs, int sorted)
+{
+ unsigned int from;
+ unsigned int to = 0;
+ unsigned int num = set->count;
+ unsigned int numCopied = 0;
+ size_t recsize = sizeof(*recs);
+
+ if (num == 0 || nrecs == 0) {
+ set->count = 0;
+ return num ? 0 : 1;
+ }
+ if (nrecs > 1 && !sorted)
+ qsort(recs, nrecs, recsize, hdrNumCmp);
+ for (from = 0; from < num; from++) {
+ if (!bsearch(&set->recs[from], recs, nrecs, recsize, hdrNumCmp)) {
+ set->count--;
+ continue;
+ }
+ if (from != to)
+ set->recs[to] = set->recs[from]; /* structure assignment */
+ to++;
+ numCopied++;
+ }
+ return (numCopied == num);
+}
+
+int dbiIndexSetFilterSet(dbiIndexSet set, dbiIndexSet oset, int sorted)
+{
+ return dbiIndexSetFilter(set, oset->recs, oset->count, sorted);
+}
+
+unsigned int dbiIndexSetCount(dbiIndexSet set)
+{
+ return (set != NULL) ? set->count : 0;
+}
+
+unsigned int dbiIndexRecordOffset(dbiIndexSet set, unsigned int recno)
+{
+ return set->recs[recno].hdrNum;
+}
+
+unsigned int dbiIndexRecordFileNumber(dbiIndexSet set, unsigned int recno)
+{
+ return set->recs[recno].tagNum;
+}
+
+dbiIndexSet dbiIndexSetFree(dbiIndexSet set)
+{
+ if (set) {
+ free(set->recs);
+ memset(set, 0, sizeof(*set)); /* trash and burn */
+ free(set);
+ }
+ return NULL;
+}
+
diff --git a/lib/backend/dbiset.h b/lib/backend/dbiset.h
new file mode 100644
index 000000000..da196c865
--- /dev/null
+++ b/lib/backend/dbiset.h
@@ -0,0 +1,130 @@
+#ifndef _DBISET_H
+#define _DBISET_H
+
+#include <rpm/rpmutil.h>
+
+/* A single item from an index database (i.e. the "data returned"). */
+typedef struct dbiIndexItem_s {
+ unsigned int hdrNum; /*!< header instance in db */
+ unsigned int tagNum; /*!< tag index in header */
+} * dbiIndexItem;
+
+/* Items retrieved from the index database.*/
+typedef struct dbiIndexSet_s {
+ dbiIndexItem recs; /*!< array of records */
+ unsigned int count; /*!< number of records */
+ size_t alloced; /*!< alloced size */
+} * dbiIndexSet;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Create an empty index set, optionally with sizehint reservation for recs */
+RPM_GNUC_INTERNAL
+dbiIndexSet dbiIndexSetNew(unsigned int sizehint);
+
+/* Reserve space for at least nrecs new records */
+RPM_GNUC_INTERNAL
+void dbiIndexSetGrow(dbiIndexSet set, unsigned int nrecs);
+
+/* Sort an index set */
+RPM_GNUC_INTERNAL
+void dbiIndexSetSort(dbiIndexSet set);
+
+/* Uniq an index set */
+RPM_GNUC_INTERNAL
+void dbiIndexSetUniq(dbiIndexSet set, int sorted);
+
+/* Append an index set to another */
+RPM_GNUC_INTERNAL
+int dbiIndexSetAppendSet(dbiIndexSet set, dbiIndexSet oset, int sortset);
+
+/**
+ * Append element(s) to set of index database items.
+ * @param set set of index database items
+ * @param recs array of items to append to set
+ * @param nrecs number of items
+ * @param sortset should resulting set be sorted?
+ * @return 0 success, 1 failure (bad args)
+ */
+RPM_GNUC_INTERNAL
+int dbiIndexSetAppend(dbiIndexSet set, dbiIndexItem recs,
+ unsigned int nrecs, int sortset);
+
+/**
+ * Append a single element to a set of index database items.
+ * @param set set of index database items
+ * @param hdrNum header instance in db
+ * @param tagNum tag index in header
+ * @param sortset should resulting set be sorted?
+ * @return 0 success, 1 failure (bad args)
+ */
+RPM_GNUC_INTERNAL
+int dbiIndexSetAppendOne(dbiIndexSet set, unsigned int hdrNum,
+ unsigned int tagNum, int sortset);
+
+/**
+ * Remove element(s) from set of index database items.
+ * @param set set of index database items
+ * @param recs array of items to remove from set
+ * @param nrecs number of items
+ * @param sorted array is already sorted?
+ * @return 0 success, 1 failure (no items found)
+ */
+RPM_GNUC_INTERNAL
+int dbiIndexSetPrune(dbiIndexSet set, dbiIndexItem recs,
+ unsigned int nrecs, int sorted);
+
+/**
+ * Remove an index set from another.
+ * @param set set of index database items
+ * @param oset set of entries that should be removed
+ * @param sorted oset is already sorted?
+ * @return 0 success, 1 failure (no items found)
+ */
+RPM_GNUC_INTERNAL
+int dbiIndexSetPruneSet(dbiIndexSet set, dbiIndexSet oset, int sorted);
+
+/**
+ * Filter element(s) from set of index database items.
+ * @param set set of index database items
+ * @param recs array of items to remove from set
+ * @param nrecs number of items
+ * @param sorted recs array is already sorted?
+ * @return 0 success, 1 failure (no items removed)
+ */
+RPM_GNUC_INTERNAL
+int dbiIndexSetFilter(dbiIndexSet set, dbiIndexItem recs,
+ unsigned int nrecs, int sorted);
+
+/**
+ * Filter (intersect) an index set with another.
+ * @param set set of index database items
+ * @param oset set of entries that should be intersected
+ * @param sorted oset is already sorted?
+ * @return 0 success, 1 failure (no items removed)
+ */
+RPM_GNUC_INTERNAL
+int dbiIndexSetFilterSet(dbiIndexSet set, dbiIndexSet oset, int sorted);
+
+/* Count items in index database set. */
+RPM_GNUC_INTERNAL
+unsigned int dbiIndexSetCount(dbiIndexSet set);
+
+/* Return record offset of header from element in index database set. */
+RPM_GNUC_INTERNAL
+unsigned int dbiIndexRecordOffset(dbiIndexSet set, unsigned int recno);
+
+/* Return file index from element in index database set. */
+RPM_GNUC_INTERNAL
+unsigned int dbiIndexRecordFileNumber(dbiIndexSet set, unsigned int recno);
+
+/* Destroy set of index database items */
+RPM_GNUC_INTERNAL
+dbiIndexSet dbiIndexSetFree(dbiIndexSet set);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/lib/backend/lmdb.c b/lib/backend/lmdb.c
new file mode 100644
index 000000000..db1270ebc
--- /dev/null
+++ b/lib/backend/lmdb.c
@@ -0,0 +1,939 @@
+/** \ingroup rpmdb
+ * \file lib/lmdb.c
+ */
+
+#include "system.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <popt.h>
+#include <lmdb.h>
+#include <signal.h>
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmfileutil.h>
+#include <rpm/rpmlog.h>
+
+#include "lib/rpmdb_internal.h"
+
+#include "debug.h"
+
+static int _debug = 1; /* XXX if < 0 debugging, > 0 unusual error returns */
+
+struct dbiCursor_s {
+ dbiIndex dbi;
+ const void *key;
+ unsigned int keylen;
+ int flags;
+ MDB_cursor * cursor;
+ MDB_txn * txn;
+};
+
+static const char * _EnvF(unsigned eflags)
+{
+ static char t[256];
+ char *te = t;
+
+ *te = '\0';
+#define _EF(_v) if (eflags & MDB_##_v) te = stpcpy(stpcpy(te,"|"),#_v)
+ _EF(FIXEDMAP);
+ _EF(NOSUBDIR);
+ _EF(NOSYNC);
+ _EF(RDONLY);
+ _EF(NOMETASYNC);
+ _EF(WRITEMAP);
+ _EF(MAPASYNC);
+ _EF(NOTLS);
+ _EF(NOLOCK);
+ _EF(NORDAHEAD);
+ _EF(NOMEMINIT);
+#undef _EF
+ if (t[0] == '\0') te += sprintf(te, "|0x%x", eflags);
+ *te = '\0';
+ return t+1;
+}
+
+static const char * _OpenF(unsigned oflags)
+{
+ static char t[256];
+ char *te = t;
+
+ *te = '\0';
+#define _OF(_v) if (oflags & MDB_##_v) te = stpcpy(stpcpy(te,"|"),#_v)
+ _OF(REVERSEKEY);
+ _OF(DUPSORT);
+ _OF(INTEGERKEY);
+ _OF(DUPFIXED);
+ _OF(INTEGERDUP);
+ _OF(REVERSEDUP);
+ _OF(CREATE);
+#undef _OF
+ if (t[0] == '\0') te += sprintf(te, "|0x%x", oflags);
+ *te = '\0';
+ return t+1;
+}
+
+static int dbapi_err(rpmdb rdb, const char * msg, int rc, int printit)
+{
+ if (printit && rc) {
+ int lvl = RPMLOG_ERR;
+ if (msg)
+ rpmlog(lvl, _("%s:\trc(%d) = %s(): %s\n"),
+ rdb->db_descr, rc, msg, (rc ? mdb_strerror(rc) : ""));
+ else
+ rpmlog(lvl, _("%s:\trc(%d) = %s()\n"),
+ rdb->db_descr, rc, (rc ? mdb_strerror(rc) : ""));
+ }
+ return rc;
+}
+
+static int cvtdberr(dbiIndex dbi, const char * msg, int rc, int printit)
+{
+ return dbapi_err(dbi->dbi_rpmdb, msg, rc, printit);
+}
+
+static void lmdb_assert(MDB_env *env, const char *msg)
+{
+ rpmlog(RPMLOG_ERR, "%s: %s\n", __FUNCTION__, msg);
+}
+
+static void lmdb_dbSetFSync(rpmdb rdb, int enable)
+{
+}
+
+static int lmdb_Ctrl(rpmdb rdb, dbCtrlOp ctrl)
+{
+ return 0;
+}
+
+static int db_fini(rpmdb rdb, const char * dbhome)
+{
+ int rc = 0;
+ MDB_env * env = rdb->db_dbenv;
+
+ if (env == NULL)
+ goto exit;
+ if (--rdb->db_opens > 0)
+ goto exit;
+
+ mdb_env_close(env);
+ rdb->db_dbenv = env = NULL;
+
+ rpmlog(RPMLOG_DEBUG, "closed db environment %s\n", dbhome);
+
+exit:
+ return rc;
+}
+
+static int db_init(rpmdb rdb, const char * dbhome)
+{
+ int rc = EINVAL;
+ MDB_env * env = NULL;
+ int retry_open = 2;
+ uint32_t eflags = 0;
+
+ if (rdb->db_dbenv != NULL) {
+ rdb->db_opens++;
+ return 0;
+ } else {
+ /* On first call, set backend description to something... */
+ free(rdb->db_descr);
+ rdb->db_descr = xstrdup("lmdb");
+ }
+
+ MDB_dbi maxdbs = 32;
+ unsigned int maxreaders = 16;
+ size_t mapsize = 256 * 1024 * 1024;
+
+ if ((rc = mdb_env_create(&env))
+ || (rc = mdb_env_set_maxreaders(env, maxreaders))
+ || (rc = mdb_env_set_mapsize(env, mapsize))
+ || (rc = mdb_env_set_maxdbs(env, maxdbs))
+ || (rc = mdb_env_set_assert(env, lmdb_assert))
+ || (rc = mdb_env_set_userctx(env, rdb))
+ ) {
+ rc = dbapi_err(rdb, "mdb_env_create", rc, _debug);
+ goto exit;
+ }
+
+ /*
+ * Actually open the environment. Fall back to private environment
+ * if we dont have permission to join/create shared environment or
+ * system doesn't support it..
+ */
+ while (retry_open) {
+ rpmlog(RPMLOG_DEBUG, "opening db environment %s eflags=%s perms=0%o\n", dbhome, _EnvF(eflags), rdb->db_perms);
+
+ eflags = 0;
+ eflags |= MDB_WRITEMAP;
+ eflags |= MDB_MAPASYNC;
+ eflags |= MDB_NOTLS;
+
+ if (access(dbhome, W_OK) && (rdb->db_mode & O_ACCMODE) == O_RDONLY)
+ eflags |= MDB_RDONLY;
+
+ rc = mdb_env_open(env, dbhome, eflags, rdb->db_perms);
+ if (rc) {
+ rc = dbapi_err(rdb, "mdb_env_open", rc, _debug);
+ if (rc == EPERM)
+ rpmlog(RPMLOG_ERR, "lmdb: %s(%s/lock.mdb): %s\n", __FUNCTION__, dbhome, mdb_strerror(rc));
+ }
+ retry_open = 0; /* XXX EAGAIN might need a retry */
+ }
+ if (rc)
+ goto exit;
+
+ rdb->db_dbenv = env;
+ rdb->db_opens = 1;
+
+exit:
+ if (rc && env) {
+ mdb_env_close(env);
+ rdb->db_dbenv = env = NULL;
+ }
+ return rc;
+}
+
+static int dbiSync(dbiIndex dbi, unsigned int flags)
+{
+ int rc = 0;
+ MDB_dbi db = (unsigned long) dbi->dbi_db;
+
+ if (db != 0xdeadbeef && !dbi->cfg.dbi_no_dbsync) {
+ MDB_env * env = dbi->dbi_rpmdb->db_dbenv;
+ unsigned eflags = 0;
+ rc = mdb_env_get_flags(env, &eflags);
+ if (rc) {
+ rc = cvtdberr(dbi, "mdb_env_get_flags", rc, _debug);
+ eflags |= MDB_RDONLY;
+ }
+ if (!(eflags & MDB_RDONLY)) {
+ int force = 0;
+ rc = mdb_env_sync(env, force);
+ if (rc)
+ rc = cvtdberr(dbi, "mdb_env_sync", rc, _debug);
+ }
+ }
+ return rc;
+}
+
+static dbiCursor lmdb_dbiCursorInit(dbiIndex dbi, unsigned int flags)
+{
+ dbiCursor dbc = NULL;
+
+ if (dbi && dbi->dbi_db != (void *)0xdeadbeefUL) {
+ MDB_env * env = dbi->dbi_rpmdb->db_dbenv;
+ MDB_txn * parent = NULL;
+ unsigned tflags = !(flags & DBC_WRITE) ? MDB_RDONLY : 0;
+ MDB_txn * txn = NULL;
+ MDB_cursor * cursor = NULL;
+ int rc = EINVAL;
+
+ rc = mdb_txn_begin(env, parent, tflags, &txn);
+ if (rc)
+ rc = cvtdberr(dbi, "mdb_txn_begin", rc, _debug);
+
+ if (rc == 0) {
+ MDB_dbi db = (unsigned long) dbi->dbi_db;
+ rc = mdb_cursor_open(txn, db, &cursor);
+ if (rc)
+ rc = cvtdberr(dbi, "mdb_cursor_open", rc, _debug);
+ }
+
+ if (rc == 0) {
+ dbc = xcalloc(1, sizeof(*dbc));
+ dbc->dbi = dbi;
+ dbc->flags = flags;
+ dbc->cursor = cursor;
+ dbc->txn = txn;
+ }
+ }
+
+ return dbc;
+}
+
+static dbiCursor lmdb_dbiCursorFree(dbiIndex dbi, dbiCursor dbc)
+{
+ if (dbc) {
+ int rc = 0;
+ MDB_cursor * cursor = dbc->cursor;
+ MDB_txn * txn = dbc->txn;
+ dbiIndex dbi = dbc->dbi;
+ unsigned flags = dbc->flags;
+
+ mdb_cursor_close(cursor);
+ dbc->cursor = cursor = NULL;
+ if (rc)
+ cvtdberr(dbc->dbi, "mdb_cursor_close", rc, _debug);
+
+ /* Automatically commit close */
+ if (txn) {
+ rc = mdb_txn_commit(txn);
+ dbc->txn = txn = NULL;
+ if (rc)
+ rc = cvtdberr(dbc->dbi, "mdb_txn_commit", rc, _debug);
+ }
+
+ /* Automatically sync on write-cursor close */
+ if (flags & DBC_WRITE)
+ dbiSync(dbi, 0);
+
+ free(dbc);
+ rc = 0;
+ }
+ return NULL;
+}
+
+static int dbiCursorPut(dbiCursor dbc, MDB_val * key, MDB_val * data, unsigned flags)
+{
+ int rc = EINVAL;
+ int sane = (key->mv_data != NULL && key->mv_size > 0 &&
+ data->mv_data != NULL && data->mv_size > 0);
+
+ if (dbc && sane) {
+ MDB_cursor * cursor = dbc->cursor;
+ rpmdb rdb = dbc->dbi->dbi_rpmdb;
+ rpmswEnter(&rdb->db_putops, (ssize_t) 0);
+
+ rc = mdb_cursor_put(cursor, key, data, flags);
+ if (rc) {
+ rc = cvtdberr(dbc->dbi, "mdb_cursor_put", rc, _debug);
+ if (dbc->txn) {
+ mdb_txn_abort(dbc->txn);
+ dbc->txn = NULL;
+ }
+ }
+
+ rpmswExit(&rdb->db_putops, (ssize_t) data->mv_size);
+ }
+ return rc;
+}
+
+static int dbiCursorGet(dbiCursor dbc, MDB_val *key, MDB_val *data, unsigned op)
+{
+ int rc = EINVAL;
+ int sane = ((op == MDB_NEXT) || (key->mv_data != NULL && key->mv_size > 0));
+
+ if (dbc && sane) {
+ MDB_cursor * cursor = dbc->cursor;
+ rpmdb rdb = dbc->dbi->dbi_rpmdb;
+
+ rpmswEnter(&rdb->db_getops, 0);
+
+ /* XXX db4 does DB_FIRST on uninitialized cursor */
+ rc = mdb_cursor_get(cursor, key, data, op);
+ if (rc && rc != MDB_NOTFOUND) {
+ rc = cvtdberr(dbc->dbi, "mdb_cursor_get", rc, _debug);
+ if (dbc->txn) {
+ mdb_txn_abort(dbc->txn);
+ dbc->txn = NULL;
+ }
+ }
+
+ /* Remember the last key fetched */
+ if (rc == 0) {
+ dbc->key = key->mv_data;
+ dbc->keylen = key->mv_size;
+ } else {
+ dbc->key = NULL;
+ dbc->keylen = 0;
+ }
+
+ rpmswExit(&rdb->db_getops, data->mv_size);
+ }
+ return rc;
+}
+
+static int dbiCursorDel(dbiCursor dbc, MDB_val *key, MDB_val *data, unsigned int flags)
+{
+ int rc = EINVAL;
+ int sane = (key->mv_data != NULL && key->mv_size > 0);
+
+ if (dbc && sane) {
+ MDB_cursor * cursor = dbc->cursor;
+ rpmdb rdb = dbc->dbi->dbi_rpmdb;
+ rpmswEnter(&rdb->db_delops, 0);
+
+ /* XXX TODO: ensure that cursor is positioned with duplicates */
+ rc = mdb_cursor_get(cursor, key, data, MDB_SET);
+ if (rc && rc != MDB_NOTFOUND) {
+ rc = cvtdberr(dbc->dbi, "mdb_cursor_get", rc, _debug);
+ if (dbc->txn)
+ dbc->txn = NULL;
+ }
+
+ if (rc == 0) {
+ rc = mdb_cursor_del(cursor, flags);
+ if (rc)
+ rc = cvtdberr(dbc->dbi, "mdb_cursor_del", rc, _debug);
+ }
+ rpmswExit(&rdb->db_delops, data->mv_size);
+ }
+ return rc;
+}
+
+static int lmdb_dbiVerify(dbiIndex dbi, unsigned int flags)
+{
+ return 0;
+}
+
+static int lmdb_dbiClose(dbiIndex dbi, unsigned int flags)
+{
+ int rc = 0;
+ rpmdb rdb = dbi->dbi_rpmdb;
+ const char * dbhome = rpmdbHome(rdb);
+ MDB_dbi db = (unsigned long) dbi->dbi_db;
+
+ if (db != 0xdeadbeef) {
+ MDB_env * env = dbi->dbi_rpmdb->db_dbenv;
+ mdb_dbi_close(env, db);
+ dbi->dbi_db = (void *) 0xdeadbeefUL;
+
+ rpmlog(RPMLOG_DEBUG, "closed db index %s/%s\n",
+ dbhome, dbi->dbi_file);
+ }
+
+ db_fini(rdb, dbhome ? dbhome : "");
+
+ dbi = dbiFree(dbi);
+
+ return rc;
+}
+
+static int lmdb_dbiOpen(rpmdb rdb, rpmDbiTagVal rpmtag, dbiIndex * dbip, int flags)
+{
+ int rc = 1;
+ const char *dbhome = rpmdbHome(rdb);
+ dbiIndex dbi = NULL;
+ int retry_open;
+
+ MDB_dbi db = 0;
+ uint32_t oflags;
+
+ if (dbip)
+ *dbip = NULL;
+
+ if ((dbi = dbiNew(rdb, rpmtag)) == NULL)
+ goto exit;
+ dbi->dbi_flags = 0;
+ dbi->dbi_db = (void *) 0xdeadbeefUL;
+
+ rc = db_init(rdb, dbhome);
+
+ retry_open = (rc == 0) ? 2 : 0;
+
+ do {
+ MDB_env * env = rdb->db_dbenv;
+ MDB_txn * parent = NULL;
+ unsigned tflags = access(dbhome, W_OK) ? MDB_RDONLY : 0;
+ MDB_txn * txn = NULL;
+
+ if (tflags & MDB_RDONLY)
+ dbi->dbi_flags |= DBI_RDONLY;
+
+ rc = mdb_txn_begin(env, parent, tflags, &txn);
+ if (rc)
+ rc = cvtdberr(dbi, "mdb_txn_begin", rc, _debug);
+
+ const char * name = dbi->dbi_file;
+ oflags = 0;
+ if (!(tflags & MDB_RDONLY))
+ oflags |= MDB_CREATE;
+ if (!strcmp(dbi->dbi_file, "Packages"))
+ oflags |= MDB_INTEGERKEY;
+
+ rpmlog(RPMLOG_DEBUG, "opening db index %s/%s oflags=%s\n",
+ dbhome, dbi->dbi_file, _OpenF(oflags));
+
+ db = 0xdeadbeef;
+ rc = mdb_dbi_open(txn, name, oflags, &db);
+ if (rc && rc != MDB_NOTFOUND) {
+ rc = cvtdberr(dbi, "mdb_dbi_open", rc, _debug);
+ if (txn) {
+ mdb_txn_abort(txn);
+ txn = NULL;
+ db = 0xdeadbeef;
+ }
+ }
+
+ if (txn) {
+ rc = mdb_txn_commit(txn);
+ if (rc)
+ rc = cvtdberr(dbi, "mdb_txn_commit", rc, _debug);
+ }
+ retry_open = 0;
+ } while (--retry_open > 0);
+
+ dbi->dbi_db = (void *) ((unsigned long)db);
+
+ if (!rc && dbip)
+ *dbip = dbi;
+ else
+ (void) dbiClose(dbi, 0);
+
+exit:
+ return rc;
+}
+
+/* The LMDB btree implementation needs BIGENDIAN primary keys. */
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+static int _dbibyteswapped = 1;
+#else
+static int _dbibyteswapped = 0;
+#endif
+
+/**
+ * Convert retrieved data to index set.
+ * @param dbi index database handle
+ * @param data retrieved data
+ * @retval setp (malloc'ed) index set
+ * @return 0 on success
+ */
+static rpmRC dbt2set(dbiIndex dbi, MDB_val * data, dbiIndexSet * setp)
+{
+ rpmRC rc = RPMRC_FAIL;
+ const char * sdbir;
+ dbiIndexSet set = NULL;
+ unsigned int i;
+
+ if (dbi == NULL || data == NULL || setp == NULL)
+ goto exit;
+
+ rc = RPMRC_OK;
+ if ((sdbir = data->mv_data) == NULL) {
+ *setp = NULL;
+ goto exit;
+ }
+
+ set = dbiIndexSetNew(data->mv_size / (2 * sizeof(int32_t)));
+ set->count = data->mv_size / (2 * sizeof(int32_t));
+
+ for (i = 0; i < set->count; i++) {
+ union _dbswap hdrNum, tagNum;
+
+ memcpy(&hdrNum.ui, sdbir, sizeof(hdrNum.ui));
+ sdbir += sizeof(hdrNum.ui);
+ memcpy(&tagNum.ui, sdbir, sizeof(tagNum.ui));
+ sdbir += sizeof(tagNum.ui);
+ if (_dbibyteswapped) {
+ _DBSWAP(hdrNum);
+ _DBSWAP(tagNum);
+ }
+ set->recs[i].hdrNum = hdrNum.ui;
+ set->recs[i].tagNum = tagNum.ui;
+ }
+ *setp = set;
+
+exit:
+ return rc;
+}
+
+/**
+ * Convert index set to database representation.
+ * @param dbi index database handle
+ * @param data retrieved data
+ * @param set index set
+ * @return 0 on success
+ */
+static rpmRC set2dbt(dbiIndex dbi, MDB_val * data, dbiIndexSet set)
+{
+ rpmRC rc = RPMRC_FAIL;
+ char * tdbir;
+ unsigned int i;
+
+ if (dbi == NULL || data == NULL || set == NULL)
+ goto exit;
+
+ rc = RPMRC_OK;
+ data->mv_size = set->count * (2 * sizeof(int32_t));
+ if (data->mv_size == 0) {
+ data->mv_data = NULL;
+ goto exit;
+ }
+ tdbir = data->mv_data = xmalloc(data->mv_size);
+
+ for (i = 0; i < set->count; i++) {
+ union _dbswap hdrNum, tagNum;
+
+ memset(&hdrNum, 0, sizeof(hdrNum));
+ memset(&tagNum, 0, sizeof(tagNum));
+ hdrNum.ui = set->recs[i].hdrNum;
+ tagNum.ui = set->recs[i].tagNum;
+ if (_dbibyteswapped) {
+ _DBSWAP(hdrNum);
+ _DBSWAP(tagNum);
+ }
+ memcpy(tdbir, &hdrNum.ui, sizeof(hdrNum.ui));
+ tdbir += sizeof(hdrNum.ui);
+ memcpy(tdbir, &tagNum.ui, sizeof(tagNum.ui));
+ tdbir += sizeof(tagNum.ui);
+ }
+exit:
+ return rc;
+}
+
+static rpmRC lmdb_idxdbGet(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen,
+ dbiIndexSet *set, int searchType)
+{
+ rpmRC rc = RPMRC_FAIL; /* assume failure */
+ if (dbi != NULL && dbc != NULL && set != NULL) {
+ int cflags = MDB_NEXT;
+ int dbrc;
+ MDB_val key = { 0, NULL };
+ MDB_val data = { 0, NULL };
+
+ if (keyp) {
+ if (keylen == 0) { /* XXX "/" fixup */
+ keyp = "";
+ keylen = 1;
+ }
+ key.mv_data = (void *) keyp; /* discards const */
+ key.mv_size = keylen;
+ cflags = searchType == DBC_PREFIX_SEARCH ? MDB_SET_RANGE : MDB_SET;
+ }
+
+ for (;;) {
+ dbiIndexSet newset = NULL;
+ dbrc = dbiCursorGet(dbc, &key, &data, cflags);
+ if (dbrc != 0)
+ break;
+ if (searchType == DBC_PREFIX_SEARCH &&
+ (key.mv_size < keylen || memcmp(key.mv_data, keyp, keylen) != 0))
+ break;
+ dbt2set(dbi, &data, &newset);
+ if (*set == NULL) {
+ *set = newset;
+ } else {
+ dbiIndexSetAppendSet(*set, newset, 0);
+ dbiIndexSetFree(newset);
+ }
+ if (searchType != DBC_PREFIX_SEARCH)
+ break;
+ key.mv_data = NULL;
+ key.mv_size = 0;
+ cflags = MDB_NEXT;
+ }
+
+ /* fixup result status for prefix search */
+ if (searchType == DBC_PREFIX_SEARCH) {
+ if (dbrc == MDB_NOTFOUND && *set != NULL && (*set)->count > 0)
+ dbrc = 0;
+ else if (dbrc == 0 && (*set == NULL || (*set)->count == 0))
+ dbrc = MDB_NOTFOUND;
+ }
+
+ if (dbrc == 0) {
+ rc = RPMRC_OK;
+ } else if (dbrc == MDB_NOTFOUND) {
+ rc = RPMRC_NOTFOUND;
+ } else {
+ rpmlog(RPMLOG_ERR,
+ _("rc(%d) getting \"%s\" records from %s index: %s\n"),
+ dbrc, keyp ? keyp : "???", dbiName(dbi), mdb_strerror(dbrc));
+ }
+ }
+ return rc;
+}
+
+/* Update secondary index. NULL set deletes the key */
+static rpmRC updateIndex(dbiCursor dbc, const char *keyp, unsigned int keylen,
+ dbiIndexSet set)
+{
+ rpmRC rc = RPMRC_FAIL;
+
+ if (dbc && keyp) {
+ dbiIndex dbi = dbc->dbi;
+ int dbrc;
+ MDB_val key = { 0, NULL };
+ MDB_val data = { 0, NULL };
+
+ key.mv_data = (void *) keyp; /* discards const */
+ key.mv_size = keylen;
+
+ if (set)
+ set2dbt(dbi, &data, set);
+
+ if (dbiIndexSetCount(set) > 0) {
+ dbrc = dbiCursorPut(dbc, &key, &data, 0);
+ if (dbrc) {
+ rpmlog(RPMLOG_ERR,
+ _("rc(%d) storing record \"%s\" into %s index: %s\n"),
+ dbrc, (char*)key.mv_data, dbiName(dbi), mdb_strerror(dbrc));
+ }
+ free(data.mv_data);
+ } else {
+ dbrc = dbiCursorDel(dbc, &key, &data, 0);
+ if (dbrc) {
+ rpmlog(RPMLOG_ERR,
+ _("rc(%d) removing record \"%s\" from %s index: %s\n"),
+ dbrc, (char*)key.mv_data, dbiName(dbi), mdb_strerror(dbrc));
+ }
+ }
+
+ if (dbrc == 0)
+ rc = RPMRC_OK;
+ }
+
+ return rc;
+}
+
+static rpmRC lmdb_idxdbPut(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen,
+ dbiIndexItem rec)
+{
+ dbiIndexSet set = NULL;
+ rpmRC rc;
+
+ if (keyp && keylen == 0) { /* XXX "/" fixup */
+ keyp = "";
+ keylen++;
+ }
+ rc = idxdbGet(dbi, dbc, keyp, keylen, &set, DBC_NORMAL_SEARCH);
+
+ /* Not found means a new key and is not an error. */
+ if (rc && rc != RPMRC_NOTFOUND)
+ goto exit;
+
+ if (set == NULL)
+ set = dbiIndexSetNew(1);
+ dbiIndexSetAppend(set, rec, 1, 0);
+
+ rc = updateIndex(dbc, keyp, keylen, set);
+
+ dbiIndexSetFree(set);
+
+exit:
+ return rc;
+}
+
+static rpmRC lmdb_idxdbDel(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen,
+ dbiIndexItem rec)
+{
+ rpmRC rc = RPMRC_FAIL;
+ dbiIndexSet set = NULL;
+
+ if (keyp && keylen == 0) { /* XXX "/" fixup */
+ keyp = "";
+ keylen++;
+ }
+ rc = idxdbGet(dbi, dbc, keyp, keylen, &set, DBC_NORMAL_SEARCH);
+ if (rc)
+ goto exit;
+
+ if (dbiIndexSetPrune(set, rec, 1, 1)) {
+ /* Nothing was pruned. XXX: Can this actually happen? */
+ rc = RPMRC_OK;
+ } else {
+ /* If there's data left, update data. Otherwise delete the key. */
+ if (dbiIndexSetCount(set) > 0) {
+ rc = updateIndex(dbc, keyp, keylen, set);
+ } else {
+ rc = updateIndex(dbc, keyp, keylen, NULL);
+ }
+ };
+ dbiIndexSetFree(set);
+
+exit:
+ return rc;
+}
+
+static const void * lmdb_idxdbKey(dbiIndex dbi, dbiCursor dbc, unsigned int *keylen)
+{
+ const void *key = NULL;
+ if (dbc) {
+ key = dbc->key;
+ if (key && keylen)
+ *keylen = dbc->keylen;
+ }
+ return key;
+}
+
+/* Update primary Packages index. NULL hdr means remove */
+static rpmRC updatePackages(dbiCursor dbc, unsigned int hdrNum, MDB_val *hdr)
+{
+ int rc = RPMRC_FAIL;
+ int dbrc = EINVAL;
+
+ if (dbc == NULL || hdrNum == 0)
+ goto exit;
+
+ union _dbswap mi_offset;
+ mi_offset.ui = hdrNum;
+ if (_dbibyteswapped)
+ _DBSWAP(mi_offset);
+
+ MDB_val key = { 0, NULL };
+ key.mv_data = (void *) &mi_offset;
+ key.mv_size = sizeof(mi_offset.ui);
+
+ MDB_val data = { 0, NULL };
+
+ dbrc = dbiCursorGet(dbc, &key, &data, MDB_SET);
+ if (dbrc && dbrc != MDB_NOTFOUND) {
+ rpmlog(RPMLOG_ERR,
+ _("rc(%d) positioning header #%d record: %s\n"), dbrc, hdrNum, mdb_strerror(dbrc));
+ goto exit;
+ }
+
+ if (hdr) {
+ dbrc = dbiCursorPut(dbc, &key, hdr, 0);
+ if (dbrc) {
+ rpmlog(RPMLOG_ERR,
+ _("rc(%d) adding header #%d record: %s\n"), dbrc, hdrNum, mdb_strerror(dbrc));
+ }
+ } else {
+ dbrc = dbiCursorDel(dbc, &key, &data, 0);
+ if (dbrc) {
+ rpmlog(RPMLOG_ERR,
+ _("rc(%d) deleting header #%d record: %s\n"), dbrc, hdrNum, mdb_strerror(dbrc));
+ }
+ }
+
+exit:
+ rc = dbrc == 0 ? RPMRC_OK : RPMRC_FAIL;
+ return rc;
+}
+
+/* Get current header instance number or try to allocate a new one */
+static unsigned int pkgInstance(dbiCursor dbc, int alloc)
+{
+ unsigned int hdrNum = 0;
+
+ MDB_val key = { 0, NULL };
+ MDB_val data = { 0, NULL };
+ unsigned int firstkey = 0;
+ union _dbswap mi_offset;
+ int rc;
+
+ /* Key 0 holds the current largest instance, fetch it */
+ key.mv_data = &firstkey;
+ key.mv_size = sizeof(firstkey);
+ rc = dbiCursorGet(dbc, &key, &data, MDB_SET);
+
+ if (!rc && data.mv_data) {
+ memcpy(&mi_offset, data.mv_data, sizeof(mi_offset.ui));
+ if (_dbibyteswapped)
+ _DBSWAP(mi_offset);
+ hdrNum = mi_offset.ui;
+ }
+
+ if (alloc) {
+ /* Rather complicated "increment by one", bswapping as needed */
+ ++hdrNum;
+ mi_offset.ui = hdrNum;
+ if (_dbibyteswapped)
+ _DBSWAP(mi_offset);
+ data.mv_data = &mi_offset;
+ data.mv_size = sizeof(mi_offset.ui);
+
+ /* Unless we manage to insert the new instance number, we failed */
+ rc = dbiCursorPut(dbc, &key, &data, 0);
+ if (rc) {
+ hdrNum = 0;
+ rpmlog(RPMLOG_ERR,
+ _("rc(%d) allocating new package instance: %s\n"), rc, mdb_strerror(rc));
+ }
+ }
+
+ return hdrNum;
+}
+
+static rpmRC lmdb_pkgdbPut(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum,
+ unsigned char *hdrBlob, unsigned int hdrLen)
+{
+ MDB_val hdr;
+ hdr.mv_data = hdrBlob;
+ hdr.mv_size = hdrLen;
+ return updatePackages(dbc, hdrNum, &hdr);
+}
+
+static rpmRC lmdb_pkgdbDel(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum)
+{
+ return updatePackages(dbc, hdrNum, NULL);
+}
+
+static rpmRC lmdb_pkgdbGet(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum,
+ unsigned char **hdrBlob, unsigned int *hdrLen)
+{
+ union _dbswap mi_offset;
+ MDB_val key = { 0, NULL };
+ MDB_val data = { 0, NULL };
+ rpmRC rc = RPMRC_FAIL;
+
+ if (dbc == NULL)
+ goto exit;
+
+ if (hdrNum) {
+ mi_offset.ui = hdrNum;
+ if (_dbibyteswapped)
+ _DBSWAP(mi_offset);
+ key.mv_data = (void *) &mi_offset;
+ key.mv_size = sizeof(mi_offset.ui);
+ }
+
+ rc = dbiCursorGet(dbc, &key, &data, hdrNum ? MDB_SET : MDB_NEXT);
+ if (rc == 0) {
+ if (hdrBlob)
+ *hdrBlob = data.mv_data;
+ if (hdrLen)
+ *hdrLen = data.mv_size;
+ rc = RPMRC_OK;
+ } else if (rc == MDB_NOTFOUND)
+ rc = RPMRC_NOTFOUND;
+ else
+ rc = RPMRC_FAIL;
+exit:
+ return rc;
+}
+
+static unsigned int lmdb_pkgdbKey(dbiIndex dbi, dbiCursor dbc)
+{
+ union _dbswap mi_offset;
+
+ if (dbc == NULL || dbc->key == NULL)
+ return 0;
+ memcpy(&mi_offset, dbc->key, sizeof(mi_offset.ui));
+ if (_dbibyteswapped)
+ _DBSWAP(mi_offset);
+ return mi_offset.ui;
+}
+
+static rpmRC lmdb_pkgdbNew(dbiIndex dbi, dbiCursor dbc, unsigned int *hdrNum)
+{
+ unsigned int num;
+ rpmRC rc = RPMRC_FAIL;
+
+ if (dbc == NULL)
+ goto exit;
+ num = pkgInstance(dbc, 1);
+ if (num) {
+ *hdrNum = num;
+ rc = RPMRC_OK;
+ }
+exit:
+ return rc;
+}
+
+struct rpmdbOps_s lmdb_dbops = {
+ .open = lmdb_dbiOpen,
+ .close = lmdb_dbiClose,
+ .verify = lmdb_dbiVerify,
+
+ .setFSync = lmdb_dbSetFSync,
+ .ctrl = lmdb_Ctrl,
+
+ .cursorInit = lmdb_dbiCursorInit,
+ .cursorFree = lmdb_dbiCursorFree,
+
+ .pkgdbGet = lmdb_pkgdbGet,
+ .pkgdbPut = lmdb_pkgdbPut,
+ .pkgdbDel = lmdb_pkgdbDel,
+ .pkgdbNew = lmdb_pkgdbNew,
+ .pkgdbKey = lmdb_pkgdbKey,
+
+ .idxdbGet = lmdb_idxdbGet,
+ .idxdbPut = lmdb_idxdbPut,
+ .idxdbDel = lmdb_idxdbDel,
+ .idxdbKey = lmdb_idxdbKey
+};
diff --git a/lib/backend/ndb/glue.c b/lib/backend/ndb/glue.c
new file mode 100644
index 000000000..144ada0e4
--- /dev/null
+++ b/lib/backend/ndb/glue.c
@@ -0,0 +1,492 @@
+#include "system.h"
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include "lib/rpmdb_internal.h"
+#include <rpm/rpmstring.h>
+#include <rpm/rpmlog.h>
+
+#include "lib/backend/ndb/rpmpkg.h"
+#include "lib/backend/ndb/rpmxdb.h"
+#include "lib/backend/ndb/rpmidx.h"
+
+#include "debug.h"
+
+struct dbiCursor_s {
+ dbiIndex dbi;
+ const void *key;
+ unsigned int keylen;
+ unsigned int hdrNum;
+ int flags;
+
+ unsigned int *list;
+ unsigned int nlist;
+ unsigned int ilist;
+ unsigned char *listdata;
+};
+
+struct ndbEnv_s {
+ rpmpkgdb pkgdb;
+ rpmxdb xdb;
+ int refs;
+
+ unsigned int hdrNum;
+ void *data;
+ unsigned int datalen;
+};
+
+static void closeEnv(rpmdb rdb)
+{
+ struct ndbEnv_s *ndbenv = rdb->db_dbenv;
+ if (--ndbenv->refs == 0) {
+ if (ndbenv->xdb) {
+ rpmxdbClose(ndbenv->xdb);
+ rpmlog(RPMLOG_DEBUG, "closed db index %s/Index.db\n", rpmdbHome(rdb));
+ }
+ if (ndbenv->pkgdb) {
+ rpmpkgClose(ndbenv->pkgdb);
+ rpmlog(RPMLOG_DEBUG, "closed db index %s/Packages.db\n", rpmdbHome(rdb));
+ }
+ if (ndbenv->data)
+ free(ndbenv->data);
+ free(ndbenv);
+ rdb->db_dbenv = 0;
+ }
+}
+
+static struct ndbEnv_s *openEnv(rpmdb rdb)
+{
+ struct ndbEnv_s *ndbenv = rdb->db_dbenv;
+ if (!ndbenv)
+ rdb->db_dbenv = ndbenv = xcalloc(1, sizeof(struct ndbEnv_s));
+ ndbenv->refs++;
+ return ndbenv;
+}
+
+static int ndb_Close(dbiIndex dbi, unsigned int flags)
+{
+ rpmdb rdb = dbi->dbi_rpmdb;
+ if (dbi->dbi_type != DBI_PRIMARY && dbi->dbi_db) {
+ rpmidxClose(dbi->dbi_db);
+ rpmlog(RPMLOG_DEBUG, "closed db index %s\n", dbi->dbi_file);
+ }
+ if (rdb->db_dbenv)
+ closeEnv(rdb);
+ dbi->dbi_db = 0;
+ return 0;
+}
+
+static int ndb_Open(rpmdb rdb, rpmDbiTagVal rpmtag, dbiIndex * dbip, int flags)
+{
+ const char *dbhome = rpmdbHome(rdb);
+ struct ndbEnv_s *ndbenv;
+ dbiIndex dbi;
+ int rc, oflags, ioflags;
+
+ if (dbip)
+ *dbip = NULL;
+
+ if ((dbi = dbiNew(rdb, rpmtag)) == NULL)
+ return 1;
+
+ ndbenv = openEnv(rdb);
+
+ oflags = O_RDWR;
+ if ((rdb->db_mode & O_ACCMODE) == O_RDONLY)
+ oflags = O_RDONLY;
+
+ if (dbi->dbi_type == DBI_PRIMARY) {
+ rpmpkgdb pkgdb = 0;
+ char *path = rstrscat(NULL, dbhome, "/Packages.db", NULL);
+ rpmlog(RPMLOG_DEBUG, "opening db index %s mode=0x%x\n", path, rdb->db_mode);
+ rc = rpmpkgOpen(&pkgdb, path, oflags, 0666);
+ if (rc && errno == ENOENT) {
+ oflags = O_RDWR|O_CREAT;
+ dbi->dbi_flags |= DBI_CREATED;
+ rc = rpmpkgOpen(&pkgdb, path, oflags, 0666);
+ }
+ if (rc) {
+ perror("rpmpkgOpen");
+ free(path);
+ ndb_Close(dbi, 0);
+ return 1;
+ }
+ free(path);
+ dbi->dbi_db = ndbenv->pkgdb = pkgdb;
+
+ if ((oflags & (O_RDWR | O_RDONLY)) == O_RDONLY)
+ dbi->dbi_flags |= DBI_RDONLY;
+ } else {
+ unsigned int id;
+ rpmidxdb idxdb = 0;
+ if (!ndbenv->pkgdb) {
+ ndb_Close(dbi, 0);
+ return 1; /* please open primary first */
+ }
+ if (!ndbenv->xdb) {
+ char *path = rstrscat(NULL, dbhome, "/Index.db", NULL);
+ rpmlog(RPMLOG_DEBUG, "opening db index %s mode=0x%x\n", path, rdb->db_mode);
+
+ /* Open indexes readwrite if possible */
+ ioflags = O_RDWR;
+ rc = rpmxdbOpen(&ndbenv->xdb, rdb->db_pkgs->dbi_db, path, ioflags, 0666);
+ if (rc && errno == EACCES) {
+ /* If it is not asked for rw explicitly, try to open ro */
+ if (!(oflags & O_RDWR)) {
+ ioflags = O_RDONLY;
+ rc = rpmxdbOpen(&ndbenv->xdb, rdb->db_pkgs->dbi_db, path, ioflags, 0666);
+ }
+ } else if (rc && errno == ENOENT) {
+ ioflags = O_CREAT|O_RDWR;
+ rc = rpmxdbOpen(&ndbenv->xdb, rdb->db_pkgs->dbi_db, path, ioflags, 0666);
+ }
+ if (rc) {
+ perror("rpmxdbOpen");
+ free(path);
+ ndb_Close(dbi, 0);
+ return 1;
+ }
+ free(path);
+ }
+ if (rpmxdbLookupBlob(ndbenv->xdb, &id, rpmtag, 0, 0) == RPMRC_NOTFOUND) {
+ dbi->dbi_flags |= DBI_CREATED;
+ }
+ rpmlog(RPMLOG_DEBUG, "opening db index %s tag=%d\n", dbiName(dbi), rpmtag);
+ if (rpmidxOpenXdb(&idxdb, rdb->db_pkgs->dbi_db, ndbenv->xdb, rpmtag)) {
+ perror("rpmidxOpenXdb");
+ ndb_Close(dbi, 0);
+ return 1;
+ }
+ dbi->dbi_db = idxdb;
+
+ if (rpmxdbIsRdonly(ndbenv->xdb))
+ dbi->dbi_flags |= DBI_RDONLY;
+ }
+
+
+ if (dbip != NULL)
+ *dbip = dbi;
+ else
+ ndb_Close(dbi, 0);
+ return 0;
+}
+
+static int ndb_Verify(dbiIndex dbi, unsigned int flags)
+{
+ return 1;
+}
+
+static void ndb_SetFSync(rpmdb rdb, int enable)
+{
+}
+
+static int indexSync(rpmpkgdb pkgdb, rpmxdb xdb)
+{
+ unsigned int generation;
+ int rc;
+ if (!pkgdb || !xdb)
+ return 1;
+ if (rpmpkgLock(pkgdb, 1))
+ return 1;
+ if (rpmpkgGeneration(pkgdb, &generation)) {
+ rpmpkgUnlock(pkgdb, 1);
+ return 1;
+ }
+ rc = rpmxdbSetUserGeneration(xdb, generation);
+ rpmpkgUnlock(pkgdb, 1);
+ return rc;
+}
+
+static int ndb_Ctrl(rpmdb rdb, dbCtrlOp ctrl)
+{
+ struct ndbEnv_s *ndbenv = rdb->db_dbenv;
+
+ switch (ctrl) {
+ case DB_CTRL_LOCK_RO:
+ if (!rdb->db_pkgs)
+ return 1;
+ return rpmpkgLock(rdb->db_pkgs->dbi_db, 0);
+ case DB_CTRL_LOCK_RW:
+ if (!rdb->db_pkgs)
+ return 1;
+ return rpmpkgLock(rdb->db_pkgs->dbi_db, 1);
+ case DB_CTRL_UNLOCK_RO:
+ if (!rdb->db_pkgs)
+ return 1;
+ return rpmpkgUnlock(rdb->db_pkgs->dbi_db, 0);
+ case DB_CTRL_UNLOCK_RW:
+ if (!rdb->db_pkgs)
+ return 1;
+ return rpmpkgUnlock(rdb->db_pkgs->dbi_db, 1);
+ case DB_CTRL_INDEXSYNC:
+ if (!ndbenv)
+ return 1;
+ return indexSync(ndbenv->pkgdb, ndbenv->xdb);
+ default:
+ break;
+ }
+ return 0;
+}
+
+static dbiCursor ndb_CursorInit(dbiIndex dbi, unsigned int flags)
+{
+ dbiCursor dbc = xcalloc(1, sizeof(*dbc));
+ dbc->dbi = dbi;
+ dbc->flags = flags;
+ return dbc;
+}
+
+static dbiCursor ndb_CursorFree(dbiIndex dbi, dbiCursor dbc)
+{
+ if (dbc) {
+ if (dbc->list)
+ free(dbc->list);
+ if (dbc->listdata)
+ free(dbc->listdata);
+ free(dbc);
+ }
+ return NULL;
+}
+
+
+static void setdata(dbiCursor dbc, unsigned int hdrNum, unsigned char *hdrBlob, unsigned int hdrLen)
+{
+ struct ndbEnv_s *ndbenv = dbc->dbi->dbi_rpmdb->db_dbenv;
+ if (ndbenv->data)
+ free(ndbenv->data);
+ ndbenv->hdrNum = hdrNum;
+ ndbenv->data = hdrBlob;
+ ndbenv->datalen = hdrLen;
+}
+
+static rpmRC ndb_pkgdbNew(dbiIndex dbi, dbiCursor dbc, unsigned int *hdrNum)
+{
+ int rc = rpmpkgNextPkgIdx(dbc->dbi->dbi_db, hdrNum);
+ if (!rc)
+ setdata(dbc, *hdrNum, 0, 0);
+ return rc;
+}
+
+static rpmRC ndb_pkgdbPut(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum, unsigned char *hdrBlob, unsigned int hdrLen)
+{
+ int rc = rpmpkgPut(dbc->dbi->dbi_db, hdrNum, hdrBlob, hdrLen);
+ if (!rc) {
+ dbc->hdrNum = hdrNum;
+ setdata(dbc, hdrNum, 0, 0);
+ }
+ return rc;
+}
+
+static rpmRC ndb_pkgdbDel(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum)
+{
+ dbc->hdrNum = 0;
+ setdata(dbc, 0, 0, 0);
+ return rpmpkgDel(dbc->dbi->dbi_db, hdrNum);
+}
+
+/* iterate over all packages */
+static rpmRC ndb_pkgdbIter(dbiIndex dbi, dbiCursor dbc, unsigned char **hdrBlob, unsigned int *hdrLen)
+{
+ int rc;
+ unsigned int hdrNum;
+
+ if (!dbc->list) {
+ rc = rpmpkgList(dbc->dbi->dbi_db, &dbc->list, &dbc->nlist);
+ if (rc)
+ return rc;
+ dbc->ilist = 0;
+ }
+ for (;;) {
+ if (dbc->ilist >= dbc->nlist) {
+ rc = RPMRC_NOTFOUND;
+ break;
+ }
+ *hdrBlob = 0;
+ hdrNum = dbc->list[dbc->ilist];
+ rc = rpmpkgGet(dbc->dbi->dbi_db, hdrNum, hdrBlob, hdrLen);
+ if (rc && rc != RPMRC_NOTFOUND)
+ break;
+ dbc->ilist++;
+ if (!rc) {
+ dbc->hdrNum = hdrNum;
+ setdata(dbc, hdrNum, *hdrBlob, *hdrLen);
+ break;
+ }
+ }
+ return rc;
+}
+
+static rpmRC ndb_pkgdbGet(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum, unsigned char **hdrBlob, unsigned int *hdrLen)
+{
+ int rc;
+ struct ndbEnv_s *ndbenv = dbc->dbi->dbi_rpmdb->db_dbenv;
+
+ if (!hdrNum)
+ return ndb_pkgdbIter(dbi, dbc, hdrBlob, hdrLen);
+ if (hdrNum == ndbenv->hdrNum && ndbenv->data) {
+ *hdrBlob = ndbenv->data;
+ *hdrLen = ndbenv->datalen;
+ return RPMRC_OK;
+ }
+ rc = rpmpkgGet(dbc->dbi->dbi_db, hdrNum, hdrBlob, hdrLen);
+ if (!rc) {
+ dbc->hdrNum = hdrNum;
+ setdata(dbc, hdrNum, *hdrBlob, *hdrLen);
+ }
+ return rc;
+}
+
+static unsigned int ndb_pkgdbKey(dbiIndex dbi, dbiCursor dbc)
+{
+ return dbc->hdrNum;
+}
+
+
+static void addtoset(dbiIndexSet *set, unsigned int *pkglist, unsigned int pkglistn)
+{
+ unsigned int i, j;
+ dbiIndexSet newset = dbiIndexSetNew(pkglistn / 2);
+ for (i = j = 0; i < pkglistn; i += 2) {
+ newset->recs[j].hdrNum = pkglist[i];
+ newset->recs[j].tagNum = pkglist[i + 1];
+ j++;
+ }
+ newset->count = j;
+ if (pkglist)
+ free(pkglist);
+ if (*set) {
+ dbiIndexSetAppendSet(*set, newset, 0);
+ dbiIndexSetFree(newset);
+ } else
+ *set = newset;
+}
+
+/* Iterate over all index entries */
+static rpmRC ndb_idxdbIter(dbiIndex dbi, dbiCursor dbc, dbiIndexSet *set)
+{
+ int rc;
+ if (!dbc->list) {
+ /* setup iteration list on first call */
+ rc = rpmidxList(dbc->dbi->dbi_db, &dbc->list, &dbc->nlist, &dbc->listdata);
+ if (rc)
+ return rc;
+ dbc->ilist = 0;
+ }
+ for (;;) {
+ unsigned char *k;
+ unsigned int kl;
+ unsigned int *pkglist, pkglistn;
+ if (dbc->ilist >= dbc->nlist) {
+ rc = RPMRC_NOTFOUND;
+ break;
+ }
+ k = dbc->listdata + dbc->list[dbc->ilist];
+ kl = dbc->list[dbc->ilist + 1];
+#if 0
+ if (searchType == DBC_KEY_SEARCH) {
+ dbc->ilist += 2;
+ dbc->key = k;
+ dbc->keylen = kl;
+ rc = RPMRC_OK;
+ break;
+ }
+#endif
+ pkglist = 0;
+ pkglistn = 0;
+ rc = rpmidxGet(dbc->dbi->dbi_db, k, kl, &pkglist, &pkglistn);
+ if (rc && rc != RPMRC_NOTFOUND)
+ break;
+ dbc->ilist += 2;
+ if (!rc && pkglistn) {
+ addtoset(set, pkglist, pkglistn);
+ dbc->key = k;
+ dbc->keylen = kl;
+ break;
+ }
+ if (pkglist)
+ free(pkglist);
+ }
+ return rc;
+}
+
+static rpmRC ndb_idxdbGet(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen, dbiIndexSet *set, int searchType)
+{
+ int rc;
+ unsigned int *pkglist = 0, pkglistn = 0;
+
+ if (!keyp)
+ return ndb_idxdbIter(dbi, dbc, set);
+
+ if (searchType == DBC_PREFIX_SEARCH) {
+ unsigned int *list = 0, nlist = 0, i = 0;
+ unsigned char *listdata = 0;
+ int rrc = RPMRC_NOTFOUND;
+ rc = rpmidxList(dbc->dbi->dbi_db, &list, &nlist, &listdata);
+ if (rc)
+ return rc;
+ for (i = 0; i < nlist && !rc; i += 2) {
+ unsigned char *k = listdata + list[i];
+ unsigned int kl = list[i + 1];
+ if (kl < keylen || memcmp(k, keyp, keylen) != 0)
+ continue;
+ rc = ndb_idxdbGet(dbi, dbc, (char *)k, kl, set, DBC_NORMAL_SEARCH);
+ if (rc == RPMRC_NOTFOUND)
+ rc = 0;
+ else
+ rrc = rc;
+ }
+ if (list)
+ free(list);
+ if (listdata)
+ free(listdata);
+ return rc ? rc : rrc;
+ }
+
+ rc = rpmidxGet(dbc->dbi->dbi_db, (const unsigned char *)keyp, keylen, &pkglist, &pkglistn);
+ if (!rc)
+ addtoset(set, pkglist, pkglistn);
+ return rc;
+}
+
+static rpmRC ndb_idxdbPut(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen, dbiIndexItem rec)
+{
+ return rpmidxPut(dbc->dbi->dbi_db, (const unsigned char *)keyp, keylen, rec->hdrNum, rec->tagNum);
+}
+
+static rpmRC ndb_idxdbDel(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen, dbiIndexItem rec)
+{
+ return rpmidxDel(dbc->dbi->dbi_db, (const unsigned char *)keyp, keylen, rec->hdrNum, rec->tagNum);
+}
+
+static const void * ndb_idxdbKey(dbiIndex dbi, dbiCursor dbc, unsigned int *keylen)
+{
+ if (dbc->key && keylen)
+ *keylen = dbc->keylen;
+ return dbc->key;
+}
+
+
+
+struct rpmdbOps_s ndb_dbops = {
+ .open = ndb_Open,
+ .close = ndb_Close,
+ .verify = ndb_Verify,
+ .setFSync = ndb_SetFSync,
+ .ctrl = ndb_Ctrl,
+
+ .cursorInit = ndb_CursorInit,
+ .cursorFree = ndb_CursorFree,
+
+ .pkgdbNew = ndb_pkgdbNew,
+ .pkgdbPut = ndb_pkgdbPut,
+ .pkgdbDel = ndb_pkgdbDel,
+ .pkgdbGet = ndb_pkgdbGet,
+ .pkgdbKey = ndb_pkgdbKey,
+
+ .idxdbGet = ndb_idxdbGet,
+ .idxdbPut = ndb_idxdbPut,
+ .idxdbDel = ndb_idxdbDel,
+ .idxdbKey = ndb_idxdbKey
+};
+
diff --git a/lib/backend/ndb/rpmidx.c b/lib/backend/ndb/rpmidx.c
new file mode 100644
index 000000000..313d2e0fb
--- /dev/null
+++ b/lib/backend/ndb/rpmidx.c
@@ -0,0 +1,1280 @@
+#define _GNU_SOURCE
+
+#include "system.h"
+
+#include <rpm/rpmlog.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <errno.h>
+
+#include <endian.h>
+
+#include "rpmidx.h"
+#include "rpmxdb.h"
+
+#define RPMRC_OK 0
+#define RPMRC_NOTFOUND 1
+#define RPMRC_FAIL 2
+
+/* Index database
+ *
+ *
+ * Layout:
+ * Header
+ * Slots
+ * Keys
+ *
+ * Each slot contains 12 bytes, they are split into a 8 byte
+ * and a 4 byte part:
+ * 4 bytes key offset + extra tag bits
+ * 4 bytes data
+ * 4 bytes data overflow
+ * The slot space first contains all 8 byte parts followed by all of
+ * the 4 byte overflow parts. This is done because most of the time we
+ * do not need the latter.
+ *
+ * If a new (key, pkgidx, datidx) tupel is added, the key is hashed with
+ * the popular murmur hash. The lower bits of the hash determine the start
+ * slot, parts of the higher bits are used as extra key equality check.
+ * The (pkgidx, datidx) pair is encoded in a (data, dataovl) pair, so that
+ * most of the time dataovl is zero.
+ *
+ * The code then checks the current entry at the start slot. If the key
+ * does not match, it advances to the next slot. If it matches, it also
+ * checks the data part for a match but it remembers the key offset.
+ * If the code found a (key, data, dataovl) match, nothing needs to be done.
+ *
+ * Otherwise, the code arrived at an empty slot. It then adds the key
+ * to the key space if it did not find a matching key, and then puts
+ * the encoded (key, data, dataovl) pair into the slot.
+ *
+ * Deleting a (key, data) pair is done by replacing the slot with a
+ * (-1, -1, 0) dummy entry.
+ *
+ */
+
+
+typedef struct rpmidxdb_s {
+ rpmpkgdb pkgdb; /* master database */
+
+ char *filename;
+ int fd; /* our file descriptor */
+ int flags;
+ int mode;
+
+ int rdonly;
+
+ /* xdb support */
+ rpmxdb xdb;
+ unsigned int xdbtag;
+ unsigned int xdbid;
+
+ unsigned char *head_mapped;
+ unsigned char *slot_mapped;
+ unsigned char *key_mapped;
+ unsigned int key_size;
+ unsigned int file_size;
+
+ unsigned int generation;
+ unsigned int nslots;
+ unsigned int usedslots;
+ unsigned int dummyslots;
+
+ unsigned int keyend;
+ unsigned int keyexcess;
+
+ unsigned int hmask;
+ unsigned int xmask;
+
+ unsigned int pagesize;
+} * rpmidxdb;
+
+static inline unsigned int le2h(unsigned char *p)
+{
+ return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
+}
+
+static inline void h2le(unsigned int x, unsigned char *p)
+{
+ p[0] = x;
+ p[1] = x >> 8;
+ p[2] = x >> 16;
+ p[3] = x >> 24;
+}
+
+/* aligned versions */
+static inline unsigned int le2ha(unsigned char *p)
+{
+ unsigned int x = *(unsigned int *)p;
+ return le32toh(x);
+}
+
+static inline void h2lea(unsigned int x, unsigned char *p)
+{
+ *(unsigned int *)p = htole32(x);
+}
+
+/*** Header management ***/
+
+#define IDXDB_MAGIC ('R' | 'p' << 8 | 'm' << 16 | 'I' << 24)
+#define IDXDB_VERSION 0
+
+#define IDXDB_OFFSET_MAGIC 0
+#define IDXDB_OFFSET_VERSION 4
+#define IDXDB_OFFSET_GENERATION 8
+#define IDXDB_OFFSET_NSLOTS 12
+#define IDXDB_OFFSET_USEDSLOTS 16
+#define IDXDB_OFFSET_DUMMYSLOTS 20
+#define IDXDB_OFFSET_XMASK 24
+#define IDXDB_OFFSET_KEYEND 28
+#define IDXDB_OFFSET_KEYEXCESS 32
+#define IDXDB_OFFSET_OBSOLETE 36
+
+#define IDXDB_SLOT_OFFSET 64
+#define IDXDB_KEY_CHUNKSIZE 4096
+
+/* XDB subids */
+
+#define IDXDB_XDB_SUBTAG 0
+#define IDXDB_XDB_SUBTAG_REBUILD 1
+
+static void set_mapped(rpmidxdb idxdb, unsigned char *addr, unsigned int size)
+{
+ if (addr) {
+ idxdb->head_mapped = addr;
+ idxdb->slot_mapped = addr + IDXDB_SLOT_OFFSET;
+ idxdb->key_mapped = addr + IDXDB_SLOT_OFFSET + idxdb->nslots * 12;
+ idxdb->key_size = size - (IDXDB_SLOT_OFFSET + idxdb->nslots * 12);
+ idxdb->file_size = size;
+ } else {
+ idxdb->head_mapped = idxdb->slot_mapped = idxdb->key_mapped = 0;
+ idxdb->file_size = idxdb->key_size = 0;
+ }
+}
+
+/* XDB callbacks */
+static void mapcb(rpmxdb xdb, void *data, void *newaddr, size_t newsize) {
+ set_mapped((rpmidxdb)data, newaddr, (unsigned int)newsize);
+}
+
+static int rpmidxReadHeader(rpmidxdb idxdb);
+
+static int rpmidxMap(rpmidxdb idxdb)
+{
+ if (idxdb->xdb) {
+ if (rpmxdbMapBlob(idxdb->xdb, idxdb->xdbid, idxdb->rdonly ? O_RDONLY : O_RDWR, mapcb, idxdb))
+ return RPMRC_FAIL;
+ if (idxdb->file_size < 4096) {
+ rpmxdbUnmapBlob(idxdb->xdb, idxdb->xdbid);
+ return RPMRC_FAIL;
+ }
+ } else {
+#ifdef IDXDB_FILESUPPORT
+ struct stat stb;
+ size_t size;
+ void *mapped;
+ if (fstat(idxdb->fd, &stb))
+ return RPMRC_FAIL;
+ size = stb.st_size;
+ if (size < 4096)
+ return RPMRC_FAIL;
+ /* round up for mmap */
+ size = (size + idxdb->pagesize - 1) & ~(idxdb->pagesize - 1);
+ mapped = mmap(0, size, idxdb->rdonly ? PROT_READ : PROT_READ | PROT_WRITE, MAP_SHARED, idxdb->fd, 0);
+ if (mapped == MAP_FAILED)
+ return RPMRC_FAIL;
+ set_mapped(idxdb, mapped, (unsigned int)stb.st_size);
+#else
+ return RPMRC_FAIL;
+#endif
+ }
+ return RPMRC_OK;
+}
+
+static void rpmidxUnmap(rpmidxdb idxdb)
+{
+ if (!idxdb->head_mapped)
+ return;
+ if (idxdb->xdb) {
+ rpmxdbUnmapBlob(idxdb->xdb, idxdb->xdbid);
+ } else {
+#ifdef IDXDB_FILESUPPORT
+ size_t size = idxdb->file_size;
+ /* round up for munmap */
+ size = (size + idxdb->pagesize - 1) & ~(idxdb->pagesize - 1);
+ munmap(idxdb->head_mapped, size);
+ set_mapped(idxdb, 0, 0);
+#else
+ return;
+#endif
+ }
+}
+
+#ifdef IDXDB_FILESUPPORT
+static int rpmidxReadHeader(rpmidxdb idxdb);
+
+/* re-open file to get the new version */
+static int rpmidxHandleObsolete(rpmidxdb idxdb)
+{
+ int nfd;
+ struct stat stb1, stb2;
+
+ if (fstat(idxdb->fd, &stb1))
+ return RPMRC_FAIL;
+ nfd = open(idxdb->filename, idxdb->rdonly ? O_RDONLY : O_RDWR, 0);
+ if (nfd == -1)
+ return RPMRC_FAIL;
+ if (fstat(nfd, &stb2)) {
+ close(nfd);
+ return RPMRC_FAIL;
+ }
+ if (stb1.st_dev == stb2.st_dev && stb1.st_ino == stb2.st_ino)
+ return RPMRC_FAIL; /* openend the same obsolete file */
+ rpmidxUnmap(idxdb);
+ close(idxdb->fd);
+ idxdb->fd = nfd;
+ return rpmidxReadHeader(idxdb); /* re-try with new file */
+}
+#endif
+
+static int rpmidxReadHeader(rpmidxdb idxdb)
+{
+ unsigned int version;
+
+ if (idxdb->head_mapped) {
+ if (le2ha(idxdb->head_mapped + IDXDB_OFFSET_GENERATION) == idxdb->generation)
+ return RPMRC_OK;
+ rpmidxUnmap(idxdb);
+ }
+ idxdb->nslots = 0;
+ if (rpmidxMap(idxdb))
+ return RPMRC_FAIL;
+
+ if (le2ha(idxdb->head_mapped + IDXDB_OFFSET_MAGIC) != IDXDB_MAGIC) {
+ rpmidxUnmap(idxdb);
+ return RPMRC_FAIL;
+ }
+ version = le2ha(idxdb->head_mapped + IDXDB_OFFSET_VERSION);
+ if (version != IDXDB_VERSION) {
+ rpmlog(RPMLOG_ERR, _("rpmidx: Version mismatch. Expected version: %u. "
+ "Found version: %u\n"), IDXDB_VERSION, version);
+ rpmidxUnmap(idxdb);
+ return RPMRC_FAIL;
+ }
+#ifdef IDXDB_FILESUPPORT
+ if (!idxdb->xdb && le2ha(idxdb->head_mapped + IDXDB_OFFSET_OBSOLETE))
+ return rpmidxHandleObsolete(idxdb);
+#endif
+ idxdb->generation = le2ha(idxdb->head_mapped + IDXDB_OFFSET_GENERATION);
+ idxdb->nslots = le2ha(idxdb->head_mapped + IDXDB_OFFSET_NSLOTS);
+ idxdb->usedslots = le2ha(idxdb->head_mapped + IDXDB_OFFSET_USEDSLOTS);
+ idxdb->dummyslots = le2ha(idxdb->head_mapped + IDXDB_OFFSET_DUMMYSLOTS);
+ idxdb->xmask = le2ha(idxdb->head_mapped + IDXDB_OFFSET_XMASK);
+ idxdb->keyend = le2ha(idxdb->head_mapped + IDXDB_OFFSET_KEYEND);
+ idxdb->keyexcess = le2ha(idxdb->head_mapped + IDXDB_OFFSET_KEYEXCESS);
+
+ idxdb->hmask = idxdb->nslots - 1;
+
+ /* now that we know nslots we can split between slots and keys */
+ if (idxdb->file_size <= IDXDB_SLOT_OFFSET + idxdb->nslots * 12) {
+ rpmidxUnmap(idxdb); /* too small, somthing is wrong */
+ return RPMRC_FAIL;
+ }
+ idxdb->key_mapped = idxdb->slot_mapped + idxdb->nslots * 12;
+ idxdb->key_size = idxdb->file_size - (IDXDB_SLOT_OFFSET + idxdb->nslots * 12);
+ return RPMRC_OK;
+}
+
+static int rpmidxWriteHeader(rpmidxdb idxdb)
+{
+ if (!idxdb->head_mapped)
+ return RPMRC_FAIL;
+ h2lea(IDXDB_MAGIC, idxdb->head_mapped + IDXDB_OFFSET_MAGIC);
+ h2lea(IDXDB_VERSION, idxdb->head_mapped + IDXDB_OFFSET_VERSION);
+ h2lea(idxdb->generation, idxdb->head_mapped + IDXDB_OFFSET_GENERATION);
+ h2lea(idxdb->nslots, idxdb->head_mapped + IDXDB_OFFSET_NSLOTS);
+ h2lea(idxdb->usedslots, idxdb->head_mapped + IDXDB_OFFSET_USEDSLOTS);
+ h2lea(idxdb->dummyslots, idxdb->head_mapped + IDXDB_OFFSET_DUMMYSLOTS);
+ h2lea(idxdb->xmask, idxdb->head_mapped + IDXDB_OFFSET_XMASK);
+ h2lea(idxdb->keyend, idxdb->head_mapped + IDXDB_OFFSET_KEYEND);
+ h2lea(idxdb->keyexcess, idxdb->head_mapped + IDXDB_OFFSET_KEYEXCESS);
+ return RPMRC_OK;
+}
+
+static inline void updateUsedslots(rpmidxdb idxdb)
+{
+ h2lea(idxdb->usedslots, idxdb->head_mapped + IDXDB_OFFSET_USEDSLOTS);
+}
+
+static inline void updateDummyslots(rpmidxdb idxdb)
+{
+ h2lea(idxdb->dummyslots, idxdb->head_mapped + IDXDB_OFFSET_DUMMYSLOTS);
+}
+
+static inline void updateKeyend(rpmidxdb idxdb)
+{
+ h2lea(idxdb->keyend, idxdb->head_mapped + IDXDB_OFFSET_KEYEND);
+}
+
+static inline void updateKeyexcess(rpmidxdb idxdb)
+{
+ h2lea(idxdb->keyexcess, idxdb->head_mapped + IDXDB_OFFSET_KEYEXCESS);
+}
+
+static inline void bumpGeneration(rpmidxdb idxdb)
+{
+ idxdb->generation++;
+ h2lea(idxdb->generation, idxdb->head_mapped + IDXDB_OFFSET_GENERATION);
+}
+
+#ifdef IDXDB_FILESUPPORT
+static int createempty(rpmidxdb idxdb, off_t off, size_t size)
+{
+ char buf[4096];
+ memset(buf, 0, sizeof(buf));
+ while (size >= 4096) {
+ if (pwrite(idxdb->fd, buf, 4096, off) != 4096)
+ return RPMRC_FAIL;
+ off += 4096;
+ size -= 4096;
+ }
+ if (size > 0 && pwrite(idxdb->fd, buf, size , off) != size)
+ return RPMRC_FAIL;
+ return RPMRC_OK;
+}
+#endif
+
+/*** Key management ***/
+
+#define MURMUR_M 0x5bd1e995
+
+static unsigned int murmurhash(const unsigned char *s, unsigned int l)
+{
+ unsigned int h = l * MURMUR_M;
+
+ while (l >= 4) {
+ h += s[0] | s[1] << 8 | s[2] << 16 | s[3] << 24;
+ h *= MURMUR_M;
+ h ^= h >> 16;
+ s += 4;
+ l -= 4;
+ }
+ switch (l) {
+ case 3:
+ h += s[2] << 16;
+ case 2:
+ h += s[1] << 8;
+ case 1:
+ h += s[0];
+ h *= MURMUR_M;
+ h ^= h >> 16;
+ default:
+ break;
+ }
+ h *= MURMUR_M;
+ h ^= h >> 10;
+ h *= MURMUR_M;
+ h ^= h >> 17;
+ return h;
+}
+
+static inline unsigned int decodekeyl(unsigned char *p, unsigned int *hl)
+{
+ if (*p != 255) {
+ *hl = 1;
+ return *p;
+ } else if (p[1] != 255 || p[2] != 255) {
+ *hl = 3;
+ return p[1] | p[2] << 8;
+ } else {
+ *hl = 7;
+ return p[3] | p[4] << 8 | p[5] << 16 | p[6] << 24;
+ }
+}
+
+static inline void encodekeyl(unsigned char *p, unsigned int keyl)
+{
+ if (keyl && keyl < 255) {
+ p[0] = keyl;
+ } else if (keyl < 65535) {
+ p[0] = 255;
+ p[1] = keyl;
+ p[2] = keyl >> 8;
+ } else {
+ p[0] = 255;
+ p[1] = 255;
+ p[2] = 255;
+ p[3] = keyl;
+ p[4] = keyl >> 8;
+ p[5] = keyl >> 16;
+ p[6] = keyl >> 24;
+ }
+}
+
+static inline unsigned int keylsize(unsigned int keyl)
+{
+ return keyl && keyl < 255 ? 1 : keyl < 65535 ? 3 : 7;
+}
+
+static inline int equalkey(rpmidxdb idxdb, unsigned int off, const unsigned char *key, unsigned int keyl)
+{
+ unsigned char *p;
+ if (off + keyl + 1 > idxdb->keyend)
+ return 0;
+ p = idxdb->key_mapped + off;
+ if (keyl && keyl < 255) {
+ if (*p != keyl)
+ return 0;
+ p += 1;
+ } else if (keyl < 65535) {
+ if (p[0] != 255 || (p[1] | p[2] << 8) != keyl)
+ return 0;
+ p += 3;
+ } else {
+ if (p[0] != 255 || p[1] != 255 || p[2] != 255 || (p[3] | p[4] << 8 | p[5] << 16 | p[6] << 24) != keyl)
+ return 0;
+ p += 7;
+ }
+ if (keyl && memcmp(key, p, keyl))
+ return 0;
+ return 1;
+}
+
+static int addkeypage(rpmidxdb idxdb) {
+ unsigned int addsize = idxdb->pagesize > IDXDB_KEY_CHUNKSIZE ? idxdb->pagesize : IDXDB_KEY_CHUNKSIZE;
+
+ if (idxdb->xdb) {
+ if (rpmxdbResizeBlob(idxdb->xdb, idxdb->xdbid, idxdb->file_size + addsize))
+ return RPMRC_FAIL;
+ } else {
+#ifdef IDXDB_FILESUPPORT
+ /* don't use ftruncate because we want to create a "backed" page */
+ void *newaddr;
+ size_t oldsize, newsize;
+ if (createempty(idxdb, idxdb->file_size, addsize))
+ return RPMRC_FAIL;
+ oldsize = idxdb->file_size;
+ newsize = idxdb->file_size + addsize;
+ /* round up for mremap */
+ oldsize = (oldsize + idxdb->pagesize - 1) & ~(idxdb->pagesize - 1);
+ newsize = (newsize + idxdb->pagesize - 1) & ~(idxdb->pagesize - 1);
+ newaddr = mremap(idxdb->head_mapped, oldsize, newsize, MREMAP_MAYMOVE);
+ if (newaddr == MAP_FAILED)
+ return RPMRC_FAIL;
+ set_mapped(idxdb, newaddr, idxdb->file_size + addsize);
+#else
+ return RPMRC_FAIL;
+#endif
+ }
+ return RPMRC_OK;
+}
+
+static int addnewkey(rpmidxdb idxdb, const unsigned char *key, unsigned int keyl, unsigned int *keyoffp)
+{
+ int hl = keylsize(keyl);
+ while (idxdb->key_size - idxdb->keyend < hl + keyl) {
+ if (addkeypage(idxdb))
+ return RPMRC_FAIL;
+ }
+ encodekeyl(idxdb->key_mapped + idxdb->keyend, keyl);
+ if (keyl)
+ memcpy(idxdb->key_mapped + idxdb->keyend + hl, key, keyl);
+ *keyoffp = idxdb->keyend;
+ idxdb->keyend += hl + keyl;
+ updateKeyend(idxdb);
+ return RPMRC_OK;
+}
+
+
+/*** Data encoding/decoding ***/
+
+/* Encode a (pkgidx, datidx) tuple into a (data, ovldata) tuple in a way
+ * that most of the time ovldata will be zero. */
+static inline unsigned int encodedata(rpmidxdb idxdb, unsigned int pkgidx, unsigned int datidx, unsigned int *ovldatap)
+{
+ if (pkgidx < 0x100000 && datidx < 0x400) {
+ *ovldatap = 0;
+ return pkgidx | datidx << 20;
+ } else if (pkgidx < 0x1000000 && datidx < 0x40) {
+ *ovldatap = 0;
+ return pkgidx | datidx << 24 | 0x40000000;
+ } else {
+ *ovldatap = pkgidx;
+ return datidx | 0x80000000;
+ }
+}
+
+/* Decode (data, ovldata) back into (pkgidx, datidx) */
+static inline unsigned int decodedata(rpmidxdb idxdb, unsigned int data, unsigned int ovldata, unsigned int *datidxp)
+{
+ if (data & 0x80000000) {
+ *datidxp = data ^ 0x80000000;
+ return ovldata;
+ } else if (data & 0x40000000) {
+ *datidxp = (data ^ 0x40000000) >> 24;
+ return data & 0xffffff;
+ } else {
+ *datidxp = data >> 20;
+ return data & 0xfffff;
+ }
+}
+
+
+/*** Rebuild helpers ***/
+
+/* copy a single data entry into the new database */
+static inline void copyentry(rpmidxdb idxdb, unsigned int keyh, unsigned int newkeyoff, unsigned int data, unsigned int ovldata)
+{
+ unsigned int h, hh = 7;
+ unsigned char *ent;
+ unsigned int hmask = idxdb->hmask;
+ unsigned int x;
+
+ /* find an empty slot */
+ for (h = keyh & hmask;; h = (h + hh++) & hmask) {
+ ent = idxdb->slot_mapped + 8 * h;
+ x = le2ha(ent);
+ if (x == 0)
+ break;
+ }
+ /* write data */
+ h2lea(newkeyoff, ent);
+ h2lea(data, ent + 4);
+ if (ovldata)
+ h2lea(ovldata, idxdb->slot_mapped + idxdb->nslots * 8 + 4 * h);
+ idxdb->usedslots++;
+}
+
+/* copy all entries belonging to a single key from the old database into the new database */
+static inline void copykeyentries(const unsigned char *key, unsigned int keyl, rpmidxdb idxdb, unsigned int oldkeyoff, rpmidxdb nidxdb, unsigned int newkeyoff, unsigned char *done)
+{
+ unsigned int h, hh;
+ unsigned int keyh = murmurhash(key, keyl);
+ unsigned int hmask = idxdb->hmask;
+
+ oldkeyoff |= keyh & idxdb->xmask;
+ newkeyoff |= keyh & nidxdb->xmask;
+ for (h = keyh & hmask, hh = 7; ; h = (h + hh++) & hmask) {
+ unsigned char *ent = idxdb->slot_mapped + 8 * h;
+ unsigned int data, ovldata;
+ unsigned int x = le2ha(ent);
+ if (x == 0)
+ break;
+ if (x != oldkeyoff)
+ continue;
+ data = le2ha(ent + 4);
+ ovldata = (data & 0x80000000) ? le2ha(idxdb->slot_mapped + idxdb->nslots * 8 + 4 * h) : 0;
+ copyentry(nidxdb, keyh, newkeyoff, data, ovldata);
+ done[h >> 3] |= 1 << (h & 7);
+ }
+}
+
+static int rpmidxRebuildInternal(rpmidxdb idxdb)
+{
+ struct rpmidxdb_s nidxdb_s, *nidxdb;
+ unsigned int i, nslots;
+ unsigned int keyend, keyoff, xmask;
+ unsigned char *done;
+ unsigned char *ent;
+ unsigned int file_size, key_size, xfile_size;
+
+ nidxdb = &nidxdb_s;
+ memset(nidxdb, 0, sizeof(*nidxdb));
+ nidxdb->pagesize = sysconf(_SC_PAGE_SIZE);
+
+ /* calculate nslots the hard way, don't trust usedslots */
+ nslots = 0;
+ for (i = 0, ent = idxdb->slot_mapped; i < idxdb->nslots; i++, ent += 8) {
+ unsigned int x = le2ha(ent);
+ if (x != 0 && x != -1)
+ nslots++;
+ }
+ if (nslots < 256)
+ nslots = 256;
+ while (nslots & (nslots - 1))
+ nslots = nslots & (nslots - 1);
+ nslots *= 4;
+
+ nidxdb->nslots = nslots;
+ nidxdb->hmask = nslots - 1;
+
+ /* calculate the new key space size */
+ key_size = idxdb->keyend;
+ if (key_size < IDXDB_KEY_CHUNKSIZE)
+ key_size = IDXDB_KEY_CHUNKSIZE;
+ file_size = IDXDB_SLOT_OFFSET + nslots * 12 + key_size;
+
+ /* round file size to multiple of the page size */
+ if (file_size & (nidxdb->pagesize - 1)) {
+ unsigned int add = nidxdb->pagesize - (file_size & (nidxdb->pagesize - 1));
+ file_size += add;
+ key_size += add;
+ }
+
+ /* calculate xmask, leave at least 8192 bytes headroom for key space */
+ for (xmask = 0x00010000; xmask && xmask < key_size + 8192; xmask <<= 1)
+ ;
+ xmask = xmask ? ~(xmask - 1) : 0;
+ nidxdb->xmask = xmask;
+
+ /* create new database */
+ if (idxdb->xdb) {
+ nidxdb->xdb = idxdb->xdb;
+ nidxdb->xdbtag = idxdb->xdbtag;
+ if (rpmxdbLookupBlob(nidxdb->xdb, &nidxdb->xdbid, idxdb->xdbtag, IDXDB_XDB_SUBTAG_REBUILD, O_CREAT|O_TRUNC)) {
+ return RPMRC_FAIL;
+ }
+ if (rpmxdbResizeBlob(nidxdb->xdb, nidxdb->xdbid, file_size)) {
+ return RPMRC_FAIL;
+ }
+ if (rpmidxMap(nidxdb)) {
+ return RPMRC_FAIL;
+ }
+ } else {
+#ifdef IDXDB_FILESUPPORT
+ void *mapped;
+ nidxdb->filename = malloc(strlen(idxdb->filename) + 8);
+ if (!nidxdb->filename)
+ return RPMRC_FAIL;
+ sprintf(nidxdb->filename, "%s-XXXXXX", idxdb->filename);
+ nidxdb->fd = mkstemp(nidxdb->filename);
+ if (nidxdb->fd == -1) {
+ free(nidxdb->filename);
+ return RPMRC_FAIL;
+ }
+ if (createempty(nidxdb, 0, file_size)) {
+ close(nidxdb->fd);
+ unlink(nidxdb->filename);
+ free(nidxdb->filename);
+ return RPMRC_FAIL;
+ }
+ mapped = mmap(0, file_size, idxdb->rdonly ? PROT_READ : PROT_READ | PROT_WRITE, MAP_SHARED, nidxdb->fd, 0);
+ if (mapped == MAP_FAILED) {
+ close(nidxdb->fd);
+ unlink(nidxdb->filename);
+ free(nidxdb->filename);
+ return RPMRC_FAIL;
+ }
+ set_mapped(nidxdb, mapped, file_size);
+#else
+ return RPMRC_FAIL;
+#endif
+ }
+
+ /* copy all entries */
+ done = calloc(idxdb->nslots / 8 + 1, 1);
+ if (!done) {
+ rpmidxUnmap(nidxdb);
+ if (!idxdb->xdb) {
+ close(nidxdb->fd);
+ unlink(nidxdb->filename);
+ free(nidxdb->filename);
+ }
+ return RPMRC_FAIL;
+ }
+ keyend = 1;
+ for (i = 0, ent = idxdb->slot_mapped; i < idxdb->nslots; i++, ent += 8) {
+ unsigned int x = le2ha(ent);
+ unsigned char *key;
+ unsigned int keyl, hl;
+
+ if (x == 0 || x == -1)
+ continue;
+ if (done[i >> 3] & (1 << (i & 7))) {
+ continue; /* we already did that one */
+ }
+ x &= ~idxdb->xmask;
+ key = idxdb->key_mapped + x;
+ keyl = decodekeyl(key, &hl);
+ keyoff = keyend;
+ keyend += hl + keyl;
+ memcpy(nidxdb->key_mapped + keyoff, key, hl + keyl);
+ copykeyentries(key + hl, keyl, idxdb, x, nidxdb, keyoff, done);
+ }
+ free(done);
+ nidxdb->keyend = keyend;
+ nidxdb->generation = idxdb->generation + 1;
+ rpmidxWriteHeader(nidxdb);
+ rpmidxUnmap(nidxdb);
+
+ /* shrink if we have allocated excessive key space */
+ xfile_size = file_size - key_size + keyend + IDXDB_KEY_CHUNKSIZE;
+ xfile_size = (xfile_size + nidxdb->pagesize - 1) & ~(nidxdb->pagesize - 1);
+ if (xfile_size < file_size) {
+ if (nidxdb->xdb) {
+ rpmxdbResizeBlob(nidxdb->xdb, nidxdb->xdbid, xfile_size);
+ } else {
+ if (ftruncate(nidxdb->fd, xfile_size)) {
+ rpmlog(RPMLOG_WARNING, _("truncate failed: %s\n"), strerror(errno));
+ }
+ }
+ }
+
+ /* now switch over to new database */
+ if (idxdb->xdb) {
+ rpmidxUnmap(idxdb);
+ if (rpmxdbRenameBlob(nidxdb->xdb, &nidxdb->xdbid, idxdb->xdbtag, IDXDB_XDB_SUBTAG))
+ return RPMRC_FAIL;
+ idxdb->xdbid = nidxdb->xdbid;
+ } else {
+#ifdef IDXDB_FILESUPPORT
+ if (rename(nidxdb->filename, idxdb->filename)) {
+ close(nidxdb->fd);
+ unlink(nidxdb->filename);
+ free(nidxdb->filename);
+ return RPMRC_FAIL;
+ }
+ if (idxdb->head_mapped) {
+ h2lea(1, idxdb->head_mapped + IDXDB_OFFSET_OBSOLETE);
+ bumpGeneration(idxdb);
+ rpmidxUnmap(idxdb);
+ }
+ free(nidxdb->filename);
+ close(idxdb->fd);
+ idxdb->fd = nidxdb->fd;
+#else
+ return RPMRC_FAIL;
+#endif
+ }
+ if (rpmidxReadHeader(idxdb))
+ return RPMRC_FAIL;
+ return RPMRC_OK;
+}
+
+/* check if we need to rebuild the index. We need to do this if
+ * - there are too many used slot, so hashing is inefficient
+ * - there is too much key excess (i.e. holes in the keys)
+ * - our keys grew so much that they need more bits
+ */
+static int rpmidxCheck(rpmidxdb idxdb)
+{
+ if (idxdb->usedslots * 2 > idxdb->nslots ||
+ (idxdb->keyexcess > 4096 && idxdb->keyexcess * 4 > idxdb->keyend) ||
+ idxdb->keyend >= ~idxdb->xmask) {
+ if (rpmidxRebuildInternal(idxdb))
+ return RPMRC_FAIL;
+ }
+ return RPMRC_OK;
+}
+
+static int rpmidxPutInternal(rpmidxdb idxdb, const unsigned char *key, unsigned int keyl, unsigned int pkgidx, unsigned int datidx)
+{
+ unsigned int keyh = murmurhash(key, keyl);
+ unsigned int keyoff = 0;
+ unsigned int freeh = -1;
+ unsigned int x, h, hh = 7;
+ unsigned int hmask;
+ unsigned int xmask;
+ unsigned char *ent;
+ unsigned int data, ovldata;
+
+ if (datidx >= 0x80000000)
+ return RPMRC_FAIL;
+ if (rpmidxCheck(idxdb))
+ return RPMRC_FAIL;
+ data = encodedata(idxdb, pkgidx, datidx, &ovldata);
+ hmask = idxdb->hmask;
+ xmask = idxdb->xmask;
+ for (h = keyh & hmask; ; h = (h + hh++) & hmask) {
+ ent = idxdb->slot_mapped + 8 * h;
+ x = le2ha(ent);
+ if (x == 0) /* reached an empty slot */
+ break;
+ if (x == -1) {
+ freeh = h; /* found a dummy slot, remember the position */
+ continue;
+ }
+ if (!keyoff) {
+ if (((x ^ keyh) & xmask) != 0)
+ continue;
+ if (!equalkey(idxdb, x & ~xmask, key, keyl))
+ continue;
+ keyoff = x;
+ }
+ if (keyoff != x)
+ continue;
+ /* string matches, check data/ovldata */
+ if (le2ha(ent + 4) == data) {
+ if (!ovldata || le2ha(idxdb->slot_mapped + idxdb->nslots * 8 + 4 * h) == ovldata)
+ return RPMRC_OK; /* already in database */
+ }
+ /* continue searching */
+ }
+ if (!keyoff) {
+ /* we did not find this key. add it */
+ if (addnewkey(idxdb, key, keyl, &keyoff))
+ return RPMRC_FAIL;
+ keyoff |= keyh & xmask; /* tag it with the extra bits */
+ /* re-calculate ent, addnewkey may have changed the mapping! */
+ ent = idxdb->slot_mapped + 8 * h;
+ }
+ if (freeh == -1) {
+ /* did not find a dummy slot, so use the current empty slot */
+ idxdb->usedslots++;
+ updateUsedslots(idxdb);
+ } else {
+ /* re-use dummy slot */
+ h = freeh;
+ ent = idxdb->slot_mapped + 8 * h;
+ if (idxdb->dummyslots) {
+ idxdb->dummyslots--;
+ updateDummyslots(idxdb);
+ }
+ }
+ h2lea(keyoff, ent);
+ h2lea(data, ent + 4);
+ if (ovldata)
+ h2lea(ovldata, idxdb->slot_mapped + idxdb->nslots * 8 + 4 * h);
+ bumpGeneration(idxdb);
+ return RPMRC_OK;
+}
+
+static int rpmidxDelInternal(rpmidxdb idxdb, const unsigned char *key, unsigned int keyl, unsigned int pkgidx, unsigned int datidx)
+{
+ unsigned int keyoff = 0;
+ unsigned int keyh = murmurhash(key, keyl);
+ unsigned int hmask;
+ unsigned int xmask;
+ unsigned int x, h, hh = 7;
+ int otherusers = 0;
+ unsigned int data, ovldata;
+
+ if (datidx >= 0x80000000)
+ return RPMRC_FAIL;
+ if (rpmidxCheck(idxdb))
+ return RPMRC_FAIL;
+ data = encodedata(idxdb, pkgidx, datidx, &ovldata);
+ hmask = idxdb->hmask;
+ xmask = idxdb->xmask;
+ for (h = keyh & hmask; ; h = (h + hh++) & hmask) {
+ unsigned char *ent = idxdb->slot_mapped + 8 * h;
+ x = le2ha(ent);
+ if (x == 0)
+ break;
+ if (x == -1)
+ continue;
+ if (!keyoff) {
+ if (((x ^ keyh) & xmask) != 0)
+ continue;
+ if (!equalkey(idxdb, x & ~xmask, key, keyl))
+ continue;
+ keyoff = x;
+ }
+ if (keyoff != x)
+ continue;
+ /* key matches, check data/ovldata */
+ if (le2ha(ent + 4) != data) {
+ otherusers = 1;
+ continue;
+ }
+ if (ovldata && le2ha(idxdb->slot_mapped + idxdb->nslots * 8 + 4 * h) != ovldata) {
+ otherusers = 1;
+ continue;
+ }
+ /* found a match. convert entry to a dummy slot */
+ h2lea(-1, ent);
+ h2lea(-1, ent + 4);
+ if (ovldata)
+ h2lea(0, idxdb->slot_mapped + idxdb->nslots * 8 + 4 * h);
+ idxdb->dummyslots++;
+ updateDummyslots(idxdb);
+ /* continue searching (so that we find other users of the key...) */
+ }
+ if (keyoff && !otherusers) {
+ /* key is no longer in use. free it */
+ int hl = keylsize(keyl);
+ memset(idxdb->key_mapped + (keyoff & ~xmask), 0, hl + keyl);
+ idxdb->keyexcess += hl + keyl;
+ updateKeyexcess(idxdb);
+ }
+ if (keyoff)
+ bumpGeneration(idxdb);
+ return RPMRC_OK;
+}
+
+static int rpmidxGetInternal(rpmidxdb idxdb, const unsigned char *key, unsigned int keyl, unsigned int **pkgidxlistp, unsigned int *pkgidxnump)
+{
+ unsigned int keyoff = 0;
+ unsigned int keyh = murmurhash(key, keyl);
+ unsigned int hmask = idxdb->hmask;
+ unsigned int xmask = idxdb->xmask;
+ unsigned int x, h, hh = 7;
+ unsigned int data, ovldata, datidx;
+ unsigned int nhits = 0;
+ unsigned int *hits = 0;
+ for (h = keyh & hmask; ; h = (h + hh++) & hmask) {
+ unsigned char *ent = idxdb->slot_mapped + 8 * h;
+ x = le2ha(ent);
+ if (x == 0)
+ break;
+ if (x == -1)
+ continue;
+ if (!keyoff) {
+ if (((x ^ keyh) & xmask) != 0)
+ continue;
+ if (!equalkey(idxdb, x & ~xmask, key, keyl))
+ continue;
+ keyoff = x;
+ }
+ if (keyoff != x)
+ continue;
+ if ((nhits & 15) == 0) {
+ if (!hits) {
+ hits = malloc(16 * sizeof(unsigned int));
+ } else {
+ hits = realloc(hits, (nhits + 16) * sizeof(unsigned int));
+ }
+ if (!hits)
+ return RPMRC_FAIL;
+ }
+ data = le2ha(ent + 4);
+ ovldata = (data & 0x80000000) ? le2ha(idxdb->slot_mapped + idxdb->nslots * 8 + 4 * h) : 0;
+ hits[nhits++] = decodedata(idxdb, data, ovldata, &datidx);
+ hits[nhits++] = datidx;
+ }
+ *pkgidxlistp = hits;
+ *pkgidxnump = nhits;
+ return nhits ? RPMRC_OK : RPMRC_NOTFOUND;
+}
+
+static int rpmidxListSort_cmp(const void *a, const void *b)
+{
+ return ((unsigned int *)a)[1] - ((unsigned int *)b)[1];
+}
+
+/* sort in hash offset order, so that we get sequential acceess */
+static void rpmidxListSort(rpmidxdb idxdb, unsigned int *keylist, unsigned int nkeylist, unsigned char *data)
+{
+ unsigned int i, *arr;
+ if (nkeylist < 2 * 2)
+ return;
+ arr = malloc(nkeylist * sizeof(unsigned int));
+ if (!arr)
+ return;
+ for (i = 0; i < nkeylist; i += 2) {
+ arr[i] = i;
+ arr[i + 1] = murmurhash(data + keylist[i], keylist[i + 1]) & idxdb->hmask;
+ }
+ qsort(arr, nkeylist / 2, 2 * sizeof(unsigned int), rpmidxListSort_cmp);
+ for (i = 0; i < nkeylist; i += 2) {
+ unsigned int ai = arr[i];
+ arr[i] = keylist[ai];
+ arr[i + 1] = keylist[ai + 1];
+ }
+ memcpy(keylist, arr, nkeylist * sizeof(unsigned int));
+ free(arr);
+}
+
+static int rpmidxListInternal(rpmidxdb idxdb, unsigned int **keylistp, unsigned int *nkeylistp, unsigned char **datap)
+{
+ unsigned int *keylist = 0;
+ unsigned int nkeylist = 0;
+ unsigned char *data, *terminate, *key, *keyendp;
+
+ data = malloc(idxdb->keyend + 1); /* +1 so we can terminate the last key */
+ if (!data)
+ return RPMRC_FAIL;
+ memcpy(data, idxdb->key_mapped, idxdb->keyend);
+ keylist = malloc(16 * sizeof(*keylist));
+ if (!keylist) {
+ free(data);
+ return RPMRC_FAIL;
+ }
+ terminate = 0;
+ for (key = data + 1, keyendp = data + idxdb->keyend; key < keyendp; ) {
+ unsigned int hl, keyl;
+ if (!*key) {
+ key++;
+ continue;
+ }
+ if ((nkeylist & 15) == 0) {
+ unsigned int *kl = realloc(keylist, (nkeylist + 16) * sizeof(*keylist));
+ if (!kl) {
+ free(keylist);
+ free(data);
+ return RPMRC_FAIL;
+ }
+ keylist = kl;
+ }
+ keyl = decodekeyl(key, &hl);
+ keylist[nkeylist++] = key + hl - data;
+ keylist[nkeylist++] = keyl;
+ key += hl + keyl;
+ if (terminate)
+ *terminate = 0;
+ terminate = key;
+ }
+ if (terminate)
+ *terminate = 0;
+ rpmidxListSort(idxdb, keylist, nkeylist, data);
+ *keylistp = keylist;
+ *nkeylistp = nkeylist;
+ *datap = data;
+ return RPMRC_OK;
+}
+
+
+static int rpmidxInitInternal(rpmidxdb idxdb)
+{
+ if (idxdb->xdb) {
+ unsigned int id;
+ int rc = rpmxdbLookupBlob(idxdb->xdb, &id, idxdb->xdbtag, IDXDB_XDB_SUBTAG, 0);
+ if (rc == RPMRC_OK && id) {
+ idxdb->xdbid = id;
+ return RPMRC_OK; /* somebody else was faster */
+ }
+ if (rc && rc != RPMRC_NOTFOUND)
+ return rc;
+ } else {
+#ifdef IDXDB_FILESUPPORT
+ struct stat stb;
+ if (stat(idxdb->filename, &stb))
+ return RPMRC_FAIL;
+ if (stb.st_size) /* somebody else was faster */
+ return rpmidxHandleObsolete(idxdb);
+#else
+ return RPMRC_FAIL;
+#endif
+ }
+ return rpmidxRebuildInternal(idxdb);
+}
+
+static int rpmidxLock(rpmidxdb idxdb, int excl)
+{
+ if (excl && idxdb->rdonly)
+ return RPMRC_FAIL;
+ if (idxdb->xdb)
+ return rpmxdbLock(idxdb->xdb, excl);
+ else
+ return rpmpkgLock(idxdb->pkgdb, excl);
+}
+
+static int rpmidxUnlock(rpmidxdb idxdb, int excl)
+{
+ if (idxdb->xdb)
+ return rpmxdbUnlock(idxdb->xdb, excl);
+ else
+ return rpmpkgUnlock(idxdb->pkgdb, excl);
+}
+
+static int rpmidxLockReadHeader(rpmidxdb idxdb, int excl)
+{
+ if (rpmidxLock(idxdb, excl))
+ return RPMRC_FAIL;
+ if (rpmidxReadHeader(idxdb)) {
+ rpmidxUnlock(idxdb, excl);
+ return RPMRC_FAIL;
+ }
+ return RPMRC_OK;
+}
+
+static int rpmidxInit(rpmidxdb idxdb)
+{
+ int rc;
+ if (rpmidxLock(idxdb, 1))
+ return RPMRC_FAIL;
+ rc = rpmidxInitInternal(idxdb);
+ rpmidxUnlock(idxdb, 1);
+ return rc;
+}
+
+int rpmidxOpen(rpmidxdb *idxdbp, rpmpkgdb pkgdb, const char *filename, int flags, int mode)
+{
+#ifdef IDXDB_FILESUPPORT
+ struct stat stb;
+ rpmidxdb idxdb;
+
+ *idxdbp = 0;
+ idxdb = calloc(1, sizeof(*idxdb));
+ if (!idxdb)
+ return RPMRC_FAIL;
+ idxdb->filename = strdup(filename);
+ if (!idxdb->filename) {
+ free(idxdb);
+ return RPMRC_FAIL;
+ }
+ if ((flags & (O_RDONLY|O_RDWR)) == O_RDONLY)
+ idxdb->rdonly = 1;
+ if ((idxdb->fd = open(filename, flags, mode)) == -1) {
+ free(idxdb->filename);
+ free(idxdb);
+ return RPMRC_FAIL;
+ }
+ if (fstat(idxdb->fd, &stb)) {
+ close(idxdb->fd);
+ free(idxdb->filename);
+ free(idxdb);
+ return RPMRC_FAIL;
+ }
+ idxdb->pkgdb = pkgdb;
+ idxdb->flags = flags;
+ idxdb->mode = mode;
+ idxdb->pagesize = sysconf(_SC_PAGE_SIZE);
+ if (stb.st_size == 0) {
+ if (rpmidxInit(idxdb)) {
+ close(idxdb->fd);
+ free(idxdb->filename);
+ free(idxdb);
+ return RPMRC_FAIL;
+ }
+ }
+ *idxdbp = idxdb;
+ return RPMRC_OK;
+#else
+ return RPMRC_FAIL;
+#endif
+}
+
+int rpmidxOpenXdb(rpmidxdb *idxdbp, rpmpkgdb pkgdb, rpmxdb xdb, unsigned int xdbtag)
+{
+ rpmidxdb idxdb;
+ unsigned int id;
+ *idxdbp = 0;
+ int rc;
+
+ if (rpmxdbLock(xdb, 0))
+ return RPMRC_FAIL;
+ rc = rpmxdbLookupBlob(xdb, &id, xdbtag, IDXDB_XDB_SUBTAG, 0);
+ if (rc == RPMRC_NOTFOUND)
+ id = 0;
+ else if (rc) {
+ rpmxdbUnlock(xdb, 0);
+ return RPMRC_FAIL;
+ }
+ idxdb = calloc(1, sizeof(*idxdb));
+ if (!idxdb) {
+ rpmxdbUnlock(xdb, 0);
+ return RPMRC_FAIL;
+ }
+ idxdb->fd = -1;
+ idxdb->xdb = xdb;
+ idxdb->xdbtag = xdbtag;
+ idxdb->xdbid = id;
+ idxdb->pkgdb = pkgdb;
+ idxdb->pagesize = sysconf(_SC_PAGE_SIZE);
+ if (rpmxdbIsRdonly(xdb))
+ idxdb->rdonly = 1;
+ if (!id) {
+ if (rpmidxInit(idxdb)) {
+ free(idxdb);
+ rpmxdbUnlock(xdb, 0);
+ return RPMRC_FAIL;
+ }
+ }
+ *idxdbp = idxdb;
+ rpmxdbUnlock(xdb, 0);
+ return RPMRC_OK;
+}
+
+int rpmidxDelXdb(rpmpkgdb pkgdb, rpmxdb xdb, unsigned int xdbtag)
+{
+ unsigned int id;
+ int rc;
+ if (rpmxdbLock(xdb, 1))
+ return RPMRC_FAIL;
+ rc = rpmxdbLookupBlob(xdb, &id, xdbtag, IDXDB_XDB_SUBTAG, 0);
+ if (rc == RPMRC_NOTFOUND)
+ id = 0;
+ else if (rc) {
+ rpmxdbUnlock(xdb, 1);
+ return rc;
+ }
+ if (id && rpmxdbDelBlob(xdb, id)) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_OK;
+}
+
+void rpmidxClose(rpmidxdb idxdb)
+{
+ rpmidxUnmap(idxdb);
+ if (idxdb->fd >= 0) {
+ close(idxdb->fd);
+ idxdb->fd = -1;
+ }
+ if (idxdb->filename)
+ free(idxdb->filename);
+ free(idxdb);
+}
+
+int rpmidxPut(rpmidxdb idxdb, const unsigned char *key, unsigned int keyl, unsigned int pkgidx, unsigned int datidx)
+{
+ int rc;
+ if (!pkgidx || datidx >= 0x80000000) {
+ return RPMRC_FAIL;
+ }
+ if (rpmidxLockReadHeader(idxdb, 1))
+ return RPMRC_FAIL;
+ rc = rpmidxPutInternal(idxdb, key, keyl, pkgidx, datidx);
+ rpmidxUnlock(idxdb, 1);
+ return rc;
+}
+
+int rpmidxDel(rpmidxdb idxdb, const unsigned char *key, unsigned int keyl, unsigned int pkgidx, unsigned int datidx)
+{
+ int rc;
+ if (!pkgidx || datidx >= 0x80000000) {
+ return RPMRC_FAIL;
+ }
+ if (rpmidxLockReadHeader(idxdb, 1))
+ return RPMRC_FAIL;
+ rc = rpmidxDelInternal(idxdb, key, keyl, pkgidx, datidx);
+ rpmidxUnlock(idxdb, 1);
+ return rc;
+}
+
+int rpmidxGet(rpmidxdb idxdb, const unsigned char *key, unsigned int keyl, unsigned int **pkgidxlistp, unsigned int *pkgidxnump)
+{
+ int rc;
+ *pkgidxlistp = 0;
+ *pkgidxnump = 0;
+ if (rpmidxLockReadHeader(idxdb, 0))
+ return RPMRC_FAIL;
+ rc = rpmidxGetInternal(idxdb, key, keyl, pkgidxlistp, pkgidxnump);
+ rpmidxUnlock(idxdb, 0);
+ return rc;
+}
+
+int rpmidxList(rpmidxdb idxdb, unsigned int **keylistp, unsigned int *nkeylistp, unsigned char **datap)
+{
+ int rc;
+ *keylistp = 0;
+ *nkeylistp = 0;
+ if (rpmidxLockReadHeader(idxdb, 0))
+ return RPMRC_FAIL;
+ rc = rpmidxListInternal(idxdb, keylistp, nkeylistp, datap);
+ rpmidxUnlock(idxdb, 0);
+ return rc;
+}
+
+int rpmidxStats(rpmidxdb idxdb)
+{
+ if (rpmidxLockReadHeader(idxdb, 0))
+ return RPMRC_FAIL;
+ printf("--- IndexDB Stats\n");
+ if (idxdb->xdb) {
+ printf("Xdb tag: %d, id: %d\n", idxdb->xdbtag, idxdb->xdbid);
+ } else {
+ printf("Filename: %s\n", idxdb->filename);
+ }
+ printf("Generation: %u\n", idxdb->generation);
+ printf("Slots: %u\n", idxdb->nslots);
+ printf("Used slots: %u\n", idxdb->usedslots);
+ printf("Dummy slots: %u\n", idxdb->dummyslots);
+ printf("Key data size: %u, left %u\n", idxdb->keyend, idxdb->key_size - idxdb->keyend);
+ printf("Key excess: %u\n", idxdb->keyexcess);
+ printf("XMask: 0x%08x\n", idxdb->xmask);
+ rpmidxUnlock(idxdb, 0);
+ return RPMRC_OK;
+}
diff --git a/lib/backend/ndb/rpmidx.h b/lib/backend/ndb/rpmidx.h
new file mode 100644
index 000000000..e89bd82f1
--- /dev/null
+++ b/lib/backend/ndb/rpmidx.h
@@ -0,0 +1,18 @@
+#include "rpmpkg.h"
+#include "rpmxdb.h"
+
+struct rpmidxdb_s;
+typedef struct rpmidxdb_s *rpmidxdb;
+
+int rpmidxOpen(rpmidxdb *idxdbp, rpmpkgdb pkgdb, const char *filename, int flags, int mode);
+int rpmidxOpenXdb(rpmidxdb *idxdbp, rpmpkgdb pkgdb, rpmxdb xdb, unsigned int xdbtag);
+int rpmidxDelXdb(rpmpkgdb pkgdb, rpmxdb xdb, unsigned int xdbtag);
+void rpmidxClose(rpmidxdb idxdbp);
+
+int rpmidxGet(rpmidxdb idxdb, const unsigned char *key, unsigned int keyl, unsigned int **pkgidxlist, unsigned int *pkgidxnum);
+int rpmidxPut(rpmidxdb idxdb, const unsigned char *key, unsigned int keyl, unsigned int pkgidx, unsigned int datidx);
+int rpmidxDel(rpmidxdb idxdb, const unsigned char *key, unsigned int keyl, unsigned int pkgidx, unsigned int datidx);
+int rpmidxList(rpmidxdb idxdb, unsigned int **keylistp, unsigned int *nkeylistp, unsigned char **datap);
+
+int rpmidxStats(rpmidxdb idxdb);
+
diff --git a/lib/backend/ndb/rpmpkg.c b/lib/backend/ndb/rpmpkg.c
new file mode 100644
index 000000000..68b03eeb7
--- /dev/null
+++ b/lib/backend/ndb/rpmpkg.c
@@ -0,0 +1,1312 @@
+#include "system.h"
+
+#include <rpm/rpmlog.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <libgen.h>
+
+#include "rpmpkg.h"
+
+#define RPMRC_FAIL 2
+#define RPMRC_NOTFOUND 1
+#define RPMRC_OK 0
+
+#ifdef RPMPKG_LZO
+static int rpmpkgLZOCompress(unsigned char **blobp, unsigned int *bloblp);
+static int rpmpkgLZODecompress(unsigned char **blobp, unsigned int *bloblp);
+#endif
+
+static int rpmpkgVerifyblob(rpmpkgdb pkgdb, unsigned int pkgidx, unsigned int blkoff, unsigned int blkcnt);
+
+typedef struct pkgslot_s {
+ unsigned int pkgidx;
+ unsigned int blkoff;
+ unsigned int blkcnt;
+ unsigned int slotno;
+} pkgslot;
+
+typedef struct rpmpkgdb_s {
+ int fd; /* our file descriptor */
+ int flags;
+ int mode;
+
+ int rdonly;
+
+ unsigned int locked_shared;
+ unsigned int locked_excl;
+
+ int header_ok; /* header data (e.g. generation) is valid */
+ unsigned int generation;
+ unsigned int slotnpages;
+ unsigned int nextpkgidx;
+
+ struct pkgslot_s *slots;
+ unsigned int aslots; /* allocated slots */
+ unsigned int nslots; /* used slots */
+
+ unsigned int *slothash;
+ unsigned int nslothash;
+
+ unsigned int freeslot; /* first free slot */
+ int slotorder;
+
+ char *filename;
+ unsigned int fileblks; /* file size in blks */
+ int dofsync;
+} * rpmpkgdb;
+
+#define SLOTORDER_UNORDERED 0
+#define SLOTORDER_BLKOFF 1
+
+
+static inline unsigned int le2h(unsigned char *p)
+{
+ return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
+}
+
+static inline void h2le(unsigned int x, unsigned char *p)
+{
+ p[0] = x;
+ p[1] = x >> 8;
+ p[2] = x >> 16;
+ p[3] = x >> 24;
+}
+
+/* adler 32 algorithm taken from RFC 1950 */
+#define ADLER32_INIT 1
+static unsigned int update_adler32(unsigned int adler, unsigned char *buf, unsigned int len)
+{
+ unsigned int s1 = adler & 0xffff;
+ unsigned int s2 = (adler >> 16) & 0xffff;
+ int n;
+
+ for (; len >= 5552; len -= 5552) {
+ for (n = 0; n < 5552; n++) {
+ s1 += *buf++;
+ s2 += s1;
+ }
+ s1 %= 65521;
+ s2 %= 65521;
+ }
+ for (n = 0; n < len; n++) {
+ s1 += *buf++;
+ s2 += s1;
+ }
+ return ((s2 % 65521) << 16) + (s1 % 65521);
+}
+
+/*** Header management ***/
+
+#define PKGDB_MAGIC ('R' | 'p' << 8 | 'm' << 16 | 'P' << 24)
+#define PKGDB_VERSION 0
+
+/* must be a multiple of SLOT_SIZE! */
+#define PKGDB_HEADER_SIZE 32
+
+#define PKGDB_OFFSET_MAGIC 0
+#define PKGDB_OFFSET_VERSION 4
+#define PKGDB_OFFSET_GENERATION 8
+#define PKGDB_OFFSET_SLOTNPAGES 12
+#define PKGDB_OFFSET_NEXTPKGIDX 16
+
+static int rpmpkgReadHeader(rpmpkgdb pkgdb)
+{
+ unsigned int generation, slotnpages, nextpkgidx, version;
+ unsigned char header[PKGDB_HEADER_SIZE];
+
+ /* if we always head the write lock then our data matches */
+ if (pkgdb->header_ok)
+ return RPMRC_OK;
+ if (pread(pkgdb->fd, header, PKGDB_HEADER_SIZE, 0) != PKGDB_HEADER_SIZE) {
+ return RPMRC_FAIL;
+ }
+ if (le2h(header + PKGDB_OFFSET_MAGIC) != PKGDB_MAGIC) {
+ return RPMRC_FAIL;
+ }
+ version = le2h(header + PKGDB_OFFSET_VERSION);
+ if (version != PKGDB_VERSION) {
+ rpmlog(RPMLOG_ERR, _("rpmpkg: Version mismatch. Expected version: %u. "
+ "Found version: %u\n"), PKGDB_VERSION, version);
+ return RPMRC_FAIL;
+ }
+ generation = le2h(header + PKGDB_OFFSET_GENERATION);
+ slotnpages = le2h(header + PKGDB_OFFSET_SLOTNPAGES);
+ nextpkgidx = le2h(header + PKGDB_OFFSET_NEXTPKGIDX);
+ /* free slots if our internal data no longer matches */
+ if (pkgdb->slots && (pkgdb->generation != generation || pkgdb->slotnpages != slotnpages)) {
+ free(pkgdb->slots);
+ pkgdb->slots = 0;
+ if (pkgdb->slothash) {
+ free(pkgdb->slothash);
+ pkgdb->slothash = 0;
+ }
+ }
+ pkgdb->generation = generation;
+ pkgdb->slotnpages = slotnpages;
+ pkgdb->nextpkgidx = nextpkgidx;
+ pkgdb->header_ok = 1;
+ return RPMRC_OK;
+}
+
+static int rpmpkgWriteHeader(rpmpkgdb pkgdb)
+{
+ unsigned char header[PKGDB_HEADER_SIZE];
+ memset(header, 0, sizeof(header));
+ h2le(PKGDB_MAGIC, header + PKGDB_OFFSET_MAGIC);
+ h2le(PKGDB_VERSION, header + PKGDB_OFFSET_VERSION);
+ h2le(pkgdb->generation, header + PKGDB_OFFSET_GENERATION);
+ h2le(pkgdb->slotnpages, header + PKGDB_OFFSET_SLOTNPAGES);
+ h2le(pkgdb->nextpkgidx, header + PKGDB_OFFSET_NEXTPKGIDX);
+ if (pwrite(pkgdb->fd, header, sizeof(header), 0) != sizeof(header)) {
+ return RPMRC_FAIL;
+ }
+ if (pkgdb->dofsync && fsync(pkgdb->fd))
+ return RPMRC_FAIL; /* write error */
+ return RPMRC_OK;
+}
+
+/*** Slot management ***/
+
+#define SLOT_MAGIC ('S' | 'l' << 8 | 'o' << 16 | 't' << 24)
+
+#define SLOT_SIZE 16
+#define BLK_SIZE 16
+#define PAGE_SIZE 4096
+
+/* the first slots (i.e. 32 bytes) are used for the header */
+#define SLOT_START (PKGDB_HEADER_SIZE / SLOT_SIZE)
+
+static inline unsigned int hashpkgidx(unsigned int h)
+{
+ h *= 0x5bd1e995;
+ h ^= h >> 16;
+ return h;
+}
+
+static int rpmpkgHashSlots(rpmpkgdb pkgdb)
+{
+ unsigned int nslots, num;
+ unsigned int *hash;
+ unsigned int h, hh, hmask;
+ int i;
+ pkgslot *slot;
+
+ pkgdb->nslothash = 0;
+ num = pkgdb->nslots;
+ while (num & (num - 1))
+ num = num & (num - 1);
+ num *= 4;
+ hash = pkgdb->slothash;
+ if (!hash || pkgdb->nslothash != num) {
+ free(pkgdb->slothash);
+ hash = pkgdb->slothash = calloc(num, sizeof(unsigned int));
+ if (!hash)
+ return RPMRC_FAIL;
+ pkgdb->nslothash = num;
+ } else {
+ memset(hash, 0, num * sizeof(unsigned int));
+ }
+ hmask = num - 1;
+ nslots = pkgdb->nslots;
+ for (i = 0, slot = pkgdb->slots; i < nslots; i++, slot++) {
+ for (h = hashpkgidx(slot->pkgidx) & hmask, hh = 7; hash[h] != 0; h = (h + hh++) & hmask)
+ ;
+ hash[h] = i + 1;
+ }
+ pkgdb->slothash = hash;
+ pkgdb->nslothash = num;
+ return RPMRC_OK;
+}
+
+static int rpmpkgReadSlots(rpmpkgdb pkgdb)
+{
+ unsigned int slotnpages = pkgdb->slotnpages;
+ struct stat stb;
+ unsigned char pagebuf[PAGE_SIZE];
+ unsigned int page;
+ unsigned int i, minblkoff, fileblks, slotno, freeslot, o;
+ pkgslot *slot;
+
+ /* free old slot data */
+ if (pkgdb->slots) {
+ free(pkgdb->slots);
+ pkgdb->slots = 0;
+ }
+ if (pkgdb->slothash) {
+ free(pkgdb->slothash);
+ pkgdb->slothash = 0;
+ }
+ pkgdb->nslots = 0;
+ pkgdb->freeslot = 0;
+
+ /* calculate current database size in blks */
+ if (fstat(pkgdb->fd, &stb))
+ return RPMRC_FAIL;
+ if (stb.st_size % BLK_SIZE)
+ return RPMRC_FAIL; /* hmm */
+ fileblks = stb.st_size / BLK_SIZE;
+
+ /* read (and somewhat verify) all slots */
+ pkgdb->aslots = slotnpages * (PAGE_SIZE / SLOT_SIZE);
+ pkgdb->slots = calloc(pkgdb->aslots, sizeof(*pkgdb->slots));
+ if (!pkgdb->slots) {
+ return RPMRC_FAIL;
+ }
+ i = 0;
+ slot = pkgdb->slots;
+ minblkoff = slotnpages * (PAGE_SIZE / BLK_SIZE);
+ slotno = SLOT_START;
+ freeslot = 0;
+ for (page = 0; page < slotnpages; page++) {
+ if (pread(pkgdb->fd, pagebuf, PAGE_SIZE, page * PAGE_SIZE) != PAGE_SIZE)
+ return RPMRC_FAIL;
+ for (o = page ? 0 : SLOT_START * SLOT_SIZE; o < PAGE_SIZE; o += SLOT_SIZE, slotno++) {
+ unsigned char *pp = pagebuf + o;
+ unsigned int blkoff, blkcnt, pkgidx;
+ if (le2h(pp) != SLOT_MAGIC) {
+ return RPMRC_FAIL;
+ }
+ blkoff = le2h(pp + 8);
+ if (!blkoff) {
+ if (!freeslot)
+ freeslot = slotno;
+ continue;
+ }
+ pkgidx = le2h(pp + 4);
+ blkcnt = le2h(pp + 12);
+ slot->pkgidx = pkgidx;
+ slot->blkoff = blkoff;
+ slot->blkcnt = blkcnt;
+ slot->slotno = slotno;
+ if (slot->blkoff + slot->blkcnt > fileblks)
+ return RPMRC_FAIL; /* truncated database */
+ if (!slot->pkgidx || !slot->blkcnt || slot->blkoff < minblkoff)
+ return RPMRC_FAIL; /* bad entry */
+ i++;
+ slot++;
+ }
+ }
+ pkgdb->nslots = i;
+ pkgdb->slotorder = SLOTORDER_UNORDERED; /* XXX: always order? */
+ pkgdb->fileblks = fileblks;
+ pkgdb->freeslot = freeslot;
+ if (rpmpkgHashSlots(pkgdb)) {
+ free(pkgdb->slots);
+ pkgdb->slots = 0;
+ return RPMRC_FAIL;
+ }
+ return RPMRC_OK;
+}
+
+static int orderslots_blkoff_cmp(const void *a, const void *b)
+{
+ unsigned int blkoffa = ((const pkgslot *)a)->blkoff;
+ unsigned int blkoffb = ((const pkgslot *)b)->blkoff;
+ return blkoffa > blkoffb ? 1 : blkoffa < blkoffb ? -1 : 0;
+}
+
+static void rpmpkgOrderSlots(rpmpkgdb pkgdb, int slotorder)
+{
+ if (pkgdb->slotorder == slotorder)
+ return;
+ if (slotorder == SLOTORDER_BLKOFF) {
+ if (pkgdb->nslots > 1)
+ qsort(pkgdb->slots, pkgdb->nslots, sizeof(*pkgdb->slots), orderslots_blkoff_cmp);
+ }
+ pkgdb->slotorder = slotorder;
+ rpmpkgHashSlots(pkgdb);
+}
+
+static inline pkgslot *rpmpkgFindSlot(rpmpkgdb pkgdb, unsigned int pkgidx)
+{
+ unsigned int i, h, hh, hmask = pkgdb->nslothash - 1;
+ unsigned int *hash = pkgdb->slothash;
+
+ for (h = hashpkgidx(pkgidx) & hmask, hh = 7; (i = hash[h]) != 0; h = (h + hh++) & hmask)
+ if (pkgdb->slots[i - 1].pkgidx == pkgidx)
+ return pkgdb->slots + (i - 1);
+ return 0;
+}
+
+static int rpmpkgFindEmptyOffset(rpmpkgdb pkgdb, unsigned int pkgidx, unsigned int blkcnt, unsigned *blkoffp, pkgslot **oldslotp, int dontprepend)
+{
+ unsigned int i, nslots = pkgdb->nslots;
+ unsigned int bestblkoff = 0;
+ unsigned int freecnt, bestfreecnt = 0;
+ unsigned int lastblkend = pkgdb->slotnpages * (PAGE_SIZE / BLK_SIZE);
+ pkgslot *slot, *oldslot = 0;
+
+ if (pkgdb->slotorder != SLOTORDER_BLKOFF)
+ rpmpkgOrderSlots(pkgdb, SLOTORDER_BLKOFF);
+
+ if (dontprepend && nslots) {
+ lastblkend = pkgdb->slots[0].blkoff;
+ }
+ /* best fit strategy */
+ for (i = 0, slot = pkgdb->slots; i < nslots; i++, slot++) {
+ if (slot->blkoff < lastblkend) {
+ return RPMRC_FAIL; /* eek, slots overlap! */
+ }
+ if (slot->pkgidx == pkgidx) {
+ if (oldslot) {
+ return RPMRC_FAIL; /* eek, two slots with our pkgid ! */
+ }
+ oldslot = slot;
+ }
+ freecnt = slot->blkoff - lastblkend;
+ if (freecnt >= blkcnt) {
+ if (!bestblkoff || bestfreecnt > freecnt) {
+ bestblkoff = lastblkend;
+ bestfreecnt = freecnt;
+ }
+ }
+ lastblkend = slot->blkoff + slot->blkcnt;
+ }
+ if (!bestblkoff) {
+ bestblkoff = lastblkend; /* append to end */
+ }
+ *oldslotp = oldslot;
+ *blkoffp = bestblkoff;
+ return RPMRC_OK;
+}
+
+static int rpmpkgNeighbourCheck(rpmpkgdb pkgdb, unsigned int blkoff, unsigned int blkcnt, unsigned int *newblkcnt)
+{
+ unsigned int i, nslots = pkgdb->nslots;
+ unsigned int lastblkend = pkgdb->slotnpages * (PAGE_SIZE / BLK_SIZE);
+ pkgslot *slot, *left = 0, *right = 0;
+
+ if (pkgdb->slotorder != SLOTORDER_BLKOFF)
+ rpmpkgOrderSlots(pkgdb, SLOTORDER_BLKOFF);
+ if (blkoff < lastblkend)
+ return RPMRC_FAIL;
+ for (i = 0, slot = pkgdb->slots; i < nslots; i++, slot++) {
+ if (slot->blkoff < lastblkend)
+ return RPMRC_FAIL; /* eek, slots overlap! */
+ if (slot->blkoff < blkoff)
+ left = slot;
+ if (!right && slot->blkoff >= blkoff)
+ right = slot;
+ lastblkend = slot->blkoff + slot->blkcnt;
+ }
+ if (left && left->blkoff + left->blkcnt != blkoff)
+ return RPMRC_FAIL; /* must always start right after the block */
+ if (!left && blkoff != pkgdb->slotnpages * (PAGE_SIZE / BLK_SIZE))
+ return RPMRC_FAIL;
+ if (right && right->blkoff < blkoff + blkcnt)
+ return RPMRC_FAIL;
+ /* check if neighbour blobs are in good shape */
+ if (left && rpmpkgVerifyblob(pkgdb, left->pkgidx, left->blkoff, left->blkcnt) != RPMRC_OK)
+ return RPMRC_FAIL;
+ if (right && rpmpkgVerifyblob(pkgdb, right->pkgidx, right->blkoff, right->blkcnt) != RPMRC_OK)
+ return RPMRC_FAIL;
+ *newblkcnt = right ? right->blkoff - blkoff : blkcnt;
+ /* bounds are intect. free area. */
+ return RPMRC_OK;
+}
+
+static int rpmpkgWriteslot(rpmpkgdb pkgdb, unsigned int slotno, unsigned int pkgidx, unsigned int blkoff, unsigned int blkcnt)
+{
+ unsigned char buf[SLOT_SIZE];
+ /* sanity */
+ if (slotno < SLOT_START)
+ return RPMRC_FAIL;
+ if (blkoff && slotno == pkgdb->freeslot)
+ pkgdb->freeslot = 0;
+ h2le(SLOT_MAGIC, buf);
+ h2le(pkgidx, buf + 4);
+ h2le(blkoff, buf + 8);
+ h2le(blkcnt, buf + 12);
+ if (pwrite(pkgdb->fd, buf, sizeof(buf), slotno * SLOT_SIZE) != sizeof(buf)) {
+ return RPMRC_FAIL;
+ }
+ pkgdb->generation++;
+ if (rpmpkgWriteHeader(pkgdb)) {
+ return RPMRC_FAIL;
+ }
+ return RPMRC_OK;
+}
+
+static int rpmpkgWriteEmptySlotpage(rpmpkgdb pkgdb, int pageno)
+{
+ unsigned char page[PAGE_SIZE];
+ int i, off = pageno ? 0 : SLOT_START * SLOT_SIZE;
+ memset(page, 0, sizeof(page));
+ for (i = 0; i < PAGE_SIZE / SLOT_SIZE; i++)
+ h2le(SLOT_MAGIC, page + i * SLOT_SIZE);
+ if (pwrite(pkgdb->fd, page, PAGE_SIZE - off, pageno * PAGE_SIZE + off) != PAGE_SIZE - off) {
+ return RPMRC_FAIL;
+ }
+ if (pkgdb->dofsync && fsync(pkgdb->fd)) {
+ return RPMRC_FAIL; /* write error */
+ }
+ return RPMRC_OK;
+}
+
+/*** Blk primitives ***/
+
+static int rpmpkgZeroBlks(rpmpkgdb pkgdb, unsigned int blkoff, unsigned int blkcnt)
+{
+ unsigned char buf[65536];
+ unsigned int towrite;
+ off_t fileoff;
+
+ memset(buf, 0, sizeof(buf));
+ fileoff = (off_t)blkoff * BLK_SIZE;
+ for (towrite = blkcnt * BLK_SIZE; towrite; ) {
+ unsigned int chunk = towrite > 65536 ? 65536 : towrite;
+ if (pwrite(pkgdb->fd, buf, chunk, fileoff) != chunk) {
+ return RPMRC_FAIL; /* write error */
+ }
+ fileoff += chunk;
+ towrite -= chunk;
+ }
+ if (blkoff + blkcnt > pkgdb->fileblks)
+ pkgdb->fileblks = blkoff + blkcnt;
+ return RPMRC_OK;
+}
+
+static int rpmpkgValidateZeroCheck(rpmpkgdb pkgdb, unsigned int blkoff, unsigned int blkcnt)
+{
+ unsigned long long buf[(65536 / sizeof(unsigned long long)) + 1];
+ off_t fileoff;
+ off_t tocheck;
+ int i;
+
+ if (blkoff > pkgdb->fileblks)
+ return RPMRC_FAIL; /* huh? */
+ fileoff = (off_t)blkoff * BLK_SIZE;
+ tocheck = blkoff + blkcnt > pkgdb->fileblks ? pkgdb->fileblks - blkoff : blkcnt;
+ tocheck *= BLK_SIZE;
+ while (tocheck >= 65536) {
+ if (pread(pkgdb->fd, (void *)buf, 65536, fileoff) != 65536)
+ return RPMRC_FAIL; /* read error */
+ for (i = 0; i < 65536 / sizeof(unsigned long long); i++)
+ if (buf[i])
+ return RPMRC_FAIL; /* not empty */
+ fileoff += 65536;
+ tocheck -= 65536;
+ }
+ if (tocheck) {
+ int cnt = (int)tocheck / sizeof(unsigned long long);
+ buf[cnt++] = 0;
+ if (pread(pkgdb->fd, (void *)buf, tocheck, fileoff) != tocheck)
+ return RPMRC_FAIL; /* read error */
+ for (i = 0; i < cnt; i++)
+ if (buf[i])
+ return RPMRC_FAIL; /* not empty */
+ }
+ return RPMRC_OK;
+}
+
+static int rpmpkgValidateZero(rpmpkgdb pkgdb, unsigned int blkoff, unsigned int blkcnt)
+{
+ if (rpmpkgValidateZeroCheck(pkgdb, blkoff, blkcnt) == RPMRC_OK)
+ return RPMRC_OK;
+ rpmlog(RPMLOG_WARNING, _("rpmpkg: detected non-zero blob, trying auto repair\n"));
+ /* auto-repair interrupted transactions */
+ if (rpmpkgNeighbourCheck(pkgdb, blkoff, blkcnt, &blkcnt) != RPMRC_OK)
+ return RPMRC_FAIL;
+ if (rpmpkgZeroBlks(pkgdb, blkoff, blkcnt) != RPMRC_OK)
+ return RPMRC_FAIL;
+ return RPMRC_OK;
+}
+
+
+/*** Blob primitives ***/
+
+/* head: magic + pkgidx + timestamp + bloblen */
+/* tail: adler32 + bloblen + magic */
+
+#define BLOBHEAD_MAGIC ('B' | 'l' << 8 | 'b' << 16 | 'S' << 24)
+#define BLOBTAIL_MAGIC ('B' | 'l' << 8 | 'b' << 16 | 'E' << 24)
+
+#define BLOBHEAD_SIZE (4 + 4 + 4 + 4)
+#define BLOBTAIL_SIZE (4 + 4 + 4)
+
+static int rpmpkgReadBlob(rpmpkgdb pkgdb, unsigned int pkgidx, unsigned int blkoff, unsigned int blkcnt, unsigned char *blob, unsigned int *bloblp, unsigned int *tstampp)
+{
+ unsigned char buf[BLOBHEAD_SIZE > BLOBTAIL_SIZE ? BLOBHEAD_SIZE : BLOBTAIL_SIZE];
+ unsigned int bloblen, toread, tstamp;
+ off_t fileoff;
+ unsigned int adl;
+ int verifyadler = bloblp ? 0 : 1;
+
+ /* sanity */
+ if (blkcnt < (BLOBHEAD_SIZE + BLOBTAIL_SIZE + BLK_SIZE - 1) / BLK_SIZE)
+ return RPMRC_FAIL; /* blkcnt too small */
+ /* read header */
+ fileoff = (off_t)blkoff * BLK_SIZE;
+ if (pread(pkgdb->fd, buf, BLOBHEAD_SIZE, fileoff) != BLOBHEAD_SIZE)
+ return RPMRC_FAIL; /* read error */
+ if (le2h(buf) != BLOBHEAD_MAGIC)
+ return RPMRC_FAIL; /* bad blob */
+ if (le2h(buf + 4) != pkgidx)
+ return RPMRC_FAIL; /* bad blob */
+ tstamp = le2h(buf + 8);
+ bloblen = le2h(buf + 12);
+ if (blkcnt != (BLOBHEAD_SIZE + bloblen + BLOBTAIL_SIZE + BLK_SIZE - 1) / BLK_SIZE)
+ return RPMRC_FAIL; /* bad blob */
+ adl = ADLER32_INIT;
+ if (verifyadler)
+ adl = update_adler32(adl, buf, BLOBHEAD_SIZE);
+ /* read in 64K chunks */
+ fileoff += BLOBHEAD_SIZE;
+ toread = blkcnt * BLK_SIZE - BLOBHEAD_SIZE;
+ if (!bloblp)
+ toread -= BLOBTAIL_SIZE;
+ while (toread) {
+ unsigned int chunk = toread > 65536 ? 65536 : toread;
+ if (pread(pkgdb->fd, blob, chunk, fileoff) != chunk) {
+ return RPMRC_FAIL; /* read error */
+ }
+ if (verifyadler) {
+ if (!bloblp)
+ adl = update_adler32(adl, blob, chunk);
+ else if (toread > BLOBTAIL_SIZE)
+ adl = update_adler32(adl, blob, toread - BLOBTAIL_SIZE > chunk ? chunk : toread - BLOBTAIL_SIZE);
+ }
+ if (bloblp)
+ blob += chunk;
+ toread -= chunk;
+ fileoff += chunk;
+ }
+ /* read trailer */
+ if (bloblp) {
+ memcpy(buf, blob - BLOBTAIL_SIZE, BLOBTAIL_SIZE);
+ } else if (pread(pkgdb->fd, buf, BLOBTAIL_SIZE, fileoff) != BLOBTAIL_SIZE) {
+ return RPMRC_FAIL; /* read error */
+ }
+ if (verifyadler && le2h(buf) != adl) {
+ return RPMRC_FAIL; /* bad blob, adler32 mismatch */
+ }
+ if (le2h(buf + 4) != bloblen) {
+ return RPMRC_FAIL; /* bad blob, bloblen mismatch */
+ }
+ if (le2h(buf + 8) != BLOBTAIL_MAGIC) {
+ return RPMRC_FAIL; /* bad blob */
+ }
+ if (bloblp)
+ *bloblp = bloblen;
+ if (tstampp)
+ *tstampp = tstamp;
+ return RPMRC_OK;
+}
+
+static int rpmpkgVerifyblob(rpmpkgdb pkgdb, unsigned int pkgidx, unsigned int blkoff, unsigned int blkcnt)
+{
+ unsigned char buf[65536];
+ return rpmpkgReadBlob(pkgdb, pkgidx, blkoff, blkcnt, buf, 0, 0);
+}
+
+static int rpmpkgWriteBlob(rpmpkgdb pkgdb, unsigned int pkgidx, unsigned int blkoff, unsigned int blkcnt, unsigned char *blob, unsigned int blobl, unsigned int now)
+{
+ unsigned char buf[(BLOBHEAD_SIZE > BLOBTAIL_SIZE ? BLOBHEAD_SIZE : BLOBTAIL_SIZE) + BLK_SIZE];
+ unsigned int towrite, pad;
+ unsigned int adl;
+ off_t fileoff;
+
+ /* sanity */
+ if (blkcnt < (BLOBHEAD_SIZE + BLOBTAIL_SIZE + BLK_SIZE - 1) / BLK_SIZE)
+ return RPMRC_FAIL; /* blkcnt too small */
+ if (blkcnt != (BLOBHEAD_SIZE + blobl + BLOBTAIL_SIZE + BLK_SIZE - 1) / BLK_SIZE)
+ return RPMRC_FAIL; /* blkcnt mismatch */
+ fileoff = (off_t)blkoff * BLK_SIZE;
+ h2le(BLOBHEAD_MAGIC, buf);
+ h2le(pkgidx, buf + 4);
+ h2le(now, buf + 8);
+ h2le(blobl, buf + 12);
+ if (pwrite(pkgdb->fd, buf, BLOBHEAD_SIZE, fileoff) != BLOBHEAD_SIZE) {
+ return RPMRC_FAIL; /* write error */
+ }
+ adl = ADLER32_INIT;
+ adl = update_adler32(adl, buf, BLOBHEAD_SIZE);
+ /* write in 64K chunks */
+ fileoff += BLOBHEAD_SIZE;
+ for (towrite = blobl; towrite;) {
+ unsigned int chunk = towrite > 65536 ? 65536 : towrite;
+ if (pwrite(pkgdb->fd, blob, chunk, fileoff) != chunk) {
+ return RPMRC_FAIL; /* write error */
+ }
+ adl = update_adler32(adl, blob, chunk);
+ blob += chunk;
+ towrite -= chunk;
+ fileoff += chunk;
+ }
+ /* pad if needed */
+ pad = blkcnt * BLK_SIZE - (BLOBHEAD_SIZE + blobl + BLOBTAIL_SIZE);
+ if (pad) {
+ memset(buf + (sizeof(buf) - BLOBTAIL_SIZE) - pad, 0, pad);
+ adl = update_adler32(adl, buf + (sizeof(buf) - BLOBTAIL_SIZE) - pad, pad);
+ }
+ h2le(adl, buf + (sizeof(buf) - BLOBTAIL_SIZE));
+ h2le(blobl, buf + (sizeof(buf) - BLOBTAIL_SIZE) + 4);
+ h2le(BLOBTAIL_MAGIC, buf + (sizeof(buf) - BLOBTAIL_SIZE) + 8);
+ if (pwrite(pkgdb->fd, buf + (sizeof(buf) - BLOBTAIL_SIZE) - pad, pad + BLOBTAIL_SIZE, fileoff) != pad + BLOBTAIL_SIZE) {
+ return RPMRC_FAIL; /* write error */
+ }
+ /* update file length */
+ if (blkoff + blkcnt > pkgdb->fileblks)
+ pkgdb->fileblks = blkoff + blkcnt;
+ if (pkgdb->dofsync && fsync(pkgdb->fd)) {
+ return RPMRC_FAIL; /* write error */
+ }
+ return RPMRC_OK;
+}
+
+static int rpmpkgDelBlob(rpmpkgdb pkgdb, unsigned int pkgidx, unsigned int blkoff, unsigned int blkcnt)
+{
+ if (rpmpkgVerifyblob(pkgdb, pkgidx, blkoff, blkcnt))
+ return RPMRC_FAIL;
+ if (rpmpkgZeroBlks(pkgdb, blkoff, blkcnt))
+ return RPMRC_FAIL;
+ if (pkgdb->dofsync && fsync(pkgdb->fd))
+ return RPMRC_FAIL; /* write error */
+ return RPMRC_OK;
+}
+
+
+static int rpmpkgMoveBlob(rpmpkgdb pkgdb, pkgslot *slot, unsigned int newblkoff)
+{
+ unsigned int pkgidx = slot->pkgidx;
+ unsigned int blkoff = slot->blkoff;
+ unsigned int blkcnt = slot->blkcnt;
+ unsigned char *blob;
+ unsigned int tstamp, blobl;
+
+ blob = malloc((size_t)blkcnt * BLK_SIZE);
+ if (rpmpkgReadBlob(pkgdb, pkgidx, blkoff, blkcnt, blob, &blobl, &tstamp)) {
+ free(blob);
+ return RPMRC_FAIL;
+ }
+ if (rpmpkgWriteBlob(pkgdb, pkgidx, newblkoff, blkcnt, blob, blobl, tstamp)) {
+ free(blob);
+ return RPMRC_FAIL;
+ }
+ free(blob);
+ if (rpmpkgWriteslot(pkgdb, slot->slotno, pkgidx, newblkoff, blkcnt)) {
+ return RPMRC_FAIL;
+ }
+ if (rpmpkgDelBlob(pkgdb, pkgidx, blkoff, blkcnt)) {
+ return RPMRC_FAIL;
+ }
+ slot->blkoff = newblkoff;
+ pkgdb->slotorder = SLOTORDER_UNORDERED;
+ return RPMRC_OK;
+}
+
+static int rpmpkgAddSlotPage(rpmpkgdb pkgdb)
+{
+ unsigned int cutoff;
+ if (pkgdb->slotorder != SLOTORDER_BLKOFF)
+ rpmpkgOrderSlots(pkgdb, SLOTORDER_BLKOFF);
+ cutoff = (pkgdb->slotnpages + 1) * (PAGE_SIZE / BLK_SIZE);
+
+ /* now move every blob before cutoff */
+ while (pkgdb->nslots && pkgdb->slots[0].blkoff < cutoff) {
+ unsigned int newblkoff;
+ pkgslot *slot = pkgdb->slots, *oldslot;
+
+ oldslot = 0;
+ if (rpmpkgFindEmptyOffset(pkgdb, slot->pkgidx, slot->blkcnt, &newblkoff, &oldslot, 1)) {
+ return RPMRC_FAIL;
+ }
+ if (!oldslot || oldslot != slot) {
+ return RPMRC_FAIL;
+ }
+ if (rpmpkgMoveBlob(pkgdb, slot, newblkoff)) {
+ return RPMRC_FAIL;
+ }
+ rpmpkgOrderSlots(pkgdb, SLOTORDER_BLKOFF);
+ }
+
+ /* make sure our new page is empty */
+ if (rpmpkgValidateZero(pkgdb, pkgdb->slotnpages * (PAGE_SIZE / BLK_SIZE), PAGE_SIZE / BLK_SIZE)) {
+ return RPMRC_FAIL;
+ }
+ if (rpmpkgWriteEmptySlotpage(pkgdb, pkgdb->slotnpages)) {
+ return RPMRC_FAIL;
+ }
+
+ /* announce free page */
+ pkgdb->freeslot = pkgdb->slotnpages * (PAGE_SIZE / SLOT_SIZE);
+ pkgdb->slotnpages++;
+ pkgdb->generation++;
+ if (rpmpkgWriteHeader(pkgdb)) {
+ return RPMRC_FAIL;
+ }
+ return RPMRC_OK;
+}
+
+static int rpmpkgGetLock(rpmpkgdb pkgdb, int type)
+{
+ if (!pkgdb->fd)
+ return RPMRC_FAIL;
+ if (flock(pkgdb->fd, type))
+ return RPMRC_FAIL;
+ return RPMRC_OK;
+}
+
+int rpmpkgLock(rpmpkgdb pkgdb, int excl)
+{
+ unsigned int *lockcntp = excl ? &pkgdb->locked_excl : &pkgdb->locked_shared;
+ if (*lockcntp > 0 || (!excl && pkgdb->locked_excl)) {
+ (*lockcntp)++;
+ return RPMRC_OK;
+ }
+ pkgdb->header_ok = 0;
+ if (rpmpkgGetLock(pkgdb, excl ? LOCK_EX : LOCK_SH)) {
+ return RPMRC_FAIL;
+ }
+ (*lockcntp)++;
+ return RPMRC_OK;
+}
+
+static int rpmpkgLockInternal(rpmpkgdb pkgdb, int excl)
+{
+ if (excl && pkgdb->rdonly)
+ return RPMRC_FAIL;
+
+ return rpmpkgLock(pkgdb, excl);
+}
+
+int rpmpkgUnlock(rpmpkgdb pkgdb, int excl)
+{
+ unsigned int *lockcntp = excl ? &pkgdb->locked_excl : &pkgdb->locked_shared;
+ if (*lockcntp == 0) {
+ return RPMRC_FAIL;
+ }
+ if (*lockcntp > 1 || (!excl && pkgdb->locked_excl)) {
+ (*lockcntp)--;
+ return RPMRC_OK;
+ }
+ if (excl && pkgdb->locked_shared) {
+ /* excl -> shared switch */
+ if (rpmpkgGetLock(pkgdb, LOCK_SH)) {
+ return RPMRC_FAIL;
+ }
+ (*lockcntp)--;
+ return RPMRC_OK;
+ }
+ flock(pkgdb->fd, LOCK_UN);
+ (*lockcntp)--;
+ pkgdb->header_ok = 0;
+ return RPMRC_OK;
+}
+
+static int rpmpkgLockReadHeader(rpmpkgdb pkgdb, int excl)
+{
+ if (rpmpkgLockInternal(pkgdb, excl))
+ return RPMRC_FAIL;
+ if (rpmpkgReadHeader(pkgdb)) {
+ rpmpkgUnlock(pkgdb, excl);
+ return RPMRC_FAIL;
+ }
+ return RPMRC_OK;
+}
+
+static int rpmpkgInitInternal(rpmpkgdb pkgdb)
+{
+ struct stat stb;
+ if (fstat(pkgdb->fd, &stb)) {
+ return RPMRC_FAIL;
+ }
+ if (stb.st_size == 0) {
+ if (rpmpkgWriteEmptySlotpage(pkgdb, 0)) {
+ return RPMRC_FAIL;
+ }
+ pkgdb->slotnpages = 1;
+ if (!pkgdb->nextpkgidx)
+ pkgdb->nextpkgidx = 1;
+ pkgdb->generation++;
+ if (rpmpkgWriteHeader(pkgdb)) {
+ return RPMRC_FAIL;
+ }
+ }
+ return RPMRC_OK;
+}
+
+static int rpmpkgInit(rpmpkgdb pkgdb)
+{
+ int rc;
+
+ if (rpmpkgLockInternal(pkgdb, 1))
+ return RPMRC_FAIL;
+ rc = rpmpkgInitInternal(pkgdb);
+ rpmpkgUnlock(pkgdb, 1);
+ return rc;
+}
+
+int rpmpkgOpen(rpmpkgdb *pkgdbp, const char *filename, int flags, int mode)
+{
+ struct stat stb;
+ rpmpkgdb pkgdb;
+
+ *pkgdbp = 0;
+ pkgdb = calloc(1, sizeof(*pkgdb));
+ pkgdb->filename = strdup(filename);
+ if (!pkgdb->filename) {
+ free(pkgdb);
+ return RPMRC_FAIL;
+ }
+ if ((flags & (O_RDONLY|O_RDWR)) == O_RDONLY)
+ pkgdb->rdonly = 1;
+ if ((pkgdb->fd = open(filename, flags, mode)) == -1) {
+ free(pkgdb->filename);
+ free(pkgdb);
+ return RPMRC_FAIL;
+ }
+ if (flags & O_CREAT) {
+ char *filenameCopy;
+ DIR *pdir;
+
+ if ((filenameCopy = strdup(pkgdb->filename)) == NULL) {
+ close(pkgdb->fd);
+ free(pkgdb->filename);
+ free(pkgdb);
+ return RPMRC_FAIL;
+ }
+
+ if ((pdir = opendir(dirname(filenameCopy))) == NULL) {
+ free(filenameCopy);
+ close(pkgdb->fd);
+ free(pkgdb->filename);
+ free(pkgdb);
+ return RPMRC_FAIL;
+ }
+
+ if (fsync(dirfd(pdir)) == -1) {
+ closedir(pdir);
+ free(filenameCopy);
+ close(pkgdb->fd);
+ free(pkgdb->filename);
+ free(pkgdb);
+ return RPMRC_FAIL;
+ }
+ closedir(pdir);
+ free(filenameCopy);
+
+ }
+ if (fstat(pkgdb->fd, &stb)) {
+ close(pkgdb->fd);
+ free(pkgdb->filename);
+ free(pkgdb);
+ return RPMRC_FAIL;
+ }
+ if (stb.st_size == 0) {
+ if (rpmpkgInit(pkgdb)) {
+ close(pkgdb->fd);
+ free(pkgdb->filename);
+ free(pkgdb);
+ return RPMRC_FAIL;
+ }
+ }
+ pkgdb->flags = flags;
+ pkgdb->mode = mode;
+ pkgdb->dofsync = 1;
+ *pkgdbp = pkgdb;
+ return RPMRC_OK;
+}
+
+void rpmpkgClose(rpmpkgdb pkgdb)
+{
+ if (pkgdb->fd >= 0) {
+ close(pkgdb->fd);
+ pkgdb->fd = -1;
+ }
+ if (pkgdb->slots)
+ free(pkgdb->slots);
+ pkgdb->slots = 0;
+ if (pkgdb->slothash)
+ free(pkgdb->slothash);
+ pkgdb->slothash = 0;
+ free(pkgdb->filename);
+ free(pkgdb);
+}
+
+void rpmpkgSetFsync(rpmpkgdb pkgdb, int dofsync)
+{
+ pkgdb->dofsync = dofsync;
+}
+
+
+static int rpmpkgGetInternal(rpmpkgdb pkgdb, unsigned int pkgidx, unsigned char **blobp, unsigned int *bloblp)
+{
+ pkgslot *slot;
+ unsigned char *blob;
+
+ if (!pkgdb->slots && rpmpkgReadSlots(pkgdb)) {
+ return RPMRC_FAIL;
+ }
+ slot = rpmpkgFindSlot(pkgdb, pkgidx);
+ if (!slot) {
+ return RPMRC_NOTFOUND;
+ }
+ blob = malloc((size_t)slot->blkcnt * BLK_SIZE);
+ if (rpmpkgReadBlob(pkgdb, pkgidx, slot->blkoff, slot->blkcnt, blob, bloblp, (unsigned int *)0)) {
+ free(blob);
+ return RPMRC_FAIL;
+ }
+ *blobp = blob;
+ return RPMRC_OK;
+}
+
+static int rpmpkgPutInternal(rpmpkgdb pkgdb, unsigned int pkgidx, unsigned char *blob, unsigned int blobl)
+{
+ unsigned int blkcnt, blkoff, slotno;
+ pkgslot *oldslot;
+
+ /* we always read all slots when writing, just in case */
+ if (rpmpkgReadSlots(pkgdb)) {
+ return RPMRC_FAIL;
+ }
+ blkcnt = (BLOBHEAD_SIZE + blobl + BLOBTAIL_SIZE + BLK_SIZE - 1) / BLK_SIZE;
+ /* find a nice place for the blob */
+ if (rpmpkgFindEmptyOffset(pkgdb, pkgidx, blkcnt, &blkoff, &oldslot, 0)) {
+ return RPMRC_FAIL;
+ }
+ /* create new slot page if we don't have a free slot and can't reuse an old one */
+ if (!oldslot && !pkgdb->freeslot) {
+ if (rpmpkgAddSlotPage(pkgdb)) {
+ return RPMRC_FAIL;
+ }
+ /* redo rpmpkgFindEmptyOffset to get another free area */
+ if (rpmpkgFindEmptyOffset(pkgdb, pkgidx, blkcnt, &blkoff, &oldslot, 0)) {
+ return RPMRC_FAIL;
+ }
+ }
+ /* make sure that we don't overwrite data */
+ if (rpmpkgValidateZero(pkgdb, blkoff, blkcnt)) {
+ return RPMRC_FAIL;
+ }
+ /* write new blob */
+ if (rpmpkgWriteBlob(pkgdb, pkgidx, blkoff, blkcnt, blob, blobl, (unsigned int)time(0))) {
+ return RPMRC_FAIL;
+ }
+ /* write slot */
+ slotno = oldslot ? oldslot->slotno : pkgdb->freeslot;
+ if (!slotno) {
+ return RPMRC_FAIL;
+ }
+ if (rpmpkgWriteslot(pkgdb, slotno, pkgidx, blkoff, blkcnt)) {
+ free(pkgdb->slots);
+ pkgdb->slots = 0;
+ return RPMRC_FAIL;
+ }
+ /* erase old blob */
+ if (oldslot && oldslot->blkoff) {
+ if (rpmpkgDelBlob(pkgdb, pkgidx, oldslot->blkoff, oldslot->blkcnt)) {
+ free(pkgdb->slots);
+ pkgdb->slots = 0;
+ return RPMRC_FAIL;
+ }
+ }
+ if (oldslot) {
+ /* just update the slot, no need to free the slot data */
+ oldslot->blkoff = blkoff;
+ oldslot->blkcnt = blkcnt;
+ pkgdb->slotorder = SLOTORDER_UNORDERED;
+ } else {
+ free(pkgdb->slots);
+ pkgdb->slots = 0;
+ }
+ return RPMRC_OK;
+}
+
+static int rpmpkgDelInternal(rpmpkgdb pkgdb, unsigned int pkgidx)
+{
+ pkgslot *slot;
+ unsigned int blkoff, blkcnt;
+
+ /* we always read all slots when writing, just in case */
+ if (rpmpkgReadSlots(pkgdb)) {
+ return RPMRC_FAIL;
+ }
+ rpmpkgOrderSlots(pkgdb, SLOTORDER_BLKOFF);
+ slot = rpmpkgFindSlot(pkgdb, pkgidx);
+ if (!slot) {
+ return RPMRC_OK;
+ }
+ if (rpmpkgWriteslot(pkgdb, slot->slotno, 0, 0, 0)) {
+ return RPMRC_FAIL;
+ }
+ if (rpmpkgDelBlob(pkgdb, pkgidx, slot->blkoff, slot->blkcnt)) {
+ return RPMRC_FAIL;
+ }
+ if (pkgdb->nslots > 1 && slot->blkoff < pkgdb->fileblks / 2) {
+ /* we freed a blob in the first half of our data. do some extra work */
+ int i;
+ if (slot == pkgdb->slots) {
+ blkoff = pkgdb->slotnpages * (PAGE_SIZE / BLK_SIZE);
+ } else {
+ blkoff = slot[-1].blkoff + slot[-1].blkcnt;
+ }
+ if (slot < pkgdb->slots + pkgdb->nslots - 1) {
+ blkcnt = slot[1].blkoff - blkoff;
+ } else {
+ blkcnt = slot->blkoff + slot->blkcnt - blkoff;
+ }
+ slot->blkoff = 0;
+ slot->blkcnt = 0;
+ slot = pkgdb->slots + pkgdb->nslots - 2;
+ if (slot->blkcnt < slot[1].blkcnt)
+ slot++; /* bigger slot first */
+ for (i = 0; i < 2; i++, slot++) {
+ if (slot == pkgdb->slots + pkgdb->nslots)
+ slot -= 2;
+ if (!slot->blkoff || slot->blkoff < blkoff)
+ continue;
+ if (slot->blkoff < pkgdb->fileblks / 2)
+ continue;
+ if (slot->blkcnt > blkcnt)
+ continue;
+ rpmpkgMoveBlob(pkgdb, slot, blkoff);
+ blkoff += slot->blkcnt;
+ blkcnt -= slot->blkcnt;
+ }
+ rpmpkgOrderSlots(pkgdb, SLOTORDER_BLKOFF);
+ } else {
+ slot->blkoff = 0;
+ slot->blkcnt = 0;
+ }
+ /* check if we can truncate the file */
+ slot = pkgdb->slots + pkgdb->nslots - 1;
+ if (!slot->blkoff && pkgdb->nslots > 1) {
+ slot--;
+ }
+ if (slot->blkoff)
+ blkoff = slot->blkoff + slot->blkcnt;
+ else
+ blkoff = pkgdb->slotnpages * (PAGE_SIZE / BLK_SIZE);
+ if (blkoff < pkgdb->fileblks / 4 * 3) {
+ /* truncate the file */
+ if (!rpmpkgValidateZero(pkgdb, blkoff, pkgdb->fileblks - blkoff)) {
+ if (!ftruncate(pkgdb->fd, blkoff * BLK_SIZE)) {
+ pkgdb->fileblks = blkoff;
+ }
+ }
+ }
+ free(pkgdb->slots);
+ pkgdb->slots = 0;
+ return RPMRC_OK;
+}
+
+static int rpmpkgListInternal(rpmpkgdb pkgdb, unsigned int **pkgidxlistp, unsigned int *npkgidxlistp)
+{
+ unsigned int i, nslots, *pkgidxlist;
+ pkgslot *slot;
+
+ if (!pkgdb->slots && rpmpkgReadSlots(pkgdb)) {
+ return RPMRC_FAIL;
+ }
+ if (!pkgidxlistp) {
+ *npkgidxlistp = pkgdb->nslots;
+ return RPMRC_OK;
+ }
+ rpmpkgOrderSlots(pkgdb, SLOTORDER_BLKOFF);
+ nslots = pkgdb->nslots;
+ pkgidxlist = calloc(nslots + 1, sizeof(unsigned int));
+ for (i = 0, slot = pkgdb->slots; i < nslots; i++, slot++) {
+ pkgidxlist[i] = slot->pkgidx;
+ }
+ *pkgidxlistp = pkgidxlist;
+ *npkgidxlistp = nslots;
+ return RPMRC_OK;
+}
+
+int rpmpkgGet(rpmpkgdb pkgdb, unsigned int pkgidx, unsigned char **blobp, unsigned int *bloblp)
+{
+ int rc;
+
+ *blobp = 0;
+ *bloblp = 0;
+ if (!pkgidx)
+ return RPMRC_FAIL;
+ if (rpmpkgLockReadHeader(pkgdb, 0))
+ return RPMRC_FAIL;
+ rc = rpmpkgGetInternal(pkgdb, pkgidx, blobp, bloblp);
+ rpmpkgUnlock(pkgdb, 0);
+#ifdef RPMPKG_LZO
+ if (!rc)
+ rc = rpmpkgLZODecompress(blobp, bloblp);
+#endif
+ return rc;
+}
+
+int rpmpkgPut(rpmpkgdb pkgdb, unsigned int pkgidx, unsigned char *blob, unsigned int blobl)
+{
+ int rc;
+
+ if (!pkgidx) {
+ return RPMRC_FAIL;
+ }
+ if (rpmpkgLockReadHeader(pkgdb, 1))
+ return RPMRC_FAIL;
+#ifdef RPMPKG_LZO
+ if (rpmpkgLZOCompress(&blob, &blobl)) {
+ rpmpkgUnlock(pkgdb, 1);
+ return RPMRC_FAIL;
+ }
+#endif
+ rc = rpmpkgPutInternal(pkgdb, pkgidx, blob, blobl);
+#ifdef RPMPKG_LZO
+ free(blob);
+#endif
+ rpmpkgUnlock(pkgdb, 1);
+ return rc;
+}
+
+int rpmpkgDel(rpmpkgdb pkgdb, unsigned int pkgidx)
+{
+ int rc;
+
+ if (!pkgidx) {
+ return RPMRC_FAIL;
+ }
+ if (rpmpkgLockReadHeader(pkgdb, 1))
+ return RPMRC_FAIL;
+ rc = rpmpkgDelInternal(pkgdb, pkgidx);
+ rpmpkgUnlock(pkgdb, 1);
+ return rc;
+}
+
+int rpmpkgList(rpmpkgdb pkgdb, unsigned int **pkgidxlistp, unsigned int *npkgidxlistp)
+{
+ int rc;
+ if (pkgidxlistp)
+ *pkgidxlistp = 0;
+ *npkgidxlistp = 0;
+ if (rpmpkgLockReadHeader(pkgdb, 0))
+ return RPMRC_FAIL;
+ rc = rpmpkgListInternal(pkgdb, pkgidxlistp, npkgidxlistp);
+ rpmpkgUnlock(pkgdb, 0);
+ return rc;
+}
+
+int rpmpkgNextPkgIdx(rpmpkgdb pkgdb, unsigned int *pkgidxp)
+{
+ if (rpmpkgLockReadHeader(pkgdb, 1))
+ return RPMRC_FAIL;
+ *pkgidxp = pkgdb->nextpkgidx++;
+ if (rpmpkgWriteHeader(pkgdb)) {
+ rpmpkgUnlock(pkgdb, 1);
+ return RPMRC_FAIL;
+ }
+ /* no fsync needed. also no need to increase the generation count,
+ * as the header is always read in */
+ rpmpkgUnlock(pkgdb, 1);
+ return RPMRC_OK;
+}
+
+int rpmpkgGeneration(rpmpkgdb pkgdb, unsigned int *generationp)
+{
+ if (rpmpkgLockReadHeader(pkgdb, 0))
+ return RPMRC_FAIL;
+ *generationp = pkgdb->generation;
+ rpmpkgUnlock(pkgdb, 0);
+ return RPMRC_OK;
+}
+
+int rpmpkgStats(rpmpkgdb pkgdb)
+{
+ unsigned int usedblks = 0;
+ int i;
+
+ if (rpmpkgLockReadHeader(pkgdb, 0))
+ return RPMRC_FAIL;
+ if (rpmpkgReadSlots(pkgdb)) {
+ rpmpkgUnlock(pkgdb, 0);
+ return RPMRC_FAIL;
+ }
+ for (i = 0; i < pkgdb->nslots; i++)
+ usedblks += pkgdb->slots[i].blkcnt;
+ printf("--- Package DB Stats\n");
+ printf("Filename: %s\n", pkgdb->filename);
+ printf("Generation: %d\n", pkgdb->generation);
+ printf("Slot pages: %d\n", pkgdb->slotnpages);
+ printf("Used slots: %d\n", pkgdb->nslots);
+ printf("Free slots: %d\n", pkgdb->slotnpages * (PAGE_SIZE / SLOT_SIZE) - pkgdb->nslots);
+ printf("Blob area size: %d\n", (pkgdb->fileblks - pkgdb->slotnpages * (PAGE_SIZE / BLK_SIZE)) * BLK_SIZE);
+ printf("Blob area used: %d\n", usedblks * BLK_SIZE);
+ rpmpkgUnlock(pkgdb, 0);
+ return RPMRC_OK;
+}
+
+#ifdef RPMPKG_LZO
+
+#include "lzo/lzoconf.h"
+#include "lzo/lzo1x.h"
+
+#define BLOBLZO_MAGIC ('L' | 'Z' << 8 | 'O' << 16 | 'B' << 24)
+
+static int rpmpkgLZOCompress(unsigned char **blobp, unsigned int *bloblp)
+{
+ unsigned char *blob = *blobp;
+ unsigned int blobl = *bloblp;
+ unsigned char *lzoblob, *workmem;
+ unsigned int lzoblobl;
+ lzo_uint blobl2;
+
+ if (lzo_init() != LZO_E_OK) {
+ return RPMRC_FAIL;
+ }
+ workmem = malloc(LZO1X_1_MEM_COMPRESS);
+ if (!workmem) {
+ return RPMRC_FAIL;
+ }
+ lzoblobl = 4 + 4 + blobl + blobl / 16 + 64 + 3;
+ lzoblob = malloc(lzoblobl);
+ if (!lzoblob) {
+ free(workmem);
+ return RPMRC_FAIL;
+ }
+ h2le(BLOBLZO_MAGIC, lzoblob);
+ h2le(blobl, lzoblob + 4);
+ if (lzo1x_1_compress(blob, blobl, lzoblob + 8, &blobl2, workmem) != LZO_E_OK) {
+ free(workmem);
+ free(lzoblob);
+ return RPMRC_FAIL;
+ }
+ free(workmem);
+ *blobp = lzoblob;
+ *bloblp = 8 + blobl2;
+ return RPMRC_OK;
+}
+
+static int rpmpkgLZODecompress(unsigned char **blobp, unsigned int *bloblp)
+{
+ unsigned char *lzoblob = *blobp;
+ unsigned int lzoblobl = *bloblp;
+ unsigned char *blob;
+ unsigned int blobl;
+ lzo_uint blobl2;
+
+ if (!lzoblob || lzoblobl < 8)
+ return RPMRC_FAIL;
+ if (le2h(lzoblob) != BLOBLZO_MAGIC)
+ return RPMRC_FAIL;
+ if (lzo_init() != LZO_E_OK)
+ return RPMRC_FAIL;
+ blobl = le2h(lzoblob + 4);
+ blob = malloc(blobl ? blobl : 1);
+ if (!blob)
+ return RPMRC_FAIL;
+ if (lzo1x_decompress(lzoblob + 8, lzoblobl - 8, blob, &blobl2, 0) != LZO_E_OK || blobl2 != blobl) {
+ free(blob);
+ return RPMRC_FAIL;
+ }
+ free(lzoblob);
+ *blobp = blob;
+ *bloblp = blobl;
+ return RPMRC_OK;
+}
+
+#endif
diff --git a/lib/backend/ndb/rpmpkg.h b/lib/backend/ndb/rpmpkg.h
new file mode 100644
index 000000000..7e5d0c67e
--- /dev/null
+++ b/lib/backend/ndb/rpmpkg.h
@@ -0,0 +1,20 @@
+struct rpmpkgdb_s;
+typedef struct rpmpkgdb_s *rpmpkgdb;
+
+int rpmpkgOpen(rpmpkgdb *pkgdbp, const char *filename, int flags, int mode);
+void rpmpkgClose(rpmpkgdb pkgdbp);
+void rpmpkgSetFsync(rpmpkgdb pkgdbp, int dofsync);
+
+int rpmpkgLock(rpmpkgdb pkgdb, int excl);
+int rpmpkgUnlock(rpmpkgdb pkgdb, int excl);
+
+int rpmpkgGet(rpmpkgdb pkgdb, unsigned int pkgidx, unsigned char **blobp, unsigned int *bloblp);
+int rpmpkgPut(rpmpkgdb pkgdb, unsigned int pkgidx, unsigned char *blob, unsigned int blobl);
+int rpmpkgDel(rpmpkgdb pkgdb, unsigned int pkgidx);
+int rpmpkgList(rpmpkgdb pkgdb, unsigned int **pkgidxlistp, unsigned int *npkgidxlistp);
+
+int rpmpkgNextPkgIdx(rpmpkgdb pkgdb, unsigned int *pkgidxp);
+int rpmpkgGeneration(rpmpkgdb pkgdb, unsigned int *generationp);
+
+int rpmpkgStats(rpmpkgdb pkgdb);
+
diff --git a/lib/backend/ndb/rpmxdb.c b/lib/backend/ndb/rpmxdb.c
new file mode 100644
index 000000000..55cc197b3
--- /dev/null
+++ b/lib/backend/ndb/rpmxdb.c
@@ -0,0 +1,1221 @@
+#define _GNU_SOURCE
+
+#include "system.h"
+
+#include <rpm/rpmlog.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <time.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <endian.h>
+#include <libgen.h>
+
+#include "rpmxdb.h"
+
+#define RPMRC_OK 0
+#define RPMRC_NOTFOUND 1
+#define RPMRC_FAIL 2
+
+typedef struct rpmxdb_s {
+ rpmpkgdb pkgdb; /* master database */
+ char *filename;
+ int fd;
+ int flags;
+ int mode;
+ int rdonly;
+ unsigned int pagesize;
+ unsigned int generation;
+ unsigned int slotnpages;
+ unsigned int usergeneration;
+
+ unsigned char *mapped;
+ unsigned int mappedlen;
+
+ struct xdb_slot {
+ unsigned int slotno;
+ unsigned int blobtag;
+ unsigned int subtag;
+ unsigned char *mapped;
+ int mapflags;
+ unsigned int startpage;
+ unsigned int pagecnt;
+ void (*mapcallback)(rpmxdb xdb, void *data, void *newaddr, size_t newsize);
+ void *mapcallbackdata;
+ unsigned int next;
+ unsigned int prev;
+ } *slots;
+ unsigned int nslots;
+ unsigned int firstfree;
+ unsigned int usedblobpages;
+ unsigned int systempagesize;
+ int dofsync;
+} *rpmxdb;
+
+
+static inline void h2le(unsigned int x, unsigned char *p)
+{
+ p[0] = x;
+ p[1] = x >> 8;
+ p[2] = x >> 16;
+ p[3] = x >> 24;
+}
+
+/* aligned versions */
+static inline unsigned int le2ha(unsigned char *p)
+{
+ unsigned int x = *(unsigned int *)p;
+ return le32toh(x);
+}
+
+static inline void h2lea(unsigned int x, unsigned char *p)
+{
+ *(unsigned int *)p = htole32(x);
+}
+
+
+#define XDB_MAGIC ('R' | 'p' << 8 | 'm' << 16 | 'X' << 24)
+#define XDB_VERSION 0
+
+#define XDB_OFFSET_MAGIC 0
+#define XDB_OFFSET_VERSION 4
+#define XDB_OFFSET_GENERATION 8
+#define XDB_OFFSET_SLOTNPAGES 12
+#define XDB_OFFSET_PAGESIZE 16
+#define XDB_OFFSET_USERGENERATION 20
+
+/* must be multiple of SLOT_SIZE */
+#define XDB_HEADER_SIZE 32
+
+#define SLOT_MAGIC ('S' | 'l' << 8 | 'o' << 16)
+
+#define SLOT_SIZE 16
+#define SLOT_START (XDB_HEADER_SIZE / SLOT_SIZE)
+
+static void rpmxdbUnmap(rpmxdb xdb)
+{
+ munmap(xdb->mapped, xdb->mappedlen);
+ xdb->mapped = 0;
+ xdb->mappedlen = 0;
+}
+
+/* slot mapping functions */
+static int mapslot(rpmxdb xdb, struct xdb_slot *slot)
+{
+ void *mapped;
+ size_t off, size, shift;
+
+ if (slot->mapped)
+ return RPMRC_FAIL;
+ size = slot->pagecnt * xdb->pagesize;
+ off = slot->startpage * xdb->pagesize;
+ shift = 0;
+ if (xdb->pagesize != xdb->systempagesize) {
+ shift = off & (xdb->systempagesize - 1);
+ off -= shift;
+ size += shift;
+ size = (size + xdb->systempagesize - 1) & ~(xdb->systempagesize - 1);
+ }
+ mapped = mmap(0, size, slot->mapflags, MAP_SHARED, xdb->fd, off);
+ if (mapped == MAP_FAILED)
+ return RPMRC_FAIL;
+ slot->mapped = (unsigned char *)mapped + shift;
+ return RPMRC_OK;
+}
+
+static void unmapslot(rpmxdb xdb, struct xdb_slot *slot)
+{
+ size_t size;
+ unsigned char *mapped = slot->mapped;
+ if (!mapped)
+ return;
+ size = slot->pagecnt * xdb->pagesize;
+ if (xdb->pagesize != xdb->systempagesize) {
+ size_t off = slot->startpage * xdb->pagesize;
+ size_t shift = off & (xdb->systempagesize - 1);
+ mapped -= shift;
+ size += shift;
+ size = (size + xdb->systempagesize - 1) & ~(xdb->systempagesize - 1);
+ }
+ munmap(mapped, size);
+ slot->mapped = 0;
+}
+
+static int remapslot(rpmxdb xdb, struct xdb_slot *slot, unsigned int newpagecnt)
+{
+ void *mapped;
+ size_t off, oldsize, newsize, shift;
+ oldsize = slot->pagecnt * xdb->pagesize;
+ newsize = newpagecnt * xdb->pagesize;
+ off = slot->startpage * xdb->pagesize;
+ shift = 0;
+ if (xdb->pagesize != xdb->systempagesize) {
+ off = slot->startpage * xdb->pagesize;
+ shift = off & (xdb->systempagesize - 1);
+ off -= shift;
+ oldsize += shift;
+ oldsize = (oldsize + xdb->systempagesize - 1) & ~(xdb->systempagesize - 1);
+ newsize += shift;
+ newsize = (newsize + xdb->systempagesize - 1) & ~(xdb->systempagesize - 1);
+ }
+ if (slot->mapped)
+ mapped = mremap(slot->mapped - shift, oldsize, newsize, MREMAP_MAYMOVE);
+ else
+ mapped = mmap(0, newsize, slot->mapflags, MAP_SHARED, xdb->fd, off);
+ if (mapped == MAP_FAILED)
+ return RPMRC_FAIL;
+ slot->mapped = (unsigned char *)mapped + shift;
+ slot->pagecnt = newpagecnt;
+ return RPMRC_OK;
+}
+
+
+static int usedslots_cmp(const void *a, const void *b)
+{
+ struct xdb_slot *sa = *(struct xdb_slot **)a;
+ struct xdb_slot *sb = *(struct xdb_slot **)b;
+ if (sa->startpage == sb->startpage) {
+ return sa->pagecnt > sb->pagecnt ? 1 : sa->pagecnt < sb->pagecnt ? -1 : 0;
+ }
+ return sa->startpage > sb->startpage ? 1 : -1;
+}
+
+static int rpmxdbReadHeader(rpmxdb xdb)
+{
+ struct xdb_slot *slot;
+ unsigned int header[XDB_HEADER_SIZE / sizeof(unsigned int)];
+ unsigned int slotnpages, pagesize, generation, usergeneration, version;
+ unsigned int page, *lastfreep;
+ unsigned char *pageptr;
+ struct xdb_slot *slots, **usedslots, *lastslot;
+ unsigned int nslots;
+ unsigned int usedblobpages;
+ int i, nused, slotno;
+ struct stat stb;
+ size_t mapsize;
+
+ if (xdb->mapped) {
+ if (le2ha(xdb->mapped + XDB_OFFSET_GENERATION) == xdb->generation) {
+ return RPMRC_OK;
+ }
+ rpmxdbUnmap(xdb);
+ }
+ if (fstat(xdb->fd, &stb)) {
+ return RPMRC_FAIL;
+ }
+ if (pread(xdb->fd, header, sizeof(header), 0) != sizeof(header)) {
+ return RPMRC_FAIL;
+ }
+ if (le2ha((unsigned char *)header + XDB_OFFSET_MAGIC) != XDB_MAGIC)
+ return RPMRC_FAIL;
+ version = le2ha((unsigned char *)header + XDB_OFFSET_VERSION);
+ if (version != XDB_VERSION) {
+ rpmlog(RPMLOG_ERR, _("rpmxdb: Version mismatch. Expected version: %u. "
+ "Found version: %u\n"), XDB_VERSION, version);
+ return RPMRC_FAIL;
+ }
+
+ generation = le2ha((unsigned char *)header + XDB_OFFSET_GENERATION);
+ slotnpages = le2ha((unsigned char *)header + XDB_OFFSET_SLOTNPAGES);
+ pagesize = le2ha((unsigned char *)header + XDB_OFFSET_PAGESIZE);
+ usergeneration = le2ha((unsigned char *)header + XDB_OFFSET_USERGENERATION);
+ if (!slotnpages || !pagesize || stb.st_size % pagesize != 0)
+ return RPMRC_FAIL;
+ xdb->pagesize = pagesize;
+
+ /* round up */
+ mapsize = slotnpages * pagesize;
+ mapsize = (mapsize + xdb->systempagesize - 1) & ~(xdb->systempagesize - 1);
+ xdb->mapped = mmap(0, mapsize, xdb->rdonly ? PROT_READ : PROT_READ | PROT_WRITE, MAP_SHARED, xdb->fd, 0);
+ if ((void *)xdb->mapped == MAP_FAILED) {
+ xdb->mapped = 0;
+ return RPMRC_FAIL;
+ }
+ xdb->mappedlen = mapsize;
+
+ /* read in all slots */
+ xdb->firstfree = 0;
+ nslots = slotnpages * (pagesize / SLOT_SIZE) - SLOT_START + 1;
+ slots = calloc(nslots + 1, sizeof(struct xdb_slot));
+ if (!slots) {
+ rpmxdbUnmap(xdb);
+ return RPMRC_FAIL;
+ }
+ usedslots = calloc(nslots + 1, sizeof(int));
+ if (!usedslots) {
+ rpmxdbUnmap(xdb);
+ free(slots);
+ return RPMRC_FAIL;
+ }
+ nused = 0;
+ slotno = 1;
+ slot = slots + 1;
+ usedblobpages = 0;
+ lastfreep = &xdb->firstfree;
+ for (page = 0, pageptr = xdb->mapped; page < slotnpages; page++, pageptr += pagesize) {
+ unsigned int o;
+ for (o = page ? 0 : SLOT_START * SLOT_SIZE; o < pagesize; o += SLOT_SIZE, slotno++, slot++) {
+ unsigned char *pp = pageptr + o;
+ slot->slotno = slotno;
+ slot->subtag = le2ha(pp);
+ if ((slot->subtag & 0x00ffffff) != SLOT_MAGIC) {
+ free(slots);
+ free(usedslots);
+ rpmxdbUnmap(xdb);
+ return RPMRC_FAIL;
+ }
+ slot->subtag = (slot->subtag >> 24) & 255;
+ slot->blobtag = le2ha(pp + 4);
+ slot->startpage = le2ha(pp + 8);
+ slot->pagecnt = le2ha(pp + 12);
+ if (slot->pagecnt == 0 && slot->startpage) /* empty but used slot? */
+ slot->startpage = slotnpages;
+ if (!slot->startpage) {
+ *lastfreep = slotno;
+ lastfreep = &slot->next;
+ } else {
+ usedslots[nused++] = slot;
+ usedblobpages += slot->pagecnt;
+ }
+ }
+ }
+ if (nused > 1) {
+ qsort(usedslots, nused, sizeof(*usedslots), usedslots_cmp);
+ }
+ /* now chain em */
+ slots[0].pagecnt = slotnpages;
+ lastslot = slots;
+ for (i = 0; i < nused; i++, lastslot = slot) {
+ slot = usedslots[i];
+ if (lastslot->startpage + lastslot->pagecnt > slot->startpage) {
+ free(slots);
+ free(usedslots);
+ rpmxdbUnmap(xdb);
+ return RPMRC_FAIL;
+ }
+ lastslot->next = slot->slotno;
+ slot->prev = lastslot->slotno;
+ }
+ lastslot->next = nslots;
+ slots[nslots].slotno = nslots;
+ slots[nslots].prev = lastslot->slotno;
+ slots[nslots].startpage = stb.st_size / pagesize;
+ free(usedslots);
+ /* now sync with the old slot data */
+ if (xdb->slots) {
+ for (i = 1, slot = xdb->slots + i; i < xdb->nslots; i++, slot++) {
+ if (slot->startpage && (slot->mapped || slot->mapcallback)) {
+ struct xdb_slot *nslot;
+ if (i >= nslots || !slots[i].startpage || slots[i].blobtag != slot->blobtag || slots[i].subtag != slot->subtag) {
+ /* slot is gone */
+ if (slot->mapped) {
+ unmapslot(xdb, slot);
+ slot->mapcallback(xdb, slot->mapcallbackdata, 0, 0);
+ }
+ continue;
+ }
+ nslot = slots + i;
+ if (slot->mapcallback) {
+ nslot->mapflags = slot->mapflags;
+ nslot->mapcallback = slot->mapcallback;
+ nslot->mapcallbackdata = slot->mapcallbackdata;
+ }
+ if (slot->startpage != nslot->startpage || slot->pagecnt != nslot->pagecnt) {
+ /* slot moved or was resized */
+ if (slot->mapped)
+ unmapslot(xdb, slot);
+ if (nslot->mapcallback) {
+ if (nslot->pagecnt) {
+ mapslot(xdb, nslot);
+ nslot->mapcallback(xdb, nslot->mapcallbackdata, nslot->mapped, nslot->mapped ? nslot->pagecnt * xdb->pagesize : 0);
+ } else {
+ nslot->mapcallback(xdb, nslot->mapcallbackdata, 0, 0);
+ }
+ }
+ }
+ }
+ }
+ free(xdb->slots);
+ }
+ xdb->slots = slots;
+ xdb->nslots = nslots;
+ xdb->generation = generation;
+ xdb->slotnpages = slotnpages;
+ xdb->usergeneration = usergeneration;
+ xdb->usedblobpages = usedblobpages;
+ return RPMRC_OK;
+}
+
+static int rpmxdbWriteHeader(rpmxdb xdb)
+{
+ if (!xdb->mapped)
+ return RPMRC_FAIL;
+ h2lea(XDB_MAGIC, xdb->mapped + XDB_OFFSET_MAGIC);
+ h2lea(XDB_VERSION, xdb->mapped + XDB_OFFSET_VERSION);
+ h2lea(xdb->generation, xdb->mapped + XDB_OFFSET_GENERATION);
+ h2lea(xdb->slotnpages, xdb->mapped + XDB_OFFSET_SLOTNPAGES);
+ h2lea(xdb->pagesize, xdb->mapped + XDB_OFFSET_PAGESIZE);
+ h2lea(xdb->usergeneration, xdb->mapped + XDB_OFFSET_USERGENERATION);
+ return RPMRC_OK;
+}
+
+static void rpmxdbUpdateSlot(rpmxdb xdb, struct xdb_slot *slot)
+{
+ unsigned char *pp = xdb->mapped + (SLOT_START - 1 + slot->slotno) * SLOT_SIZE;
+ h2lea(SLOT_MAGIC | (slot->subtag << 24), pp);
+ h2lea(slot->blobtag, pp + 4);
+ if (slot->pagecnt || !slot->startpage)
+ h2lea(slot->startpage, pp + 8);
+ else
+ h2lea(1, pp + 8); /* "empty but used" blobs always start at 1 */
+ h2lea(slot->pagecnt, pp + 12);
+ xdb->generation++;
+ h2lea(xdb->generation, xdb->mapped + XDB_OFFSET_GENERATION);
+}
+
+static int rpmxdbWriteEmptyPages(rpmxdb xdb, unsigned int pageno, unsigned int count)
+{
+ unsigned char *page;
+ if (!count)
+ return RPMRC_OK;
+ page = malloc(xdb->pagesize);
+ if (!page)
+ return RPMRC_FAIL;
+ memset(page, 0, xdb->pagesize);
+ for (; count; count--, pageno++) {
+ if (pwrite(xdb->fd, page, xdb->pagesize, pageno * xdb->pagesize) != xdb->pagesize) {
+ free(page);
+ return RPMRC_FAIL;
+ }
+ }
+ free(page);
+ return RPMRC_OK;
+}
+
+static int rpmxdbWriteEmptySlotpage(rpmxdb xdb, int pageno)
+{
+ unsigned char *page;
+ int i, spp;
+ page = malloc(xdb->pagesize);
+ if (!page)
+ return RPMRC_FAIL;
+ memset(page, 0, xdb->pagesize);
+ spp = xdb->pagesize / SLOT_SIZE; /* slots per page */
+ for (i = pageno ? 0 : SLOT_START; i < spp; i++)
+ h2le(SLOT_MAGIC, page + i * SLOT_SIZE);
+ if (!pageno) {
+ /* only used when called from InitInternal */
+ if (xdb->mapped) {
+ free(page);
+ return RPMRC_FAIL;
+ }
+ xdb->mapped = page;
+ rpmxdbWriteHeader(xdb);
+ xdb->mapped = 0;
+ }
+ if (pwrite(xdb->fd, page, xdb->pagesize, pageno * xdb->pagesize) != xdb->pagesize) {
+ free(page);
+ return RPMRC_FAIL;
+ }
+ free(page);
+ return RPMRC_OK;
+}
+
+static int rpmxdbInitInternal(rpmxdb xdb)
+{
+ struct stat stb;
+ if (fstat(xdb->fd, &stb)) {
+ return RPMRC_FAIL;
+ }
+ if (stb.st_size == 0) {
+ xdb->slotnpages = 1;
+ xdb->generation++;
+ xdb->pagesize = sysconf(_SC_PAGE_SIZE);
+ if (rpmxdbWriteEmptySlotpage(xdb, 0)) {
+ return RPMRC_FAIL;
+ }
+ }
+ return RPMRC_OK;
+}
+
+/* we use the master pdb for locking */
+static int rpmxdbLockOnly(rpmxdb xdb, int excl)
+{
+ if (excl && xdb->rdonly)
+ return RPMRC_FAIL;
+ return rpmpkgLock(xdb->pkgdb, excl);
+}
+
+/* this is the same as rpmxdbLockReadHeader. It does the
+ * ReadHeader to sync the mappings if xdb moved some blobs.
+ */
+int rpmxdbLock(rpmxdb xdb, int excl)
+{
+ if (rpmxdbLockOnly(xdb, excl))
+ return RPMRC_FAIL;
+ if (rpmxdbReadHeader(xdb)) {
+ rpmxdbUnlock(xdb, excl);
+ return RPMRC_FAIL;
+ }
+ return RPMRC_OK;
+}
+
+int rpmxdbUnlock(rpmxdb xdb, int excl)
+{
+ return rpmpkgUnlock(xdb->pkgdb, excl);
+}
+
+static int rpmxdbLockReadHeader(rpmxdb xdb, int excl)
+{
+ if (rpmxdbLockOnly(xdb, excl))
+ return RPMRC_FAIL;
+ if (rpmxdbReadHeader(xdb)) {
+ rpmxdbUnlock(xdb, excl);
+ return RPMRC_FAIL;
+ }
+ return RPMRC_OK;
+}
+
+static int rpmxdbInit(rpmxdb xdb)
+{
+ int rc;
+
+ if (rpmxdbLockOnly(xdb, 1))
+ return RPMRC_FAIL;
+ rc = rpmxdbInitInternal(xdb);
+ rpmxdbUnlock(xdb, 1);
+ return rc;
+}
+
+int rpmxdbOpen(rpmxdb *xdbp, rpmpkgdb pkgdb, const char *filename, int flags, int mode)
+{
+ struct stat stb;
+ rpmxdb xdb;
+
+ *xdbp = 0;
+ xdb = calloc(1, sizeof(*xdb));
+ xdb->pkgdb = pkgdb;
+ xdb->filename = strdup(filename);
+ xdb->systempagesize = sysconf(_SC_PAGE_SIZE);
+ if (!xdb->filename) {
+ free(xdb);
+ return RPMRC_FAIL;
+ }
+ if ((flags & (O_RDONLY|O_RDWR)) == O_RDONLY)
+ xdb->rdonly = 1;
+ if ((xdb->fd = open(filename, flags, mode)) == -1) {
+ free(xdb->filename);
+ free(xdb);
+ return RPMRC_FAIL;
+ }
+ if (flags & O_CREAT) {
+ char *filenameCopy;
+ DIR *pdir;
+
+ if ((filenameCopy = strdup(xdb->filename)) == NULL) {
+ close(xdb->fd);
+ free(xdb->filename);
+ free(xdb);
+ return RPMRC_FAIL;
+ }
+
+ if ((pdir = opendir(dirname(filenameCopy))) == NULL) {
+ free(filenameCopy);
+ close(xdb->fd);
+ free(xdb->filename);
+ free(xdb);
+ return RPMRC_FAIL;
+ }
+
+ if (fsync(dirfd(pdir)) == -1) {
+ closedir(pdir);
+ free(filenameCopy);
+ close(xdb->fd);
+ free(xdb->filename);
+ free(xdb);
+ return RPMRC_FAIL;
+ }
+ closedir(pdir);
+ free(filenameCopy);
+ }
+ if (fstat(xdb->fd, &stb)) {
+ close(xdb->fd);
+ free(xdb->filename);
+ free(xdb);
+ return RPMRC_FAIL;
+ }
+ if (stb.st_size == 0) {
+ if (rpmxdbInit(xdb)) {
+ close(xdb->fd);
+ free(xdb->filename);
+ free(xdb);
+ return RPMRC_FAIL;
+ }
+ }
+ xdb->flags = flags;
+ xdb->mode = mode;
+ xdb->dofsync = 1;
+ *xdbp = xdb;
+ return RPMRC_OK;
+}
+
+void rpmxdbClose(rpmxdb xdb)
+{
+ struct xdb_slot *slot;
+ int i;
+
+ for (i = 1, slot = xdb->slots + 1; i < xdb->nslots; i++, slot++) {
+ if (slot->mapped) {
+ unmapslot(xdb, slot);
+ slot->mapcallback(xdb, slot->mapcallbackdata, 0, 0);
+ }
+ }
+ if (xdb->slots)
+ free(xdb->slots);
+ if (xdb->fd >= 0)
+ close(xdb->fd);
+ if (xdb->filename)
+ free(xdb->filename);
+ free(xdb);
+}
+
+/* moves the blob to a given new location (possibly resizeing) */
+static int moveblobto(rpmxdb xdb, struct xdb_slot *oldslot, struct xdb_slot *afterslot, unsigned int newpagecnt)
+{
+ struct xdb_slot *nextslot;
+ unsigned int newstartpage, oldpagecnt;
+ unsigned int tocopy;
+ int didmap;
+
+ newstartpage = afterslot->startpage + afterslot->pagecnt;
+ nextslot = xdb->slots + afterslot->next;
+
+ /* make sure there's enough room */
+ if (newpagecnt > nextslot->startpage - newstartpage)
+ return RPMRC_FAIL;
+
+#if 0
+ printf("moveblobto %d %d %d %d, afterslot %d\n", oldslot->startpage, oldslot->pagecnt, newstartpage, newpagecnt, afterslot->slotno);
+#endif
+ /* map old content */
+ didmap = 0;
+ oldpagecnt = oldslot->pagecnt;
+ if (!oldslot->mapped && oldpagecnt) {
+ if (mapslot(xdb, oldslot))
+ return RPMRC_FAIL;
+ didmap = 1;
+ }
+
+ /* copy content */
+ tocopy = newpagecnt > oldpagecnt ? oldpagecnt : newpagecnt;
+ if (tocopy && pwrite(xdb->fd, oldslot->mapped, tocopy * xdb->pagesize, newstartpage * xdb->pagesize) != tocopy * xdb->pagesize) {
+ if (didmap)
+ unmapslot(xdb, oldslot);
+ return RPMRC_FAIL;
+ }
+ /* zero out new pages */
+ if (newpagecnt > oldpagecnt) {
+ if (rpmxdbWriteEmptyPages(xdb, newstartpage + oldpagecnt, newpagecnt - oldpagecnt)) {
+ if (didmap)
+ unmapslot(xdb, oldslot);
+ return RPMRC_FAIL;
+ }
+ }
+
+ if (oldslot->mapped)
+ unmapslot(xdb, oldslot);
+
+ /* set new offset and position */
+ oldslot->startpage = newstartpage;
+ oldslot->pagecnt = newpagecnt;
+ rpmxdbUpdateSlot(xdb, oldslot);
+ xdb->usedblobpages -= oldpagecnt;
+ xdb->usedblobpages += newpagecnt;
+
+ if (afterslot != oldslot && nextslot != oldslot) {
+ /* remove from old chain */
+ xdb->slots[oldslot->prev].next = oldslot->next;
+ xdb->slots[oldslot->next].prev = oldslot->prev;
+
+ /* chain into new position, between lastslot and nextslot */
+ oldslot->prev = afterslot->slotno;
+ afterslot->next = oldslot->slotno;
+
+ oldslot->next = nextslot->slotno;
+ nextslot->prev = oldslot->slotno;
+ }
+
+ /* map again (if needed) */
+ if (oldslot->mapcallback) {
+ if (newpagecnt) {
+ if (mapslot(xdb, oldslot))
+ oldslot->mapped = 0; /* XXX: HELP, what can we do here? */
+ }
+ oldslot->mapcallback(xdb, oldslot->mapcallbackdata, oldslot->mapped, oldslot->mapped ? oldslot->pagecnt * xdb->pagesize : 0);
+ }
+ return RPMRC_OK;
+}
+
+/* moves the blob to a new location (possibly resizeing) */
+static int moveblob(rpmxdb xdb, struct xdb_slot *oldslot, unsigned int newpagecnt)
+{
+ struct xdb_slot *slot, *lastslot;
+ unsigned int nslots;
+ unsigned int freecnt;
+ int i;
+
+ nslots = xdb->nslots;
+ freecnt = 0;
+ lastslot = xdb->slots;
+ for (i = xdb->slots[0].next; ; lastslot = slot, i = slot->next) {
+ slot = xdb->slots + i;
+ freecnt = slot->startpage - (lastslot->startpage + lastslot->pagecnt);
+ if (freecnt >= newpagecnt)
+ break;
+ if (i == nslots)
+ break;
+ }
+ if (i == nslots && newpagecnt > freecnt) {
+ /* need to grow the file */
+ if (rpmxdbWriteEmptyPages(xdb, slot->startpage, newpagecnt - freecnt)) {
+ return RPMRC_FAIL;
+ }
+ slot->startpage += newpagecnt - freecnt;
+ }
+ return moveblobto(xdb, oldslot, lastslot, newpagecnt);
+}
+
+/* move the two blobs at the end of our file to the free area after the provided slot */
+static int moveblobstofront(rpmxdb xdb, struct xdb_slot *afterslot)
+{
+ struct xdb_slot *slot1, *slot2;
+ unsigned int freestart = afterslot->startpage + afterslot->pagecnt;
+ unsigned int freecount = xdb->slots[afterslot->next].startpage - freestart;
+
+ slot1 = xdb->slots + xdb->slots[xdb->nslots].prev;
+ if (slot1 == xdb->slots)
+ slot1 = slot2 = 0;
+ else {
+ slot2 = xdb->slots + slot1->prev;
+ if (slot2 == xdb->slots)
+ slot2 = 0;
+ }
+ if (slot1->pagecnt < slot2->pagecnt) {
+ struct xdb_slot *tmp = slot1;
+ slot1 = slot2;
+ slot2 = tmp;
+ }
+ if (slot1 && slot1->pagecnt && slot1->pagecnt <= freecount && slot1->startpage > freestart) {
+ if (moveblobto(xdb, slot1, afterslot, slot1->pagecnt))
+ return RPMRC_FAIL;
+ freestart += slot1->pagecnt;
+ freecount -= slot1->pagecnt;
+ afterslot = slot1;
+ }
+ if (slot2 && slot2->pagecnt && slot2->pagecnt <= freecount && slot2->startpage > freestart) {
+ if (moveblobto(xdb, slot2, afterslot, slot2->pagecnt))
+ return RPMRC_FAIL;
+ }
+ return RPMRC_OK;
+}
+
+/* add a single page containing empty slots */
+static int addslotpage(rpmxdb xdb)
+{
+ unsigned char *newaddr;
+ struct xdb_slot *slot;
+ int i, spp, nslots;
+ size_t newmappedlen;
+
+ if (xdb->firstfree)
+ return RPMRC_FAIL;
+
+ /* move first blob if needed */
+ nslots = xdb->nslots;
+ for (i = xdb->slots[0].next; i != nslots; i = slot->next) {
+ slot = xdb->slots + i;
+ if (slot->pagecnt)
+ break;
+ }
+ if (i != nslots && slot->pagecnt && slot->startpage == xdb->slotnpages) {
+ /* the blob at this slot is in the way. move it. */
+ if (moveblob(xdb, slot, slot->pagecnt))
+ return RPMRC_FAIL;
+ }
+
+ spp = xdb->pagesize / SLOT_SIZE; /* slots per page */
+ slot = realloc(xdb->slots, (nslots + 1 + spp) * sizeof(*slot));
+ if (!slot)
+ return RPMRC_FAIL;
+ xdb->slots = slot;
+
+ if (rpmxdbWriteEmptySlotpage(xdb, xdb->slotnpages)) {
+ return RPMRC_FAIL;
+ }
+ /* remap slots */
+ newmappedlen = xdb->slotnpages * xdb->pagesize + xdb->pagesize;
+ newmappedlen = (newmappedlen + xdb->systempagesize - 1) & ~(xdb->systempagesize - 1);
+ newaddr = mremap(xdb->mapped, xdb->mappedlen, newmappedlen, MREMAP_MAYMOVE);
+ if (newaddr == MAP_FAILED)
+ return RPMRC_FAIL;
+ xdb->mapped = newaddr;
+ xdb->mappedlen = newmappedlen;
+
+ /* update the header */
+ xdb->slotnpages++;
+ xdb->generation++;
+ rpmxdbWriteHeader(xdb);
+
+ /* fixup empty but used slots */
+ for (i = xdb->slots[0].next; i != nslots; i = slot->next) {
+ slot = xdb->slots + i;
+ if (slot->startpage >= xdb->slotnpages)
+ break;
+ slot->startpage = xdb->slotnpages;
+ if (slot->pagecnt)
+ abort();
+ }
+
+ /* move tail element to the new end */
+ slot = xdb->slots + nslots + spp;
+ *slot = xdb->slots[nslots];
+ slot->slotno = nslots + spp;
+ xdb->slots[slot->prev].next = slot->slotno;
+ xdb->nslots += spp;
+
+ /* add new free slots to the firstfree chain */
+ memset(xdb->slots + nslots, 0, sizeof(*slot) * spp);
+ for (i = 0; i < spp - 1; i++) {
+ xdb->slots[nslots + i].slotno = nslots + i;
+ xdb->slots[nslots + i].next = i + 1;
+ }
+ xdb->slots[nslots + i].slotno = nslots + i;
+ xdb->firstfree = nslots;
+ return RPMRC_OK;
+}
+
+static int createblob(rpmxdb xdb, unsigned int *idp, unsigned int blobtag, unsigned int subtag)
+{
+ struct xdb_slot *slot;
+ unsigned int id;
+
+ if (subtag > 255)
+ return RPMRC_FAIL;
+ if (!xdb->firstfree) {
+ if (addslotpage(xdb))
+ return RPMRC_FAIL;
+ }
+ id = xdb->firstfree;
+ slot = xdb->slots + xdb->firstfree;
+ xdb->firstfree = slot->next;
+
+ slot->mapped = 0;
+ slot->blobtag = blobtag;
+ slot->subtag = subtag;
+ slot->startpage = xdb->slotnpages;
+ slot->pagecnt = 0;
+ rpmxdbUpdateSlot(xdb, slot);
+ /* enqueue */
+ slot->prev = 0;
+ slot->next = xdb->slots[0].next;
+ xdb->slots[slot->next].prev = id;
+ xdb->slots[0].next = id;
+#if 0
+ printf("createblob #%d %d/%d\n", id, blobtag, subtag);
+#endif
+ if (slot->slotno != id)
+ abort();
+ if (slot->mapped)
+ abort();
+ *idp = id;
+ return RPMRC_OK;
+}
+
+int rpmxdbLookupBlob(rpmxdb xdb, unsigned int *idp, unsigned int blobtag, unsigned int subtag, int flags)
+{
+ struct xdb_slot *slot;
+ unsigned int i, nslots;
+ if (rpmxdbLockReadHeader(xdb, flags ? 1 : 0))
+ return RPMRC_FAIL;
+ nslots = xdb->nslots;
+ slot = 0;
+ for (i = xdb->slots[0].next; i != nslots; i = slot->next) {
+ slot = xdb->slots + i;
+ if (slot->blobtag == blobtag && slot->subtag == subtag)
+ break;
+ }
+ if (i == nslots)
+ i = 0;
+ if (i && (flags & O_TRUNC) != 0) {
+ if (rpmxdbResizeBlob(xdb, i, 0)) {
+ rpmxdbUnlock(xdb, flags ? 1 : 0);
+ return RPMRC_FAIL;
+ }
+ }
+ if (!i && (flags & O_CREAT) != 0) {
+ if (createblob(xdb, &i, blobtag, subtag)) {
+ rpmxdbUnlock(xdb, flags ? 1 : 0);
+ return RPMRC_FAIL;
+ }
+ }
+ *idp = i;
+ rpmxdbUnlock(xdb, flags ? 1 : 0);
+ return i ? RPMRC_OK : RPMRC_NOTFOUND;
+}
+
+int rpmxdbDelBlob(rpmxdb xdb, unsigned int id)
+{
+ struct xdb_slot *slot;
+ if (!id)
+ return RPMRC_FAIL;
+ if (rpmxdbLockReadHeader(xdb, 1))
+ return RPMRC_FAIL;
+ if (id >= xdb->nslots) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ slot = xdb->slots + id;
+ if (!slot->startpage) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_OK;
+ }
+ if (slot->mapped) {
+ unmapslot(xdb, slot);
+ slot->mapcallback(xdb, slot->mapcallbackdata, 0, 0);
+ }
+ /* remove from old chain */
+ xdb->slots[slot->prev].next = slot->next;
+ xdb->slots[slot->next].prev = slot->prev;
+ xdb->usedblobpages -= slot->pagecnt;
+
+ if (xdb->usedblobpages * 2 < xdb->slots[xdb->nslots].startpage && (slot->startpage + slot->pagecnt) * 2 < xdb->slots[xdb->nslots].startpage) {
+ /* freed in first half of pages, move last two blobs if we can */
+ moveblobstofront(xdb, xdb->slots + slot->prev);
+ }
+
+ /* zero slot */
+ memset(slot, 0, sizeof(*slot));
+ slot->slotno = id;
+ rpmxdbUpdateSlot(xdb, slot);
+
+ /* enqueue into free chain */
+ slot->next = xdb->firstfree;
+ xdb->firstfree = slot->slotno;
+
+ /* check if we should truncate the file */
+ slot = xdb->slots + xdb->slots[xdb->nslots].prev;
+ if (slot->startpage + slot->pagecnt < xdb->slots[xdb->nslots].startpage / 4 * 3) {
+ unsigned int newend = slot->startpage + slot->pagecnt;
+ if (!ftruncate(xdb->fd, newend * xdb->pagesize))
+ xdb->slots[xdb->nslots].startpage = newend;
+ }
+
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_OK;
+}
+
+int rpmxdbResizeBlob(rpmxdb xdb, unsigned int id, size_t newsize)
+{
+ struct xdb_slot *slot;
+ unsigned int oldpagecnt, newpagecnt;
+ if (!id)
+ return RPMRC_FAIL;
+ if (rpmxdbLockReadHeader(xdb, 1))
+ return RPMRC_FAIL;
+ if (id >= xdb->nslots) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ slot = xdb->slots + id;
+ if (!slot->startpage) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ oldpagecnt = slot->pagecnt;
+ newpagecnt = (newsize + xdb->pagesize - 1) / xdb->pagesize;
+ if (oldpagecnt && newpagecnt && newpagecnt <= oldpagecnt) {
+ /* reducing size. zero to end of page */
+ unsigned int pg = newsize & (xdb->pagesize - 1);
+ if (pg) {
+ if (slot->mapped) {
+ memset(slot->mapped + pg, 0, xdb->pagesize - pg);
+ } else {
+ char *empty = calloc(1, xdb->pagesize - pg);
+ if (!empty) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ if (pwrite(xdb->fd, empty, xdb->pagesize - pg, (slot->startpage + newpagecnt - 1) * xdb->pagesize + pg ) != xdb->pagesize - pg) {
+ free(empty);
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ free(empty);
+ }
+ }
+ }
+ if (newpagecnt == oldpagecnt) {
+ /* no size change */
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_OK;
+ }
+ if (!newpagecnt) {
+ /* special case: zero size blob, no longer mapped */
+ if (slot->mapped)
+ unmapslot(xdb, slot);
+ slot->pagecnt = 0;
+ slot->startpage = xdb->slotnpages;
+ /* remove from old chain */
+ xdb->slots[slot->prev].next = slot->next;
+ xdb->slots[slot->next].prev = slot->prev;
+ /* enqueue into head */
+ slot->prev = 0;
+ slot->next = xdb->slots[0].next;
+ xdb->slots[slot->next].prev = slot->slotno;
+ xdb->slots[0].next = slot->slotno;
+ rpmxdbUpdateSlot(xdb, slot);
+ xdb->usedblobpages -= oldpagecnt;
+ if (slot->mapcallback)
+ slot->mapcallback(xdb, slot->mapcallbackdata, 0, 0);
+ } else if (newpagecnt <= xdb->slots[slot->next].startpage - slot->startpage) {
+ /* can do it inplace */
+ if (newpagecnt > oldpagecnt) {
+ /* zero new pages */
+ if (rpmxdbWriteEmptyPages(xdb, slot->startpage + oldpagecnt, newpagecnt - oldpagecnt)) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ }
+ if (slot->mapcallback) {
+ if (remapslot(xdb, slot, newpagecnt)) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ } else {
+ if (slot->mapped)
+ unmapslot(xdb, slot);
+ slot->pagecnt = newpagecnt;
+ }
+ rpmxdbUpdateSlot(xdb, slot);
+ xdb->usedblobpages -= oldpagecnt;
+ xdb->usedblobpages += newpagecnt;
+ if (slot->mapcallback)
+ slot->mapcallback(xdb, slot->mapcallbackdata, slot->mapped, slot->pagecnt * xdb->pagesize);
+ } else {
+ /* need to relocate to a new page area */
+ if (moveblob(xdb, slot, newpagecnt)) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ }
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_OK;
+}
+
+int rpmxdbMapBlob(rpmxdb xdb, unsigned int id, int flags, void (*mapcallback)(rpmxdb xdb, void *data, void *newaddr, size_t newsize), void *mapcallbackdata)
+{
+ struct xdb_slot *slot;
+ if (!id || !mapcallback)
+ return RPMRC_FAIL;
+ if ((flags & (O_RDONLY|O_RDWR)) == O_RDWR && xdb->rdonly)
+ return RPMRC_FAIL;
+ if (rpmxdbLockReadHeader(xdb, 0))
+ return RPMRC_FAIL;
+ if (id >= xdb->nslots) {
+ rpmxdbUnlock(xdb, 0);
+ return RPMRC_FAIL;
+ }
+ slot = xdb->slots + id;
+ if (!slot->startpage || slot->mapped) {
+ rpmxdbUnlock(xdb, 0);
+ return RPMRC_FAIL;
+ }
+ slot->mapflags = (flags & (O_RDONLY|O_RDWR)) == O_RDWR ? PROT_READ | PROT_WRITE : PROT_READ;
+ if (slot->pagecnt) {
+ if (mapslot(xdb, slot)) {
+ slot->mapflags = 0;
+ rpmxdbUnlock(xdb, 0);
+ return RPMRC_FAIL;
+ }
+ }
+ slot->mapcallback = mapcallback;
+ slot->mapcallbackdata = mapcallbackdata;
+ mapcallback(xdb, mapcallbackdata, slot->mapped, slot->mapped ? slot->pagecnt * xdb->pagesize : 0);
+ rpmxdbUnlock(xdb, 0);
+ return RPMRC_OK;
+}
+
+int rpmxdbUnmapBlob(rpmxdb xdb, unsigned int id)
+{
+ struct xdb_slot *slot;
+ if (!id)
+ return RPMRC_OK;
+ if (rpmxdbLockReadHeader(xdb, 0))
+ return RPMRC_FAIL;
+ if (id >= xdb->nslots) {
+ rpmxdbUnlock(xdb, 0);
+ return RPMRC_FAIL;
+ }
+ slot = xdb->slots + id;
+ if (slot->mapped) {
+ unmapslot(xdb, slot);
+ slot->mapcallback(xdb, slot->mapcallbackdata, 0, 0);
+ }
+ slot->mapcallback = 0;
+ slot->mapcallbackdata = 0;
+ slot->mapflags = 0;
+ rpmxdbUnlock(xdb, 0);
+ return RPMRC_OK;
+}
+
+int rpmxdbRenameBlob(rpmxdb xdb, unsigned int *idp, unsigned int blobtag, unsigned int subtag)
+{
+ struct xdb_slot *slot;
+ unsigned int otherid;
+ unsigned int id = *idp;
+ int rc;
+
+ if (!id || subtag > 255)
+ return RPMRC_FAIL;
+ if (rpmxdbLockReadHeader(xdb, 1))
+ return RPMRC_FAIL;
+ if (id >= xdb->nslots) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ slot = xdb->slots + id;
+#if 0
+ printf("rpmxdbRenameBlob #%d %d/%d -> %d/%d\n", id, slot->blobtag, slot->subtag, blobtag, subtag);
+#endif
+ if (!slot->startpage) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ if (slot->blobtag == blobtag && slot->subtag == subtag) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_OK;
+ }
+ rc = rpmxdbLookupBlob(xdb, &otherid, blobtag, subtag, 0);
+ if (rc == RPMRC_NOTFOUND)
+ otherid = 0;
+ else if (rc) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ if (otherid) {
+#if 0
+ printf("(replacing #%d)\n", otherid);
+#endif
+ if (rpmxdbDelBlob(xdb, otherid)) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ /* get otherid back from free chain */
+ if (xdb->firstfree != otherid)
+ return RPMRC_FAIL;
+ xdb->firstfree = xdb->slots[otherid].next;
+
+ slot->blobtag = blobtag;
+ slot->subtag = subtag;
+ xdb->slots[otherid] = *slot;
+ /* fixup ids */
+ xdb->slots[otherid].slotno = otherid;
+ xdb->slots[slot->prev].next = otherid;
+ xdb->slots[slot->next].prev = otherid;
+ /* write */
+ rpmxdbUpdateSlot(xdb, xdb->slots + otherid);
+ memset(slot, 0, sizeof(*slot));
+ slot->slotno = id;
+ rpmxdbUpdateSlot(xdb, slot);
+ slot->next = xdb->firstfree;
+ xdb->firstfree = slot->slotno;
+ *idp = otherid;
+ } else {
+ slot = xdb->slots + id;
+ slot->blobtag = blobtag;
+ slot->subtag = subtag;
+ rpmxdbUpdateSlot(xdb, slot);
+ }
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_OK;
+}
+
+void rpmxdbSetFsync(rpmxdb xdb, int dofsync)
+{
+ xdb->dofsync = dofsync;
+}
+
+int rpmxdbIsRdonly(rpmxdb xdb)
+{
+ return xdb->rdonly;
+}
+
+int rpmxdbSetUserGeneration(rpmxdb xdb, unsigned int usergeneration)
+{
+ if (rpmxdbLockReadHeader(xdb, 1))
+ return RPMRC_FAIL;
+ /* sync before the update */
+ if (xdb->dofsync && fsync(xdb->fd)) {
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_FAIL;
+ }
+ xdb->usergeneration = usergeneration;
+ xdb->generation++;
+ rpmxdbWriteHeader(xdb);
+ rpmxdbUnlock(xdb, 1);
+ return RPMRC_OK;
+}
+
+int rpmxdbGetUserGeneration(rpmxdb xdb, unsigned int *usergenerationp)
+{
+ if (rpmxdbLockReadHeader(xdb, 0))
+ return RPMRC_FAIL;
+ *usergenerationp = xdb->usergeneration;
+ rpmxdbUnlock(xdb, 0);
+ return RPMRC_OK;
+}
+
+int rpmxdbStats(rpmxdb xdb)
+{
+ struct xdb_slot *slot;
+ unsigned int i, nslots;
+
+ if (rpmxdbLockReadHeader(xdb, 0))
+ return RPMRC_FAIL;
+ nslots = xdb->nslots;
+ printf("--- XDB Stats\n");
+ printf("Filename: %s\n", xdb->filename);
+ printf("Generation: %d\n", xdb->generation);
+ printf("Slot pages: %d\n", xdb->slotnpages);
+ printf("Blob pages: %d\n", xdb->usedblobpages);
+ printf("Free pages: %d\n", xdb->slots[nslots].startpage - xdb->usedblobpages - xdb->slotnpages);
+ printf("Pagesize: %d / %d\n", xdb->pagesize, xdb->systempagesize);
+ for (i = 1, slot = xdb->slots + i; i < nslots; i++, slot++) {
+ if (!slot->startpage)
+ continue;
+ printf("%2d: tag %d/%d, startpage %d, pagecnt %d%s\n", i, slot->blobtag, slot->subtag, slot->startpage, slot->pagecnt, slot->mapcallbackdata ? ", mapped" : "");
+ }
+#if 0
+ printf("Again in offset order:\n");
+ for (i = xdb->slots[0].next; i != nslots; i = slot->next) {
+ slot = xdb->slots + i;
+ printf("%2d: tag %d/%d, startpage %d, pagecnt %d%s\n", i, slot->blobtag, slot->subtag, slot->startpage, slot->pagecnt, slot->mapcallbackdata ? ", mapped" : "");
+ }
+#endif
+#if 0
+ printf("Free chain:\n");
+ for (i = xdb->firstfree; i; i = slot->next) {
+ slot = xdb->slots + i;
+ printf("%2d [%2d]: tag %d/%d, startpage %d, pagecnt %d%s\n", i, slot->slotno, slot->blobtag, slot->subtag, slot->startpage, slot->pagecnt, slot->mapcallbackdata ? ", mapped" : "");
+ }
+#endif
+ rpmxdbUnlock(xdb, 0);
+ return RPMRC_OK;
+}
+
diff --git a/lib/backend/ndb/rpmxdb.h b/lib/backend/ndb/rpmxdb.h
new file mode 100644
index 000000000..4358536ef
--- /dev/null
+++ b/lib/backend/ndb/rpmxdb.h
@@ -0,0 +1,27 @@
+#include "rpmpkg.h"
+
+struct rpmxdb_s;
+typedef struct rpmxdb_s *rpmxdb;
+
+int rpmxdbOpen(rpmxdb *xdbp, rpmpkgdb pkgdb, const char *filename, int flags, int mode);
+void rpmxdbClose(rpmxdb xdb);
+void rpmxdbSetFsync(rpmxdb xdb, int dofsync);
+int rpmxdbIsRdonly(rpmxdb xdb);
+
+int rpmxdbLock(rpmxdb xdb, int excl);
+int rpmxdbUnlock(rpmxdb xdb, int excl);
+
+int rpmxdbLookupBlob(rpmxdb xdb, unsigned int *idp, unsigned int blobtag, unsigned int subtag, int flags);
+int rpmxdbDelBlob(rpmxdb xdb, unsigned int id) ;
+
+int rpmxdbMapBlob(rpmxdb xdb, unsigned int id, int flags, void (*mapcallback)(rpmxdb xdb, void *data, void *newaddr, size_t newsize), void *mapcallbackdata);
+int rpmxdbUnmapBlob(rpmxdb xdb, unsigned int id);
+
+int rpmxdbResizeBlob(rpmxdb xdb, unsigned int id, size_t newsize);
+int rpmxdbRenameBlob(rpmxdb xdb, unsigned int *idp, unsigned int blobtag, unsigned int subtag);
+
+int rpmxdbSetUserGeneration(rpmxdb xdb, unsigned int usergeneration);
+int rpmxdbGetUserGeneration(rpmxdb xdb, unsigned int *usergenerationp);
+
+int rpmxdbStats(rpmxdb xdb);
+