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:
parent
871c6d54e4
commit
68c58c67b5
|
@ -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><</literal>,
|
||||
<literal><=</literal>, <literal>=</literal>, <literal>>=</literal>, <literal>></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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue