diff --git a/bin/xbps-cmpver/main.c b/bin/xbps-cmpver/main.c index 8668a9249b3..91960d43912 100644 --- a/bin/xbps-cmpver/main.c +++ b/bin/xbps-cmpver/main.c @@ -9,21 +9,13 @@ int main(int argc, char **argv) { - if (argc<3) { - printf("Usage: xbps-cmpver [old] [new]\n"); - printf(" xbpks-cmpver foo-1.2 foo-2.2 # $? = 1\n"); - printf(" xbpks-cmpver foo-1.2 foo-1.2 # $? = 0\n"); + if (argc < 3) { + printf("Usage: xbps-cmpver [installed] [required]\n"); + printf(" xbps-cmpver foo-1.2 foo-2.2 # $? = 1\n"); + printf(" xbps-cmpver foo-1.2 foo-1.1.0 # $? = 0\n"); + printf(" xbps-cmpver foo-1.2 foo-1.2 # $? = 0\n"); return 1; } -#if UNIT_TEST - printf("1.2 2.2 = %d\n", chkver("1.2", "2.2")); - printf("1.0 10.3 = %d\n", chkver("1.0", "10.3")); - printf("1.0 1.0 = %d\n", chkver("1.0", "1.0")); - printf("1.0 1.2 = %d\n", chkver("1.0", "1.2")); - printf("1.0.1 1.0.2 = %d\n", chkver("1.0.1", "1.0.2")); - printf("1.0beta 1.0rc1 = %d\n", chkver("1.0beta", "1.0rc1")); -#endif - - return (xbps_cmpver_packages(argv[1], argv[2]) > 0)?1:0; + return xbps_cmpver_packages(argv[1], argv[2]); } diff --git a/doc/TODO b/doc/TODO index d4e0af0c7ba..5452abd2249 100644 --- a/doc/TODO +++ b/doc/TODO @@ -10,10 +10,6 @@ xbps-src: automatically add triggers, shared by src/bin pkgs. [IN PROGRESS] * Fix the "automatic-install" object in regpkgdb.plist. -xbps-cmpver: - * It fails in some cases: foo-2.0 vs foo-1.99 and foo-2a vs foo-2b. - Another case that fails: 1.7.0 vs 1.6.[56789]. - Packages: * fix mpfr hardcoded rpath for gmp. diff --git a/lib/cmpver.c b/lib/cmpver.c index 72e6985a3b9..4bc828811a2 100644 --- a/lib/cmpver.c +++ b/lib/cmpver.c @@ -1,66 +1,303 @@ -/* - * Compare package and version strings - * @ 2008 - * Author: pancake +/* + * FreeBSD install - a package for the installation and maintenance + * of non-core utilities. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * Maxim Sobolev + * 31 July 2001 + * */ + #include +#include #include +#include #include -static int chkchr(const char *ch) +/* + * split_version(pkgname, endname, epoch, revision) returns a pointer to + * the version portion of a package name and the two special components. + * + * Syntax is: ${PORTNAME}-${PORTVERSION}[_${PORTREVISION}][,${PORTEPOCH}] + * + * Written by Oliver Eikemeier + * Based on work of Jeremy D. Lea. + */ +static const char * +split_version(const char *pkgname, const char **endname, unsigned long *epoch, + unsigned long *revision) { - if (*ch>='0' && *ch<='9') - return *ch-'0'; - if (ch[1]=='\0') { - if (*ch>='a'&&*ch<='z'){ - return *ch-'a'; - } - } - switch(*ch) { - case 'a': if (ch[1]=='l') - return 0xa; - return -1; - case 'b': return 0xb; - case 'r': return 0xc; - } - return -1; + char *ch; + const char *versionstr; + const char *endversionstr; + + assert(pkgname != NULL); + + /* Look for the last '-' the the pkgname */ + ch = strrchr(pkgname, '-'); + /* Cheat if we are just passed a version, not a valid package name */ + versionstr = ch ? ch + 1 : pkgname; + + /* Look for the last '_' in the version string, advancing the end pointer */ + ch = strrchr(versionstr, '_'); + if (revision != NULL) { + *revision = ch ? strtoul(ch + 1, NULL, 10) : 0; + } + endversionstr = ch; + + /* Look for the last ',' in the remaining version string */ + ch = strrchr(endversionstr ? endversionstr + 1 : versionstr, ','); + if (epoch != NULL) { + *epoch = ch ? strtoul(ch + 1, NULL, 10) : 0; + } + if (ch && !endversionstr) + endversionstr = ch; + + /* + * set the pointer behind the last character of the version without + * revision or epoch + */ + if (endname) + *endname = endversionstr ? endversionstr : strrchr(versionstr, '\0'); + + return versionstr; } -static int ver2int(const char *a0, int *pow, int mpow) +/* + * PORTVERSIONs are composed of components separated by dots. A component + * consists of a version number, a letter and a patchlevel number. This does + * not conform to the porter's handbook, but let us formulate rules that + * fit the current practice and are far simpler than to make decisions + * based on the order of netters and lumbers. Besides, people use versions + * like 10b2 in the ports... + */ + +typedef struct { + long n; + long pl; + int a; +} version_component; + +/* + * get_component(position, component) gets the value of the next component + * (number - letter - number triple) and returns a pointer to the next character + * after any leading separators + * + * - components are separated by dots + * - characters !~ [a-zA-Z0-9.+*] are treated as separators + * (1.0:2003.09.16 = 1.0.2003.09.16), this may not be what you expect: + * 1.0.1:2003.09.16 < 1.0:2003.09.16 + * - consecutive separators are collapsed (10..1 = 10.1) + * - missing separators are inserted, essentially + * letter number letter => letter number . letter (10a1b2 = 10a1.b2) + * - missing components are assumed to be equal to 0 (10 = 10.0 = 10.0.0) + * - the letter sort order is: [none], a, b, ..., z; numbers without letters + * sort first (10 < 10a < 10b) + * - missing version numbers (in components starting with a letter) sort as -1 + * (a < 0, 10.a < 10) + * - a separator is inserted before the special strings "pl", "alpha", "beta", + * "pre" and "rc". + * - "pl" sorts before every other letter, "alpha", "beta", "pre" and "rc" + * sort as a, b, p and r. (10alpha = 10.a < 10, but 10 < 10a; pl11 < alpha3 + * < 0.1beta2 = 0.1.b2 < 0.1) + * - other strings use only the first letter for sorting, case is ignored + * (1.d2 = 1.dev2 = 1.Development2) + * - The special component `*' is guaranteed to be the smallest possible + * component (2.* < 2pl1 < 2alpha3 < 2.9f7 < 3.*) + * - components separated by `+' are handled by version_cmp below + * + * Oliver Eikemeier + */ + +static const struct { + const char *name; + size_t namelen; + int value; +} stage[] = { + { "pl", 2, 0 }, + { "alpha", 5, 'a'-'a'+1 }, + { "beta", 4, 'b'-'a'+1 }, + { "pre", 3, 'p'-'a'+1 }, + { "rc", 2, 'r'-'a'+1 }, + { NULL, 0, -1 } +}; + +static const char * +get_component(const char *position, version_component *component) { - int r,ret = 0; - int pos = 0; - const char *a = a0+strlen(a0)-1; - for(*pow=0;a>=a0;a=a-1) { - if (*a=='.') { - *pow=*pow+1; - } else { - r = chkchr(a); - if (r != -1) - ret+=((r+1)*((*pow)+1))<n = strtol(pos, &endptr, 10); + /* should we test for errno == ERANGE? */ + pos = endptr; + } else if (*pos == '*') { + component->n = -2; + do { + pos++; + } while(*pos && *pos != '+'); + } else { + component->n = -1; + hasstage = 1; + } + + /* handle letter */ + if (isalpha((unsigned char)*pos)) { + int c = tolower((unsigned char)*pos); + haspatchlevel = 1; + /* handle special suffixes */ + if (isalpha((unsigned char)pos[1])) { + int i; + for (i = 0; stage[i].name; i++) { + if (strncasecmp(pos, stage[i].name, stage[i].namelen) == 0 + && !isalpha((unsigned char)pos[stage[i].namelen])) { + if (hasstage) { + /* stage to value */ + component->a = stage[i].value; + pos += stage[i].namelen; + } else { + /* insert dot */ + component->a = 0; + haspatchlevel = 0; + } + c = 0; + break; } - if (mpow>0 && *pow > mpow) - break; + } } - return ret; + /* unhandled above */ + if (c) { + /* use the first letter and skip following */ + component->a = c - 'a' + 1; + do { + ++pos; + } while (isalpha((unsigned char)*pos)); + } + } else { + component->a = 0; + haspatchlevel = 0; + } + + if (haspatchlevel) { + /* handle patch number */ + if (isdigit((unsigned char)*pos)) { + char *endptr; + component->pl = strtol(pos, &endptr, 10); + /* should we test for errno == ERANGE? */ + pos = endptr; + } else { + component->pl = -1; + } + } else { + component->pl = 0; + } + + /* skip trailing separators */ + while (*pos && !isdigit((unsigned char)*pos) && + !isalpha((unsigned char)*pos) && + *pos != '+' && *pos != '*') { + pos++; + } + + return pos; +} + +/* + * version_cmp(pkg1, pkg2) returns -1, 0 or 1 depending on if the version + * components of pkg1 is less than, equal to or greater than pkg2. No + * comparison of the basenames is done. + * + * The port version is defined by: + * ${PORTVERSION}[_${PORTREVISION}][,${PORTEPOCH}] + * ${PORTEPOCH} supersedes ${PORTVERSION} supersedes ${PORTREVISION}. + * See the commit log for revision 1.349 of ports/Mk/bsd.port.mk + * for more information. + * + * The epoch and revision are defined to be a single number, while the rest + * of the version should conform to the porting guidelines. It can contain + * multiple components, separated by a period, including letters. + * + * The tests allow for significantly more latitude in the version numbers + * than is allowed in the guidelines. No point in enforcing them here. + * That's what portlint is for. + * + * Jeremy D. Lea. + * reimplemented by Oliver Eikemeier + */ +int +xbps_cmpver_packages(const char *pkg1, const char *pkg2) +{ + const char *v1, *v2, *ve1, *ve2; + unsigned long e1, e2, r1, r2; + int result = 0; + + v1 = split_version(pkg1, &ve1, &e1, &r1); + v2 = split_version(pkg2, &ve2, &e2, &r2); + + /* Check epoch, port version, and port revision, in that order. */ + if (e1 != e2) { + result = (e1 < e2 ? -1 : 1); + } + + /* Shortcut check for equality before invoking the parsing routines. */ + if (result == 0 && (ve1 - v1 != ve2 - v2 || strncasecmp(v1, v2, ve1 - v1) != 0)) { + /* Loop over different components (the parts separated by dots). + * If any component differs, we have the basis for an inequality. */ + while(result == 0 && (v1 < ve1 || v2 < ve2)) { + int block_v1 = 0; + int block_v2 = 0; + version_component vc1 = {0, 0, 0}; + version_component vc2 = {0, 0, 0}; + if (v1 < ve1 && *v1 != '+') { + v1 = get_component(v1, &vc1); + } else { + block_v1 = 1; + } + if (v2 < ve2 && *v2 != '+') { + v2 = get_component(v2, &vc2); + } else { + block_v2 = 1; + } + if (block_v1 && block_v2) { + if (v1 < ve1) + v1++; + if (v2 < ve2) + v2++; + } else if (vc1.n != vc2.n) { + result = (vc1.n < vc2.n ? -1 : 1); + } else if (vc1.a != vc2.a) { + result = (vc1.a < vc2.a ? -1 : 1); + } else if (vc1.pl != vc2.pl) { + result = (vc1.pl < vc2.pl ? -1 : 1); + } + } + } + + /* Compare FreeBSD revision numbers. */ + if (result == 0 && r1 != r2) { + result = (r1 < r2 ? -1 : 1); + } + + return result == -1 ? 1 : 0; } int -xbps_cmpver_versions(const char *a0, const char *a1) +xbps_cmpver_versions(const char *inst, const char *req) { - int p0,p1; - int v0 = ver2int(a0, &p0, 0); - int v1 = ver2int(a1, &p1, p0); - return v1-v0; -} - -int -xbps_cmpver_packages(const char *a0, const char *b0) -{ - char *a = strrchr(a0, '-'); - char *b = strrchr(b0, '-'); - - assert(a != NULL || b != NULL); - - return xbps_cmpver_versions(a+1, b+1); + int res = xbps_cmpver_packages(inst, req); + + return res == -1 ? 1 : 0; } diff --git a/lib/util.c b/lib/util.c index e30908424d1..30349775826 100644 --- a/lib/util.c +++ b/lib/util.c @@ -48,7 +48,7 @@ xbps_check_is_installed_pkg(const char *pkg) plist = xbps_append_full_path(true, NULL, XBPS_REGPKGDB); if (plist == NULL) - return EINVAL; + return -1; pkgname = xbps_get_pkg_name(pkg); reqver = xbps_get_pkg_version(pkg); @@ -77,7 +77,7 @@ xbps_check_is_installed_pkg(const char *pkg) assert(instver != NULL); /* Compare installed and required version. */ - rv = xbps_cmpver_versions(instver, reqver) > 0 ? 1 : 0; + rv = xbps_cmpver_versions(instver, reqver); free(pkgname); free(plist);