]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/analyze/analyze.c
Merge pull request #19591 from poettering/terminal-fixes
[thirdparty/systemd.git] / src / analyze / analyze.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /***
3 Copyright © 2013 Simon Peeters
4 ***/
5
6 #include <getopt.h>
7 #include <inttypes.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <unistd.h>
11
12 #include "sd-bus.h"
13
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"
23 #include "cap-list.h"
24 #include "capability-util.h"
25 #include "conf-files.h"
26 #include "copy.h"
27 #include "def.h"
28 #include "exit-status.h"
29 #include "fd-util.h"
30 #include "fileio.h"
31 #include "format-table.h"
32 #include "glob-util.h"
33 #include "hashmap.h"
34 #include "locale-util.h"
35 #include "log.h"
36 #include "main-func.h"
37 #include "nulstr-util.h"
38 #include "pager.h"
39 #include "parse-argument.h"
40 #include "parse-util.h"
41 #include "path-util.h"
42 #include "pretty-print.h"
43 #if HAVE_SECCOMP
44 # include "seccomp-util.h"
45 #endif
46 #include "sort-util.h"
47 #include "special.h"
48 #include "strv.h"
49 #include "strxcpyx.h"
50 #include "terminal-util.h"
51 #include "time-util.h"
52 #include "unit-name.h"
53 #include "util.h"
54 #include "verbs.h"
55 #include "version.h"
56
57 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
58 #define SCALE_Y (20.0)
59
60 #define svg(...) printf(__VA_ARGS__)
61
62 #define svg_bar(class, x1, x2, y) \
63 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
64 (class), \
65 SCALE_X * (x1), SCALE_Y * (y), \
66 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
67
68 #define svg_text(b, x, y, format, ...) \
69 do { \
70 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
71 svg(format, ## __VA_ARGS__); \
72 svg("</text>\n"); \
73 } while (false)
74
75 static enum dot {
76 DEP_ALL,
77 DEP_ORDER,
78 DEP_REQUIRE
79 } arg_dot = DEP_ALL;
80 static char **arg_dot_from_patterns = NULL;
81 static char **arg_dot_to_patterns = NULL;
82 static usec_t arg_fuzz = 0;
83 static PagerFlags arg_pager_flags = 0;
84 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
85 static const char *arg_host = NULL;
86 static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
87 static bool arg_man = true;
88 static bool arg_generators = false;
89 static const char *arg_root = NULL;
90 static unsigned arg_iterations = 1;
91 static usec_t arg_base_time = USEC_INFINITY;
92
93 STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
94 STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
95
96 typedef struct BootTimes {
97 usec_t firmware_time;
98 usec_t loader_time;
99 usec_t kernel_time;
100 usec_t kernel_done_time;
101 usec_t initrd_time;
102 usec_t userspace_time;
103 usec_t finish_time;
104 usec_t security_start_time;
105 usec_t security_finish_time;
106 usec_t generators_start_time;
107 usec_t generators_finish_time;
108 usec_t unitsload_start_time;
109 usec_t unitsload_finish_time;
110 usec_t initrd_security_start_time;
111 usec_t initrd_security_finish_time;
112 usec_t initrd_generators_start_time;
113 usec_t initrd_generators_finish_time;
114 usec_t initrd_unitsload_start_time;
115 usec_t initrd_unitsload_finish_time;
116
117 /*
118 * If we're analyzing the user instance, all timestamps will be offset
119 * by its own start-up timestamp, which may be arbitrarily big.
120 * With "plot", this causes arbitrarily wide output SVG files which almost
121 * completely consist of empty space. Thus we cancel out this offset.
122 *
123 * This offset is subtracted from times above by acquire_boot_times(),
124 * but it still needs to be subtracted from unit-specific timestamps
125 * (so it is stored here for reference).
126 */
127 usec_t reverse_offset;
128 } BootTimes;
129
130 typedef struct UnitTimes {
131 bool has_data;
132 char *name;
133 usec_t activating;
134 usec_t activated;
135 usec_t deactivated;
136 usec_t deactivating;
137 usec_t time;
138 } UnitTimes;
139
140 typedef struct HostInfo {
141 char *hostname;
142 char *kernel_name;
143 char *kernel_release;
144 char *kernel_version;
145 char *os_pretty_name;
146 char *virtualization;
147 char *architecture;
148 } HostInfo;
149
150 static int acquire_bus(sd_bus **bus, bool *use_full_bus) {
151 bool user = arg_scope != UNIT_FILE_SYSTEM;
152 int r;
153
154 if (use_full_bus && *use_full_bus) {
155 r = bus_connect_transport(arg_transport, arg_host, user, bus);
156 if (IN_SET(r, 0, -EHOSTDOWN))
157 return r;
158
159 *use_full_bus = false;
160 }
161
162 return bus_connect_transport_systemd(arg_transport, arg_host, user, bus);
163 }
164
165 static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
166 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
167 int r;
168
169 assert(bus);
170 assert(path);
171 assert(interface);
172 assert(property);
173 assert(val);
174
175 r = sd_bus_get_property_trivial(
176 bus,
177 "org.freedesktop.systemd1",
178 path,
179 interface,
180 property,
181 &error,
182 't', val);
183
184 if (r < 0)
185 return log_error_errno(r, "Failed to parse reply: %s", bus_error_message(&error, r));
186
187 return 0;
188 }
189
190 static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) {
191 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
192 int r;
193
194 assert(bus);
195 assert(path);
196 assert(property);
197 assert(strv);
198
199 r = sd_bus_get_property_strv(
200 bus,
201 "org.freedesktop.systemd1",
202 path,
203 "org.freedesktop.systemd1.Unit",
204 property,
205 &error,
206 strv);
207 if (r < 0)
208 return log_error_errno(r, "Failed to get unit property %s: %s", property, bus_error_message(&error, r));
209
210 return 0;
211 }
212
213 static int compare_unit_start(const UnitTimes *a, const UnitTimes *b) {
214 return CMP(a->activating, b->activating);
215 }
216
217 static UnitTimes* unit_times_free_array(UnitTimes *t) {
218 for (UnitTimes *p = t; p && p->has_data; p++)
219 free(p->name);
220 return mfree(t);
221 }
222 DEFINE_TRIVIAL_CLEANUP_FUNC(UnitTimes*, unit_times_free_array);
223
224 static void subtract_timestamp(usec_t *a, usec_t b) {
225 assert(a);
226
227 if (*a > 0) {
228 assert(*a >= b);
229 *a -= b;
230 }
231 }
232
233 static int acquire_boot_times(sd_bus *bus, BootTimes **bt) {
234 static const struct bus_properties_map property_map[] = {
235 { "FirmwareTimestampMonotonic", "t", NULL, offsetof(BootTimes, firmware_time) },
236 { "LoaderTimestampMonotonic", "t", NULL, offsetof(BootTimes, loader_time) },
237 { "KernelTimestamp", "t", NULL, offsetof(BootTimes, kernel_time) },
238 { "InitRDTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_time) },
239 { "UserspaceTimestampMonotonic", "t", NULL, offsetof(BootTimes, userspace_time) },
240 { "FinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, finish_time) },
241 { "SecurityStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, security_start_time) },
242 { "SecurityFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, security_finish_time) },
243 { "GeneratorsStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, generators_start_time) },
244 { "GeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, generators_finish_time) },
245 { "UnitsLoadStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, unitsload_start_time) },
246 { "UnitsLoadFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, unitsload_finish_time) },
247 { "InitRDSecurityStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_security_start_time) },
248 { "InitRDSecurityFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_security_finish_time) },
249 { "InitRDGeneratorsStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_generators_start_time) },
250 { "InitRDGeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_generators_finish_time) },
251 { "InitRDUnitsLoadStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_unitsload_start_time) },
252 { "InitRDUnitsLoadFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_unitsload_finish_time) },
253 {},
254 };
255 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
256 static BootTimes times;
257 static bool cached = false;
258 int r;
259
260 if (cached)
261 goto finish;
262
263 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
264
265 r = bus_map_all_properties(
266 bus,
267 "org.freedesktop.systemd1",
268 "/org/freedesktop/systemd1",
269 property_map,
270 BUS_MAP_STRDUP,
271 &error,
272 NULL,
273 &times);
274 if (r < 0)
275 return log_error_errno(r, "Failed to get timestamp properties: %s", bus_error_message(&error, r));
276
277 if (times.finish_time <= 0)
278 return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS),
279 "Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64").\n"
280 "Please try again later.\n"
281 "Hint: Use 'systemctl%s list-jobs' to see active jobs",
282 times.finish_time,
283 arg_scope == UNIT_FILE_SYSTEM ? "" : " --user");
284
285 if (arg_scope == UNIT_FILE_SYSTEM && times.security_start_time > 0) {
286 /* security_start_time is set when systemd is not running under container environment. */
287 if (times.initrd_time > 0)
288 times.kernel_done_time = times.initrd_time;
289 else
290 times.kernel_done_time = times.userspace_time;
291 } else {
292 /*
293 * User-instance-specific or container-system-specific timestamps processing
294 * (see comment to reverse_offset in BootTimes).
295 */
296 times.reverse_offset = times.userspace_time;
297
298 times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time =
299 times.userspace_time = times.security_start_time = times.security_finish_time = 0;
300
301 subtract_timestamp(&times.finish_time, times.reverse_offset);
302
303 subtract_timestamp(&times.generators_start_time, times.reverse_offset);
304 subtract_timestamp(&times.generators_finish_time, times.reverse_offset);
305
306 subtract_timestamp(&times.unitsload_start_time, times.reverse_offset);
307 subtract_timestamp(&times.unitsload_finish_time, times.reverse_offset);
308 }
309
310 cached = true;
311
312 finish:
313 *bt = &times;
314 return 0;
315 }
316
317 static HostInfo* free_host_info(HostInfo *hi) {
318 if (!hi)
319 return NULL;
320
321 free(hi->hostname);
322 free(hi->kernel_name);
323 free(hi->kernel_release);
324 free(hi->kernel_version);
325 free(hi->os_pretty_name);
326 free(hi->virtualization);
327 free(hi->architecture);
328 return mfree(hi);
329 }
330
331 DEFINE_TRIVIAL_CLEANUP_FUNC(HostInfo *, free_host_info);
332
333 static int acquire_time_data(sd_bus *bus, UnitTimes **out) {
334 static const struct bus_properties_map property_map[] = {
335 { "InactiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activating) },
336 { "ActiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activated) },
337 { "ActiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, deactivating) },
338 { "InactiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, deactivated) },
339 {},
340 };
341 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
342 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
343 _cleanup_(unit_times_free_arrayp) UnitTimes *unit_times = NULL;
344 BootTimes *boot_times = NULL;
345 size_t c = 0;
346 UnitInfo u;
347 int r;
348
349 r = acquire_boot_times(bus, &boot_times);
350 if (r < 0)
351 return r;
352
353 r = bus_call_method(bus, bus_systemd_mgr, "ListUnits", &error, &reply, NULL);
354 if (r < 0)
355 return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
356
357 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
358 if (r < 0)
359 return bus_log_parse_error(r);
360
361 while ((r = bus_parse_unit_info(reply, &u)) > 0) {
362 UnitTimes *t;
363
364 if (!GREEDY_REALLOC(unit_times, c + 2))
365 return log_oom();
366
367 unit_times[c + 1].has_data = false;
368 t = &unit_times[c];
369 t->name = NULL;
370
371 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
372
373 r = bus_map_all_properties(
374 bus,
375 "org.freedesktop.systemd1",
376 u.unit_path,
377 property_map,
378 BUS_MAP_STRDUP,
379 &error,
380 NULL,
381 t);
382 if (r < 0)
383 return log_error_errno(r, "Failed to get timestamp properties of unit %s: %s",
384 u.id, bus_error_message(&error, r));
385
386 subtract_timestamp(&t->activating, boot_times->reverse_offset);
387 subtract_timestamp(&t->activated, boot_times->reverse_offset);
388 subtract_timestamp(&t->deactivating, boot_times->reverse_offset);
389 subtract_timestamp(&t->deactivated, boot_times->reverse_offset);
390
391 if (t->activated >= t->activating)
392 t->time = t->activated - t->activating;
393 else if (t->deactivated >= t->activating)
394 t->time = t->deactivated - t->activating;
395 else
396 t->time = 0;
397
398 if (t->activating == 0)
399 continue;
400
401 t->name = strdup(u.id);
402 if (!t->name)
403 return log_oom();
404
405 t->has_data = true;
406 c++;
407 }
408 if (r < 0)
409 return bus_log_parse_error(r);
410
411 *out = TAKE_PTR(unit_times);
412 return c;
413 }
414
415 static int acquire_host_info(sd_bus *bus, HostInfo **hi) {
416 static const struct bus_properties_map hostname_map[] = {
417 { "Hostname", "s", NULL, offsetof(HostInfo, hostname) },
418 { "KernelName", "s", NULL, offsetof(HostInfo, kernel_name) },
419 { "KernelRelease", "s", NULL, offsetof(HostInfo, kernel_release) },
420 { "KernelVersion", "s", NULL, offsetof(HostInfo, kernel_version) },
421 { "OperatingSystemPrettyName", "s", NULL, offsetof(HostInfo, os_pretty_name) },
422 {}
423 };
424
425 static const struct bus_properties_map manager_map[] = {
426 { "Virtualization", "s", NULL, offsetof(HostInfo, virtualization) },
427 { "Architecture", "s", NULL, offsetof(HostInfo, architecture) },
428 {}
429 };
430
431 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
432 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *system_bus = NULL;
433 _cleanup_(free_host_infop) HostInfo *host = NULL;
434 int r;
435
436 host = new0(HostInfo, 1);
437 if (!host)
438 return log_oom();
439
440 if (arg_scope != UNIT_FILE_SYSTEM) {
441 r = bus_connect_transport(arg_transport, arg_host, false, &system_bus);
442 if (r < 0) {
443 log_debug_errno(r, "Failed to connect to system bus, ignoring: %m");
444 goto manager;
445 }
446 }
447
448 r = bus_map_all_properties(
449 system_bus ?: bus,
450 "org.freedesktop.hostname1",
451 "/org/freedesktop/hostname1",
452 hostname_map,
453 BUS_MAP_STRDUP,
454 &error,
455 NULL,
456 host);
457 if (r < 0) {
458 log_debug_errno(r, "Failed to get host information from systemd-hostnamed, ignoring: %s",
459 bus_error_message(&error, r));
460 sd_bus_error_free(&error);
461 }
462
463 manager:
464 r = bus_map_all_properties(
465 bus,
466 "org.freedesktop.systemd1",
467 "/org/freedesktop/systemd1",
468 manager_map,
469 BUS_MAP_STRDUP,
470 &error,
471 NULL,
472 host);
473 if (r < 0)
474 return log_error_errno(r, "Failed to get host information from systemd: %s",
475 bus_error_message(&error, r));
476
477 *hi = TAKE_PTR(host);
478 return 0;
479 }
480
481 static int pretty_boot_time(sd_bus *bus, char **_buf) {
482 char ts[FORMAT_TIMESPAN_MAX];
483 BootTimes *t;
484 static char buf[4096];
485 size_t size;
486 char *ptr;
487 int r;
488 usec_t activated_time = USEC_INFINITY;
489 _cleanup_free_ char *path = NULL, *unit_id = NULL;
490 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
491
492 r = acquire_boot_times(bus, &t);
493 if (r < 0)
494 return r;
495
496 path = unit_dbus_path_from_name(SPECIAL_DEFAULT_TARGET);
497 if (!path)
498 return log_oom();
499
500 r = sd_bus_get_property_string(
501 bus,
502 "org.freedesktop.systemd1",
503 path,
504 "org.freedesktop.systemd1.Unit",
505 "Id",
506 &error,
507 &unit_id);
508 if (r < 0) {
509 log_error_errno(r, "default.target doesn't seem to exist: %s", bus_error_message(&error, r));
510 unit_id = NULL;
511 }
512
513 r = bus_get_uint64_property(bus, path,
514 "org.freedesktop.systemd1.Unit",
515 "ActiveEnterTimestampMonotonic",
516 &activated_time);
517 if (r < 0) {
518 log_info_errno(r, "Could not get time to reach default.target, ignoring: %m");
519 activated_time = USEC_INFINITY;
520 }
521
522 ptr = buf;
523 size = sizeof(buf);
524
525 size = strpcpyf(&ptr, size, "Startup finished in ");
526 if (t->firmware_time > 0)
527 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
528 if (t->loader_time > 0)
529 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
530 if (t->kernel_done_time > 0)
531 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
532 if (t->initrd_time > 0)
533 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
534
535 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
536 if (t->kernel_done_time > 0)
537 strpcpyf(&ptr, size, "= %s ", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
538
539 if (unit_id && timestamp_is_set(activated_time)) {
540 usec_t base = t->userspace_time > 0 ? t->userspace_time : t->reverse_offset;
541
542 size = strpcpyf(&ptr, size, "\n%s reached after %s in userspace", unit_id,
543 format_timespan(ts, sizeof(ts), activated_time - base, USEC_PER_MSEC));
544 } else if (unit_id && activated_time == 0)
545 size = strpcpyf(&ptr, size, "\n%s was never reached", unit_id);
546 else if (unit_id && activated_time == USEC_INFINITY)
547 size = strpcpyf(&ptr, size, "\nCould not get time to reach %s.", unit_id);
548 else if (!unit_id)
549 size = strpcpyf(&ptr, size, "\ncould not find default.target");
550
551 ptr = strdup(buf);
552 if (!ptr)
553 return log_oom();
554
555 *_buf = ptr;
556 return 0;
557 }
558
559 static void svg_graph_box(double height, double begin, double end) {
560 /* outside box, fill */
561 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
562 SCALE_X * (end - begin),
563 SCALE_Y * height);
564
565 for (long long i = ((long long) (begin / 100000)) * 100000; i <= end; i += 100000) {
566 /* lines for each second */
567 if (i % 5000000 == 0)
568 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
569 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
570 SCALE_X * i,
571 SCALE_X * i,
572 SCALE_Y * height,
573 SCALE_X * i,
574 -5.0,
575 0.000001 * i);
576 else if (i % 1000000 == 0)
577 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
578 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
579 SCALE_X * i,
580 SCALE_X * i,
581 SCALE_Y * height,
582 SCALE_X * i,
583 -5.0,
584 0.000001 * i);
585 else
586 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
587 SCALE_X * i,
588 SCALE_X * i,
589 SCALE_Y * height);
590 }
591 }
592
593 static int plot_unit_times(UnitTimes *u, double width, int y) {
594 char ts[FORMAT_TIMESPAN_MAX];
595 bool b;
596
597 if (!u->name)
598 return 0;
599
600 svg_bar("activating", u->activating, u->activated, y);
601 svg_bar("active", u->activated, u->deactivating, y);
602 svg_bar("deactivating", u->deactivating, u->deactivated, y);
603
604 /* place the text on the left if we have passed the half of the svg width */
605 b = u->activating * SCALE_X < width / 2;
606 if (u->time)
607 svg_text(b, u->activating, y, "%s (%s)",
608 u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
609 else
610 svg_text(b, u->activating, y, "%s", u->name);
611
612 return 1;
613 }
614
615 static int analyze_plot(int argc, char *argv[], void *userdata) {
616 _cleanup_(free_host_infop) HostInfo *host = NULL;
617 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
618 _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
619 _cleanup_free_ char *pretty_times = NULL;
620 bool use_full_bus = arg_scope == UNIT_FILE_SYSTEM;
621 BootTimes *boot;
622 UnitTimes *u;
623 int n, m = 1, y = 0, r;
624 double width;
625
626 r = acquire_bus(&bus, &use_full_bus);
627 if (r < 0)
628 return bus_log_connect_error(r);
629
630 n = acquire_boot_times(bus, &boot);
631 if (n < 0)
632 return n;
633
634 n = pretty_boot_time(bus, &pretty_times);
635 if (n < 0)
636 return n;
637
638 if (use_full_bus || arg_scope != UNIT_FILE_SYSTEM) {
639 n = acquire_host_info(bus, &host);
640 if (n < 0)
641 return n;
642 }
643
644 n = acquire_time_data(bus, &times);
645 if (n <= 0)
646 return n;
647
648 typesafe_qsort(times, n, compare_unit_start);
649
650 width = SCALE_X * (boot->firmware_time + boot->finish_time);
651 if (width < 800.0)
652 width = 800.0;
653
654 if (boot->firmware_time > boot->loader_time)
655 m++;
656 if (boot->loader_time > 0) {
657 m++;
658 if (width < 1000.0)
659 width = 1000.0;
660 }
661 if (boot->initrd_time > 0)
662 m++;
663 if (boot->kernel_done_time > 0)
664 m++;
665
666 for (u = times; u->has_data; u++) {
667 double text_start, text_width;
668
669 if (u->activating > boot->finish_time) {
670 u->name = mfree(u->name);
671 continue;
672 }
673
674 /* If the text cannot fit on the left side then
675 * increase the svg width so it fits on the right.
676 * TODO: calculate the text width more accurately */
677 text_width = 8.0 * strlen(u->name);
678 text_start = (boot->firmware_time + u->activating) * SCALE_X;
679 if (text_width > text_start && text_width + text_start > width)
680 width = text_width + text_start;
681
682 if (u->deactivated > u->activating &&
683 u->deactivated <= boot->finish_time &&
684 u->activated == 0 && u->deactivating == 0)
685 u->activated = u->deactivating = u->deactivated;
686 if (u->activated < u->activating || u->activated > boot->finish_time)
687 u->activated = boot->finish_time;
688 if (u->deactivating < u->activated || u->deactivating > boot->finish_time)
689 u->deactivating = boot->finish_time;
690 if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
691 u->deactivated = boot->finish_time;
692 m++;
693 }
694
695 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
696 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
697 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
698
699 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
700 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
701 80.0 + width, 150.0 + (m * SCALE_Y) +
702 5 * SCALE_Y /* legend */);
703
704 /* write some basic info as a comment, including some help */
705 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
706 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
707 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
708 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
709 "<!-- point your browser to this file. -->\n\n"
710 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", GIT_VERSION);
711
712 /* style sheet */
713 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
714 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
715 " rect.background { fill: rgb(255,255,255); }\n"
716 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
717 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
718 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
719 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
720 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
721 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
722 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
723 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
724 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
725 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
726 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
727 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
728 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
729 "// line.sec1 { }\n"
730 " line.sec5 { stroke-width: 2; }\n"
731 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
732 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
733 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
734 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
735 " text.sec { font-size: 10px; }\n"
736 " ]]>\n </style>\n</defs>\n\n");
737
738 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
739 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
740 if (host)
741 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
742 isempty(host->os_pretty_name) ? "Linux" : host->os_pretty_name,
743 strempty(host->hostname),
744 strempty(host->kernel_name),
745 strempty(host->kernel_release),
746 strempty(host->kernel_version),
747 strempty(host->architecture),
748 strempty(host->virtualization));
749
750 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
751 svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time);
752
753 if (boot->firmware_time > 0) {
754 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
755 svg_text(true, -(double) boot->firmware_time, y, "firmware");
756 y++;
757 }
758 if (boot->loader_time > 0) {
759 svg_bar("loader", -(double) boot->loader_time, 0, y);
760 svg_text(true, -(double) boot->loader_time, y, "loader");
761 y++;
762 }
763 if (boot->kernel_done_time > 0) {
764 svg_bar("kernel", 0, boot->kernel_done_time, y);
765 svg_text(true, 0, y, "kernel");
766 y++;
767 }
768 if (boot->initrd_time > 0) {
769 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
770 if (boot->initrd_security_start_time < boot->initrd_security_finish_time)
771 svg_bar("security", boot->initrd_security_start_time, boot->initrd_security_finish_time, y);
772 if (boot->initrd_generators_start_time < boot->initrd_generators_finish_time)
773 svg_bar("generators", boot->initrd_generators_start_time, boot->initrd_generators_finish_time, y);
774 if (boot->initrd_unitsload_start_time < boot->initrd_unitsload_finish_time)
775 svg_bar("unitsload", boot->initrd_unitsload_start_time, boot->initrd_unitsload_finish_time, y);
776 svg_text(true, boot->initrd_time, y, "initrd");
777 y++;
778 }
779
780 for (u = times; u->has_data; u++) {
781 if (u->activating >= boot->userspace_time)
782 break;
783
784 y += plot_unit_times(u, width, y);
785 }
786
787 svg_bar("active", boot->userspace_time, boot->finish_time, y);
788 if (boot->security_start_time > 0)
789 svg_bar("security", boot->security_start_time, boot->security_finish_time, y);
790 svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
791 svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y);
792 svg_text(true, boot->userspace_time, y, "systemd");
793 y++;
794
795 for (; u->has_data; u++)
796 y += plot_unit_times(u, width, y);
797
798 svg("</g>\n");
799
800 /* Legend */
801 svg("<g transform=\"translate(20,100)\">\n");
802 y++;
803 svg_bar("activating", 0, 300000, y);
804 svg_text(true, 400000, y, "Activating");
805 y++;
806 svg_bar("active", 0, 300000, y);
807 svg_text(true, 400000, y, "Active");
808 y++;
809 svg_bar("deactivating", 0, 300000, y);
810 svg_text(true, 400000, y, "Deactivating");
811 y++;
812 if (boot->security_start_time > 0) {
813 svg_bar("security", 0, 300000, y);
814 svg_text(true, 400000, y, "Setting up security module");
815 y++;
816 }
817 svg_bar("generators", 0, 300000, y);
818 svg_text(true, 400000, y, "Generators");
819 y++;
820 svg_bar("unitsload", 0, 300000, y);
821 svg_text(true, 400000, y, "Loading unit files");
822 y++;
823
824 svg("</g>\n\n");
825
826 svg("</svg>\n");
827
828 return 0;
829 }
830
831 static int list_dependencies_print(
832 const char *name,
833 unsigned level,
834 unsigned branches,
835 bool last,
836 UnitTimes *times,
837 BootTimes *boot) {
838
839 char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
840
841 for (unsigned i = level; i != 0; i--)
842 printf("%s", special_glyph(branches & (1 << (i-1)) ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE));
843
844 printf("%s", special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH));
845
846 if (times) {
847 if (times->time > 0)
848 printf("%s%s @%s +%s%s", ansi_highlight_red(), name,
849 format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC),
850 format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ansi_normal());
851 else if (times->activated > boot->userspace_time)
852 printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
853 else
854 printf("%s", name);
855 } else
856 printf("%s", name);
857 printf("\n");
858
859 return 0;
860 }
861
862 static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
863 _cleanup_free_ char *path = NULL;
864
865 assert(bus);
866 assert(name);
867 assert(deps);
868
869 path = unit_dbus_path_from_name(name);
870 if (!path)
871 return -ENOMEM;
872
873 return bus_get_unit_property_strv(bus, path, "After", deps);
874 }
875
876 static Hashmap *unit_times_hashmap;
877
878 static int list_dependencies_compare(char *const *a, char *const *b) {
879 usec_t usa = 0, usb = 0;
880 UnitTimes *times;
881
882 times = hashmap_get(unit_times_hashmap, *a);
883 if (times)
884 usa = times->activated;
885 times = hashmap_get(unit_times_hashmap, *b);
886 if (times)
887 usb = times->activated;
888
889 return CMP(usb, usa);
890 }
891
892 static bool times_in_range(const UnitTimes *times, const BootTimes *boot) {
893 return times && times->activated > 0 && times->activated <= boot->finish_time;
894 }
895
896 static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level, char ***units, unsigned branches) {
897 _cleanup_strv_free_ char **deps = NULL;
898 char **c;
899 int r;
900 usec_t service_longest = 0;
901 int to_print = 0;
902 UnitTimes *times;
903 BootTimes *boot;
904
905 if (strv_extend(units, name))
906 return log_oom();
907
908 r = list_dependencies_get_dependencies(bus, name, &deps);
909 if (r < 0)
910 return r;
911
912 typesafe_qsort(deps, strv_length(deps), list_dependencies_compare);
913
914 r = acquire_boot_times(bus, &boot);
915 if (r < 0)
916 return r;
917
918 STRV_FOREACH(c, deps) {
919 times = hashmap_get(unit_times_hashmap, *c);
920 if (times_in_range(times, boot) && times->activated >= service_longest)
921 service_longest = times->activated;
922 }
923
924 if (service_longest == 0)
925 return r;
926
927 STRV_FOREACH(c, deps) {
928 times = hashmap_get(unit_times_hashmap, *c);
929 if (times_in_range(times, boot) && service_longest - times->activated <= arg_fuzz)
930 to_print++;
931 }
932
933 if (!to_print)
934 return r;
935
936 STRV_FOREACH(c, deps) {
937 times = hashmap_get(unit_times_hashmap, *c);
938 if (!times_in_range(times, boot) || service_longest - times->activated > arg_fuzz)
939 continue;
940
941 to_print--;
942
943 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
944 if (r < 0)
945 return r;
946
947 if (strv_contains(*units, *c)) {
948 r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
949 true, NULL, boot);
950 if (r < 0)
951 return r;
952 continue;
953 }
954
955 r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (to_print ? 1 : 0));
956 if (r < 0)
957 return r;
958
959 if (to_print == 0)
960 break;
961 }
962 return 0;
963 }
964
965 static int list_dependencies(sd_bus *bus, const char *name) {
966 _cleanup_strv_free_ char **units = NULL;
967 char ts[FORMAT_TIMESPAN_MAX];
968 UnitTimes *times;
969 int r;
970 const char *id;
971 _cleanup_free_ char *path = NULL;
972 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
973 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
974 BootTimes *boot;
975
976 assert(bus);
977
978 path = unit_dbus_path_from_name(name);
979 if (!path)
980 return -ENOMEM;
981
982 r = sd_bus_get_property(
983 bus,
984 "org.freedesktop.systemd1",
985 path,
986 "org.freedesktop.systemd1.Unit",
987 "Id",
988 &error,
989 &reply,
990 "s");
991 if (r < 0)
992 return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r));
993
994 r = sd_bus_message_read(reply, "s", &id);
995 if (r < 0)
996 return bus_log_parse_error(r);
997
998 times = hashmap_get(unit_times_hashmap, id);
999
1000 r = acquire_boot_times(bus, &boot);
1001 if (r < 0)
1002 return r;
1003
1004 if (times) {
1005 if (times->time)
1006 printf("%s%s +%s%s\n", ansi_highlight_red(), id,
1007 format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ansi_normal());
1008 else if (times->activated > boot->userspace_time)
1009 printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
1010 else
1011 printf("%s\n", id);
1012 }
1013
1014 return list_dependencies_one(bus, name, 0, &units, 0);
1015 }
1016
1017 static int analyze_critical_chain(int argc, char *argv[], void *userdata) {
1018 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1019 _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
1020 Hashmap *h;
1021 int n, r;
1022
1023 r = acquire_bus(&bus, NULL);
1024 if (r < 0)
1025 return bus_log_connect_error(r);
1026
1027 n = acquire_time_data(bus, &times);
1028 if (n <= 0)
1029 return n;
1030
1031 h = hashmap_new(&string_hash_ops);
1032 if (!h)
1033 return log_oom();
1034
1035 for (UnitTimes *u = times; u->has_data; u++) {
1036 r = hashmap_put(h, u->name, u);
1037 if (r < 0)
1038 return log_error_errno(r, "Failed to add entry to hashmap: %m");
1039 }
1040 unit_times_hashmap = h;
1041
1042 (void) pager_open(arg_pager_flags);
1043
1044 puts("The time when unit became active or started is printed after the \"@\" character.\n"
1045 "The time the unit took to start is printed after the \"+\" character.\n");
1046
1047 if (argc > 1) {
1048 char **name;
1049 STRV_FOREACH(name, strv_skip(argv, 1))
1050 list_dependencies(bus, *name);
1051 } else
1052 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
1053
1054 h = hashmap_free(h);
1055 return 0;
1056 }
1057
1058 static int analyze_blame(int argc, char *argv[], void *userdata) {
1059 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1060 _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
1061 _cleanup_(table_unrefp) Table *table = NULL;
1062 TableCell *cell;
1063 int n, r;
1064
1065 r = acquire_bus(&bus, NULL);
1066 if (r < 0)
1067 return bus_log_connect_error(r);
1068
1069 n = acquire_time_data(bus, &times);
1070 if (n <= 0)
1071 return n;
1072
1073 table = table_new("time", "unit");
1074 if (!table)
1075 return log_oom();
1076
1077 table_set_header(table, false);
1078
1079 assert_se(cell = table_get_cell(table, 0, 0));
1080 r = table_set_ellipsize_percent(table, cell, 100);
1081 if (r < 0)
1082 return r;
1083
1084 r = table_set_align_percent(table, cell, 100);
1085 if (r < 0)
1086 return r;
1087
1088 assert_se(cell = table_get_cell(table, 0, 1));
1089 r = table_set_ellipsize_percent(table, cell, 100);
1090 if (r < 0)
1091 return r;
1092
1093 r = table_set_sort(table, (size_t) 0);
1094 if (r < 0)
1095 return r;
1096
1097 r = table_set_reverse(table, 0, true);
1098 if (r < 0)
1099 return r;
1100
1101 for (UnitTimes *u = times; u->has_data; u++) {
1102 if (u->time <= 0)
1103 continue;
1104
1105 r = table_add_many(table,
1106 TABLE_TIMESPAN_MSEC, u->time,
1107 TABLE_STRING, u->name);
1108 if (r < 0)
1109 return table_log_add_error(r);
1110 }
1111
1112 (void) pager_open(arg_pager_flags);
1113
1114 return table_print(table, NULL);
1115 }
1116
1117 static int analyze_time(int argc, char *argv[], void *userdata) {
1118 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1119 _cleanup_free_ char *buf = NULL;
1120 int r;
1121
1122 r = acquire_bus(&bus, NULL);
1123 if (r < 0)
1124 return bus_log_connect_error(r);
1125
1126 r = pretty_boot_time(bus, &buf);
1127 if (r < 0)
1128 return r;
1129
1130 puts(buf);
1131 return 0;
1132 }
1133
1134 static int graph_one_property(
1135 sd_bus *bus,
1136 const UnitInfo *u,
1137 const char *prop,
1138 const char *color,
1139 char *patterns[],
1140 char *from_patterns[],
1141 char *to_patterns[]) {
1142
1143 _cleanup_strv_free_ char **units = NULL;
1144 char **unit;
1145 int r;
1146 bool match_patterns;
1147
1148 assert(u);
1149 assert(prop);
1150 assert(color);
1151
1152 match_patterns = strv_fnmatch(patterns, u->id);
1153
1154 if (!strv_isempty(from_patterns) && !match_patterns && !strv_fnmatch(from_patterns, u->id))
1155 return 0;
1156
1157 r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units);
1158 if (r < 0)
1159 return r;
1160
1161 STRV_FOREACH(unit, units) {
1162 bool match_patterns2;
1163
1164 match_patterns2 = strv_fnmatch(patterns, *unit);
1165
1166 if (!strv_isempty(to_patterns) && !match_patterns2 && !strv_fnmatch(to_patterns, *unit))
1167 continue;
1168
1169 if (!strv_isempty(patterns) && !match_patterns && !match_patterns2)
1170 continue;
1171
1172 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color);
1173 }
1174
1175 return 0;
1176 }
1177
1178 static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *from_patterns[], char *to_patterns[]) {
1179 int r;
1180
1181 assert(bus);
1182 assert(u);
1183
1184 if (IN_SET(arg_dot, DEP_ORDER, DEP_ALL)) {
1185 r = graph_one_property(bus, u, "After", "green", patterns, from_patterns, to_patterns);
1186 if (r < 0)
1187 return r;
1188 }
1189
1190 if (IN_SET(arg_dot, DEP_REQUIRE, DEP_ALL)) {
1191 r = graph_one_property(bus, u, "Requires", "black", patterns, from_patterns, to_patterns);
1192 if (r < 0)
1193 return r;
1194 r = graph_one_property(bus, u, "Requisite", "darkblue", patterns, from_patterns, to_patterns);
1195 if (r < 0)
1196 return r;
1197 r = graph_one_property(bus, u, "Wants", "grey66", patterns, from_patterns, to_patterns);
1198 if (r < 0)
1199 return r;
1200 r = graph_one_property(bus, u, "Conflicts", "red", patterns, from_patterns, to_patterns);
1201 if (r < 0)
1202 return r;
1203 }
1204
1205 return 0;
1206 }
1207
1208 static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) {
1209 _cleanup_strv_free_ char **expanded_patterns = NULL;
1210 char **pattern;
1211 int r;
1212
1213 STRV_FOREACH(pattern, patterns) {
1214 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1215 _cleanup_free_ char *unit = NULL, *unit_id = NULL;
1216
1217 if (strv_extend(&expanded_patterns, *pattern) < 0)
1218 return log_oom();
1219
1220 if (string_is_glob(*pattern))
1221 continue;
1222
1223 unit = unit_dbus_path_from_name(*pattern);
1224 if (!unit)
1225 return log_oom();
1226
1227 r = sd_bus_get_property_string(
1228 bus,
1229 "org.freedesktop.systemd1",
1230 unit,
1231 "org.freedesktop.systemd1.Unit",
1232 "Id",
1233 &error,
1234 &unit_id);
1235 if (r < 0)
1236 return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r));
1237
1238 if (!streq(*pattern, unit_id)) {
1239 if (strv_extend(&expanded_patterns, unit_id) < 0)
1240 return log_oom();
1241 }
1242 }
1243
1244 *ret = TAKE_PTR(expanded_patterns); /* do not free */
1245
1246 return 0;
1247 }
1248
1249 static int dot(int argc, char *argv[], void *userdata) {
1250 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
1251 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1252 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1253 _cleanup_strv_free_ char **expanded_patterns = NULL;
1254 _cleanup_strv_free_ char **expanded_from_patterns = NULL;
1255 _cleanup_strv_free_ char **expanded_to_patterns = NULL;
1256 int r;
1257 UnitInfo u;
1258
1259 r = acquire_bus(&bus, NULL);
1260 if (r < 0)
1261 return bus_log_connect_error(r);
1262
1263 r = expand_patterns(bus, strv_skip(argv, 1), &expanded_patterns);
1264 if (r < 0)
1265 return r;
1266
1267 r = expand_patterns(bus, arg_dot_from_patterns, &expanded_from_patterns);
1268 if (r < 0)
1269 return r;
1270
1271 r = expand_patterns(bus, arg_dot_to_patterns, &expanded_to_patterns);
1272 if (r < 0)
1273 return r;
1274
1275 r = bus_call_method(bus, bus_systemd_mgr, "ListUnits", &error, &reply, NULL);
1276 if (r < 0)
1277 log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
1278
1279 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
1280 if (r < 0)
1281 return bus_log_parse_error(r);
1282
1283 printf("digraph systemd {\n");
1284
1285 while ((r = bus_parse_unit_info(reply, &u)) > 0) {
1286
1287 r = graph_one(bus, &u, expanded_patterns, expanded_from_patterns, expanded_to_patterns);
1288 if (r < 0)
1289 return r;
1290 }
1291 if (r < 0)
1292 return bus_log_parse_error(r);
1293
1294 printf("}\n");
1295
1296 log_info(" Color legend: black = Requires\n"
1297 " dark blue = Requisite\n"
1298 " dark grey = Wants\n"
1299 " red = Conflicts\n"
1300 " green = After\n");
1301
1302 if (on_tty())
1303 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1304 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1305
1306 return 0;
1307 }
1308
1309 static int dump_fallback(sd_bus *bus) {
1310 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1311 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
1312 const char *text = NULL;
1313 int r;
1314
1315 assert(bus);
1316
1317 r = bus_call_method(bus, bus_systemd_mgr, "Dump", &error, &reply, NULL);
1318 if (r < 0)
1319 return log_error_errno(r, "Failed to issue method call Dump: %s", bus_error_message(&error, r));
1320
1321 r = sd_bus_message_read(reply, "s", &text);
1322 if (r < 0)
1323 return bus_log_parse_error(r);
1324
1325 fputs(text, stdout);
1326 return 0;
1327 }
1328
1329 static int dump(int argc, char *argv[], void *userdata) {
1330 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1331 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
1332 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1333 int fd = -1;
1334 int r;
1335
1336 r = acquire_bus(&bus, NULL);
1337 if (r < 0)
1338 return bus_log_connect_error(r);
1339
1340 (void) pager_open(arg_pager_flags);
1341
1342 if (!sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD))
1343 return dump_fallback(bus);
1344
1345 r = bus_call_method(bus, bus_systemd_mgr, "DumpByFileDescriptor", &error, &reply, NULL);
1346 if (r < 0) {
1347 /* fall back to Dump if DumpByFileDescriptor is not supported */
1348 if (!IN_SET(r, -EACCES, -EBADR))
1349 return log_error_errno(r, "Failed to issue method call DumpByFileDescriptor: %s",
1350 bus_error_message(&error, r));
1351
1352 return dump_fallback(bus);
1353 }
1354
1355 r = sd_bus_message_read(reply, "h", &fd);
1356 if (r < 0)
1357 return bus_log_parse_error(r);
1358
1359 fflush(stdout);
1360 return copy_bytes(fd, STDOUT_FILENO, UINT64_MAX, 0);
1361 }
1362
1363 static int cat_config(int argc, char *argv[], void *userdata) {
1364 char **arg, **list;
1365 int r;
1366
1367 (void) pager_open(arg_pager_flags);
1368
1369 list = strv_skip(argv, 1);
1370 STRV_FOREACH(arg, list) {
1371 const char *t = NULL;
1372
1373 if (arg != list)
1374 print_separator();
1375
1376 if (path_is_absolute(*arg)) {
1377 const char *dir;
1378
1379 NULSTR_FOREACH(dir, CONF_PATHS_NULSTR("")) {
1380 t = path_startswith(*arg, dir);
1381 if (t)
1382 break;
1383 }
1384
1385 if (!t)
1386 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1387 "Path %s does not start with any known prefix.", *arg);
1388 } else
1389 t = *arg;
1390
1391 r = conf_files_cat(arg_root, t);
1392 if (r < 0)
1393 return r;
1394 }
1395
1396 return 0;
1397 }
1398
1399 static int set_log_level(int argc, char *argv[], void *userdata) {
1400 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1401 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1402 int r;
1403
1404 assert(argc == 2);
1405 assert(argv);
1406
1407 r = acquire_bus(&bus, NULL);
1408 if (r < 0)
1409 return bus_log_connect_error(r);
1410
1411 r = bus_set_property(bus, bus_systemd_mgr, "LogLevel", &error, "s", argv[1]);
1412 if (r < 0)
1413 return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r));
1414
1415 return 0;
1416 }
1417
1418 static int get_log_level(int argc, char *argv[], void *userdata) {
1419 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1420 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1421 _cleanup_free_ char *level = NULL;
1422 int r;
1423
1424 r = acquire_bus(&bus, NULL);
1425 if (r < 0)
1426 return bus_log_connect_error(r);
1427
1428 r = bus_get_property_string(bus, bus_systemd_mgr, "LogLevel", &error, &level);
1429 if (r < 0)
1430 return log_error_errno(r, "Failed to get log level: %s", bus_error_message(&error, r));
1431
1432 puts(level);
1433 return 0;
1434 }
1435
1436 static int get_or_set_log_level(int argc, char *argv[], void *userdata) {
1437 return (argc == 1) ? get_log_level(argc, argv, userdata) : set_log_level(argc, argv, userdata);
1438 }
1439
1440 static int set_log_target(int argc, char *argv[], void *userdata) {
1441 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1442 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1443 int r;
1444
1445 assert(argc == 2);
1446 assert(argv);
1447
1448 r = acquire_bus(&bus, NULL);
1449 if (r < 0)
1450 return bus_log_connect_error(r);
1451
1452 r = bus_set_property(bus, bus_systemd_mgr, "LogTarget", &error, "s", argv[1]);
1453 if (r < 0)
1454 return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r));
1455
1456 return 0;
1457 }
1458
1459 static int get_log_target(int argc, char *argv[], void *userdata) {
1460 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1461 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1462 _cleanup_free_ char *target = NULL;
1463 int r;
1464
1465 r = acquire_bus(&bus, NULL);
1466 if (r < 0)
1467 return bus_log_connect_error(r);
1468
1469 r = bus_get_property_string(bus, bus_systemd_mgr, "LogTarget", &error, &target);
1470 if (r < 0)
1471 return log_error_errno(r, "Failed to get log target: %s", bus_error_message(&error, r));
1472
1473 puts(target);
1474 return 0;
1475 }
1476
1477 static int get_or_set_log_target(int argc, char *argv[], void *userdata) {
1478 return (argc == 1) ? get_log_target(argc, argv, userdata) : set_log_target(argc, argv, userdata);
1479 }
1480
1481 static bool strv_fnmatch_strv_or_empty(char* const* patterns, char **strv, int flags) {
1482 char **s;
1483 STRV_FOREACH(s, strv)
1484 if (strv_fnmatch_or_empty(patterns, *s, flags))
1485 return true;
1486
1487 return false;
1488 }
1489
1490 static int do_unit_files(int argc, char *argv[], void *userdata) {
1491 _cleanup_(lookup_paths_free) LookupPaths lp = {};
1492 _cleanup_hashmap_free_ Hashmap *unit_ids = NULL;
1493 _cleanup_hashmap_free_ Hashmap *unit_names = NULL;
1494 char **patterns = strv_skip(argv, 1);
1495 const char *k, *dst;
1496 char **v;
1497 int r;
1498
1499 r = lookup_paths_init(&lp, arg_scope, 0, NULL);
1500 if (r < 0)
1501 return log_error_errno(r, "lookup_paths_init() failed: %m");
1502
1503 r = unit_file_build_name_map(&lp, NULL, &unit_ids, &unit_names, NULL);
1504 if (r < 0)
1505 return log_error_errno(r, "unit_file_build_name_map() failed: %m");
1506
1507 HASHMAP_FOREACH_KEY(dst, k, unit_ids) {
1508 if (!strv_fnmatch_or_empty(patterns, k, FNM_NOESCAPE) &&
1509 !strv_fnmatch_or_empty(patterns, dst, FNM_NOESCAPE))
1510 continue;
1511
1512 printf("ids: %s → %s\n", k, dst);
1513 }
1514
1515 HASHMAP_FOREACH_KEY(v, k, unit_names) {
1516 if (!strv_fnmatch_or_empty(patterns, k, FNM_NOESCAPE) &&
1517 !strv_fnmatch_strv_or_empty(patterns, v, FNM_NOESCAPE))
1518 continue;
1519
1520 _cleanup_free_ char *j = strv_join(v, ", ");
1521 printf("aliases: %s ← %s\n", k, j);
1522 }
1523
1524 return 0;
1525 }
1526
1527 static int dump_unit_paths(int argc, char *argv[], void *userdata) {
1528 _cleanup_(lookup_paths_free) LookupPaths paths = {};
1529 int r;
1530 char **p;
1531
1532 r = lookup_paths_init(&paths, arg_scope, 0, NULL);
1533 if (r < 0)
1534 return log_error_errno(r, "lookup_paths_init() failed: %m");
1535
1536 STRV_FOREACH(p, paths.search_path)
1537 puts(*p);
1538
1539 return 0;
1540 }
1541
1542 static int dump_exit_status(int argc, char *argv[], void *userdata) {
1543 _cleanup_(table_unrefp) Table *table = NULL;
1544 int r;
1545
1546 table = table_new("name", "status", "class");
1547 if (!table)
1548 return log_oom();
1549
1550 r = table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
1551 if (r < 0)
1552 return log_error_errno(r, "Failed to right-align status: %m");
1553
1554 if (strv_isempty(strv_skip(argv, 1)))
1555 for (size_t i = 0; i < ELEMENTSOF(exit_status_mappings); i++) {
1556 if (!exit_status_mappings[i].name)
1557 continue;
1558
1559 r = table_add_many(table,
1560 TABLE_STRING, exit_status_mappings[i].name,
1561 TABLE_INT, (int) i,
1562 TABLE_STRING, exit_status_class(i));
1563 if (r < 0)
1564 return table_log_add_error(r);
1565 }
1566 else
1567 for (int i = 1; i < argc; i++) {
1568 int status;
1569
1570 status = exit_status_from_string(argv[i]);
1571 if (status < 0)
1572 return log_error_errno(status, "Invalid exit status \"%s\".", argv[i]);
1573
1574 assert(status >= 0 && (size_t) status < ELEMENTSOF(exit_status_mappings));
1575 r = table_add_many(table,
1576 TABLE_STRING, exit_status_mappings[status].name ?: "-",
1577 TABLE_INT, status,
1578 TABLE_STRING, exit_status_class(status) ?: "-");
1579 if (r < 0)
1580 return table_log_add_error(r);
1581 }
1582
1583 (void) pager_open(arg_pager_flags);
1584
1585 return table_print(table, NULL);
1586 }
1587
1588 static int dump_capabilities(int argc, char *argv[], void *userdata) {
1589 _cleanup_(table_unrefp) Table *table = NULL;
1590 unsigned last_cap;
1591 int r;
1592
1593 table = table_new("name", "number");
1594 if (!table)
1595 return log_oom();
1596
1597 (void) table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
1598
1599 /* Determine the maximum of the last cap known by the kernel and by us */
1600 last_cap = MAX((unsigned) CAP_LAST_CAP, cap_last_cap());
1601
1602 if (strv_isempty(strv_skip(argv, 1)))
1603 for (unsigned c = 0; c <= last_cap; c++) {
1604 r = table_add_many(table,
1605 TABLE_STRING, capability_to_name(c) ?: "cap_???",
1606 TABLE_UINT, c);
1607 if (r < 0)
1608 return table_log_add_error(r);
1609 }
1610 else {
1611 for (int i = 1; i < argc; i++) {
1612 int c;
1613
1614 c = capability_from_name(argv[i]);
1615 if (c < 0 || (unsigned) c > last_cap)
1616 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Capability \"%s\" not known.", argv[i]);
1617
1618 r = table_add_many(table,
1619 TABLE_STRING, capability_to_name(c) ?: "cap_???",
1620 TABLE_UINT, (unsigned) c);
1621 if (r < 0)
1622 return table_log_add_error(r);
1623 }
1624
1625 (void) table_set_sort(table, (size_t) 1);
1626 }
1627
1628 (void) pager_open(arg_pager_flags);
1629
1630 return table_print(table, NULL);
1631 }
1632
1633 #if HAVE_SECCOMP
1634
1635 static int load_kernel_syscalls(Set **ret) {
1636 _cleanup_set_free_ Set *syscalls = NULL;
1637 _cleanup_fclose_ FILE *f = NULL;
1638 int r;
1639
1640 /* Let's read the available system calls from the list of available tracing events. Slightly dirty,
1641 * but good enough for analysis purposes. */
1642
1643 f = fopen("/sys/kernel/tracing/available_events", "re");
1644 if (!f) {
1645 /* We tried the non-debugfs mount point and that didn't work. If it wasn't mounted, maybe the
1646 * old debugfs mount point works? */
1647 f = fopen("/sys/kernel/debug/tracing/available_events", "re");
1648 if (!f)
1649 return log_full_errno(IN_SET(errno, EPERM, EACCES, ENOENT) ? LOG_DEBUG : LOG_WARNING, errno,
1650 "Can't read open tracefs' available_events file: %m");
1651 }
1652
1653 for (;;) {
1654 _cleanup_free_ char *line = NULL;
1655 const char *e;
1656
1657 r = read_line(f, LONG_LINE_MAX, &line);
1658 if (r < 0)
1659 return log_error_errno(r, "Failed to read system call list: %m");
1660 if (r == 0)
1661 break;
1662
1663 e = startswith(line, "syscalls:sys_enter_");
1664 if (!e)
1665 continue;
1666
1667 /* These are named differently inside the kernel than their external name for historical
1668 * reasons. Let's hide them here. */
1669 if (STR_IN_SET(e, "newuname", "newfstat", "newstat", "newlstat", "sysctl"))
1670 continue;
1671
1672 r = set_put_strdup(&syscalls, e);
1673 if (r < 0)
1674 return log_error_errno(r, "Failed to add system call to list: %m");
1675 }
1676
1677 *ret = TAKE_PTR(syscalls);
1678 return 0;
1679 }
1680
1681 static void syscall_set_remove(Set *s, const SyscallFilterSet *set) {
1682 const char *syscall;
1683
1684 NULSTR_FOREACH(syscall, set->value) {
1685 if (syscall[0] == '@')
1686 continue;
1687
1688 free(set_remove(s, syscall));
1689 }
1690 }
1691
1692 static void dump_syscall_filter(const SyscallFilterSet *set) {
1693 const char *syscall;
1694
1695 printf("%s%s%s\n"
1696 " # %s\n",
1697 ansi_highlight(),
1698 set->name,
1699 ansi_normal(),
1700 set->help);
1701
1702 NULSTR_FOREACH(syscall, set->value)
1703 printf(" %s%s%s\n", syscall[0] == '@' ? ansi_underline() : "", syscall, ansi_normal());
1704 }
1705
1706 static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
1707 bool first = true;
1708
1709 (void) pager_open(arg_pager_flags);
1710
1711 if (strv_isempty(strv_skip(argv, 1))) {
1712 _cleanup_set_free_ Set *kernel = NULL, *known = NULL;
1713 const char *sys;
1714 int k;
1715
1716 NULSTR_FOREACH(sys, syscall_filter_sets[SYSCALL_FILTER_SET_KNOWN].value)
1717 if (set_put_strdup(&known, sys) < 0)
1718 return log_oom();
1719
1720 k = load_kernel_syscalls(&kernel);
1721
1722 for (int i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) {
1723 const SyscallFilterSet *set = syscall_filter_sets + i;
1724 if (!first)
1725 puts("");
1726
1727 dump_syscall_filter(set);
1728 syscall_set_remove(kernel, set);
1729 if (i != SYSCALL_FILTER_SET_KNOWN)
1730 syscall_set_remove(known, set);
1731 first = false;
1732 }
1733
1734 if (!set_isempty(known)) {
1735 _cleanup_free_ char **l = NULL;
1736 char **syscall;
1737
1738 printf("\n"
1739 "# %sUngrouped System Calls%s (known but not included in any of the groups except @known):\n",
1740 ansi_highlight(), ansi_normal());
1741
1742 l = set_get_strv(known);
1743 if (!l)
1744 return log_oom();
1745
1746 strv_sort(l);
1747
1748 STRV_FOREACH(syscall, l)
1749 printf("# %s\n", *syscall);
1750 }
1751
1752 if (k < 0) {
1753 fputc('\n', stdout);
1754 fflush(stdout);
1755 log_notice_errno(k, "# Not showing unlisted system calls, couldn't retrieve kernel system call list: %m");
1756 } else if (!set_isempty(kernel)) {
1757 _cleanup_free_ char **l = NULL;
1758 char **syscall;
1759
1760 printf("\n"
1761 "# %sUnlisted System Calls%s (supported by the local kernel, but not included in any of the groups listed above):\n",
1762 ansi_highlight(), ansi_normal());
1763
1764 l = set_get_strv(kernel);
1765 if (!l)
1766 return log_oom();
1767
1768 strv_sort(l);
1769
1770 STRV_FOREACH(syscall, l)
1771 printf("# %s\n", *syscall);
1772 }
1773 } else {
1774 char **name;
1775
1776 STRV_FOREACH(name, strv_skip(argv, 1)) {
1777 const SyscallFilterSet *set;
1778
1779 if (!first)
1780 puts("");
1781
1782 set = syscall_filter_set_find(*name);
1783 if (!set) {
1784 /* make sure the error appears below normal output */
1785 fflush(stdout);
1786
1787 return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
1788 "Filter set \"%s\" not found.", *name);
1789 }
1790
1791 dump_syscall_filter(set);
1792 first = false;
1793 }
1794 }
1795
1796 return 0;
1797 }
1798
1799 #else
1800 static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
1801 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not compiled with syscall filters, sorry.");
1802 }
1803 #endif
1804
1805 static void parsing_hint(const char *p, bool calendar, bool timestamp, bool timespan) {
1806 if (calendar && calendar_spec_from_string(p, NULL) >= 0)
1807 log_notice("Hint: this expression is a valid calendar specification. "
1808 "Use 'systemd-analyze calendar \"%s\"' instead?", p);
1809 if (timestamp && parse_timestamp(p, NULL) >= 0)
1810 log_notice("Hint: this expression is a valid timestamp. "
1811 "Use 'systemd-analyze timestamp \"%s\"' instead?", p);
1812 if (timespan && parse_time(p, NULL, USEC_PER_SEC) >= 0)
1813 log_notice("Hint: this expression is a valid timespan. "
1814 "Use 'systemd-analyze timespan \"%s\"' instead?", p);
1815 }
1816
1817 static int dump_timespan(int argc, char *argv[], void *userdata) {
1818 char **input_timespan;
1819
1820 STRV_FOREACH(input_timespan, strv_skip(argv, 1)) {
1821 _cleanup_(table_unrefp) Table *table = NULL;
1822 usec_t output_usecs;
1823 TableCell *cell;
1824 int r;
1825
1826 r = parse_time(*input_timespan, &output_usecs, USEC_PER_SEC);
1827 if (r < 0) {
1828 log_error_errno(r, "Failed to parse time span '%s': %m", *input_timespan);
1829 parsing_hint(*input_timespan, true, true, false);
1830 return r;
1831 }
1832
1833 table = table_new("name", "value");
1834 if (!table)
1835 return log_oom();
1836
1837 table_set_header(table, false);
1838
1839 assert_se(cell = table_get_cell(table, 0, 0));
1840 r = table_set_ellipsize_percent(table, cell, 100);
1841 if (r < 0)
1842 return r;
1843
1844 r = table_set_align_percent(table, cell, 100);
1845 if (r < 0)
1846 return r;
1847
1848 assert_se(cell = table_get_cell(table, 0, 1));
1849 r = table_set_ellipsize_percent(table, cell, 100);
1850 if (r < 0)
1851 return r;
1852
1853 r = table_add_many(table,
1854 TABLE_STRING, "Original:",
1855 TABLE_STRING, *input_timespan);
1856 if (r < 0)
1857 return table_log_add_error(r);
1858
1859 r = table_add_cell_stringf(table, NULL, "%ss:", special_glyph(SPECIAL_GLYPH_MU));
1860 if (r < 0)
1861 return table_log_add_error(r);
1862
1863 r = table_add_many(table,
1864 TABLE_UINT64, output_usecs,
1865 TABLE_STRING, "Human:",
1866 TABLE_TIMESPAN, output_usecs,
1867 TABLE_SET_COLOR, ansi_highlight());
1868 if (r < 0)
1869 return table_log_add_error(r);
1870
1871 r = table_print(table, NULL);
1872 if (r < 0)
1873 return r;
1874
1875 if (input_timespan[1])
1876 putchar('\n');
1877 }
1878
1879 return EXIT_SUCCESS;
1880 }
1881
1882 static int test_timestamp_one(const char *p) {
1883 _cleanup_(table_unrefp) Table *table = NULL;
1884 TableCell *cell;
1885 usec_t usec;
1886 int r;
1887
1888 r = parse_timestamp(p, &usec);
1889 if (r < 0) {
1890 log_error_errno(r, "Failed to parse \"%s\": %m", p);
1891 parsing_hint(p, true, false, true);
1892 return r;
1893 }
1894
1895 table = table_new("name", "value");
1896 if (!table)
1897 return log_oom();
1898
1899 table_set_header(table, false);
1900
1901 assert_se(cell = table_get_cell(table, 0, 0));
1902 r = table_set_ellipsize_percent(table, cell, 100);
1903 if (r < 0)
1904 return r;
1905
1906 r = table_set_align_percent(table, cell, 100);
1907 if (r < 0)
1908 return r;
1909
1910 assert_se(cell = table_get_cell(table, 0, 1));
1911 r = table_set_ellipsize_percent(table, cell, 100);
1912 if (r < 0)
1913 return r;
1914
1915 r = table_add_many(table,
1916 TABLE_STRING, "Original form:",
1917 TABLE_STRING, p,
1918 TABLE_STRING, "Normalized form:",
1919 TABLE_TIMESTAMP, usec,
1920 TABLE_SET_COLOR, ansi_highlight_blue());
1921 if (r < 0)
1922 return table_log_add_error(r);
1923
1924 if (!in_utc_timezone()) {
1925 r = table_add_many(table,
1926 TABLE_STRING, "(in UTC):",
1927 TABLE_TIMESTAMP_UTC, usec);
1928 if (r < 0)
1929 return table_log_add_error(r);
1930 }
1931
1932 r = table_add_cell(table, NULL, TABLE_STRING, "UNIX seconds:");
1933 if (r < 0)
1934 return table_log_add_error(r);
1935
1936 if (usec % USEC_PER_SEC == 0)
1937 r = table_add_cell_stringf(table, NULL, "@%"PRI_USEC,
1938 usec / USEC_PER_SEC);
1939 else
1940 r = table_add_cell_stringf(table, NULL, "@%"PRI_USEC".%06"PRI_USEC"",
1941 usec / USEC_PER_SEC,
1942 usec % USEC_PER_SEC);
1943 if (r < 0)
1944 return r;
1945
1946 r = table_add_many(table,
1947 TABLE_STRING, "From now:",
1948 TABLE_TIMESTAMP_RELATIVE, usec);
1949 if (r < 0)
1950 return table_log_add_error(r);
1951
1952 return table_print(table, NULL);
1953 }
1954
1955 static int test_timestamp(int argc, char *argv[], void *userdata) {
1956 int ret = 0, r;
1957 char **p;
1958
1959 STRV_FOREACH(p, strv_skip(argv, 1)) {
1960 r = test_timestamp_one(*p);
1961 if (ret == 0 && r < 0)
1962 ret = r;
1963
1964 if (*(p + 1))
1965 putchar('\n');
1966 }
1967
1968 return ret;
1969 }
1970
1971 static int test_calendar_one(usec_t n, const char *p) {
1972 _cleanup_(calendar_spec_freep) CalendarSpec *spec = NULL;
1973 _cleanup_(table_unrefp) Table *table = NULL;
1974 _cleanup_free_ char *t = NULL;
1975 TableCell *cell;
1976 int r;
1977
1978 r = calendar_spec_from_string(p, &spec);
1979 if (r < 0) {
1980 log_error_errno(r, "Failed to parse calendar specification '%s': %m", p);
1981 parsing_hint(p, false, true, true);
1982 return r;
1983 }
1984
1985 r = calendar_spec_to_string(spec, &t);
1986 if (r < 0)
1987 return log_error_errno(r, "Failed to format calendar specification '%s': %m", p);
1988
1989 table = table_new("name", "value");
1990 if (!table)
1991 return log_oom();
1992
1993 table_set_header(table, false);
1994
1995 assert_se(cell = table_get_cell(table, 0, 0));
1996 r = table_set_ellipsize_percent(table, cell, 100);
1997 if (r < 0)
1998 return r;
1999
2000 r = table_set_align_percent(table, cell, 100);
2001 if (r < 0)
2002 return r;
2003
2004 assert_se(cell = table_get_cell(table, 0, 1));
2005 r = table_set_ellipsize_percent(table, cell, 100);
2006 if (r < 0)
2007 return r;
2008
2009 if (!streq(t, p)) {
2010 r = table_add_many(table,
2011 TABLE_STRING, "Original form:",
2012 TABLE_STRING, p);
2013 if (r < 0)
2014 return table_log_add_error(r);
2015 }
2016
2017 r = table_add_many(table,
2018 TABLE_STRING, "Normalized form:",
2019 TABLE_STRING, t);
2020 if (r < 0)
2021 return table_log_add_error(r);
2022
2023 for (unsigned i = 0; i < arg_iterations; i++) {
2024 usec_t next;
2025
2026 r = calendar_spec_next_usec(spec, n, &next);
2027 if (r == -ENOENT) {
2028 if (i == 0) {
2029 r = table_add_many(table,
2030 TABLE_STRING, "Next elapse:",
2031 TABLE_STRING, "never",
2032 TABLE_SET_COLOR, ansi_highlight_yellow());
2033 if (r < 0)
2034 return table_log_add_error(r);
2035 }
2036 break;
2037 }
2038 if (r < 0)
2039 return log_error_errno(r, "Failed to determine next elapse for '%s': %m", p);
2040
2041 if (i == 0) {
2042 r = table_add_many(table,
2043 TABLE_STRING, "Next elapse:",
2044 TABLE_TIMESTAMP, next,
2045 TABLE_SET_COLOR, ansi_highlight_blue());
2046 if (r < 0)
2047 return table_log_add_error(r);
2048 } else {
2049 int k = DECIMAL_STR_WIDTH(i + 1);
2050
2051 if (k < 8)
2052 k = 8 - k;
2053 else
2054 k = 0;
2055
2056 r = table_add_cell_stringf(table, NULL, "Iter. #%u:", i+1);
2057 if (r < 0)
2058 return table_log_add_error(r);
2059
2060 r = table_add_many(table,
2061 TABLE_TIMESTAMP, next,
2062 TABLE_SET_COLOR, ansi_highlight_blue());
2063 if (r < 0)
2064 return table_log_add_error(r);
2065 }
2066
2067 if (!in_utc_timezone()) {
2068 r = table_add_many(table,
2069 TABLE_STRING, "(in UTC):",
2070 TABLE_TIMESTAMP_UTC, next);
2071 if (r < 0)
2072 return table_log_add_error(r);
2073 }
2074
2075 r = table_add_many(table,
2076 TABLE_STRING, "From now:",
2077 TABLE_TIMESTAMP_RELATIVE, next);
2078 if (r < 0)
2079 return table_log_add_error(r);
2080
2081 n = next;
2082 }
2083
2084 return table_print(table, NULL);
2085 }
2086
2087 static int test_calendar(int argc, char *argv[], void *userdata) {
2088 int ret = 0, r;
2089 char **p;
2090 usec_t n;
2091
2092 if (arg_base_time != USEC_INFINITY)
2093 n = arg_base_time;
2094 else
2095 n = now(CLOCK_REALTIME); /* We want to use the same "base" for all expressions */
2096
2097 STRV_FOREACH(p, strv_skip(argv, 1)) {
2098 r = test_calendar_one(n, *p);
2099 if (ret == 0 && r < 0)
2100 ret = r;
2101
2102 if (*(p + 1))
2103 putchar('\n');
2104 }
2105
2106 return ret;
2107 }
2108
2109 static int service_watchdogs(int argc, char *argv[], void *userdata) {
2110 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
2111 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
2112 int b, r;
2113
2114 assert(IN_SET(argc, 1, 2));
2115 assert(argv);
2116
2117 r = acquire_bus(&bus, NULL);
2118 if (r < 0)
2119 return bus_log_connect_error(r);
2120
2121 if (argc == 1) {
2122 /* get ServiceWatchdogs */
2123 r = bus_get_property_trivial(bus, bus_systemd_mgr, "ServiceWatchdogs", &error, 'b', &b);
2124 if (r < 0)
2125 return log_error_errno(r, "Failed to get service-watchdog state: %s", bus_error_message(&error, r));
2126
2127 printf("%s\n", yes_no(!!b));
2128
2129 } else {
2130 /* set ServiceWatchdogs */
2131 b = parse_boolean(argv[1]);
2132 if (b < 0)
2133 return log_error_errno(b, "Failed to parse service-watchdogs argument: %m");
2134
2135 r = bus_set_property(bus, bus_systemd_mgr, "ServiceWatchdogs", &error, "b", b);
2136 if (r < 0)
2137 return log_error_errno(r, "Failed to set service-watchdog state: %s", bus_error_message(&error, r));
2138 }
2139
2140 return 0;
2141 }
2142
2143 static int do_condition(int argc, char *argv[], void *userdata) {
2144 return verify_conditions(strv_skip(argv, 1), arg_scope);
2145 }
2146
2147 static int do_verify(int argc, char *argv[], void *userdata) {
2148 return verify_units(strv_skip(argv, 1), arg_scope, arg_man, arg_generators);
2149 }
2150
2151 static int do_security(int argc, char *argv[], void *userdata) {
2152 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
2153 int r;
2154
2155 r = acquire_bus(&bus, NULL);
2156 if (r < 0)
2157 return bus_log_connect_error(r);
2158
2159 (void) pager_open(arg_pager_flags);
2160
2161 return analyze_security(bus, strv_skip(argv, 1), 0);
2162 }
2163
2164 static int help(int argc, char *argv[], void *userdata) {
2165 _cleanup_free_ char *link = NULL, *dot_link = NULL;
2166 int r;
2167
2168 (void) pager_open(arg_pager_flags);
2169
2170 r = terminal_urlify_man("systemd-analyze", "1", &link);
2171 if (r < 0)
2172 return log_oom();
2173
2174 /* Not using terminal_urlify_man() for this, since we don't want the "man page" text suffix in this case. */
2175 r = terminal_urlify("man:dot(1)", "dot(1)", &dot_link);
2176 if (r < 0)
2177 return log_oom();
2178
2179 printf("%s [OPTIONS...] COMMAND ...\n\n"
2180 "%sProfile systemd, show unit dependencies, check unit files.%s\n"
2181 "\nCommands:\n"
2182 " [time] Print time required to boot the machine\n"
2183 " blame Print list of running units ordered by time to init\n"
2184 " critical-chain [UNIT...] Print a tree of the time critical chain of units\n"
2185 " plot Output SVG graphic showing service initialization\n"
2186 " dot [UNIT...] Output dependency graph in %s format\n"
2187 " dump Output state serialization of service manager\n"
2188 " cat-config Show configuration file and drop-ins\n"
2189 " unit-files List files and symlinks for units\n"
2190 " unit-paths List load directories for units\n"
2191 " exit-status [STATUS...] List exit status definitions\n"
2192 " capability [CAP...] List capability definitions\n"
2193 " syscall-filter [NAME...] Print list of syscalls in seccomp filter\n"
2194 " condition CONDITION... Evaluate conditions and asserts\n"
2195 " verify FILE... Check unit files for correctness\n"
2196 " calendar SPEC... Validate repetitive calendar time events\n"
2197 " timestamp TIMESTAMP... Validate a timestamp\n"
2198 " timespan SPAN... Validate a time span\n"
2199 " security [UNIT...] Analyze security of unit\n"
2200 "\nOptions:\n"
2201 " -h --help Show this help\n"
2202 " --version Show package version\n"
2203 " --no-pager Do not pipe output into a pager\n"
2204 " --system Operate on system systemd instance\n"
2205 " --user Operate on user systemd instance\n"
2206 " --global Operate on global user configuration\n"
2207 " -H --host=[USER@]HOST Operate on remote host\n"
2208 " -M --machine=CONTAINER Operate on local container\n"
2209 " --order Show only order in the graph\n"
2210 " --require Show only requirement in the graph\n"
2211 " --from-pattern=GLOB Show only origins in the graph\n"
2212 " --to-pattern=GLOB Show only destinations in the graph\n"
2213 " --fuzz=SECONDS Also print services which finished SECONDS earlier\n"
2214 " than the latest in the branch\n"
2215 " --man[=BOOL] Do [not] check for existence of man pages\n"
2216 " --generators[=BOOL] Do [not] run unit generators (requires privileges)\n"
2217 " --iterations=N Show the specified number of iterations\n"
2218 " --base-time=TIMESTAMP Calculate calendar times relative to specified time\n"
2219 "\nSee the %s for details.\n",
2220 program_invocation_short_name,
2221 ansi_highlight(),
2222 ansi_normal(),
2223 dot_link,
2224 link);
2225
2226 /* When updating this list, including descriptions, apply changes to
2227 * shell-completion/bash/systemd-analyze and shell-completion/zsh/_systemd-analyze too. */
2228
2229 return 0;
2230 }
2231
2232 static int parse_argv(int argc, char *argv[]) {
2233 enum {
2234 ARG_VERSION = 0x100,
2235 ARG_ORDER,
2236 ARG_REQUIRE,
2237 ARG_ROOT,
2238 ARG_SYSTEM,
2239 ARG_USER,
2240 ARG_GLOBAL,
2241 ARG_DOT_FROM_PATTERN,
2242 ARG_DOT_TO_PATTERN,
2243 ARG_FUZZ,
2244 ARG_NO_PAGER,
2245 ARG_MAN,
2246 ARG_GENERATORS,
2247 ARG_ITERATIONS,
2248 ARG_BASE_TIME,
2249 };
2250
2251 static const struct option options[] = {
2252 { "help", no_argument, NULL, 'h' },
2253 { "version", no_argument, NULL, ARG_VERSION },
2254 { "order", no_argument, NULL, ARG_ORDER },
2255 { "require", no_argument, NULL, ARG_REQUIRE },
2256 { "root", required_argument, NULL, ARG_ROOT },
2257 { "system", no_argument, NULL, ARG_SYSTEM },
2258 { "user", no_argument, NULL, ARG_USER },
2259 { "global", no_argument, NULL, ARG_GLOBAL },
2260 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
2261 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
2262 { "fuzz", required_argument, NULL, ARG_FUZZ },
2263 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
2264 { "man", optional_argument, NULL, ARG_MAN },
2265 { "generators", optional_argument, NULL, ARG_GENERATORS },
2266 { "host", required_argument, NULL, 'H' },
2267 { "machine", required_argument, NULL, 'M' },
2268 { "iterations", required_argument, NULL, ARG_ITERATIONS },
2269 { "base-time", required_argument, NULL, ARG_BASE_TIME },
2270 {}
2271 };
2272
2273 int r, c;
2274
2275 assert(argc >= 0);
2276 assert(argv);
2277
2278 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
2279 switch (c) {
2280
2281 case 'h':
2282 return help(0, NULL, NULL);
2283
2284 case ARG_VERSION:
2285 return version();
2286
2287 case ARG_ROOT:
2288 arg_root = optarg;
2289 break;
2290
2291 case ARG_SYSTEM:
2292 arg_scope = UNIT_FILE_SYSTEM;
2293 break;
2294
2295 case ARG_USER:
2296 arg_scope = UNIT_FILE_USER;
2297 break;
2298
2299 case ARG_GLOBAL:
2300 arg_scope = UNIT_FILE_GLOBAL;
2301 break;
2302
2303 case ARG_ORDER:
2304 arg_dot = DEP_ORDER;
2305 break;
2306
2307 case ARG_REQUIRE:
2308 arg_dot = DEP_REQUIRE;
2309 break;
2310
2311 case ARG_DOT_FROM_PATTERN:
2312 if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
2313 return log_oom();
2314
2315 break;
2316
2317 case ARG_DOT_TO_PATTERN:
2318 if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
2319 return log_oom();
2320
2321 break;
2322
2323 case ARG_FUZZ:
2324 r = parse_sec(optarg, &arg_fuzz);
2325 if (r < 0)
2326 return r;
2327 break;
2328
2329 case ARG_NO_PAGER:
2330 arg_pager_flags |= PAGER_DISABLE;
2331 break;
2332
2333 case 'H':
2334 arg_transport = BUS_TRANSPORT_REMOTE;
2335 arg_host = optarg;
2336 break;
2337
2338 case 'M':
2339 arg_transport = BUS_TRANSPORT_MACHINE;
2340 arg_host = optarg;
2341 break;
2342
2343 case ARG_MAN:
2344 r = parse_boolean_argument("--man", optarg, &arg_man);
2345 if (r < 0)
2346 return r;
2347 break;
2348
2349 case ARG_GENERATORS:
2350 r = parse_boolean_argument("--generators", optarg, &arg_generators);
2351 if (r < 0)
2352 return r;
2353 break;
2354
2355 case ARG_ITERATIONS:
2356 r = safe_atou(optarg, &arg_iterations);
2357 if (r < 0)
2358 return log_error_errno(r, "Failed to parse iterations: %s", optarg);
2359
2360 break;
2361
2362 case ARG_BASE_TIME:
2363 r = parse_timestamp(optarg, &arg_base_time);
2364 if (r < 0)
2365 return log_error_errno(r, "Failed to parse --base-time= parameter: %s", optarg);
2366
2367 break;
2368
2369 case '?':
2370 return -EINVAL;
2371
2372 default:
2373 assert_not_reached("Unhandled option code.");
2374 }
2375
2376 if (arg_scope == UNIT_FILE_GLOBAL &&
2377 !STR_IN_SET(argv[optind] ?: "time", "dot", "unit-paths", "verify"))
2378 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2379 "Option --global only makes sense with verbs dot, unit-paths, verify.");
2380
2381 if (streq_ptr(argv[optind], "cat-config") && arg_scope == UNIT_FILE_USER)
2382 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2383 "Option --user is not supported for cat-config right now.");
2384
2385 if (arg_root && !streq_ptr(argv[optind], "cat-config"))
2386 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2387 "Option --root is only supported for cat-config right now.");
2388
2389 return 1; /* work to do */
2390 }
2391
2392 static int run(int argc, char *argv[]) {
2393
2394 static const Verb verbs[] = {
2395 { "help", VERB_ANY, VERB_ANY, 0, help },
2396 { "time", VERB_ANY, 1, VERB_DEFAULT, analyze_time },
2397 { "blame", VERB_ANY, 1, 0, analyze_blame },
2398 { "critical-chain", VERB_ANY, VERB_ANY, 0, analyze_critical_chain },
2399 { "plot", VERB_ANY, 1, 0, analyze_plot },
2400 { "dot", VERB_ANY, VERB_ANY, 0, dot },
2401 /* The following seven verbs are deprecated */
2402 { "log-level", VERB_ANY, 2, 0, get_or_set_log_level },
2403 { "log-target", VERB_ANY, 2, 0, get_or_set_log_target },
2404 { "set-log-level", 2, 2, 0, set_log_level },
2405 { "get-log-level", VERB_ANY, 1, 0, get_log_level },
2406 { "set-log-target", 2, 2, 0, set_log_target },
2407 { "get-log-target", VERB_ANY, 1, 0, get_log_target },
2408 { "service-watchdogs", VERB_ANY, 2, 0, service_watchdogs },
2409 { "dump", VERB_ANY, 1, 0, dump },
2410 { "cat-config", 2, VERB_ANY, 0, cat_config },
2411 { "unit-files", VERB_ANY, VERB_ANY, 0, do_unit_files },
2412 { "unit-paths", 1, 1, 0, dump_unit_paths },
2413 { "exit-status", VERB_ANY, VERB_ANY, 0, dump_exit_status },
2414 { "syscall-filter", VERB_ANY, VERB_ANY, 0, dump_syscall_filters },
2415 { "capability", VERB_ANY, VERB_ANY, 0, dump_capabilities },
2416 { "condition", 2, VERB_ANY, 0, do_condition },
2417 { "verify", 2, VERB_ANY, 0, do_verify },
2418 { "calendar", 2, VERB_ANY, 0, test_calendar },
2419 { "timestamp", 2, VERB_ANY, 0, test_timestamp },
2420 { "timespan", 2, VERB_ANY, 0, dump_timespan },
2421 { "security", VERB_ANY, VERB_ANY, 0, do_security },
2422 {}
2423 };
2424
2425 int r;
2426
2427 setlocale(LC_ALL, "");
2428 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
2429
2430 log_setup();
2431
2432 r = parse_argv(argc, argv);
2433 if (r <= 0)
2434 return r;
2435
2436 return dispatch_verb(argc, argv, verbs, NULL);
2437 }
2438
2439 DEFINE_MAIN_FUNCTION(run);