From edfea9fe0db025d8b90f07d969b48a1017399265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 26 Jun 2019 14:58:45 +0200 Subject: analyze: add 'condition' verb We didn't have a straightforward way to parse and evaluate those strings. Prompted by #12881. --- src/analyze/analyze-condition.c | 155 ++++++++++++++++++++++++++++++++++++++++ src/analyze/analyze-condition.h | 6 ++ src/analyze/analyze.c | 7 ++ src/analyze/meson.build | 2 + 4 files changed, 170 insertions(+) create mode 100644 src/analyze/analyze-condition.c create mode 100644 src/analyze/analyze-condition.h (limited to 'src/analyze') 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 + +#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 -- cgit v1.2.3