Add (beginnings of) a rpm version API (parsing, comparison)

It's more than a little hysterical that rpm hasn't had a meaningful
public API for parsing and comparing version strings. The API may seem
kinda overkill for some things but then this does give us a place to
check for invalid version strings, which is a thing we never had before
(although the current code doesn't do much in the way of checking)

Fixes: #561
This commit is contained in:
Panu Matilainen 2020-05-15 11:33:51 +03:00 committed by Florian Festi
parent 6800e0a4df
commit be0c4b5dce
5 changed files with 296 additions and 2 deletions

View File

@ -37,6 +37,9 @@
/** \defgroup rpmstrpool String Pool API.
\brief How to store strings in pools.
*/
/** \defgroup rpmver RPM version API.
\brief Rpm version comparison API.
*/
/** @} */
/** \defgroup install (un)Installing packages:
*

View File

@ -70,6 +70,7 @@ typedef struct rpmdbMatchIterator_s * rpmdbMatchIterator;
typedef struct rpmtsi_s * rpmtsi;
typedef struct rpmps_s * rpmps;
typedef struct rpmtxn_s * rpmtxn;
typedef struct rpmver_s * rpmver;
typedef struct rpmdbIndexIterator_s * rpmdbIndexIterator;
typedef const void * fnpyKey;

View File

@ -20,7 +20,7 @@ librpmio_la_SOURCES = \
argv.c base64.c digest.h digest.c expression.c macro.c \
rpmhook.c rpmio.c rpmlog.c rpmmalloc.c \
rpmpgp.c rpmsq.c rpmsw.c url.c \
rpmio_internal.h rpmhook.h rpmvercmp.c \
rpmio_internal.h rpmhook.h rpmvercmp.c rpmver.c \
rpmstring.c rpmfileutil.c rpmglob.c \
rpmkeyring.c rpmstrpool.c rpmmacro_internal.h

212
rpmio/rpmver.c Normal file
View File

@ -0,0 +1,212 @@
#include "system.h"
#include <rpm/rpmver.h>
#include <rpm/rpmstring.h>
#include <stdlib.h>
#include "debug.h"
struct rpmver_s {
const char *e;
const char *v;
const char *r;
char arena[];
};
/**
* Split EVR into epoch, version, and release components.
* @param evr [epoch:]version[-release] string
* @retval *ep pointer to epoch
* @retval *vp pointer to version
* @retval *rp pointer to release
*/
static
void parseEVR(char * evr,
const char ** ep,
const char ** vp,
const char ** rp)
{
const char *epoch;
const char *version; /* assume only version is present */
const char *release;
char *s, *se;
s = evr;
while (*s && risdigit(*s)) s++; /* s points to epoch terminator */
se = strrchr(s, '-'); /* se points to version terminator */
if (*s == ':') {
epoch = evr;
*s++ = '\0';
version = s;
if (*epoch == '\0') epoch = "0";
} else {
epoch = NULL; /* XXX disable epoch compare if missing */
version = evr;
}
if (se) {
*se++ = '\0';
release = se;
} else {
release = NULL;
}
if (ep) *ep = epoch;
if (vp) *vp = version;
if (rp) *rp = release;
}
int rpmverOverlap(rpmver v1, rpmsenseFlags f1, rpmver v2, rpmsenseFlags f2)
{
int sense = 0;
int result = 0;
/* Compare {A,B} [epoch:]version[-release] */
if (v1->e && *v1->e && v2->e && *v2->e)
sense = rpmvercmp(v1->e, v2->e);
else if (v1->e && *v1->e && atol(v1->e) > 0) {
sense = 1;
} else if (v2->e && *v2->e && atol(v2->e) > 0)
sense = -1;
if (sense == 0) {
sense = rpmvercmp(v1->v, v2->v);
if (sense == 0) {
if (v1->r && *v1->r && v2->r && *v2->r) {
sense = rpmvercmp(v1->r, v2->r);
} else {
/* always matches if the side with no release has SENSE_EQUAL */
if ((v1->r && *v1->r && (f2 & RPMSENSE_EQUAL)) ||
(v2->r && *v2->r && (f1 & RPMSENSE_EQUAL))) {
result = 1;
goto exit;
}
}
}
}
/* Detect overlap of {A,B} range. */
if (sense < 0 && ((f1 & RPMSENSE_GREATER) || (f2 & RPMSENSE_LESS))) {
result = 1;
} else if (sense > 0 && ((f1 & RPMSENSE_LESS) || (f2 & RPMSENSE_GREATER))) {
result = 1;
} else if (sense == 0 &&
(((f1 & RPMSENSE_EQUAL) && (f2 & RPMSENSE_EQUAL)) ||
((f1 & RPMSENSE_LESS) && (f2 & RPMSENSE_LESS)) ||
((f1 & RPMSENSE_GREATER) && (f2 & RPMSENSE_GREATER)))) {
result = 1;
}
exit:
return result;
}
static int compare_values(const char *str1, const char *str2)
{
if (!str1 && !str2)
return 0;
else if (str1 && !str2)
return 1;
else if (!str1 && str2)
return -1;
return rpmvercmp(str1, str2);
}
int rpmverCmp(rpmver v1, rpmver v2)
{
const char *e1 = (v1->e != NULL) ? v1->e : "0";
const char *e2 = (v2->e != NULL) ? v2->e : "0";
int rc = compare_values(e1, e2);
if (!rc) {
rc = compare_values(v1->v, v2->v);
if (!rc)
rc = compare_values(v1->r, v2->r);
}
return rc;
}
uint32_t rpmverEVal(rpmver rv)
{
return (rv != NULL && rv->e != NULL) ? atol(rv->e) : 0;
}
const char *rpmverE(rpmver rv)
{
return (rv != NULL) ? rv->e : NULL;
}
const char *rpmverV(rpmver rv)
{
return (rv != NULL) ? rv->v : NULL;
}
const char *rpmverR(rpmver rv)
{
return (rv != NULL) ? rv->r : NULL;
}
char *rpmverEVR(rpmver rv)
{
char *EVR = NULL;
if (rv) {
rstrscat(&EVR, rv->e ? rv-> e : "", rv->e ? ":" : "",
rv->v,
rv->r ? "-" : "", rv->r ? rv->r : "", NULL);
}
return EVR;
}
rpmver rpmverParse(const char *evr)
{
rpmver rv = NULL;
if (evr && *evr) {
size_t evrlen = strlen(evr) + 1;
rv = xmalloc(sizeof(*rv) + evrlen);
memcpy(rv->arena, evr, evrlen);
parseEVR(rv->arena, &rv->e, &rv->v, &rv->r);
}
return rv;
}
rpmver rpmverNew(const char *e, const char *v, const char *r)
{
rpmver rv = NULL;
if (v && *v) {
size_t nb = strlen(v) + 1;
nb += (e != NULL) ? strlen(e) + 1 : 0;
nb += (r != NULL) ? strlen(r) + 1 : 0;
rv = xmalloc(sizeof(*rv) + nb);
rv->e = NULL;
rv->v = NULL;
rv->r = NULL;
char *p = rv->arena;
if (e) {
rv->e = p;
p = stpcpy(p, e);
p++;
}
rv->v = p;
p = stpcpy(p, v);
p++;
if (r) {
rv->r = p;
p = stpcpy(p, r);
p++;
}
}
return rv;
}
rpmver rpmverFree(rpmver rv)
{
if (rv) {
free(rv);
}
return NULL;
}

View File

@ -1,11 +1,14 @@
#ifndef _RPMVER_H
#define _RPMVER_H
#include <rpm/rpmtypes.h>
#include <rpm/rpmds.h> /* sense flags */
#ifdef __cplusplus
extern "C" {
#endif
/** \ingroup rpmtrans
/** \ingroup rpmver
* Segmented string compare for version or release strings.
*
* @param a 1st string
@ -14,6 +17,81 @@ extern "C" {
*/
int rpmvercmp(const char * a, const char * b);
/** \ingroup rpmver
* Parse rpm version handle from evr string
*
* @param evr [epoch:]version[-release] string
* @return rpm version, NULL on invalid evr
*/
rpmver rpmverParse(const char *evr);
/** \ingroup rpmver
* Create new rpm version handle from e, v, r components
*
* @param e epoch (or NULL)
* @param v version
* @param r release (or NULL)
* @return rpm version, NULL on invalid
*/
rpmver rpmverNew(const char *e, const char *v, const char *r);
/** \ingroup rpmver
* Free rpm version handle
*
* @param rv rpm version handle
* @return NULL always
*/
rpmver rpmverFree(rpmver rv);
/** \ingroup rpmver
* @param rv rpm version handle
* @return numerical value of epoch
*/
uint32_t rpmverEVal(rpmver rv);
/** \ingroup rpmver
* @param rv rpm version handle
* @return epoch portion
*/
const char *rpmverE(rpmver rv);
/** \ingroup rpmver
* @param rv rpm version handle
* @return version portion
*/
const char *rpmverV(rpmver rv);
/** \ingroup rpmver
* @param rv rpm version handle
* @return release portion
*/
const char *rpmverR(rpmver rv);
/** \ingroup rpmver
* @param rv rpm version handle
* @return formatted [E:]V[-R] string (malloced)
*/
char *rpmverEVR(rpmver rv);
/** \ingroup rpmver
* Compare two rpm version handles
*
* @param v1 1st version handle
* @param v2 2nd version handle
* @return 0 if equal, -1 if v1 smaller, 1 if greater, than v2
*/
int rpmverCmp(rpmver v1, rpmver v2);
/** \ingroup rpmver
* Determine whether two versioned ranges overlap.
* @param v1 1st version
* @param f1 1st sense flags
* @param v2 2nd version
* @param f2 2nd sense flags
* @return 1 if ranges overlap, 0 otherwise
*/
int rpmverOverlap(rpmver v1, rpmsenseFlags f1, rpmver v2, rpmsenseFlags f2);
#ifdef __cplusplus
}
#endif