diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/core/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/lib/core/include/version_compare.h | 20 | ||||
| -rw-r--r-- | src/lib/core/version_compare.c | 185 |
3 files changed, 206 insertions, 0 deletions
diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt index 0fb273c..462d7d8 100644 --- a/src/lib/core/CMakeLists.txt +++ b/src/lib/core/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(stasis_core STATIC envctl.c multiprocessing.c semaphore.c + version_compare.c ) target_include_directories(stasis_core PRIVATE ${core_INCLUDE} diff --git a/src/lib/core/include/version_compare.h b/src/lib/core/include/version_compare.h new file mode 100644 index 0000000..857de47 --- /dev/null +++ b/src/lib/core/include/version_compare.h @@ -0,0 +1,20 @@ +#ifndef STASIS_VERSION_COMPARE_H +#define STASIS_VERSION_COMPARE_H + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "str.h" + +#define GT 1 << 1 +#define LT 1 << 2 +#define EQ 1 << 3 +#define NOT 1 << 4 +#define EPOCH_MOD 10000 + +int version_sum(const char *str); +int version_parse_operator(const char *str); +int version_compare(int flags, const char *aa, const char *bb); + +#endif //STASIS_VERSION_COMPARE_H
\ No newline at end of file diff --git a/src/lib/core/version_compare.c b/src/lib/core/version_compare.c new file mode 100644 index 0000000..4939c8f --- /dev/null +++ b/src/lib/core/version_compare.c @@ -0,0 +1,185 @@ +#include "version_compare.h" + +const struct { + const char *key; + int value; +} WEIGHT[] = { + {.key = "post", 1000}, + {.key = "rc", -1000}, + {.key = "dev", -2000}, +}; + +/** + * Sum each part of a '.'-delimited version string + * @param str version string + * @return sum of each part + * @return -1 on error + */ +int version_sum(const char *str) { + char *end; + + if (!str || isempty((char *) str)) { + return -1; + } + + int result = 0; + int epoch = 0; + char *s = strdup(str); + if (!s) { + return -1; + } + char *ptr = s; + end = ptr; + + // Parsing stops at the first non-alpha, non-'.' character + // Digits are processed until the first invalid character + // I'm torn whether this should be considered an error + int i = 0; + while (end != NULL) { + int tmp_result = 0; + + tmp_result = (int) strtoul(ptr, &end, 10); + + // Circumvent a bug which allows a smaller version to be greater + // than a larger version + // Bug: + // 1.0.3 == 1 + 0 + 3 = 4 + // 2.0.0 == 2 + 0 + 0 = 2 + // Correction: + // ((1 * EPOCH_MOD) + 1).0.3 = 104 + // ((2 * EPOCH_MOD) + 2).0.0 = 202 + if (!i && tmp_result && *end != ':') { + result += tmp_result * EPOCH_MOD; + i++; + } + + ptr = end; + if (*ptr == '.' || *ptr == '-') { + ptr++; + } + else if (!epoch && *ptr == ':') { + epoch = 1; + result += EPOCH_MOD; + ptr++; + } + else if (isalpha(*ptr)) { + int adjusted = 0; + for (size_t w = 0; w < sizeof(WEIGHT) / sizeof(WEIGHT[0]); w++) { + const int has_suffix = strncasecmp(ptr, WEIGHT[w].key, strlen(WEIGHT[w].key)) == 0; + if (has_suffix) { + // skip the suffix + ptr += strlen(WEIGHT[w].key); + // adjust result based on suffix weight + result += WEIGHT[w].value; + adjusted = 1; + break; + } + } + + if (!adjusted) { + result += *ptr - ('a' - 1); + ptr++; + } + } + else { + end = NULL; + } + + if (tmp_result) { + result += tmp_result; + } + } + + free(s); + return result; +} + +/** + * Convert version operator(s) to flags + * @param str input string + * @return operator flags + */ +int version_parse_operator(const char *str) { + const char *valid = "><=!"; + + const char *pos = str; + int result = 0; + + if (isempty((char *) str)) { + return -1; + } + while ((pos = strpbrk(pos, valid)) != NULL) { + switch (*pos) { + case '>': + result |= GT; + break; + case '<': + result |= LT; + break; + case '=': + result |= EQ; + break; + case '!': + result |= NOT; + break; + default: + return -1; + } + pos++; + } + + return result; +} + +static int version_has_epoch(const char *str) { + const char *result = strchr(str, ':'); + return result ? 1 : 0; +} + +/** + * Compare version strings based on flag(s) + * @param flags verison operators + * @param aa version1 + * @param bb version2 + * @return 1 flag operation is true + * @return 0 flag operation is false + */ +int version_compare(const int flags, const char *aa, const char *bb) { + if (!flags || flags < 0) { + return -1; + } + + int result_a = version_sum(aa); + if (result_a < 0) { + return -1; + } + + int result_b = version_sum(bb); + if (result_b < 0) { + return -1; + } + + if (version_has_epoch(aa) && !version_has_epoch(bb)) { + result_a -= EPOCH_MOD; + } + if (!version_has_epoch(aa) && version_has_epoch(bb)) { + result_b -= EPOCH_MOD; + } + + int result = 0; + if (flags & GT && flags & EQ) { + result |= result_a >= result_b; + } else if (flags & LT && flags & EQ) { + result |= result_a <= result_b; + } else if (flags & NOT && flags & EQ) { + result |= result_a != result_b; + } else if (flags & GT) { + result |= result_a > result_b; + } else if (flags & LT) { + result |= result_a < result_b; + } else if (flags & EQ) { + result |= result_a == result_b; + } + + return result; +}
\ No newline at end of file |
