summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--man/systemd-analyze.xml33
-rw-r--r--man/systemd.unit.xml10
-rw-r--r--src/analyze/analyze-condition.c155
-rw-r--r--src/analyze/analyze-condition.h6
-rw-r--r--src/analyze/analyze.c7
-rw-r--r--src/analyze/meson.build2
-rw-r--r--src/core/load-fragment-gperf.gperf.m41
7 files changed, 212 insertions, 2 deletions
diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml
index 651a73848e..5dce2ae8fb 100644
--- a/man/systemd-analyze.xml
+++ b/man/systemd-analyze.xml
@@ -86,6 +86,12 @@
<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>
<arg choice="plain">syscall-filter</arg>
<arg choice="opt"><replaceable>SET</replaceable>…</arg>
</cmdsynopsis>
@@ -349,6 +355,33 @@ $ eog targets.svg</programlisting>
</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>
<para>This command will list system calls contained in the specified system call set
diff --git a/man/systemd.unit.xml b/man/systemd.unit.xml
index 81bce696bd..045931038b 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -1030,7 +1030,9 @@
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
@@ -1326,7 +1328,11 @@
<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
index 0000000000..d0cefa0992
--- /dev/null
+++ b/src/analyze/analyze-condition.c
@@ -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
index 0000000000..2ef278eb5c
--- /dev/null
+++ b/src/analyze/analyze-condition.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "install.h"
+
+int verify_conditions(char **lines, UnitFileScope scope);
diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c
index 5217a92b43..40f54f9d46 100644
--- a/src/analyze/analyze.c
+++ b/src/analyze/analyze.c
@@ -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 },
diff --git a/src/analyze/meson.build b/src/analyze/meson.build
index 4db4dfa552..58760d609b 100644
--- a/src/analyze/meson.build
+++ b/src/analyze/meson.build
@@ -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
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index f7906b374a..19ee56662c 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -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)