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