diff options
Diffstat (limited to 'ext/repo_rpmdb_bdb.h')
-rw-r--r-- | ext/repo_rpmdb_bdb.h | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/ext/repo_rpmdb_bdb.h b/ext/repo_rpmdb_bdb.h new file mode 100644 index 0000000..c34ff70 --- /dev/null +++ b/ext/repo_rpmdb_bdb.h @@ -0,0 +1,487 @@ +/* + * Copyright (c) 2018 SUSE Inc. + * + * This program is licensed under the BSD license, read LICENSE.BSD + * for further information + */ + +/* + * repo_rpmdb_bdb.h + * + * Use BerkeleyDB to access the rpm database + * + */ + + +#if !defined(DB_CREATE) && !defined(ENABLE_RPMDB_LIBRPM) +# if defined(SUSE) || defined(HAVE_RPM_DB_H) +# include <rpm/db.h> +# else +# include <db.h> +# endif +#endif + +#ifdef RPM5 +# define RPM_INDEX_SIZE 4 /* just the rpmdbid */ +#else +# define RPM_INDEX_SIZE 8 /* rpmdbid + array index */ +#endif + + +/******************************************************************/ +/* Rpm Database stuff + */ + +struct rpmdbstate { + Pool *pool; + char *rootdir; + + RpmHead *rpmhead; /* header storage space */ + int rpmheadsize; + + int dbenvopened; /* database environment opened */ + int pkgdbopened; /* package database openend */ + int is_ostree; /* read-only db that lives in /usr/share/rpm */ + + DB_ENV *dbenv; /* database environment */ + DB *db; /* packages database */ + int byteswapped; /* endianess of packages database */ + DBC *dbc; /* iterator over packages database */ +}; + + +static int +stat_database(struct rpmdbstate *state, char *dbname, struct stat *statbuf, int seterror) +{ + char *dbpath; + dbpath = solv_dupjoin(state->rootdir, state->is_ostree ? "/usr/share/rpm/" : "/var/lib/rpm/", dbname); + if (stat(dbpath, statbuf)) + { + if (seterror) + pool_error(state->pool, -1, "%s: %s", dbpath, strerror(errno)); + free(dbpath); + return -1; + } + free(dbpath); + return 0; +} + + +static inline Id +db2rpmdbid(unsigned char *db, int byteswapped) +{ +#ifdef RPM5 + return db[0] << 24 | db[1] << 16 | db[2] << 8 | db[3]; +#else +# if defined(WORDS_BIGENDIAN) + if (!byteswapped) +# else + if (byteswapped) +# endif + return db[0] << 24 | db[1] << 16 | db[2] << 8 | db[3]; + else + return db[3] << 24 | db[2] << 16 | db[1] << 8 | db[0]; +#endif +} + +static inline void +rpmdbid2db(unsigned char *db, Id id, int byteswapped) +{ +#ifdef RPM5 + db[0] = id >> 24, db[1] = id >> 16, db[2] = id >> 8, db[3] = id; +#else +# if defined(WORDS_BIGENDIAN) + if (!byteswapped) +# else + if (byteswapped) +# endif + db[0] = id >> 24, db[1] = id >> 16, db[2] = id >> 8, db[3] = id; + else + db[3] = id >> 24, db[2] = id >> 16, db[1] = id >> 8, db[0] = id; +#endif +} + +#if defined(FEDORA) || defined(MAGEIA) +static int +serialize_dbenv_ops(struct rpmdbstate *state) +{ + char *lpath; + mode_t oldmask; + int fd; + struct flock fl; + + lpath = solv_dupjoin(state->rootdir, "/var/lib/rpm/.dbenv.lock", 0); + oldmask = umask(022); + fd = open(lpath, (O_RDWR|O_CREAT), 0644); + free(lpath); + umask(oldmask); + if (fd < 0) + return -1; + memset(&fl, 0, sizeof(fl)); + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + for (;;) + { + if (fcntl(fd, F_SETLKW, &fl) != -1) + return fd; + if (errno != EINTR) + break; + } + close(fd); + return -1; +} + +#endif + +/* should look in /usr/lib/rpm/macros instead, but we want speed... */ +static int +opendbenv(struct rpmdbstate *state) +{ + const char *rootdir = state->rootdir; + char *dbpath; + DB_ENV *dbenv = 0; + int r; + + if (db_env_create(&dbenv, 0)) + return pool_error(state->pool, 0, "db_env_create: %s", strerror(errno)); +#if (defined(FEDORA) || defined(MAGEIA)) && (DB_VERSION_MAJOR >= 5 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 5)) + dbenv->set_thread_count(dbenv, 8); +#endif + dbpath = solv_dupjoin(rootdir, "/var/lib/rpm", 0); + if (access(dbpath, W_OK) == -1) + { + free(dbpath); + dbpath = solv_dupjoin(rootdir, "/usr/share/rpm/Packages", 0); + if (access(dbpath, R_OK) == 0) + state->is_ostree = 1; + free(dbpath); + dbpath = solv_dupjoin(rootdir, state->is_ostree ? "/usr/share/rpm" : "/var/lib/rpm", 0); + r = dbenv->open(dbenv, dbpath, DB_CREATE|DB_PRIVATE|DB_INIT_MPOOL, 0); + } + else + { +#if defined(FEDORA) || defined(MAGEIA) + int serialize_fd = serialize_dbenv_ops(state); + int eflags = DB_CREATE|DB_INIT_CDB|DB_INIT_MPOOL; + r = dbenv->open(dbenv, dbpath, eflags, 0644); + /* see rpm commit 2822ccbcdf3e898b960fafb23c4d571e26cef0a4 */ + if (r == DB_VERSION_MISMATCH) + { + eflags |= DB_PRIVATE; + dbenv->errx(dbenv, "warning: DB_VERSION_MISMATCH, retrying with DB_PRIVATE"); + r = dbenv->open(dbenv, dbpath, eflags, 0644); + } + if (serialize_fd >= 0) + close(serialize_fd); +#else + r = dbenv->open(dbenv, dbpath, DB_CREATE|DB_PRIVATE|DB_INIT_MPOOL, 0); +#endif + } + if (r) + { + pool_error(state->pool, 0, "dbenv->open: %s", strerror(errno)); + free(dbpath); + dbenv->close(dbenv, 0); + return 0; + } + free(dbpath); + state->dbenv = dbenv; + state->dbenvopened = 1; + return 1; +} + +static void +closedbenv(struct rpmdbstate *state) +{ +#if defined(FEDORA) || defined(MAGEIA) + uint32_t eflags = 0; +#endif + + if (!state->dbenv) + return; +#if defined(FEDORA) || defined(MAGEIA) + (void)state->dbenv->get_open_flags(state->dbenv, &eflags); + if (!(eflags & DB_PRIVATE)) + { + int serialize_fd = serialize_dbenv_ops(state); + state->dbenv->close(state->dbenv, 0); + if (serialize_fd >= 0) + close(serialize_fd); + } + else + state->dbenv->close(state->dbenv, 0); +#else + state->dbenv->close(state->dbenv, 0); +#endif + state->dbenv = 0; + state->dbenvopened = 0; +} + +static int +openpkgdb(struct rpmdbstate *state) +{ + if (state->pkgdbopened) + return state->pkgdbopened > 0 ? 1 : 0; + state->pkgdbopened = -1; + if (state->dbenvopened != 1 && !opendbenv(state)) + return 0; + if (db_create(&state->db, state->dbenv, 0)) + { + pool_error(state->pool, 0, "db_create: %s", strerror(errno)); + state->db = 0; + closedbenv(state); + return 0; + } + if (state->db->open(state->db, 0, "Packages", 0, DB_UNKNOWN, DB_RDONLY, 0664)) + { + pool_error(state->pool, 0, "db->open Packages: %s", strerror(errno)); + state->db->close(state->db, 0); + state->db = 0; + closedbenv(state); + return 0; + } + if (state->db->get_byteswapped(state->db, &state->byteswapped)) + { + pool_error(state->pool, 0, "db->get_byteswapped: %s", strerror(errno)); + state->db->close(state->db, 0); + state->db = 0; + closedbenv(state); + return 0; + } + state->pkgdbopened = 1; + return 1; +} + +static void +closepkgdb(struct rpmdbstate *state) +{ + if (!state->db) + return; + state->db->close(state->db, 0); + state->db = 0; + state->pkgdbopened = 0; +} + +/* get the rpmdbids of all installed packages from the Name index database. + * This is much faster then querying the big Packages database */ +static struct rpmdbentry * +getinstalledrpmdbids(struct rpmdbstate *state, const char *index, const char *match, int *nentriesp, char **namedatap) +{ + DB_ENV *dbenv = 0; + DB *db = 0; + DBC *dbc = 0; + int byteswapped; + DBT dbkey; + DBT dbdata; + unsigned char *dp; + int dl; + Id nameoff; + + char *namedata = 0; + int namedatal = 0; + struct rpmdbentry *entries = 0; + int nentries = 0; + + *nentriesp = 0; + if (namedatap) + *namedatap = 0; + + if (state->dbenvopened != 1 && !opendbenv(state)) + return 0; + dbenv = state->dbenv; + if (db_create(&db, dbenv, 0)) + { + pool_error(state->pool, 0, "db_create: %s", strerror(errno)); + return 0; + } + if (db->open(db, 0, index, 0, DB_UNKNOWN, DB_RDONLY, 0664)) + { + pool_error(state->pool, 0, "db->open %s: %s", index, strerror(errno)); + db->close(db, 0); + return 0; + } + if (db->get_byteswapped(db, &byteswapped)) + { + pool_error(state->pool, 0, "db->get_byteswapped: %s", strerror(errno)); + db->close(db, 0); + return 0; + } + if (db->cursor(db, NULL, &dbc, 0)) + { + pool_error(state->pool, 0, "db->cursor: %s", strerror(errno)); + db->close(db, 0); + return 0; + } + memset(&dbkey, 0, sizeof(dbkey)); + memset(&dbdata, 0, sizeof(dbdata)); + if (match) + { + dbkey.data = (void *)match; + dbkey.size = strlen(match); + } + while (dbc->c_get(dbc, &dbkey, &dbdata, match ? DB_SET : DB_NEXT) == 0) + { + if (!match && dbkey.size == 10 && !memcmp(dbkey.data, "gpg-pubkey", 10)) + continue; + dl = dbdata.size; + dp = dbdata.data; + nameoff = namedatal; + if (namedatap) + { + namedata = solv_extend(namedata, namedatal, dbkey.size + 1, 1, NAMEDATA_BLOCK); + memcpy(namedata + namedatal, dbkey.data, dbkey.size); + namedata[namedatal + dbkey.size] = 0; + namedatal += dbkey.size + 1; + } + while(dl >= RPM_INDEX_SIZE) + { + entries = solv_extend(entries, nentries, 1, sizeof(*entries), ENTRIES_BLOCK); + entries[nentries].rpmdbid = db2rpmdbid(dp, byteswapped); + entries[nentries].nameoff = nameoff; + nentries++; + dp += RPM_INDEX_SIZE; + dl -= RPM_INDEX_SIZE; + } + if (match) + break; + } + dbc->c_close(dbc); + db->close(db, 0); + /* make sure that enteries is != 0 if there was no error */ + if (!entries) + entries = solv_extend(entries, 1, 1, sizeof(*entries), ENTRIES_BLOCK); + *nentriesp = nentries; + if (namedatap) + *namedatap = namedata; + return entries; +} + +/* common code, return dbid on success, -1 on error */ +static int +getrpm_dbdata(struct rpmdbstate *state, DBT *dbdata, int dbid) +{ + unsigned int dsize, cnt, l; + RpmHead *rpmhead; + + if (dbdata->size < 8) + return pool_error(state->pool, -1, "corrupt rpm database (size)"); + cnt = getu32((const unsigned char *)dbdata->data); + dsize = getu32((const unsigned char *)dbdata->data + 4); + if (cnt >= MAX_HDR_CNT || dsize >= MAX_HDR_DSIZE) + return pool_error(state->pool, -1, "corrupt rpm database (cnt/dcnt)"); + l = cnt * 16 + dsize; + if (8 + l > dbdata->size) + return pool_error(state->pool, -1, "corrupt rpm database (data size)"); + if (l + 1 > state->rpmheadsize) + { + state->rpmheadsize = l + 128; + state->rpmhead = solv_realloc(state->rpmhead, sizeof(*rpmhead) + state->rpmheadsize); + } + rpmhead = state->rpmhead; + rpmhead->cnt = cnt; + rpmhead->dcnt = dsize; + memcpy(rpmhead->data, (unsigned char *)dbdata->data + 8, l); + rpmhead->data[l] = 0; + rpmhead->dp = rpmhead->data + cnt * 16; + return dbid; +} + +/* retrive header by rpmdbid, returns 0 if not found, -1 on error */ +static int +getrpm_dbid(struct rpmdbstate *state, Id dbid) +{ + unsigned char buf[4]; + DBT dbkey; + DBT dbdata; + + if (dbid <= 0) + return pool_error(state->pool, -1, "illegal rpmdbid %d", dbid); + if (state->pkgdbopened != 1 && !openpkgdb(state)) + return -1; + rpmdbid2db(buf, dbid, state->byteswapped); + memset(&dbkey, 0, sizeof(dbkey)); + memset(&dbdata, 0, sizeof(dbdata)); + dbkey.data = buf; + dbkey.size = 4; + dbdata.data = 0; + dbdata.size = 0; + if (state->db->get(state->db, NULL, &dbkey, &dbdata, 0)) + return 0; + return getrpm_dbdata(state, &dbdata, dbid); +} + +static int +count_headers(struct rpmdbstate *state) +{ + Pool *pool = state->pool; + struct stat statbuf; + DB *db = 0; + DBC *dbc = 0; + int count = 0; + DBT dbkey; + DBT dbdata; + + if (stat_database(state, "Name", &statbuf, 0)) + return 0; + memset(&dbkey, 0, sizeof(dbkey)); + memset(&dbdata, 0, sizeof(dbdata)); + if (db_create(&db, state->dbenv, 0)) + { + pool_error(pool, 0, "db_create: %s", strerror(errno)); + return 0; + } + if (db->open(db, 0, "Name", 0, DB_UNKNOWN, DB_RDONLY, 0664)) + { + pool_error(pool, 0, "db->open Name: %s", strerror(errno)); + db->close(db, 0); + return 0; + } + if (db->cursor(db, NULL, &dbc, 0)) + { + db->close(db, 0); + pool_error(pool, 0, "db->cursor: %s", strerror(errno)); + return 0; + } + while (dbc->c_get(dbc, &dbkey, &dbdata, DB_NEXT) == 0) + count += dbdata.size / RPM_INDEX_SIZE; + dbc->c_close(dbc); + db->close(db, 0); + return count; +} + +static int +pkgdb_cursor_open(struct rpmdbstate *state) +{ + if (state->db->cursor(state->db, NULL, &state->dbc, 0)) + return pool_error(state->pool, -1, "db->cursor failed"); + return 0; +} + +static void +pkgdb_cursor_close(struct rpmdbstate *state) +{ + state->dbc->c_close(state->dbc); + state->dbc = 0; +} + +/* retrive header by berkeleydb cursor, returns 0 on EOF, -1 on error */ +static Id +pkgdb_cursor_getrpm(struct rpmdbstate *state) +{ + DBT dbkey; + DBT dbdata; + Id dbid; + + memset(&dbkey, 0, sizeof(dbkey)); + memset(&dbdata, 0, sizeof(dbdata)); + while (state->dbc->c_get(state->dbc, &dbkey, &dbdata, DB_NEXT) == 0) + { + if (dbkey.size != 4) + return pool_error(state->pool, -1, "corrupt Packages database (key size)"); + dbid = db2rpmdbid(dbkey.data, state->byteswapped); + if (dbid) /* ignore join key */ + return getrpm_dbdata(state, &dbdata, dbid); + } + return 0; /* no more entries */ +} + |