diff --git a/docs/TRANSIENT-SETTINGS.md b/docs/TRANSIENT-SETTINGS.md
index ebb8ba536a..4992acbf67 100644
--- a/docs/TRANSIENT-SETTINGS.md
+++ b/docs/TRANSIENT-SETTINGS.md
@@ -69,6 +69,7 @@ Most generic unit settings are available for transient units.
✓ ConditionFirstBoot=
✓ ConditionKernelCommandLine=
✓ ConditionKernelVersion=
+✓ ConditionVersion=
✓ ConditionArchitecture=
✓ ConditionFirmware=
✓ ConditionVirtualization=
diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml
index a1250eb10f..b3975d5f03 100644
--- a/man/systemd-analyze.xml
+++ b/man/systemd-analyze.xml
@@ -525,8 +525,8 @@ cap_net_raw 13
Evaluate conditions that check kernel versions
- $ systemd-analyze condition 'ConditionKernelVersion = ! <4.0' \
- 'ConditionKernelVersion = >=5.1' \
+ $ systemd-analyze condition 'ConditionVersion = ! <4.0' \
+ 'ConditionVersion = >=5.1' \
'ConditionACPower=|false' \
'ConditionArchitecture=|!arm' \
'AssertPathExists=/etc/os-release'
@@ -534,8 +534,8 @@ test.service: AssertPathExists=/etc/os-release succeeded.
Asserts succeeded.
test.service: ConditionArchitecture=|!arm succeeded.
test.service: ConditionACPower=|false failed.
-test.service: ConditionKernelVersion=>=5.1 succeeded.
-test.service: ConditionKernelVersion=!<4.0 succeeded.
+test.service: ConditionVersion=>=5.1 succeeded.
+test.service: ConditionVersion=!<4.0 succeeded.
Conditions succeeded.
diff --git a/man/systemd.link.xml b/man/systemd.link.xml
index 5441da377e..217352f979 100644
--- a/man/systemd.link.xml
+++ b/man/systemd.link.xml
@@ -306,6 +306,19 @@
+
+ Version=
+
+ Checks whether a software version matches a certain expression. See
+ ConditionVersion= in
+ systemd.unit5
+ for details. When prefixed with an exclamation mark (!), the result is negated.
+ If an empty string is assigned, the previously assigned value is cleared.
+
+
+
+
+
Credential=
diff --git a/man/systemd.netdev.xml b/man/systemd.netdev.xml
index b88c51b90e..1b4673ab80 100644
--- a/man/systemd.netdev.xml
+++ b/man/systemd.netdev.xml
@@ -223,6 +223,7 @@
+
diff --git a/man/systemd.network.xml b/man/systemd.network.xml
index c1618c0f94..52194c4fc0 100644
--- a/man/systemd.network.xml
+++ b/man/systemd.network.xml
@@ -152,6 +152,7 @@
+
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index e931ef6f76..5be9225a58 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -1481,6 +1481,24 @@
+
+ ConditionVersion=
+
+ ConditionVersion= may be used to check whether a software
+ version matches a certain expression, or if prefixed with the exclamation mark, does not match.
+ The first argument is the software whose version has to be checked. Currently
+ kernel and systemd are supported. If this argument is
+ omitted, kernel is implied. The second argument must be a list
+ of (potentially quoted) expressions. Each expression starts with one of = or
+ != for string comparisons, <, <=,
+ ==, <>, >=,
+ > for version comparisons, or $=, !$=
+ for a shell-style glob match. If no operator is specified, $= is implied.
+
+
+
+
+
ConditionCredential=
diff --git a/src/analyze/analyze-condition.c b/src/analyze/analyze-condition.c
index e7871060fe..630c4b389b 100644
--- a/src/analyze/analyze-condition.c
+++ b/src/analyze/analyze-condition.c
@@ -14,41 +14,48 @@ static int parse_condition(Unit *u, const char *line) {
assert(u);
assert(line);
- for (ConditionType t = 0; t < _CONDITION_TYPE_MAX; t++) {
- ConfigParserCallback callback;
- Condition **target;
- const char *p, *name;
+ line = skip_leading_chars(line, /* bad = */ NULL);
- name = condition_type_to_string(t);
- p = startswith(line, name);
- if (p)
- target = &u->conditions;
- else {
- name = assert_type_to_string(t);
- p = startswith(line, name);
- if (!p)
- continue;
+ const char *eq = strchr(line, '=');
+ if (!eq)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot parse \"%s\".", line);
- target = &u->asserts;
- }
+ _cleanup_free_ char *type_string = strndup(line, eq - line);
+ if (!type_string)
+ return log_oom();
- p += strspn(p, WHITESPACE);
+ delete_trailing_chars(type_string, /* bad = */ NULL);
- if (*p != '=')
- continue;
- p++;
-
- p += strspn(p, WHITESPACE);
-
- if (condition_takes_path(t))
- callback = config_parse_unit_condition_path;
- else
- callback = config_parse_unit_condition_string;
-
- return callback(NULL, "(cmdline)", 0, NULL, 0, name, t, p, target, u);
+ Condition **target;
+ ConditionType type;
+ if (startswith(type_string, "Condition")) {
+ target = &u->conditions;
+ type = condition_type_from_string(type_string);
+ } else {
+ target = &u->asserts;
+ type = assert_type_from_string(type_string);
}
+ if (type < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot parse \"%s\".", line);
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot parse \"%s\".", line);
+ const char *val = skip_leading_chars(eq + 1, /* bad = */ NULL);
+
+ ConfigParserCallback callback;
+ if (condition_takes_path(type))
+ callback = config_parse_unit_condition_path;
+ else
+ callback = config_parse_unit_condition_string;
+
+ return callback(/* unit = */ NULL,
+ /* filename = */ "(cmdline)",
+ /* line = */ 0,
+ /* section = */ NULL,
+ /* section_line = */ 0,
+ /* lvalue = */ type_string,
+ /* ltype = */ type,
+ /* rvalue = */ val,
+ /* data = */ target,
+ /* userdata = */ u);
}
_printf_(7, 8)
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 7344e56c4a..6307dbe83f 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -360,7 +360,8 @@ Unit.ConditionFirmware, config_parse_unit_condition_string
Unit.ConditionVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, conditions)
Unit.ConditionHost, config_parse_unit_condition_string, CONDITION_HOST, 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.ConditionKernelVersion, config_parse_unit_condition_string, CONDITION_VERSION, offsetof(Unit, conditions)
+Unit.ConditionVersion, config_parse_unit_condition_string, CONDITION_VERSION, offsetof(Unit, conditions)
Unit.ConditionCredential, config_parse_unit_condition_string, CONDITION_CREDENTIAL, offsetof(Unit, conditions)
Unit.ConditionSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, offsetof(Unit, conditions)
Unit.ConditionCapability, config_parse_unit_condition_string, CONDITION_CAPABILITY, offsetof(Unit, conditions)
@@ -393,7 +394,8 @@ Unit.AssertArchitecture, config_parse_unit_condition_string
Unit.AssertVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, asserts)
Unit.AssertHost, config_parse_unit_condition_string, CONDITION_HOST, 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.AssertKernelVersion, config_parse_unit_condition_string, CONDITION_VERSION, offsetof(Unit, asserts)
+Unit.AssertVersion, config_parse_unit_condition_string, CONDITION_VERSION, offsetof(Unit, asserts)
Unit.AssertCredential, config_parse_unit_condition_string, CONDITION_CREDENTIAL, offsetof(Unit, asserts)
Unit.AssertSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, offsetof(Unit, asserts)
Unit.AssertCapability, config_parse_unit_condition_string, CONDITION_CAPABILITY, offsetof(Unit, asserts)
diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf
index 612f463e33..4535c2cf3e 100644
--- a/src/network/netdev/netdev-gperf.gperf
+++ b/src/network/netdev/netdev-gperf.gperf
@@ -45,7 +45,8 @@ struct ConfigPerfItem;
Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(NetDev, conditions)
Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(NetDev, conditions)
Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(NetDev, conditions)
-Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(NetDev, conditions)
+Match.KernelVersion, config_parse_net_condition, CONDITION_VERSION, offsetof(NetDev, conditions)
+Match.Version, config_parse_net_condition, CONDITION_VERSION, offsetof(NetDev, conditions)
Match.Credential, config_parse_net_condition, CONDITION_CREDENTIAL, offsetof(NetDev, conditions)
Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(NetDev, conditions)
Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(NetDev, conditions)
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index bdbb3ad2c8..8e99a059c1 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -67,7 +67,8 @@ Match.Property, config_parse_match_property,
Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(Network, conditions)
Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(Network, conditions)
Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(Network, conditions)
-Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(Network, conditions)
+Match.KernelVersion, config_parse_net_condition, CONDITION_VERSION, offsetof(Network, conditions)
+Match.Version, config_parse_net_condition, CONDITION_VERSION, offsetof(Network, conditions)
Match.Credential, config_parse_net_condition, CONDITION_CREDENTIAL, offsetof(Network, conditions)
Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(Network, conditions)
Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(Network, conditions)
diff --git a/src/shared/condition.c b/src/shared/condition.c
index d57f46e8a0..b65d64d300 100644
--- a/src/shared/condition.c
+++ b/src/shared/condition.c
@@ -185,18 +185,14 @@ static int condition_test_credential(Condition *c, char **env) {
return false;
}
-static int condition_test_kernel_version(Condition *c, char **env) {
+static int condition_test_version_cmp(const char *condition, const char *ver) {
CompareOperator operator;
- struct utsname u;
bool first = true;
- assert(c);
- assert(c->parameter);
- assert(c->type == CONDITION_KERNEL_VERSION);
+ assert(condition);
+ assert(ver);
- assert_se(uname(&u) >= 0);
-
- for (const char *p = c->parameter;;) {
+ for (const char *p = condition;;) {
_cleanup_free_ char *word = NULL;
const char *s;
int r;
@@ -228,7 +224,7 @@ static int condition_test_kernel_version(Condition *c, char **env) {
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected end of expression: %s", p);
}
- r = version_or_fnmatch_compare(operator, u.release, s);
+ r = version_or_fnmatch_compare(operator, ver, s);
if (r < 0)
return r;
if (!r)
@@ -240,6 +236,37 @@ static int condition_test_kernel_version(Condition *c, char **env) {
return true;
}
+static int condition_test_version(Condition *c, char **env) {
+ int r;
+
+ assert(c);
+ assert(c->type == CONDITION_VERSION);
+
+ /* An empty condition is considered true. */
+ if (isempty(c->parameter))
+ return true;
+
+ const char *p = c->parameter;
+ _cleanup_free_ char *word = NULL;
+ r = extract_first_word(&p, &word, COMPARE_OPERATOR_WITH_FNMATCH_CHARS WHITESPACE,
+ EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_SEPARATORS);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse compare predicate \"%s\": %m", p);
+ if (r == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing right operand in condition: %s", c->parameter);
+
+ if (streq(word, "systemd"))
+ return condition_test_version_cmp(p, STRINGIFY(PROJECT_VERSION));
+
+ /* if no predicate has been set, default to "kernel" and use the whole parameter as condition */
+ if (!streq(word, "kernel"))
+ p = c->parameter;
+
+ struct utsname u;
+ assert_se(uname(&u) >= 0);
+ return condition_test_version_cmp(p, u.release);
+}
+
static int condition_test_osrelease(Condition *c, char **env) {
int r;
@@ -1222,7 +1249,7 @@ int condition_test(Condition *c, char **env) {
[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_VERSION] = condition_test_version,
[CONDITION_CREDENTIAL] = condition_test_credential,
[CONDITION_VIRTUALIZATION] = condition_test_virtualization,
[CONDITION_SECURITY] = condition_test_security,
@@ -1335,89 +1362,113 @@ void condition_dump_list(Condition *first, FILE *f, const char *prefix, conditio
condition_dump(c, f, prefix, to_string);
}
-static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
- [CONDITION_ARCHITECTURE] = "ConditionArchitecture",
- [CONDITION_FIRMWARE] = "ConditionFirmware",
- [CONDITION_VIRTUALIZATION] = "ConditionVirtualization",
- [CONDITION_HOST] = "ConditionHost",
- [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine",
- [CONDITION_KERNEL_VERSION] = "ConditionKernelVersion",
- [CONDITION_CREDENTIAL] = "ConditionCredential",
- [CONDITION_SECURITY] = "ConditionSecurity",
- [CONDITION_CAPABILITY] = "ConditionCapability",
- [CONDITION_AC_POWER] = "ConditionACPower",
- [CONDITION_NEEDS_UPDATE] = "ConditionNeedsUpdate",
- [CONDITION_FIRST_BOOT] = "ConditionFirstBoot",
- [CONDITION_PATH_EXISTS] = "ConditionPathExists",
- [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob",
- [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory",
- [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink",
- [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint",
- [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite",
- [CONDITION_PATH_IS_ENCRYPTED] = "ConditionPathIsEncrypted",
- [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty",
- [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty",
- [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable",
- [CONDITION_USER] = "ConditionUser",
- [CONDITION_GROUP] = "ConditionGroup",
+static const char* const _condition_type_table[_CONDITION_TYPE_MAX] = {
+ [CONDITION_ARCHITECTURE] = "ConditionArchitecture",
+ [CONDITION_FIRMWARE] = "ConditionFirmware",
+ [CONDITION_VIRTUALIZATION] = "ConditionVirtualization",
+ [CONDITION_HOST] = "ConditionHost",
+ [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine",
+ [CONDITION_VERSION] = "ConditionVersion",
+ [CONDITION_CREDENTIAL] = "ConditionCredential",
+ [CONDITION_SECURITY] = "ConditionSecurity",
+ [CONDITION_CAPABILITY] = "ConditionCapability",
+ [CONDITION_AC_POWER] = "ConditionACPower",
+ [CONDITION_NEEDS_UPDATE] = "ConditionNeedsUpdate",
+ [CONDITION_FIRST_BOOT] = "ConditionFirstBoot",
+ [CONDITION_PATH_EXISTS] = "ConditionPathExists",
+ [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob",
+ [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory",
+ [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink",
+ [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint",
+ [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite",
+ [CONDITION_PATH_IS_ENCRYPTED] = "ConditionPathIsEncrypted",
+ [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty",
+ [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty",
+ [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable",
+ [CONDITION_USER] = "ConditionUser",
+ [CONDITION_GROUP] = "ConditionGroup",
[CONDITION_CONTROL_GROUP_CONTROLLER] = "ConditionControlGroupController",
- [CONDITION_CPUS] = "ConditionCPUs",
- [CONDITION_MEMORY] = "ConditionMemory",
- [CONDITION_ENVIRONMENT] = "ConditionEnvironment",
- [CONDITION_CPU_FEATURE] = "ConditionCPUFeature",
- [CONDITION_OS_RELEASE] = "ConditionOSRelease",
- [CONDITION_MEMORY_PRESSURE] = "ConditionMemoryPressure",
- [CONDITION_CPU_PRESSURE] = "ConditionCPUPressure",
- [CONDITION_IO_PRESSURE] = "ConditionIOPressure",
- [CONDITION_KERNEL_MODULE_LOADED] = "ConditionKernelModuleLoaded",
+ [CONDITION_CPUS] = "ConditionCPUs",
+ [CONDITION_MEMORY] = "ConditionMemory",
+ [CONDITION_ENVIRONMENT] = "ConditionEnvironment",
+ [CONDITION_CPU_FEATURE] = "ConditionCPUFeature",
+ [CONDITION_OS_RELEASE] = "ConditionOSRelease",
+ [CONDITION_MEMORY_PRESSURE] = "ConditionMemoryPressure",
+ [CONDITION_CPU_PRESSURE] = "ConditionCPUPressure",
+ [CONDITION_IO_PRESSURE] = "ConditionIOPressure",
+ [CONDITION_KERNEL_MODULE_LOADED] = "ConditionKernelModuleLoaded",
};
-DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(_condition_type, ConditionType);
-static const char* const assert_type_table[_CONDITION_TYPE_MAX] = {
- [CONDITION_ARCHITECTURE] = "AssertArchitecture",
- [CONDITION_FIRMWARE] = "AssertFirmware",
- [CONDITION_VIRTUALIZATION] = "AssertVirtualization",
- [CONDITION_HOST] = "AssertHost",
- [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine",
- [CONDITION_KERNEL_VERSION] = "AssertKernelVersion",
- [CONDITION_CREDENTIAL] = "AssertCredential",
- [CONDITION_SECURITY] = "AssertSecurity",
- [CONDITION_CAPABILITY] = "AssertCapability",
- [CONDITION_AC_POWER] = "AssertACPower",
- [CONDITION_NEEDS_UPDATE] = "AssertNeedsUpdate",
- [CONDITION_FIRST_BOOT] = "AssertFirstBoot",
- [CONDITION_PATH_EXISTS] = "AssertPathExists",
- [CONDITION_PATH_EXISTS_GLOB] = "AssertPathExistsGlob",
- [CONDITION_PATH_IS_DIRECTORY] = "AssertPathIsDirectory",
- [CONDITION_PATH_IS_SYMBOLIC_LINK] = "AssertPathIsSymbolicLink",
- [CONDITION_PATH_IS_MOUNT_POINT] = "AssertPathIsMountPoint",
- [CONDITION_PATH_IS_READ_WRITE] = "AssertPathIsReadWrite",
- [CONDITION_PATH_IS_ENCRYPTED] = "AssertPathIsEncrypted",
- [CONDITION_DIRECTORY_NOT_EMPTY] = "AssertDirectoryNotEmpty",
- [CONDITION_FILE_NOT_EMPTY] = "AssertFileNotEmpty",
- [CONDITION_FILE_IS_EXECUTABLE] = "AssertFileIsExecutable",
- [CONDITION_USER] = "AssertUser",
- [CONDITION_GROUP] = "AssertGroup",
+const char* condition_type_to_string(ConditionType t) {
+ return _condition_type_to_string(t);
+}
+
+ConditionType condition_type_from_string(const char *s) {
+ /* for backward compatibility */
+ if (streq_ptr(s, "ConditionKernelVersion"))
+ return CONDITION_VERSION;
+
+ return _condition_type_from_string(s);
+}
+
+static const char* const _assert_type_table[_CONDITION_TYPE_MAX] = {
+ [CONDITION_ARCHITECTURE] = "AssertArchitecture",
+ [CONDITION_FIRMWARE] = "AssertFirmware",
+ [CONDITION_VIRTUALIZATION] = "AssertVirtualization",
+ [CONDITION_HOST] = "AssertHost",
+ [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine",
+ [CONDITION_VERSION] = "AssertVersion",
+ [CONDITION_CREDENTIAL] = "AssertCredential",
+ [CONDITION_SECURITY] = "AssertSecurity",
+ [CONDITION_CAPABILITY] = "AssertCapability",
+ [CONDITION_AC_POWER] = "AssertACPower",
+ [CONDITION_NEEDS_UPDATE] = "AssertNeedsUpdate",
+ [CONDITION_FIRST_BOOT] = "AssertFirstBoot",
+ [CONDITION_PATH_EXISTS] = "AssertPathExists",
+ [CONDITION_PATH_EXISTS_GLOB] = "AssertPathExistsGlob",
+ [CONDITION_PATH_IS_DIRECTORY] = "AssertPathIsDirectory",
+ [CONDITION_PATH_IS_SYMBOLIC_LINK] = "AssertPathIsSymbolicLink",
+ [CONDITION_PATH_IS_MOUNT_POINT] = "AssertPathIsMountPoint",
+ [CONDITION_PATH_IS_READ_WRITE] = "AssertPathIsReadWrite",
+ [CONDITION_PATH_IS_ENCRYPTED] = "AssertPathIsEncrypted",
+ [CONDITION_DIRECTORY_NOT_EMPTY] = "AssertDirectoryNotEmpty",
+ [CONDITION_FILE_NOT_EMPTY] = "AssertFileNotEmpty",
+ [CONDITION_FILE_IS_EXECUTABLE] = "AssertFileIsExecutable",
+ [CONDITION_USER] = "AssertUser",
+ [CONDITION_GROUP] = "AssertGroup",
[CONDITION_CONTROL_GROUP_CONTROLLER] = "AssertControlGroupController",
- [CONDITION_CPUS] = "AssertCPUs",
- [CONDITION_MEMORY] = "AssertMemory",
- [CONDITION_ENVIRONMENT] = "AssertEnvironment",
- [CONDITION_CPU_FEATURE] = "AssertCPUFeature",
- [CONDITION_OS_RELEASE] = "AssertOSRelease",
- [CONDITION_MEMORY_PRESSURE] = "AssertMemoryPressure",
- [CONDITION_CPU_PRESSURE] = "AssertCPUPressure",
- [CONDITION_IO_PRESSURE] = "AssertIOPressure",
- [CONDITION_KERNEL_MODULE_LOADED] = "AssertKernelModuleLoaded",
+ [CONDITION_CPUS] = "AssertCPUs",
+ [CONDITION_MEMORY] = "AssertMemory",
+ [CONDITION_ENVIRONMENT] = "AssertEnvironment",
+ [CONDITION_CPU_FEATURE] = "AssertCPUFeature",
+ [CONDITION_OS_RELEASE] = "AssertOSRelease",
+ [CONDITION_MEMORY_PRESSURE] = "AssertMemoryPressure",
+ [CONDITION_CPU_PRESSURE] = "AssertCPUPressure",
+ [CONDITION_IO_PRESSURE] = "AssertIOPressure",
+ [CONDITION_KERNEL_MODULE_LOADED] = "AssertKernelModuleLoaded",
};
-DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType);
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(_assert_type, ConditionType);
+
+const char* assert_type_to_string(ConditionType t) {
+ return _assert_type_to_string(t);
+}
+
+ConditionType assert_type_from_string(const char *s) {
+ /* for backward compatibility */
+ if (streq_ptr(s, "AssertKernelVersion"))
+ return CONDITION_VERSION;
+
+ return _assert_type_from_string(s);
+}
static const char* const condition_result_table[_CONDITION_RESULT_MAX] = {
- [CONDITION_UNTESTED] = "untested",
+ [CONDITION_UNTESTED] = "untested",
[CONDITION_SUCCEEDED] = "succeeded",
- [CONDITION_FAILED] = "failed",
- [CONDITION_ERROR] = "error",
+ [CONDITION_FAILED] = "failed",
+ [CONDITION_ERROR] = "error",
};
DEFINE_STRING_TABLE_LOOKUP(condition_result, ConditionResult);
diff --git a/src/shared/condition.h b/src/shared/condition.h
index 378028a73e..84b9273841 100644
--- a/src/shared/condition.h
+++ b/src/shared/condition.h
@@ -13,7 +13,7 @@ typedef enum ConditionType {
CONDITION_VIRTUALIZATION,
CONDITION_HOST,
CONDITION_KERNEL_COMMAND_LINE,
- CONDITION_KERNEL_VERSION,
+ CONDITION_VERSION,
CONDITION_CREDENTIAL,
CONDITION_SECURITY,
CONDITION_CAPABILITY,
diff --git a/src/test/test-condition.c b/src/test/test-condition.c
index 9334a8ca89..a6132f1132 100644
--- a/src/test/test-condition.c
+++ b/src/test/test-condition.c
@@ -460,115 +460,220 @@ TEST(condition_test_kernel_version) {
struct utsname u;
const char *v;
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, "*thisreallyshouldntbeinthekernelversion*", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "*thisreallyshouldntbeinthekernelversion*", false, false)));
ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, "*", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "*", false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
/* An artificially empty condition. It evaluates to true, but normally
* such condition cannot be created, because the condition list is reset instead. */
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, "", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "", false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
ASSERT_OK_ERRNO(uname(&u));
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, u.release, false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, u.release, false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
strshorten(u.release, 4);
strcpy(strchr(u.release, 0), "*");
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, u.release, false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, u.release, false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
/* 0.1.2 would be a very very very old kernel */
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, "> 0.1.2", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "> 0.1.2", false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, ">0.1.2", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, ">0.1.2", false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, "'>0.1.2' '<9.0.0'", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "'>0.1.2' '<9.0.0'", false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, "> 0.1.2 < 9.0.0", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "> 0.1.2 < 9.0.0", false, false)));
ASSERT_ERROR(condition_test(condition, environ), EINVAL);
condition_free(condition);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, ">", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, ">", false, false)));
ASSERT_ERROR(condition_test(condition, environ), EINVAL);
condition_free(condition);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, ">= 0.1.2", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, ">= 0.1.2", false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, "< 0.1.2", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "< 0.1.2", false, false)));
ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, "<= 0.1.2", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "<= 0.1.2", false, false)));
ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, "= 0.1.2", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "= 0.1.2", false, false)));
ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition);
/* 4711.8.15 is a very very very future kernel */
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, "< 4711.8.15", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "< 4711.8.15", false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, "<= 4711.8.15", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "<= 4711.8.15", false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, "= 4711.8.15", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "= 4711.8.15", false, false)));
ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, "> 4711.8.15", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "> 4711.8.15", false, false)));
ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, " >= 4711.8.15", false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, " >= 4711.8.15", false, false)));
ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition);
ASSERT_OK_ERRNO(uname(&u));
v = strjoina(">=", u.release);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, v, false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
v = strjoina("= ", u.release);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, v, false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
v = strjoina("<=", u.release);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, v, false, false)));
ASSERT_OK_POSITIVE(condition_test(condition, environ));
condition_free(condition);
v = strjoina("> ", u.release);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, v, false, false)));
ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition);
v = strjoina("< ", u.release);
- ASSERT_NOT_NULL((condition = condition_new(CONDITION_KERNEL_VERSION, v, false, false)));
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, v, false, false)));
+ ASSERT_OK_ZERO(condition_test(condition, environ));
+ condition_free(condition);
+}
+
+TEST(condition_test_version) {
+ Condition *condition;
+ const char *v;
+ char ver[8];
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd *thisreallyshouldntbeinthesystemdversion*", false, false)));
+ ASSERT_OK_ZERO(condition_test(condition, environ));
+ condition_free(condition);
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd *", false, false)));
+ ASSERT_OK_POSITIVE(condition_test(condition, environ));
+ condition_free(condition);
+
+ /* An artificially empty condition. It evaluates to true, but normally
+ * such condition cannot be created, because the condition list is reset instead. */
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "", false, false)));
+ ASSERT_OK_POSITIVE(condition_test(condition, environ));
+ condition_free(condition);
+
+ /* 42 would be a very very very old systemd release */
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd > 42", false, false)));
+ ASSERT_OK_POSITIVE(condition_test(condition, environ));
+ condition_free(condition);
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd>42", false, false)));
+ ASSERT_OK_POSITIVE(condition_test(condition, environ));
+ condition_free(condition);
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd '>42' '<9000'", false, false)));
+ ASSERT_OK_POSITIVE(condition_test(condition, environ));
+ condition_free(condition);
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd > 42 < 9000", false, false)));
+ ASSERT_ERROR(condition_test(condition, environ), EINVAL);
+ condition_free(condition);
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd>", false, false)));
+ ASSERT_ERROR(condition_test(condition, environ), EINVAL);
+ condition_free(condition);
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd >= 42", false, false)));
+ ASSERT_OK_POSITIVE(condition_test(condition, environ));
+ condition_free(condition);
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd < 42", false, false)));
+ ASSERT_OK_ZERO(condition_test(condition, environ));
+ condition_free(condition);
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd <= 42", false, false)));
+ ASSERT_OK_ZERO(condition_test(condition, environ));
+ condition_free(condition);
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd = 42", false, false)));
+ ASSERT_OK_ZERO(condition_test(condition, environ));
+ condition_free(condition);
+
+ /* 9000 is a very very very future systemd release */
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd < 9000", false, false)));
+ ASSERT_OK_POSITIVE(condition_test(condition, environ));
+ condition_free(condition);
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd <= 9000", false, false)));
+ ASSERT_OK_POSITIVE(condition_test(condition, environ));
+ condition_free(condition);
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd = 9000", false, false)));
+ ASSERT_OK_ZERO(condition_test(condition, environ));
+ condition_free(condition);
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd > 9000", false, false)));
+ ASSERT_OK_ZERO(condition_test(condition, environ));
+ condition_free(condition);
+
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, "systemd >= 9000", false, false)));
+ ASSERT_OK_ZERO(condition_test(condition, environ));
+ condition_free(condition);
+
+ xsprintf(ver, "%d", PROJECT_VERSION);
+
+ v = strjoina("systemd>=", ver);
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, v, false, false)));
+ ASSERT_OK_POSITIVE(condition_test(condition, environ));
+ condition_free(condition);
+
+ v = strjoina("systemd = ", ver);
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, v, false, false)));
+ ASSERT_OK_POSITIVE(condition_test(condition, environ));
+ condition_free(condition);
+
+ v = strjoina("systemd<=", ver);
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, v, false, false)));
+ ASSERT_OK_POSITIVE(condition_test(condition, environ));
+ condition_free(condition);
+
+ v = strjoina("systemd > ", ver);
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, v, false, false)));
+ ASSERT_OK_ZERO(condition_test(condition, environ));
+ condition_free(condition);
+
+ v = strjoina("systemd < ", ver);
+ ASSERT_NOT_NULL((condition = condition_new(CONDITION_VERSION, v, false, false)));
ASSERT_OK_ZERO(condition_test(condition, environ));
condition_free(condition);
}
diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf
index 4f4a016a42..d7cb07ba00 100644
--- a/src/udev/net/link-config-gperf.gperf
+++ b/src/udev/net/link-config-gperf.gperf
@@ -34,7 +34,8 @@ Match.Property, config_parse_match_property,
Match.Host, config_parse_net_condition, CONDITION_HOST, offsetof(LinkConfig, conditions)
Match.Virtualization, config_parse_net_condition, CONDITION_VIRTUALIZATION, offsetof(LinkConfig, conditions)
Match.KernelCommandLine, config_parse_net_condition, CONDITION_KERNEL_COMMAND_LINE, offsetof(LinkConfig, conditions)
-Match.KernelVersion, config_parse_net_condition, CONDITION_KERNEL_VERSION, offsetof(LinkConfig, conditions)
+Match.KernelVersion, config_parse_net_condition, CONDITION_VERSION, offsetof(LinkConfig, conditions)
+Match.Version, config_parse_net_condition, CONDITION_VERSION, offsetof(LinkConfig, conditions)
Match.Credential, config_parse_net_condition, CONDITION_CREDENTIAL, offsetof(LinkConfig, conditions)
Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(LinkConfig, conditions)
Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(LinkConfig, conditions)
diff --git a/test/test-execute/exec-basic.service b/test/test-execute/exec-basic.service
index a54aca9dc3..1fb300dc15 100644
--- a/test/test-execute/exec-basic.service
+++ b/test/test-execute/exec-basic.service
@@ -6,6 +6,21 @@ ConditionKernelVersion=">=2.0" "<=60" "!=1.4"
ConditionKernelVersion=" >= 2.0" " <= 60 " "!= 1.4"
ConditionKernelVersion=" >= 2.0" " * " "*.*"
+ConditionVersion=kernel ">=3.0"
+ConditionVersion=kernel ">=2.0" "<=60" "!=1.4"
+ConditionVersion=kernel " >= 2.0" " <= 60 " "!= 1.4"
+ConditionVersion=kernel " >= 2.0" " * " "*.*"
+
+ConditionVersion=">=3.0"
+ConditionVersion=">=2.0" "<=60" "!=1.4"
+ConditionVersion=" >= 2.0" " <= 60 " "!= 1.4"
+ConditionVersion=" >= 2.0" " * " "*.*"
+
+ConditionVersion=systemd ">=30"
+ConditionVersion=systemd ">=20" "<=9000" "!=14"
+ConditionVersion=systemd " >= 20" " <= 9000 " "!= 14"
+ConditionVersion=systemd " >= 20" " * "
+
[Service]
ExecStart=touch /tmp/a ; /bin/sh -c 'touch /tmp/b' ; touch /tmp/c
ExecStart=test -f /tmp/a