]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/analyze/analyze.c
analyze: split out "unit-paths" verb
[thirdparty/systemd.git] / src / analyze / analyze.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2265fbf7 2/***
96b2fb93 3 Copyright © 2013 Simon Peeters
2265fbf7
SP
4***/
5
2265fbf7 6#include <getopt.h>
3f1c1287 7#include <inttypes.h>
3f6fd1ba
LP
8#include <stdio.h>
9#include <stdlib.h>
ca78ad1d 10#include <unistd.h>
2265fbf7 11
048ecf5b 12#include "sd-bus.h"
3f6fd1ba 13
b5efdb8a 14#include "alloc-util.h"
30bddc06 15#include "analyze.h"
113dd9cb 16#include "analyze-blame.h"
5229b03c 17#include "analyze-calendar.h"
6488e844 18#include "analyze-capability.h"
c649b343 19#include "analyze-cat-config.h"
edfea9fe 20#include "analyze-condition.h"
cccd2af6 21#include "analyze-dot.h"
25eb70af 22#include "analyze-dump.h"
917e6554 23#include "analyze-elf.h"
73cb64c4 24#include "analyze-exit-status.h"
08e36480 25#include "analyze-filesystems.h"
faaa6ea0 26#include "analyze-log-control.h"
ba474dad 27#include "analyze-plot.h"
ec16f3b6 28#include "analyze-security.h"
2b04e72c 29#include "analyze-service-watchdogs.h"
389638d3 30#include "analyze-syscall-filter.h"
113dd9cb 31#include "analyze-time-data.h"
30bddc06 32#include "analyze-timespan.h"
503ccaaa 33#include "analyze-timestamp.h"
36258fbe 34#include "analyze-unit-paths.h"
3f6fd1ba 35#include "analyze-verify.h"
048ecf5b 36#include "bus-error.h"
9b71e4ab 37#include "bus-locator.h"
807542be 38#include "bus-map-properties.h"
20b16441 39#include "bus-unit-util.h"
6d86f4bd 40#include "calendarspec.h"
b2af819b
LP
41#include "cap-list.h"
42#include "capability-util.h"
854a42fb 43#include "conf-files.h"
c0a1bfac 44#include "copy.h"
90bea744 45#include "def.h"
76ed04d9 46#include "exit-status.h"
da845dab 47#include "extract-word.h"
c0a1bfac 48#include "fd-util.h"
cdf6258c 49#include "fileio.h"
b41711cd 50#include "filesystems.h"
d8bfdbe1 51#include "format-table.h"
7d50b32a 52#include "glob-util.h"
bb150966 53#include "hashmap.h"
8752c575 54#include "locale-util.h"
3f6fd1ba 55#include "log.h"
d665c7b2 56#include "main-func.h"
e5ea5c3a 57#include "mount-util.h"
d8b4d14d 58#include "nulstr-util.h"
9ea9d4cf 59#include "pager.h"
599c7c54 60#include "parse-argument.h"
6bedfcbb 61#include "parse-util.h"
854a42fb 62#include "path-util.h"
294bf0c3 63#include "pretty-print.h"
da845dab 64#include "rm-rf.h"
349cc4a5 65#if HAVE_SECCOMP
294bf0c3 66# include "seccomp-util.h"
0f734bdc 67#endif
760877e9 68#include "sort-util.h"
3f6fd1ba 69#include "special.h"
b41711cd 70#include "stat-util.h"
3cc3dc77 71#include "string-table.h"
3f6fd1ba
LP
72#include "strv.h"
73#include "strxcpyx.h"
288a74cc 74#include "terminal-util.h"
760877e9 75#include "time-util.h"
da845dab 76#include "tmpfile-util.h"
3f6fd1ba
LP
77#include "unit-name.h"
78#include "util.h"
a87b151a 79#include "verb-log-control.h"
a6bcef29 80#include "verbs.h"
47350c5f 81#include "version.h"
2265fbf7 82
cccd2af6
LP
83DotMode arg_dot = DEP_ALL;
84char **arg_dot_from_patterns = NULL, **arg_dot_to_patterns = NULL;
9ea9d4cf 85static usec_t arg_fuzz = 0;
08e36480 86PagerFlags arg_pager_flags = 0;
25eb70af 87BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
ba474dad 88const char *arg_host = NULL;
113dd9cb 89UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
3cc3dc77 90static RecursiveErrors arg_recursive_errors = RECURSIVE_ERRORS_YES;
1d3bc017 91static bool arg_man = true;
641c0fd1 92static bool arg_generators = false;
c649b343 93char *arg_root = NULL;
e5ea5c3a 94static char *arg_image = NULL;
ecfd082b 95static char *arg_security_policy = NULL;
bb43d853 96static bool arg_offline = false;
dfbda879 97static unsigned arg_threshold = 100;
5229b03c
LP
98unsigned arg_iterations = 1;
99usec_t arg_base_time = USEC_INFINITY;
8de7929d 100static char *arg_unit = NULL;
4b4a8ef7 101static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
08e36480 102bool arg_quiet = false;
04469211 103static char *arg_profile = NULL;
bb150966 104
d665c7b2
YW
105STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
106STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
782671bc 107STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
e5ea5c3a 108STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
ecfd082b 109STATIC_DESTRUCTOR_REGISTER(arg_security_policy, freep);
8de7929d 110STATIC_DESTRUCTOR_REGISTER(arg_unit, freep);
04469211 111STATIC_DESTRUCTOR_REGISTER(arg_profile, freep);
d665c7b2 112
25eb70af 113int acquire_bus(sd_bus **bus, bool *use_full_bus) {
5c69b31c 114 bool user = arg_scope != UNIT_FILE_SYSTEM;
fb507898 115 int r;
5c69b31c 116
f7e29336 117 if (use_full_bus && *use_full_bus) {
fb507898
YW
118 r = bus_connect_transport(arg_transport, arg_host, user, bus);
119 if (IN_SET(r, 0, -EHOSTDOWN))
120 return r;
5c69b31c
GJ
121
122 *use_full_bus = false;
123 }
124
f7e29336 125 return bus_connect_transport_systemd(arg_transport, arg_host, user, bus);
bf0e0a4d
ZJS
126}
127
cccd2af6 128int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) {
4afd3348 129 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
988b9df2
LP
130 int r;
131
132 assert(bus);
133 assert(path);
134 assert(property);
135 assert(strv);
136
137 r = sd_bus_get_property_strv(
138 bus,
139 "org.freedesktop.systemd1",
140 path,
141 "org.freedesktop.systemd1.Unit",
142 property,
143 &error,
144 strv);
4ae25393 145 if (r < 0)
0ed3da7c 146 return log_error_errno(r, "Failed to get unit property %s: %s", property, bus_error_message(&error, r));
988b9df2
LP
147
148 return 0;
149}
150
da845dab
AB
151static int process_aliases(char *argv[], char *tempdir, char ***ret) {
152 _cleanup_strv_free_ char **filenames = NULL;
153 char **filename;
154 int r;
155
156 assert(argv);
157 assert(tempdir);
158 assert(ret);
159
160 STRV_FOREACH(filename, strv_skip(argv, 1)) {
437346c9
AB
161 _cleanup_free_ char *src = NULL, *dst = NULL, *base = NULL;
162 const char *parse_arg;
da845dab 163
437346c9
AB
164 parse_arg = *filename;
165 r = extract_first_word(&parse_arg, &src, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_ESCAPE);
da845dab
AB
166 if (r < 0)
167 return r;
168
169 if (!parse_arg) {
437346c9 170 r = strv_consume(&filenames, TAKE_PTR(src));
da845dab 171 if (r < 0)
437346c9 172 return r;
da845dab
AB
173
174 continue;
175 }
176
437346c9
AB
177 r = path_extract_filename(parse_arg, &base);
178 if (r < 0)
179 return r;
180
181 dst = path_join(tempdir, base);
da845dab
AB
182 if (!dst)
183 return -ENOMEM;
184
185 r = copy_file(src, dst, 0, 0644, 0, 0, COPY_REFLINK);
186 if (r < 0)
187 return r;
188
189 r = strv_consume(&filenames, TAKE_PTR(dst));
190 if (r < 0)
437346c9 191 return r;
da845dab
AB
192 }
193
194 *ret = TAKE_PTR(filenames);
195 return 0;
196}
197
1ace223c
SJ
198static int list_dependencies_print(
199 const char *name,
200 unsigned level,
201 unsigned branches,
202 bool last,
6aa601c5
ZJS
203 UnitTimes *times,
204 BootTimes *boot) {
1ace223c 205
6aa601c5 206 for (unsigned i = level; i != 0; i--)
9a6f746f 207 printf("%s", special_glyph(branches & (1 << (i-1)) ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE));
bb150966 208
9a6f746f 209 printf("%s", special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH));
bb150966
HH
210
211 if (times) {
baa4880b 212 if (times->time > 0)
54f8c958 213 printf("%s%s @%s +%s%s", ansi_highlight_red(), name,
5291f26d
ZJS
214 FORMAT_TIMESPAN(times->activating - boot->userspace_time, USEC_PER_MSEC),
215 FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal());
cc27380c 216 else if (times->activated > boot->userspace_time)
5291f26d 217 printf("%s @%s", name, FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC));
bb150966
HH
218 else
219 printf("%s", name);
988b9df2
LP
220 } else
221 printf("%s", name);
bb150966
HH
222 printf("\n");
223
224 return 0;
225}
226
048ecf5b 227static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
988b9df2 228 _cleanup_free_ char *path = NULL;
bb150966
HH
229
230 assert(bus);
231 assert(name);
232 assert(deps);
233
234 path = unit_dbus_path_from_name(name);
234519ae 235 if (!path)
988b9df2 236 return -ENOMEM;
bb150966 237
988b9df2 238 return bus_get_unit_property_strv(bus, path, "After", deps);
bb150966
HH
239}
240
241static Hashmap *unit_times_hashmap;
242
1ace223c 243static int list_dependencies_compare(char *const *a, char *const *b) {
bb150966 244 usec_t usa = 0, usb = 0;
6aa601c5 245 UnitTimes *times;
bb150966
HH
246
247 times = hashmap_get(unit_times_hashmap, *a);
248 if (times)
cc27380c 249 usa = times->activated;
bb150966
HH
250 times = hashmap_get(unit_times_hashmap, *b);
251 if (times)
cc27380c 252 usb = times->activated;
bb150966 253
93bab288 254 return CMP(usb, usa);
bb150966
HH
255}
256
6aa601c5 257static bool times_in_range(const UnitTimes *times, const BootTimes *boot) {
1ace223c 258 return times && times->activated > 0 && times->activated <= boot->finish_time;
230cc99a
ZJS
259}
260
1ace223c 261static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level, char ***units, unsigned branches) {
bb150966
HH
262 _cleanup_strv_free_ char **deps = NULL;
263 char **c;
05f7a068 264 int r;
bb150966
HH
265 usec_t service_longest = 0;
266 int to_print = 0;
6aa601c5
ZJS
267 UnitTimes *times;
268 BootTimes *boot;
bb150966 269
988b9df2 270 if (strv_extend(units, name))
bb150966
HH
271 return log_oom();
272
273 r = list_dependencies_get_dependencies(bus, name, &deps);
274 if (r < 0)
275 return r;
276
93bab288 277 typesafe_qsort(deps, strv_length(deps), list_dependencies_compare);
bb150966
HH
278
279 r = acquire_boot_times(bus, &boot);
280 if (r < 0)
281 return r;
282
283 STRV_FOREACH(c, deps) {
33b7988d 284 times = hashmap_get(unit_times_hashmap, *c); /* lgtm [cpp/inconsistent-null-check] */
1ace223c 285 if (times_in_range(times, boot) && times->activated >= service_longest)
cc27380c 286 service_longest = times->activated;
bb150966
HH
287 }
288
234519ae 289 if (service_longest == 0)
bb150966
HH
290 return r;
291
292 STRV_FOREACH(c, deps) {
33b7988d 293 times = hashmap_get(unit_times_hashmap, *c); /* lgtm [cpp/inconsistent-null-check] */
1ace223c 294 if (times_in_range(times, boot) && service_longest - times->activated <= arg_fuzz)
bb150966 295 to_print++;
bb150966
HH
296 }
297
f168c273 298 if (!to_print)
bb150966
HH
299 return r;
300
301 STRV_FOREACH(c, deps) {
33b7988d 302 times = hashmap_get(unit_times_hashmap, *c); /* lgtm [cpp/inconsistent-null-check] */
1ace223c 303 if (!times_in_range(times, boot) || service_longest - times->activated > arg_fuzz)
bb150966
HH
304 continue;
305
306 to_print--;
307
308 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
309 if (r < 0)
310 return r;
311
312 if (strv_contains(*units, *c)) {
313 r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
314 true, NULL, boot);
872c8faa
ZJS
315 if (r < 0)
316 return r;
bb150966
HH
317 continue;
318 }
319
1ace223c 320 r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (to_print ? 1 : 0));
872c8faa 321 if (r < 0)
bb150966
HH
322 return r;
323
baa4880b 324 if (to_print == 0)
bb150966 325 break;
bb150966
HH
326 }
327 return 0;
328}
329
048ecf5b 330static int list_dependencies(sd_bus *bus, const char *name) {
bb150966 331 _cleanup_strv_free_ char **units = NULL;
6aa601c5 332 UnitTimes *times;
bb150966 333 int r;
0ee9613d
TA
334 const char *id;
335 _cleanup_free_ char *path = NULL;
4afd3348
LP
336 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
337 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
6aa601c5 338 BootTimes *boot;
bb150966
HH
339
340 assert(bus);
341
805bf39c 342 path = unit_dbus_path_from_name(name);
234519ae 343 if (!path)
988b9df2 344 return -ENOMEM;
bb150966 345
a936124f
TA
346 r = sd_bus_get_property(
347 bus,
348 "org.freedesktop.systemd1",
349 path,
350 "org.freedesktop.systemd1.Unit",
351 "Id",
352 &error,
353 &reply,
354 "s");
4ae25393 355 if (r < 0)
0ed3da7c 356 return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r));
bb150966 357
048ecf5b 358 r = sd_bus_message_read(reply, "s", &id);
5b30bef8
LP
359 if (r < 0)
360 return bus_log_parse_error(r);
bb150966 361
bb150966
HH
362 times = hashmap_get(unit_times_hashmap, id);
363
364 r = acquire_boot_times(bus, &boot);
365 if (r < 0)
366 return r;
367
368 if (times) {
369 if (times->time)
54f8c958 370 printf("%s%s +%s%s\n", ansi_highlight_red(), id,
5291f26d 371 FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal());
cc27380c 372 else if (times->activated > boot->userspace_time)
5291f26d
ZJS
373 printf("%s @%s\n", id,
374 FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC));
bb150966
HH
375 else
376 printf("%s\n", id);
377 }
378
805bf39c 379 return list_dependencies_one(bus, name, 0, &units, 0);
bb150966
HH
380}
381
a6bcef29
LP
382static int analyze_critical_chain(int argc, char *argv[], void *userdata) {
383 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
15567b3a 384 _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
bb150966 385 Hashmap *h;
988b9df2 386 int n, r;
bb150966 387
f7e29336 388 r = acquire_bus(&bus, NULL);
a6bcef29 389 if (r < 0)
10a7340a 390 return bus_log_connect_error(r, arg_transport);
a6bcef29 391
bb150966
HH
392 n = acquire_time_data(bus, &times);
393 if (n <= 0)
394 return n;
395
d5099efc 396 h = hashmap_new(&string_hash_ops);
bb150966 397 if (!h)
8efbce13 398 return log_oom();
bb150966 399
6aa601c5 400 for (UnitTimes *u = times; u->has_data; u++) {
df560cf6 401 r = hashmap_put(h, u->name, u);
bb150966 402 if (r < 0)
8efbce13 403 return log_error_errno(r, "Failed to add entry to hashmap: %m");
bb150966
HH
404 }
405 unit_times_hashmap = h;
406
384c2c32 407 pager_open(arg_pager_flags);
9ea9d4cf 408
2fffb93b
ZJS
409 puts("The time when unit became active or started is printed after the \"@\" character.\n"
410 "The time the unit took to start is printed after the \"+\" character.\n");
bb150966 411
a6bcef29 412 if (argc > 1) {
805bf39c 413 char **name;
a6bcef29 414 STRV_FOREACH(name, strv_skip(argv, 1))
805bf39c 415 list_dependencies(bus, *name);
9ea9d4cf 416 } else
805bf39c 417 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
bb150966 418
a6bcef29 419 h = hashmap_free(h);
bb150966
HH
420 return 0;
421}
422
a6bcef29
LP
423static int analyze_time(int argc, char *argv[], void *userdata) {
424 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
c170f3a4
LP
425 _cleanup_free_ char *buf = NULL;
426 int r;
427
f7e29336 428 r = acquire_bus(&bus, NULL);
a6bcef29 429 if (r < 0)
10a7340a 430 return bus_log_connect_error(r, arg_transport);
a6bcef29 431
c170f3a4
LP
432 r = pretty_boot_time(bus, &buf);
433 if (r < 0)
434 return r;
435
436 puts(buf);
2265fbf7
SP
437 return 0;
438}
439
e67cd21d
ZJS
440static bool strv_fnmatch_strv_or_empty(char* const* patterns, char **strv, int flags) {
441 char **s;
442 STRV_FOREACH(s, strv)
443 if (strv_fnmatch_or_empty(patterns, *s, flags))
444 return true;
445
446 return false;
447}
448
449static int do_unit_files(int argc, char *argv[], void *userdata) {
450 _cleanup_(lookup_paths_free) LookupPaths lp = {};
451 _cleanup_hashmap_free_ Hashmap *unit_ids = NULL;
452 _cleanup_hashmap_free_ Hashmap *unit_names = NULL;
453 char **patterns = strv_skip(argv, 1);
e67cd21d
ZJS
454 const char *k, *dst;
455 char **v;
456 int r;
457
458 r = lookup_paths_init(&lp, arg_scope, 0, NULL);
459 if (r < 0)
460 return log_error_errno(r, "lookup_paths_init() failed: %m");
461
91e0ee5f 462 r = unit_file_build_name_map(&lp, NULL, &unit_ids, &unit_names, NULL);
e67cd21d
ZJS
463 if (r < 0)
464 return log_error_errno(r, "unit_file_build_name_map() failed: %m");
465
90e74a66 466 HASHMAP_FOREACH_KEY(dst, k, unit_ids) {
e67cd21d
ZJS
467 if (!strv_fnmatch_or_empty(patterns, k, FNM_NOESCAPE) &&
468 !strv_fnmatch_or_empty(patterns, dst, FNM_NOESCAPE))
469 continue;
470
471 printf("ids: %s → %s\n", k, dst);
472 }
473
90e74a66 474 HASHMAP_FOREACH_KEY(v, k, unit_names) {
e67cd21d
ZJS
475 if (!strv_fnmatch_or_empty(patterns, k, FNM_NOESCAPE) &&
476 !strv_fnmatch_strv_or_empty(patterns, v, FNM_NOESCAPE))
477 continue;
478
479 _cleanup_free_ char *j = strv_join(v, ", ");
480 printf("aliases: %s ← %s\n", k, j);
481 }
482
483 return 0;
484}
485
30bddc06 486void time_parsing_hint(const char *p, bool calendar, bool timestamp, bool timespan) {
c269607f
ZJS
487 if (calendar && calendar_spec_from_string(p, NULL) >= 0)
488 log_notice("Hint: this expression is a valid calendar specification. "
489 "Use 'systemd-analyze calendar \"%s\"' instead?", p);
490 if (timestamp && parse_timestamp(p, NULL) >= 0)
491 log_notice("Hint: this expression is a valid timestamp. "
492 "Use 'systemd-analyze timestamp \"%s\"' instead?", p);
493 if (timespan && parse_time(p, NULL, USEC_PER_SEC) >= 0)
494 log_notice("Hint: this expression is a valid timespan. "
495 "Use 'systemd-analyze timespan \"%s\"' instead?", p);
6d86f4bd
LP
496}
497
edfea9fe 498static int do_condition(int argc, char *argv[], void *userdata) {
8de7929d 499 return verify_conditions(strv_skip(argv, 1), arg_scope, arg_unit, arg_root);
edfea9fe
ZJS
500}
501
a6bcef29 502static int do_verify(int argc, char *argv[], void *userdata) {
da845dab
AB
503 _cleanup_strv_free_ char **filenames = NULL;
504 _cleanup_(rm_rf_physical_and_freep) char *tempdir = NULL;
505 int r;
506
507 r = mkdtemp_malloc("/tmp/systemd-analyze-XXXXXX", &tempdir);
508 if (r < 0)
509 return log_error_errno(r, "Failed to setup working directory: %m");
510
511 r = process_aliases(argv, tempdir, &filenames);
512 if (r < 0)
513 return log_error_errno(r, "Couldn't process aliases: %m");
514
515 return verify_units(filenames, arg_scope, arg_man, arg_generators, arg_recursive_errors, arg_root);
a6bcef29
LP
516}
517
ec16f3b6
LP
518static int do_security(int argc, char *argv[], void *userdata) {
519 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
ecfd082b 520 _cleanup_(json_variant_unrefp) JsonVariant *policy = NULL;
ec16f3b6 521 int r;
ecfd082b 522 unsigned line, column;
ec16f3b6 523
741c4c8d
LB
524 if (!arg_offline) {
525 r = acquire_bus(&bus, NULL);
526 if (r < 0)
527 return bus_log_connect_error(r, arg_transport);
528 }
ec16f3b6 529
384c2c32 530 pager_open(arg_pager_flags);
ec16f3b6 531
ecfd082b
MG
532 if (arg_security_policy) {
533 r = json_parse_file(/*f=*/ NULL, arg_security_policy, /*flags=*/ 0, &policy, &line, &column);
534 if (r < 0)
535 return log_error_errno(r, "Failed to parse '%s' at %u:%u: %m", arg_security_policy, line, column);
536 } else {
537 _cleanup_fclose_ FILE *f = NULL;
538 _cleanup_free_ char *pp = NULL;
539
540 r = search_and_fopen_nulstr("systemd-analyze-security.policy", "re", /*root=*/ NULL, CONF_PATHS_NULSTR("systemd"), &f, &pp);
541 if (r < 0 && r != -ENOENT)
542 return r;
543
b98416e1 544 if (f) {
ecfd082b
MG
545 r = json_parse_file(f, pp, /*flags=*/ 0, &policy, &line, &column);
546 if (r < 0)
547 return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON policy: %m", pp, line, column);
548 }
549 }
550
551 return analyze_security(bus,
552 strv_skip(argv, 1),
553 policy,
554 arg_scope,
555 arg_man,
556 arg_generators,
557 arg_offline,
558 arg_threshold,
559 arg_root,
04469211 560 arg_profile,
4b4a8ef7
MG
561 arg_json_format_flags,
562 arg_pager_flags,
ecfd082b 563 /*flags=*/ 0);
ec16f3b6
LP
564}
565
917e6554
LB
566static int do_elf_inspection(int argc, char *argv[], void *userdata) {
567 pager_open(arg_pager_flags);
568
569 return analyze_elf(strv_skip(argv, 1), arg_json_format_flags);
570}
571
a6bcef29 572static int help(int argc, char *argv[], void *userdata) {
49139a5d 573 _cleanup_free_ char *link = NULL, *dot_link = NULL;
37ec0fdd 574 int r;
9ea9d4cf 575
384c2c32 576 pager_open(arg_pager_flags);
9ea9d4cf 577
37ec0fdd
LP
578 r = terminal_urlify_man("systemd-analyze", "1", &link);
579 if (r < 0)
580 return log_oom();
581
49139a5d
LP
582 /* Not using terminal_urlify_man() for this, since we don't want the "man page" text suffix in this case. */
583 r = terminal_urlify("man:dot(1)", "dot(1)", &dot_link);
584 if (r < 0)
585 return log_oom();
586
353b2baa
LP
587 printf("%s [OPTIONS...] COMMAND ...\n\n"
588 "%sProfile systemd, show unit dependencies, check unit files.%s\n"
20a51f6a 589 "\nCommands:\n"
3cc3dc77
MG
590 " [time] Print time required to boot the machine\n"
591 " blame Print list of running units ordered by\n"
592 " time to init\n"
593 " critical-chain [UNIT...] Print a tree of the time critical chain\n"
594 " of units\n"
595 " plot Output SVG graphic showing service\n"
596 " initialization\n"
597 " dot [UNIT...] Output dependency graph in %s format\n"
598 " dump Output state serialization of service\n"
599 " manager\n"
600 " cat-config Show configuration file and drop-ins\n"
601 " unit-files List files and symlinks for units\n"
602 " unit-paths List load directories for units\n"
603 " exit-status [STATUS...] List exit status definitions\n"
604 " capability [CAP...] List capability definitions\n"
52117f5a
ZJS
605 " syscall-filter [NAME...] List syscalls in seccomp filters\n"
606 " filesystems [NAME...] List known filesystems\n"
3cc3dc77
MG
607 " condition CONDITION... Evaluate conditions and asserts\n"
608 " verify FILE... Check unit files for correctness\n"
609 " calendar SPEC... Validate repetitive calendar time\n"
610 " events\n"
611 " timestamp TIMESTAMP... Validate a timestamp\n"
612 " timespan SPAN... Validate a time span\n"
613 " security [UNIT...] Analyze security of unit\n"
917e6554 614 " inspect-elf FILE... Parse and print ELF package metadata\n"
353b2baa 615 "\nOptions:\n"
3cc3dc77 616 " --recursive-errors=MODE Control which units are verified\n"
bb43d853 617 " --offline=BOOL Perform a security review on unit file(s)\n"
dfbda879
MG
618 " --threshold=N Exit with a non-zero status when overall\n"
619 " exposure level is over threshold value\n"
ecfd082b
MG
620 " --security-policy=PATH Use custom JSON security policy instead\n"
621 " of built-in one\n"
4b4a8ef7
MG
622 " --json=pretty|short|off Generate JSON output of the security\n"
623 " analysis table\n"
3cc3dc77
MG
624 " --no-pager Do not pipe output into a pager\n"
625 " --system Operate on system systemd instance\n"
626 " --user Operate on user systemd instance\n"
627 " --global Operate on global user configuration\n"
628 " -H --host=[USER@]HOST Operate on remote host\n"
629 " -M --machine=CONTAINER Operate on local container\n"
630 " --order Show only order in the graph\n"
631 " --require Show only requirement in the graph\n"
632 " --from-pattern=GLOB Show only origins in the graph\n"
633 " --to-pattern=GLOB Show only destinations in the graph\n"
634 " --fuzz=SECONDS Also print services which finished SECONDS\n"
635 " earlier than the latest in the branch\n"
636 " --man[=BOOL] Do [not] check for existence of man pages\n"
637 " --generators[=BOOL] Do [not] run unit generators\n"
638 " (requires privileges)\n"
639 " --iterations=N Show the specified number of iterations\n"
640 " --base-time=TIMESTAMP Calculate calendar times relative to\n"
641 " specified time\n"
04469211
LB
642 " --profile=name|PATH Include the specified profile in the\n"
643 " security review of the unit(s)\n"
52117f5a
ZJS
644 " -h --help Show this help\n"
645 " --version Show package version\n"
646 " -q --quiet Do not emit hints\n"
bc556335
DDM
647 "\nSee the %s for details.\n",
648 program_invocation_short_name,
649 ansi_highlight(),
650 ansi_normal(),
651 dot_link,
652 link);
96de7c04 653
1ace223c
SJ
654 /* When updating this list, including descriptions, apply changes to
655 * shell-completion/bash/systemd-analyze and shell-completion/zsh/_systemd-analyze too. */
a6bcef29
LP
656
657 return 0;
2265fbf7
SP
658}
659
9ea9d4cf 660static int parse_argv(int argc, char *argv[]) {
2265fbf7
SP
661 enum {
662 ARG_VERSION = 0x100,
1700761b
SP
663 ARG_ORDER,
664 ARG_REQUIRE,
46d8646a 665 ARG_ROOT,
e5ea5c3a 666 ARG_IMAGE,
e55933db 667 ARG_SYSTEM,
28b35ef2
ZJS
668 ARG_USER,
669 ARG_GLOBAL,
e55933db 670 ARG_DOT_FROM_PATTERN,
bb150966 671 ARG_DOT_TO_PATTERN,
9ea9d4cf 672 ARG_FUZZ,
1d3bc017 673 ARG_NO_PAGER,
dad29dff 674 ARG_MAN,
641c0fd1 675 ARG_GENERATORS,
f2ccf832 676 ARG_ITERATIONS,
985c1880 677 ARG_BASE_TIME,
3cc3dc77 678 ARG_RECURSIVE_ERRORS,
bb43d853 679 ARG_OFFLINE,
dfbda879 680 ARG_THRESHOLD,
ecfd082b 681 ARG_SECURITY_POLICY,
4b4a8ef7 682 ARG_JSON,
04469211 683 ARG_PROFILE,
2265fbf7
SP
684 };
685
686 static const struct option options[] = {
3cc3dc77
MG
687 { "help", no_argument, NULL, 'h' },
688 { "version", no_argument, NULL, ARG_VERSION },
52117f5a 689 { "quiet", no_argument, NULL, 'q' },
3cc3dc77
MG
690 { "order", no_argument, NULL, ARG_ORDER },
691 { "require", no_argument, NULL, ARG_REQUIRE },
692 { "root", required_argument, NULL, ARG_ROOT },
693 { "image", required_argument, NULL, ARG_IMAGE },
694 { "recursive-errors", required_argument, NULL, ARG_RECURSIVE_ERRORS },
bb43d853 695 { "offline", required_argument, NULL, ARG_OFFLINE },
dfbda879 696 { "threshold", required_argument, NULL, ARG_THRESHOLD },
ecfd082b 697 { "security-policy", required_argument, NULL, ARG_SECURITY_POLICY },
3cc3dc77
MG
698 { "system", no_argument, NULL, ARG_SYSTEM },
699 { "user", no_argument, NULL, ARG_USER },
700 { "global", no_argument, NULL, ARG_GLOBAL },
701 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
702 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
703 { "fuzz", required_argument, NULL, ARG_FUZZ },
704 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
705 { "man", optional_argument, NULL, ARG_MAN },
706 { "generators", optional_argument, NULL, ARG_GENERATORS },
707 { "host", required_argument, NULL, 'H' },
708 { "machine", required_argument, NULL, 'M' },
709 { "iterations", required_argument, NULL, ARG_ITERATIONS },
710 { "base-time", required_argument, NULL, ARG_BASE_TIME },
8de7929d 711 { "unit", required_argument, NULL, 'U' },
4b4a8ef7 712 { "json", required_argument, NULL, ARG_JSON },
04469211 713 { "profile", required_argument, NULL, ARG_PROFILE },
eb9da376 714 {}
2265fbf7
SP
715 };
716
eb9da376
LP
717 int r, c;
718
2265fbf7
SP
719 assert(argc >= 0);
720 assert(argv);
721
8de7929d 722 while ((c = getopt_long(argc, argv, "hH:M:U:", options, NULL)) >= 0)
eb9da376 723 switch (c) {
c170f3a4
LP
724
725 case 'h':
a6bcef29 726 return help(0, NULL, NULL);
c170f3a4 727
52117f5a
ZJS
728 case ARG_VERSION:
729 return version();
730
731 case 'q':
732 arg_quiet = true;
733 break;
734
3cc3dc77
MG
735 case ARG_RECURSIVE_ERRORS:
736 if (streq(optarg, "help")) {
737 DUMP_STRING_TABLE(recursive_errors, RecursiveErrors, _RECURSIVE_ERRORS_MAX);
738 return 0;
739 }
740 r = recursive_errors_from_string(optarg);
741 if (r < 0)
742 return log_error_errno(r, "Unknown mode passed to --recursive-errors='%s'.", optarg);
743
744 arg_recursive_errors = r;
745 break;
746
46d8646a 747 case ARG_ROOT:
782671bc
MG
748 r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root);
749 if (r < 0)
750 return r;
46d8646a
ZJS
751 break;
752
e5ea5c3a
MG
753 case ARG_IMAGE:
754 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
755 if (r < 0)
756 return r;
757 break;
758
28b35ef2
ZJS
759 case ARG_SYSTEM:
760 arg_scope = UNIT_FILE_SYSTEM;
761 break;
762
c170f3a4 763 case ARG_USER:
28b35ef2 764 arg_scope = UNIT_FILE_USER;
c170f3a4
LP
765 break;
766
28b35ef2
ZJS
767 case ARG_GLOBAL:
768 arg_scope = UNIT_FILE_GLOBAL;
c170f3a4
LP
769 break;
770
771 case ARG_ORDER:
772 arg_dot = DEP_ORDER;
773 break;
774
775 case ARG_REQUIRE:
776 arg_dot = DEP_REQUIRE;
777 break;
778
e55933db 779 case ARG_DOT_FROM_PATTERN:
903a0b07
LP
780 if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
781 return log_oom();
782
e55933db
ŁS
783 break;
784
785 case ARG_DOT_TO_PATTERN:
903a0b07
LP
786 if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
787 return log_oom();
788
e55933db
ŁS
789 break;
790
bb150966
HH
791 case ARG_FUZZ:
792 r = parse_sec(optarg, &arg_fuzz);
793 if (r < 0)
794 return r;
795 break;
796
9ea9d4cf 797 case ARG_NO_PAGER:
0221d68a 798 arg_pager_flags |= PAGER_DISABLE;
9ea9d4cf
LP
799 break;
800
3cd26e7c
LP
801 case 'H':
802 arg_transport = BUS_TRANSPORT_REMOTE;
803 arg_host = optarg;
804 break;
805
806 case 'M':
de33fc62 807 arg_transport = BUS_TRANSPORT_MACHINE;
3cd26e7c
LP
808 arg_host = optarg;
809 break;
810
dad29dff 811 case ARG_MAN:
599c7c54
ZJS
812 r = parse_boolean_argument("--man", optarg, &arg_man);
813 if (r < 0)
814 return r;
1d3bc017
ZJS
815 break;
816
641c0fd1 817 case ARG_GENERATORS:
599c7c54
ZJS
818 r = parse_boolean_argument("--generators", optarg, &arg_generators);
819 if (r < 0)
820 return r;
641c0fd1
ZJS
821 break;
822
bb43d853
MG
823 case ARG_OFFLINE:
824 r = parse_boolean_argument("--offline", optarg, &arg_offline);
825 if (r < 0)
826 return r;
827 break;
828
dfbda879
MG
829 case ARG_THRESHOLD:
830 r = safe_atou(optarg, &arg_threshold);
831 if (r < 0 || arg_threshold > 100)
832 return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse threshold: %s", optarg);
833
834 break;
835
ecfd082b
MG
836 case ARG_SECURITY_POLICY:
837 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_security_policy);
838 if (r < 0)
839 return r;
840 break;
841
4b4a8ef7
MG
842 case ARG_JSON:
843 r = parse_json_argument(optarg, &arg_json_format_flags);
844 if (r <= 0)
845 return r;
846 break;
847
f2ccf832
LP
848 case ARG_ITERATIONS:
849 r = safe_atou(optarg, &arg_iterations);
850 if (r < 0)
851 return log_error_errno(r, "Failed to parse iterations: %s", optarg);
852
853 break;
854
985c1880
LP
855 case ARG_BASE_TIME:
856 r = parse_timestamp(optarg, &arg_base_time);
857 if (r < 0)
858 return log_error_errno(r, "Failed to parse --base-time= parameter: %s", optarg);
859
860 break;
861
04469211
LB
862 case ARG_PROFILE:
863 if (isempty(optarg))
864 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Profile file name is empty");
865
866 if (is_path(optarg)) {
867 r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_profile);
868 if (r < 0)
869 return r;
870 if (!endswith(arg_profile, ".conf"))
871 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Profile file name must end with .conf: %s", arg_profile);
872 } else {
873 r = free_and_strdup(&arg_profile, optarg);
874 if (r < 0)
875 return log_oom();
876 }
877
878 break;
879
8de7929d
DDM
880 case 'U': {
881 _cleanup_free_ char *mangled = NULL;
882
883 r = unit_name_mangle(optarg, UNIT_NAME_MANGLE_WARN, &mangled);
884 if (r < 0)
885 return log_error_errno(r, "Failed to mangle unit name %s: %m", optarg);
886
887 free_and_replace(arg_unit, mangled);
888 break;
889 }
c170f3a4
LP
890 case '?':
891 return -EINVAL;
892
893 default:
04499a70 894 assert_not_reached();
2265fbf7 895 }
eb9da376 896
bb43d853
MG
897 if (arg_offline && !streq_ptr(argv[optind], "security"))
898 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
899 "Option --offline= is only supported for security right now.");
900
917e6554 901 if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf"))
4b4a8ef7 902 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
c0f65909 903 "Option --json= is only supported for security and inspect-elf right now.");
4b4a8ef7 904
dfbda879
MG
905 if (arg_threshold != 100 && !streq_ptr(argv[optind], "security"))
906 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
907 "Option --threshold= is only supported for security right now.");
908
31a5924e 909 if (arg_scope == UNIT_FILE_GLOBAL &&
baaa35ad
ZJS
910 !STR_IN_SET(argv[optind] ?: "time", "dot", "unit-paths", "verify"))
911 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
912 "Option --global only makes sense with verbs dot, unit-paths, verify.");
31a5924e 913
f1d9d36a
ZJS
914 if (streq_ptr(argv[optind], "cat-config") && arg_scope == UNIT_FILE_USER)
915 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
916 "Option --user is not supported for cat-config right now.");
917
ecfd082b
MG
918 if (arg_security_policy && !streq_ptr(argv[optind], "security"))
919 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
920 "Option --security-policy= is only supported for security.");
921
8de7929d 922 if ((arg_root || arg_image) && (!STRPTR_IN_SET(argv[optind], "cat-config", "verify", "condition")) &&
bb43d853 923 (!(streq_ptr(argv[optind], "security") && arg_offline)))
baaa35ad 924 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
8de7929d 925 "Options --root= and --image= are only supported for cat-config, verify, condition and security when used with --offline= right now.");
e5ea5c3a
MG
926
927 /* Having both an image and a root is not supported by the code */
928 if (arg_root && arg_image)
929 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
46d8646a 930
8de7929d
DDM
931 if (arg_unit && !streq_ptr(argv[optind], "condition"))
932 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --unit= is only supported for condition");
933
934 if (streq_ptr(argv[optind], "condition") && !arg_unit && optind >= argc - 1)
935 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments for condition");
936
937 if (streq_ptr(argv[optind], "condition") && arg_unit && optind < argc - 1)
938 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No conditions can be passed if --unit= is used.");
939
1d3bc017 940 return 1; /* work to do */
2265fbf7
SP
941}
942
d665c7b2 943static int run(int argc, char *argv[]) {
e5ea5c3a
MG
944 _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
945 _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
946 _cleanup_(umount_and_rmdir_and_freep) char *unlink_dir = NULL;
a6bcef29
LP
947
948 static const Verb verbs[] = {
889d695d
JK
949 { "help", VERB_ANY, VERB_ANY, 0, help },
950 { "time", VERB_ANY, 1, VERB_DEFAULT, analyze_time },
951 { "blame", VERB_ANY, 1, 0, analyze_blame },
952 { "critical-chain", VERB_ANY, VERB_ANY, 0, analyze_critical_chain },
953 { "plot", VERB_ANY, 1, 0, analyze_plot },
954 { "dot", VERB_ANY, VERB_ANY, 0, dot },
ab86ccba 955 /* ↓ The following seven verbs are deprecated, from here … ↓ */
a87b151a
DDM
956 { "log-level", VERB_ANY, 2, 0, verb_log_control },
957 { "log-target", VERB_ANY, 2, 0, verb_log_control },
958 { "set-log-level", 2, 2, 0, verb_log_control },
959 { "get-log-level", VERB_ANY, 1, 0, verb_log_control },
960 { "set-log-target", 2, 2, 0, verb_log_control },
961 { "get-log-target", VERB_ANY, 1, 0, verb_log_control },
26e1e973 962 { "service-watchdogs", VERB_ANY, 2, 0, service_watchdogs },
ab86ccba 963 /* ↑ … until here ↑ */
889d695d 964 { "dump", VERB_ANY, 1, 0, dump },
854a42fb 965 { "cat-config", 2, VERB_ANY, 0, cat_config },
e67cd21d 966 { "unit-files", VERB_ANY, VERB_ANY, 0, do_unit_files },
31a5924e 967 { "unit-paths", 1, 1, 0, dump_unit_paths },
5238d9a8 968 { "exit-status", VERB_ANY, VERB_ANY, 0, dump_exit_status },
889d695d 969 { "syscall-filter", VERB_ANY, VERB_ANY, 0, dump_syscall_filters },
b2af819b 970 { "capability", VERB_ANY, VERB_ANY, 0, dump_capabilities },
b41711cd 971 { "filesystems", VERB_ANY, VERB_ANY, 0, dump_filesystems },
8de7929d 972 { "condition", VERB_ANY, VERB_ANY, 0, do_condition },
889d695d
JK
973 { "verify", 2, VERB_ANY, 0, do_verify },
974 { "calendar", 2, VERB_ANY, 0, test_calendar },
2cae4711 975 { "timestamp", 2, VERB_ANY, 0, test_timestamp },
3f1c1287 976 { "timespan", 2, VERB_ANY, 0, dump_timespan },
ec16f3b6 977 { "security", VERB_ANY, VERB_ANY, 0, do_security },
917e6554 978 { "inspect-elf", 2, VERB_ANY, 0, do_elf_inspection },
a6bcef29
LP
979 {}
980 };
981
5220a6f3 982 int r;
2265fbf7
SP
983
984 setlocale(LC_ALL, "");
c170f3a4 985 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
a6bcef29 986
d2acb93d 987 log_setup();
2265fbf7
SP
988
989 r = parse_argv(argc, argv);
9ea9d4cf 990 if (r <= 0)
d665c7b2 991 return r;
c170f3a4 992
e5ea5c3a
MG
993 /* Open up and mount the image */
994 if (arg_image) {
995 assert(!arg_root);
996
997 r = mount_image_privately_interactively(
998 arg_image,
999 DISSECT_IMAGE_GENERIC_ROOT |
1000 DISSECT_IMAGE_RELAX_VAR_CHECK |
1001 DISSECT_IMAGE_READ_ONLY,
1002 &unlink_dir,
1003 &loop_device,
1004 &decrypted_image);
1005 if (r < 0)
1006 return r;
1007
1008 arg_root = strdup(unlink_dir);
1009 if (!arg_root)
1010 return log_oom();
1011 }
1012
d665c7b2 1013 return dispatch_verb(argc, argv, verbs, NULL);
2265fbf7 1014}
d665c7b2
YW
1015
1016DEFINE_MAIN_FUNCTION(run);