diff options
Diffstat (limited to 'lib/backend')
-rw-r--r-- | lib/backend/db3.c | 1184 | ||||
-rw-r--r-- | lib/backend/dbconfig.c | 473 | ||||
-rw-r--r-- | lib/backend/sqlite.c | 1386 |
3 files changed, 3043 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 +}; + |