]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/analyze/analyze.c
analyze: split out "capability" verb
[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.h"
16 #include "analyze-calendar.h"
17 #include "analyze-capability.h"
18 #include "analyze-condition.h"
19 #include "analyze-dot.h"
20 #include "analyze-dump.h"
21 #include "analyze-elf.h"
22 #include "analyze-exit-status.h"
23 #include "analyze-filesystems.h"
24 #include "analyze-security.h"
25 #include "analyze-service-watchdogs.h"
26 #include "analyze-syscall-filter.h"
27 #include "analyze-timespan.h"
28 #include "analyze-timestamp.h"
29 #include "analyze-verify.h"
30 #include "bus-error.h"
31 #include "bus-locator.h"
32 #include "bus-map-properties.h"
33 #include "bus-unit-util.h"
34 #include "calendarspec.h"
35 #include "cap-list.h"
36 #include "capability-util.h"
37 #include "conf-files.h"
38 #include "copy.h"
39 #include "def.h"
40 #include "exit-status.h"
41 #include "extract-word.h"
42 #include "fd-util.h"
43 #include "fileio.h"
44 #include "filesystems.h"
45 #include "format-table.h"
46 #include "glob-util.h"
47 #include "hashmap.h"
48 #include "locale-util.h"
49 #include "log.h"
50 #include "main-func.h"
51 #include "mount-util.h"
52 #include "nulstr-util.h"
53 #include "pager.h"
54 #include "parse-argument.h"
55 #include "parse-util.h"
56 #include "path-util.h"
57 #include "pretty-print.h"
58 #include "rm-rf.h"
59 #if HAVE_SECCOMP
60 # include "seccomp-util.h"
61 #endif
62 #include "sort-util.h"
63 #include "special.h"
64 #include "stat-util.h"
65 #include "string-table.h"
66 #include "strv.h"
67 #include "strxcpyx.h"
68 #include "terminal-util.h"
69 #include "time-util.h"
70 #include "tmpfile-util.h"
71 #include "unit-name.h"
72 #include "util.h"
73 #include "verb-log-control.h"
74 #include "verbs.h"
75 #include "version.h"
76
77 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
78 #define SCALE_Y (20.0)
79
80 #define svg(...) printf(__VA_ARGS__)
81
82 #define svg_bar(class, x1, x2, y) \
83 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
84 (class), \
85 SCALE_X * (x1), SCALE_Y * (y), \
86 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
87
88 #define svg_text(b, x, y, format, ...) \
89 do { \
90 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
91 svg(format, ## __VA_ARGS__); \
92 svg("</text>\n"); \
93 } while (false)
94
95 DotMode arg_dot = DEP_ALL;
96 char **arg_dot_from_patterns = NULL, **arg_dot_to_patterns = NULL;
97 static usec_t arg_fuzz = 0;
98 PagerFlags arg_pager_flags = 0;
99 BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
100 static const char *arg_host = NULL;
101 static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
102 static RecursiveErrors arg_recursive_errors = RECURSIVE_ERRORS_YES;
103 static bool arg_man = true;
104 static bool arg_generators = false;
105 static char *arg_root = NULL;
106 static char *arg_image = NULL;
107 static char *arg_security_policy = NULL;
108 static bool arg_offline = false;
109 static unsigned arg_threshold = 100;
110 unsigned arg_iterations = 1;
111 usec_t arg_base_time = USEC_INFINITY;
112 static char *arg_unit = NULL;
113 static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
114 bool arg_quiet = false;
115 static char *arg_profile = NULL;
116
117 STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
118 STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
119 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
120 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
121 STATIC_DESTRUCTOR_REGISTER(arg_security_policy, freep);
122 STATIC_DESTRUCTOR_REGISTER(arg_unit, freep);
123 STATIC_DESTRUCTOR_REGISTER(arg_profile, freep);
124
125 typedef struct BootTimes {
126 usec_t firmware_time;
127 usec_t loader_time;
128 usec_t kernel_time;
129 usec_t kernel_done_time;
130 usec_t initrd_time;
131 usec_t userspace_time;
132 usec_t finish_time;
133 usec_t security_start_time;
134 usec_t security_finish_time;
135 usec_t generators_start_time;
136 usec_t generators_finish_time;
137 usec_t unitsload_start_time;
138 usec_t unitsload_finish_time;
139 usec_t initrd_security_start_time;
140 usec_t initrd_security_finish_time;
141 usec_t initrd_generators_start_time;
142 usec_t initrd_generators_finish_time;
143 usec_t initrd_unitsload_start_time;
144 usec_t initrd_unitsload_finish_time;
145
146 /*
147 * If we're analyzing the user instance, all timestamps will be offset
148 * by its own start-up timestamp, which may be arbitrarily big.
149 * With "plot", this causes arbitrarily wide output SVG files which almost
150 * completely consist of empty space. Thus we cancel out this offset.
151 *
152 * This offset is subtracted from times above by acquire_boot_times(),
153 * but it still needs to be subtracted from unit-specific timestamps
154 * (so it is stored here for reference).
155 */
156 usec_t reverse_offset;
157 } BootTimes;
158
159 typedef struct UnitTimes {
160 bool has_data;
161 char *name;
162 usec_t activating;
163 usec_t activated;
164 usec_t deactivated;
165 usec_t deactivating;
166 usec_t time;
167 } UnitTimes;
168
169 typedef struct HostInfo {
170 char *hostname;
171 char *kernel_name;
172 char *kernel_release;
173 char *kernel_version;
174 char *os_pretty_name;
175 char *virtualization;
176 char *architecture;
177 } HostInfo;
178
179 int acquire_bus(sd_bus **bus, bool *use_full_bus) {
180 bool user = arg_scope != UNIT_FILE_SYSTEM;
181 int r;
182
183 if (use_full_bus && *use_full_bus) {
184 r = bus_connect_transport(arg_transport, arg_host, user, bus);
185 if (IN_SET(r, 0, -EHOSTDOWN))
186 return r;
187
188 *use_full_bus = false;
189 }
190
191 return bus_connect_transport_systemd(arg_transport, arg_host, user, bus);
192 }
193
194 static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
195 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
196 int r;
197
198 assert(bus);
199 assert(path);
200 assert(interface);
201 assert(property);
202 assert(val);
203
204 r = sd_bus_get_property_trivial(
205 bus,
206 "org.freedesktop.systemd1",
207 path,
208 interface,
209 property,
210 &error,
211 't', val);
212
213 if (r < 0)
214 return log_error_errno(r, "Failed to parse reply: %s", bus_error_message(&error, r));
215
216 return 0;
217 }
218
219 int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) {
220 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
221 int r;
222
223 assert(bus);
224 assert(path);
225 assert(property);
226 assert(strv);
227
228 r = sd_bus_get_property_strv(
229 bus,
230 "org.freedesktop.systemd1",
231 path,
232 "org.freedesktop.systemd1.Unit",
233 property,
234 &error,
235 strv);
236 if (r < 0)
237 return log_error_errno(r, "Failed to get unit property %s: %s", property, bus_error_message(&error, r));
238
239 return 0;
240 }
241
242 static int compare_unit_start(const UnitTimes *a, const UnitTimes *b) {
243 return CMP(a->activating, b->activating);
244 }
245
246 static int process_aliases(char *argv[], char *tempdir, char ***ret) {
247 _cleanup_strv_free_ char **filenames = NULL;
248 char **filename;
249 int r;
250
251 assert(argv);
252 assert(tempdir);
253 assert(ret);
254
255 STRV_FOREACH(filename, strv_skip(argv, 1)) {
256 _cleanup_free_ char *src = NULL, *dst = NULL, *base = NULL;
257 const char *parse_arg;
258
259 parse_arg = *filename;
260 r = extract_first_word(&parse_arg, &src, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_ESCAPE);
261 if (r < 0)
262 return r;
263
264 if (!parse_arg) {
265 r = strv_consume(&filenames, TAKE_PTR(src));
266 if (r < 0)
267 return r;
268
269 continue;
270 }
271
272 r = path_extract_filename(parse_arg, &base);
273 if (r < 0)
274 return r;
275
276 dst = path_join(tempdir, base);
277 if (!dst)
278 return -ENOMEM;
279
280 r = copy_file(src, dst, 0, 0644, 0, 0, COPY_REFLINK);
281 if (r < 0)
282 return r;
283
284 r = strv_consume(&filenames, TAKE_PTR(dst));
285 if (r < 0)
286 return r;
287 }
288
289 *ret = TAKE_PTR(filenames);
290 return 0;
291 }
292
293 static UnitTimes* unit_times_free_array(UnitTimes *t) {
294 for (UnitTimes *p = t; p && p->has_data; p++)
295 free(p->name);
296 return mfree(t);
297 }
298 DEFINE_TRIVIAL_CLEANUP_FUNC(UnitTimes*, unit_times_free_array);
299
300 static void subtract_timestamp(usec_t *a, usec_t b) {
301 assert(a);
302
303 if (*a > 0) {
304 assert(*a >= b);
305 *a -= b;
306 }
307 }
308
309 static int acquire_boot_times(sd_bus *bus, BootTimes **bt) {
310 static const struct bus_properties_map property_map[] = {
311 { "FirmwareTimestampMonotonic", "t", NULL, offsetof(BootTimes, firmware_time) },
312 { "LoaderTimestampMonotonic", "t", NULL, offsetof(BootTimes, loader_time) },
313 { "KernelTimestamp", "t", NULL, offsetof(BootTimes, kernel_time) },
314 { "InitRDTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_time) },
315 { "UserspaceTimestampMonotonic", "t", NULL, offsetof(BootTimes, userspace_time) },
316 { "FinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, finish_time) },
317 { "SecurityStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, security_start_time) },
318 { "SecurityFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, security_finish_time) },
319 { "GeneratorsStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, generators_start_time) },
320 { "GeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, generators_finish_time) },
321 { "UnitsLoadStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, unitsload_start_time) },
322 { "UnitsLoadFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, unitsload_finish_time) },
323 { "InitRDSecurityStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_security_start_time) },
324 { "InitRDSecurityFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_security_finish_time) },
325 { "InitRDGeneratorsStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_generators_start_time) },
326 { "InitRDGeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_generators_finish_time) },
327 { "InitRDUnitsLoadStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_unitsload_start_time) },
328 { "InitRDUnitsLoadFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_unitsload_finish_time) },
329 {},
330 };
331 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
332 static BootTimes times;
333 static bool cached = false;
334 int r;
335
336 if (cached)
337 goto finish;
338
339 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
340
341 r = bus_map_all_properties(
342 bus,
343 "org.freedesktop.systemd1",
344 "/org/freedesktop/systemd1",
345 property_map,
346 BUS_MAP_STRDUP,
347 &error,
348 NULL,
349 &times);
350 if (r < 0)
351 return log_error_errno(r, "Failed to get timestamp properties: %s", bus_error_message(&error, r));
352
353 if (times.finish_time <= 0)
354 return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS),
355 "Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64").\n"
356 "Please try again later.\n"
357 "Hint: Use 'systemctl%s list-jobs' to see active jobs",
358 times.finish_time,
359 arg_scope == UNIT_FILE_SYSTEM ? "" : " --user");
360
361 if (arg_scope == UNIT_FILE_SYSTEM && times.security_start_time > 0) {
362 /* security_start_time is set when systemd is not running under container environment. */
363 if (times.initrd_time > 0)
364 times.kernel_done_time = times.initrd_time;
365 else
366 times.kernel_done_time = times.userspace_time;
367 } else {
368 /*
369 * User-instance-specific or container-system-specific timestamps processing
370 * (see comment to reverse_offset in BootTimes).
371 */
372 times.reverse_offset = times.userspace_time;
373
374 times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time =
375 times.userspace_time = times.security_start_time = times.security_finish_time = 0;
376
377 subtract_timestamp(&times.finish_time, times.reverse_offset);
378
379 subtract_timestamp(&times.generators_start_time, times.reverse_offset);
380 subtract_timestamp(&times.generators_finish_time, times.reverse_offset);
381
382 subtract_timestamp(&times.unitsload_start_time, times.reverse_offset);
383 subtract_timestamp(&times.unitsload_finish_time, times.reverse_offset);
384 }
385
386 cached = true;
387
388 finish:
389 *bt = &times;
390 return 0;
391 }
392
393 static HostInfo* free_host_info(HostInfo *hi) {
394 if (!hi)
395 return NULL;
396
397 free(hi->hostname);
398 free(hi->kernel_name);
399 free(hi->kernel_release);
400 free(hi->kernel_version);
401 free(hi->os_pretty_name);
402 free(hi->virtualization);
403 free(hi->architecture);
404 return mfree(hi);
405 }
406
407 DEFINE_TRIVIAL_CLEANUP_FUNC(HostInfo *, free_host_info);
408
409 static int acquire_time_data(sd_bus *bus, UnitTimes **out) {
410 static const struct bus_properties_map property_map[] = {
411 { "InactiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activating) },
412 { "ActiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activated) },
413 { "ActiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, deactivating) },
414 { "InactiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, deactivated) },
415 {},
416 };
417 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
418 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
419 _cleanup_(unit_times_free_arrayp) UnitTimes *unit_times = NULL;
420 BootTimes *boot_times = NULL;
421 size_t c = 0;
422 UnitInfo u;
423 int r;
424
425 r = acquire_boot_times(bus, &boot_times);
426 if (r < 0)
427 return r;
428
429 r = bus_call_method(bus, bus_systemd_mgr, "ListUnits", &error, &reply, NULL);
430 if (r < 0)
431 return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
432
433 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
434 if (r < 0)
435 return bus_log_parse_error(r);
436
437 while ((r = bus_parse_unit_info(reply, &u)) > 0) {
438 UnitTimes *t;
439
440 if (!GREEDY_REALLOC(unit_times, c + 2))
441 return log_oom();
442
443 unit_times[c + 1].has_data = false;
444 t = &unit_times[c];
445 t->name = NULL;
446
447 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
448
449 r = bus_map_all_properties(
450 bus,
451 "org.freedesktop.systemd1",
452 u.unit_path,
453 property_map,
454 BUS_MAP_STRDUP,
455 &error,
456 NULL,
457 t);
458 if (r < 0)
459 return log_error_errno(r, "Failed to get timestamp properties of unit %s: %s",
460 u.id, bus_error_message(&error, r));
461
462 subtract_timestamp(&t->activating, boot_times->reverse_offset);
463 subtract_timestamp(&t->activated, boot_times->reverse_offset);
464 subtract_timestamp(&t->deactivating, boot_times->reverse_offset);
465 subtract_timestamp(&t->deactivated, boot_times->reverse_offset);
466
467 if (t->activated >= t->activating)
468 t->time = t->activated - t->activating;
469 else if (t->deactivated >= t->activating)
470 t->time = t->deactivated - t->activating;
471 else
472 t->time = 0;
473
474 if (t->activating == 0)
475 continue;
476
477 t->name = strdup(u.id);
478 if (!t->name)
479 return log_oom();
480
481 t->has_data = true;
482 c++;
483 }
484 if (r < 0)
485 return bus_log_parse_error(r);
486
487 *out = TAKE_PTR(unit_times);
488 return c;
489 }
490
491 static int acquire_host_info(sd_bus *bus, HostInfo **hi) {
492 static const struct bus_properties_map hostname_map[] = {
493 { "Hostname", "s", NULL, offsetof(HostInfo, hostname) },
494 { "KernelName", "s", NULL, offsetof(HostInfo, kernel_name) },
495 { "KernelRelease", "s", NULL, offsetof(HostInfo, kernel_release) },
496 { "KernelVersion", "s", NULL, offsetof(HostInfo, kernel_version) },
497 { "OperatingSystemPrettyName", "s", NULL, offsetof(HostInfo, os_pretty_name) },
498 {}
499 };
500
501 static const struct bus_properties_map manager_map[] = {
502 { "Virtualization", "s", NULL, offsetof(HostInfo, virtualization) },
503 { "Architecture", "s", NULL, offsetof(HostInfo, architecture) },
504 {}
505 };
506
507 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
508 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *system_bus = NULL;
509 _cleanup_(free_host_infop) HostInfo *host = NULL;
510 int r;
511
512 host = new0(HostInfo, 1);
513 if (!host)
514 return log_oom();
515
516 if (arg_scope != UNIT_FILE_SYSTEM) {
517 r = bus_connect_transport(arg_transport, arg_host, false, &system_bus);
518 if (r < 0) {
519 log_debug_errno(r, "Failed to connect to system bus, ignoring: %m");
520 goto manager;
521 }
522 }
523
524 r = bus_map_all_properties(
525 system_bus ?: bus,
526 "org.freedesktop.hostname1",
527 "/org/freedesktop/hostname1",
528 hostname_map,
529 BUS_MAP_STRDUP,
530 &error,
531 NULL,
532 host);
533 if (r < 0) {
534 log_debug_errno(r, "Failed to get host information from systemd-hostnamed, ignoring: %s",
535 bus_error_message(&error, r));
536 sd_bus_error_free(&error);
537 }
538
539 manager:
540 r = bus_map_all_properties(
541 bus,
542 "org.freedesktop.systemd1",
543 "/org/freedesktop/systemd1",
544 manager_map,
545 BUS_MAP_STRDUP,
546 &error,
547 NULL,
548 host);
549 if (r < 0)
550 return log_error_errno(r, "Failed to get host information from systemd: %s",
551 bus_error_message(&error, r));
552
553 *hi = TAKE_PTR(host);
554 return 0;
555 }
556
557 static int pretty_boot_time(sd_bus *bus, char **_buf) {
558 BootTimes *t;
559 static char buf[4096];
560 size_t size;
561 char *ptr;
562 int r;
563 usec_t activated_time = USEC_INFINITY;
564 _cleanup_free_ char *path = NULL, *unit_id = NULL;
565 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
566
567 r = acquire_boot_times(bus, &t);
568 if (r < 0)
569 return r;
570
571 path = unit_dbus_path_from_name(SPECIAL_DEFAULT_TARGET);
572 if (!path)
573 return log_oom();
574
575 r = sd_bus_get_property_string(
576 bus,
577 "org.freedesktop.systemd1",
578 path,
579 "org.freedesktop.systemd1.Unit",
580 "Id",
581 &error,
582 &unit_id);
583 if (r < 0) {
584 log_error_errno(r, "default.target doesn't seem to exist: %s", bus_error_message(&error, r));
585 unit_id = NULL;
586 }
587
588 r = bus_get_uint64_property(bus, path,
589 "org.freedesktop.systemd1.Unit",
590 "ActiveEnterTimestampMonotonic",
591 &activated_time);
592 if (r < 0) {
593 log_info_errno(r, "Could not get time to reach default.target, ignoring: %m");
594 activated_time = USEC_INFINITY;
595 }
596
597 ptr = buf;
598 size = sizeof(buf);
599
600 size = strpcpyf(&ptr, size, "Startup finished in ");
601 if (t->firmware_time > 0)
602 size = strpcpyf(&ptr, size, "%s (firmware) + ", FORMAT_TIMESPAN(t->firmware_time - t->loader_time, USEC_PER_MSEC));
603 if (t->loader_time > 0)
604 size = strpcpyf(&ptr, size, "%s (loader) + ", FORMAT_TIMESPAN(t->loader_time, USEC_PER_MSEC));
605 if (t->kernel_done_time > 0)
606 size = strpcpyf(&ptr, size, "%s (kernel) + ", FORMAT_TIMESPAN(t->kernel_done_time, USEC_PER_MSEC));
607 if (t->initrd_time > 0)
608 size = strpcpyf(&ptr, size, "%s (initrd) + ", FORMAT_TIMESPAN(t->userspace_time - t->initrd_time, USEC_PER_MSEC));
609
610 size = strpcpyf(&ptr, size, "%s (userspace) ", FORMAT_TIMESPAN(t->finish_time - t->userspace_time, USEC_PER_MSEC));
611 if (t->kernel_done_time > 0)
612 strpcpyf(&ptr, size, "= %s ", FORMAT_TIMESPAN(t->firmware_time + t->finish_time, USEC_PER_MSEC));
613
614 if (unit_id && timestamp_is_set(activated_time)) {
615 usec_t base = t->userspace_time > 0 ? t->userspace_time : t->reverse_offset;
616
617 size = strpcpyf(&ptr, size, "\n%s reached after %s in userspace", unit_id,
618 FORMAT_TIMESPAN(activated_time - base, USEC_PER_MSEC));
619 } else if (unit_id && activated_time == 0)
620 size = strpcpyf(&ptr, size, "\n%s was never reached", unit_id);
621 else if (unit_id && activated_time == USEC_INFINITY)
622 size = strpcpyf(&ptr, size, "\nCould not get time to reach %s.", unit_id);
623 else if (!unit_id)
624 size = strpcpyf(&ptr, size, "\ncould not find default.target");
625
626 ptr = strdup(buf);
627 if (!ptr)
628 return log_oom();
629
630 *_buf = ptr;
631 return 0;
632 }
633
634 static void svg_graph_box(double height, double begin, double end) {
635 /* outside box, fill */
636 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
637 SCALE_X * (end - begin),
638 SCALE_Y * height);
639
640 for (long long i = ((long long) (begin / 100000)) * 100000; i <= end; i += 100000) {
641 /* lines for each second */
642 if (i % 5000000 == 0)
643 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
644 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
645 SCALE_X * i,
646 SCALE_X * i,
647 SCALE_Y * height,
648 SCALE_X * i,
649 -5.0,
650 0.000001 * i);
651 else if (i % 1000000 == 0)
652 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
653 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
654 SCALE_X * i,
655 SCALE_X * i,
656 SCALE_Y * height,
657 SCALE_X * i,
658 -5.0,
659 0.000001 * i);
660 else
661 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
662 SCALE_X * i,
663 SCALE_X * i,
664 SCALE_Y * height);
665 }
666 }
667
668 static int plot_unit_times(UnitTimes *u, double width, int y) {
669 bool b;
670
671 if (!u->name)
672 return 0;
673
674 svg_bar("activating", u->activating, u->activated, y);
675 svg_bar("active", u->activated, u->deactivating, y);
676 svg_bar("deactivating", u->deactivating, u->deactivated, y);
677
678 /* place the text on the left if we have passed the half of the svg width */
679 b = u->activating * SCALE_X < width / 2;
680 if (u->time)
681 svg_text(b, u->activating, y, "%s (%s)",
682 u->name, FORMAT_TIMESPAN(u->time, USEC_PER_MSEC));
683 else
684 svg_text(b, u->activating, y, "%s", u->name);
685
686 return 1;
687 }
688
689 static int analyze_plot(int argc, char *argv[], void *userdata) {
690 _cleanup_(free_host_infop) HostInfo *host = NULL;
691 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
692 _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
693 _cleanup_free_ char *pretty_times = NULL;
694 bool use_full_bus = arg_scope == UNIT_FILE_SYSTEM;
695 BootTimes *boot;
696 UnitTimes *u;
697 int n, m = 1, y = 0, r;
698 double width;
699
700 r = acquire_bus(&bus, &use_full_bus);
701 if (r < 0)
702 return bus_log_connect_error(r, arg_transport);
703
704 n = acquire_boot_times(bus, &boot);
705 if (n < 0)
706 return n;
707
708 n = pretty_boot_time(bus, &pretty_times);
709 if (n < 0)
710 return n;
711
712 if (use_full_bus || arg_scope != UNIT_FILE_SYSTEM) {
713 n = acquire_host_info(bus, &host);
714 if (n < 0)
715 return n;
716 }
717
718 n = acquire_time_data(bus, &times);
719 if (n <= 0)
720 return n;
721
722 typesafe_qsort(times, n, compare_unit_start);
723
724 width = SCALE_X * (boot->firmware_time + boot->finish_time);
725 if (width < 800.0)
726 width = 800.0;
727
728 if (boot->firmware_time > boot->loader_time)
729 m++;
730 if (boot->loader_time > 0) {
731 m++;
732 if (width < 1000.0)
733 width = 1000.0;
734 }
735 if (boot->initrd_time > 0)
736 m++;
737 if (boot->kernel_done_time > 0)
738 m++;
739
740 for (u = times; u->has_data; u++) {
741 double text_start, text_width;
742
743 if (u->activating > boot->finish_time) {
744 u->name = mfree(u->name);
745 continue;
746 }
747
748 /* If the text cannot fit on the left side then
749 * increase the svg width so it fits on the right.
750 * TODO: calculate the text width more accurately */
751 text_width = 8.0 * strlen(u->name);
752 text_start = (boot->firmware_time + u->activating) * SCALE_X;
753 if (text_width > text_start && text_width + text_start > width)
754 width = text_width + text_start;
755
756 if (u->deactivated > u->activating &&
757 u->deactivated <= boot->finish_time &&
758 u->activated == 0 && u->deactivating == 0)
759 u->activated = u->deactivating = u->deactivated;
760 if (u->activated < u->activating || u->activated > boot->finish_time)
761 u->activated = boot->finish_time;
762 if (u->deactivating < u->activated || u->deactivating > boot->finish_time)
763 u->deactivating = boot->finish_time;
764 if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
765 u->deactivated = boot->finish_time;
766 m++;
767 }
768
769 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
770 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
771 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
772
773 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
774 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
775 80.0 + width, 150.0 + (m * SCALE_Y) +
776 5 * SCALE_Y /* legend */);
777
778 /* write some basic info as a comment, including some help */
779 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
780 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
781 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
782 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
783 "<!-- point your browser to this file. -->\n\n"
784 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", GIT_VERSION);
785
786 /* style sheet */
787 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
788 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
789 " rect.background { fill: rgb(255,255,255); }\n"
790 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
791 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
792 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
793 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
794 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
795 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
796 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
797 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
798 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
799 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
800 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
801 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
802 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
803 "// line.sec1 { }\n"
804 " line.sec5 { stroke-width: 2; }\n"
805 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
806 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
807 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
808 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
809 " text.sec { font-size: 10px; }\n"
810 " ]]>\n </style>\n</defs>\n\n");
811
812 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
813 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
814 if (host)
815 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
816 isempty(host->os_pretty_name) ? "Linux" : host->os_pretty_name,
817 strempty(host->hostname),
818 strempty(host->kernel_name),
819 strempty(host->kernel_release),
820 strempty(host->kernel_version),
821 strempty(host->architecture),
822 strempty(host->virtualization));
823
824 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
825 svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time);
826
827 if (boot->firmware_time > 0) {
828 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
829 svg_text(true, -(double) boot->firmware_time, y, "firmware");
830 y++;
831 }
832 if (boot->loader_time > 0) {
833 svg_bar("loader", -(double) boot->loader_time, 0, y);
834 svg_text(true, -(double) boot->loader_time, y, "loader");
835 y++;
836 }
837 if (boot->kernel_done_time > 0) {
838 svg_bar("kernel", 0, boot->kernel_done_time, y);
839 svg_text(true, 0, y, "kernel");
840 y++;
841 }
842 if (boot->initrd_time > 0) {
843 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
844 if (boot->initrd_security_start_time < boot->initrd_security_finish_time)
845 svg_bar("security", boot->initrd_security_start_time, boot->initrd_security_finish_time, y);
846 if (boot->initrd_generators_start_time < boot->initrd_generators_finish_time)
847 svg_bar("generators", boot->initrd_generators_start_time, boot->initrd_generators_finish_time, y);
848 if (boot->initrd_unitsload_start_time < boot->initrd_unitsload_finish_time)
849 svg_bar("unitsload", boot->initrd_unitsload_start_time, boot->initrd_unitsload_finish_time, y);
850 svg_text(true, boot->initrd_time, y, "initrd");
851 y++;
852 }
853
854 for (u = times; u->has_data; u++) {
855 if (u->activating >= boot->userspace_time)
856 break;
857
858 y += plot_unit_times(u, width, y);
859 }
860
861 svg_bar("active", boot->userspace_time, boot->finish_time, y);
862 if (boot->security_start_time > 0)
863 svg_bar("security", boot->security_start_time, boot->security_finish_time, y);
864 svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
865 svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y);
866 svg_text(true, boot->userspace_time, y, "systemd");
867 y++;
868
869 for (; u->has_data; u++)
870 y += plot_unit_times(u, width, y);
871
872 svg("</g>\n");
873
874 /* Legend */
875 svg("<g transform=\"translate(20,100)\">\n");
876 y++;
877 svg_bar("activating", 0, 300000, y);
878 svg_text(true, 400000, y, "Activating");
879 y++;
880 svg_bar("active", 0, 300000, y);
881 svg_text(true, 400000, y, "Active");
882 y++;
883 svg_bar("deactivating", 0, 300000, y);
884 svg_text(true, 400000, y, "Deactivating");
885 y++;
886 if (boot->security_start_time > 0) {
887 svg_bar("security", 0, 300000, y);
888 svg_text(true, 400000, y, "Setting up security module");
889 y++;
890 }
891 svg_bar("generators", 0, 300000, y);
892 svg_text(true, 400000, y, "Generators");
893 y++;
894 svg_bar("unitsload", 0, 300000, y);
895 svg_text(true, 400000, y, "Loading unit files");
896 y++;
897
898 svg("</g>\n\n");
899
900 svg("</svg>\n");
901
902 return 0;
903 }
904
905 static int list_dependencies_print(
906 const char *name,
907 unsigned level,
908 unsigned branches,
909 bool last,
910 UnitTimes *times,
911 BootTimes *boot) {
912
913 for (unsigned i = level; i != 0; i--)
914 printf("%s", special_glyph(branches & (1 << (i-1)) ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE));
915
916 printf("%s", special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH));
917
918 if (times) {
919 if (times->time > 0)
920 printf("%s%s @%s +%s%s", ansi_highlight_red(), name,
921 FORMAT_TIMESPAN(times->activating - boot->userspace_time, USEC_PER_MSEC),
922 FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal());
923 else if (times->activated > boot->userspace_time)
924 printf("%s @%s", name, FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC));
925 else
926 printf("%s", name);
927 } else
928 printf("%s", name);
929 printf("\n");
930
931 return 0;
932 }
933
934 static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
935 _cleanup_free_ char *path = NULL;
936
937 assert(bus);
938 assert(name);
939 assert(deps);
940
941 path = unit_dbus_path_from_name(name);
942 if (!path)
943 return -ENOMEM;
944
945 return bus_get_unit_property_strv(bus, path, "After", deps);
946 }
947
948 static Hashmap *unit_times_hashmap;
949
950 static int list_dependencies_compare(char *const *a, char *const *b) {
951 usec_t usa = 0, usb = 0;
952 UnitTimes *times;
953
954 times = hashmap_get(unit_times_hashmap, *a);
955 if (times)
956 usa = times->activated;
957 times = hashmap_get(unit_times_hashmap, *b);
958 if (times)
959 usb = times->activated;
960
961 return CMP(usb, usa);
962 }
963
964 static bool times_in_range(const UnitTimes *times, const BootTimes *boot) {
965 return times && times->activated > 0 && times->activated <= boot->finish_time;
966 }
967
968 static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level, char ***units, unsigned branches) {
969 _cleanup_strv_free_ char **deps = NULL;
970 char **c;
971 int r;
972 usec_t service_longest = 0;
973 int to_print = 0;
974 UnitTimes *times;
975 BootTimes *boot;
976
977 if (strv_extend(units, name))
978 return log_oom();
979
980 r = list_dependencies_get_dependencies(bus, name, &deps);
981 if (r < 0)
982 return r;
983
984 typesafe_qsort(deps, strv_length(deps), list_dependencies_compare);
985
986 r = acquire_boot_times(bus, &boot);
987 if (r < 0)
988 return r;
989
990 STRV_FOREACH(c, deps) {
991 times = hashmap_get(unit_times_hashmap, *c); /* lgtm [cpp/inconsistent-null-check] */
992 if (times_in_range(times, boot) && times->activated >= service_longest)
993 service_longest = times->activated;
994 }
995
996 if (service_longest == 0)
997 return r;
998
999 STRV_FOREACH(c, deps) {
1000 times = hashmap_get(unit_times_hashmap, *c); /* lgtm [cpp/inconsistent-null-check] */
1001 if (times_in_range(times, boot) && service_longest - times->activated <= arg_fuzz)
1002 to_print++;
1003 }
1004
1005 if (!to_print)
1006 return r;
1007
1008 STRV_FOREACH(c, deps) {
1009 times = hashmap_get(unit_times_hashmap, *c); /* lgtm [cpp/inconsistent-null-check] */
1010 if (!times_in_range(times, boot) || service_longest - times->activated > arg_fuzz)
1011 continue;
1012
1013 to_print--;
1014
1015 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
1016 if (r < 0)
1017 return r;
1018
1019 if (strv_contains(*units, *c)) {
1020 r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
1021 true, NULL, boot);
1022 if (r < 0)
1023 return r;
1024 continue;
1025 }
1026
1027 r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (to_print ? 1 : 0));
1028 if (r < 0)
1029 return r;
1030
1031 if (to_print == 0)
1032 break;
1033 }
1034 return 0;
1035 }
1036
1037 static int list_dependencies(sd_bus *bus, const char *name) {
1038 _cleanup_strv_free_ char **units = NULL;
1039 UnitTimes *times;
1040 int r;
1041 const char *id;
1042 _cleanup_free_ char *path = NULL;
1043 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
1044 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1045 BootTimes *boot;
1046
1047 assert(bus);
1048
1049 path = unit_dbus_path_from_name(name);
1050 if (!path)
1051 return -ENOMEM;
1052
1053 r = sd_bus_get_property(
1054 bus,
1055 "org.freedesktop.systemd1",
1056 path,
1057 "org.freedesktop.systemd1.Unit",
1058 "Id",
1059 &error,
1060 &reply,
1061 "s");
1062 if (r < 0)
1063 return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r));
1064
1065 r = sd_bus_message_read(reply, "s", &id);
1066 if (r < 0)
1067 return bus_log_parse_error(r);
1068
1069 times = hashmap_get(unit_times_hashmap, id);
1070
1071 r = acquire_boot_times(bus, &boot);
1072 if (r < 0)
1073 return r;
1074
1075 if (times) {
1076 if (times->time)
1077 printf("%s%s +%s%s\n", ansi_highlight_red(), id,
1078 FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal());
1079 else if (times->activated > boot->userspace_time)
1080 printf("%s @%s\n", id,
1081 FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC));
1082 else
1083 printf("%s\n", id);
1084 }
1085
1086 return list_dependencies_one(bus, name, 0, &units, 0);
1087 }
1088
1089 static int analyze_critical_chain(int argc, char *argv[], void *userdata) {
1090 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1091 _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
1092 Hashmap *h;
1093 int n, r;
1094
1095 r = acquire_bus(&bus, NULL);
1096 if (r < 0)
1097 return bus_log_connect_error(r, arg_transport);
1098
1099 n = acquire_time_data(bus, &times);
1100 if (n <= 0)
1101 return n;
1102
1103 h = hashmap_new(&string_hash_ops);
1104 if (!h)
1105 return log_oom();
1106
1107 for (UnitTimes *u = times; u->has_data; u++) {
1108 r = hashmap_put(h, u->name, u);
1109 if (r < 0)
1110 return log_error_errno(r, "Failed to add entry to hashmap: %m");
1111 }
1112 unit_times_hashmap = h;
1113
1114 pager_open(arg_pager_flags);
1115
1116 puts("The time when unit became active or started is printed after the \"@\" character.\n"
1117 "The time the unit took to start is printed after the \"+\" character.\n");
1118
1119 if (argc > 1) {
1120 char **name;
1121 STRV_FOREACH(name, strv_skip(argv, 1))
1122 list_dependencies(bus, *name);
1123 } else
1124 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
1125
1126 h = hashmap_free(h);
1127 return 0;
1128 }
1129
1130 static int analyze_blame(int argc, char *argv[], void *userdata) {
1131 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1132 _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
1133 _cleanup_(table_unrefp) Table *table = NULL;
1134 TableCell *cell;
1135 int n, r;
1136
1137 r = acquire_bus(&bus, NULL);
1138 if (r < 0)
1139 return bus_log_connect_error(r, arg_transport);
1140
1141 n = acquire_time_data(bus, &times);
1142 if (n <= 0)
1143 return n;
1144
1145 table = table_new("time", "unit");
1146 if (!table)
1147 return log_oom();
1148
1149 table_set_header(table, false);
1150
1151 assert_se(cell = table_get_cell(table, 0, 0));
1152 r = table_set_ellipsize_percent(table, cell, 100);
1153 if (r < 0)
1154 return r;
1155
1156 r = table_set_align_percent(table, cell, 100);
1157 if (r < 0)
1158 return r;
1159
1160 assert_se(cell = table_get_cell(table, 0, 1));
1161 r = table_set_ellipsize_percent(table, cell, 100);
1162 if (r < 0)
1163 return r;
1164
1165 r = table_set_sort(table, (size_t) 0);
1166 if (r < 0)
1167 return r;
1168
1169 r = table_set_reverse(table, 0, true);
1170 if (r < 0)
1171 return r;
1172
1173 for (UnitTimes *u = times; u->has_data; u++) {
1174 if (u->time <= 0)
1175 continue;
1176
1177 r = table_add_many(table,
1178 TABLE_TIMESPAN_MSEC, u->time,
1179 TABLE_STRING, u->name);
1180 if (r < 0)
1181 return table_log_add_error(r);
1182 }
1183
1184 pager_open(arg_pager_flags);
1185
1186 return table_print(table, NULL);
1187 }
1188
1189 static int analyze_time(int argc, char *argv[], void *userdata) {
1190 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1191 _cleanup_free_ char *buf = NULL;
1192 int r;
1193
1194 r = acquire_bus(&bus, NULL);
1195 if (r < 0)
1196 return bus_log_connect_error(r, arg_transport);
1197
1198 r = pretty_boot_time(bus, &buf);
1199 if (r < 0)
1200 return r;
1201
1202 puts(buf);
1203 return 0;
1204 }
1205
1206 static int cat_config(int argc, char *argv[], void *userdata) {
1207 char **arg, **list;
1208 int r;
1209
1210 pager_open(arg_pager_flags);
1211
1212 list = strv_skip(argv, 1);
1213 STRV_FOREACH(arg, list) {
1214 const char *t = NULL;
1215
1216 if (arg != list)
1217 print_separator();
1218
1219 if (path_is_absolute(*arg)) {
1220 const char *dir;
1221
1222 NULSTR_FOREACH(dir, CONF_PATHS_NULSTR("")) {
1223 t = path_startswith(*arg, dir);
1224 if (t)
1225 break;
1226 }
1227
1228 if (!t)
1229 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1230 "Path %s does not start with any known prefix.", *arg);
1231 } else
1232 t = *arg;
1233
1234 r = conf_files_cat(arg_root, t);
1235 if (r < 0)
1236 return r;
1237 }
1238
1239 return 0;
1240 }
1241
1242 static int verb_log_control(int argc, char *argv[], void *userdata) {
1243 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1244 int r;
1245
1246 assert(IN_SET(argc, 1, 2));
1247
1248 r = acquire_bus(&bus, NULL);
1249 if (r < 0)
1250 return bus_log_connect_error(r, arg_transport);
1251
1252 return verb_log_control_common(bus, "org.freedesktop.systemd1", argv[0], argc == 2 ? argv[1] : NULL);
1253 }
1254
1255 static bool strv_fnmatch_strv_or_empty(char* const* patterns, char **strv, int flags) {
1256 char **s;
1257 STRV_FOREACH(s, strv)
1258 if (strv_fnmatch_or_empty(patterns, *s, flags))
1259 return true;
1260
1261 return false;
1262 }
1263
1264 static int do_unit_files(int argc, char *argv[], void *userdata) {
1265 _cleanup_(lookup_paths_free) LookupPaths lp = {};
1266 _cleanup_hashmap_free_ Hashmap *unit_ids = NULL;
1267 _cleanup_hashmap_free_ Hashmap *unit_names = NULL;
1268 char **patterns = strv_skip(argv, 1);
1269 const char *k, *dst;
1270 char **v;
1271 int r;
1272
1273 r = lookup_paths_init(&lp, arg_scope, 0, NULL);
1274 if (r < 0)
1275 return log_error_errno(r, "lookup_paths_init() failed: %m");
1276
1277 r = unit_file_build_name_map(&lp, NULL, &unit_ids, &unit_names, NULL);
1278 if (r < 0)
1279 return log_error_errno(r, "unit_file_build_name_map() failed: %m");
1280
1281 HASHMAP_FOREACH_KEY(dst, k, unit_ids) {
1282 if (!strv_fnmatch_or_empty(patterns, k, FNM_NOESCAPE) &&
1283 !strv_fnmatch_or_empty(patterns, dst, FNM_NOESCAPE))
1284 continue;
1285
1286 printf("ids: %s → %s\n", k, dst);
1287 }
1288
1289 HASHMAP_FOREACH_KEY(v, k, unit_names) {
1290 if (!strv_fnmatch_or_empty(patterns, k, FNM_NOESCAPE) &&
1291 !strv_fnmatch_strv_or_empty(patterns, v, FNM_NOESCAPE))
1292 continue;
1293
1294 _cleanup_free_ char *j = strv_join(v, ", ");
1295 printf("aliases: %s ← %s\n", k, j);
1296 }
1297
1298 return 0;
1299 }
1300
1301 static int dump_unit_paths(int argc, char *argv[], void *userdata) {
1302 _cleanup_(lookup_paths_free) LookupPaths paths = {};
1303 int r;
1304 char **p;
1305
1306 r = lookup_paths_init(&paths, arg_scope, 0, NULL);
1307 if (r < 0)
1308 return log_error_errno(r, "lookup_paths_init() failed: %m");
1309
1310 STRV_FOREACH(p, paths.search_path)
1311 puts(*p);
1312
1313 return 0;
1314 }
1315
1316 void time_parsing_hint(const char *p, bool calendar, bool timestamp, bool timespan) {
1317 if (calendar && calendar_spec_from_string(p, NULL) >= 0)
1318 log_notice("Hint: this expression is a valid calendar specification. "
1319 "Use 'systemd-analyze calendar \"%s\"' instead?", p);
1320 if (timestamp && parse_timestamp(p, NULL) >= 0)
1321 log_notice("Hint: this expression is a valid timestamp. "
1322 "Use 'systemd-analyze timestamp \"%s\"' instead?", p);
1323 if (timespan && parse_time(p, NULL, USEC_PER_SEC) >= 0)
1324 log_notice("Hint: this expression is a valid timespan. "
1325 "Use 'systemd-analyze timespan \"%s\"' instead?", p);
1326 }
1327
1328 static int do_condition(int argc, char *argv[], void *userdata) {
1329 return verify_conditions(strv_skip(argv, 1), arg_scope, arg_unit, arg_root);
1330 }
1331
1332 static int do_verify(int argc, char *argv[], void *userdata) {
1333 _cleanup_strv_free_ char **filenames = NULL;
1334 _cleanup_(rm_rf_physical_and_freep) char *tempdir = NULL;
1335 int r;
1336
1337 r = mkdtemp_malloc("/tmp/systemd-analyze-XXXXXX", &tempdir);
1338 if (r < 0)
1339 return log_error_errno(r, "Failed to setup working directory: %m");
1340
1341 r = process_aliases(argv, tempdir, &filenames);
1342 if (r < 0)
1343 return log_error_errno(r, "Couldn't process aliases: %m");
1344
1345 return verify_units(filenames, arg_scope, arg_man, arg_generators, arg_recursive_errors, arg_root);
1346 }
1347
1348 static int do_security(int argc, char *argv[], void *userdata) {
1349 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
1350 _cleanup_(json_variant_unrefp) JsonVariant *policy = NULL;
1351 int r;
1352 unsigned line, column;
1353
1354 if (!arg_offline) {
1355 r = acquire_bus(&bus, NULL);
1356 if (r < 0)
1357 return bus_log_connect_error(r, arg_transport);
1358 }
1359
1360 pager_open(arg_pager_flags);
1361
1362 if (arg_security_policy) {
1363 r = json_parse_file(/*f=*/ NULL, arg_security_policy, /*flags=*/ 0, &policy, &line, &column);
1364 if (r < 0)
1365 return log_error_errno(r, "Failed to parse '%s' at %u:%u: %m", arg_security_policy, line, column);
1366 } else {
1367 _cleanup_fclose_ FILE *f = NULL;
1368 _cleanup_free_ char *pp = NULL;
1369
1370 r = search_and_fopen_nulstr("systemd-analyze-security.policy", "re", /*root=*/ NULL, CONF_PATHS_NULSTR("systemd"), &f, &pp);
1371 if (r < 0 && r != -ENOENT)
1372 return r;
1373
1374 if (f) {
1375 r = json_parse_file(f, pp, /*flags=*/ 0, &policy, &line, &column);
1376 if (r < 0)
1377 return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON policy: %m", pp, line, column);
1378 }
1379 }
1380
1381 return analyze_security(bus,
1382 strv_skip(argv, 1),
1383 policy,
1384 arg_scope,
1385 arg_man,
1386 arg_generators,
1387 arg_offline,
1388 arg_threshold,
1389 arg_root,
1390 arg_profile,
1391 arg_json_format_flags,
1392 arg_pager_flags,
1393 /*flags=*/ 0);
1394 }
1395
1396 static int do_elf_inspection(int argc, char *argv[], void *userdata) {
1397 pager_open(arg_pager_flags);
1398
1399 return analyze_elf(strv_skip(argv, 1), arg_json_format_flags);
1400 }
1401
1402 static int help(int argc, char *argv[], void *userdata) {
1403 _cleanup_free_ char *link = NULL, *dot_link = NULL;
1404 int r;
1405
1406 pager_open(arg_pager_flags);
1407
1408 r = terminal_urlify_man("systemd-analyze", "1", &link);
1409 if (r < 0)
1410 return log_oom();
1411
1412 /* Not using terminal_urlify_man() for this, since we don't want the "man page" text suffix in this case. */
1413 r = terminal_urlify("man:dot(1)", "dot(1)", &dot_link);
1414 if (r < 0)
1415 return log_oom();
1416
1417 printf("%s [OPTIONS...] COMMAND ...\n\n"
1418 "%sProfile systemd, show unit dependencies, check unit files.%s\n"
1419 "\nCommands:\n"
1420 " [time] Print time required to boot the machine\n"
1421 " blame Print list of running units ordered by\n"
1422 " time to init\n"
1423 " critical-chain [UNIT...] Print a tree of the time critical chain\n"
1424 " of units\n"
1425 " plot Output SVG graphic showing service\n"
1426 " initialization\n"
1427 " dot [UNIT...] Output dependency graph in %s format\n"
1428 " dump Output state serialization of service\n"
1429 " manager\n"
1430 " cat-config Show configuration file and drop-ins\n"
1431 " unit-files List files and symlinks for units\n"
1432 " unit-paths List load directories for units\n"
1433 " exit-status [STATUS...] List exit status definitions\n"
1434 " capability [CAP...] List capability definitions\n"
1435 " syscall-filter [NAME...] List syscalls in seccomp filters\n"
1436 " filesystems [NAME...] List known filesystems\n"
1437 " condition CONDITION... Evaluate conditions and asserts\n"
1438 " verify FILE... Check unit files for correctness\n"
1439 " calendar SPEC... Validate repetitive calendar time\n"
1440 " events\n"
1441 " timestamp TIMESTAMP... Validate a timestamp\n"
1442 " timespan SPAN... Validate a time span\n"
1443 " security [UNIT...] Analyze security of unit\n"
1444 " inspect-elf FILE... Parse and print ELF package metadata\n"
1445 "\nOptions:\n"
1446 " --recursive-errors=MODE Control which units are verified\n"
1447 " --offline=BOOL Perform a security review on unit file(s)\n"
1448 " --threshold=N Exit with a non-zero status when overall\n"
1449 " exposure level is over threshold value\n"
1450 " --security-policy=PATH Use custom JSON security policy instead\n"
1451 " of built-in one\n"
1452 " --json=pretty|short|off Generate JSON output of the security\n"
1453 " analysis table\n"
1454 " --no-pager Do not pipe output into a pager\n"
1455 " --system Operate on system systemd instance\n"
1456 " --user Operate on user systemd instance\n"
1457 " --global Operate on global user configuration\n"
1458 " -H --host=[USER@]HOST Operate on remote host\n"
1459 " -M --machine=CONTAINER Operate on local container\n"
1460 " --order Show only order in the graph\n"
1461 " --require Show only requirement in the graph\n"
1462 " --from-pattern=GLOB Show only origins in the graph\n"
1463 " --to-pattern=GLOB Show only destinations in the graph\n"
1464 " --fuzz=SECONDS Also print services which finished SECONDS\n"
1465 " earlier than the latest in the branch\n"
1466 " --man[=BOOL] Do [not] check for existence of man pages\n"
1467 " --generators[=BOOL] Do [not] run unit generators\n"
1468 " (requires privileges)\n"
1469 " --iterations=N Show the specified number of iterations\n"
1470 " --base-time=TIMESTAMP Calculate calendar times relative to\n"
1471 " specified time\n"
1472 " --profile=name|PATH Include the specified profile in the\n"
1473 " security review of the unit(s)\n"
1474 " -h --help Show this help\n"
1475 " --version Show package version\n"
1476 " -q --quiet Do not emit hints\n"
1477 "\nSee the %s for details.\n",
1478 program_invocation_short_name,
1479 ansi_highlight(),
1480 ansi_normal(),
1481 dot_link,
1482 link);
1483
1484 /* When updating this list, including descriptions, apply changes to
1485 * shell-completion/bash/systemd-analyze and shell-completion/zsh/_systemd-analyze too. */
1486
1487 return 0;
1488 }
1489
1490 static int parse_argv(int argc, char *argv[]) {
1491 enum {
1492 ARG_VERSION = 0x100,
1493 ARG_ORDER,
1494 ARG_REQUIRE,
1495 ARG_ROOT,
1496 ARG_IMAGE,
1497 ARG_SYSTEM,
1498 ARG_USER,
1499 ARG_GLOBAL,
1500 ARG_DOT_FROM_PATTERN,
1501 ARG_DOT_TO_PATTERN,
1502 ARG_FUZZ,
1503 ARG_NO_PAGER,
1504 ARG_MAN,
1505 ARG_GENERATORS,
1506 ARG_ITERATIONS,
1507 ARG_BASE_TIME,
1508 ARG_RECURSIVE_ERRORS,
1509 ARG_OFFLINE,
1510 ARG_THRESHOLD,
1511 ARG_SECURITY_POLICY,
1512 ARG_JSON,
1513 ARG_PROFILE,
1514 };
1515
1516 static const struct option options[] = {
1517 { "help", no_argument, NULL, 'h' },
1518 { "version", no_argument, NULL, ARG_VERSION },
1519 { "quiet", no_argument, NULL, 'q' },
1520 { "order", no_argument, NULL, ARG_ORDER },
1521 { "require", no_argument, NULL, ARG_REQUIRE },
1522 { "root", required_argument, NULL, ARG_ROOT },
1523 { "image", required_argument, NULL, ARG_IMAGE },
1524 { "recursive-errors", required_argument, NULL, ARG_RECURSIVE_ERRORS },
1525 { "offline", required_argument, NULL, ARG_OFFLINE },
1526 { "threshold", required_argument, NULL, ARG_THRESHOLD },
1527 { "security-policy", required_argument, NULL, ARG_SECURITY_POLICY },
1528 { "system", no_argument, NULL, ARG_SYSTEM },
1529 { "user", no_argument, NULL, ARG_USER },
1530 { "global", no_argument, NULL, ARG_GLOBAL },
1531 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
1532 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
1533 { "fuzz", required_argument, NULL, ARG_FUZZ },
1534 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1535 { "man", optional_argument, NULL, ARG_MAN },
1536 { "generators", optional_argument, NULL, ARG_GENERATORS },
1537 { "host", required_argument, NULL, 'H' },
1538 { "machine", required_argument, NULL, 'M' },
1539 { "iterations", required_argument, NULL, ARG_ITERATIONS },
1540 { "base-time", required_argument, NULL, ARG_BASE_TIME },
1541 { "unit", required_argument, NULL, 'U' },
1542 { "json", required_argument, NULL, ARG_JSON },
1543 { "profile", required_argument, NULL, ARG_PROFILE },
1544 {}
1545 };
1546
1547 int r, c;
1548
1549 assert(argc >= 0);
1550 assert(argv);
1551
1552 while ((c = getopt_long(argc, argv, "hH:M:U:", options, NULL)) >= 0)
1553 switch (c) {
1554
1555 case 'h':
1556 return help(0, NULL, NULL);
1557
1558 case ARG_VERSION:
1559 return version();
1560
1561 case 'q':
1562 arg_quiet = true;
1563 break;
1564
1565 case ARG_RECURSIVE_ERRORS:
1566 if (streq(optarg, "help")) {
1567 DUMP_STRING_TABLE(recursive_errors, RecursiveErrors, _RECURSIVE_ERRORS_MAX);
1568 return 0;
1569 }
1570 r = recursive_errors_from_string(optarg);
1571 if (r < 0)
1572 return log_error_errno(r, "Unknown mode passed to --recursive-errors='%s'.", optarg);
1573
1574 arg_recursive_errors = r;
1575 break;
1576
1577 case ARG_ROOT:
1578 r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root);
1579 if (r < 0)
1580 return r;
1581 break;
1582
1583 case ARG_IMAGE:
1584 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
1585 if (r < 0)
1586 return r;
1587 break;
1588
1589 case ARG_SYSTEM:
1590 arg_scope = UNIT_FILE_SYSTEM;
1591 break;
1592
1593 case ARG_USER:
1594 arg_scope = UNIT_FILE_USER;
1595 break;
1596
1597 case ARG_GLOBAL:
1598 arg_scope = UNIT_FILE_GLOBAL;
1599 break;
1600
1601 case ARG_ORDER:
1602 arg_dot = DEP_ORDER;
1603 break;
1604
1605 case ARG_REQUIRE:
1606 arg_dot = DEP_REQUIRE;
1607 break;
1608
1609 case ARG_DOT_FROM_PATTERN:
1610 if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
1611 return log_oom();
1612
1613 break;
1614
1615 case ARG_DOT_TO_PATTERN:
1616 if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
1617 return log_oom();
1618
1619 break;
1620
1621 case ARG_FUZZ:
1622 r = parse_sec(optarg, &arg_fuzz);
1623 if (r < 0)
1624 return r;
1625 break;
1626
1627 case ARG_NO_PAGER:
1628 arg_pager_flags |= PAGER_DISABLE;
1629 break;
1630
1631 case 'H':
1632 arg_transport = BUS_TRANSPORT_REMOTE;
1633 arg_host = optarg;
1634 break;
1635
1636 case 'M':
1637 arg_transport = BUS_TRANSPORT_MACHINE;
1638 arg_host = optarg;
1639 break;
1640
1641 case ARG_MAN:
1642 r = parse_boolean_argument("--man", optarg, &arg_man);
1643 if (r < 0)
1644 return r;
1645 break;
1646
1647 case ARG_GENERATORS:
1648 r = parse_boolean_argument("--generators", optarg, &arg_generators);
1649 if (r < 0)
1650 return r;
1651 break;
1652
1653 case ARG_OFFLINE:
1654 r = parse_boolean_argument("--offline", optarg, &arg_offline);
1655 if (r < 0)
1656 return r;
1657 break;
1658
1659 case ARG_THRESHOLD:
1660 r = safe_atou(optarg, &arg_threshold);
1661 if (r < 0 || arg_threshold > 100)
1662 return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse threshold: %s", optarg);
1663
1664 break;
1665
1666 case ARG_SECURITY_POLICY:
1667 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_security_policy);
1668 if (r < 0)
1669 return r;
1670 break;
1671
1672 case ARG_JSON:
1673 r = parse_json_argument(optarg, &arg_json_format_flags);
1674 if (r <= 0)
1675 return r;
1676 break;
1677
1678 case ARG_ITERATIONS:
1679 r = safe_atou(optarg, &arg_iterations);
1680 if (r < 0)
1681 return log_error_errno(r, "Failed to parse iterations: %s", optarg);
1682
1683 break;
1684
1685 case ARG_BASE_TIME:
1686 r = parse_timestamp(optarg, &arg_base_time);
1687 if (r < 0)
1688 return log_error_errno(r, "Failed to parse --base-time= parameter: %s", optarg);
1689
1690 break;
1691
1692 case ARG_PROFILE:
1693 if (isempty(optarg))
1694 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Profile file name is empty");
1695
1696 if (is_path(optarg)) {
1697 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_profile);
1698 if (r < 0)
1699 return r;
1700 if (!endswith(arg_profile, ".conf"))
1701 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Profile file name must end with .conf: %s", arg_profile);
1702 } else {
1703 r = free_and_strdup(&arg_profile, optarg);
1704 if (r < 0)
1705 return log_oom();
1706 }
1707
1708 break;
1709
1710 case 'U': {
1711 _cleanup_free_ char *mangled = NULL;
1712
1713 r = unit_name_mangle(optarg, UNIT_NAME_MANGLE_WARN, &mangled);
1714 if (r < 0)
1715 return log_error_errno(r, "Failed to mangle unit name %s: %m", optarg);
1716
1717 free_and_replace(arg_unit, mangled);
1718 break;
1719 }
1720 case '?':
1721 return -EINVAL;
1722
1723 default:
1724 assert_not_reached();
1725 }
1726
1727 if (arg_offline && !streq_ptr(argv[optind], "security"))
1728 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1729 "Option --offline= is only supported for security right now.");
1730
1731 if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf"))
1732 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1733 "Option --json= is only supported for security and inspect-elf right now.");
1734
1735 if (arg_threshold != 100 && !streq_ptr(argv[optind], "security"))
1736 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1737 "Option --threshold= is only supported for security right now.");
1738
1739 if (arg_scope == UNIT_FILE_GLOBAL &&
1740 !STR_IN_SET(argv[optind] ?: "time", "dot", "unit-paths", "verify"))
1741 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1742 "Option --global only makes sense with verbs dot, unit-paths, verify.");
1743
1744 if (streq_ptr(argv[optind], "cat-config") && arg_scope == UNIT_FILE_USER)
1745 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1746 "Option --user is not supported for cat-config right now.");
1747
1748 if (arg_security_policy && !streq_ptr(argv[optind], "security"))
1749 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1750 "Option --security-policy= is only supported for security.");
1751
1752 if ((arg_root || arg_image) && (!STRPTR_IN_SET(argv[optind], "cat-config", "verify", "condition")) &&
1753 (!(streq_ptr(argv[optind], "security") && arg_offline)))
1754 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1755 "Options --root= and --image= are only supported for cat-config, verify, condition and security when used with --offline= right now.");
1756
1757 /* Having both an image and a root is not supported by the code */
1758 if (arg_root && arg_image)
1759 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
1760
1761 if (arg_unit && !streq_ptr(argv[optind], "condition"))
1762 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --unit= is only supported for condition");
1763
1764 if (streq_ptr(argv[optind], "condition") && !arg_unit && optind >= argc - 1)
1765 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments for condition");
1766
1767 if (streq_ptr(argv[optind], "condition") && arg_unit && optind < argc - 1)
1768 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No conditions can be passed if --unit= is used.");
1769
1770 return 1; /* work to do */
1771 }
1772
1773 static int run(int argc, char *argv[]) {
1774 _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
1775 _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
1776 _cleanup_(umount_and_rmdir_and_freep) char *unlink_dir = NULL;
1777
1778 static const Verb verbs[] = {
1779 { "help", VERB_ANY, VERB_ANY, 0, help },
1780 { "time", VERB_ANY, 1, VERB_DEFAULT, analyze_time },
1781 { "blame", VERB_ANY, 1, 0, analyze_blame },
1782 { "critical-chain", VERB_ANY, VERB_ANY, 0, analyze_critical_chain },
1783 { "plot", VERB_ANY, 1, 0, analyze_plot },
1784 { "dot", VERB_ANY, VERB_ANY, 0, dot },
1785 /* ↓ The following seven verbs are deprecated, from here … ↓ */
1786 { "log-level", VERB_ANY, 2, 0, verb_log_control },
1787 { "log-target", VERB_ANY, 2, 0, verb_log_control },
1788 { "set-log-level", 2, 2, 0, verb_log_control },
1789 { "get-log-level", VERB_ANY, 1, 0, verb_log_control },
1790 { "set-log-target", 2, 2, 0, verb_log_control },
1791 { "get-log-target", VERB_ANY, 1, 0, verb_log_control },
1792 { "service-watchdogs", VERB_ANY, 2, 0, service_watchdogs },
1793 /* ↑ … until here ↑ */
1794 { "dump", VERB_ANY, 1, 0, dump },
1795 { "cat-config", 2, VERB_ANY, 0, cat_config },
1796 { "unit-files", VERB_ANY, VERB_ANY, 0, do_unit_files },
1797 { "unit-paths", 1, 1, 0, dump_unit_paths },
1798 { "exit-status", VERB_ANY, VERB_ANY, 0, dump_exit_status },
1799 { "syscall-filter", VERB_ANY, VERB_ANY, 0, dump_syscall_filters },
1800 { "capability", VERB_ANY, VERB_ANY, 0, dump_capabilities },
1801 { "filesystems", VERB_ANY, VERB_ANY, 0, dump_filesystems },
1802 { "condition", VERB_ANY, VERB_ANY, 0, do_condition },
1803 { "verify", 2, VERB_ANY, 0, do_verify },
1804 { "calendar", 2, VERB_ANY, 0, test_calendar },
1805 { "timestamp", 2, VERB_ANY, 0, test_timestamp },
1806 { "timespan", 2, VERB_ANY, 0, dump_timespan },
1807 { "security", VERB_ANY, VERB_ANY, 0, do_security },
1808 { "inspect-elf", 2, VERB_ANY, 0, do_elf_inspection },
1809 {}
1810 };
1811
1812 int r;
1813
1814 setlocale(LC_ALL, "");
1815 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
1816
1817 log_setup();
1818
1819 r = parse_argv(argc, argv);
1820 if (r <= 0)
1821 return r;
1822
1823 /* Open up and mount the image */
1824 if (arg_image) {
1825 assert(!arg_root);
1826
1827 r = mount_image_privately_interactively(
1828 arg_image,
1829 DISSECT_IMAGE_GENERIC_ROOT |
1830 DISSECT_IMAGE_RELAX_VAR_CHECK |
1831 DISSECT_IMAGE_READ_ONLY,
1832 &unlink_dir,
1833 &loop_device,
1834 &decrypted_image);
1835 if (r < 0)
1836 return r;
1837
1838 arg_root = strdup(unlink_dir);
1839 if (!arg_root)
1840 return log_oom();
1841 }
1842
1843 return dispatch_verb(argc, argv, verbs, NULL);
1844 }
1845
1846 DEFINE_MAIN_FUNCTION(run);