From bfc6f7541d6e6229df6d7951d063c7631850cdcb Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Aug 24 2021 22:40:37 +0000 Subject: import rpm-4.14.3-17.el8 --- diff --git a/SOURCES/0001-Monkey-patch-.decode-method-to-our-strings-as-a-temp.patch b/SOURCES/0001-Monkey-patch-.decode-method-to-our-strings-as-a-temp.patch deleted file mode 100644 index 6df9fab..0000000 --- a/SOURCES/0001-Monkey-patch-.decode-method-to-our-strings-as-a-temp.patch +++ /dev/null @@ -1,89 +0,0 @@ -From 13b0ebee7cdb1e4d200b3c40d0ec9440f198a1d4 Mon Sep 17 00:00:00 2001 -Message-Id: <13b0ebee7cdb1e4d200b3c40d0ec9440f198a1d4.1554886141.git.pmatilai@redhat.com> -From: Panu Matilainen -Date: Wed, 10 Apr 2019 11:24:44 +0300 -Subject: [PATCH] Monkey-patch .decode() method to our strings as a temporary - compat crutch - -As a temporary crutch to support faster deployment of the sane -string behavior on python3, monkey-patch a decode method into all -strings we return. This seems to be enough to fix practically all -API users who have already adapted to the long-standing broken API -on Python 3. API users compatible with both Python 2 and 3 never needed -this anyway. Issue a warning with pointer to the relevant bug when the -fake decode() method is used to alert users to the issue. - -This is certainly an evil thing to do and will be removed as soon as -the critical users have been fixed to work with the new, corrected -behavior. ---- - python/rpm/__init__.py | 3 +++ - python/rpmmodule.c | 1 + - python/rpmsystem-py.h | 22 ++++++++++++++++++++-- - 3 files changed, 24 insertions(+), 2 deletions(-) - -diff --git a/python/rpm/__init__.py b/python/rpm/__init__.py -index 54728bbd4..6d69eda7b 100644 ---- a/python/rpm/__init__.py -+++ b/python/rpm/__init__.py -@@ -61,6 +61,9 @@ except ImportError: - # backwards compatibility + give the same class both ways - ts = TransactionSet - -+def _fakedecode(self, encoding='utf-8', errors='strict'): -+ warnings.warn("decode() called on unicode string, see https://bugzilla.redhat.com/show_bug.cgi?id=1693751", UnicodeWarning, stacklevel=2) -+ return self - - def headerLoad(*args, **kwds): - """DEPRECATED! Use rpm.hdr() instead.""" -diff --git a/python/rpmmodule.c b/python/rpmmodule.c -index 05032edc7..2a76cfbd0 100644 ---- a/python/rpmmodule.c -+++ b/python/rpmmodule.c -@@ -28,6 +28,7 @@ - */ - - PyObject * pyrpmError; -+PyObject * fakedecode = NULL; - - static PyObject * archScore(PyObject * self, PyObject * arg) - { -diff --git a/python/rpmsystem-py.h b/python/rpmsystem-py.h -index 25938464a..803da0fc1 100644 ---- a/python/rpmsystem-py.h -+++ b/python/rpmsystem-py.h -@@ -19,12 +19,29 @@ - #define PyInt_AsSsize_t PyLong_AsSsize_t - #endif - -+PyObject * fakedecode; -+ - static inline PyObject * utf8FromString(const char *s) - { - /* In Python 3, we return all strings as surrogate-escaped utf-8 */ - #if PY_MAJOR_VERSION >= 3 -- if (s != NULL) -- return PyUnicode_DecodeUTF8(s, strlen(s), "surrogateescape"); -+ if (s != NULL) { -+ PyObject *o = PyUnicode_DecodeUTF8(s, strlen(s), "surrogateescape"); -+ /* fish the fake decode function from python side if not done yet */ -+ if (fakedecode == NULL) { -+ PyObject *n = PyUnicode_FromString("rpm"); -+ PyObject *m = PyImport_Import(n); -+ PyObject *md = PyModule_GetDict(m); -+ fakedecode = PyDict_GetItemString(md, "_fakedecode"); -+ Py_DECREF(m); -+ Py_DECREF(n); -+ } -+ if (fakedecode && o) { -+ /* monkey-patch it into the string object as "decode" */ -+ PyDict_SetItemString(Py_TYPE(o)->tp_dict, "decode", fakedecode); -+ } -+ return o; -+ } - #else - if (s != NULL) - return PyBytes_FromString(s); --- -2.20.1 - diff --git a/SOURCES/rpm-4.14.3-add-read-only-support-for-sqlite.patch b/SOURCES/rpm-4.14.3-add-read-only-support-for-sqlite.patch new file mode 100644 index 0000000..419d754 --- /dev/null +++ b/SOURCES/rpm-4.14.3-add-read-only-support-for-sqlite.patch @@ -0,0 +1,798 @@ +From 34790c335fe6e5e1099c9320d7b3134398104120 Mon Sep 17 00:00:00 2001 +Message-Id: <34790c335fe6e5e1099c9320d7b3134398104120.1624429665.git.pmatilai@redhat.com> +From: Panu Matilainen +Date: Wed, 23 Jun 2021 08:24:44 +0300 +Subject: [PATCH] Add read-only support for sqlite + +Based on latest upstream sqlite backend version, chainsaw write support +out and adjust for the infra differences (which there are more than a +few) and add an error message instead. +--- + configure.ac | 23 ++ + lib/Makefile.am | 6 + + lib/backend/dbi.c | 14 + + lib/backend/dbi.h | 5 + + lib/backend/sqlite.c | 659 +++++++++++++++++++++++++++++++++++++++++++ + macros.in | 1 + + 6 files changed, 708 insertions(+) + create mode 100644 lib/backend/sqlite.c + +diff --git a/configure.ac b/configure.ac +index 3fcb3ff20..e04aced68 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -589,6 +589,29 @@ AS_IF([test "$enable_ndb" = yes],[ + ]) + AM_CONDITIONAL([NDB], [test "$enable_ndb" = yes]) + ++# Check for SQLITE support ++AC_ARG_ENABLE([sqlite], ++ [AS_HELP_STRING([--enable-sqlite=@<:@yes/no/auto@:>@)], ++ [build with sqlite rpm database format support (default=yes)])], ++ [enable_sqlite="$enableval"], ++ [enable_sqlite=yes]) ++ ++AS_IF([test "x$enable_sqlite" != "xno"], [ ++ PKG_CHECK_MODULES([SQLITE], [sqlite3 >= 3.22.0], [have_sqlite=yes], [have_sqlite=no]) ++ AS_IF([test "$enable_sqlite" = "yes"], [ ++ if test "$have_sqlite" = "no"; then ++ AC_MSG_ERROR([--enable-sqlite specified, but not available]) ++ fi ++ ]) ++]) ++ ++if test "x$have_sqlite" = "xyes"; then ++ AC_DEFINE([WITH_SQLITE], [1], [Define if SQLITE is available]) ++ SQLITE_REQUIRES=sqlite ++ AC_SUBST(SQLITE_REQUIRES) ++fi ++AM_CONDITIONAL([SQLITE], [test "x$have_sqlite" = "xyes"]) ++ + #================= + # Check for LMDB support + AC_ARG_ENABLE([lmdb], +diff --git a/lib/Makefile.am b/lib/Makefile.am +index baf3238ee..8a9fe77bd 100644 +--- a/lib/Makefile.am ++++ b/lib/Makefile.am +@@ -76,6 +76,12 @@ librpm_la_SOURCES += \ + backend/ndb/rpmxdb.h + endif + ++if SQLITE ++AM_CPPFLAGS += $(SQLITE_CFLAGS) ++librpm_la_LIBADD += $(SQLITE_LIBS) ++librpm_la_SOURCES += backend/sqlite.c ++endif ++ + if LMDB + AM_CPPFLAGS += $(LMDB_CFLAGS) + librpm_la_LIBADD += $(LMDB_LIBS) +diff --git a/lib/backend/dbi.c b/lib/backend/dbi.c +index e99a5f2b2..dc3587f58 100644 +--- a/lib/backend/dbi.c ++++ b/lib/backend/dbi.c +@@ -48,6 +48,11 @@ dbDetectBackend(rpmdb rdb) + if (!strcmp(db_backend, "ndb")) { + rdb->db_ops = &ndb_dbops; + } else ++#endif ++#ifdef WITH_SQLITE ++ if (!strcmp(db_backend, "sqlite")) { ++ rdb->db_ops = &sqlite_dbops; ++ } else + #endif + { + rdb->db_ops = &db3_dbops; +@@ -75,6 +80,15 @@ dbDetectBackend(rpmdb rdb) + free(path); + #endif + ++#ifdef WITH_SQLITE ++ path = rstrscat(NULL, dbhome, "/rpmdb.sqlite", NULL); ++ if (access(path, F_OK) == 0 && rdb->db_ops != &sqlite_dbops) { ++ rdb->db_ops = &sqlite_dbops; ++ rpmlog(RPMLOG_WARNING, _("Found SQLITE rpmdb.sqlite database while attempting %s backend: using sqlite backend.\n"), db_backend); ++ } ++ free(path); ++#endif ++ + path = rstrscat(NULL, dbhome, "/Packages", NULL); + if (access(path, F_OK) == 0 && rdb->db_ops != &db3_dbops) { + rdb->db_ops = &db3_dbops; +diff --git a/lib/backend/dbi.h b/lib/backend/dbi.h +index 02f49c8fd..ff2b4f974 100644 +--- a/lib/backend/dbi.h ++++ b/lib/backend/dbi.h +@@ -275,6 +275,11 @@ RPM_GNUC_INTERNAL + extern struct rpmdbOps_s lmdb_dbops; + #endif + ++#if defined(WITH_SQLITE) ++RPM_GNUC_INTERNAL ++extern struct rpmdbOps_s sqlite_dbops; ++#endif ++ + #ifdef __cplusplus + } + #endif +diff --git a/lib/backend/sqlite.c b/lib/backend/sqlite.c +new file mode 100644 +index 000000000..3caeba5f0 +--- /dev/null ++++ b/lib/backend/sqlite.c +@@ -0,0 +1,659 @@ ++#include "system.h" ++ ++#include ++#include ++ ++#include ++#include ++#include ++#include "lib/rpmdb_internal.h" ++ ++#include "debug.h" ++ ++static const int sleep_ms = 50; ++ ++struct dbiCursor_s { ++ sqlite3 *sdb; ++ sqlite3_stmt *stmt; ++ const char *fmt; ++ int flags; ++ rpmTagVal tag; ++ int ctype; ++ struct dbiCursor_s *subc; ++ ++ const void *key; ++ unsigned int keylen; ++}; ++ ++static int sqlexec(sqlite3 *sdb, const char *fmt, ...); ++ ++static void rpm_match3(sqlite3_context *sctx, int argc, sqlite3_value **argv) ++{ ++ int match = 0; ++ if (argc == 3) { ++ int b1len = sqlite3_value_bytes(argv[0]); ++ int b2len = sqlite3_value_bytes(argv[1]); ++ int n = sqlite3_value_int(argv[2]); ++ if (b1len >= n && b2len >= n) { ++ const char *b1 = sqlite3_value_blob(argv[0]); ++ const char *b2 = sqlite3_value_blob(argv[1]); ++ match = (memcmp(b1, b2, n) == 0); ++ } ++ } ++ sqlite3_result_int(sctx, match); ++} ++ ++static void errCb(void *data, int err, const char *msg) ++{ ++ rpmdb rdb = data; ++ rpmlog(RPMLOG_WARNING, "%s: %s: %s\n", ++ rdb->db_descr, sqlite3_errstr(err), msg); ++} ++ ++static int dbiCursorReset(dbiCursor dbc) ++{ ++ if (dbc->stmt) { ++ sqlite3_reset(dbc->stmt); ++ sqlite3_clear_bindings(dbc->stmt); ++ } ++ return 0; ++} ++ ++static int dbiCursorResult(dbiCursor dbc) ++{ ++ int rc = sqlite3_errcode(dbc->sdb); ++ int err = (rc != SQLITE_OK && rc != SQLITE_DONE && rc != SQLITE_ROW); ++ if (err) { ++ rpmlog(RPMLOG_ERR, "%s: %d: %s\n", sqlite3_sql(dbc->stmt), ++ sqlite3_errcode(dbc->sdb), sqlite3_errmsg(dbc->sdb)); ++ } ++ return err ? RPMRC_FAIL : RPMRC_OK; ++} ++ ++static int dbiCursorPrep(dbiCursor dbc, const char *fmt, ...) ++{ ++ if (dbc->stmt == NULL) { ++ char *cmd = NULL; ++ va_list ap; ++ ++ va_start(ap, fmt); ++ cmd = sqlite3_vmprintf(fmt, ap); ++ va_end(ap); ++ ++ sqlite3_prepare_v2(dbc->sdb, cmd, -1, &dbc->stmt, NULL); ++ sqlite3_free(cmd); ++ } else { ++ dbiCursorReset(dbc); ++ } ++ ++ return dbiCursorResult(dbc); ++} ++ ++static int dbiCursorBindPkg(dbiCursor dbc, unsigned int hnum, ++ void *blob, unsigned int bloblen) ++{ ++ int rc = 0; ++ ++ if (hnum) ++ rc = sqlite3_bind_int(dbc->stmt, 1, hnum); ++ else ++ rc = sqlite3_bind_null(dbc->stmt, 1); ++ ++ if (blob) { ++ if (!rc) ++ rc = sqlite3_bind_blob(dbc->stmt, 2, blob, bloblen, NULL); ++ } ++ return dbiCursorResult(dbc); ++} ++ ++static int dbiCursorBindIdx(dbiCursor dbc, const void *key, int keylen, ++ dbiIndexItem rec) ++{ ++ int rc; ++ if (dbc->ctype == SQLITE_TEXT) { ++ rc = sqlite3_bind_text(dbc->stmt, 1, key, keylen, NULL); ++ } else { ++ rc = sqlite3_bind_blob(dbc->stmt, 1, key, keylen, NULL); ++ } ++ ++ if (rec) { ++ if (!rc) ++ rc = sqlite3_bind_int(dbc->stmt, 2, rec->hdrNum); ++ if (!rc) ++ rc = sqlite3_bind_int(dbc->stmt, 3, rec->tagNum); ++ } ++ ++ return dbiCursorResult(dbc); ++} ++ ++static int sqlite_init(rpmdb rdb, const char * dbhome) ++{ ++ int rc = 0; ++ char *dbfile = NULL; ++ ++ if (rdb->db_dbenv == NULL) { ++ dbfile = rpmGenPath(dbhome, "rpmdb.sqlite", NULL); ++ sqlite3 *sdb = NULL; ++ int xx, flags = 0; ++ int retry_open = 1; ++ ++ free(rdb->db_descr); ++ rdb->db_descr = xstrdup("sqlite"); ++ ++ if ((rdb->db_mode & O_ACCMODE) == O_RDONLY) ++ flags |= SQLITE_OPEN_READONLY; ++ else { ++ rpmlog(RPMLOG_ERR, ++ _("unable to open sqlite database %s for writing, sqlite support is read-only\n"), dbfile); ++ rc = RPMRC_FAIL; ++ goto exit; ++ } ++ ++ while (retry_open--) { ++ xx = sqlite3_open_v2(dbfile, &sdb, flags, NULL); ++ /* Attempt to create if missing, discarding OPEN_READONLY (!) */ ++ if (xx == SQLITE_CANTOPEN && (flags & SQLITE_OPEN_READONLY)) { ++ /* Sqlite allocates resources even on failure to open (!) */ ++ sqlite3_close(sdb); ++ flags &= ~SQLITE_OPEN_READONLY; ++ flags |= (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); ++ retry_open++; ++ } ++ } ++ ++ if (xx != SQLITE_OK) { ++ rpmlog(RPMLOG_ERR, _("Unable to open sqlite database %s: %s\n"), ++ dbfile, sqlite3_errstr(xx)); ++ rc = 1; ++ goto exit; ++ } ++ ++ sqlite3_create_function(sdb, "match", 3, ++ (SQLITE_UTF8|SQLITE_DETERMINISTIC), ++ NULL, rpm_match3, NULL, NULL); ++ ++ sqlite3_busy_timeout(sdb, sleep_ms); ++ sqlite3_config(SQLITE_CONFIG_LOG, errCb, rdb); ++ ++ sqlexec(sdb, "PRAGMA secure_delete = OFF"); ++ sqlexec(sdb, "PRAGMA case_sensitive_like = ON"); ++ ++ if (sqlite3_db_readonly(sdb, NULL) == 0) { ++ if (sqlexec(sdb, "PRAGMA journal_mode = WAL") == 0) { ++ int one = 1; ++ /* Annoying but necessary to support non-privileged readers */ ++ sqlite3_file_control(sdb, NULL, SQLITE_FCNTL_PERSIST_WAL, &one); ++ ++ if (!rpmExpandNumeric("%{?_flush_io}")) ++ sqlexec(sdb, "PRAGMA wal_autocheckpoint = 0"); ++ } ++ } ++ ++ rdb->db_dbenv = sdb; ++ } ++ rdb->db_opens++; ++ ++exit: ++ free(dbfile); ++ return rc; ++} ++ ++static int sqlite_fini(rpmdb rdb) ++{ ++ int rc = 0; ++ if (rdb) { ++ sqlite3 *sdb = rdb->db_dbenv; ++ if (rdb->db_opens > 1) { ++ rdb->db_opens--; ++ } else { ++ if (sqlite3_db_readonly(sdb, NULL) == 0) { ++ sqlexec(sdb, "PRAGMA optimize"); ++ sqlexec(sdb, "PRAGMA wal_checkpoint = TRUNCATE"); ++ } ++ rdb->db_dbenv = NULL; ++ int xx = sqlite3_close(sdb); ++ rc = (xx != SQLITE_OK); ++ } ++ } ++ ++ return rc; ++} ++ ++static int sqlexec(sqlite3 *sdb, const char *fmt, ...) ++{ ++ int rc = 0; ++ char *cmd = NULL; ++ char *err = NULL; ++ va_list ap; ++ ++ va_start(ap, fmt); ++ cmd = sqlite3_vmprintf(fmt, ap); ++ va_end(ap); ++ ++ /* sqlite3_exec() doesn't seeem to honor sqlite3_busy_timeout() */ ++ while ((rc = sqlite3_exec(sdb, cmd, NULL, NULL, &err)) == SQLITE_BUSY) { ++ usleep(sleep_ms); ++ } ++ ++ if (rc) ++ rpmlog(RPMLOG_ERR, "sqlite failure: %s: %s\n", cmd, err); ++ else ++ rpmlog(RPMLOG_DEBUG, "%s: %d\n", cmd, rc); ++ ++ sqlite3_free(cmd); ++ ++ return rc ? RPMRC_FAIL : RPMRC_OK; ++} ++ ++static int dbiExists(dbiIndex dbi) ++{ ++ const char *col = (dbi->dbi_type == DBI_PRIMARY) ? "hnum" : "key"; ++ const char *tbl = dbi->dbi_file; ++ int rc = sqlite3_table_column_metadata(dbi->dbi_db, NULL, tbl, col, ++ NULL, NULL, NULL, NULL, NULL); ++ return (rc == 0); ++} ++ ++static int init_table(dbiIndex dbi, rpmTagVal tag) ++{ ++ int rc = 0; ++ ++ if (dbiExists(dbi)) ++ return 0; ++ ++ if (dbi->dbi_type == DBI_PRIMARY) { ++ rc = sqlexec(dbi->dbi_db, ++ "CREATE TABLE IF NOT EXISTS '%q' (" ++ "hnum INTEGER PRIMARY KEY AUTOINCREMENT," ++ "blob BLOB NOT NULL" ++ ")", ++ dbi->dbi_file); ++ } else { ++ const char *keytype = (rpmTagGetClass(tag) == RPM_STRING_CLASS) ? ++ "TEXT" : "BLOB"; ++ rc = sqlexec(dbi->dbi_db, ++ "CREATE TABLE IF NOT EXISTS '%q' (" ++ "key '%q' NOT NULL, " ++ "hnum INTEGER NOT NULL, " ++ "idx INTEGER NOT NULL, " ++ "FOREIGN KEY (hnum) REFERENCES 'Packages'(hnum)" ++ ")", ++ dbi->dbi_file, keytype); ++ } ++ if (!rc) ++ dbi->dbi_flags |= DBI_CREATED; ++ ++ return rc; ++} ++ ++static int create_index(sqlite3 *sdb, const char *table, const char *col) ++{ ++ return sqlexec(sdb, ++ "CREATE INDEX IF NOT EXISTS '%s_%s_idx' ON '%q'(%s ASC)", ++ table, col, table, col); ++} ++ ++static int init_index(dbiIndex dbi, rpmTagVal tag) ++{ ++ int rc = 0; ++ ++ /* Can't create on readonly database, but things will still work */ ++ if (sqlite3_db_readonly(dbi->dbi_db, NULL) == 1) ++ return 0; ++ ++ if (dbi->dbi_type == DBI_SECONDARY) { ++ int string = (rpmTagGetClass(tag) == RPM_STRING_CLASS); ++ int array = (rpmTagGetReturnType(tag) == RPM_ARRAY_RETURN_TYPE); ++ if (!rc && string) ++ rc = create_index(dbi->dbi_db, dbi->dbi_file, "key"); ++ if (!rc && array) ++ rc = create_index(dbi->dbi_db, dbi->dbi_file, "hnum"); ++ } ++ return rc; ++} ++ ++static int sqlite_Open(rpmdb rdb, rpmDbiTagVal rpmtag, dbiIndex * dbip, int flags) ++{ ++ int rc = sqlite_init(rdb, rpmdbHome(rdb)); ++ ++ if (!rc) { ++ dbiIndex dbi = dbiNew(rdb, rpmtag); ++ dbi->dbi_db = rdb->db_dbenv; ++ ++ rc = init_table(dbi, rpmtag); ++ ++ if (!rc && !(rdb->db_flags & RPMDB_FLAG_REBUILD)) ++ rc = init_index(dbi, rpmtag); ++ ++ if (!rc && dbip) ++ *dbip = dbi; ++ else ++ dbiFree(dbi); ++ } ++ ++ return rc; ++} ++ ++static int sqlite_Close(dbiIndex dbi, unsigned int flags) ++{ ++ rpmdb rdb = dbi->dbi_rpmdb; ++ int rc = 0; ++ if (rdb->db_flags & RPMDB_FLAG_REBUILD) ++ rc = init_index(dbi, rpmTagGetValue(dbi->dbi_file)); ++ sqlite_fini(dbi->dbi_rpmdb); ++ dbiFree(dbi); ++ return rc; ++} ++ ++static int sqlite_Verify(dbiIndex dbi, unsigned int flags) ++{ ++ int errors = -1; ++ int key_errors = -1; ++ sqlite3_stmt *s = NULL; ++ const char *cmd = "PRAGMA integrity_check"; ++ ++ if (dbi->dbi_type == DBI_SECONDARY) ++ return RPMRC_OK; ++ ++ if (sqlite3_prepare_v2(dbi->dbi_db, cmd, -1, &s, NULL) == SQLITE_OK) { ++ errors = 0; ++ while (sqlite3_step(s) == SQLITE_ROW) { ++ const char *txt = (const char *)sqlite3_column_text(s, 0); ++ if (!rstreq(txt, "ok")) { ++ errors++; ++ rpmlog(RPMLOG_ERR, "verify: %s\n", txt); ++ } ++ } ++ sqlite3_finalize(s); ++ } else { ++ rpmlog(RPMLOG_ERR, "%s: %s\n", cmd, sqlite3_errmsg(dbi->dbi_db)); ++ } ++ ++ /* No point checking higher-level errors if low-level errors exist */ ++ if (errors) ++ goto exit; ++ ++ cmd = "PRAGMA foreign_key_check"; ++ if (sqlite3_prepare_v2(dbi->dbi_db, cmd, -1, &s, NULL) == SQLITE_OK) { ++ key_errors = 0; ++ while (sqlite3_step(s) == SQLITE_ROW) { ++ key_errors++; ++ rpmlog(RPMLOG_ERR, "verify key: %s[%lld]\n", ++ sqlite3_column_text(s, 0), ++ sqlite3_column_int64(s, 1)); ++ } ++ sqlite3_finalize(s); ++ } else { ++ rpmlog(RPMLOG_ERR, "%s: %s\n", cmd, sqlite3_errmsg(dbi->dbi_db)); ++ } ++ ++exit: ++ ++ return (errors == 0 && key_errors == 0) ? RPMRC_OK : RPMRC_FAIL; ++} ++ ++static void sqlite_SetFSync(rpmdb rdb, int enable) ++{ ++ if (rdb->db_dbenv) { ++ sqlexec(rdb->db_dbenv, ++ "PRAGMA synchronous = %s", enable ? "FULL" : "OFF"); ++ } ++} ++ ++static int sqlite_Ctrl(rpmdb rdb, dbCtrlOp ctrl) ++{ ++ int rc = 0; ++ ++ switch (ctrl) { ++ case DB_CTRL_LOCK_RW: ++ rc = sqlexec(rdb->db_dbenv, "SAVEPOINT 'rwlock'"); ++ break; ++ case DB_CTRL_UNLOCK_RW: ++ rc = sqlexec(rdb->db_dbenv, "RELEASE 'rwlock'"); ++ break; ++ default: ++ break; ++ } ++ ++ return rc; ++} ++ ++static dbiCursor sqlite_CursorInit(dbiIndex dbi, unsigned int flags) ++{ ++ dbiCursor dbc = xcalloc(1, sizeof(*dbc)); ++ dbc->sdb = dbi->dbi_db; ++ dbc->flags = flags; ++ dbc->tag = rpmTagGetValue(dbi->dbi_file); ++ if (rpmTagGetClass(dbc->tag) == RPM_STRING_CLASS) { ++ dbc->ctype = SQLITE_TEXT; ++ } else { ++ dbc->ctype = SQLITE_BLOB; ++ } ++ if (dbc->flags & DBC_WRITE) ++ sqlexec(dbc->sdb, "SAVEPOINT '%s'", dbi->dbi_file); ++ return dbc; ++} ++ ++static dbiCursor sqlite_CursorFree(dbiIndex dbi, dbiCursor dbc) ++{ ++ if (dbc) { ++ sqlite3_finalize(dbc->stmt); ++ if (dbc->subc) ++ dbiCursorFree(dbi, dbc->subc); ++ if (dbc->flags & DBC_WRITE) ++ sqlexec(dbc->sdb, "RELEASE '%s'", dbi->dbi_file); ++ free(dbc); ++ } ++ return NULL; ++} ++ ++static rpmRC sqlite_pkgdbNew(dbiIndex dbi, dbiCursor dbc, unsigned int *hdrNum) ++{ ++ return RPMRC_FAIL; ++} ++ ++static rpmRC sqlite_pkgdbPut(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum, unsigned char *hdrBlob, unsigned int hdrLen) ++{ ++ return RPMRC_FAIL; ++} ++ ++static rpmRC sqlite_pkgdbDel(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum) ++{ ++ return RPMRC_FAIL; ++} ++ ++static rpmRC sqlite_stepPkg(dbiCursor dbc, unsigned char **hdrBlob, unsigned int *hdrLen) ++{ ++ int rc = sqlite3_step(dbc->stmt); ++ ++ if (rc == SQLITE_ROW) { ++ if (hdrLen) ++ *hdrLen = sqlite3_column_bytes(dbc->stmt, 1); ++ if (hdrBlob) ++ *hdrBlob = (void *) sqlite3_column_blob(dbc->stmt, 1); ++ } ++ return rc; ++} ++ ++static rpmRC sqlite_pkgdbByKey(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum, unsigned char **hdrBlob, unsigned int *hdrLen) ++{ ++ int rc = dbiCursorPrep(dbc, "SELECT hnum, blob FROM '%q' WHERE hnum=?", ++ dbi->dbi_file); ++ ++ if (!rc) ++ rc = dbiCursorBindPkg(dbc, hdrNum, NULL, 0); ++ ++ if (!rc) ++ rc = sqlite_stepPkg(dbc, hdrBlob, hdrLen); ++ ++ return dbiCursorResult(dbc); ++} ++ ++static rpmRC sqlite_pkgdbIter(dbiIndex dbi, dbiCursor dbc, ++ unsigned char **hdrBlob, unsigned int *hdrLen) ++{ ++ int rc = RPMRC_OK; ++ if (dbc->stmt == NULL) { ++ rc = dbiCursorPrep(dbc, "SELECT hnum, blob FROM '%q'", dbi->dbi_file); ++ } ++ ++ if (!rc) ++ rc = sqlite_stepPkg(dbc, hdrBlob, hdrLen); ++ ++ if (rc == SQLITE_DONE) { ++ rc = RPMRC_NOTFOUND; ++ } else { ++ rc = dbiCursorResult(dbc); ++ } ++ ++ return rc; ++} ++ ++static rpmRC sqlite_pkgdbGet(dbiIndex dbi, dbiCursor dbc, unsigned int hdrNum, unsigned char **hdrBlob, unsigned int *hdrLen) ++{ ++ int rc; ++ ++ if (hdrNum) { ++ rc = sqlite_pkgdbByKey(dbi, dbc, hdrNum, hdrBlob, hdrLen); ++ } else { ++ rc = sqlite_pkgdbIter(dbi, dbc, hdrBlob, hdrLen); ++ } ++ ++ return rc; ++} ++ ++static unsigned int sqlite_pkgdbKey(dbiIndex dbi, dbiCursor dbc) ++{ ++ return sqlite3_column_int(dbc->stmt, 0); ++} ++ ++static rpmRC sqlite_idxdbByKey(dbiIndex dbi, dbiCursor dbc, ++ const char *keyp, size_t keylen, int searchType, ++ dbiIndexSet *set) ++{ ++ int rc = RPMRC_NOTFOUND; ++ ++ if (searchType == DBC_PREFIX_SEARCH) { ++ rc = dbiCursorPrep(dbc, "SELECT hnum, idx FROM '%q' " ++ "WHERE MATCH(key,'%q',%d) " ++ "ORDER BY key", ++ dbi->dbi_file, keyp, keylen); ++ } else { ++ rc = dbiCursorPrep(dbc, "SELECT hnum, idx FROM '%q' WHERE key=?", ++ dbi->dbi_file); ++ if (!rc) ++ rc = dbiCursorBindIdx(dbc, keyp, keylen, NULL); ++ } ++ ++ ++ if (!rc) { ++ while ((rc = sqlite3_step(dbc->stmt)) == SQLITE_ROW) { ++ unsigned int hnum = sqlite3_column_int(dbc->stmt, 0); ++ unsigned int tnum = sqlite3_column_int(dbc->stmt, 1); ++ ++ if (*set == NULL) ++ *set = dbiIndexSetNew(5); ++ dbiIndexSetAppendOne(*set, hnum, tnum, 0); ++ } ++ } ++ ++ if (rc == SQLITE_DONE) { ++ rc = (*set) ? RPMRC_OK : RPMRC_NOTFOUND; ++ } else { ++ rc = dbiCursorResult(dbc); ++ } ++ ++ return rc; ++} ++ ++static rpmRC sqlite_idxdbIter(dbiIndex dbi, dbiCursor dbc, dbiIndexSet *set) ++{ ++ int rc = RPMRC_OK; ++ ++ if (dbc->stmt == NULL) { ++ rc = dbiCursorPrep(dbc, "SELECT DISTINCT key FROM '%q' ORDER BY key", ++ dbi->dbi_file); ++ if (set) ++ dbc->subc = dbiCursorInit(dbi, 0); ++ } ++ ++ if (!rc) ++ rc = sqlite3_step(dbc->stmt); ++ ++ if (rc == SQLITE_ROW) { ++ if (dbc->ctype == SQLITE_TEXT) { ++ dbc->key = sqlite3_column_text(dbc->stmt, 0); ++ } else { ++ dbc->key = sqlite3_column_blob(dbc->stmt, 0); ++ } ++ dbc->keylen = sqlite3_column_bytes(dbc->stmt, 0); ++ if (dbc->subc) { ++ rc = sqlite_idxdbByKey(dbi, dbc->subc, dbc->key, dbc->keylen, ++ DBC_NORMAL_SEARCH, set); ++ } else { ++ rc = RPMRC_OK; ++ } ++ } else if (rc == SQLITE_DONE) { ++ rc = RPMRC_NOTFOUND; ++ } else { ++ rc = dbiCursorResult(dbc); ++ } ++ ++ return rc; ++} ++ ++static rpmRC sqlite_idxdbGet(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen, dbiIndexSet *set, int searchType) ++{ ++ int rc; ++ if (keyp) { ++ rc = sqlite_idxdbByKey(dbi, dbc, keyp, keylen, searchType, set); ++ } else { ++ rc = sqlite_idxdbIter(dbi, dbc, set); ++ } ++ ++ return rc; ++} ++ ++static rpmRC sqlite_idxdbPut(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen, dbiIndexItem rec) ++{ ++ return RPMRC_FAIL; ++} ++ ++static rpmRC sqlite_idxdbDel(dbiIndex dbi, dbiCursor dbc, const char *keyp, size_t keylen, dbiIndexItem rec) ++{ ++ return RPMRC_FAIL; ++} ++ ++static const void * sqlite_idxdbKey(dbiIndex dbi, dbiCursor dbc, unsigned int *keylen) ++{ ++ const void *key = NULL; ++ if (dbc) { ++ key = dbc->key; ++ if (key && keylen) ++ *keylen = dbc->keylen; ++ } ++ return key; ++} ++ ++struct rpmdbOps_s sqlite_dbops = { ++ .open = sqlite_Open, ++ .close = sqlite_Close, ++ .verify = sqlite_Verify, ++ .setFSync = sqlite_SetFSync, ++ .ctrl = sqlite_Ctrl, ++ ++ .cursorInit = sqlite_CursorInit, ++ .cursorFree = sqlite_CursorFree, ++ ++ .pkgdbPut = sqlite_pkgdbPut, ++ .pkgdbDel = sqlite_pkgdbDel, ++ .pkgdbGet = sqlite_pkgdbGet, ++ .pkgdbKey = sqlite_pkgdbKey, ++ .pkgdbNew = sqlite_pkgdbNew, ++ ++ .idxdbGet = sqlite_idxdbGet, ++ .idxdbPut = sqlite_idxdbPut, ++ .idxdbDel = sqlite_idxdbDel, ++ .idxdbKey = sqlite_idxdbKey ++}; ++ +diff --git a/macros.in b/macros.in +index a6069ee4d..9ad3d60ef 100644 +--- a/macros.in ++++ b/macros.in +@@ -620,6 +620,7 @@ package or when debugging this package.\ + # bdb Berkeley DB + # lmdb Lightning Memory-mapped Database + # ndb new data base format ++# sqlite Sqlite database (read-only in this version!) + # + %_db_backend bdb + +-- +2.31.1 + diff --git a/SOURCES/rpm-4.14.3-imp-covscan-fixes.patch b/SOURCES/rpm-4.14.3-imp-covscan-fixes.patch new file mode 100644 index 0000000..d0db6f4 --- /dev/null +++ b/SOURCES/rpm-4.14.3-imp-covscan-fixes.patch @@ -0,0 +1,284 @@ +commit c7d7c5acd0c14d0450016887cba1d86483086794 +Author: Michal Domonkos +Date: Mon Jun 21 10:05:10 2021 +0200 + + Add quoting to literal curly brackets + + These curly brackets are already treated as literals by the shell, so + let's make that explicit for clarity, and silence a ShellCheck warning + at the same time. + + More info: https://github.com/koalaman/shellcheck/wiki/SC1083 + + Found by ShellCheck. + +diff -up rpm-4.16.1.3/scripts/check-rpaths-worker.orig rpm-4.16.1.3/scripts/check-rpaths-worker +--- rpm-4.16.1.3/scripts/check-rpaths-worker.orig 2021-06-29 15:34:31.671003589 +0200 ++++ rpm-4.16.1.3/scripts/check-rpaths-worker 2021-06-29 15:34:51.993414093 +0200 +@@ -120,13 +120,13 @@ for i; do + (/lib64/*|/usr/lib64/*|/usr/X11R6/lib64/*|/usr/local/lib64/*) + badness=0;; + +- (\$ORIGIN|\${ORIGINX}|\$ORIGIN/*|\${ORIGINX}/*) ++ (\$ORIGIN|\$\{ORIGINX\}|\$ORIGIN/*|\$\{ORIGINX\}/*) + test $allow_ORIGIN -eq 0 && badness=8 || { + badness=0 + new_allow_ORIGIN=1 + } + ;; +- (/*\$PLATFORM*|/*\${PLATFORM}*|/*\$LIB*|/*\${LIB}*) ++ (/*\$PLATFORM*|/*\$\{PLATFORM\}*|/*\$LIB*|/*\$\{LIB\}*) + badness=0;; + + (/lib|/usr/lib|/usr/X11R6/lib) +From d8dc4fd37b1d90cd97de7fcf484d449ec132c9b3 Mon Sep 17 00:00:00 2001 +From: Michal Domonkos +Date: Wed, 9 Jun 2021 21:31:40 +0200 +Subject: [PATCH 1/7] Fix memory leak in sqlexec() + +Callers are supposed to free the error strings themselves: +https://www.sqlite.org/capi3ref.html#sqlite3_exec + +Found by Coverity. +--- + lib/backend/sqlite.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/lib/backend/sqlite.c b/lib/backend/sqlite.c +index 7c2de45aa..dbefeb163 100644 +--- a/lib/backend/sqlite.c ++++ b/lib/backend/sqlite.c +@@ -233,6 +233,7 @@ static int sqlexec(sqlite3 *sdb, const char *fmt, ...) + rpmlog(RPMLOG_DEBUG, "%s: %d\n", cmd, rc); + + sqlite3_free(cmd); ++ sqlite3_free(err); + + return rc ? RPMRC_FAIL : RPMRC_OK; + } +-- +2.31.1 + +From 5baf73feb4951cc3b3f553a4b18d3b3599cbf87c Mon Sep 17 00:00:00 2001 +From: Michal Domonkos +Date: Fri, 25 Jun 2021 11:21:46 +0200 +Subject: [PATCH 2/7] Always free the arg list passed to rpmGlob() + +Even though the actual implementation of rpmGlob() does not allocate the +passed arg list (av) if the return code (rc) is non-zero or arg count +(ac) is 0, it's the responsibility of the caller (rpmInstall() here) to +free that memory, so make sure we do that irrespectively of the above +conditions. + +Found by Coverity. +--- + lib/rpminstall.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/lib/rpminstall.c b/lib/rpminstall.c +index 724126e94..302ec0ba1 100644 +--- a/lib/rpminstall.c ++++ b/lib/rpminstall.c +@@ -461,6 +461,7 @@ int rpmInstall(rpmts ts, struct rpmInstallArguments_s * ia, ARGV_t fileArgv) + rpmlog(RPMLOG_ERR, _("File not found by glob: %s\n"), *eiu->fnp); + } + eiu->numFailed++; ++ argvFree(av); + continue; + } + +-- +2.31.1 + +From 3c8b01b67ec907afaaffe71691fa41b878578527 Mon Sep 17 00:00:00 2001 +From: Michal Domonkos +Date: Mon, 14 Jun 2021 10:21:25 +0200 +Subject: [PATCH 3/7] Fix resource leak in Fts_children() + +This function is not used anywhere within our codebase (and neither is +it part of the public API) so it's basically a no-op... Still, rather +than yanking it completely, let's just silence the Coverity error here. + +Found by Coverity. +--- + misc/fts.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/misc/fts.c b/misc/fts.c +index d3ebb2946..caf27495d 100644 +--- a/misc/fts.c ++++ b/misc/fts.c +@@ -585,8 +585,10 @@ Fts_children(FTS * sp, int instr) + if ((fd = __open(".", O_RDONLY, 0)) < 0) + return (NULL); + sp->fts_child = fts_build(sp, instr); +- if (__fchdir(fd)) ++ if (__fchdir(fd)) { ++ (void)__close(fd); + return (NULL); ++ } + (void)__close(fd); + return (sp->fts_child); + } +-- +2.31.1 + +From 39b7bf8579e0522cf16347b3a7e332d3b6d742c6 Mon Sep 17 00:00:00 2001 +From: Michal Domonkos +Date: Mon, 14 Jun 2021 12:34:23 +0200 +Subject: [PATCH 4/7] Fix memory leak in fts_build() + +Turns out this leak is already fixed in glibc's current version of fts.c +(where our copy originates from), so let's just backport that. + +Original commit in glibc: +https://sourceware.org/git/?p=glibc.git;\ +a=commit;h=db67c2c98b89a5723af44df54f38b779de8d4a65 + +Found by Coverity. +--- + misc/fts.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/misc/fts.c b/misc/fts.c +index caf27495d..f7fce0eaa 100644 +--- a/misc/fts.c ++++ b/misc/fts.c +@@ -855,6 +855,7 @@ mem1: saved_errno = errno; + fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) { + cur->fts_info = FTS_ERR; + SET(FTS_STOP); ++ fts_lfree(head); + return (NULL); + } + +@@ -862,6 +863,7 @@ mem1: saved_errno = errno; + if (!nitems) { + if (type == BREAD) + cur->fts_info = FTS_DP; ++ fts_lfree(head); + return (NULL); + } + +-- +2.31.1 + +From 9c093c4f092dd6bd1e0c8d2b852a72b74db076c2 Mon Sep 17 00:00:00 2001 +From: Michal Domonkos +Date: Tue, 15 Jun 2021 13:34:21 +0200 +Subject: [PATCH 5/7] Fix memory leak in decodePkts() + +Found by Coverity. +--- + rpmio/rpmpgp.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/rpmio/rpmpgp.c b/rpmio/rpmpgp.c +index c59185dce..ee5c81e24 100644 +--- a/rpmio/rpmpgp.c ++++ b/rpmio/rpmpgp.c +@@ -1371,9 +1371,13 @@ static pgpArmor decodePkts(uint8_t *b, uint8_t **pkt, size_t *pktlen) + crc = pgpCRC(dec, declen); + if (crcpkt != crc) { + ec = PGPARMOR_ERR_CRC_CHECK; ++ _free(dec); + goto exit; + } +- if (pkt) *pkt = dec; ++ if (pkt) ++ *pkt = dec; ++ else ++ _free(dec); + if (pktlen) *pktlen = declen; + ec = PGPARMOR_PUBKEY; /* XXX ASCII Pubkeys only, please. */ + goto exit; +-- +2.31.1 + +From 590b2fc06252567eb7d57197dc361a8b459d62a3 Mon Sep 17 00:00:00 2001 +From: Michal Domonkos +Date: Mon, 21 Jun 2021 17:51:14 +0200 +Subject: [PATCH 6/7] Fix memory leak with multiple %lang-s in one line + +We permit two equivalent forms of specifying a list of languages per +file: + + %lang(xx,yy,zz) /path/to/file + %lang(xx) %lang(yy) %lang(zz) /path/to/file + +The leak was when parsing the second form. + +Found by Coverity. +--- + build/files.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/build/files.c b/build/files.c +index f8153ad2b..0c8859f6c 100644 +--- a/build/files.c ++++ b/build/files.c +@@ -777,6 +777,8 @@ static rpmRC parseForLang(char * buf, FileEntry cur) + + if (*pe == ',') pe++; /* skip , if present */ + } ++ ++ q = _free(q); + } + + rc = RPMRC_OK; +-- +2.31.1 + +From b7a1e996326ee29a163d67ceb1e6127fdc251c14 Mon Sep 17 00:00:00 2001 +From: Michal Domonkos +Date: Fri, 25 Jun 2021 15:15:08 +0200 +Subject: [PATCH 7/7] Fix memory leaks in Lua rex extension + +This covers the following usage: + +expr = rex.newPOSIX() +expr:match() # A leak occurred here +expr:gmatch(, ) # A leak occurred here + +Found by Coverity. +--- + luaext/lrexlib.c | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/luaext/lrexlib.c b/luaext/lrexlib.c +index 09c5a6454..0f29b6371 100644 +--- a/luaext/lrexlib.c ++++ b/luaext/lrexlib.c +@@ -80,6 +80,7 @@ static void rex_push_matches(lua_State *L, const char *text, regmatch_t *match, + + static int rex_match(lua_State *L) + { ++ int rc = 0; + int res; + #ifdef REG_BASIC + size_t len; +@@ -109,9 +110,10 @@ static int rex_match(lua_State *L) + lua_pushstring(L, "n"); + lua_pushnumber(L, ncapt); + lua_rawset(L, -3); +- return 3; +- } else +- return 0; ++ rc = 3; ++ } ++ free(match); ++ return rc; + } + + static int rex_gmatch(lua_State *L) +@@ -158,6 +160,7 @@ static int rex_gmatch(lua_State *L) + break; + } + lua_pushnumber(L, nmatch); ++ free(match); + return 1; + } + +-- +2.31.1 + diff --git a/SPECS/rpm.spec b/SPECS/rpm.spec index fa86546..3f6351a 100644 --- a/SPECS/rpm.spec +++ b/SPECS/rpm.spec @@ -18,6 +18,8 @@ %bcond_without zstd # build with lmdb support? %bcond_with lmdb +# build with readonly sqlite support? +%bcond_without sqlite %if 0%{?rhel} > 7 # Disable python2 build by default @@ -30,7 +32,7 @@ %global rpmver 4.14.3 #global snapver rc2 -%global rel 15 +%global rel 17 %global srcver %{version}%{?snapver:-%{snapver}} %global srcdir %{?snapver:testing}%{!?snapver:%{name}-%(echo %{version} | cut -d'.' -f1-2).x} @@ -106,12 +108,12 @@ Patch153: rpm-4.14.3-ELF-files-strip-when-debuginfo-disabled.patch Patch154: rpm-4.14.3-more-careful-sig-hdr-copy.patch Patch155: rpm-4.14.3-preserve-kmod-secure-boot-signature.patch Patch156: rpm-4.14.3-hdrblobInit-add-bounds-check.patch +Patch157: rpm-4.14.3-add-read-only-support-for-sqlite.patch +Patch158: rpm-4.14.3-imp-covscan-fixes.patch # Python 3 string API sanity Patch500: 0001-In-Python-3-return-all-our-string-data-as-surrogate-.patch Patch501: 0001-Return-NULL-string-as-None-from-utf8FromString.patch -# Temporary compat crutch, not upstream -Patch502: 0001-Monkey-patch-.decode-method-to-our-strings-as-a-temp.patch # Make test-suite work with Python 3 Patch503: 0001-Honor-PYTHON-from-configure-when-running-tests.patch Patch504: 0002-Use-Python-3-compatible-exception-syntax-in-tests.patch @@ -179,6 +181,9 @@ BuildRequires: lua-devel >= 5.1 BuildRequires: libcap-devel BuildRequires: libacl-devel BuildRequires: audit-libs-devel +%if %{with sqlite} +BuildRequires: sqlite-devel +%endif %if %{with xz} BuildRequires: xz-devel >= 4.999.8 %endif @@ -461,6 +466,7 @@ done; %{?with_libimaevm: --with-imaevm} \ %{?with_zstd: --enable-zstd} \ %{?with_lmdb: --enable-lmdb} \ + %{?with_sqlite: --enable-sqlite} \ --with-fapolicyd \ --enable-python \ --with-crypto=openssl \ @@ -684,6 +690,13 @@ make check || cat tests/rpmtests.log %doc doc/librpm/html/* %changelog +* Mon Aug 23 2021 Michal Domonkos - 4.14.3-17 +- Address important covscan issues (#1996665) + +* Thu Aug 19 2021 Michal Domonkos - 4.14.3-16 +- Add support for read-only sqlite rpmdb (#1938928) +- Drop compat .decode() method from returned Py3 strings (#1840142) + * Thu Jul 15 2021 Michal Domonkos - 4.14.3-15 - Add out-of-bounds checks to hdrblobInit() (#1929445) - Fixes CVE-2021-20266