Version compare utility

I recently posted about comparing dotted version numbers:
http://bkhome.org/blog/?viewDetailed=02199

There are some issues with different versions of the 'sort' utility, so I have found a version comparison utility written in C:
http://svn.openmoko.org/trunk/src/host/opkg-utils/opkg-compare-versions.c

I modified it a little bit:
/*

* libdpkg - Debian packaging suite library routines
* vercmp.c - comparison of version numbers
*
* Copyright (C) 1995 Ian Jackson <iwj10@cus.cam.ac.uk>
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2,
* or (at your option) any later version.
*
* This is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with dpkg; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*BK modified april 2011*/
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

# define _(Text) Text

struct versionrevision {
unsigned long epoch;
char *version;
const char *revision;
};

static int verrevcmp(const char *val, const char *ref)
{
int vc, rc;
long vl, rl;
const char *vp, *rp;
const char *vsep, *rsep;

if (!val) val= "";
if (!ref) ref= "";
for (;;) {
vp= val; while (*vp && !isdigit(*vp)) vp++;
rp= ref; while (*rp && !isdigit(*rp)) rp++;
for (;;) {
vc= val == vp ? 0 : *val++;
rc= ref == rp ? 0 : *ref++;
if (!rc && !vc) break;
if (vc && !isalpha(vc)) vc += 256; /* assumes ASCII character set */
if (rc && !isalpha(rc)) rc += 256;
if (vc != rc) return vc - rc;
}
val= vp;
ref= rp;
vl=0; if (isdigit(*vp)) vl= strtol(val,(char**)&val,10);
rl=0; if (isdigit(*rp)) rl= strtol(ref,(char**)&ref,10);
if (vl != rl) return vl - rl;

vc = *val;
rc = *ref;
vsep = strchr(".-", vc);
rsep = strchr(".-", rc);
if (vsep && !rsep) return -1;
if (!vsep && rsep) return +1;

if (!*val && !*ref) return 0;
if (!*val) return -1;
if (!*ref) return +1;
}
}

int versioncompare(const struct versionrevision *version,
const struct versionrevision *refversion)
{
int r;

if (version->epoch > refversion->epoch) return 1;
if (version->epoch < refversion->epoch) return -1;
r= verrevcmp(version->version,refversion->version); if (r) return r;
r= verrevcmp(version->revision,refversion->revision); if (r) return r;
return r;
}

int versionsatisfied3(const struct versionrevision *it,
const struct versionrevision *ref,
const char *op)
{
int r;
r= versioncompare(it,ref);
if (strcmp(op, "le") == 0 || strcmp(op, "<") == 0)
return r <= 0;
if (strcmp(op, "ge") == 0 || strcmp(op, ">") == 0)
return r >= 0;
if (strcmp(op, "lt") == 0)
return r < 0;
if (strcmp(op, "gt") == 0)
return r > 0;
if (strcmp(op, "eq") == 0)
return r == 0;
fprintf(stderr, "unknown operator: %s", op);

exit(1);
}

const char *parseversion(struct versionrevision *rversion, const char *string)
{
char *hyphen, *colon, *eepochcolon;
unsigned long epoch;

if (!*string) return _("version string is empty");

colon= strchr(string,':');
if (colon) {
epoch= strtoul(string,&eepochcolon,10);
if (colon != eepochcolon) return _("epoch in version is not number");
if (!*++colon) return _("nothing after colon in version number");
string= colon;
rversion->epoch= epoch;
} else {
rversion->epoch= 0;
}

rversion->revision = "";

rversion->version= malloc(strlen(string)+1);
strcpy(rversion->version, string);

#if 0
fprintf(stderr,"Parsed version: %lu, %s\n",
rversion->epoch,
rversion->version);
#endif

return 0;
}

int main(int argc, char *argv[])
{
const char *err;
struct versionrevision ver, ref;

if (argc < 4) {
fprintf(stderr, "usage: %s version1 lt|gt|le|ge|eq version2\n return value 0 if true, else 1\n", argv[0]);
return 2;
}

err = parseversion(&ver, argv[1]);
if (err) {
fprintf(stderr, "Invalid version `%s': %s\n", argv[1], err);
return 2;
}

err = parseversion(&ref, argv[3]);
if (err) {
fprintf(stderr, "Invalid version `%s': %s\n", argv[3], err);
return 2;
}

return ! versionsatisfied3(&ver, &ref, argv[2]);
}


Compiled:

# diet gcc -nostdinc -O2 -static -o vercmp vercmp.c

Example:

#./vercmp 5.1.1 lt 5.1.1.99 ; echo $?
0


This utility is now in Woof, in the initrd and in the main filesystem, in /bin.


Posted on 21 Apr 2011, 20:48


Comments:

Posted on 21 Apr 2011, 20:57 by BarryK
Dotted version numbers
I should make a comment about Unix/Linux dotted version numbers. The dot is not a decimal point, it is a delimiter between major-version-number, minor-version-number, sub-minor number, etc.

major.minor[.sub-minor ...]

So, 5.2.5 is not the same as 5.25. 5.2.5 means a minor version number of 2, 5.25 means a minor version number of 25.

Note, I am planning to open up the version numbering in Puppy, eliminate the 3-digit limitation. This means, for example, that after Wary 5.1.1 I could release a beta of 5.1.2 and give it (say) version number 5.1.1.90, then 5.1.1.91, etc.



Posted on 22 Apr 2011, 5:54 by L18L
vercmp internationalized
I have internationalized your modified version:

Compiled in diet: no internationalization.

Compiled without diet:
# gcc -nostdinc -O2 -static -o vercmp vercmp.c
Size is 623289 Bytes

# gcc -O2 -o vercmp vercmp.c
Size is 7568 Bytes

generated vercmp.pot by:
# xgettext --language=C --keyword=_ vercmp.c -o - > vercmp.pot

# compare dotted version numbers

# Copyright (C) 2011 THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the vercmp package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: vercmp VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-04-21 16:58+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: vercmp.c:207
#, c-format
msgid "unknown operator: %s"
msgstr ""

#: vercmp.c:227
msgid "version string is empty"
msgstr ""

#: vercmp.c:237
msgid "epoch in version is not number"
msgstr ""

#: vercmp.c:239
msgid "nothing after colon in version number"
msgstr ""

#: vercmp.c:297
#, c-format
msgid ""
"usage: %s version1 lt|gt|le|ge|eq version2\n"
" return value 0 if true, else 1\n"
msgstr ""

#: vercmp.c:321
#, c-format
msgid "Invalid version `%s': %s\n"
msgstr ""


for anybody who likes to translate




Posted on 22 Apr 2011, 7:33 by BarryK
Re vercmp
It has to be compiled either by dietlibc or uClibc, as it must be static, to run in the initramfs.



Posted on 22 Apr 2011, 7:41 by BarryK
vercmp
Also, this is a very low-level systems utility. Internationalization of the message strings will not be supported in the initramfs, nor is it desirable in the main filesystem unless we really want to bulk-up Puppy.



Posted on 22 Apr 2011, 9:02 by BarryK
vercmp
Well, unless there are developers who want to work in non-English, but generally we expect developers to have knowledge of English.



Posted on 22 Apr 2011, 16:00 by L18L
Re re vercmp...
Barry,
thanks for your comments

"it must be static, to run in the initramfs"
"nor is it desirable in the main filesystem unless we really want to bulk-up Puppy"
"we expect developers to have knowledge of English"

Totally agreed !

So let us see it as a small example (just a little bit complexer than hello world) of demonstrating how easy it can be to make a C program international?

Regards