diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/backend/db3.c | 1184 | ||||
-rw-r--r-- | lib/backend/dbconfig.c | 473 | ||||
-rw-r--r-- | lib/backend/sqlite.c | 1386 | ||||
-rw-r--r-- | lib/fprint.c | 265 | ||||
-rw-r--r-- | lib/fprint.h | 141 | ||||
-rwxr-xr-x | lib/gentagtbl.sh | 84 | ||||
-rw-r--r-- | lib/hdrNVR.c | 130 | ||||
-rw-r--r-- | lib/header.c | 3039 | ||||
-rw-r--r-- | lib/header.h | 615 | ||||
-rw-r--r-- | lib/header_internal.c | 170 | ||||
-rw-r--r-- | lib/header_internal.h | 178 | ||||
-rw-r--r-- | lib/merge.c | 344 | ||||
-rw-r--r-- | lib/poptDB.c | 26 | ||||
-rw-r--r-- | lib/rpmdb.c | 3525 | ||||
-rw-r--r-- | lib/rpmdb.h | 287 | ||||
-rw-r--r-- | lib/rpmdb_internal.h | 665 | ||||
-rw-r--r-- | lib/rpmhash.c | 176 | ||||
-rw-r--r-- | lib/rpmhash.h | 96 | ||||
-rw-r--r-- | lib/rpmints.h.in | 29 | ||||
-rw-r--r-- | lib/tagname.c | 291 |
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)); +} + |