Implement a database cookie API for determining whether rpmdb has changed

Add rpmdbCookie() public function and matching python bindings.
The returned value is an opaque string which changes any time the rpmdb
is changed (eg packages added or removed, rebuild changes things etc).
A key point is that this is entirely database backend agnostic.

The actual implementation here is an SHA1 hash of RPMDBI_NAME keys and
the corresponding package offsets, but this is an implementation detail
that can be changed anytime and callers must not rely on. The only thing
that matters is whether the string equals an earlier cookie value or not.
A cryptographic hash seems like an overkill, but then it saves us the
headaches of coming up with something that reflects order changes etc.

Closes: #388
This commit is contained in:
Panu Matilainen 2019-05-23 13:23:05 +03:00 committed by Florian Festi
parent 1d6fde768c
commit d1ebf65598
4 changed files with 115 additions and 0 deletions

View File

@ -2644,3 +2644,23 @@ int rpmdbCtrl(rpmdb db, rpmdbCtrlOp ctrl)
return dbctrl ? dbCtrl(db, dbctrl) : 1;
}
char *rpmdbCookie(rpmdb db)
{
void *cookie = NULL;
rpmdbIndexIterator ii = rpmdbIndexIteratorInit(db, RPMDBI_NAME);
if (ii) {
DIGEST_CTX ctx = rpmDigestInit(PGPHASHALGO_SHA1, RPMDIGEST_NONE);
const void *key = 0;
size_t keylen = 0;
while ((rpmdbIndexIteratorNext(ii, &key, &keylen)) == 0) {
const unsigned int *offsets = rpmdbIndexIteratorPkgOffsets(ii);
unsigned int npkgs = rpmdbIndexIteratorNumPkgs(ii);
rpmDigestUpdate(ctx, key, keylen);
rpmDigestUpdate(ctx, offsets, sizeof(*offsets) * npkgs);
}
rpmDigestFinal(ctx, &cookie, NULL, 1);
}
rpmdbIndexIteratorFree(ii);
return cookie;
}

View File

@ -218,6 +218,14 @@ rpmdbIndexIterator rpmdbIndexIteratorFree(rpmdbIndexIterator ii);
*/
int rpmdbCtrl(rpmdb db, rpmdbCtrlOp ctrl);
/** \ingroup rpmdb
* Retrieve rpm database changed-cookie.
* Useful for eg. determining cache validity.
* @param db rpm database
* @return cookie string (malloced), or NULL on error
*/
char *rpmdbCookie(rpmdb db);
#ifdef __cplusplus
}
#endif

View File

@ -366,6 +366,21 @@ rpmts_VerifyDB(rpmtsObject * s)
return Py_BuildValue("i", rc);
}
static PyObject *
rpmts_dbCookie(rpmtsObject * s)
{
PyObject *ret = NULL;
char *cookie = NULL;
Py_BEGIN_ALLOW_THREADS
cookie = rpmdbCookie(rpmtsGetRdb(s->ts));
Py_END_ALLOW_THREADS
ret = utf8FromString(cookie);
free(cookie);
return ret;
}
static PyObject *
rpmts_HdrFromFdno(rpmtsObject * s, PyObject *arg)
{
@ -797,6 +812,9 @@ Remove all elements from the transaction set\n" },
{"dbIndex", (PyCFunction) rpmts_index, METH_VARARGS|METH_KEYWORDS,
"ts.dbIndex(TagN) -> ii\n\
- Create a key iterator for the default transaction rpmdb.\n" },
{"dbCookie", (PyCFunction) rpmts_dbCookie, METH_NOARGS,
"dbCookie -> cookie\n\
- Return a cookie string for determining if database has changed\n" },
{NULL, NULL} /* sentinel */
};

View File

@ -368,6 +368,75 @@ hi
[])
AT_CLEANUP
AT_SETUP([database cookies])
AT_KEYWORDS([python rpmdb])
AT_CHECK([
RPMDB_CLEAR
RPMDB_INIT
],
[0],
[],
[])
RPMPY_CHECK([
ts = rpm.ts()
ts.openDB()
c1 = ts.dbCookie()
ts.closeDB()
ts.openDB()
c2 = ts.dbCookie()
myprint(c1 == c2 != None)
open("dbcookie", "w+").write(c1)
],
[True
],
[])
AT_CHECK([
runroot rpm -i \
--justdb --nodeps --ignorearch --ignoreos \
/data/RPMS/foo-1.0-1.noarch.rpm \
/data/RPMS/hello-2.0-1.i686.rpm \
],
[0],
[],
[])
RPMPY_CHECK([
ts = rpm.ts()
ts.openDB()
c1 = ts.dbCookie()
c2 = open("dbcookie", "r").read()
myprint(c1 != c2)
open("dbcookie", "w+").write(c1)
],
[True
],
[])
AT_CHECK([
runroot rpm -i \
--justdb --nodeps --ignorearch --ignoreos \
--define "_transaction_color 3" \
--define "_prefer_color 2" \
/data/RPMS/hello-2.0-1.x86_64.rpm
],
[0],
[],
[])
RPMPY_CHECK([
ts = rpm.ts()
ts.openDB()
c1 = ts.dbCookie()
c2 = open("dbcookie", "r").read()
myprint(c1 != c2)
],
[True
],
[])
AT_CLEANUP
RPMPY_TEST([dependency sets 1],[
ts = rpm.ts()
h = ts.hdrFromFdno('${RPMDATA}/RPMS/hello-1.0-1.ppc64.rpm')