]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
systemd-analyze: option to exit with an error when 'verify' fails 20233/head
authorMaanya Goenka <t-magoenka@microsoft.com>
Mon, 26 Jul 2021 20:02:17 +0000 (13:02 -0700)
committerMaanya Goenka <t-magoenka@microsoft.com>
Thu, 12 Aug 2021 14:22:15 +0000 (07:22 -0700)
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 <<EOF>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 <<EOF>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
shell-completion/bash/systemd-analyze
shell-completion/zsh/_systemd-analyze
src/analyze/analyze-verify.c
src/analyze/analyze-verify.h
src/analyze/analyze.c
src/basic/log.c
src/basic/log.h
src/basic/macro.h

index dc93ac4e72e6fabcd4deebc4e9dd366928035c7a..48976f52bf9c1ea000333f122d90675ca4e92254 100644 (file)
@@ -744,6 +744,18 @@ Service b@0.service not loaded, b.socket cannot be started.
         generators enabled will generally result in some warnings.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--recursive-errors=<replaceable>MODE</replaceable></option></term>
+
+        <listitem><para>Control verification of units and their dependencies and whether
+        <command>systemd-analyze verify</command> exits with a non-zero process exit status or not. With
+        <command>yes</command>, 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
+        <command>no</command>, return a non-zero process exit status when warnings arise during verification
+        of only the specified unit. With <command>one</command>, return a non-zero process exit status when
+        warnings arise during verification of either the specified unit or its immediate dependencies. </para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--root=<replaceable>PATH</replaceable></option></term>
 
index 872518add0d3f6f72f94fbc70bad887ec22bfcc1..1b9447a125b097e791fb886bdcd8378f8833fe52 100644 (file)
@@ -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
index 88a09d84b9d5f2d2473647d554ae5e145c0db382..0dd080afb746f14905dd45f24c20b4b03d79717d 100644 (file)
@@ -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]' \
index 9ef6868367d28acabb3880e422beb86d2fcf1e0d..cd5377200b026884e795fe6d40db1057b6cc97ab 100644 (file)
 #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);
index 6b3d6a5ab5d6b85f8a1771292b4cb332916d0f8e..09d3aea02c91ff85c9f3f36c11891bb7dedfa8eb 100644 (file)
@@ -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_;
index 8d637ff8de9e44be9f1a9c98b178bf634510372b..ceb18db7406e5ff33bd671d512a5d2322c01d54b 100644 (file)
@@ -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();
 
index 7f7e7a2d606745c338ee1dc88c086c8b0669df38..5fd2c5dcb4d3f755d529b95d7242cd4dc8d47361 100644 (file)
@@ -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;
index 9a17cd6c3d77249f7b29161417bd190e46bc1e40..b34bdffd1b8fed1811e2bb874520e4d56c9c4b76 100644 (file)
@@ -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);
index 92498b0f20e189faec1acbb5ac683e8900372d83..3d6aa6457a1d2184a23763f8ec7e77bb5e3155a4 100644 (file)
@@ -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"