]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/analyze/analyze.c
analyze: give a hint about calendar/timestamp/timespan use
[thirdparty/systemd.git] / src / analyze / analyze.c
index 757ddea0988e885f269e0c0351897509fbac8813..b23dcf724096acb29bcaaa8da1b72dbd4d0e1642 100644 (file)
@@ -8,18 +8,21 @@
 #include <locale.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <unistd.h>
 
 #include "sd-bus.h"
 
 #include "alloc-util.h"
+#include "analyze-security.h"
 #include "analyze-verify.h"
+#include "build.h"
 #include "bus-error.h"
 #include "bus-unit-util.h"
 #include "bus-util.h"
 #include "calendarspec.h"
-#include "def.h"
 #include "conf-files.h"
 #include "copy.h"
+#include "def.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "glob-util.h"
@@ -27,6 +30,7 @@
 #include "locale-util.h"
 #include "log.h"
 #include "main-func.h"
+#include "nulstr-util.h"
 #include "pager.h"
 #include "parse-util.h"
 #include "path-util.h"
 #if HAVE_SECCOMP
 #  include "seccomp-util.h"
 #endif
+#include "sort-util.h"
 #include "special.h"
 #include "strv.h"
 #include "strxcpyx.h"
-#include "time-util.h"
 #include "terminal-util.h"
+#include "time-util.h"
 #include "unit-name.h"
 #include "util.h"
 #include "verbs.h"
 
-#define SCALE_X (0.1 / 1000.0)   /* pixels per us */
+#define SCALE_X (0.1 / 1000.0) /* pixels per us */
 #define SCALE_Y (20.0)
 
 #define svg(...) printf(__VA_ARGS__)
@@ -66,8 +71,8 @@ static enum dot {
         DEP_ORDER,
         DEP_REQUIRE
 } arg_dot = DEP_ALL;
-static char** arg_dot_from_patterns = NULL;
-static char** arg_dot_to_patterns = NULL;
+static char **arg_dot_from_patterns = NULL;
+static char **arg_dot_to_patterns = NULL;
 static usec_t arg_fuzz = 0;
 static PagerFlags arg_pager_flags = 0;
 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
@@ -76,6 +81,7 @@ static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
 static bool arg_man = true;
 static bool arg_generators = false;
 static const char *arg_root = NULL;
+static unsigned arg_iterations = 1;
 
 STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
@@ -169,7 +175,7 @@ static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *in
                         't', val);
 
         if (r < 0)
-                return log_error_errno(r, "Failed to parse reply: %s", bus_error_message(&error, -r));
+                return log_error_errno(r, "Failed to parse reply: %s", bus_error_message(&error, r));
 
         return 0;
 }
@@ -192,7 +198,7 @@ static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char
                         &error,
                         strv);
         if (r < 0)
-                return log_error_errno(r, "Failed to get unit property %s: %s", property, bus_error_message(&error, -r));
+                return log_error_errno(r, "Failed to get unit property %s: %s", property, bus_error_message(&error, r));
 
         return 0;
 }
@@ -268,14 +274,13 @@ static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) {
         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"
-                          "Please try again later.\n"
-                          "Hint: Use 'systemctl%s list-jobs' to see active jobs",
-                          times.finish_time,
-                          arg_scope == UNIT_FILE_SYSTEM ? "" : " --user");
-                return -EINPROGRESS;
-        }
+        if (times.finish_time <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS),
+                                       "Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64").\n"
+                                       "Please try again later.\n"
+                                       "Hint: Use 'systemctl%s list-jobs' to see active jobs",
+                                       times.finish_time,
+                                       arg_scope == UNIT_FILE_SYSTEM ? "" : " --user");
 
         if (arg_scope == UNIT_FILE_SYSTEM && times.security_start_time > 0) {
                 /* security_start_time is set when systemd is not running under container environment. */
@@ -290,8 +295,8 @@ static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) {
                  */
                 times.reverse_offset = times.userspace_time;
 
-                times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time =
-                        times.security_start_time = times.security_finish_time = 0;
+                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.finish_time, times.reverse_offset);
 
@@ -310,7 +315,6 @@ finish:
 }
 
 static void free_host_info(struct host_info *hi) {
-
         if (!hi)
                 return;
 
@@ -324,7 +328,7 @@ static void free_host_info(struct host_info *hi) {
         free(hi);
 }
 
-DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info*, free_host_info);
+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[] = {
@@ -355,7 +359,7 @@ static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
                         &error, &reply,
                         NULL);
         if (r < 0)
-                return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, -r));
+                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)
@@ -364,10 +368,10 @@ static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
         while ((r = bus_parse_unit_info(reply, &u)) > 0) {
                 struct unit_times *t;
 
-                if (!GREEDY_REALLOC(unit_times, allocated, c+2))
+                if (!GREEDY_REALLOC(unit_times, allocated, c + 2))
                         return log_oom();
 
-                unit_times[c+1].has_data = false;
+                unit_times[c + 1].has_data = false;
                 t = &unit_times[c];
                 t->name = NULL;
 
@@ -383,7 +387,8 @@ static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
                                 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));
+                        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);
@@ -425,8 +430,8 @@ static int acquire_host_info(sd_bus *bus, struct host_info **hi) {
         };
 
         static const struct bus_properties_map manager_map[] = {
-                { "Virtualization",            "s", NULL, offsetof(struct host_info, virtualization) },
-                { "Architecture",              "s", NULL, offsetof(struct host_info, architecture)   },
+                { "Virtualization", "s", NULL, offsetof(struct host_info, virtualization) },
+                { "Architecture",   "s", NULL, offsetof(struct host_info, architecture)   },
                 {}
         };
 
@@ -447,33 +452,36 @@ static int acquire_host_info(sd_bus *bus, struct host_info **hi) {
                 }
         }
 
-        r = bus_map_all_properties(system_bus ?: bus,
-                                   "org.freedesktop.hostname1",
-                                   "/org/freedesktop/hostname1",
-                                   hostname_map,
-                                   BUS_MAP_STRDUP,
-                                   &error,
-                                   NULL,
-                                   host);
+        r = bus_map_all_properties(
+                        system_bus ?: bus,
+                        "org.freedesktop.hostname1",
+                        "/org/freedesktop/hostname1",
+                        hostname_map,
+                        BUS_MAP_STRDUP,
+                        &error,
+                        NULL,
+                        host);
         if (r < 0) {
-                log_debug_errno(r, "Failed to get host information from systemd-hostnamed, ignoring: %s", bus_error_message(&error, r));
+                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",
-                                   manager_map,
-                                   BUS_MAP_STRDUP,
-                                   &error,
-                                   NULL,
-                                   host);
+        r = bus_map_all_properties(
+                        bus,
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        manager_map,
+                        BUS_MAP_STRDUP,
+                        &error,
+                        NULL,
+                        host);
         if (r < 0)
-                return log_error_errno(r, "Failed to get host information from systemd: %s", bus_error_message(&error, r));
+                return log_error_errno(r, "Failed to get host information from systemd: %s",
+                                       bus_error_message(&error, r));
 
         *hi = TAKE_PTR(host);
-
         return 0;
 }
 
@@ -485,7 +493,7 @@ static int pretty_boot_time(sd_bus *bus, char **_buf) {
         char *ptr;
         int r;
         usec_t activated_time = USEC_INFINITY;
-        _cleanup_free_ charpath = NULL, *unit_id = NULL;
+        _cleanup_free_ char *path = NULL, *unit_id = NULL;
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
 
         r = acquire_boot_times(bus, &t);
@@ -560,21 +568,34 @@ static void svg_graph_box(double height, double begin, double end) {
 
         /* outside box, fill */
         svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
-            SCALE_X * (end - begin), SCALE_Y * height);
+            SCALE_X * (end - begin),
+            SCALE_Y * height);
 
-        for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
+        for (i = ((long long) (begin / 100000)) * 100000; i <= end; i += 100000) {
                 /* lines for each second */
                 if (i % 5000000 == 0)
                         svg("  <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
                             "  <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
-                            SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
+                            SCALE_X * i,
+                            SCALE_X * i,
+                            SCALE_Y * height,
+                            SCALE_X * i,
+                            -5.0,
+                            0.000001 * i);
                 else if (i % 1000000 == 0)
                         svg("  <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
                             "  <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
-                            SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
+                            SCALE_X * i,
+                            SCALE_X * i,
+                            SCALE_Y * height,
+                            SCALE_X * i,
+                            -5.0,
+                            0.000001 * i);
                 else
                         svg("  <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
-                            SCALE_X * i, SCALE_X * i, SCALE_Y * height);
+                            SCALE_X * i,
+                            SCALE_X * i,
+                            SCALE_Y * height);
         }
 }
 
@@ -695,7 +716,7 @@ static int analyze_plot(int argc, char *argv[], void *userdata) {
             "<!-- that render these files properly but much slower are ImageMagick,   -->\n"
             "<!-- gimp, inkscape, etc. To display the files on your system, just      -->\n"
             "<!-- point your browser to this file.                                    -->\n\n"
-            "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", PACKAGE_VERSION);
+            "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", GIT_VERSION);
 
         /* style sheet */
         svg("<defs>\n  <style type=\"text/css\">\n    <![CDATA[\n"
@@ -816,15 +837,21 @@ static int analyze_plot(int argc, char *argv[], void *userdata) {
         return 0;
 }
 
-static int list_dependencies_print(const char *name, unsigned level, unsigned branches,
-                                   bool last, struct unit_times *times, struct boot_times *boot) {
+static int list_dependencies_print(
+                const char *name,
+                unsigned level,
+                unsigned branches,
+                bool last,
+                struct unit_times *times,
+                struct boot_times *boot) {
+
         unsigned i;
         char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
 
         for (i = level; i != 0; i--)
-                printf("%s", special_glyph(branches & (1 << (i-1)) ? TREE_VERTICAL : TREE_SPACE));
+                printf("%s", special_glyph(branches & (1 << (i-1)) ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE));
 
-        printf("%s", special_glyph(last ? TREE_RIGHT : TREE_BRANCH));
+        printf("%s", special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH));
 
         if (times) {
                 if (times->time > 0)
@@ -858,7 +885,7 @@ static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, cha
 
 static Hashmap *unit_times_hashmap;
 
-static int list_dependencies_compare(char * const *a, char * const *b) {
+static int list_dependencies_compare(char *const *a, char *const *b) {
         usec_t usa = 0, usb = 0;
         struct unit_times *times;
 
@@ -873,12 +900,10 @@ static int list_dependencies_compare(char * const *a, char * const *b) {
 }
 
 static bool times_in_range(const struct unit_times *times, const struct boot_times *boot) {
-        return times &&
-                times->activated > 0 && times->activated <= boot->finish_time;
+        return times && times->activated > 0 && times->activated <= boot->finish_time;
 }
 
-static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level, char ***units,
-                                 unsigned 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;
@@ -902,8 +927,7 @@ static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level,
 
         STRV_FOREACH(c, deps) {
                 times = hashmap_get(unit_times_hashmap, *c);
-                if (times_in_range(times, boot) &&
-                    times->activated >= service_longest)
+                if (times_in_range(times, boot) && times->activated >= service_longest)
                         service_longest = times->activated;
         }
 
@@ -912,8 +936,7 @@ static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level,
 
         STRV_FOREACH(c, deps) {
                 times = hashmap_get(unit_times_hashmap, *c);
-                if (times_in_range(times, boot) &&
-                    service_longest - times->activated <= arg_fuzz)
+                if (times_in_range(times, boot) && service_longest - times->activated <= arg_fuzz)
                         to_print++;
         }
 
@@ -922,8 +945,7 @@ static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level,
 
         STRV_FOREACH(c, deps) {
                 times = hashmap_get(unit_times_hashmap, *c);
-                if (!times_in_range(times, boot) ||
-                    service_longest - times->activated > arg_fuzz)
+                if (!times_in_range(times, boot) || service_longest - times->activated > arg_fuzz)
                         continue;
 
                 to_print--;
@@ -940,8 +962,7 @@ static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level,
                         continue;
                 }
 
-                r = list_dependencies_one(bus, *c, level + 1, units,
-                                          (branches << 1) | (to_print ? 1 : 0));
+                r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (to_print ? 1 : 0));
                 if (r < 0)
                         return r;
 
@@ -978,7 +999,7 @@ static int list_dependencies(sd_bus *bus, const char *name) {
                         &reply,
                         "s");
         if (r < 0)
-                return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, -r));
+                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)
@@ -1031,8 +1052,8 @@ static int analyze_critical_chain(int argc, char *argv[], void *userdata) {
 
         (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");
+        puts("The time when unit became active or started is printed after the \"@\" character.\n"
+             "The time the unit took to start is printed after the \"+\" character.\n");
 
         if (argc > 1) {
                 char **name;
@@ -1090,7 +1111,15 @@ static int analyze_time(int argc, char *argv[], void *userdata) {
         return 0;
 }
 
-static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[], char* from_patterns[], char* to_patterns[]) {
+static int graph_one_property(
+                sd_bus *bus,
+                const UnitInfo *u,
+                const char *prop,
+                const char *color,
+                char *patterns[],
+                char *from_patterns[],
+                char *to_patterns[]) {
+
         _cleanup_strv_free_ char **units = NULL;
         char **unit;
         int r;
@@ -1102,10 +1131,8 @@ static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop,
 
         match_patterns = strv_fnmatch(patterns, u->id, 0);
 
-        if (!strv_isempty(from_patterns) &&
-            !match_patterns &&
-            !strv_fnmatch(from_patterns, u->id, 0))
-                        return 0;
+        if (!strv_isempty(from_patterns) && !match_patterns && !strv_fnmatch(from_patterns, u->id, 0))
+                return 0;
 
         r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units);
         if (r < 0)
@@ -1116,9 +1143,7 @@ static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop,
 
                 match_patterns2 = strv_fnmatch(patterns, *unit, 0);
 
-                if (!strv_isempty(to_patterns) &&
-                    !match_patterns2 &&
-                    !strv_fnmatch(to_patterns, *unit, 0))
+                if (!strv_isempty(to_patterns) && !match_patterns2 && !strv_fnmatch(to_patterns, *unit, 0))
                         continue;
 
                 if (!strv_isempty(patterns) && !match_patterns && !match_patterns2)
@@ -1238,7 +1263,7 @@ static int dot(int argc, char *argv[], void *userdata) {
                        &reply,
                        "");
         if (r < 0)
-                log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, -r));
+                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)
@@ -1326,7 +1351,8 @@ static int dump(int argc, char *argv[], void *userdata) {
         if (r < 0) {
                 /* fall back to Dump if DumpByFileDescriptor is not supported */
                 if (!IN_SET(r, -EACCES, -EBADR))
-                        return log_error_errno(r, "Failed to issue method call DumpByFileDescriptor: %s", bus_error_message(&error, r));
+                        return log_error_errno(r, "Failed to issue method call DumpByFileDescriptor: %s",
+                                               bus_error_message(&error, r));
 
                 return dump_fallback(bus);
         }
@@ -1340,15 +1366,16 @@ static int dump(int argc, char *argv[], void *userdata) {
 }
 
 static int cat_config(int argc, char *argv[], void *userdata) {
-        char **arg;
+        char **arg, **list;
         int r;
 
         (void) pager_open(arg_pager_flags);
 
-        STRV_FOREACH(arg, argv + 1) {
+        list = strv_skip(argv, 1);
+        STRV_FOREACH(arg, list) {
                 const char *t = NULL;
 
-                if (arg != argv + 1)
+                if (arg != list)
                         print_separator();
 
                 if (path_is_absolute(*arg)) {
@@ -1362,8 +1389,7 @@ static int cat_config(int argc, char *argv[], void *userdata) {
 
                         if (!t)
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                       "Path %s does not start with any known prefix.",
-                                                       *arg);
+                                                       "Path %s does not start with any known prefix.", *arg);
                 } else
                         t = *arg;
 
@@ -1509,12 +1535,18 @@ static int load_kernel_syscalls(Set **ret) {
         _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");
+        /* 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/tracing/available_events", "re");
+        if (!f) {
+                /* We tried the non-debugfs mount point and that didn't work. If it wasn't mounted, maybe the
+                 * old debugfs mount point works? */
+                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 tracefs' available_events file: %m");
+        }
 
         for (;;) {
                 _cleanup_free_ char *line = NULL;
@@ -1530,7 +1562,8 @@ static int load_kernel_syscalls(Set **ret) {
                 if (!e)
                         continue;
 
-                /* These are named differently inside the kernel than their external name for historical reasons. Let's hide them here. */
+                /* 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;
 
@@ -1569,10 +1602,7 @@ static void dump_syscall_filter(const SyscallFilterSet *set) {
                set->help);
 
         NULSTR_FOREACH(syscall, set->value)
-                printf("    %s%s%s\n",
-                       syscall[0] == '@' ? ansi_underline() : "",
-                       syscall,
-                       ansi_normal());
+                printf("    %s%s%s\n", syscall[0] == '@' ? ansi_underline() : "", syscall, ansi_normal());
 }
 
 static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
@@ -1625,8 +1655,8 @@ static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
                                 /* make sure the error appears below normal output */
                                 fflush(stdout);
 
-                                log_error("Filter set \"%s\" not found.", *name);
-                                return -ENOENT;
+                                return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
+                                                       "Filter set \"%s\" not found.", *name);
                         }
 
                         dump_syscall_filter(set);
@@ -1638,11 +1668,23 @@ static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
 }
 
 #else
-static int dump_syscall_filters(int argc, char *argv[], void *userdata)
-        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
-                               "Not compiled with syscall filters, sorry.");
+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 void parsing_hint(const char *p, bool calendar, bool timestamp, bool timespan) {
+        if (calendar && calendar_spec_from_string(p, NULL) >= 0)
+                log_notice("Hint: this expression is a valid calendar specification. "
+                           "Use 'systemd-analyze calendar \"%s\"' instead?", p);
+        if (timestamp && parse_timestamp(p, NULL) >= 0)
+                log_notice("Hint: this expression is a valid timestamp. "
+                           "Use 'systemd-analyze timestamp \"%s\"' instead?", p);
+        if (timespan && parse_time(p, NULL, USEC_PER_SEC) >= 0)
+                log_notice("Hint: this expression is a valid timespan. "
+                           "Use 'systemd-analyze timespan \"%s\"' instead?", p);
+}
+
 static int dump_timespan(int argc, char *argv[], void *userdata) {
         char **input_timespan;
 
@@ -1652,12 +1694,18 @@ static int dump_timespan(int argc, char *argv[], void *userdata) {
                 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);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to parse time span '%s': %m", *input_timespan);
+                        parsing_hint(*input_timespan, true, true, false);
+                        return r;
+                }
 
                 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));
+                printf("      %ss: %" PRIu64 "\n", special_glyph(SPECIAL_GLYPH_MU), output_usecs);
+                printf("   Human: %s%s%s\n",
+                       ansi_highlight(),
+                       format_timespan(ft_buf, sizeof(ft_buf), output_usecs, usec_magnitude),
+                       ansi_normal());
 
                 if (input_timespan[1])
                         putchar('\n');
@@ -1666,59 +1714,124 @@ static int dump_timespan(int argc, char *argv[], void *userdata) {
         return EXIT_SUCCESS;
 }
 
-static int test_calendar(int argc, char *argv[], void *userdata) {
+static int test_timestamp_one(const char *p) {
+        usec_t usec;
+        char buf[FORMAT_TIMESTAMP_MAX];
+        int r;
+
+        r = parse_timestamp(p, &usec);
+        if (r < 0) {
+                log_error_errno(r, "Failed to parse \"%s\": %m", p);
+                parsing_hint(p, true, false, true);
+                return r;
+        }
+
+        printf("  Original form: %s\n", p);
+        printf("Normalized form: %s%s%s\n",
+               ansi_highlight_blue(),
+               format_timestamp(buf, sizeof buf, usec),
+               ansi_normal());
+
+        if (!in_utc_timezone())
+                printf("       (in UTC): %s\n", format_timestamp_utc(buf, sizeof buf, usec));
+
+        printf("       From now: %s\n", format_timestamp_relative(buf, sizeof buf, usec));
+
+        return 0;
+}
+
+static int test_timestamp(int argc, char *argv[], void *userdata) {
         int ret = 0, r;
         char **p;
-        usec_t n;
-
-        n = now(CLOCK_REALTIME);
 
         STRV_FOREACH(p, strv_skip(argv, 1)) {
-                _cleanup_(calendar_spec_freep) CalendarSpec *spec = NULL;
-                _cleanup_free_ char *t = NULL;
+                r = test_timestamp_one(*p);
+                if (ret == 0 && r < 0)
+                        ret = r;
+
+                if (*(p + 1))
+                        putchar('\n');
+        }
+
+        return ret;
+}
+
+static int test_calendar_one(usec_t n, const char *p) {
+        _cleanup_(calendar_spec_freep) CalendarSpec *spec = NULL;
+        _cleanup_free_ char *t = NULL;
+        int r;
+
+        r = calendar_spec_from_string(p, &spec);
+        if (r < 0) {
+                log_error_errno(r, "Failed to parse calendar specification '%s': %m", p);
+                parsing_hint(p, false, true, true);
+                return r;
+        }
+
+        r = calendar_spec_to_string(spec, &t);
+        if (r < 0)
+                return log_error_errno(r, "Failed to format calendar specification '%s': %m", p);
+
+        if (!streq(t, p))
+                printf("  Original form: %s\n", p);
+
+        printf("Normalized form: %s\n", t);
+
+        for (unsigned i = 0; i < arg_iterations; i++) {
+                char buffer[CONST_MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESTAMP_RELATIVE_MAX)];
                 usec_t next;
 
-                r = calendar_spec_from_string(*p, &spec);
-                if (r < 0) {
-                        ret = log_error_errno(r, "Failed to parse calendar specification '%s': %m", *p);
-                        continue;
+                r = calendar_spec_next_usec(spec, n, &next);
+                if (r == -ENOENT) {
+                        if (i == 0)
+                                printf("    Next elapse: %snever%s\n", ansi_highlight_yellow(), ansi_normal());
+                        return 0;
                 }
-
-                r = calendar_spec_normalize(spec);
-                if (r < 0) {
-                        ret = log_error_errno(r, "Failed to normalize calendar specification '%s': %m", *p);
-                        continue;
+                if (r < 0)
+                        return log_error_errno(r, "Failed to determine next elapse for '%s': %m", p);
+
+                if (i == 0)
+                        printf("    Next elapse: %s%s%s\n",
+                               ansi_highlight_blue(),
+                               format_timestamp(buffer, sizeof(buffer), next),
+                               ansi_normal());
+                else {
+                        int k = DECIMAL_STR_WIDTH(i + 1);
+
+                        if (k < 8)
+                                k = 8 - k;
+                        else
+                                k = 0;
+
+                        printf("%*sIter. #%u: %s%s%s\n",
+                               k, "", i+1,
+                               ansi_highlight_blue(), format_timestamp(buffer, sizeof(buffer), next), ansi_normal());
                 }
 
-                r = calendar_spec_to_string(spec, &t);
-                if (r < 0) {
-                        ret = log_error_errno(r, "Failed to format calendar specification '%s': %m", *p);
-                        continue;
-                }
+                if (!in_utc_timezone())
+                        printf("       (in UTC): %s\n", format_timestamp_utc(buffer, sizeof(buffer), next));
 
-                if (!streq(t, *p))
-                        printf("  Original form: %s\n", *p);
+                printf("       From now: %s\n", format_timestamp_relative(buffer, sizeof(buffer), next));
 
-                printf("Normalized form: %s\n", t);
+                n = next;
+        }
 
-                r = calendar_spec_next_usec(spec, n, &next);
-                if (r == -ENOENT)
-                        printf("    Next elapse: never\n");
-                else if (r < 0) {
-                        ret = log_error_errno(r, "Failed to determine next elapse for '%s': %m", *p);
-                        continue;
-                } else {
-                        char buffer[CONST_MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESTAMP_RELATIVE_MAX)];
+        return 0;
+}
 
-                        printf("    Next elapse: %s\n", format_timestamp(buffer, sizeof(buffer), next));
+static int test_calendar(int argc, char *argv[], void *userdata) {
+        int ret = 0, r;
+        char **p;
+        usec_t n;
 
-                        if (!in_utc_timezone())
-                                printf("       (in UTC): %s\n", format_timestamp_utc(buffer, sizeof(buffer), next));
+        n = now(CLOCK_REALTIME); /* We want to use the same "base" for all expressions */
 
-                        printf("       From now: %s\n", format_timestamp_relative(buffer, sizeof(buffer), next));
-                }
+        STRV_FOREACH(p, strv_skip(argv, 1)) {
+                r = test_calendar_one(n, *p);
+                if (ret == 0 && r < 0)
+                        ret = r;
 
-                if (*(p+1))
+                if (*(p + 1))
                         putchar('\n');
         }
 
@@ -1782,6 +1895,19 @@ static int do_verify(int argc, char *argv[], void *userdata) {
         return verify_units(strv_skip(argv, 1), arg_scope, arg_man, arg_generators);
 }
 
+static int do_security(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        int r;
+
+        r = acquire_bus(&bus, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create bus connection: %m");
+
+        (void) pager_open(arg_pager_flags);
+
+        return analyze_security(bus, strv_skip(argv, 1), 0);
+}
+
 static int help(int argc, char *argv[], void *userdata) {
         _cleanup_free_ char *link = NULL;
         int r;
@@ -1806,10 +1932,11 @@ static int help(int argc, char *argv[], void *userdata) {
                "     --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 also services which finished SECONDS\n"
-               "                           earlier than the latest in the branch\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"
                "\nCommands:\n"
                "  time                     Print time spent in the kernel\n"
                "  blame                    Print list of running units ordered by time to init\n"
@@ -1823,16 +1950,18 @@ static int help(int argc, char *argv[], void *userdata) {
                "  unit-paths               List load directories for units\n"
                "  syscall-filter [NAME...] Print list of syscalls in seccomp filter\n"
                "  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"
+               "  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"
                "\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
-         * shell-completion/zsh/_systemd-analyze too. */
+        /* When updating this list, including descriptions, apply changes to
+         * shell-completion/bash/systemd-analyze and shell-completion/zsh/_systemd-analyze too. */
 
         return 0;
 }
@@ -1852,6 +1981,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_NO_PAGER,
                 ARG_MAN,
                 ARG_GENERATORS,
+                ARG_ITERATIONS,
         };
 
         static const struct option options[] = {
@@ -1871,6 +2001,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "generators",   optional_argument, NULL, ARG_GENERATORS       },
                 { "host",         required_argument, NULL, 'H'                  },
                 { "machine",      required_argument, NULL, 'M'                  },
+                { "iterations",   required_argument, NULL, ARG_ITERATIONS       },
                 {}
         };
 
@@ -1970,6 +2101,13 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case ARG_ITERATIONS:
+                        r = safe_atou(optarg, &arg_iterations);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse iterations: %s", optarg);
+
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -1982,6 +2120,10 @@ static int parse_argv(int argc, char *argv[]) {
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "Option --global only makes sense with verbs dot, unit-paths, verify.");
 
+        if (streq_ptr(argv[optind], "cat-config") && arg_scope == UNIT_FILE_USER)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Option --user is not supported for cat-config right now.");
+
         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.");
@@ -2011,8 +2153,10 @@ static int run(int argc, char *argv[]) {
                 { "syscall-filter",    VERB_ANY, VERB_ANY, 0,            dump_syscall_filters   },
                 { "verify",            2,        VERB_ANY, 0,            do_verify              },
                 { "calendar",          2,        VERB_ANY, 0,            test_calendar          },
-                { "service-watchdogs", VERB_ANY, 2,        0,            service_watchdogs      },
+                { "timestamp",         2,        VERB_ANY, 0,            test_timestamp         },
                 { "timespan",          2,        VERB_ANY, 0,            dump_timespan          },
+                { "service-watchdogs", VERB_ANY, 2,        0,            service_watchdogs      },
+                { "security",          VERB_ANY, VERB_ANY, 0,            do_security            },
                 {}
         };
 
@@ -2021,6 +2165,7 @@ static int run(int argc, char *argv[]) {
         setlocale(LC_ALL, "");
         setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
 
+        log_show_color(true);
         log_parse_environment();
         log_open();