Created
November 29, 2017 14:03
-
-
Save thewisenerd/50ee906ef060863cb0148205ba2e04f1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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