]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
condition: introduce ConditionVersion=/AssertVersion= 36468/head
authorMatteo Croce <teknoraver@meta.com>
Thu, 20 Feb 2025 12:07:24 +0000 (13:07 +0100)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 18 Mar 2025 09:36:59 +0000 (18:36 +0900)
Add a new condition wich checks against systemd version.
Change condition_test_kernel_version() into a generic condition_test_version()
so most of the code can be reused.

    $ systemctl --version
    systemd 258 (258~devel-g53ca5f6)

    $ systemd-analyze condition 'ConditionVersion=systemd>255'
    test.service: ConditionVersion=>255 succeeded.

    $ systemd-analyze condition 'ConditionVersion=systemd>260'
    test.service: ConditionVersion=>260 failed.

    $ systemd-analyze condition 'ConditionVersion=systemd>=258'
    test.service: ConditionVersion=>=258 succeeded.

    $ systemd-analyze condition 'ConditionVersion=systemd>=257.1'
    test.service: ConditionVersion=>=257.1 succeeded.

    $ uname -r
    6.12.13-200.fc41.aarch64

    $ systemd-analyze condition 'ConditionVersion=kernel > 4.4'
    test.service: ConditionVersion=kernel > 4.4 succeeded.

    $ systemd-analyze condition 'ConditionVersion=kernel > 6.20'
    test.service: ConditionVersion=kernel > 6.20 failed.

    $ systemd-analyze condition 'ConditionVersion=kernel < 9.0'
    test.service: ConditionVersion=kernel < 9.0 succeeded.

14 files changed:
docs/TRANSIENT-SETTINGS.md
man/systemd-analyze.xml
man/systemd.link.xml
man/systemd.netdev.xml
man/systemd.network.xml
man/systemd.unit.xml
src/core/load-fragment-gperf.gperf.in
src/network/netdev/netdev-gperf.gperf
src/network/networkd-network-gperf.gperf
src/shared/condition.c
src/shared/condition.h
src/test/test-condition.c
src/udev/net/link-config-gperf.gperf
test/test-execute/exec-basic.service

index ebb8ba536a3bba7f688f838174cdf4f47da04e92..4992acbf6732576d8d9023e3b10a8185fc508478 100644 (file)
@@ -69,6 +69,7 @@ Most generic unit settings are available for transient units.
 ✓ ConditionFirstBoot=
 ✓ ConditionKernelCommandLine=
 ✓ ConditionKernelVersion=
+✓ ConditionVersion=
 ✓ ConditionArchitecture=
 ✓ ConditionFirmware=
 ✓ ConditionVirtualization=
index a1250eb10ff56d5a8589ca383fb09e8a2ad6ad8c..b3975d5f03e10779b732edf9f67d8b09a75ac5d6 100644 (file)
@@ -525,8 +525,8 @@ cap_net_raw              13
       <example>
         <title>Evaluate conditions that check kernel versions</title>
 
-        <programlisting>$ systemd-analyze condition 'ConditionKernelVersion = ! &lt;4.0' \
-        'ConditionKernelVersion = &gt;=5.1' \
+        <programlisting>$ systemd-analyze condition 'ConditionVersion = ! &lt;4.0' \
+        'ConditionVersion = &gt;=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=&gt;=5.1 succeeded.
-test.service: ConditionKernelVersion=!&lt;4.0 succeeded.
+test.service: ConditionVersion=&gt;=5.1 succeeded.
+test.service: ConditionVersion=!&lt;4.0 succeeded.
 Conditions succeeded.</programlisting>
       </example>
     </refsect2>
index 5441da377e5e586b80e7929cb524a9937e580784..217352f979709a3832990c72d97feff477db9353 100644 (file)
         </listitem>
       </varlistentry>
 
+      <varlistentry id='version'>
+        <term><varname>Version=</varname></term>
+        <listitem>
+          <para>Checks whether a software version matches a certain expression. See
+          <varname>ConditionVersion=</varname> in
+          <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+          for details. When prefixed with an exclamation mark (<literal>!</literal>), the result is negated.
+          If an empty string is assigned, the previously assigned value is cleared.</para>
+
+          <xi:include href="version-info.xml" xpointer="v258"/>
+        </listitem>
+      </varlistentry>
+
       <varlistentry id='credential'>
         <term><varname>Credential=</varname></term>
         <listitem>
index b88c51b90e70b5f2dbc35008cabc3ee9c21aaec0..1b4673ab804d77fd9805f9a620706118ac3610d9 100644 (file)
       <xi:include href="systemd.link.xml" xpointer="virtualization" />
       <xi:include href="systemd.link.xml" xpointer="kernel-command-line" />
       <xi:include href="systemd.link.xml" xpointer="kernel-version" />
+      <xi:include href="systemd.link.xml" xpointer="version" />
       <xi:include href="systemd.link.xml" xpointer="credential" />
       <xi:include href="systemd.link.xml" xpointer="architecture" />
       <xi:include href="systemd.link.xml" xpointer="firmware" />
index c1618c0f94c27a3daa571ab4810384e1f164aa76..52194c4fc0c2bb8a3c7227d7c015db30832e5c1f 100644 (file)
       <xi:include href="systemd.link.xml" xpointer="virtualization" />
       <xi:include href="systemd.link.xml" xpointer="kernel-command-line" />
       <xi:include href="systemd.link.xml" xpointer="kernel-version" />
+      <xi:include href="systemd.link.xml" xpointer="version" />
       <xi:include href="systemd.link.xml" xpointer="credential" />
       <xi:include href="systemd.link.xml" xpointer="architecture" />
       <xi:include href="systemd.link.xml" xpointer="firmware" />
index e931ef6f76b2fb893ca548c7dbdbba542fcdfc57..5be9225a587fe4b0cadb785977fcb93fbdcf9ea4 100644 (file)
           </listitem>
         </varlistentry>
 
+        <varlistentry>
+          <term><varname>ConditionVersion=</varname></term>
+
+          <listitem><para><varname>ConditionVersion=</varname> 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
+          <literal>kernel</literal> and <literal>systemd</literal> are supported. If this argument is
+          omitted, <literal>kernel</literal> is implied. The second argument must be a list
+          of (potentially quoted) expressions. Each expression starts with one of <literal>=</literal> or
+          <literal>!=</literal> for string comparisons, <literal>&lt;</literal>, <literal>&lt;=</literal>,
+          <literal>==</literal>, <literal>&lt;&gt;</literal>, <literal>&gt;=</literal>,
+          <literal>&gt;</literal> for version comparisons, or <literal>$=</literal>, <literal>!$=</literal>
+          for a shell-style glob match. If no operator is specified, <literal>$=</literal> is implied.</para>
+
+          <xi:include href="version-info.xml" xpointer="v258"/>
+          </listitem>
+        </varlistentry>
+
         <varlistentry>
           <term><varname>ConditionCredential=</varname></term>
 
index 7344e56c4a2287f89e1a7147a8140fef19b87ac2..6307dbe83f183d970e730f38abb81c3b530d343b 100644 (file)
@@ -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)
index 612f463e337329f101b49a386c101e12464eaabb..4535c2cf3e0d2bb1ea95869e728b8a4b4d4f19aa 100644 (file)
@@ -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)
index bdbb3ad2c8a90b29ac0581ac3033c5b8baf5ab1d..8e99a059c1b978a78bf6f136595f50e92e51a0fb 100644 (file)
@@ -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)
index 983bda7d7b35489af3fe5e13f3725323ae43f80a..b65d64d300d852877de899bc11543db37f7776c3 100644 (file)
@@ -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_se(uname(&u) >= 0);
+        assert(condition);
+        assert(ver);
 
-        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,13 +1362,13 @@ 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] = {
+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_VERSION]                  = "ConditionVersion",
         [CONDITION_CREDENTIAL]               = "ConditionCredential",
         [CONDITION_SECURITY]                 = "ConditionSecurity",
         [CONDITION_CAPABILITY]               = "ConditionCapability",
@@ -1372,15 +1399,27 @@ static const char* const condition_type_table[_CONDITION_TYPE_MAX] = {
         [CONDITION_KERNEL_MODULE_LOADED]     = "ConditionKernelModuleLoaded",
 };
 
-DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType);
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(_condition_type, ConditionType);
+
+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;
 
-static const char* const assert_type_table[_CONDITION_TYPE_MAX] = {
+        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_KERNEL_VERSION]           = "AssertKernelVersion",
+        [CONDITION_VERSION]                  = "AssertVersion",
         [CONDITION_CREDENTIAL]               = "AssertCredential",
         [CONDITION_SECURITY]                 = "AssertSecurity",
         [CONDITION_CAPABILITY]               = "AssertCapability",
@@ -1411,7 +1450,19 @@ static const char* const assert_type_table[_CONDITION_TYPE_MAX] = {
         [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",
index 378028a73e2de42f438ba81a10e500049369629e..84b927384144c8f7e282ddae5e678d44fddb65ea 100644 (file)
@@ -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,
index 9334a8ca89ab3dbb853eff5dee9c4e874be1f1e9..a6132f1132b7b5aa208767fb4bd5fe74c85d14ba 100644 (file)
@@ -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);
 }
index 4f4a016a4258f46b83b8fda804c84c3acdeeb6b6..d7cb07ba00f76ff75cf140fdb18846b560cc0b1b 100644 (file)
@@ -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)
index a54aca9dc39f5de25986568bf0ac0b9993000c8b..1fb300dc152bceffeec5ad3c217ecdb25da86bc5 100644 (file)
@@ -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