From 1545051c79104365a22c9449a470e96c8658689b Mon Sep 17 00:00:00 2001 From: Maanya Goenka Date: Thu, 29 Jul 2021 13:01:08 -0700 Subject: [PATCH 1/3] manager: use FLAGS_SET when checking for MANAGER_TEST_RUN_MINIMAL Allows multiple flags to be set, for example, in systemd-analyze. --- src/core/manager.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/manager.c b/src/core/manager.c index e67f320bca..63679268fb 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -1541,7 +1541,7 @@ Manager* manager_free(Manager *m) { static void manager_enumerate_perpetual(Manager *m) { assert(m); - if (m->test_run_flags == MANAGER_TEST_RUN_MINIMAL) + if (FLAGS_SET(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 */ @@ -1559,7 +1559,7 @@ static void manager_enumerate_perpetual(Manager *m) { static void manager_enumerate(Manager *m) { assert(m); - if (m->test_run_flags == MANAGER_TEST_RUN_MINIMAL) + if (FLAGS_SET(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 */ From f14d6810e0a08a1ed5e9f1a213d71f9a531ccf50 Mon Sep 17 00:00:00 2001 From: Maanya Goenka Date: Wed, 7 Jul 2021 18:28:20 -0700 Subject: [PATCH 2/3] manager: add a test flag to ignore dependencies The MANAGER_TEST_RUN_IGNORE_DEPENDENCIES flag was added in order to allow the caller to skip the recursive loading of dependency units when loading specific unit files. This includes the default dependencies, the specified dependencies, the slice. This will be used by systemd-analyze to allow checking individual unit files in isolation. --- src/core/manager.h | 11 ++++++----- src/core/unit.c | 12 ++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/core/manager.h b/src/core/manager.h index 4ce4368474..1220c9fb16 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -128,11 +128,12 @@ typedef enum WatchdogType { #include "unit-name.h" typedef enum ManagerTestRunFlags { - MANAGER_TEST_NORMAL = 0, /* run normally */ - MANAGER_TEST_RUN_MINIMAL = 1 << 0, /* create basic data structures */ - MANAGER_TEST_RUN_BASIC = 1 << 1, /* interact with the environment */ - MANAGER_TEST_RUN_ENV_GENERATORS = 1 << 2, /* also run env generators */ - MANAGER_TEST_RUN_GENERATORS = 1 << 3, /* also run unit generators */ + MANAGER_TEST_NORMAL = 0, /* run normally */ + MANAGER_TEST_RUN_MINIMAL = 1 << 0, /* create basic data structures */ + MANAGER_TEST_RUN_BASIC = 1 << 1, /* interact with the environment */ + MANAGER_TEST_RUN_ENV_GENERATORS = 1 << 2, /* also run env generators */ + MANAGER_TEST_RUN_GENERATORS = 1 << 3, /* also run unit generators */ + MANAGER_TEST_RUN_IGNORE_DEPENDENCIES = 1 << 4, /* run while ignoring dependencies */ MANAGER_TEST_FULL = MANAGER_TEST_RUN_BASIC | MANAGER_TEST_RUN_ENV_GENERATORS | MANAGER_TEST_RUN_GENERATORS, } ManagerTestRunFlags; diff --git a/src/core/unit.c b/src/core/unit.c index 1b9fb079b4..7c39e4d0f8 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -3053,6 +3053,9 @@ int unit_add_dependency( return 0; } + if (u->manager && FLAGS_SET(u->manager->test_run_flags, MANAGER_TEST_RUN_IGNORE_DEPENDENCIES)) + return 0; + /* Note that ordering a device unit after a unit is permitted since it allows to start its job * running timeout at a specific time. */ if (FLAGS_SET(a, UNIT_ATOM_BEFORE) && other->type == UNIT_DEVICE) { @@ -3176,6 +3179,9 @@ int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, boo if (r < 0) return r; + if (u->manager && FLAGS_SET(u->manager->test_run_flags, MANAGER_TEST_RUN_IGNORE_DEPENDENCIES)) + return 0; + r = manager_load_unit(u->manager, name, NULL, NULL, &other); if (r < 0) return r; @@ -3195,6 +3201,9 @@ int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency if (r < 0) return r; + if (u->manager && FLAGS_SET(u->manager->test_run_flags, MANAGER_TEST_RUN_IGNORE_DEPENDENCIES)) + return 0; + r = manager_load_unit(u->manager, name, NULL, NULL, &other); if (r < 0) return r; @@ -3312,6 +3321,9 @@ int unit_set_default_slice(Unit *u) { assert(u); + if (u->manager && FLAGS_SET(u->manager->test_run_flags, MANAGER_TEST_RUN_IGNORE_DEPENDENCIES)) + return 0; + if (UNIT_GET_SLICE(u)) return 0; From 3cc3dc7736727973df852c4bdf7d312c2e752e5e Mon Sep 17 00:00:00 2001 From: Maanya Goenka Date: Mon, 26 Jul 2021 13:02:17 -0700 Subject: [PATCH 3/3] systemd-analyze: option to exit with an error when 'verify' fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit introduces a callback invoked from log_syntax_internal. Use it from systemd-analyze to gather a list of units that contain syntax warnings. A new command line option is added to make use of this. The new option --recursive-errors takes in three possible modes: 1. yes - which is the default. systemd-analyze exits with an error when syntax warnings arise during verification of the specified units or any of their dependencies. 3. no - systemd-analyze exits with an error when syntax warnings arise during verification of only the selected unit. Analyzing and loading any dependencies will be skipped. 4. one - systemd-analyze exits with an error when syntax warnings arise during verification of only the selected units and their direct dependencies. Below are two service unit files that I created for the purposes of testing: 1. First, we run the commands on a unit that does not have dependencies but has a non-existing key-value setting (i.e. foo = bar). > cat <testcase.service [Unit] foo = bar [Service] ExecStart = echo hello EOF OUTPUT: maanya-goenka@debian:~/systemd (log-error)$ sudo build/systemd-analyze verify testcase.service /home/maanya-goenka/systemd/testcase.service:2: Unknown key name 'foo' in section 'Unit', ignoring. /usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'. Support for KillMode=none is deprecated and will eventually be removed. /usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating /var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly. /usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your unit file, and consider removing the setting altogether. maanya-goenka@debian:~/systemd (log-error)$ echo $? 1 maanya-goenka@debian:~/systemd (log-error)$ sudo build/systemd-analyze verify --recursive-errors=yes testcase.service /home/maanya-goenka/systemd/testcase.service:2: Unknown key name 'foo' in section 'Unit', ignoring. /usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'. Support for KillMode=none is deprecated and will eventually be removed. /usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating /var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly. /usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your unit file, and consider removing the setting altogether. maanya-goenka@debian:~/systemd (log-error)$ echo $? 1 maanya-goenka@debian:~/systemd (log-error)$ sudo build/systemd-analyze verify --recursive-errors=no testcase.service /home/maanya-goenka/systemd/testcase.service:2: Unknown key name 'foo' in section 'Unit', ignoring. maanya-goenka@debian:~/systemd (log-error)$ echo $? 1 maanya-goenka@debian:~/systemd (log-error)$ sudo build/systemd-analyze verify --recursive-errors=one testcase.service /home/maanya-goenka/systemd/testcase.service:2: Unknown key name 'foo' in section 'Unit', ignoring. /usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'. Support for KillMode=none is deprecated and will eventually be removed. /usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating /var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly. /usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your unit file, and consider removing the setting altogether. maanya-goenka@debian:~/systemd (log-error)$ echo $? 1 2. Next, we run the commands on a unit that is syntactically valid but has a non-existing dependency (i.e. foo2.service) > cat <foobar.service [Unit] Requires = foo2.service [Service] ExecStart = echo hello EOF OUTPUT: maanya-goenka@debian:~/systemd (log-error)$ sudo build/systemd-analyze verify foobar.service /usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'. Support for KillMode=none is deprecated and will eventually be removed. /usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating /var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly. /usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your unit file, and consider removing the setting altogether. foobar.service: Failed to create foobar.service/start: Unit foo2.service not found. maanya-goenka@debian:~/systemd (log-error)$ echo $? 1 maanya-goenka@debian:~/systemd (log-error)$ sudo build/systemd-analyze verify --recursive-errors=yes foobar.service /usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'. Support for KillMode=none is deprecated and will eventually be removed. /usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating /var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly. /usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your unit file, and consider removing the setting altogether. foobar.service: Failed to create foobar.service/start: Unit foo2.service not found. maanya-goenka@debian:~/systemd (log-error)$ echo $? 1 maanya-goenka@debian:~/systemd (log-error)$ sudo build/systemd-analyze verify --recursive-errors=no foobar.service maanya-goenka@debian:~/systemd (log-error)$ echo $? 0 maanya-goenka@debian:~/systemd (log-error)$ sudo build/systemd-analyze verify --recursive-errors=one foobar.service /usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'. Support for KillMode=none is deprecated and will eventually be removed. /usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating /var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly. /usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your unit file, and consider removing the setting altogether. foobar.service: Failed to create foobar.service/start: Unit foo2.service not found. maanya-goenka@debian:~/systemd (log-error)$ echo $? 1 --- man/systemd-analyze.xml | 12 +++ shell-completion/bash/systemd-analyze | 2 +- shell-completion/zsh/_systemd-analyze | 1 + src/analyze/analyze-verify.c | 65 +++++++++++- src/analyze/analyze-verify.h | 13 ++- src/analyze/analyze.c | 137 +++++++++++++++----------- src/basic/log.c | 14 +++ src/basic/log.h | 9 ++ src/basic/macro.h | 6 ++ 9 files changed, 199 insertions(+), 60 deletions(-) diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index dc93ac4e72..48976f52bf 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -744,6 +744,18 @@ Service b@0.service not loaded, b.socket cannot be started. generators enabled will generally result in some warnings. + + + + Control verification of units and their dependencies and whether + systemd-analyze verify exits with a non-zero process exit status or not. With + yes, return a non-zero process exit status when warnings arise during verification + of either the specified unit or any of its associated dependencies. This is the default. With + no, return a non-zero process exit status when warnings arise during verification + of only the specified unit. With one, return a non-zero process exit status when + warnings arise during verification of either the specified unit or its immediate dependencies. + + diff --git a/shell-completion/bash/systemd-analyze b/shell-completion/bash/systemd-analyze index 872518add0..1b9447a125 100644 --- a/shell-completion/bash/systemd-analyze +++ b/shell-completion/bash/systemd-analyze @@ -125,7 +125,7 @@ _systemd_analyze() { elif __contains_word "$verb" ${VERBS[VERIFY]}; then if [[ $cur = -* ]]; then - comps='--help --version --system --user --global --man=no --generators=yes --root --image' + comps='--help --version --system --user --global --man=no --generators=yes --root --image --recursive-errors=no --recursive-errors=yes --recursive-errors=one' else comps=$( compgen -A file -- "$cur" ) compopt -o filenames diff --git a/shell-completion/zsh/_systemd-analyze b/shell-completion/zsh/_systemd-analyze index 88a09d84b9..0dd080afb7 100644 --- a/shell-completion/zsh/_systemd-analyze +++ b/shell-completion/zsh/_systemd-analyze @@ -89,6 +89,7 @@ _arguments \ '--global[Show global user instance config]' \ '--root=[Add support for root argument]:PATH' \ '--image=[Add support for discrete images]:PATH' \ + '--recursive-errors=[When verifying a unit, control dependency verification]:MODE' \ '--no-pager[Do not pipe output into a pager]' \ '--man=[Do (not) check for existence of man pages]:boolean:(1 0)' \ '--order[When generating graph for dot, show only order]' \ diff --git a/src/analyze/analyze-verify.c b/src/analyze/analyze-verify.c index 9ef6868367..cd5377200b 100644 --- a/src/analyze/analyze-verify.c +++ b/src/analyze/analyze-verify.c @@ -11,10 +11,28 @@ #include "manager.h" #include "pager.h" #include "path-util.h" +#include "string-table.h" #include "strv.h" #include "unit-name.h" #include "unit-serialize.h" +static void log_syntax_callback(const char *unit, int level, void *userdata) { + Set **s = userdata; + int r; + + assert(userdata); + assert(unit); + + if (level > LOG_WARNING) + return; + + r = set_put_strdup(s, unit); + if (r < 0) { + set_free_free(*s); + *s = POINTER_MAX; + } +} + static int prepare_filename(const char *filename, char **ret) { int r; const char *name; @@ -218,13 +236,22 @@ static int verify_unit(Unit *u, bool check_man, const char *root) { return r; } -int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run_generators, const char *root) { +static void set_destroy_ignore_pointer_max(Set** s) { + if (*s == POINTER_MAX) + return; + set_free_free(*s); +} + +int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run_generators, RecursiveErrors recursive_errors, const char *root) { const ManagerTestRunFlags flags = MANAGER_TEST_RUN_MINIMAL | MANAGER_TEST_RUN_ENV_GENERATORS | + (recursive_errors == RECURSIVE_ERRORS_NO) * MANAGER_TEST_RUN_IGNORE_DEPENDENCIES | run_generators * MANAGER_TEST_RUN_GENERATORS; _cleanup_(manager_freep) Manager *m = NULL; + _cleanup_(set_destroy_ignore_pointer_max) Set *s = NULL; + _unused_ _cleanup_(clear_log_syntax_callback) dummy_t dummy; Unit *units[strv_length(filenames)]; _cleanup_free_ char *var = NULL; int r, k, i, count = 0; @@ -233,6 +260,11 @@ int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run if (strv_isempty(filenames)) return 0; + /* Allow systemd-analyze to hook in a callback function so that it can get + * all the required log data from the function itself without having to rely + * on a global set variable for the same */ + set_log_syntax_callback(log_syntax_callback, &s); + /* set the path */ r = generate_path(&var, filenames); if (r < 0) @@ -283,5 +315,34 @@ int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run r = k; } - return r; + if (s == POINTER_MAX) + return log_oom(); + + if (set_isempty(s) || r != 0) + return r; + + /* If all previous verifications succeeded, then either the recursive parsing of all the + * associated dependencies with RECURSIVE_ERRORS_YES or the parsing of the specified unit file + * with RECURSIVE_ERRORS_NO must have yielded a syntax warning and hence, a non-empty set. */ + if (IN_SET(recursive_errors, RECURSIVE_ERRORS_YES, RECURSIVE_ERRORS_NO)) + return -ENOTRECOVERABLE; + + /* If all previous verifications succeeded, then the non-empty set could have resulted from + * a syntax warning encountered during the recursive parsing of the specified unit file and + * its direct dependencies. Hence, search for any of the filenames in the set and if found, + * return a non-zero process exit status. */ + if (recursive_errors == RECURSIVE_ERRORS_ONE) + STRV_FOREACH(filename, filenames) + if (set_contains(s, basename(*filename))) + return -ENOTRECOVERABLE; + + return 0; } + +static const char* const recursive_errors_table[_RECURSIVE_ERRORS_MAX] = { + [RECURSIVE_ERRORS_NO] = "no", + [RECURSIVE_ERRORS_YES] = "yes", + [RECURSIVE_ERRORS_ONE] = "one", +}; + +DEFINE_STRING_TABLE_LOOKUP(recursive_errors, RecursiveErrors); diff --git a/src/analyze/analyze-verify.h b/src/analyze/analyze-verify.h index 6b3d6a5ab5..09d3aea02c 100644 --- a/src/analyze/analyze-verify.h +++ b/src/analyze/analyze-verify.h @@ -6,5 +6,16 @@ #include "execute.h" #include "path-lookup.h" +typedef enum RecursiveErrors { + RECURSIVE_ERRORS_YES, /* Look for errors in all associated units */ + RECURSIVE_ERRORS_NO, /* Don't look for errors in any but the selected unit */ + RECURSIVE_ERRORS_ONE, /* Look for errors in the selected unit and its direct dependencies */ + _RECURSIVE_ERRORS_MAX, + _RECURSIVE_ERRORS_INVALID = -EINVAL, +} RecursiveErrors; + int verify_executable(Unit *u, const ExecCommand *exec, const char *root); -int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run_generators, const char *root); +int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run_generators, RecursiveErrors recursive_errors, const char *root); + +const char* recursive_errors_to_string(RecursiveErrors i) _const_; +RecursiveErrors recursive_errors_from_string(const char *s) _pure_; diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 8d637ff8de..ceb18db740 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -46,6 +46,7 @@ #endif #include "sort-util.h" #include "special.h" +#include "string-table.h" #include "strv.h" #include "strxcpyx.h" #include "terminal-util.h" @@ -85,6 +86,7 @@ static PagerFlags arg_pager_flags = 0; static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; static const char *arg_host = NULL; static UnitFileScope arg_scope = UNIT_FILE_SYSTEM; +static RecursiveErrors arg_recursive_errors = RECURSIVE_ERRORS_YES; static bool arg_man = true; static bool arg_generators = false; static char *arg_root = NULL; @@ -2145,7 +2147,7 @@ static int do_condition(int argc, char *argv[], void *userdata) { } static int do_verify(int argc, char *argv[], void *userdata) { - return verify_units(strv_skip(argv, 1), arg_scope, arg_man, arg_generators, arg_root); + return verify_units(strv_skip(argv, 1), arg_scope, arg_man, arg_generators, arg_recursive_errors, arg_root); } static int do_security(int argc, char *argv[], void *userdata) { @@ -2179,43 +2181,52 @@ static int help(int argc, char *argv[], void *userdata) { printf("%s [OPTIONS...] COMMAND ...\n\n" "%sProfile systemd, show unit dependencies, check unit files.%s\n" "\nCommands:\n" - " [time] Print time required to boot the machine\n" - " blame Print list of running units ordered by time to init\n" - " critical-chain [UNIT...] Print a tree of the time critical chain of units\n" - " plot Output SVG graphic showing service initialization\n" - " dot [UNIT...] Output dependency graph in %s format\n" - " dump Output state serialization of service manager\n" - " cat-config Show configuration file and drop-ins\n" - " unit-files List files and symlinks for units\n" - " unit-paths List load directories for units\n" - " exit-status [STATUS...] List exit status definitions\n" - " capability [CAP...] List capability definitions\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" - " calendar SPEC... Validate repetitive calendar time events\n" - " timestamp TIMESTAMP... Validate a timestamp\n" - " timespan SPAN... Validate a time span\n" - " security [UNIT...] Analyze security of unit\n" + " [time] Print time required to boot the machine\n" + " blame Print list of running units ordered by\n" + " time to init\n" + " critical-chain [UNIT...] Print a tree of the time critical chain\n" + " of units\n" + " plot Output SVG graphic showing service\n" + " initialization\n" + " dot [UNIT...] Output dependency graph in %s format\n" + " dump Output state serialization of service\n" + " manager\n" + " cat-config Show configuration file and drop-ins\n" + " unit-files List files and symlinks for units\n" + " unit-paths List load directories for units\n" + " exit-status [STATUS...] List exit status definitions\n" + " capability [CAP...] List capability definitions\n" + " syscall-filter [NAME...] Print list of syscalls in seccomp\n" + " filter\n" + " condition CONDITION... Evaluate conditions and asserts\n" + " verify FILE... Check unit files for correctness\n" + " calendar SPEC... Validate repetitive calendar time\n" + " events\n" + " timestamp TIMESTAMP... Validate a timestamp\n" + " timespan SPAN... Validate a time span\n" + " security [UNIT...] Analyze security of unit\n" "\nOptions:\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --system Operate on system systemd instance\n" - " --user Operate on user systemd instance\n" - " --global Operate on global user configuration\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --order Show only order in the graph\n" - " --require Show only requirement in the graph\n" - " --from-pattern=GLOB Show only origins in the graph\n" - " --to-pattern=GLOB Show only destinations in the graph\n" - " --fuzz=SECONDS Also print services which finished SECONDS earlier\n" - " than the latest in the branch\n" - " --man[=BOOL] Do [not] check for existence of man pages\n" - " --generators[=BOOL] Do [not] run unit generators (requires privileges)\n" - " --iterations=N Show the specified number of iterations\n" - " --base-time=TIMESTAMP Calculate calendar times relative to specified time\n" + " -h --help Show this help\n" + " --recursive-errors=MODE Control which units are verified\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --system Operate on system systemd instance\n" + " --user Operate on user systemd instance\n" + " --global Operate on global user configuration\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --order Show only order in the graph\n" + " --require Show only requirement in the graph\n" + " --from-pattern=GLOB Show only origins in the graph\n" + " --to-pattern=GLOB Show only destinations in the graph\n" + " --fuzz=SECONDS Also print services which finished SECONDS\n" + " earlier than the latest in the branch\n" + " --man[=BOOL] Do [not] check for existence of man pages\n" + " --generators[=BOOL] Do [not] run unit generators\n" + " (requires privileges)\n" + " --iterations=N Show the specified number of iterations\n" + " --base-time=TIMESTAMP Calculate calendar times relative to\n" + " specified time\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -2247,28 +2258,30 @@ static int parse_argv(int argc, char *argv[]) { ARG_GENERATORS, ARG_ITERATIONS, ARG_BASE_TIME, + ARG_RECURSIVE_ERRORS, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "order", no_argument, NULL, ARG_ORDER }, - { "require", no_argument, NULL, ARG_REQUIRE }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "global", no_argument, NULL, ARG_GLOBAL }, - { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN }, - { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN }, - { "fuzz", required_argument, NULL, ARG_FUZZ }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "man", optional_argument, NULL, ARG_MAN }, - { "generators", optional_argument, NULL, ARG_GENERATORS }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "iterations", required_argument, NULL, ARG_ITERATIONS }, - { "base-time", required_argument, NULL, ARG_BASE_TIME }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "order", no_argument, NULL, ARG_ORDER }, + { "require", no_argument, NULL, ARG_REQUIRE }, + { "root", required_argument, NULL, ARG_ROOT }, + { "image", required_argument, NULL, ARG_IMAGE }, + { "recursive-errors", required_argument, NULL, ARG_RECURSIVE_ERRORS }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "global", no_argument, NULL, ARG_GLOBAL }, + { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN }, + { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN }, + { "fuzz", required_argument, NULL, ARG_FUZZ }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "man", optional_argument, NULL, ARG_MAN }, + { "generators", optional_argument, NULL, ARG_GENERATORS }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "iterations", required_argument, NULL, ARG_ITERATIONS }, + { "base-time", required_argument, NULL, ARG_BASE_TIME }, {} }; @@ -2283,6 +2296,18 @@ static int parse_argv(int argc, char *argv[]) { case 'h': return help(0, NULL, NULL); + case ARG_RECURSIVE_ERRORS: + if (streq(optarg, "help")) { + DUMP_STRING_TABLE(recursive_errors, RecursiveErrors, _RECURSIVE_ERRORS_MAX); + return 0; + } + r = recursive_errors_from_string(optarg); + if (r < 0) + return log_error_errno(r, "Unknown mode passed to --recursive-errors='%s'.", optarg); + + arg_recursive_errors = r; + break; + case ARG_VERSION: return version(); diff --git a/src/basic/log.c b/src/basic/log.c index 7f7e7a2d60..5fd2c5dcb4 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -39,6 +39,9 @@ #define SNDBUF_SIZE (8*1024*1024) +static log_syntax_callback_t log_syntax_callback = NULL; +static void *log_syntax_callback_userdata = NULL; + static LogTarget log_target = LOG_TARGET_CONSOLE; static int log_max_level = LOG_INFO; static int log_facility = LOG_DAEMON; @@ -1341,6 +1344,14 @@ void log_received_signal(int level, const struct signalfd_siginfo *si) { signal_to_string(si->ssi_signo)); } +void set_log_syntax_callback(log_syntax_callback_t cb, void *userdata) { + assert(!log_syntax_callback || !cb); + assert(!log_syntax_callback_userdata || !userdata); + + log_syntax_callback = cb; + log_syntax_callback_userdata = userdata; +} + int log_syntax_internal( const char *unit, int level, @@ -1352,6 +1363,9 @@ int log_syntax_internal( const char *func, const char *format, ...) { + if (log_syntax_callback) + log_syntax_callback(unit, level, log_syntax_callback_userdata); + PROTECT_ERRNO; char buffer[LINE_MAX]; va_list ap; diff --git a/src/basic/log.h b/src/basic/log.h index 9a17cd6c3d..b34bdffd1b 100644 --- a/src/basic/log.h +++ b/src/basic/log.h @@ -32,6 +32,15 @@ typedef enum LogTarget{ #define IS_SYNTHETIC_ERRNO(val) ((val) >> 30 & 1) #define ERRNO_VALUE(val) (abs(val) & 255) +/* The callback function to be invoked when syntax warnings are seen + * in the unit files. */ +typedef void (*log_syntax_callback_t)(const char *unit, int level, void *userdata); +void set_log_syntax_callback(log_syntax_callback_t cb, void *userdata); + +static inline void clear_log_syntax_callback(dummy_t *dummy) { + set_log_syntax_callback(/* cb= */ NULL, /* userdata= */ NULL); +} + const char *log_target_to_string(LogTarget target) _const_; LogTarget log_target_from_string(const char *s) _pure_; void log_set_target(LogTarget target); diff --git a/src/basic/macro.h b/src/basic/macro.h index 92498b0f20..3d6aa6457a 100644 --- a/src/basic/macro.h +++ b/src/basic/macro.h @@ -483,4 +483,10 @@ static inline size_t size_add(size_t x, size_t y) { return y >= SIZE_MAX - x ? SIZE_MAX : x + y; } +typedef struct { + int _empty[0]; +} dummy_t; + +assert_cc(sizeof(dummy_t) == 0); + #include "log.h"