diff options
Diffstat (limited to 'lib/backend')
-rw-r--r-- | lib/backend/db3.c | 994 | ||||
-rw-r--r-- | lib/backend/dbconfig.c | 2 | ||||
-rw-r--r-- | lib/backend/dbi.c | 184 | ||||
-rw-r--r-- | lib/backend/dbi.h | 202 | ||||
-rw-r--r-- | lib/backend/dbiset.c | 214 | ||||
-rw-r--r-- | lib/backend/dbiset.h | 130 | ||||
-rw-r--r-- | lib/backend/lmdb.c | 939 | ||||
-rw-r--r-- | lib/backend/ndb/glue.c | 492 | ||||
-rw-r--r-- | lib/backend/ndb/rpmidx.c | 1280 | ||||
-rw-r--r-- | lib/backend/ndb/rpmidx.h | 18 | ||||
-rw-r--r-- | lib/backend/ndb/rpmpkg.c | 1312 | ||||
-rw-r--r-- | lib/backend/ndb/rpmpkg.h | 20 | ||||
-rw-r--r-- | lib/backend/ndb/rpmxdb.c | 1221 | ||||
-rw-r--r-- | lib/backend/ndb/rpmxdb.h | 27 |
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); + |