summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/backend/db3.c1184
-rw-r--r--lib/backend/dbconfig.c473
-rw-r--r--lib/backend/sqlite.c1386
-rw-r--r--lib/fprint.c265
-rw-r--r--lib/fprint.h141
-rwxr-xr-xlib/gentagtbl.sh84
-rw-r--r--lib/hdrNVR.c130
-rw-r--r--lib/header.c3039
-rw-r--r--lib/header.h615
-rw-r--r--lib/header_internal.c170
-rw-r--r--lib/header_internal.h178
-rw-r--r--lib/merge.c344
-rw-r--r--lib/poptDB.c26
-rw-r--r--lib/rpmdb.c3525
-rw-r--r--lib/rpmdb.h287
-rw-r--r--lib/rpmdb_internal.h665
-rw-r--r--lib/rpmhash.c176
-rw-r--r--lib/rpmhash.h96
-rw-r--r--lib/rpmints.h.in29
-rw-r--r--lib/tagname.c291
20 files changed, 13104 insertions, 0 deletions
diff --git a/lib/backend/db3.c b/lib/backend/db3.c
new file mode 100644
index 000000000..e1983de41
--- /dev/null
+++ b/lib/backend/db3.c
@@ -0,0 +1,1184 @@
+/** \ingroup db3
+ * \file rpmdb/db3.c
+ */
+
+static int _debug = 1; /* XXX if < 0 debugging, > 0 unusual error returns */
+
+#include "system.h"
+
+#if defined(HAVE_FTOK) && defined(HAVE_SYS_IPC_H)
+#include <sys/ipc.h>
+#endif
+
+#include <rpm/rpmtag.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmfileutil.h> /* rpmioMkPath */
+#include <rpm/rpmlog.h>
+
+#include "rpmdb/rpmdb_internal.h"
+
+#include "debug.h"
+
+#if !defined(DB_CLIENT) /* XXX db-4.2.42 retrofit */
+#define DB_CLIENT DB_RPCCLIENT
+#endif
+
+
+/** \ingroup dbi
+ * Hash database statistics.
+ */
+struct dbiHStats_s {
+ unsigned int hash_magic; /*!< hash database magic number. */
+ unsigned int hash_version; /*!< version of the hash database. */
+ unsigned int hash_nkeys; /*!< no. of unique keys in the database. */
+ unsigned int hash_ndata; /*!< no. of key/data pairs in the database. */
+ unsigned int hash_pagesize; /*!< db page (and bucket) size, in bytes. */
+ unsigned int hash_nelem; /*!< estimated size of the hash table. */
+ unsigned int hash_ffactor; /*!< no. of items per bucket. */
+ unsigned int hash_buckets; /*!< no. of hash buckets. */
+ unsigned int hash_free; /*!< no. of pages on the free list. */
+ unsigned int hash_bfree; /*!< no. of bytes free on bucket pages. */
+ unsigned int hash_bigpages; /*!< no. of big key/data pages. */
+ unsigned int hash_big_bfree;/*!< no. of bytes free on big item pages. */
+ unsigned int hash_overflows;/*!< no. of overflow pages. */
+ unsigned int hash_ovfl_free;/*!< no. of bytes free on overflow pages. */
+ unsigned int hash_dup; /*!< no. of duplicate pages. */
+ unsigned int hash_dup_free; /*!< no. bytes free on duplicate pages. */
+};
+
+/** \ingroup dbi
+ * B-tree database statistics.
+ */
+struct dbiBStats_s {
+ unsigned int bt_magic; /*!< btree database magic. */
+ unsigned int bt_version; /*!< version of the btree database. */
+ unsigned int bt_nkeys; /*!< no. of unique keys in the database. */
+ unsigned int bt_ndata; /*!< no. of key/data pairs in the database. */
+ unsigned int bt_pagesize; /*!< database page size, in bytes. */
+ unsigned int bt_minkey; /*!< minimum keys per page. */
+ unsigned int bt_re_len; /*!< length of fixed-length records. */
+ unsigned int bt_re_pad; /*!< padding byte for fixed-length records. */
+ unsigned int bt_levels; /*!< no. of levels in the database. */
+ unsigned int bt_int_pg; /*!< no. of database internal pages. */
+ unsigned int bt_leaf_pg; /*!< no. of database leaf pages. */
+ unsigned int bt_dup_pg; /*!< no. of database duplicate pages. */
+ unsigned int bt_over_pg; /*!< no. of database overflow pages. */
+ unsigned int bt_free; /*!< no. of pages on the free list. */
+ unsigned int bt_int_pgfree; /*!< no. of bytes free in internal pages. */
+ unsigned int bt_leaf_pgfree;/*!< no. of bytes free in leaf pages. */
+ unsigned int bt_dup_pgfree; /*!< no. of bytes free in duplicate pages. */
+ unsigned int bt_over_pgfree;/*!< no. of bytes free in overflow pages. */
+};
+
+#ifdef NOTNOW
+static const char * bfstring(unsigned int x, const char * xbf)
+{
+ const char * s = xbf;
+ static char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
+ static char buf[256];
+ char * t, * te;
+ unsigned radix;
+ unsigned c, i, k;
+
+ radix = (s != NULL ? *s++ : 16);
+
+ if (radix <= 1 || radix >= 32)
+ radix = 16;
+
+ t = buf;
+ switch (radix) {
+ case 8: *t++ = '0'; break;
+ case 16: *t++ = '0'; *t++ = 'x'; break;
+ }
+
+ i = 0;
+ k = x;
+ do { i++; k /= radix; } while (k);
+
+ te = t + i;
+
+ k = x;
+ do { --i; t[i] = digits[k % radix]; k /= radix; } while (k);
+
+ t = te;
+ i = '<';
+ if (s != NULL)
+ while ((c = *s++) != '\0') {
+ if (c > ' ') continue;
+
+ k = (1 << (c - 1));
+ if (!(x & k)) continue;
+
+ if (t == te) *t++ = '=';
+
+ *t++ = i;
+ i = ',';
+ while (*s > ' ')
+ *t++ = *s++;
+ }
+ if (t > te) *t++ = '>';
+ *t = '\0';
+ return buf;
+}
+
+static const char * dbtFlags =
+ "\20\1APPMALLOC\2ISSET\3MALLOC\4PARTIAL\5REALLOC\6USERMEM\7DUPOK";
+
+static const char * dbenvOpenFlags =
+ "\20\1CREATE\2NO_EXCEPTIONS\3FORCE\4NOMMAP\5RDONLY\6RECOVER\7THREAD\10TXN_NOSYNC\11USE_ENVIRON\12USE_ENVIRON_ROOT\13CDB\14LOCK\15LOG\16MPOOL\17TXN\20JOINENV\21LOCKDOWN\22PRIVATE\23RECOVER_FATAL\24SYSTEM_MEM";
+
+static const char * dbOpenFlags =
+ "\20\1CREATE\2NO_EXCEPTIONS\3FORCE\4NOMMAP\5RDONLY\6RECOVER\7THREAD\10TXN_NOSYNC\11USE_ENVIRON\12USE_ENVIRON_ROOT\13EXCL\14FCNTL_LOCKING\15RDWRMASTER\16TRUNCATE\17EXTENT\20APPLY_LOGREG";
+
+static const char * dbenvSetFlags =
+ "\20\1CREATE\2NO_EXCEPTIONS\3FORCE\4NOMMAP\5RDONLY\6RECOVER\7THREAD\10TXN_NOSYNC\11USE_ENVIRON\12USE_ENVIRON_ROOT\13CDB_ALLDB\14NOLOCKING\15NOPANIC\16PANIC_ENV\17REGION_INIT\20YIELDCPU";
+
+static const char * dbSetFlags =
+ "\20\1DUP\2DUPSORT\3RECNUM\4RENUMBER\5REVSPLITOFF\6SNAPSHOT";
+
+static const char * dbiModeFlags =
+ "\20\1WRONLY\2RDWR\7CREAT\10EXCL\11NOCTTY\12TRUNC\13APPEND\14NONBLOCK\15SYNC\16ASYNC\17DIRECT\20LARGEFILE\21DIRECTORY\22NOFOLLOW";
+#endif /* NOTNOW */
+
+
+static int cvtdberr(dbiIndex dbi, const char * msg, int error, int printit)
+{
+ int rc = error;
+
+ if (printit && rc) {
+ if (msg)
+ rpmlog(RPMLOG_ERR, _("db%d error(%d) from %s: %s\n"),
+ dbi->dbi_api, rc, msg, db_strerror(error));
+ else
+ rpmlog(RPMLOG_ERR, _("db%d error(%d): %s\n"),
+ dbi->dbi_api, rc, db_strerror(error));
+ }
+
+ return rc;
+}
+
+static int db_fini(dbiIndex dbi, const char * dbhome,
+ const char * dbfile,
+ const char * dbsubfile)
+{
+ rpmdb rpmdb = dbi->dbi_rpmdb;
+ DB_ENV * dbenv = rpmdb->db_dbenv;
+ int rc;
+
+ if (dbenv == NULL)
+ return 0;
+
+ rc = dbenv->close(dbenv, 0);
+ rc = cvtdberr(dbi, "dbenv->close", rc, _debug);
+
+ if (dbfile)
+ rpmlog(RPMLOG_DEBUG, "closed db environment %s/%s\n",
+ dbhome, dbfile);
+
+ if (rpmdb->db_remove_env) {
+ int xx;
+
+ xx = db_env_create(&dbenv, 0);
+ xx = cvtdberr(dbi, "db_env_create", xx, _debug);
+ xx = dbenv->remove(dbenv, dbhome, 0);
+ xx = cvtdberr(dbi, "dbenv->remove", xx, _debug);
+
+ if (dbfile)
+ rpmlog(RPMLOG_DEBUG, "removed db environment %s/%s\n",
+ dbhome, dbfile);
+
+ }
+ return rc;
+}
+
+static int db3_fsync_disable(int fd)
+{
+ return 0;
+}
+
+#if (DB_VERSION_MAJOR >= 4 && DB_VERSION_MINOR >= 5)
+/*
+ * dbenv->failchk() callback method for determining is the given pid/tid
+ * is alive. We only care about pid's though.
+ */
+static int db3isalive(DB_ENV *dbenv, pid_t pid, db_threadid_t tid, uint32_t flags)
+{
+ int alive = 0;
+
+ if (pid == getpid()) {
+ alive = 1;
+ } else if (kill(pid, 0) == 0) {
+ alive = 1;
+ /* only existing processes can fail with EPERM */
+ } else if (errno == EPERM) {
+ alive = 1;
+ }
+
+ return alive;
+}
+#endif
+
+static int db_init(dbiIndex dbi, const char * dbhome,
+ const char * dbfile,
+ const char * dbsubfile,
+ DB_ENV ** dbenvp)
+{
+ rpmdb rpmdb = dbi->dbi_rpmdb;
+ DB_ENV *dbenv = NULL;
+ int eflags;
+ int rc;
+
+ if (dbenvp == NULL)
+ return 1;
+
+ /* XXX HACK */
+ if (rpmdb->db_errfile == NULL)
+ rpmdb->db_errfile = stderr;
+
+ eflags = (dbi->dbi_oeflags | dbi->dbi_eflags);
+ if (eflags & DB_JOINENV) eflags &= DB_JOINENV;
+
+ if (dbfile) {
+ char *dbiflags = prDbiOpenFlags(eflags, 1);
+ rpmlog(RPMLOG_DEBUG, "opening db environment %s/%s %s\n",
+ dbhome, dbfile, dbiflags);
+ free(dbiflags);
+ }
+
+ /* XXX Can't do RPC w/o host. */
+ if (dbi->dbi_host == NULL)
+ dbi->dbi_ecflags &= ~DB_CLIENT;
+
+ /* XXX Set a default shm_key. */
+ if ((dbi->dbi_eflags & DB_SYSTEM_MEM) && dbi->dbi_shmkey == 0) {
+#if defined(HAVE_FTOK)
+ dbi->dbi_shmkey = ftok(dbhome, 0);
+#else
+ dbi->dbi_shmkey = 0x44631380;
+#endif
+ }
+
+ rc = db_env_create(&dbenv, dbi->dbi_ecflags);
+ rc = cvtdberr(dbi, "db_env_create", rc, _debug);
+ if (dbenv == NULL || rc)
+ goto errxit;
+
+ { int xx;
+
+ /* 4.1: dbenv->set_app_dispatch(???) */
+ /* 4.1: dbenv->set_alloc(???) */
+ /* 4.1: dbenv->set_data_dir(???) */
+ /* 4.1: dbenv->set_encrypt(???) */
+
+ dbenv->set_errcall(dbenv, (void *) rpmdb->db_errcall);
+ dbenv->set_errfile(dbenv, rpmdb->db_errfile);
+ dbenv->set_errpfx(dbenv, rpmdb->db_errpfx);
+
+ /* 4.1: dbenv->set_feedback(???) */
+ /* 4.1: dbenv->set_flags(???) */
+
+ /* dbenv->set_paniccall(???) */
+
+#if (DB_VERSION_MAJOR >= 4 && DB_VERSION_MINOR >= 5)
+ /*
+ * These enable automatic stale lock removal.
+ * thread_count 8 is some kind of "magic minimum" value...
+ */
+ dbenv->set_thread_count(dbenv, 8);
+ dbenv->set_isalive(dbenv, db3isalive);
+#endif
+
+ if ((dbi->dbi_ecflags & DB_CLIENT) && dbi->dbi_host) {
+ const char * home;
+ int retry = 0;
+
+ if ((home = strrchr(dbhome, '/')) != NULL)
+ dbhome = ++home;
+
+ while (retry++ < 5) {
+ xx = dbenv->set_rpc_server(dbenv, NULL, dbi->dbi_host,
+ dbi->dbi_cl_timeout, dbi->dbi_sv_timeout, 0);
+ xx = cvtdberr(dbi, "dbenv->set_server", xx, _debug);
+ if (!xx)
+ break;
+ (void) sleep(15);
+ }
+ } else {
+#if !(DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
+ xx = dbenv->set_verbose(dbenv, DB_VERB_CHKPOINT,
+ (dbi->dbi_verbose & DB_VERB_CHKPOINT));
+#endif
+ xx = dbenv->set_verbose(dbenv, DB_VERB_DEADLOCK,
+ (dbi->dbi_verbose & DB_VERB_DEADLOCK));
+ xx = dbenv->set_verbose(dbenv, DB_VERB_RECOVERY,
+ (dbi->dbi_verbose & DB_VERB_RECOVERY));
+ xx = dbenv->set_verbose(dbenv, DB_VERB_WAITSFOR,
+ (dbi->dbi_verbose & DB_VERB_WAITSFOR));
+
+ if (dbi->dbi_mmapsize) {
+ xx = dbenv->set_mp_mmapsize(dbenv, dbi->dbi_mmapsize);
+ xx = cvtdberr(dbi, "dbenv->set_mp_mmapsize", xx, _debug);
+ }
+ if (dbi->dbi_tmpdir) {
+ const char * root;
+ char * tmpdir;
+
+ root = (dbi->dbi_root ? dbi->dbi_root : rpmdb->db_root);
+ if ((root[0] == '/' && root[1] == '\0') || rpmdb->db_chrootDone)
+ root = NULL;
+ tmpdir = rpmGenPath(root, dbi->dbi_tmpdir, NULL);
+ xx = dbenv->set_tmp_dir(dbenv, tmpdir);
+ xx = cvtdberr(dbi, "dbenv->set_tmp_dir", xx, _debug);
+ tmpdir = _free(tmpdir);
+ }
+ }
+
+ /* dbenv->set_lk_conflicts(???) */
+ /* dbenv->set_lk_detect(???) */
+ /* 4.1: dbenv->set_lk_max_lockers(???) */
+ /* 4.1: dbenv->set_lk_max_locks(???) */
+ /* 4.1: dbenv->set_lk_max_objects(???) */
+
+ /* 4.1: dbenv->set_lg_bsize(???) */
+ /* 4.1: dbenv->set_lg_dir(???) */
+ /* 4.1: dbenv->set_lg_max(???) */
+ /* 4.1: dbenv->set_lg_regionmax(???) */
+
+ if (dbi->dbi_cachesize) {
+ xx = dbenv->set_cachesize(dbenv, 0, dbi->dbi_cachesize, 0);
+ xx = cvtdberr(dbi, "dbenv->set_cachesize", xx, _debug);
+ }
+
+ /* 4.1 dbenv->set_timeout(???) */
+ /* dbenv->set_tx_max(???) */
+ /* 4.1: dbenv->set_tx_timestamp(???) */
+ /* dbenv->set_tx_recover(???) */
+
+ /* dbenv->set_rep_transport(???) */
+ /* dbenv->set_rep_limit(???) */
+
+ if (dbi->dbi_no_fsync) {
+ xx = db_env_set_func_fsync(db3_fsync_disable);
+ xx = cvtdberr(dbi, "db_env_set_func_fsync", xx, _debug);
+ }
+
+ if (dbi->dbi_shmkey) {
+ xx = dbenv->set_shm_key(dbenv, dbi->dbi_shmkey);
+ xx = cvtdberr(dbi, "dbenv->set_shm_key", xx, _debug);
+ }
+ }
+
+ rc = (dbenv->open)(dbenv, dbhome, eflags, dbi->dbi_perms);
+ rc = cvtdberr(dbi, "dbenv->open", rc, _debug);
+ if (rc)
+ goto errxit;
+
+#if (DB_VERSION_MAJOR >= 4 && DB_VERSION_MINOR >= 5)
+ /* stale lock removal */
+ rc = dbenv->failchk(dbenv, 0);
+#endif
+
+ *dbenvp = dbenv;
+
+ return 0;
+
+errxit:
+ if (dbenv) {
+ int xx;
+ xx = dbenv->close(dbenv, 0);
+ xx = cvtdberr(dbi, "dbenv->close", xx, _debug);
+ }
+ return rc;
+}
+
+static int db3sync(dbiIndex dbi, unsigned int flags)
+{
+ DB * db = dbi->dbi_db;
+ int rc = 0;
+ int _printit;
+
+ if (db != NULL)
+ rc = db->sync(db, flags);
+ /* XXX DB_INCOMPLETE is returned occaisionally with multiple access. */
+ _printit = _debug;
+ rc = cvtdberr(dbi, "db->sync", rc, _printit);
+ return rc;
+}
+
+static int db3cdup(dbiIndex dbi, DBC * dbcursor, DBC ** dbcp,
+ unsigned int flags)
+{
+ int rc;
+
+ if (dbcp) *dbcp = NULL;
+ rc = dbcursor->c_dup(dbcursor, dbcp, flags);
+ rc = cvtdberr(dbi, "dbcursor->c_dup", rc, _debug);
+ /* FIX: *dbcp can be NULL */
+ return rc;
+}
+
+static int db3cclose(dbiIndex dbi, DBC * dbcursor,
+ unsigned int flags)
+{
+ int rc = -2;
+
+ /* XXX db3copen error pathways come through here. */
+ if (dbcursor != NULL) {
+ rc = dbcursor->c_close(dbcursor);
+ rc = cvtdberr(dbi, "dbcursor->c_close", rc, _debug);
+ }
+ return rc;
+}
+
+static int db3copen(dbiIndex dbi, DB_TXN * txnid,
+ DBC ** dbcp, unsigned int dbiflags)
+{
+ DB * db = dbi->dbi_db;
+ DBC * dbcursor = NULL;
+ int flags;
+ int rc;
+
+ /* XXX DB_WRITECURSOR cannot be used with sunrpc dbenv. */
+ assert(db != NULL);
+ if ((dbiflags & DB_WRITECURSOR) &&
+ (dbi->dbi_eflags & DB_INIT_CDB) && !(dbi->dbi_oflags & DB_RDONLY))
+ {
+ flags = DB_WRITECURSOR;
+ } else
+ flags = 0;
+
+ rc = db->cursor(db, txnid, &dbcursor, flags);
+ rc = cvtdberr(dbi, "db->cursor", rc, _debug);
+
+ if (dbcp)
+ *dbcp = dbcursor;
+ else
+ (void) db3cclose(dbi, dbcursor, 0);
+
+ return rc;
+}
+
+static int db3cput(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags)
+{
+ DB * db = dbi->dbi_db;
+ int rc;
+
+ assert(db != NULL);
+ if (dbcursor == NULL) {
+ rc = db->put(db, dbi->dbi_txnid, key, data, 0);
+ rc = cvtdberr(dbi, "db->put", rc, _debug);
+ } else {
+ rc = dbcursor->c_put(dbcursor, key, data, DB_KEYLAST);
+ rc = cvtdberr(dbi, "dbcursor->c_put", rc, _debug);
+ }
+
+ return rc;
+}
+
+static int db3cdel(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags)
+{
+ DB * db = dbi->dbi_db;
+ int rc;
+
+ assert(db != NULL);
+ if (dbcursor == NULL) {
+ rc = db->del(db, dbi->dbi_txnid, key, flags);
+ rc = cvtdberr(dbi, "db->del", rc, _debug);
+ } else {
+ int _printit;
+
+ /* XXX TODO: insure that cursor is positioned with duplicates */
+ rc = dbcursor->c_get(dbcursor, key, data, DB_SET);
+ /* XXX DB_NOTFOUND can be returned */
+ _printit = (rc == DB_NOTFOUND ? 0 : _debug);
+ rc = cvtdberr(dbi, "dbcursor->c_get", rc, _printit);
+
+ if (rc == 0) {
+ rc = dbcursor->c_del(dbcursor, flags);
+ rc = cvtdberr(dbi, "dbcursor->c_del", rc, _debug);
+ }
+ }
+
+ return rc;
+}
+
+static int db3cget(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags)
+{
+ DB * db = dbi->dbi_db;
+ int _printit;
+ int rc;
+
+ assert(db != NULL);
+ if (dbcursor == NULL) {
+ /* XXX duplicates require cursors. */
+ rc = db->get(db, dbi->dbi_txnid, key, data, 0);
+ /* XXX DB_NOTFOUND can be returned */
+ _printit = (rc == DB_NOTFOUND ? 0 : _debug);
+ rc = cvtdberr(dbi, "db->get", rc, _printit);
+ } else {
+ /* XXX db3 does DB_FIRST on uninitialized cursor */
+ rc = dbcursor->c_get(dbcursor, key, data, flags);
+ /* XXX DB_NOTFOUND can be returned */
+ _printit = (rc == DB_NOTFOUND ? 0 : _debug);
+ rc = cvtdberr(dbi, "dbcursor->c_get", rc, _printit);
+ }
+
+ return rc;
+}
+
+static int db3cpget(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * pkey,
+ DBT * data, unsigned int flags)
+{
+ DB * db = dbi->dbi_db;
+ int _printit;
+ int rc;
+
+ assert(db != NULL);
+ assert(dbcursor != NULL);
+
+ /* XXX db3 does DB_FIRST on uninitialized cursor */
+ rc = dbcursor->c_pget(dbcursor, key, pkey, data, flags);
+ /* XXX DB_NOTFOUND can be returned */
+ _printit = (rc == DB_NOTFOUND ? 0 : _debug);
+ rc = cvtdberr(dbi, "dbcursor->c_pget", rc, _printit);
+
+ return rc;
+}
+
+static int db3ccount(dbiIndex dbi, DBC * dbcursor,
+ unsigned int * countp,
+ unsigned int flags)
+{
+ db_recno_t count = 0;
+ int rc = 0;
+
+ flags = 0;
+ rc = dbcursor->c_count(dbcursor, &count, flags);
+ rc = cvtdberr(dbi, "dbcursor->c_count", rc, _debug);
+ if (rc) return rc;
+ if (countp) *countp = count;
+
+ return rc;
+}
+
+static int db3byteswapped(dbiIndex dbi)
+{
+ DB * db = dbi->dbi_db;
+ int rc = 0;
+
+ if (db != NULL) {
+ int isswapped = 0;
+ rc = db->get_byteswapped(db, &isswapped);
+ if (rc == 0)
+ rc = isswapped;
+ }
+
+ return rc;
+}
+
+static int db3stat(dbiIndex dbi, unsigned int flags)
+{
+ DB * db = dbi->dbi_db;
+#if (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
+ DB_TXN * txnid = NULL;
+#endif
+ int rc = 0;
+
+ assert(db != NULL);
+#if defined(DB_FAST_STAT)
+ if (flags)
+ flags = DB_FAST_STAT;
+ else
+#endif
+ flags = 0;
+ dbi->dbi_stats = _free(dbi->dbi_stats);
+#if (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
+ rc = db->stat(db, txnid, &dbi->dbi_stats, flags);
+#else
+ rc = db->stat(db, &dbi->dbi_stats, flags);
+#endif
+ rc = cvtdberr(dbi, "db->stat", rc, _debug);
+ return rc;
+}
+
+static int db3associate(dbiIndex dbi, dbiIndex dbisecondary,
+ int (*callback)(DB *, const DBT *, const DBT *, DBT *),
+ unsigned int flags)
+{
+ DB * db = dbi->dbi_db;
+ DB * secondary = dbisecondary->dbi_db;
+ int rc;
+
+ DB_TXN * txnid = NULL;
+
+assert(db != NULL);
+ rc = db->associate(db, txnid, secondary, callback, flags);
+ rc = cvtdberr(dbi, "db->associate", rc, _debug);
+ return rc;
+}
+
+static int db3join(dbiIndex dbi, DBC ** curslist, DBC ** dbcp,
+ unsigned int flags)
+{
+ DB * db = dbi->dbi_db;
+ int rc;
+
+assert(db != NULL);
+ rc = db->join(db, curslist, dbcp, flags);
+ rc = cvtdberr(dbi, "db->join", rc, _debug);
+ return rc;
+}
+
+static int db3close(dbiIndex dbi, unsigned int flags)
+{
+ rpmdb rpmdb = dbi->dbi_rpmdb;
+ const char * root;
+ const char * home;
+ char * dbhome;
+ const char * dbfile;
+ const char * dbsubfile;
+ DB * db = dbi->dbi_db;
+ int _printit;
+ int rc = 0, xx;
+
+ flags = 0; /* XXX unused */
+
+ /*
+ * Get the prefix/root component and directory path.
+ */
+ root = (dbi->dbi_root ? dbi->dbi_root : rpmdb->db_root);
+ if ((root[0] == '/' && root[1] == '\0') || rpmdb->db_chrootDone)
+ root = NULL;
+ home = (dbi->dbi_home ? dbi->dbi_home : rpmdb->db_home);
+
+ dbhome = rpmGenPath(root, home, NULL);
+ if (dbi->dbi_temporary) {
+ dbfile = NULL;
+ dbsubfile = NULL;
+ } else {
+#ifdef HACK /* XXX necessary to support dbsubfile */
+ dbfile = (dbi->dbi_file ? dbi->dbi_file : db3basename);
+ dbsubfile = (dbi->dbi_subfile ? dbi->dbi_subfile : rpmTagGetName(dbi->dbi_rpmtag));
+#else
+ dbfile = (dbi->dbi_file ? dbi->dbi_file : rpmTagGetName(dbi->dbi_rpmtag));
+ dbsubfile = NULL;
+#endif
+ }
+
+ if (db) {
+ rc = db->close(db, 0);
+ /* XXX ignore not found error messages. */
+ _printit = (rc == ENOENT ? 0 : _debug);
+ rc = cvtdberr(dbi, "db->close", rc, _printit);
+ db = dbi->dbi_db = NULL;
+
+ rpmlog(RPMLOG_DEBUG, "closed db index %s/%s\n",
+ dbhome, (dbfile ? dbfile : rpmTagGetName(dbi->dbi_rpmtag)));
+
+ }
+
+ if (rpmdb->db_dbenv != NULL && dbi->dbi_use_dbenv) {
+ if (rpmdb->db_opens == 1) {
+ xx = db_fini(dbi, (dbhome ? dbhome : ""), dbfile, dbsubfile);
+ rpmdb->db_dbenv = NULL;
+ }
+ rpmdb->db_opens--;
+ }
+
+ if (dbi->dbi_verify_on_close && !dbi->dbi_temporary) {
+ DB_ENV * dbenv = NULL;
+
+ rc = db_env_create(&dbenv, 0);
+ rc = cvtdberr(dbi, "db_env_create", rc, _debug);
+ if (rc || dbenv == NULL) goto exit;
+
+ dbenv->set_errcall(dbenv, (void *) rpmdb->db_errcall);
+ dbenv->set_errfile(dbenv, rpmdb->db_errfile);
+ dbenv->set_errpfx(dbenv, rpmdb->db_errpfx);
+ /* dbenv->set_paniccall(???) */
+#if !(DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
+ xx = dbenv->set_verbose(dbenv, DB_VERB_CHKPOINT,
+ (dbi->dbi_verbose & DB_VERB_CHKPOINT));
+#endif
+ xx = dbenv->set_verbose(dbenv, DB_VERB_DEADLOCK,
+ (dbi->dbi_verbose & DB_VERB_DEADLOCK));
+ xx = dbenv->set_verbose(dbenv, DB_VERB_RECOVERY,
+ (dbi->dbi_verbose & DB_VERB_RECOVERY));
+ xx = dbenv->set_verbose(dbenv, DB_VERB_WAITSFOR,
+ (dbi->dbi_verbose & DB_VERB_WAITSFOR));
+
+ if (dbi->dbi_tmpdir) {
+ char * tmpdir = rpmGenPath(root, dbi->dbi_tmpdir, NULL);
+ rc = dbenv->set_tmp_dir(dbenv, tmpdir);
+ rc = cvtdberr(dbi, "dbenv->set_tmp_dir", rc, _debug);
+ tmpdir = _free(tmpdir);
+ if (rc) goto exit;
+ }
+
+ rc = (dbenv->open)(dbenv, dbhome,
+ DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_USE_ENVIRON, 0);
+ rc = cvtdberr(dbi, "dbenv->open", rc, _debug);
+ if (rc) goto exit;
+
+ rc = db_create(&db, dbenv, 0);
+ rc = cvtdberr(dbi, "db_create", rc, _debug);
+
+ if (db != NULL) {
+ char * dbf = rpmGetPath(dbhome, "/", dbfile, NULL);
+
+ rc = db->verify(db, dbf, NULL, NULL, flags);
+ rc = cvtdberr(dbi, "db->verify", rc, _debug);
+
+ rpmlog(RPMLOG_DEBUG, "verified db index %s/%s\n",
+ (dbhome ? dbhome : ""),
+ (dbfile ? dbfile : rpmTagGetName(dbi->dbi_rpmtag)));
+
+ /*
+ * The DB handle may not be accessed again after
+ * DB->verify is called, regardless of its return.
+ */
+ db = NULL;
+ dbf = _free(dbf);
+ }
+ xx = dbenv->close(dbenv, 0);
+ xx = cvtdberr(dbi, "dbenv->close", xx, _debug);
+ if (rc == 0 && xx) rc = xx;
+ }
+
+exit:
+ dbi->dbi_db = NULL;
+
+ free(dbhome);
+
+ dbi = db3Free(dbi);
+
+ return rc;
+}
+
+static int db3open(rpmdb rpmdb, rpmTag rpmtag, dbiIndex * dbip)
+{
+ extern const struct _dbiVec db3vec;
+ const char * root;
+ const char * home;
+ char * dbhome;
+ const char * dbfile;
+ const char * dbsubfile;
+ dbiIndex dbi = NULL;
+ int rc = 0;
+ int xx;
+
+ DB * db = NULL;
+ DB_ENV * dbenv = NULL;
+ DB_TXN * txnid = NULL;
+ uint32_t oflags;
+ int _printit;
+
+ if (dbip)
+ *dbip = NULL;
+
+ /*
+ * Parse db configuration parameters.
+ */
+ if ((dbi = db3New(rpmdb, rpmtag)) == NULL)
+ return 1;
+ dbi->dbi_api = DB_VERSION_MAJOR;
+
+ /*
+ * Get the prefix/root component and directory path.
+ */
+ root = (dbi->dbi_root ? dbi->dbi_root : rpmdb->db_root);
+ if ((root[0] == '/' && root[1] == '\0') || rpmdb->db_chrootDone)
+ root = NULL;
+ home = (dbi->dbi_home ? dbi->dbi_home : rpmdb->db_home);
+
+ dbhome = rpmGenPath(root, home, NULL);
+ if (dbi->dbi_temporary) {
+ dbfile = NULL;
+ dbsubfile = NULL;
+ } else {
+#ifdef HACK /* XXX necessary to support dbsubfile */
+ dbfile = (dbi->dbi_file ? dbi->dbi_file : db3basename);
+ dbsubfile = (dbi->dbi_subfile ? dbi->dbi_subfile : rpmTagGetName(dbi->dbi_rpmtag));
+#else
+ dbfile = (dbi->dbi_file ? dbi->dbi_file : rpmTagGetName(dbi->dbi_rpmtag));
+ dbsubfile = NULL;
+#endif
+ }
+
+ oflags = (dbi->dbi_oeflags | dbi->dbi_oflags);
+ oflags &= ~DB_TRUNCATE; /* XXX this is dangerous */
+
+#if 0 /* XXX rpmdb: illegal flag combination specified to DB->open */
+ if ( dbi->dbi_mode & O_EXCL) oflags |= DB_EXCL;
+#endif
+
+ /*
+ * Map open mode flags onto configured database/environment flags.
+ */
+ if (dbi->dbi_temporary) {
+ oflags |= DB_CREATE;
+ dbi->dbi_oeflags |= DB_CREATE;
+ oflags &= ~DB_RDONLY;
+ dbi->dbi_oflags &= ~DB_RDONLY;
+ } else {
+ if (!(dbi->dbi_mode & (O_RDWR|O_WRONLY))) oflags |= DB_RDONLY;
+ if (dbi->dbi_mode & O_CREAT) {
+ oflags |= DB_CREATE;
+ dbi->dbi_oeflags |= DB_CREATE;
+ }
+#ifdef DANGEROUS
+ if ( dbi->dbi_mode & O_TRUNC) oflags |= DB_TRUNCATE;
+#endif
+ }
+
+ /*
+ * Create the /var/lib/rpm directory if it doesn't exist (root only).
+ */
+ (void) rpmioMkpath(dbhome, 0755, getuid(), getgid());
+
+ /*
+ * Avoid incompatible DB_CREATE/DB_RDONLY flags on DBENV->open.
+ */
+ if (dbi->dbi_use_dbenv) {
+
+ if (access(dbhome, W_OK) == -1) {
+
+ /* dbhome is unwritable, don't attempt DB_CREATE on DB->open ... */
+ oflags &= ~DB_CREATE;
+
+ /* ... but DBENV->open might still need DB_CREATE ... */
+ if (dbi->dbi_eflags & DB_PRIVATE) {
+ dbi->dbi_eflags &= ~DB_JOINENV;
+ } else {
+ dbi->dbi_eflags |= DB_JOINENV;
+ dbi->dbi_oeflags &= ~DB_CREATE;
+ dbi->dbi_oeflags &= ~DB_THREAD;
+ /* ... but, unless DB_PRIVATE is used, skip DBENV. */
+ dbi->dbi_use_dbenv = 0;
+ }
+
+ /* ... DB_RDONLY maps dbhome perms across files ... */
+ if (dbi->dbi_temporary) {
+ oflags |= DB_CREATE;
+ dbi->dbi_oeflags |= DB_CREATE;
+ oflags &= ~DB_RDONLY;
+ dbi->dbi_oflags &= ~DB_RDONLY;
+ } else {
+ oflags |= DB_RDONLY;
+ /* ... and DB_WRITECURSOR won't be needed ... */
+ dbi->dbi_oflags |= DB_RDONLY;
+ }
+
+ } else { /* dbhome is writable, check for persistent dbenv. */
+ char * dbf = rpmGetPath(dbhome, "/__db.001", NULL);
+
+ if (access(dbf, F_OK) == -1) {
+ /* ... non-existent (or unwritable) DBENV, will create ... */
+ dbi->dbi_oeflags |= DB_CREATE;
+ dbi->dbi_eflags &= ~DB_JOINENV;
+ } else {
+ /* ... pre-existent (or bogus) DBENV, will join ... */
+ if (dbi->dbi_eflags & DB_PRIVATE) {
+ dbi->dbi_eflags &= ~DB_JOINENV;
+ } else {
+ dbi->dbi_eflags |= DB_JOINENV;
+ dbi->dbi_oeflags &= ~DB_CREATE;
+ dbi->dbi_oeflags &= ~DB_THREAD;
+ }
+ }
+ dbf = _free(dbf);
+ }
+ }
+
+ /*
+ * Avoid incompatible DB_CREATE/DB_RDONLY flags on DB->open.
+ */
+ if ((oflags & DB_CREATE) && (oflags & DB_RDONLY)) {
+ /* dbhome is writable, and DB->open flags may conflict. */
+ const char * dbfn = (dbfile ? dbfile : rpmTagGetName(dbi->dbi_rpmtag));
+ char * dbf = rpmGetPath(dbhome, "/", dbfn, NULL);
+
+ if (access(dbf, F_OK) == -1) {
+ /* File does not exist, DB->open might create ... */
+ oflags &= ~DB_RDONLY;
+ } else {
+ /* File exists, DB->open need not create ... */
+ oflags &= ~DB_CREATE;
+ }
+
+ /* Only writers need DB_WRITECURSOR ... */
+ if (!(oflags & DB_RDONLY) && access(dbf, W_OK) == 0) {
+ dbi->dbi_oflags &= ~DB_RDONLY;
+ } else {
+ dbi->dbi_oflags |= DB_RDONLY;
+ }
+ dbf = _free(dbf);
+ }
+
+ /*
+ * Turn off verify-on-close if opening read-only.
+ */
+ if (oflags & DB_RDONLY)
+ dbi->dbi_verify_on_close = 0;
+
+ if (dbi->dbi_use_dbenv) {
+ if (rpmdb->db_dbenv == NULL) {
+ rc = db_init(dbi, dbhome, dbfile, dbsubfile, &dbenv);
+ if (rc == 0) {
+ rpmdb->db_dbenv = dbenv;
+ rpmdb->db_opens = 1;
+ }
+ } else {
+ dbenv = rpmdb->db_dbenv;
+ rpmdb->db_opens++;
+ }
+ }
+
+ { char *dbiflags = prDbiOpenFlags(oflags, 0);
+ rpmlog(RPMLOG_DEBUG, "opening db index %s/%s %s mode=0x%x\n",
+ dbhome, (dbfile ? dbfile : rpmTagGetName(dbi->dbi_rpmtag)),
+ dbiflags, dbi->dbi_mode);
+ free(dbiflags);
+ }
+
+ if (rc == 0) {
+ static int _lockdbfd = 0;
+
+ rc = db_create(&db, dbenv, dbi->dbi_cflags);
+ rc = cvtdberr(dbi, "db_create", rc, _debug);
+ if (rc == 0 && db != NULL) {
+
+ if (rc == 0 &&
+ rpmdb->db_malloc && rpmdb->db_realloc && rpmdb->db_free)
+ {
+ rc = db->set_alloc(db,
+ rpmdb->db_malloc, rpmdb->db_realloc, rpmdb->db_free);
+ rc = cvtdberr(dbi, "db->set_alloc", rc, _debug);
+ }
+
+/* 4.1: db->set_cache_priority(???) */
+ if (rc == 0 && !dbi->dbi_use_dbenv && dbi->dbi_cachesize) {
+ rc = db->set_cachesize(db, 0, dbi->dbi_cachesize, 0);
+ rc = cvtdberr(dbi, "db->set_cachesize", rc, _debug);
+ }
+/* 4.1: db->set_encrypt(???) */
+/* 4.1: db->set_errcall(dbenv, rpmdb->db_errcall); */
+/* 4.1: db->set_errfile(dbenv, rpmdb->db_errfile); */
+/* 4.1: db->set_errpfx(dbenv, rpmdb->db_errpfx); */
+ /* 4.1: db->set_feedback(???) */
+
+ if (rc == 0 && dbi->dbi_lorder) {
+ rc = db->set_lorder(db, dbi->dbi_lorder);
+ rc = cvtdberr(dbi, "db->set_lorder", rc, _debug);
+ }
+ if (rc == 0 && dbi->dbi_pagesize) {
+ rc = db->set_pagesize(db, dbi->dbi_pagesize);
+ rc = cvtdberr(dbi, "db->set_pagesize", rc, _debug);
+ }
+ /* 4.1: db->set_paniccall(???) */
+ if (rc == 0 && oflags & DB_CREATE) {
+ switch(dbi->dbi_type) {
+ default:
+ case DB_HASH:
+ if (dbi->dbi_h_ffactor) {
+ rc = db->set_h_ffactor(db, dbi->dbi_h_ffactor);
+ rc = cvtdberr(dbi, "db->set_h_ffactor", rc, _debug);
+ if (rc) break;
+ }
+ if (dbi->dbi_h_nelem) {
+ rc = db->set_h_nelem(db, dbi->dbi_h_nelem);
+ rc = cvtdberr(dbi, "db->set_h_nelem", rc, _debug);
+ if (rc) break;
+ }
+ if (dbi->dbi_h_flags) {
+ rc = db->set_flags(db, dbi->dbi_h_flags);
+ rc = cvtdberr(dbi, "db->set_h_flags", rc, _debug);
+ if (rc) break;
+ }
+ if (dbi->dbi_h_hash_fcn) {
+ rc = db->set_h_hash(db, dbi->dbi_h_hash_fcn);
+ rc = cvtdberr(dbi, "db->set_h_hash", rc, _debug);
+ if (rc) break;
+ }
+ if (dbi->dbi_h_dup_compare_fcn) {
+ rc = db->set_dup_compare(db, dbi->dbi_h_dup_compare_fcn);
+ rc = cvtdberr(dbi, "db->set_dup_compare", rc, _debug);
+ if (rc) break;
+ }
+ break;
+ case DB_BTREE:
+/* 4.1: db->set_append_recno(???) */
+ if (dbi->dbi_bt_flags) {
+ rc = db->set_flags(db, dbi->dbi_bt_flags);
+ rc = cvtdberr(dbi, "db->set_bt_flags", rc, _debug);
+ if (rc) break;
+ }
+ if (dbi->dbi_bt_minkey) {
+ rc = db->set_bt_minkey(db, dbi->dbi_bt_minkey);
+ rc = cvtdberr(dbi, "db->set_bt_minkey", rc, _debug);
+ if (rc) break;
+ }
+ if (dbi->dbi_bt_compare_fcn) {
+ rc = db->set_bt_compare(db, dbi->dbi_bt_compare_fcn);
+ rc = cvtdberr(dbi, "db->set_bt_compare", rc, _debug);
+ if (rc) break;
+ }
+ if (dbi->dbi_bt_dup_compare_fcn) {
+ rc = db->set_dup_compare(db, dbi->dbi_bt_dup_compare_fcn);
+ rc = cvtdberr(dbi, "db->set_dup_compare", rc, _debug);
+ if (rc) break;
+ }
+ if (dbi->dbi_bt_prefix_fcn) {
+ rc = db->set_bt_prefix(db, dbi->dbi_bt_prefix_fcn);
+ rc = cvtdberr(dbi, "db->set_bt_prefix", rc, _debug);
+ if (rc) break;
+ }
+ break;
+ case DB_RECNO:
+ if (dbi->dbi_re_delim) {
+/* 4.1: db->set_append_recno(???) */
+ rc = db->set_re_delim(db, dbi->dbi_re_delim);
+ rc = cvtdberr(dbi, "db->set_re_selim", rc, _debug);
+ if (rc) break;
+ }
+ if (dbi->dbi_re_len) {
+ rc = db->set_re_len(db, dbi->dbi_re_len);
+ rc = cvtdberr(dbi, "db->set_re_len", rc, _debug);
+ if (rc) break;
+ }
+ if (dbi->dbi_re_pad) {
+ rc = db->set_re_pad(db, dbi->dbi_re_pad);
+ rc = cvtdberr(dbi, "db->set_re_pad", rc, _debug);
+ if (rc) break;
+ }
+ if (dbi->dbi_re_source) {
+ rc = db->set_re_source(db, dbi->dbi_re_source);
+ rc = cvtdberr(dbi, "db->set_re_source", rc, _debug);
+ if (rc) break;
+ }
+ break;
+ case DB_QUEUE:
+ if (dbi->dbi_q_extentsize) {
+ rc = db->set_q_extentsize(db, dbi->dbi_q_extentsize);
+ rc = cvtdberr(dbi, "db->set_q_extentsize", rc, _debug);
+ if (rc) break;
+ }
+ break;
+ }
+ }
+
+ if (rc == 0) {
+ char * fullpath;
+ const char * dbpath;
+ fullpath = rpmGetPath(dbhome, "/", dbfile ? dbfile : "", NULL);
+
+#ifdef HACK /* XXX necessary to support dbsubfile */
+ dbpath = (!dbi->dbi_use_dbenv && !dbi->dbi_temporary)
+ ? fullpath : dbfile;
+#else
+ dbpath = (!dbi->dbi_temporary)
+ ? fullpath : dbfile;
+#endif
+
+ rc = (db->open)(db, txnid, dbpath, dbsubfile,
+ dbi->dbi_type, oflags, dbi->dbi_perms);
+
+ if (rc == 0 && dbi->dbi_type == DB_UNKNOWN) {
+ DBTYPE dbi_type = DB_UNKNOWN;
+ xx = db->get_type(db, &dbi_type);
+ if (xx == 0)
+ dbi->dbi_type = dbi_type;
+ }
+ free(fullpath);
+ }
+
+ /* XXX return rc == errno without printing */
+ _printit = (rc > 0 ? 0 : _debug);
+ xx = cvtdberr(dbi, "db->open", rc, _printit);
+
+ dbi->dbi_txnid = NULL;
+
+ /*
+ * Lock a file using fcntl(2). Traditionally this is Packages,
+ * the file used to store metadata of installed header(s),
+ * as Packages is always opened, and should be opened first,
+ * for any rpmdb access.
+ *
+ * If no DBENV is used, then access is protected with a
+ * shared/exclusive locking scheme, as always.
+ *
+ * With a DBENV, the fcntl(2) lock is necessary only to keep
+ * the riff-raff from playing where they don't belong, as
+ * the DBENV should provide it's own locking scheme. So try to
+ * acquire a lock, but permit failures, as some other
+ * DBENV player may already have acquired the lock.
+ *
+ * With NPTL posix mutexes, revert to fcntl lock on non-functioning
+ * glibc/kernel combinations.
+ */
+ if (rc == 0 && dbi->dbi_lockdbfd &&
+ !((dbi->dbi_ecflags & DB_CLIENT) && dbi->dbi_host) &&
+ (!dbi->dbi_use_dbenv || _lockdbfd++ == 0))
+ {
+ int fdno = -1;
+
+ if (!(db->fd(db, &fdno) == 0 && fdno >= 0)) {
+ rc = 1;
+ } else {
+ struct flock l;
+ memset(&l, 0, sizeof(l));
+ l.l_whence = 0;
+ l.l_start = 0;
+ l.l_len = 0;
+ l.l_type = (dbi->dbi_mode & (O_RDWR|O_WRONLY))
+ ? F_WRLCK : F_RDLCK;
+ l.l_pid = 0;
+
+ rc = fcntl(fdno, F_SETLK, (void *) &l);
+ if (rc) {
+ /* Warning iff using non-private CDB locking. */
+ rc = ((dbi->dbi_use_dbenv &&
+ (dbi->dbi_eflags & DB_INIT_CDB) &&
+ !(dbi->dbi_eflags & DB_PRIVATE))
+ ? 0 : 1);
+ rpmlog( (rc ? RPMLOG_ERR : RPMLOG_WARNING),
+ _("cannot get %s lock on %s/%s\n"),
+ ((dbi->dbi_mode & (O_RDWR|O_WRONLY))
+ ? _("exclusive") : _("shared")),
+ dbhome, (dbfile ? dbfile : ""));
+ } else if (dbfile) {
+ rpmlog(RPMLOG_DEBUG,
+ "locked db index %s/%s\n",
+ dbhome, dbfile);
+ }
+ }
+ }
+ }
+ }
+
+ dbi->dbi_db = db;
+
+ if (rc == 0 && dbi->dbi_db != NULL && dbip != NULL) {
+ dbi->dbi_vec = &db3vec;
+ *dbip = dbi;
+ } else {
+ dbi->dbi_verify_on_close = 0;
+ (void) db3close(dbi, 0);
+ }
+
+ free(dbhome);
+
+ return rc;
+}
+
+/** \ingroup db3
+ */
+const struct _dbiVec db3vec = {
+ DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH,
+ db3open, db3close, db3sync, db3associate, db3join,
+ db3copen, db3cclose, db3cdup, db3cdel, db3cget, db3cpget, db3cput, db3ccount,
+ db3byteswapped, db3stat
+};
diff --git a/lib/backend/dbconfig.c b/lib/backend/dbconfig.c
new file mode 100644
index 000000000..fc75af3bf
--- /dev/null
+++ b/lib/backend/dbconfig.c
@@ -0,0 +1,473 @@
+/** \ingroup rpmdb
+ * \file rpmdb/dbconfig.c
+ */
+
+#include "system.h"
+
+#include <popt.h>
+
+#include <rpm/rpmtag.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmlog.h>
+#include <rpm/argv.h>
+#include "rpmdb/rpmdb_internal.h"
+#include "debug.h"
+
+
+#if (DB_VERSION_MAJOR == 3) || (DB_VERSION_MAJOR == 4)
+#define __USE_DB3 1
+
+struct _dbiIndex db3dbi;
+
+static int dbi_use_cursors;
+
+static int dbi_tear_down;
+
+/** \ingroup db3
+ */
+struct poptOption rdbOptions[] = {
+ /* XXX DB_CXX_NO_EXCEPTIONS */
+#if defined(DB_CLIENT)
+ { "client", 0,POPT_BIT_SET, &db3dbi.dbi_ecflags, DB_CLIENT,
+ NULL, NULL },
+#endif
+#if defined(DB_RPCCLIENT)
+ { "client", 0,POPT_BIT_SET, &db3dbi.dbi_ecflags, DB_RPCCLIENT,
+ NULL, NULL },
+ { "rpcclient", 0,POPT_BIT_SET, &db3dbi.dbi_ecflags, DB_RPCCLIENT,
+ NULL, NULL },
+#endif
+
+ { "xa_create", 0,POPT_BIT_SET, &db3dbi.dbi_cflags, DB_XA_CREATE,
+ NULL, NULL },
+
+ { "create", 0,POPT_BIT_SET, &db3dbi.dbi_oeflags, DB_CREATE,
+ NULL, NULL },
+ { "thread", 0,POPT_BIT_SET, &db3dbi.dbi_oeflags, DB_THREAD,
+ NULL, NULL },
+
+ { "force", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_FORCE,
+ NULL, NULL },
+ { "cdb", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_INIT_CDB,
+ NULL, NULL },
+ { "lock", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_INIT_LOCK,
+ NULL, NULL },
+ { "log", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_INIT_LOG,
+ NULL, NULL },
+ { "mpool", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_INIT_MPOOL,
+ NULL, NULL },
+ { "txn", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_INIT_TXN,
+ NULL, NULL },
+ { "joinenv", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_JOINENV,
+ NULL, NULL },
+ { "recover", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_RECOVER,
+ NULL, NULL },
+ { "recover_fatal", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_RECOVER_FATAL,
+ NULL, NULL },
+ { "shared", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_SYSTEM_MEM,
+ NULL, NULL },
+ { "txn_nosync", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_TXN_NOSYNC,
+ NULL, NULL },
+ { "use_environ_root", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_USE_ENVIRON_ROOT,
+ NULL, NULL },
+ { "use_environ", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_USE_ENVIRON,
+ NULL, NULL },
+ { "lockdown", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_LOCKDOWN,
+ NULL, NULL },
+ { "private", 0,POPT_BIT_SET, &db3dbi.dbi_eflags, DB_PRIVATE,
+ NULL, NULL },
+
+ { "txn_sync", 0,POPT_BIT_SET, &db3dbi.dbi_tflags, DB_TXN_SYNC,
+ NULL, NULL },
+ { "txn_nowait",0,POPT_BIT_SET, &db3dbi.dbi_tflags, DB_TXN_NOWAIT,
+ NULL, NULL },
+
+ { "excl", 0,POPT_BIT_SET, &db3dbi.dbi_oflags, DB_EXCL,
+ NULL, NULL },
+ { "nommap", 0,POPT_BIT_SET, &db3dbi.dbi_oflags, DB_NOMMAP,
+ NULL, NULL },
+ { "rdonly", 0,POPT_BIT_SET, &db3dbi.dbi_oflags, DB_RDONLY,
+ NULL, NULL },
+ { "truncate", 0,POPT_BIT_SET, &db3dbi.dbi_oflags, DB_TRUNCATE,
+ NULL, NULL },
+ { "fcntl_locking",0,POPT_BIT_SET, &db3dbi.dbi_oflags, DB_FCNTL_LOCKING,
+ NULL, NULL },
+
+ { "btree", 0,POPT_ARG_VAL, &db3dbi.dbi_type, DB_BTREE,
+ NULL, NULL },
+ { "hash", 0,POPT_ARG_VAL, &db3dbi.dbi_type, DB_HASH,
+ NULL, NULL },
+ { "recno", 0,POPT_ARG_VAL, &db3dbi.dbi_type, DB_RECNO,
+ NULL, NULL },
+ { "queue", 0,POPT_ARG_VAL, &db3dbi.dbi_type, DB_QUEUE,
+ NULL, NULL },
+ { "unknown", 0,POPT_ARG_VAL, &db3dbi.dbi_type, DB_UNKNOWN,
+ NULL, NULL },
+
+ { "root", 0,POPT_ARG_STRING, &db3dbi.dbi_root, 0,
+ NULL, NULL },
+ { "home", 0,POPT_ARG_STRING, &db3dbi.dbi_home, 0,
+ NULL, NULL },
+ { "file", 0,POPT_ARG_STRING, &db3dbi.dbi_file, 0,
+ NULL, NULL },
+ { "subfile", 0,POPT_ARG_STRING, &db3dbi.dbi_subfile, 0,
+ NULL, NULL },
+ { "mode", 0,POPT_ARG_INT, &db3dbi.dbi_mode, 0,
+ NULL, NULL },
+ { "perms", 0,POPT_ARG_INT, &db3dbi.dbi_perms, 0,
+ NULL, NULL },
+ { "shmkey", 0,POPT_ARG_LONG, &db3dbi.dbi_shmkey, 0,
+ NULL, NULL },
+ { "tmpdir", 0,POPT_ARG_STRING, &db3dbi.dbi_tmpdir, 0,
+ NULL, NULL },
+
+ { "host", 0,POPT_ARG_STRING, &db3dbi.dbi_host, 0,
+ NULL, NULL },
+ { "server", 0,POPT_ARG_STRING, &db3dbi.dbi_host, 0,
+ NULL, NULL },
+ { "cl_timeout", 0,POPT_ARG_LONG, &db3dbi.dbi_cl_timeout, 0,
+ NULL, NULL },
+ { "sv_timeout", 0,POPT_ARG_LONG, &db3dbi.dbi_sv_timeout, 0,
+ NULL, NULL },
+
+ { "verify", 0,POPT_ARG_NONE, &db3dbi.dbi_verify_on_close, 0,
+ NULL, NULL },
+ { "teardown", 0,POPT_ARG_NONE, &dbi_tear_down, 0,
+ NULL, NULL },
+ { "usecursors",0,POPT_ARG_NONE, &dbi_use_cursors, 0,
+ NULL, NULL },
+ { "usedbenv", 0,POPT_ARG_NONE, &db3dbi.dbi_use_dbenv, 0,
+ NULL, NULL },
+ { "nofsync", 0,POPT_ARG_NONE, &db3dbi.dbi_no_fsync, 0,
+ NULL, NULL },
+ { "nodbsync", 0,POPT_ARG_NONE, &db3dbi.dbi_no_dbsync, 0,
+ NULL, NULL },
+ { "lockdbfd", 0,POPT_ARG_NONE, &db3dbi.dbi_lockdbfd, 0,
+ NULL, NULL },
+ { "temporary", 0,POPT_ARG_NONE, &db3dbi.dbi_temporary, 0,
+ NULL, NULL },
+ { "debug", 0,POPT_ARG_NONE, &db3dbi.dbi_debug, 0,
+ NULL, NULL },
+
+ { "cachesize", 0,POPT_ARG_INT, &db3dbi.dbi_cachesize, 0,
+ NULL, NULL },
+ { "errpfx", 0,POPT_ARG_STRING, &db3dbi.dbi_errpfx, 0,
+ NULL, NULL },
+ { "region_init", 0,POPT_ARG_VAL, &db3dbi.dbi_region_init, 1,
+ NULL, NULL },
+ { "tas_spins", 0,POPT_ARG_INT, &db3dbi.dbi_tas_spins, 0,
+ NULL, NULL },
+
+#if defined(DB_VERB_CHKPOINT)
+ { "chkpoint", 0,POPT_BIT_SET, &db3dbi.dbi_verbose, DB_VERB_CHKPOINT,
+ NULL, NULL },
+#endif
+ { "deadlock", 0,POPT_BIT_SET, &db3dbi.dbi_verbose, DB_VERB_DEADLOCK,
+ NULL, NULL },
+ { "recovery", 0,POPT_BIT_SET, &db3dbi.dbi_verbose, DB_VERB_RECOVERY,
+ NULL, NULL },
+ { "waitsfor", 0,POPT_BIT_SET, &db3dbi.dbi_verbose, DB_VERB_WAITSFOR,
+ NULL, NULL },
+ { "verbose", 0,POPT_ARG_VAL, &db3dbi.dbi_verbose, -1,
+ NULL, NULL },
+
+ { "lk_oldest", 0,POPT_ARG_VAL, &db3dbi.dbi_lk_detect, DB_LOCK_OLDEST,
+ NULL, NULL },
+ { "lk_random", 0,POPT_ARG_VAL, &db3dbi.dbi_lk_detect, DB_LOCK_RANDOM,
+ NULL, NULL },
+ { "lk_youngest",0, POPT_ARG_VAL, &db3dbi.dbi_lk_detect, DB_LOCK_YOUNGEST,
+ NULL, NULL },
+/* XXX lk_conflicts matrix */
+ { "lk_max", 0,POPT_ARG_INT, &db3dbi.dbi_lk_max, 0,
+ NULL, NULL },
+
+ { "lg_bsize", 0,POPT_ARG_INT, &db3dbi.dbi_lg_bsize, 0,
+ NULL, NULL },
+ { "lg_max", 0,POPT_ARG_INT, &db3dbi.dbi_lg_max, 0,
+ NULL, NULL },
+
+/* XXX tx_recover */
+ { "tx_max", 0,POPT_ARG_INT, &db3dbi.dbi_tx_max, 0,
+ NULL, NULL },
+
+ { "lorder", 0,POPT_ARG_INT, &db3dbi.dbi_lorder, 0,
+ NULL, NULL },
+
+ { "mmapsize", 0,POPT_ARG_INT, &db3dbi.dbi_mmapsize, 0,
+ NULL, NULL },
+ { "mp_mmapsize", 0,POPT_ARG_INT, &db3dbi.dbi_mmapsize, 0,
+ NULL, NULL },
+ { "mp_size", 0,POPT_ARG_INT, &db3dbi.dbi_cachesize, 0,
+ NULL, NULL },
+ { "pagesize", 0,POPT_ARG_INT, &db3dbi.dbi_pagesize, 0,
+ NULL, NULL },
+
+/* XXX bt_minkey */
+/* XXX bt_compare */
+/* XXX bt_dup_compare */
+/* XXX bt_prefix */
+ { "bt_dup", 0,POPT_BIT_SET, &db3dbi.dbi_bt_flags, DB_DUP,
+ NULL, NULL },
+ { "bt_dupsort",0,POPT_BIT_SET, &db3dbi.dbi_bt_flags, DB_DUPSORT,
+ NULL, NULL },
+ { "bt_recnum", 0,POPT_BIT_SET, &db3dbi.dbi_bt_flags, DB_RECNUM,
+ NULL, NULL },
+ { "bt_revsplitoff", 0,POPT_BIT_SET, &db3dbi.dbi_bt_flags, DB_REVSPLITOFF,
+ NULL, NULL },
+
+ { "h_dup", 0,POPT_BIT_SET, &db3dbi.dbi_h_flags, DB_DUP,
+ NULL, NULL },
+ { "h_dupsort", 0,POPT_BIT_SET, &db3dbi.dbi_h_flags, DB_DUPSORT,
+ NULL, NULL },
+ { "h_ffactor", 0,POPT_ARG_INT, &db3dbi.dbi_h_ffactor, 0,
+ NULL, NULL },
+ { "h_nelem", 0,POPT_ARG_INT, &db3dbi.dbi_h_nelem, 0,
+ NULL, NULL },
+
+ { "re_renumber", 0,POPT_BIT_SET, &db3dbi.dbi_re_flags, DB_RENUMBER,
+ NULL, NULL },
+ { "re_snapshot",0,POPT_BIT_SET, &db3dbi.dbi_re_flags, DB_SNAPSHOT,
+ NULL, NULL },
+ { "re_delim", 0,POPT_ARG_INT, &db3dbi.dbi_re_delim, 0,
+ NULL, NULL },
+ { "re_len", 0,POPT_ARG_INT, &db3dbi.dbi_re_len, 0,
+ NULL, NULL },
+ { "re_pad", 0,POPT_ARG_INT, &db3dbi.dbi_re_pad, 0,
+ NULL, NULL },
+ { "re_source", 0,POPT_ARG_STRING, &db3dbi.dbi_re_source, 0,
+ NULL, NULL },
+
+ { "q_extentsize", 0,POPT_ARG_INT, &db3dbi.dbi_q_extentsize, 0,
+ NULL, NULL },
+
+ POPT_TABLEEND
+};
+
+dbiIndex db3Free(dbiIndex dbi)
+{
+ if (dbi) {
+ dbi->dbi_root = _free(dbi->dbi_root);
+ dbi->dbi_home = _free(dbi->dbi_home);
+ dbi->dbi_file = _free(dbi->dbi_file);
+ dbi->dbi_subfile = _free(dbi->dbi_subfile);
+ dbi->dbi_tmpdir = _free(dbi->dbi_tmpdir);
+ dbi->dbi_host = _free(dbi->dbi_host);
+ dbi->dbi_errpfx = _free(dbi->dbi_errpfx);
+ dbi->dbi_re_source = _free(dbi->dbi_re_source);
+ dbi->dbi_stats = _free(dbi->dbi_stats);
+ dbi = _free(dbi);
+ }
+ return dbi;
+}
+
+/** @todo Set a reasonable "last gasp" default db config. */
+static const char * const db3_config_default =
+ "db3:hash:mpool:cdb:usecursors:verbose:mp_mmapsize=8Mb:cachesize=512Kb:pagesize=512:perms=0644";
+
+dbiIndex db3New(rpmdb rpmdb, rpmTag rpmtag)
+{
+ dbiIndex dbi = xcalloc(1, sizeof(*dbi));
+ 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 = rpmExpand(db3_config_default, NULL);
+ }
+ }
+
+ /* Parse the options for the database element(s). */
+ if (dbOpts && *dbOpts && *dbOpts != '%') {
+ char *o, *oe;
+ char *p, *pe;
+
+ memset(&db3dbi, 0, sizeof(db3dbi));
+/*=========*/
+ for (o = dbOpts; o && *o; o = oe) {
+ 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 (strcmp(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);
+
+ *dbi = db3dbi; /* structure assignment */
+ memset(&db3dbi, 0, sizeof(db3dbi));
+
+ if (!(dbi->dbi_perms & 0600))
+ dbi->dbi_perms = 0644;
+ dbi->dbi_mode = rpmdb->db_mode;
+ /* FIX: figger rpmdb/dbi refcounts */
+ dbi->dbi_rpmdb = rpmdb;
+ dbi->dbi_rpmtag = rpmtag;
+
+ /*
+ * Inverted lists have join length of 2, primary data has join length of 1.
+ */
+ switch (rpmtag) {
+ case RPMDBI_PACKAGES:
+ case RPMDBI_DEPENDS:
+ dbi->dbi_jlen = 1 * sizeof(int32_t);
+ break;
+ default:
+ dbi->dbi_jlen = 2 * sizeof(int32_t);
+ break;
+ }
+
+ dbi->dbi_byteswapped = -1; /* -1 unknown, 0 native order, 1 alien order */
+
+ if (!dbi->dbi_use_dbenv) { /* db3 dbenv is always used now. */
+ dbi->dbi_use_dbenv = 1;
+ dbi->dbi_eflags |= (DB_INIT_MPOOL|DB_JOINENV);
+ dbi->dbi_mmapsize = 16 * 1024 * 1024;
+ dbi->dbi_cachesize = 1 * 1024 * 1024;
+ }
+
+ if ((dbi->dbi_bt_flags | dbi->dbi_h_flags) & DB_DUP)
+ dbi->dbi_permit_dups = 1;
+
+ /* FIX: *(rdbOptions->arg) reachable */
+ return dbi;
+}
+
+char * prDbiOpenFlags(int dbflags, int print_dbenv_flags)
+{
+ ARGV_t flags = NULL;
+ 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 == &db3dbi.dbi_oeflags ||
+ opt->arg == &db3dbi.dbi_eflags))
+ continue;
+ } else {
+ if (!(opt->arg == &db3dbi.dbi_oeflags ||
+ opt->arg == &db3dbi.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;
+}
+
+#endif
diff --git a/lib/backend/sqlite.c b/lib/backend/sqlite.c
new file mode 100644
index 000000000..e290a5b25
--- /dev/null
+++ b/lib/backend/sqlite.c
@@ -0,0 +1,1386 @@
+
+/*
+ * sqlite.c
+ * sqlite interface for rpmdb
+ *
+ * Author: Mark Hatle <mhatle@mvista.com> or <fray@kernel.crashing.org>
+ * Copyright (c) 2004 MontaVista Software, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * or GNU Library General Public License, at your option,
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * and GNU Library Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "system.h"
+
+#include <sqlite3.h>
+
+#include <rpm/rpmtag.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmfileutil.h> /* rpmioMkpath */
+#include <rpm/rpmstring.h>
+#include <rpm/rpmdb.h>
+
+#include "rpmdb/rpmdb_internal.h"
+
+#include "debug.h"
+
+
+static int _debug = 0;
+
+/* Define the things normally in a header... */
+struct _sql_db_s; typedef struct _sql_db_s SQL_DB;
+struct _sql_dbcursor_s; typedef struct _sql_dbcursor_s *SCP_t;
+
+struct _sql_db_s {
+ sqlite3 * db; /* Database pointer */
+ int transaction; /* Do we have a transaction open? */
+};
+
+struct _sql_dbcursor_s {
+ DB *dbp;
+
+ char * cmd; /* SQL command string */
+ sqlite3_stmt *pStmt; /* SQL byte code */
+ const char * pzErrmsg; /* SQL error msg */
+
+ /* Table -- result of query */
+ char ** av; /* item ptrs */
+ int * avlen; /* item sizes */
+ int nalloc;
+ int ac; /* no. of items */
+ int rx; /* Which row are we on? 1, 2, 3 ... */
+ int nr; /* no. of rows */
+ int nc; /* no. of columns */
+
+ int all; /* sequential iteration cursor */
+ DBT ** keys; /* array of package keys */
+ int nkeys;
+
+ int count;
+
+ void * lkey; /* Last key returned */
+ void * ldata; /* Last data returned */
+
+ int used;
+};
+
+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; \
+ }
+
+static const unsigned int endian = 0x11223344;
+
+static char * sqlCwd = NULL;
+static int sqlInRoot = 0;
+
+static void enterChroot(dbiIndex dbi)
+{
+ int xx;
+
+ if ((dbi->dbi_root[0] == '/' && dbi->dbi_root[1] == '\0') || dbi->dbi_rpmdb->db_chrootDone || sqlInRoot)
+ /* Nothing to do, was not already in chroot */
+ return;
+
+if (_debug)
+fprintf(stderr, "sql:chroot(%s)\n", dbi->dbi_root);
+
+ sqlCwd = rpmGetCwd();
+ xx = chdir("/");
+ xx = chroot(dbi->dbi_root);
+assert(xx == 0);
+ sqlInRoot=1;
+}
+
+static void leaveChroot(dbiIndex dbi)
+{
+ int xx;
+
+ if ((dbi->dbi_root[0] == '/' && dbi->dbi_root[1] == '\0') || dbi->dbi_rpmdb->db_chrootDone || !sqlInRoot)
+ /* Nothing to do, not in chroot */
+ return;
+
+if (_debug)
+fprintf(stderr, "sql:chroot(.)\n");
+
+ xx = chroot(".");
+assert(xx == 0);
+ xx = chdir(sqlCwd);
+ sqlCwd = _free(sqlCwd);
+
+ sqlInRoot=0;
+}
+
+static void dbg_scp(void *ptr)
+{
+ SCP_t scp = ptr;
+
+if (_debug)
+fprintf(stderr, "\tscp %p [%d:%d] av %p avlen %p nr [%d:%d] nc %d all %d\n", scp, scp->ac, scp->nalloc, scp->av, scp->avlen, scp->rx, scp->nr, scp->nc, scp->all);
+
+}
+
+static void dbg_keyval(const char * msg, dbiIndex dbi, DBC * dbcursor,
+ DBT * key, DBT * data, unsigned int flags)
+{
+
+if (!_debug) return;
+
+ fprintf(stderr, "%s on %s (%p,%p,%p,0x%x)", msg, dbi->dbi_subfile, dbcursor, key, data, flags);
+
+ /* XXX FIXME: ptr alignment is fubar here. */
+ if (key != NULL && key->data != NULL) {
+ fprintf(stderr, " key 0x%x[%d]", *(unsigned int *)key->data, key->size);
+ if (dbi->dbi_rpmtag == RPMTAG_NAME)
+ fprintf(stderr, " \"%s\"", (const char *)key->data);
+ }
+ if (data != NULL && data->data != NULL)
+ fprintf(stderr, " data 0x%x[%d]", *(unsigned int *)data->data, data->size);
+
+ fprintf(stderr, "\n");
+ dbg_scp(dbcursor);
+}
+
+
+static SCP_t scpResetKeys(SCP_t scp)
+{
+ int ix;
+
+if (_debug)
+fprintf(stderr, "*** %s(%p)\n", __FUNCTION__, scp);
+dbg_scp(scp);
+
+ for ( ix =0 ; ix < scp->nkeys ; ix++ ) {
+ scp->keys[ix]->data = _free(scp->keys[ix]->data);
+ scp->keys[ix] = _free(scp->keys[ix]);
+ }
+ scp->keys = _free(scp->keys);
+ scp->nkeys = 0;
+
+ return scp;
+}
+
+
+static SCP_t scpResetAv(SCP_t scp)
+{
+ int xx;
+
+if (_debug)
+fprintf(stderr, "*** %s(%p)\n", __FUNCTION__, scp);
+dbg_scp(scp);
+
+ if (scp->av) {
+ if (scp->nalloc <= 0) {
+ /* Clean up SCP_t used by sqlite3_get_table(). */
+ sqlite3_free_table(scp->av);
+ scp->av = NULL;
+ scp->nalloc = 0;
+ } else {
+ /* Clean up SCP_t used by sql_step(). */
+ for (xx = 0; xx < scp->ac; xx++)
+ scp->av[xx] = _free(scp->av[xx]);
+ if (scp->av != NULL)
+ memset(scp->av, 0, scp->nalloc * sizeof(*scp->av));
+ if (scp->avlen != NULL)
+ memset(scp->avlen, 0, scp->nalloc * sizeof(*scp->avlen));
+ scp->av = _free(scp->av);
+ scp->avlen = _free(scp->avlen);
+ scp->nalloc = 0;
+ }
+ } else
+ scp->nalloc = 0;
+ scp->ac = 0;
+ scp->nr = 0;
+ scp->nc = 0;
+
+ return scp;
+}
+
+
+static SCP_t scpReset(SCP_t scp)
+{
+ int xx;
+
+if (_debug)
+fprintf(stderr, "*** %s(%p)\n", __FUNCTION__, scp);
+dbg_scp(scp);
+
+ if (scp->cmd) {
+ sqlite3_free(scp->cmd);
+ scp->cmd = NULL;
+ }
+ if (scp->pStmt) {
+ xx = sqlite3_reset(scp->pStmt);
+ if (xx) rpmlog(RPMLOG_DEBUG, "reset %d\n", xx);
+ xx = sqlite3_finalize(scp->pStmt);
+ if (xx) rpmlog(RPMLOG_DEBUG, "finalize %d\n", xx);
+ scp->pStmt = NULL;
+ }
+
+ scp = scpResetAv(scp);
+
+ scp->rx = 0;
+ return scp;
+}
+
+static SCP_t scpFree(SCP_t scp)
+{
+ scp = scpReset(scp);
+ scp = scpResetKeys(scp);
+ scp->av = _free(scp->av);
+ scp->avlen = _free(scp->avlen);
+
+if (_debug)
+fprintf(stderr, "*** %s(%p)\n", __FUNCTION__, scp);
+ scp = _free(scp);
+ return NULL;
+}
+
+static SCP_t scpNew(DB * dbp)
+{
+ SCP_t scp = xcalloc(1, sizeof(*scp));
+ scp->dbp = dbp;
+
+ scp->used = 0;
+
+ scp->lkey = NULL;
+ scp->ldata = NULL;
+
+if (_debug)
+fprintf(stderr, "*** %s(%p)\n", __FUNCTION__, scp);
+ return scp;
+}
+
+static int sql_step(dbiIndex dbi, SCP_t scp)
+{
+ const char * cname;
+ const char * vtype;
+ size_t nb;
+ int loop;
+ int need;
+ int rc;
+ int i;
+
+ scp->nc = sqlite3_column_count(scp->pStmt);
+
+ if (scp->nr == 0 && scp->av != NULL)
+ need = 2 * scp->nc;
+ else
+ need = scp->nc;
+
+ /* XXX scp->nc = need = scp->nalloc = 0 case forces + 1 here */
+ if (!scp->ac && !need && !scp->nalloc)
+ need++;
+
+ if (scp->ac + need >= scp->nalloc) {
+ /* XXX +4 is bogus, was +1 */
+ scp->nalloc = 2 * scp->nalloc + need + 4;
+ scp->av = xrealloc(scp->av, scp->nalloc * sizeof(*scp->av));
+ scp->avlen = xrealloc(scp->avlen, scp->nalloc * sizeof(*scp->avlen));
+ }
+
+ if (scp->nr == 0) {
+ for (i = 0; i < scp->nc; i++) {
+ scp->av[scp->ac] = xstrdup(sqlite3_column_name(scp->pStmt, i));
+ if (scp->avlen) scp->avlen[scp->ac] = strlen(scp->av[scp->ac]) + 1;
+ scp->ac++;
+assert(scp->ac <= scp->nalloc);
+ }
+ }
+
+ loop = 1;
+ while (loop) {
+ rc = sqlite3_step(scp->pStmt);
+ switch (rc) {
+ case SQLITE_DONE:
+if (_debug)
+fprintf(stderr, "sqlite3_step: DONE scp %p [%d:%d] av %p avlen %p\n", scp, scp->ac, scp->nalloc, scp->av, scp->avlen);
+ loop = 0;
+ break;
+ case SQLITE_ROW:
+ if (scp->av != NULL)
+ for (i = 0; i < scp->nc; i++) {
+ /* Expand the row array for new elements */
+ if (scp->ac + need >= scp->nalloc) {
+ /* XXX +4 is bogus, was +1 */
+ scp->nalloc = 2 * scp->nalloc + need + 4;
+ scp->av = xrealloc(scp->av, scp->nalloc * sizeof(*scp->av));
+ scp->avlen = xrealloc(scp->avlen, scp->nalloc * sizeof(*scp->avlen));
+ }
+
+ cname = sqlite3_column_name(scp->pStmt, i);
+ vtype = sqlite3_column_decltype(scp->pStmt, i);
+ nb = 0;
+
+ if (!strcmp(vtype, "blob")) {
+ const void * v = sqlite3_column_blob(scp->pStmt, i);
+ nb = sqlite3_column_bytes(scp->pStmt, i);
+if (_debug)
+fprintf(stderr, "\t%d %s %s %p[%zd]\n", i, cname, vtype, v, nb);
+ if (nb > 0) {
+ void * t = xmalloc(nb);
+ scp->av[scp->ac] = memcpy(t, v, nb);
+ scp->avlen[scp->ac] = nb;
+ scp->ac++;
+ }
+ } else
+ if (!strcmp(vtype, "double")) {
+ double v = sqlite3_column_double(scp->pStmt, i);
+ nb = sizeof(v);
+if (_debug)
+fprintf(stderr, "\t%d %s %s %g\n", i, cname, vtype, v);
+ if (nb > 0) {
+ scp->av[scp->ac] = memcpy(xmalloc(nb), &v, nb);
+ scp->avlen[scp->ac] = nb;
+assert(dbiByteSwapped(dbi) == 0); /* Byte swap?! */
+ scp->ac++;
+ }
+ } else
+ if (!strcmp(vtype, "int")) {
+ int32_t v = sqlite3_column_int(scp->pStmt, i);
+ nb = sizeof(v);
+if (_debug)
+fprintf(stderr, "\t%d %s %s %d\n", i, cname, vtype, v);
+ if (nb > 0) {
+ scp->av[scp->ac] = memcpy(xmalloc(nb), &v, nb);
+ scp->avlen[scp->ac] = nb;
+if (dbiByteSwapped(dbi) == 1)
+{
+ union _dbswap dbswap;
+ memcpy(&dbswap.ui, scp->av[scp->ac], sizeof(dbswap.ui));
+ _DBSWAP(dbswap);
+ memcpy(scp->av[scp->ac], &dbswap.ui, sizeof(dbswap.ui));
+}
+ scp->ac++;
+ }
+ } else
+ if (!strcmp(vtype, "int64")) {
+ int64_t v = sqlite3_column_int64(scp->pStmt, i);
+ nb = sizeof(v);
+if (_debug)
+fprintf(stderr, "\t%d %s %s %ld\n", i, cname, vtype, (long)v);
+ if (nb > 0) {
+ scp->av[scp->ac] = memcpy(xmalloc(nb), &v, nb);
+ scp->avlen[scp->ac] = nb;
+assert(dbiByteSwapped(dbi) == 0); /* Byte swap?! */
+ scp->ac++;
+ }
+ } else
+ if (!strcmp(vtype, "text")) {
+ const char * v = (const char *) sqlite3_column_text(scp->pStmt, i);
+ nb = strlen(v) + 1;
+if (_debug)
+fprintf(stderr, "\t%d %s %s \"%s\"\n", i, cname, vtype, v);
+ if (nb > 0) {
+ scp->av[scp->ac] = memcpy(xmalloc(nb), v, nb);
+ scp->avlen[scp->ac] = nb;
+ scp->ac++;
+ }
+ }
+assert(scp->ac <= scp->nalloc);
+ }
+ scp->nr++;
+ break;
+ case SQLITE_BUSY:
+ fprintf(stderr, "sqlite3_step: BUSY %d\n", rc);
+ break;
+ case SQLITE_ERROR: {
+ char *cwd = rpmGetCwd();
+ fprintf(stderr, "sqlite3_step: ERROR %d -- %s\n", rc, scp->cmd);
+ fprintf(stderr, " %s (%d)\n",
+ sqlite3_errmsg(((SQL_DB*)dbi->dbi_db)->db), sqlite3_errcode(((SQL_DB*)dbi->dbi_db)->db));
+
+ fprintf(stderr, " cwd '%s'\n", cwd);
+ free(cwd);
+ loop = 0;
+ }
+ break;
+ case SQLITE_MISUSE:
+ fprintf(stderr, "sqlite3_step: MISUSE %d\n", rc);
+ loop = 0;
+ break;
+ default:
+ fprintf(stderr, "sqlite3_step: rc %d\n", rc);
+ loop = 0;
+ break;
+ }
+ }
+
+ if (rc == SQLITE_DONE)
+ rc = SQLITE_OK;
+
+ return rc;
+}
+
+static int sql_bind_key(dbiIndex dbi, SCP_t scp, int pos, DBT * key)
+{
+ int rc = 0;
+
+ union _dbswap dbswap;
+
+assert(key->data != NULL);
+ switch (dbi->dbi_rpmtag) {
+ case RPMDBI_PACKAGES:
+ { unsigned int hnum;
+assert(key->size == sizeof(int32_t));
+ memcpy(&hnum, key->data, sizeof(hnum));
+
+if (dbiByteSwapped(dbi) == 1)
+{
+ memcpy(&dbswap.ui, &hnum, sizeof(dbswap.ui));
+ _DBSWAP(dbswap);
+ memcpy(&hnum, &dbswap.ui, sizeof(dbswap.ui));
+}
+ rc = sqlite3_bind_int(scp->pStmt, pos, hnum);
+ } break;
+ default:
+ switch (rpmTagGetType(dbi->dbi_rpmtag)) {
+ case RPM_NULL_TYPE:
+ case RPM_BIN_TYPE:
+ rc = sqlite3_bind_blob(scp->pStmt, pos, key->data, key->size, SQLITE_STATIC);
+ break;
+ case RPM_CHAR_TYPE:
+ case RPM_INT8_TYPE:
+ { unsigned char i;
+assert(key->size == sizeof(unsigned char));
+assert(dbiByteSwapped(dbi) == 0); /* Byte swap?! */
+ memcpy(&i, key->data, sizeof(i));
+ rc = sqlite3_bind_int(scp->pStmt, pos, i);
+ } break;
+ case RPM_INT16_TYPE:
+ { unsigned short i;
+assert(key->size == sizeof(int16_t));
+assert(dbiByteSwapped(dbi) == 0); /* Byte swap?! */
+ memcpy(&i, key->data, sizeof(i));
+ rc = sqlite3_bind_int(scp->pStmt, pos, i);
+ } break;
+ case RPM_INT32_TYPE:
+/* case RPM_INT64_TYPE: */
+ default:
+ { unsigned int i;
+assert(key->size == sizeof(int32_t));
+ memcpy(&i, key->data, sizeof(i));
+
+if (dbiByteSwapped(dbi) == 1)
+{
+ memcpy(&dbswap.ui, &i, sizeof(dbswap.ui));
+ _DBSWAP(dbswap);
+ memcpy(&i, &dbswap.ui, sizeof(dbswap.ui));
+}
+ rc = sqlite3_bind_int(scp->pStmt, pos, i);
+ } break;
+ case RPM_STRING_TYPE:
+ case RPM_STRING_ARRAY_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ rc = sqlite3_bind_text(scp->pStmt, pos, key->data, key->size, SQLITE_STATIC);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+static int sql_bind_data(dbiIndex dbi, SCP_t scp, int pos, DBT * data)
+{
+ int rc;
+
+assert(data->data != NULL);
+ rc = sqlite3_bind_blob(scp->pStmt, pos, data->data, data->size, SQLITE_STATIC);
+
+ return rc;
+}
+
+/*===================================================================*/
+/*
+ * Transaction support
+ */
+
+static int sql_startTransaction(dbiIndex dbi)
+{
+ SQL_DB * sqldb = (SQL_DB *) dbi->dbi_db;
+ int rc = 0;
+
+ /* XXX: Transaction Support */
+ if (!sqldb->transaction) {
+ char * pzErrmsg;
+ rc = sqlite3_exec(sqldb->db, "BEGIN TRANSACTION;", NULL, NULL, &pzErrmsg);
+
+if (_debug)
+fprintf(stderr, "Begin %s SQL transaction %s (%d)\n",
+ dbi->dbi_subfile, pzErrmsg, rc);
+
+ if (rc == 0)
+ sqldb->transaction = 1;
+ }
+
+ return rc;
+}
+
+static int sql_endTransaction(dbiIndex dbi)
+{
+ SQL_DB * sqldb = (SQL_DB *) dbi->dbi_db;
+ int rc=0;
+
+ /* XXX: Transaction Support */
+ if (sqldb->transaction) {
+ char * pzErrmsg;
+ rc = sqlite3_exec(sqldb->db, "END TRANSACTION;", NULL, NULL, &pzErrmsg);
+
+if (_debug)
+fprintf(stderr, "End %s SQL transaction %s (%d)\n",
+ dbi->dbi_subfile, pzErrmsg, rc);
+
+ if (rc == 0)
+ sqldb->transaction = 0;
+ }
+
+ return rc;
+}
+
+static int sql_commitTransaction(dbiIndex dbi, int flag)
+{
+ SQL_DB * sqldb = (SQL_DB *) dbi->dbi_db;
+ int rc = 0;
+
+ /* XXX: Transactions */
+ if ( sqldb->transaction ) {
+ char * pzErrmsg;
+ rc = sqlite3_exec(sqldb->db, "COMMIT;", NULL, NULL, &pzErrmsg);
+
+if (_debug)
+fprintf(stderr, "Commit %s SQL transaction(s) %s (%d)\n",
+ dbi->dbi_subfile, pzErrmsg, rc);
+
+ sqldb->transaction=0;
+
+ /* Start a new transaction if we were in the middle of one */
+ if ( flag == 0 )
+ rc = sql_startTransaction(dbi);
+ }
+
+ return rc;
+}
+
+static int sql_busy_handler(void * dbi_void, int time)
+{
+ dbiIndex dbi = (dbiIndex) dbi_void;
+
+ rpmlog(RPMLOG_WARNING, _("Unable to get lock on db %s, retrying... (%d)\n"),
+ dbi->dbi_file, time);
+
+ (void) sleep(1);
+
+ return 1;
+}
+
+/*===================================================================*/
+
+/**
+ * Verify the DB is setup.. if not initialize it
+ *
+ * Create the table.. create the db_info
+ */
+static int sql_initDB(dbiIndex dbi)
+{
+ SQL_DB * sqldb = (SQL_DB *) dbi->dbi_db;
+ SCP_t scp = scpNew(dbi->dbi_db);
+ char *cmd = NULL;
+ int rc = 0;
+
+ /* Check if the table exists... */
+ rasprintf(&cmd,
+ "SELECT name FROM 'sqlite_master' WHERE type='table' and name='%s';",
+ dbi->dbi_subfile);
+ rc = sqlite3_get_table(sqldb->db, cmd,
+ &scp->av, &scp->nr, &scp->nc, (char **)&scp->pzErrmsg);
+ cmd = _free(cmd);
+ if (rc)
+ goto exit;
+
+ if (scp->nr < 1) {
+ const char * valtype = "blob";
+ const char * keytype;
+
+ switch (dbi->dbi_rpmtag) {
+ case RPMDBI_PACKAGES:
+ keytype = "int UNIQUE PRIMARY KEY";
+ valtype = "blob";
+ break;
+ default:
+ switch (rpmTagGetType(dbi->dbi_rpmtag)) {
+ case RPM_NULL_TYPE:
+ case RPM_BIN_TYPE:
+ default:
+ keytype = "blob UNIQUE";
+ break;
+ case RPM_CHAR_TYPE:
+ case RPM_INT8_TYPE:
+ case RPM_INT16_TYPE:
+ case RPM_INT32_TYPE:
+/* case RPM_INT64_TYPE: */
+ keytype = "int UNIQUE";
+ break;
+ case RPM_STRING_TYPE:
+ case RPM_STRING_ARRAY_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ keytype = "text UNIQUE";
+ break;
+ }
+ }
+if (_debug)
+fprintf(stderr, "\t%s(%d) type(%d) keytype %s\n", rpmTagGetName(dbi->dbi_rpmtag), dbi->dbi_rpmtag, rpmTagGetType(dbi->dbi_rpmtag), keytype);
+ rasprintf(&cmd, "CREATE TABLE '%s' (key %s, value %s)",
+ dbi->dbi_subfile, keytype, valtype);
+ rc = sqlite3_exec(sqldb->db, cmd, NULL, NULL, (char **)&scp->pzErrmsg);
+ cmd = _free(cmd);
+ if (rc)
+ goto exit;
+
+ rasprintf(&cmd, "CREATE TABLE 'db_info' (endian TEXT)");
+ rc = sqlite3_exec(sqldb->db, cmd, NULL, NULL, (char **)&scp->pzErrmsg);
+ cmd = _free(cmd);
+ if (rc)
+ goto exit;
+
+ rasprintf(&cmd, "INSERT INTO 'db_info' values('%d')", ((union _dbswap *)&endian)->uc[0]);
+ rc = sqlite3_exec(sqldb->db, cmd, NULL, NULL, (char **)&scp->pzErrmsg);
+ _free(cmd);
+ if (rc)
+ goto exit;
+ }
+
+ if (dbi->dbi_no_fsync) {
+ int xx;
+ rasprintf(&cmd, "PRAGMA synchronous = OFF;");
+ xx = sqlite3_exec(sqldb->db, cmd, NULL, NULL, (char **)&scp->pzErrmsg);
+ cmd = _free(cmd);
+ }
+
+exit:
+ if (rc)
+ rpmlog(RPMLOG_WARNING, _("Unable to initDB %s (%d)\n"),
+ scp->pzErrmsg, rc);
+
+ scp = scpFree(scp);
+
+ return rc;
+}
+
+/**
+ * Close database cursor.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_cclose (dbiIndex dbi, DBC * dbcursor,
+ unsigned int flags)
+{
+ SCP_t scp = (SCP_t)dbcursor;
+ int rc;
+
+if (_debug)
+fprintf(stderr, "==> %s(%p)\n", __FUNCTION__, scp);
+
+ if (scp->lkey)
+ scp->lkey = _free(scp->lkey);
+
+ if (scp->ldata)
+ scp->ldata = _free(scp->ldata);
+
+enterChroot(dbi);
+
+ if (flags == DB_WRITECURSOR)
+ rc = sql_commitTransaction(dbi, 1);
+ else
+ rc = sql_endTransaction(dbi);
+
+ scp = scpFree(scp);
+
+leaveChroot(dbi);
+
+ return rc;
+}
+
+/**
+ * Close index database, and destroy database handle.
+ * @param dbi index database handle
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_close(dbiIndex dbi, unsigned int flags)
+{
+ SQL_DB * sqldb = (SQL_DB *) dbi->dbi_db;
+ int rc = 0;
+
+ if (sqldb) {
+enterChroot(dbi);
+
+ /* Commit, don't open a new one */
+ rc = sql_commitTransaction(dbi, 1);
+
+ (void) sqlite3_close(sqldb->db);
+
+ rpmlog(RPMLOG_DEBUG, "closed sql db %s\n",
+ dbi->dbi_subfile);
+
+ dbi->dbi_stats = _free(dbi->dbi_stats);
+ dbi->dbi_file = _free(dbi->dbi_file);
+ dbi->dbi_db = _free(dbi->dbi_db);
+
+leaveChroot(dbi);
+ }
+
+ dbi = _free(dbi);
+
+ return rc;
+}
+
+/**
+ * Return handle for an index database.
+ * @param rpmdb rpm database
+ * @param rpmtag rpm tag
+ * @param dbip
+ * @return 0 on success
+ */
+static int sql_open(rpmdb rpmdb, rpmTag rpmtag, dbiIndex * dbip)
+{
+ extern const struct _dbiVec sqlitevec;
+
+ char * root;
+ char * home;
+ char * dbhome;
+ const char * dbfile;
+ char * dbfname;
+ const char * sql_errcode;
+ dbiIndex dbi;
+ SQL_DB * sqldb;
+ size_t len;
+ int rc = 0;
+ int xx;
+
+ if (dbip)
+ *dbip = NULL;
+
+ /*
+ * Parse db configuration parameters.
+ */
+ if ((dbi = db3New(rpmdb, rpmtag)) == NULL)
+ return 1;
+
+ /*
+ * Get the prefix/root component and directory path
+ */
+ root = rpmdb->db_root;
+ home = rpmdb->db_home;
+
+ dbi->dbi_root = root;
+ dbi->dbi_home = home;
+
+ dbfile = rpmTagGetName(dbi->dbi_rpmtag);
+
+enterChroot(dbi);
+
+ /*
+ * Make a copy of the tagName result..
+ * use this for the filename and table name
+ */
+ {
+ char * t;
+ len = strlen(dbfile);
+ t = xcalloc(len + 1, sizeof(*t));
+ (void) stpcpy( t, dbfile );
+ dbi->dbi_file = t;
+/* WRONG */
+ dbi->dbi_subfile = t;
+ }
+
+ dbi->dbi_mode=O_RDWR;
+
+ dbhome = rpmGenPath(NULL, home, NULL);
+
+ /*
+ * Create the /var/lib/rpm directory if it doesn't exist (root only).
+ */
+ (void) rpmioMkpath(dbhome, 0755, getuid(), getgid());
+
+ dbfname = rpmGenPath(dbhome, dbi->dbi_file, NULL);
+
+ rpmlog(RPMLOG_DEBUG, "opening sql db %s (%s) mode=0x%x\n",
+ dbfname, dbi->dbi_subfile, dbi->dbi_mode);
+
+ /* Open the Database */
+ sqldb = xcalloc(1,sizeof(*sqldb));
+
+ sql_errcode = NULL;
+ xx = sqlite3_open(dbfname, &sqldb->db);
+ if (xx != SQLITE_OK)
+ sql_errcode = sqlite3_errmsg(sqldb->db);
+
+ if (sqldb->db)
+ (void) sqlite3_busy_handler(sqldb->db, &sql_busy_handler, dbi);
+
+ sqldb->transaction = 0; /* Initialize no current transactions */
+
+ dbi->dbi_db = (DB *)sqldb;
+
+ if (sql_errcode != NULL) {
+ rpmlog(RPMLOG_ERR, _("Unable to open database: %s\n"), sql_errcode);
+ rc = EINVAL;
+ }
+
+ /* initialize table */
+ if (rc == 0)
+ rc = sql_initDB(dbi);
+
+ if (rc == 0 && dbi->dbi_db != NULL && dbip != NULL) {
+ dbi->dbi_vec = &sqlitevec;
+ *dbip = dbi;
+ } else {
+ (void) sql_close(dbi, 0);
+ }
+
+ free(dbhome);
+ dbfname = _free(dbfname);
+
+leaveChroot(dbi);
+
+ return rc;
+}
+
+/**
+ * Flush pending operations to disk.
+ * @param dbi index database handle
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_sync (dbiIndex dbi, unsigned int flags)
+{
+ int rc = 0;
+
+enterChroot(dbi);
+ rc = sql_commitTransaction(dbi, 0);
+leaveChroot(dbi);
+
+ return rc;
+}
+
+/**
+ * Open database cursor.
+ * @param dbi index database handle
+ * @param txnid database transaction handle
+ * @retval dbcp address of new database cursor
+ * @param flags DB_WRITECURSOR or 0
+ * @return 0 on success
+ */
+static int sql_copen (dbiIndex dbi, DB_TXN * txnid,
+ DBC ** dbcp, unsigned int flags)
+{
+ SCP_t scp = scpNew(dbi->dbi_db);
+ DBC * dbcursor = (DBC *)scp;
+ int rc = 0;
+
+if (_debug)
+fprintf(stderr, "==> %s(%s) tag %d type %d scp %p\n", __FUNCTION__, rpmTagGetName(dbi->dbi_rpmtag), dbi->dbi_rpmtag, rpmTagGetType(dbi->dbi_rpmtag), scp);
+
+enterChroot(dbi);
+
+ /* If we're going to write, start a transaction (lock the DB) */
+ if (flags == DB_WRITECURSOR)
+ rc = sql_startTransaction(dbi);
+
+ if (dbcp)
+ *dbcp = dbcursor;
+ else
+ (void) sql_cclose(dbi, dbcursor, 0);
+
+leaveChroot(dbi);
+
+ return rc;
+}
+
+/**
+ * Delete (key,data) pair(s) using db->del or dbcursor->c_del.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->del)
+ * @param key delete key value/length/flags
+ * @param data delete data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_cdel (dbiIndex dbi, DBC * dbcursor, DBT * key,
+ DBT * data, unsigned int flags)
+{
+ SQL_DB * sqldb = (SQL_DB *) dbi->dbi_db;
+ SCP_t scp = scpNew(dbi->dbi_db);
+ int rc = 0;
+
+dbg_keyval(__FUNCTION__, dbi, dbcursor, key, data, flags);
+enterChroot(dbi);
+
+ scp->cmd = sqlite3_mprintf("DELETE FROM '%q' WHERE key=? AND value=?;",
+ dbi->dbi_subfile);
+
+ rc = sqlite3_prepare(sqldb->db, scp->cmd, strlen(scp->cmd), &scp->pStmt, &scp->pzErrmsg);
+ if (rc) rpmlog(RPMLOG_DEBUG, "cdel(%s) prepare %s (%d)\n", dbi->dbi_subfile, sqlite3_errmsg(sqldb->db), rc);
+ rc = sql_bind_key(dbi, scp, 1, key);
+ if (rc) rpmlog(RPMLOG_DEBUG, "cdel(%s) bind key %s (%d)\n", dbi->dbi_subfile, sqlite3_errmsg(sqldb->db), rc);
+ rc = sql_bind_data(dbi, scp, 2, data);
+ if (rc) rpmlog(RPMLOG_DEBUG, "cdel(%s) bind data %s (%d)\n", dbi->dbi_subfile, sqlite3_errmsg(sqldb->db), rc);
+
+ rc = sql_step(dbi, scp);
+ if (rc) rpmlog(RPMLOG_DEBUG, "cdel(%s) sql_step rc %d\n", dbi->dbi_subfile, rc);
+
+ scp = scpFree(scp);
+
+leaveChroot(dbi);
+
+ return rc;
+}
+
+/**
+ * Retrieve (key,data) pair using db->get or dbcursor->c_get.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->get)
+ * @param key retrieve key value/length/flags
+ * @param data retrieve data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_cget (dbiIndex dbi, DBC * dbcursor, DBT * key,
+ DBT * data, unsigned int flags)
+{
+ SQL_DB * sqldb = (SQL_DB *) dbi->dbi_db;
+ SCP_t scp = (SCP_t)dbcursor;
+ int rc = 0;
+ int ix;
+
+dbg_keyval(__FUNCTION__, dbi, dbcursor, key, data, flags);
+
+enterChroot(dbi);
+
+ /*
+ * First determine if this is a new scan or existing scan
+ */
+
+if (_debug)
+fprintf(stderr, "\tcget(%s) scp %p rc %d flags %d av %p\n",
+ dbi->dbi_subfile, scp, rc, flags, scp->av);
+ if ( flags == DB_SET || scp->used == 0 ) {
+ scp->used = 1; /* Signal this scp as now in use... */
+ scp = scpReset(scp); /* Free av and avlen, reset counters.*/
+
+/* XXX: Should we also reset the key table here? Can you re-use a cursor? */
+
+ /*
+ * If we're scanning everything, load the iterator key table
+ */
+ if ( key->size == 0) {
+ scp->all = 1;
+
+/*
+ * The only condition not dealt with is if there are multiple identical keys. This can lead
+ * to later iteration confusion. (It may return the same value for the multiple keys.)
+ */
+
+/* Only RPMDBI_PACKAGES is supposed to be iterating, and this is guarenteed to be unique */
+assert(dbi->dbi_rpmtag == RPMDBI_PACKAGES);
+
+ switch (dbi->dbi_rpmtag) {
+ case RPMDBI_PACKAGES:
+ scp->cmd = sqlite3_mprintf("SELECT key FROM '%q' ORDER BY key;",
+ dbi->dbi_subfile);
+ break;
+ default:
+ scp->cmd = sqlite3_mprintf("SELECT key FROM '%q';",
+ dbi->dbi_subfile);
+ break;
+ }
+ rc = sqlite3_prepare(sqldb->db, scp->cmd, strlen(scp->cmd), &scp->pStmt, &scp->pzErrmsg);
+ if (rc) rpmlog(RPMLOG_DEBUG, "cget(%s) sequential prepare %s (%d)\n", dbi->dbi_subfile, sqlite3_errmsg(sqldb->db), rc);
+
+ rc = sql_step(dbi, scp);
+ if (rc) rpmlog(RPMLOG_DEBUG, "cget(%s) sequential sql_step rc %d\n", dbi->dbi_subfile, rc);
+
+ scp = scpResetKeys(scp);
+ scp->nkeys = scp->nr;
+ scp->keys = xcalloc(scp->nkeys, sizeof(*scp->keys));
+ for (ix = 0; ix < scp->nkeys; ix++) {
+ scp->keys[ix] = xmalloc(sizeof(DBT));
+ scp->keys[ix]->size = scp->avlen[ix+1];
+ scp->keys[ix]->data = xmalloc(scp->keys[ix]->size);
+ memcpy(scp->keys[ix]->data, scp->av[ix+1], scp->avlen[ix+1]);
+ }
+ } else {
+ /*
+ * We're only scanning ONE element
+ */
+ scp = scpResetKeys(scp);
+ scp->nkeys = 1;
+ scp->keys = xcalloc(scp->nkeys, sizeof(*scp->keys));
+ scp->keys[0] = xmalloc(sizeof(DBT));
+ scp->keys[0]->size = key->size;
+ scp->keys[0]->data = xmalloc(scp->keys[0]->size);
+ memcpy(scp->keys[0]->data, key->data, key->size);
+ }
+
+ scp = scpReset(scp); /* reset */
+
+ /* Prepare SQL statement to retrieve the value for the current key */
+ scp->cmd = sqlite3_mprintf("SELECT value FROM '%q' WHERE key=?;", dbi->dbi_subfile);
+ rc = sqlite3_prepare(sqldb->db, scp->cmd, strlen(scp->cmd), &scp->pStmt, &scp->pzErrmsg);
+
+ if (rc) rpmlog(RPMLOG_DEBUG, "cget(%s) prepare %s (%d)\n", dbi->dbi_subfile, sqlite3_errmsg(sqldb->db), rc);
+ }
+
+ scp = scpResetAv(scp); /* Free av and avlen, reset counters.*/
+
+ /* Now continue with a normal retrive based on key */
+ if ((scp->rx + 1) > scp->nkeys )
+ rc = DB_NOTFOUND; /* At the end of the list */
+
+ if (rc != 0)
+ goto exit;
+
+ /* Bind key to prepared statement */
+ rc = sql_bind_key(dbi, scp, 1, scp->keys[scp->rx]);
+ if (rc) rpmlog(RPMLOG_DEBUG, "cget(%s) key bind %s (%d)\n", dbi->dbi_subfile, sqlite3_errmsg(sqldb->db), rc);
+
+ rc = sql_step(dbi, scp);
+ if (rc) rpmlog(RPMLOG_DEBUG, "cget(%s) sql_step rc %d\n", dbi->dbi_subfile, rc);
+
+ rc = sqlite3_reset(scp->pStmt);
+ if (rc) rpmlog(RPMLOG_DEBUG, "reset %d\n", rc);
+
+/* 1 key should return 0 or 1 row/value */
+assert(scp->nr < 2);
+
+ if (scp->nr == 0 && scp->all == 0)
+ rc = DB_NOTFOUND; /* No data for that key found! */
+
+ if (rc != 0)
+ goto exit;
+
+ /* If we're looking at the whole db, return the key */
+ if (scp->all) {
+
+/* To get this far there has to be _1_ key returned! (protect against dup keys) */
+assert(scp->nr == 1);
+
+ if ( scp->lkey ) {
+ scp->lkey = _free(scp->lkey);
+ }
+
+ key->size = scp->keys[scp->rx]->size;
+ key->data = xmalloc(key->size);
+ if (! (key->flags & DB_DBT_MALLOC))
+ scp->lkey = key->data;
+
+ (void) memcpy(key->data, scp->keys[scp->rx]->data, key->size);
+ }
+
+ /* Construct and return the data element (element 0 is "value", 1 is _THE_ value)*/
+ switch (dbi->dbi_rpmtag) {
+ default:
+ if ( scp->ldata ) {
+ scp->ldata = _free(scp->ldata);
+ }
+
+ data->size = scp->avlen[1];
+ data->data = xmalloc(data->size);
+ if (! (data->flags & DB_DBT_MALLOC) )
+ scp->ldata = data->data;
+
+ (void) memcpy(data->data, scp->av[1], data->size);
+ }
+
+ scp->rx++;
+
+ /* XXX FIXME: ptr alignment is fubar here. */
+if (_debug)
+fprintf(stderr, "\tcget(%s) found key 0x%x (%d)\n", dbi->dbi_subfile,
+ key->data == NULL ? 0 : *(unsigned int *)key->data, key->size);
+if (_debug)
+fprintf(stderr, "\tcget(%s) found data 0x%x (%d)\n", dbi->dbi_subfile,
+ key->data == NULL ? 0 : *(unsigned int *)data->data, data->size);
+
+exit:
+ if (rc == DB_NOTFOUND) {
+if (_debug)
+fprintf(stderr, "\tcget(%s) not found\n", dbi->dbi_subfile);
+ }
+
+leaveChroot(dbi);
+
+ return rc;
+}
+
+/**
+ * Store (key,data) pair using db->put or dbcursor->c_put.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->put)
+ * @param key store key value/length/flags
+ * @param data store data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_cput (dbiIndex dbi, DBC * dbcursor, DBT * key,
+ DBT * data, unsigned int flags)
+{
+ SQL_DB * sqldb = (SQL_DB *) dbi->dbi_db;
+ SCP_t scp = scpNew(dbi->dbi_db);
+ int rc = 0;
+
+dbg_keyval(__FUNCTION__, dbi, dbcursor, key, data, flags);
+
+enterChroot(dbi);
+
+ switch (dbi->dbi_rpmtag) {
+ default:
+ scp->cmd = sqlite3_mprintf("INSERT OR REPLACE INTO '%q' VALUES(?, ?);",
+ dbi->dbi_subfile);
+ rc = sqlite3_prepare(sqldb->db, scp->cmd, strlen(scp->cmd), &scp->pStmt, &scp->pzErrmsg);
+ if (rc) rpmlog(RPMLOG_DEBUG, "cput(%s) prepare %s (%d)\n",dbi->dbi_subfile, sqlite3_errmsg(sqldb->db), rc);
+ rc = sql_bind_key(dbi, scp, 1, key);
+ if (rc) rpmlog(RPMLOG_DEBUG, "cput(%s) key bind %s (%d)\n", dbi->dbi_subfile, sqlite3_errmsg(sqldb->db), rc);
+ rc = sql_bind_data(dbi, scp, 2, data);
+ if (rc) rpmlog(RPMLOG_DEBUG, "cput(%s) data bind %s (%d)\n", dbi->dbi_subfile, sqlite3_errmsg(sqldb->db), rc);
+
+ rc = sql_step(dbi, scp);
+ if (rc) rpmlog(RPMLOG_DEBUG, "cput(%s) sql_step rc %d\n", dbi->dbi_subfile, rc);
+
+ break;
+ }
+
+ scp = scpFree(scp);
+
+leaveChroot(dbi);
+
+ return rc;
+}
+
+/**
+ * Is database byte swapped?
+ * @param dbi index database handle
+ * @return 0 no
+ */
+static int sql_byteswapped (dbiIndex dbi)
+{
+ SQL_DB * sqldb = (SQL_DB *) dbi->dbi_db;
+ SCP_t scp = scpNew(dbi->dbi_db);
+ int sql_rc, rc = 0;
+ union _dbswap db_endian;
+
+enterChroot(dbi);
+
+ sql_rc = sqlite3_get_table(sqldb->db, "SELECT endian FROM 'db_info';",
+ &scp->av, &scp->nr, &scp->nc, (char **)&scp->pzErrmsg);
+
+ if (sql_rc == 0 && scp->nr > 0) {
+assert(scp->av != NULL);
+ db_endian.uc[0] = strtol(scp->av[1], NULL, 10);
+
+ if ( db_endian.uc[0] == ((union _dbswap *)&endian)->uc[0] )
+ rc = 0; /* Native endian */
+ else
+ rc = 1; /* swapped */
+
+ } else {
+ if ( sql_rc ) {
+ rpmlog(RPMLOG_DEBUG, "db_info failed %s (%d)\n",
+ scp->pzErrmsg, sql_rc);
+ }
+ rpmlog(RPMLOG_WARNING, _("Unable to determine DB endianess.\n"));
+ }
+
+ scp = scpFree(scp);
+
+leaveChroot(dbi);
+
+ return rc;
+}
+
+/**************************************************
+ *
+ * All of the following are not implemented!
+ * they are not used by the rest of the system
+ *
+ **************************************************/
+
+/**
+ * Associate secondary database with primary.
+ * @param dbi index database handle
+ * @param dbisecondary secondary index database handle
+ * @param callback create secondary key from primary (NULL if DB_RDONLY)
+ * @param flags DB_CREATE or 0
+ * @return 0 on success
+ */
+static int sql_associate (dbiIndex dbi, dbiIndex dbisecondary,
+ int (*callback) (DB *, const DBT *, const DBT *, DBT *),
+ unsigned int flags)
+{
+if (_debug)
+fprintf(stderr, "*** %s:\n", __FUNCTION__);
+ return EINVAL;
+}
+
+/**
+ * Return join cursor for list of cursors.
+ * @param dbi index database handle
+ * @param curslist NULL terminated list of database cursors
+ * @retval dbcp address of join database cursor
+ * @param flags DB_JOIN_NOSORT or 0
+ * @return 0 on success
+ */
+static int sql_join (dbiIndex dbi, DBC ** curslist, DBC ** dbcp,
+ unsigned int flags)
+{
+if (_debug)
+fprintf(stderr, "*** %s:\n", __FUNCTION__);
+ return EINVAL;
+}
+
+/**
+ * Duplicate a database cursor.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @retval dbcp address of new database cursor
+ * @param flags DB_POSITION for same position, 0 for uninitialized
+ * @return 0 on success
+ */
+static int sql_cdup (dbiIndex dbi, DBC * dbcursor, DBC ** dbcp,
+ unsigned int flags)
+{
+if (_debug)
+fprintf(stderr, "*** %s:\n", __FUNCTION__);
+ return EINVAL;
+}
+
+/**
+ * Retrieve (key,data) pair using dbcursor->c_pget.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @param key secondary retrieve key value/length/flags
+ * @param pkey primary retrieve key value/length/flags
+ * @param data primary retrieve data value/length/flags
+ * @param flags DB_NEXT, DB_SET, or 0
+ * @return 0 on success
+ */
+static int sql_cpget (dbiIndex dbi, DBC * dbcursor,
+ DBT * key, DBT * pkey, DBT * data, unsigned int flags)
+{
+if (_debug)
+fprintf(stderr, "*** %s:\n", __FUNCTION__);
+ return EINVAL;
+}
+
+/**
+ * Retrieve count of (possible) duplicate items using dbcursor->c_count.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @param countp address of count
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static int sql_ccount (dbiIndex dbi, DBC * dbcursor,
+ unsigned int * countp,
+ unsigned int flags)
+{
+if (_debug)
+fprintf(stderr, "*** %s:\n", __FUNCTION__);
+ return EINVAL;
+}
+
+/** \ingroup dbi
+ * Save statistics in database handle.
+ * @param dbi index database handle
+ * @param flags retrieve statistics that don't require traversal?
+ * @return 0 on success
+ */
+static int sql_stat (dbiIndex dbi, unsigned int flags)
+{
+ SQL_DB * sqldb = (SQL_DB *) dbi->dbi_db;
+ SCP_t scp = scpNew(dbi->dbi_db);
+ int rc = 0;
+ long nkeys = -1;
+
+enterChroot(dbi);
+
+ dbi->dbi_stats = _free(dbi->dbi_stats);
+
+ dbi->dbi_stats = xcalloc(1, sizeof(DB_HASH_STAT));
+
+ scp->cmd = sqlite3_mprintf("SELECT COUNT('key') FROM '%q';", dbi->dbi_subfile);
+ rc = sqlite3_get_table(sqldb->db, scp->cmd,
+ &scp->av, &scp->nr, &scp->nc, (char **)&scp->pzErrmsg);
+
+ if ( rc == 0 && scp->nr > 0) {
+assert(scp->av != NULL);
+ nkeys = strtol(scp->av[1], NULL, 10);
+
+ rpmlog(RPMLOG_DEBUG, " stat on %s nkeys %ld\n",
+ dbi->dbi_subfile, nkeys);
+ } else {
+ if ( rc ) {
+ rpmlog(RPMLOG_DEBUG, "stat failed %s (%d)\n",
+ scp->pzErrmsg, rc);
+ }
+ }
+
+ if (nkeys < 0)
+ nkeys = 4096; /* Good high value */
+
+ ((DB_HASH_STAT *)(dbi->dbi_stats))->hash_nkeys = nkeys;
+
+ scp = scpFree(scp);
+
+leaveChroot(dbi);
+
+ return rc;
+}
+
+/* Major, minor, patch version of DB.. we're not using db.. so set to 0 */
+/* open, close, sync, associate, join */
+/* cursor_open, cursor_close, cursor_dup, cursor_delete, cursor_get, */
+/* cursor_pget?, cursor_put, cursor_count */
+/* db_bytewapped, stat */
+const struct _dbiVec sqlitevec = {
+ 0, 0, 0,
+ sql_open,
+ sql_close,
+ sql_sync,
+ sql_associate,
+ sql_join,
+ sql_copen,
+ sql_cclose,
+ sql_cdup,
+ sql_cdel,
+ sql_cget,
+ sql_cpget,
+ sql_cput,
+ sql_ccount,
+ sql_byteswapped,
+ sql_stat
+};
+
diff --git a/lib/fprint.c b/lib/fprint.c
new file mode 100644
index 000000000..b1996689c
--- /dev/null
+++ b/lib/fprint.c
@@ -0,0 +1,265 @@
+/**
+ * \file rpmdb/fprint.c
+ */
+
+#include "system.h"
+
+#include <rpm/rpmfileutil.h> /* for rpmCleanPath */
+
+#include "rpmdb/rpmdb_internal.h"
+#include "rpmdb/fprint.h"
+#include "debug.h"
+
+fingerPrintCache fpCacheCreate(int sizeHint)
+{
+ fingerPrintCache fpc;
+
+ fpc = xmalloc(sizeof(*fpc));
+ fpc->ht = htCreate(sizeHint * 2, 0, 1, hashFunctionString,
+ hashEqualityString);
+ return fpc;
+}
+
+fingerPrintCache fpCacheFree(fingerPrintCache cache)
+{
+ cache->ht = htFree(cache->ht);
+ free(cache);
+ return NULL;
+}
+
+/**
+ * Find directory name entry in cache.
+ * @param cache pointer to fingerprint cache
+ * @param dirName string to locate in cache
+ * @return pointer to directory name entry (or NULL if not found).
+ */
+static const struct fprintCacheEntry_s * cacheContainsDirectory(
+ fingerPrintCache cache,
+ const char * dirName)
+{
+ const void ** data;
+
+ if (htGetEntry(cache->ht, dirName, &data, NULL, NULL))
+ return NULL;
+ return data[0];
+}
+
+/**
+ * Return finger print of a file path.
+ * @param cache pointer to fingerprint cache
+ * @param dirName leading directory name of path
+ * @param baseName file name of path
+ * @param scareMemory
+ * @return pointer to the finger print associated with a file path.
+ */
+/* LCL: segfault */
+static fingerPrint doLookup(fingerPrintCache cache,
+ const char * dirName, const char * baseName, int scareMemory)
+{
+ char dir[PATH_MAX];
+ const char * cleanDirName;
+ size_t cdnl;
+ char * end; /* points to the '\0' at the end of "buf" */
+ fingerPrint fp;
+ struct stat sb;
+ char *buf = NULL;
+ char *cdnbuf = NULL;
+ const struct fprintCacheEntry_s * cacheHit;
+
+ /* assert(*dirName == '/' || !scareMemory); */
+
+ /* XXX WATCHOUT: fp.subDir is set below from relocated dirName arg */
+ cleanDirName = dirName;
+ cdnl = strlen(cleanDirName);
+
+ if (*cleanDirName == '/') {
+ if (!scareMemory) {
+ cdnbuf = xstrdup(dirName);
+ cleanDirName = rpmCleanPath(cdnbuf);
+ }
+ } else {
+ scareMemory = 0; /* XXX causes memory leak */
+
+ /* Using realpath on the arg isn't correct if the arg is a symlink,
+ * especially if the symlink is a dangling link. What we
+ * do instead is use realpath() on `.' and then append arg to
+ * the result.
+ */
+
+ /* if the current directory doesn't exist, we might fail.
+ oh well. likewise if it's too long. */
+ dir[0] = '\0';
+ if (realpath(".", dir) != NULL) {
+ end = dir + strlen(dir);
+ if (end[-1] != '/') *end++ = '/';
+ end = stpncpy(end, cleanDirName, sizeof(dir) - (end - dir));
+ *end = '\0';
+ (void)rpmCleanPath(dir); /* XXX possible /../ from concatenation */
+ end = dir + strlen(dir);
+ if (end[-1] != '/') *end++ = '/';
+ *end = '\0';
+ cleanDirName = dir;
+ cdnl = end - dir;
+ }
+ }
+ fp.entry = NULL;
+ fp.subDir = NULL;
+ fp.baseName = NULL;
+ if (cleanDirName == NULL) goto exit; /* XXX can't happen */
+
+ buf = xstrdup(cleanDirName);
+ end = buf + cdnl;
+
+ /* no need to pay attention to that extra little / at the end of dirName */
+ if (buf[1] && end[-1] == '/') {
+ end--;
+ *end = '\0';
+ }
+
+ while (1) {
+
+ /* as we're stating paths here, we want to follow symlinks */
+
+ cacheHit = cacheContainsDirectory(cache, (*buf != '\0' ? buf : "/"));
+ if (cacheHit != NULL) {
+ fp.entry = cacheHit;
+ } else if (!stat((*buf != '\0' ? buf : "/"), &sb)) {
+ size_t nb = sizeof(*fp.entry) + (*buf != '\0' ? (end-buf) : 1) + 1;
+ char * dn = xmalloc(nb);
+ struct fprintCacheEntry_s * newEntry = (void *)dn;
+
+ /* LCL: contiguous malloc confusion */
+ dn += sizeof(*newEntry);
+ strcpy(dn, (*buf != '\0' ? buf : "/"));
+ newEntry->ino = sb.st_ino;
+ newEntry->dev = sb.st_dev;
+ newEntry->dirName = dn;
+ fp.entry = newEntry;
+
+ htAddEntry(cache->ht, dn, fp.entry);
+ }
+
+ if (fp.entry) {
+ fp.subDir = cleanDirName + (end - buf);
+ if (fp.subDir[0] == '/' && fp.subDir[1] != '\0')
+ fp.subDir++;
+ if (fp.subDir[0] == '\0' ||
+ /* XXX don't bother saving '/' as subdir */
+ (fp.subDir[0] == '/' && fp.subDir[1] == '\0'))
+ fp.subDir = NULL;
+ fp.baseName = baseName;
+ if (!scareMemory && fp.subDir != NULL)
+ fp.subDir = xstrdup(fp.subDir);
+ /* FIX: fp.entry.{dirName,dev,ino} undef @*/
+ goto exit;
+ }
+
+ /* stat of '/' just failed! */
+ if (end == buf + 1)
+ abort();
+
+ end--;
+ while ((end > buf) && *end != '/') end--;
+ if (end == buf) /* back to stat'ing just '/' */
+ end++;
+
+ *end = '\0';
+ }
+
+exit:
+ free(buf);
+ free(cdnbuf);
+ /* FIX: fp.entry.{dirName,dev,ino} undef @*/
+ return fp;
+}
+
+fingerPrint fpLookup(fingerPrintCache cache, const char * dirName,
+ const char * baseName, int scareMemory)
+{
+ return doLookup(cache, dirName, baseName, scareMemory);
+}
+
+unsigned int fpHashFunction(const void * key)
+{
+ const fingerPrint * fp = key;
+ unsigned int hash = 0;
+ char ch;
+ const char * chptr;
+
+ ch = 0;
+ chptr = fp->baseName;
+ while (*chptr != '\0') ch ^= *chptr++;
+
+ hash |= ((unsigned)ch) << 24;
+ hash |= (((((unsigned)fp->entry->dev) >> 8) ^ fp->entry->dev) & 0xFF) << 16;
+ hash |= fp->entry->ino & 0xFFFF;
+
+ return hash;
+}
+
+int fpEqual(const void * key1, const void * key2)
+{
+ const fingerPrint *k1 = key1;
+ const fingerPrint *k2 = key2;
+
+ /* If the addresses are the same, so are the values. */
+ if (k1 == k2)
+ return 0;
+
+ /* Otherwise, compare fingerprints by value. */
+ /* LCL: whines about (*k2).subdir */
+ if (FP_EQUAL(*k1, *k2))
+ return 0;
+ return 1;
+
+}
+
+void fpLookupList(fingerPrintCache cache, const char ** dirNames,
+ const char ** baseNames, const uint32_t * dirIndexes,
+ int fileCount, fingerPrint * fpList)
+{
+ int i;
+
+ for (i = 0; i < fileCount; i++) {
+ /* If this is in the same directory as the last file, don't bother
+ redoing all of this work */
+ if (i > 0 && dirIndexes[i - 1] == dirIndexes[i]) {
+ fpList[i].entry = fpList[i - 1].entry;
+ fpList[i].subDir = fpList[i - 1].subDir;
+ fpList[i].baseName = baseNames[i];
+ } else {
+ fpList[i] = doLookup(cache, dirNames[dirIndexes[i]], baseNames[i],
+ 1);
+ }
+ }
+}
+
+#ifdef UNUSED
+/**
+ * Return finger prints of all file names in header.
+ * @warning: scareMemory is assumed!
+ * @param cache pointer to fingerprint cache
+ * @param h package header
+ * @retval fpList pointer to array of finger prints
+ */
+static
+void fpLookupHeader(fingerPrintCache cache, Header h, fingerPrint * fpList);
+{
+ HGE_t hge = (HGE_t)headerGetEntryMinMemory;
+ HFD_t hfd = headerFreeData;
+ const char ** baseNames, ** dirNames;
+ rpmTagTypebnt, dnt;
+ uint32_t * dirIndexes;
+ int fileCount;
+ int xx;
+
+ if (!hge(h, RPMTAG_BASENAMES, &bnt, (rpm_data_t *) &baseNames, &fileCount))
+ return;
+
+ xx = hge(h, RPMTAG_DIRNAMES, &dnt, (rpm_data_t *) &dirNames, NULL);
+ xx = hge(h, RPMTAG_DIRINDEXES, NULL, (rpm_data_t *) &dirIndexes, NULL);
+ fpLookupList(cache, dirNames, baseNames, dirIndexes, fileCount, fpList);
+ dirNames = hfd(dirNames, dnt);
+ baseNames = hfd(baseNames, bnt);
+}
+#endif
diff --git a/lib/fprint.h b/lib/fprint.h
new file mode 100644
index 000000000..3e478fbbe
--- /dev/null
+++ b/lib/fprint.h
@@ -0,0 +1,141 @@
+#ifndef H_FINGERPRINT
+#define H_FINGERPRINT
+
+/** \ingroup rpmtrans
+ * \file rpmdb/fprint.h
+ * Identify a file name path by a unique "finger print".
+ */
+
+#include <rpm/header.h>
+#include "rpmdb/rpmhash.h"
+
+/**
+ */
+typedef struct fprintCache_s * fingerPrintCache;
+
+/**
+ * @todo Convert to pointer and make abstract.
+ */
+typedef struct fingerPrint_s fingerPrint;
+
+/**
+ * Finger print cache entry.
+ * This is really a directory and symlink cache. We don't differentiate between
+ * the two. We can prepopulate it, which allows us to easily conduct "fake"
+ * installs of a system w/o actually mounting filesystems.
+ */
+struct fprintCacheEntry_s {
+ const char * dirName; /*!< path to existing directory */
+ dev_t dev; /*!< stat(2) device number */
+ ino_t ino; /*!< stat(2) inode number */
+};
+
+/**
+ * Finger print cache.
+ */
+struct fprintCache_s {
+ hashTable ht; /*!< hashed by dirName */
+};
+
+/**
+ * Associates a trailing sub-directory and final base name with an existing
+ * directory finger print.
+ */
+struct fingerPrint_s {
+/*! directory finger print entry (the directory path is stat(2)-able */
+ const struct fprintCacheEntry_s * entry;
+/*! trailing sub-directory path (directories that are not stat(2)-able */
+const char * subDir;
+const char * baseName; /*!< file base name */
+};
+
+/** */
+#define FP_ENTRY_EQUAL(a, b) (((a)->dev == (b)->dev) && ((a)->ino == (b)->ino))
+
+/** */
+#define FP_EQUAL(a, b) ( \
+ FP_ENTRY_EQUAL((a).entry, (b).entry) && \
+ !strcmp((a).baseName, (b).baseName) && ( \
+ ((a).subDir == (b).subDir) || \
+ ((a).subDir && (b).subDir && !strcmp((a).subDir, (b).subDir)) \
+ ) \
+ )
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup rpmdb
+ * Find fingerprint matches in database.
+ * @param db rpm database
+ * @param fpList fingerprint array
+ * @retval matchList returned fingerprint matches
+ * @param numItems number of fingerprint items
+ * @return 0 always
+ */
+int rpmdbFindFpList(rpmdb db, fingerPrint * fpList,
+ dbiIndexSet * matchList, int numItems);
+
+/* Be carefull with the memory... assert(*fullName == '/' || !scareMemory) */
+
+/**
+ * Create finger print cache.
+ * @param sizeHint number of elements expected
+ * @return pointer to initialized fingerprint cache
+ */
+fingerPrintCache fpCacheCreate(int sizeHint);
+
+/**
+ * Destroy finger print cache.
+ * @param cache pointer to fingerprint cache
+ * @return NULL always
+ */
+fingerPrintCache fpCacheFree(fingerPrintCache cache);
+
+/**
+ * Return finger print of a file path.
+ * @param cache pointer to fingerprint cache
+ * @param dirName leading directory name of file path
+ * @param baseName base name of file path
+ * @param scareMemory
+ * @return pointer to the finger print associated with a file path.
+ */
+fingerPrint fpLookup(fingerPrintCache cache, const char * dirName,
+ const char * baseName, int scareMemory);
+
+/**
+ * Return hash value for a finger print.
+ * Hash based on dev and inode only!
+ * @param key pointer to finger print entry
+ * @return hash value
+ */
+unsigned int fpHashFunction(const void * key);
+
+/**
+ * Compare two finger print entries.
+ * This routine is exactly equivalent to the FP_EQUAL macro.
+ * @param key1 finger print 1
+ * @param key2 finger print 2
+ * @return result of comparing key1 and key2
+ */
+int fpEqual(const void * key1, const void * key2);
+
+/**
+ * Return finger prints of an array of file paths.
+ * @warning: scareMemory is assumed!
+ * @param cache pointer to fingerprint cache
+ * @param dirNames directory names
+ * @param baseNames file base names
+ * @param dirIndexes index into dirNames for each baseNames
+ * @param fileCount number of file entries
+ * @retval fpList pointer to array of finger prints
+ */
+void fpLookupList(fingerPrintCache cache, const char ** dirNames,
+ const char ** baseNames, const uint32_t * dirIndexes,
+ int fileCount, fingerPrint * fpList);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/gentagtbl.sh b/lib/gentagtbl.sh
new file mode 100755
index 000000000..04ad2fe5e
--- /dev/null
+++ b/lib/gentagtbl.sh
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+cat << EOF
+#include "system.h"
+#include <rpm/header.h>
+#include <rpm/rpmtag.h>
+#include "debug.h"
+
+static const struct headerTagTableEntry_s rpmTagTbl[] = {
+EOF
+
+${AWK} '/[\t ](RPMTAG_[A-Z0-9]*)[ \t]+([0-9]*)/ && !/internal/ {
+ tt = "NULL"
+ ta = "ANY"
+ if ($5 == "c") {
+ tt = "CHAR"
+ ta = "SCALAR"
+ }
+ if ($5 == "c[]") {
+ tt = "CHAR"
+ ta = "ARRAY"
+ }
+ if ($5 == "h") {
+ tt = "INT16"
+ ta = "SCALAR"
+ }
+ if ($5 == "h[]") {
+ tt = "INT16"
+ ta = "ARRAY"
+ }
+ if ($5 == "i") {
+ tt = "INT32"
+ ta = "SCALAR"
+ }
+ if ($5 == "i[]") {
+ tt = "INT32"
+ ta = "ARRAY"
+ }
+ if ($5 == "l") {
+ tt = "INT64"
+ ta = "SCALAR"
+ }
+ if ($5 == "l[]") {
+ tt = "INT64"
+ ta = "ARRAY"
+ }
+ if ($5 == "s") {
+ tt = "STRING"
+ ta = "SCALAR"
+ }
+ if ($5 == "s[]") {
+ tt = "STRING_ARRAY"
+ ta = "ARRAY"
+ }
+ if ($5 == "s{}") {
+ tt = "I18NSTRING"
+ ta = "SCALAR"
+ }
+ if ($5 == "x") {
+ tt = "BIN"
+ ta = "SCALAR"
+ }
+ if ($2 == "=") {
+ tnarg = $1
+ } else {
+ tnarg = $2
+ }
+ tn = substr(tnarg, index(tnarg, "_") + 1)
+ sn = (substr(tn, 1, 1) tolower(substr(tn, 2)))
+ if ($2 == "=") {
+ printf(" { \"%s\", \"%s\", %s RPM_%s_TYPE + RPM_%s_RETURN_TYPE },\n", tnarg, sn, $3, tt, ta)
+ } else {
+ printf(" { \"%s\", \"%s\", %s, RPM_%s_TYPE + RPM_%s_RETURN_TYPE },\n", tnarg, sn, $3, tt, ta)
+ }
+}' < $1 | sort
+
+cat << EOF
+ { NULL, NULL, 0, 0 }
+};
+
+const struct headerTagTableEntry_s * const rpmTagTable = rpmTagTbl;
+const int rpmTagTableSize = sizeof(rpmTagTbl) / sizeof(rpmTagTbl[0]) - 1;
+EOF
+
diff --git a/lib/hdrNVR.c b/lib/hdrNVR.c
new file mode 100644
index 000000000..367d165a1
--- /dev/null
+++ b/lib/hdrNVR.c
@@ -0,0 +1,130 @@
+/** \ingroup rpmdb
+ * \file rpmdb/hdrNVR.c
+ */
+
+#include "system.h"
+
+#include <rpm/rpmtypes.h>
+#include <rpm/header.h>
+#include <rpm/rpmstring.h>
+
+#include "debug.h"
+
+int headerNVR(Header h, const char **np, const char **vp, const char **rp)
+{
+ return headerNEVRA(h, np, NULL, vp, rp, NULL);
+}
+
+int headerNEVRA(Header h, const char **np,
+ int32_t **ep, const char **vp, const char **rp,
+ const char **ap)
+{
+ rpmTagType type;
+ rpm_count_t count;
+
+ if (np) {
+ if (!(headerGetEntry(h, RPMTAG_NAME, &type, (rpm_data_t *) np, &count)
+ && type == RPM_STRING_TYPE && count == 1))
+ *np = NULL;
+ }
+ if (vp) {
+ if (!(headerGetEntry(h, RPMTAG_VERSION, &type, (rpm_data_t *) vp, &count)
+ && type == RPM_STRING_TYPE && count == 1))
+ *vp = NULL;
+ }
+ if (rp) {
+ if (!(headerGetEntry(h, RPMTAG_RELEASE, &type, (rpm_data_t *) rp, &count)
+ && type == RPM_STRING_TYPE && count == 1))
+ *rp = NULL;
+ }
+ if (ap) {
+ if (!(headerGetEntry(h, RPMTAG_ARCH, &type, (rpm_data_t *) ap, &count)
+ && type == RPM_STRING_TYPE && count == 1))
+ *ap = NULL;
+ }
+ if (ep) {
+ if (!(headerGetEntry(h, RPMTAG_EPOCH, &type, (rpm_data_t *) ep, &count)
+ && type == RPM_INT32_TYPE && count == 1))
+ *ep = NULL;
+ }
+ return 0;
+}
+
+char * headerGetNEVR(Header h, const char ** np)
+{
+ const char * n;
+ char * evr, *nevr = NULL;
+
+ evr = headerGetEVR(h, &n);
+ rasprintf(&nevr, "%s-%s", n, evr);
+ free(evr);
+ if (np)
+ *np = n;
+ return nevr;
+}
+
+char * headerGetNEVRA(Header h, const char ** np)
+{
+ const char *n = NULL, *a = NULL;
+ char *nevr, *nevra = NULL;
+
+ nevr = headerGetNEVR(h, &n);
+ headerGetEntry(h, RPMTAG_ARCH, NULL, (rpm_data_t *) &a, NULL);
+
+ /* XXX gpg-pubkey packages have no arch, urgh... */
+ if (a) {
+ const char *arch = headerIsSource(h) ? "src" : a;
+ rasprintf(&nevra, "%s.%s", nevr, arch);
+ free(nevr);
+ } else {
+ nevra = nevr;
+ }
+
+ if (np)
+ *np = n;
+ return nevra;
+}
+
+char * headerGetEVR(Header h, const char ** np)
+{
+ const char *n, *v, *r;
+ char *evr = NULL;
+ int32_t *e;
+
+ (void) headerNEVRA(h, &n, &e, &v, &r, NULL);
+ if (e) {
+ rasprintf(&evr, "%d:%s-%s", *e, v, r);
+ } else {
+ rasprintf(&evr, "%s-%s", v, r);
+ }
+ if (np)
+ *np = n;
+ return evr;
+}
+
+rpm_color_t headerGetColor(Header h)
+{
+ HGE_t hge = (HGE_t)headerGetEntryMinMemory;
+ rpm_color_t hcolor = 0;
+ rpm_color_t * fcolors;
+ rpm_count_t ncolors;
+ int i;
+
+ fcolors = NULL;
+ ncolors = 0;
+ if (hge(h, RPMTAG_FILECOLORS, NULL, (rpm_data_t *)&fcolors, &ncolors)
+ && fcolors != NULL && ncolors > 0)
+ {
+ for (i = 0; i < ncolors; i++)
+ hcolor |= fcolors[i];
+ }
+ hcolor &= 0x0f;
+
+ return hcolor;
+}
+
+int headerIsSource(Header h)
+{
+ return (!headerIsEntry(h, RPMTAG_SOURCERPM));
+}
+
diff --git a/lib/header.c b/lib/header.c
new file mode 100644
index 000000000..1639c4313
--- /dev/null
+++ b/lib/header.c
@@ -0,0 +1,3039 @@
+/** \ingroup header
+ * \file rpmdb/header.c
+ */
+
+/* RPM - Copyright (C) 1995-2002 Red Hat Software */
+
+/* Data written to file descriptors is in network byte order. */
+/* Data read from file descriptors is expected to be in */
+/* network byte order and is converted on the fly to host order. */
+
+#include "system.h"
+
+#include <rpm/rpmtag.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmpgp.h>
+#include "rpmdb/header_internal.h"
+
+#include "debug.h"
+
+int _hdr_debug = 0;
+
+
+
+#define PARSER_BEGIN 0
+#define PARSER_IN_ARRAY 1
+#define PARSER_IN_EXPR 2
+
+/** \ingroup header
+ */
+static unsigned char const header_magic[8] = {
+ 0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00
+};
+
+/** \ingroup header
+ * Alignment needed for header data types.
+ */
+static const int typeAlign[16] = {
+ 1, /*!< RPM_NULL_TYPE */
+ 1, /*!< RPM_CHAR_TYPE */
+ 1, /*!< RPM_INT8_TYPE */
+ 2, /*!< RPM_INT16_TYPE */
+ 4, /*!< RPM_INT32_TYPE */
+ 8, /*!< RPM_INT64_TYPE */
+ 1, /*!< RPM_STRING_TYPE */
+ 1, /*!< RPM_BIN_TYPE */
+ 1, /*!< RPM_STRING_ARRAY_TYPE */
+ 1, /*!< RPM_I18NSTRING_TYPE */
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+};
+
+/** \ingroup header
+ * Size of header data types.
+ */
+static const int typeSizes[16] = {
+ 0, /*!< RPM_NULL_TYPE */
+ 1, /*!< RPM_CHAR_TYPE */
+ 1, /*!< RPM_INT8_TYPE */
+ 2, /*!< RPM_INT16_TYPE */
+ 4, /*!< RPM_INT32_TYPE */
+ -1, /*!< RPM_INT64_TYPE */
+ -1, /*!< RPM_STRING_TYPE */
+ 1, /*!< RPM_BIN_TYPE */
+ -1, /*!< RPM_STRING_ARRAY_TYPE */
+ -1, /*!< RPM_I18NSTRING_TYPE */
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+};
+
+/** \ingroup header
+ * Maximum no. of bytes permitted in a header.
+ */
+static const size_t headerMaxbytes = (32*1024*1024);
+
+/**
+ * Sanity check on no. of tags.
+ * This check imposes a limit of 65K tags, more than enough.
+ */
+#define hdrchkTags(_ntags) ((_ntags) & 0xffff0000)
+
+/**
+ * Sanity check on type values.
+ */
+#define hdrchkType(_type) ((_type) < RPM_MIN_TYPE || (_type) > RPM_MAX_TYPE)
+
+/**
+ * Sanity check on data size and/or offset and/or count.
+ * This check imposes a limit of 16Mb, more than enough.
+ */
+#define hdrchkData(_nbytes) ((_nbytes) & 0xff000000)
+
+/**
+ * Sanity check on alignment for data type.
+ */
+#define hdrchkAlign(_type, _off) ((_off) & (typeAlign[_type]-1))
+
+/**
+ * Sanity check on range of data offset.
+ */
+#define hdrchkRange(_dl, _off) ((_off) < 0 || (_off) > (_dl))
+
+Header headerLink(Header h)
+{
+ if (h == NULL) return NULL;
+
+ h->nrefs++;
+if (_hdr_debug)
+fprintf(stderr, "--> h %p ++ %d at %s:%u\n", h, h->nrefs, __FILE__, __LINE__);
+
+ return h;
+}
+
+Header headerUnlink(Header h)
+{
+ if (h == NULL) return NULL;
+if (_hdr_debug)
+fprintf(stderr, "--> h %p -- %d at %s:%u\n", h, h->nrefs, __FILE__, __LINE__);
+ h->nrefs--;
+ return NULL;
+}
+
+Header headerFree(Header h)
+{
+ (void) headerUnlink(h);
+
+ if (h == NULL || h->nrefs > 0)
+ return NULL; /* XXX return previous header? */
+
+ if (h->index) {
+ indexEntry entry = h->index;
+ int i;
+ for (i = 0; i < h->indexUsed; i++, entry++) {
+ if ((h->flags & HEADERFLAG_ALLOCATED) && ENTRY_IS_REGION(entry)) {
+ if (entry->length > 0) {
+ int32_t * ei = entry->data;
+ if ((ei - 2) == h->blob) h->blob = _free(h->blob);
+ entry->data = NULL;
+ }
+ } else if (!ENTRY_IN_REGION(entry)) {
+ entry->data = _free(entry->data);
+ }
+ entry->data = NULL;
+ }
+ h->index = _free(h->index);
+ }
+
+ h = _free(h);
+ return h;
+}
+
+Header headerNew(void)
+{
+ Header h = xcalloc(1, sizeof(*h));
+
+ h->blob = NULL;
+ h->indexAlloced = INDEX_MALLOC_SIZE;
+ h->indexUsed = 0;
+ h->flags |= HEADERFLAG_SORTED;
+
+ h->index = (h->indexAlloced
+ ? xcalloc(h->indexAlloced, sizeof(*h->index))
+ : NULL);
+
+ h->nrefs = 0;
+ return headerLink(h);
+}
+
+/**
+ */
+static int indexCmp(const void * avp, const void * bvp)
+{
+ indexEntry ap = (indexEntry) avp, bp = (indexEntry) bvp;
+ return (ap->info.tag - bp->info.tag);
+}
+
+void headerSort(Header h)
+{
+ if (!(h->flags & HEADERFLAG_SORTED)) {
+ qsort(h->index, h->indexUsed, sizeof(*h->index), indexCmp);
+ h->flags |= HEADERFLAG_SORTED;
+ }
+}
+
+/**
+ */
+static int offsetCmp(const void * avp, const void * bvp)
+{
+ indexEntry ap = (indexEntry) avp, bp = (indexEntry) bvp;
+ int rc = (ap->info.offset - bp->info.offset);
+
+ if (rc == 0) {
+ /* Within a region, entries sort by address. Added drips sort by tag. */
+ if (ap->info.offset < 0)
+ rc = (((char *)ap->data) - ((char *)bp->data));
+ else
+ rc = (ap->info.tag - bp->info.tag);
+ }
+ return rc;
+}
+
+/** \ingroup header
+ * Restore tags in header to original ordering.
+ * @param h header
+ */
+void headerUnsort(Header h)
+{
+ qsort(h->index, h->indexUsed, sizeof(*h->index), offsetCmp);
+}
+
+unsigned headerSizeof(Header h, enum hMagic magicp)
+{
+ indexEntry entry;
+ unsigned int size = 0;
+ unsigned int pad = 0;
+ int i;
+
+ if (h == NULL)
+ return size;
+
+ headerSort(h);
+
+ switch (magicp) {
+ case HEADER_MAGIC_YES:
+ size += sizeof(header_magic);
+ break;
+ case HEADER_MAGIC_NO:
+ break;
+ }
+
+ size += 2 * sizeof(int32_t); /* count of index entries */
+
+ for (i = 0, entry = h->index; i < h->indexUsed; i++, entry++) {
+ unsigned diff;
+ rpmTagType type;
+
+ /* Regions go in as is ... */
+ if (ENTRY_IS_REGION(entry)) {
+ size += entry->length;
+ /* XXX Legacy regions do not include the region tag and data. */
+ if (i == 0 && (h->flags & HEADERFLAG_LEGACY))
+ size += sizeof(struct entryInfo_s) + entry->info.count;
+ continue;
+ }
+
+ /* ... and region elements are skipped. */
+ if (entry->info.offset < 0)
+ continue;
+
+ /* Alignment */
+ type = entry->info.type;
+ if (typeSizes[type] > 1) {
+ diff = typeSizes[type] - (size % typeSizes[type]);
+ if (diff != typeSizes[type]) {
+ size += diff;
+ pad += diff;
+ }
+ }
+
+ size += sizeof(struct entryInfo_s) + entry->length;
+ }
+
+ return size;
+}
+
+/**
+ * Return length of entry data.
+ * @param type entry data type
+ * @param p entry data
+ * @param count entry item count
+ * @param onDisk data is concatenated strings (with NUL's))?
+ * @param pend pointer to end of data (or NULL)
+ * @return no. bytes in data, -1 on failure
+ */
+static int dataLength(rpmTagType type, rpm_constdata_t p, rpm_count_t count,
+ int onDisk, rpm_constdata_t pend)
+{
+ const unsigned char * s = p;
+ const unsigned char * se = pend;
+ int length = 0;
+
+ switch (type) {
+ case RPM_STRING_TYPE:
+ if (count != 1)
+ return -1;
+ while (*s++) {
+ if (se && s > se)
+ return -1;
+ length++;
+ }
+ length++; /* count nul terminator too. */
+ break;
+
+ case RPM_STRING_ARRAY_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ /* These are like RPM_STRING_TYPE, except they're *always* an array */
+ /* Compute sum of length of all strings, including nul terminators */
+
+ if (onDisk) {
+ while (count--) {
+ length++; /* count nul terminator too */
+ while (*s++) {
+ if (se && s > se)
+ return -1;
+ length++;
+ }
+ }
+ } else {
+ const char ** av = (const char **)p;
+ while (count--) {
+ /* add one for null termination */
+ length += strlen(*av++) + 1;
+ }
+ }
+ break;
+
+ default:
+ if (typeSizes[type] == -1)
+ return -1;
+ length = typeSizes[(type & 0xf)] * count;
+ if (length < 0 || (se && (s + length) > se))
+ return -1;
+ break;
+ }
+
+ return length;
+}
+
+/** \ingroup header
+ * Swap int32_t and int16_t arrays within header region.
+ *
+ * This code is way more twisty than I would like.
+ *
+ * A bug with RPM_I18NSTRING_TYPE in rpm-2.5.x (fixed in August 1998)
+ * causes the offset and length of elements in a header region to disagree
+ * regarding the total length of the region data.
+ *
+ * The "fix" is to compute the size using both offset and length and
+ * return the larger of the two numbers as the size of the region.
+ * Kinda like computing left and right Riemann sums of the data elements
+ * to determine the size of a data structure, go figger :-).
+ *
+ * There's one other twist if a header region tag is in the set to be swabbed,
+ * as the data for a header region is located after all other tag data.
+ *
+ * @param entry header entry
+ * @param il no. of entries
+ * @param dl start no. bytes of data
+ * @param pe header physical entry pointer (swapped)
+ * @param dataStart header data start
+ * @param dataEnd header data end
+ * @param regionid region offset
+ * @return no. bytes of data in region, -1 on error
+ */
+static int regionSwab(indexEntry entry, int il, int dl,
+ entryInfo pe,
+ unsigned char * dataStart,
+ const unsigned char * dataEnd,
+ int regionid)
+{
+ unsigned char * tprev = NULL;
+ unsigned char * t = NULL;
+ int tdel = 0;
+ int tl = dl;
+ struct indexEntry_s ieprev;
+
+ memset(&ieprev, 0, sizeof(ieprev));
+ for (; il > 0; il--, pe++) {
+ struct indexEntry_s ie;
+ rpmTagType type;
+
+ ie.info.tag = ntohl(pe->tag);
+ ie.info.type = ntohl(pe->type);
+ ie.info.count = ntohl(pe->count);
+ ie.info.offset = ntohl(pe->offset);
+
+ if (hdrchkType(ie.info.type))
+ return -1;
+ if (hdrchkData(ie.info.count))
+ return -1;
+ if (hdrchkData(ie.info.offset))
+ return -1;
+ if (hdrchkAlign(ie.info.type, ie.info.offset))
+ return -1;
+
+ ie.data = t = dataStart + ie.info.offset;
+ if (dataEnd && t >= dataEnd)
+ return -1;
+
+ ie.length = dataLength(ie.info.type, ie.data, ie.info.count, 1, dataEnd);
+ if (ie.length < 0 || hdrchkData(ie.length))
+ return -1;
+
+ ie.rdlen = 0;
+
+ if (entry) {
+ ie.info.offset = regionid;
+ *entry = ie; /* structure assignment */
+ entry++;
+ }
+
+ /* Alignment */
+ type = ie.info.type;
+ if (typeSizes[type] > 1) {
+ unsigned diff;
+ diff = typeSizes[type] - (dl % typeSizes[type]);
+ if (diff != typeSizes[type]) {
+ dl += diff;
+ if (ieprev.info.type == RPM_I18NSTRING_TYPE)
+ ieprev.length += diff;
+ }
+ }
+ tdel = (tprev ? (t - tprev) : 0);
+ if (ieprev.info.type == RPM_I18NSTRING_TYPE)
+ tdel = ieprev.length;
+
+ if (ie.info.tag >= HEADER_I18NTABLE) {
+ tprev = t;
+ } else {
+ tprev = dataStart;
+ /* XXX HEADER_IMAGE tags don't include region sub-tag. */
+ if (ie.info.tag == HEADER_IMAGE)
+ tprev -= REGION_TAG_COUNT;
+ }
+
+ /* Perform endian conversions */
+ switch (ntohl(pe->type)) {
+ case RPM_INT32_TYPE:
+ { int32_t * it = (int32_t *)t;
+ for (; ie.info.count > 0; ie.info.count--, it += 1) {
+ if (dataEnd && ((unsigned char *)it) >= dataEnd)
+ return -1;
+ *it = htonl(*it);
+ }
+ t = (unsigned char *) it;
+ } break;
+ case RPM_INT16_TYPE:
+ { int16_t * it = (int16_t *) t;
+ for (; ie.info.count > 0; ie.info.count--, it += 1) {
+ if (dataEnd && ((unsigned char *)it) >= dataEnd)
+ return -1;
+ *it = htons(*it);
+ }
+ t = (unsigned char *) it;
+ } break;
+ default:
+ t += ie.length;
+ break;
+ }
+
+ dl += ie.length;
+ tl += tdel;
+ ieprev = ie; /* structure assignment */
+
+ }
+ tdel = (tprev ? (t - tprev) : 0);
+ tl += tdel;
+
+ /* XXX
+ * There are two hacks here:
+ * 1) tl is 16b (i.e. REGION_TAG_COUNT) short while doing headerReload().
+ * 2) the 8/98 rpm bug with inserting i18n tags needs to use tl, not dl.
+ */
+ if (tl+REGION_TAG_COUNT == dl)
+ tl += REGION_TAG_COUNT;
+
+ return dl;
+}
+
+/** \ingroup header
+ * doHeaderUnload.
+ * @param h header
+ * @retval *lengthPtr no. bytes in unloaded header blob
+ * @return unloaded header blob (NULL on error)
+ */
+static void * doHeaderUnload(Header h,
+ size_t * lengthPtr)
+{
+ int32_t * ei = NULL;
+ entryInfo pe;
+ char * dataStart;
+ char * te;
+ unsigned pad;
+ unsigned len;
+ int32_t il = 0;
+ int32_t dl = 0;
+ indexEntry entry;
+ rpmTagType type;
+ int i;
+ int drlen, ndribbles;
+ int driplen, ndrips;
+ int legacy = 0;
+
+ /* Sort entries by (offset,tag). */
+ headerUnsort(h);
+
+ /* Compute (il,dl) for all tags, including those deleted in region. */
+ pad = 0;
+ drlen = ndribbles = driplen = ndrips = 0;
+ for (i = 0, entry = h->index; i < h->indexUsed; i++, entry++) {
+ if (ENTRY_IS_REGION(entry)) {
+ int32_t rdl = -entry->info.offset; /* negative offset */
+ int32_t ril = rdl/sizeof(*pe);
+ int rid = entry->info.offset;
+
+ il += ril;
+ dl += entry->rdlen + entry->info.count;
+ /* XXX Legacy regions do not include the region tag and data. */
+ if (i == 0 && (h->flags & HEADERFLAG_LEGACY))
+ il += 1;
+
+ /* Skip rest of entries in region, but account for dribbles. */
+ for (; i < h->indexUsed && entry->info.offset <= rid+1; i++, entry++) {
+ if (entry->info.offset <= rid)
+ continue;
+
+ /* Alignment */
+ type = entry->info.type;
+ if (typeSizes[type] > 1) {
+ unsigned diff;
+ diff = typeSizes[type] - (dl % typeSizes[type]);
+ if (diff != typeSizes[type]) {
+ drlen += diff;
+ pad += diff;
+ dl += diff;
+ }
+ }
+
+ ndribbles++;
+ il++;
+ drlen += entry->length;
+ dl += entry->length;
+ }
+ i--;
+ entry--;
+ continue;
+ }
+
+ /* Ignore deleted drips. */
+ if (entry->data == NULL || entry->length <= 0)
+ continue;
+
+ /* Alignment */
+ type = entry->info.type;
+ if (typeSizes[type] > 1) {
+ unsigned diff;
+ diff = typeSizes[type] - (dl % typeSizes[type]);
+ if (diff != typeSizes[type]) {
+ driplen += diff;
+ pad += diff;
+ dl += diff;
+ } else
+ diff = 0;
+ }
+
+ ndrips++;
+ il++;
+ driplen += entry->length;
+ dl += entry->length;
+ }
+
+ /* Sanity checks on header intro. */
+ if (hdrchkTags(il) || hdrchkData(dl))
+ goto errxit;
+
+ len = sizeof(il) + sizeof(dl) + (il * sizeof(*pe)) + dl;
+
+ ei = xmalloc(len);
+ ei[0] = htonl(il);
+ ei[1] = htonl(dl);
+
+ pe = (entryInfo) &ei[2];
+ dataStart = te = (char *) (pe + il);
+
+ pad = 0;
+ for (i = 0, entry = h->index; i < h->indexUsed; i++, entry++) {
+ const char * src;
+ unsigned char *t;
+ int count;
+ int rdlen;
+
+ if (entry->data == NULL || entry->length <= 0)
+ continue;
+
+ t = (unsigned char*)te;
+ pe->tag = htonl(entry->info.tag);
+ pe->type = htonl(entry->info.type);
+ pe->count = htonl(entry->info.count);
+
+ if (ENTRY_IS_REGION(entry)) {
+ int32_t rdl = -entry->info.offset; /* negative offset */
+ int32_t ril = rdl/sizeof(*pe) + ndribbles;
+ int rid = entry->info.offset;
+
+ src = (char *)entry->data;
+ rdlen = entry->rdlen;
+
+ /* XXX Legacy regions do not include the region tag and data. */
+ if (i == 0 && (h->flags & HEADERFLAG_LEGACY)) {
+ int32_t stei[4];
+
+ legacy = 1;
+ memcpy(pe+1, src, rdl);
+ memcpy(te, src + rdl, rdlen);
+ te += rdlen;
+
+ pe->offset = htonl(te - dataStart);
+ stei[0] = pe->tag;
+ stei[1] = pe->type;
+ stei[2] = htonl(-rdl-entry->info.count);
+ stei[3] = pe->count;
+ memcpy(te, stei, entry->info.count);
+ te += entry->info.count;
+ ril++;
+ rdlen += entry->info.count;
+
+ count = regionSwab(NULL, ril, 0, pe, t, NULL, 0);
+ if (count != rdlen)
+ goto errxit;
+
+ } else {
+
+ memcpy(pe+1, src + sizeof(*pe), ((ril-1) * sizeof(*pe)));
+ memcpy(te, src + (ril * sizeof(*pe)), rdlen+entry->info.count+drlen);
+ te += rdlen;
+ {
+ entryInfo se = (entryInfo)src;
+ int off = ntohl(se->offset);
+ pe->offset = (off) ? htonl(te - dataStart) : htonl(off);
+ }
+ te += entry->info.count + drlen;
+
+ count = regionSwab(NULL, ril, 0, pe, t, NULL, 0);
+ if (count != (rdlen + entry->info.count + drlen))
+ goto errxit;
+ }
+
+ /* Skip rest of entries in region. */
+ while (i < h->indexUsed && entry->info.offset <= rid+1) {
+ i++;
+ entry++;
+ }
+ i--;
+ entry--;
+ pe += ril;
+ continue;
+ }
+
+ /* Ignore deleted drips. */
+ if (entry->data == NULL || entry->length <= 0)
+ continue;
+
+ /* Alignment */
+ type = entry->info.type;
+ if (typeSizes[type] > 1) {
+ unsigned diff;
+ diff = typeSizes[type] - ((te - dataStart) % typeSizes[type]);
+ if (diff != typeSizes[type]) {
+ memset(te, 0, diff);
+ te += diff;
+ pad += diff;
+ }
+ }
+
+ pe->offset = htonl(te - dataStart);
+
+ /* copy data w/ endian conversions */
+ switch (entry->info.type) {
+ case RPM_INT32_TYPE:
+ count = entry->info.count;
+ src = entry->data;
+ while (count--) {
+ *((int32_t *)te) = htonl(*((int32_t *)src));
+ te += sizeof(int32_t);
+ src += sizeof(int32_t);
+ }
+ break;
+
+ case RPM_INT16_TYPE:
+ count = entry->info.count;
+ src = entry->data;
+ while (count--) {
+ *((int16_t *)te) = htons(*((int16_t *)src));
+ te += sizeof(int16_t);
+ src += sizeof(int16_t);
+ }
+ break;
+
+ default:
+ memcpy(te, entry->data, entry->length);
+ te += entry->length;
+ break;
+ }
+ pe++;
+ }
+
+ /* Insure that there are no memcpy underruns/overruns. */
+ if (((char *)pe) != dataStart)
+ goto errxit;
+ if ((((char *)ei)+len) != te)
+ goto errxit;
+
+ if (lengthPtr)
+ *lengthPtr = len;
+
+ h->flags &= ~HEADERFLAG_SORTED;
+ headerSort(h);
+
+ return (void *) ei;
+
+errxit:
+ ei = _free(ei);
+ return (void *) ei;
+}
+
+void * headerUnload(Header h)
+{
+ size_t length;
+ void * uh = doHeaderUnload(h, &length);
+ return uh;
+}
+
+/**
+ * Find matching (tag,type) entry in header.
+ * @param h header
+ * @param tag entry tag
+ * @param type entry type
+ * @return header entry
+ */
+static
+indexEntry findEntry(Header h, rpmTag tag, rpmTagType type)
+{
+ indexEntry entry, entry2, last;
+ struct indexEntry_s key;
+
+ if (h == NULL) return NULL;
+ if (!(h->flags & HEADERFLAG_SORTED)) headerSort(h);
+
+ key.info.tag = tag;
+
+ entry2 = entry =
+ bsearch(&key, h->index, h->indexUsed, sizeof(*h->index), indexCmp);
+ if (entry == NULL)
+ return NULL;
+
+ if (type == RPM_NULL_TYPE)
+ return entry;
+
+ /* look backwards */
+ while (entry->info.tag == tag && entry->info.type != type &&
+ entry > h->index) entry--;
+
+ if (entry->info.tag == tag && entry->info.type == type)
+ return entry;
+
+ last = h->index + h->indexUsed;
+ /* FIX: entry2 = entry. Code looks bogus as well. */
+ while (entry2->info.tag == tag && entry2->info.type != type &&
+ entry2 < last) entry2++;
+
+ if (entry->info.tag == tag && entry->info.type == type)
+ return entry;
+
+ return NULL;
+}
+
+int headerRemoveEntry(Header h, rpmTag tag)
+{
+ indexEntry last = h->index + h->indexUsed;
+ indexEntry entry, first;
+ int ne;
+
+ entry = findEntry(h, tag, RPM_NULL_TYPE);
+ if (!entry) return 1;
+
+ /* Make sure entry points to the first occurence of this tag. */
+ while (entry > h->index && (entry - 1)->info.tag == tag)
+ entry--;
+
+ /* Free data for tags being removed. */
+ for (first = entry; first < last; first++) {
+ rpm_data_t data;
+ if (first->info.tag != tag)
+ break;
+ data = first->data;
+ first->data = NULL;
+ first->length = 0;
+ if (ENTRY_IN_REGION(first))
+ continue;
+ data = _free(data);
+ }
+
+ ne = (first - entry);
+ if (ne > 0) {
+ h->indexUsed -= ne;
+ ne = last - first;
+ if (ne > 0)
+ memmove(entry, first, (ne * sizeof(*entry)));
+ }
+
+ return 0;
+}
+
+Header headerLoad(void * uh)
+{
+ int32_t * ei = (int32_t *) uh;
+ int32_t il = ntohl(ei[0]); /* index length */
+ int32_t dl = ntohl(ei[1]); /* data length */
+ size_t pvlen = sizeof(il) + sizeof(dl) +
+ (il * sizeof(struct entryInfo_s)) + dl;
+ void * pv = uh;
+ Header h = NULL;
+ entryInfo pe;
+ unsigned char * dataStart;
+ unsigned char * dataEnd;
+ indexEntry entry;
+ int rdlen;
+ int i;
+
+ /* Sanity checks on header intro. */
+ if (hdrchkTags(il) || hdrchkData(dl))
+ goto errxit;
+
+ ei = (int32_t *) pv;
+ pe = (entryInfo) &ei[2];
+ dataStart = (unsigned char *) (pe + il);
+ dataEnd = dataStart + dl;
+
+ h = xcalloc(1, sizeof(*h));
+ h->blob = uh;
+ h->indexAlloced = il + 1;
+ h->indexUsed = il;
+ h->index = xcalloc(h->indexAlloced, sizeof(*h->index));
+ h->flags |= HEADERFLAG_SORTED;
+ h->nrefs = 0;
+ h = headerLink(h);
+
+ entry = h->index;
+ i = 0;
+ if (!(htonl(pe->tag) < HEADER_I18NTABLE)) {
+ h->flags |= HEADERFLAG_LEGACY;
+ entry->info.type = REGION_TAG_TYPE;
+ entry->info.tag = HEADER_IMAGE;
+ entry->info.count = REGION_TAG_COUNT;
+ entry->info.offset = ((unsigned char *)pe - dataStart); /* negative offset */
+
+ entry->data = pe;
+ entry->length = pvlen - sizeof(il) - sizeof(dl);
+ rdlen = regionSwab(entry+1, il, 0, pe, dataStart, dataEnd, entry->info.offset);
+#if 0 /* XXX don't check, the 8/98 i18n bug fails here. */
+ if (rdlen != dl)
+ goto errxit;
+#endif
+ entry->rdlen = rdlen;
+ entry++;
+ h->indexUsed++;
+ } else {
+ int32_t rdl;
+ int32_t ril;
+
+ h->flags &= ~HEADERFLAG_LEGACY;
+
+ entry->info.type = htonl(pe->type);
+ entry->info.count = htonl(pe->count);
+
+ if (hdrchkType(entry->info.type))
+ goto errxit;
+ if (hdrchkTags(entry->info.count))
+ goto errxit;
+
+ { int off = ntohl(pe->offset);
+
+ if (hdrchkData(off))
+ goto errxit;
+ if (off) {
+ size_t nb = REGION_TAG_COUNT;
+ int32_t stei[nb];
+ /* XXX Hmm, why the copy? */
+ memcpy(&stei, dataStart + off, nb);
+ rdl = -ntohl(stei[2]); /* negative offset */
+ ril = rdl/sizeof(*pe);
+ if (hdrchkTags(ril) || hdrchkData(rdl))
+ goto errxit;
+ entry->info.tag = htonl(pe->tag);
+ } else {
+ ril = il;
+ rdl = (ril * sizeof(struct entryInfo_s));
+ entry->info.tag = HEADER_IMAGE;
+ }
+ }
+ entry->info.offset = -rdl; /* negative offset */
+
+ entry->data = pe;
+ entry->length = pvlen - sizeof(il) - sizeof(dl);
+ rdlen = regionSwab(entry+1, ril-1, 0, pe+1, dataStart, dataEnd, entry->info.offset);
+ if (rdlen < 0)
+ goto errxit;
+ entry->rdlen = rdlen;
+
+ if (ril < h->indexUsed) {
+ indexEntry newEntry = entry + ril;
+ int ne = (h->indexUsed - ril);
+ int rid = entry->info.offset+1;
+ int rc;
+
+ /* Load dribble entries from region. */
+ rc = regionSwab(newEntry, ne, 0, pe+ril, dataStart, dataEnd, rid);
+ if (rc < 0)
+ goto errxit;
+ rdlen += rc;
+
+ { indexEntry firstEntry = newEntry;
+ int save = h->indexUsed;
+ int j;
+
+ /* Dribble entries replace duplicate region entries. */
+ h->indexUsed -= ne;
+ for (j = 0; j < ne; j++, newEntry++) {
+ (void) headerRemoveEntry(h, newEntry->info.tag);
+ if (newEntry->info.tag == HEADER_BASENAMES)
+ (void) headerRemoveEntry(h, HEADER_OLDFILENAMES);
+ }
+
+ /* If any duplicate entries were replaced, move new entries down. */
+ if (h->indexUsed < (save - ne)) {
+ memmove(h->index + h->indexUsed, firstEntry,
+ (ne * sizeof(*entry)));
+ }
+ h->indexUsed += ne;
+ }
+ }
+ }
+
+ h->flags &= ~HEADERFLAG_SORTED;
+ headerSort(h);
+
+ return h;
+
+errxit:
+ if (h) {
+ h->index = _free(h->index);
+ h = _free(h);
+ }
+ return h;
+}
+
+Header headerReload(Header h, rpmTag tag)
+{
+ Header nh;
+ size_t length;
+ void * uh = doHeaderUnload(h, &length);
+
+ h = headerFree(h);
+ if (uh == NULL)
+ return NULL;
+ nh = headerLoad(uh);
+ if (nh == NULL) {
+ uh = _free(uh);
+ return NULL;
+ }
+ if (nh->flags & HEADERFLAG_ALLOCATED)
+ uh = _free(uh);
+ nh->flags |= HEADERFLAG_ALLOCATED;
+ if (ENTRY_IS_REGION(nh->index)) {
+ if (tag == HEADER_SIGNATURES || tag == HEADER_IMMUTABLE)
+ nh->index[0].info.tag = tag;
+ }
+ return nh;
+}
+
+Header headerCopyLoad(const void * uh)
+{
+ int32_t * ei = (int32_t *) uh;
+ int32_t il = ntohl(ei[0]); /* index length */
+ int32_t dl = ntohl(ei[1]); /* data length */
+ size_t pvlen = sizeof(il) + sizeof(dl) +
+ (il * sizeof(struct entryInfo_s)) + dl;
+ void * nuh = NULL;
+ Header h = NULL;
+
+ /* Sanity checks on header intro. */
+ if (!(hdrchkTags(il) || hdrchkData(dl)) && pvlen < headerMaxbytes) {
+ nuh = memcpy(xmalloc(pvlen), uh, pvlen);
+ if ((h = headerLoad(nuh)) != NULL)
+ h->flags |= HEADERFLAG_ALLOCATED;
+ }
+ if (h == NULL)
+ nuh = _free(nuh);
+ return h;
+}
+
+/** \ingroup header
+ * Read (and load) header from file handle.
+ * @param fd file handle
+ * @param magicp read (and verify) 8 bytes of (magic, 0)?
+ * @return header (or NULL on error)
+ */
+Header headerRead(FD_t fd, enum hMagic magicp)
+{
+ int32_t block[4];
+ int32_t reserved;
+ int32_t * ei = NULL;
+ int32_t il;
+ int32_t dl;
+ int32_t magic;
+ Header h = NULL;
+ size_t len;
+ int i;
+
+ memset(block, 0, sizeof(block));
+ i = 2;
+ if (magicp == HEADER_MAGIC_YES)
+ i += 2;
+
+ /* FIX: cast? */
+ if (timedRead(fd, (char *)block, i*sizeof(*block)) != (i * sizeof(*block)))
+ goto exit;
+
+ i = 0;
+
+ if (magicp == HEADER_MAGIC_YES) {
+ magic = block[i++];
+ if (memcmp(&magic, header_magic, sizeof(magic)))
+ goto exit;
+ reserved = block[i++];
+ }
+
+ il = ntohl(block[i]); i++;
+ dl = ntohl(block[i]); i++;
+
+ len = sizeof(il) + sizeof(dl) + (il * sizeof(struct entryInfo_s)) + dl;
+
+ /* Sanity checks on header intro. */
+ if (hdrchkTags(il) || hdrchkData(dl) || len > headerMaxbytes)
+ goto exit;
+
+ ei = xmalloc(len);
+ ei[0] = htonl(il);
+ ei[1] = htonl(dl);
+ len -= sizeof(il) + sizeof(dl);
+
+ /* FIX: cast? */
+ if (timedRead(fd, (char *)&ei[2], len) != len)
+ goto exit;
+
+ h = headerLoad(ei);
+
+exit:
+ if (h) {
+ if (h->flags & HEADERFLAG_ALLOCATED)
+ ei = _free(ei);
+ h->flags |= HEADERFLAG_ALLOCATED;
+ } else if (ei)
+ ei = _free(ei);
+ /* FIX: timedRead macro obscures annotation */
+ return h;
+}
+
+int headerWrite(FD_t fd, Header h, enum hMagic magicp)
+{
+ ssize_t nb;
+ size_t length;
+ void * uh;
+
+ if (h == NULL)
+ return 1;
+ uh = doHeaderUnload(h, &length);
+ if (uh == NULL)
+ return 1;
+ switch (magicp) {
+ case HEADER_MAGIC_YES:
+ nb = Fwrite(header_magic, sizeof(char), sizeof(header_magic), fd);
+ if (nb != sizeof(header_magic))
+ goto exit;
+ break;
+ case HEADER_MAGIC_NO:
+ break;
+ }
+
+ nb = Fwrite(uh, sizeof(char), length, fd);
+
+exit:
+ uh = _free(uh);
+ return (nb == length ? 0 : 1);
+}
+
+int headerIsEntry(Header h, rpmTag tag)
+{
+ /* FIX: h modified by sort. */
+ return (findEntry(h, tag, RPM_NULL_TYPE) ? 1 : 0);
+
+}
+
+/** \ingroup header
+ * Retrieve data from header entry.
+ * @todo Permit retrieval of regions other than HEADER_IMUTABLE.
+ * @param entry header entry
+ * @retval type address of type (or NULL)
+ * @retval p address of data (or NULL)
+ * @retval c address of count (or NULL)
+ * @param minMem string pointers refer to header memory?
+ * @return 1 on success, otherwise error.
+ */
+static int copyEntry(const indexEntry entry,
+ rpmTagType * type,
+ rpm_data_t * p,
+ rpm_count_t * c,
+ int minMem)
+{
+ rpm_count_t count = entry->info.count;
+ int rc = 1; /* XXX 1 on success. */
+
+ if (p)
+ switch (entry->info.type) {
+ case RPM_BIN_TYPE:
+ /*
+ * XXX This only works for
+ * XXX "sealed" HEADER_IMMUTABLE/HEADER_SIGNATURES/HEADER_IMAGE.
+ * XXX This will *not* work for unsealed legacy HEADER_IMAGE (i.e.
+ * XXX a legacy header freshly read, but not yet unloaded to the rpmdb).
+ */
+ if (ENTRY_IS_REGION(entry)) {
+ int32_t * ei = ((int32_t *)entry->data) - 2;
+ entryInfo pe = (entryInfo) (ei + 2);
+ unsigned char * dataStart = (unsigned char *) (pe + ntohl(ei[0]));
+ int32_t rdl = -entry->info.offset; /* negative offset */
+ int32_t ril = rdl/sizeof(*pe);
+
+ rdl = entry->rdlen;
+ count = 2 * sizeof(*ei) + (ril * sizeof(*pe)) + rdl;
+ if (entry->info.tag == HEADER_IMAGE) {
+ ril -= 1;
+ pe += 1;
+ } else {
+ count += REGION_TAG_COUNT;
+ rdl += REGION_TAG_COUNT;
+ }
+
+ *p = xmalloc(count);
+ ei = (int32_t *) *p;
+ ei[0] = htonl(ril);
+ ei[1] = htonl(rdl);
+
+ pe = (entryInfo) memcpy(ei + 2, pe, (ril * sizeof(*pe)));
+
+ dataStart = (unsigned char *) memcpy(pe + ril, dataStart, rdl);
+
+ rc = regionSwab(NULL, ril, 0, pe, dataStart, dataStart + rdl, 0);
+ /* XXX 1 on success. */
+ rc = (rc < 0) ? 0 : 1;
+ } else {
+ count = entry->length;
+ *p = (!minMem
+ ? memcpy(xmalloc(count), entry->data, count)
+ : entry->data);
+ }
+ break;
+ case RPM_STRING_TYPE:
+ if (count == 1) {
+ *p = entry->data;
+ break;
+ }
+ case RPM_STRING_ARRAY_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ { const char ** ptrEntry;
+ int tableSize = count * sizeof(char *);
+ char * t;
+ int i;
+
+ if (minMem) {
+ *p = xmalloc(tableSize);
+ ptrEntry = (const char **) *p;
+ t = entry->data;
+ } else {
+ t = xmalloc(tableSize + entry->length);
+ *p = (void *)t;
+ ptrEntry = (const char **) *p;
+ t += tableSize;
+ memcpy(t, entry->data, entry->length);
+ }
+ for (i = 0; i < count; i++) {
+ *ptrEntry++ = t;
+ t = strchr(t, 0);
+ t++;
+ }
+ } break;
+
+ default:
+ *p = entry->data;
+ break;
+ }
+ if (type) *type = entry->info.type;
+ if (c) *c = count;
+ return rc;
+}
+
+/**
+ * Does locale match entry in header i18n table?
+ *
+ * \verbatim
+ * The range [l,le) contains the next locale to match:
+ * ll[_CC][.EEEEE][@dddd]
+ * where
+ * ll ISO language code (in lowercase).
+ * CC (optional) ISO coutnry code (in uppercase).
+ * EEEEE (optional) encoding (not really standardized).
+ * dddd (optional) dialect.
+ * \endverbatim
+ *
+ * @param td header i18n table data, NUL terminated
+ * @param l start of locale to match
+ * @param le end of locale to match
+ * @return 1 on good match, 2 on weak match, 0 on no match
+ */
+static int headerMatchLocale(const char *td, const char *l, const char *le)
+{
+ const char *fe;
+
+ /* First try a complete match. */
+ if (strlen(td) == (le-l) && !strncmp(td, l, (le - l)))
+ return 1;
+
+ /* Next, try stripping optional dialect and matching. */
+ for (fe = l; fe < le && *fe != '@'; fe++)
+ {};
+ if (fe < le && !strncmp(td, l, (fe - l)))
+ return 1;
+
+ /* Next, try stripping optional codeset and matching. */
+ for (fe = l; fe < le && *fe != '.'; fe++)
+ {};
+ if (fe < le && !strncmp(td, l, (fe - l)))
+ return 1;
+
+ /* Finally, try stripping optional country code and matching. */
+ for (fe = l; fe < le && *fe != '_'; fe++)
+ {};
+ if (fe < le && !strncmp(td, l, (fe - l)))
+ return 2;
+
+ return 0;
+}
+
+/**
+ * Return i18n string from header that matches locale.
+ * @param h header
+ * @param entry i18n string data
+ * @return matching i18n string (or 1st string if no match)
+ */
+static char *
+headerFindI18NString(Header h, indexEntry entry)
+{
+ const char *lang, *l, *le;
+ indexEntry table;
+
+ /* XXX Drepper sez' this is the order. */
+ if ((lang = getenv("LANGUAGE")) == NULL &&
+ (lang = getenv("LC_ALL")) == NULL &&
+ (lang = getenv("LC_MESSAGES")) == NULL &&
+ (lang = getenv("LANG")) == NULL)
+ return entry->data;
+
+ if ((table = findEntry(h, HEADER_I18NTABLE, RPM_STRING_ARRAY_TYPE)) == NULL)
+ return entry->data;
+
+ for (l = lang; *l != '\0'; l = le) {
+ const char *td;
+ char *ed, *ed_weak = NULL;
+ int langNum;
+
+ while (*l && *l == ':') /* skip leading colons */
+ l++;
+ if (*l == '\0')
+ break;
+ for (le = l; *le && *le != ':'; le++) /* find end of this locale */
+ {};
+
+ /* For each entry in the header ... */
+ for (langNum = 0, td = table->data, ed = entry->data;
+ langNum < entry->info.count;
+ langNum++, td += strlen(td) + 1, ed += strlen(ed) + 1) {
+
+ int match = headerMatchLocale(td, l, le);
+ if (match == 1) return ed;
+ else if (match == 2) ed_weak = ed;
+
+ }
+ if (ed_weak) return ed_weak;
+ }
+
+ return entry->data;
+}
+
+/**
+ * Retrieve tag data from header.
+ * @param h header
+ * @param tag tag to retrieve
+ * @retval type address of type (or NULL)
+ * @retval p address of data (or NULL)
+ * @retval c address of count (or NULL)
+ * @param minMem string pointers reference header memory?
+ * @return 1 on success, 0 on not found
+ */
+static int intGetEntry(Header h, rpmTag tag,
+ rpmTagType * type,
+ rpm_data_t * p,
+ rpm_count_t * c,
+ int minMem)
+{
+ indexEntry entry;
+ int rc;
+
+ /* First find the tag */
+ /* FIX: h modified by sort. */
+ entry = findEntry(h, tag, RPM_NULL_TYPE);
+ if (entry == NULL) {
+ if (type) type = 0;
+ if (p) *p = NULL;
+ if (c) *c = 0;
+ return 0;
+ }
+
+ switch (entry->info.type) {
+ case RPM_I18NSTRING_TYPE:
+ rc = 1;
+ if (type) *type = RPM_STRING_TYPE;
+ if (c) *c = 1;
+ if (p) *p = headerFindI18NString(h, entry);
+ break;
+ default:
+ rc = copyEntry(entry, type, p, c, minMem);
+ break;
+ }
+
+ /* XXX 1 on success */
+ return ((rc == 1) ? 1 : 0);
+}
+
+void * headerFreeTag(Header h, rpm_data_t data, rpmTagType type)
+{
+ if (data) {
+ if (type == RPM_FORCEFREE_TYPE ||
+ type == RPM_STRING_ARRAY_TYPE ||
+ type == RPM_I18NSTRING_TYPE ||
+ type == RPM_BIN_TYPE)
+ data = _free(data);
+ }
+ return NULL;
+}
+
+int headerGetEntry(Header h, rpmTag tag,
+ rpmTagType * type,
+ rpm_data_t * p,
+ rpm_count_t * c)
+{
+ return intGetEntry(h, tag, type, p, c, 0);
+}
+
+int headerGetEntryMinMemory(Header h, rpmTag tag,
+ rpmTagType * type,
+ rpm_data_t * p,
+ rpm_count_t * c)
+{
+ return intGetEntry(h, tag, type, (rpm_data_t) p, c, 1);
+}
+
+int headerGetRawEntry(Header h, rpmTag tag, rpmTagType * type, rpm_data_t * p,
+ rpm_count_t * c)
+{
+ indexEntry entry;
+ int rc;
+
+ if (p == NULL) return headerIsEntry(h, tag);
+
+ /* First find the tag */
+ /* FIX: h modified by sort. */
+ entry = findEntry(h, tag, RPM_NULL_TYPE);
+ if (!entry) {
+ if (p) *p = NULL;
+ if (c) *c = 0;
+ return 0;
+ }
+
+ rc = copyEntry(entry, type, p, c, 0);
+
+ /* XXX 1 on success */
+ return ((rc == 1) ? 1 : 0);
+}
+
+/**
+ */
+static void copyData(rpmTagType type, rpm_data_t dstPtr,
+ rpm_constdata_t srcPtr, rpm_count_t cnt, int dataLength)
+{
+ switch (type) {
+ case RPM_STRING_ARRAY_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ { const char ** av = (const char **) srcPtr;
+ char * t = dstPtr;
+
+ while (cnt-- > 0 && dataLength > 0) {
+ const char * s;
+ if ((s = *av++) == NULL)
+ continue;
+ do {
+ *t++ = *s++;
+ } while (s[-1] && --dataLength > 0);
+ }
+ } break;
+
+ default:
+ memmove(dstPtr, srcPtr, dataLength);
+ break;
+ }
+}
+
+/**
+ * Return (malloc'ed) copy of entry data.
+ * @param type entry data type
+ * @param p entry data
+ * @param c entry item count
+ * @retval lengthPtr no. bytes in returned data
+ * @return (malloc'ed) copy of entry data, NULL on error
+ */
+static void *
+grabData(rpmTagType type, rpm_constdata_t p, rpm_count_t c, int * lengthPtr)
+{
+ rpm_data_t data = NULL;
+ int length;
+
+ length = dataLength(type, p, c, 0, NULL);
+ if (length > 0) {
+ data = xmalloc(length);
+ copyData(type, data, p, c, length);
+ }
+
+ if (lengthPtr)
+ *lengthPtr = length;
+ return data;
+}
+
+int headerAddEntry(Header h, rpmTag tag, rpmTagType type,
+ rpm_constdata_t p, rpm_count_t c)
+{
+ indexEntry entry;
+ rpm_data_t data;
+ int length;
+
+ /* Count must always be >= 1 for headerAddEntry. */
+ if (c <= 0)
+ return 0;
+
+ if (hdrchkType(type))
+ return 0;
+ if (hdrchkData(c))
+ return 0;
+
+ length = 0;
+ data = grabData(type, p, c, &length);
+ if (data == NULL || length <= 0)
+ return 0;
+
+ /* Allocate more index space if necessary */
+ if (h->indexUsed == h->indexAlloced) {
+ h->indexAlloced += INDEX_MALLOC_SIZE;
+ h->index = xrealloc(h->index, h->indexAlloced * sizeof(*h->index));
+ }
+
+ /* Fill in the index */
+ entry = h->index + h->indexUsed;
+ entry->info.tag = tag;
+ entry->info.type = type;
+ entry->info.count = c;
+ entry->info.offset = 0;
+ entry->data = data;
+ entry->length = length;
+
+ if (h->indexUsed > 0 && tag < h->index[h->indexUsed-1].info.tag)
+ h->flags &= ~HEADERFLAG_SORTED;
+ h->indexUsed++;
+
+ return 1;
+}
+
+int headerAppendEntry(Header h, rpmTag tag, rpmTagType type,
+ rpm_constdata_t p, rpm_count_t c)
+{
+ indexEntry entry;
+ int length;
+
+ if (type == RPM_STRING_TYPE || type == RPM_I18NSTRING_TYPE) {
+ /* we can't do this */
+ return 0;
+ }
+
+ /* Find the tag entry in the header. */
+ entry = findEntry(h, tag, type);
+ if (!entry)
+ return 0;
+
+ length = dataLength(type, p, c, 0, NULL);
+ if (length < 0)
+ return 0;
+
+ if (ENTRY_IN_REGION(entry)) {
+ char * t = xmalloc(entry->length + length);
+ memcpy(t, entry->data, entry->length);
+ entry->data = t;
+ entry->info.offset = 0;
+ } else
+ entry->data = xrealloc(entry->data, entry->length + length);
+
+ copyData(type, ((char *) entry->data) + entry->length, p, c, length);
+
+ entry->length += length;
+
+ entry->info.count += c;
+
+ return 1;
+}
+
+int headerAddOrAppendEntry(Header h, rpmTag tag, rpmTagType type,
+ rpm_constdata_t p, rpm_count_t c)
+{
+ return (findEntry(h, tag, type)
+ ? headerAppendEntry(h, tag, type, p, c)
+ : headerAddEntry(h, tag, type, p, c));
+}
+
+int headerAddI18NString(Header h, rpmTag tag, const char * string,
+ const char * lang)
+{
+ indexEntry table, entry;
+ const char ** strArray;
+ int length;
+ int ghosts;
+ rpm_count_t i, langNum;
+ char * buf;
+
+ table = findEntry(h, HEADER_I18NTABLE, RPM_STRING_ARRAY_TYPE);
+ entry = findEntry(h, tag, RPM_I18NSTRING_TYPE);
+
+ if (!table && entry)
+ return 0; /* this shouldn't ever happen!! */
+
+ if (!table && !entry) {
+ const char * charArray[2];
+ rpm_count_t count = 0;
+ if (!lang || (lang[0] == 'C' && lang[1] == '\0')) {
+ charArray[count++] = "C";
+ } else {
+ charArray[count++] = "C";
+ charArray[count++] = lang;
+ }
+ if (!headerAddEntry(h, HEADER_I18NTABLE, RPM_STRING_ARRAY_TYPE,
+ &charArray, count))
+ return 0;
+ table = findEntry(h, HEADER_I18NTABLE, RPM_STRING_ARRAY_TYPE);
+ }
+
+ if (!table)
+ return 0;
+ if (!lang) lang = "C";
+
+ { const char * l = table->data;
+ for (langNum = 0; langNum < table->info.count; langNum++) {
+ if (!strcmp(l, lang)) break;
+ l += strlen(l) + 1;
+ }
+ }
+
+ if (langNum >= table->info.count) {
+ length = strlen(lang) + 1;
+ if (ENTRY_IN_REGION(table)) {
+ char * t = xmalloc(table->length + length);
+ memcpy(t, table->data, table->length);
+ table->data = t;
+ table->info.offset = 0;
+ } else
+ table->data = xrealloc(table->data, table->length + length);
+ memmove(((char *)table->data) + table->length, lang, length);
+ table->length += length;
+ table->info.count++;
+ }
+
+ if (!entry) {
+ int rc;
+ strArray = xmalloc(sizeof(*strArray) * (langNum + 1));
+ for (i = 0; i < langNum; i++)
+ strArray[i] = "";
+ strArray[langNum] = string;
+ rc = headerAddEntry(h, tag, RPM_I18NSTRING_TYPE, strArray, langNum + 1);
+ free(strArray);
+ return rc;
+ } else if (langNum >= entry->info.count) {
+ ghosts = langNum - entry->info.count;
+
+ length = strlen(string) + 1 + ghosts;
+ if (ENTRY_IN_REGION(entry)) {
+ char * t = xmalloc(entry->length + length);
+ memcpy(t, entry->data, entry->length);
+ entry->data = t;
+ entry->info.offset = 0;
+ } else
+ entry->data = xrealloc(entry->data, entry->length + length);
+
+ memset(((char *)entry->data) + entry->length, '\0', ghosts);
+ memmove(((char *)entry->data) + entry->length + ghosts, string, strlen(string)+1);
+
+ entry->length += length;
+ entry->info.count = langNum + 1;
+ } else {
+ char *b, *be, *e, *ee, *t;
+ size_t bn, sn, en;
+
+ /* Set beginning/end pointers to previous data */
+ b = be = e = ee = entry->data;
+ for (i = 0; i < table->info.count; i++) {
+ if (i == langNum)
+ be = ee;
+ ee += strlen(ee) + 1;
+ if (i == langNum)
+ e = ee;
+ }
+
+ /* Get storage for new buffer */
+ bn = (be-b);
+ sn = strlen(string) + 1;
+ en = (ee-e);
+ length = bn + sn + en;
+ t = buf = xmalloc(length);
+
+ /* Copy values into new storage */
+ memcpy(t, b, bn);
+ t += bn;
+ memcpy(t, string, sn);
+ t += sn;
+ memcpy(t, e, en);
+ t += en;
+
+ /* Replace i18N string array */
+ entry->length -= strlen(be) + 1;
+ entry->length += sn;
+
+ if (ENTRY_IN_REGION(entry)) {
+ entry->info.offset = 0;
+ } else
+ entry->data = _free(entry->data);
+ entry->data = buf;
+ }
+
+ return 0;
+}
+
+int headerModifyEntry(Header h, rpmTag tag, rpmTagType type,
+ rpm_constdata_t p, rpm_count_t c)
+{
+ indexEntry entry;
+ rpm_data_t oldData;
+ rpm_data_t data;
+ int length;
+
+ /* First find the tag */
+ entry = findEntry(h, tag, type);
+ if (!entry)
+ return 0;
+
+ length = 0;
+ data = grabData(type, p, c, &length);
+ if (data == NULL || length <= 0)
+ return 0;
+
+ /* make sure entry points to the first occurence of this tag */
+ while (entry > h->index && (entry - 1)->info.tag == tag)
+ entry--;
+
+ /* free after we've grabbed the new data in case the two are intertwined;
+ that's a bad idea but at least we won't break */
+ oldData = entry->data;
+
+ entry->info.count = c;
+ entry->info.type = type;
+ entry->data = data;
+ entry->length = length;
+
+ if (ENTRY_IN_REGION(entry)) {
+ entry->info.offset = 0;
+ } else
+ oldData = _free(oldData);
+
+ return 1;
+}
+
+/**
+ */
+static char escapedChar(const char ch)
+{
+ switch (ch) {
+ case 'a': return '\a';
+ case 'b': return '\b';
+ case 'f': return '\f';
+ case 'n': return '\n';
+ case 'r': return '\r';
+ case 't': return '\t';
+ case 'v': return '\v';
+ default: return ch;
+ }
+}
+
+/**
+ * Destroy headerSprintf format array.
+ * @param format sprintf format array
+ * @param num number of elements
+ * @return NULL always
+ */
+static sprintfToken
+freeFormat( sprintfToken format, int num)
+{
+ int i;
+
+ if (format == NULL) return NULL;
+
+ for (i = 0; i < num; i++) {
+ switch (format[i].type) {
+ case PTOK_ARRAY:
+ format[i].u.array.format =
+ freeFormat(format[i].u.array.format,
+ format[i].u.array.numTokens);
+ break;
+ case PTOK_COND:
+ format[i].u.cond.ifFormat =
+ freeFormat(format[i].u.cond.ifFormat,
+ format[i].u.cond.numIfTokens);
+ format[i].u.cond.elseFormat =
+ freeFormat(format[i].u.cond.elseFormat,
+ format[i].u.cond.numElseTokens);
+ break;
+ case PTOK_NONE:
+ case PTOK_TAG:
+ case PTOK_STRING:
+ default:
+ break;
+ }
+ }
+ format = _free(format);
+ return NULL;
+}
+
+/**
+ * Header tag iterator data structure.
+ */
+struct headerIterator_s {
+ Header h; /*!< Header being iterated. */
+ int next_index; /*!< Next tag index. */
+};
+
+HeaderIterator headerFreeIterator(HeaderIterator hi)
+{
+ if (hi != NULL) {
+ hi->h = headerFree(hi->h);
+ hi = _free(hi);
+ }
+ return hi;
+}
+
+HeaderIterator headerInitIterator(Header h)
+{
+ HeaderIterator hi = xmalloc(sizeof(*hi));
+
+ headerSort(h);
+
+ hi->h = headerLink(h);
+ hi->next_index = 0;
+ return hi;
+}
+
+int headerNextIterator(HeaderIterator hi,
+ rpmTag * tag,
+ rpmTagType * type,
+ rpm_data_t * p,
+ rpm_count_t * c)
+{
+ Header h = hi->h;
+ int slot = hi->next_index;
+ indexEntry entry = NULL;
+ int rc;
+
+ for (slot = hi->next_index; slot < h->indexUsed; slot++) {
+ entry = h->index + slot;
+ if (!ENTRY_IS_REGION(entry))
+ break;
+ }
+ hi->next_index = slot;
+ if (entry == NULL || slot >= h->indexUsed)
+ return 0;
+
+ /* LCL: no clue */
+ hi->next_index++;
+
+ if (tag)
+ *tag = entry->info.tag;
+
+ rc = copyEntry(entry, type, p, c, 0);
+
+ /* XXX 1 on success */
+ return ((rc == 1) ? 1 : 0);
+}
+
+/** \ingroup header
+ * Duplicate a header.
+ * @param h header
+ * @return new header instance
+ */
+Header headerCopy(Header h)
+{
+ Header nh = headerNew();
+ HeaderIterator hi;
+ rpmTagType type;
+ rpmTag tag;
+ rpm_count_t count;
+ rpm_data_t ptr;
+
+ for (hi = headerInitIterator(h);
+ headerNextIterator(hi, &tag, &type, &ptr, &count);
+ ptr = headerFreeData(ptr, type))
+ {
+ if (ptr) (void) headerAddEntry(nh, tag, type, ptr, count);
+ }
+ hi = headerFreeIterator(hi);
+
+ return headerReload(nh, HEADER_IMAGE);
+}
+
+/**
+ */
+typedef struct headerSprintfArgs_s {
+ Header h;
+ char * fmt;
+ headerTagTableEntry tags;
+ headerSprintfExtension exts;
+ const char * errmsg;
+ rpmec ec;
+ sprintfToken format;
+ HeaderIterator hi;
+ char * val;
+ size_t vallen;
+ size_t alloced;
+ int numTokens;
+ int i;
+} * headerSprintfArgs;
+
+/**
+ * Initialize an hsa iteration.
+ * @param hsa headerSprintf args
+ */
+static void hsaInit(headerSprintfArgs hsa)
+{
+ sprintfTag tag =
+ (hsa->format->type == PTOK_TAG
+ ? &hsa->format->u.tag :
+ (hsa->format->type == PTOK_ARRAY
+ ? &hsa->format->u.array.format->u.tag :
+ NULL));
+
+ hsa->i = 0;
+ if (tag != NULL && tag->tag == -2)
+ hsa->hi = headerInitIterator(hsa->h);
+}
+
+/**
+ * Return next hsa iteration item.
+ * @param hsa headerSprintf args
+ * @return next sprintfToken (or NULL)
+ */
+static sprintfToken hsaNext(headerSprintfArgs hsa)
+{
+ sprintfToken fmt = NULL;
+ sprintfTag tag =
+ (hsa->format->type == PTOK_TAG
+ ? &hsa->format->u.tag :
+ (hsa->format->type == PTOK_ARRAY
+ ? &hsa->format->u.array.format->u.tag :
+ NULL));
+
+ if (hsa->i >= 0 && hsa->i < hsa->numTokens) {
+ fmt = hsa->format + hsa->i;
+ if (hsa->hi == NULL) {
+ hsa->i++;
+ } else {
+ rpmTag tagno;
+ rpmTagType type;
+ rpm_count_t count;
+
+ if (!headerNextIterator(hsa->hi, &tagno, &type, NULL, &count))
+ fmt = NULL;
+ tag->tag = tagno;
+ }
+ }
+
+ return fmt;
+}
+
+/**
+ * Finish an hsa iteration.
+ * @param hsa headerSprintf args
+ */
+static void hsaFini(headerSprintfArgs hsa)
+{
+ hsa->hi = headerFreeIterator(hsa->hi);
+ hsa->i = 0;
+}
+
+/**
+ * Reserve sufficient buffer space for next output value.
+ * @param hsa headerSprintf args
+ * @param need no. of bytes to reserve
+ * @return pointer to reserved space
+ */
+static char * hsaReserve(headerSprintfArgs hsa, size_t need)
+{
+ if ((hsa->vallen + need) >= hsa->alloced) {
+ if (hsa->alloced <= need)
+ hsa->alloced += need;
+ hsa->alloced <<= 1;
+ hsa->val = xrealloc(hsa->val, hsa->alloced+1);
+ }
+ return hsa->val + hsa->vallen;
+}
+
+/**
+ * Return tag name from value.
+ * @todo bsearch on sorted value table.
+ * @param tbl tag table
+ * @param val tag value to find
+ * @return tag name, NULL on not found
+ */
+static const char * myTagName(headerTagTableEntry tbl, int val)
+{
+ static char name[128];
+ const char * s;
+ char *t;
+
+ for (; tbl->name != NULL; tbl++) {
+ if (tbl->val == val)
+ break;
+ }
+ if ((s = tbl->name) == NULL)
+ return NULL;
+ s += sizeof("RPMTAG_") - 1;
+ t = name;
+ *t++ = *s++;
+ while (*s != '\0')
+ *t++ = rtolower(*s++);
+ *t = '\0';
+ return name;
+}
+
+/**
+ * Return tag value from name.
+ * @todo bsearch on sorted name table.
+ * @param tbl tag table
+ * @param name tag name to find
+ * @return tag value, 0 on not found
+ */
+static int myTagValue(headerTagTableEntry tbl, const char * name)
+{
+ for (; tbl->name != NULL; tbl++) {
+ if (!rstrcasecmp(tbl->name + sizeof("RPMTAG"), name))
+ return tbl->val;
+ }
+ return 0;
+}
+
+/**
+ * Search extensions and tags for a name.
+ * @param hsa headerSprintf args
+ * @param token parsed fields
+ * @param name name to find
+ * @return 0 on success, 1 on not found
+ */
+static int findTag(headerSprintfArgs hsa, sprintfToken token, const char * name)
+{
+ const char *tagname = name;
+ headerSprintfExtension ext;
+ sprintfTag stag = (token->type == PTOK_COND
+ ? &token->u.cond.tag : &token->u.tag);
+
+ stag->fmt = NULL;
+ stag->ext = NULL;
+ stag->extNum = 0;
+ stag->tag = -1;
+
+ if (!strcmp(tagname, "*")) {
+ stag->tag = -2;
+ goto bingo;
+ }
+
+ if (strncmp("RPMTAG_", tagname, sizeof("RPMTAG_")-1) == 0) {
+ tagname += sizeof("RPMTAG");
+ }
+
+ /* Search extensions for specific tag override. */
+ for (ext = hsa->exts; ext != NULL && ext->type != HEADER_EXT_LAST;
+ ext = (ext->type == HEADER_EXT_MORE ? ext->u.more : ext+1))
+ {
+ if (ext->name == NULL || ext->type != HEADER_EXT_TAG)
+ continue;
+ if (!rstrcasecmp(ext->name + sizeof("RPMTAG"), tagname)) {
+ stag->ext = ext->u.tagFunction;
+ stag->extNum = ext - hsa->exts;
+ goto bingo;
+ }
+ }
+
+ /* Search tag names. */
+ stag->tag = myTagValue(hsa->tags, tagname);
+ if (stag->tag != 0)
+ goto bingo;
+
+ return 1;
+
+bingo:
+ /* Search extensions for specific format. */
+ if (stag->type != NULL)
+ for (ext = hsa->exts; ext != NULL && ext->type != HEADER_EXT_LAST;
+ ext = (ext->type == HEADER_EXT_MORE ? ext->u.more : ext+1))
+ {
+ if (ext->name == NULL || ext->type != HEADER_EXT_FORMAT)
+ continue;
+ if (!strcmp(ext->name, stag->type)) {
+ stag->fmt = ext->u.formatFunction;
+ break;
+ }
+ }
+ return 0;
+}
+
+/* forward ref */
+/**
+ * Parse an expression.
+ * @param hsa headerSprintf args
+ * @param token token
+ * @param str string
+ * @param[out] *endPtr
+ * @return 0 on success
+ */
+static int parseExpression(headerSprintfArgs hsa, sprintfToken token,
+ char * str,char ** endPtr);
+
+/**
+ * Parse a headerSprintf term.
+ * @param hsa headerSprintf args
+ * @param str
+ * @retval *formatPtr
+ * @retval *numTokensPtr
+ * @retval *endPtr
+ * @param state
+ * @return 0 on success
+ */
+static int parseFormat(headerSprintfArgs hsa, char * str,
+ sprintfToken * formatPtr,int * numTokensPtr,
+ char ** endPtr, int state)
+{
+ char * chptr, * start, * next, * dst;
+ sprintfToken format;
+ sprintfToken token;
+ int numTokens;
+ int i;
+ int done = 0;
+
+ /* upper limit on number of individual formats */
+ numTokens = 0;
+ if (str != NULL)
+ for (chptr = str; *chptr != '\0'; chptr++)
+ if (*chptr == '%') numTokens++;
+ numTokens = numTokens * 2 + 1;
+
+ format = xcalloc(numTokens, sizeof(*format));
+ if (endPtr) *endPtr = NULL;
+
+ /* LCL: can't detect done termination */
+ dst = start = str;
+ numTokens = 0;
+ token = NULL;
+ if (start != NULL)
+ while (*start != '\0') {
+ switch (*start) {
+ case '%':
+ /* handle %% */
+ if (*(start + 1) == '%') {
+ if (token == NULL || token->type != PTOK_STRING) {
+ token = format + numTokens++;
+ token->type = PTOK_STRING;
+ dst = token->u.string.string = start;
+ }
+ start++;
+ *dst++ = *start++;
+ break;
+ }
+
+ token = format + numTokens++;
+ *dst++ = '\0';
+ start++;
+
+ if (*start == '|') {
+ char * newEnd;
+
+ start++;
+ if (parseExpression(hsa, token, start, &newEnd))
+ {
+ format = freeFormat(format, numTokens);
+ return 1;
+ }
+ start = newEnd;
+ break;
+ }
+
+ token->u.tag.format = start;
+ token->u.tag.pad = 0;
+ token->u.tag.justOne = 0;
+ token->u.tag.arrayCount = 0;
+
+ chptr = start;
+ while (*chptr && *chptr != '{' && *chptr != '%') chptr++;
+ if (!*chptr || *chptr == '%') {
+ hsa->errmsg = _("missing { after %");
+ format = freeFormat(format, numTokens);
+ return 1;
+ }
+
+ *chptr++ = '\0';
+
+ while (start < chptr) {
+ if (risdigit(*start)) {
+ i = strtoul(start, &start, 10);
+ token->u.tag.pad += i;
+ start = chptr;
+ break;
+ } else {
+ start++;
+ }
+ }
+
+ if (*start == '=') {
+ token->u.tag.justOne = 1;
+ start++;
+ } else if (*start == '#') {
+ token->u.tag.justOne = 1;
+ token->u.tag.arrayCount = 1;
+ start++;
+ }
+
+ dst = next = start;
+ while (*next && *next != '}') next++;
+ if (!*next) {
+ hsa->errmsg = _("missing } after %{");
+ format = freeFormat(format, numTokens);
+ return 1;
+ }
+ *next++ = '\0';
+
+ chptr = start;
+ while (*chptr && *chptr != ':') chptr++;
+
+ if (*chptr != '\0') {
+ *chptr++ = '\0';
+ if (!*chptr) {
+ hsa->errmsg = _("empty tag format");
+ format = freeFormat(format, numTokens);
+ return 1;
+ }
+ token->u.tag.type = chptr;
+ } else {
+ token->u.tag.type = NULL;
+ }
+
+ if (!*start) {
+ hsa->errmsg = _("empty tag name");
+ format = freeFormat(format, numTokens);
+ return 1;
+ }
+
+ i = 0;
+ token->type = PTOK_TAG;
+
+ if (findTag(hsa, token, start)) {
+ hsa->errmsg = _("unknown tag");
+ format = freeFormat(format, numTokens);
+ return 1;
+ }
+
+ start = next;
+ break;
+
+ case '[':
+ *dst++ = '\0';
+ *start++ = '\0';
+ token = format + numTokens++;
+
+ if (parseFormat(hsa, start,
+ &token->u.array.format,
+ &token->u.array.numTokens,
+ &start, PARSER_IN_ARRAY))
+ {
+ format = freeFormat(format, numTokens);
+ return 1;
+ }
+
+ if (!start) {
+ hsa->errmsg = _("] expected at end of array");
+ format = freeFormat(format, numTokens);
+ return 1;
+ }
+
+ dst = start;
+
+ token->type = PTOK_ARRAY;
+
+ break;
+
+ case ']':
+ if (state != PARSER_IN_ARRAY) {
+ hsa->errmsg = _("unexpected ]");
+ format = freeFormat(format, numTokens);
+ return 1;
+ }
+ *start++ = '\0';
+ if (endPtr) *endPtr = start;
+ done = 1;
+ break;
+
+ case '}':
+ if (state != PARSER_IN_EXPR) {
+ hsa->errmsg = _("unexpected }");
+ format = freeFormat(format, numTokens);
+ return 1;
+ }
+ *start++ = '\0';
+ if (endPtr) *endPtr = start;
+ done = 1;
+ break;
+
+ default:
+ if (token == NULL || token->type != PTOK_STRING) {
+ token = format + numTokens++;
+ token->type = PTOK_STRING;
+ dst = token->u.string.string = start;
+ }
+
+ if (*start == '\\') {
+ start++;
+ *dst++ = escapedChar(*start++);
+ } else {
+ *dst++ = *start++;
+ }
+ break;
+ }
+ if (done)
+ break;
+ }
+
+ if (dst != NULL)
+ *dst = '\0';
+
+ for (i = 0; i < numTokens; i++) {
+ token = format + i;
+ if (token->type == PTOK_STRING)
+ token->u.string.len = strlen(token->u.string.string);
+ }
+
+ *numTokensPtr = numTokens;
+ *formatPtr = format;
+
+ return 0;
+}
+
+static int parseExpression(headerSprintfArgs hsa, sprintfToken token,
+ char * str, char ** endPtr)
+{
+ char * chptr;
+ char * end;
+
+ hsa->errmsg = NULL;
+ chptr = str;
+ while (*chptr && *chptr != '?') chptr++;
+
+ if (*chptr != '?') {
+ hsa->errmsg = _("? expected in expression");
+ return 1;
+ }
+
+ *chptr++ = '\0';;
+
+ if (*chptr != '{') {
+ hsa->errmsg = _("{ expected after ? in expression");
+ return 1;
+ }
+
+ chptr++;
+
+ if (parseFormat(hsa, chptr, &token->u.cond.ifFormat,
+ &token->u.cond.numIfTokens, &end, PARSER_IN_EXPR))
+ return 1;
+
+ /* XXX fix segfault on "rpm -q rpm --qf='%|NAME?{%}:{NAME}|\n'"*/
+ if (!(end && *end)) {
+ hsa->errmsg = _("} expected in expression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+
+ chptr = end;
+ if (*chptr != ':' && *chptr != '|') {
+ hsa->errmsg = _(": expected following ? subexpression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+
+ if (*chptr == '|') {
+ if (parseFormat(hsa, NULL, &token->u.cond.elseFormat,
+ &token->u.cond.numElseTokens, &end, PARSER_IN_EXPR))
+ {
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+ } else {
+ chptr++;
+
+ if (*chptr != '{') {
+ hsa->errmsg = _("{ expected after : in expression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+
+ chptr++;
+
+ if (parseFormat(hsa, chptr, &token->u.cond.elseFormat,
+ &token->u.cond.numElseTokens, &end, PARSER_IN_EXPR))
+ return 1;
+
+ /* XXX fix segfault on "rpm -q rpm --qf='%|NAME?{a}:{%}|{NAME}\n'" */
+ if (!(end && *end)) {
+ hsa->errmsg = _("} expected in expression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ return 1;
+ }
+
+ chptr = end;
+ if (*chptr != '|') {
+ hsa->errmsg = _("| expected at end of expression");
+ token->u.cond.ifFormat =
+ freeFormat(token->u.cond.ifFormat, token->u.cond.numIfTokens);
+ token->u.cond.elseFormat =
+ freeFormat(token->u.cond.elseFormat, token->u.cond.numElseTokens);
+ return 1;
+ }
+ }
+
+ chptr++;
+
+ *endPtr = chptr;
+
+ token->type = PTOK_COND;
+
+ (void) findTag(hsa, token, str);
+
+ return 0;
+}
+
+/**
+ * Call a header extension only once, saving results.
+ * @param hsa headerSprintf args
+ * @param fn
+ * @retval *typeptr
+ * @retval *data
+ * @retval *countptr
+ * @retval ec extension cache
+ * @return 0 on success, 1 on failure
+ */
+static int getExtension(headerSprintfArgs hsa, headerTagTagFunction fn,
+ rpmTagType * typeptr,
+ rpm_data_t * data,
+ rpm_count_t * countptr,
+ rpmec ec)
+{
+ if (!ec->avail) {
+ if (fn(hsa->h, &ec->type, &ec->data, &ec->count, &ec->freeit))
+ return 1;
+ ec->avail = 1;
+ }
+
+ if (typeptr) *typeptr = ec->type;
+ if (data) *data = ec->data;
+ if (countptr) *countptr = ec->count;
+
+ return 0;
+}
+
+/**
+ * formatValue
+ * @param hsa headerSprintf args
+ * @param tag
+ * @param element
+ * @return end of formatted string (NULL on error)
+ */
+static char * formatValue(headerSprintfArgs hsa, sprintfTag tag, int element)
+{
+ char * val = NULL;
+ size_t need = 0;
+ char * t, * te;
+ char buf[20];
+ rpmTagType type;
+ rpm_count_t count;
+ rpm_data_t data;
+ unsigned int intVal;
+ const char ** strarray;
+ int datafree = 0;
+ int countBuf;
+
+ memset(buf, 0, sizeof(buf));
+ if (tag->ext) {
+ if (getExtension(hsa, tag->ext, &type, &data, &count, hsa->ec + tag->extNum))
+ {
+ count = 1;
+ type = RPM_STRING_TYPE;
+ data = "(none)";
+ }
+ } else {
+ if (!headerGetEntry(hsa->h, tag->tag, &type, &data, &count)) {
+ count = 1;
+ type = RPM_STRING_TYPE;
+ data = "(none)";
+ }
+
+ /* XXX this test is unnecessary, array sizes are checked */
+ switch (type) {
+ default:
+ if (element >= count) {
+ data = headerFreeData(data, type);
+
+ hsa->errmsg = _("(index out of range)");
+ return NULL;
+ }
+ break;
+ case RPM_BIN_TYPE:
+ case RPM_STRING_TYPE:
+ break;
+ }
+ datafree = 1;
+ }
+
+ if (tag->arrayCount) {
+ if (datafree)
+ data = headerFreeData(data, type);
+
+ countBuf = count;
+ data = &countBuf;
+ count = 1;
+ type = RPM_INT32_TYPE;
+ }
+
+ (void) stpcpy( stpcpy(buf, "%"), tag->format);
+
+ if (data)
+ switch (type) {
+ case RPM_STRING_ARRAY_TYPE:
+ strarray = (const char **)data;
+
+ if (tag->fmt)
+ val = tag->fmt(RPM_STRING_TYPE, strarray[element], buf, tag->pad,
+ (rpm_count_t) element);
+
+ if (val) {
+ need = strlen(val);
+ } else {
+ need = strlen(strarray[element]) + tag->pad + 20;
+ val = xmalloc(need+1);
+ strcat(buf, "s");
+ sprintf(val, buf, strarray[element]);
+ }
+
+ break;
+
+ case RPM_STRING_TYPE:
+ if (tag->fmt)
+ val = tag->fmt(RPM_STRING_TYPE, data, buf, tag->pad, 0);
+
+ if (val) {
+ need = strlen(val);
+ } else {
+ need = strlen(data) + tag->pad + 20;
+ val = xmalloc(need+1);
+ strcat(buf, "s");
+ sprintf(val, buf, data);
+ }
+ break;
+
+ case RPM_CHAR_TYPE:
+ case RPM_INT8_TYPE:
+ case RPM_INT16_TYPE:
+ case RPM_INT32_TYPE:
+ switch (type) {
+ case RPM_CHAR_TYPE:
+ case RPM_INT8_TYPE:
+ intVal = *(((int8_t *) data) + element);
+ break;
+ case RPM_INT16_TYPE:
+ intVal = *(((uint16_t *) data) + element);
+ break;
+ default: /* keep -Wall quiet */
+ case RPM_INT32_TYPE:
+ intVal = *(((int32_t *) data) + element);
+ break;
+ }
+
+ if (tag->fmt)
+ val = tag->fmt(RPM_INT32_TYPE, &intVal, buf, tag->pad,
+ (rpm_count_t) element);
+
+ if (val) {
+ need = strlen(val);
+ } else {
+ need = 10 + tag->pad + 20;
+ val = xmalloc(need+1);
+ strcat(buf, "d");
+ sprintf(val, buf, intVal);
+ }
+ break;
+
+ case RPM_BIN_TYPE:
+ /* XXX HACK ALERT: element field abused as no. bytes of binary data. */
+ if (tag->fmt)
+ val = tag->fmt(RPM_BIN_TYPE, data, buf, tag->pad, (rpm_count_t) count);
+
+ if (val) {
+ need = strlen(val);
+ } else {
+ val = pgpHexStr(data, count);
+ need = strlen(val) + tag->pad;
+ }
+ break;
+
+ default:
+ need = sizeof("(unknown type)") - 1;
+ val = xstrdup("(unknown type)");
+ break;
+ }
+
+ if (datafree)
+ data = headerFreeData(data, type);
+
+ if (val && need > 0) {
+ t = hsaReserve(hsa, need);
+ te = stpcpy(t, val);
+ hsa->vallen += (te - t);
+ val = _free(val);
+ }
+
+ return (hsa->val + hsa->vallen);
+}
+
+/**
+ * Format a single headerSprintf item.
+ * @param hsa headerSprintf args
+ * @param token
+ * @param element
+ * @return end of formatted string (NULL on error)
+ */
+static char * singleSprintf(headerSprintfArgs hsa, sprintfToken token,
+ int element)
+{
+ char * t, * te;
+ int i, j, found;
+ rpmTagType type;
+ rpm_count_t count, numElements;
+ sprintfToken spft;
+ int condNumFormats;
+ size_t need;
+
+ /* we assume the token and header have been validated already! */
+
+ switch (token->type) {
+ case PTOK_NONE:
+ break;
+
+ case PTOK_STRING:
+ need = token->u.string.len;
+ if (need == 0) break;
+ t = hsaReserve(hsa, need);
+ te = stpcpy(t, token->u.string.string);
+ hsa->vallen += (te - t);
+ break;
+
+ case PTOK_TAG:
+ t = hsa->val + hsa->vallen;
+ te = formatValue(hsa, &token->u.tag,
+ (token->u.tag.justOne ? 0 : element));
+ if (te == NULL)
+ return NULL;
+ break;
+
+ case PTOK_COND:
+ if (token->u.cond.tag.ext || headerIsEntry(hsa->h, token->u.cond.tag.tag)) {
+ spft = token->u.cond.ifFormat;
+ condNumFormats = token->u.cond.numIfTokens;
+ } else {
+ spft = token->u.cond.elseFormat;
+ condNumFormats = token->u.cond.numElseTokens;
+ }
+
+ need = condNumFormats * 20;
+ if (spft == NULL || need == 0) break;
+
+ t = hsaReserve(hsa, need);
+ for (i = 0; i < condNumFormats; i++, spft++) {
+ te = singleSprintf(hsa, spft, element);
+ if (te == NULL)
+ return NULL;
+ }
+ break;
+
+ case PTOK_ARRAY:
+ numElements = 0;
+ found = 0;
+ spft = token->u.array.format;
+ for (i = 0; i < token->u.array.numTokens; i++, spft++)
+ {
+ if (spft->type != PTOK_TAG ||
+ spft->u.tag.arrayCount ||
+ spft->u.tag.justOne) continue;
+
+ if (spft->u.tag.ext) {
+ if (getExtension(hsa, spft->u.tag.ext, &type, NULL, &count,
+ hsa->ec + spft->u.tag.extNum))
+ continue;
+ } else {
+ if (!headerGetEntry(hsa->h, spft->u.tag.tag, &type, NULL, &count))
+ continue;
+ }
+
+ found = 1;
+
+ if (type == RPM_BIN_TYPE)
+ count = 1; /* XXX count abused as no. of bytes. */
+
+ if (numElements > 1 && count != numElements)
+ switch (type) {
+ default:
+ hsa->errmsg =
+ _("array iterator used with different sized arrays");
+ return NULL;
+ break;
+ case RPM_BIN_TYPE:
+ case RPM_STRING_TYPE:
+ break;
+ }
+ if (count > numElements)
+ numElements = count;
+ }
+
+ if (! found) {
+ need = sizeof("(none)") - 1;
+ t = hsaReserve(hsa, need);
+ te = stpcpy(t, "(none)");
+ hsa->vallen += (te - t);
+ } else {
+ int isxml;
+
+ need = numElements * token->u.array.numTokens * 10;
+ if (need == 0) break;
+
+ spft = token->u.array.format;
+ isxml = (spft->type == PTOK_TAG && spft->u.tag.type != NULL &&
+ !strcmp(spft->u.tag.type, "xml"));
+
+ if (isxml) {
+ const char * tagN = myTagName(hsa->tags, spft->u.tag.tag);
+
+ need = sizeof(" <rpmTag name=\"\">\n") - 1;
+ if (tagN != NULL)
+ need += strlen(tagN);
+ t = hsaReserve(hsa, need);
+ te = stpcpy(t, " <rpmTag name=\"");
+ if (tagN != NULL)
+ te = stpcpy(te, tagN);
+ te = stpcpy(te, "\">\n");
+ hsa->vallen += (te - t);
+ }
+
+ t = hsaReserve(hsa, need);
+ for (j = 0; j < numElements; j++) {
+ spft = token->u.array.format;
+ for (i = 0; i < token->u.array.numTokens; i++, spft++) {
+ te = singleSprintf(hsa, spft, j);
+ if (te == NULL)
+ return NULL;
+ }
+ }
+
+ if (isxml) {
+ need = sizeof(" </rpmTag>\n") - 1;
+ t = hsaReserve(hsa, need);
+ te = stpcpy(t, " </rpmTag>\n");
+ hsa->vallen += (te - t);
+ }
+
+ }
+ break;
+ }
+
+ return (hsa->val + hsa->vallen);
+}
+
+/**
+ * Create an extension cache.
+ * @param exts headerSprintf extensions
+ * @return new extension cache
+ */
+static rpmec
+rpmecNew(const headerSprintfExtension exts)
+{
+ headerSprintfExtension ext;
+ rpmec ec;
+ int i = 0;
+
+ for (ext = exts; ext != NULL && ext->type != HEADER_EXT_LAST;
+ ext = (ext->type == HEADER_EXT_MORE ? ext->u.more : ext+1))
+ {
+ i++;
+ }
+
+ ec = xcalloc(i, sizeof(*ec));
+ return ec;
+}
+
+/**
+ * Destroy an extension cache.
+ * @param exts headerSprintf extensions
+ * @param ec extension cache
+ * @return NULL always
+ */
+static rpmec
+rpmecFree(const headerSprintfExtension exts, rpmec ec)
+{
+ headerSprintfExtension ext;
+ int i = 0;
+
+ for (ext = exts; ext != NULL && ext->type != HEADER_EXT_LAST;
+ ext = (ext->type == HEADER_EXT_MORE ? ext->u.more : ext+1))
+ {
+ if (ec[i].freeit) ec[i].data = _free(ec[i].data);
+ i++;
+ }
+
+ ec = _free(ec);
+ return NULL;
+}
+
+char * headerSprintf(Header h, const char * fmt,
+ const struct headerTagTableEntry_s * tbltags,
+ const struct headerSprintfExtension_s * extensions,
+ errmsg_t * errmsg)
+{
+ struct headerSprintfArgs_s hsa;
+ sprintfToken nextfmt;
+ sprintfTag tag;
+ char * t, * te;
+ int isxml;
+ size_t need;
+
+ memset(&hsa, 0, sizeof(hsa));
+ hsa.h = headerLink(h);
+ hsa.fmt = xstrdup(fmt);
+ hsa.exts = (headerSprintfExtension) extensions;
+ hsa.tags = (headerTagTableEntry) tbltags;
+ hsa.errmsg = NULL;
+
+ if (parseFormat(&hsa, hsa.fmt, &hsa.format, &hsa.numTokens, NULL, PARSER_BEGIN))
+ goto exit;
+
+ hsa.ec = rpmecNew(hsa.exts);
+ hsa.val = xstrdup("");
+
+ tag =
+ (hsa.format->type == PTOK_TAG
+ ? &hsa.format->u.tag :
+ (hsa.format->type == PTOK_ARRAY
+ ? &hsa.format->u.array.format->u.tag :
+ NULL));
+ isxml = (tag != NULL && tag->tag == -2 && tag->type != NULL && !strcmp(tag->type, "xml"));
+
+ if (isxml) {
+ need = sizeof("<rpmHeader>\n") - 1;
+ t = hsaReserve(&hsa, need);
+ te = stpcpy(t, "<rpmHeader>\n");
+ hsa.vallen += (te - t);
+ }
+
+ hsaInit(&hsa);
+ while ((nextfmt = hsaNext(&hsa)) != NULL) {
+ te = singleSprintf(&hsa, nextfmt, 0);
+ if (te == NULL) {
+ hsa.val = _free(hsa.val);
+ break;
+ }
+ }
+ hsaFini(&hsa);
+
+ if (isxml) {
+ need = sizeof("</rpmHeader>\n") - 1;
+ t = hsaReserve(&hsa, need);
+ te = stpcpy(t, "</rpmHeader>\n");
+ hsa.vallen += (te - t);
+ }
+
+ if (hsa.val != NULL && hsa.vallen < hsa.alloced)
+ hsa.val = xrealloc(hsa.val, hsa.vallen+1);
+
+ hsa.ec = rpmecFree(hsa.exts, hsa.ec);
+ hsa.format = freeFormat(hsa.format, hsa.numTokens);
+
+exit:
+ if (errmsg)
+ *errmsg = hsa.errmsg;
+ hsa.h = headerFree(hsa.h);
+ hsa.fmt = _free(hsa.fmt);
+ return hsa.val;
+}
+
+/**
+ * octalFormat.
+ * @param type tag type
+ * @param data tag value
+ * @param formatPrefix sprintf format string
+ * @param padding no. additional bytes needed by format string
+ * @param element (unused)
+ * @return formatted string
+ */
+static char * octalFormat(rpmTagType type, rpm_constdata_t data,
+ char * formatPrefix, size_t padding,int element)
+{
+ char * val;
+
+ if (type != RPM_INT32_TYPE) {
+ val = xstrdup(_("(not a number)"));
+ } else {
+ val = xmalloc(20 + padding);
+ strcat(formatPrefix, "o");
+ sprintf(val, formatPrefix, *((const int32_t *) data));
+ }
+
+ return val;
+}
+
+/**
+ * hexFormat.
+ * @param type tag type
+ * @param data tag value
+ * @param formatPrefix sprintf format string
+ * @param padding no. additional bytes needed by format string
+ * @param element (unused)
+ * @return formatted string
+ */
+static char * hexFormat(rpmTagType type, rpm_constdata_t data,
+ char * formatPrefix, size_t padding,int element)
+{
+ char * val;
+
+ if (type != RPM_INT32_TYPE) {
+ val = xstrdup(_("(not a number)"));
+ } else {
+ val = xmalloc(20 + padding);
+ strcat(formatPrefix, "x");
+ sprintf(val, formatPrefix, *((const int32_t *) data));
+ }
+
+ return val;
+}
+
+/**
+ */
+static char * realDateFormat(rpmTagType type, rpm_constdata_t data,
+ char * formatPrefix, size_t padding,int element,
+ const char * strftimeFormat)
+{
+ char * val;
+
+ if (type != RPM_INT32_TYPE) {
+ val = xstrdup(_("(not a number)"));
+ } else {
+ struct tm * tstruct;
+ char buf[50];
+
+ val = xmalloc(50 + padding);
+ strcat(formatPrefix, "s");
+
+ /* this is important if sizeof(rpm_time_t) ! sizeof(time_t) */
+ { time_t dateint = *((const rpm_time_t *) data);
+ tstruct = localtime(&dateint);
+ }
+ buf[0] = '\0';
+ if (tstruct)
+ (void) strftime(buf, sizeof(buf) - 1, strftimeFormat, tstruct);
+ sprintf(val, formatPrefix, buf);
+ }
+
+ return val;
+}
+
+/**
+ * Format a date.
+ * @param type tag type
+ * @param data tag value
+ * @param formatPrefix sprintf format string
+ * @param padding no. additional bytes needed by format string
+ * @param element (unused)
+ * @return formatted string
+ */
+static char * dateFormat(rpmTagType type, rpm_constdata_t data,
+ char * formatPrefix, size_t padding, int element)
+{
+ return realDateFormat(type, data, formatPrefix, padding, element,
+ _("%c"));
+}
+
+/**
+ * Format a day.
+ * @param type tag type
+ * @param data tag value
+ * @param formatPrefix sprintf format string
+ * @param padding no. additional bytes needed by format string
+ * @param element (unused)
+ * @return formatted string
+ */
+static char * dayFormat(rpmTagType type, rpm_constdata_t data,
+ char * formatPrefix, size_t padding, int element)
+{
+ return realDateFormat(type, data, formatPrefix, padding, element,
+ _("%a %b %d %Y"));
+}
+
+/**
+ * Return shell escape formatted data.
+ * @param type tag type
+ * @param data tag value
+ * @param formatPrefix sprintf format string
+ * @param padding no. additional bytes needed by format string
+ * @param element (unused)
+ * @return formatted string
+ */
+static char * shescapeFormat(rpmTagType type, rpm_constdata_t data,
+ char * formatPrefix, size_t padding,int element)
+{
+ char * result, * dst, * src;
+
+ if (type == RPM_INT32_TYPE) {
+ result = xmalloc(padding + 20);
+ strcat(formatPrefix, "d");
+ sprintf(result, formatPrefix, *((const int32_t *) data));
+ } else {
+ char *buf = NULL;
+ strcat(formatPrefix, "s");
+ rasprintf(&buf, formatPrefix, data);
+
+ result = dst = xmalloc(strlen(buf) * 4 + 3);
+ *dst++ = '\'';
+ for (src = buf; *src != '\0'; src++) {
+ if (*src == '\'') {
+ *dst++ = '\'';
+ *dst++ = '\\';
+ *dst++ = '\'';
+ *dst++ = '\'';
+ } else {
+ *dst++ = *src;
+ }
+ }
+ *dst++ = '\'';
+ *dst = '\0';
+ free(buf);
+ }
+
+ return result;
+}
+
+/* FIX: cast? */
+const struct headerSprintfExtension_s headerDefaultFormats[] = {
+ { HEADER_EXT_FORMAT, "octal", { octalFormat } },
+ { HEADER_EXT_FORMAT, "hex", { hexFormat } },
+ { HEADER_EXT_FORMAT, "date", { dateFormat } },
+ { HEADER_EXT_FORMAT, "day", { dayFormat } },
+ { HEADER_EXT_FORMAT, "shescape", { shescapeFormat } },
+ { HEADER_EXT_LAST, NULL, { NULL } }
+};
+
+void headerCopyTags(Header headerFrom, Header headerTo,
+ const rpmTag * tagstocopy)
+{
+ const rpmTag * p;
+
+ if (headerFrom == headerTo)
+ return;
+
+ for (p = tagstocopy; *p != 0; p++) {
+ rpm_data_t s;
+ rpmTagType type;
+ rpm_count_t count;
+ if (headerIsEntry(headerTo, *p))
+ continue;
+ if (!headerGetEntryMinMemory(headerFrom, *p, &type, &s, &count))
+ continue;
+ (void) headerAddEntry(headerTo, *p, type, s, count);
+ /* XXXX freeing up *any* data from headerGetEntryMinMemory ?! */
+ s = headerFreeData(s, type);
+ }
+}
+
+void * headerFreeData(rpm_data_t data, rpmTagType type)
+{
+ if (data) {
+ if (type == RPM_FORCEFREE_TYPE ||
+ type == RPM_STRING_ARRAY_TYPE ||
+ type == RPM_I18NSTRING_TYPE ||
+ type == RPM_BIN_TYPE)
+ free(data); /* XXX _free() */
+ }
+ return NULL;
+}
diff --git a/lib/header.h b/lib/header.h
new file mode 100644
index 000000000..19dd0a53f
--- /dev/null
+++ b/lib/header.h
@@ -0,0 +1,615 @@
+#ifndef H_HEADER
+#define H_HEADER
+
+/** \ingroup header
+ * \file rpmdb/header.h
+ *
+ * An rpm header carries all information about a package. A header is
+ * a collection of data elements called tags. Each tag has a data type,
+ * and includes 1 or more values.
+ *
+ */
+
+/* RPM - Copyright (C) 1995-2001 Red Hat Software */
+
+#include <rpm/rpmio.h>
+#include <rpm/rpmtypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup header
+ * Associate tag names with numeric values.
+ */
+typedef struct headerTagTableEntry_s * headerTagTableEntry;
+struct headerTagTableEntry_s {
+ const char * name; /*!< Tag name. */
+ const char * shortname; /*!< "Human readable" short name. */
+ rpmTag val; /*!< Tag numeric value. */
+ rpmTagType type; /*!< Tag type. */
+};
+
+/**
+ */
+typedef struct headerTagIndices_s * headerTagIndices;
+
+/** \ingroup header
+ */
+enum headerSprintfExtensionType {
+ HEADER_EXT_LAST = 0, /*!< End of extension chain. */
+ HEADER_EXT_FORMAT, /*!< headerTagFormatFunction() extension */
+ HEADER_EXT_MORE, /*!< Chain to next table. */
+ HEADER_EXT_TAG /*!< headerTagTagFunction() extension */
+};
+
+/** \ingroup header
+ * HEADER_EXT_TAG format function prototype.
+ * This will only ever be passed RPM_INT32_TYPE or RPM_STRING_TYPE to
+ * help keep things simple.
+ *
+ * @param type tag type
+ * @param data tag value
+ * @param formatPrefix
+ * @param padding
+ * @param element RPM_BIN_TYPE: no. bytes of data
+ * @return formatted string
+ */
+typedef char * (*headerTagFormatFunction)(rpmTagType type,
+ rpm_constdata_t data, char * formatPrefix,
+ size_t padding, rpm_count_t element);
+
+/** \ingroup header
+ * HEADER_EXT_FORMAT format function prototype.
+ * This is allowed to fail, which indicates the tag doesn't exist.
+ *
+ * @param h header
+ * @retval *type tag type
+ * @retval *data tag value
+ * @retval *count no. of data items
+ * @retval *freedata data-was-malloc'ed indicator
+ * @return 0 on success
+ */
+typedef int (*headerTagTagFunction) (Header h,
+ rpmTagType * type,
+ rpm_data_t * data,
+ rpm_count_t * count,
+ int * freeData);
+
+/** \ingroup header
+ * Define header tag output formats.
+ */
+typedef struct headerSprintfExtension_s * headerSprintfExtension;
+struct headerSprintfExtension_s {
+ enum headerSprintfExtensionType type; /*!< Type of extension. */
+ const char * name; /*!< Name of extension. */
+ union {
+ void * generic; /*!< Private extension. */
+ headerTagFormatFunction formatFunction; /*!< HEADER_EXT_TAG extension. */
+ headerTagTagFunction tagFunction; /*!< HEADER_EXT_FORMAT extension. */
+ struct headerSprintfExtension_s * more; /*!< Chained table extension. */
+ } u;
+};
+
+/** \ingroup header
+ * Supported default header tag output formats.
+ */
+extern const struct headerSprintfExtension_s headerDefaultFormats[];
+
+/** \ingroup rpmtag
+ * Automatically generated table of tag name/value pairs.
+ */
+extern const struct headerTagTableEntry_s * const rpmTagTable;
+
+/** \ingroup rpmtag
+ * Number of entries in rpmTagTable.
+ */
+extern const int rpmTagTableSize;
+
+/** \ingroup rpmtag
+ */
+extern headerTagIndices const rpmTags;
+
+/** \ingroup header
+ * Table of query format extensions.
+ * @note Chains to headerDefaultFormats[].
+ */
+extern const struct headerSprintfExtension_s rpmHeaderFormats[];
+
+/** \ingroup header
+ * Include calculation for 8 bytes of (magic, 0)?
+ */
+enum hMagic {
+ HEADER_MAGIC_NO = 0,
+ HEADER_MAGIC_YES = 1
+};
+
+/* Return types for header data. Not yet... */
+#if 0
+/**
+ */
+typedef union hRET_s {
+ const void * ptr;
+ const char ** argv;
+ const char * str;
+ uint32_t * ui32p;
+ uint16_t * ui16p;
+ int32_t * i32p;
+ int16_t * i16p;
+ int8_t * i8p;
+} * hRET_t;
+
+/**
+ */
+typedef struct HE_s {
+ rpmTag tag;
+ rpmTagType * typ;
+ union {
+ hPTR_t * ptr;
+ hRET_t * ret;
+ } u;
+ rpm_count_t * cnt;
+} * HE_t;
+#endif
+
+/** \ingroup header
+ * Create new (empty) header instance.
+ * @return header
+ */
+Header headerNew(void);
+
+/** \ingroup header
+ * Dereference a header instance.
+ * @param h header
+ * @return NULL always
+ */
+Header headerFree( Header h);
+
+/** \ingroup header
+ * Reference a header instance.
+ * @param h header
+ * @return new header reference
+ */
+Header headerLink(Header h);
+
+/** \ingroup header
+ * Dereference a header instance.
+ * @param h header
+ * @return new header reference
+ */
+Header headerUnlink(Header h);
+
+/** \ingroup header
+ * Sort tags in header.
+ * @param h header
+ */
+void headerSort(Header h);
+
+/** \ingroup header
+ * Restore tags in header to original ordering.
+ * @param h header
+ */
+void headerUnsort(Header h);
+
+/** \ingroup header
+ * Return size of on-disk header representation in bytes.
+ * @param h header
+ * @param magicp include size of 8 bytes for (magic, 0)?
+ * @return size of on-disk header
+ */
+unsigned int headerSizeof(Header h, enum hMagic magicp);
+
+/** \ingroup header
+ * Convert header to on-disk representation.
+ * @param h header (with pointers)
+ * @return on-disk header blob (i.e. with offsets)
+ */
+void * headerUnload(Header h);
+
+/** \ingroup header
+ * Convert header to on-disk representation, and then reload.
+ * This is used to insure that all header data is in one chunk.
+ * @param h header (with pointers)
+ * @param tag region tag
+ * @return on-disk header (with offsets)
+ */
+Header headerReload(Header h, rpmTag tag);
+
+/** \ingroup header
+ * Duplicate a header.
+ * @param h header
+ * @return new header instance
+ */
+Header headerCopy(Header h);
+
+/** \ingroup header
+ * Convert header to in-memory representation.
+ * @param uh on-disk header blob (i.e. with offsets)
+ * @return header
+ */
+Header headerLoad(void * uh);
+
+/** \ingroup header
+ * Make a copy and convert header to in-memory representation.
+ * @param uh on-disk header blob (i.e. with offsets)
+ * @return header
+ */
+Header headerCopyLoad(const void * uh);
+
+/** \ingroup header
+ * Read (and load) header from file handle.
+ * @param fd file handle
+ * @param magicp read (and verify) 8 bytes of (magic, 0)?
+ * @return header (or NULL on error)
+ */
+Header headerRead(FD_t fd, enum hMagic magicp);
+
+/** \ingroup header
+ * Write (with unload) header to file handle.
+ * @param fd file handle
+ * @param h header
+ * @param magicp prefix write with 8 bytes of (magic, 0)?
+ * @return 0 on success, 1 on error
+ */
+int headerWrite(FD_t fd, Header h, enum hMagic magicp);
+
+/** \ingroup header
+ * Check if tag is in header.
+ * @param h header
+ * @param tag tag
+ * @return 1 on success, 0 on failure
+ */
+int headerIsEntry(Header h, rpmTag tag);
+
+/** \ingroup header
+ * Free data allocated when retrieved from header.
+ * @param h header
+ * @param data pointer to tag value(s)
+ * @param type type of data (or -1 to force free)
+ * @return NULL always
+ */
+void * headerFreeTag(Header h, rpm_data_t data, rpmTagType type);
+
+/** \ingroup header
+ * Retrieve tag value.
+ * Will never return RPM_I18NSTRING_TYPE! RPM_STRING_TYPE elements with
+ * RPM_I18NSTRING_TYPE equivalent entries are translated (if HEADER_I18NTABLE
+ * entry is present).
+ *
+ * @param h header
+ * @param tag tag
+ * @retval *type tag value data type (or NULL)
+ * @retval *p pointer to tag value(s) (or NULL)
+ * @retval *c number of values (or NULL)
+ * @return 1 on success, 0 on failure
+ */
+int headerGetEntry(Header h, rpmTag tag,
+ rpmTagType * type,
+ rpm_data_t * p,
+ rpm_count_t * c);
+
+/** \ingroup header
+ * Retrieve tag value using header internal array.
+ * Get an entry using as little extra RAM as possible to return the tag value.
+ * This is only an issue for RPM_STRING_ARRAY_TYPE.
+ *
+ * @param h header
+ * @param tag tag
+ * @retval *type tag value data type (or NULL)
+ * @retval *p pointer to tag value(s) (or NULL)
+ * @retval *c number of values (or NULL)
+ * @return 1 on success, 0 on failure
+ */
+int headerGetEntryMinMemory(Header h, rpmTag tag,
+ rpmTagType * type,
+ rpm_data_t * p,
+ rpm_count_t * c);
+
+/** \ingroup header
+ * Add tag to header.
+ * Duplicate tags are okay, but only defined for iteration (with the
+ * exceptions noted below). While you are allowed to add i18n string
+ * arrays through this function, you probably don't mean to. See
+ * headerAddI18NString() instead.
+ *
+ * @param h header
+ * @param tag tag
+ * @param type tag value data type
+ * @param p pointer to tag value(s)
+ * @param c number of values
+ * @return 1 on success, 0 on failure
+ */
+int headerAddEntry(Header h, rpmTag tag, rpmTagType type, rpm_constdata_t p, rpm_count_t c);
+
+/** \ingroup header
+ * Append element to tag array in header.
+ * Appends item p to entry w/ tag and type as passed. Won't work on
+ * RPM_STRING_TYPE. Any pointers into header memory returned from
+ * headerGetEntryMinMemory() for this entry are invalid after this
+ * call has been made!
+ *
+ * @param h header
+ * @param tag tag
+ * @param type tag value data type
+ * @param p pointer to tag value(s)
+ * @param c number of values
+ * @return 1 on success, 0 on failure
+ */
+int headerAppendEntry(Header h, rpmTag tag, rpmTagType type,
+ rpm_constdata_t p, rpm_count_t c);
+
+/** \ingroup header
+ * Add or append element to tag array in header.
+ * @todo Arg "p" should have const.
+ * @param h header
+ * @param tag tag
+ * @param type tag value data type
+ * @param p pointer to tag value(s)
+ * @param c number of values
+ * @return 1 on success, 0 on failure
+ */
+int headerAddOrAppendEntry(Header h, rpmTag tag, rpmTagType type,
+ rpm_constdata_t p, rpm_count_t c);
+
+/** \ingroup header
+ * Add locale specific tag to header.
+ * A NULL lang is interpreted as the C locale. Here are the rules:
+ * \verbatim
+ * - If the tag isn't in the header, it's added with the passed string
+ * as new value.
+ * - If the tag occurs multiple times in entry, which tag is affected
+ * by the operation is undefined.
+ * - If the tag is in the header w/ this language, the entry is
+ * *replaced* (like headerModifyEntry()).
+ * \endverbatim
+ * This function is intended to just "do the right thing". If you need
+ * more fine grained control use headerAddEntry() and headerModifyEntry().
+ *
+ * @param h header
+ * @param tag tag
+ * @param string tag value
+ * @param lang locale
+ * @return 1 on success, 0 on failure
+ */
+int headerAddI18NString(Header h, rpmTag tag, const char * string,
+ const char * lang);
+
+/** \ingroup header
+ * Modify tag in header.
+ * If there are multiple entries with this tag, the first one gets replaced.
+ * @param h header
+ * @param tag tag
+ * @param type tag value data type
+ * @param p pointer to tag value(s)
+ * @param c number of values
+ * @return 1 on success, 0 on failure
+ */
+int headerModifyEntry(Header h, rpmTag tag, rpmTagType type,
+ rpm_constdata_t p, rpm_count_t c);
+
+/** \ingroup header
+ * Delete tag in header.
+ * Removes all entries of type tag from the header, returns 1 if none were
+ * found.
+ *
+ * @param h header
+ * @param tag tag
+ * @return 0 on success, 1 on failure (INCONSISTENT)
+ */
+int headerRemoveEntry(Header h, rpmTag tag);
+
+/** \ingroup header
+ * Return formatted output string from header tags.
+ * The returned string must be free()d.
+ *
+ * @param h header
+ * @param fmt format to use
+ * @param tbltags array of tag name/value pairs
+ * @param extensions chained table of formatting extensions.
+ * @retval errmsg error message (if any)
+ * @return formatted output string (malloc'ed)
+ */
+char * headerSprintf(Header h, const char * fmt,
+ const struct headerTagTableEntry_s * tbltags,
+ const struct headerSprintfExtension_s * extensions,
+ errmsg_t * errmsg);
+
+/** \ingroup header
+ * Duplicate tag values from one header into another.
+ * @param headerFrom source header
+ * @param headerTo destination header
+ * @param tagstocopy array of tags that are copied
+ */
+void headerCopyTags(Header headerFrom, Header headerTo,
+ const rpmTag * tagstocopy);
+
+/** \ingroup header
+ * Destroy header tag iterator.
+ * @param hi header tag iterator
+ * @return NULL always
+ */
+HeaderIterator headerFreeIterator(HeaderIterator hi);
+
+/** \ingroup header
+ * Create header tag iterator.
+ * @param h header
+ * @return header tag iterator
+ */
+HeaderIterator headerInitIterator(Header h);
+
+/** \ingroup header
+ * Return next tag from header.
+ * @param hi header tag iterator
+ * @retval *tag tag
+ * @retval *type tag value data type
+ * @retval *p pointer to tag value(s)
+ * @retval *c number of values
+ * @return 1 on success, 0 on failure
+ */
+int headerNextIterator(HeaderIterator hi,
+ rpmTag * tag,
+ rpmTagType * type,
+ rpm_data_t * p,
+ rpm_count_t * c);
+
+
+
+/** \ingroup header
+ * Free data allocated when retrieved from header.
+ * @deprecated Use headerFreeTag() instead.
+ * @todo Remove from API.
+ *
+ * @param data address of data (or NULL)
+ * @param type type of data (or RPM_FORCEFREE_TYPE to force free)
+ * @return NULL always
+ */
+void * headerFreeData(rpm_data_t data, rpmTagType type);
+
+/** \ingroup header
+ * Return name, version, release strings from header.
+ * @param h header
+ * @retval *np name pointer (or NULL)
+ * @retval *vp version pointer (or NULL)
+ * @retval *rp release pointer (or NULL)
+ * @return 0 always
+ */
+int headerNVR(Header h,
+ const char ** np,
+ const char ** vp,
+ const char ** rp);
+
+/** \ingroup header
+ * Return name, epoch, version, release, arch strings from header.
+ * @param h header
+ * @retval *np name pointer (or NULL)
+ * @retval *ep epoch pointer (or NULL)
+ * @retval *vp version pointer (or NULL)
+ * @retval *rp release pointer (or NULL)
+ * @retval *ap arch pointer (or NULL)
+ * @return 0 always
+ */
+int headerNEVRA(Header h,
+ const char ** np,
+ int32_t ** ep,
+ const char ** vp,
+ const char ** rp,
+ const char ** ap);
+
+/** \ingroup header
+ * Return (malloc'd) header name-version-release string.
+ * @param h header
+ * @retval np name tag value
+ * @return name-version-release string
+ */
+char * headerGetNEVR(Header h, const char ** np );
+
+/** \ingroup header
+ * Return (malloc'd) header name-version-release.arch string.
+ * @param h header
+ * @retval np name tag value
+ * @return name-version-release string
+ */
+char * headerGetNEVRA(Header h, const char ** np );
+
+/* \ingroup header
+ * Return (malloc'd) header (epoch:)version-release string.
+ * @param h header
+ * @retval np name tag value (or NULL)
+ * @return (epoch:)version-release string
+ */
+char * headerGetEVR(Header h, const char **np);
+
+/** \ingroup header
+ * Return header color.
+ * @param h header
+ * @return header color
+ */
+rpm_color_t headerGetColor(Header h);
+
+/** \ingroup header
+ * Check if header is a source or binary package header
+ * @param h header
+ * @return 0 == binary, 1 == source
+ */
+int headerIsSource(Header h);
+
+/* ==================================================================== */
+/** \name RPMTS */
+/**
+ * Prototype for headerFreeData() vector.
+ *
+ * @param data address of data (or NULL)
+ * @param type type of data (or to force free)
+ * @return NULL always
+ */
+typedef
+ void * (*HFD_t) (rpm_data_t data, rpmTagType type);
+
+/**
+ * Prototype for headerGetEntry() vector.
+ *
+ * Will never return RPM_I18NSTRING_TYPE! RPM_STRING_TYPE elements with
+ * RPM_I18NSTRING_TYPE equivalent entries are translated (if HEADER_I18NTABLE
+ * entry is present).
+ *
+ * @param h header
+ * @param tag tag
+ * @retval type address of tag value data type (or NULL)
+ * @retval p address of pointer to tag value(s) (or NULL)
+ * @retval c address of number of values (or NULL)
+ * @return 1 on success, 0 on failure
+ */
+typedef int (*HGE_t) (Header h, rpmTag tag,
+ rpmTagType * type,
+ rpm_data_t * p,
+ rpm_count_t * c);
+
+/**
+ * Prototype for headerAddEntry() vector.
+ *
+ * Duplicate tags are okay, but only defined for iteration (with the
+ * exceptions noted below). While you are allowed to add i18n string
+ * arrays through this function, you probably don't mean to. See
+ * headerAddI18NString() instead.
+ *
+ * @param h header
+ * @param tag tag
+ * @param type tag value data type
+ * @param p pointer to tag value(s)
+ * @param c number of values
+ * @return 1 on success, 0 on failure
+ */
+typedef int (*HAE_t) (Header h, rpmTag tag, rpmTagType type,
+ rpm_constdata_t p, rpm_count_t c);
+
+/**
+ * Prototype for headerModifyEntry() vector.
+ * If there are multiple entries with this tag, the first one gets replaced.
+ *
+ * @param h header
+ * @param tag tag
+ * @param type tag value data type
+ * @param p pointer to tag value(s)
+ * @param c number of values
+ * @return 1 on success, 0 on failure
+ */
+typedef int (*HME_t) (Header h, rpmTag tag, rpmTagType type,
+ rpm_constdata_t p, rpm_count_t c);
+
+/**
+ * Prototype for headerRemoveEntry() vector.
+ * Delete tag in header.
+ * Removes all entries of type tag from the header, returns 1 if none were
+ * found.
+ *
+ * @param h header
+ * @param tag tag
+ * @return 0 on success, 1 on failure (INCONSISTENT)
+ */
+typedef int (*HRE_t) (Header h, rpmTag tag);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_HEADER */
diff --git a/lib/header_internal.c b/lib/header_internal.c
new file mode 100644
index 000000000..1af4bc0ab
--- /dev/null
+++ b/lib/header_internal.c
@@ -0,0 +1,170 @@
+/** \ingroup header
+ * \file rpmdb/header_internal.c
+ */
+
+#include "system.h"
+
+#include <rpm/rpmtag.h>
+#include "rpmdb/header_internal.h"
+
+#include "debug.h"
+
+char ** headerGetLangs(Header h)
+{
+ char **s, *e, **table;
+ rpmTagType type;
+ rpm_count_t i, count;
+
+ if (!headerGetRawEntry(h, HEADER_I18NTABLE, &type, (rpm_data_t)&s, &count))
+ return NULL;
+
+ /* XXX xcalloc never returns NULL. */
+ if ((table = (char **)xcalloc((count+1), sizeof(char *))) == NULL)
+ return NULL;
+
+ for (i = 0, e = *s; i < count; i++, e += strlen(e)+1)
+ table[i] = e;
+ table[count] = NULL;
+
+ return table; /* LCL: double indirection? */
+}
+
+/* FIX: shrug */
+void headerDump(Header h, FILE *f, int flags,
+ const struct headerTagTableEntry_s * tags)
+{
+ int i;
+ indexEntry p;
+ const struct headerTagTableEntry_s * tage;
+ const char * tag;
+ const char * type;
+
+ /* First write out the length of the index (count of index entries) */
+ fprintf(f, "Entry count: %d\n", h->indexUsed);
+
+ /* Now write the index */
+ p = h->index;
+ fprintf(f, "\n CT TAG TYPE "
+ " OFSET COUNT\n");
+ for (i = 0; i < h->indexUsed; i++) {
+ switch (p->info.type) {
+ case RPM_NULL_TYPE:
+ type = "NULL";
+ break;
+ case RPM_CHAR_TYPE:
+ type = "CHAR";
+ break;
+ case RPM_BIN_TYPE:
+ type = "BIN";
+ break;
+ case RPM_INT8_TYPE:
+ type = "INT8";
+ break;
+ case RPM_INT16_TYPE:
+ type = "INT16";
+ break;
+ case RPM_INT32_TYPE:
+ type = "INT32";
+ break;
+ /*case RPM_INT64_TYPE: type = "INT64"; break;*/
+ case RPM_STRING_TYPE:
+ type = "STRING";
+ break;
+ case RPM_STRING_ARRAY_TYPE:
+ type = "STRING_ARRAY";
+ break;
+ case RPM_I18NSTRING_TYPE:
+ type = "I18N_STRING";
+ break;
+ default:
+ type = "(unknown)";
+ break;
+ }
+
+ tage = tags;
+ while (tage->name && tage->val != p->info.tag) tage++;
+
+ if (!tage->name)
+ tag = "(unknown)";
+ else
+ tag = tage->name;
+
+ fprintf(f, "Entry : %3.3d (%d)%-14s %-18s 0x%.8x %.8d\n", i,
+ p->info.tag, tag, type, (unsigned) p->info.offset,
+ (int) p->info.count);
+
+ if (flags & HEADER_DUMP_INLINE) {
+ char *dp = p->data;
+ int c = p->info.count;
+ int ct = 0;
+
+ /* Print the data inline */
+ switch (p->info.type) {
+ case RPM_INT32_TYPE:
+ while (c--) {
+ fprintf(f, " Data: %.3d 0x%08x (%d)\n", ct++,
+ (unsigned) *((int32_t *) dp),
+ (int) *((int32_t *) dp));
+ dp += sizeof(int32_t);
+ }
+ break;
+
+ case RPM_INT16_TYPE:
+ while (c--) {
+ fprintf(f, " Data: %.3d 0x%04x (%d)\n", ct++,
+ (unsigned) (*((int16_t *) dp) & 0xffff),
+ (int) *((int16_t *) dp));
+ dp += sizeof(int16_t);
+ }
+ break;
+ case RPM_INT8_TYPE:
+ while (c--) {
+ fprintf(f, " Data: %.3d 0x%02x (%d)\n", ct++,
+ (unsigned) (*((int8_t *) dp) & 0xff),
+ (int) *((int8_t *) dp));
+ dp += sizeof(int8_t);
+ }
+ break;
+ case RPM_BIN_TYPE:
+ while (c > 0) {
+ fprintf(f, " Data: %.3d ", ct);
+ while (c--) {
+ fprintf(f, "%02x ", (unsigned) (*(int8_t *)dp & 0xff));
+ ct++;
+ dp += sizeof(int8_t);
+ if (! (ct % 8)) {
+ break;
+ }
+ }
+ fprintf(f, "\n");
+ }
+ break;
+ case RPM_CHAR_TYPE:
+ while (c--) {
+ char ch = (char) *((char *) dp);
+ fprintf(f, " Data: %.3d 0x%2x %c (%d)\n", ct++,
+ (unsigned)(ch & 0xff),
+ (isprint(ch) ? ch : ' '),
+ (int) *((char *) dp));
+ dp += sizeof(char);
+ }
+ break;
+ case RPM_STRING_TYPE:
+ case RPM_STRING_ARRAY_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ while (c--) {
+ fprintf(f, " Data: %.3d %s\n", ct++, (char *) dp);
+ dp = strchr(dp, 0);
+ dp++;
+ }
+ break;
+ default:
+ fprintf(stderr, _("Data type %d not supported\n"),
+ (int) p->info.type);
+ break;
+ }
+ }
+ p++;
+ }
+}
+
diff --git a/lib/header_internal.h b/lib/header_internal.h
new file mode 100644
index 000000000..f789fbbbe
--- /dev/null
+++ b/lib/header_internal.h
@@ -0,0 +1,178 @@
+#ifndef H_HEADER_INTERNAL
+#define H_HEADER_INTERNAL
+
+/** \ingroup header
+ * \file rpmdb/header_internal.h
+ */
+
+#include <netinet/in.h>
+
+#include <rpm/header.h>
+
+#define INDEX_MALLOC_SIZE 8
+
+/*
+ * Teach header.c about legacy tags.
+ */
+#define HEADER_OLDFILENAMES 1027
+#define HEADER_BASENAMES 1117
+
+/** \ingroup header
+ * Description of tag data.
+ */
+typedef struct entryInfo_s * entryInfo;
+struct entryInfo_s {
+ rpmTag tag; /*!< Tag identifier. */
+ rpmTagType type; /*!< Tag data type. */
+ int32_t offset; /*!< Offset into data segment (ondisk only). */
+ rpm_count_t count; /*!< Number of tag elements. */
+};
+
+#define REGION_TAG_TYPE RPM_BIN_TYPE
+#define REGION_TAG_COUNT sizeof(struct entryInfo_s)
+
+#define ENTRY_IS_REGION(_e) \
+ (((_e)->info.tag >= HEADER_IMAGE) && ((_e)->info.tag < HEADER_REGIONS))
+#define ENTRY_IN_REGION(_e) ((_e)->info.offset < 0)
+
+/** \ingroup header
+ * A single tag from a Header.
+ */
+typedef struct indexEntry_s * indexEntry;
+struct indexEntry_s {
+ struct entryInfo_s info; /*!< Description of tag data. */
+ rpm_data_t data; /*!< Location of tag data. */
+ int length; /*!< No. bytes of data. */
+ int rdlen; /*!< No. bytes of data in region. */
+};
+
+/** \ingroup header
+ * The Header data structure.
+ */
+struct headerToken_s {
+ void * blob; /*!< Header region blob. */
+ indexEntry index; /*!< Array of tags. */
+ int indexUsed; /*!< Current size of tag array. */
+ int indexAlloced; /*!< Allocated size of tag array. */
+ int flags;
+#define HEADERFLAG_SORTED (1 << 0) /*!< Are header entries sorted? */
+#define HEADERFLAG_ALLOCATED (1 << 1) /*!< Is 1st header region allocated? */
+#define HEADERFLAG_LEGACY (1 << 2) /*!< Header came from legacy source? */
+#define HEADERFLAG_DEBUG (1 << 3) /*!< Debug this header? */
+ int nrefs; /*!< Reference count. */
+};
+
+/** \ingroup header
+ */
+typedef struct sprintfTag_s * sprintfTag;
+struct sprintfTag_s {
+ headerTagFormatFunction fmt;
+ headerTagTagFunction ext; /*!< NULL if tag element is invalid */
+ int extNum;
+ rpmTag tag;
+ int justOne;
+ int arrayCount;
+ char * format;
+ char * type;
+ int pad;
+};
+
+/** \ingroup header
+ * Extension cache.
+ */
+typedef struct rpmec_s * rpmec;
+struct rpmec_s {
+ rpmTagType type;
+ rpm_count_t count;
+ int avail;
+ int freeit;
+ rpm_data_t data;
+};
+
+/** \ingroup header
+ */
+typedef struct sprintfToken_s * sprintfToken;
+struct sprintfToken_s {
+ enum {
+ PTOK_NONE = 0,
+ PTOK_TAG,
+ PTOK_ARRAY,
+ PTOK_STRING,
+ PTOK_COND
+ } type;
+ union {
+ struct sprintfTag_s tag; /*!< PTOK_TAG */
+ struct {
+ sprintfToken format;
+ int i;
+ int numTokens;
+ } array; /*!< PTOK_ARRAY */
+ struct {
+ char * string;
+ int len;
+ } string; /*!< PTOK_STRING */
+ struct {
+ sprintfToken ifFormat;
+ int numIfTokens;
+ sprintfToken elseFormat;
+ int numElseTokens;
+ struct sprintfTag_s tag;
+ } cond; /*!< PTOK_COND */
+ } u;
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup header
+ * Return array of locales found in header.
+ * The array is terminated with a NULL sentinel.
+ * @param h header
+ * @return array of locales (or NULL on error)
+ */
+char ** headerGetLangs(Header h);
+
+/** \ingroup header
+ * Retrieve tag value with type match.
+ * If *type is RPM_NULL_TYPE any type will match, otherwise only *type will
+ * match.
+ *
+ * @param h header
+ * @param tag tag
+ * @retval type address of tag value data type (or NULL)
+ * @retval p address of pointer to tag value(s) (or NULL)
+ * @retval c address of number of values (or NULL)
+ * @return 1 on success, 0 on failure
+ */
+int headerGetRawEntry(Header h, rpmTag tag,
+ rpmTagType * type,
+ rpm_data_t * p,
+ rpm_count_t * c);
+
+/** \ingroup header
+ * Return header reference count.
+ * @param h header
+ * @return no. of references
+ */
+/* FIX: cast? */
+static inline int headerUsageCount(Header h) {
+ return h->nrefs;
+}
+
+/** \ingroup header
+ * Dump a header in human readable format (for debugging).
+ * @param h header
+ * @param f file handle
+ * @param flags 0 or HEADER_DUMP_INLINE
+ * @param tags array of tag name/value pairs
+ */
+void headerDump(Header h, FILE *f, int flags,
+ const struct headerTagTableEntry_s * tags);
+#define HEADER_DUMP_INLINE 1
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_HEADER_INTERNAL */
diff --git a/lib/merge.c b/lib/merge.c
new file mode 100644
index 000000000..02f922d7f
--- /dev/null
+++ b/lib/merge.c
@@ -0,0 +1,344 @@
+#ifndef __APPLE__
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Peter McIlroy.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)merge.c 8.2 (Berkeley) 2/14/94";
+#endif /* LIBC_SCCS and not lint */
+
+/*
+ * Hybrid exponential search/linear search merge sort with hybrid
+ * natural/pairwise first pass. Requires about .3% more comparisons
+ * for random data than LSMS with pairwise first pass alone.
+ * It works for objects as small as two bytes.
+ */
+
+#define NATURAL
+#define THRESHOLD 16 /* Best choice for natural merge cut-off. */
+
+/* #define NATURAL to get hybrid natural merge.
+ * (The default is pairwise merging.)
+ */
+
+#include "system.h"
+#include "rpmdb/rpmdb_internal.h" /* XXX for mergesort */
+
+#define ISIZE sizeof(int)
+#define PSIZE sizeof(unsigned char *)
+#define ICOPY_LIST(src, dst, last) \
+ do \
+ *(int*)dst = *(int*)src, src += ISIZE, dst += ISIZE; \
+ while(src < last)
+#define ICOPY_ELT(src, dst, i) \
+ do \
+ *(int*) dst = *(int*) src, src += ISIZE, dst += ISIZE; \
+ while (i -= ISIZE)
+
+#define CCOPY_LIST(src, dst, last) \
+ do \
+ *dst++ = *src++; \
+ while (src < last)
+#define CCOPY_ELT(src, dst, i) \
+ do \
+ *dst++ = *src++; \
+ while (i -= 1)
+
+/*
+ * Find the next possible pointer head. (Trickery for forcing an array
+ * to do double duty as a linked list when objects do not align with word
+ * boundaries.
+ */
+/* Assumption: PSIZE is a power of 2. */
+#define EVAL(p) (unsigned char **) \
+ ((unsigned char *)0 + \
+ (((unsigned char *)p + PSIZE - 1 - (unsigned char *) 0) & ~(PSIZE - 1)))
+
+#define swap(a, b) { \
+ s = b; \
+ i = size; \
+ do { \
+ tmp = *a; *a++ = *s; *s++ = tmp; \
+ } while (--i); \
+ a -= size; \
+ }
+#define reverse(bot, top) { \
+ s = top; \
+ do { \
+ i = size; \
+ do { \
+ tmp = *bot; *bot++ = *s; *s++ = tmp; \
+ } while (--i); \
+ s -= size2; \
+ } while(bot < s); \
+}
+
+/*
+ * This is to avoid out-of-bounds addresses in sorting the
+ * last 4 elements.
+ */
+static void
+insertionsort(unsigned char *a, size_t n, size_t size,
+ int (*cmp) (const void *, const void *))
+{
+ unsigned char *ai, *s, *t, *u, tmp;
+ int i;
+
+ for (ai = a+size; --n >= 1; ai += size)
+ for (t = ai; t > a; t -= size) {
+ u = t - size;
+ if (cmp(u, t) <= 0)
+ break;
+ swap(u, t);
+ }
+}
+
+/*
+ * Optional hybrid natural/pairwise first pass. Eats up list1 in runs of
+ * increasing order, list2 in a corresponding linked list. Checks for runs
+ * when THRESHOLD/2 pairs compare with same sense. (Only used when NATURAL
+ * is defined. Otherwise simple pairwise merging is used.)
+ */
+static void
+setup(unsigned char *list1, unsigned char *list2,
+ size_t n, size_t size, int (*cmp) (const void *, const void *))
+{
+ int i, length, size2, tmp, sense;
+ unsigned char *f1, *f2, *s, *l2, *last, *p2;
+
+ size2 = size*2;
+ if (n <= 5) {
+ insertionsort(list1, n, size, cmp);
+ *EVAL(list2) = (unsigned char*) list2 + n*size;
+ return;
+ }
+ /*
+ * Avoid running pointers out of bounds; limit n to evens
+ * for simplicity.
+ */
+ i = 4 + (n & 1);
+ insertionsort(list1 + (n - i) * size, i, size, cmp);
+ last = list1 + size * (n - i);
+ *EVAL(list2 + (last - list1)) = list2 + n * size;
+
+#ifdef NATURAL
+ p2 = list2;
+ f1 = list1;
+ sense = (cmp(f1, f1 + size) > 0);
+ for (; f1 < last; sense = !sense) {
+ length = 2;
+ /* Find pairs with same sense. */
+ for (f2 = f1 + size2; f2 < last; f2 += size2) {
+ if ((cmp(f2, f2+ size) > 0) != sense)
+ break;
+ length += 2;
+ }
+ if (length < THRESHOLD) { /* Pairwise merge */
+ do {
+ p2 = *EVAL(p2) = f1 + size2 - list1 + list2;
+ if (sense > 0)
+ swap (f1, f1 + size);
+ } while ((f1 += size2) < f2);
+ } else { /* Natural merge */
+ l2 = f2;
+ for (f2 = f1 + size2; f2 < l2; f2 += size2) {
+ if ((cmp(f2-size, f2) > 0) != sense) {
+ p2 = *EVAL(p2) = f2 - list1 + list2;
+ if (sense > 0)
+ reverse(f1, f2-size);
+ f1 = f2;
+ }
+ }
+ if (sense > 0)
+ reverse (f1, f2-size);
+ f1 = f2;
+ if (f2 < last || cmp(f2 - size, f2) > 0)
+ p2 = *EVAL(p2) = f2 - list1 + list2;
+ else
+ p2 = *EVAL(p2) = list2 + n*size;
+ }
+ }
+#else /* pairwise merge only. */
+ for (f1 = list1, p2 = list2; f1 < last; f1 += size2) {
+ p2 = *EVAL(p2) = p2 + size2;
+ if (cmp (f1, f1 + size) > 0)
+ swap(f1, f1 + size);
+ }
+#endif /* NATURAL */
+}
+
+/*
+ * Arguments are as for qsort.
+ */
+int
+mergesort(void *base, size_t nmemb, size_t size,
+ int (*cmp) (const void *, const void *))
+{
+ register int i, sense;
+ int big, iflag;
+ register unsigned char *f1, *f2, *t, *b, *q, *l1, *l2;
+ register unsigned char *tp2;
+ unsigned char *list2;
+ unsigned char *list1;
+ unsigned char *p2, *p, *last, **p1;
+
+ if (size < PSIZE / 2) { /* Pointers must fit into 2 * size. */
+ errno = EINVAL;
+ return (-1);
+ }
+
+ if (nmemb == 0)
+ return (0);
+
+ /*
+ * XXX
+ * Stupid subtraction for the Cray.
+ */
+ iflag = 0;
+ if (!(size % ISIZE) && !(((char *)base - (char *)0) % ISIZE))
+ iflag = 1;
+
+ if ((list2 = malloc(nmemb * size + PSIZE)) == NULL)
+ return (-1);
+
+ list1 = base;
+ setup(list1, list2, nmemb, size, cmp);
+ last = list2 + nmemb * size;
+ i = big = 0;
+ while (*EVAL(list2) != last) {
+ l2 = list1;
+ p1 = EVAL(list1);
+ for (tp2 = p2 = list2; p2 != last; p1 = EVAL(l2)) {
+ p2 = *EVAL(p2);
+ f1 = l2;
+ f2 = l1 = list1 + (p2 - list2);
+ if (p2 != last)
+ p2 = *EVAL(p2);
+ l2 = list1 + (p2 - list2);
+ while (f1 < l1 && f2 < l2) {
+ if ((*cmp)(f1, f2) <= 0) {
+ q = f2;
+ b = f1, t = l1;
+ sense = -1;
+ } else {
+ q = f1;
+ b = f2, t = l2;
+ sense = 0;
+ }
+ if (!big) { /* here i = 0 */
+ while ((b += size) < t && cmp(q, b) >sense)
+ if (++i == 6) {
+ big = 1;
+ goto EXPONENTIAL;
+ }
+ } else {
+EXPONENTIAL: for (i = size; ; i <<= 1)
+ if ((p = (b + i)) >= t) {
+ if ((p = t - size) > b &&
+ (*cmp)(q, p) <= sense)
+ t = p;
+ else
+ b = p;
+ break;
+ } else if ((*cmp)(q, p) <= sense) {
+ t = p;
+ if (i == size)
+ big = 0;
+ goto FASTCASE;
+ } else
+ b = p;
+ while (t > b+size) {
+ i = (((t - b) / size) >> 1) * size;
+ if ((*cmp)(q, p = b + i) <= sense)
+ t = p;
+ else
+ b = p;
+ }
+ goto COPY;
+FASTCASE: while (i > size)
+ if ((*cmp)(q,
+ p = b + (i >>= 1)) <= sense)
+ t = p;
+ else
+ b = p;
+COPY: b = t;
+ }
+ i = size;
+ if (q == f1) {
+ if (iflag) {
+ ICOPY_LIST(f2, tp2, b);
+ ICOPY_ELT(f1, tp2, i);
+ } else {
+ CCOPY_LIST(f2, tp2, b);
+ CCOPY_ELT(f1, tp2, i);
+ }
+ } else {
+ if (iflag) {
+ ICOPY_LIST(f1, tp2, b);
+ ICOPY_ELT(f2, tp2, i);
+ } else {
+ CCOPY_LIST(f1, tp2, b);
+ CCOPY_ELT(f2, tp2, i);
+ }
+ }
+ }
+ if (f2 < l2) {
+ if (iflag)
+ ICOPY_LIST(f2, tp2, l2);
+ else
+ CCOPY_LIST(f2, tp2, l2);
+ } else if (f1 < l1) {
+ if (iflag)
+ ICOPY_LIST(f1, tp2, l1);
+ else
+ CCOPY_LIST(f1, tp2, l1);
+ }
+ *p1 = l2;
+ }
+ tp2 = list1; /* swap list1, list2 */
+ list1 = list2;
+ list2 = tp2;
+ last = list2 + nmemb*size;
+ }
+ if (base == list2) {
+ memmove(list2, list1, nmemb*size);
+ list2 = list1;
+ }
+ free(list2);
+ return (0);
+}
+#else
+/* mergesort is implemented in System on Mac OS X */
+#endif /* __APPLE__ */
diff --git a/lib/poptDB.c b/lib/poptDB.c
new file mode 100644
index 000000000..db3266306
--- /dev/null
+++ b/lib/poptDB.c
@@ -0,0 +1,26 @@
+/** \ingroup rpmcli
+ * \file rpmdb/poptDB.c
+ * Popt tables for database modes.
+ */
+
+#include "system.h"
+
+#include <rpm/rpmcli.h>
+
+#include "debug.h"
+
+struct rpmDatabaseArguments_s rpmDBArgs;
+
+/**
+ */
+struct poptOption rpmDatabasePoptTable[] = {
+ { "initdb", '\0', POPT_ARG_VAL, &rpmDBArgs.init, 1,
+ N_("initialize database"), NULL},
+ { "rebuilddb", '\0', POPT_ARG_VAL, &rpmDBArgs.rebuild, 1,
+ N_("rebuild database inverted lists from installed package headers"),
+ NULL},
+ { "verifydb", '\0', POPT_ARG_VAL|POPT_ARGFLAG_DOC_HIDDEN, &rpmDBArgs.verify, 1,
+ N_("verify database files"), NULL},
+
+ POPT_TABLEEND
+};
diff --git a/lib/rpmdb.c b/lib/rpmdb.c
new file mode 100644
index 000000000..72d86adee
--- /dev/null
+++ b/lib/rpmdb.c
@@ -0,0 +1,3525 @@
+/** \ingroup rpmdb dbi
+ * \file rpmdb/rpmdb.c
+ */
+
+#include "system.h"
+
+#define _USE_COPY_LOAD /* XXX don't use DB_DBT_MALLOC (yet) */
+
+#include <sys/file.h>
+
+#ifndef DYING /* XXX already in "system.h" */
+#include <fnmatch.h>
+#endif
+
+#include <regex.h>
+
+#include <rpm/rpmtag.h>
+#include <rpm/rpmurl.h>
+#include <rpm/rpmpgp.h>
+#include <rpm/rpmpgp.h>
+#include <rpm/rpmmacro.h>
+#include <rpm/rpmsq.h>
+#include <rpm/rpmstring.h>
+#include <rpm/rpmfileutil.h>
+#include <rpm/rpmds.h> /* XXX isInstallPreReq macro only */
+#include <rpm/rpmlog.h>
+#include <rpm/rpmdb.h>
+#include <rpm/argv.h>
+
+#include "rpmdb/rpmdb_internal.h"
+#include "rpmdb/fprint.h"
+#include "rpmdb/header_internal.h" /* XXX for HEADERFLAG_ALLOCATED */
+#include "debug.h"
+
+int _rpmdb_debug = 0;
+
+static int _rebuildinprogress = 0;
+static int _db_filter_dups = 0;
+
+#define _DBI_FLAGS 0
+#define _DBI_PERMS 0644
+#define _DBI_MAJOR -1
+
+struct dbiTags_s {
+ rpmTag * tags;
+ rpmTag max;
+ int nlink;
+};
+
+/* XXX should dbitags be per-db instead? */
+static struct dbiTags_s dbiTags = { NULL, 0, 0 };
+
+/* Bit mask macros. */
+typedef unsigned int __pbm_bits;
+#define __PBM_NBITS (8 * sizeof (__pbm_bits))
+#define __PBM_IX(d) ((d) / __PBM_NBITS)
+#define __PBM_MASK(d) ((__pbm_bits) 1 << (((unsigned)(d)) % __PBM_NBITS))
+typedef struct {
+ __pbm_bits bits[1];
+} pbm_set;
+#define __PBM_BITS(set) ((set)->bits)
+
+#define PBM_FREE(s) _free(s);
+#define PBM_SET(d, s) (__PBM_BITS (s)[__PBM_IX (d)] |= __PBM_MASK (d))
+#define PBM_CLR(d, s) (__PBM_BITS (s)[__PBM_IX (d)] &= ~__PBM_MASK (d))
+#define PBM_ISSET(d, s) ((__PBM_BITS (s)[__PBM_IX (d)] & __PBM_MASK (d)) != 0)
+
+#define PBM_ALLOC(d) xcalloc(__PBM_IX (d) + 1, sizeof(__pbm_bits))
+
+/**
+ * Reallocate a bit map.
+ * @retval sp address of bit map pointer
+ * @retval odp no. of bits in map
+ * @param nd desired no. of bits
+ */
+static inline pbm_set * PBM_REALLOC(pbm_set ** sp, int * odp, int nd)
+{
+ int i, nb;
+
+ if (nd > (*odp)) {
+ nd *= 2;
+ nb = __PBM_IX(nd) + 1;
+ *sp = xrealloc(*sp, nb * sizeof(__pbm_bits));
+ for (i = __PBM_IX(*odp) + 1; i < nb; i++)
+ __PBM_BITS(*sp)[i] = 0;
+ *odp = nd;
+ }
+ return *sp;
+}
+
+/**
+ * Return dbi index used for rpm tag.
+ * @param rpmtag rpm header tag
+ * @return dbi index, -1 on error
+ */
+static int dbiTagToDbix(rpmTag rpmtag)
+{
+ int dbix;
+
+ if (dbiTags.tags != NULL)
+ for (dbix = 0; dbix < dbiTags.max; dbix++) {
+ if (rpmtag == dbiTags.tags[dbix])
+ return dbix;
+ }
+ return -1;
+}
+
+/**
+ * Initialize database (index, tag) tuple from configuration.
+ */
+static void dbiTagsInit(void)
+{
+ static const char * const _dbiTagStr_default =
+ "Packages:Name:Basenames:Group:Requirename:Providename:Conflictname:Triggername:Dirnames:Requireversion:Provideversion:Installtid:Sigmd5:Sha1header:Filemd5s:Depends:Pubkeys";
+ char * dbiTagStr = NULL;
+ char * o, * oe;
+ rpmTag rpmtag;
+
+ dbiTags.nlink++;
+ if (dbiTags.tags != NULL && dbiTags.max > 0) {
+ return;
+ }
+
+ dbiTagStr = rpmExpand("%{?_dbi_tags}", NULL);
+ if (!(dbiTagStr && *dbiTagStr)) {
+ dbiTagStr = _free(dbiTagStr);
+ dbiTagStr = xstrdup(_dbiTagStr_default);
+ }
+
+ /* Discard previous values. */
+ dbiTags.tags = _free(dbiTags.tags);
+ dbiTags.max = 0;
+
+ /* Always allocate package index */
+ dbiTags.tags = xcalloc(1, sizeof(*dbiTags.tags));
+ dbiTags.tags[dbiTags.max++] = RPMDBI_PACKAGES;
+
+ for (o = dbiTagStr; o && *o; o = oe) {
+ while (*o && risspace(*o))
+ o++;
+ if (*o == '\0')
+ break;
+ for (oe = o; oe && *oe; oe++) {
+ if (risspace(*oe))
+ break;
+ if (oe[0] == ':' && !(oe[1] == '/' && oe[2] == '/'))
+ break;
+ }
+ if (oe && *oe)
+ *oe++ = '\0';
+ rpmtag = rpmTagGetValue(o);
+ if (rpmtag == RPMTAG_NOT_FOUND) {
+ rpmlog(RPMLOG_WARNING,
+ _("dbiTagsInit: unrecognized tag name: \"%s\" ignored\n"), o);
+ continue;
+ }
+ if (dbiTagToDbix(rpmtag) >= 0)
+ continue;
+
+ dbiTags.tags = xrealloc(dbiTags.tags, (dbiTags.max + 1) * sizeof(*dbiTags.tags)); /* XXX memory leak */
+ dbiTags.tags[dbiTags.max++] = rpmtag;
+ }
+
+ dbiTagStr = _free(dbiTagStr);
+}
+
+static void dbiTagsFree(void)
+{
+ if (--dbiTags.nlink > 0) {
+ return;
+ }
+ dbiTags.tags = _free(dbiTags.tags);
+ dbiTags.max = 0;
+}
+
+#define DB1vec NULL
+#define DB2vec NULL
+
+#ifdef HAVE_DB_H
+extern struct _dbiVec db3vec;
+#define DB3vec &db3vec
+#else
+#define DB3vec NULL
+#endif
+
+#ifdef HAVE_SQLITE3_H
+extern struct _dbiVec sqlitevec;
+#define SQLITEvec &sqlitevec
+#else
+#define SQLITEvec NULL
+#endif
+
+static struct _dbiVec * const mydbvecs[] = {
+ DB1vec, DB1vec, DB2vec, DB3vec, SQLITEvec, NULL
+};
+
+dbiIndex dbiOpen(rpmdb db, rpmTag rpmtag, unsigned int flags)
+{
+ int dbix;
+ dbiIndex dbi = NULL;
+ int _dbapi, _dbapi_rebuild, _dbapi_wanted;
+ int rc = 0;
+
+ if (db == NULL)
+ return NULL;
+
+ dbix = dbiTagToDbix(rpmtag);
+ if (dbix < 0 || dbix >= dbiTags.max)
+ return NULL;
+
+ /* Is this index already open ? */
+ /* FIX: db->_dbi may be NULL */
+ if ((dbi = db->_dbi[dbix]) != NULL)
+ return dbi;
+
+ _dbapi_rebuild = rpmExpandNumeric("%{_dbapi_rebuild}");
+ if (_dbapi_rebuild < 1 || _dbapi_rebuild > 4)
+ _dbapi_rebuild = 4;
+/* _dbapi_wanted = (_rebuildinprogress ? -1 : db->db_api); */
+ _dbapi_wanted = (_rebuildinprogress ? _dbapi_rebuild : db->db_api);
+
+ switch (_dbapi_wanted) {
+ default:
+ _dbapi = _dbapi_wanted;
+ if (_dbapi < 0 || _dbapi >= 5 || mydbvecs[_dbapi] == NULL) {
+ rpmlog(RPMLOG_ERR, _("dbiOpen: dbapi %d not available\n"), _dbapi);
+ return NULL;
+ }
+ errno = 0;
+ dbi = NULL;
+ rc = (*mydbvecs[_dbapi]->open) (db, rpmtag, &dbi);
+ if (rc) {
+ static int _printed[32];
+ if (!_printed[dbix & 0x1f]++)
+ rpmlog(RPMLOG_ERR,
+ _("cannot open %s index using db%d - %s (%d)\n"),
+ rpmTagGetName(rpmtag), _dbapi,
+ (rc > 0 ? strerror(rc) : ""), rc);
+ _dbapi = -1;
+ }
+ break;
+ case -1:
+ _dbapi = 5;
+ while (_dbapi-- > 1) {
+ if (mydbvecs[_dbapi] == NULL)
+ continue;
+ errno = 0;
+ dbi = NULL;
+ rc = (*mydbvecs[_dbapi]->open) (db, rpmtag, &dbi);
+ if (rc == 0 && dbi)
+ break;
+ }
+ if (_dbapi <= 0) {
+ static int _printed[32];
+ if (!_printed[dbix & 0x1f]++)
+ rpmlog(RPMLOG_ERR, _("cannot open %s index\n"),
+ rpmTagGetName(rpmtag));
+ rc = 1;
+ goto exit;
+ }
+ if (db->db_api == -1 && _dbapi > 0)
+ db->db_api = _dbapi;
+ break;
+ }
+
+/* We don't ever _REQUIRE_ conversion... */
+#define SQLITE_HACK
+#ifdef SQLITE_HACK_XXX
+ /* Require conversion. */
+ if (rc && _dbapi_wanted >= 0 && _dbapi != _dbapi_wanted && _dbapi_wanted == _dbapi_rebuild) {
+ rc = (_rebuildinprogress ? 0 : 1);
+ goto exit;
+ }
+
+ /* Suggest possible configuration */
+ if (_dbapi_wanted >= 0 && _dbapi != _dbapi_wanted) {
+ rc = 1;
+ goto exit;
+ }
+
+ /* Suggest possible configuration */
+ if (_dbapi_wanted < 0 && _dbapi != _dbapi_rebuild) {
+ rc = (_rebuildinprogress ? 0 : 1);
+ goto exit;
+ }
+#endif
+
+exit:
+ if (dbi != NULL && rc == 0) {
+ db->_dbi[dbix] = dbi;
+ if (rpmtag == RPMDBI_PACKAGES && db->db_bits == NULL) {
+ db->db_nbits = 1024;
+ if (!dbiStat(dbi, DB_FAST_STAT)) {
+ DB_HASH_STAT * hash = (DB_HASH_STAT *)dbi->dbi_stats;
+ if (hash)
+ db->db_nbits += hash->hash_nkeys;
+ }
+ db->db_bits = PBM_ALLOC(db->db_nbits);
+ }
+ }
+#ifdef HAVE_DB_H
+ else
+ dbi = db3Free(dbi);
+#endif
+
+/* FIX: db->_dbi may be NULL */
+ return dbi;
+}
+
+/**
+ * Create and initialize item for index database set.
+ * @param hdrNum header instance in db
+ * @param tagNum tag index in header
+ * @return new item
+ */
+static dbiIndexItem dbiIndexNewItem(unsigned int hdrNum, unsigned int tagNum)
+{
+ dbiIndexItem rec = xcalloc(1, sizeof(*rec));
+ rec->hdrNum = hdrNum;
+ rec->tagNum = tagNum;
+ return rec;
+}
+
+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; \
+\
+ }
+
+/**
+ * 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)
+{
+ int _dbbyteswapped = dbiByteSwapped(dbi);
+ const char * sdbir;
+ dbiIndexSet set;
+ int i;
+
+ if (dbi == NULL || data == NULL || setp == NULL)
+ return -1;
+
+ if ((sdbir = data->data) == NULL) {
+ *setp = NULL;
+ return 0;
+ }
+
+ set = xmalloc(sizeof(*set));
+ set->count = data->size / dbi->dbi_jlen;
+ set->recs = xmalloc(set->count * sizeof(*(set->recs)));
+
+ switch (dbi->dbi_jlen) {
+ default:
+ case 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;
+ set->recs[i].fpNum = 0;
+ }
+ break;
+ case 1*sizeof(int32_t):
+ for (i = 0; i < set->count; i++) {
+ union _dbswap hdrNum;
+
+ memcpy(&hdrNum.ui, sdbir, sizeof(hdrNum.ui));
+ sdbir += sizeof(hdrNum.ui);
+ if (_dbbyteswapped) {
+ _DBSWAP(hdrNum);
+ }
+ set->recs[i].hdrNum = hdrNum.ui;
+ set->recs[i].tagNum = 0;
+ set->recs[i].fpNum = 0;
+ }
+ break;
+ }
+ *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;
+ int i;
+
+ if (dbi == NULL || data == NULL || set == NULL)
+ return -1;
+
+ data->size = set->count * (dbi->dbi_jlen);
+ if (data->size == 0) {
+ data->data = NULL;
+ return 0;
+ }
+ tdbir = data->data = xmalloc(data->size);
+
+ switch (dbi->dbi_jlen) {
+ default:
+ case 2*sizeof(int32_t):
+ 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);
+ }
+ break;
+ case 1*sizeof(int32_t):
+ for (i = 0; i < set->count; i++) {
+ union _dbswap hdrNum;
+
+ memset(&hdrNum, 0, sizeof(hdrNum));
+ hdrNum.ui = set->recs[i].hdrNum;
+ if (_dbbyteswapped) {
+ _DBSWAP(hdrNum);
+ }
+ memcpy(tdbir, &hdrNum.ui, sizeof(hdrNum.ui));
+ tdbir += sizeof(hdrNum.ui);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* XXX assumes hdrNum is first int in dbiIndexItem */
+static int hdrNumCmp(const void * one, const void * two)
+{
+ const int * a = one, * b = two;
+ return (*a - *b);
+}
+
+/**
+ * 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 recsize size of an array item
+ * @param sortset should resulting set be sorted?
+ * @return 0 success, 1 failure (bad args)
+ */
+static int dbiAppendSet(dbiIndexSet set, const void * recs,
+ int nrecs, size_t recsize, int sortset)
+{
+ const char * rptr = recs;
+ size_t rlen = (recsize < sizeof(*(set->recs)))
+ ? recsize : sizeof(*(set->recs));
+
+ if (set == NULL || recs == NULL || nrecs <= 0 || recsize == 0)
+ return 1;
+
+ set->recs = xrealloc(set->recs,
+ (set->count + nrecs) * sizeof(*(set->recs)));
+
+ memset(set->recs + set->count, 0, nrecs * sizeof(*(set->recs)));
+
+ while (nrecs-- > 0) {
+ memcpy(set->recs + set->count, rptr, rlen);
+ rptr += recsize;
+ set->count++;
+ }
+
+ if (sortset && set->count > 1)
+ qsort(set->recs, set->count, sizeof(*(set->recs)), hdrNumCmp);
+
+ return 0;
+}
+
+/**
+ * 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 recsize size of an array item
+ * @param sorted array is already sorted?
+ * @return 0 success, 1 failure (no items found)
+ */
+static int dbiPruneSet(dbiIndexSet set, void * recs, int nrecs,
+ size_t recsize, int sorted)
+{
+ int from;
+ int to = 0;
+ int num = set->count;
+ int numCopied = 0;
+
+ assert(set->count > 0);
+ 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);
+}
+
+/* XXX transaction.c */
+unsigned int dbiIndexSetCount(dbiIndexSet set) {
+ return set->count;
+}
+
+/* XXX transaction.c */
+unsigned int dbiIndexRecordOffset(dbiIndexSet set, int recno) {
+ return set->recs[recno].hdrNum;
+}
+
+/* XXX transaction.c */
+unsigned int dbiIndexRecordFileNumber(dbiIndexSet set, int recno) {
+ return set->recs[recno].tagNum;
+}
+
+/* XXX transaction.c */
+dbiIndexSet dbiFreeIndexSet(dbiIndexSet set) {
+ if (set) {
+ set->recs = _free(set->recs);
+ set = _free(set);
+ }
+ return set;
+}
+
+typedef struct miRE_s {
+ rpmTag tag; /*!< header tag */
+ rpmMireMode mode; /*!< pattern match mode */
+ char * pattern; /*!< pattern string */
+ int notmatch; /*!< like "grep -v" */
+ regex_t * preg; /*!< regex compiled pattern buffer */
+ int cflags; /*!< regcomp(3) flags */
+ int eflags; /*!< regexec(3) flags */
+ int fnflags; /*!< fnmatch(3) flags */
+} * miRE;
+
+struct rpmdbMatchIterator_s {
+ rpmdbMatchIterator mi_next;
+ void * mi_keyp;
+ size_t mi_keylen;
+ rpmdb mi_db;
+ rpmTag mi_rpmtag;
+ dbiIndexSet mi_set;
+ DBC * mi_dbc;
+ DBT mi_key;
+ DBT mi_data;
+ int mi_setx;
+ Header mi_h;
+ int mi_sorted;
+ int mi_cflags;
+ int mi_modified;
+ unsigned int mi_prevoffset; /* header instance (native endian) */
+ unsigned int mi_offset; /* header instance (native endian) */
+ unsigned int mi_filenum; /* tag element (native endian) */
+ int mi_nre;
+ miRE mi_re;
+ rpmts mi_ts;
+ rpmRC (*mi_hdrchk) (rpmts ts, const void * uh, size_t uc, char ** msg);
+
+};
+
+static rpmdb rpmdbRock;
+
+static rpmdbMatchIterator rpmmiRock;
+
+int rpmdbCheckTerminate(int terminate)
+{
+ sigset_t newMask, oldMask;
+ static int terminating = 0;
+
+ if (terminating) return 0;
+
+ (void) sigfillset(&newMask); /* block all signals */
+ (void) sigprocmask(SIG_BLOCK, &newMask, &oldMask);
+
+ if (sigismember(&rpmsqCaught, SIGINT)
+ || sigismember(&rpmsqCaught, SIGQUIT)
+ || sigismember(&rpmsqCaught, SIGHUP)
+ || sigismember(&rpmsqCaught, SIGTERM)
+ || sigismember(&rpmsqCaught, SIGPIPE)
+ || terminate)
+ terminating = 1;
+
+ if (terminating) {
+ rpmdb db;
+ rpmdbMatchIterator mi;
+
+ while ((mi = rpmmiRock) != NULL) {
+ rpmmiRock = mi->mi_next;
+ mi->mi_next = NULL;
+ mi = rpmdbFreeIterator(mi);
+ }
+
+ while ((db = rpmdbRock) != NULL) {
+ rpmdbRock = db->db_next;
+ db->db_next = NULL;
+ (void) rpmdbClose(db);
+ }
+ }
+ sigprocmask(SIG_SETMASK, &oldMask, NULL);
+ return terminating;
+}
+
+int rpmdbCheckSignals(void)
+{
+ if (rpmdbCheckTerminate(0)) {
+ /* sigset_t is abstract type */
+ rpmlog(RPMLOG_DEBUG, "Exiting on signal(0x%lx) ...\n",
+ *((unsigned long *)&rpmsqCaught));
+ exit(EXIT_FAILURE);
+ }
+ return 0;
+}
+
+/**
+ * Block all signals, returning previous signal mask.
+ */
+static int blockSignals(sigset_t * oldMask)
+{
+ sigset_t newMask;
+
+ (void) sigfillset(&newMask); /* block all signals */
+ (void) sigprocmask(SIG_BLOCK, &newMask, oldMask);
+ (void) sigdelset(&newMask, SIGINT);
+ (void) sigdelset(&newMask, SIGQUIT);
+ (void) sigdelset(&newMask, SIGHUP);
+ (void) sigdelset(&newMask, SIGTERM);
+ (void) sigdelset(&newMask, SIGPIPE);
+ return sigprocmask(SIG_BLOCK, &newMask, NULL);
+}
+
+/**
+ * Restore signal mask.
+ */
+static int unblockSignals(sigset_t * oldMask)
+{
+ (void) rpmdbCheckSignals();
+ return sigprocmask(SIG_SETMASK, oldMask, NULL);
+}
+
+#define _DB_ROOT "/"
+#define _DB_HOME "%{_dbpath}"
+#define _DB_FLAGS 0
+#define _DB_MODE 0
+#define _DB_PERMS 0644
+
+#define _DB_MAJOR -1
+#define _DB_ERRPFX "rpmdb"
+
+static struct rpmdb_s const dbTemplate = {
+ _DB_ROOT, _DB_HOME, _DB_FLAGS, _DB_MODE, _DB_PERMS,
+ _DB_MAJOR, _DB_ERRPFX
+};
+
+static int isTemporaryDB(rpmTag rpmtag)
+{
+ int rc = 0;
+ switch (rpmtag) {
+ case RPMDBI_AVAILABLE:
+ case RPMDBI_ADDED:
+ case RPMDBI_REMOVED:
+ case RPMDBI_DEPENDS:
+ rc = 1;
+ break;
+ default:
+ break;
+ }
+ return rc;
+}
+
+rpmop rpmdbOp(rpmdb rpmdb, rpmdbOpX opx)
+{
+ rpmop op = NULL;
+ switch (opx) {
+ case RPMDB_OP_DBGET:
+ op = &rpmdb->db_getops;
+ break;
+ case RPMDB_OP_DBPUT:
+ op = &rpmdb->db_putops;
+ break;
+ case RPMDB_OP_DBDEL:
+ op = &rpmdb->db_delops;
+ break;
+ default:
+ break;
+ }
+ return op;
+}
+
+int rpmdbSetChrootDone(rpmdb db, int chrootDone)
+{
+ int ochrootDone = 0;
+ if (db != NULL) {
+ ochrootDone = db->db_chrootDone;
+ db->db_chrootDone = chrootDone;
+ }
+ return ochrootDone;
+}
+
+int rpmdbOpenAll(rpmdb db)
+{
+ int dbix;
+ int rc = 0;
+
+ if (db == NULL) return -2;
+
+ if (dbiTags.tags != NULL)
+ for (dbix = 0; dbix < dbiTags.max; dbix++) {
+ if (db->_dbi[dbix] != NULL)
+ continue;
+ /* Filter out temporary databases */
+ if (isTemporaryDB(dbiTags.tags[dbix]))
+ continue;
+ (void) dbiOpen(db, dbiTags.tags[dbix], db->db_flags);
+ }
+ return rc;
+}
+
+int rpmdbCloseDBI(rpmdb db, rpmTag rpmtag)
+{
+ int dbix;
+ int rc = 0;
+
+ if (db == NULL || db->_dbi == NULL || dbiTags.tags == NULL)
+ return 0;
+
+ for (dbix = 0; dbix < dbiTags.max; dbix++) {
+ if (dbiTags.tags[dbix] != rpmtag)
+ continue;
+ if (db->_dbi[dbix] != NULL) {
+ int xx;
+ /* FIX: double indirection. */
+ xx = dbiClose(db->_dbi[dbix], 0);
+ if (xx && rc == 0) rc = xx;
+ db->_dbi[dbix] = NULL;
+ }
+ break;
+ }
+ return rc;
+}
+
+/* XXX query.c, rpminstall.c, verify.c */
+int rpmdbClose(rpmdb db)
+{
+ rpmdb * prev, next;
+ int dbix;
+ int rc = 0;
+
+ if (db == NULL)
+ goto exit;
+
+ (void) rpmdbUnlink(db, RPMDBG_M("rpmdbClose"));
+
+ if (db->nrefs > 0)
+ goto exit;
+
+ if (db->_dbi)
+ for (dbix = db->db_ndbi; --dbix >= 0; ) {
+ int xx;
+ if (db->_dbi[dbix] == NULL)
+ continue;
+ xx = dbiClose(db->_dbi[dbix], 0);
+ if (xx && rc == 0) rc = xx;
+ db->_dbi[dbix] = NULL;
+ }
+ db->db_errpfx = _free(db->db_errpfx);
+ db->db_root = _free(db->db_root);
+ db->db_home = _free(db->db_home);
+ db->db_bits = PBM_FREE(db->db_bits);
+ db->_dbi = _free(db->_dbi);
+
+ prev = &rpmdbRock;
+ while ((next = *prev) != NULL && next != db)
+ prev = &next->db_next;
+ if (next) {
+ *prev = next->db_next;
+ next->db_next = NULL;
+ }
+
+ db = _free(db);
+
+ dbiTagsFree();
+
+exit:
+ (void) rpmsqEnable(-SIGHUP, NULL);
+ (void) rpmsqEnable(-SIGINT, NULL);
+ (void) rpmsqEnable(-SIGTERM,NULL);
+ (void) rpmsqEnable(-SIGQUIT,NULL);
+ (void) rpmsqEnable(-SIGPIPE,NULL);
+ return rc;
+}
+
+int rpmdbSync(rpmdb db)
+{
+ int dbix;
+ int rc = 0;
+
+ if (db == NULL) return 0;
+ for (dbix = 0; dbix < db->db_ndbi; dbix++) {
+ int xx;
+ if (db->_dbi[dbix] == NULL)
+ continue;
+ if (db->_dbi[dbix]->dbi_no_dbsync)
+ continue;
+ xx = dbiSync(db->_dbi[dbix], 0);
+ if (xx && rc == 0) rc = xx;
+ }
+ return rc;
+}
+
+/* FIX: dbTemplate structure assignment */
+static
+rpmdb newRpmdb(const char * root,
+ const char * home,
+ int mode, int perms, int flags)
+{
+ rpmdb db = xcalloc(sizeof(*db), 1);
+ const char * epfx = _DB_ERRPFX;
+ static int _initialized = 0;
+
+ if (!_initialized) {
+ _db_filter_dups = rpmExpandNumeric("%{_filterdbdups}");
+ _initialized = 1;
+ }
+
+ *db = dbTemplate; /* structure assignment */
+
+ db->_dbi = NULL;
+
+ if (!(perms & 0600)) perms = 0644; /* XXX sanity */
+
+ if (mode >= 0) db->db_mode = mode;
+ if (perms >= 0) db->db_perms = perms;
+ if (flags >= 0) db->db_flags = flags;
+
+ if (root && *root) {
+ db->db_root = rpmGetPath(root, NULL);
+ } else
+ db->db_root = rpmGetPath(_DB_ROOT, NULL);
+
+ db->db_home = rpmGetPath( (home && *home ? home : _DB_HOME), NULL);
+ if (!(db->db_home && db->db_home[0] != '%')) {
+ rpmlog(RPMLOG_ERR, _("no dbpath has been set\n"));
+ db->db_root = _free(db->db_root);
+ db->db_home = _free(db->db_home);
+ db = _free(db);
+ return NULL;
+ }
+ db->db_errpfx = rpmExpand( (epfx && *epfx ? epfx : _DB_ERRPFX), NULL);
+ db->db_remove_env = 0;
+ db->db_filter_dups = _db_filter_dups;
+ db->db_ndbi = dbiTags.max;
+ db->_dbi = xcalloc(db->db_ndbi, sizeof(*db->_dbi));
+ db->nrefs = 0;
+ return rpmdbLink(db, RPMDBG_M("rpmdbCreate"));
+}
+
+static int openDatabase(const char * prefix,
+ const char * dbpath,
+ int _dbapi, rpmdb *dbp,
+ int mode, int perms, int flags)
+{
+ rpmdb db;
+ int rc, xx;
+ int justCheck = flags & RPMDB_FLAG_JUSTCHECK;
+ int minimal = flags & RPMDB_FLAG_MINIMAL;
+
+ dbiTagsInit();
+
+ /* Insure that _dbapi has one of -1, 1, 2, or 3 */
+ if (_dbapi < -1 || _dbapi > 4)
+ _dbapi = -1;
+ if (_dbapi == 0)
+ _dbapi = 1;
+
+ if (dbp)
+ *dbp = NULL;
+ if (mode & O_WRONLY)
+ return 1;
+
+ db = newRpmdb(prefix, dbpath, mode, perms, flags);
+ if (db == NULL)
+ return 1;
+
+ (void) rpmsqEnable(SIGHUP, NULL);
+ (void) rpmsqEnable(SIGINT, NULL);
+ (void) rpmsqEnable(SIGTERM,NULL);
+ (void) rpmsqEnable(SIGQUIT,NULL);
+ (void) rpmsqEnable(SIGPIPE,NULL);
+
+ db->db_api = _dbapi;
+
+ { int dbix;
+
+ rc = 0;
+ if (dbiTags.tags != NULL)
+ for (dbix = 0; rc == 0 && dbix < dbiTags.max; dbix++) {
+ dbiIndex dbi;
+ rpmTag rpmtag;
+
+ /* Filter out temporary databases */
+ if (isTemporaryDB((rpmtag = dbiTags.tags[dbix])))
+ continue;
+
+ dbi = dbiOpen(db, rpmtag, 0);
+ if (dbi == NULL) {
+ rc = -2;
+ break;
+ }
+
+ switch (rpmtag) {
+ case RPMDBI_PACKAGES:
+ if (dbi == NULL) rc |= 1;
+#if 0
+ /* XXX open only Packages, indices created on the fly. */
+ if (db->db_api == 3)
+#endif
+ goto exit;
+ break;
+ case RPMTAG_NAME:
+ if (dbi == NULL) rc |= 1;
+ if (minimal)
+ goto exit;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+exit:
+ if (rc || justCheck || dbp == NULL)
+ xx = rpmdbClose(db);
+ else {
+ db->db_next = rpmdbRock;
+ rpmdbRock = db;
+ *dbp = db;
+ }
+
+ return rc;
+}
+
+rpmdb rpmdbUnlink(rpmdb db, const char * msg)
+{
+if (_rpmdb_debug)
+fprintf(stderr, "--> db %p -- %d %s\n", db, db->nrefs, msg);
+ db->nrefs--;
+ return NULL;
+}
+
+rpmdb rpmdbLink(rpmdb db, const char * msg)
+{
+ db->nrefs++;
+if (_rpmdb_debug)
+fprintf(stderr, "--> db %p ++ %d %s\n", db, db->nrefs, msg);
+ return db;
+}
+
+/* XXX python/rpmmodule.c */
+int rpmdbOpen (const char * prefix, rpmdb *dbp, int mode, int perms)
+{
+ int _dbapi = rpmExpandNumeric("%{_dbapi}");
+ return openDatabase(prefix, NULL, _dbapi, dbp, mode, perms, 0);
+}
+
+int rpmdbInit (const char * prefix, int perms)
+{
+ rpmdb db = NULL;
+ int _dbapi = rpmExpandNumeric("%{_dbapi}");
+ int rc;
+
+ rc = openDatabase(prefix, NULL, _dbapi, &db, (O_CREAT | O_RDWR),
+ perms, RPMDB_FLAG_JUSTCHECK);
+ if (db != NULL) {
+ int xx;
+ xx = rpmdbOpenAll(db);
+ if (xx && rc == 0) rc = xx;
+ xx = rpmdbClose(db);
+ if (xx && rc == 0) rc = xx;
+ db = NULL;
+ }
+ return rc;
+}
+
+int rpmdbVerify(const char * prefix)
+{
+ rpmdb db = NULL;
+ int _dbapi = rpmExpandNumeric("%{_dbapi}");
+ int rc = 0;
+
+ rc = openDatabase(prefix, NULL, _dbapi, &db, O_RDONLY, 0644, 0);
+
+ if (db != NULL) {
+ int dbix;
+ int xx;
+ rc = rpmdbOpenAll(db);
+
+ for (dbix = db->db_ndbi; --dbix >= 0; ) {
+ if (db->_dbi[dbix] == NULL)
+ continue;
+ /* FIX: double indirection. */
+ xx = dbiVerify(db->_dbi[dbix], 0);
+ if (xx && rc == 0) rc = xx;
+ db->_dbi[dbix] = NULL;
+ }
+
+ /* FIX: db->_dbi[] may be NULL. */
+ xx = rpmdbClose(db);
+ if (xx && rc == 0) rc = xx;
+ db = NULL;
+ }
+ return rc;
+}
+
+/**
+ * Find file matches in database.
+ * @param db rpm database
+ * @param filespec
+ * @param key
+ * @param data
+ * @param matches
+ * @return 0 on success, 1 on not found, -2 on error
+ */
+static int rpmdbFindByFile(rpmdb db, const char * filespec,
+ DBT * key, DBT * data, dbiIndexSet * matches)
+{
+ HGE_t hge = (HGE_t)headerGetEntryMinMemory;
+ HFD_t hfd = headerFreeData;
+ char * dirName;
+ const char * baseName;
+ rpmTagType bnt, dnt;
+ fingerPrintCache fpc;
+ fingerPrint fp1;
+ dbiIndex dbi = NULL;
+ DBC * dbcursor;
+ dbiIndexSet allMatches = NULL;
+ dbiIndexItem rec = NULL;
+ int i;
+ int rc;
+ int xx;
+
+ *matches = NULL;
+ if (filespec == NULL) return -2;
+
+ if ((baseName = strrchr(filespec, '/')) != NULL) {
+ size_t len = baseName - filespec + 1;
+ dirName = strncpy(xmalloc(len + 1), filespec, len);
+ dirName[len] = '\0';
+ baseName++;
+ } else {
+ dirName = xstrdup("");
+ baseName = filespec;
+ }
+ if (baseName == NULL)
+ return -2;
+
+ fpc = fpCacheCreate(20);
+ fp1 = fpLookup(fpc, dirName, baseName, 1);
+ free(dirName);
+
+ dbi = dbiOpen(db, RPMTAG_BASENAMES, 0);
+ if (dbi != NULL) {
+ dbcursor = NULL;
+ xx = dbiCopen(dbi, dbi->dbi_txnid, &dbcursor, 0);
+
+ key->data = (void *) baseName;
+ key->size = strlen(baseName);
+ if (key->size == 0)
+ key->size++; /* XXX "/" fixup. */
+
+ rc = dbiGet(dbi, dbcursor, key, data, DB_SET);
+ if (rc > 0) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index\n"),
+ rc, (char*)key->data, rpmTagGetName(dbi->dbi_rpmtag));
+ }
+
+ if (rc == 0)
+ (void) dbt2set(dbi, data, &allMatches);
+
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+ } else
+ rc = -2;
+
+ if (rc) {
+ allMatches = dbiFreeIndexSet(allMatches);
+ fpc = fpCacheFree(fpc);
+ return rc;
+ }
+
+ *matches = xcalloc(1, sizeof(**matches));
+ rec = dbiIndexNewItem(0, 0);
+ i = 0;
+ if (allMatches != NULL)
+ while (i < allMatches->count) {
+ const char ** baseNames, ** dirNames;
+ uint32_t * dirIndexes;
+ unsigned int offset = dbiIndexRecordOffset(allMatches, i);
+ unsigned int prevoff;
+ Header h;
+
+ { rpmdbMatchIterator mi;
+ mi = rpmdbInitIterator(db, RPMDBI_PACKAGES, &offset, sizeof(offset));
+ h = rpmdbNextIterator(mi);
+ if (h)
+ h = headerLink(h);
+ mi = rpmdbFreeIterator(mi);
+ }
+
+ if (h == NULL) {
+ i++;
+ continue;
+ }
+
+ xx = hge(h, RPMTAG_BASENAMES, &bnt, (rpm_data_t *) &baseNames, NULL);
+ xx = hge(h, RPMTAG_DIRNAMES, &dnt, (rpm_data_t *) &dirNames, NULL);
+ xx = hge(h, RPMTAG_DIRINDEXES, NULL, (rpm_data_t *) &dirIndexes, NULL);
+
+ do {
+ fingerPrint fp2;
+ int num = dbiIndexRecordFileNumber(allMatches, i);
+
+ fp2 = fpLookup(fpc, dirNames[dirIndexes[num]], baseNames[num], 1);
+ if (FP_EQUAL(fp1, fp2)) {
+ rec->hdrNum = dbiIndexRecordOffset(allMatches, i);
+ rec->tagNum = dbiIndexRecordFileNumber(allMatches, i);
+ xx = dbiAppendSet(*matches, rec, 1, sizeof(*rec), 0);
+ }
+
+ prevoff = offset;
+ i++;
+ if (i < allMatches->count)
+ offset = dbiIndexRecordOffset(allMatches, i);
+ } while (i < allMatches->count && offset == prevoff);
+
+ baseNames = hfd(baseNames, bnt);
+ dirNames = hfd(dirNames, dnt);
+ h = headerFree(h);
+ }
+
+ rec = _free(rec);
+ allMatches = dbiFreeIndexSet(allMatches);
+
+ fpc = fpCacheFree(fpc);
+
+ if ((*matches)->count == 0) {
+ *matches = dbiFreeIndexSet(*matches);
+ return 1;
+ }
+
+ return 0;
+}
+
+/* XXX python/upgrade.c, install.c, uninstall.c */
+int rpmdbCountPackages(rpmdb db, const char * name)
+{
+ DBC * dbcursor = NULL;
+ DBT key;
+ DBT data;
+ dbiIndex dbi;
+ int rc;
+ int xx;
+
+ if (db == NULL)
+ return 0;
+
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+
+ dbi = dbiOpen(db, RPMTAG_NAME, 0);
+ if (dbi == NULL)
+ return 0;
+
+ key.data = (void *) name;
+ key.size = strlen(name);
+
+ xx = dbiCopen(dbi, dbi->dbi_txnid, &dbcursor, 0);
+ rc = dbiGet(dbi, dbcursor, &key, &data, DB_SET);
+#ifndef SQLITE_HACK
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+#endif
+
+ if (rc == 0) { /* success */
+ dbiIndexSet matches;
+ /* FIX: matches might be NULL */
+ matches = NULL;
+ (void) dbt2set(dbi, &data, &matches);
+ if (matches) {
+ rc = dbiIndexSetCount(matches);
+ matches = dbiFreeIndexSet(matches);
+ }
+ } else
+ if (rc == DB_NOTFOUND) { /* not found */
+ rc = 0;
+ } else { /* error */
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index\n"),
+ rc, (char*)key.data, rpmTagGetName(dbi->dbi_rpmtag));
+ rc = -1;
+ }
+
+#ifdef SQLITE_HACK
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+#endif
+
+ return rc;
+}
+
+/**
+ * Attempt partial matches on name[-version[-release]] strings.
+ * @param dbi index database handle (always RPMTAG_NAME)
+ * @param dbcursor index database cursor
+ * @param key search key/length/flags
+ * @param data search data/length/flags
+ * @param name package name
+ * @param version package version (can be a pattern)
+ * @param release package release (can be a pattern)
+ * @retval matches set of header instances that match
+ * @return RPMRC_OK on match, RPMRC_NOMATCH or RPMRC_FAIL
+ */
+static rpmRC dbiFindMatches(dbiIndex dbi, DBC * dbcursor,
+ DBT * key, DBT * data,
+ const char * name,
+ const char * version,
+ const char * release,
+ dbiIndexSet * matches)
+{
+ int gotMatches = 0;
+ int rc;
+ int i;
+
+ key->data = (void *) name;
+ key->size = strlen(name);
+
+ rc = dbiGet(dbi, dbcursor, key, data, DB_SET);
+
+ if (rc == 0) { /* success */
+ (void) dbt2set(dbi, data, matches);
+ if (version == NULL && release == NULL)
+ return RPMRC_OK;
+ } else
+ if (rc == DB_NOTFOUND) { /* not found */
+ return RPMRC_NOTFOUND;
+ } else { /* error */
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index\n"),
+ rc, (char*)key->data, rpmTagGetName(dbi->dbi_rpmtag));
+ return RPMRC_FAIL;
+ }
+
+ /* Make sure the version and release match. */
+ for (i = 0; i < dbiIndexSetCount(*matches); i++) {
+ unsigned int recoff = dbiIndexRecordOffset(*matches, i);
+ rpmdbMatchIterator mi;
+ Header h;
+
+ if (recoff == 0)
+ continue;
+
+ mi = rpmdbInitIterator(dbi->dbi_rpmdb,
+ RPMDBI_PACKAGES, &recoff, sizeof(recoff));
+
+ /* Set iterator selectors for version/release if available. */
+ if (version &&
+ rpmdbSetIteratorRE(mi, RPMTAG_VERSION, RPMMIRE_DEFAULT, version))
+ {
+ rc = RPMRC_FAIL;
+ goto exit;
+ }
+ if (release &&
+ rpmdbSetIteratorRE(mi, RPMTAG_RELEASE, RPMMIRE_DEFAULT, release))
+ {
+ rc = RPMRC_FAIL;
+ goto exit;
+ }
+
+ h = rpmdbNextIterator(mi);
+ if (h)
+ (*matches)->recs[gotMatches++] = (*matches)->recs[i];
+ else
+ (*matches)->recs[i].hdrNum = 0;
+ mi = rpmdbFreeIterator(mi);
+ }
+
+ if (gotMatches) {
+ (*matches)->count = gotMatches;
+ rc = RPMRC_OK;
+ } else
+ rc = RPMRC_NOTFOUND;
+
+exit:
+/* FIX: double indirection */
+ if (rc && matches && *matches)
+ *matches = dbiFreeIndexSet(*matches);
+ return rc;
+}
+
+/**
+ * Lookup by name, name-version, and finally by name-version-release.
+ * Both version and release can be patterns.
+ * @todo Name must be an exact match, as name is a db key.
+ * @param dbi index database handle (always RPMTAG_NAME)
+ * @param dbcursor index database cursor
+ * @param key search key/length/flags
+ * @param data search data/length/flags
+ * @param arg name[-version[-release]] string
+ * @retval matches set of header instances that match
+ * @return RPMRC_OK on match, RPMRC_NOMATCH or RPMRC_FAIL
+ */
+static rpmRC dbiFindByLabel(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ const char * arg, dbiIndexSet * matches)
+{
+ const char * release;
+ char * localarg;
+ char * s;
+ char c;
+ int brackets;
+ rpmRC rc;
+
+ if (arg == NULL || strlen(arg) == 0) return RPMRC_NOTFOUND;
+
+ /* did they give us just a name? */
+ rc = dbiFindMatches(dbi, dbcursor, key, data, arg, NULL, NULL, matches);
+ if (rc != RPMRC_NOTFOUND) return rc;
+
+ /* FIX: double indirection */
+ *matches = dbiFreeIndexSet(*matches);
+
+ /* maybe a name and a release */
+ localarg = xmalloc(strlen(arg) + 1);
+ s = stpcpy(localarg, arg);
+
+ c = '\0';
+ brackets = 0;
+ for (s -= 1; s > localarg; s--) {
+ switch (*s) {
+ case '[':
+ brackets = 1;
+ break;
+ case ']':
+ if (c != '[') brackets = 0;
+ break;
+ }
+ c = *s;
+ if (!brackets && *s == '-')
+ break;
+ }
+
+ /* FIX: *matches may be NULL. */
+ if (s == localarg) {
+ rc = RPMRC_NOTFOUND;
+ goto exit;
+ }
+
+ *s = '\0';
+ rc = dbiFindMatches(dbi, dbcursor, key, data, localarg, s + 1, NULL, matches);
+ if (rc != RPMRC_NOTFOUND) goto exit;
+
+ /* FIX: double indirection */
+ *matches = dbiFreeIndexSet(*matches);
+
+ /* how about name-version-release? */
+
+ release = s + 1;
+
+ c = '\0';
+ brackets = 0;
+ for (; s > localarg; s--) {
+ switch (*s) {
+ case '[':
+ brackets = 1;
+ break;
+ case ']':
+ if (c != '[') brackets = 0;
+ break;
+ }
+ c = *s;
+ if (!brackets && *s == '-')
+ break;
+ }
+
+ if (s == localarg) {
+ rc = RPMRC_NOTFOUND;
+ goto exit;
+ }
+
+ *s = '\0';
+ /* FIX: *matches may be NULL. */
+ rc = dbiFindMatches(dbi, dbcursor, key, data, localarg, s + 1, release, matches);
+exit:
+ free(localarg);
+ return rc;
+}
+
+/**
+ * Rewrite a header into packages (if necessary) and free the header.
+ * Note: this is called from a markReplacedFiles iteration, and *must*
+ * preserve the "join key" (i.e. offset) for the header.
+ * @param mi database iterator
+ * @param dbi index database handle
+ * @return 0 on success
+ */
+static int miFreeHeader(rpmdbMatchIterator mi, dbiIndex dbi)
+{
+ int rc = 0;
+
+ if (mi == NULL || mi->mi_h == NULL)
+ return 0;
+
+ if (dbi && mi->mi_dbc && mi->mi_modified && mi->mi_prevoffset) {
+ DBT * key = &mi->mi_key;
+ DBT * data = &mi->mi_data;
+ sigset_t signalMask;
+ rpmRC rpmrc = RPMRC_NOTFOUND;
+ int xx;
+
+ key->data = (void *) &mi->mi_prevoffset;
+ key->size = sizeof(mi->mi_prevoffset);
+ data->data = headerUnload(mi->mi_h);
+ data->size = headerSizeof(mi->mi_h, HEADER_MAGIC_NO);
+
+ /* Check header digest/signature on blob export (if requested). */
+ if (mi->mi_hdrchk && mi->mi_ts) {
+ char * msg = NULL;
+ int lvl;
+
+ rpmrc = (*mi->mi_hdrchk) (mi->mi_ts, data->data, data->size, &msg);
+ lvl = (rpmrc == RPMRC_FAIL ? RPMLOG_ERR : RPMLOG_DEBUG);
+ rpmlog(lvl, "%s h#%8u %s",
+ (rpmrc == RPMRC_FAIL ? _("miFreeHeader: skipping") : "write"),
+ mi->mi_prevoffset, (msg ? msg : "\n"));
+ msg = _free(msg);
+ }
+
+ if (data->data != NULL && rpmrc != RPMRC_FAIL) {
+ (void) blockSignals(&signalMask);
+ rc = dbiPut(dbi, mi->mi_dbc, key, data, DB_KEYLAST);
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) storing record #%d into %s\n"),
+ rc, mi->mi_prevoffset, rpmTagGetName(dbi->dbi_rpmtag));
+ }
+ xx = dbiSync(dbi, 0);
+ (void) unblockSignals(&signalMask);
+ }
+ data->data = _free(data->data);
+ data->size = 0;
+ }
+
+ mi->mi_h = headerFree(mi->mi_h);
+
+ return rc;
+}
+
+rpmdbMatchIterator rpmdbFreeIterator(rpmdbMatchIterator mi)
+{
+ rpmdbMatchIterator * prev, next;
+ dbiIndex dbi;
+ int xx;
+ int i;
+
+ if (mi == NULL)
+ return NULL;
+
+ prev = &rpmmiRock;
+ while ((next = *prev) != NULL && next != mi)
+ prev = &next->mi_next;
+ if (next) {
+ *prev = next->mi_next;
+ next->mi_next = NULL;
+ }
+
+ dbi = dbiOpen(mi->mi_db, RPMDBI_PACKAGES, 0);
+ if (dbi == NULL) /* XXX can't happen */
+ return NULL;
+
+ xx = miFreeHeader(mi, dbi);
+
+ if (mi->mi_dbc)
+ xx = dbiCclose(dbi, mi->mi_dbc, 0);
+ mi->mi_dbc = NULL;
+
+ if (mi->mi_re != NULL)
+ for (i = 0; i < mi->mi_nre; i++) {
+ miRE mire = mi->mi_re + i;
+ mire->pattern = _free(mire->pattern);
+ if (mire->preg != NULL) {
+ regfree(mire->preg);
+ /* LCL: regfree has bogus only */
+ mire->preg = _free(mire->preg);
+ }
+ }
+ mi->mi_re = _free(mi->mi_re);
+
+ mi->mi_set = dbiFreeIndexSet(mi->mi_set);
+ mi->mi_keyp = _free(mi->mi_keyp);
+ mi->mi_db = rpmdbUnlink(mi->mi_db, RPMDBG_M("matchIterator"));
+
+ mi = _free(mi);
+
+ (void) rpmdbCheckSignals();
+
+ return mi;
+}
+
+unsigned int rpmdbGetIteratorOffset(rpmdbMatchIterator mi) {
+ return (mi ? mi->mi_offset : 0);
+}
+
+unsigned int rpmdbGetIteratorFileNum(rpmdbMatchIterator mi) {
+ return (mi ? mi->mi_filenum : 0);
+}
+
+int rpmdbGetIteratorCount(rpmdbMatchIterator mi) {
+ return (mi && mi->mi_set ? mi->mi_set->count : 0);
+}
+
+/**
+ * Return pattern match.
+ * @param mire match iterator regex
+ * @param val value to match
+ * @return 0 if pattern matches, >0 on nomatch, <0 on error
+ */
+static int miregexec(miRE mire, const char * val)
+{
+ int rc = 0;
+
+ switch (mire->mode) {
+ case RPMMIRE_STRCMP:
+ rc = strcmp(mire->pattern, val);
+ if (rc) rc = 1;
+ break;
+ case RPMMIRE_DEFAULT:
+ case RPMMIRE_REGEX:
+ rc = regexec(mire->preg, val, 0, NULL, mire->eflags);
+ if (rc && rc != REG_NOMATCH) {
+ char msg[256];
+ (void) regerror(rc, mire->preg, msg, sizeof(msg)-1);
+ msg[sizeof(msg)-1] = '\0';
+ rpmlog(RPMLOG_ERR, _("%s: regexec failed: %s\n"),
+ mire->pattern, msg);
+ rc = -1;
+ }
+ break;
+ case RPMMIRE_GLOB:
+ rc = fnmatch(mire->pattern, val, mire->fnflags);
+ if (rc && rc != FNM_NOMATCH)
+ rc = -1;
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * Compare iterator selectors by rpm tag (qsort/bsearch).
+ * @param a 1st iterator selector
+ * @param b 2nd iterator selector
+ * @return result of comparison
+ */
+static int mireCmp(const void * a, const void * b)
+{
+ const miRE mireA = (const miRE) a;
+ const miRE mireB = (const miRE) b;
+ return (mireA->tag - mireB->tag);
+}
+
+/**
+ * Copy pattern, escaping for appropriate mode.
+ * @param tag rpm tag
+ * @retval modep type of pattern match
+ * @param pattern pattern to duplicate
+ * @return duplicated pattern
+ */
+static char * mireDup(rpmTag tag, rpmMireMode *modep,
+ const char * pattern)
+{
+ const char * s;
+ char * pat;
+ char * t;
+ int brackets;
+ size_t nb;
+ int c;
+
+ switch (*modep) {
+ default:
+ case RPMMIRE_DEFAULT:
+ if (tag == RPMTAG_DIRNAMES || tag == RPMTAG_BASENAMES) {
+ *modep = RPMMIRE_GLOB;
+ pat = xstrdup(pattern);
+ break;
+ }
+
+ nb = strlen(pattern) + sizeof("^$");
+
+ /* Find no. of bytes needed for pattern. */
+ /* periods and plusses are escaped, splats become '.*' */
+ c = '\0';
+ brackets = 0;
+ for (s = pattern; *s != '\0'; s++) {
+ switch (*s) {
+ case '.':
+ case '+':
+ case '*':
+ if (!brackets) nb++;
+ break;
+ case '\\':
+ s++;
+ break;
+ case '[':
+ brackets = 1;
+ break;
+ case ']':
+ if (c != '[') brackets = 0;
+ break;
+ }
+ c = *s;
+ }
+
+ pat = t = xmalloc(nb);
+
+ if (pattern[0] != '^') *t++ = '^';
+
+ /* Copy pattern, escaping periods, prefixing splats with period. */
+ c = '\0';
+ brackets = 0;
+ for (s = pattern; *s != '\0'; s++, t++) {
+ switch (*s) {
+ case '.':
+ case '+':
+ if (!brackets) *t++ = '\\';
+ break;
+ case '*':
+ if (!brackets) *t++ = '.';
+ break;
+ case '\\':
+ *t++ = *s++;
+ break;
+ case '[':
+ brackets = 1;
+ break;
+ case ']':
+ if (c != '[') brackets = 0;
+ break;
+ }
+ c = *t = *s;
+ }
+
+ if (s > pattern && s[-1] != '$') *t++ = '$';
+ *t = '\0';
+ *modep = RPMMIRE_REGEX;
+ break;
+ case RPMMIRE_STRCMP:
+ case RPMMIRE_REGEX:
+ case RPMMIRE_GLOB:
+ pat = xstrdup(pattern);
+ break;
+ }
+
+ return pat;
+}
+
+int rpmdbSetIteratorRE(rpmdbMatchIterator mi, rpmTag tag,
+ rpmMireMode mode, const char * pattern)
+{
+ static rpmMireMode defmode = (rpmMireMode)-1;
+ miRE mire = NULL;
+ char * allpat = NULL;
+ int notmatch = 0;
+ regex_t * preg = NULL;
+ int cflags = 0;
+ int eflags = 0;
+ int fnflags = 0;
+ int rc = 0;
+
+ if (defmode == (rpmMireMode)-1) {
+ char *t = rpmExpand("%{?_query_selector_match}", NULL);
+
+ if (*t == '\0' || !strcmp(t, "default"))
+ defmode = RPMMIRE_DEFAULT;
+ else if (!strcmp(t, "strcmp"))
+ defmode = RPMMIRE_STRCMP;
+ else if (!strcmp(t, "regex"))
+ defmode = RPMMIRE_REGEX;
+ else if (!strcmp(t, "glob"))
+ defmode = RPMMIRE_GLOB;
+ else
+ defmode = RPMMIRE_DEFAULT;
+ t = _free(t);
+ }
+
+ if (mi == NULL || pattern == NULL)
+ return rc;
+
+ /* Leading '!' inverts pattern match sense, like "grep -v". */
+ if (*pattern == '!') {
+ notmatch = 1;
+ pattern++;
+ }
+
+ allpat = mireDup(tag, &mode, pattern);
+
+ if (mode == RPMMIRE_DEFAULT)
+ mode = defmode;
+
+ switch (mode) {
+ case RPMMIRE_DEFAULT:
+ case RPMMIRE_STRCMP:
+ break;
+ case RPMMIRE_REGEX:
+ preg = xcalloc(1, sizeof(*preg));
+ cflags = (REG_EXTENDED | REG_NOSUB);
+ rc = regcomp(preg, allpat, cflags);
+ if (rc) {
+ char msg[256];
+ (void) regerror(rc, preg, msg, sizeof(msg)-1);
+ msg[sizeof(msg)-1] = '\0';
+ rpmlog(RPMLOG_ERR, _("%s: regcomp failed: %s\n"), allpat, msg);
+ }
+ break;
+ case RPMMIRE_GLOB:
+ fnflags = FNM_PATHNAME | FNM_PERIOD;
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+
+ if (rc) {
+ /* FIX: mire has kept values */
+ allpat = _free(allpat);
+ if (preg) {
+ regfree(preg);
+ /* LCL: regfree has bogus only */
+ preg = _free(preg);
+ }
+ return rc;
+ }
+
+ mi->mi_re = xrealloc(mi->mi_re, (mi->mi_nre + 1) * sizeof(*mi->mi_re));
+ mire = mi->mi_re + mi->mi_nre;
+ mi->mi_nre++;
+
+ mire->tag = tag;
+ mire->mode = mode;
+ mire->pattern = allpat;
+ mire->notmatch = notmatch;
+ mire->preg = preg;
+ mire->cflags = cflags;
+ mire->eflags = eflags;
+ mire->fnflags = fnflags;
+
+ if (mi->mi_nre > 1)
+ qsort(mi->mi_re, mi->mi_nre, sizeof(*mi->mi_re), mireCmp);
+
+ return rc;
+}
+
+/**
+ * Return iterator selector match.
+ * @param mi rpm database iterator
+ * @return 1 if header should be skipped
+ */
+static int mireSkip (const rpmdbMatchIterator mi)
+{
+ HGE_t hge = (HGE_t) headerGetEntryMinMemory;
+ HFD_t hfd = (HFD_t) headerFreeData;
+ union {
+ void * ptr;
+ const char ** argv;
+ const char * str;
+ int32_t * i32p;
+ int16_t * i16p;
+ int8_t * i8p;
+ } u;
+ char numbuf[32];
+ rpmTagType t;
+ rpm_count_t c;
+ miRE mire;
+ static int32_t zero = 0;
+ int ntags = 0;
+ int nmatches = 0;
+ int i, j;
+ int rc;
+
+ if (mi->mi_h == NULL) /* XXX can't happen */
+ return 0;
+
+ /*
+ * Apply tag tests, implicitly "||" for multiple patterns/values of a
+ * single tag, implicitly "&&" between multiple tag patterns.
+ */
+ if ((mire = mi->mi_re) != NULL)
+ for (i = 0; i < mi->mi_nre; i++, mire++) {
+ int anymatch;
+
+ if (!hge(mi->mi_h, mire->tag, &t, (rpm_data_t *)&u, &c)) {
+ if (mire->tag != RPMTAG_EPOCH) {
+ ntags++;
+ continue;
+ }
+ t = RPM_INT32_TYPE;
+ u.i32p = &zero;
+ c = 1;
+ }
+
+ anymatch = 0; /* no matches yet */
+ while (1) {
+ switch (t) {
+ case RPM_CHAR_TYPE:
+ case RPM_INT8_TYPE:
+ sprintf(numbuf, "%d", (int) *u.i8p);
+ rc = miregexec(mire, numbuf);
+ if ((!rc && !mire->notmatch) || (rc && mire->notmatch))
+ anymatch++;
+ break;
+ case RPM_INT16_TYPE:
+ sprintf(numbuf, "%d", (int) *u.i16p);
+ rc = miregexec(mire, numbuf);
+ if ((!rc && !mire->notmatch) || (rc && mire->notmatch))
+ anymatch++;
+ break;
+ case RPM_INT32_TYPE:
+ sprintf(numbuf, "%d", (int) *u.i32p);
+ rc = miregexec(mire, numbuf);
+ if ((!rc && !mire->notmatch) || (rc && mire->notmatch))
+ anymatch++;
+ break;
+ case RPM_STRING_TYPE:
+ rc = miregexec(mire, u.str);
+ if ((!rc && !mire->notmatch) || (rc && mire->notmatch))
+ anymatch++;
+ break;
+ case RPM_I18NSTRING_TYPE:
+ case RPM_STRING_ARRAY_TYPE:
+ for (j = 0; j < c; j++) {
+ rc = miregexec(mire, u.argv[j]);
+ if ((!rc && !mire->notmatch) || (rc && mire->notmatch)) {
+ anymatch++;
+ break;
+ }
+ }
+ break;
+ case RPM_BIN_TYPE:
+ {
+ char * str = pgpHexStr((const unsigned char*) u.ptr, c);
+ rc = miregexec(mire, str);
+ if ((!rc && !mire->notmatch) || (rc && mire->notmatch))
+ anymatch++;
+ _free(str);
+ }
+ break;
+ case RPM_NULL_TYPE:
+ default:
+ break;
+ }
+ if ((i+1) < mi->mi_nre && mire[0].tag == mire[1].tag) {
+ i++;
+ mire++;
+ continue;
+ }
+ break;
+ }
+
+ if ((rpmTagGetType(mire->tag) & RPM_MASK_RETURN_TYPE) ==
+ RPM_ARRAY_RETURN_TYPE) {
+ u.ptr = hfd(u.ptr, t);
+ }
+
+ ntags++;
+ if (anymatch)
+ nmatches++;
+ }
+
+ return (ntags == nmatches ? 0 : 1);
+}
+
+int rpmdbSetIteratorRewrite(rpmdbMatchIterator mi, int rewrite)
+{
+ int rc;
+ if (mi == NULL)
+ return 0;
+ rc = (mi->mi_cflags & DB_WRITECURSOR) ? 1 : 0;
+ if (rewrite)
+ mi->mi_cflags |= DB_WRITECURSOR;
+ else
+ mi->mi_cflags &= ~DB_WRITECURSOR;
+ return rc;
+}
+
+int rpmdbSetIteratorModified(rpmdbMatchIterator mi, int modified)
+{
+ int rc;
+ if (mi == NULL)
+ return 0;
+ rc = mi->mi_modified;
+ mi->mi_modified = modified;
+ return rc;
+}
+
+int rpmdbSetHdrChk(rpmdbMatchIterator mi, rpmts ts,
+ rpmRC (*hdrchk) (rpmts ts, const void *uh, size_t uc, char ** msg))
+{
+ int rc = 0;
+ if (mi == NULL)
+ return 0;
+ /* XXX forward linkage prevents rpmtsLink */
+ mi->mi_ts = ts;
+ mi->mi_hdrchk = hdrchk;
+ return rc;
+}
+
+
+/* FIX: mi->mi_key.data may be NULL */
+Header rpmdbNextIterator(rpmdbMatchIterator mi)
+{
+ dbiIndex dbi;
+ void * uh;
+ size_t uhlen;
+ DBT * key;
+ DBT * data;
+ void * keyp;
+ size_t keylen;
+ int rc;
+ int xx;
+
+ if (mi == NULL)
+ return NULL;
+
+ dbi = dbiOpen(mi->mi_db, RPMDBI_PACKAGES, 0);
+ if (dbi == NULL)
+ return NULL;
+
+ /*
+ * Cursors are per-iterator, not per-dbi, so get a cursor for the
+ * iterator on 1st call. If the iteration is to rewrite headers, and the
+ * CDB model is used for the database, then the cursor needs to
+ * marked with DB_WRITECURSOR as well.
+ */
+ if (mi->mi_dbc == NULL)
+ xx = dbiCopen(dbi, dbi->dbi_txnid, &mi->mi_dbc, mi->mi_cflags);
+
+ key = &mi->mi_key;
+ memset(key, 0, sizeof(*key));
+ data = &mi->mi_data;
+ memset(data, 0, sizeof(*data));
+
+top:
+ uh = NULL;
+ uhlen = 0;
+
+ do {
+ union _dbswap mi_offset;
+
+ if (mi->mi_set) {
+ if (!(mi->mi_setx < mi->mi_set->count))
+ return NULL;
+ mi->mi_offset = dbiIndexRecordOffset(mi->mi_set, mi->mi_setx);
+ mi->mi_filenum = dbiIndexRecordFileNumber(mi->mi_set, mi->mi_setx);
+ mi_offset.ui = mi->mi_offset;
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(mi_offset);
+ keyp = &mi_offset;
+ keylen = sizeof(mi_offset.ui);
+ } else {
+
+ key->data = keyp = (void *)mi->mi_keyp;
+ key->size = keylen = mi->mi_keylen;
+ data->data = uh;
+ data->size = uhlen;
+#if !defined(_USE_COPY_LOAD)
+ data->flags |= DB_DBT_MALLOC;
+#endif
+ rc = dbiGet(dbi, mi->mi_dbc, key, data,
+ (key->data == NULL ? DB_NEXT : DB_SET));
+ data->flags = 0;
+ keyp = key->data;
+ keylen = key->size;
+ uh = data->data;
+ uhlen = data->size;
+
+ /*
+ * If we got the next key, save the header instance number.
+ *
+ * For db3 Packages, instance 0 (i.e. mi->mi_setx == 0) is the
+ * largest header instance in the database, and should be
+ * skipped.
+ */
+ if (keyp && mi->mi_setx && rc == 0) {
+ memcpy(&mi_offset, keyp, sizeof(mi_offset.ui));
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(mi_offset);
+ mi->mi_offset = mi_offset.ui;
+ }
+
+ /* Terminate on error or end of keys */
+ if (rc || (mi->mi_setx && mi->mi_offset == 0))
+ return NULL;
+ }
+ mi->mi_setx++;
+ } while (mi->mi_offset == 0);
+
+ /* If next header is identical, return it now. */
+ if (mi->mi_prevoffset && mi->mi_offset == mi->mi_prevoffset)
+ return mi->mi_h;
+
+ /* Retrieve next header blob for index iterator. */
+ if (uh == NULL) {
+ key->data = keyp;
+ key->size = keylen;
+#if !defined(_USE_COPY_LOAD)
+ data->flags |= DB_DBT_MALLOC;
+#endif
+ rc = dbiGet(dbi, mi->mi_dbc, key, data, DB_SET);
+ data->flags = 0;
+ keyp = key->data;
+ keylen = key->size;
+ uh = data->data;
+ uhlen = data->size;
+ if (rc)
+ return NULL;
+ }
+
+ /* Rewrite current header (if necessary) and unlink. */
+ xx = miFreeHeader(mi, dbi);
+
+ /* Is this the end of the iteration? */
+ if (uh == NULL)
+ return NULL;
+
+ /* Check header digest/signature once (if requested). */
+ if (mi->mi_hdrchk && mi->mi_ts) {
+ rpmRC rpmrc = RPMRC_NOTFOUND;
+
+ /* Don't bother re-checking a previously read header. */
+ if (mi->mi_db->db_bits) {
+ pbm_set * set;
+
+ set = PBM_REALLOC((pbm_set **)&mi->mi_db->db_bits,
+ &mi->mi_db->db_nbits, mi->mi_offset);
+ if (PBM_ISSET(mi->mi_offset, set))
+ rpmrc = RPMRC_OK;
+ }
+
+ /* If blob is unchecked, check blob import consistency now. */
+ if (rpmrc != RPMRC_OK) {
+ char * msg = NULL;
+ int lvl;
+
+ rpmrc = (*mi->mi_hdrchk) (mi->mi_ts, uh, uhlen, &msg);
+ lvl = (rpmrc == RPMRC_FAIL ? RPMLOG_ERR : RPMLOG_DEBUG);
+ rpmlog(lvl, "%s h#%8u %s",
+ (rpmrc == RPMRC_FAIL ? _("rpmdbNextIterator: skipping") : " read"),
+ mi->mi_offset, (msg ? msg : "\n"));
+ msg = _free(msg);
+
+ /* Mark header checked. */
+ if (mi->mi_db && mi->mi_db->db_bits && rpmrc == RPMRC_OK) {
+ pbm_set * set;
+
+ set = PBM_REALLOC((pbm_set **)&mi->mi_db->db_bits,
+ &mi->mi_db->db_nbits, mi->mi_offset);
+ PBM_SET(mi->mi_offset, set);
+ }
+
+ /* Skip damaged and inconsistent headers. */
+ if (rpmrc == RPMRC_FAIL)
+ goto top;
+ }
+ }
+
+ /* Did the header blob load correctly? */
+#if !defined(_USE_COPY_LOAD)
+ mi->mi_h = headerLoad(uh);
+ if (mi->mi_h)
+ mi->mi_h->flags |= HEADERFLAG_ALLOCATED;
+#else
+ mi->mi_h = headerCopyLoad(uh);
+#endif
+ if (mi->mi_h == NULL || !headerIsEntry(mi->mi_h, RPMTAG_NAME)) {
+ rpmlog(RPMLOG_ERR,
+ _("rpmdb: damaged header #%u retrieved -- skipping.\n"),
+ mi->mi_offset);
+ goto top;
+ }
+
+ /*
+ * Skip this header if iterator selector (if any) doesn't match.
+ */
+ if (mireSkip(mi)) {
+ /* XXX hack, can't restart with Packages locked on single instance. */
+ if (mi->mi_set || mi->mi_keyp == NULL)
+ goto top;
+ return NULL;
+ }
+
+ mi->mi_prevoffset = mi->mi_offset;
+ mi->mi_modified = 0;
+
+ return mi->mi_h;
+}
+
+static void rpmdbSortIterator(rpmdbMatchIterator mi)
+{
+ if (mi && mi->mi_set && mi->mi_set->recs && mi->mi_set->count > 0) {
+ /*
+ * mergesort is much (~10x with lots of identical basenames) faster
+ * than pure quicksort, but glibc uses msort_with_tmp() on stack.
+ */
+#if defined(__GLIBC__)
+ qsort(mi->mi_set->recs, mi->mi_set->count,
+ sizeof(*mi->mi_set->recs), hdrNumCmp);
+#else
+ mergesort(mi->mi_set->recs, mi->mi_set->count,
+ sizeof(*mi->mi_set->recs), hdrNumCmp);
+#endif
+ mi->mi_sorted = 1;
+ }
+}
+
+/* LCL: segfault */
+static int rpmdbGrowIterator(rpmdbMatchIterator mi, int fpNum)
+{
+ DBC * dbcursor;
+ DBT * key;
+ DBT * data;
+ dbiIndex dbi = NULL;
+ dbiIndexSet set;
+ int rc;
+ int xx;
+ int i;
+
+ if (mi == NULL)
+ return 1;
+
+ dbcursor = mi->mi_dbc;
+ key = &mi->mi_key;
+ data = &mi->mi_data;
+ if (key->data == NULL)
+ return 1;
+
+ dbi = dbiOpen(mi->mi_db, mi->mi_rpmtag, 0);
+ if (dbi == NULL)
+ return 1;
+
+ xx = dbiCopen(dbi, dbi->dbi_txnid, &dbcursor, 0);
+ rc = dbiGet(dbi, dbcursor, key, data, DB_SET);
+#ifndef SQLITE_HACK
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+#endif
+
+ if (rc) { /* error/not found */
+ if (rc != DB_NOTFOUND)
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index\n"),
+ rc, (char*)key->data, rpmTagGetName(dbi->dbi_rpmtag));
+#ifdef SQLITE_HACK
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+#endif
+ return rc;
+ }
+
+ set = NULL;
+ (void) dbt2set(dbi, data, &set);
+ for (i = 0; i < set->count; i++)
+ set->recs[i].fpNum = fpNum;
+
+#ifdef SQLITE_HACK
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+#endif
+
+ if (mi->mi_set == NULL) {
+ mi->mi_set = set;
+ } else {
+#if 0
+fprintf(stderr, "+++ %d = %d + %d\t\"%s\"\n", (mi->mi_set->count + set->count), mi->mi_set->count, set->count, ((char *)key->data));
+#endif
+ mi->mi_set->recs = xrealloc(mi->mi_set->recs,
+ (mi->mi_set->count + set->count) * sizeof(*(mi->mi_set->recs)));
+ memcpy(mi->mi_set->recs + mi->mi_set->count, set->recs,
+ set->count * sizeof(*(mi->mi_set->recs)));
+ mi->mi_set->count += set->count;
+ set = dbiFreeIndexSet(set);
+ }
+
+ return rc;
+}
+
+int rpmdbPruneIterator(rpmdbMatchIterator mi, int * hdrNums,
+ int nHdrNums, int sorted)
+{
+ if (mi == NULL || hdrNums == NULL || nHdrNums <= 0)
+ return 1;
+
+ if (mi->mi_set)
+ (void) dbiPruneSet(mi->mi_set, hdrNums, nHdrNums, sizeof(*hdrNums), sorted);
+ return 0;
+}
+
+int rpmdbAppendIterator(rpmdbMatchIterator mi, const int * hdrNums, int nHdrNums)
+{
+ if (mi == NULL || hdrNums == NULL || nHdrNums <= 0)
+ return 1;
+
+ if (mi->mi_set == NULL)
+ mi->mi_set = xcalloc(1, sizeof(*mi->mi_set));
+ (void) dbiAppendSet(mi->mi_set, hdrNums, nHdrNums, sizeof(*hdrNums), 0);
+ return 0;
+}
+
+rpmdbMatchIterator rpmdbInitIterator(rpmdb db, rpmTag rpmtag,
+ const void * keyp, size_t keylen)
+{
+ rpmdbMatchIterator mi;
+ DBT * key;
+ DBT * data;
+ dbiIndexSet set = NULL;
+ dbiIndex dbi;
+ void * mi_keyp = NULL;
+ int isLabel = 0;
+
+ if (db == NULL)
+ return NULL;
+
+ (void) rpmdbCheckSignals();
+
+ /* XXX HACK to remove rpmdbFindByLabel/findMatches from the API */
+ if (rpmtag == RPMDBI_LABEL) {
+ rpmtag = RPMTAG_NAME;
+ isLabel = 1;
+ }
+
+ dbi = dbiOpen(db, rpmtag, 0);
+ if (dbi == NULL)
+ return NULL;
+
+ /* Chain cursors for teardown on abnormal exit. */
+ mi = xcalloc(1, sizeof(*mi));
+ mi->mi_next = rpmmiRock;
+ rpmmiRock = mi;
+
+ key = &mi->mi_key;
+ data = &mi->mi_data;
+
+ /*
+ * Handle label and file name special cases.
+ * Otherwise, retrieve join keys for secondary lookup.
+ */
+ if (rpmtag != RPMDBI_PACKAGES && keyp) {
+ DBC * dbcursor = NULL;
+ int rc;
+ int xx;
+
+ if (isLabel) {
+ xx = dbiCopen(dbi, dbi->dbi_txnid, &dbcursor, 0);
+ rc = dbiFindByLabel(dbi, dbcursor, key, data, keyp, &set);
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+ } else if (rpmtag == RPMTAG_BASENAMES) {
+ rc = rpmdbFindByFile(db, keyp, key, data, &set);
+ } else {
+ xx = dbiCopen(dbi, dbi->dbi_txnid, &dbcursor, 0);
+
+ key->data = (void *) keyp;
+ key->size = keylen;
+ if (key->data && key->size == 0)
+ key->size = strlen((char *)key->data);
+ if (key->data && key->size == 0)
+ key->size++; /* XXX "/" fixup. */
+
+ rc = dbiGet(dbi, dbcursor, key, data, DB_SET);
+ if (rc > 0) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index\n"),
+ rc, (key->data ? (char *)key->data : "???"),
+ rpmTagGetName(dbi->dbi_rpmtag));
+ }
+
+ /* Join keys need to be native endian internally. */
+ if (rc == 0)
+ (void) dbt2set(dbi, data, &set);
+
+ xx = dbiCclose(dbi, dbcursor, 0);
+ dbcursor = NULL;
+ }
+ if (rc) { /* error/not found */
+ set = dbiFreeIndexSet(set);
+ rpmmiRock = mi->mi_next;
+ mi->mi_next = NULL;
+ mi = _free(mi);
+ return NULL;
+ }
+ }
+
+ /* Copy the retrieval key, byte swapping header instance if necessary. */
+ if (keyp) {
+ switch (rpmtag) {
+ case RPMDBI_PACKAGES:
+ { union _dbswap *k;
+
+ assert(keylen == sizeof(k->ui)); /* xxx programmer error */
+ k = xmalloc(sizeof(*k));
+ memcpy(k, keyp, keylen);
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(*k);
+ mi_keyp = k;
+ } break;
+ default:
+ { char * k;
+ if (keylen == 0)
+ keylen = strlen(keyp);
+ k = xmalloc(keylen + 1);
+ memcpy(k, keyp, keylen);
+ k[keylen] = '\0'; /* XXX assumes strings */
+ mi_keyp = k;
+ } break;
+ }
+ }
+
+ mi->mi_keyp = mi_keyp;
+ mi->mi_keylen = keylen;
+
+ mi->mi_db = rpmdbLink(db, RPMDBG_M("matchIterator"));
+ mi->mi_rpmtag = rpmtag;
+
+ mi->mi_dbc = NULL;
+ mi->mi_set = set;
+ mi->mi_setx = 0;
+ mi->mi_h = NULL;
+ mi->mi_sorted = 0;
+ mi->mi_cflags = 0;
+ mi->mi_modified = 0;
+ mi->mi_prevoffset = 0;
+ mi->mi_offset = 0;
+ mi->mi_filenum = 0;
+ mi->mi_nre = 0;
+ mi->mi_re = NULL;
+
+ mi->mi_ts = NULL;
+ mi->mi_hdrchk = NULL;
+
+ return mi;
+}
+
+/* XXX psm.c */
+int rpmdbRemove(rpmdb db, int rid, unsigned int hdrNum,
+ rpmts ts,
+ rpmRC (*hdrchk) (rpmts ts, const void *uh, size_t uc, char ** msg))
+{
+ DBC * dbcursor = NULL;
+ DBT key;
+ DBT data;
+ union _dbswap mi_offset;
+ HGE_t hge = (HGE_t)headerGetEntryMinMemory;
+ HFD_t hfd = headerFreeData;
+ Header h;
+ sigset_t signalMask;
+ int ret = 0;
+ int rc = 0;
+
+ if (db == NULL)
+ return 0;
+
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+
+ { rpmdbMatchIterator mi;
+ mi = rpmdbInitIterator(db, RPMDBI_PACKAGES, &hdrNum, sizeof(hdrNum));
+ h = rpmdbNextIterator(mi);
+ if (h)
+ h = headerLink(h);
+ mi = rpmdbFreeIterator(mi);
+ }
+
+ if (h == NULL) {
+ rpmlog(RPMLOG_ERR, _("%s: cannot read header at 0x%x\n"),
+ "rpmdbRemove", hdrNum);
+ return 1;
+ }
+
+ {
+ char *nevra = headerGetNEVRA(h, NULL);
+ rpmlog(RPMLOG_DEBUG, " --- h#%8u %s\n", hdrNum, nevra);
+ free(nevra);
+ }
+
+ (void) blockSignals(&signalMask);
+
+ /* FIX: rpmvals heartburn */
+ { int dbix;
+ dbiIndexItem rec = dbiIndexNewItem(hdrNum, 0);
+
+ if (dbiTags.tags != NULL)
+ for (dbix = 0; dbix < dbiTags.max; dbix++) {
+ dbiIndex dbi;
+ const char *av[1];
+ const char ** rpmvals = NULL;
+ rpmTagType rpmtype = 0;
+ rpm_count_t rpmcnt = 0;
+ rpmTag rpmtag;
+ int xx;
+ rpm_count_t i, j;
+
+ dbi = NULL;
+ rpmtag = dbiTags.tags[dbix];
+
+ /* Filter out temporary databases */
+ if (isTemporaryDB(rpmtag))
+ continue;
+
+ switch ((rpm_tag_t) rpmtag) {
+ case RPMDBI_PACKAGES:
+ dbi = dbiOpen(db, rpmtag, 0);
+ if (dbi == NULL) /* XXX shouldn't happen */
+ continue;
+
+ mi_offset.ui = hdrNum;
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(mi_offset);
+ key.data = &mi_offset;
+ key.size = sizeof(mi_offset.ui);
+
+ rc = dbiCopen(dbi, dbi->dbi_txnid, &dbcursor, DB_WRITECURSOR);
+ rc = dbiGet(dbi, dbcursor, &key, &data, DB_SET);
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) setting header #%d record for %s removal\n"),
+ rc, hdrNum, rpmTagGetName(dbi->dbi_rpmtag));
+ } else
+ rc = dbiDel(dbi, dbcursor, &key, &data, 0);
+ xx = dbiCclose(dbi, dbcursor, DB_WRITECURSOR);
+ dbcursor = NULL;
+ if (!dbi->dbi_no_dbsync)
+ xx = dbiSync(dbi, 0);
+ continue;
+ break;
+ }
+
+ if (!hge(h, rpmtag, &rpmtype, (rpm_data_t *) &rpmvals, &rpmcnt))
+ continue;
+
+ dbi = dbiOpen(db, rpmtag, 0);
+ if (dbi != NULL) {
+ int printed;
+
+ if (rpmtype == RPM_STRING_TYPE) {
+ /* XXX force uniform headerGetEntry return */
+ av[0] = (const char *) rpmvals;
+ rpmvals = av;
+ rpmcnt = 1;
+ }
+
+ printed = 0;
+ xx = dbiCopen(dbi, dbi->dbi_txnid, &dbcursor, DB_WRITECURSOR);
+ for (i = 0; i < rpmcnt; i++) {
+ dbiIndexSet set;
+ int stringvalued;
+ uint8_t bin[32];
+
+ switch (dbi->dbi_rpmtag) {
+ case RPMTAG_FILEMD5S:
+ /* Filter out empty MD5 strings. */
+ if (!(rpmvals[i] && *rpmvals[i] != '\0'))
+ continue;
+ break;
+ default:
+ break;
+ }
+
+ /* Identify value pointer and length. */
+ stringvalued = 0;
+ switch (rpmtype) {
+ case RPM_CHAR_TYPE:
+ case RPM_INT8_TYPE:
+ key.size = sizeof(RPM_CHAR_TYPE);
+ key.data = rpmvals + i;
+ break;
+ case RPM_INT16_TYPE:
+ key.size = sizeof(int16_t);
+ key.data = rpmvals + i;
+ break;
+ case RPM_INT32_TYPE:
+ key.size = sizeof(int32_t);
+ key.data = rpmvals + i;
+ break;
+ case RPM_BIN_TYPE:
+ key.size = rpmcnt;
+ key.data = rpmvals;
+ rpmcnt = 1; /* XXX break out of loop. */
+ break;
+ case RPM_STRING_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ rpmcnt = 1; /* XXX break out of loop. */
+ case RPM_STRING_ARRAY_TYPE:
+ /* Convert from hex to binary. */
+ if (dbi->dbi_rpmtag == RPMTAG_FILEMD5S) {
+ const char * s;
+ uint8_t * t;
+
+ s = rpmvals[i];
+ t = bin;
+ for (j = 0; j < 16; j++, t++, s += 2)
+ *t = (rnibble(s[0]) << 4) | rnibble(s[1]);
+ key.data = bin;
+ key.size = 16;
+ break;
+ }
+ /* Extract the pubkey id from the base64 blob. */
+ if (dbi->dbi_rpmtag == RPMTAG_PUBKEYS) {
+ int nbin = pgpExtractPubkeyFingerprint(rpmvals[i], bin);
+ if (nbin <= 0)
+ continue;
+ key.data = bin;
+ key.size = nbin;
+ break;
+ }
+ default:
+ key.data = (void *) rpmvals[i];
+ key.size = strlen(rpmvals[i]);
+ stringvalued = 1;
+ break;
+ }
+
+ if (!printed) {
+ if (rpmcnt == 1 && stringvalued) {
+ rpmlog(RPMLOG_DEBUG,
+ "removing \"%s\" from %s index.\n",
+ (char *)key.data, rpmTagGetName(dbi->dbi_rpmtag));
+ } else {
+ rpmlog(RPMLOG_DEBUG,
+ "removing %d entries from %s index.\n",
+ rpmcnt, rpmTagGetName(dbi->dbi_rpmtag));
+ }
+ printed++;
+ }
+
+ /* XXX
+ * This is almost right, but, if there are duplicate tag
+ * values, there will be duplicate attempts to remove
+ * the header instance. It's faster to just ignore errors
+ * than to do things correctly.
+ */
+
+ /*
+ * XXX with duplicates, an accurate data value and
+ * DB_GET_BOTH is needed.
+ * */
+ set = NULL;
+
+ if (key.size == 0)
+ key.size = strlen((char *)key.data);
+ if (key.size == 0)
+ key.size++; /* XXX "/" fixup. */
+
+ rc = dbiGet(dbi, dbcursor, &key, &data, DB_SET);
+ if (rc == 0) { /* success */
+ (void) dbt2set(dbi, &data, &set);
+ } else if (rc == DB_NOTFOUND) { /* not found */
+ continue;
+ } else { /* error */
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) setting \"%s\" records from %s index\n"),
+ rc, (char*)key.data, rpmTagGetName(dbi->dbi_rpmtag));
+ ret += 1;
+ continue;
+ }
+
+ rc = dbiPruneSet(set, rec, 1, sizeof(*rec), 1);
+
+ /* If nothing was pruned, then don't bother updating. */
+ if (rc) {
+ set = dbiFreeIndexSet(set);
+ continue;
+ }
+
+ if (set->count > 0) {
+ (void) set2dbt(dbi, &data, set);
+ rc = dbiPut(dbi, dbcursor, &key, &data, DB_KEYLAST);
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) storing record \"%s\" into %s\n"),
+ rc, (char*)key.data, rpmTagGetName(dbi->dbi_rpmtag));
+ ret += 1;
+ }
+ data.data = _free(data.data);
+ data.size = 0;
+ } else {
+ rc = dbiDel(dbi, dbcursor, &key, &data, 0);
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) removing record \"%s\" from %s\n"),
+ rc, (char*)key.data, rpmTagGetName(dbi->dbi_rpmtag));
+ ret += 1;
+ }
+ }
+ set = dbiFreeIndexSet(set);
+ }
+
+ xx = dbiCclose(dbi, dbcursor, DB_WRITECURSOR);
+ dbcursor = NULL;
+
+ if (!dbi->dbi_no_dbsync)
+ xx = dbiSync(dbi, 0);
+ }
+
+ if (rpmtype != RPM_BIN_TYPE) /* XXX WTFO? HACK ALERT */
+ rpmvals = hfd(rpmvals, rpmtype);
+ rpmtype = 0;
+ rpmcnt = 0;
+ }
+
+ rec = _free(rec);
+ }
+
+ (void) unblockSignals(&signalMask);
+
+ h = headerFree(h);
+
+ /* XXX return ret; */
+ return 0;
+}
+
+/* XXX install.c */
+int rpmdbAdd(rpmdb db, int iid, Header h,
+ rpmts ts,
+ rpmRC (*hdrchk) (rpmts ts, const void *uh, size_t uc, char ** msg))
+{
+ DBC * dbcursor = NULL;
+ DBT key;
+ DBT data;
+ HGE_t hge = (HGE_t)headerGetEntryMinMemory;
+ HFD_t hfd = headerFreeData;
+ sigset_t signalMask;
+ const char ** baseNames;
+ rpmTagType bnt;
+ rpm_count_t count = 0;
+ dbiIndex dbi;
+ int dbix;
+ union _dbswap mi_offset;
+ unsigned int hdrNum = 0;
+ int ret = 0;
+ int rc;
+ int xx;
+
+ if (db == NULL)
+ return 0;
+
+ memset(&key, 0, sizeof(key));
+ memset(&data, 0, sizeof(data));
+
+#ifdef NOTYET /* XXX headerRemoveEntry() broken on dribbles. */
+ xx = headerRemoveEntry(h, RPMTAG_REMOVETID);
+#endif
+ if (iid != 0 && iid != -1) {
+ rpm_tid_t tid = iid;
+ if (!headerIsEntry(h, RPMTAG_INSTALLTID))
+ xx = headerAddEntry(h, RPMTAG_INSTALLTID, RPM_INT32_TYPE, &tid, 1);
+ }
+
+ /*
+ * If old style filename tags is requested, the basenames need to be
+ * retrieved early, and the header needs to be converted before
+ * being written to the package header database.
+ */
+
+ xx = hge(h, RPMTAG_BASENAMES, &bnt, (rpm_data_t *) &baseNames, &count);
+
+ (void) blockSignals(&signalMask);
+
+ {
+ unsigned int firstkey = 0;
+ void * keyp = &firstkey;
+ size_t keylen = sizeof(firstkey);
+ void * datap = NULL;
+ size_t datalen = 0;
+
+ dbi = dbiOpen(db, RPMDBI_PACKAGES, 0);
+ if (dbi != NULL) {
+
+ /* XXX db0: hack to pass sizeof header to fadAlloc */
+ datap = h;
+ datalen = headerSizeof(h, HEADER_MAGIC_NO);
+
+ xx = dbiCopen(dbi, dbi->dbi_txnid, &dbcursor, DB_WRITECURSOR);
+
+ /* Retrieve join key for next header instance. */
+
+ key.data = keyp;
+ key.size = keylen;
+ data.data = datap;
+ data.size = datalen;
+ ret = dbiGet(dbi, dbcursor, &key, &data, DB_SET);
+ keyp = key.data;
+ keylen = key.size;
+ datap = data.data;
+ datalen = data.size;
+
+ hdrNum = 0;
+ if (ret == 0 && datap) {
+ memcpy(&mi_offset, datap, sizeof(mi_offset.ui));
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(mi_offset);
+ hdrNum = mi_offset.ui;
+ }
+ ++hdrNum;
+ mi_offset.ui = hdrNum;
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(mi_offset);
+ if (ret == 0 && datap) {
+ memcpy(datap, &mi_offset, sizeof(mi_offset.ui));
+ } else {
+ datap = &mi_offset;
+ datalen = sizeof(mi_offset.ui);
+ }
+
+ key.data = keyp;
+ key.size = keylen;
+ data.data = datap;
+ data.size = datalen;
+
+ ret = dbiPut(dbi, dbcursor, &key, &data, DB_KEYLAST);
+ xx = dbiSync(dbi, 0);
+
+ xx = dbiCclose(dbi, dbcursor, DB_WRITECURSOR);
+ dbcursor = NULL;
+ }
+
+ }
+
+ if (ret) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) allocating new package instance\n"), ret);
+ goto exit;
+ }
+
+ /* Now update the indexes */
+
+ if (hdrNum)
+ {
+ dbiIndexItem rec = dbiIndexNewItem(hdrNum, 0);
+
+ if (dbiTags.tags != NULL)
+ for (dbix = 0; dbix < dbiTags.max; dbix++) {
+ const char *av[1];
+ const char **rpmvals = NULL;
+ rpmTagType rpmtype = 0;
+ rpm_count_t rpmcnt = 0;
+ rpmTag rpmtag;
+ rpm_flag_t * requireFlags;
+ rpmRC rpmrc;
+ int i, j;
+
+ rpmrc = RPMRC_NOTFOUND;
+ dbi = NULL;
+ requireFlags = NULL;
+ rpmtag = dbiTags.tags[dbix];
+
+ /* Filter out temporary databases */
+ if (isTemporaryDB(rpmtag))
+ continue;
+
+ switch (rpmtag) {
+ case RPMDBI_PACKAGES:
+ dbi = dbiOpen(db, rpmtag, 0);
+ if (dbi == NULL) /* XXX shouldn't happen */
+ continue;
+ xx = dbiCopen(dbi, dbi->dbi_txnid, &dbcursor, DB_WRITECURSOR);
+
+ mi_offset.ui = hdrNum;
+ if (dbiByteSwapped(dbi) == 1)
+ _DBSWAP(mi_offset);
+ key.data = (void *) &mi_offset;
+ key.size = sizeof(mi_offset.ui);
+ data.data = headerUnload(h);
+ data.size = headerSizeof(h, HEADER_MAGIC_NO);
+
+ /* Check header digest/signature on blob export. */
+ if (hdrchk && ts) {
+ char * msg = NULL;
+ int lvl;
+
+ rpmrc = (*hdrchk) (ts, data.data, data.size, &msg);
+ lvl = (rpmrc == RPMRC_FAIL ? RPMLOG_ERR : RPMLOG_DEBUG);
+ rpmlog(lvl, "%s h#%8u %s",
+ (rpmrc == RPMRC_FAIL ? _("rpmdbAdd: skipping") : " +++"),
+ hdrNum, (msg ? msg : "\n"));
+ msg = _free(msg);
+ }
+
+ if (data.data != NULL && rpmrc != RPMRC_FAIL) {
+ xx = dbiPut(dbi, dbcursor, &key, &data, DB_KEYLAST);
+ xx = dbiSync(dbi, 0);
+ }
+ data.data = _free(data.data);
+ data.size = 0;
+ xx = dbiCclose(dbi, dbcursor, DB_WRITECURSOR);
+ dbcursor = NULL;
+ if (!dbi->dbi_no_dbsync)
+ xx = dbiSync(dbi, 0);
+ continue;
+ break;
+ case RPMTAG_BASENAMES: /* XXX preserve legacy behavior */
+ rpmtype = bnt;
+ rpmvals = baseNames;
+ rpmcnt = count;
+ break;
+ case RPMTAG_REQUIRENAME:
+ xx = hge(h, rpmtag, &rpmtype, (rpm_data_t *)&rpmvals, &rpmcnt);
+ xx = hge(h, RPMTAG_REQUIREFLAGS, NULL, (rpm_data_t *)&requireFlags, NULL);
+ break;
+ default:
+ xx = hge(h, rpmtag, &rpmtype, (rpm_data_t *)&rpmvals, &rpmcnt);
+ break;
+ }
+
+ if (rpmcnt == 0) {
+ if (rpmtag != RPMTAG_GROUP)
+ continue;
+
+ /* XXX preserve legacy behavior */
+ rpmtype = RPM_STRING_TYPE;
+ rpmvals = (const char **) "Unknown";
+ rpmcnt = 1;
+ }
+
+ dbi = dbiOpen(db, rpmtag, 0);
+ if (dbi != NULL) {
+ int printed;
+
+ if (rpmtype == RPM_STRING_TYPE) {
+ /* XXX force uniform headerGetEntry return */
+ av[0] = (const char *) rpmvals;
+ rpmvals = av;
+ rpmcnt = 1;
+ }
+
+ printed = 0;
+ xx = dbiCopen(dbi, dbi->dbi_txnid, &dbcursor, DB_WRITECURSOR);
+
+ for (i = 0; i < rpmcnt; i++) {
+ dbiIndexSet set;
+ int stringvalued;
+ uint8_t bin[32];
+ uint8_t * t;
+
+ /*
+ * Include the tagNum in all indices. rpm-3.0.4 and earlier
+ * included the tagNum only for files.
+ */
+ rec->tagNum = i;
+ switch (dbi->dbi_rpmtag) {
+ case RPMTAG_PUBKEYS:
+ break;
+ case RPMTAG_FILEMD5S:
+ /* Filter out empty MD5 strings. */
+ if (!(rpmvals[i] && *rpmvals[i] != '\0'))
+ continue;
+ break;
+ case RPMTAG_REQUIRENAME:
+ /* Filter out install prerequisites. */
+ if (requireFlags && isInstallPreReq(requireFlags[i]))
+ continue;
+ break;
+ case RPMTAG_TRIGGERNAME:
+ if (i) { /* don't add duplicates */
+ for (j = 0; j < i; j++) {
+ if (!strcmp(rpmvals[i], rpmvals[j]))
+ break;
+ }
+ if (j < i)
+ continue;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* Identify value pointer and length. */
+ stringvalued = 0;
+ switch (rpmtype) {
+ case RPM_CHAR_TYPE:
+ case RPM_INT8_TYPE:
+ key.size = sizeof(int8_t);
+ key.data = rpmvals + i;
+ break;
+ case RPM_INT16_TYPE:
+ key.size = sizeof(int16_t);
+ key.data = rpmvals + i;
+ break;
+ case RPM_INT32_TYPE:
+ key.size = sizeof(int32_t);
+ key.data = rpmvals + i;
+ break;
+ case RPM_BIN_TYPE:
+ key.size = rpmcnt;
+ key.data = rpmvals;
+ rpmcnt = 1; /* XXX break out of loop. */
+ break;
+ case RPM_STRING_TYPE:
+ case RPM_I18NSTRING_TYPE:
+ rpmcnt = 1; /* XXX break out of loop. */
+ case RPM_STRING_ARRAY_TYPE:
+ /* Convert from hex to binary. */
+ if (dbi->dbi_rpmtag == RPMTAG_FILEMD5S) {
+ const char * s;
+
+ s = rpmvals[i];
+ t = bin;
+ for (j = 0; j < 16; j++, t++, s += 2)
+ *t = (rnibble(s[0]) << 4) | rnibble(s[1]);
+ key.data = bin;
+ key.size = 16;
+ break;
+ }
+ /* Extract the pubkey id from the base64 blob. */
+ if (dbi->dbi_rpmtag == RPMTAG_PUBKEYS) {
+ int nbin = pgpExtractPubkeyFingerprint(rpmvals[i], bin);
+ if (nbin <= 0)
+ continue;
+ key.data = bin;
+ key.size = nbin;
+ break;
+ }
+ default:
+ key.data = (void *) rpmvals[i];
+ key.size = strlen(rpmvals[i]);
+ stringvalued = 1;
+ break;
+ }
+
+ if (!printed) {
+ if (rpmcnt == 1 && stringvalued) {
+ rpmlog(RPMLOG_DEBUG,
+ "adding \"%s\" to %s index.\n",
+ (char *)key.data, rpmTagGetName(dbi->dbi_rpmtag));
+ } else {
+ rpmlog(RPMLOG_DEBUG,
+ "adding %d entries to %s index.\n",
+ rpmcnt, rpmTagGetName(dbi->dbi_rpmtag));
+ }
+ printed++;
+ }
+
+ /*
+ * XXX with duplicates, an accurate data value and
+ * DB_GET_BOTH is needed.
+ */
+
+ set = NULL;
+
+ if (key.size == 0)
+ key.size = strlen((char *)key.data);
+ if (key.size == 0)
+ key.size++; /* XXX "/" fixup. */
+
+ rc = dbiGet(dbi, dbcursor, &key, &data, DB_SET);
+ if (rc == 0) { /* success */
+ /* With duplicates, cursor is positioned, discard the record. */
+ if (!dbi->dbi_permit_dups)
+ (void) dbt2set(dbi, &data, &set);
+ } else if (rc != DB_NOTFOUND) { /* error */
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) getting \"%s\" records from %s index\n"),
+ rc, (char*)key.data, rpmTagGetName(dbi->dbi_rpmtag));
+ ret += 1;
+ continue;
+ }
+
+ if (set == NULL) /* not found or duplicate */
+ set = xcalloc(1, sizeof(*set));
+
+ (void) dbiAppendSet(set, rec, 1, sizeof(*rec), 0);
+
+ (void) set2dbt(dbi, &data, set);
+ rc = dbiPut(dbi, dbcursor, &key, &data, DB_KEYLAST);
+
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("error(%d) storing record %s into %s\n"),
+ rc, (char*)key.data, rpmTagGetName(dbi->dbi_rpmtag));
+ ret += 1;
+ }
+ data.data = _free(data.data);
+ data.size = 0;
+ set = dbiFreeIndexSet(set);
+ }
+
+ xx = dbiCclose(dbi, dbcursor, DB_WRITECURSOR);
+ dbcursor = NULL;
+
+ if (!dbi->dbi_no_dbsync)
+ xx = dbiSync(dbi, 0);
+ }
+
+ if (rpmtype != RPM_BIN_TYPE) /* XXX WTFO? HACK ALERT */
+ rpmvals = hfd(rpmvals, rpmtype);
+ rpmtype = 0;
+ rpmcnt = 0;
+ }
+
+ rec = _free(rec);
+ }
+
+exit:
+ (void) unblockSignals(&signalMask);
+
+ return ret;
+}
+
+#define _skip(_dn) { sizeof(_dn)-1, (_dn) }
+
+static struct skipDir_s {
+ int dnlen;
+ const char * dn;
+} skipDirs[] = {
+ { 0, NULL }
+};
+
+static int skipDir(const char * dn)
+{
+ struct skipDir_s * sd = skipDirs;
+ int dnlen;
+
+ dnlen = strlen(dn);
+ for (sd = skipDirs; sd->dn != NULL; sd++) {
+ if (dnlen < sd->dnlen)
+ continue;
+ if (strncmp(dn, sd->dn, sd->dnlen))
+ continue;
+ return 1;
+ }
+ return 0;
+}
+
+/* XXX transaction.c */
+int rpmdbFindFpList(rpmdb db, fingerPrint * fpList, dbiIndexSet * matchList,
+ int numItems)
+{
+ DBT * key;
+ DBT * data;
+ HGE_t hge = (HGE_t)headerGetEntryMinMemory;
+ HFD_t hfd = headerFreeData;
+ rpmdbMatchIterator mi;
+ fingerPrintCache fpc;
+ Header h;
+ int i, xx;
+
+ if (db == NULL) return 1;
+
+ mi = rpmdbInitIterator(db, RPMTAG_BASENAMES, NULL, 0);
+ if (mi == NULL) /* XXX should never happen */
+ return 1;
+
+ key = &mi->mi_key;
+ data = &mi->mi_data;
+
+ /* Gather all installed headers with matching basename's. */
+ for (i = 0; i < numItems; i++) {
+
+ matchList[i] = xcalloc(1, sizeof(*(matchList[i])));
+
+ key->data = (void *) fpList[i].baseName;
+ key->size = strlen((char *)key->data);
+ if (key->size == 0)
+ key->size++; /* XXX "/" fixup. */
+
+ if (skipDir(fpList[i].entry->dirName))
+ continue;
+
+ xx = rpmdbGrowIterator(mi, i);
+
+ }
+
+ if ((i = rpmdbGetIteratorCount(mi)) == 0) {
+ mi = rpmdbFreeIterator(mi);
+ return 0;
+ }
+ fpc = fpCacheCreate(i);
+
+ rpmdbSortIterator(mi);
+ /* iterator is now sorted by (recnum, filenum) */
+
+ /* For all installed headers with matching basename's ... */
+ if (mi != NULL)
+ while ((h = rpmdbNextIterator(mi)) != NULL) {
+ const char ** dirNames;
+ const char ** baseNames;
+ const char ** fullBaseNames;
+ rpmTagType bnt, dnt;
+ uint32_t * dirIndexes;
+ uint32_t * fullDirIndexes;
+ fingerPrint * fps;
+ dbiIndexItem im;
+ int start;
+ int num;
+ int end;
+
+ start = mi->mi_setx - 1;
+ im = mi->mi_set->recs + start;
+
+ /* Find the end of the set of matched basename's in this package. */
+ for (end = start + 1; end < mi->mi_set->count; end++) {
+ if (im->hdrNum != mi->mi_set->recs[end].hdrNum)
+ break;
+ }
+ num = end - start;
+
+ /* Compute fingerprints for this installed header's matches */
+ xx = hge(h, RPMTAG_BASENAMES, &bnt, (rpm_data_t *) &fullBaseNames, NULL);
+ xx = hge(h, RPMTAG_DIRNAMES, &dnt, (rpm_data_t *) &dirNames, NULL);
+ xx = hge(h, RPMTAG_DIRINDEXES, NULL, (rpm_data_t *) &fullDirIndexes, NULL);
+
+ baseNames = xcalloc(num, sizeof(*baseNames));
+ dirIndexes = xcalloc(num, sizeof(*dirIndexes));
+ for (i = 0; i < num; i++) {
+ baseNames[i] = fullBaseNames[im[i].tagNum];
+ dirIndexes[i] = fullDirIndexes[im[i].tagNum];
+ }
+
+ fps = xcalloc(num, sizeof(*fps));
+ fpLookupList(fpc, dirNames, baseNames, dirIndexes, num, fps);
+
+ /* Add db (recnum,filenum) to list for fingerprint matches. */
+ for (i = 0; i < num; i++, im++) {
+ /* FIX: fpList[].subDir may be NULL */
+ if (!FP_EQUAL(fps[i], fpList[im->fpNum]))
+ continue;
+ xx = dbiAppendSet(matchList[im->fpNum], im, 1, sizeof(*im), 0);
+ }
+
+ fps = _free(fps);
+ dirNames = hfd(dirNames, dnt);
+ fullBaseNames = hfd(fullBaseNames, bnt);
+ baseNames = _free(baseNames);
+ dirIndexes = _free(dirIndexes);
+
+ mi->mi_setx = end;
+ }
+
+ mi = rpmdbFreeIterator(mi);
+
+ fpc = fpCacheFree(fpc);
+
+ return 0;
+
+}
+
+/*
+ * Remove DB4 environment (and lock), ie the equivalent of
+ * rm -f <prefix>/<dbpath>/__db.???
+ * Environment files not existing is not an error, failure to unlink is,
+ * return zero on success.
+ * Only useful for BDB, dbapi 3 and 4.
+ * TODO/FIX: push this down to db3.c where it belongs
+ */
+static int cleanDbenv(const char *prefix, const char *dbpath)
+{
+ ARGV_t paths = NULL, p;
+ int rc = 0;
+ char *pattern = rpmGetPath(prefix, "/", dbpath, "/__db.???", NULL);
+
+ if (rpmGlob(pattern, NULL, &paths) == 0) {
+ for (p = paths; *p; p++) {
+ rc += unlink(*p);
+ }
+ argvFree(paths);
+ }
+ free(pattern);
+ return rc;
+}
+
+static int rpmdbRemoveDatabase(const char * prefix,
+ const char * dbpath, int _dbapi)
+{
+ int i;
+ char *path;
+ int xx;
+
+ switch (_dbapi) {
+ case 4:
+ case 3:
+ if (dbiTags.tags != NULL)
+ for (i = 0; i < dbiTags.max; i++) {
+ const char * base = rpmTagGetName(dbiTags.tags[i]);
+ path = rpmGetPath(prefix, "/", dbpath, "/", base, NULL);
+ if (access(path, F_OK) == 0)
+ xx = unlink(path);
+ free(path);
+ }
+ cleanDbenv(prefix, dbpath);
+ break;
+ case 2:
+ case 1:
+ case 0:
+ break;
+ }
+
+ path = rpmGetPath(prefix, "/", dbpath, NULL);
+ xx = rmdir(path);
+ free(path);
+
+ return 0;
+}
+
+static int rpmdbMoveDatabase(const char * prefix,
+ const char * olddbpath, int _olddbapi,
+ const char * newdbpath, int _newdbapi)
+{
+ int i;
+ struct stat st;
+ int rc = 0;
+ int xx;
+ int selinux = is_selinux_enabled() && (matchpathcon_init(NULL) != -1);
+ sigset_t sigMask;
+
+ blockSignals(&sigMask);
+ switch (_olddbapi) {
+ case 4:
+ /* Fall through */
+ case 3:
+ if (dbiTags.tags != NULL)
+ for (i = 0; i < dbiTags.max; i++) {
+ const char * base;
+ char *src, *dest;
+ rpmTag rpmtag;
+
+ /* Filter out temporary databases */
+ if (isTemporaryDB((rpmtag = dbiTags.tags[i])))
+ continue;
+
+ base = rpmTagGetName(rpmtag);
+ src = rpmGetPath(prefix, "/", olddbpath, "/", base, NULL);
+ dest = rpmGetPath(prefix, "/", newdbpath, "/", base, NULL);
+
+ if (access(src, F_OK) != 0)
+ goto cont;
+
+ /*
+ * Restore uid/gid/mode/mtime/security context if possible.
+ */
+ if (stat(dest, &st) < 0)
+ if (stat(src, &st) < 0)
+ goto cont;
+
+ if ((xx = rename(src, dest)) != 0) {
+ rc = 1;
+ goto cont;
+ }
+ xx = chown(dest, st.st_uid, st.st_gid);
+ xx = chmod(dest, (st.st_mode & 07777));
+ { struct utimbuf stamp;
+ stamp.actime = st.st_atime;
+ stamp.modtime = st.st_mtime;
+ xx = utime(dest, &stamp);
+ }
+
+ if (selinux) {
+ security_context_t scon = NULL;
+ if (matchpathcon(dest, st.st_mode, &scon) != -1) {
+ (void) setfilecon(dest, scon);
+ freecon(scon);
+ }
+ }
+
+cont:
+ free(src);
+ free(dest);
+ }
+
+ cleanDbenv(prefix, olddbpath);
+ cleanDbenv(prefix, newdbpath);
+ break;
+ case 2:
+ case 1:
+ case 0:
+ break;
+ }
+ unblockSignals(&sigMask);
+
+#ifdef SQLITE_HACK_XXX
+ if (rc || _olddbapi == _newdbapi)
+ return rc;
+
+ rc = rpmdbRemoveDatabase(prefix, newdbpath, _newdbapi);
+
+#endif
+ if (selinux) {
+ (void) matchpathcon_fini();
+ }
+ return rc;
+}
+
+int rpmdbRebuild(const char * prefix, rpmts ts,
+ rpmRC (*hdrchk) (rpmts ts, const void *uh, size_t uc, char ** msg))
+{
+ rpmdb olddb;
+ char * dbpath = NULL;
+ char * rootdbpath = NULL;
+ rpmdb newdb;
+ char * newdbpath = NULL;
+ char * newrootdbpath = NULL;
+ char * tfn;
+ int nocleanup = 1;
+ int failed = 0;
+ int removedir = 0;
+ int rc = 0, xx;
+ int _dbapi;
+ int _dbapi_rebuild;
+
+ if (prefix == NULL) prefix = "/";
+
+ _dbapi = rpmExpandNumeric("%{_dbapi}");
+ _dbapi_rebuild = rpmExpandNumeric("%{_dbapi_rebuild}");
+
+ tfn = rpmGetPath("%{?_dbpath}", NULL);
+ if (!(tfn && tfn[0] != '\0'))
+ {
+ rpmlog(RPMLOG_ERR, _("no dbpath has been set"));
+ rc = 1;
+ goto exit;
+ }
+ dbpath = rootdbpath = rpmGetPath(prefix, tfn, NULL);
+ if (!(prefix[0] == '/' && prefix[1] == '\0'))
+ dbpath += strlen(prefix) - 1;
+ tfn = _free(tfn);
+
+ tfn = rpmGetPath("%{?_dbpath_rebuild}", NULL);
+ if (!(tfn && tfn[0] != '\0' && strcmp(tfn, dbpath)))
+ {
+ tfn = _free(tfn);
+ rasprintf(&tfn, "%srebuilddb.%d", dbpath, (int) getpid());
+ nocleanup = 0;
+ }
+ newdbpath = newrootdbpath = rpmGetPath(prefix, tfn, NULL);
+ if (!(prefix[0] == '/' && prefix[1] == '\0'))
+ newdbpath += strlen(prefix) - 1;
+ tfn = _free(tfn);
+
+ rpmlog(RPMLOG_DEBUG, "rebuilding database %s into %s\n",
+ rootdbpath, newrootdbpath);
+
+ if (!access(newrootdbpath, F_OK)) {
+ rpmlog(RPMLOG_ERR, _("temporary database %s already exists\n"),
+ newrootdbpath);
+ rc = 1;
+ goto exit;
+ }
+
+ rpmlog(RPMLOG_DEBUG, "creating directory %s\n", newrootdbpath);
+ if (mkdir(newrootdbpath, 0755)) {
+ rpmlog(RPMLOG_ERR, _("failed to create directory %s: %s\n"),
+ newrootdbpath, strerror(errno));
+ rc = 1;
+ goto exit;
+ }
+ removedir = 1;
+
+ _rebuildinprogress = 0;
+
+ rpmlog(RPMLOG_DEBUG, "opening old database with dbapi %d\n",
+ _dbapi);
+ if (openDatabase(prefix, dbpath, _dbapi, &olddb, O_RDONLY, 0644,
+ RPMDB_FLAG_MINIMAL)) {
+ rc = 1;
+ goto exit;
+ }
+ _dbapi = olddb->db_api;
+ _rebuildinprogress = 1;
+ rpmlog(RPMLOG_DEBUG, "opening new database with dbapi %d\n",
+ _dbapi_rebuild);
+ (void) rpmDefineMacro(NULL, "_rpmdb_rebuild %{nil}", -1);
+ if (openDatabase(prefix, newdbpath, _dbapi_rebuild, &newdb, O_RDWR | O_CREAT, 0644, 0)) {
+ rc = 1;
+ goto exit;
+ }
+
+ _rebuildinprogress = 0;
+
+ _dbapi_rebuild = newdb->db_api;
+
+ { Header h = NULL;
+ rpmdbMatchIterator mi;
+#define _RECNUM rpmdbGetIteratorOffset(mi)
+
+ mi = rpmdbInitIterator(olddb, RPMDBI_PACKAGES, NULL, 0);
+ if (ts && hdrchk)
+ (void) rpmdbSetHdrChk(mi, ts, hdrchk);
+
+ while ((h = rpmdbNextIterator(mi)) != NULL) {
+
+ /* let's sanity check this record a bit, otherwise just skip it */
+ if (!(headerIsEntry(h, RPMTAG_NAME) &&
+ headerIsEntry(h, RPMTAG_VERSION) &&
+ headerIsEntry(h, RPMTAG_RELEASE) &&
+ headerIsEntry(h, RPMTAG_BUILDTIME)))
+ {
+ rpmlog(RPMLOG_ERR,
+ _("header #%u in the database is bad -- skipping.\n"),
+ _RECNUM);
+ continue;
+ }
+
+ /* Filter duplicate entries ? (bug in pre rpm-3.0.4) */
+ if (_db_filter_dups || newdb->db_filter_dups) {
+ const char * name, * version, * release;
+ int skip = 0;
+
+ (void) headerNVR(h, &name, &version, &release);
+
+ { rpmdbMatchIterator mi;
+ mi = rpmdbInitIterator(newdb, RPMTAG_NAME, name, 0);
+ (void) rpmdbSetIteratorRE(mi, RPMTAG_VERSION,
+ RPMMIRE_DEFAULT, version);
+ (void) rpmdbSetIteratorRE(mi, RPMTAG_RELEASE,
+ RPMMIRE_DEFAULT, release);
+ while (rpmdbNextIterator(mi)) {
+ skip = 1;
+ break;
+ }
+ mi = rpmdbFreeIterator(mi);
+ }
+
+ if (skip)
+ continue;
+ }
+
+ /* Deleted entries are eliminated in legacy headers by copy. */
+ { Header nh = (headerIsEntry(h, RPMTAG_HEADERIMAGE)
+ ? headerCopy(h) : NULL);
+ rc = rpmdbAdd(newdb, -1, (nh ? nh : h), ts, hdrchk);
+ nh = headerFree(nh);
+ }
+
+ if (rc) {
+ rpmlog(RPMLOG_ERR,
+ _("cannot add record originally at %u\n"), _RECNUM);
+ failed = 1;
+ break;
+ }
+ }
+
+ mi = rpmdbFreeIterator(mi);
+
+ }
+
+ xx = rpmdbClose(olddb);
+ xx = rpmdbClose(newdb);
+
+ if (failed) {
+ rpmlog(RPMLOG_NOTICE, _("failed to rebuild database: original database "
+ "remains in place\n"));
+
+ xx = rpmdbRemoveDatabase(prefix, newdbpath, _dbapi_rebuild);
+ rc = 1;
+ goto exit;
+ } else if (!nocleanup) {
+ if (rpmdbMoveDatabase(prefix, newdbpath, _dbapi_rebuild, dbpath, _dbapi)) {
+ rpmlog(RPMLOG_ERR, _("failed to replace old database with new "
+ "database!\n"));
+ rpmlog(RPMLOG_ERR, _("replace files in %s with files from %s "
+ "to recover"), dbpath, newdbpath);
+ rc = 1;
+ goto exit;
+ }
+ }
+ rc = 0;
+
+exit:
+ if (removedir && !(rc == 0 && nocleanup)) {
+ rpmlog(RPMLOG_DEBUG, "removing directory %s\n", newrootdbpath);
+ if (rmdir(newrootdbpath))
+ rpmlog(RPMLOG_ERR, _("failed to remove directory %s: %s\n"),
+ newrootdbpath, strerror(errno));
+ }
+ newrootdbpath = _free(newrootdbpath);
+ rootdbpath = _free(rootdbpath);
+
+ return rc;
+}
diff --git a/lib/rpmdb.h b/lib/rpmdb.h
new file mode 100644
index 000000000..9526133bd
--- /dev/null
+++ b/lib/rpmdb.h
@@ -0,0 +1,287 @@
+#ifndef H_RPMDB
+#define H_RPMDB
+
+/** \ingroup rpmdb dbi db1 db3
+ * \file rpmdb/rpmdb.h
+ * Access RPM indices using Berkeley DB interface(s).
+ */
+
+#include <rpm/rpmtypes.h>
+#include <rpm/rpmsw.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int _rpmdb_debug;
+
+/**
+ * Tag value pattern match mode.
+ */
+typedef enum rpmMireMode_e {
+ RPMMIRE_DEFAULT = 0, /*!< regex with \., .* and ^...$ added */
+ RPMMIRE_STRCMP = 1, /*!< strings using strcmp(3) */
+ RPMMIRE_REGEX = 2, /*!< regex(7) patterns through regcomp(3) */
+ RPMMIRE_GLOB = 3 /*!< glob(7) patterns through fnmatch(3) */
+} rpmMireMode;
+
+typedef enum rpmdbOpX_e {
+ RPMDB_OP_DBGET = 1,
+ RPMDB_OP_DBPUT = 2,
+ RPMDB_OP_DBDEL = 3,
+ RPMDB_OP_MAX = 4
+} rpmdbOpX;
+
+/** \ingroup rpmdb
+ * Retrieve operation timestamp from rpm database.
+ * @param db rpm database
+ * @param opx operation timestamp index
+ * @return pointer to operation timestamp.
+ */
+rpmop rpmdbOp(rpmdb db, rpmdbOpX opx);
+
+/** \ingroup rpmdb
+ * Set chrootDone flag, i.e. has chroot(2) been performed?
+ * @param db rpm database
+ * @param chrootDone new chrootDone flag
+ * @return previous chrootDone flag
+ */
+int rpmdbSetChrootDone(rpmdb db, int chrootDone);
+
+/** \ingroup rpmdb
+ * Unreference a database instance.
+ * @param db rpm database
+ * @param msg
+ * @return NULL always
+ */
+rpmdb rpmdbUnlink (rpmdb db, const char * msg);
+
+/** \ingroup rpmdb
+ * Reference a database instance.
+ * @param db rpm database
+ * @param msg
+ * @return new rpm database reference
+ */
+rpmdb rpmdbLink (rpmdb db, const char * msg);
+
+/** \ingroup rpmdb
+ * Open rpm database.
+ * @param prefix path to top of install tree
+ * @retval dbp address of rpm database
+ * @param mode open(2) flags: O_RDWR or O_RDONLY (O_CREAT also)
+ * @param perms database permissions
+ * @return 0 on success
+ */
+int rpmdbOpen (const char * prefix, rpmdb * dbp,
+ int mode, int perms);
+
+/** \ingroup rpmdb
+ * Initialize database.
+ * @param prefix path to top of install tree
+ * @param perms database permissions
+ * @return 0 on success
+ */
+int rpmdbInit(const char * prefix, int perms);
+
+/** \ingroup rpmdb
+ * Verify database components.
+ * @param prefix path to top of install tree
+ * @return 0 on success
+ */
+int rpmdbVerify(const char * prefix);
+
+/**
+ * Close a single database index.
+ * @param db rpm database
+ * @param rpmtag rpm tag
+ * @return 0 on success
+ */
+int rpmdbCloseDBI(rpmdb db, rpmTag rpmtag);
+
+/** \ingroup rpmdb
+ * Close all database indices and free rpmdb.
+ * @param db rpm database
+ * @return 0 on success
+ */
+int rpmdbClose (rpmdb db);
+
+/** \ingroup rpmdb
+ * Sync all database indices.
+ * @param db rpm database
+ * @return 0 on success
+ */
+int rpmdbSync (rpmdb db);
+
+/** \ingroup rpmdb
+ * Open all database indices.
+ * @param db rpm database
+ * @return 0 on success
+ */
+int rpmdbOpenAll (rpmdb db);
+
+/** \ingroup rpmdb
+ * Return number of instances of package in rpm database.
+ * @param db rpm database
+ * @param name rpm package name
+ * @return number of instances
+ */
+int rpmdbCountPackages(rpmdb db, const char * name);
+
+/** \ingroup rpmdb
+ * Return header join key for current position of rpm database iterator.
+ * @param mi rpm database iterator
+ * @return current header join key
+ */
+unsigned int rpmdbGetIteratorOffset(rpmdbMatchIterator mi);
+
+/** \ingroup rpmdb
+ * Return number of elements in rpm database iterator.
+ * @param mi rpm database iterator
+ * @return number of elements
+ */
+int rpmdbGetIteratorCount(rpmdbMatchIterator mi);
+
+/** \ingroup rpmdb
+ */
+unsigned int rpmdbGetIteratorFileNum(rpmdbMatchIterator mi);
+
+/** \ingroup rpmdb
+ * Append items to set of package instances to iterate.
+ * @param mi rpm database iterator
+ * @param hdrNums array of package instances
+ * @param nHdrNums number of elements in array
+ * @return 0 on success, 1 on failure (bad args)
+ */
+int rpmdbAppendIterator(rpmdbMatchIterator mi,
+ const int * hdrNums, int nHdrNums);
+
+/** \ingroup rpmdb
+ * Remove items from set of package instances to iterate.
+ * @note Sorted hdrNums are always passed in rpmlib.
+ * @param mi rpm database iterator
+ * @param hdrNums array of package instances
+ * @param nHdrNums number of elements in array
+ * @param sorted is the array sorted? (array will be sorted on return)
+ * @return 0 on success, 1 on failure (bad args)
+ */
+int rpmdbPruneIterator(rpmdbMatchIterator mi,
+ int * hdrNums, int nHdrNums, int sorted);
+
+/** \ingroup rpmdb
+ * Add pattern to iterator selector.
+ * @param mi rpm database iterator
+ * @param tag rpm tag
+ * @param mode type of pattern match
+ * @param pattern pattern to match
+ * @return 0 on success
+ */
+int rpmdbSetIteratorRE(rpmdbMatchIterator mi, rpmTag tag,
+ rpmMireMode mode, const char * pattern);
+
+/** \ingroup rpmdb
+ * Prepare iterator for lazy writes.
+ * @note Must be called before rpmdbNextIterator() with CDB model database.
+ * @param mi rpm database iterator
+ * @param rewrite new value of rewrite
+ * @return previous value
+ */
+int rpmdbSetIteratorRewrite(rpmdbMatchIterator mi, int rewrite);
+
+/** \ingroup rpmdb
+ * Modify iterator to mark header for lazy write on release.
+ * @param mi rpm database iterator
+ * @param modified new value of modified
+ * @return previous value
+ */
+int rpmdbSetIteratorModified(rpmdbMatchIterator mi, int modified);
+
+/** \ingroup rpmdb
+ * Modify iterator to verify retrieved header blobs.
+ * @param mi rpm database iterator
+ * @param ts transaction set
+ * @param (*hdrchk) headerCheck() vector
+ * @return 0 always
+ */
+int rpmdbSetHdrChk(rpmdbMatchIterator mi, rpmts ts,
+ rpmRC (*hdrchk) (rpmts ts, const void * uh, size_t uc, char ** msg));
+
+/** \ingroup rpmdb
+ * Return database iterator.
+ * @param db rpm database
+ * @param rpmtag rpm tag
+ * @param keyp key data (NULL for sequential access)
+ * @param keylen key data length (0 will use strlen(keyp))
+ * @return NULL on failure
+ */
+rpmdbMatchIterator rpmdbInitIterator(rpmdb db, rpmTag rpmtag,
+ const void * keyp, size_t keylen);
+
+/** \ingroup rpmdb
+ * Return next package header from iteration.
+ * @param mi rpm database iterator
+ * @return NULL on end of iteration.
+ */
+Header rpmdbNextIterator(rpmdbMatchIterator mi);
+
+/** \ingroup rpmdb
+ * Check for and exit on termination signals.
+ */
+int rpmdbCheckSignals(void);
+
+/** \ingroup rpmdb
+ * Check rpmdb signal handler for trapped signal and/or requested exit,
+ * clean up any open iterators and databases on termination condition.
+ * On non-zero exit any open references to rpmdb are invalid and cannot
+ * be accessed anymore, calling process should terminate immediately.
+ * @param terminate 0 to only check for signals, 1 to terminate anyway
+ * @return 0 to continue, 1 if termination cleanup was done.
+ */
+int rpmdbCheckTerminate(int terminate);
+
+/** \ingroup rpmdb
+ * Destroy rpm database iterator.
+ * @param mi rpm database iterator
+ * @return NULL always
+ */
+rpmdbMatchIterator rpmdbFreeIterator(rpmdbMatchIterator mi);
+
+/** \ingroup rpmdb
+ * Add package header to rpm database and indices.
+ * @param db rpm database
+ * @param iid install transaction id (iid = 0 or -1 to skip)
+ * @param h header
+ * @param ts (unused) transaction set (or NULL)
+ * @param (*hdrchk) (unused) headerCheck() vector (or NULL)
+ * @return 0 on success
+ */
+int rpmdbAdd(rpmdb db, int iid, Header h, rpmts ts,
+ rpmRC (*hdrchk) (rpmts ts, const void *uh, size_t uc, char ** msg));
+
+/** \ingroup rpmdb
+ * Remove package header from rpm database and indices.
+ * @param db rpm database
+ * @param rid (unused) remove transaction id (rid = 0 or -1 to skip)
+ * @param hdrNum package instance number in database
+ * @param ts (unused) transaction set (or NULL)
+ * @param (*hdrchk) (unused) headerCheck() vector (or NULL)
+ * @return 0 on success
+ */
+int rpmdbRemove(rpmdb db, int rid, unsigned int hdrNum,
+ rpmts ts,
+ rpmRC (*hdrchk) (rpmts ts, const void *uh, size_t uc, char ** msg));
+
+/** \ingroup rpmdb
+ * Rebuild database indices from package headers.
+ * @param prefix path to top of install tree
+ * @param ts transaction set (or NULL)
+ * @param (*hdrchk) headerCheck() vector (or NULL)
+ * @return 0 on success
+ */
+int rpmdbRebuild(const char * prefix, rpmts ts,
+ rpmRC (*hdrchk) (rpmts ts, const void *uh, size_t uc, char ** msg));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_RPMDB */
diff --git a/lib/rpmdb_internal.h b/lib/rpmdb_internal.h
new file mode 100644
index 000000000..40b3c1364
--- /dev/null
+++ b/lib/rpmdb_internal.h
@@ -0,0 +1,665 @@
+#ifndef H_RPMDB_INTERNAL
+#define H_RPMDB_INTERNAL
+
+#include <assert.h>
+#include <db.h>
+
+#include <rpm/rpmsw.h>
+#include <rpm/rpmtypes.h>
+
+/**
+ */
+typedef struct _dbiIndexItem * dbiIndexItem;
+
+/** \ingroup rpmdb
+ * A single element (i.e. inverted list from tag values) of a database.
+ */
+typedef struct _dbiIndexSet * dbiIndexSet;
+
+/**
+ */
+typedef struct _dbiIndex * dbiIndex;
+
+/* this will break if sizeof(int) != 4 */
+/** \ingroup dbi
+ * A single item from an index database (i.e. the "data returned").
+ * Note: In rpm-3.0.4 and earlier, this structure was passed by value,
+ * and was identical to the "data saved" structure below.
+ */
+struct _dbiIndexItem {
+ unsigned int hdrNum; /*!< header instance in db */
+ unsigned int tagNum; /*!< tag index in header */
+ unsigned int fpNum; /*!< finger print index */
+};
+
+/** \ingroup dbi
+ * Items retrieved from the index database.
+ */
+struct _dbiIndexSet {
+struct _dbiIndexItem * recs; /*!< array of records */
+ int count; /*!< number of records */
+};
+
+/** \ingroup dbi
+ * Private methods for accessing an index database.
+ */
+struct _dbiVec {
+ int dbv_major; /*!< Berkeley db version major */
+ int dbv_minor; /*!< Berkeley db version minor */
+ int dbv_patch; /*!< Berkeley db version patch */
+
+/** \ingroup dbi
+ * Return handle for an index database.
+ * @param rpmdb rpm database
+ * @param rpmtag rpm tag
+ * @return 0 on success
+ */
+ int (*open) (rpmdb rpmdb, rpmTag rpmtag, dbiIndex * dbip);
+
+/** \ingroup dbi
+ * Close index database, and destroy database handle.
+ * @param dbi index database handle
+ * @param flags (unused)
+ * @return 0 on success
+ */
+ int (*close) (dbiIndex dbi, unsigned int flags);
+
+/** \ingroup dbi
+ * Flush pending operations to disk.
+ * @param dbi index database handle
+ * @param flags (unused)
+ * @return 0 on success
+ */
+ int (*sync) (dbiIndex dbi, unsigned int flags);
+
+/** \ingroup dbi
+ * Associate secondary database with primary.
+ * @param dbi index database handle
+ * @param dbisecondary secondary index database handle
+ * @param callback create secondary key from primary (NULL if DB_RDONLY)
+ * @param flags DB_CREATE or 0
+ * @return 0 on success
+ */
+ int (*associate) (dbiIndex dbi, dbiIndex dbisecondary,
+ int (*callback) (DB *, const DBT *, const DBT *, DBT *),
+ unsigned int flags);
+
+/** \ingroup dbi
+ * Return join cursor for list of cursors.
+ * @param dbi index database handle
+ * @param curslist NULL terminated list of database cursors
+ * @retval dbcp address of join database cursor
+ * @param flags DB_JOIN_NOSORT or 0
+ * @return 0 on success
+ */
+ int (*join) (dbiIndex dbi, DBC ** curslist, DBC ** dbcp,
+ unsigned int flags);
+
+/** \ingroup dbi
+ * Open database cursor.
+ * @param dbi index database handle
+ * @param txnid database transaction handle
+ * @retval dbcp address of new database cursor
+ * @param dbiflags DB_WRITECURSOR or 0
+ * @return 0 on success
+ */
+ int (*copen) (dbiIndex dbi, DB_TXN * txnid,
+ DBC ** dbcp, unsigned int dbiflags);
+
+/** \ingroup dbi
+ * Close database cursor.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @param flags (unused)
+ * @return 0 on success
+ */
+ int (*cclose) (dbiIndex dbi, DBC * dbcursor, unsigned int flags);
+
+/** \ingroup dbi
+ * Duplicate a database cursor.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @retval dbcp address of new database cursor
+ * @param flags DB_POSITION for same position, 0 for uninitialized
+ * @return 0 on success
+ */
+ int (*cdup) (dbiIndex dbi, DBC * dbcursor, DBC ** dbcp,
+ unsigned int flags);
+
+/** \ingroup dbi
+ * Delete (key,data) pair(s) using db->del or dbcursor->c_del.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->del)
+ * @param key delete key value/length/flags
+ * @param data delete data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+ int (*cdel) (dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags);
+
+/** \ingroup dbi
+ * Retrieve (key,data) pair using db->get or dbcursor->c_get.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->get)
+ * @param key retrieve key value/length/flags
+ * @param data retrieve data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+ int (*cget) (dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags);
+
+/** \ingroup dbi
+ * Retrieve (key,data) pair using dbcursor->c_pget.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @param key secondary retrieve key value/length/flags
+ * @param pkey primary retrieve key value/length/flags
+ * @param data primary retrieve data value/length/flags
+ * @param flags DB_NEXT, DB_SET, or 0
+ * @return 0 on success
+ */
+ int (*cpget) (dbiIndex dbi, DBC * dbcursor,
+ DBT * key, DBT * pkey, DBT * data, unsigned int flags);
+
+/** \ingroup dbi
+ * Store (key,data) pair using db->put or dbcursor->c_put.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->put)
+ * @param key store key value/length/flags
+ * @param data store data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+ int (*cput) (dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags);
+
+/** \ingroup dbi
+ * Retrieve count of (possible) duplicate items using dbcursor->c_count.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @param countp address of count
+ * @param flags (unused)
+ * @return 0 on success
+ */
+ int (*ccount) (dbiIndex dbi, DBC * dbcursor,
+ unsigned int * countp,
+ unsigned int flags);
+
+/** \ingroup dbi
+ * Is database byte swapped?
+ * @param dbi index database handle
+ * @return 0 no
+ */
+ int (*byteswapped) (dbiIndex dbi);
+
+/** \ingroup dbi
+ * Save statistics in database handle.
+ * @param dbi index database handle
+ * @param flags retrieve statistics that don't require traversal?
+ * @return 0 on success
+ */
+ int (*stat) (dbiIndex dbi, unsigned int flags);
+};
+
+/** \ingroup dbi
+ * Describes an index database (implemented on Berkeley db3 functionality).
+ */
+struct _dbiIndex {
+ char * dbi_root; /*!< chroot(2) component of path */
+ char * dbi_home; /*!< directory component of path */
+ char * dbi_file; /*!< file component of path */
+ char * dbi_subfile;
+ char * dbi_tmpdir; /*!< temporary directory */
+
+ int dbi_ecflags; /*!< db_env_create flags */
+ int dbi_cflags; /*!< db_create flags */
+ int dbi_oeflags; /*!< common (db,dbenv}->open flags */
+ int dbi_eflags; /*!< dbenv->open flags */
+ int dbi_oflags; /*!< db->open flags */
+ int dbi_tflags; /*!< dbenv->txn_begin flags */
+
+ int dbi_type; /*!< db index type */
+ unsigned dbi_mode; /*!< mode to use on open */
+ int dbi_perms; /*!< file permission to use on open */
+ long dbi_shmkey; /*!< shared memory base key */
+ int dbi_api; /*!< Berkeley API type */
+
+ int dbi_verify_on_close;
+ int dbi_use_dbenv; /*!< use db environment? */
+ int dbi_permit_dups; /*!< permit duplicate entries? */
+ int dbi_no_fsync; /*!< no-op fsync for db */
+ int dbi_no_dbsync; /*!< don't call dbiSync */
+ int dbi_lockdbfd; /*!< do fcntl lock on db fd */
+ int dbi_temporary; /*!< non-persistent */
+ int dbi_debug;
+ int dbi_byteswapped;
+
+ char * dbi_host;
+ unsigned long dbi_cl_timeout;
+ unsigned long dbi_sv_timeout;
+
+ /* dbenv parameters */
+ int dbi_lorder;
+ /* XXX db-4.3.14 adds dbenv as 1st arg. */
+ void (*db_errcall) (void * dbenv, const char *db_errpfx, char *buffer);
+ FILE * dbi_errfile;
+ char * dbi_errpfx;
+ int dbi_verbose;
+ int dbi_region_init;
+ int dbi_tas_spins;
+ /* mpool sub-system parameters */
+ int dbi_mmapsize; /*!< (10Mb) */
+ int dbi_cachesize; /*!< (128Kb) */
+ /* lock sub-system parameters */
+ unsigned int dbi_lk_max;
+ unsigned int dbi_lk_detect;
+int dbi_lk_nmodes;
+unsigned char * dbi_lk_conflicts;
+ /* log sub-system parameters */
+ unsigned int dbi_lg_max;
+ unsigned int dbi_lg_bsize;
+ /* transaction sub-system parameters */
+ unsigned int dbi_tx_max;
+#if 0
+ int (*dbi_tx_recover) (DB_ENV *dbenv, DBT *log_rec,
+ DB_LSN *lsnp, int redo, void *info);
+#endif
+ /* dbinfo parameters */
+ int dbi_pagesize; /*!< (fs blksize) */
+ void * (*dbi_malloc) (size_t nbytes);
+ /* hash access parameters */
+ unsigned int dbi_h_ffactor; /*!< */
+ unsigned int (*dbi_h_hash_fcn) (DB *, const void *bytes,
+ unsigned int length);
+ unsigned int dbi_h_nelem; /*!< */
+ unsigned int dbi_h_flags; /*!< DB_DUP, DB_DUPSORT */
+ int (*dbi_h_dup_compare_fcn) (DB *, const DBT *, const DBT *);
+ /* btree access parameters */
+ int dbi_bt_flags;
+ int dbi_bt_minkey;
+ int (*dbi_bt_compare_fcn) (DB *, const DBT *, const DBT *);
+ int (*dbi_bt_dup_compare_fcn) (DB *, const DBT *, const DBT *);
+ size_t (*dbi_bt_prefix_fcn) (DB *, const DBT *, const DBT *);
+ /* recno access parameters */
+ int dbi_re_flags;
+ int dbi_re_delim;
+ unsigned int dbi_re_len;
+ int dbi_re_pad;
+ char * dbi_re_source;
+ /* queue access parameters */
+ unsigned int dbi_q_extentsize;
+
+ rpmdb dbi_rpmdb; /*!< the parent rpm database */
+ rpmTag dbi_rpmtag; /*!< rpm tag used for index */
+ int dbi_jlen; /*!< size of join key */
+
+ DB * dbi_db; /*!< Berkeley DB * handle */
+ DB_TXN * dbi_txnid; /*!< Bekerley DB_TXN * transaction id */
+ void * dbi_stats; /*!< Berkeley db statistics */
+
+ const struct _dbiVec * dbi_vec; /*!< private methods */
+
+};
+
+/** \ingroup rpmdb
+ * Describes the collection of index databases used by rpm.
+ */
+struct rpmdb_s {
+ char * db_root;/*!< path prefix */
+ char * db_home;/*!< directory path */
+ int db_flags;
+ int db_mode; /*!< open mode */
+ int db_perms; /*!< open permissions */
+ int db_api; /*!< Berkeley API type */
+ char * db_errpfx;
+ int db_remove_env;
+ int db_filter_dups;
+ int db_chrootDone; /*!< If chroot(2) done, ignore db_root. */
+ void (*db_errcall) (const char *db_errpfx, char *buffer);
+ FILE * db_errfile;
+ void * (*db_malloc) (size_t nbytes);
+ void * (*db_realloc) (void * ptr,
+ size_t nbytes);
+ void (*db_free) (void * ptr);
+ unsigned char * db_bits; /*!< package instance bit mask. */
+ int db_nbits; /*!< no. of bits in mask. */
+ rpmdb db_next;
+ int db_opens;
+ void * db_dbenv; /*!< Berkeley DB_ENV handle. */
+ int db_ndbi; /*!< No. of tag indices. */
+ dbiIndex * _dbi; /*!< Tag indices. */
+
+ struct rpmop_s db_getops;
+ struct rpmop_s db_putops;
+ struct rpmop_s db_delops;
+
+ int nrefs; /*!< Reference count. */
+};
+
+/* for RPM's internal use only */
+
+/** \ingroup rpmdb
+ */
+enum rpmdbFlags {
+ RPMDB_FLAG_JUSTCHECK = (1 << 0),
+ RPMDB_FLAG_MINIMAL = (1 << 1),
+ RPMDB_FLAG_CHROOT = (1 << 2)
+};
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \ingroup db3
+ * Return new configured index database handle instance.
+ * @param rpmdb rpm database
+ * @param rpmtag rpm tag
+ * @return index database handle
+ */
+dbiIndex db3New(rpmdb rpmdb, rpmTag rpmtag);
+
+/** \ingroup db3
+ * Destroy index database handle instance.
+ * @param dbi index database handle
+ * @return NULL always
+ */
+dbiIndex db3Free( dbiIndex dbi);
+
+/** \ingroup db3
+ * Format db3 open flags for debugging print.
+ * @param dbflags db open flags
+ * @param print_dbenv_flags format db env flags instead?
+ * @return formatted flags (malloced)
+ */
+char * prDbiOpenFlags(int dbflags, int print_dbenv_flags);
+
+/** \ingroup dbi
+ * Return handle for an index database.
+ * @param db rpm database
+ * @param rpmtag rpm tag
+ * @param flags (unused)
+ * @return index database handle
+ */
+dbiIndex dbiOpen(rpmdb db, rpmTag rpmtag,
+ unsigned int flags);
+
+/* FIX: vector annotations */
+/** \ingroup dbi
+ * Open a database cursor.
+ * @param dbi index database handle
+ * @param txnid database transaction handle
+ * @retval dbcp returned database cursor
+ * @param flags DB_WRITECURSOR if writing, or 0
+ * @return 0 on success
+ */
+static inline
+int dbiCopen(dbiIndex dbi, DB_TXN * txnid,
+ DBC ** dbcp, unsigned int flags)
+{
+ return (*dbi->dbi_vec->copen) (dbi, txnid, dbcp, flags);
+}
+
+/** \ingroup dbi
+ * Close a database cursor.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static inline
+int dbiCclose(dbiIndex dbi, DBC * dbcursor, unsigned int flags)
+{
+ return (*dbi->dbi_vec->cclose) (dbi, dbcursor, flags);
+}
+
+/** \ingroup dbi
+ * Duplicate a database cursor.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @retval dbcp address of new database cursor
+ * @param flags DB_POSITION for same position, 0 for uninitialized
+ * @return 0 on success
+ */
+static inline
+int dbiCdup(dbiIndex dbi, DBC * dbcursor, DBC ** dbcp,
+ unsigned int flags)
+{
+ return (*dbi->dbi_vec->cdup) (dbi, dbcursor, dbcp, flags);
+}
+
+/** \ingroup dbi
+ * Delete (key,data) pair(s) from index database.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->del)
+ * @param key delete key value/length/flags
+ * @param data delete data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static inline
+int dbiDel(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags)
+{
+ int rc;
+ assert(key->data != NULL && key->size > 0);
+ (void) rpmswEnter(&dbi->dbi_rpmdb->db_delops, 0);
+ rc = (dbi->dbi_vec->cdel) (dbi, dbcursor, key, data, flags);
+ (void) rpmswExit(&dbi->dbi_rpmdb->db_delops, data->size);
+ return rc;
+}
+
+/** \ingroup dbi
+ * Retrieve (key,data) pair from index database.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->get)
+ * @param key retrieve key value/length/flags
+ * @param data retrieve data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static inline
+int dbiGet(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags)
+{
+ int rc;
+ assert((flags == DB_NEXT) || (key->data != NULL && key->size > 0));
+ (void) rpmswEnter(&dbi->dbi_rpmdb->db_getops, 0);
+ rc = (dbi->dbi_vec->cget) (dbi, dbcursor, key, data, flags);
+ (void) rpmswExit(&dbi->dbi_rpmdb->db_getops, data->size);
+ return rc;
+}
+
+/** \ingroup dbi
+ * Retrieve (key,data) pair using dbcursor->c_pget.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->get)
+ * @param key secondary retrieve key value/length/flags
+ * @param pkey primary retrieve key value/length/flags
+ * @param data primary retrieve data value/length/flags
+ * @param flags DB_NEXT, DB_SET, or 0
+ * @return 0 on success
+ */
+static inline
+int dbiPget(dbiIndex dbi, DBC * dbcursor,
+ DBT * key, DBT * pkey, DBT * data, unsigned int flags)
+{
+ int rc;
+ assert((flags == DB_NEXT) || (key->data != NULL && key->size > 0));
+ (void) rpmswEnter(&dbi->dbi_rpmdb->db_getops, 0);
+ rc = (dbi->dbi_vec->cpget) (dbi, dbcursor, key, pkey, data, flags);
+ (void) rpmswExit(&dbi->dbi_rpmdb->db_getops, (ssize_t) data->size);
+ return rc;
+}
+
+/** \ingroup dbi
+ * Store (key,data) pair in index database.
+ * @param dbi index database handle
+ * @param dbcursor database cursor (NULL will use db->put)
+ * @param key store key value/length/flags
+ * @param data store data value/length/flags
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static inline
+int dbiPut(dbiIndex dbi, DBC * dbcursor, DBT * key, DBT * data,
+ unsigned int flags)
+{
+ int rc;
+ assert(key->data != NULL && key->size > 0 && data->data != NULL && data->size > 0);
+ (void) rpmswEnter(&dbi->dbi_rpmdb->db_putops, (ssize_t) 0);
+ rc = (dbi->dbi_vec->cput) (dbi, dbcursor, key, data, flags);
+ (void) rpmswExit(&dbi->dbi_rpmdb->db_putops, (ssize_t) data->size);
+ return rc;
+}
+
+/** \ingroup dbi
+ * Retrieve count of (possible) duplicate items.
+ * @param dbi index database handle
+ * @param dbcursor database cursor
+ * @param countp address of count
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static inline
+int dbiCount(dbiIndex dbi, DBC * dbcursor, unsigned int * countp,
+ unsigned int flags)
+{
+ return (*dbi->dbi_vec->ccount) (dbi, dbcursor, countp, flags);
+}
+
+/** \ingroup dbi
+ * Verify (and close) index database.
+ * @param dbi index database handle
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static inline
+int dbiVerify(dbiIndex dbi, unsigned int flags)
+{
+ dbi->dbi_verify_on_close = 1;
+ return (*dbi->dbi_vec->close) (dbi, flags);
+}
+
+/** \ingroup dbi
+ * Close index database.
+ * @param dbi index database handle
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static inline
+int dbiClose(dbiIndex dbi, unsigned int flags)
+{
+ return (*dbi->dbi_vec->close) (dbi, flags);
+}
+
+/** \ingroup dbi
+ * Flush pending operations to disk.
+ * @param dbi index database handle
+ * @param flags (unused)
+ * @return 0 on success
+ */
+static inline
+int dbiSync (dbiIndex dbi, unsigned int flags)
+{
+ return (*dbi->dbi_vec->sync) (dbi, flags);
+}
+
+/** \ingroup dbi
+ * Associate secondary database with primary.
+ * @param dbi index database handle
+ * @param dbisecondary secondary index database handle
+ * @param callback create secondary key from primary (NULL if DB_RDONLY)
+ * @param flags DB_CREATE or 0
+ * @return 0 on success
+ */
+static inline
+int dbiAssociate(dbiIndex dbi, dbiIndex dbisecondary,
+ int (*callback) (DB *, const DBT *, const DBT *, DBT *),
+ unsigned int flags)
+{
+ return (*dbi->dbi_vec->associate) (dbi, dbisecondary, callback, flags);
+}
+
+/** \ingroup dbi
+ * Return join cursor for list of cursors.
+ * @param dbi index database handle
+ * @param curslist NULL terminated list of database cursors
+ * @retval dbcp address of join database cursor
+ * @param flags DB_JOIN_NOSORT or 0
+ * @return 0 on success
+ */
+static inline
+int dbiJoin(dbiIndex dbi, DBC ** curslist, DBC ** dbcp,
+ unsigned int flags)
+{
+ return (*dbi->dbi_vec->join) (dbi, curslist, dbcp, flags);
+}
+
+/** \ingroup dbi
+ * Is database byte swapped?
+ * @param dbi index database handle
+ * @return 0 same order, 1 swapped order
+ */
+static inline
+int dbiByteSwapped(dbiIndex dbi)
+{
+ if (dbi->dbi_byteswapped == -1)
+ dbi->dbi_byteswapped = (*dbi->dbi_vec->byteswapped) (dbi);
+ return dbi->dbi_byteswapped;
+}
+/** \ingroup dbi
+ * Is database byte swapped?
+ * @param dbi index database handle
+ * @param flags DB_FAST_STAT or 0
+ * @return 0 on success
+ */
+static inline
+int dbiStat(dbiIndex dbi, unsigned int flags)
+{
+ return (*dbi->dbi_vec->stat) (dbi, flags);
+}
+
+
+/** \ingroup dbi
+ * Destroy set of index database items.
+ * @param set set of index database items
+ * @return NULL always
+ */
+dbiIndexSet dbiFreeIndexSet(dbiIndexSet set);
+
+/** \ingroup dbi
+ * Count items in index database set.
+ * @param set set of index database items
+ * @return number of items
+ */
+unsigned int dbiIndexSetCount(dbiIndexSet set);
+
+/** \ingroup dbi
+ * Return record offset of header from element in index database set.
+ * @param set set of index database items
+ * @param recno index of item in set
+ * @return record offset of header
+ */
+unsigned int dbiIndexRecordOffset(dbiIndexSet set, int recno);
+
+/** \ingroup dbi
+ * Return file index from element in index database set.
+ * @param set set of index database items
+ * @param recno index of item in set
+ * @return file index
+ */
+unsigned int dbiIndexRecordFileNumber(dbiIndexSet set, int recno);
+
+#ifndef __APPLE__
+/**
+ * * Mergesort, same arguments as qsort(2).
+ * */
+int mergesort(void *base, size_t nmemb, size_t size,
+ int (*cmp) (const void *, const void *));
+#else
+/* mergesort is defined in stdlib.h on Mac OS X */
+#endif /* __APPLE__ */
+
+#endif
diff --git a/lib/rpmhash.c b/lib/rpmhash.c
new file mode 100644
index 000000000..c7f7fc506
--- /dev/null
+++ b/lib/rpmhash.c
@@ -0,0 +1,176 @@
+/**
+ * \file rpmdb/rpmhash.c
+ * Hash table implemenation
+ */
+
+#include "system.h"
+#include "rpmdb/rpmhash.h"
+#include "debug.h"
+
+typedef const void * voidptr;
+
+typedef struct hashBucket_s * hashBucket;
+
+/**
+ */
+struct hashBucket_s {
+ voidptr key; /*!< hash key */
+voidptr * data; /*!< pointer to hashed data */
+ int dataCount; /*!< length of data (0 if unknown) */
+ hashBucket next; /*!< pointer to next item in bucket */
+};
+
+/**
+ */
+struct hashTable_s {
+ int numBuckets; /*!< number of hash buckets */
+ size_t keySize; /*!< size of key (0 if unknown) */
+ int freeData; /*!< should data be freed when table is destroyed? */
+ hashBucket * buckets; /*!< hash bucket array */
+ hashFunctionType fn; /*!< generate hash value for key */
+ hashEqualityType eq; /*!< compare hash keys for equality */
+};
+
+/**
+ * Find entry in hash table.
+ * @param ht pointer to hash table
+ * @param key pointer to key value
+ * @return pointer to hash bucket of key (or NULL)
+ */
+static
+hashBucket findEntry(hashTable ht, const void * key)
+{
+ unsigned int hash;
+ hashBucket b;
+
+ hash = ht->fn(key) % ht->numBuckets;
+ b = ht->buckets[hash];
+
+ while (b && b->key && ht->eq(b->key, key))
+ b = b->next;
+
+ return b;
+}
+
+int hashEqualityString(const void * key1, const void * key2)
+{
+ const char *k1 = (const char *)key1;
+ const char *k2 = (const char *)key2;
+ return strcmp(k1, k2);
+}
+
+unsigned int hashFunctionString(const void * string)
+{
+ char xorValue = 0;
+ char sum = 0;
+ short len;
+ int i;
+ const char * chp = string;
+
+ len = strlen(string);
+ for (i = 0; i < len; i++, chp++) {
+ xorValue ^= *chp;
+ sum += *chp;
+ }
+
+ return ((((unsigned)len) << 16) + (((unsigned)sum) << 8) + xorValue);
+}
+
+hashTable htCreate(int numBuckets, size_t keySize, int freeData,
+ hashFunctionType fn, hashEqualityType eq)
+{
+ hashTable ht;
+
+ ht = xmalloc(sizeof(*ht));
+ ht->numBuckets = numBuckets;
+ ht->buckets = xcalloc(numBuckets, sizeof(*ht->buckets));
+ ht->keySize = keySize;
+ ht->freeData = freeData;
+ ht->fn = fn;
+ ht->eq = eq;
+
+ return ht;
+}
+
+void htAddEntry(hashTable ht, const void * key, const void * data)
+{
+ unsigned int hash;
+ hashBucket b;
+
+ hash = ht->fn(key) % ht->numBuckets;
+ b = ht->buckets[hash];
+
+ while (b && b->key && ht->eq(b->key, key))
+ b = b->next;
+
+ if (b == NULL) {
+ b = xmalloc(sizeof(*b));
+ if (ht->keySize) {
+ char *k = xmalloc(ht->keySize);
+ memcpy(k, key, ht->keySize);
+ b->key = k;
+ } else {
+ b->key = key;
+ }
+ b->dataCount = 0;
+ b->next = ht->buckets[hash];
+ b->data = NULL;
+ ht->buckets[hash] = b;
+ }
+
+ b->data = xrealloc(b->data, sizeof(*b->data) * (b->dataCount + 1));
+ b->data[b->dataCount++] = data;
+}
+
+hashTable htFree(hashTable ht)
+{
+ hashBucket b, n;
+ int i;
+
+ for (i = 0; i < ht->numBuckets; i++) {
+ b = ht->buckets[i];
+ if (b == NULL)
+ continue;
+ ht->buckets[i] = NULL;
+ if (ht->keySize > 0)
+ b->key = _constfree(b->key);
+ do {
+ n = b->next;
+ if (b->data) {
+ if (ht->freeData)
+ *b->data = _constfree(*b->data);
+ b->data = _free(b->data);
+ }
+ b = _free(b);
+ } while ((b = n) != NULL);
+ }
+
+ ht->buckets = _free(ht->buckets);
+ ht = _free(ht);
+ return NULL;
+}
+
+int htHasEntry(hashTable ht, const void * key)
+{
+ hashBucket b;
+
+ if (!(b = findEntry(ht, key))) return 0; else return 1;
+}
+
+int htGetEntry(hashTable ht, const void * key, const void *** data,
+ int * dataCount, const void ** tableKey)
+{
+ hashBucket b;
+
+ if ((b = findEntry(ht, key)) == NULL)
+ return 1;
+
+ if (data)
+ *data = (const void **) b->data;
+ if (dataCount)
+ *dataCount = b->dataCount;
+ if (tableKey)
+ *tableKey = b->key;
+
+ return 0;
+}
diff --git a/lib/rpmhash.h b/lib/rpmhash.h
new file mode 100644
index 000000000..5e840bd5a
--- /dev/null
+++ b/lib/rpmhash.h
@@ -0,0 +1,96 @@
+#ifndef H_RPMHASH
+#define H_RPMHASH
+
+/**
+ * \file rpmdb/rpmhash.h
+ * Hash table implemenation.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ */
+typedef struct hashTable_s * hashTable;
+
+/**
+ */
+typedef unsigned int (*hashFunctionType) (const void * string);
+
+/**
+ */
+typedef int (*hashEqualityType) (const void * key1, const void * key2);
+
+/**
+ * Return hash value of a string
+ * @param string string on which to calculate hash value
+ * @return hash value
+ */
+unsigned int hashFunctionString(const void * string);
+
+/**
+ * Compare two hash table entries for equality.
+ * @param key1 entry 1
+ * @param key2 entry 2
+ * @return 0 if entries are equal
+ */
+int hashEqualityString(const void * key1, const void * key2);
+
+/**
+ * Create hash table.
+ * If keySize > 0, the key is duplicated within the table (which costs
+ * memory, but may be useful anyway.
+ * @param numBuckets number of hash buckets
+ * @param keySize size of key (0 if unknown)
+ * @param freeData Should data be freed when table is destroyed?
+ * @param fn function to generate hash value for key
+ * @param eq function to compare hash keys for equality
+ * @return pointer to initialized hash table
+ */
+hashTable htCreate(int numBuckets, size_t keySize, int freeData,
+ hashFunctionType fn, hashEqualityType eq);
+
+/**
+ * Destroy hash table.
+ * @param ht pointer to hash table
+ * @return NULL always
+ */
+hashTable htFree( hashTable ht);
+
+/**
+ * Add item to hash table.
+ * @param ht pointer to hash table
+ * @param key pointer to key
+ * @param data pointer to data value
+ */
+void htAddEntry(hashTable ht, const void * key,
+ const void * data);
+
+/**
+ * Retrieve item from hash table.
+ * @param ht pointer to hash table
+ * @param key pointer to key value
+ * @retval data address to store data value from bucket
+ * @retval dataCount address to store data value size from bucket
+ * @retval tableKey address to store key value from bucket (may be NULL)
+ * @return 0 on success, 1 if the item is not found.
+ */
+int htGetEntry(hashTable ht, const void * key,
+ const void *** data,
+ int * dataCount,
+ const void ** tableKey);
+
+/**
+ * Check for key in hash table.
+ * @param ht pointer to hash table
+ * @param key pointer to key value
+ * @return 1 if the key is present, 0 otherwise
+ */
+int htHasEntry(hashTable ht, const void * key);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/rpmints.h.in b/lib/rpmints.h.in
new file mode 100644
index 000000000..ae4507b0b
--- /dev/null
+++ b/lib/rpmints.h.in
@@ -0,0 +1,29 @@
+#ifndef _RPM_RPMINTS_H
+#define _RPM_RPMINTS_H
+
+/* @cond NODOXYGEN */
+/* if rpm uses stdint.h */
+#undef __RPM_USES_STDINT_H__
+/* @endcond */
+
+#ifdef __RPM_USES_STDINT_H__
+#include <stdint.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef int32_t int_32; /*!< @deprecated Provided for backward compatibility only. Don't use in new code. */
+typedef int16_t int_16; /*!< @deprecated Provided for backward compatibility only. Don't use in new code. */
+typedef int8_t int_8; /*!< @deprecated Provided for backward compatibility only. Don't use in new code. */
+
+typedef uint32_t uint_32; /*!< @deprecated Provided for backward compatibility only. Don't use in new code. */
+typedef uint16_t uint_16; /*!< @deprecated Provided for backward compatibility only. Don't use in new code. */
+typedef uint8_t uint_8; /*!< @deprecated Provided for backward compatibility only. Don't use in new code. */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/tagname.c b/lib/tagname.c
new file mode 100644
index 000000000..2ea0a96b1
--- /dev/null
+++ b/lib/tagname.c
@@ -0,0 +1,291 @@
+/**
+ * \file rpmdb/tagname.c
+ */
+
+#include "system.h"
+
+#include <rpm/header.h>
+#include <rpm/rpmstring.h>
+#include "debug.h"
+
+struct headerTagIndices_s {
+ int (*loadIndex) (headerTagTableEntry ** ipp, int * np,
+ int (*cmp) (const void * avp, const void * bvp));
+ /*!< load sorted tag index. */
+ headerTagTableEntry * byName; /*!< header tags sorted by name. */
+ int byNameSize; /*!< no. of entries. */
+ int (*byNameCmp) (const void * avp, const void * bvp); /*!< compare entries by name. */
+ rpmTag (*tagValue) (const char * name); /* return value from name. */
+ headerTagTableEntry * byValue; /*!< header tags sorted by value. */
+ int byValueSize; /*!< no. of entries. */
+ int (*byValueCmp) (const void * avp, const void * bvp); /*!< compare entries by value. */
+ const char * (*tagName) (rpmTag value); /* Return name from value. */
+ rpmTagType (*tagType) (rpmTag value); /* Return type from value. */
+};
+
+/**
+ * Compare tag table entries by name.
+ * @param *avp tag table entry a
+ * @param *bvp tag table entry b
+ * @return comparison
+ */
+static int tagCmpName(const void * avp, const void * bvp)
+{
+ headerTagTableEntry a = *(const headerTagTableEntry *) avp;
+ headerTagTableEntry b = *(const headerTagTableEntry *) bvp;
+ return strcmp(a->name, b->name);
+}
+
+/**
+ * Compare tag table entries by value.
+ * @param *avp tag table entry a
+ * @param *bvp tag table entry b
+ * @return comparison
+ */
+static int tagCmpValue(const void * avp, const void * bvp)
+{
+ headerTagTableEntry a = *(const headerTagTableEntry *) avp;
+ headerTagTableEntry b = *(const headerTagTableEntry *) bvp;
+ int ret = (a->val - b->val);
+ /* Make sure that sort is stable, longest name first. */
+ if (ret == 0)
+ ret = (strlen(b->name) - strlen(a->name));
+ return ret;
+}
+
+/**
+ * Load/sort a tag index.
+ * @retval *ipp tag index
+ * @retval *np no. of tags
+ * @param cmp sort compare routine
+ * @return 0 always
+ */
+static int tagLoadIndex(headerTagTableEntry ** ipp, int * np,
+ int (*cmp) (const void * avp, const void * bvp))
+{
+ headerTagTableEntry tte, *ip;
+ int n = 0;
+
+ ip = xcalloc(rpmTagTableSize, sizeof(*ip));
+ n = 0;
+ for (tte = (headerTagTableEntry)rpmTagTable; tte->name != NULL; tte++) {
+ ip[n] = tte;
+ n++;
+ }
+assert(n == rpmTagTableSize);
+
+ if (n > 1)
+ qsort(ip, n, sizeof(*ip), cmp);
+ *ipp = ip;
+ *np = n;
+ return 0;
+}
+
+
+/* forward refs */
+static const char * _tagName(rpmTag tag);
+static rpmTagType _tagType(rpmTag tag);
+static rpmTag _tagValue(const char * tagstr);
+
+static struct headerTagIndices_s _rpmTags = {
+ tagLoadIndex,
+ NULL, 0, tagCmpName, _tagValue,
+ NULL, 0, tagCmpValue, _tagName, _tagType,
+};
+
+headerTagIndices const rpmTags = &_rpmTags;
+
+static const char * _tagName(rpmTag tag)
+{
+ const char *name = "(unknown)";
+ const struct headerTagTableEntry_s *t;
+ int comparison, i, l, u;
+ int xx;
+
+ if (_rpmTags.byValue == NULL)
+ xx = tagLoadIndex(&_rpmTags.byValue, &_rpmTags.byValueSize, tagCmpValue);
+
+ switch (tag) {
+ case RPMDBI_PACKAGES:
+ name = "Packages";
+ break;
+ case RPMDBI_DEPENDS:
+ name = "Depends";
+ break;
+ case RPMDBI_ADDED:
+ name = "Added";
+ break;
+ case RPMDBI_REMOVED:
+ name = "Removed";
+ break;
+ case RPMDBI_AVAILABLE:
+ name = "Available";
+ break;
+ case RPMDBI_HDLIST:
+ name = "Hdlist";
+ break;
+ case RPMDBI_ARGLIST:
+ name = "Arglist";
+ break;
+ case RPMDBI_FTSWALK:
+ name = "Ftswalk";
+ break;
+
+ /* XXX make sure rpmdb indices are identically named. */
+ case RPMTAG_CONFLICTS:
+ name = "Conflictname";
+ break;
+ case RPMTAG_HDRID:
+ name = "Sha1header";
+ break;
+
+ default:
+ if (_rpmTags.byValue == NULL)
+ break;
+ l = 0;
+ u = _rpmTags.byValueSize;
+ while (l < u) {
+ i = (l + u) / 2;
+ t = _rpmTags.byValue[i];
+
+ comparison = (tag - t->val);
+
+ if (comparison < 0)
+ u = i;
+ else if (comparison > 0)
+ l = i + 1;
+ else {
+ /* Make sure that the bsearch retrieve is stable. */
+ while (i > 0 && tag == _rpmTags.byValue[i-1]->val) {
+ i--;
+ }
+ t = _rpmTags.byValue[i];
+ if (t->shortname != NULL)
+ name = t->shortname;
+ break;
+ }
+ }
+ break;
+ }
+ return name;
+}
+
+static rpmTagType _tagType(rpmTag tag)
+{
+ const struct headerTagTableEntry_s *t;
+ int comparison, i, l, u;
+ int xx;
+
+ if (_rpmTags.byValue == NULL)
+ xx = tagLoadIndex(&_rpmTags.byValue, &_rpmTags.byValueSize, tagCmpValue);
+
+ switch (tag) {
+ case RPMDBI_PACKAGES:
+ case RPMDBI_DEPENDS:
+ case RPMDBI_ADDED:
+ case RPMDBI_REMOVED:
+ case RPMDBI_AVAILABLE:
+ case RPMDBI_HDLIST:
+ case RPMDBI_ARGLIST:
+ case RPMDBI_FTSWALK:
+ break;
+ default:
+ if (_rpmTags.byValue == NULL)
+ break;
+ l = 0;
+ u = _rpmTags.byValueSize;
+ while (l < u) {
+ i = (l + u) / 2;
+ t = _rpmTags.byValue[i];
+
+ comparison = (tag - t->val);
+
+ if (comparison < 0)
+ u = i;
+ else if (comparison > 0)
+ l = i + 1;
+ else {
+ /* Make sure that the bsearch retrieve is stable. */
+ while (i > 0 && t->val == _rpmTags.byValue[i-1]->val) {
+ i--;
+ }
+ t = _rpmTags.byValue[i];
+ return t->type;
+ }
+ }
+ break;
+ }
+ return RPM_NULL_TYPE;
+}
+
+static rpmTag _tagValue(const char * tagstr)
+{
+ const struct headerTagTableEntry_s *t;
+ int comparison, i, l, u;
+ int xx;
+
+ if (!rstrcasecmp(tagstr, "Packages"))
+ return RPMDBI_PACKAGES;
+ if (!rstrcasecmp(tagstr, "Depends"))
+ return RPMDBI_DEPENDS;
+ if (!rstrcasecmp(tagstr, "Added"))
+ return RPMDBI_ADDED;
+ if (!rstrcasecmp(tagstr, "Removed"))
+ return RPMDBI_REMOVED;
+ if (!rstrcasecmp(tagstr, "Available"))
+ return RPMDBI_AVAILABLE;
+ if (!rstrcasecmp(tagstr, "Hdlist"))
+ return RPMDBI_HDLIST;
+ if (!rstrcasecmp(tagstr, "Arglist"))
+ return RPMDBI_ARGLIST;
+ if (!rstrcasecmp(tagstr, "Ftswalk"))
+ return RPMDBI_FTSWALK;
+
+ if (_rpmTags.byName == NULL)
+ xx = tagLoadIndex(&_rpmTags.byName, &_rpmTags.byNameSize, tagCmpName);
+ if (_rpmTags.byName == NULL)
+ return RPMTAG_NOT_FOUND;
+
+ l = 0;
+ u = _rpmTags.byNameSize;
+ while (l < u) {
+ i = (l + u) / 2;
+ t = _rpmTags.byName[i];
+
+ comparison = rstrcasecmp(tagstr, t->shortname);
+
+ if (comparison < 0)
+ u = i;
+ else if (comparison > 0)
+ l = i + 1;
+ else
+ return t->val;
+ }
+ return RPMTAG_NOT_FOUND;
+}
+
+const char * rpmTagGetName(rpmTag tag)
+{
+ return ((*rpmTags->tagName)(tag));
+}
+
+/**
+ * Return tag data type from value.
+ * @param tag tag value
+ * @return tag data type, RPM_NULL_TYPE on not found.
+ */
+rpmTagType rpmTagGetType(rpmTag tag)
+{
+ return ((*rpmTags->tagType)(tag));
+}
+
+/**
+ * Return tag value from name.
+ * @param tagstr name of tag
+ * @return tag value, RPMTAG_NOT_FOUND on not found
+ */
+rpmTag rpmTagGetValue(const char * tagstr)
+{
+ return ((*rpmTags->tagValue)(tagstr));
+}
+