Merge pull request #12889 from keszybz/analyze-condition

Add systemd-analyze condition
This commit is contained in:
Yu Watanabe
2019-06-28 02:37:20 +09:00
committed by GitHub
11 changed files with 242 additions and 31 deletions
+155
View 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
View File
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
#pragma once
#include "install.h"
int verify_conditions(char **lines, UnitFileScope scope);
+7
View 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 },
+2
View 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
+1
View 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)
+4 -4
View File
@@ -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) {
+6
View File
@@ -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)) {
+4 -4
View File
@@ -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);
}
+5 -2
View File
@@ -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));
}