mirror of
https://github.com/systemd/systemd.git
synced 2024-12-02 23:03:50 +08:00
Merge pull request #12889 from keszybz/analyze-condition
Add systemd-analyze condition
This commit is contained in:
commit
22800b473e
@ -83,6 +83,12 @@
|
||||
<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 = ! <4.0' \
|
||||
'ConditionKernelVersion = >=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=>=5.1 succeeded.
|
||||
test.service: ConditionKernelVersion=!<4.0 succeeded.
|
||||
Conditions succeeded.</programlisting>
|
||||
</example>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title><command>systemd-analyze syscall-filter <optional><replaceable>SET</replaceable>...</optional></command></title>
|
||||
|
||||
|
@ -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>
|
||||
|
155
src/analyze/analyze-condition.c
Normal file
155
src/analyze/analyze-condition.c
Normal file
@ -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;
|
||||
}
|
6
src/analyze/analyze-condition.h
Normal file
6
src/analyze/analyze-condition.h
Normal file
@ -0,0 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include "install.h"
|
||||
|
||||
int verify_conditions(char **lines, UnitFileScope scope);
|
@ -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 },
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user