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=.
This commit is contained in:
Lennart Poettering 2017-12-23 15:02:58 +01:00
parent 871c6d54e4
commit 68c58c67b5
6 changed files with 206 additions and 62 deletions

View file

@ -1053,7 +1053,9 @@
<para><varname>ConditionKernelVersion=</varname> may be used to check whether the kernel version (as reported
by <command>uname -r</command>) 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.</para>
match it). The argument must be a single string. If the string starts with one of <literal>&lt;</literal>,
<literal>&lt;=</literal>, <literal>=</literal>, <literal>&gt;=</literal>, <literal>&gt;</literal> a relative
version comparison is done, otherwise the specified string is matched with shell-style globs.</para>
<para>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

View file

@ -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);
}

View file

@ -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);

View file

@ -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);
}

View file

@ -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) {

View file

@ -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) {