aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/core/CMakeLists.txt1
-rw-r--r--src/lib/core/include/version_compare.h20
-rw-r--r--src/lib/core/version_compare.c185
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