From b0c5db32f5f53b5d43170756668e0c0387a88f13 Mon Sep 17 00:00:00 2001 From: Nathan Wagner Date: Tue, 26 Feb 2019 05:10:18 +0000 Subject: [PATCH] fix vercmp bugs --- Makefile | 6 +- doc/zpm-vercmp.8 | 38 +++++++--- lib/parse.c | 75 +++++++++++++++++++- lib/vercmp.c | 177 ++++++++++++++++++++++++++++++++++++----------- src/vercmp.c | 73 +++++++++---------- t/vercmp.t | 35 ++++++++-- zpm.h | 9 +++ 7 files changed, 308 insertions(+), 105 deletions(-) diff --git a/Makefile b/Makefile index 802360b..dabebe8 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ lib/jsw/jsw_rbtree.c JSWOBJ=$(JSWSRC:%.c=%.o) LIBZPMSRC=sha256.c db.c compress.c uncompress.c zpm.c zpm_hash.c \ foreach_path.c vercmp.c findpkg.c quote.c dbquery.c script_hash.c \ - parse.c integ.c seterror.c notes.c createpkg.c + parse.c integ.c seterror.c notes.c createpkg.c buffer.c LIBZPMOBJ=$(addprefix lib/, $(LIBZPMSRC:%.c=%.o)) @@ -198,10 +198,10 @@ sqlite/shell.o: sqlite/shell.c sqlite/config.h Makefile lib/zpm.o: newdb.c -zpm-vercmp: src/vercmp.o lib/vercmp.o +zpm-vercmp: src/vercmp.o lib/vercmp.o lib/parse.o $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $+ -zpm-shell: sqlite/sqlite3.o sqlite/shell.o sqlite/extensions.o lib/vercmp.o +zpm-shell: sqlite/sqlite3.o sqlite/shell.o sqlite/extensions.o lib/vercmp.o lib/parse.o $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $+ libelf.a: elf/libelf.o diff --git a/doc/zpm-vercmp.8 b/doc/zpm-vercmp.8 index 9a31fac..d046998 100644 --- a/doc/zpm-vercmp.8 +++ b/doc/zpm-vercmp.8 @@ -1,28 +1,46 @@ -.TH zpm-vercmp 8 2018-12-10 "ZPM 0.4" +.TH zpm-vercmp 8 2019-02-26 "ZPM 0.4" .SH NAME zpm-vercmp \- compare strings with version numbering .SH SYNOPSIS .B zpm vercmp \fR[\fB-q\fR] \fIstring1 string2\fR .SH DESCRIPTION -\fBzpm-vercmp\fR compares its first two arguments, using -the usual version numbering semantics. It will write -the result of the comparison to stdout, 0 if the arguments -compare equal, -1 if the first is greater, and 1 if the -second is greater. +\fBzpm-vercmp\fR compares its arguments, using +version numbering semantics. It can operate in +test mode or search mode. In search mode, activated with \-G or \-L +it will print the highest or lowest version string to stdout. +.PP +In test mode, it compares all the arguments, and prints '0' if they are all +equal, '-1' if they are in version order, and 1 if they are out of order. +Adjacent identical version strings are considered both equal and in order. +Additionally in test mode, +the process will exit 0 if the strings are all equal, 1 if the +strings are not in order, and 2 if they are in order. .SH OPTIONS .TP \-q write nothing to stdout. The program will still exit with the usual exit status +.TP +\-G +Print the largest argument version string found. +.TP +\-L +Print the smallest argument version string found. .SH EXAMPLES .PP .nf -zpm-vercmp a b -> "1" +zpm-vercmp a b -> "-1" .fi .SH EXIT STATUS -0 if the two strings compare equal --1 if the first string is greater than the second -1 if the second string is greater then the first +.TP +0 +if the strings are all equal +.TP +1 +if the strings are not in version order +.TP +2 +if the strings are in version order .SH FILES None .SH ENVIRONMENT diff --git a/lib/parse.c b/lib/parse.c index 5009c01..2451e3f 100644 --- a/lib/parse.c +++ b/lib/parse.c @@ -7,10 +7,81 @@ #include "zpm.h" -#include "sqlite3.h" - #define DMARK fprintf(stderr, "mark %s %s:%d\n", __FILE__, __func__, __LINE__) +int zpm_parse_version(const char *pstr, struct zpm_version_info *info) { + int cur = 0, ch; + + info->verstr = pstr; + info->name = 0; + info->namelen = 0; + info->version = 0; + info->verlen = 0; + info->release = -1; + info->rellen = 0; + info->relstr = 0; + + if (!pstr) { + return 0; + } + + /* skip leading whitespace */ + while (isspace(pstr[cur])) { + cur++; + } + + /* get the name, if any */ + if (isalpha(pstr[cur])) { + info->name = pstr + cur; + for (ch = pstr[cur]; ch; ch = pstr[++cur]) { + if (ch == '-') { + if (isdigit(pstr[cur+1])) { + break; + } + } + if (!isgraph(ch)) { + break; + } + info->namelen++; + } + } + + while (pstr[cur] == '-') { + cur++; + } + + /* should be pointing at a digit. if not, we're done */ + if (isdigit(pstr[cur])) { + info->version = pstr + cur; + for (ch = pstr[cur]; ch; ch = pstr[++cur]) { + if (ch == '-') { + break; + } + if (!isgraph(ch)) { + break; + } + info->verlen++; + } + } + + while (pstr[cur] == '-') { + cur++; + } + + if (isdigit(pstr[cur])) { + info->relstr = pstr + cur; + while (isdigit(pstr[cur++])) { + info->rellen++; + } + } + + if (info->relstr) { + info->release = atoi(info->relstr); + } + + return info->namelen || info->verlen || info->rellen; +} + int zpm_parse_package(char *pstr, char *name, char *ver, int *rel) { if (name) *name = 0; if (ver) *ver = 0; diff --git a/lib/vercmp.c b/lib/vercmp.c index cfc7edd..5d22aea 100644 --- a/lib/vercmp.c +++ b/lib/vercmp.c @@ -3,12 +3,23 @@ #include #include +#include "zpm.h" + +#define TEXT 1 +#define NUMERIC 2 + struct ver { - char str[1024]; /* string rep */ - char *s; /* start component */ +#if 0 + char name[1024]; + char str[1024]; /* string rep of the version */ +#endif + const char *str; + int release; /* a trailing -\d+ value */ + const char *s; /* start component */ int cn; /* current component number */ - char *next; /* start of potential next component */ - char keep; /* character over-written with null byte */ + int len; /* current component length */ + + const char *next; /* start of potential next component */ int sep; /* number of characters in separator */ int nv; /* numeric value */ @@ -16,13 +27,53 @@ struct ver { }; static void init_ver(struct ver *v, const char *s) { +#if 0 strncpy(v->str, s, 1023); v->str[1023] = 0; - v->s = 0; + v->name[1023] = 0; +#endif + v->str = s; + v->release = 0; + v->s = v->str; v->cn = 0; v->next = v->str; - v->keep = 0; v->sep = 0; + v->len = 0; + v->type = 0; +} + +static int cmp_strlen(const char *a, size_t alen, const char *b, size_t blen) { + size_t shortest; + int base; + + if (a && !b) { + return 1; + } else if (b && !a) { + return -1; + } else if (!a && !b) { + return 0; + } + + if (alen && !blen) { + return 1; + } else if (blen && !alen) { + return -1; + } else if (!alen && !blen) { + return 0; + } + + shortest = alen < blen ? alen : blen; + + base = strncmp(a, b, shortest); + + if (base == 0) { + if (alen == blen) { + return 0; + } + return alen < blen ? -1 : 1; + } + + return base < 0 ? -1 : 1; } static int ver_cmp(struct ver *a, struct ver *b) { @@ -32,7 +83,8 @@ static int ver_cmp(struct ver *a, struct ver *b) { if (a->type != b->type) { return a->type < b->type ? -1 : 1; } - if (a->type == 1) { + + if (a->type == TEXT) { int cmp; if (a->s && ! b->s) return 1; if (b->s && ! a->s) return -1; @@ -45,7 +97,7 @@ static int ver_cmp(struct ver *a, struct ver *b) { } return cmp < 0 ? -1 : 1; } - if (a->type == 2) { + if (a->type == NUMERIC) { if (a->nv != b->nv) return a->nv < b->nv ? -1 : 1; } @@ -53,53 +105,67 @@ static int ver_cmp(struct ver *a, struct ver *b) { } static int next_comp(struct ver *v) { - char *s; + const char *s, *next; - /* restore over-written character */ - if (v->keep) { - v->next[0] = v->keep; - v->keep = 0; - } - s = v->next; + next = v->next; + v->len = 0; + v->type = 0; /* skip over anything that isn't alphanumeric */ v->sep = 0; - while (*s && !isalnum(*s)) { - v->sep++; - s++; + while (*next && !isalnum(*next)) { + next++; } - v->next = s; /* zero return if at end of string */ - if (!*s) { + if (!*next) { + v->s = next; return 0; } + + s = next; + v->s = next; + if (isdigit(*s)) { - v->type = 2; - while (isdigit(*s)) s++; - v->keep = *s; - *s = 0; - v->s = v->next; - v->nv = atoi(v->s); + v->type = NUMERIC; + v->nv = *s-'0'; + while (isdigit(*s)) { + v->nv = v->nv * 10 + *s-'0'; + v->len++; + s++; + } + v->next = s; } else if (isalpha(*s)) { - v->type = 1; - while (isalpha(*s)) s++; - v->keep = *s; - *s = 0; - v->s = v->next; + v->type = TEXT; + while (*s && (isalpha(*s) || *s == '-') ) { + v->len++; + s++; + } + v->next = s; + /* last character in a alphabetic can't be a - */ + while (*s == '-') { + v->len--; + s--; + } + } else { + /* impossible */ + return 0; } - v->next = s; - return ++v->cn; + v->cn++; + return v->cn; } /* * alphabetic less than numeric + * return -1 if vsa < vsb, 0 if equal, 1 if vsa > vsb */ int zpm_vercmp(const char *vsa, const char *vsb) { struct ver a, b; + struct zpm_version_info ainfo, binfo; int an, bn; int cmp; + char astr[256], bstr[256]; if (vsa && !vsb) { return 1; @@ -111,27 +177,56 @@ int zpm_vercmp(const char *vsa, const char *vsb) { return 0; } - init_ver(&a, vsa); - init_ver(&b, vsb); + zpm_parse_version(vsa, &ainfo); + zpm_parse_version(vsb, &binfo); + cmp = cmp_strlen(ainfo.name, ainfo.namelen, binfo.name, binfo.namelen); + + if (cmp != 0) { + return cmp; + } + + if (ainfo.verlen > 255) { + ainfo.verlen = 255; + } + if (binfo.verlen > 255) { + binfo.verlen = 255; + } + + strncpy(astr, ainfo.verstr, ainfo.verlen); + strncpy(bstr, binfo.verstr, binfo.verlen); + astr[ainfo.verlen] = 0; + bstr[binfo.verlen] = 0; + + init_ver(&a, astr); + init_ver(&b, bstr); do { an = next_comp(&a); bn = next_comp(&b); if (an != bn) { if (an == 0 && a.type == 2 && b.sep == 0 && b.type == 1) { - return 1; + cmp = 1; break; } else if (bn == 0 && b.type == 2 && a.sep == 0 && a.type == 1) { - return -1; + cmp = -1; break; } - return an < bn ? -1 : 1; + cmp = an < bn ? -1 : 1; break; } if (an == 0 && bn == 0) { - return 0; + break; } cmp = ver_cmp(&a, &b); if (cmp != 0) { - return cmp; + break; } } while (an && bn); - return 0; + if (cmp != 0) { + return cmp; + } + + /* compare release */ + if (ainfo.release != binfo.release) { + cmp = ainfo.release < binfo.release ? -1 : 1; + } + + return cmp; } diff --git a/src/vercmp.c b/src/vercmp.c index b0e5117..340d80f 100644 --- a/src/vercmp.c +++ b/src/vercmp.c @@ -19,30 +19,23 @@ int main(int ac, char *av[]) { int cmp; char *a = 0, *b = 0; int print = 1; - int argn, opt, pass, failed = 0; - - /* 0x1 = less than, 0x2 = equal, 0x4 = greater */ - unsigned testmask = 0; - unsigned testcases = 0; + int argn, opt = 0; + int inorder = 1, equal = 1; char *greatest = 0; char *least = 0; - if (ac < 2) return 1; - - while ((opt = getopt(ac, av, "qgleLG")) != -1) { + while ((opt = getopt(ac, av, "qLG")) != -1) { switch (opt) { case 'q': print = 0; break; - case 'l': testmask |= 0x1; break; - case 'e': testmask |= 0x2; break; - case 'g': testmask |= 0x4; break; - case 'L': print = 2; break; - case 'G': print = 3; break; + case 'G': print = 2; break; + case 'L': print = 3; break; default: exit(EXIT_FAILURE); break; } } + argn = optind; if (ac > argn) { @@ -54,43 +47,44 @@ int main(int ac, char *av[]) { while (ac > argn) { b = av[argn++]; - cmp = zpm_vercmp(a, b) + 1; - testcases |= (1< 0) { greatest = b; } - cmp = zpm_vercmp(least, b); + cmp = zpm_vercmp(b, least); if (cmp < 0) { least = b; } + a = b; } - switch (testcases) { - case 0: /* no tests */ - cmp = 0; break; - case 1: /* only less than */ - cmp = 2; break; - case 2: /* only equal */ - cmp = 0; break; - case 4: /* only greater than */ - cmp = 1; break; - default: /* mixed */ - cmp = 3; break; + if (equal) { + cmp = 0; + } else if (inorder) { + cmp = -1; + } else { + cmp = 1; } switch (print) { case 1: - printf("%d\n", cmp == 2 ? -1 : cmp); + printf("%d\n", cmp); break; case 2: if (greatest) printf("%s\n", greatest); @@ -101,11 +95,6 @@ int main(int ac, char *av[]) { default: break; } - fflush(stdout); - if (testmask == 0) { - return cmp; - } else { - return failed ? 1 : 0; - } + return cmp == -1 ? 2 : cmp; } diff --git a/t/vercmp.t b/t/vercmp.t index a877c08..205f950 100755 --- a/t/vercmp.t +++ b/t/vercmp.t @@ -4,13 +4,29 @@ . tap.sh +# 17 vtests, 3 least, 3 greatest, 2 other, 3 temp handling +# 17 * 6 + 3 + 3 +plan 111 + +require rm -rf tmp +require mkdir tmp + vtest() { res=$(zpm-vercmp "$1" "$2") okstreq "$res" -1 "$1 < $2" + res=$(zpm-vercmp "$2" "$1") okstreq "$res" 1 "$2 > $1" + res=$(zpm-vercmp "$1" "$1") okstreq "$res" 0 "$1 == $1" + + res=$(zpm shell vercmp.db "select '$1' < '$2' collate vercmp") + okstreq "$res" 1 "zpm shell $1 < $2" + res=$(zpm shell vercmp.db "select '$1' > '$2' collate vercmp") + okstreq "$res" 0 "zpm shell not $1 > $2" + res=$(zpm shell vercmp.db "select '$1' = '$1' collate vercmp") + okstreq "$res" 1 "zpm shell $1 = $1" } least() { @@ -27,8 +43,6 @@ greatest() { okstreq "$g" "$want" "greatest $*" } -plan 53 - least 1.0a 1.0a 1.0b least 1.0a 1.0b 1.0a greatest z a b z @@ -36,11 +50,11 @@ greatest z a z b least abc abc greatest abc abc -zpm vercmp -gq 'gnupg-1.0-1' "gnupg-2.0-1" -exitwith 1 vercmp -g not gt +#zpm vercmp -gq 'gnupg-1.0-1' "gnupg-2.0-1" +#exitwith 1 vercmp -g not gt -zpm vercmp -gq 'gnupg-2.0-1' "gnupg-1.0-1" -exitwith 0 vercmp -g is gt +#zpm vercmp -gq 'gnupg-2.0-1' "gnupg-1.0-1" +#exitwith 0 vercmp -g is gt # alpha vtest 1.0a 1.0b @@ -48,12 +62,14 @@ vtest 1.0b 1.0beta vtest 1.0beta 1.0p vtest 1.0p 1.0pre vtest 1.0pre 1.0rc -vtest 1.0rc 1.0 + +vtest 1.0 1.0rc vtest 1.0 1.0.a vtest 1.0.a 1.0.1 vtest 1 1.0 vtest 1.0 1.1 + vtest 1.1 1.1.1 vtest 1.2 2.0 vtest 2.0 3.0.0 @@ -62,4 +78,9 @@ vtest 2.0 3.0.0 vtest 1.0-1 1.0-2 vtest 1.0-2 2.0-1 +# full +vtest ffmpeg-4.1-3 ffmpeg-4.1.1-1 +vtest 4.1-3 4.1.1-1 + +require rm -rf tmp finish diff --git a/zpm.h b/zpm.h index 5eddd56..98971c1 100644 --- a/zpm.h +++ b/zpm.h @@ -198,6 +198,15 @@ void *compresslzma(void *buf, size_t bufsize, size_t *len); int zpm_hash(char *path, char *hash, uint32_t flags); int zpm_readopts(struct zpm *pkg, int ac, char **av); +struct zpm_version_info { + const char *verstr; + const char *name; int namelen; + const char *version; int verlen; + const char *relstr; int rellen; + int release; +}; + +int zpm_parse_version(const char *pstr, struct zpm_version_info *info); int zpm_vercmp(const char *a, const char *b); /* add vercmp collation to db */ -- 2.40.0