From 3cc3dc7736727973df852c4bdf7d312c2e752e5e Mon Sep 17 00:00:00 2001 From: Maanya Goenka Date: Mon, 26 Jul 2021 13:02:17 -0700 Subject: [PATCH] systemd-analyze: option to exit with an error when 'verify' fails MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 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 dc93ac4e72e..48976f52bf9 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 872518add0d..1b9447a125b 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 88a09d84b9d..0dd080afb74 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 9ef6868367d..cd5377200b0 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 6b3d6a5ab5d..09d3aea02c9 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 8d637ff8de9..ceb18db7406 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 7f7e7a2d606..5fd2c5dcb4d 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 9a17cd6c3d7..b34bdffd1b8 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 92498b0f20e..3d6aa6457a1 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" -- 2.47.3