]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: add ConditionOSRelease= directive 19941/head
authorLuca Boccassi <luca.boccassi@microsoft.com>
Mon, 22 Feb 2021 18:20:37 +0000 (18:20 +0000)
committerLuca Boccassi <luca.boccassi@microsoft.com>
Thu, 24 Jun 2021 12:57:48 +0000 (13:57 +0100)
man/systemd.unit.xml
src/core/load-fragment-gperf.gperf.in
src/shared/condition.c
src/shared/condition.h
src/test/test-condition.c
test/fuzz/fuzz-unit-file/directives-all.service
test/fuzz/fuzz-unit-file/directives.service

index a17b752d54d7877dd177a2633a4b410ae66d7bfc..78ba9731dd5a526df76a8ab28ae153aaa84d1844 100644 (file)
           </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>
           <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
index 17f2bea5b823b9f8dec49cec99856276af2d55e4..d343145fa99ac5a084ac9f100770d1346858a122 100644 (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)
index b86312548d06191ea987325c5de4f46fc5ea021e..7ca9e92eaa89407042c6a90c52bcb8fa854f8fb9 100644 (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);
index 6678689d7c3a53038039baaebbb96c9f7ecdcaf9..3a5420c402c1cff615666d29bdfd52db0b79293f 100644 (file)
@@ -21,6 +21,7 @@ typedef enum ConditionType {
         CONDITION_CPUS,
         CONDITION_ENVIRONMENT,
         CONDITION_CPU_FEATURE,
+        CONDITION_OS_RELEASE,
 
         CONDITION_NEEDS_UPDATE,
         CONDITION_FIRST_BOOT,
index adba383fddbc2556c0215406549984e07b4379df..d1dee22bd62969d75665a173981efb2ac46bdde1 100644 (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;
 }
index a152bebd734f6ba02b786046006225da410bbcdf..3039d1c0cdcf9890a79965f64dcbe9c76528193b 100644 (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=
index 1ce6967c659a6d794be0a81f5650b60b128f0e79..b5df300a6bcf536ef69437fc7a0214f2bed44c34 100644 (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=