1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 Copyright © 2013 Simon Peeters
14 #include "alloc-util.h"
16 #include "analyze-calendar.h"
17 #include "analyze-capability.h"
18 #include "analyze-condition.h"
19 #include "analyze-dot.h"
20 #include "analyze-dump.h"
21 #include "analyze-elf.h"
22 #include "analyze-exit-status.h"
23 #include "analyze-filesystems.h"
24 #include "analyze-security.h"
25 #include "analyze-service-watchdogs.h"
26 #include "analyze-syscall-filter.h"
27 #include "analyze-timespan.h"
28 #include "analyze-timestamp.h"
29 #include "analyze-verify.h"
30 #include "bus-error.h"
31 #include "bus-locator.h"
32 #include "bus-map-properties.h"
33 #include "bus-unit-util.h"
34 #include "calendarspec.h"
36 #include "capability-util.h"
37 #include "conf-files.h"
40 #include "exit-status.h"
41 #include "extract-word.h"
44 #include "filesystems.h"
45 #include "format-table.h"
46 #include "glob-util.h"
48 #include "locale-util.h"
50 #include "main-func.h"
51 #include "mount-util.h"
52 #include "nulstr-util.h"
54 #include "parse-argument.h"
55 #include "parse-util.h"
56 #include "path-util.h"
57 #include "pretty-print.h"
60 # include "seccomp-util.h"
62 #include "sort-util.h"
64 #include "stat-util.h"
65 #include "string-table.h"
68 #include "terminal-util.h"
69 #include "time-util.h"
70 #include "tmpfile-util.h"
71 #include "unit-name.h"
73 #include "verb-log-control.h"
77 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
78 #define SCALE_Y (20.0)
80 #define svg(...) printf(__VA_ARGS__)
82 #define svg_bar(class, x1, x2, y) \
83 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
85 SCALE_X * (x1), SCALE_Y * (y), \
86 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
88 #define svg_text(b, x, y, format, ...) \
90 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
91 svg(format, ## __VA_ARGS__); \
95 DotMode arg_dot
= DEP_ALL
;
96 char **arg_dot_from_patterns
= NULL
, **arg_dot_to_patterns
= NULL
;
97 static usec_t arg_fuzz
= 0;
98 PagerFlags arg_pager_flags
= 0;
99 BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
100 static const char *arg_host
= NULL
;
101 static UnitFileScope arg_scope
= UNIT_FILE_SYSTEM
;
102 static RecursiveErrors arg_recursive_errors
= RECURSIVE_ERRORS_YES
;
103 static bool arg_man
= true;
104 static bool arg_generators
= false;
105 static char *arg_root
= NULL
;
106 static char *arg_image
= NULL
;
107 static char *arg_security_policy
= NULL
;
108 static bool arg_offline
= false;
109 static unsigned arg_threshold
= 100;
110 unsigned arg_iterations
= 1;
111 usec_t arg_base_time
= USEC_INFINITY
;
112 static char *arg_unit
= NULL
;
113 static JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
114 bool arg_quiet
= false;
115 static char *arg_profile
= NULL
;
117 STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns
, strv_freep
);
118 STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns
, strv_freep
);
119 STATIC_DESTRUCTOR_REGISTER(arg_root
, freep
);
120 STATIC_DESTRUCTOR_REGISTER(arg_image
, freep
);
121 STATIC_DESTRUCTOR_REGISTER(arg_security_policy
, freep
);
122 STATIC_DESTRUCTOR_REGISTER(arg_unit
, freep
);
123 STATIC_DESTRUCTOR_REGISTER(arg_profile
, freep
);
125 typedef struct BootTimes
{
126 usec_t firmware_time
;
129 usec_t kernel_done_time
;
131 usec_t userspace_time
;
133 usec_t security_start_time
;
134 usec_t security_finish_time
;
135 usec_t generators_start_time
;
136 usec_t generators_finish_time
;
137 usec_t unitsload_start_time
;
138 usec_t unitsload_finish_time
;
139 usec_t initrd_security_start_time
;
140 usec_t initrd_security_finish_time
;
141 usec_t initrd_generators_start_time
;
142 usec_t initrd_generators_finish_time
;
143 usec_t initrd_unitsload_start_time
;
144 usec_t initrd_unitsload_finish_time
;
147 * If we're analyzing the user instance, all timestamps will be offset
148 * by its own start-up timestamp, which may be arbitrarily big.
149 * With "plot", this causes arbitrarily wide output SVG files which almost
150 * completely consist of empty space. Thus we cancel out this offset.
152 * This offset is subtracted from times above by acquire_boot_times(),
153 * but it still needs to be subtracted from unit-specific timestamps
154 * (so it is stored here for reference).
156 usec_t reverse_offset
;
159 typedef struct UnitTimes
{
169 typedef struct HostInfo
{
172 char *kernel_release
;
173 char *kernel_version
;
174 char *os_pretty_name
;
175 char *virtualization
;
179 int acquire_bus(sd_bus
**bus
, bool *use_full_bus
) {
180 bool user
= arg_scope
!= UNIT_FILE_SYSTEM
;
183 if (use_full_bus
&& *use_full_bus
) {
184 r
= bus_connect_transport(arg_transport
, arg_host
, user
, bus
);
185 if (IN_SET(r
, 0, -EHOSTDOWN
))
188 *use_full_bus
= false;
191 return bus_connect_transport_systemd(arg_transport
, arg_host
, user
, bus
);
194 static int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
195 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
204 r
= sd_bus_get_property_trivial(
206 "org.freedesktop.systemd1",
214 return log_error_errno(r
, "Failed to parse reply: %s", bus_error_message(&error
, r
));
219 int bus_get_unit_property_strv(sd_bus
*bus
, const char *path
, const char *property
, char ***strv
) {
220 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
228 r
= sd_bus_get_property_strv(
230 "org.freedesktop.systemd1",
232 "org.freedesktop.systemd1.Unit",
237 return log_error_errno(r
, "Failed to get unit property %s: %s", property
, bus_error_message(&error
, r
));
242 static int compare_unit_start(const UnitTimes
*a
, const UnitTimes
*b
) {
243 return CMP(a
->activating
, b
->activating
);
246 static int process_aliases(char *argv
[], char *tempdir
, char ***ret
) {
247 _cleanup_strv_free_
char **filenames
= NULL
;
255 STRV_FOREACH(filename
, strv_skip(argv
, 1)) {
256 _cleanup_free_
char *src
= NULL
, *dst
= NULL
, *base
= NULL
;
257 const char *parse_arg
;
259 parse_arg
= *filename
;
260 r
= extract_first_word(&parse_arg
, &src
, ":", EXTRACT_DONT_COALESCE_SEPARATORS
|EXTRACT_RETAIN_ESCAPE
);
265 r
= strv_consume(&filenames
, TAKE_PTR(src
));
272 r
= path_extract_filename(parse_arg
, &base
);
276 dst
= path_join(tempdir
, base
);
280 r
= copy_file(src
, dst
, 0, 0644, 0, 0, COPY_REFLINK
);
284 r
= strv_consume(&filenames
, TAKE_PTR(dst
));
289 *ret
= TAKE_PTR(filenames
);
293 static UnitTimes
* unit_times_free_array(UnitTimes
*t
) {
294 for (UnitTimes
*p
= t
; p
&& p
->has_data
; p
++)
298 DEFINE_TRIVIAL_CLEANUP_FUNC(UnitTimes
*, unit_times_free_array
);
300 static void subtract_timestamp(usec_t
*a
, usec_t b
) {
309 static int acquire_boot_times(sd_bus
*bus
, BootTimes
**bt
) {
310 static const struct bus_properties_map property_map
[] = {
311 { "FirmwareTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, firmware_time
) },
312 { "LoaderTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, loader_time
) },
313 { "KernelTimestamp", "t", NULL
, offsetof(BootTimes
, kernel_time
) },
314 { "InitRDTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_time
) },
315 { "UserspaceTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, userspace_time
) },
316 { "FinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, finish_time
) },
317 { "SecurityStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, security_start_time
) },
318 { "SecurityFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, security_finish_time
) },
319 { "GeneratorsStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, generators_start_time
) },
320 { "GeneratorsFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, generators_finish_time
) },
321 { "UnitsLoadStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, unitsload_start_time
) },
322 { "UnitsLoadFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, unitsload_finish_time
) },
323 { "InitRDSecurityStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_security_start_time
) },
324 { "InitRDSecurityFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_security_finish_time
) },
325 { "InitRDGeneratorsStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_generators_start_time
) },
326 { "InitRDGeneratorsFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_generators_finish_time
) },
327 { "InitRDUnitsLoadStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_unitsload_start_time
) },
328 { "InitRDUnitsLoadFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_unitsload_finish_time
) },
331 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
332 static BootTimes times
;
333 static bool cached
= false;
339 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
341 r
= bus_map_all_properties(
343 "org.freedesktop.systemd1",
344 "/org/freedesktop/systemd1",
351 return log_error_errno(r
, "Failed to get timestamp properties: %s", bus_error_message(&error
, r
));
353 if (times
.finish_time
<= 0)
354 return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS
),
355 "Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64
").\n"
356 "Please try again later.\n"
357 "Hint: Use 'systemctl%s list-jobs' to see active jobs",
359 arg_scope
== UNIT_FILE_SYSTEM
? "" : " --user");
361 if (arg_scope
== UNIT_FILE_SYSTEM
&& times
.security_start_time
> 0) {
362 /* security_start_time is set when systemd is not running under container environment. */
363 if (times
.initrd_time
> 0)
364 times
.kernel_done_time
= times
.initrd_time
;
366 times
.kernel_done_time
= times
.userspace_time
;
369 * User-instance-specific or container-system-specific timestamps processing
370 * (see comment to reverse_offset in BootTimes).
372 times
.reverse_offset
= times
.userspace_time
;
374 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
=
375 times
.userspace_time
= times
.security_start_time
= times
.security_finish_time
= 0;
377 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
379 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
380 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
382 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
383 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
393 static HostInfo
* free_host_info(HostInfo
*hi
) {
398 free(hi
->kernel_name
);
399 free(hi
->kernel_release
);
400 free(hi
->kernel_version
);
401 free(hi
->os_pretty_name
);
402 free(hi
->virtualization
);
403 free(hi
->architecture
);
407 DEFINE_TRIVIAL_CLEANUP_FUNC(HostInfo
*, free_host_info
);
409 static int acquire_time_data(sd_bus
*bus
, UnitTimes
**out
) {
410 static const struct bus_properties_map property_map
[] = {
411 { "InactiveExitTimestampMonotonic", "t", NULL
, offsetof(UnitTimes
, activating
) },
412 { "ActiveEnterTimestampMonotonic", "t", NULL
, offsetof(UnitTimes
, activated
) },
413 { "ActiveExitTimestampMonotonic", "t", NULL
, offsetof(UnitTimes
, deactivating
) },
414 { "InactiveEnterTimestampMonotonic", "t", NULL
, offsetof(UnitTimes
, deactivated
) },
417 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
418 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
419 _cleanup_(unit_times_free_arrayp
) UnitTimes
*unit_times
= NULL
;
420 BootTimes
*boot_times
= NULL
;
425 r
= acquire_boot_times(bus
, &boot_times
);
429 r
= bus_call_method(bus
, bus_systemd_mgr
, "ListUnits", &error
, &reply
, NULL
);
431 return log_error_errno(r
, "Failed to list units: %s", bus_error_message(&error
, r
));
433 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
435 return bus_log_parse_error(r
);
437 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
440 if (!GREEDY_REALLOC(unit_times
, c
+ 2))
443 unit_times
[c
+ 1].has_data
= false;
447 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
449 r
= bus_map_all_properties(
451 "org.freedesktop.systemd1",
459 return log_error_errno(r
, "Failed to get timestamp properties of unit %s: %s",
460 u
.id
, bus_error_message(&error
, r
));
462 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
463 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
464 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
465 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
467 if (t
->activated
>= t
->activating
)
468 t
->time
= t
->activated
- t
->activating
;
469 else if (t
->deactivated
>= t
->activating
)
470 t
->time
= t
->deactivated
- t
->activating
;
474 if (t
->activating
== 0)
477 t
->name
= strdup(u
.id
);
485 return bus_log_parse_error(r
);
487 *out
= TAKE_PTR(unit_times
);
491 static int acquire_host_info(sd_bus
*bus
, HostInfo
**hi
) {
492 static const struct bus_properties_map hostname_map
[] = {
493 { "Hostname", "s", NULL
, offsetof(HostInfo
, hostname
) },
494 { "KernelName", "s", NULL
, offsetof(HostInfo
, kernel_name
) },
495 { "KernelRelease", "s", NULL
, offsetof(HostInfo
, kernel_release
) },
496 { "KernelVersion", "s", NULL
, offsetof(HostInfo
, kernel_version
) },
497 { "OperatingSystemPrettyName", "s", NULL
, offsetof(HostInfo
, os_pretty_name
) },
501 static const struct bus_properties_map manager_map
[] = {
502 { "Virtualization", "s", NULL
, offsetof(HostInfo
, virtualization
) },
503 { "Architecture", "s", NULL
, offsetof(HostInfo
, architecture
) },
507 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
508 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*system_bus
= NULL
;
509 _cleanup_(free_host_infop
) HostInfo
*host
= NULL
;
512 host
= new0(HostInfo
, 1);
516 if (arg_scope
!= UNIT_FILE_SYSTEM
) {
517 r
= bus_connect_transport(arg_transport
, arg_host
, false, &system_bus
);
519 log_debug_errno(r
, "Failed to connect to system bus, ignoring: %m");
524 r
= bus_map_all_properties(
526 "org.freedesktop.hostname1",
527 "/org/freedesktop/hostname1",
534 log_debug_errno(r
, "Failed to get host information from systemd-hostnamed, ignoring: %s",
535 bus_error_message(&error
, r
));
536 sd_bus_error_free(&error
);
540 r
= bus_map_all_properties(
542 "org.freedesktop.systemd1",
543 "/org/freedesktop/systemd1",
550 return log_error_errno(r
, "Failed to get host information from systemd: %s",
551 bus_error_message(&error
, r
));
553 *hi
= TAKE_PTR(host
);
557 static int pretty_boot_time(sd_bus
*bus
, char **_buf
) {
559 static char buf
[4096];
563 usec_t activated_time
= USEC_INFINITY
;
564 _cleanup_free_
char *path
= NULL
, *unit_id
= NULL
;
565 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
567 r
= acquire_boot_times(bus
, &t
);
571 path
= unit_dbus_path_from_name(SPECIAL_DEFAULT_TARGET
);
575 r
= sd_bus_get_property_string(
577 "org.freedesktop.systemd1",
579 "org.freedesktop.systemd1.Unit",
584 log_error_errno(r
, "default.target doesn't seem to exist: %s", bus_error_message(&error
, r
));
588 r
= bus_get_uint64_property(bus
, path
,
589 "org.freedesktop.systemd1.Unit",
590 "ActiveEnterTimestampMonotonic",
593 log_info_errno(r
, "Could not get time to reach default.target, ignoring: %m");
594 activated_time
= USEC_INFINITY
;
600 size
= strpcpyf(&ptr
, size
, "Startup finished in ");
601 if (t
->firmware_time
> 0)
602 size
= strpcpyf(&ptr
, size
, "%s (firmware) + ", FORMAT_TIMESPAN(t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
));
603 if (t
->loader_time
> 0)
604 size
= strpcpyf(&ptr
, size
, "%s (loader) + ", FORMAT_TIMESPAN(t
->loader_time
, USEC_PER_MSEC
));
605 if (t
->kernel_done_time
> 0)
606 size
= strpcpyf(&ptr
, size
, "%s (kernel) + ", FORMAT_TIMESPAN(t
->kernel_done_time
, USEC_PER_MSEC
));
607 if (t
->initrd_time
> 0)
608 size
= strpcpyf(&ptr
, size
, "%s (initrd) + ", FORMAT_TIMESPAN(t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
));
610 size
= strpcpyf(&ptr
, size
, "%s (userspace) ", FORMAT_TIMESPAN(t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
));
611 if (t
->kernel_done_time
> 0)
612 strpcpyf(&ptr
, size
, "= %s ", FORMAT_TIMESPAN(t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
));
614 if (unit_id
&& timestamp_is_set(activated_time
)) {
615 usec_t base
= t
->userspace_time
> 0 ? t
->userspace_time
: t
->reverse_offset
;
617 size
= strpcpyf(&ptr
, size
, "\n%s reached after %s in userspace", unit_id
,
618 FORMAT_TIMESPAN(activated_time
- base
, USEC_PER_MSEC
));
619 } else if (unit_id
&& activated_time
== 0)
620 size
= strpcpyf(&ptr
, size
, "\n%s was never reached", unit_id
);
621 else if (unit_id
&& activated_time
== USEC_INFINITY
)
622 size
= strpcpyf(&ptr
, size
, "\nCould not get time to reach %s.", unit_id
);
624 size
= strpcpyf(&ptr
, size
, "\ncould not find default.target");
634 static void svg_graph_box(double height
, double begin
, double end
) {
635 /* outside box, fill */
636 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
637 SCALE_X
* (end
- begin
),
640 for (long long i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+= 100000) {
641 /* lines for each second */
642 if (i
% 5000000 == 0)
643 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
644 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
651 else if (i
% 1000000 == 0)
652 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
653 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
661 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
668 static int plot_unit_times(UnitTimes
*u
, double width
, int y
) {
674 svg_bar("activating", u
->activating
, u
->activated
, y
);
675 svg_bar("active", u
->activated
, u
->deactivating
, y
);
676 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
678 /* place the text on the left if we have passed the half of the svg width */
679 b
= u
->activating
* SCALE_X
< width
/ 2;
681 svg_text(b
, u
->activating
, y
, "%s (%s)",
682 u
->name
, FORMAT_TIMESPAN(u
->time
, USEC_PER_MSEC
));
684 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
689 static int analyze_plot(int argc
, char *argv
[], void *userdata
) {
690 _cleanup_(free_host_infop
) HostInfo
*host
= NULL
;
691 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
692 _cleanup_(unit_times_free_arrayp
) UnitTimes
*times
= NULL
;
693 _cleanup_free_
char *pretty_times
= NULL
;
694 bool use_full_bus
= arg_scope
== UNIT_FILE_SYSTEM
;
697 int n
, m
= 1, y
= 0, r
;
700 r
= acquire_bus(&bus
, &use_full_bus
);
702 return bus_log_connect_error(r
, arg_transport
);
704 n
= acquire_boot_times(bus
, &boot
);
708 n
= pretty_boot_time(bus
, &pretty_times
);
712 if (use_full_bus
|| arg_scope
!= UNIT_FILE_SYSTEM
) {
713 n
= acquire_host_info(bus
, &host
);
718 n
= acquire_time_data(bus
, ×
);
722 typesafe_qsort(times
, n
, compare_unit_start
);
724 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
728 if (boot
->firmware_time
> boot
->loader_time
)
730 if (boot
->loader_time
> 0) {
735 if (boot
->initrd_time
> 0)
737 if (boot
->kernel_done_time
> 0)
740 for (u
= times
; u
->has_data
; u
++) {
741 double text_start
, text_width
;
743 if (u
->activating
> boot
->finish_time
) {
744 u
->name
= mfree(u
->name
);
748 /* If the text cannot fit on the left side then
749 * increase the svg width so it fits on the right.
750 * TODO: calculate the text width more accurately */
751 text_width
= 8.0 * strlen(u
->name
);
752 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
753 if (text_width
> text_start
&& text_width
+ text_start
> width
)
754 width
= text_width
+ text_start
;
756 if (u
->deactivated
> u
->activating
&&
757 u
->deactivated
<= boot
->finish_time
&&
758 u
->activated
== 0 && u
->deactivating
== 0)
759 u
->activated
= u
->deactivating
= u
->deactivated
;
760 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
761 u
->activated
= boot
->finish_time
;
762 if (u
->deactivating
< u
->activated
|| u
->deactivating
> boot
->finish_time
)
763 u
->deactivating
= boot
->finish_time
;
764 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
765 u
->deactivated
= boot
->finish_time
;
769 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
770 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
771 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
773 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
774 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
775 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
776 5 * SCALE_Y
/* legend */);
778 /* write some basic info as a comment, including some help */
779 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
780 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
781 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
782 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
783 "<!-- point your browser to this file. -->\n\n"
784 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", GIT_VERSION
);
787 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
788 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
789 " rect.background { fill: rgb(255,255,255); }\n"
790 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
791 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
792 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
793 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
794 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
795 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
796 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
797 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
798 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
799 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
800 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
801 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
802 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
804 " line.sec5 { stroke-width: 2; }\n"
805 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
806 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
807 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
808 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
809 " text.sec { font-size: 10px; }\n"
810 " ]]>\n </style>\n</defs>\n\n");
812 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
813 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
815 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
816 isempty(host
->os_pretty_name
) ? "Linux" : host
->os_pretty_name
,
817 strempty(host
->hostname
),
818 strempty(host
->kernel_name
),
819 strempty(host
->kernel_release
),
820 strempty(host
->kernel_version
),
821 strempty(host
->architecture
),
822 strempty(host
->virtualization
));
824 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
825 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
827 if (boot
->firmware_time
> 0) {
828 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
829 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
832 if (boot
->loader_time
> 0) {
833 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
834 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
837 if (boot
->kernel_done_time
> 0) {
838 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
839 svg_text(true, 0, y
, "kernel");
842 if (boot
->initrd_time
> 0) {
843 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
844 if (boot
->initrd_security_start_time
< boot
->initrd_security_finish_time
)
845 svg_bar("security", boot
->initrd_security_start_time
, boot
->initrd_security_finish_time
, y
);
846 if (boot
->initrd_generators_start_time
< boot
->initrd_generators_finish_time
)
847 svg_bar("generators", boot
->initrd_generators_start_time
, boot
->initrd_generators_finish_time
, y
);
848 if (boot
->initrd_unitsload_start_time
< boot
->initrd_unitsload_finish_time
)
849 svg_bar("unitsload", boot
->initrd_unitsload_start_time
, boot
->initrd_unitsload_finish_time
, y
);
850 svg_text(true, boot
->initrd_time
, y
, "initrd");
854 for (u
= times
; u
->has_data
; u
++) {
855 if (u
->activating
>= boot
->userspace_time
)
858 y
+= plot_unit_times(u
, width
, y
);
861 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
862 if (boot
->security_start_time
> 0)
863 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
864 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
865 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
866 svg_text(true, boot
->userspace_time
, y
, "systemd");
869 for (; u
->has_data
; u
++)
870 y
+= plot_unit_times(u
, width
, y
);
875 svg("<g transform=\"translate(20,100)\">\n");
877 svg_bar("activating", 0, 300000, y
);
878 svg_text(true, 400000, y
, "Activating");
880 svg_bar("active", 0, 300000, y
);
881 svg_text(true, 400000, y
, "Active");
883 svg_bar("deactivating", 0, 300000, y
);
884 svg_text(true, 400000, y
, "Deactivating");
886 if (boot
->security_start_time
> 0) {
887 svg_bar("security", 0, 300000, y
);
888 svg_text(true, 400000, y
, "Setting up security module");
891 svg_bar("generators", 0, 300000, y
);
892 svg_text(true, 400000, y
, "Generators");
894 svg_bar("unitsload", 0, 300000, y
);
895 svg_text(true, 400000, y
, "Loading unit files");
905 static int list_dependencies_print(
913 for (unsigned i
= level
; i
!= 0; i
--)
914 printf("%s", special_glyph(branches
& (1 << (i
-1)) ? SPECIAL_GLYPH_TREE_VERTICAL
: SPECIAL_GLYPH_TREE_SPACE
));
916 printf("%s", special_glyph(last
? SPECIAL_GLYPH_TREE_RIGHT
: SPECIAL_GLYPH_TREE_BRANCH
));
920 printf("%s%s @%s +%s%s", ansi_highlight_red(), name
,
921 FORMAT_TIMESPAN(times
->activating
- boot
->userspace_time
, USEC_PER_MSEC
),
922 FORMAT_TIMESPAN(times
->time
, USEC_PER_MSEC
), ansi_normal());
923 else if (times
->activated
> boot
->userspace_time
)
924 printf("%s @%s", name
, FORMAT_TIMESPAN(times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
934 static int list_dependencies_get_dependencies(sd_bus
*bus
, const char *name
, char ***deps
) {
935 _cleanup_free_
char *path
= NULL
;
941 path
= unit_dbus_path_from_name(name
);
945 return bus_get_unit_property_strv(bus
, path
, "After", deps
);
948 static Hashmap
*unit_times_hashmap
;
950 static int list_dependencies_compare(char *const *a
, char *const *b
) {
951 usec_t usa
= 0, usb
= 0;
954 times
= hashmap_get(unit_times_hashmap
, *a
);
956 usa
= times
->activated
;
957 times
= hashmap_get(unit_times_hashmap
, *b
);
959 usb
= times
->activated
;
961 return CMP(usb
, usa
);
964 static bool times_in_range(const UnitTimes
*times
, const BootTimes
*boot
) {
965 return times
&& times
->activated
> 0 && times
->activated
<= boot
->finish_time
;
968 static int list_dependencies_one(sd_bus
*bus
, const char *name
, unsigned level
, char ***units
, unsigned branches
) {
969 _cleanup_strv_free_
char **deps
= NULL
;
972 usec_t service_longest
= 0;
977 if (strv_extend(units
, name
))
980 r
= list_dependencies_get_dependencies(bus
, name
, &deps
);
984 typesafe_qsort(deps
, strv_length(deps
), list_dependencies_compare
);
986 r
= acquire_boot_times(bus
, &boot
);
990 STRV_FOREACH(c
, deps
) {
991 times
= hashmap_get(unit_times_hashmap
, *c
); /* lgtm [cpp/inconsistent-null-check] */
992 if (times_in_range(times
, boot
) && times
->activated
>= service_longest
)
993 service_longest
= times
->activated
;
996 if (service_longest
== 0)
999 STRV_FOREACH(c
, deps
) {
1000 times
= hashmap_get(unit_times_hashmap
, *c
); /* lgtm [cpp/inconsistent-null-check] */
1001 if (times_in_range(times
, boot
) && service_longest
- times
->activated
<= arg_fuzz
)
1008 STRV_FOREACH(c
, deps
) {
1009 times
= hashmap_get(unit_times_hashmap
, *c
); /* lgtm [cpp/inconsistent-null-check] */
1010 if (!times_in_range(times
, boot
) || service_longest
- times
->activated
> arg_fuzz
)
1015 r
= list_dependencies_print(*c
, level
, branches
, to_print
== 0, times
, boot
);
1019 if (strv_contains(*units
, *c
)) {
1020 r
= list_dependencies_print("...", level
+ 1, (branches
<< 1) | (to_print
? 1 : 0),
1027 r
= list_dependencies_one(bus
, *c
, level
+ 1, units
, (branches
<< 1) | (to_print
? 1 : 0));
1037 static int list_dependencies(sd_bus
*bus
, const char *name
) {
1038 _cleanup_strv_free_
char **units
= NULL
;
1042 _cleanup_free_
char *path
= NULL
;
1043 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1044 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1049 path
= unit_dbus_path_from_name(name
);
1053 r
= sd_bus_get_property(
1055 "org.freedesktop.systemd1",
1057 "org.freedesktop.systemd1.Unit",
1063 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1065 r
= sd_bus_message_read(reply
, "s", &id
);
1067 return bus_log_parse_error(r
);
1069 times
= hashmap_get(unit_times_hashmap
, id
);
1071 r
= acquire_boot_times(bus
, &boot
);
1077 printf("%s%s +%s%s\n", ansi_highlight_red(), id
,
1078 FORMAT_TIMESPAN(times
->time
, USEC_PER_MSEC
), ansi_normal());
1079 else if (times
->activated
> boot
->userspace_time
)
1080 printf("%s @%s\n", id
,
1081 FORMAT_TIMESPAN(times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
1086 return list_dependencies_one(bus
, name
, 0, &units
, 0);
1089 static int analyze_critical_chain(int argc
, char *argv
[], void *userdata
) {
1090 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1091 _cleanup_(unit_times_free_arrayp
) UnitTimes
*times
= NULL
;
1095 r
= acquire_bus(&bus
, NULL
);
1097 return bus_log_connect_error(r
, arg_transport
);
1099 n
= acquire_time_data(bus
, ×
);
1103 h
= hashmap_new(&string_hash_ops
);
1107 for (UnitTimes
*u
= times
; u
->has_data
; u
++) {
1108 r
= hashmap_put(h
, u
->name
, u
);
1110 return log_error_errno(r
, "Failed to add entry to hashmap: %m");
1112 unit_times_hashmap
= h
;
1114 pager_open(arg_pager_flags
);
1116 puts("The time when unit became active or started is printed after the \"@\" character.\n"
1117 "The time the unit took to start is printed after the \"+\" character.\n");
1121 STRV_FOREACH(name
, strv_skip(argv
, 1))
1122 list_dependencies(bus
, *name
);
1124 list_dependencies(bus
, SPECIAL_DEFAULT_TARGET
);
1126 h
= hashmap_free(h
);
1130 static int analyze_blame(int argc
, char *argv
[], void *userdata
) {
1131 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1132 _cleanup_(unit_times_free_arrayp
) UnitTimes
*times
= NULL
;
1133 _cleanup_(table_unrefp
) Table
*table
= NULL
;
1137 r
= acquire_bus(&bus
, NULL
);
1139 return bus_log_connect_error(r
, arg_transport
);
1141 n
= acquire_time_data(bus
, ×
);
1145 table
= table_new("time", "unit");
1149 table_set_header(table
, false);
1151 assert_se(cell
= table_get_cell(table
, 0, 0));
1152 r
= table_set_ellipsize_percent(table
, cell
, 100);
1156 r
= table_set_align_percent(table
, cell
, 100);
1160 assert_se(cell
= table_get_cell(table
, 0, 1));
1161 r
= table_set_ellipsize_percent(table
, cell
, 100);
1165 r
= table_set_sort(table
, (size_t) 0);
1169 r
= table_set_reverse(table
, 0, true);
1173 for (UnitTimes
*u
= times
; u
->has_data
; u
++) {
1177 r
= table_add_many(table
,
1178 TABLE_TIMESPAN_MSEC
, u
->time
,
1179 TABLE_STRING
, u
->name
);
1181 return table_log_add_error(r
);
1184 pager_open(arg_pager_flags
);
1186 return table_print(table
, NULL
);
1189 static int analyze_time(int argc
, char *argv
[], void *userdata
) {
1190 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1191 _cleanup_free_
char *buf
= NULL
;
1194 r
= acquire_bus(&bus
, NULL
);
1196 return bus_log_connect_error(r
, arg_transport
);
1198 r
= pretty_boot_time(bus
, &buf
);
1206 static int cat_config(int argc
, char *argv
[], void *userdata
) {
1210 pager_open(arg_pager_flags
);
1212 list
= strv_skip(argv
, 1);
1213 STRV_FOREACH(arg
, list
) {
1214 const char *t
= NULL
;
1219 if (path_is_absolute(*arg
)) {
1222 NULSTR_FOREACH(dir
, CONF_PATHS_NULSTR("")) {
1223 t
= path_startswith(*arg
, dir
);
1229 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1230 "Path %s does not start with any known prefix.", *arg
);
1234 r
= conf_files_cat(arg_root
, t
);
1242 static int verb_log_control(int argc
, char *argv
[], void *userdata
) {
1243 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1246 assert(IN_SET(argc
, 1, 2));
1248 r
= acquire_bus(&bus
, NULL
);
1250 return bus_log_connect_error(r
, arg_transport
);
1252 return verb_log_control_common(bus
, "org.freedesktop.systemd1", argv
[0], argc
== 2 ? argv
[1] : NULL
);
1255 static bool strv_fnmatch_strv_or_empty(char* const* patterns
, char **strv
, int flags
) {
1257 STRV_FOREACH(s
, strv
)
1258 if (strv_fnmatch_or_empty(patterns
, *s
, flags
))
1264 static int do_unit_files(int argc
, char *argv
[], void *userdata
) {
1265 _cleanup_(lookup_paths_free
) LookupPaths lp
= {};
1266 _cleanup_hashmap_free_ Hashmap
*unit_ids
= NULL
;
1267 _cleanup_hashmap_free_ Hashmap
*unit_names
= NULL
;
1268 char **patterns
= strv_skip(argv
, 1);
1269 const char *k
, *dst
;
1273 r
= lookup_paths_init(&lp
, arg_scope
, 0, NULL
);
1275 return log_error_errno(r
, "lookup_paths_init() failed: %m");
1277 r
= unit_file_build_name_map(&lp
, NULL
, &unit_ids
, &unit_names
, NULL
);
1279 return log_error_errno(r
, "unit_file_build_name_map() failed: %m");
1281 HASHMAP_FOREACH_KEY(dst
, k
, unit_ids
) {
1282 if (!strv_fnmatch_or_empty(patterns
, k
, FNM_NOESCAPE
) &&
1283 !strv_fnmatch_or_empty(patterns
, dst
, FNM_NOESCAPE
))
1286 printf("ids: %s → %s\n", k
, dst
);
1289 HASHMAP_FOREACH_KEY(v
, k
, unit_names
) {
1290 if (!strv_fnmatch_or_empty(patterns
, k
, FNM_NOESCAPE
) &&
1291 !strv_fnmatch_strv_or_empty(patterns
, v
, FNM_NOESCAPE
))
1294 _cleanup_free_
char *j
= strv_join(v
, ", ");
1295 printf("aliases: %s ← %s\n", k
, j
);
1301 static int dump_unit_paths(int argc
, char *argv
[], void *userdata
) {
1302 _cleanup_(lookup_paths_free
) LookupPaths paths
= {};
1306 r
= lookup_paths_init(&paths
, arg_scope
, 0, NULL
);
1308 return log_error_errno(r
, "lookup_paths_init() failed: %m");
1310 STRV_FOREACH(p
, paths
.search_path
)
1316 void time_parsing_hint(const char *p
, bool calendar
, bool timestamp
, bool timespan
) {
1317 if (calendar
&& calendar_spec_from_string(p
, NULL
) >= 0)
1318 log_notice("Hint: this expression is a valid calendar specification. "
1319 "Use 'systemd-analyze calendar \"%s\"' instead?", p
);
1320 if (timestamp
&& parse_timestamp(p
, NULL
) >= 0)
1321 log_notice("Hint: this expression is a valid timestamp. "
1322 "Use 'systemd-analyze timestamp \"%s\"' instead?", p
);
1323 if (timespan
&& parse_time(p
, NULL
, USEC_PER_SEC
) >= 0)
1324 log_notice("Hint: this expression is a valid timespan. "
1325 "Use 'systemd-analyze timespan \"%s\"' instead?", p
);
1328 static int do_condition(int argc
, char *argv
[], void *userdata
) {
1329 return verify_conditions(strv_skip(argv
, 1), arg_scope
, arg_unit
, arg_root
);
1332 static int do_verify(int argc
, char *argv
[], void *userdata
) {
1333 _cleanup_strv_free_
char **filenames
= NULL
;
1334 _cleanup_(rm_rf_physical_and_freep
) char *tempdir
= NULL
;
1337 r
= mkdtemp_malloc("/tmp/systemd-analyze-XXXXXX", &tempdir
);
1339 return log_error_errno(r
, "Failed to setup working directory: %m");
1341 r
= process_aliases(argv
, tempdir
, &filenames
);
1343 return log_error_errno(r
, "Couldn't process aliases: %m");
1345 return verify_units(filenames
, arg_scope
, arg_man
, arg_generators
, arg_recursive_errors
, arg_root
);
1348 static int do_security(int argc
, char *argv
[], void *userdata
) {
1349 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1350 _cleanup_(json_variant_unrefp
) JsonVariant
*policy
= NULL
;
1352 unsigned line
, column
;
1355 r
= acquire_bus(&bus
, NULL
);
1357 return bus_log_connect_error(r
, arg_transport
);
1360 pager_open(arg_pager_flags
);
1362 if (arg_security_policy
) {
1363 r
= json_parse_file(/*f=*/ NULL
, arg_security_policy
, /*flags=*/ 0, &policy
, &line
, &column
);
1365 return log_error_errno(r
, "Failed to parse '%s' at %u:%u: %m", arg_security_policy
, line
, column
);
1367 _cleanup_fclose_
FILE *f
= NULL
;
1368 _cleanup_free_
char *pp
= NULL
;
1370 r
= search_and_fopen_nulstr("systemd-analyze-security.policy", "re", /*root=*/ NULL
, CONF_PATHS_NULSTR("systemd"), &f
, &pp
);
1371 if (r
< 0 && r
!= -ENOENT
)
1375 r
= json_parse_file(f
, pp
, /*flags=*/ 0, &policy
, &line
, &column
);
1377 return log_error_errno(r
, "[%s:%u:%u] Failed to parse JSON policy: %m", pp
, line
, column
);
1381 return analyze_security(bus
,
1391 arg_json_format_flags
,
1396 static int do_elf_inspection(int argc
, char *argv
[], void *userdata
) {
1397 pager_open(arg_pager_flags
);
1399 return analyze_elf(strv_skip(argv
, 1), arg_json_format_flags
);
1402 static int help(int argc
, char *argv
[], void *userdata
) {
1403 _cleanup_free_
char *link
= NULL
, *dot_link
= NULL
;
1406 pager_open(arg_pager_flags
);
1408 r
= terminal_urlify_man("systemd-analyze", "1", &link
);
1412 /* Not using terminal_urlify_man() for this, since we don't want the "man page" text suffix in this case. */
1413 r
= terminal_urlify("man:dot(1)", "dot(1)", &dot_link
);
1417 printf("%s [OPTIONS...] COMMAND ...\n\n"
1418 "%sProfile systemd, show unit dependencies, check unit files.%s\n"
1420 " [time] Print time required to boot the machine\n"
1421 " blame Print list of running units ordered by\n"
1423 " critical-chain [UNIT...] Print a tree of the time critical chain\n"
1425 " plot Output SVG graphic showing service\n"
1427 " dot [UNIT...] Output dependency graph in %s format\n"
1428 " dump Output state serialization of service\n"
1430 " cat-config Show configuration file and drop-ins\n"
1431 " unit-files List files and symlinks for units\n"
1432 " unit-paths List load directories for units\n"
1433 " exit-status [STATUS...] List exit status definitions\n"
1434 " capability [CAP...] List capability definitions\n"
1435 " syscall-filter [NAME...] List syscalls in seccomp filters\n"
1436 " filesystems [NAME...] List known filesystems\n"
1437 " condition CONDITION... Evaluate conditions and asserts\n"
1438 " verify FILE... Check unit files for correctness\n"
1439 " calendar SPEC... Validate repetitive calendar time\n"
1441 " timestamp TIMESTAMP... Validate a timestamp\n"
1442 " timespan SPAN... Validate a time span\n"
1443 " security [UNIT...] Analyze security of unit\n"
1444 " inspect-elf FILE... Parse and print ELF package metadata\n"
1446 " --recursive-errors=MODE Control which units are verified\n"
1447 " --offline=BOOL Perform a security review on unit file(s)\n"
1448 " --threshold=N Exit with a non-zero status when overall\n"
1449 " exposure level is over threshold value\n"
1450 " --security-policy=PATH Use custom JSON security policy instead\n"
1451 " of built-in one\n"
1452 " --json=pretty|short|off Generate JSON output of the security\n"
1454 " --no-pager Do not pipe output into a pager\n"
1455 " --system Operate on system systemd instance\n"
1456 " --user Operate on user systemd instance\n"
1457 " --global Operate on global user configuration\n"
1458 " -H --host=[USER@]HOST Operate on remote host\n"
1459 " -M --machine=CONTAINER Operate on local container\n"
1460 " --order Show only order in the graph\n"
1461 " --require Show only requirement in the graph\n"
1462 " --from-pattern=GLOB Show only origins in the graph\n"
1463 " --to-pattern=GLOB Show only destinations in the graph\n"
1464 " --fuzz=SECONDS Also print services which finished SECONDS\n"
1465 " earlier than the latest in the branch\n"
1466 " --man[=BOOL] Do [not] check for existence of man pages\n"
1467 " --generators[=BOOL] Do [not] run unit generators\n"
1468 " (requires privileges)\n"
1469 " --iterations=N Show the specified number of iterations\n"
1470 " --base-time=TIMESTAMP Calculate calendar times relative to\n"
1472 " --profile=name|PATH Include the specified profile in the\n"
1473 " security review of the unit(s)\n"
1474 " -h --help Show this help\n"
1475 " --version Show package version\n"
1476 " -q --quiet Do not emit hints\n"
1477 "\nSee the %s for details.\n",
1478 program_invocation_short_name
,
1484 /* When updating this list, including descriptions, apply changes to
1485 * shell-completion/bash/systemd-analyze and shell-completion/zsh/_systemd-analyze too. */
1490 static int parse_argv(int argc
, char *argv
[]) {
1492 ARG_VERSION
= 0x100,
1500 ARG_DOT_FROM_PATTERN
,
1508 ARG_RECURSIVE_ERRORS
,
1511 ARG_SECURITY_POLICY
,
1516 static const struct option options
[] = {
1517 { "help", no_argument
, NULL
, 'h' },
1518 { "version", no_argument
, NULL
, ARG_VERSION
},
1519 { "quiet", no_argument
, NULL
, 'q' },
1520 { "order", no_argument
, NULL
, ARG_ORDER
},
1521 { "require", no_argument
, NULL
, ARG_REQUIRE
},
1522 { "root", required_argument
, NULL
, ARG_ROOT
},
1523 { "image", required_argument
, NULL
, ARG_IMAGE
},
1524 { "recursive-errors", required_argument
, NULL
, ARG_RECURSIVE_ERRORS
},
1525 { "offline", required_argument
, NULL
, ARG_OFFLINE
},
1526 { "threshold", required_argument
, NULL
, ARG_THRESHOLD
},
1527 { "security-policy", required_argument
, NULL
, ARG_SECURITY_POLICY
},
1528 { "system", no_argument
, NULL
, ARG_SYSTEM
},
1529 { "user", no_argument
, NULL
, ARG_USER
},
1530 { "global", no_argument
, NULL
, ARG_GLOBAL
},
1531 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
1532 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
1533 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
1534 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1535 { "man", optional_argument
, NULL
, ARG_MAN
},
1536 { "generators", optional_argument
, NULL
, ARG_GENERATORS
},
1537 { "host", required_argument
, NULL
, 'H' },
1538 { "machine", required_argument
, NULL
, 'M' },
1539 { "iterations", required_argument
, NULL
, ARG_ITERATIONS
},
1540 { "base-time", required_argument
, NULL
, ARG_BASE_TIME
},
1541 { "unit", required_argument
, NULL
, 'U' },
1542 { "json", required_argument
, NULL
, ARG_JSON
},
1543 { "profile", required_argument
, NULL
, ARG_PROFILE
},
1552 while ((c
= getopt_long(argc
, argv
, "hH:M:U:", options
, NULL
)) >= 0)
1556 return help(0, NULL
, NULL
);
1565 case ARG_RECURSIVE_ERRORS
:
1566 if (streq(optarg
, "help")) {
1567 DUMP_STRING_TABLE(recursive_errors
, RecursiveErrors
, _RECURSIVE_ERRORS_MAX
);
1570 r
= recursive_errors_from_string(optarg
);
1572 return log_error_errno(r
, "Unknown mode passed to --recursive-errors='%s'.", optarg
);
1574 arg_recursive_errors
= r
;
1578 r
= parse_path_argument(optarg
, /* suppress_root= */ true, &arg_root
);
1584 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_image
);
1590 arg_scope
= UNIT_FILE_SYSTEM
;
1594 arg_scope
= UNIT_FILE_USER
;
1598 arg_scope
= UNIT_FILE_GLOBAL
;
1602 arg_dot
= DEP_ORDER
;
1606 arg_dot
= DEP_REQUIRE
;
1609 case ARG_DOT_FROM_PATTERN
:
1610 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
1615 case ARG_DOT_TO_PATTERN
:
1616 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
1622 r
= parse_sec(optarg
, &arg_fuzz
);
1628 arg_pager_flags
|= PAGER_DISABLE
;
1632 arg_transport
= BUS_TRANSPORT_REMOTE
;
1637 arg_transport
= BUS_TRANSPORT_MACHINE
;
1642 r
= parse_boolean_argument("--man", optarg
, &arg_man
);
1647 case ARG_GENERATORS
:
1648 r
= parse_boolean_argument("--generators", optarg
, &arg_generators
);
1654 r
= parse_boolean_argument("--offline", optarg
, &arg_offline
);
1660 r
= safe_atou(optarg
, &arg_threshold
);
1661 if (r
< 0 || arg_threshold
> 100)
1662 return log_error_errno(r
< 0 ? r
: SYNTHETIC_ERRNO(EINVAL
), "Failed to parse threshold: %s", optarg
);
1666 case ARG_SECURITY_POLICY
:
1667 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_security_policy
);
1673 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
1678 case ARG_ITERATIONS
:
1679 r
= safe_atou(optarg
, &arg_iterations
);
1681 return log_error_errno(r
, "Failed to parse iterations: %s", optarg
);
1686 r
= parse_timestamp(optarg
, &arg_base_time
);
1688 return log_error_errno(r
, "Failed to parse --base-time= parameter: %s", optarg
);
1693 if (isempty(optarg
))
1694 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Profile file name is empty");
1696 if (is_path(optarg
)) {
1697 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_profile
);
1700 if (!endswith(arg_profile
, ".conf"))
1701 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Profile file name must end with .conf: %s", arg_profile
);
1703 r
= free_and_strdup(&arg_profile
, optarg
);
1711 _cleanup_free_
char *mangled
= NULL
;
1713 r
= unit_name_mangle(optarg
, UNIT_NAME_MANGLE_WARN
, &mangled
);
1715 return log_error_errno(r
, "Failed to mangle unit name %s: %m", optarg
);
1717 free_and_replace(arg_unit
, mangled
);
1724 assert_not_reached();
1727 if (arg_offline
&& !streq_ptr(argv
[optind
], "security"))
1728 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1729 "Option --offline= is only supported for security right now.");
1731 if (arg_json_format_flags
!= JSON_FORMAT_OFF
&& !STRPTR_IN_SET(argv
[optind
], "security", "inspect-elf"))
1732 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1733 "Option --json= is only supported for security and inspect-elf right now.");
1735 if (arg_threshold
!= 100 && !streq_ptr(argv
[optind
], "security"))
1736 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1737 "Option --threshold= is only supported for security right now.");
1739 if (arg_scope
== UNIT_FILE_GLOBAL
&&
1740 !STR_IN_SET(argv
[optind
] ?: "time", "dot", "unit-paths", "verify"))
1741 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1742 "Option --global only makes sense with verbs dot, unit-paths, verify.");
1744 if (streq_ptr(argv
[optind
], "cat-config") && arg_scope
== UNIT_FILE_USER
)
1745 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1746 "Option --user is not supported for cat-config right now.");
1748 if (arg_security_policy
&& !streq_ptr(argv
[optind
], "security"))
1749 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1750 "Option --security-policy= is only supported for security.");
1752 if ((arg_root
|| arg_image
) && (!STRPTR_IN_SET(argv
[optind
], "cat-config", "verify", "condition")) &&
1753 (!(streq_ptr(argv
[optind
], "security") && arg_offline
)))
1754 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1755 "Options --root= and --image= are only supported for cat-config, verify, condition and security when used with --offline= right now.");
1757 /* Having both an image and a root is not supported by the code */
1758 if (arg_root
&& arg_image
)
1759 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Please specify either --root= or --image=, the combination of both is not supported.");
1761 if (arg_unit
&& !streq_ptr(argv
[optind
], "condition"))
1762 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Option --unit= is only supported for condition");
1764 if (streq_ptr(argv
[optind
], "condition") && !arg_unit
&& optind
>= argc
- 1)
1765 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Too few arguments for condition");
1767 if (streq_ptr(argv
[optind
], "condition") && arg_unit
&& optind
< argc
- 1)
1768 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "No conditions can be passed if --unit= is used.");
1770 return 1; /* work to do */
1773 static int run(int argc
, char *argv
[]) {
1774 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
1775 _cleanup_(decrypted_image_unrefp
) DecryptedImage
*decrypted_image
= NULL
;
1776 _cleanup_(umount_and_rmdir_and_freep
) char *unlink_dir
= NULL
;
1778 static const Verb verbs
[] = {
1779 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
1780 { "time", VERB_ANY
, 1, VERB_DEFAULT
, analyze_time
},
1781 { "blame", VERB_ANY
, 1, 0, analyze_blame
},
1782 { "critical-chain", VERB_ANY
, VERB_ANY
, 0, analyze_critical_chain
},
1783 { "plot", VERB_ANY
, 1, 0, analyze_plot
},
1784 { "dot", VERB_ANY
, VERB_ANY
, 0, dot
},
1785 /* ↓ The following seven verbs are deprecated, from here … ↓ */
1786 { "log-level", VERB_ANY
, 2, 0, verb_log_control
},
1787 { "log-target", VERB_ANY
, 2, 0, verb_log_control
},
1788 { "set-log-level", 2, 2, 0, verb_log_control
},
1789 { "get-log-level", VERB_ANY
, 1, 0, verb_log_control
},
1790 { "set-log-target", 2, 2, 0, verb_log_control
},
1791 { "get-log-target", VERB_ANY
, 1, 0, verb_log_control
},
1792 { "service-watchdogs", VERB_ANY
, 2, 0, service_watchdogs
},
1793 /* ↑ … until here ↑ */
1794 { "dump", VERB_ANY
, 1, 0, dump
},
1795 { "cat-config", 2, VERB_ANY
, 0, cat_config
},
1796 { "unit-files", VERB_ANY
, VERB_ANY
, 0, do_unit_files
},
1797 { "unit-paths", 1, 1, 0, dump_unit_paths
},
1798 { "exit-status", VERB_ANY
, VERB_ANY
, 0, dump_exit_status
},
1799 { "syscall-filter", VERB_ANY
, VERB_ANY
, 0, dump_syscall_filters
},
1800 { "capability", VERB_ANY
, VERB_ANY
, 0, dump_capabilities
},
1801 { "filesystems", VERB_ANY
, VERB_ANY
, 0, dump_filesystems
},
1802 { "condition", VERB_ANY
, VERB_ANY
, 0, do_condition
},
1803 { "verify", 2, VERB_ANY
, 0, do_verify
},
1804 { "calendar", 2, VERB_ANY
, 0, test_calendar
},
1805 { "timestamp", 2, VERB_ANY
, 0, test_timestamp
},
1806 { "timespan", 2, VERB_ANY
, 0, dump_timespan
},
1807 { "security", VERB_ANY
, VERB_ANY
, 0, do_security
},
1808 { "inspect-elf", 2, VERB_ANY
, 0, do_elf_inspection
},
1814 setlocale(LC_ALL
, "");
1815 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
1819 r
= parse_argv(argc
, argv
);
1823 /* Open up and mount the image */
1827 r
= mount_image_privately_interactively(
1829 DISSECT_IMAGE_GENERIC_ROOT
|
1830 DISSECT_IMAGE_RELAX_VAR_CHECK
|
1831 DISSECT_IMAGE_READ_ONLY
,
1838 arg_root
= strdup(unlink_dir
);
1843 return dispatch_verb(argc
, argv
, verbs
, NULL
);
1846 DEFINE_MAIN_FUNCTION(run
);