summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
7 files changed, 274 insertions, 9 deletions
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) {