From 68c58c67b55e1de5e447bcc9def9532634503a69 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 23 Dec 2017 15:02:58 +0100 Subject: [PATCH] condition: extend ConditionKernelVersion= with relative version checks Now that we have str_verscmp() in our source tree anyway, let's make it generic and reuse it for ConditionKernelVersion=. --- man/systemd.unit.xml | 4 ++- src/basic/util.c | 62 ++++++++++++++++++++++++++++++++++ src/basic/util.h | 2 ++ src/shared/bootspec.c | 61 +--------------------------------- src/shared/condition.c | 54 +++++++++++++++++++++++++++++- src/test/test-condition.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 206 insertions(+), 62 deletions(-) diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 73f33a4..19a6db7 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -1053,7 +1053,9 @@ ConditionKernelVersion= may be used to check whether the kernel version (as reported by uname -r) matches a certain expression (or if prefixed with the exclamation mark does not - match it). The argument must be a single string, optionally containing shell-style globs. + match it). The argument must be a single string. If the string starts with one of <, + <=, =, >=, > a relative + version comparison is done, otherwise the specified string is matched with shell-style globs. Note that using the kernel version string is an unreliable way to determine which features are supported by a kernel, because of the widespread practice of backporting drivers, features, and fixes from newer upstream diff --git a/src/basic/util.c b/src/basic/util.c index 5dd3c30..2a39ff2 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -553,3 +553,65 @@ int version(void) { SYSTEMD_FEATURES); return 0; } + +/* This is a direct translation of str_verscmp from boot.c */ +static bool is_digit(int c) { + return c >= '0' && c <= '9'; +} + +static int c_order(int c) { + if (c == 0 || is_digit(c)) + return 0; + + if ((c >= 'a') && (c <= 'z')) + return c; + + return c + 0x10000; +} + +int str_verscmp(const char *s1, const char *s2) { + const char *os1, *os2; + + assert(s1); + assert(s2); + + os1 = s1; + os2 = s2; + + while (*s1 || *s2) { + int first; + + while ((*s1 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) { + int order; + + order = c_order(*s1) - c_order(*s2); + if (order != 0) + return order; + s1++; + s2++; + } + + while (*s1 == '0') + s1++; + while (*s2 == '0') + s2++; + + first = 0; + while (is_digit(*s1) && is_digit(*s2)) { + if (first == 0) + first = *s1 - *s2; + s1++; + s2++; + } + + if (is_digit(*s1)) + return 1; + if (is_digit(*s2)) + return -1; + + if (first != 0) + return first; + } + + return strcmp(os1, os2); +} diff --git a/src/basic/util.h b/src/basic/util.h index 8cd7817..20181ab 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -189,3 +189,5 @@ uint64_t system_tasks_max_scale(uint64_t v, uint64_t max); int update_reboot_parameter_and_warn(const char *param); int version(void); + +int str_verscmp(const char *s1, const char *s2); diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index e97b5cd..4f255f6 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -203,67 +203,8 @@ int boot_loader_read_conf(const char *path, BootConfig *config) { return 0; } -/* This is a direct translation of str_verscmp from boot.c */ -static bool is_digit(int c) { - return c >= '0' && c <= '9'; -} - -static int c_order(int c) { - if (c == '\0') - return 0; - if (is_digit(c)) - return 0; - else if ((c >= 'a') && (c <= 'z')) - return c; - else - return c + 0x10000; -} - -static int str_verscmp(const char *s1, const char *s2) { - const char *os1 = s1; - const char *os2 = s2; - - while (*s1 || *s2) { - int first; - - while ((*s1 && !is_digit(*s1)) || (*s2 && !is_digit(*s2))) { - int order; - - order = c_order(*s1) - c_order(*s2); - if (order) - return order; - s1++; - s2++; - } - - while (*s1 == '0') - s1++; - while (*s2 == '0') - s2++; - - first = 0; - while (is_digit(*s1) && is_digit(*s2)) { - if (first == 0) - first = *s1 - *s2; - s1++; - s2++; - } - - if (is_digit(*s1)) - return 1; - if (is_digit(*s2)) - return -1; - - if (first != 0) - return first; - } - - return strcmp(os1, os2); -} - static int boot_entry_compare(const void *a, const void *b) { - const BootEntry *aa = a; - const BootEntry *bb = b; + const BootEntry *aa = a, *bb = b; return str_verscmp(aa->filename, bb->filename); } diff --git a/src/shared/condition.c b/src/shared/condition.c index 887c9a7..7a825c3 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -145,7 +145,28 @@ static int condition_test_kernel_command_line(Condition *c) { } static int condition_test_kernel_version(Condition *c) { + enum { + /* Listed in order of checking. Note that some comparators are prefixes of others, hence the longest + * should be listed first. */ + LOWER_OR_EQUAL, + GREATER_OR_EQUAL, + LOWER, + GREATER, + EQUAL, + _ORDER_MAX, + }; + + static const char *const prefix[_ORDER_MAX] = { + [LOWER_OR_EQUAL] = "<=", + [GREATER_OR_EQUAL] = ">=", + [LOWER] = "<", + [GREATER] = ">", + [EQUAL] = "=", + }; + const char *p = NULL; struct utsname u; + size_t i; + int k; assert(c); assert(c->parameter); @@ -153,7 +174,38 @@ static int condition_test_kernel_version(Condition *c) { assert_se(uname(&u) >= 0); - return fnmatch(c->parameter, u.release, 0) == 0; + for (i = 0; i < _ORDER_MAX; i++) { + p = startswith(c->parameter, prefix[i]); + if (p) + break; + } + + /* No prefix? Then treat as glob string */ + if (!p) + return fnmatch(skip_leading_chars(c->parameter, NULL), u.release, 0) == 0; + + k = str_verscmp(u.release, skip_leading_chars(p, NULL)); + + switch (i) { + + case LOWER: + return k < 0; + + case LOWER_OR_EQUAL: + return k <= 0; + + case EQUAL: + return k == 0; + + case GREATER_OR_EQUAL: + return k >= 0; + + case GREATER: + return k > 0; + + default: + assert_not_reached("Can't compare"); + } } static int condition_test_user(Condition *c) { diff --git a/src/test/test-condition.c b/src/test/test-condition.c index 8fb2ad5..8323a66 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -303,6 +303,7 @@ static void test_condition_test_kernel_command_line(void) { static void test_condition_test_kernel_version(void) { Condition *condition; struct utsname u; + const char *v; condition = condition_new(CONDITION_KERNEL_VERSION, "*thisreallyshouldntbeinthekernelversion*", false, false); assert_se(condition); @@ -333,6 +334,90 @@ static void test_condition_test_kernel_version(void) { assert_se(condition); assert_se(condition_test(condition)); condition_free(condition); + + /* 0.1.2 would be a very very very old kernel */ + condition = condition_new(CONDITION_KERNEL_VERSION, "> 0.1.2", false, false); + assert_se(condition); + assert_se(condition_test(condition)); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, ">= 0.1.2", false, false); + assert_se(condition); + assert_se(condition_test(condition)); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "< 0.1.2", false, false); + assert_se(condition); + assert_se(!condition_test(condition)); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "<= 0.1.2", false, false); + assert_se(condition); + assert_se(!condition_test(condition)); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "= 0.1.2", false, false); + assert_se(condition); + assert_se(!condition_test(condition)); + condition_free(condition); + + /* 4711.8.15 is a very very very future kernel */ + condition = condition_new(CONDITION_KERNEL_VERSION, "< 4711.8.15", false, false); + assert_se(condition); + assert_se(condition_test(condition)); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "<= 4711.8.15", false, false); + assert_se(condition); + assert_se(condition_test(condition)); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "= 4711.8.15", false, false); + assert_se(condition); + assert_se(!condition_test(condition)); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "> 4711.8.15", false, false); + assert_se(condition); + assert_se(!condition_test(condition)); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, ">= 4711.8.15", false, false); + assert_se(condition); + assert_se(!condition_test(condition)); + condition_free(condition); + + assert_se(uname(&u) >= 0); + + v = strjoina(">=", u.release); + condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false); + assert_se(condition); + assert_se(condition_test(condition)); + condition_free(condition); + + v = strjoina("= ", u.release); + condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false); + assert_se(condition); + assert_se(condition_test(condition)); + condition_free(condition); + + v = strjoina("<=", u.release); + condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false); + assert_se(condition); + assert_se(condition_test(condition)); + condition_free(condition); + + v = strjoina("> ", u.release); + condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false); + assert_se(condition); + assert_se(!condition_test(condition)); + condition_free(condition); + + v = strjoina("< ", u.release); + condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false); + assert_se(condition); + assert_se(!condition_test(condition)); + condition_free(condition); } static void test_condition_test_null(void) { -- 2.7.4