258 lines
5.5 KiB
C
258 lines
5.5 KiB
C
/* version.c - Alpine Package Keeper (APK)
|
|
*
|
|
* Copyright (C) 2005-2008 Natanael Copa <n@tanael.org>
|
|
* Copyright (C) 2008-2011 Timo Teräs <timo.teras@iki.fi>
|
|
* All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-only
|
|
*/
|
|
#include <stdio.h>
|
|
|
|
#include <ctype.h>
|
|
#include "apk_defines.h"
|
|
#include "apk_version.h"
|
|
|
|
/* Gentoo version: {digit}{.digit}...{letter}{_suf{#}}...{-r#} */
|
|
|
|
enum PARTS {
|
|
TOKEN_INVALID = -1,
|
|
TOKEN_DIGIT_OR_ZERO,
|
|
TOKEN_DIGIT,
|
|
TOKEN_LETTER,
|
|
TOKEN_SUFFIX,
|
|
TOKEN_SUFFIX_NO,
|
|
TOKEN_REVISION_NO,
|
|
TOKEN_END,
|
|
};
|
|
|
|
static void next_token(int *type, apk_blob_t *blob)
|
|
{
|
|
int n = TOKEN_INVALID;
|
|
|
|
if (blob->len == 0 || blob->ptr[0] == 0) {
|
|
n = TOKEN_END;
|
|
} else if ((*type == TOKEN_DIGIT || *type == TOKEN_DIGIT_OR_ZERO) &&
|
|
islower(blob->ptr[0])) {
|
|
n = TOKEN_LETTER;
|
|
} else if (*type == TOKEN_LETTER && isdigit(blob->ptr[0])) {
|
|
n = TOKEN_DIGIT;
|
|
} else if (*type == TOKEN_SUFFIX && isdigit(blob->ptr[0])) {
|
|
n = TOKEN_SUFFIX_NO;
|
|
} else {
|
|
switch (blob->ptr[0]) {
|
|
case '.':
|
|
n = TOKEN_DIGIT_OR_ZERO;
|
|
break;
|
|
case '_':
|
|
n = TOKEN_SUFFIX;
|
|
break;
|
|
case '-':
|
|
if (blob->len > 1 && blob->ptr[1] == 'r') {
|
|
n = TOKEN_REVISION_NO;
|
|
blob->ptr++;
|
|
blob->len--;
|
|
} else
|
|
n = TOKEN_INVALID;
|
|
break;
|
|
}
|
|
blob->ptr++;
|
|
blob->len--;
|
|
}
|
|
|
|
if (n < *type) {
|
|
if (! ((n == TOKEN_DIGIT_OR_ZERO && *type == TOKEN_DIGIT) ||
|
|
(n == TOKEN_SUFFIX && *type == TOKEN_SUFFIX_NO) ||
|
|
(n == TOKEN_DIGIT && *type == TOKEN_LETTER)))
|
|
n = TOKEN_INVALID;
|
|
}
|
|
*type = n;
|
|
}
|
|
|
|
static int get_token(int *type, apk_blob_t *blob)
|
|
{
|
|
static const char *pre_suffixes[] = { "alpha", "beta", "pre", "rc" };
|
|
static const char *post_suffixes[] = { "cvs", "svn", "git", "hg", "p" };
|
|
int v = 0, i = 0, nt = TOKEN_INVALID;
|
|
|
|
if (blob->len <= 0) {
|
|
*type = TOKEN_END;
|
|
return 0;
|
|
}
|
|
|
|
switch (*type) {
|
|
case TOKEN_DIGIT_OR_ZERO:
|
|
/* Leading zero digits get a special treatment */
|
|
if (blob->ptr[i] == '0') {
|
|
while (i < blob->len && blob->ptr[i] == '0')
|
|
i++;
|
|
nt = TOKEN_DIGIT;
|
|
v = -i;
|
|
break;
|
|
}
|
|
case TOKEN_DIGIT:
|
|
case TOKEN_SUFFIX_NO:
|
|
case TOKEN_REVISION_NO:
|
|
while (i < blob->len && isdigit(blob->ptr[i])) {
|
|
v *= 10;
|
|
v += blob->ptr[i++] - '0';
|
|
}
|
|
break;
|
|
case TOKEN_LETTER:
|
|
v = blob->ptr[i++];
|
|
break;
|
|
case TOKEN_SUFFIX:
|
|
for (v = 0; v < ARRAY_SIZE(pre_suffixes); v++) {
|
|
i = strlen(pre_suffixes[v]);
|
|
if (i <= blob->len &&
|
|
strncmp(pre_suffixes[v], blob->ptr, i) == 0)
|
|
break;
|
|
}
|
|
if (v < ARRAY_SIZE(pre_suffixes)) {
|
|
v = v - ARRAY_SIZE(pre_suffixes);
|
|
break;
|
|
}
|
|
for (v = 0; v < ARRAY_SIZE(post_suffixes); v++) {
|
|
i = strlen(post_suffixes[v]);
|
|
if (i <= blob->len &&
|
|
strncmp(post_suffixes[v], blob->ptr, i) == 0)
|
|
break;
|
|
}
|
|
if (v < ARRAY_SIZE(post_suffixes))
|
|
break;
|
|
/* fallthrough: invalid suffix */
|
|
default:
|
|
*type = TOKEN_INVALID;
|
|
return -1;
|
|
}
|
|
blob->ptr += i;
|
|
blob->len -= i;
|
|
if (blob->len == 0)
|
|
*type = TOKEN_END;
|
|
else if (nt != TOKEN_INVALID)
|
|
*type = nt;
|
|
else
|
|
next_token(type, blob);
|
|
|
|
return v;
|
|
}
|
|
|
|
const char *apk_version_op_string(int mask)
|
|
{
|
|
switch (mask) {
|
|
case APK_VERSION_LESS:
|
|
return "<";
|
|
case APK_VERSION_LESS|APK_VERSION_EQUAL:
|
|
return "<=";
|
|
case APK_VERSION_EQUAL|APK_VERSION_FUZZY:
|
|
case APK_VERSION_FUZZY:
|
|
return "~";
|
|
case APK_VERSION_EQUAL:
|
|
return "=";
|
|
case APK_VERSION_GREATER|APK_VERSION_EQUAL:
|
|
return ">=";
|
|
case APK_VERSION_GREATER:
|
|
return ">";
|
|
case APK_DEPMASK_CHECKSUM:
|
|
return "><";
|
|
case APK_DEPMASK_ANY:
|
|
return "";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
int apk_version_result_mask_blob(apk_blob_t op)
|
|
{
|
|
int i, r = 0;
|
|
for (i = 0; i < op.len; i++) {
|
|
switch (op.ptr[i]) {
|
|
case '<':
|
|
r |= APK_VERSION_LESS;
|
|
break;
|
|
case '>':
|
|
r |= APK_VERSION_GREATER;
|
|
break;
|
|
case '=':
|
|
r |= APK_VERSION_EQUAL;
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int apk_version_result_mask(const char *op)
|
|
{
|
|
return apk_version_result_mask_blob(APK_BLOB_STR(op));
|
|
}
|
|
|
|
int apk_version_validate(apk_blob_t ver)
|
|
{
|
|
int t = TOKEN_DIGIT;
|
|
|
|
while (t != TOKEN_END && t != TOKEN_INVALID)
|
|
get_token(&t, &ver);
|
|
|
|
return t == TOKEN_END;
|
|
}
|
|
|
|
int apk_version_compare_blob_fuzzy(apk_blob_t a, apk_blob_t b, int fuzzy)
|
|
{
|
|
int at = TOKEN_DIGIT, bt = TOKEN_DIGIT, tt;
|
|
int av = 0, bv = 0;
|
|
|
|
if (APK_BLOB_IS_NULL(a) || APK_BLOB_IS_NULL(b)) {
|
|
if (APK_BLOB_IS_NULL(a) && APK_BLOB_IS_NULL(b))
|
|
return APK_VERSION_EQUAL;
|
|
return APK_VERSION_EQUAL | APK_VERSION_GREATER | APK_VERSION_LESS;
|
|
}
|
|
|
|
while (at == bt && at != TOKEN_END && at != TOKEN_INVALID && av == bv) {
|
|
av = get_token(&at, &a);
|
|
bv = get_token(&bt, &b);
|
|
#if 0
|
|
fprintf(stderr,
|
|
"av=%d, at=%d, a.len=%d\n"
|
|
"bv=%d, bt=%d, b.len=%d\n",
|
|
av, at, a.len, bv, bt, b.len);
|
|
#endif
|
|
}
|
|
|
|
/* value of this token differs? */
|
|
if (av < bv)
|
|
return APK_VERSION_LESS;
|
|
if (av > bv)
|
|
return APK_VERSION_GREATER;
|
|
|
|
/* both have TOKEN_END or TOKEN_INVALID next? */
|
|
if (at == bt || fuzzy)
|
|
return APK_VERSION_EQUAL;
|
|
|
|
/* leading version components and their values are equal,
|
|
* now the non-terminating version is greater unless it's a suffix
|
|
* indicating pre-release */
|
|
tt = at;
|
|
if (at == TOKEN_SUFFIX && get_token(&tt, &a) < 0)
|
|
return APK_VERSION_LESS;
|
|
tt = bt;
|
|
if (bt == TOKEN_SUFFIX && get_token(&tt, &b) < 0)
|
|
return APK_VERSION_GREATER;
|
|
if (at > bt)
|
|
return APK_VERSION_LESS;
|
|
if (bt > at)
|
|
return APK_VERSION_GREATER;
|
|
|
|
return APK_VERSION_EQUAL;
|
|
}
|
|
|
|
int apk_version_compare_blob(apk_blob_t a, apk_blob_t b)
|
|
{
|
|
return apk_version_compare_blob_fuzzy(a, b, FALSE);
|
|
}
|
|
|
|
int apk_version_compare(const char *str1, const char *str2)
|
|
{
|
|
return apk_version_compare_blob(APK_BLOB_STR(str1), APK_BLOB_STR(str2));
|
|
}
|