]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/analyze/analyze.c
coccinelle: make use of SYNTHETIC_ERRNO
[thirdparty/systemd.git] / src / analyze / analyze.c
index 580bc30e1210c6d5f04a1a0df94f644d8a00489b..757ddea0988e885f269e0c0351897509fbac8813 100644 (file)
@@ -1,12 +1,10 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 /***
-  This file is part of systemd.
-
-  Copyright 2010-2013 Lennart Poettering
-  Copyright 2013 Simon Peeters
+  Copyright © 2013 Simon Peeters
 ***/
 
 #include <getopt.h>
+#include <inttypes.h>
 #include <locale.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include "conf-files.h"
 #include "copy.h"
 #include "fd-util.h"
+#include "fileio.h"
 #include "glob-util.h"
 #include "hashmap.h"
 #include "locale-util.h"
 #include "log.h"
+#include "main-func.h"
 #include "pager.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "pretty-print.h"
 #if HAVE_SECCOMP
-#include "seccomp-util.h"
+#  include "seccomp-util.h"
 #endif
 #include "special.h"
 #include "strv.h"
 #include "strxcpyx.h"
+#include "time-util.h"
 #include "terminal-util.h"
 #include "unit-name.h"
 #include "util.h"
@@ -44,8 +46,6 @@
 #define SCALE_X (0.1 / 1000.0)   /* pixels per us */
 #define SCALE_Y (20.0)
 
-#define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
-
 #define svg(...) printf(__VA_ARGS__)
 
 #define svg_bar(class, x1, x2, y)                                       \
@@ -69,7 +69,7 @@ static enum dot {
 static char** arg_dot_from_patterns = NULL;
 static char** arg_dot_to_patterns = NULL;
 static usec_t arg_fuzz = 0;
-static bool arg_no_pager = false;
+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;
@@ -77,6 +77,9 @@ static bool arg_man = true;
 static bool arg_generators = false;
 static const char *arg_root = NULL;
 
+STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
+
 struct boot_times {
         usec_t firmware_time;
         usec_t loader_time;
@@ -91,6 +94,12 @@ struct boot_times {
         usec_t generators_finish_time;
         usec_t unitsload_start_time;
         usec_t unitsload_finish_time;
+        usec_t initrd_security_start_time;
+        usec_t initrd_security_finish_time;
+        usec_t initrd_generators_start_time;
+        usec_t initrd_generators_finish_time;
+        usec_t initrd_unitsload_start_time;
+        usec_t initrd_unitsload_finish_time;
 
         /*
          * If we're analyzing the user instance, all timestamps will be offset
@@ -106,6 +115,7 @@ struct boot_times {
 };
 
 struct unit_times {
+        bool has_data;
         char *name;
         usec_t activating;
         usec_t activated;
@@ -158,10 +168,8 @@ static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *in
                         &error,
                         't', val);
 
-        if (r < 0) {
-                log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
-                return r;
-        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse reply: %s", bus_error_message(&error, -r));
 
         return 0;
 }
@@ -183,33 +191,30 @@ static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char
                         property,
                         &error,
                         strv);
-        if (r < 0) {
-                log_error("Failed to get unit property %s: %s", property, bus_error_message(&error, -r));
-                return r;
-        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to get unit property %s: %s", property, bus_error_message(&error, -r));
 
         return 0;
 }
 
-static int compare_unit_time(const void *a, const void *b) {
-        return compare(((struct unit_times *)b)->time,
-                       ((struct unit_times *)a)->time);
+static int compare_unit_time(const struct unit_times *a, const struct unit_times *b) {
+        return CMP(b->time, a->time);
 }
 
-static int compare_unit_start(const void *a, const void *b) {
-        return compare(((struct unit_times *)a)->activating,
-                       ((struct unit_times *)b)->activating);
+static int compare_unit_start(const struct unit_times *a, const struct unit_times *b) {
+        return CMP(a->activating, b->activating);
 }
 
-static void free_unit_times(struct unit_times *t, unsigned n) {
+static void unit_times_free(struct unit_times *t) {
         struct unit_times *p;
 
-        for (p = t; p < t + n; p++)
+        for (p = t; p->has_data; p++)
                 free(p->name);
-
         free(t);
 }
 
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct unit_times *, unit_times_free);
+
 static void subtract_timestamp(usec_t *a, usec_t b) {
         assert(a);
 
@@ -220,75 +225,48 @@ static void subtract_timestamp(usec_t *a, usec_t b) {
 }
 
 static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) {
+        static const struct bus_properties_map property_map[] = {
+                { "FirmwareTimestampMonotonic",               "t", NULL, offsetof(struct boot_times, firmware_time)                 },
+                { "LoaderTimestampMonotonic",                 "t", NULL, offsetof(struct boot_times, loader_time)                   },
+                { "KernelTimestamp",                          "t", NULL, offsetof(struct boot_times, kernel_time)                   },
+                { "InitRDTimestampMonotonic",                 "t", NULL, offsetof(struct boot_times, initrd_time)                   },
+                { "UserspaceTimestampMonotonic",              "t", NULL, offsetof(struct boot_times, userspace_time)                },
+                { "FinishTimestampMonotonic",                 "t", NULL, offsetof(struct boot_times, finish_time)                   },
+                { "SecurityStartTimestampMonotonic",          "t", NULL, offsetof(struct boot_times, security_start_time)           },
+                { "SecurityFinishTimestampMonotonic",         "t", NULL, offsetof(struct boot_times, security_finish_time)          },
+                { "GeneratorsStartTimestampMonotonic",        "t", NULL, offsetof(struct boot_times, generators_start_time)         },
+                { "GeneratorsFinishTimestampMonotonic",       "t", NULL, offsetof(struct boot_times, generators_finish_time)        },
+                { "UnitsLoadStartTimestampMonotonic",         "t", NULL, offsetof(struct boot_times, unitsload_start_time)          },
+                { "UnitsLoadFinishTimestampMonotonic",        "t", NULL, offsetof(struct boot_times, unitsload_finish_time)         },
+                { "InitRDSecurityStartTimestampMonotonic",    "t", NULL, offsetof(struct boot_times, initrd_security_start_time)    },
+                { "InitRDSecurityFinishTimestampMonotonic",   "t", NULL, offsetof(struct boot_times, initrd_security_finish_time)   },
+                { "InitRDGeneratorsStartTimestampMonotonic",  "t", NULL, offsetof(struct boot_times, initrd_generators_start_time)  },
+                { "InitRDGeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(struct boot_times, initrd_generators_finish_time) },
+                { "InitRDUnitsLoadStartTimestampMonotonic",   "t", NULL, offsetof(struct boot_times, initrd_unitsload_start_time)   },
+                { "InitRDUnitsLoadFinishTimestampMonotonic",  "t", NULL, offsetof(struct boot_times, initrd_unitsload_finish_time)  },
+                {},
+        };
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         static struct boot_times times;
         static bool cached = false;
+        int r;
 
         if (cached)
                 goto finish;
 
         assert_cc(sizeof(usec_t) == sizeof(uint64_t));
 
-        if (bus_get_uint64_property(bus,
-                                    "/org/freedesktop/systemd1",
-                                    "org.freedesktop.systemd1.Manager",
-                                    "FirmwareTimestampMonotonic",
-                                    &times.firmware_time) < 0 ||
-            bus_get_uint64_property(bus,
-                                    "/org/freedesktop/systemd1",
-                                    "org.freedesktop.systemd1.Manager",
-                                    "LoaderTimestampMonotonic",
-                                    &times.loader_time) < 0 ||
-            bus_get_uint64_property(bus,
-                                    "/org/freedesktop/systemd1",
-                                    "org.freedesktop.systemd1.Manager",
-                                    "KernelTimestamp",
-                                    &times.kernel_time) < 0 ||
-            bus_get_uint64_property(bus,
-                                    "/org/freedesktop/systemd1",
-                                    "org.freedesktop.systemd1.Manager",
-                                    "InitRDTimestampMonotonic",
-                                    &times.initrd_time) < 0 ||
-            bus_get_uint64_property(bus,
-                                    "/org/freedesktop/systemd1",
-                                    "org.freedesktop.systemd1.Manager",
-                                    "UserspaceTimestampMonotonic",
-                                    &times.userspace_time) < 0 ||
-            bus_get_uint64_property(bus,
-                                    "/org/freedesktop/systemd1",
-                                    "org.freedesktop.systemd1.Manager",
-                                    "FinishTimestampMonotonic",
-                                    &times.finish_time) < 0 ||
-            bus_get_uint64_property(bus,
-                                    "/org/freedesktop/systemd1",
-                                    "org.freedesktop.systemd1.Manager",
-                                    "SecurityStartTimestampMonotonic",
-                                    &times.security_start_time) < 0 ||
-            bus_get_uint64_property(bus,
-                                    "/org/freedesktop/systemd1",
-                                    "org.freedesktop.systemd1.Manager",
-                                    "SecurityFinishTimestampMonotonic",
-                                    &times.security_finish_time) < 0 ||
-            bus_get_uint64_property(bus,
-                                    "/org/freedesktop/systemd1",
-                                    "org.freedesktop.systemd1.Manager",
-                                    "GeneratorsStartTimestampMonotonic",
-                                    &times.generators_start_time) < 0 ||
-            bus_get_uint64_property(bus,
-                                    "/org/freedesktop/systemd1",
-                                    "org.freedesktop.systemd1.Manager",
-                                    "GeneratorsFinishTimestampMonotonic",
-                                    &times.generators_finish_time) < 0 ||
-            bus_get_uint64_property(bus,
-                                    "/org/freedesktop/systemd1",
-                                    "org.freedesktop.systemd1.Manager",
-                                    "UnitsLoadStartTimestampMonotonic",
-                                    &times.unitsload_start_time) < 0 ||
-            bus_get_uint64_property(bus,
-                                    "/org/freedesktop/systemd1",
-                                    "org.freedesktop.systemd1.Manager",
-                                    "UnitsLoadFinishTimestampMonotonic",
-                                    &times.unitsload_finish_time) < 0)
-                return -EIO;
+        r = bus_map_all_properties(
+                        bus,
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        property_map,
+                        BUS_MAP_STRDUP,
+                        &error,
+                        NULL,
+                        &times);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get timestamp properties: %s", bus_error_message(&error, r));
 
         if (times.finish_time <= 0) {
                 log_error("Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64").\n"
@@ -299,23 +277,23 @@ static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) {
                 return -EINPROGRESS;
         }
 
-        if (arg_scope == UNIT_FILE_SYSTEM) {
+        if (arg_scope == UNIT_FILE_SYSTEM && times.security_start_time > 0) {
+                /* security_start_time is set when systemd is not running under container environment. */
                 if (times.initrd_time > 0)
                         times.kernel_done_time = times.initrd_time;
                 else
                         times.kernel_done_time = times.userspace_time;
         } else {
                 /*
-                 * User-instance-specific timestamps processing
+                 * User-instance-specific or container-system-specific timestamps processing
                  * (see comment to reverse_offset in struct boot_times).
                  */
                 times.reverse_offset = times.userspace_time;
 
-                times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time = 0;
-                subtract_timestamp(&times.finish_time, times.reverse_offset);
+                times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time =
+                        times.security_start_time = times.security_finish_time = 0;
 
-                subtract_timestamp(&times.security_start_time, times.reverse_offset);
-                subtract_timestamp(&times.security_finish_time, times.reverse_offset);
+                subtract_timestamp(&times.finish_time, times.reverse_offset);
 
                 subtract_timestamp(&times.generators_start_time, times.reverse_offset);
                 subtract_timestamp(&times.generators_finish_time, times.reverse_offset);
@@ -349,17 +327,24 @@ static void free_host_info(struct host_info *hi) {
 DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info*, free_host_info);
 
 static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
+        static const struct bus_properties_map property_map[] = {
+                { "InactiveExitTimestampMonotonic",  "t", NULL, offsetof(struct unit_times, activating)   },
+                { "ActiveEnterTimestampMonotonic",   "t", NULL, offsetof(struct unit_times, activated)    },
+                { "ActiveExitTimestampMonotonic",    "t", NULL, offsetof(struct unit_times, deactivating) },
+                { "InactiveEnterTimestampMonotonic", "t", NULL, offsetof(struct unit_times, deactivated)  },
+                {},
+        };
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
-        int r, c = 0;
+        _cleanup_(unit_times_freep) struct unit_times *unit_times = NULL;
         struct boot_times *boot_times = NULL;
-        struct unit_times *unit_times = NULL;
-        size_t size = 0;
+        size_t allocated = 0, c = 0;
         UnitInfo u;
+        int r;
 
         r = acquire_boot_times(bus, &boot_times);
         if (r < 0)
-                goto fail;
+                return r;
 
         r = sd_bus_call_method(
                         bus,
@@ -369,49 +354,36 @@ static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
                         "ListUnits",
                         &error, &reply,
                         NULL);
-        if (r < 0) {
-                log_error("Failed to list units: %s", bus_error_message(&error, -r));
-                goto fail;
-        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, -r));
 
         r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
-        if (r < 0) {
-                bus_log_parse_error(r);
-                goto fail;
-        }
+        if (r < 0)
+                return bus_log_parse_error(r);
 
         while ((r = bus_parse_unit_info(reply, &u)) > 0) {
                 struct unit_times *t;
 
-                if (!GREEDY_REALLOC(unit_times, size, c+1)) {
-                        r = log_oom();
-                        goto fail;
-                }
+                if (!GREEDY_REALLOC(unit_times, allocated, c+2))
+                        return log_oom();
 
-                t = unit_times+c;
+                unit_times[c+1].has_data = false;
+                t = &unit_times[c];
                 t->name = NULL;
 
                 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
 
-                if (bus_get_uint64_property(bus, u.unit_path,
-                                            "org.freedesktop.systemd1.Unit",
-                                            "InactiveExitTimestampMonotonic",
-                                            &t->activating) < 0 ||
-                    bus_get_uint64_property(bus, u.unit_path,
-                                            "org.freedesktop.systemd1.Unit",
-                                            "ActiveEnterTimestampMonotonic",
-                                            &t->activated) < 0 ||
-                    bus_get_uint64_property(bus, u.unit_path,
-                                            "org.freedesktop.systemd1.Unit",
-                                            "ActiveExitTimestampMonotonic",
-                                            &t->deactivating) < 0 ||
-                    bus_get_uint64_property(bus, u.unit_path,
-                                            "org.freedesktop.systemd1.Unit",
-                                            "InactiveEnterTimestampMonotonic",
-                                            &t->deactivated) < 0) {
-                        r = -EIO;
-                        goto fail;
-                }
+                r = bus_map_all_properties(
+                                bus,
+                                "org.freedesktop.systemd1",
+                                u.unit_path,
+                                property_map,
+                                BUS_MAP_STRDUP,
+                                &error,
+                                NULL,
+                                t);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to get timestamp properties of unit %s: %s", u.id, bus_error_message(&error, r));
 
                 subtract_timestamp(&t->activating, boot_times->reverse_offset);
                 subtract_timestamp(&t->activated, boot_times->reverse_offset);
@@ -429,23 +401,17 @@ static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
                         continue;
 
                 t->name = strdup(u.id);
-                if (!t->name) {
-                        r = log_oom();
-                        goto fail;
-                }
+                if (!t->name)
+                        return log_oom();
+
+                t->has_data = true;
                 c++;
         }
-        if (r < 0) {
-                bus_log_parse_error(r);
-                goto fail;
-        }
+        if (r < 0)
+                return bus_log_parse_error(r);
 
-        *out = unit_times;
+        *out = TAKE_PTR(unit_times);
         return c;
-
-fail:
-        free_unit_times(unit_times, (unsigned) c);
-        return r;
 }
 
 static int acquire_host_info(sd_bus *bus, struct host_info **hi) {
@@ -465,6 +431,7 @@ static int acquire_host_info(sd_bus *bus, struct host_info **hi) {
         };
 
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *system_bus = NULL;
         _cleanup_(free_host_infop) struct host_info *host;
         int r;
 
@@ -472,7 +439,15 @@ static int acquire_host_info(sd_bus *bus, struct host_info **hi) {
         if (!host)
                 return log_oom();
 
-        r = bus_map_all_properties(bus,
+        if (arg_scope != UNIT_FILE_SYSTEM) {
+                r = bus_connect_transport(arg_transport, arg_host, false, &system_bus);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to connect to system bus, ignoring: %m");
+                        goto manager;
+                }
+        }
+
+        r = bus_map_all_properties(system_bus ?: bus,
                                    "org.freedesktop.hostname1",
                                    "/org/freedesktop/hostname1",
                                    hostname_map,
@@ -480,9 +455,12 @@ static int acquire_host_info(sd_bus *bus, struct host_info **hi) {
                                    &error,
                                    NULL,
                                    host);
-        if (r < 0)
-                log_debug_errno(r, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error, r));
+        if (r < 0) {
+                log_debug_errno(r, "Failed to get host information from systemd-hostnamed, ignoring: %s", bus_error_message(&error, r));
+                sd_bus_error_free(&error);
+        }
 
+manager:
         r = bus_map_all_properties(bus,
                                    "org.freedesktop.systemd1",
                                    "/org/freedesktop/systemd1",
@@ -536,7 +514,7 @@ static int pretty_boot_time(sd_bus *bus, char **_buf) {
                         "ActiveEnterTimestampMonotonic",
                         &activated_time);
         if (r < 0) {
-                log_info_errno(r, "Could not get time to reach default.target. Continuing...");
+                log_info_errno(r, "Could not get time to reach default.target, ignoring: %m");
                 activated_time = USEC_INFINITY;
         }
 
@@ -548,21 +526,24 @@ static int pretty_boot_time(sd_bus *bus, char **_buf) {
                 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
         if (t->loader_time > 0)
                 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
-        if (t->kernel_time > 0)
+        if (t->kernel_done_time > 0)
                 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
         if (t->initrd_time > 0)
                 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
 
         size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
-        if (t->kernel_time > 0)
-                strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
+        if (t->kernel_done_time > 0)
+                strpcpyf(&ptr, size, "= %s ", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
+
+        if (unit_id && activated_time > 0 && activated_time != USEC_INFINITY) {
+                usec_t base = t->userspace_time > 0 ? t->userspace_time : t->reverse_offset;
 
-        if (unit_id && activated_time > 0 && activated_time != USEC_INFINITY)
-                size = strpcpyf(&ptr, size, "\n%s reached after %s in userspace", unit_id, format_timespan(ts, sizeof(ts), activated_time - t->userspace_time, USEC_PER_MSEC));
-        else if (unit_id && activated_time == 0)
+                size = strpcpyf(&ptr, size, "\n%s reached after %s in userspace", unit_id,
+                                format_timespan(ts, sizeof(ts), activated_time - base, USEC_PER_MSEC));
+        else if (unit_id && activated_time == 0)
                 size = strpcpyf(&ptr, size, "\n%s was never reached", unit_id);
         else if (unit_id && activated_time == USEC_INFINITY)
-                size = strpcpyf(&ptr, size, "\nCould not get time to reach %s.",unit_id);
+                size = strpcpyf(&ptr, size, "\nCould not get time to reach %s.", unit_id);
         else if (!unit_id)
                 size = strpcpyf(&ptr, size, "\ncould not find default.target");
 
@@ -597,16 +578,38 @@ static void svg_graph_box(double height, double begin, double end) {
         }
 }
 
+static int plot_unit_times(struct unit_times *u, double width, int y) {
+        char ts[FORMAT_TIMESPAN_MAX];
+        bool b;
+
+        if (!u->name)
+                return 0;
+
+        svg_bar("activating",   u->activating, u->activated, y);
+        svg_bar("active",       u->activated, u->deactivating, y);
+        svg_bar("deactivating", u->deactivating, u->deactivated, y);
+
+        /* place the text on the left if we have passed the half of the svg width */
+        b = u->activating * SCALE_X < width / 2;
+        if (u->time)
+                svg_text(b, u->activating, y, "%s (%s)",
+                         u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
+        else
+                svg_text(b, u->activating, y, "%s", u->name);
+
+        return 1;
+}
+
 static int analyze_plot(int argc, char *argv[], void *userdata) {
         _cleanup_(free_host_infop) struct host_info *host = NULL;
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        struct unit_times *times;
+        _cleanup_(unit_times_freep) struct unit_times *times = NULL;
+        _cleanup_free_ char *pretty_times = NULL;
+        bool use_full_bus = arg_scope == UNIT_FILE_SYSTEM;
         struct boot_times *boot;
+        struct unit_times *u;
         int n, m = 1, y = 0, r;
-        bool use_full_bus = true;
         double width;
-        _cleanup_free_ char *pretty_times = NULL;
-        struct unit_times *u;
 
         r = acquire_bus(&bus, &use_full_bus);
         if (r < 0)
@@ -620,7 +623,7 @@ static int analyze_plot(int argc, char *argv[], void *userdata) {
         if (n < 0)
                 return n;
 
-        if (use_full_bus) {
+        if (use_full_bus || arg_scope != UNIT_FILE_SYSTEM) {
                 n = acquire_host_info(bus, &host);
                 if (n < 0)
                         return n;
@@ -630,7 +633,7 @@ static int analyze_plot(int argc, char *argv[], void *userdata) {
         if (n <= 0)
                 return n;
 
-        qsort(times, n, sizeof(struct unit_times), compare_unit_start);
+        typesafe_qsort(times, n, compare_unit_start);
 
         width = SCALE_X * (boot->firmware_time + boot->finish_time);
         if (width < 800.0)
@@ -645,14 +648,13 @@ static int analyze_plot(int argc, char *argv[], void *userdata) {
         }
         if (boot->initrd_time > 0)
                 m++;
-        if (boot->kernel_time > 0)
+        if (boot->kernel_done_time > 0)
                 m++;
 
-        for (u = times; u < times + n; u++) {
+        for (u = times; u->has_data; u++) {
                 double text_start, text_width;
 
-                if (u->activating < boot->userspace_time ||
-                    u->activating > boot->finish_time) {
+                if (u->activating > boot->finish_time) {
                         u->name = mfree(u->name);
                         continue;
                 }
@@ -665,12 +667,13 @@ static int analyze_plot(int argc, char *argv[], void *userdata) {
                 if (text_width > text_start && text_width + text_start > width)
                         width = text_width + text_start;
 
-                if (u->deactivated > u->activating && u->deactivated <= boot->finish_time
-                                && u->activated == 0 && u->deactivating == 0)
+                if (u->deactivated > u->activating &&
+                    u->deactivated <= boot->finish_time &&
+                    u->activated == 0 && u->deactivating == 0)
                         u->activated = u->deactivating = u->deactivated;
                 if (u->activated < u->activating || u->activated > boot->finish_time)
                         u->activated = boot->finish_time;
-                if (u->deactivating < u->activated || u->activated > boot->finish_time)
+                if (u->deactivating < u->activated || u->deactivating > boot->finish_time)
                         u->deactivating = boot->finish_time;
                 if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
                         u->deactivated = boot->finish_time;
@@ -722,7 +725,7 @@ static int analyze_plot(int argc, char *argv[], void *userdata) {
 
         svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
         svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
-        if (use_full_bus)
+        if (host)
                 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
                     isempty(host->os_pretty_name) ? "Linux" : host->os_pretty_name,
                     strempty(host->hostname),
@@ -745,43 +748,40 @@ static int analyze_plot(int argc, char *argv[], void *userdata) {
                 svg_text(true, -(double) boot->loader_time, y, "loader");
                 y++;
         }
-        if (boot->kernel_time > 0) {
+        if (boot->kernel_done_time > 0) {
                 svg_bar("kernel", 0, boot->kernel_done_time, y);
                 svg_text(true, 0, y, "kernel");
                 y++;
         }
         if (boot->initrd_time > 0) {
                 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
+                if (boot->initrd_security_start_time < boot->initrd_security_finish_time)
+                        svg_bar("security", boot->initrd_security_start_time, boot->initrd_security_finish_time, y);
+                if (boot->initrd_generators_start_time < boot->initrd_generators_finish_time)
+                        svg_bar("generators", boot->initrd_generators_start_time, boot->initrd_generators_finish_time, y);
+                if (boot->initrd_unitsload_start_time < boot->initrd_unitsload_finish_time)
+                        svg_bar("unitsload", boot->initrd_unitsload_start_time, boot->initrd_unitsload_finish_time, y);
                 svg_text(true, boot->initrd_time, y, "initrd");
                 y++;
         }
+
+        for (u = times; u->has_data; u++) {
+                if (u->activating >= boot->userspace_time)
+                        break;
+
+                y += plot_unit_times(u, width, y);
+        }
+
         svg_bar("active", boot->userspace_time, boot->finish_time, y);
-        svg_bar("security", boot->security_start_time, boot->security_finish_time, y);
+        if (boot->security_start_time > 0)
+                svg_bar("security", boot->security_start_time, boot->security_finish_time, y);
         svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
         svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y);
         svg_text(true, boot->userspace_time, y, "systemd");
         y++;
 
-        for (u = times; u < times + n; u++) {
-                char ts[FORMAT_TIMESPAN_MAX];
-                bool b;
-
-                if (!u->name)
-                        continue;
-
-                svg_bar("activating",   u->activating, u->activated, y);
-                svg_bar("active",       u->activated, u->deactivating, y);
-                svg_bar("deactivating", u->deactivating, u->deactivated, y);
-
-                /* place the text on the left if we have passed the half of the svg width */
-                b = u->activating * SCALE_X < width / 2;
-                if (u->time)
-                        svg_text(b, u->activating, y, "%s (%s)",
-                                 u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
-                else
-                        svg_text(b, u->activating, y, "%s", u->name);
-                y++;
-        }
+        for (; u->has_data; u++)
+                y += plot_unit_times(u, width, y);
 
         svg("</g>\n");
 
@@ -797,9 +797,11 @@ static int analyze_plot(int argc, char *argv[], void *userdata) {
         svg_bar("deactivating", 0, 300000, y);
         svg_text(true, 400000, y, "Deactivating");
         y++;
-        svg_bar("security", 0, 300000, y);
-        svg_text(true, 400000, y, "Setting up security module");
-        y++;
+        if (boot->security_start_time > 0) {
+                svg_bar("security", 0, 300000, y);
+                svg_text(true, 400000, y, "Setting up security module");
+                y++;
+        }
         svg_bar("generators", 0, 300000, y);
         svg_text(true, 400000, y, "Generators");
         y++;
@@ -811,15 +813,12 @@ static int analyze_plot(int argc, char *argv[], void *userdata) {
 
         svg("</svg>\n");
 
-        free_unit_times(times, (unsigned) n);
-
-        n = 0;
-        return n;
+        return 0;
 }
 
-static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
+static int list_dependencies_print(const char *name, unsigned level, unsigned branches,
                                    bool last, struct unit_times *times, struct boot_times *boot) {
-        unsigned int i;
+        unsigned i;
         char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
 
         for (i = level; i != 0; i--)
@@ -859,8 +858,7 @@ static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, cha
 
 static Hashmap *unit_times_hashmap;
 
-static int list_dependencies_compare(const void *_a, const void *_b) {
-        const char **a = (const char**) _a, **b = (const char**) _b;
+static int list_dependencies_compare(char * const *a, char * const *b) {
         usec_t usa = 0, usb = 0;
         struct unit_times *times;
 
@@ -871,7 +869,7 @@ static int list_dependencies_compare(const void *_a, const void *_b) {
         if (times)
                 usb = times->activated;
 
-        return usb - usa;
+        return CMP(usb, usa);
 }
 
 static bool times_in_range(const struct unit_times *times, const struct boot_times *boot) {
@@ -879,8 +877,8 @@ static bool times_in_range(const struct unit_times *times, const struct boot_tim
                 times->activated > 0 && times->activated <= boot->finish_time;
 }
 
-static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units,
-                                 unsigned int branches) {
+static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level, char ***units,
+                                 unsigned branches) {
         _cleanup_strv_free_ char **deps = NULL;
         char **c;
         int r = 0;
@@ -896,7 +894,7 @@ static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int lev
         if (r < 0)
                 return r;
 
-        qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
+        typesafe_qsort(deps, strv_length(deps), list_dependencies_compare);
 
         r = acquire_boot_times(bus, &boot);
         if (r < 0)
@@ -905,11 +903,8 @@ static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int lev
         STRV_FOREACH(c, deps) {
                 times = hashmap_get(unit_times_hashmap, *c);
                 if (times_in_range(times, boot) &&
-                    (times->activated >= service_longest
-                     || service_longest == 0)) {
+                    times->activated >= service_longest)
                         service_longest = times->activated;
-                        break;
-                }
         }
 
         if (service_longest == 0)
@@ -982,10 +977,8 @@ static int list_dependencies(sd_bus *bus, const char *name) {
                         &error,
                         &reply,
                         "s");
-        if (r < 0) {
-                log_error("Failed to get ID: %s", bus_error_message(&error, -r));
-                return r;
-        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, -r));
 
         r = sd_bus_message_read(reply, "s", &id);
         if (r < 0)
@@ -1012,8 +1005,8 @@ static int list_dependencies(sd_bus *bus, const char *name) {
 
 static int analyze_critical_chain(int argc, char *argv[], void *userdata) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        struct unit_times *times;
-        unsigned int i;
+        _cleanup_(unit_times_freep) struct unit_times *times = NULL;
+        struct unit_times *u;
         Hashmap *h;
         int n, r;
 
@@ -1029,14 +1022,14 @@ static int analyze_critical_chain(int argc, char *argv[], void *userdata) {
         if (!h)
                 return log_oom();
 
-        for (i = 0; i < (unsigned) n; i++) {
-                r = hashmap_put(h, times[i].name, &times[i]);
+        for (u = times; u->has_data; u++) {
+                r = hashmap_put(h, u->name, u);
                 if (r < 0)
                         return log_error_errno(r, "Failed to add entry to hashmap: %m");
         }
         unit_times_hashmap = h;
 
-        (void) pager_open(arg_no_pager, false);
+        (void) pager_open(arg_pager_flags);
 
         puts("The time after the unit is active or started is printed after the \"@\" character.\n"
              "The time the unit takes to start is printed after the \"+\" character.\n");
@@ -1049,14 +1042,13 @@ static int analyze_critical_chain(int argc, char *argv[], void *userdata) {
                 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
 
         h = hashmap_free(h);
-        free_unit_times(times, (unsigned) n);
         return 0;
 }
 
 static int analyze_blame(int argc, char *argv[], void *userdata) {
         _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        struct unit_times *times;
-        unsigned i;
+        _cleanup_(unit_times_freep) struct unit_times *times = NULL;
+        struct unit_times *u;
         int n, r;
 
         r = acquire_bus(&bus, NULL);
@@ -1067,18 +1059,17 @@ static int analyze_blame(int argc, char *argv[], void *userdata) {
         if (n <= 0)
                 return n;
 
-        qsort(times, n, sizeof(struct unit_times), compare_unit_time);
+        typesafe_qsort(times, n, compare_unit_time);
 
-        (void) pager_open(arg_no_pager, false);
+        (void) pager_open(arg_pager_flags);
 
-        for (i = 0; i < (unsigned) n; i++) {
+        for (u = times; u->has_data; u++) {
                 char ts[FORMAT_TIMESPAN_MAX];
 
-                if (times[i].time > 0)
-                        printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
+                if (u->time > 0)
+                        printf("%16s %s\n", format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC), u->name);
         }
 
-        free_unit_times(times, (unsigned) n);
         return 0;
 }
 
@@ -1246,10 +1237,8 @@ static int dot(int argc, char *argv[], void *userdata) {
                        &error,
                        &reply,
                        "");
-        if (r < 0) {
-                log_error("Failed to list units: %s", bus_error_message(&error, -r));
-                return r;
-        }
+        if (r < 0)
+                log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, -r));
 
         r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
         if (r < 0)
@@ -1320,7 +1309,7 @@ static int dump(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return log_error_errno(r, "Failed to create bus connection: %m");
 
-        (void) pager_open(arg_no_pager, false);
+        (void) pager_open(arg_pager_flags);
 
         if (!sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD))
                 return dump_fallback(bus);
@@ -1354,7 +1343,7 @@ static int cat_config(int argc, char *argv[], void *userdata) {
         char **arg;
         int r;
 
-        (void) pager_open(arg_no_pager, false);
+        (void) pager_open(arg_pager_flags);
 
         STRV_FOREACH(arg, argv + 1) {
                 const char *t = NULL;
@@ -1371,10 +1360,10 @@ static int cat_config(int argc, char *argv[], void *userdata) {
                                         break;
                         }
 
-                        if (!t) {
-                                log_error("Path %s does not start with any known prefix.", *arg);
-                                return -EINVAL;
-                        }
+                        if (!t)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Path %s does not start with any known prefix.",
+                                                       *arg);
                 } else
                         t = *arg;
 
@@ -1514,29 +1503,114 @@ static int dump_unit_paths(int argc, char *argv[], void *userdata) {
 }
 
 #if HAVE_SECCOMP
+
+static int load_kernel_syscalls(Set **ret) {
+        _cleanup_(set_free_freep) Set *syscalls = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        int r;
+
+        /* Let's read the available system calls from the list of available tracing events. Slightly dirty, but good
+         * enough for analysis purposes. */
+
+        f = fopen("/sys/kernel/debug/tracing/available_events", "re");
+        if (!f)
+                return log_full_errno(IN_SET(errno, EPERM, EACCES, ENOENT) ? LOG_DEBUG : LOG_WARNING, errno, "Can't read open /sys/kernel/debug/tracing/available_events: %m");
+
+        for (;;) {
+                _cleanup_free_ char *line = NULL;
+                const char *e;
+
+                r = read_line(f, LONG_LINE_MAX, &line);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to read system call list: %m");
+                if (r == 0)
+                        break;
+
+                e = startswith(line, "syscalls:sys_enter_");
+                if (!e)
+                        continue;
+
+                /* These are named differently inside the kernel than their external name for historical reasons. Let's hide them here. */
+                if (STR_IN_SET(e, "newuname", "newfstat", "newstat", "newlstat", "sysctl"))
+                        continue;
+
+                r = set_ensure_allocated(&syscalls, &string_hash_ops);
+                if (r < 0)
+                        return log_oom();
+
+                r = set_put_strdup(syscalls, e);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add system call to list: %m");
+        }
+
+        *ret = TAKE_PTR(syscalls);
+        return 0;
+}
+
+static void kernel_syscalls_remove(Set *s, const SyscallFilterSet *set) {
+        const char *syscall;
+
+        NULSTR_FOREACH(syscall, set->value) {
+                if (syscall[0] == '@')
+                        continue;
+
+                (void) set_remove(s, syscall);
+        }
+}
+
 static void dump_syscall_filter(const SyscallFilterSet *set) {
         const char *syscall;
 
-        printf("%s\n", set->name);
-        printf("    # %s\n", set->help);
+        printf("%s%s%s\n"
+               "    # %s\n",
+               ansi_highlight(),
+               set->name,
+               ansi_normal(),
+               set->help);
+
         NULSTR_FOREACH(syscall, set->value)
-                printf("    %s\n", syscall);
+                printf("    %s%s%s\n",
+                       syscall[0] == '@' ? ansi_underline() : "",
+                       syscall,
+                       ansi_normal());
 }
 
 static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
         bool first = true;
 
-        (void) pager_open(arg_no_pager, false);
+        (void) pager_open(arg_pager_flags);
 
         if (strv_isempty(strv_skip(argv, 1))) {
-                int i;
+                _cleanup_(set_free_freep) Set *kernel = NULL;
+                int i, k;
+
+                k = load_kernel_syscalls(&kernel);
 
                 for (i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) {
+                        const SyscallFilterSet *set = syscall_filter_sets + i;
                         if (!first)
                                 puts("");
-                        dump_syscall_filter(syscall_filter_sets + i);
+
+                        dump_syscall_filter(set);
+                        kernel_syscalls_remove(kernel, set);
                         first = false;
                 }
+
+                if (k < 0) {
+                        fputc('\n', stdout);
+                        fflush(stdout);
+                        log_notice_errno(k, "# Not showing unlisted system calls, couldn't retrieve kernel system call list: %m");
+                } else if (!set_isempty(kernel)) {
+                        const char *syscall;
+                        Iterator j;
+
+                        printf("\n"
+                               "# %sUnlisted System Calls%s (supported by the local kernel, but not included in any of the groups listed above):\n",
+                               ansi_highlight(), ansi_normal());
+
+                        SET_FOREACH(syscall, kernel, j)
+                                printf("#   %s\n", syscall);
+                }
         } else {
                 char **name;
 
@@ -1564,12 +1638,34 @@ static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
 }
 
 #else
-static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
-        log_error("Not compiled with syscall filters, sorry.");
-        return -EOPNOTSUPP;
-}
+static int dump_syscall_filters(int argc, char *argv[], void *userdata)
+        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                               "Not compiled with syscall filters, sorry.");
 #endif
 
+static int dump_timespan(int argc, char *argv[], void *userdata) {
+        char **input_timespan;
+
+        STRV_FOREACH(input_timespan, strv_skip(argv, 1)) {
+                int r;
+                usec_t usec_magnitude = 1, output_usecs;
+                char ft_buf[FORMAT_TIMESPAN_MAX];
+
+                r = parse_time(*input_timespan, &output_usecs, USEC_PER_SEC);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse time span '%s': %m", *input_timespan);
+
+                printf("Original: %s\n", *input_timespan);
+                printf("      %ss: %" PRIu64 "\n", special_glyph(MU), output_usecs);
+                printf("   Human: %s\n", format_timespan(ft_buf, sizeof(ft_buf), output_usecs, usec_magnitude));
+
+                if (input_timespan[1])
+                        putchar('\n');
+        }
+
+        return EXIT_SUCCESS;
+}
+
 static int test_calendar(int argc, char *argv[], void *userdata) {
         int ret = 0, r;
         char **p;
@@ -1687,8 +1783,14 @@ static int do_verify(int argc, char *argv[], void *userdata) {
 }
 
 static int help(int argc, char *argv[], void *userdata) {
+        _cleanup_free_ char *link = NULL;
+        int r;
 
-        (void) pager_open(arg_no_pager, false);
+        (void) pager_open(arg_pager_flags);
+
+        r = terminal_urlify_man("systemd-analyze", "1", &link);
+        if (r < 0)
+                return log_oom();
 
         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
                "Profile systemd, show unit dependencies, check unit files.\n\n"
@@ -1706,9 +1808,9 @@ static int help(int argc, char *argv[], void *userdata) {
                "     --to-pattern=GLOB     Show only destinations in the graph\n"
                "     --fuzz=SECONDS        Also print also services which finished SECONDS\n"
                "                           earlier than the latest in the branch\n"
-               "     --man[=BOOL]          Do [not] check for existence of man pages\n\n"
-               "     --generators[=BOOL]   Do [not] run unit generators (requires privileges)\n\n"
-               "Commands:\n"
+               "     --man[=BOOL]          Do [not] check for existence of man pages\n"
+               "     --generators[=BOOL]   Do [not] run unit generators (requires privileges)\n"
+               "\nCommands:\n"
                "  time                     Print time spent in the kernel\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"
@@ -1723,10 +1825,13 @@ static int help(int argc, char *argv[], void *userdata) {
                "  verify FILE...           Check unit files for correctness\n"
                "  calendar SPEC...         Validate repetitive calendar time events\n"
                "  service-watchdogs [BOOL] Get/set service watchdog state\n"
-               , program_invocation_short_name);
+               "  timespan SPAN...         Validate a time span\n"
+               "\nSee the %s for details.\n"
+               , program_invocation_short_name
+               , link
+        );
 
-        /* When updating this list, including descriptions, apply
-         * changes to shell-completion/bash/systemd-analyze and
+        /* When updating this list, including descriptions, apply changes to shell-completion/bash/systemd-analyze and
          * shell-completion/zsh/_systemd-analyze too. */
 
         return 0;
@@ -1826,7 +1931,7 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_NO_PAGER:
-                        arg_no_pager = true;
+                        arg_pager_flags |= PAGER_DISABLE;
                         break;
 
                 case 'H':
@@ -1842,12 +1947,11 @@ static int parse_argv(int argc, char *argv[]) {
                 case ARG_MAN:
                         if (optarg) {
                                 r = parse_boolean(optarg);
-                                if (r < 0) {
-                                        log_error("Failed to parse --man= argument.");
-                                        return -EINVAL;
-                                }
+                                if (r < 0)
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                               "Failed to parse --man= argument.");
 
-                                arg_man = !!r;
+                                arg_man = r;
                         } else
                                 arg_man = true;
 
@@ -1856,12 +1960,11 @@ static int parse_argv(int argc, char *argv[]) {
                 case ARG_GENERATORS:
                         if (optarg) {
                                 r = parse_boolean(optarg);
-                                if (r < 0) {
-                                        log_error("Failed to parse --generators= argument.");
-                                        return -EINVAL;
-                                }
+                                if (r < 0)
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                               "Failed to parse --generators= argument.");
 
-                                arg_generators = !!r;
+                                arg_generators = r;
                         } else
                                 arg_generators = true;
 
@@ -1875,20 +1978,18 @@ static int parse_argv(int argc, char *argv[]) {
                 }
 
         if (arg_scope == UNIT_FILE_GLOBAL &&
-            !STR_IN_SET(argv[optind] ?: "time", "dot", "unit-paths", "verify")) {
-                log_error("Option --global only makes sense with verbs dot, unit-paths, verify.");
-                return -EINVAL;
-        }
+            !STR_IN_SET(argv[optind] ?: "time", "dot", "unit-paths", "verify"))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Option --global only makes sense with verbs dot, unit-paths, verify.");
 
-        if (arg_root && !streq_ptr(argv[optind], "cat-config")) {
-                log_error("Option --root is only supported for cat-config right now.");
-                return -EINVAL;
-        }
+        if (arg_root && !streq_ptr(argv[optind], "cat-config"))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Option --root is only supported for cat-config right now.");
 
         return 1; /* work to do */
 }
 
-int main(int argc, char *argv[]) {
+static int run(int argc, char *argv[]) {
 
         static const Verb verbs[] = {
                 { "help",              VERB_ANY, VERB_ANY, 0,            help                   },
@@ -1911,6 +2012,7 @@ int main(int argc, char *argv[]) {
                 { "verify",            2,        VERB_ANY, 0,            do_verify              },
                 { "calendar",          2,        VERB_ANY, 0,            test_calendar          },
                 { "service-watchdogs", VERB_ANY, 2,        0,            service_watchdogs      },
+                { "timespan",          2,        VERB_ANY, 0,            dump_timespan          },
                 {}
         };
 
@@ -1924,15 +2026,9 @@ int main(int argc, char *argv[]) {
 
         r = parse_argv(argc, argv);
         if (r <= 0)
-                goto finish;
-
-        r = dispatch_verb(argc, argv, verbs, NULL);
-
-finish:
-        pager_close();
-
-        strv_free(arg_dot_from_patterns);
-        strv_free(arg_dot_to_patterns);
+                return r;
 
-        return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+        return dispatch_verb(argc, argv, verbs, NULL);
 }
+
+DEFINE_MAIN_FUNCTION(run);