site  contact  subhomenews

Version compare utility

April 21, 2011 — BarryK
I recently posted about comparing dotted version numbers:
http://bkhome.org/archive/blog2/201103/comparing-dotted-version-numbers.html

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, %sn",
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 version2n return value 0 if true, else 1n", argv[0]);
return 2;
}

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

err = parseversion(&ref, argv[3]);
if (err) {
fprintf(stderr, "Invalid version `%s': %sn", 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.

Comments

Dotted version numbers
Username: BarryK
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.

vercmp internationalized
Username: L18L
"I have internationalized your modified version: Compiled in diet: no internationalization. Compiled [b]without[/b] 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 [code]# 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 VERSIONn" "Report-Msgid-Bugs-To: n" "POT-Creation-Date: 2011-04-21 16:58+0200n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONEn" "Last-Translator: FULL NAME <EMAIL@ADDRESS>n" "Language-Team: LANGUAGE <LL@li.org>n" "MIME-Version: 1.0n" "Content-Type: text/plain; charset=UTF-8n" "Content-Transfer-Encoding: 8bitn" #: 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 version2n" " return value 0 if true, else 1n" msgstr "" #: vercmp.c:321 #, c-format msgid "Invalid version `%s': %sn" msgstr "" [/code] for anybody who likes to translate

Re vercmp
Username: BarryK
"It has to be compiled either by dietlibc or uClibc, as it must be static, to run in the initramfs.

vercmp
Username: BarryK
"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.

vercmp
Username: BarryK
"Well, unless there are developers who want to work in non-English, but generally we expect developers to have knowledge of English.

Re re vercmp...
Username: L18L
"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 [b]C[/b] program international? Regards


Tags: woof