1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 Copyright © 2013 Simon Peeters
14 #include "alloc-util.h"
15 #include "analyze-condition.h"
16 #include "analyze-security.h"
17 #include "analyze-verify.h"
18 #include "bus-error.h"
19 #include "bus-locator.h"
20 #include "bus-map-properties.h"
21 #include "bus-unit-util.h"
22 #include "calendarspec.h"
24 #include "capability-util.h"
25 #include "conf-files.h"
28 #include "exit-status.h"
29 #include "extract-word.h"
32 #include "filesystems.h"
33 #include "format-table.h"
34 #include "glob-util.h"
36 #include "locale-util.h"
38 #include "main-func.h"
39 #include "mount-util.h"
40 #include "nulstr-util.h"
42 #include "parse-argument.h"
43 #include "parse-util.h"
44 #include "path-util.h"
45 #include "pretty-print.h"
48 # include "seccomp-util.h"
50 #include "sort-util.h"
52 #include "stat-util.h"
53 #include "string-table.h"
56 #include "terminal-util.h"
57 #include "time-util.h"
58 #include "tmpfile-util.h"
59 #include "unit-name.h"
61 #include "verb-log-control.h"
65 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
66 #define SCALE_Y (20.0)
68 #define svg(...) printf(__VA_ARGS__)
70 #define svg_bar(class, x1, x2, y) \
71 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
73 SCALE_X * (x1), SCALE_Y * (y), \
74 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
76 #define svg_text(b, x, y, format, ...) \
78 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
79 svg(format, ## __VA_ARGS__); \
88 static char **arg_dot_from_patterns
= NULL
;
89 static char **arg_dot_to_patterns
= NULL
;
90 static usec_t arg_fuzz
= 0;
91 static PagerFlags arg_pager_flags
= 0;
92 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
93 static const char *arg_host
= NULL
;
94 static UnitFileScope arg_scope
= UNIT_FILE_SYSTEM
;
95 static RecursiveErrors arg_recursive_errors
= RECURSIVE_ERRORS_YES
;
96 static bool arg_man
= true;
97 static bool arg_generators
= false;
98 static char *arg_root
= NULL
;
99 static char *arg_image
= NULL
;
100 static char *arg_security_policy
= NULL
;
101 static bool arg_offline
= false;
102 static unsigned arg_threshold
= 100;
103 static unsigned arg_iterations
= 1;
104 static usec_t arg_base_time
= USEC_INFINITY
;
105 static char *arg_unit
= NULL
;
106 static JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
108 STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns
, strv_freep
);
109 STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns
, strv_freep
);
110 STATIC_DESTRUCTOR_REGISTER(arg_root
, freep
);
111 STATIC_DESTRUCTOR_REGISTER(arg_image
, freep
);
112 STATIC_DESTRUCTOR_REGISTER(arg_security_policy
, freep
);
113 STATIC_DESTRUCTOR_REGISTER(arg_unit
, freep
);
115 typedef struct BootTimes
{
116 usec_t firmware_time
;
119 usec_t kernel_done_time
;
121 usec_t userspace_time
;
123 usec_t security_start_time
;
124 usec_t security_finish_time
;
125 usec_t generators_start_time
;
126 usec_t generators_finish_time
;
127 usec_t unitsload_start_time
;
128 usec_t unitsload_finish_time
;
129 usec_t initrd_security_start_time
;
130 usec_t initrd_security_finish_time
;
131 usec_t initrd_generators_start_time
;
132 usec_t initrd_generators_finish_time
;
133 usec_t initrd_unitsload_start_time
;
134 usec_t initrd_unitsload_finish_time
;
137 * If we're analyzing the user instance, all timestamps will be offset
138 * by its own start-up timestamp, which may be arbitrarily big.
139 * With "plot", this causes arbitrarily wide output SVG files which almost
140 * completely consist of empty space. Thus we cancel out this offset.
142 * This offset is subtracted from times above by acquire_boot_times(),
143 * but it still needs to be subtracted from unit-specific timestamps
144 * (so it is stored here for reference).
146 usec_t reverse_offset
;
149 typedef struct UnitTimes
{
159 typedef struct HostInfo
{
162 char *kernel_release
;
163 char *kernel_version
;
164 char *os_pretty_name
;
165 char *virtualization
;
169 static int acquire_bus(sd_bus
**bus
, bool *use_full_bus
) {
170 bool user
= arg_scope
!= UNIT_FILE_SYSTEM
;
173 if (use_full_bus
&& *use_full_bus
) {
174 r
= bus_connect_transport(arg_transport
, arg_host
, user
, bus
);
175 if (IN_SET(r
, 0, -EHOSTDOWN
))
178 *use_full_bus
= false;
181 return bus_connect_transport_systemd(arg_transport
, arg_host
, user
, bus
);
184 static int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
185 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
194 r
= sd_bus_get_property_trivial(
196 "org.freedesktop.systemd1",
204 return log_error_errno(r
, "Failed to parse reply: %s", bus_error_message(&error
, r
));
209 static int bus_get_unit_property_strv(sd_bus
*bus
, const char *path
, const char *property
, char ***strv
) {
210 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
218 r
= sd_bus_get_property_strv(
220 "org.freedesktop.systemd1",
222 "org.freedesktop.systemd1.Unit",
227 return log_error_errno(r
, "Failed to get unit property %s: %s", property
, bus_error_message(&error
, r
));
232 static int compare_unit_start(const UnitTimes
*a
, const UnitTimes
*b
) {
233 return CMP(a
->activating
, b
->activating
);
236 static int process_aliases(char *argv
[], char *tempdir
, char ***ret
) {
237 _cleanup_strv_free_
char **filenames
= NULL
;
245 STRV_FOREACH(filename
, strv_skip(argv
, 1)) {
246 _cleanup_free_
char *src
= NULL
, *dst
= NULL
, *base
= NULL
;
247 const char *parse_arg
;
249 parse_arg
= *filename
;
250 r
= extract_first_word(&parse_arg
, &src
, ":", EXTRACT_DONT_COALESCE_SEPARATORS
|EXTRACT_RETAIN_ESCAPE
);
255 r
= strv_consume(&filenames
, TAKE_PTR(src
));
262 r
= path_extract_filename(parse_arg
, &base
);
266 dst
= path_join(tempdir
, base
);
270 r
= copy_file(src
, dst
, 0, 0644, 0, 0, COPY_REFLINK
);
274 r
= strv_consume(&filenames
, TAKE_PTR(dst
));
279 *ret
= TAKE_PTR(filenames
);
283 static UnitTimes
* unit_times_free_array(UnitTimes
*t
) {
284 for (UnitTimes
*p
= t
; p
&& p
->has_data
; p
++)
288 DEFINE_TRIVIAL_CLEANUP_FUNC(UnitTimes
*, unit_times_free_array
);
290 static void subtract_timestamp(usec_t
*a
, usec_t b
) {
299 static int acquire_boot_times(sd_bus
*bus
, BootTimes
**bt
) {
300 static const struct bus_properties_map property_map
[] = {
301 { "FirmwareTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, firmware_time
) },
302 { "LoaderTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, loader_time
) },
303 { "KernelTimestamp", "t", NULL
, offsetof(BootTimes
, kernel_time
) },
304 { "InitRDTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_time
) },
305 { "UserspaceTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, userspace_time
) },
306 { "FinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, finish_time
) },
307 { "SecurityStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, security_start_time
) },
308 { "SecurityFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, security_finish_time
) },
309 { "GeneratorsStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, generators_start_time
) },
310 { "GeneratorsFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, generators_finish_time
) },
311 { "UnitsLoadStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, unitsload_start_time
) },
312 { "UnitsLoadFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, unitsload_finish_time
) },
313 { "InitRDSecurityStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_security_start_time
) },
314 { "InitRDSecurityFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_security_finish_time
) },
315 { "InitRDGeneratorsStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_generators_start_time
) },
316 { "InitRDGeneratorsFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_generators_finish_time
) },
317 { "InitRDUnitsLoadStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_unitsload_start_time
) },
318 { "InitRDUnitsLoadFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_unitsload_finish_time
) },
321 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
322 static BootTimes times
;
323 static bool cached
= false;
329 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
331 r
= bus_map_all_properties(
333 "org.freedesktop.systemd1",
334 "/org/freedesktop/systemd1",
341 return log_error_errno(r
, "Failed to get timestamp properties: %s", bus_error_message(&error
, r
));
343 if (times
.finish_time
<= 0)
344 return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS
),
345 "Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64
").\n"
346 "Please try again later.\n"
347 "Hint: Use 'systemctl%s list-jobs' to see active jobs",
349 arg_scope
== UNIT_FILE_SYSTEM
? "" : " --user");
351 if (arg_scope
== UNIT_FILE_SYSTEM
&& times
.security_start_time
> 0) {
352 /* security_start_time is set when systemd is not running under container environment. */
353 if (times
.initrd_time
> 0)
354 times
.kernel_done_time
= times
.initrd_time
;
356 times
.kernel_done_time
= times
.userspace_time
;
359 * User-instance-specific or container-system-specific timestamps processing
360 * (see comment to reverse_offset in BootTimes).
362 times
.reverse_offset
= times
.userspace_time
;
364 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
=
365 times
.userspace_time
= times
.security_start_time
= times
.security_finish_time
= 0;
367 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
369 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
370 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
372 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
373 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
383 static HostInfo
* free_host_info(HostInfo
*hi
) {
388 free(hi
->kernel_name
);
389 free(hi
->kernel_release
);
390 free(hi
->kernel_version
);
391 free(hi
->os_pretty_name
);
392 free(hi
->virtualization
);
393 free(hi
->architecture
);
397 DEFINE_TRIVIAL_CLEANUP_FUNC(HostInfo
*, free_host_info
);
399 static int acquire_time_data(sd_bus
*bus
, UnitTimes
**out
) {
400 static const struct bus_properties_map property_map
[] = {
401 { "InactiveExitTimestampMonotonic", "t", NULL
, offsetof(UnitTimes
, activating
) },
402 { "ActiveEnterTimestampMonotonic", "t", NULL
, offsetof(UnitTimes
, activated
) },
403 { "ActiveExitTimestampMonotonic", "t", NULL
, offsetof(UnitTimes
, deactivating
) },
404 { "InactiveEnterTimestampMonotonic", "t", NULL
, offsetof(UnitTimes
, deactivated
) },
407 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
408 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
409 _cleanup_(unit_times_free_arrayp
) UnitTimes
*unit_times
= NULL
;
410 BootTimes
*boot_times
= NULL
;
415 r
= acquire_boot_times(bus
, &boot_times
);
419 r
= bus_call_method(bus
, bus_systemd_mgr
, "ListUnits", &error
, &reply
, NULL
);
421 return log_error_errno(r
, "Failed to list units: %s", bus_error_message(&error
, r
));
423 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
425 return bus_log_parse_error(r
);
427 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
430 if (!GREEDY_REALLOC(unit_times
, c
+ 2))
433 unit_times
[c
+ 1].has_data
= false;
437 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
439 r
= bus_map_all_properties(
441 "org.freedesktop.systemd1",
449 return log_error_errno(r
, "Failed to get timestamp properties of unit %s: %s",
450 u
.id
, bus_error_message(&error
, r
));
452 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
453 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
454 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
455 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
457 if (t
->activated
>= t
->activating
)
458 t
->time
= t
->activated
- t
->activating
;
459 else if (t
->deactivated
>= t
->activating
)
460 t
->time
= t
->deactivated
- t
->activating
;
464 if (t
->activating
== 0)
467 t
->name
= strdup(u
.id
);
475 return bus_log_parse_error(r
);
477 *out
= TAKE_PTR(unit_times
);
481 static int acquire_host_info(sd_bus
*bus
, HostInfo
**hi
) {
482 static const struct bus_properties_map hostname_map
[] = {
483 { "Hostname", "s", NULL
, offsetof(HostInfo
, hostname
) },
484 { "KernelName", "s", NULL
, offsetof(HostInfo
, kernel_name
) },
485 { "KernelRelease", "s", NULL
, offsetof(HostInfo
, kernel_release
) },
486 { "KernelVersion", "s", NULL
, offsetof(HostInfo
, kernel_version
) },
487 { "OperatingSystemPrettyName", "s", NULL
, offsetof(HostInfo
, os_pretty_name
) },
491 static const struct bus_properties_map manager_map
[] = {
492 { "Virtualization", "s", NULL
, offsetof(HostInfo
, virtualization
) },
493 { "Architecture", "s", NULL
, offsetof(HostInfo
, architecture
) },
497 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
498 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*system_bus
= NULL
;
499 _cleanup_(free_host_infop
) HostInfo
*host
= NULL
;
502 host
= new0(HostInfo
, 1);
506 if (arg_scope
!= UNIT_FILE_SYSTEM
) {
507 r
= bus_connect_transport(arg_transport
, arg_host
, false, &system_bus
);
509 log_debug_errno(r
, "Failed to connect to system bus, ignoring: %m");
514 r
= bus_map_all_properties(
516 "org.freedesktop.hostname1",
517 "/org/freedesktop/hostname1",
524 log_debug_errno(r
, "Failed to get host information from systemd-hostnamed, ignoring: %s",
525 bus_error_message(&error
, r
));
526 sd_bus_error_free(&error
);
530 r
= bus_map_all_properties(
532 "org.freedesktop.systemd1",
533 "/org/freedesktop/systemd1",
540 return log_error_errno(r
, "Failed to get host information from systemd: %s",
541 bus_error_message(&error
, r
));
543 *hi
= TAKE_PTR(host
);
547 static int pretty_boot_time(sd_bus
*bus
, char **_buf
) {
549 static char buf
[4096];
553 usec_t activated_time
= USEC_INFINITY
;
554 _cleanup_free_
char *path
= NULL
, *unit_id
= NULL
;
555 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
557 r
= acquire_boot_times(bus
, &t
);
561 path
= unit_dbus_path_from_name(SPECIAL_DEFAULT_TARGET
);
565 r
= sd_bus_get_property_string(
567 "org.freedesktop.systemd1",
569 "org.freedesktop.systemd1.Unit",
574 log_error_errno(r
, "default.target doesn't seem to exist: %s", bus_error_message(&error
, r
));
578 r
= bus_get_uint64_property(bus
, path
,
579 "org.freedesktop.systemd1.Unit",
580 "ActiveEnterTimestampMonotonic",
583 log_info_errno(r
, "Could not get time to reach default.target, ignoring: %m");
584 activated_time
= USEC_INFINITY
;
590 size
= strpcpyf(&ptr
, size
, "Startup finished in ");
591 if (t
->firmware_time
> 0)
592 size
= strpcpyf(&ptr
, size
, "%s (firmware) + ", FORMAT_TIMESPAN(t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
));
593 if (t
->loader_time
> 0)
594 size
= strpcpyf(&ptr
, size
, "%s (loader) + ", FORMAT_TIMESPAN(t
->loader_time
, USEC_PER_MSEC
));
595 if (t
->kernel_done_time
> 0)
596 size
= strpcpyf(&ptr
, size
, "%s (kernel) + ", FORMAT_TIMESPAN(t
->kernel_done_time
, USEC_PER_MSEC
));
597 if (t
->initrd_time
> 0)
598 size
= strpcpyf(&ptr
, size
, "%s (initrd) + ", FORMAT_TIMESPAN(t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
));
600 size
= strpcpyf(&ptr
, size
, "%s (userspace) ", FORMAT_TIMESPAN(t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
));
601 if (t
->kernel_done_time
> 0)
602 strpcpyf(&ptr
, size
, "= %s ", FORMAT_TIMESPAN(t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
));
604 if (unit_id
&& timestamp_is_set(activated_time
)) {
605 usec_t base
= t
->userspace_time
> 0 ? t
->userspace_time
: t
->reverse_offset
;
607 size
= strpcpyf(&ptr
, size
, "\n%s reached after %s in userspace", unit_id
,
608 FORMAT_TIMESPAN(activated_time
- base
, USEC_PER_MSEC
));
609 } else if (unit_id
&& activated_time
== 0)
610 size
= strpcpyf(&ptr
, size
, "\n%s was never reached", unit_id
);
611 else if (unit_id
&& activated_time
== USEC_INFINITY
)
612 size
= strpcpyf(&ptr
, size
, "\nCould not get time to reach %s.", unit_id
);
614 size
= strpcpyf(&ptr
, size
, "\ncould not find default.target");
624 static void svg_graph_box(double height
, double begin
, double end
) {
625 /* outside box, fill */
626 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
627 SCALE_X
* (end
- begin
),
630 for (long long i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+= 100000) {
631 /* lines for each second */
632 if (i
% 5000000 == 0)
633 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
634 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
641 else if (i
% 1000000 == 0)
642 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
643 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
651 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
658 static int plot_unit_times(UnitTimes
*u
, double width
, int y
) {
664 svg_bar("activating", u
->activating
, u
->activated
, y
);
665 svg_bar("active", u
->activated
, u
->deactivating
, y
);
666 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
668 /* place the text on the left if we have passed the half of the svg width */
669 b
= u
->activating
* SCALE_X
< width
/ 2;
671 svg_text(b
, u
->activating
, y
, "%s (%s)",
672 u
->name
, FORMAT_TIMESPAN(u
->time
, USEC_PER_MSEC
));
674 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
679 static int analyze_plot(int argc
, char *argv
[], void *userdata
) {
680 _cleanup_(free_host_infop
) HostInfo
*host
= NULL
;
681 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
682 _cleanup_(unit_times_free_arrayp
) UnitTimes
*times
= NULL
;
683 _cleanup_free_
char *pretty_times
= NULL
;
684 bool use_full_bus
= arg_scope
== UNIT_FILE_SYSTEM
;
687 int n
, m
= 1, y
= 0, r
;
690 r
= acquire_bus(&bus
, &use_full_bus
);
692 return bus_log_connect_error(r
, arg_transport
);
694 n
= acquire_boot_times(bus
, &boot
);
698 n
= pretty_boot_time(bus
, &pretty_times
);
702 if (use_full_bus
|| arg_scope
!= UNIT_FILE_SYSTEM
) {
703 n
= acquire_host_info(bus
, &host
);
708 n
= acquire_time_data(bus
, ×
);
712 typesafe_qsort(times
, n
, compare_unit_start
);
714 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
718 if (boot
->firmware_time
> boot
->loader_time
)
720 if (boot
->loader_time
> 0) {
725 if (boot
->initrd_time
> 0)
727 if (boot
->kernel_done_time
> 0)
730 for (u
= times
; u
->has_data
; u
++) {
731 double text_start
, text_width
;
733 if (u
->activating
> boot
->finish_time
) {
734 u
->name
= mfree(u
->name
);
738 /* If the text cannot fit on the left side then
739 * increase the svg width so it fits on the right.
740 * TODO: calculate the text width more accurately */
741 text_width
= 8.0 * strlen(u
->name
);
742 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
743 if (text_width
> text_start
&& text_width
+ text_start
> width
)
744 width
= text_width
+ text_start
;
746 if (u
->deactivated
> u
->activating
&&
747 u
->deactivated
<= boot
->finish_time
&&
748 u
->activated
== 0 && u
->deactivating
== 0)
749 u
->activated
= u
->deactivating
= u
->deactivated
;
750 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
751 u
->activated
= boot
->finish_time
;
752 if (u
->deactivating
< u
->activated
|| u
->deactivating
> boot
->finish_time
)
753 u
->deactivating
= boot
->finish_time
;
754 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
755 u
->deactivated
= boot
->finish_time
;
759 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
760 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
761 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
763 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
764 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
765 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
766 5 * SCALE_Y
/* legend */);
768 /* write some basic info as a comment, including some help */
769 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
770 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
771 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
772 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
773 "<!-- point your browser to this file. -->\n\n"
774 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", GIT_VERSION
);
777 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
778 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
779 " rect.background { fill: rgb(255,255,255); }\n"
780 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
781 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
782 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
783 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
784 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
785 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
786 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
787 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
788 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
789 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
790 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
791 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
792 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
794 " line.sec5 { stroke-width: 2; }\n"
795 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
796 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
797 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
798 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
799 " text.sec { font-size: 10px; }\n"
800 " ]]>\n </style>\n</defs>\n\n");
802 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
803 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
805 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
806 isempty(host
->os_pretty_name
) ? "Linux" : host
->os_pretty_name
,
807 strempty(host
->hostname
),
808 strempty(host
->kernel_name
),
809 strempty(host
->kernel_release
),
810 strempty(host
->kernel_version
),
811 strempty(host
->architecture
),
812 strempty(host
->virtualization
));
814 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
815 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
817 if (boot
->firmware_time
> 0) {
818 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
819 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
822 if (boot
->loader_time
> 0) {
823 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
824 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
827 if (boot
->kernel_done_time
> 0) {
828 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
829 svg_text(true, 0, y
, "kernel");
832 if (boot
->initrd_time
> 0) {
833 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
834 if (boot
->initrd_security_start_time
< boot
->initrd_security_finish_time
)
835 svg_bar("security", boot
->initrd_security_start_time
, boot
->initrd_security_finish_time
, y
);
836 if (boot
->initrd_generators_start_time
< boot
->initrd_generators_finish_time
)
837 svg_bar("generators", boot
->initrd_generators_start_time
, boot
->initrd_generators_finish_time
, y
);
838 if (boot
->initrd_unitsload_start_time
< boot
->initrd_unitsload_finish_time
)
839 svg_bar("unitsload", boot
->initrd_unitsload_start_time
, boot
->initrd_unitsload_finish_time
, y
);
840 svg_text(true, boot
->initrd_time
, y
, "initrd");
844 for (u
= times
; u
->has_data
; u
++) {
845 if (u
->activating
>= boot
->userspace_time
)
848 y
+= plot_unit_times(u
, width
, y
);
851 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
852 if (boot
->security_start_time
> 0)
853 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
854 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
855 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
856 svg_text(true, boot
->userspace_time
, y
, "systemd");
859 for (; u
->has_data
; u
++)
860 y
+= plot_unit_times(u
, width
, y
);
865 svg("<g transform=\"translate(20,100)\">\n");
867 svg_bar("activating", 0, 300000, y
);
868 svg_text(true, 400000, y
, "Activating");
870 svg_bar("active", 0, 300000, y
);
871 svg_text(true, 400000, y
, "Active");
873 svg_bar("deactivating", 0, 300000, y
);
874 svg_text(true, 400000, y
, "Deactivating");
876 if (boot
->security_start_time
> 0) {
877 svg_bar("security", 0, 300000, y
);
878 svg_text(true, 400000, y
, "Setting up security module");
881 svg_bar("generators", 0, 300000, y
);
882 svg_text(true, 400000, y
, "Generators");
884 svg_bar("unitsload", 0, 300000, y
);
885 svg_text(true, 400000, y
, "Loading unit files");
895 static int list_dependencies_print(
903 for (unsigned i
= level
; i
!= 0; i
--)
904 printf("%s", special_glyph(branches
& (1 << (i
-1)) ? SPECIAL_GLYPH_TREE_VERTICAL
: SPECIAL_GLYPH_TREE_SPACE
));
906 printf("%s", special_glyph(last
? SPECIAL_GLYPH_TREE_RIGHT
: SPECIAL_GLYPH_TREE_BRANCH
));
910 printf("%s%s @%s +%s%s", ansi_highlight_red(), name
,
911 FORMAT_TIMESPAN(times
->activating
- boot
->userspace_time
, USEC_PER_MSEC
),
912 FORMAT_TIMESPAN(times
->time
, USEC_PER_MSEC
), ansi_normal());
913 else if (times
->activated
> boot
->userspace_time
)
914 printf("%s @%s", name
, FORMAT_TIMESPAN(times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
924 static int list_dependencies_get_dependencies(sd_bus
*bus
, const char *name
, char ***deps
) {
925 _cleanup_free_
char *path
= NULL
;
931 path
= unit_dbus_path_from_name(name
);
935 return bus_get_unit_property_strv(bus
, path
, "After", deps
);
938 static Hashmap
*unit_times_hashmap
;
940 static int list_dependencies_compare(char *const *a
, char *const *b
) {
941 usec_t usa
= 0, usb
= 0;
944 times
= hashmap_get(unit_times_hashmap
, *a
);
946 usa
= times
->activated
;
947 times
= hashmap_get(unit_times_hashmap
, *b
);
949 usb
= times
->activated
;
951 return CMP(usb
, usa
);
954 static bool times_in_range(const UnitTimes
*times
, const BootTimes
*boot
) {
955 return times
&& times
->activated
> 0 && times
->activated
<= boot
->finish_time
;
958 static int list_dependencies_one(sd_bus
*bus
, const char *name
, unsigned level
, char ***units
, unsigned branches
) {
959 _cleanup_strv_free_
char **deps
= NULL
;
962 usec_t service_longest
= 0;
967 if (strv_extend(units
, name
))
970 r
= list_dependencies_get_dependencies(bus
, name
, &deps
);
974 typesafe_qsort(deps
, strv_length(deps
), list_dependencies_compare
);
976 r
= acquire_boot_times(bus
, &boot
);
980 STRV_FOREACH(c
, deps
) {
981 times
= hashmap_get(unit_times_hashmap
, *c
);
982 if (times_in_range(times
, boot
) && times
->activated
>= service_longest
)
983 service_longest
= times
->activated
;
986 if (service_longest
== 0)
989 STRV_FOREACH(c
, deps
) {
990 times
= hashmap_get(unit_times_hashmap
, *c
);
991 if (times_in_range(times
, boot
) && service_longest
- times
->activated
<= arg_fuzz
)
998 STRV_FOREACH(c
, deps
) {
999 times
= hashmap_get(unit_times_hashmap
, *c
);
1000 if (!times_in_range(times
, boot
) || service_longest
- times
->activated
> arg_fuzz
)
1005 r
= list_dependencies_print(*c
, level
, branches
, to_print
== 0, times
, boot
);
1009 if (strv_contains(*units
, *c
)) {
1010 r
= list_dependencies_print("...", level
+ 1, (branches
<< 1) | (to_print
? 1 : 0),
1017 r
= list_dependencies_one(bus
, *c
, level
+ 1, units
, (branches
<< 1) | (to_print
? 1 : 0));
1027 static int list_dependencies(sd_bus
*bus
, const char *name
) {
1028 _cleanup_strv_free_
char **units
= NULL
;
1032 _cleanup_free_
char *path
= NULL
;
1033 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1034 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1039 path
= unit_dbus_path_from_name(name
);
1043 r
= sd_bus_get_property(
1045 "org.freedesktop.systemd1",
1047 "org.freedesktop.systemd1.Unit",
1053 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1055 r
= sd_bus_message_read(reply
, "s", &id
);
1057 return bus_log_parse_error(r
);
1059 times
= hashmap_get(unit_times_hashmap
, id
);
1061 r
= acquire_boot_times(bus
, &boot
);
1067 printf("%s%s +%s%s\n", ansi_highlight_red(), id
,
1068 FORMAT_TIMESPAN(times
->time
, USEC_PER_MSEC
), ansi_normal());
1069 else if (times
->activated
> boot
->userspace_time
)
1070 printf("%s @%s\n", id
,
1071 FORMAT_TIMESPAN(times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
1076 return list_dependencies_one(bus
, name
, 0, &units
, 0);
1079 static int analyze_critical_chain(int argc
, char *argv
[], void *userdata
) {
1080 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1081 _cleanup_(unit_times_free_arrayp
) UnitTimes
*times
= NULL
;
1085 r
= acquire_bus(&bus
, NULL
);
1087 return bus_log_connect_error(r
, arg_transport
);
1089 n
= acquire_time_data(bus
, ×
);
1093 h
= hashmap_new(&string_hash_ops
);
1097 for (UnitTimes
*u
= times
; u
->has_data
; u
++) {
1098 r
= hashmap_put(h
, u
->name
, u
);
1100 return log_error_errno(r
, "Failed to add entry to hashmap: %m");
1102 unit_times_hashmap
= h
;
1104 pager_open(arg_pager_flags
);
1106 puts("The time when unit became active or started is printed after the \"@\" character.\n"
1107 "The time the unit took to start is printed after the \"+\" character.\n");
1111 STRV_FOREACH(name
, strv_skip(argv
, 1))
1112 list_dependencies(bus
, *name
);
1114 list_dependencies(bus
, SPECIAL_DEFAULT_TARGET
);
1116 h
= hashmap_free(h
);
1120 static int analyze_blame(int argc
, char *argv
[], void *userdata
) {
1121 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1122 _cleanup_(unit_times_free_arrayp
) UnitTimes
*times
= NULL
;
1123 _cleanup_(table_unrefp
) Table
*table
= NULL
;
1127 r
= acquire_bus(&bus
, NULL
);
1129 return bus_log_connect_error(r
, arg_transport
);
1131 n
= acquire_time_data(bus
, ×
);
1135 table
= table_new("time", "unit");
1139 table_set_header(table
, false);
1141 assert_se(cell
= table_get_cell(table
, 0, 0));
1142 r
= table_set_ellipsize_percent(table
, cell
, 100);
1146 r
= table_set_align_percent(table
, cell
, 100);
1150 assert_se(cell
= table_get_cell(table
, 0, 1));
1151 r
= table_set_ellipsize_percent(table
, cell
, 100);
1155 r
= table_set_sort(table
, (size_t) 0);
1159 r
= table_set_reverse(table
, 0, true);
1163 for (UnitTimes
*u
= times
; u
->has_data
; u
++) {
1167 r
= table_add_many(table
,
1168 TABLE_TIMESPAN_MSEC
, u
->time
,
1169 TABLE_STRING
, u
->name
);
1171 return table_log_add_error(r
);
1174 pager_open(arg_pager_flags
);
1176 return table_print(table
, NULL
);
1179 static int analyze_time(int argc
, char *argv
[], void *userdata
) {
1180 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1181 _cleanup_free_
char *buf
= NULL
;
1184 r
= acquire_bus(&bus
, NULL
);
1186 return bus_log_connect_error(r
, arg_transport
);
1188 r
= pretty_boot_time(bus
, &buf
);
1196 static int graph_one_property(
1202 char *from_patterns
[],
1203 char *to_patterns
[]) {
1205 _cleanup_strv_free_
char **units
= NULL
;
1208 bool match_patterns
;
1214 match_patterns
= strv_fnmatch(patterns
, u
->id
);
1216 if (!strv_isempty(from_patterns
) && !match_patterns
&& !strv_fnmatch(from_patterns
, u
->id
))
1219 r
= bus_get_unit_property_strv(bus
, u
->unit_path
, prop
, &units
);
1223 STRV_FOREACH(unit
, units
) {
1224 bool match_patterns2
;
1226 match_patterns2
= strv_fnmatch(patterns
, *unit
);
1228 if (!strv_isempty(to_patterns
) && !match_patterns2
&& !strv_fnmatch(to_patterns
, *unit
))
1231 if (!strv_isempty(patterns
) && !match_patterns
&& !match_patterns2
)
1234 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u
->id
, *unit
, color
);
1240 static int graph_one(sd_bus
*bus
, const UnitInfo
*u
, char *patterns
[], char *from_patterns
[], char *to_patterns
[]) {
1246 if (IN_SET(arg_dot
, DEP_ORDER
, DEP_ALL
)) {
1247 r
= graph_one_property(bus
, u
, "After", "green", patterns
, from_patterns
, to_patterns
);
1252 if (IN_SET(arg_dot
, DEP_REQUIRE
, DEP_ALL
)) {
1253 r
= graph_one_property(bus
, u
, "Requires", "black", patterns
, from_patterns
, to_patterns
);
1256 r
= graph_one_property(bus
, u
, "Requisite", "darkblue", patterns
, from_patterns
, to_patterns
);
1259 r
= graph_one_property(bus
, u
, "Wants", "grey66", patterns
, from_patterns
, to_patterns
);
1262 r
= graph_one_property(bus
, u
, "Conflicts", "red", patterns
, from_patterns
, to_patterns
);
1270 static int expand_patterns(sd_bus
*bus
, char **patterns
, char ***ret
) {
1271 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1275 STRV_FOREACH(pattern
, patterns
) {
1276 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1277 _cleanup_free_
char *unit
= NULL
, *unit_id
= NULL
;
1279 if (strv_extend(&expanded_patterns
, *pattern
) < 0)
1282 if (string_is_glob(*pattern
))
1285 unit
= unit_dbus_path_from_name(*pattern
);
1289 r
= sd_bus_get_property_string(
1291 "org.freedesktop.systemd1",
1293 "org.freedesktop.systemd1.Unit",
1298 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1300 if (!streq(*pattern
, unit_id
)) {
1301 if (strv_extend(&expanded_patterns
, unit_id
) < 0)
1306 *ret
= TAKE_PTR(expanded_patterns
); /* do not free */
1311 static int dot(int argc
, char *argv
[], void *userdata
) {
1312 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1313 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1314 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1315 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1316 _cleanup_strv_free_
char **expanded_from_patterns
= NULL
;
1317 _cleanup_strv_free_
char **expanded_to_patterns
= NULL
;
1321 r
= acquire_bus(&bus
, NULL
);
1323 return bus_log_connect_error(r
, arg_transport
);
1325 r
= expand_patterns(bus
, strv_skip(argv
, 1), &expanded_patterns
);
1329 r
= expand_patterns(bus
, arg_dot_from_patterns
, &expanded_from_patterns
);
1333 r
= expand_patterns(bus
, arg_dot_to_patterns
, &expanded_to_patterns
);
1337 r
= bus_call_method(bus
, bus_systemd_mgr
, "ListUnits", &error
, &reply
, NULL
);
1339 log_error_errno(r
, "Failed to list units: %s", bus_error_message(&error
, r
));
1341 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
1343 return bus_log_parse_error(r
);
1345 printf("digraph systemd {\n");
1347 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
1349 r
= graph_one(bus
, &u
, expanded_patterns
, expanded_from_patterns
, expanded_to_patterns
);
1354 return bus_log_parse_error(r
);
1358 log_info(" Color legend: black = Requires\n"
1359 " dark blue = Requisite\n"
1360 " dark grey = Wants\n"
1361 " red = Conflicts\n"
1362 " green = After\n");
1365 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1366 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1371 static int dump_fallback(sd_bus
*bus
) {
1372 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1373 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1374 const char *text
= NULL
;
1379 r
= bus_call_method(bus
, bus_systemd_mgr
, "Dump", &error
, &reply
, NULL
);
1381 return log_error_errno(r
, "Failed to issue method call Dump: %s", bus_error_message(&error
, r
));
1383 r
= sd_bus_message_read(reply
, "s", &text
);
1385 return bus_log_parse_error(r
);
1387 fputs(text
, stdout
);
1391 static int dump(int argc
, char *argv
[], void *userdata
) {
1392 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1393 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1394 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1398 r
= acquire_bus(&bus
, NULL
);
1400 return bus_log_connect_error(r
, arg_transport
);
1402 pager_open(arg_pager_flags
);
1404 if (!sd_bus_can_send(bus
, SD_BUS_TYPE_UNIX_FD
))
1405 return dump_fallback(bus
);
1407 r
= bus_call_method(bus
, bus_systemd_mgr
, "DumpByFileDescriptor", &error
, &reply
, NULL
);
1409 /* fall back to Dump if DumpByFileDescriptor is not supported */
1410 if (!IN_SET(r
, -EACCES
, -EBADR
))
1411 return log_error_errno(r
, "Failed to issue method call DumpByFileDescriptor: %s",
1412 bus_error_message(&error
, r
));
1414 return dump_fallback(bus
);
1417 r
= sd_bus_message_read(reply
, "h", &fd
);
1419 return bus_log_parse_error(r
);
1422 return copy_bytes(fd
, STDOUT_FILENO
, UINT64_MAX
, 0);
1425 static int cat_config(int argc
, char *argv
[], void *userdata
) {
1429 pager_open(arg_pager_flags
);
1431 list
= strv_skip(argv
, 1);
1432 STRV_FOREACH(arg
, list
) {
1433 const char *t
= NULL
;
1438 if (path_is_absolute(*arg
)) {
1441 NULSTR_FOREACH(dir
, CONF_PATHS_NULSTR("")) {
1442 t
= path_startswith(*arg
, dir
);
1448 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
1449 "Path %s does not start with any known prefix.", *arg
);
1453 r
= conf_files_cat(arg_root
, t
);
1461 static int verb_log_control(int argc
, char *argv
[], void *userdata
) {
1462 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1465 assert(IN_SET(argc
, 1, 2));
1467 r
= acquire_bus(&bus
, NULL
);
1469 return bus_log_connect_error(r
, arg_transport
);
1471 return verb_log_control_common(bus
, "org.freedesktop.systemd1", argv
[0], argc
== 2 ? argv
[1] : NULL
);
1474 static bool strv_fnmatch_strv_or_empty(char* const* patterns
, char **strv
, int flags
) {
1476 STRV_FOREACH(s
, strv
)
1477 if (strv_fnmatch_or_empty(patterns
, *s
, flags
))
1483 static int do_unit_files(int argc
, char *argv
[], void *userdata
) {
1484 _cleanup_(lookup_paths_free
) LookupPaths lp
= {};
1485 _cleanup_hashmap_free_ Hashmap
*unit_ids
= NULL
;
1486 _cleanup_hashmap_free_ Hashmap
*unit_names
= NULL
;
1487 char **patterns
= strv_skip(argv
, 1);
1488 const char *k
, *dst
;
1492 r
= lookup_paths_init(&lp
, arg_scope
, 0, NULL
);
1494 return log_error_errno(r
, "lookup_paths_init() failed: %m");
1496 r
= unit_file_build_name_map(&lp
, NULL
, &unit_ids
, &unit_names
, NULL
);
1498 return log_error_errno(r
, "unit_file_build_name_map() failed: %m");
1500 HASHMAP_FOREACH_KEY(dst
, k
, unit_ids
) {
1501 if (!strv_fnmatch_or_empty(patterns
, k
, FNM_NOESCAPE
) &&
1502 !strv_fnmatch_or_empty(patterns
, dst
, FNM_NOESCAPE
))
1505 printf("ids: %s → %s\n", k
, dst
);
1508 HASHMAP_FOREACH_KEY(v
, k
, unit_names
) {
1509 if (!strv_fnmatch_or_empty(patterns
, k
, FNM_NOESCAPE
) &&
1510 !strv_fnmatch_strv_or_empty(patterns
, v
, FNM_NOESCAPE
))
1513 _cleanup_free_
char *j
= strv_join(v
, ", ");
1514 printf("aliases: %s ← %s\n", k
, j
);
1520 static int dump_unit_paths(int argc
, char *argv
[], void *userdata
) {
1521 _cleanup_(lookup_paths_free
) LookupPaths paths
= {};
1525 r
= lookup_paths_init(&paths
, arg_scope
, 0, NULL
);
1527 return log_error_errno(r
, "lookup_paths_init() failed: %m");
1529 STRV_FOREACH(p
, paths
.search_path
)
1535 static int dump_exit_status(int argc
, char *argv
[], void *userdata
) {
1536 _cleanup_(table_unrefp
) Table
*table
= NULL
;
1539 table
= table_new("name", "status", "class");
1543 r
= table_set_align_percent(table
, table_get_cell(table
, 0, 1), 100);
1545 return log_error_errno(r
, "Failed to right-align status: %m");
1547 if (strv_isempty(strv_skip(argv
, 1)))
1548 for (size_t i
= 0; i
< ELEMENTSOF(exit_status_mappings
); i
++) {
1549 if (!exit_status_mappings
[i
].name
)
1552 r
= table_add_many(table
,
1553 TABLE_STRING
, exit_status_mappings
[i
].name
,
1555 TABLE_STRING
, exit_status_class(i
));
1557 return table_log_add_error(r
);
1560 for (int i
= 1; i
< argc
; i
++) {
1563 status
= exit_status_from_string(argv
[i
]);
1565 return log_error_errno(status
, "Invalid exit status \"%s\".", argv
[i
]);
1567 assert(status
>= 0 && (size_t) status
< ELEMENTSOF(exit_status_mappings
));
1568 r
= table_add_many(table
,
1569 TABLE_STRING
, exit_status_mappings
[status
].name
?: "-",
1571 TABLE_STRING
, exit_status_class(status
) ?: "-");
1573 return table_log_add_error(r
);
1576 pager_open(arg_pager_flags
);
1578 return table_print(table
, NULL
);
1581 static int dump_capabilities(int argc
, char *argv
[], void *userdata
) {
1582 _cleanup_(table_unrefp
) Table
*table
= NULL
;
1586 table
= table_new("name", "number");
1590 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 1), 100);
1592 /* Determine the maximum of the last cap known by the kernel and by us */
1593 last_cap
= MAX((unsigned) CAP_LAST_CAP
, cap_last_cap());
1595 if (strv_isempty(strv_skip(argv
, 1)))
1596 for (unsigned c
= 0; c
<= last_cap
; c
++) {
1597 r
= table_add_many(table
,
1598 TABLE_STRING
, capability_to_name(c
) ?: "cap_???",
1601 return table_log_add_error(r
);
1604 for (int i
= 1; i
< argc
; i
++) {
1607 c
= capability_from_name(argv
[i
]);
1608 if (c
< 0 || (unsigned) c
> last_cap
)
1609 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Capability \"%s\" not known.", argv
[i
]);
1611 r
= table_add_many(table
,
1612 TABLE_STRING
, capability_to_name(c
) ?: "cap_???",
1613 TABLE_UINT
, (unsigned) c
);
1615 return table_log_add_error(r
);
1618 (void) table_set_sort(table
, (size_t) 1);
1621 pager_open(arg_pager_flags
);
1623 return table_print(table
, NULL
);
1628 static int load_kernel_syscalls(Set
**ret
) {
1629 _cleanup_set_free_ Set
*syscalls
= NULL
;
1630 _cleanup_fclose_
FILE *f
= NULL
;
1633 /* Let's read the available system calls from the list of available tracing events. Slightly dirty,
1634 * but good enough for analysis purposes. */
1636 f
= fopen("/sys/kernel/tracing/available_events", "re");
1638 /* We tried the non-debugfs mount point and that didn't work. If it wasn't mounted, maybe the
1639 * old debugfs mount point works? */
1640 f
= fopen("/sys/kernel/debug/tracing/available_events", "re");
1642 return log_full_errno(IN_SET(errno
, EPERM
, EACCES
, ENOENT
) ? LOG_DEBUG
: LOG_WARNING
, errno
,
1643 "Can't read open tracefs' available_events file: %m");
1647 _cleanup_free_
char *line
= NULL
;
1650 r
= read_line(f
, LONG_LINE_MAX
, &line
);
1652 return log_error_errno(r
, "Failed to read system call list: %m");
1656 e
= startswith(line
, "syscalls:sys_enter_");
1660 /* These are named differently inside the kernel than their external name for historical
1661 * reasons. Let's hide them here. */
1662 if (STR_IN_SET(e
, "newuname", "newfstat", "newstat", "newlstat", "sysctl"))
1665 r
= set_put_strdup(&syscalls
, e
);
1667 return log_error_errno(r
, "Failed to add system call to list: %m");
1670 *ret
= TAKE_PTR(syscalls
);
1674 static void syscall_set_remove(Set
*s
, const SyscallFilterSet
*set
) {
1675 const char *syscall
;
1680 NULSTR_FOREACH(syscall
, set
->value
) {
1681 if (syscall
[0] == '@')
1684 free(set_remove(s
, syscall
));
1688 static void dump_syscall_filter(const SyscallFilterSet
*set
) {
1689 const char *syscall
;
1698 NULSTR_FOREACH(syscall
, set
->value
)
1699 printf(" %s%s%s\n", syscall
[0] == '@' ? ansi_underline() : "", syscall
, ansi_normal());
1702 static int dump_syscall_filters(int argc
, char *argv
[], void *userdata
) {
1705 pager_open(arg_pager_flags
);
1707 if (strv_isempty(strv_skip(argv
, 1))) {
1708 _cleanup_set_free_ Set
*kernel
= NULL
, *known
= NULL
;
1712 NULSTR_FOREACH(sys
, syscall_filter_sets
[SYSCALL_FILTER_SET_KNOWN
].value
)
1713 if (set_put_strdup(&known
, sys
) < 0)
1716 k
= load_kernel_syscalls(&kernel
);
1718 for (int i
= 0; i
< _SYSCALL_FILTER_SET_MAX
; i
++) {
1719 const SyscallFilterSet
*set
= syscall_filter_sets
+ i
;
1723 dump_syscall_filter(set
);
1724 syscall_set_remove(kernel
, set
);
1725 if (i
!= SYSCALL_FILTER_SET_KNOWN
)
1726 syscall_set_remove(known
, set
);
1730 if (!set_isempty(known
)) {
1731 _cleanup_free_
char **l
= NULL
;
1735 "# %sUngrouped System Calls%s (known but not included in any of the groups except @known):\n",
1736 ansi_highlight(), ansi_normal());
1738 l
= set_get_strv(known
);
1744 STRV_FOREACH(syscall
, l
)
1745 printf("# %s\n", *syscall
);
1749 fputc('\n', stdout
);
1751 log_notice_errno(k
, "# Not showing unlisted system calls, couldn't retrieve kernel system call list: %m");
1752 } else if (!set_isempty(kernel
)) {
1753 _cleanup_free_
char **l
= NULL
;
1757 "# %sUnlisted System Calls%s (supported by the local kernel, but not included in any of the groups listed above):\n",
1758 ansi_highlight(), ansi_normal());
1760 l
= set_get_strv(kernel
);
1766 STRV_FOREACH(syscall
, l
)
1767 printf("# %s\n", *syscall
);
1772 STRV_FOREACH(name
, strv_skip(argv
, 1)) {
1773 const SyscallFilterSet
*set
;
1778 set
= syscall_filter_set_find(*name
);
1780 /* make sure the error appears below normal output */
1783 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
),
1784 "Filter set \"%s\" not found.", *name
);
1787 dump_syscall_filter(set
);
1796 static int dump_syscall_filters(int argc
, char *argv
[], void *userdata
) {
1797 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "Not compiled with syscall filters, sorry.");
1801 static int load_available_kernel_filesystems(Set
**ret
) {
1802 _cleanup_set_free_ Set
*filesystems
= NULL
;
1808 /* Let's read the available filesystems */
1810 r
= read_virtual_file("/proc/filesystems", SIZE_MAX
, &t
, NULL
);
1815 _cleanup_free_
char *line
= NULL
;
1818 r
= string_extract_line(t
, i
++, &line
);
1827 p
= strchr(line
, '\t');
1831 p
+= strspn(p
, WHITESPACE
);
1833 r
= set_put_strdup(&filesystems
, p
);
1835 return log_error_errno(r
, "Failed to add filesystem to list: %m");
1838 *ret
= TAKE_PTR(filesystems
);
1842 static void filesystem_set_remove(Set
*s
, const FilesystemSet
*set
) {
1843 const char *filesystem
;
1845 NULSTR_FOREACH(filesystem
, set
->value
) {
1846 if (filesystem
[0] == '@')
1849 free(set_remove(s
, filesystem
));
1853 static void dump_filesystem(const FilesystemSet
*set
) {
1854 const char *filesystem
;
1866 NULSTR_FOREACH(filesystem
, set
->value
)
1867 printf(" %s%s%s\n", filesystem
[0] == '@' ? ansi_underline() : "", filesystem
, ansi_normal());
1870 static int dump_filesystems(int argc
, char *argv
[], void *userdata
) {
1874 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP
), "Not compiled with libbpf support, sorry.");
1877 pager_open(arg_pager_flags
);
1879 if (strv_isempty(strv_skip(argv
, 1))) {
1880 _cleanup_set_free_ Set
*kernel
= NULL
, *known
= NULL
;
1884 NULSTR_FOREACH(fs
, filesystem_sets
[FILESYSTEM_SET_KNOWN
].value
)
1885 if (set_put_strdup(&known
, fs
) < 0)
1888 k
= load_available_kernel_filesystems(&kernel
);
1890 for (FilesystemGroups i
= 0; i
< _FILESYSTEM_SET_MAX
; i
++) {
1891 const FilesystemSet
*set
= filesystem_sets
+ i
;
1895 dump_filesystem(set
);
1896 filesystem_set_remove(kernel
, set
);
1897 if (i
!= FILESYSTEM_SET_KNOWN
)
1898 filesystem_set_remove(known
, set
);
1902 if (!set_isempty(known
)) {
1903 _cleanup_free_
char **l
= NULL
;
1907 "# %sUngrouped filesystems%s (known but not included in any of the groups except @known):\n",
1908 ansi_highlight(), ansi_normal());
1910 l
= set_get_strv(known
);
1916 STRV_FOREACH(filesystem
, l
)
1917 printf("# %s\n", *filesystem
);
1921 fputc('\n', stdout
);
1923 log_notice_errno(k
, "# Not showing unlisted filesystems, couldn't retrieve kernel filesystem list: %m");
1924 } else if (!set_isempty(kernel
)) {
1925 _cleanup_free_
char **l
= NULL
;
1929 "# %sUnlisted filesystems%s (available to the local kernel, but not included in any of the groups listed above):\n",
1930 ansi_highlight(), ansi_normal());
1932 l
= set_get_strv(kernel
);
1938 STRV_FOREACH(filesystem
, l
)
1939 printf("# %s\n", *filesystem
);
1944 STRV_FOREACH(name
, strv_skip(argv
, 1)) {
1945 const FilesystemSet
*set
;
1950 set
= filesystem_set_find(*name
);
1952 /* make sure the error appears below normal output */
1955 return log_error_errno(SYNTHETIC_ERRNO(ENOENT
),
1956 "Filesystem set \"%s\" not found.", *name
);
1959 dump_filesystem(set
);
1967 static void parsing_hint(const char *p
, bool calendar
, bool timestamp
, bool timespan
) {
1968 if (calendar
&& calendar_spec_from_string(p
, NULL
) >= 0)
1969 log_notice("Hint: this expression is a valid calendar specification. "
1970 "Use 'systemd-analyze calendar \"%s\"' instead?", p
);
1971 if (timestamp
&& parse_timestamp(p
, NULL
) >= 0)
1972 log_notice("Hint: this expression is a valid timestamp. "
1973 "Use 'systemd-analyze timestamp \"%s\"' instead?", p
);
1974 if (timespan
&& parse_time(p
, NULL
, USEC_PER_SEC
) >= 0)
1975 log_notice("Hint: this expression is a valid timespan. "
1976 "Use 'systemd-analyze timespan \"%s\"' instead?", p
);
1979 static int dump_timespan(int argc
, char *argv
[], void *userdata
) {
1980 char **input_timespan
;
1982 STRV_FOREACH(input_timespan
, strv_skip(argv
, 1)) {
1983 _cleanup_(table_unrefp
) Table
*table
= NULL
;
1984 usec_t output_usecs
;
1988 r
= parse_time(*input_timespan
, &output_usecs
, USEC_PER_SEC
);
1990 log_error_errno(r
, "Failed to parse time span '%s': %m", *input_timespan
);
1991 parsing_hint(*input_timespan
, true, true, false);
1995 table
= table_new("name", "value");
1999 table_set_header(table
, false);
2001 assert_se(cell
= table_get_cell(table
, 0, 0));
2002 r
= table_set_ellipsize_percent(table
, cell
, 100);
2006 r
= table_set_align_percent(table
, cell
, 100);
2010 assert_se(cell
= table_get_cell(table
, 0, 1));
2011 r
= table_set_ellipsize_percent(table
, cell
, 100);
2015 r
= table_add_many(table
,
2016 TABLE_STRING
, "Original:",
2017 TABLE_STRING
, *input_timespan
);
2019 return table_log_add_error(r
);
2021 r
= table_add_cell_stringf(table
, NULL
, "%ss:", special_glyph(SPECIAL_GLYPH_MU
));
2023 return table_log_add_error(r
);
2025 r
= table_add_many(table
,
2026 TABLE_UINT64
, output_usecs
,
2027 TABLE_STRING
, "Human:",
2028 TABLE_TIMESPAN
, output_usecs
,
2029 TABLE_SET_COLOR
, ansi_highlight());
2031 return table_log_add_error(r
);
2033 r
= table_print(table
, NULL
);
2037 if (input_timespan
[1])
2041 return EXIT_SUCCESS
;
2044 static int test_timestamp_one(const char *p
) {
2045 _cleanup_(table_unrefp
) Table
*table
= NULL
;
2050 r
= parse_timestamp(p
, &usec
);
2052 log_error_errno(r
, "Failed to parse \"%s\": %m", p
);
2053 parsing_hint(p
, true, false, true);
2057 table
= table_new("name", "value");
2061 table_set_header(table
, false);
2063 assert_se(cell
= table_get_cell(table
, 0, 0));
2064 r
= table_set_ellipsize_percent(table
, cell
, 100);
2068 r
= table_set_align_percent(table
, cell
, 100);
2072 assert_se(cell
= table_get_cell(table
, 0, 1));
2073 r
= table_set_ellipsize_percent(table
, cell
, 100);
2077 r
= table_add_many(table
,
2078 TABLE_STRING
, "Original form:",
2080 TABLE_STRING
, "Normalized form:",
2081 TABLE_TIMESTAMP
, usec
,
2082 TABLE_SET_COLOR
, ansi_highlight_blue());
2084 return table_log_add_error(r
);
2086 if (!in_utc_timezone()) {
2087 r
= table_add_many(table
,
2088 TABLE_STRING
, "(in UTC):",
2089 TABLE_TIMESTAMP_UTC
, usec
);
2091 return table_log_add_error(r
);
2094 r
= table_add_cell(table
, NULL
, TABLE_STRING
, "UNIX seconds:");
2096 return table_log_add_error(r
);
2098 if (usec
% USEC_PER_SEC
== 0)
2099 r
= table_add_cell_stringf(table
, NULL
, "@%"PRI_USEC
,
2100 usec
/ USEC_PER_SEC
);
2102 r
= table_add_cell_stringf(table
, NULL
, "@%"PRI_USEC
".%06"PRI_USEC
"",
2103 usec
/ USEC_PER_SEC
,
2104 usec
% USEC_PER_SEC
);
2108 r
= table_add_many(table
,
2109 TABLE_STRING
, "From now:",
2110 TABLE_TIMESTAMP_RELATIVE
, usec
);
2112 return table_log_add_error(r
);
2114 return table_print(table
, NULL
);
2117 static int test_timestamp(int argc
, char *argv
[], void *userdata
) {
2121 STRV_FOREACH(p
, strv_skip(argv
, 1)) {
2122 r
= test_timestamp_one(*p
);
2123 if (ret
== 0 && r
< 0)
2133 static int test_calendar_one(usec_t n
, const char *p
) {
2134 _cleanup_(calendar_spec_freep
) CalendarSpec
*spec
= NULL
;
2135 _cleanup_(table_unrefp
) Table
*table
= NULL
;
2136 _cleanup_free_
char *t
= NULL
;
2140 r
= calendar_spec_from_string(p
, &spec
);
2142 log_error_errno(r
, "Failed to parse calendar specification '%s': %m", p
);
2143 parsing_hint(p
, false, true, true);
2147 r
= calendar_spec_to_string(spec
, &t
);
2149 return log_error_errno(r
, "Failed to format calendar specification '%s': %m", p
);
2151 table
= table_new("name", "value");
2155 table_set_header(table
, false);
2157 assert_se(cell
= table_get_cell(table
, 0, 0));
2158 r
= table_set_ellipsize_percent(table
, cell
, 100);
2162 r
= table_set_align_percent(table
, cell
, 100);
2166 assert_se(cell
= table_get_cell(table
, 0, 1));
2167 r
= table_set_ellipsize_percent(table
, cell
, 100);
2172 r
= table_add_many(table
,
2173 TABLE_STRING
, "Original form:",
2176 return table_log_add_error(r
);
2179 r
= table_add_many(table
,
2180 TABLE_STRING
, "Normalized form:",
2183 return table_log_add_error(r
);
2185 for (unsigned i
= 0; i
< arg_iterations
; i
++) {
2188 r
= calendar_spec_next_usec(spec
, n
, &next
);
2191 r
= table_add_many(table
,
2192 TABLE_STRING
, "Next elapse:",
2193 TABLE_STRING
, "never",
2194 TABLE_SET_COLOR
, ansi_highlight_yellow());
2196 return table_log_add_error(r
);
2201 return log_error_errno(r
, "Failed to determine next elapse for '%s': %m", p
);
2204 r
= table_add_many(table
,
2205 TABLE_STRING
, "Next elapse:",
2206 TABLE_TIMESTAMP
, next
,
2207 TABLE_SET_COLOR
, ansi_highlight_blue());
2209 return table_log_add_error(r
);
2211 int k
= DECIMAL_STR_WIDTH(i
+ 1);
2218 r
= table_add_cell_stringf(table
, NULL
, "Iter. #%u:", i
+1);
2220 return table_log_add_error(r
);
2222 r
= table_add_many(table
,
2223 TABLE_TIMESTAMP
, next
,
2224 TABLE_SET_COLOR
, ansi_highlight_blue());
2226 return table_log_add_error(r
);
2229 if (!in_utc_timezone()) {
2230 r
= table_add_many(table
,
2231 TABLE_STRING
, "(in UTC):",
2232 TABLE_TIMESTAMP_UTC
, next
);
2234 return table_log_add_error(r
);
2237 r
= table_add_many(table
,
2238 TABLE_STRING
, "From now:",
2239 TABLE_TIMESTAMP_RELATIVE
, next
);
2241 return table_log_add_error(r
);
2246 return table_print(table
, NULL
);
2249 static int test_calendar(int argc
, char *argv
[], void *userdata
) {
2254 if (arg_base_time
!= USEC_INFINITY
)
2257 n
= now(CLOCK_REALTIME
); /* We want to use the same "base" for all expressions */
2259 STRV_FOREACH(p
, strv_skip(argv
, 1)) {
2260 r
= test_calendar_one(n
, *p
);
2261 if (ret
== 0 && r
< 0)
2271 static int service_watchdogs(int argc
, char *argv
[], void *userdata
) {
2272 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
2273 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
2276 assert(IN_SET(argc
, 1, 2));
2279 r
= acquire_bus(&bus
, NULL
);
2281 return bus_log_connect_error(r
, arg_transport
);
2284 /* get ServiceWatchdogs */
2285 r
= bus_get_property_trivial(bus
, bus_systemd_mgr
, "ServiceWatchdogs", &error
, 'b', &b
);
2287 return log_error_errno(r
, "Failed to get service-watchdog state: %s", bus_error_message(&error
, r
));
2289 printf("%s\n", yes_no(!!b
));
2292 /* set ServiceWatchdogs */
2293 b
= parse_boolean(argv
[1]);
2295 return log_error_errno(b
, "Failed to parse service-watchdogs argument: %m");
2297 r
= bus_set_property(bus
, bus_systemd_mgr
, "ServiceWatchdogs", &error
, "b", b
);
2299 return log_error_errno(r
, "Failed to set service-watchdog state: %s", bus_error_message(&error
, r
));
2305 static int do_condition(int argc
, char *argv
[], void *userdata
) {
2306 return verify_conditions(strv_skip(argv
, 1), arg_scope
, arg_unit
, arg_root
);
2309 static int do_verify(int argc
, char *argv
[], void *userdata
) {
2310 _cleanup_strv_free_
char **filenames
= NULL
;
2311 _cleanup_(rm_rf_physical_and_freep
) char *tempdir
= NULL
;
2314 r
= mkdtemp_malloc("/tmp/systemd-analyze-XXXXXX", &tempdir
);
2316 return log_error_errno(r
, "Failed to setup working directory: %m");
2318 r
= process_aliases(argv
, tempdir
, &filenames
);
2320 return log_error_errno(r
, "Couldn't process aliases: %m");
2322 return verify_units(filenames
, arg_scope
, arg_man
, arg_generators
, arg_recursive_errors
, arg_root
);
2325 static int do_security(int argc
, char *argv
[], void *userdata
) {
2326 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
2327 _cleanup_(json_variant_unrefp
) JsonVariant
*policy
= NULL
;
2329 unsigned line
, column
;
2331 r
= acquire_bus(&bus
, NULL
);
2333 return bus_log_connect_error(r
, arg_transport
);
2335 pager_open(arg_pager_flags
);
2337 if (arg_security_policy
) {
2338 r
= json_parse_file(/*f=*/ NULL
, arg_security_policy
, /*flags=*/ 0, &policy
, &line
, &column
);
2340 return log_error_errno(r
, "Failed to parse '%s' at %u:%u: %m", arg_security_policy
, line
, column
);
2342 _cleanup_fclose_
FILE *f
= NULL
;
2343 _cleanup_free_
char *pp
= NULL
;
2345 r
= search_and_fopen_nulstr("systemd-analyze-security.policy", "re", /*root=*/ NULL
, CONF_PATHS_NULSTR("systemd"), &f
, &pp
);
2346 if (r
< 0 && r
!= -ENOENT
)
2350 r
= json_parse_file(f
, pp
, /*flags=*/ 0, &policy
, &line
, &column
);
2352 return log_error_errno(r
, "[%s:%u:%u] Failed to parse JSON policy: %m", pp
, line
, column
);
2356 return analyze_security(bus
,
2365 arg_json_format_flags
,
2370 static int help(int argc
, char *argv
[], void *userdata
) {
2371 _cleanup_free_
char *link
= NULL
, *dot_link
= NULL
;
2374 pager_open(arg_pager_flags
);
2376 r
= terminal_urlify_man("systemd-analyze", "1", &link
);
2380 /* Not using terminal_urlify_man() for this, since we don't want the "man page" text suffix in this case. */
2381 r
= terminal_urlify("man:dot(1)", "dot(1)", &dot_link
);
2385 printf("%s [OPTIONS...] COMMAND ...\n\n"
2386 "%sProfile systemd, show unit dependencies, check unit files.%s\n"
2388 " [time] Print time required to boot the machine\n"
2389 " blame Print list of running units ordered by\n"
2391 " critical-chain [UNIT...] Print a tree of the time critical chain\n"
2393 " plot Output SVG graphic showing service\n"
2395 " dot [UNIT...] Output dependency graph in %s format\n"
2396 " dump Output state serialization of service\n"
2398 " cat-config Show configuration file and drop-ins\n"
2399 " unit-files List files and symlinks for units\n"
2400 " unit-paths List load directories for units\n"
2401 " exit-status [STATUS...] List exit status definitions\n"
2402 " capability [CAP...] List capability definitions\n"
2403 " syscall-filter [NAME...] Print list of syscalls in seccomp\n"
2405 " filesystems [NAME...] Print list of filesystems\n"
2406 " condition CONDITION... Evaluate conditions and asserts\n"
2407 " verify FILE... Check unit files for correctness\n"
2408 " calendar SPEC... Validate repetitive calendar time\n"
2410 " timestamp TIMESTAMP... Validate a timestamp\n"
2411 " timespan SPAN... Validate a time span\n"
2412 " security [UNIT...] Analyze security of unit\n"
2414 " -h --help Show this help\n"
2415 " --recursive-errors=MODE Control which units are verified\n"
2416 " --offline=BOOL Perform a security review on unit file(s)\n"
2417 " --threshold=N Exit with a non-zero status when overall\n"
2418 " exposure level is over threshold value\n"
2419 " --version Show package version\n"
2420 " --security-policy=PATH Use custom JSON security policy instead\n"
2421 " of built-in one\n"
2422 " --json=pretty|short|off Generate JSON output of the security\n"
2424 " --no-pager Do not pipe output into a pager\n"
2425 " --system Operate on system systemd instance\n"
2426 " --user Operate on user systemd instance\n"
2427 " --global Operate on global user configuration\n"
2428 " -H --host=[USER@]HOST Operate on remote host\n"
2429 " -M --machine=CONTAINER Operate on local container\n"
2430 " --order Show only order in the graph\n"
2431 " --require Show only requirement in the graph\n"
2432 " --from-pattern=GLOB Show only origins in the graph\n"
2433 " --to-pattern=GLOB Show only destinations in the graph\n"
2434 " --fuzz=SECONDS Also print services which finished SECONDS\n"
2435 " earlier than the latest in the branch\n"
2436 " --man[=BOOL] Do [not] check for existence of man pages\n"
2437 " --generators[=BOOL] Do [not] run unit generators\n"
2438 " (requires privileges)\n"
2439 " --iterations=N Show the specified number of iterations\n"
2440 " --base-time=TIMESTAMP Calculate calendar times relative to\n"
2442 "\nSee the %s for details.\n",
2443 program_invocation_short_name
,
2449 /* When updating this list, including descriptions, apply changes to
2450 * shell-completion/bash/systemd-analyze and shell-completion/zsh/_systemd-analyze too. */
2455 static int parse_argv(int argc
, char *argv
[]) {
2457 ARG_VERSION
= 0x100,
2465 ARG_DOT_FROM_PATTERN
,
2473 ARG_RECURSIVE_ERRORS
,
2476 ARG_SECURITY_POLICY
,
2480 static const struct option options
[] = {
2481 { "help", no_argument
, NULL
, 'h' },
2482 { "version", no_argument
, NULL
, ARG_VERSION
},
2483 { "order", no_argument
, NULL
, ARG_ORDER
},
2484 { "require", no_argument
, NULL
, ARG_REQUIRE
},
2485 { "root", required_argument
, NULL
, ARG_ROOT
},
2486 { "image", required_argument
, NULL
, ARG_IMAGE
},
2487 { "recursive-errors", required_argument
, NULL
, ARG_RECURSIVE_ERRORS
},
2488 { "offline", required_argument
, NULL
, ARG_OFFLINE
},
2489 { "threshold", required_argument
, NULL
, ARG_THRESHOLD
},
2490 { "security-policy", required_argument
, NULL
, ARG_SECURITY_POLICY
},
2491 { "system", no_argument
, NULL
, ARG_SYSTEM
},
2492 { "user", no_argument
, NULL
, ARG_USER
},
2493 { "global", no_argument
, NULL
, ARG_GLOBAL
},
2494 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
2495 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
2496 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
2497 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
2498 { "man", optional_argument
, NULL
, ARG_MAN
},
2499 { "generators", optional_argument
, NULL
, ARG_GENERATORS
},
2500 { "host", required_argument
, NULL
, 'H' },
2501 { "machine", required_argument
, NULL
, 'M' },
2502 { "iterations", required_argument
, NULL
, ARG_ITERATIONS
},
2503 { "base-time", required_argument
, NULL
, ARG_BASE_TIME
},
2504 { "unit", required_argument
, NULL
, 'U' },
2505 { "json", required_argument
, NULL
, ARG_JSON
},
2514 while ((c
= getopt_long(argc
, argv
, "hH:M:U:", options
, NULL
)) >= 0)
2518 return help(0, NULL
, NULL
);
2520 case ARG_RECURSIVE_ERRORS
:
2521 if (streq(optarg
, "help")) {
2522 DUMP_STRING_TABLE(recursive_errors
, RecursiveErrors
, _RECURSIVE_ERRORS_MAX
);
2525 r
= recursive_errors_from_string(optarg
);
2527 return log_error_errno(r
, "Unknown mode passed to --recursive-errors='%s'.", optarg
);
2529 arg_recursive_errors
= r
;
2536 r
= parse_path_argument(optarg
, /* suppress_root= */ true, &arg_root
);
2542 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_image
);
2548 arg_scope
= UNIT_FILE_SYSTEM
;
2552 arg_scope
= UNIT_FILE_USER
;
2556 arg_scope
= UNIT_FILE_GLOBAL
;
2560 arg_dot
= DEP_ORDER
;
2564 arg_dot
= DEP_REQUIRE
;
2567 case ARG_DOT_FROM_PATTERN
:
2568 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
2573 case ARG_DOT_TO_PATTERN
:
2574 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
2580 r
= parse_sec(optarg
, &arg_fuzz
);
2586 arg_pager_flags
|= PAGER_DISABLE
;
2590 arg_transport
= BUS_TRANSPORT_REMOTE
;
2595 arg_transport
= BUS_TRANSPORT_MACHINE
;
2600 r
= parse_boolean_argument("--man", optarg
, &arg_man
);
2605 case ARG_GENERATORS
:
2606 r
= parse_boolean_argument("--generators", optarg
, &arg_generators
);
2612 r
= parse_boolean_argument("--offline", optarg
, &arg_offline
);
2618 r
= safe_atou(optarg
, &arg_threshold
);
2619 if (r
< 0 || arg_threshold
> 100)
2620 return log_error_errno(r
< 0 ? r
: SYNTHETIC_ERRNO(EINVAL
), "Failed to parse threshold: %s", optarg
);
2624 case ARG_SECURITY_POLICY
:
2625 r
= parse_path_argument(optarg
, /* suppress_root= */ false, &arg_security_policy
);
2631 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
2636 case ARG_ITERATIONS
:
2637 r
= safe_atou(optarg
, &arg_iterations
);
2639 return log_error_errno(r
, "Failed to parse iterations: %s", optarg
);
2644 r
= parse_timestamp(optarg
, &arg_base_time
);
2646 return log_error_errno(r
, "Failed to parse --base-time= parameter: %s", optarg
);
2651 _cleanup_free_
char *mangled
= NULL
;
2653 r
= unit_name_mangle(optarg
, UNIT_NAME_MANGLE_WARN
, &mangled
);
2655 return log_error_errno(r
, "Failed to mangle unit name %s: %m", optarg
);
2657 free_and_replace(arg_unit
, mangled
);
2664 assert_not_reached();
2667 if (arg_offline
&& !streq_ptr(argv
[optind
], "security"))
2668 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2669 "Option --offline= is only supported for security right now.");
2671 if (arg_json_format_flags
!= JSON_FORMAT_OFF
&& !streq_ptr(argv
[optind
], "security"))
2672 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2673 "Option --json= is only supported for security right now.");
2675 if (arg_threshold
!= 100 && !streq_ptr(argv
[optind
], "security"))
2676 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2677 "Option --threshold= is only supported for security right now.");
2679 if (arg_scope
== UNIT_FILE_GLOBAL
&&
2680 !STR_IN_SET(argv
[optind
] ?: "time", "dot", "unit-paths", "verify"))
2681 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2682 "Option --global only makes sense with verbs dot, unit-paths, verify.");
2684 if (streq_ptr(argv
[optind
], "cat-config") && arg_scope
== UNIT_FILE_USER
)
2685 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2686 "Option --user is not supported for cat-config right now.");
2688 if (arg_security_policy
&& !streq_ptr(argv
[optind
], "security"))
2689 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2690 "Option --security-policy= is only supported for security.");
2692 if ((arg_root
|| arg_image
) && (!STRPTR_IN_SET(argv
[optind
], "cat-config", "verify", "condition")) &&
2693 (!(streq_ptr(argv
[optind
], "security") && arg_offline
)))
2694 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
2695 "Options --root= and --image= are only supported for cat-config, verify, condition and security when used with --offline= right now.");
2697 /* Having both an image and a root is not supported by the code */
2698 if (arg_root
&& arg_image
)
2699 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Please specify either --root= or --image=, the combination of both is not supported.");
2701 if (arg_unit
&& !streq_ptr(argv
[optind
], "condition"))
2702 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Option --unit= is only supported for condition");
2704 if (streq_ptr(argv
[optind
], "condition") && !arg_unit
&& optind
>= argc
- 1)
2705 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Too few arguments for condition");
2707 if (streq_ptr(argv
[optind
], "condition") && arg_unit
&& optind
< argc
- 1)
2708 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "No conditions can be passed if --unit= is used.");
2710 return 1; /* work to do */
2713 static int run(int argc
, char *argv
[]) {
2714 _cleanup_(loop_device_unrefp
) LoopDevice
*loop_device
= NULL
;
2715 _cleanup_(decrypted_image_unrefp
) DecryptedImage
*decrypted_image
= NULL
;
2716 _cleanup_(umount_and_rmdir_and_freep
) char *unlink_dir
= NULL
;
2718 static const Verb verbs
[] = {
2719 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
2720 { "time", VERB_ANY
, 1, VERB_DEFAULT
, analyze_time
},
2721 { "blame", VERB_ANY
, 1, 0, analyze_blame
},
2722 { "critical-chain", VERB_ANY
, VERB_ANY
, 0, analyze_critical_chain
},
2723 { "plot", VERB_ANY
, 1, 0, analyze_plot
},
2724 { "dot", VERB_ANY
, VERB_ANY
, 0, dot
},
2725 /* The following seven verbs are deprecated */
2726 { "log-level", VERB_ANY
, 2, 0, verb_log_control
},
2727 { "log-target", VERB_ANY
, 2, 0, verb_log_control
},
2728 { "set-log-level", 2, 2, 0, verb_log_control
},
2729 { "get-log-level", VERB_ANY
, 1, 0, verb_log_control
},
2730 { "set-log-target", 2, 2, 0, verb_log_control
},
2731 { "get-log-target", VERB_ANY
, 1, 0, verb_log_control
},
2732 { "service-watchdogs", VERB_ANY
, 2, 0, service_watchdogs
},
2733 { "dump", VERB_ANY
, 1, 0, dump
},
2734 { "cat-config", 2, VERB_ANY
, 0, cat_config
},
2735 { "unit-files", VERB_ANY
, VERB_ANY
, 0, do_unit_files
},
2736 { "unit-paths", 1, 1, 0, dump_unit_paths
},
2737 { "exit-status", VERB_ANY
, VERB_ANY
, 0, dump_exit_status
},
2738 { "syscall-filter", VERB_ANY
, VERB_ANY
, 0, dump_syscall_filters
},
2739 { "capability", VERB_ANY
, VERB_ANY
, 0, dump_capabilities
},
2740 { "filesystems", VERB_ANY
, VERB_ANY
, 0, dump_filesystems
},
2741 { "condition", VERB_ANY
, VERB_ANY
, 0, do_condition
},
2742 { "verify", 2, VERB_ANY
, 0, do_verify
},
2743 { "calendar", 2, VERB_ANY
, 0, test_calendar
},
2744 { "timestamp", 2, VERB_ANY
, 0, test_timestamp
},
2745 { "timespan", 2, VERB_ANY
, 0, dump_timespan
},
2746 { "security", VERB_ANY
, VERB_ANY
, 0, do_security
},
2752 setlocale(LC_ALL
, "");
2753 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
2757 r
= parse_argv(argc
, argv
);
2761 /* Open up and mount the image */
2765 r
= mount_image_privately_interactively(
2767 DISSECT_IMAGE_GENERIC_ROOT
|
2768 DISSECT_IMAGE_RELAX_VAR_CHECK
|
2769 DISSECT_IMAGE_READ_ONLY
,
2776 arg_root
= strdup(unlink_dir
);
2781 return dispatch_verb(argc
, argv
, verbs
, NULL
);
2784 DEFINE_MAIN_FUNCTION(run
);