From e6a3a5537973f060bbfbe9b5aecfd9c0312a1a7f Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Mon, 1 Jan 2007 12:52:33 +0000 Subject: [PATCH] servutil: Implement version number comparison. Organization: Straylight/Edgeware From: Mark Wooding We use an extended version of the dpkg version comparison algorithm. See the code for a description. --- server/servutil.c | 153 ++++++++++++++++++++++++++++++++++++++++++++++ server/tripe.h | 33 ++++++++++ 2 files changed, 186 insertions(+) diff --git a/server/servutil.c b/server/servutil.c index 92a5f775..77cfd2c6 100644 --- a/server/servutil.c +++ b/server/servutil.c @@ -140,4 +140,157 @@ int seq_check(seqwin *s, uint32 q, const char *service) return (0); } +/* --- @versioncmp@ --- * + * + * Arguments: @const char *va, *vb@ = two version strings + * + * Returns: Less than, equal to, or greater than zero, according to + * whether @va@ is less than, equal to, or greater than @vb@. + * + * Use: Compares version number strings. + * + * The algorithm is an extension of the Debian version + * comparison algorithm. A version number consists of three + * components: + * + * [EPOCH :] MAIN [- SUB] + * + * The MAIN part may contain colons or hyphens if there is an + * EPOCH or SUB, respectively. Version strings are compared + * componentwise: first epochs, then main parts, and finally + * subparts. + * + * The component comparison is done as follows. First, the + * initial subsequence of nondigit characters is extracted from + * each string, and these are compared lexicographically, using + * ASCII ordering, except that letters precede non-letters. If + * both are the same, an initial sequence of digits is extracted + * from the remaining parts of the version strings, and these + * are compared numerically (an empty sequence being considered + * to have the value zero). This process is repeated until we + * have a winner or until both strings are exhausted. + */ + +struct vinfo { + const char *e, *el; + const char *m, *ml; + const char *s, *sl; +}; + +static int vint(const char **vv, const char *vl) +{ + int n = 0; + const char *v = *vv; + int ch; + + while (v < vl) { + ch = *v; + if (!isdigit((unsigned char)ch)) + break; + v++; + n = n * 10 + (ch - '0'); + } + *vv = v; + return (n); +} + +static const char *vchr(const char **vv, const char *vl) +{ + const char *v = *vv; + const char *b = v; + int ch; + + while (v < vl) { + ch = *v; + if (isdigit((unsigned char)ch)) + break; + v++; + } + *vv = v; + return (b); +} + +#define CMP(x, y) ((x) < (y) ? -1 : +1) + +static int vcmp(const char *va, const char *val, + const char *vb, const char *vbl) +{ + const char *pa, *pb; + int ia, ib; + + for (;;) { + + /* --- See if we're done --- */ + + if (va == val && vb == vbl) + return (0); + + /* --- Compare nondigit portions --- */ + + pa = vchr(&va, val); pb = vchr(&vb, vbl); + for (;;) { + if (pa == va && pb == vb) + break; + else if (pa == va) + return (-1); + else if (pb == vb) + return (+1); + else if (*pa == *pb) { + pa++; pb++; + continue; + } else if (isalpha((unsigned char)*pa) == isalpha((unsigned char)*pb)) + return (CMP(*pa, *pb)); + else if (isalpha((unsigned char)*pa)) + return (-1); + else + return (+1); + } + + /* --- Compare digit portions --- */ + + ia = vint(&va, val); ib = vint(&vb, vbl); + if (ia != ib) + return (CMP(ia, ib)); + } +} + +static void vsplit(const char *v, struct vinfo *vi) +{ + const char *p; + size_t n; + + if ((p = strchr(v, ':')) == 0) + vi->e = vi->el = 0; + else { + vi->e = v; + vi->el = p; + v = p + 1; + } + + n = strlen(v); + if ((p = strrchr(v, '-')) == 0) + vi->s = vi->sl = 0; + else { + vi->s = p + 1; + vi->sl = v + n; + n = p - v; + } + + vi->m = v; + vi->ml = v + n; +} + +int versioncmp(const char *va, const char *vb) +{ + struct vinfo via, vib; + int rc; + + vsplit(va, &via); vsplit(vb, &vib); + if ((rc = vcmp(via.e, via.el, vib.e, vib.el)) != 0 || + (rc = vcmp(via.m, via.ml, vib.m, vib.ml)) != 0 || + (rc = vcmp(via.s, via.sl, vib.s, vib.sl)) != 0) + return (rc); + return (0); +} + /*----- That's all, folks -------------------------------------------------*/ diff --git a/server/tripe.h b/server/tripe.h index a9a785dc..e917ddb0 100644 --- a/server/tripe.h +++ b/server/tripe.h @@ -1146,6 +1146,39 @@ extern void seq_reset(seqwin */*s*/); extern int seq_check(seqwin */*s*/, uint32 /*q*/, const char */*service*/); +/* --- @versioncmp@ --- * + * + * Arguments: @const char *va, *vb@ = two version strings + * + * Returns: Less than, equal to, or greater than zero, according to + * whether @va@ is less than, equal to, or greater than @vb@. + * + * Use: Compares version number strings. + * + * The algorithm is an extension of the Debian version + * comparison algorithm. A version number consists of three + * components: + * + * [EPOCH :] MAIN [- SUB] + * + * The MAIN part may contain colons or hyphens if there is an + * EPOCH or SUB, respectively. Version strings are compared + * componentwise: first epochs, then main parts, and finally + * subparts. + * + * The component comparison is done as follows. First, the + * initial subsequence of nondigit characters is extracted from + * each string, and these are compared lexicographically, using + * ASCII ordering, except that letters precede non-letters. If + * both are the same, an initial sequence of digits is extracted + * from the remaining parts of the version strings, and these + * are compared numerically (an empty sequence being considered + * to have the value zero). This process is repeated until we + * have a winner or until both strings are exhausted. + */ + +extern int versioncmp(const char */*va*/, const char */*vb*/); + /*----- That's all, folks -------------------------------------------------*/ #ifdef __cplusplus -- [mdw]