Skip to content

Instantly share code, notes, and snippets.

@thewisenerd
Created November 29, 2017 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thewisenerd/50ee906ef060863cb0148205ba2e04f1 to your computer and use it in GitHub Desktop.
Save thewisenerd/50ee906ef060863cb0148205ba2e04f1 to your computer and use it in GitHub Desktop.
/*
* kang from git://anonscm.debian.org/dpkg/dpkg.git
* branch: master
* head: b9798daaa596ad5d539bcdd5ca89de1cb0b81697
*/
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define fatal(...) ({ \
fprintf(stderr, "error: " __VA_ARGS__); \
-EXIT_FAILURE; \
})
#define warn(...) ({ \
fprintf(stderr, "warn: " __VA_ARGS__); \
EXIT_SUCCESS; \
})
#define c_isblank(x) (isblank(x))
#define c_isdigit(x) (isdigit(x))
#define c_isalpha(x) (isalpha(x))
/**
* Check if a string has content.
*/
static inline bool
str_is_set(const char *str)
{
return str != NULL && str[0] != '\0';
}
/**
* Data structure representing a Debian version.
*
* @see deb-version(5)
*/
struct dpkg_version {
/** The epoch. It will be zero if no epoch is present. */
unsigned int epoch;
/** The upstream part of the version. */
/* const */ char *version;
/** The Debian revision part of the version. */
/* const */ char *revision;
};
/**
* Turn the passed version into an empty version.
*
* This can be used to ensure the version is properly initialized.
*
* @param version The version to clear.
*/
void
dpkg_version_blank(struct dpkg_version *version)
{
version->epoch = 0;
version->version = NULL;
version->revision = NULL;
}
/**
* Test if a version is not empty.
*
* @param version The version to test.
*
* @retval true If the version is informative (i.e. not an empty version).
* @retval false If the version is empty.
*/
bool
dpkg_version_is_informative(const struct dpkg_version *version)
{
return (version->epoch ||
str_is_set(version->version) ||
str_is_set(version->revision));
}
/**
* Parse a version string and check for invalid syntax.
*
* Distinguish between lax (warnings) and strict (error) parsing.
*
* @param rversion The parsed version.
* @param string The version string to parse.
* @param err The warning or error message if any.
*
* @retval 0 On success.
* @retval -1 On failure, and err is set accordingly.
*/
int
parseversion(struct dpkg_version *rversion, const char *string)
{
char *hyphen, *colon, *eepochcolon;
const char *end, *ptr;
/* Trim leading and trailing space. */
while (*string && c_isblank(*string))
string++;
if (!*string)
return fatal("version string is empty\n");
/* String now points to the first non-whitespace char. */
end = string;
/* Find either the end of the string, or a whitespace char. */
while (*end && !c_isblank(*end))
end++;
/* Check for extra chars after trailing space. */
ptr = end;
while (*ptr && c_isblank(*ptr))
ptr++;
if (*ptr)
return fatal("version string has embedded spaces\n");
colon= strchr(string,':');
if (colon) {
long epoch;
errno = 0;
epoch = strtol(string, &eepochcolon, 10);
if (string == eepochcolon)
return fatal("epoch in version is empty\n");
if (colon != eepochcolon)
return fatal("epoch in version is not number\n");
if (epoch < 0)
return fatal("epoch in version is negative\n");
if (epoch > INT_MAX || errno == ERANGE)
return fatal("epoch in version is too big\n");
if (!*++colon)
return fatal("nothing after colon in version number\n");
string= colon;
rversion->epoch= epoch;
} else {
rversion->epoch= 0;
}
// rversion->version = nfstrnsave(string,end-string);
rversion->version = malloc( sizeof(char) * (end - string + 1) );
strncpy(rversion->version, string, end-string);
rversion->version[end - string] = 0;
hyphen= strrchr(rversion->version,'-');
if (hyphen) {
*hyphen++ = '\0';
if (*hyphen == '\0')
return fatal("revision number is empty\n");
}
rversion->revision= hyphen ? hyphen : "";
/* XXX: Would be faster to use something like cisversion and cisrevision. */
ptr = rversion->version;
if (!*ptr)
return fatal("version number is empty\n");
if (*ptr && !c_isdigit(*ptr++))
return warn("version number does not start with digit\n");
for (; *ptr; ptr++) {
if (!c_isdigit(*ptr) && !c_isalpha(*ptr) && strchr(".-+~:", *ptr) == NULL)
return warn("invalid character in version number\n");
}
for (ptr = rversion->revision; *ptr; ptr++) {
if (!c_isdigit(*ptr) && !c_isalpha(*ptr) && strchr(".+~", *ptr) == NULL)
return warn("invalid character in revision number\n");
}
return 0;
}
/**
* Give a weight to the character to order in the version comparison.
*
* @param c An ASCII character.
*/
static int
order(int c)
{
if (c_isdigit(c))
return 0;
else if (c_isalpha(c))
return c;
else if (c == '~')
return -1;
else if (c)
return c + 256;
else
return 0;
}
static int
verrevcmp(const char *a, const char *b)
{
if (a == NULL)
a = "";
if (b == NULL)
b = "";
while (*a || *b) {
int first_diff = 0;
while ((*a && !c_isdigit(*a)) || (*b && !c_isdigit(*b))) {
int ac = order(*a);
int bc = order(*b);
if (ac != bc)
return ac - bc;
a++;
b++;
}
while (*a == '0')
a++;
while (*b == '0')
b++;
while (c_isdigit(*a) && c_isdigit(*b)) {
if (!first_diff)
first_diff = *a - *b;
a++;
b++;
}
if (c_isdigit(*a))
return 1;
if (c_isdigit(*b))
return -1;
if (first_diff)
return first_diff;
}
return 0;
}
/**
* Compares two Debian versions.
*
* This function follows the convention of the comparator functions used by
* qsort().
*
* @see deb-version(5)
*
* @param a The first version.
* @param b The second version.
*
* @retval 0 If a and b are equal.
* @retval <0 If a is smaller than b.
* @retval >0 If a is greater than b.
*/
int
dpkg_version_compare(const struct dpkg_version *a,
const struct dpkg_version *b)
{
int rc;
if (a->epoch > b->epoch)
return 1;
if (a->epoch < b->epoch)
return -1;
rc = verrevcmp(a->version, b->version);
if (rc)
return rc;
return verrevcmp(a->revision, b->revision);
}
int main (int argc, char **argv)
{
struct relationinfo {
const char *string;
/* These values are exit status codes, so 0 = true, 1 = false. */
int if_lesser, if_equal, if_greater;
int if_none_a, if_none_both, if_none_b;
bool obsolete;
};
static const struct relationinfo relationinfos[]= {
/* < = > !a!2!b */
{ "le", 0,0,1, 0,0,1 },
{ "lt", 0,1,1, 0,1,1 },
{ "eq", 1,0,1, 1,0,1 },
{ "ne", 0,1,0, 0,1,0 },
{ "ge", 1,0,0, 1,0,0 },
{ "gt", 1,1,0, 1,1,0 },
/* These treat an empty version as later than any version. */
{ "le-nl", 0,0,1, 1,0,0 },
{ "lt-nl", 0,1,1, 1,1,0 },
{ "ge-nl", 1,0,0, 0,0,1 },
{ "gt-nl", 1,1,0, 0,1,1 },
/* For compatibility with dpkg control file syntax. */
{ "<", 0,0,1, 0,0,1, .obsolete = true },
{ "<=", 0,0,1, 0,0,1 },
{ "<<", 0,1,1, 0,1,1 },
{ "=", 1,0,1, 1,0,1 },
{ ">", 1,0,0, 1,0,0, .obsolete = true },
{ ">=", 1,0,0, 1,0,0 },
{ ">>", 1,1,0, 1,1,0 },
{ NULL }
};
const struct relationinfo *rip;
struct dpkg_version a, b;
int rc;
if (!argv[1] || !argv[2] || !argv[3] || argv[4])
return fatal("dpkg_version_compare takes three arguments:"
" <version> <relation> <version>\n");
for (rip=relationinfos; rip->string && strcmp(rip->string,argv[2]); rip++);
if (!rip->string) return fatal("dpkg_version_compare bad relation\n");
if (rip->obsolete)
warn("dpkg_version_compare used with obsolete relation operator '%s'\n", rip->string);
dpkg_version_blank(&a);
if (argv[1] && strcmp(argv[1],"<unknown>")) {
if (parseversion(&a, argv[1]) < 0) {
if (a.version)
free(a.version);
return fatal("version '%s' has bad syntax\n", argv[1]);
}
}
dpkg_version_blank(&b);
if (argv[3] && strcmp(argv[3],"<unknown>")) {
if (parseversion(&b, argv[3]) < 0) {
if (b.version)
free(b.version);
return fatal("version '%s' has bad syntax\n", argv[3]);
}
}
rc = dpkg_version_compare(&a, &b);
fprintf(stderr, "cmpversions a='%s' b='%s' r=%d\n",
argv[1],
argv[3],
rc);
if (a.version)
free(a.version);
if (b.version)
free(b.version);
if (rc > 0)
return rip->if_greater;
else if (rc < 0)
return rip->if_lesser;
else
return rip->if_equal;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment