summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYu Watanabe <watanabe.yu+github@gmail.com>2019-06-28 02:37:20 +0900
committerGitHub <noreply@github.com>2019-06-28 02:37:20 +0900
commit22800b473e9eef3bb5e439beb1aa06b8bb279704 (patch)
tree355dc2b763faf412773333bac3c719d190ce0e9c
parentdc2c231684c898ba360dc6a39010010dd1dbb645 (diff)
parent9266f31e61b6b6bda7929911e8e3d2fc87587911 (diff)
downloadsystemd-22800b473e9eef3bb5e439beb1aa06b8bb279704.tar.gz
systemd-22800b473e9eef3bb5e439beb1aa06b8bb279704.tar.bz2
systemd-22800b473e9eef3bb5e439beb1aa06b8bb279704.zip
Merge pull request #12889 from keszybz/analyze-condition
Add systemd-analyze condition
-rw-r--r--man/systemd-analyze.xml33
-rw-r--r--man/systemd.unit.xml40
-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
-rw-r--r--src/core/load-fragment.c8
-rw-r--r--src/core/manager.c6
-rw-r--r--src/core/unit-printf.c8
-rw-r--r--src/shared/condition.c7
11 files changed, 242 insertions, 31 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 38a239a0e8..045931038b 100644
--- a/man/systemd.unit.xml
+++ b/man/systemd.unit.xml
@@ -1007,9 +1007,7 @@
<term><varname>ConditionMemory=</varname></term>
<term><varname>ConditionCPUs=</varname></term>
- <!-- We do not document ConditionNull=
- here, as it is not particularly
- useful and probably just
+ <!-- We do not document ConditionNull= here, as it is not particularly useful and probably just
confusing. -->
<listitem><para>Before starting a unit, verify that the specified condition is true. If it is not true, the
@@ -1024,6 +1022,18 @@
conditions are considered to be in a clean state and will be garbage collected if they are not referenced.
This means, that when queried, the condition failure may or may not show up in the state of the unit.</para>
+ <para>If multiple conditions are specified, the unit will be executed if all of them apply (i.e. a
+ logical AND is applied). Condition checks can be prefixed with a pipe symbol (<literal>|</literal>)
+ in which case a condition becomes a triggering condition. If at least one triggering condition is
+ defined for a unit, then the unit will be executed if at least one of the triggering conditions apply
+ and all of the non-triggering conditions. If you prefix an argument with the pipe symbol and an
+ 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. 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
architecture. Takes one of
@@ -1279,23 +1289,7 @@
comparison operator. On physical systems the number of CPUs in the affinity mask of the service
manager usually matches the number of physical CPUs, but in special and virtual environments might
differ. In particular, in containers the affinity mask usually matches the number of CPUs assigned to
- the container and not the physically available ones.</para>
-
- <para>If multiple conditions are specified, the unit will be
- executed if all of them apply (i.e. a logical AND is applied).
- Condition checks can be prefixed with a pipe symbol (|) in
- which case a condition becomes a triggering condition. If at
- least one triggering condition is defined for a unit, then the
- unit will be executed if at least one of the triggering
- conditions apply and all of the non-triggering conditions. If
- you prefix an argument with the pipe symbol and an 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></listitem>
+ the container and not the physically available ones.</para></listitem>
</varlistentry>
<varlistentry>
@@ -1334,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)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 8e8f26b636..d88b9317e9 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -2587,13 +2587,13 @@ int config_parse_unit_condition_string(
return 0;
}
- trigger = rvalue[0] == '|';
+ trigger = *rvalue == '|';
if (trigger)
- rvalue++;
+ rvalue += 1 + strspn(rvalue + 1, WHITESPACE);
- negate = rvalue[0] == '!';
+ negate = *rvalue == '!';
if (negate)
- rvalue++;
+ rvalue += 1 + strspn(rvalue + 1, WHITESPACE);
r = unit_full_printf(u, rvalue, &s);
if (r < 0) {
diff --git a/src/core/manager.c b/src/core/manager.c
index 78f03a824c..0c1adf2850 100644
--- a/src/core/manager.c
+++ b/src/core/manager.c
@@ -1378,6 +1378,9 @@ static void manager_enumerate_perpetual(Manager *m) {
assert(m);
+ if (m->test_run_flags == MANAGER_TEST_RUN_MINIMAL)
+ return;
+
/* Let's ask every type to load all units from disk/kernel that it might know */
for (c = 0; c < _UNIT_TYPE_MAX; c++) {
if (!unit_type_supported(c)) {
@@ -1395,6 +1398,9 @@ static void manager_enumerate(Manager *m) {
assert(m);
+ if (m->test_run_flags == MANAGER_TEST_RUN_MINIMAL)
+ return;
+
/* Let's ask every type to load all units from disk/kernel that it might know */
for (c = 0; c < _UNIT_TYPE_MAX; c++) {
if (!unit_type_supported(c)) {
diff --git a/src/core/unit-printf.c b/src/core/unit-printf.c
index 72391ace3a..ffa12d8a92 100644
--- a/src/core/unit-printf.c
+++ b/src/core/unit-printf.c
@@ -244,6 +244,10 @@ int unit_full_printf(Unit *u, const char *format, char **ret) {
* before or after the relevant configuration setting. Hence: don't add them.
*/
+ assert(u);
+ assert(format);
+ assert(ret);
+
const Specifier table[] = {
{ 'n', specifier_string, u->id },
{ 'N', specifier_prefix_and_instance, NULL },
@@ -281,9 +285,5 @@ int unit_full_printf(Unit *u, const char *format, char **ret) {
{}
};
- assert(u);
- assert(format);
- assert(ret);
-
return specifier_printf(format, table, u, ret);
}
diff --git a/src/shared/condition.c b/src/shared/condition.c
index 2d521bc8c6..70ede533c0 100644
--- a/src/shared/condition.c
+++ b/src/shared/condition.c
@@ -747,20 +747,23 @@ bool condition_test_list(Condition *first, const char *(*to_string)(ConditionTyp
r = condition_test(c);
if (logger) {
+ const char *p = c->type == CONDITION_NULL ? "true" : c->parameter;
+ assert(p);
+
if (r < 0)
logger(userdata, LOG_WARNING, r, __FILE__, __LINE__, __func__,
"Couldn't determine result for %s=%s%s%s, assuming failed: %m",
to_string(c->type),
c->trigger ? "|" : "",
c->negate ? "!" : "",
- c->parameter);
+ p);
else
logger(userdata, LOG_DEBUG, 0, __FILE__, __LINE__, __func__,
"%s=%s%s%s %s.",
to_string(c->type),
c->trigger ? "|" : "",
c->negate ? "!" : "",
- c->parameter,
+ p,
condition_result_to_string(c->result));
}