diff options
Diffstat (limited to 'src/analyze/analyze.c')
-rw-r--r-- | src/analyze/analyze.c | 605 |
1 files changed, 368 insertions, 237 deletions
diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index de0fe6eba8..1f69b9fda1 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -4,6 +4,7 @@ ***/ #include <getopt.h> +#include <inttypes.h> #include <locale.h> #include <stdio.h> #include <stdlib.h> @@ -11,28 +12,33 @@ #include "sd-bus.h" #include "alloc-util.h" +#include "analyze-security.h" #include "analyze-verify.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" #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" @@ -41,8 +47,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) \ @@ -66,7 +70,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; @@ -74,6 +78,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; @@ -88,6 +95,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 @@ -156,10 +169,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; } @@ -181,22 +192,18 @@ 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 unit_times_free(struct unit_times *t) { @@ -219,75 +226,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", - ×.firmware_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LoaderTimestampMonotonic", - ×.loader_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "KernelTimestamp", - ×.kernel_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "InitRDTimestampMonotonic", - ×.initrd_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UserspaceTimestampMonotonic", - ×.userspace_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "FinishTimestampMonotonic", - ×.finish_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SecurityStartTimestampMonotonic", - ×.security_start_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SecurityFinishTimestampMonotonic", - ×.security_finish_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GeneratorsStartTimestampMonotonic", - ×.generators_start_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GeneratorsFinishTimestampMonotonic", - ×.generators_finish_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UnitsLoadStartTimestampMonotonic", - ×.unitsload_start_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UnitsLoadFinishTimestampMonotonic", - ×.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, + ×); + 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" @@ -298,23 +278,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(×.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(×.security_start_time, times.reverse_offset); - subtract_timestamp(×.security_finish_time, times.reverse_offset); + subtract_timestamp(×.finish_time, times.reverse_offset); subtract_timestamp(×.generators_start_time, times.reverse_offset); subtract_timestamp(×.generators_finish_time, times.reverse_offset); @@ -348,13 +328,20 @@ 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; - struct boot_times *boot_times = NULL; _cleanup_(unit_times_freep) struct unit_times *unit_times = NULL; - size_t size = 0; + struct boot_times *boot_times = NULL; + size_t allocated = 0, c = 0; UnitInfo u; + int r; r = acquire_boot_times(bus, &boot_times); if (r < 0) @@ -368,10 +355,8 @@ 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)); - return r; - } + 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) @@ -380,7 +365,7 @@ 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, size, c+2)) + if (!GREEDY_REALLOC(unit_times, allocated, c+2)) return log_oom(); unit_times[c+1].has_data = false; @@ -389,23 +374,17 @@ static int acquire_time_data(sd_bus *bus, struct unit_times **out) { 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) - return -EIO; + 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); @@ -453,6 +432,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; @@ -460,7 +440,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, @@ -468,9 +456,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", @@ -524,7 +515,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; } @@ -536,21 +527,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"); @@ -585,16 +579,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; _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) @@ -608,7 +624,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; @@ -618,7 +634,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) @@ -633,14 +649,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->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; } @@ -653,12 +668,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; @@ -710,7 +726,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), @@ -733,43 +749,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->has_data; 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"); @@ -785,9 +798,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++; @@ -802,15 +817,15 @@ static int analyze_plot(int argc, char *argv[], void *userdata) { 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--) - 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) @@ -844,8 +859,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; @@ -856,7 +870,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) { @@ -864,8 +878,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; @@ -881,7 +895,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) @@ -890,11 +904,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) @@ -967,10 +978,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) @@ -1021,7 +1030,7 @@ static int analyze_critical_chain(int argc, char *argv[], void *userdata) { } 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"); @@ -1051,9 +1060,9 @@ 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 (u = times; u->has_data; u++) { char ts[FORMAT_TIMESPAN_MAX]; @@ -1229,10 +1238,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) @@ -1303,7 +1310,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); @@ -1334,15 +1341,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_no_pager, false); + (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)) { @@ -1354,10 +1362,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; @@ -1497,29 +1505,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; @@ -1548,11 +1641,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; + 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(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; @@ -1669,9 +1785,28 @@ 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; + + (void) pager_open(arg_pager_flags); - (void) pager_open(arg_no_pager, false); + 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" @@ -1689,9 +1824,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" @@ -1706,10 +1841,14 @@ 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); - - /* When updating this list, including descriptions, apply - * changes to shell-completion/bash/systemd-analyze and + " 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. */ return 0; @@ -1809,7 +1948,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': @@ -1825,10 +1964,9 @@ 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; } else @@ -1839,10 +1977,9 @@ 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; } else @@ -1858,20 +1995,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 }, @@ -1894,6 +2029,8 @@ 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 }, + { "security", VERB_ANY, VERB_ANY, 0, do_security }, {} }; @@ -1907,15 +2044,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); |