From 5022f08a23c0b64973b74cd71be9f5122ec655a6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 13 Dec 2017 20:34:13 +0100 Subject: [PATCH 1/3] core,udev,networkd: add ConditionKernelVersion= This adds a simple condition/assert/match to the service manager, to udev's .link handling and to networkd, for matching the kernel version string. In this version we only do fnmatch() based globbing, but we might want to extend that to version comparisons later on, if we like, by slightly extending the syntax with ">=", "<=", ">", "<" and "==" expressions. --- man/systemd.link.xml | 11 ++++++ man/systemd.netdev.xml | 10 ++++++ man/systemd.network.xml | 11 ++++++ man/systemd.unit.xml | 6 ++++ src/core/load-fragment-gperf.gperf.m4 | 2 ++ src/libsystemd-network/network-internal.c | 8 +++-- src/libsystemd-network/network-internal.h | 3 +- src/network/netdev/netdev-gperf.gperf | 3 +- src/network/netdev/netdev.c | 6 ++-- src/network/netdev/netdev.h | 3 +- src/network/networkd-network-gperf.gperf | 3 +- src/network/networkd-network.c | 7 ++-- src/network/networkd-network.h | 3 +- src/shared/condition.c | 16 +++++++++ src/shared/condition.h | 1 + src/test/test-condition.c | 42 +++++++++++++++++++++-- src/udev/net/link-config-gperf.gperf | 3 +- src/udev/net/link-config.c | 6 ++-- src/udev/net/link-config.h | 3 +- 19 files changed, 129 insertions(+), 18 deletions(-) diff --git a/man/systemd.link.xml b/man/systemd.link.xml index 162674f769..fb091b883c 100644 --- a/man/systemd.link.xml +++ b/man/systemd.link.xml @@ -172,6 +172,17 @@ for details. + + KernelVersion= + + Checks whether the kernel version (as reported by uname -r) matches a certain + expression (or if prefixed with the exclamation mark does not match it). See + ConditionKernelVersion= in + systemd.unit5 for + details. + + + Architecture= diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml index 4e5d47f585..b9647a2f7a 100644 --- a/man/systemd.netdev.xml +++ b/man/systemd.netdev.xml @@ -232,6 +232,16 @@ + + KernelVersion= + + Checks whether the kernel version (as reported by uname -r) matches a certain + expression (or if prefixed with the exclamation mark does not match it). See + ConditionKernelVersion= in + systemd.unit5 for details. + + + Architecture= diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 3466f3a3cf..f78beaa7dc 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -193,6 +193,17 @@ + + KernelVersion= + + Checks whether the kernel version (as reported by uname -r) matches a certain + expression (or if prefixed with the exclamation mark does not match it). See + ConditionKernelVersion= in + systemd.unit5 for + details. + + + Architecture= diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 005fdea73c..0d35d619e0 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -923,6 +923,7 @@ ConditionVirtualization= ConditionHost= ConditionKernelCommandLine= + ConditionKernelVersion= ConditionSecurity= ConditionCapability= ConditionACPower= @@ -1050,6 +1051,10 @@ the exact assignment is looked for with right and left hand side matching. + 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. + ConditionSecurity= may be used to check whether the given security module is enabled on the system. Currently, the recognized values are @@ -1201,6 +1206,7 @@ AssertVirtualization= AssertHost= AssertKernelCommandLine= + AssertKernelVersion= AssertSecurity= AssertCapability= AssertACPower= diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 5604312bc5..549c7eb4d6 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -241,6 +241,7 @@ Unit.ConditionFileIsExecutable, config_parse_unit_condition_path, CONDITION_F Unit.ConditionNeedsUpdate, config_parse_unit_condition_path, CONDITION_NEEDS_UPDATE, offsetof(Unit, conditions) Unit.ConditionFirstBoot, config_parse_unit_condition_string, CONDITION_FIRST_BOOT, offsetof(Unit, conditions) Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, conditions) +Unit.ConditionKernelVersion, config_parse_unit_condition_string, CONDITION_KERNEL_VERSION, offsetof(Unit, conditions) Unit.ConditionArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, conditions) Unit.ConditionVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, conditions) Unit.ConditionSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, offsetof(Unit, conditions) @@ -263,6 +264,7 @@ Unit.AssertFileIsExecutable, config_parse_unit_condition_path, CONDITION_F Unit.AssertNeedsUpdate, config_parse_unit_condition_path, CONDITION_NEEDS_UPDATE, offsetof(Unit, asserts) Unit.AssertFirstBoot, config_parse_unit_condition_string, CONDITION_FIRST_BOOT, offsetof(Unit, asserts) Unit.AssertKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, asserts) +Unit.AssertKernelVersion, config_parse_unit_condition_string, CONDITION_KERNEL_VERSION, offsetof(Unit, asserts) Unit.AssertArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, asserts) Unit.AssertVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, asserts) Unit.AssertSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, offsetof(Unit, asserts) diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index e48b7d22dd..c20e9fca35 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -116,7 +116,8 @@ bool net_match_config(const struct ether_addr *match_mac, char * const *match_names, Condition *match_host, Condition *match_virt, - Condition *match_kernel, + Condition *match_kernel_cmdline, + Condition *match_kernel_version, Condition *match_arch, const struct ether_addr *dev_mac, const char *dev_path, @@ -131,7 +132,10 @@ bool net_match_config(const struct ether_addr *match_mac, if (match_virt && condition_test(match_virt) <= 0) return false; - if (match_kernel && condition_test(match_kernel) <= 0) + if (match_kernel_cmdline && condition_test(match_kernel_cmdline) <= 0) + return false; + + if (match_kernel_version && condition_test(match_kernel_version) <= 0) return false; if (match_arch && condition_test(match_arch) <= 0) diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h index a54adac602..4e69f1a598 100644 --- a/src/libsystemd-network/network-internal.h +++ b/src/libsystemd-network/network-internal.h @@ -37,7 +37,8 @@ bool net_match_config(const struct ether_addr *match_mac, char * const *match_name, Condition *match_host, Condition *match_virt, - Condition *match_kernel, + Condition *match_kernel_cmdline, + Condition *match_kernel_version, Condition *match_arch, const struct ether_addr *dev_mac, const char *dev_path, diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf index 3aeba06ee0..03b8661957 100644 --- a/src/network/netdev/netdev-gperf.gperf +++ b/src/network/netdev/netdev-gperf.gperf @@ -33,7 +33,8 @@ struct ConfigPerfItem; %% Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(NetDev, match_host) Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(NetDev, match_virt) -Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(NetDev, match_kernel) +Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(NetDev, match_kernel_cmdline) +Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(NetDev, match_kernel_version) Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(NetDev, match_arch) NetDev.Description, config_parse_string, 0, offsetof(NetDev, description) NetDev.Name, config_parse_ifname, 0, offsetof(NetDev, ifname) diff --git a/src/network/netdev/netdev.c b/src/network/netdev/netdev.c index 7afe30f7fb..7cf110672f 100644 --- a/src/network/netdev/netdev.c +++ b/src/network/netdev/netdev.c @@ -149,7 +149,8 @@ static void netdev_free(NetDev *netdev) { condition_free_list(netdev->match_host); condition_free_list(netdev->match_virt); - condition_free_list(netdev->match_kernel); + condition_free_list(netdev->match_kernel_cmdline); + condition_free_list(netdev->match_kernel_version); condition_free_list(netdev->match_arch); if (NETDEV_VTABLE(netdev) && @@ -642,7 +643,8 @@ static int netdev_load_one(Manager *manager, const char *filename) { /* skip out early if configuration does not match the environment */ if (net_match_config(NULL, NULL, NULL, NULL, NULL, netdev_raw->match_host, netdev_raw->match_virt, - netdev_raw->match_kernel, netdev_raw->match_arch, + netdev_raw->match_kernel_cmdline, netdev_raw->match_kernel_version, + netdev_raw->match_arch, NULL, NULL, NULL, NULL, NULL, NULL) <= 0) return 0; diff --git a/src/network/netdev/netdev.h b/src/network/netdev/netdev.h index 24915b2b04..507b86a078 100644 --- a/src/network/netdev/netdev.h +++ b/src/network/netdev/netdev.h @@ -93,7 +93,8 @@ typedef struct NetDev { Condition *match_host; Condition *match_virt; - Condition *match_kernel; + Condition *match_kernel_cmdline; + Condition *match_kernel_version; Condition *match_arch; NetDevState state; diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 57a96aff94..cb0c01fbe7 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -27,7 +27,8 @@ Match.Type, config_parse_strv, Match.Name, config_parse_ifnames, 0, offsetof(Network, match_name) Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(Network, match_host) Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(Network, match_virt) -Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(Network, match_kernel) +Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(Network, match_kernel_cmdline) +Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(Network, match_kernel_version) Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, match_arch) Link.MACAddress, config_parse_hwaddr, 0, offsetof(Network, mac) Link.MTUBytes, config_parse_iec_size, 0, offsetof(Network, mtu) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 8e37a0a229..b037fc67e3 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -431,7 +431,8 @@ void network_free(Network *network) { condition_free_list(network->match_host); condition_free_list(network->match_virt); - condition_free_list(network->match_kernel); + condition_free_list(network->match_kernel_cmdline); + condition_free_list(network->match_kernel_version); condition_free_list(network->match_arch); free(network->dhcp_server_timezone); @@ -485,8 +486,8 @@ int network_get(Manager *manager, struct udev_device *device, if (net_match_config(network->match_mac, network->match_path, network->match_driver, network->match_type, network->match_name, network->match_host, - network->match_virt, network->match_kernel, - network->match_arch, + network->match_virt, network->match_kernel_cmdline, + network->match_kernel_version, network->match_arch, address, path, parent_driver, driver, devtype, ifname)) { if (network->match_name && device) { diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 49c62654b6..702aa7692a 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -112,7 +112,8 @@ struct Network { Condition *match_host; Condition *match_virt; - Condition *match_kernel; + Condition *match_kernel_cmdline; + Condition *match_kernel_version; Condition *match_arch; char *description; diff --git a/src/shared/condition.c b/src/shared/condition.c index d4bbaf3c65..887c9a7588 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -143,6 +144,18 @@ static int condition_test_kernel_command_line(Condition *c) { return false; } +static int condition_test_kernel_version(Condition *c) { + struct utsname u; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_KERNEL_VERSION); + + assert_se(uname(&u) >= 0); + + return fnmatch(c->parameter, u.release, 0) == 0; +} + static int condition_test_user(Condition *c) { uid_t id; int r; @@ -552,6 +565,7 @@ int condition_test(Condition *c) { [CONDITION_FILE_NOT_EMPTY] = condition_test_file_not_empty, [CONDITION_FILE_IS_EXECUTABLE] = condition_test_file_is_executable, [CONDITION_KERNEL_COMMAND_LINE] = condition_test_kernel_command_line, + [CONDITION_KERNEL_VERSION] = condition_test_kernel_version, [CONDITION_VIRTUALIZATION] = condition_test_virtualization, [CONDITION_SECURITY] = condition_test_security, [CONDITION_CAPABILITY] = condition_test_capability, @@ -612,6 +626,7 @@ static const char* const condition_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_VIRTUALIZATION] = "ConditionVirtualization", [CONDITION_HOST] = "ConditionHost", [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine", + [CONDITION_KERNEL_VERSION] = "ConditionKernelVersion", [CONDITION_SECURITY] = "ConditionSecurity", [CONDITION_CAPABILITY] = "ConditionCapability", [CONDITION_AC_POWER] = "ConditionACPower", @@ -639,6 +654,7 @@ static const char* const assert_type_table[_CONDITION_TYPE_MAX] = { [CONDITION_VIRTUALIZATION] = "AssertVirtualization", [CONDITION_HOST] = "AssertHost", [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine", + [CONDITION_KERNEL_VERSION] = "AssertKernelVersion", [CONDITION_SECURITY] = "AssertSecurity", [CONDITION_CAPABILITY] = "AssertCapability", [CONDITION_AC_POWER] = "AssertACPower", diff --git a/src/shared/condition.h b/src/shared/condition.h index 715866be70..98af2daaee 100644 --- a/src/shared/condition.h +++ b/src/shared/condition.h @@ -31,6 +31,7 @@ typedef enum ConditionType { CONDITION_VIRTUALIZATION, CONDITION_HOST, CONDITION_KERNEL_COMMAND_LINE, + CONDITION_KERNEL_VERSION, CONDITION_SECURITY, CONDITION_CAPABILITY, CONDITION_AC_POWER, diff --git a/src/test/test-condition.c b/src/test/test-condition.c index bf455aac89..8fb2ad5d18 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -20,6 +20,7 @@ #include #include +#include #include #include "sd-id128.h" @@ -38,10 +39,11 @@ #include "selinux-util.h" #include "set.h" #include "smack-util.h" +#include "string-util.h" #include "strv.h" -#include "virt.h" -#include "util.h" #include "user-util.h" +#include "util.h" +#include "virt.h" static void test_condition_test_path(void) { Condition *condition; @@ -298,6 +300,41 @@ static void test_condition_test_kernel_command_line(void) { condition_free(condition); } +static void test_condition_test_kernel_version(void) { + Condition *condition; + struct utsname u; + + condition = condition_new(CONDITION_KERNEL_VERSION, "*thisreallyshouldntbeinthekernelversion*", false, false); + assert_se(condition); + assert_se(!condition_test(condition)); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "*", false, false); + assert_se(condition); + assert_se(condition_test(condition)); + condition_free(condition); + + condition = condition_new(CONDITION_KERNEL_VERSION, "", false, false); + assert_se(condition); + assert_se(!condition_test(condition)); + condition_free(condition); + + assert_se(uname(&u) >= 0); + + condition = condition_new(CONDITION_KERNEL_VERSION, u.release, false, false); + assert_se(condition); + assert_se(condition_test(condition)); + condition_free(condition); + + strshorten(u.release, 4); + strcpy(strchr(u.release, 0), "*"); + + condition = condition_new(CONDITION_KERNEL_VERSION, u.release, false, false); + assert_se(condition); + assert_se(condition_test(condition)); + condition_free(condition); +} + static void test_condition_test_null(void) { Condition *condition; @@ -556,6 +593,7 @@ int main(int argc, char *argv[]) { test_condition_test_host(); test_condition_test_architecture(); test_condition_test_kernel_command_line(); + test_condition_test_kernel_version(); test_condition_test_null(); test_condition_test_security(); test_condition_test_virtualization(); diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf index 85f0a0625b..5cb126d870 100644 --- a/src/udev/net/link-config-gperf.gperf +++ b/src/udev/net/link-config-gperf.gperf @@ -26,7 +26,8 @@ Match.Driver, config_parse_strv, 0, Match.Type, config_parse_strv, 0, offsetof(link_config, match_type) Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(link_config, match_host) Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(link_config, match_virt) -Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(link_config, match_kernel) +Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(link_config, match_kernel_cmdline) +Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(link_config, match_kernel_version) Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(link_config, match_arch) Link.Description, config_parse_string, 0, offsetof(link_config, description) Link.MACAddressPolicy, config_parse_mac_policy, 0, offsetof(link_config, mac_policy) diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index 92d41a6e95..a4368f088d 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -77,7 +77,8 @@ static void link_config_free(link_config *link) { free(link->match_name); free(link->match_host); free(link->match_virt); - free(link->match_kernel); + free(link->match_kernel_cmdline); + free(link->match_kernel_version); free(link->match_arch); free(link->description); @@ -248,7 +249,8 @@ int link_config_get(link_config_ctx *ctx, struct udev_device *device, if (net_match_config(link->match_mac, link->match_path, link->match_driver, link->match_type, link->match_name, link->match_host, - link->match_virt, link->match_kernel, link->match_arch, + link->match_virt, link->match_kernel_cmdline, + link->match_kernel_version, link->match_arch, attr_value ? ether_aton(attr_value) : NULL, udev_device_get_property_value(device, "ID_PATH"), udev_device_get_driver(udev_device_get_parent(device)), diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h index a413251bd0..dabc3eff8f 100644 --- a/src/udev/net/link-config.h +++ b/src/udev/net/link-config.h @@ -58,7 +58,8 @@ struct link_config { char **match_name; Condition *match_host; Condition *match_virt; - Condition *match_kernel; + Condition *match_kernel_cmdline; + Condition *match_kernel_version; Condition *match_arch; char *description; From 871c6d54e4194732eaf7c7956de256c07d507e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 Dec 2017 21:44:29 +0100 Subject: [PATCH 2/3] Add note about kernel version unportability --- man/systemd.unit.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml index 0d35d619e0..73f33a4580 100644 --- a/man/systemd.unit.xml +++ b/man/systemd.unit.xml @@ -1055,6 +1055,11 @@ 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. + 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 + kernels into older versions provided by distributions. Hence, this check is inherently unportable and should + not be used for units which may be used on different distributions. + ConditionSecurity= may be used to check whether the given security module is enabled on the system. Currently, the recognized values are From 68c58c67b55e1de5e447bcc9def9532634503a69 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sat, 23 Dec 2017 15:02:58 +0100 Subject: [PATCH 3/3] 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 73f33a4580..19a6db7f65 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 5dd3c3014f..2a39ff2b53 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 8cd78171bc..20181ab917 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 e97b5cdb79..4f255f620b 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 887c9a7588..7a825c3973 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 8fb2ad5d18..8323a66ad3 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) {