summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2021-06-25 09:22:50 +0200
committerGitHub <noreply@github.com>2021-06-25 09:22:50 +0200
commita2e2917162e7ca587cbad94b5366f5cee0385b29 (patch)
tree83181f2197b2c1e2fbe26b15ade4f75a3abd7f32
parentde61a04b188f81a85cdb5c64ddb4987dcd9d30d3 (diff)
parent1e26f8a60bd0f3e5acf1931968af053eebb9a454 (diff)
downloadsystemd-a2e2917162e7ca587cbad94b5366f5cee0385b29.tar.gz
Merge pull request #19941 from bluca/condition_os_release
core: add ConditionOSRelease= directive
-rw-r--r--man/systemd.unit.xml14
-rw-r--r--src/basic/extract-word.c21
-rw-r--r--src/basic/extract-word.h1
-rw-r--r--src/core/load-fragment-gperf.gperf.in2
-rw-r--r--src/shared/condition.c60
-rw-r--r--src/shared/condition.h1
-rw-r--r--src/test/test-condition.c146
-rw-r--r--src/test/test-extract-word.c52
-rw-r--r--test/fuzz/fuzz-unit-file/directives-all.service2
-rw-r--r--test/fuzz/fuzz-unit-file/directives.service2
10 files changed, 292 insertions, 9 deletions
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index a17b752d54..78ba9731dd 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -1647,6 +1647,19 @@
</varlistentry>
<varlistentry>
+ <term><varname>ConditionOSRelease=</varname></term>
+
+ <listitem><para>Verify that a specific <literal>key=value</literal> pair is set in the host's
+ <citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+ <para>Other than exact matching with <literal>=</literal>, and <literal>!=</literal>, relative
+ comparisons are supported for versioned parameters (e.g. <literal>VERSION_ID</literal>). The
+ comparator can be one of <literal>&lt;</literal>, <literal>&lt;=</literal>, <literal>=</literal>,
+ <literal>!=</literal>, <literal>&gt;=</literal> and <literal>&gt;</literal>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><varname>AssertArchitecture=</varname></term>
<term><varname>AssertVirtualization=</varname></term>
<term><varname>AssertHost=</varname></term>
@@ -1673,6 +1686,7 @@
<term><varname>AssertControlGroupController=</varname></term>
<term><varname>AssertMemory=</varname></term>
<term><varname>AssertCPUs=</varname></term>
+ <term><varname>AssertOSRelease=</varname></term>
<listitem><para>Similar to the <varname>ConditionArchitecture=</varname>,
<varname>ConditionVirtualization=</varname>, …, condition settings described above, these settings
diff --git a/src/basic/extract-word.c b/src/basic/extract-word.c
index 0c440db691..9f9bb0c791 100644
--- a/src/basic/extract-word.c
+++ b/src/basic/extract-word.c
@@ -51,7 +51,8 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
goto finish_force_terminate;
else if (strchr(separators, c)) {
if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
- (*p)++;
+ if (!(flags & EXTRACT_RETAIN_SEPARATORS))
+ (*p)++;
goto finish_force_next;
}
} else {
@@ -153,16 +154,18 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
break;
} else if (strchr(separators, c)) {
if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) {
- (*p)++;
+ if (!(flags & EXTRACT_RETAIN_SEPARATORS))
+ (*p)++;
goto finish_force_next;
}
- /* Skip additional coalesced separators. */
- for (;; (*p)++, c = **p) {
- if (c == 0)
- goto finish_force_terminate;
- if (!strchr(separators, c))
- break;
- }
+ if (!(flags & EXTRACT_RETAIN_SEPARATORS))
+ /* Skip additional coalesced separators. */
+ for (;; (*p)++, c = **p) {
+ if (c == 0)
+ goto finish_force_terminate;
+ if (!strchr(separators, c))
+ break;
+ }
goto finish;
}
diff --git a/src/basic/extract-word.h b/src/basic/extract-word.h
index f415872fac..c82ad761ef 100644
--- a/src/basic/extract-word.h
+++ b/src/basic/extract-word.h
@@ -12,6 +12,7 @@ typedef enum ExtractFlags {
EXTRACT_UNQUOTE = 1 << 5, /* Ignore separators in quoting with "" and '', and remove the quotes. */
EXTRACT_DONT_COALESCE_SEPARATORS = 1 << 6, /* Don't treat multiple adjacent separators as one */
EXTRACT_RETAIN_ESCAPE = 1 << 7, /* Treat escape character '\' as any other character without special meaning */
+ EXTRACT_RETAIN_SEPARATORS = 1 << 8, /* Do not advance the original string pointer past the separator(s) */
/* Note that if no flags are specified, escaped escape characters will be silently stripped. */
} ExtractFlags;
diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in
index 17f2bea5b8..d343145fa9 100644
--- a/src/core/load-fragment-gperf.gperf.in
+++ b/src/core/load-fragment-gperf.gperf.in
@@ -330,6 +330,7 @@ Unit.ConditionEnvironment, config_parse_unit_condition_string,
Unit.ConditionUser, config_parse_unit_condition_string, CONDITION_USER, offsetof(Unit, conditions)
Unit.ConditionGroup, config_parse_unit_condition_string, CONDITION_GROUP, offsetof(Unit, conditions)
Unit.ConditionControlGroupController, config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER, offsetof(Unit, conditions)
+Unit.ConditionOSRelease, config_parse_unit_condition_string, CONDITION_OS_RELEASE, offsetof(Unit, conditions)
Unit.AssertPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, asserts)
Unit.AssertPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, asserts)
Unit.AssertPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, offsetof(Unit, asserts)
@@ -356,6 +357,7 @@ Unit.AssertEnvironment, config_parse_unit_condition_string,
Unit.AssertUser, config_parse_unit_condition_string, CONDITION_USER, offsetof(Unit, asserts)
Unit.AssertGroup, config_parse_unit_condition_string, CONDITION_GROUP, offsetof(Unit, asserts)
Unit.AssertControlGroupController, config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER, offsetof(Unit, asserts)
+Unit.AssertOSRelease, config_parse_unit_condition_string, CONDITION_OS_RELEASE, offsetof(Unit, asserts)
Unit.CollectMode, config_parse_collect_mode, 0, offsetof(Unit, collect_mode)
Service.PIDFile, config_parse_pid_file, 0, offsetof(Service, pid_file)
Service.ExecCondition, config_parse_exec, SERVICE_EXEC_CONDITION, offsetof(Service, exec_command)
diff --git a/src/shared/condition.c b/src/shared/condition.c
index 55fb636673..ec9d57b292 100644
--- a/src/shared/condition.c
+++ b/src/shared/condition.c
@@ -24,6 +24,7 @@
#include "cpu-set-util.h"
#include "efi-loader.h"
#include "env-file.h"
+#include "env-util.h"
#include "extract-word.h"
#include "fd-util.h"
#include "fileio.h"
@@ -35,6 +36,7 @@
#include "list.h"
#include "macro.h"
#include "mountpoint-util.h"
+#include "os-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "proc-cmdline.h"
@@ -263,6 +265,61 @@ static int condition_test_kernel_version(Condition *c, char **env) {
return true;
}
+static int condition_test_osrelease(Condition *c, char **env) {
+ const char *parameter = c->parameter;
+ int r;
+
+ assert(c);
+ assert(c->parameter);
+ assert(c->type == CONDITION_OS_RELEASE);
+
+ for (;;) {
+ _cleanup_free_ char *key = NULL, *condition = NULL, *actual_value = NULL;
+ OrderOperator order;
+ const char *word;
+ bool matches;
+
+ r = extract_first_word(&parameter, &condition, NULL, EXTRACT_UNQUOTE);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse parameter: %m");
+ if (r == 0)
+ break;
+
+ /* parse_order() needs the string to start with the comparators */
+ word = condition;
+ r = extract_first_word(&word, &key, "!<=>", EXTRACT_RETAIN_SEPARATORS);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse parameter: %m");
+ /* The os-release spec mandates env-var-like key names */
+ if (r == 0 || isempty(word) || !env_name_is_valid(key))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Failed to parse parameter, key/value format expected: %m");
+
+ /* Do not allow whitespace after the separator, as that's not a valid os-release format */
+ order = parse_order(&word);
+ if (order < 0 || isempty(word) || strchr(WHITESPACE, *word) != NULL)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Failed to parse parameter, key/value format expected: %m");
+
+ r = parse_os_release(NULL, key, &actual_value);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse os-release: %m");
+
+ /* Might not be comparing versions, so do exact string matching */
+ if (order == ORDER_EQUAL)
+ matches = streq_ptr(actual_value, word);
+ else if (order == ORDER_UNEQUAL)
+ matches = !streq_ptr(actual_value, word);
+ else
+ matches = test_order(strverscmp_improved(actual_value, word), order);
+
+ if (!matches)
+ return false;
+ }
+
+ return true;
+}
+
static int condition_test_memory(Condition *c, char **env) {
OrderOperator order;
uint64_t m, k;
@@ -935,6 +992,7 @@ int condition_test(Condition *c, char **env) {
[CONDITION_MEMORY] = condition_test_memory,
[CONDITION_ENVIRONMENT] = condition_test_environment,
[CONDITION_CPU_FEATURE] = condition_test_cpufeature,
+ [CONDITION_OS_RELEASE] = condition_test_osrelease,
};
int r, b;
@@ -1059,6 +1117,7 @@ static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
[CONDITION_MEMORY] = "ConditionMemory",
[CONDITION_ENVIRONMENT] = "ConditionEnvironment",
[CONDITION_CPU_FEATURE] = "ConditionCPUFeature",
+ [CONDITION_OS_RELEASE] = "ConditionOSRelease",
};
DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);
@@ -1092,6 +1151,7 @@ static const char* const assert_type_table[_CONDITION_TYPE_MAX] = {
[CONDITION_MEMORY] = "AssertMemory",
[CONDITION_ENVIRONMENT] = "AssertEnvironment",
[CONDITION_CPU_FEATURE] = "AssertCPUFeature",
+ [CONDITION_OS_RELEASE] = "AssertOSRelease",
};
DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType);
diff --git a/src/shared/condition.h b/src/shared/condition.h
index 6678689d7c..3a5420c402 100644
--- a/src/shared/condition.h
+++ b/src/shared/condition.h
@@ -21,6 +21,7 @@ typedef enum ConditionType {
CONDITION_CPUS,
CONDITION_ENVIRONMENT,
CONDITION_CPU_FEATURE,
+ CONDITION_OS_RELEASE,
CONDITION_NEEDS_UPDATE,
CONDITION_FIRST_BOOT,
diff --git a/src/test/test-condition.c b/src/test/test-condition.c
index adba383fdd..d1dee22bd6 100644
--- a/src/test/test-condition.c
+++ b/src/test/test-condition.c
@@ -23,6 +23,7 @@
#include "log.h"
#include "macro.h"
#include "nulstr-util.h"
+#include "os-util.h"
#include "process-util.h"
#include "selinux-util.h"
#include "set.h"
@@ -890,6 +891,150 @@ static void test_condition_test_environment(void) {
test_condition_test_environment_one("EXISTINGENVVAR=", false);
}
+static void test_condition_test_os_release(void) {
+ _cleanup_strv_free_ char **os_release_pairs = NULL;
+ _cleanup_free_ char *version_id = NULL;
+ const char *key_value_pair;
+ Condition *condition;
+
+ /* Should not happen, but it's a test so we don't know the environment. */
+ if (load_os_release_pairs(NULL, &os_release_pairs) < 0)
+ return;
+ if (strv_length(os_release_pairs) < 2)
+ return;
+
+ condition = condition_new(CONDITION_OS_RELEASE, "_THISHOPEFULLYWONTEXIST=01234 56789", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_OS_RELEASE, "WRONG FORMAT", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_OS_RELEASE, "WRONG!<>=FORMAT", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_OS_RELEASE, "WRONG FORMAT=", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_OS_RELEASE, "WRONG =FORMAT", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_OS_RELEASE, "WRONG = FORMAT", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_OS_RELEASE, "WRONGFORMAT= ", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_OS_RELEASE, "WRO NG=FORMAT", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == -EINVAL);
+ condition_free(condition);
+
+ condition = condition_new(CONDITION_OS_RELEASE, "", false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ /* load_os_release_pairs() removes quotes, we have to add them back,
+ * otherwise we get a string: "PRETTY_NAME=Debian GNU/Linux 10 (buster)"
+ * which is wrong, as the value is not quoted anymore. */
+ const char *quote = strchr(os_release_pairs[1], ' ') ? "\"" : "";
+ key_value_pair = strjoina(os_release_pairs[0], "=", quote, os_release_pairs[1], quote);
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ key_value_pair = strjoina(os_release_pairs[0], "!=", quote, os_release_pairs[1], quote);
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+
+ /* Some distros (eg: Arch) do not set VERSION_ID */
+ if (parse_os_release(NULL, "VERSION_ID", &version_id) <= 0)
+ return;
+
+ key_value_pair = strjoina("VERSION_ID", "=", version_id);
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ key_value_pair = strjoina("VERSION_ID", "!=", version_id);
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+
+ key_value_pair = strjoina("VERSION_ID", "<=", version_id);
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ key_value_pair = strjoina("VERSION_ID", ">=", version_id);
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ key_value_pair = strjoina("VERSION_ID", "<", version_id, ".1");
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ key_value_pair = strjoina("VERSION_ID", ">", version_id, ".1");
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+
+ key_value_pair = strjoina("VERSION_ID", "=", version_id, " ", os_release_pairs[0], "=", quote, os_release_pairs[1], quote);
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+
+ key_value_pair = strjoina("VERSION_ID", "!=", version_id, " ", os_release_pairs[0], "=", quote, os_release_pairs[1], quote);
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+
+ key_value_pair = strjoina("VERSION_ID", "=", version_id, " ", os_release_pairs[0], "!=", quote, os_release_pairs[1], quote);
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+
+ key_value_pair = strjoina("VERSION_ID", "!=", version_id, " ", os_release_pairs[0], "!=", quote, os_release_pairs[1], quote);
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ) == 0);
+ condition_free(condition);
+
+ key_value_pair = strjoina("VERSION_ID", "<", version_id, ".1", " ", os_release_pairs[0], "=", quote, os_release_pairs[1], quote);
+ condition = condition_new(CONDITION_OS_RELEASE, key_value_pair, false, false);
+ assert_se(condition);
+ assert_se(condition_test(condition, environ));
+ condition_free(condition);
+}
+
int main(int argc, char *argv[]) {
test_setup_logging(LOG_DEBUG);
@@ -912,6 +1057,7 @@ int main(int argc, char *argv[]) {
#if defined(__i386__) || defined(__x86_64__)
test_condition_test_cpufeature();
#endif
+ test_condition_test_os_release();
return 0;
}
diff --git a/src/test/test-extract-word.c b/src/test/test-extract-word.c
index a392ec7588..8cf0b63a4c 100644
--- a/src/test/test-extract-word.c
+++ b/src/test/test-extract-word.c
@@ -482,6 +482,58 @@ static void test_extract_first_word(void) {
assert_se(extract_first_word(&p, &t, ",", EXTRACT_UNQUOTE) > 0);
assert_se(streq(t, "context=system_u:object_r:svirt_sandbox_file_t:s0:c0,c1"));
free(t);
+
+ p = "a:b";
+ assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS) == 1);
+ assert_se(streq(t, "a"));
+ assert_se(streq(p, ":b"));
+ free(t);
+ assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS) == 1);
+ assert_se(streq(t, "b"));
+ free(t);
+
+ p = "a>:b";
+ assert_se(extract_first_word(&p, &t, ">:", EXTRACT_RETAIN_SEPARATORS) == 1);
+ assert_se(streq(t, "a"));
+ assert_se(streq(p, ">:b"));
+ free(t);
+ assert_se(extract_first_word(&p, &t, ">:", EXTRACT_RETAIN_SEPARATORS) == 1);
+ assert_se(streq(t, "b"));
+ free(t);
+
+ p = "a>:b";
+ assert_se(extract_first_word(&p, &t, ">:", EXTRACT_RETAIN_SEPARATORS|EXTRACT_DONT_COALESCE_SEPARATORS) == 1);
+ assert_se(streq(t, "a"));
+ assert_se(streq(p, ">:b"));
+ free(t);
+ assert_se(extract_first_word(&p, &t, ">:", EXTRACT_RETAIN_SEPARATORS|EXTRACT_DONT_COALESCE_SEPARATORS) == 1);
+ assert_se(streq(t, ""));
+ assert_se(streq(p, ">:b"));
+ free(t);
+
+ p = "a\\:b";
+ assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS|EXTRACT_RETAIN_ESCAPE) == 1);
+ assert_se(streq(t, "a\\"));
+ assert_se(streq(p, ":b"));
+ free(t);
+
+ p = "a\\:b";
+ assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS) == 1);
+ assert_se(streq(t, "a:b"));
+ assert_se(!p);
+ free(t);
+
+ p = "a\\:b";
+ assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS|EXTRACT_UNESCAPE_SEPARATORS) == 1);
+ assert_se(streq(t, "a:b"));
+ assert_se(!p);
+ free(t);
+
+ p = "a\\:a:b";
+ assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS|EXTRACT_UNESCAPE_SEPARATORS) == 1);
+ assert_se(streq(t, "a:a"));
+ assert_se(streq(p, ":b"));
+ free(t);
}
static void test_extract_first_word_and_warn(void) {
diff --git a/test/fuzz/fuzz-unit-file/directives-all.service b/test/fuzz/fuzz-unit-file/directives-all.service
index a152bebd73..3039d1c0cd 100644
--- a/test/fuzz/fuzz-unit-file/directives-all.service
+++ b/test/fuzz/fuzz-unit-file/directives-all.service
@@ -21,6 +21,7 @@ AssertHost=
AssertKernelCommandLine=
AssertKernelVersion=
AssertNeedsUpdate=
+AssertOSRelease=
AssertPathExists=
AssertPathExistsGlob=
AssertPathIsDirectory=
@@ -64,6 +65,7 @@ ConditionHost=
ConditionKernelCommandLine=
ConditionKernelVersion=
ConditionNeedsUpdate=
+ConditionOSRelease=
ConditionPathExists=
ConditionPathExistsGlob=
ConditionPathIsDirectory=
diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service
index 1ce6967c65..b5df300a6b 100644
--- a/test/fuzz/fuzz-unit-file/directives.service
+++ b/test/fuzz/fuzz-unit-file/directives.service
@@ -18,6 +18,7 @@ AssertKernelCommandLine=
AssertKernelVersion=
AssertMemory=
AssertNeedsUpdate=
+AssertOSRelease=
AssertPathExists=
AssertPathExistsGlob=
AssertPathIsDirectory=
@@ -50,6 +51,7 @@ ConditionKernelCommandLine=
ConditionKernelVersion=
ConditionMemory=
ConditionNeedsUpdate=
+ConditionOSRelease=
ConditionPathExists=
ConditionPathExistsGlob=
ConditionPathIsDirectory=