]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
analyze: add 'condition' verb
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 26 Jun 2019 12:58:45 +0000 (14:58 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 27 Jun 2019 08:54:37 +0000 (10:54 +0200)
We didn't have a straightforward way to parse and evaluate those strings.
Prompted by #12881.

man/systemd-analyze.xml
man/systemd.unit.xml
src/analyze/analyze-condition.c [new file with mode: 0644]
src/analyze/analyze-condition.h [new file with mode: 0644]
src/analyze/analyze.c
src/analyze/meson.build
src/core/load-fragment-gperf.gperf.m4

index 651a73848ee983f74b65e8902ee27564ee07d42b..5dce2ae8fb58368ac6e68f49a7300a71c472e216 100644 (file)
       <arg choice="opt" rep="repeat">OPTIONS</arg>
       <arg choice="plain">unit-paths</arg>
     </cmdsynopsis>
+    <cmdsynopsis>
+      <command>systemd-analyze</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+      <arg choice="plain">condition</arg>
+      <arg choice="plain"><replaceable>CONDITION</replaceable>…</arg>
+    </cmdsynopsis>
     <cmdsynopsis>
       <command>systemd-analyze</command>
       <arg choice="opt" rep="repeat">OPTIONS</arg>
@@ -348,6 +354,33 @@ $ eog targets.svg</programlisting>
       to retrieve the actual list that the manager uses, with any empty directories omitted.</para>
     </refsect2>
 
+    <refsect2>
+      <title><command>systemd-analyze condition <replaceable>CONDITION</replaceable>...</command></title>
+
+      <para>This command will evaluate <varname noindex='true'>Condition*=...</varname> and
+      <varname noindex='true'>Assert*=...</varname> assignments, and print their values, and
+      the resulting value of the combined condition set. See
+      <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+      for a list of available conditions and asserts.</para>
+
+      <example>
+        <title>Evaluate conditions that check kernel versions</title>
+
+        <programlisting>$ systemd-analyze condition 'ConditionKernelVersion = ! &lt;4.0' \
+        'ConditionKernelVersion = &gt;=5.1' \
+        'ConditionACPower=|false' \
+        'ConditionArchitecture=|!arm' \
+        'AssertPathExists=/etc/os-release'
+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.
+Conditions succeeded.</programlisting>
+      </example>
+    </refsect2>
+
     <refsect2>
       <title><command>systemd-analyze syscall-filter <optional><replaceable>SET</replaceable>...</optional></command></title>
 
index 81bce696bd3c1e8d35b41df9fb266124cce7656e..045931038bb7c04aa61e89523ff0ca125c27e1e7 100644 (file)
         exclamation mark, the pipe symbol must be passed first, the exclamation second. Except for
         <varname>ConditionPathIsSymbolicLink=</varname>, all path checks follow symlinks. If any of these
         options is assigned the empty string, the list of conditions is reset completely, all previous
-        condition settings (of any kind) will have no effect.</para>
+        condition settings (of any kind) will have no effect. The <command>condition</command> verb of
+        <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        can be used to test condition and assert expressions.</para>
 
         <para><varname>ConditionArchitecture=</varname> may be used to
         check whether the system is running on a specific
         <para>Note that neither assertion nor condition expressions result in unit state changes. Also note that both
         are checked at the time the job is to be executed, i.e. long after depending jobs and it itself were
         queued. Thus, neither condition nor assertion expressions are suitable for conditionalizing unit
-        dependencies.</para></listitem>
+        dependencies.</para>
+
+        <para>The <command>condition</command> verb of
+        <citerefentry><refentrytitle>systemd-analyze</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        can be used to test condition and assert expressions.</para></listitem>
       </varlistentry>
 
       <varlistentry>
diff --git a/src/analyze/analyze-condition.c b/src/analyze/analyze-condition.c
new file mode 100644 (file)
index 0000000..d0cefa0
--- /dev/null
@@ -0,0 +1,155 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <stdlib.h>
+
+#include "analyze-condition.h"
+#include "condition.h"
+#include "conf-parser.h"
+#include "load-fragment.h"
+#include "service.h"
+
+typedef struct condition_definition {
+        const char *name;
+        ConfigParserCallback parser;
+        ConditionType type;
+} condition_definition;
+
+static const condition_definition condition_definitions[] = {
+        { "ConditionPathExists",             config_parse_unit_condition_path,   CONDITION_PATH_EXISTS              },
+        { "ConditionPathExistsGlob",         config_parse_unit_condition_path,   CONDITION_PATH_EXISTS_GLOB         },
+        { "ConditionPathIsDirectory",        config_parse_unit_condition_path,   CONDITION_PATH_IS_DIRECTORY        },
+        { "ConditionPathIsSymbolicLink",     config_parse_unit_condition_path,   CONDITION_PATH_IS_SYMBOLIC_LINK    },
+        { "ConditionPathIsMountPoint",       config_parse_unit_condition_path,   CONDITION_PATH_IS_MOUNT_POINT      },
+        { "ConditionPathIsReadWrite",        config_parse_unit_condition_path,   CONDITION_PATH_IS_READ_WRITE       },
+        { "ConditionDirectoryNotEmpty",      config_parse_unit_condition_path,   CONDITION_DIRECTORY_NOT_EMPTY      },
+        { "ConditionFileNotEmpty",           config_parse_unit_condition_path,   CONDITION_FILE_NOT_EMPTY           },
+        { "ConditionFileIsExecutable",       config_parse_unit_condition_path,   CONDITION_FILE_IS_EXECUTABLE       },
+        { "ConditionNeedsUpdate",            config_parse_unit_condition_path,   CONDITION_NEEDS_UPDATE             },
+        { "ConditionFirstBoot",              config_parse_unit_condition_string, CONDITION_FIRST_BOOT               },
+        { "ConditionKernelCommandLine",      config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE      },
+        { "ConditionKernelVersion",          config_parse_unit_condition_string, CONDITION_KERNEL_VERSION           },
+        { "ConditionArchitecture",           config_parse_unit_condition_string, CONDITION_ARCHITECTURE             },
+        { "ConditionVirtualization",         config_parse_unit_condition_string, CONDITION_VIRTUALIZATION           },
+        { "ConditionSecurity",               config_parse_unit_condition_string, CONDITION_SECURITY                 },
+        { "ConditionCapability",             config_parse_unit_condition_string, CONDITION_CAPABILITY               },
+        { "ConditionHost",                   config_parse_unit_condition_string, CONDITION_HOST                     },
+        { "ConditionACPower",                config_parse_unit_condition_string, CONDITION_AC_POWER                 },
+        { "ConditionUser",                   config_parse_unit_condition_string, CONDITION_USER                     },
+        { "ConditionGroup",                  config_parse_unit_condition_string, CONDITION_GROUP                    },
+        { "ConditionControlGroupController", config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER },
+
+        { "AssertPathExists",                config_parse_unit_condition_path,   CONDITION_PATH_EXISTS              },
+        { "AssertPathExistsGlob",            config_parse_unit_condition_path,   CONDITION_PATH_EXISTS_GLOB         },
+        { "AssertPathIsDirectory",           config_parse_unit_condition_path,   CONDITION_PATH_IS_DIRECTORY        },
+        { "AssertPathIsSymbolicLink",        config_parse_unit_condition_path,   CONDITION_PATH_IS_SYMBOLIC_LINK    },
+        { "AssertPathIsMountPoint",          config_parse_unit_condition_path,   CONDITION_PATH_IS_MOUNT_POINT      },
+        { "AssertPathIsReadWrite",           config_parse_unit_condition_path,   CONDITION_PATH_IS_READ_WRITE       },
+        { "AssertDirectoryNotEmpty",         config_parse_unit_condition_path,   CONDITION_DIRECTORY_NOT_EMPTY      },
+        { "AssertFileNotEmpty",              config_parse_unit_condition_path,   CONDITION_FILE_NOT_EMPTY           },
+        { "AssertFileIsExecutable",          config_parse_unit_condition_path,   CONDITION_FILE_IS_EXECUTABLE       },
+        { "AssertNeedsUpdate",               config_parse_unit_condition_path,   CONDITION_NEEDS_UPDATE             },
+        { "AssertFirstBoot",                 config_parse_unit_condition_string, CONDITION_FIRST_BOOT               },
+        { "AssertKernelCommandLine",         config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE      },
+        { "AssertKernelVersion",             config_parse_unit_condition_string, CONDITION_KERNEL_VERSION           },
+        { "AssertArchitecture",              config_parse_unit_condition_string, CONDITION_ARCHITECTURE             },
+        { "AssertVirtualization",            config_parse_unit_condition_string, CONDITION_VIRTUALIZATION           },
+        { "AssertSecurity",                  config_parse_unit_condition_string, CONDITION_SECURITY                 },
+        { "AssertCapability",                config_parse_unit_condition_string, CONDITION_CAPABILITY               },
+        { "AssertHost",                      config_parse_unit_condition_string, CONDITION_HOST                     },
+        { "AssertACPower",                   config_parse_unit_condition_string, CONDITION_AC_POWER                 },
+        { "AssertUser",                      config_parse_unit_condition_string, CONDITION_USER                     },
+        { "AssertGroup",                     config_parse_unit_condition_string, CONDITION_GROUP                    },
+        { "AssertControlGroupController",    config_parse_unit_condition_string, CONDITION_CONTROL_GROUP_CONTROLLER },
+
+        /* deprecated, but we should still parse them */
+        { "ConditionNull",                   config_parse_unit_condition_null,   0                                  },
+        { "AssertNull",                      config_parse_unit_condition_null,   0                                  },
+};
+
+static int parse_condition(Unit *u, const char *line) {
+        const char *p;
+        Condition **target;
+
+        if ((p = startswith(line, "Condition")))
+                target = &u->conditions;
+        else if ((p = startswith(line, "Assert")))
+                target = &u->asserts;
+        else
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot parse \"%s\".", line);
+
+        for (size_t i = 0; i < ELEMENTSOF(condition_definitions); i++) {
+                const condition_definition *c = &condition_definitions[i];
+
+                p = startswith(line, c->name);
+                if (!p)
+                        continue;
+                p += strspn(p, WHITESPACE);
+                if (*p != '=')
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected \"=\" in \"%s\".", line);
+
+                p += 1 + strspn(p + 1, WHITESPACE);
+
+                return c->parser(NULL, "(stdin)", 0, NULL, 0, c->name, c->type, p, target, u);
+        }
+
+        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot parse \"%s\".", line);
+}
+
+_printf_(7, 8)
+static int log_helper(void *userdata, int level, int error, const char *file, int line, const char *func, const char *format, ...) {
+        Unit *u = userdata;
+        va_list ap;
+        int r;
+
+        assert(u);
+
+        /* "upgrade" debug messages */
+        level = MIN(LOG_INFO, level);
+
+        va_start(ap, format);
+        r = log_object_internalv(level, error, file, line, func,
+                                 NULL,
+                                 u->id,
+                                 NULL,
+                                 NULL,
+                                 format, ap);
+        va_end(ap);
+
+        return r;
+}
+
+int verify_conditions(char **lines, UnitFileScope scope) {
+        _cleanup_(manager_freep) Manager *m = NULL;
+        Unit *u;
+        char **line;
+        int r, q = 1;
+
+        r = manager_new(scope, MANAGER_TEST_RUN_MINIMAL, &m);
+        if (r < 0)
+                return log_error_errno(r, "Failed to initialize manager: %m");
+
+        log_debug("Starting manager...");
+        r = manager_startup(m, NULL, NULL);
+        if (r < 0)
+                return r;
+
+        r = unit_new_for_name(m, sizeof(Service), "test.service", &u);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create test.service: %m");
+
+        STRV_FOREACH(line, lines) {
+                r = parse_condition(u, *line);
+                if (r < 0)
+                        return r;
+        }
+
+        r = condition_test_list(u->asserts, assert_type_to_string, log_helper, u);
+        if (u->asserts)
+                log_notice("Asserts %s.", r > 0 ? "succeeded" : "failed");
+
+        q = condition_test_list(u->conditions, condition_type_to_string, log_helper, u);
+        if (u->conditions)
+                log_notice("Conditions %s.", q > 0 ? "succeeded" : "failed");
+
+        return r > 0 && q > 0 ? 0 : -EIO;
+}
diff --git a/src/analyze/analyze-condition.h b/src/analyze/analyze-condition.h
new file mode 100644 (file)
index 0000000..2ef278e
--- /dev/null
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "install.h"
+
+int verify_conditions(char **lines, UnitFileScope scope);
index 5217a92b438583fb2148c7d3b83b7161d9c963e9..40f54f9d46cf657657dbd8232466db5b3e6e50ec 100644 (file)
@@ -13,6 +13,7 @@
 #include "sd-bus.h"
 
 #include "alloc-util.h"
+#include "analyze-condition.h"
 #include "analyze-security.h"
 #include "analyze-verify.h"
 #include "build.h"
@@ -1897,6 +1898,10 @@ static int service_watchdogs(int argc, char *argv[], void *userdata) {
         return 0;
 }
 
+static int do_condition(int argc, char *argv[], void *userdata) {
+        return verify_conditions(strv_skip(argv, 1), arg_scope);
+}
+
 static int do_verify(int argc, char *argv[], void *userdata) {
         return verify_units(strv_skip(argv, 1), arg_scope, arg_man, arg_generators);
 }
@@ -1955,6 +1960,7 @@ static int help(int argc, char *argv[], void *userdata) {
                "  cat-config               Show configuration file and drop-ins\n"
                "  unit-paths               List load directories for units\n"
                "  syscall-filter [NAME...] Print list of syscalls in seccomp filter\n"
+               "  condition CONDITION...   Evaluate conditions and asserts\n"
                "  verify FILE...           Check unit files for correctness\n"
                "  service-watchdogs [BOOL] Get/set service watchdog state\n"
                "  calendar SPEC...         Validate repetitive calendar time events\n"
@@ -2157,6 +2163,7 @@ static int run(int argc, char *argv[]) {
                 { "cat-config",        2,        VERB_ANY, 0,            cat_config             },
                 { "unit-paths",        1,        1,        0,            dump_unit_paths        },
                 { "syscall-filter",    VERB_ANY, VERB_ANY, 0,            dump_syscall_filters   },
+                { "condition",         2,        VERB_ANY, 0,            do_condition           },
                 { "verify",            2,        VERB_ANY, 0,            do_verify              },
                 { "calendar",          2,        VERB_ANY, 0,            test_calendar          },
                 { "timestamp",         2,        VERB_ANY, 0,            test_timestamp         },
index 4db4dfa5526ad72bc38e05538e6e318a18702c25..58760d609b36650bd45ac964b4dd0538dd288686 100644 (file)
@@ -2,6 +2,8 @@
 
 systemd_analyze_sources = files('''
         analyze.c
+        analyze-condition.c
+        analyze-condition.h
         analyze-verify.c
         analyze-verify.h
         analyze-security.c
index f7906b374ac795ba5bfb22964e952bb242f1c05a..19ee56662c5626a69049e070dc4794ab703d91cf 100644 (file)
@@ -254,6 +254,7 @@ Unit.SuccessAction,              config_parse_emergency_action,      0,
 Unit.FailureActionExitStatus,    config_parse_exit_status,           0,                             offsetof(Unit, failure_action_exit_status)
 Unit.SuccessActionExitStatus,    config_parse_exit_status,           0,                             offsetof(Unit, success_action_exit_status)
 Unit.RebootArgument,             config_parse_unit_string_printf,    0,                             offsetof(Unit, reboot_arg)
+m4_dnl Also add any conditions to condition_definitions[] in src/analyze/analyze-condition.c.
 Unit.ConditionPathExists,        config_parse_unit_condition_path,   CONDITION_PATH_EXISTS,         offsetof(Unit, conditions)
 Unit.ConditionPathExistsGlob,    config_parse_unit_condition_path,   CONDITION_PATH_EXISTS_GLOB,    offsetof(Unit, conditions)
 Unit.ConditionPathIsDirectory,   config_parse_unit_condition_path,   CONDITION_PATH_IS_DIRECTORY,   offsetof(Unit, conditions)