core: add ConditionOSRelease= directive

This commit is contained in:
Luca Boccassi 2021-02-22 18:20:37 +00:00
parent 70b6ee6110
commit 1e26f8a60b
7 changed files with 227 additions and 0 deletions

View File

@ -1646,6 +1646,19 @@
</listitem>
</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>
@ -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

View File

@ -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)

View File

@ -23,6 +23,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"
@ -34,6 +35,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"
@ -262,6 +264,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;
@ -934,6 +991,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;
@ -1058,6 +1116,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);
@ -1091,6 +1150,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);

View File

@ -21,6 +21,7 @@ typedef enum ConditionType {
CONDITION_CPUS,
CONDITION_ENVIRONMENT,
CONDITION_CPU_FEATURE,
CONDITION_OS_RELEASE,
CONDITION_NEEDS_UPDATE,
CONDITION_FIRST_BOOT,

View File

@ -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;
}

View File

@ -21,6 +21,7 @@ AssertHost=
AssertKernelCommandLine=
AssertKernelVersion=
AssertNeedsUpdate=
AssertOSRelease=
AssertPathExists=
AssertPathExistsGlob=
AssertPathIsDirectory=
@ -64,6 +65,7 @@ ConditionHost=
ConditionKernelCommandLine=
ConditionKernelVersion=
ConditionNeedsUpdate=
ConditionOSRelease=
ConditionPathExists=
ConditionPathExistsGlob=
ConditionPathIsDirectory=

View File

@ -18,6 +18,7 @@ AssertKernelCommandLine=
AssertKernelVersion=
AssertMemory=
AssertNeedsUpdate=
AssertOSRelease=
AssertPathExists=
AssertPathExistsGlob=
AssertPathIsDirectory=
@ -50,6 +51,7 @@ ConditionKernelCommandLine=
ConditionKernelVersion=
ConditionMemory=
ConditionNeedsUpdate=
ConditionOSRelease=
ConditionPathExists=
ConditionPathExistsGlob=
ConditionPathIsDirectory=