1
0
mirror of https://github.com/systemd/systemd.git synced 2024-10-30 06:25:37 +03:00

Merge pull request #22585 from poettering/analyze-split-up

analyze: split out each verb into its own .c/.h pair
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2022-02-22 11:02:57 +01:00 committed by GitHub
commit 22049270c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 3056 additions and 2726 deletions

View File

@ -0,0 +1,65 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-blame.h"
#include "analyze-time-data.h"
#include "format-table.h"
int verb_blame(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
_cleanup_(table_unrefp) Table *table = NULL;
TableCell *cell;
int n, r;
r = acquire_bus(&bus, NULL);
if (r < 0)
return bus_log_connect_error(r, arg_transport);
n = acquire_time_data(bus, &times);
if (n <= 0)
return n;
table = table_new("time", "unit");
if (!table)
return log_oom();
table_set_header(table, false);
assert_se(cell = table_get_cell(table, 0, 0));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
return r;
r = table_set_align_percent(table, cell, 100);
if (r < 0)
return r;
assert_se(cell = table_get_cell(table, 0, 1));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
return r;
r = table_set_sort(table, (size_t) 0);
if (r < 0)
return r;
r = table_set_reverse(table, 0, true);
if (r < 0)
return r;
for (UnitTimes *u = times; u->has_data; u++) {
if (u->time <= 0)
continue;
r = table_add_many(table,
TABLE_TIMESPAN_MSEC, u->time,
TABLE_STRING, u->name);
if (r < 0)
return table_log_add_error(r);
}
pager_open(arg_pager_flags);
return table_print(table, NULL);
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_blame(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,145 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-calendar.h"
#include "calendarspec.h"
#include "format-table.h"
#include "terminal-util.h"
static int test_calendar_one(usec_t n, const char *p) {
_cleanup_(calendar_spec_freep) CalendarSpec *spec = NULL;
_cleanup_(table_unrefp) Table *table = NULL;
_cleanup_free_ char *t = NULL;
TableCell *cell;
int r;
r = calendar_spec_from_string(p, &spec);
if (r < 0) {
log_error_errno(r, "Failed to parse calendar specification '%s': %m", p);
time_parsing_hint(p, /* calendar= */ false, /* timestamp= */ true, /* timespan= */ true);
return r;
}
r = calendar_spec_to_string(spec, &t);
if (r < 0)
return log_error_errno(r, "Failed to format calendar specification '%s': %m", p);
table = table_new("name", "value");
if (!table)
return log_oom();
table_set_header(table, false);
assert_se(cell = table_get_cell(table, 0, 0));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
return r;
r = table_set_align_percent(table, cell, 100);
if (r < 0)
return r;
assert_se(cell = table_get_cell(table, 0, 1));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
return r;
if (!streq(t, p)) {
r = table_add_many(table,
TABLE_STRING, "Original form:",
TABLE_STRING, p);
if (r < 0)
return table_log_add_error(r);
}
r = table_add_many(table,
TABLE_STRING, "Normalized form:",
TABLE_STRING, t);
if (r < 0)
return table_log_add_error(r);
for (unsigned i = 0; i < arg_iterations; i++) {
usec_t next;
r = calendar_spec_next_usec(spec, n, &next);
if (r == -ENOENT) {
if (i == 0) {
r = table_add_many(table,
TABLE_STRING, "Next elapse:",
TABLE_STRING, "never",
TABLE_SET_COLOR, ansi_highlight_yellow());
if (r < 0)
return table_log_add_error(r);
}
break;
}
if (r < 0)
return log_error_errno(r, "Failed to determine next elapse for '%s': %m", p);
if (i == 0) {
r = table_add_many(table,
TABLE_STRING, "Next elapse:",
TABLE_TIMESTAMP, next,
TABLE_SET_COLOR, ansi_highlight_blue());
if (r < 0)
return table_log_add_error(r);
} else {
int k = DECIMAL_STR_WIDTH(i + 1);
if (k < 8)
k = 8 - k;
else
k = 0;
r = table_add_cell_stringf(table, NULL, "Iter. #%u:", i+1);
if (r < 0)
return table_log_add_error(r);
r = table_add_many(table,
TABLE_TIMESTAMP, next,
TABLE_SET_COLOR, ansi_highlight_blue());
if (r < 0)
return table_log_add_error(r);
}
if (!in_utc_timezone()) {
r = table_add_many(table,
TABLE_STRING, "(in UTC):",
TABLE_TIMESTAMP_UTC, next);
if (r < 0)
return table_log_add_error(r);
}
r = table_add_many(table,
TABLE_STRING, "From now:",
TABLE_TIMESTAMP_RELATIVE, next);
if (r < 0)
return table_log_add_error(r);
n = next;
}
return table_print(table, NULL);
}
int verb_calendar(int argc, char *argv[], void *userdata) {
int ret = 0, r;
char **p;
usec_t n;
if (arg_base_time != USEC_INFINITY)
n = arg_base_time;
else
n = now(CLOCK_REALTIME); /* We want to use the same "base" for all expressions */
STRV_FOREACH(p, strv_skip(argv, 1)) {
r = test_calendar_one(n, *p);
if (ret == 0 && r < 0)
ret = r;
if (*(p + 1))
putchar('\n');
}
return ret;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_calendar(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,52 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-capability.h"
#include "cap-list.h"
#include "capability-util.h"
#include "format-table.h"
int verb_capabilities(int argc, char *argv[], void *userdata) {
_cleanup_(table_unrefp) Table *table = NULL;
unsigned last_cap;
int r;
table = table_new("name", "number");
if (!table)
return log_oom();
(void) table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
/* Determine the maximum of the last cap known by the kernel and by us */
last_cap = MAX((unsigned) CAP_LAST_CAP, cap_last_cap());
if (strv_isempty(strv_skip(argv, 1)))
for (unsigned c = 0; c <= last_cap; c++) {
r = table_add_many(table,
TABLE_STRING, capability_to_name(c) ?: "cap_???",
TABLE_UINT, c);
if (r < 0)
return table_log_add_error(r);
}
else {
for (int i = 1; i < argc; i++) {
int c;
c = capability_from_name(argv[i]);
if (c < 0 || (unsigned) c > last_cap)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Capability \"%s\" not known.", argv[i]);
r = table_add_many(table,
TABLE_STRING, capability_to_name(c) ?: "cap_???",
TABLE_UINT, (unsigned) c);
if (r < 0)
return table_log_add_error(r);
}
(void) table_set_sort(table, (size_t) 1);
}
pager_open(arg_pager_flags);
return table_print(table, NULL);
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_capabilities(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,46 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-cat-config.h"
#include "conf-files.h"
#include "def.h"
#include "nulstr-util.h"
#include "path-util.h"
#include "pretty-print.h"
#include "strv.h"
int verb_cat_config(int argc, char *argv[], void *userdata) {
char **arg, **list;
int r;
pager_open(arg_pager_flags);
list = strv_skip(argv, 1);
STRV_FOREACH(arg, list) {
const char *t = NULL;
if (arg != list)
print_separator();
if (path_is_absolute(*arg)) {
const char *dir;
NULSTR_FOREACH(dir, CONF_PATHS_NULSTR("")) {
t = path_startswith(*arg, dir);
if (t)
break;
}
if (!t)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Path %s does not start with any known prefix.", *arg);
} else
t = *arg;
r = conf_files_cat(arg_root, t);
if (r < 0)
return r;
}
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_cat_config(int argc, char *argv[], void *userdata);

View File

@ -2,8 +2,9 @@
#include <stdlib.h>
#include "analyze.h"
#include "analyze-condition.h"
#include "analyze-verify.h"
#include "analyze-verify-util.h"
#include "condition.h"
#include "conf-parser.h"
#include "load-fragment.h"
@ -73,7 +74,7 @@ static int log_helper(void *userdata, int level, int error, const char *file, in
return r;
}
int verify_conditions(char **lines, UnitFileScope scope, const char *unit, const char *root) {
static int verify_conditions(char **lines, UnitFileScope scope, const char *unit, const char *root) {
_cleanup_(manager_freep) Manager *m = NULL;
Unit *u;
int r, q = 1;
@ -136,3 +137,7 @@ int verify_conditions(char **lines, UnitFileScope scope, const char *unit, const
return r > 0 && q > 0 ? 0 : -EIO;
}
int verb_condition(int argc, char *argv[], void *userdata) {
return verify_conditions(strv_skip(argv, 1), arg_scope, arg_unit, arg_root);
}

View File

@ -1,6 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "install.h"
int verify_conditions(char **lines, UnitFileScope scope, const char *unit, const char *root);
int verb_condition(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,237 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-critical-chain.h"
#include "analyze-time-data.h"
#include "strv.h"
#include "copy.h"
#include "path-util.h"
#include "terminal-util.h"
#include "sort-util.h"
#include "special.h"
#include "bus-error.h"
static int list_dependencies_print(
const char *name,
unsigned level,
unsigned branches,
bool last,
UnitTimes *times,
BootTimes *boot) {
for (unsigned i = level; i != 0; i--)
printf("%s", special_glyph(branches & (1 << (i-1)) ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE));
printf("%s", special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH));
if (times) {
if (times->time > 0)
printf("%s%s @%s +%s%s", ansi_highlight_red(), name,
FORMAT_TIMESPAN(times->activating - boot->userspace_time, USEC_PER_MSEC),
FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal());
else if (times->activated > boot->userspace_time)
printf("%s @%s", name, FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC));
else
printf("%s", name);
} else
printf("%s", name);
printf("\n");
return 0;
}
static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
_cleanup_free_ char *path = NULL;
assert(bus);
assert(name);
assert(deps);
path = unit_dbus_path_from_name(name);
if (!path)
return -ENOMEM;
return bus_get_unit_property_strv(bus, path, "After", deps);
}
static Hashmap *unit_times_hashmap;
static int list_dependencies_compare(char *const *a, char *const *b) {
usec_t usa = 0, usb = 0;
UnitTimes *times;
times = hashmap_get(unit_times_hashmap, *a);
if (times)
usa = times->activated;
times = hashmap_get(unit_times_hashmap, *b);
if (times)
usb = times->activated;
return CMP(usb, usa);
}
static bool times_in_range(const UnitTimes *times, const BootTimes *boot) {
return times && times->activated > 0 && times->activated <= boot->finish_time;
}
static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level, char ***units, unsigned branches) {
_cleanup_strv_free_ char **deps = NULL;
char **c;
int r;
usec_t service_longest = 0;
int to_print = 0;
UnitTimes *times;
BootTimes *boot;
if (strv_extend(units, name))
return log_oom();
r = list_dependencies_get_dependencies(bus, name, &deps);
if (r < 0)
return r;
typesafe_qsort(deps, strv_length(deps), list_dependencies_compare);
r = acquire_boot_times(bus, &boot);
if (r < 0)
return r;
STRV_FOREACH(c, deps) {
times = hashmap_get(unit_times_hashmap, *c); /* lgtm [cpp/inconsistent-null-check] */
if (times_in_range(times, boot) && times->activated >= service_longest)
service_longest = times->activated;
}
if (service_longest == 0)
return r;
STRV_FOREACH(c, deps) {
times = hashmap_get(unit_times_hashmap, *c); /* lgtm [cpp/inconsistent-null-check] */
if (times_in_range(times, boot) && service_longest - times->activated <= arg_fuzz)
to_print++;
}
if (!to_print)
return r;
STRV_FOREACH(c, deps) {
times = hashmap_get(unit_times_hashmap, *c); /* lgtm [cpp/inconsistent-null-check] */
if (!times_in_range(times, boot) || service_longest - times->activated > arg_fuzz)
continue;
to_print--;
r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
if (r < 0)
return r;
if (strv_contains(*units, *c)) {
r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
true, NULL, boot);
if (r < 0)
return r;
continue;
}
r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (to_print ? 1 : 0));
if (r < 0)
return r;
if (to_print == 0)
break;
}
return 0;
}
static int list_dependencies(sd_bus *bus, const char *name) {
_cleanup_strv_free_ char **units = NULL;
UnitTimes *times;
int r;
const char *id;
_cleanup_free_ char *path = NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
BootTimes *boot;
assert(bus);
path = unit_dbus_path_from_name(name);
if (!path)
return -ENOMEM;
r = sd_bus_get_property(
bus,
"org.freedesktop.systemd1",
path,
"org.freedesktop.systemd1.Unit",
"Id",
&error,
&reply,
"s");
if (r < 0)
return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r));
r = sd_bus_message_read(reply, "s", &id);
if (r < 0)
return bus_log_parse_error(r);
times = hashmap_get(unit_times_hashmap, id);
r = acquire_boot_times(bus, &boot);
if (r < 0)
return r;
if (times) {
if (times->time)
printf("%s%s +%s%s\n", ansi_highlight_red(), id,
FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal());
else if (times->activated > boot->userspace_time)
printf("%s @%s\n", id,
FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC));
else
printf("%s\n", id);
}
return list_dependencies_one(bus, name, 0, &units, 0);
}
int verb_critical_chain(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
Hashmap *h;
int n, r;
r = acquire_bus(&bus, NULL);
if (r < 0)
return bus_log_connect_error(r, arg_transport);
n = acquire_time_data(bus, &times);
if (n <= 0)
return n;
h = hashmap_new(&string_hash_ops);
if (!h)
return log_oom();
for (UnitTimes *u = times; u->has_data; u++) {
r = hashmap_put(h, u->name, u);
if (r < 0)
return log_error_errno(r, "Failed to add entry to hashmap: %m");
}
unit_times_hashmap = h;
pager_open(arg_pager_flags);
puts("The time when unit became active or started is printed after the \"@\" character.\n"
"The time the unit took to start is printed after the \"+\" character.\n");
if (argc > 1) {
char **name;
STRV_FOREACH(name, strv_skip(argv, 1))
list_dependencies(bus, *name);
} else
list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
h = hashmap_free(h);
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_critical_chain(int argc, char *argv[], void *userdata);

184
src/analyze/analyze-dot.c Normal file
View File

@ -0,0 +1,184 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-dot.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "bus-unit-util.h"
#include "glob-util.h"
#include "terminal-util.h"
static int graph_one_property(
sd_bus *bus,
const UnitInfo *u,
const char *prop,
const char *color,
char *patterns[],
char *from_patterns[],
char *to_patterns[]) {
_cleanup_strv_free_ char **units = NULL;
char **unit;
int r;
bool match_patterns;
assert(u);
assert(prop);
assert(color);
match_patterns = strv_fnmatch(patterns, u->id);
if (!strv_isempty(from_patterns) && !match_patterns && !strv_fnmatch(from_patterns, u->id))
return 0;
r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units);
if (r < 0)
return r;
STRV_FOREACH(unit, units) {
bool match_patterns2;
match_patterns2 = strv_fnmatch(patterns, *unit);
if (!strv_isempty(to_patterns) && !match_patterns2 && !strv_fnmatch(to_patterns, *unit))
continue;
if (!strv_isempty(patterns) && !match_patterns && !match_patterns2)
continue;
printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color);
}
return 0;
}
static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *from_patterns[], char *to_patterns[]) {
int r;
assert(bus);
assert(u);
if (IN_SET(arg_dot, DEP_ORDER, DEP_ALL)) {
r = graph_one_property(bus, u, "After", "green", patterns, from_patterns, to_patterns);
if (r < 0)
return r;
}
if (IN_SET(arg_dot, DEP_REQUIRE, DEP_ALL)) {
r = graph_one_property(bus, u, "Requires", "black", patterns, from_patterns, to_patterns);
if (r < 0)
return r;
r = graph_one_property(bus, u, "Requisite", "darkblue", patterns, from_patterns, to_patterns);
if (r < 0)
return r;
r = graph_one_property(bus, u, "Wants", "grey66", patterns, from_patterns, to_patterns);
if (r < 0)
return r;
r = graph_one_property(bus, u, "Conflicts", "red", patterns, from_patterns, to_patterns);
if (r < 0)
return r;
}
return 0;
}
static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) {
_cleanup_strv_free_ char **expanded_patterns = NULL;
char **pattern;
int r;
STRV_FOREACH(pattern, patterns) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *unit = NULL, *unit_id = NULL;
if (strv_extend(&expanded_patterns, *pattern) < 0)
return log_oom();
if (string_is_glob(*pattern))
continue;
unit = unit_dbus_path_from_name(*pattern);
if (!unit)
return log_oom();
r = sd_bus_get_property_string(
bus,
"org.freedesktop.systemd1",
unit,
"org.freedesktop.systemd1.Unit",
"Id",
&error,
&unit_id);
if (r < 0)
return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r));
if (!streq(*pattern, unit_id)) {
if (strv_extend(&expanded_patterns, unit_id) < 0)
return log_oom();
}
}
*ret = TAKE_PTR(expanded_patterns); /* do not free */
return 0;
}
int verb_dot(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_strv_free_ char **expanded_patterns = NULL;
_cleanup_strv_free_ char **expanded_from_patterns = NULL;
_cleanup_strv_free_ char **expanded_to_patterns = NULL;
int r;
UnitInfo u;
r = acquire_bus(&bus, NULL);
if (r < 0)
return bus_log_connect_error(r, arg_transport);
r = expand_patterns(bus, strv_skip(argv, 1), &expanded_patterns);
if (r < 0)
return r;
r = expand_patterns(bus, arg_dot_from_patterns, &expanded_from_patterns);
if (r < 0)
return r;
r = expand_patterns(bus, arg_dot_to_patterns, &expanded_to_patterns);
if (r < 0)
return r;
r = bus_call_method(bus, bus_systemd_mgr, "ListUnits", &error, &reply, NULL);
if (r < 0)
log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
if (r < 0)
return bus_log_parse_error(r);
printf("digraph systemd {\n");
while ((r = bus_parse_unit_info(reply, &u)) > 0) {
r = graph_one(bus, &u, expanded_patterns, expanded_from_patterns, expanded_to_patterns);
if (r < 0)
return r;
}
if (r < 0)
return bus_log_parse_error(r);
printf("}\n");
log_info(" Color legend: black = Requires\n"
" dark blue = Requisite\n"
" dark grey = Wants\n"
" red = Conflicts\n"
" green = After\n");
if (on_tty() && !arg_quiet)
log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
"-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_dot(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,64 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-bus.h"
#include "analyze-dump.h"
#include "analyze.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "bus-util.h"
#include "copy.h"
static int dump_fallback(sd_bus *bus) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
const char *text = NULL;
int r;
assert(bus);
r = bus_call_method(bus, bus_systemd_mgr, "Dump", &error, &reply, NULL);
if (r < 0)
return log_error_errno(r, "Failed to issue method call Dump: %s", bus_error_message(&error, r));
r = sd_bus_message_read(reply, "s", &text);
if (r < 0)
return bus_log_parse_error(r);
fputs(text, stdout);
return 0;
}
int verb_dump(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int fd = -1;
int r;
r = acquire_bus(&bus, NULL);
if (r < 0)
return bus_log_connect_error(r, arg_transport);
pager_open(arg_pager_flags);
if (!sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD))
return dump_fallback(bus);
r = bus_call_method(bus, bus_systemd_mgr, "DumpByFileDescriptor", &error, &reply, NULL);
if (r < 0) {
/* fall back to Dump if DumpByFileDescriptor is not supported */
if (!IN_SET(r, -EACCES, -EBADR))
return log_error_errno(r, "Failed to issue method call DumpByFileDescriptor: %s",
bus_error_message(&error, r));
return dump_fallback(bus);
}
r = sd_bus_message_read(reply, "h", &fd);
if (r < 0)
return bus_log_parse_error(r);
fflush(stdout);
return copy_bytes(fd, STDOUT_FILENO, UINT64_MAX, 0);
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_dump(int argc, char *argv[], void *userdata);

View File

@ -1,6 +0,0 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "json.h"
int analyze_elf(char **filenames, JsonFormatFlags json_flags);

View File

@ -0,0 +1,52 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-exit-status.h"
#include "exit-status.h"
#include "format-table.h"
int verb_exit_status(int argc, char *argv[], void *userdata) {
_cleanup_(table_unrefp) Table *table = NULL;
int r;
table = table_new("name", "status", "class");
if (!table)
return log_oom();
r = table_set_align_percent(table, table_get_cell(table, 0, 1), 100);
if (r < 0)
return log_error_errno(r, "Failed to right-align status: %m");
if (strv_isempty(strv_skip(argv, 1)))
for (size_t i = 0; i < ELEMENTSOF(exit_status_mappings); i++) {
if (!exit_status_mappings[i].name)
continue;
r = table_add_many(table,
TABLE_STRING, exit_status_mappings[i].name,
TABLE_INT, (int) i,
TABLE_STRING, exit_status_class(i));
if (r < 0)
return table_log_add_error(r);
}
else
for (int i = 1; i < argc; i++) {
int status;
status = exit_status_from_string(argv[i]);
if (status < 0)
return log_error_errno(status, "Invalid exit status \"%s\".", argv[i]);
assert(status >= 0 && (size_t) status < ELEMENTSOF(exit_status_mappings));
r = table_add_many(table,
TABLE_STRING, exit_status_mappings[status].name ?: "-",
TABLE_INT, status,
TABLE_STRING, exit_status_class(status) ?: "-");
if (r < 0)
return table_log_add_error(r);
}
pager_open(arg_pager_flags);
return table_print(table, NULL);
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_exit_status(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,230 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-filesystems.h"
#include "fileio.h"
#include "filesystems.h"
#include "set.h"
#include "strv.h"
#include "terminal-util.h"
static int load_available_kernel_filesystems(Set **ret) {
_cleanup_set_free_ Set *filesystems = NULL;
_cleanup_free_ char *t = NULL;
int r;
assert(ret);
/* Let's read the available filesystems */
r = read_virtual_file("/proc/filesystems", SIZE_MAX, &t, NULL);
if (r < 0)
return r;
for (int i = 0;;) {
_cleanup_free_ char *line = NULL;
const char *p;
r = string_extract_line(t, i++, &line);
if (r < 0)
return log_oom();
if (r == 0)
break;
if (!line)
line = t;
p = strchr(line, '\t');
if (!p)
continue;
p += strspn(p, WHITESPACE);
r = set_put_strdup(&filesystems, p);
if (r < 0)
return log_error_errno(r, "Failed to add filesystem to list: %m");
}
*ret = TAKE_PTR(filesystems);
return 0;
}
static void filesystem_set_remove(Set *s, const FilesystemSet *set) {
const char *filesystem;
NULSTR_FOREACH(filesystem, set->value) {
if (filesystem[0] == '@')
continue;
free(set_remove(s, filesystem));
}
}
static void dump_filesystem_set(const FilesystemSet *set) {
const char *filesystem;
int r;
if (!set)
return;
printf("%s%s%s\n"
" # %s\n",
ansi_highlight(),
set->name,
ansi_normal(),
set->help);
NULSTR_FOREACH(filesystem, set->value) {
const statfs_f_type_t *magic;
if (filesystem[0] == '@') {
printf(" %s%s%s\n", ansi_underline(), filesystem, ansi_normal());
continue;
}
r = fs_type_from_string(filesystem, &magic);
assert_se(r >= 0);
printf(" %s", filesystem);
for (size_t i = 0; magic[i] != 0; i++) {
const char *primary;
if (i == 0)
printf(" %s(magic: ", ansi_grey());
else
printf(", ");
printf("0x%llx", (unsigned long long) magic[i]);
primary = fs_type_to_string(magic[i]);
if (primary && !streq(primary, filesystem))
printf("[%s]", primary);
if (magic[i+1] == 0)
printf(")%s", ansi_normal());
}
printf("\n");
}
}
int verb_filesystems(int argc, char *argv[], void *userdata) {
bool first = true;
#if ! HAVE_LIBBPF
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not compiled with libbpf support, sorry.");
#endif
pager_open(arg_pager_flags);
if (strv_isempty(strv_skip(argv, 1))) {
_cleanup_set_free_ Set *kernel = NULL, *known = NULL;
const char *fs;
int k;
NULSTR_FOREACH(fs, filesystem_sets[FILESYSTEM_SET_KNOWN].value)
if (set_put_strdup(&known, fs) < 0)
return log_oom();
k = load_available_kernel_filesystems(&kernel);
for (FilesystemGroups i = 0; i < _FILESYSTEM_SET_MAX; i++) {
const FilesystemSet *set = filesystem_sets + i;
if (!first)
puts("");
dump_filesystem_set(set);
filesystem_set_remove(kernel, set);
if (i != FILESYSTEM_SET_KNOWN)
filesystem_set_remove(known, set);
first = false;
}
if (arg_quiet) /* Let's not show the extra stuff in quiet mode */
return 0;
if (!set_isempty(known)) {
_cleanup_free_ char **l = NULL;
char **filesystem;
printf("\n"
"# %sUngrouped filesystems%s (known but not included in any of the groups except @known):\n",
ansi_highlight(), ansi_normal());
l = set_get_strv(known);
if (!l)
return log_oom();
strv_sort(l);
STRV_FOREACH(filesystem, l) {
const statfs_f_type_t *magic;
bool is_primary = false;
assert_se(fs_type_from_string(*filesystem, &magic) >= 0);
for (size_t i = 0; magic[i] != 0; i++) {
const char *primary;
primary = fs_type_to_string(magic[i]);
assert(primary);
if (streq(primary, *filesystem))
is_primary = true;
}
if (!is_primary) {
log_debug("Skipping ungrouped file system '%s', because it's an alias for another one.", *filesystem);
continue;
}
printf("# %s\n", *filesystem);
}
}
if (k < 0) {
fputc('\n', stdout);
fflush(stdout);
log_notice_errno(k, "# Not showing unlisted filesystems, couldn't retrieve kernel filesystem list: %m");
} else if (!set_isempty(kernel)) {
_cleanup_free_ char **l = NULL;
char **filesystem;
printf("\n"
"# %sUnlisted filesystems%s (available to the local kernel, but not included in any of the groups listed above):\n",
ansi_highlight(), ansi_normal());
l = set_get_strv(kernel);
if (!l)
return log_oom();
strv_sort(l);
STRV_FOREACH(filesystem, l)
printf("# %s\n", *filesystem);
}
} else {
char **name;
STRV_FOREACH(name, strv_skip(argv, 1)) {
const FilesystemSet *set;
if (!first)
puts("");
set = filesystem_set_find(*name);
if (!set) {
/* make sure the error appears below normal output */
fflush(stdout);
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"Filesystem set \"%s\" not found.", *name);
}
dump_filesystem_set(set);
first = false;
}
}
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_filesystems(int argc, char *argv[], void *userdata);

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze-elf.h"
#include "analyze.h"
#include "analyze-inspect-elf.h"
#include "elf-util.h"
#include "errno-util.h"
#include "fd-util.h"
@ -10,7 +11,7 @@
#include "path-util.h"
#include "strv.h"
int analyze_elf(char **filenames, JsonFormatFlags json_flags) {
static int analyze_elf(char **filenames, JsonFormatFlags json_flags) {
char **filename;
int r;
@ -126,3 +127,9 @@ int analyze_elf(char **filenames, JsonFormatFlags json_flags) {
return 0;
}
int verb_elf_inspection(int argc, char *argv[], void *userdata) {
pager_open(arg_pager_flags);
return analyze_elf(strv_skip(argv, 1), arg_json_format_flags);
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_elf_inspection(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-log-control.h"
#include "verb-log-control.h"
int verb_log_control(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int r;
assert(IN_SET(argc, 1, 2));
r = acquire_bus(&bus, NULL);
if (r < 0)
return bus_log_connect_error(r, arg_transport);
return verb_log_control_common(bus, "org.freedesktop.systemd1", argv[0], argc == 2 ? argv[1] : NULL);
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_log_control(int argc, char *argv[], void *userdata);

395
src/analyze/analyze-plot.c Normal file
View File

@ -0,0 +1,395 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-plot.h"
#include "analyze-time-data.h"
#include "bus-error.h"
#include "bus-map-properties.h"
#include "sort-util.h"
#include "version.h"
#define SCALE_X (0.1 / 1000.0) /* pixels per us */
#define SCALE_Y (20.0)
#define svg(...) printf(__VA_ARGS__)
#define svg_bar(class, x1, x2, y) \
svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
(class), \
SCALE_X * (x1), SCALE_Y * (y), \
SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
#define svg_text(b, x, y, format, ...) \
do { \
svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
svg(format, ## __VA_ARGS__); \
svg("</text>\n"); \
} while (false)
typedef struct HostInfo {
char *hostname;
char *kernel_name;
char *kernel_release;
char *kernel_version;
char *os_pretty_name;
char *virtualization;
char *architecture;
} HostInfo;
static HostInfo* free_host_info(HostInfo *hi) {
if (!hi)
return NULL;
free(hi->hostname);
free(hi->kernel_name);
free(hi->kernel_release);
free(hi->kernel_version);
free(hi->os_pretty_name);
free(hi->virtualization);
free(hi->architecture);
return mfree(hi);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(HostInfo *, free_host_info);
static int acquire_host_info(sd_bus *bus, HostInfo **hi) {
static const struct bus_properties_map hostname_map[] = {
{ "Hostname", "s", NULL, offsetof(HostInfo, hostname) },
{ "KernelName", "s", NULL, offsetof(HostInfo, kernel_name) },
{ "KernelRelease", "s", NULL, offsetof(HostInfo, kernel_release) },
{ "KernelVersion", "s", NULL, offsetof(HostInfo, kernel_version) },
{ "OperatingSystemPrettyName", "s", NULL, offsetof(HostInfo, os_pretty_name) },
{}
};
static const struct bus_properties_map manager_map[] = {
{ "Virtualization", "s", NULL, offsetof(HostInfo, virtualization) },
{ "Architecture", "s", NULL, offsetof(HostInfo, architecture) },
{}
};
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *system_bus = NULL;
_cleanup_(free_host_infop) HostInfo *host = NULL;
int r;
host = new0(HostInfo, 1);
if (!host)
return log_oom();
if (arg_scope != UNIT_FILE_SYSTEM) {
r = bus_connect_transport(arg_transport, arg_host, false, &system_bus);
if (r < 0) {
log_debug_errno(r, "Failed to connect to system bus, ignoring: %m");
goto manager;
}
}
r = bus_map_all_properties(
system_bus ?: bus,
"org.freedesktop.hostname1",
"/org/freedesktop/hostname1",
hostname_map,
BUS_MAP_STRDUP,
&error,
NULL,
host);
if (r < 0) {
log_debug_errno(r, "Failed to get host information from systemd-hostnamed, ignoring: %s",
bus_error_message(&error, r));
sd_bus_error_free(&error);
}
manager:
r = bus_map_all_properties(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
manager_map,
BUS_MAP_STRDUP,
&error,
NULL,
host);
if (r < 0)
return log_error_errno(r, "Failed to get host information from systemd: %s",
bus_error_message(&error, r));
*hi = TAKE_PTR(host);
return 0;
}
static int compare_unit_start(const UnitTimes *a, const UnitTimes *b) {
return CMP(a->activating, b->activating);
}
static void svg_graph_box(double height, double begin, double end) {
/* outside box, fill */
svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
SCALE_X * (end - begin),
SCALE_Y * height);
for (long long i = ((long long) (begin / 100000)) * 100000; i <= end; i += 100000) {
/* lines for each second */
if (i % 5000000 == 0)
svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
SCALE_X * i,
SCALE_X * i,
SCALE_Y * height,
SCALE_X * i,
-5.0,
0.000001 * i);
else if (i % 1000000 == 0)
svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
" <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
SCALE_X * i,
SCALE_X * i,
SCALE_Y * height,
SCALE_X * i,
-5.0,
0.000001 * i);
else
svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
SCALE_X * i,
SCALE_X * i,
SCALE_Y * height);
}
}
static int plot_unit_times(UnitTimes *u, double width, int y) {
bool b;
if (!u->name)
return 0;
svg_bar("activating", u->activating, u->activated, y);
svg_bar("active", u->activated, u->deactivating, y);
svg_bar("deactivating", u->deactivating, u->deactivated, y);
/* place the text on the left if we have passed the half of the svg width */
b = u->activating * SCALE_X < width / 2;
if (u->time)
svg_text(b, u->activating, y, "%s (%s)",
u->name, FORMAT_TIMESPAN(u->time, USEC_PER_MSEC));
else
svg_text(b, u->activating, y, "%s", u->name);
return 1;
}
int verb_plot(int argc, char *argv[], void *userdata) {
_cleanup_(free_host_infop) HostInfo *host = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
_cleanup_free_ char *pretty_times = NULL;
bool use_full_bus = arg_scope == UNIT_FILE_SYSTEM;
BootTimes *boot;
UnitTimes *u;
int n, m = 1, y = 0, r;
double width;
r = acquire_bus(&bus, &use_full_bus);
if (r < 0)
return bus_log_connect_error(r, arg_transport);
n = acquire_boot_times(bus, &boot);
if (n < 0)
return n;
n = pretty_boot_time(bus, &pretty_times);
if (n < 0)
return n;
if (use_full_bus || arg_scope != UNIT_FILE_SYSTEM) {
n = acquire_host_info(bus, &host);
if (n < 0)
return n;
}
n = acquire_time_data(bus, &times);
if (n <= 0)
return n;
typesafe_qsort(times, n, compare_unit_start);
width = SCALE_X * (boot->firmware_time + boot->finish_time);
if (width < 800.0)
width = 800.0;
if (boot->firmware_time > boot->loader_time)
m++;
if (boot->loader_time > 0) {
m++;
if (width < 1000.0)
width = 1000.0;
}
if (boot->initrd_time > 0)
m++;
if (boot->kernel_done_time > 0)
m++;
for (u = times; u->has_data; u++) {
double text_start, text_width;
if (u->activating > boot->finish_time) {
u->name = mfree(u->name);
continue;
}
/* If the text cannot fit on the left side then
* increase the svg width so it fits on the right.
* TODO: calculate the text width more accurately */
text_width = 8.0 * strlen(u->name);
text_start = (boot->firmware_time + u->activating) * SCALE_X;
if (text_width > text_start && text_width + text_start > width)
width = text_width + text_start;
if (u->deactivated > u->activating &&
u->deactivated <= boot->finish_time &&
u->activated == 0 && u->deactivating == 0)
u->activated = u->deactivating = u->deactivated;
if (u->activated < u->activating || u->activated > boot->finish_time)
u->activated = boot->finish_time;
if (u->deactivating < u->activated || u->deactivating > boot->finish_time)
u->deactivating = boot->finish_time;
if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
u->deactivated = boot->finish_time;
m++;
}
svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
"\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
"xmlns=\"http://www.w3.org/2000/svg\">\n\n",
80.0 + width, 150.0 + (m * SCALE_Y) +
5 * SCALE_Y /* legend */);
/* write some basic info as a comment, including some help */
svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
"<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
"<!-- that render these files properly but much slower are ImageMagick, -->\n"
"<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
"<!-- point your browser to this file. -->\n\n"
"<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", GIT_VERSION);
/* style sheet */
svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
" rect { stroke-width: 1; stroke-opacity: 0; }\n"
" rect.background { fill: rgb(255,255,255); }\n"
" rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
" rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
" rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
" rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
" rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
" rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
" rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
" rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
" rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
" rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
" rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
" rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
" line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
"// line.sec1 { }\n"
" line.sec5 { stroke-width: 2; }\n"
" line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
" text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
" text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
" text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
" text.sec { font-size: 10px; }\n"
" ]]>\n </style>\n</defs>\n\n");
svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
if (host)
svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
isempty(host->os_pretty_name) ? "Linux" : host->os_pretty_name,
strempty(host->hostname),
strempty(host->kernel_name),
strempty(host->kernel_release),
strempty(host->kernel_version),
strempty(host->architecture),
strempty(host->virtualization));
svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time);
if (boot->firmware_time > 0) {
svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
svg_text(true, -(double) boot->firmware_time, y, "firmware");
y++;
}
if (boot->loader_time > 0) {
svg_bar("loader", -(double) boot->loader_time, 0, y);
svg_text(true, -(double) boot->loader_time, y, "loader");
y++;
}
if (boot->kernel_done_time > 0) {
svg_bar("kernel", 0, boot->kernel_done_time, y);
svg_text(true, 0, y, "kernel");
y++;
}
if (boot->initrd_time > 0) {
svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
if (boot->initrd_security_start_time < boot->initrd_security_finish_time)
svg_bar("security", boot->initrd_security_start_time, boot->initrd_security_finish_time, y);
if (boot->initrd_generators_start_time < boot->initrd_generators_finish_time)
svg_bar("generators", boot->initrd_generators_start_time, boot->initrd_generators_finish_time, y);
if (boot->initrd_unitsload_start_time < boot->initrd_unitsload_finish_time)
svg_bar("unitsload", boot->initrd_unitsload_start_time, boot->initrd_unitsload_finish_time, y);
svg_text(true, boot->initrd_time, y, "initrd");
y++;
}
for (u = times; u->has_data; u++) {
if (u->activating >= boot->userspace_time)
break;
y += plot_unit_times(u, width, y);
}
svg_bar("active", boot->userspace_time, boot->finish_time, y);
if (boot->security_start_time > 0)
svg_bar("security", boot->security_start_time, boot->security_finish_time, y);
svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y);
svg_text(true, boot->userspace_time, y, "systemd");
y++;
for (; u->has_data; u++)
y += plot_unit_times(u, width, y);
svg("</g>\n");
/* Legend */
svg("<g transform=\"translate(20,100)\">\n");
y++;
svg_bar("activating", 0, 300000, y);
svg_text(true, 400000, y, "Activating");
y++;
svg_bar("active", 0, 300000, y);
svg_text(true, 400000, y, "Active");
y++;
svg_bar("deactivating", 0, 300000, y);
svg_text(true, 400000, y, "Deactivating");
y++;
if (boot->security_start_time > 0) {
svg_bar("security", 0, 300000, y);
svg_text(true, 400000, y, "Setting up security module");
y++;
}
svg_bar("generators", 0, 300000, y);
svg_text(true, 400000, y, "Generators");
y++;
svg_bar("unitsload", 0, 300000, y);
svg_text(true, 400000, y, "Loading unit files");
y++;
svg("</g>\n\n");
svg("</svg>\n");
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_plot(int argc, char *argv[], void *userdata);

View File

@ -3,6 +3,7 @@
#include <sys/utsname.h>
#include "af-list.h"
#include "analyze.h"
#include "analyze-security.h"
#include "analyze-verify.h"
#include "bus-error.h"
@ -11,6 +12,8 @@
#include "bus-util.h"
#include "copy.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "in-addr-prefix-util.h"
#include "locale-util.h"
@ -2753,7 +2756,7 @@ static int offline_security_checks(char **filenames,
return r;
}
int analyze_security(sd_bus *bus,
static int analyze_security(sd_bus *bus,
char **units,
JsonVariant *policy,
UnitFileScope scope,
@ -2886,3 +2889,51 @@ int analyze_security(sd_bus *bus,
}
return ret;
}
int verb_security(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *policy = NULL;
int r;
unsigned line, column;
if (!arg_offline) {
r = acquire_bus(&bus, NULL);
if (r < 0)
return bus_log_connect_error(r, arg_transport);
}
pager_open(arg_pager_flags);
if (arg_security_policy) {
r = json_parse_file(/*f=*/ NULL, arg_security_policy, /*flags=*/ 0, &policy, &line, &column);
if (r < 0)
return log_error_errno(r, "Failed to parse '%s' at %u:%u: %m", arg_security_policy, line, column);
} else {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *pp = NULL;
r = search_and_fopen_nulstr("systemd-analyze-security.policy", "re", /*root=*/ NULL, CONF_PATHS_NULSTR("systemd"), &f, &pp);
if (r < 0 && r != -ENOENT)
return r;
if (f) {
r = json_parse_file(f, pp, /*flags=*/ 0, &policy, &line, &column);
if (r < 0)
return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON policy: %m", pp, line, column);
}
}
return analyze_security(bus,
strv_skip(argv, 1),
policy,
arg_scope,
arg_man,
arg_generators,
arg_offline,
arg_threshold,
arg_root,
arg_profile,
arg_json_format_flags,
arg_pager_flags,
/*flags=*/ 0);
}

View File

@ -1,30 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <stdbool.h>
#include "sd-bus.h"
#include "json.h"
#include "pager.h"
#include "unit-file.h"
typedef enum AnalyzeSecurityFlags {
ANALYZE_SECURITY_SHORT = 1 << 0,
ANALYZE_SECURITY_ONLY_LOADED = 1 << 1,
ANALYZE_SECURITY_ONLY_LONG_RUNNING = 1 << 2,
} AnalyzeSecurityFlags;
int analyze_security(sd_bus *bus,
char **units,
JsonVariant *policy,
UnitFileScope scope,
bool check_man,
bool run_generators,
bool offline,
unsigned threshold,
const char *root,
const char *profile,
JsonFormatFlags json_format_flags,
PagerFlags pager_flags,
AnalyzeSecurityFlags flags);
int verb_security(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,41 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-service-watchdogs.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "parse-util.h"
int verb_service_watchdogs(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
int b, r;
assert(IN_SET(argc, 1, 2));
assert(argv);
r = acquire_bus(&bus, NULL);
if (r < 0)
return bus_log_connect_error(r, arg_transport);
if (argc == 1) {
/* get ServiceWatchdogs */
r = bus_get_property_trivial(bus, bus_systemd_mgr, "ServiceWatchdogs", &error, 'b', &b);
if (r < 0)
return log_error_errno(r, "Failed to get service-watchdog state: %s", bus_error_message(&error, r));
printf("%s\n", yes_no(!!b));
} else {
/* set ServiceWatchdogs */
b = parse_boolean(argv[1]);
if (b < 0)
return log_error_errno(b, "Failed to parse service-watchdogs argument: %m");
r = bus_set_property(bus, bus_systemd_mgr, "ServiceWatchdogs", &error, "b", b);
if (r < 0)
return log_error_errno(r, "Failed to set service-watchdog state: %s", bus_error_message(&error, r));
}
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_service_watchdogs(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,191 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze-syscall-filter.h"
#include "analyze.h"
#include "fd-util.h"
#include "fileio.h"
#include "nulstr-util.h"
#include "seccomp-util.h"
#include "set.h"
#include "strv.h"
#include "terminal-util.h"
#if HAVE_SECCOMP
static int load_kernel_syscalls(Set **ret) {
_cleanup_set_free_ Set *syscalls = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
/* Let's read the available system calls from the list of available tracing events. Slightly dirty,
* but good enough for analysis purposes. */
f = fopen("/sys/kernel/tracing/available_events", "re");
if (!f) {
/* We tried the non-debugfs mount point and that didn't work. If it wasn't mounted, maybe the
* old debugfs mount point works? */
f = fopen("/sys/kernel/debug/tracing/available_events", "re");
if (!f)
return log_full_errno(IN_SET(errno, EPERM, EACCES, ENOENT) ? LOG_DEBUG : LOG_WARNING, errno,
"Can't read open tracefs' available_events file: %m");
}
for (;;) {
_cleanup_free_ char *line = NULL;
const char *e;
r = read_line(f, LONG_LINE_MAX, &line);
if (r < 0)
return log_error_errno(r, "Failed to read system call list: %m");
if (r == 0)
break;
e = startswith(line, "syscalls:sys_enter_");
if (!e)
continue;
/* These are named differently inside the kernel than their external name for historical
* reasons. Let's hide them here. */
if (STR_IN_SET(e, "newuname", "newfstat", "newstat", "newlstat", "sysctl"))
continue;
r = set_put_strdup(&syscalls, e);
if (r < 0)
return log_error_errno(r, "Failed to add system call to list: %m");
}
*ret = TAKE_PTR(syscalls);
return 0;
}
static void syscall_set_remove(Set *s, const SyscallFilterSet *set) {
const char *syscall;
if (!set)
return;
NULSTR_FOREACH(syscall, set->value) {
if (syscall[0] == '@')
continue;
free(set_remove(s, syscall));
}
}
static void dump_syscall_filter(const SyscallFilterSet *set) {
const char *syscall;
printf("%s%s%s\n"
" # %s\n",
ansi_highlight(),
set->name,
ansi_normal(),
set->help);
NULSTR_FOREACH(syscall, set->value)
printf(" %s%s%s\n", syscall[0] == '@' ? ansi_underline() : "", syscall, ansi_normal());
}
int verb_syscall_filters(int argc, char *argv[], void *userdata) {
bool first = true;
pager_open(arg_pager_flags);
if (strv_isempty(strv_skip(argv, 1))) {
_cleanup_set_free_ Set *kernel = NULL, *known = NULL;
const char *sys;
int k = 0; /* explicit initialization to appease gcc */
NULSTR_FOREACH(sys, syscall_filter_sets[SYSCALL_FILTER_SET_KNOWN].value)
if (set_put_strdup(&known, sys) < 0)
return log_oom();
if (!arg_quiet)
k = load_kernel_syscalls(&kernel);
for (int i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) {
const SyscallFilterSet *set = syscall_filter_sets + i;
if (!first)
puts("");
dump_syscall_filter(set);
syscall_set_remove(kernel, set);
if (i != SYSCALL_FILTER_SET_KNOWN)
syscall_set_remove(known, set);
first = false;
}
if (arg_quiet) /* Let's not show the extra stuff in quiet mode */
return 0;
if (!set_isempty(known)) {
_cleanup_free_ char **l = NULL;
char **syscall;
printf("\n"
"# %sUngrouped System Calls%s (known but not included in any of the groups except @known):\n",
ansi_highlight(), ansi_normal());
l = set_get_strv(known);
if (!l)
return log_oom();
strv_sort(l);
STRV_FOREACH(syscall, l)
printf("# %s\n", *syscall);
}
if (k < 0) {
fputc('\n', stdout);
fflush(stdout);
if (!arg_quiet)
log_notice_errno(k, "# Not showing unlisted system calls, couldn't retrieve kernel system call list: %m");
} else if (!set_isempty(kernel)) {
_cleanup_free_ char **l = NULL;
char **syscall;
printf("\n"
"# %sUnlisted System Calls%s (supported by the local kernel, but not included in any of the groups listed above):\n",
ansi_highlight(), ansi_normal());
l = set_get_strv(kernel);
if (!l)
return log_oom();
strv_sort(l);
STRV_FOREACH(syscall, l)
printf("# %s\n", *syscall);
}
} else {
char **name;
STRV_FOREACH(name, strv_skip(argv, 1)) {
const SyscallFilterSet *set;
if (!first)
puts("");
set = syscall_filter_set_find(*name);
if (!set) {
/* make sure the error appears below normal output */
fflush(stdout);
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"Filter set \"%s\" not found.", *name);
}
dump_syscall_filter(set);
first = false;
}
}
return 0;
}
#else
int dump_syscall_filters(int argc, char *argv[], void *userdata) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not compiled with syscall filters, sorry.");
}
#endif

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_syscall_filters(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,297 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-time-data.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "bus-map-properties.h"
#include "bus-unit-util.h"
#include "special.h"
static void subtract_timestamp(usec_t *a, usec_t b) {
assert(a);
if (*a > 0) {
assert(*a >= b);
*a -= b;
}
}
int acquire_boot_times(sd_bus *bus, BootTimes **ret) {
static const struct bus_properties_map property_map[] = {
{ "FirmwareTimestampMonotonic", "t", NULL, offsetof(BootTimes, firmware_time) },
{ "LoaderTimestampMonotonic", "t", NULL, offsetof(BootTimes, loader_time) },
{ "KernelTimestamp", "t", NULL, offsetof(BootTimes, kernel_time) },
{ "InitRDTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_time) },
{ "UserspaceTimestampMonotonic", "t", NULL, offsetof(BootTimes, userspace_time) },
{ "FinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, finish_time) },
{ "SecurityStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, security_start_time) },
{ "SecurityFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, security_finish_time) },
{ "GeneratorsStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, generators_start_time) },
{ "GeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, generators_finish_time) },
{ "UnitsLoadStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, unitsload_start_time) },
{ "UnitsLoadFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, unitsload_finish_time) },
{ "InitRDSecurityStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_security_start_time) },
{ "InitRDSecurityFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_security_finish_time) },
{ "InitRDGeneratorsStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_generators_start_time) },
{ "InitRDGeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_generators_finish_time) },
{ "InitRDUnitsLoadStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_unitsload_start_time) },
{ "InitRDUnitsLoadFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_unitsload_finish_time) },
{},
};
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
static BootTimes times;
static bool cached = false;
int r;
if (cached)
goto finish;
assert_cc(sizeof(usec_t) == sizeof(uint64_t));
r = bus_map_all_properties(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
property_map,
BUS_MAP_STRDUP,
&error,
NULL,
&times);
if (r < 0)
return log_error_errno(r, "Failed to get timestamp properties: %s", bus_error_message(&error, r));
if (times.finish_time <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS),
"Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64").\n"
"Please try again later.\n"
"Hint: Use 'systemctl%s list-jobs' to see active jobs",
times.finish_time,
arg_scope == UNIT_FILE_SYSTEM ? "" : " --user");
if (arg_scope == UNIT_FILE_SYSTEM && times.security_start_time > 0) {
/* security_start_time is set when systemd is not running under container environment. */
if (times.initrd_time > 0)
times.kernel_done_time = times.initrd_time;
else
times.kernel_done_time = times.userspace_time;
} else {
/*
* User-instance-specific or container-system-specific timestamps processing
* (see comment to reverse_offset in BootTimes).
*/
times.reverse_offset = times.userspace_time;
times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time =
times.userspace_time = times.security_start_time = times.security_finish_time = 0;
subtract_timestamp(&times.finish_time, times.reverse_offset);
subtract_timestamp(&times.generators_start_time, times.reverse_offset);
subtract_timestamp(&times.generators_finish_time, times.reverse_offset);
subtract_timestamp(&times.unitsload_start_time, times.reverse_offset);
subtract_timestamp(&times.unitsload_finish_time, times.reverse_offset);
}
cached = true;
finish:
*ret = &times;
return 0;
}
static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
int r;
assert(bus);
assert(path);
assert(interface);
assert(property);
assert(val);
r = sd_bus_get_property_trivial(
bus,
"org.freedesktop.systemd1",
path,
interface,
property,
&error,
't', val);
if (r < 0)
return log_error_errno(r, "Failed to parse reply: %s", bus_error_message(&error, r));
return 0;
}
int pretty_boot_time(sd_bus *bus, char **ret) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_free_ char *path = NULL, *unit_id = NULL, *text = NULL;
usec_t activated_time = USEC_INFINITY;
BootTimes *t;
int r;
r = acquire_boot_times(bus, &t);
if (r < 0)
return r;
path = unit_dbus_path_from_name(SPECIAL_DEFAULT_TARGET);
if (!path)
return log_oom();
r = sd_bus_get_property_string(
bus,
"org.freedesktop.systemd1",
path,
"org.freedesktop.systemd1.Unit",
"Id",
&error,
&unit_id);
if (r < 0)
log_warning_errno(r, "default.target doesn't seem to exist, ignoring: %s", bus_error_message(&error, r));
r = bus_get_uint64_property(bus, path,
"org.freedesktop.systemd1.Unit",
"ActiveEnterTimestampMonotonic",
&activated_time);
if (r < 0)
log_warning_errno(r, "Could not get time to reach default.target, ignoring: %m");
text = strdup("Startup finished in ");
if (!text)
return log_oom();
if (t->firmware_time > 0 && !strextend(&text, FORMAT_TIMESPAN(t->firmware_time - t->loader_time, USEC_PER_MSEC), " (firmware) + "))
return log_oom();
if (t->loader_time > 0 && !strextend(&text, FORMAT_TIMESPAN(t->loader_time, USEC_PER_MSEC), " (loader) + "))
return log_oom();
if (t->kernel_done_time > 0 && !strextend(&text, FORMAT_TIMESPAN(t->kernel_done_time, USEC_PER_MSEC), " (kernel) + "))
return log_oom();
if (t->initrd_time > 0 && !strextend(&text, FORMAT_TIMESPAN(t->userspace_time - t->initrd_time, USEC_PER_MSEC), " (initrd) + "))
return log_oom();
if (!strextend(&text, FORMAT_TIMESPAN(t->finish_time - t->userspace_time, USEC_PER_MSEC), " (userspace) "))
return log_oom();
if (t->kernel_done_time > 0)
if (!strextend(&text, "= ", FORMAT_TIMESPAN(t->firmware_time + t->finish_time, USEC_PER_MSEC), " "))
return log_oom();
if (unit_id && timestamp_is_set(activated_time)) {
usec_t base = t->userspace_time > 0 ? t->userspace_time : t->reverse_offset;
if (!strextend(&text, "\n", unit_id, " reached after ", FORMAT_TIMESPAN(activated_time - base, USEC_PER_MSEC), " in userspace."))
return log_oom();
} else if (unit_id && activated_time == 0) {
if (!strextend(&text, "\n", unit_id, " was never reached."))
return log_oom();
} else if (unit_id && activated_time == USEC_INFINITY) {
if (!strextend(&text, "\nCould not get time to reach ", unit_id, "."))
return log_oom();
} else if (!unit_id) {
if (!strextend(&text, "\ncould not find default.target."))
return log_oom();
}
*ret = TAKE_PTR(text);
return 0;
}
UnitTimes* unit_times_free_array(UnitTimes *t) {
if (!t)
return NULL;
for (UnitTimes *p = t; p->has_data; p++)
free(p->name);
return mfree(t);
}
int acquire_time_data(sd_bus *bus, UnitTimes **out) {
static const struct bus_properties_map property_map[] = {
{ "InactiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activating) },
{ "ActiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activated) },
{ "ActiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, deactivating) },
{ "InactiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, deactivated) },
{},
};
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_(unit_times_free_arrayp) UnitTimes *unit_times = NULL;
BootTimes *boot_times = NULL;
size_t c = 0;
UnitInfo u;
int r;
r = acquire_boot_times(bus, &boot_times);
if (r < 0)
return r;
r = bus_call_method(bus, bus_systemd_mgr, "ListUnits", &error, &reply, NULL);
if (r < 0)
return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
if (r < 0)
return bus_log_parse_error(r);
while ((r = bus_parse_unit_info(reply, &u)) > 0) {
UnitTimes *t;
if (!GREEDY_REALLOC(unit_times, c + 2))
return log_oom();
unit_times[c + 1].has_data = false;
t = &unit_times[c];
t->name = NULL;
assert_cc(sizeof(usec_t) == sizeof(uint64_t));
r = bus_map_all_properties(
bus,
"org.freedesktop.systemd1",
u.unit_path,
property_map,
BUS_MAP_STRDUP,
&error,
NULL,
t);
if (r < 0)
return log_error_errno(r, "Failed to get timestamp properties of unit %s: %s",
u.id, bus_error_message(&error, r));
subtract_timestamp(&t->activating, boot_times->reverse_offset);
subtract_timestamp(&t->activated, boot_times->reverse_offset);
subtract_timestamp(&t->deactivating, boot_times->reverse_offset);
subtract_timestamp(&t->deactivated, boot_times->reverse_offset);
if (t->activated >= t->activating)
t->time = t->activated - t->activating;
else if (t->deactivated >= t->activating)
t->time = t->deactivated - t->activating;
else
t->time = 0;
if (t->activating == 0)
continue;
t->name = strdup(u.id);
if (!t->name)
return log_oom();
t->has_data = true;
c++;
}
if (r < 0)
return bus_log_parse_error(r);
*out = TAKE_PTR(unit_times);
return c;
}

View File

@ -0,0 +1,54 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "time-util.h"
typedef struct BootTimes {
usec_t firmware_time;
usec_t loader_time;
usec_t kernel_time;
usec_t kernel_done_time;
usec_t initrd_time;
usec_t userspace_time;
usec_t finish_time;
usec_t security_start_time;
usec_t security_finish_time;
usec_t generators_start_time;
usec_t generators_finish_time;
usec_t unitsload_start_time;
usec_t unitsload_finish_time;
usec_t initrd_security_start_time;
usec_t initrd_security_finish_time;
usec_t initrd_generators_start_time;
usec_t initrd_generators_finish_time;
usec_t initrd_unitsload_start_time;
usec_t initrd_unitsload_finish_time;
/*
* If we're analyzing the user instance, all timestamps will be offset by its own start-up timestamp,
* which may be arbitrarily big. With "plot", this causes arbitrarily wide output SVG files which
* almost completely consist of empty space. Thus we cancel out this offset.
*
* This offset is subtracted from times above by acquire_boot_times(), but it still needs to be
* subtracted from unit-specific timestamps (so it is stored here for reference).
*/
usec_t reverse_offset;
} BootTimes;
typedef struct UnitTimes {
bool has_data;
char *name;
usec_t activating;
usec_t activated;
usec_t deactivated;
usec_t deactivating;
usec_t time;
} UnitTimes;
int acquire_boot_times(sd_bus *bus, BootTimes **ret);
int pretty_boot_time(sd_bus *bus, char **ret);
UnitTimes* unit_times_free_array(UnitTimes *t);
DEFINE_TRIVIAL_CLEANUP_FUNC(UnitTimes*, unit_times_free_array);
int acquire_time_data(sd_bus *bus, UnitTimes **out);

View File

@ -0,0 +1,22 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-time.h"
#include "analyze-time-data.h"
int verb_time(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_free_ char *buf = NULL;
int r;
r = acquire_bus(&bus, NULL);
if (r < 0)
return bus_log_connect_error(r, arg_transport);
r = pretty_boot_time(bus, &buf);
if (r < 0)
return r;
puts(buf);
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_time(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,74 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-timespan.h"
#include "calendarspec.h"
#include "format-table.h"
#include "glyph-util.h"
#include "strv.h"
#include "terminal-util.h"
int verb_timespan(int argc, char *argv[], void *userdata) {
char **input_timespan;
STRV_FOREACH(input_timespan, strv_skip(argv, 1)) {
_cleanup_(table_unrefp) Table *table = NULL;
usec_t output_usecs;
TableCell *cell;
int r;
r = parse_time(*input_timespan, &output_usecs, USEC_PER_SEC);
if (r < 0) {
log_error_errno(r, "Failed to parse time span '%s': %m", *input_timespan);
time_parsing_hint(*input_timespan, /* calendar= */ true, /* timestamp= */ true, /* timespan= */ false);
return r;
}
table = table_new("name", "value");
if (!table)
return log_oom();
table_set_header(table, false);
assert_se(cell = table_get_cell(table, 0, 0));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
return r;
r = table_set_align_percent(table, cell, 100);
if (r < 0)
return r;
assert_se(cell = table_get_cell(table, 0, 1));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
return r;
r = table_add_many(table,
TABLE_STRING, "Original:",
TABLE_STRING, *input_timespan);
if (r < 0)
return table_log_add_error(r);
r = table_add_cell_stringf(table, NULL, "%ss:", special_glyph(SPECIAL_GLYPH_MU));
if (r < 0)
return table_log_add_error(r);
r = table_add_many(table,
TABLE_UINT64, output_usecs,
TABLE_STRING, "Human:",
TABLE_TIMESPAN, output_usecs,
TABLE_SET_COLOR, ansi_highlight());
if (r < 0)
return table_log_add_error(r);
r = table_print(table, NULL);
if (r < 0)
return r;
if (input_timespan[1])
putchar('\n');
}
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_timespan(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,95 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-timestamp.h"
#include "format-table.h"
#include "terminal-util.h"
static int test_timestamp_one(const char *p) {
_cleanup_(table_unrefp) Table *table = NULL;
TableCell *cell;
usec_t usec;
int r;
r = parse_timestamp(p, &usec);
if (r < 0) {
log_error_errno(r, "Failed to parse \"%s\": %m", p);
time_parsing_hint(p, /* calendar= */ true, /* timestamp= */ false, /* timespan= */ true);
return r;
}
table = table_new("name", "value");
if (!table)
return log_oom();
table_set_header(table, false);
assert_se(cell = table_get_cell(table, 0, 0));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
return r;
r = table_set_align_percent(table, cell, 100);
if (r < 0)
return r;
assert_se(cell = table_get_cell(table, 0, 1));
r = table_set_ellipsize_percent(table, cell, 100);
if (r < 0)
return r;
r = table_add_many(table,
TABLE_STRING, "Original form:",
TABLE_STRING, p,
TABLE_STRING, "Normalized form:",
TABLE_TIMESTAMP, usec,
TABLE_SET_COLOR, ansi_highlight_blue());
if (r < 0)
return table_log_add_error(r);
if (!in_utc_timezone()) {
r = table_add_many(table,
TABLE_STRING, "(in UTC):",
TABLE_TIMESTAMP_UTC, usec);
if (r < 0)
return table_log_add_error(r);
}
r = table_add_cell(table, NULL, TABLE_STRING, "UNIX seconds:");
if (r < 0)
return table_log_add_error(r);
if (usec % USEC_PER_SEC == 0)
r = table_add_cell_stringf(table, NULL, "@%"PRI_USEC,
usec / USEC_PER_SEC);
else
r = table_add_cell_stringf(table, NULL, "@%"PRI_USEC".%06"PRI_USEC"",
usec / USEC_PER_SEC,
usec % USEC_PER_SEC);
if (r < 0)
return r;
r = table_add_many(table,
TABLE_STRING, "From now:",
TABLE_TIMESTAMP_RELATIVE, usec);
if (r < 0)
return table_log_add_error(r);
return table_print(table, NULL);
}
int verb_timestamp(int argc, char *argv[], void *userdata) {
int ret = 0, r;
char **p;
STRV_FOREACH(p, strv_skip(argv, 1)) {
r = test_timestamp_one(*p);
if (ret == 0 && r < 0)
ret = r;
if (*(p + 1))
putchar('\n');
}
return ret;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_timestamp(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,52 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-unit-files.h"
#include "path-lookup.h"
#include "strv.h"
static bool strv_fnmatch_strv_or_empty(char* const* patterns, char **strv, int flags) {
char **s;
STRV_FOREACH(s, strv)
if (strv_fnmatch_or_empty(patterns, *s, flags))
return true;
return false;
}
int verb_unit_files(int argc, char *argv[], void *userdata) {
_cleanup_hashmap_free_ Hashmap *unit_ids = NULL, *unit_names = NULL;
_cleanup_(lookup_paths_free) LookupPaths lp = {};
char **patterns = strv_skip(argv, 1);
const char *k, *dst;
char **v;
int r;
r = lookup_paths_init(&lp, arg_scope, 0, NULL);
if (r < 0)
return log_error_errno(r, "lookup_paths_init() failed: %m");
r = unit_file_build_name_map(&lp, NULL, &unit_ids, &unit_names, NULL);
if (r < 0)
return log_error_errno(r, "unit_file_build_name_map() failed: %m");
HASHMAP_FOREACH_KEY(dst, k, unit_ids) {
if (!strv_fnmatch_or_empty(patterns, k, FNM_NOESCAPE) &&
!strv_fnmatch_or_empty(patterns, dst, FNM_NOESCAPE))
continue;
printf("ids: %s → %s\n", k, dst);
}
HASHMAP_FOREACH_KEY(v, k, unit_names) {
if (!strv_fnmatch_or_empty(patterns, k, FNM_NOESCAPE) &&
!strv_fnmatch_strv_or_empty(patterns, v, FNM_NOESCAPE))
continue;
_cleanup_free_ char *j = strv_join(v, ", ");
printf("aliases: %s ← %s\n", k, j);
}
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_unit_files(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,21 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-unit-paths.h"
#include "path-lookup.h"
#include "strv.h"
int verb_unit_paths(int argc, char *argv[], void *userdata) {
_cleanup_(lookup_paths_free) LookupPaths paths = {};
int r;
char **p;
r = lookup_paths_init(&paths, arg_scope, 0, NULL);
if (r < 0)
return log_error_errno(r, "lookup_paths_init() failed: %m");
STRV_FOREACH(p, paths.search_path)
puts(*p);
return 0;
}

View File

@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_unit_paths(int argc, char *argv[], void *userdata);

View File

@ -0,0 +1,351 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdlib.h>
#include "all-units.h"
#include "alloc-util.h"
#include "analyze-verify-util.h"
#include "bus-error.h"
#include "bus-util.h"
#include "log.h"
#include "manager.h"
#include "pager.h"
#include "path-util.h"
#include "string-table.h"
#include "strv.h"
#include "unit-name.h"
#include "unit-serialize.h"
static void log_syntax_callback(const char *unit, int level, void *userdata) {
Set **s = userdata;
int r;
assert(userdata);
assert(unit);
if (level > LOG_WARNING)
return;
if (*s == POINTER_MAX)
return;
r = set_put_strdup(s, unit);
if (r < 0) {
set_free_free(*s);
*s = POINTER_MAX;
}
}
int verify_prepare_filename(const char *filename, char **ret) {
int r;
const char *name;
_cleanup_free_ char *abspath = NULL;
_cleanup_free_ char *dir = NULL;
_cleanup_free_ char *with_instance = NULL;
char *c;
assert(filename);
assert(ret);
r = path_make_absolute_cwd(filename, &abspath);
if (r < 0)
return r;
name = basename(abspath);
if (!unit_name_is_valid(name, UNIT_NAME_ANY))
return -EINVAL;
if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) {
r = unit_name_replace_instance(name, "i", &with_instance);
if (r < 0)
return r;
}
dir = dirname_malloc(abspath);
if (!dir)
return -ENOMEM;
c = path_join(dir, with_instance ?: name);
if (!c)
return -ENOMEM;
*ret = c;
return 0;
}
int verify_generate_path(char **var, char **filenames) {
const char *old;
char **filename;
_cleanup_strv_free_ char **ans = NULL;
int r;
STRV_FOREACH(filename, filenames) {
char *t;
t = dirname_malloc(*filename);
if (!t)
return -ENOMEM;
r = strv_consume(&ans, t);
if (r < 0)
return r;
}
assert_se(strv_uniq(ans));
/* First, prepend our directories. Second, if some path was specified, use that, and
* otherwise use the defaults. Any duplicates will be filtered out in path-lookup.c.
* Treat explicit empty path to mean that nothing should be appended.
*/
old = getenv("SYSTEMD_UNIT_PATH");
if (!streq_ptr(old, "")) {
if (!old)
old = ":";
r = strv_extend(&ans, old);
if (r < 0)
return r;
}
*var = strv_join(ans, ":");
if (!*var)
return -ENOMEM;
return 0;
}
static int verify_socket(Unit *u) {
Unit *service;
int r;
assert(u);
if (u->type != UNIT_SOCKET)
return 0;
r = socket_load_service_unit(SOCKET(u), -1, &service);
if (r < 0)
return log_unit_error_errno(u, r, "service unit for the socket cannot be loaded: %m");
if (service->load_state != UNIT_LOADED)
return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOENT),
"service %s not loaded, socket cannot be started.", service->id);
log_unit_debug(u, "using service unit %s.", service->id);
return 0;
}
int verify_executable(Unit *u, const ExecCommand *exec, const char *root) {
int r;
if (!exec)
return 0;
if (exec->flags & EXEC_COMMAND_IGNORE_FAILURE)
return 0;
r = find_executable_full(exec->path, root, NULL, false, NULL, NULL);
if (r < 0)
return log_unit_error_errno(u, r, "Command %s is not executable: %m", exec->path);
return 0;
}
static int verify_executables(Unit *u, const char *root) {
ExecCommand *exec;
int r = 0, k;
unsigned i;
assert(u);
exec = u->type == UNIT_SOCKET ? SOCKET(u)->control_command :
u->type == UNIT_MOUNT ? MOUNT(u)->control_command :
u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL;
k = verify_executable(u, exec, root);
if (k < 0 && r == 0)
r = k;
if (u->type == UNIT_SERVICE)
for (i = 0; i < ELEMENTSOF(SERVICE(u)->exec_command); i++) {
k = verify_executable(u, SERVICE(u)->exec_command[i], root);
if (k < 0 && r == 0)
r = k;
}
if (u->type == UNIT_SOCKET)
for (i = 0; i < ELEMENTSOF(SOCKET(u)->exec_command); i++) {
k = verify_executable(u, SOCKET(u)->exec_command[i], root);
if (k < 0 && r == 0)
r = k;
}
return r;
}
static int verify_documentation(Unit *u, bool check_man) {
char **p;
int r = 0, k;
STRV_FOREACH(p, u->documentation) {
log_unit_debug(u, "Found documentation item: %s", *p);
if (check_man && startswith(*p, "man:")) {
k = show_man_page(*p + 4, true);
if (k != 0) {
if (k < 0)
log_unit_error_errno(u, k, "Can't show %s: %m", *p + 4);
else {
log_unit_error(u, "Command 'man %s' failed with code %d", *p + 4, k);
k = -ENOEXEC;
}
if (r == 0)
r = k;
}
}
}
/* Check remote URLs? */
return r;
}
static int verify_unit(Unit *u, bool check_man, const char *root) {
_cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL;
int r, k;
assert(u);
if (DEBUG_LOGGING)
unit_dump(u, stdout, "\t");
log_unit_debug(u, "Creating %s/start job", u->id);
r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, NULL, &err, NULL);
if (r < 0)
log_unit_error_errno(u, r, "Failed to create %s/start: %s", u->id, bus_error_message(&err, r));
k = verify_socket(u);
if (k < 0 && r == 0)
r = k;
k = verify_executables(u, root);
if (k < 0 && r == 0)
r = k;
k = verify_documentation(u, check_man);
if (k < 0 && r == 0)
r = k;
return r;
}
static void set_destroy_ignore_pointer_max(Set** s) {
if (*s == POINTER_MAX)
return;
set_free_free(*s);
}
int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run_generators, RecursiveErrors recursive_errors, const char *root) {
const ManagerTestRunFlags flags =
MANAGER_TEST_RUN_MINIMAL |
MANAGER_TEST_RUN_ENV_GENERATORS |
(recursive_errors == RECURSIVE_ERRORS_NO) * MANAGER_TEST_RUN_IGNORE_DEPENDENCIES |
run_generators * MANAGER_TEST_RUN_GENERATORS;
_cleanup_(manager_freep) Manager *m = NULL;
_cleanup_(set_destroy_ignore_pointer_max) Set *s = NULL;
_unused_ _cleanup_(clear_log_syntax_callback) dummy_t dummy;
Unit *units[strv_length(filenames)];
_cleanup_free_ char *var = NULL;
int r, k, i, count = 0;
char **filename;
if (strv_isempty(filenames))
return 0;
/* Allow systemd-analyze to hook in a callback function so that it can get
* all the required log data from the function itself without having to rely
* on a global set variable for the same */
set_log_syntax_callback(log_syntax_callback, &s);
/* set the path */
r = verify_generate_path(&var, filenames);
if (r < 0)
return log_error_errno(r, "Failed to generate unit load path: %m");
assert_se(set_unit_path(var) >= 0);
r = manager_new(scope, flags, &m);
if (r < 0)
return log_error_errno(r, "Failed to initialize manager: %m");
log_debug("Starting manager...");
r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, root);
if (r < 0)
return r;
manager_clear_jobs(m);
log_debug("Loading remaining units from the command line...");
STRV_FOREACH(filename, filenames) {
_cleanup_free_ char *prepared = NULL;
log_debug("Handling %s...", *filename);
k = verify_prepare_filename(*filename, &prepared);
if (k < 0) {
log_error_errno(k, "Failed to prepare filename %s: %m", *filename);
if (r == 0)
r = k;
continue;
}
k = manager_load_startable_unit_or_warn(m, NULL, prepared, &units[count]);
if (k < 0) {
if (r == 0)
r = k;
continue;
}
count++;
}
for (i = 0; i < count; i++) {
k = verify_unit(units[i], check_man, root);
if (k < 0 && r == 0)
r = k;
}
if (s == POINTER_MAX)
return log_oom();
if (set_isempty(s) || r != 0)
return r;
/* If all previous verifications succeeded, then either the recursive parsing of all the
* associated dependencies with RECURSIVE_ERRORS_YES or the parsing of the specified unit file
* with RECURSIVE_ERRORS_NO must have yielded a syntax warning and hence, a non-empty set. */
if (IN_SET(recursive_errors, RECURSIVE_ERRORS_YES, RECURSIVE_ERRORS_NO))
return -ENOTRECOVERABLE;
/* If all previous verifications succeeded, then the non-empty set could have resulted from
* a syntax warning encountered during the recursive parsing of the specified unit file and
* its direct dependencies. Hence, search for any of the filenames in the set and if found,
* return a non-zero process exit status. */
if (recursive_errors == RECURSIVE_ERRORS_ONE)
STRV_FOREACH(filename, filenames)
if (set_contains(s, basename(*filename)))
return -ENOTRECOVERABLE;
return 0;
}
static const char* const recursive_errors_table[_RECURSIVE_ERRORS_MAX] = {
[RECURSIVE_ERRORS_NO] = "no",
[RECURSIVE_ERRORS_YES] = "yes",
[RECURSIVE_ERRORS_ONE] = "one",
};
DEFINE_STRING_TABLE_LOOKUP(recursive_errors, RecursiveErrors);

View File

@ -0,0 +1,23 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <stdbool.h>
#include "execute.h"
#include "path-lookup.h"
typedef enum RecursiveErrors {
RECURSIVE_ERRORS_YES, /* Look for errors in all associated units */
RECURSIVE_ERRORS_NO, /* Don't look for errors in any but the selected unit */
RECURSIVE_ERRORS_ONE, /* Look for errors in the selected unit and its direct dependencies */
_RECURSIVE_ERRORS_MAX,
_RECURSIVE_ERRORS_INVALID = -EINVAL,
} RecursiveErrors;
int verify_generate_path(char **var, char **filenames);
int verify_prepare_filename(const char *filename, char **ret);
int verify_executable(Unit *u, const ExecCommand *exec, const char *root);
int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run_generators, RecursiveErrors recursive_errors, const char *root);
const char* recursive_errors_to_string(RecursiveErrors i) _const_;
RecursiveErrors recursive_errors_from_string(const char *s) _pure_;

View File

@ -1,351 +1,71 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdlib.h>
#include "alloc-util.h"
#include "all-units.h"
#include "analyze.h"
#include "analyze-verify.h"
#include "bus-error.h"
#include "bus-util.h"
#include "log.h"
#include "manager.h"
#include "pager.h"
#include "path-util.h"
#include "string-table.h"
#include "strv.h"
#include "unit-name.h"
#include "unit-serialize.h"
#include "analyze-verify-util.h"
#include "copy.h"
#include "rm-rf.h"
#include "tmpfile-util.h"
static void log_syntax_callback(const char *unit, int level, void *userdata) {
Set **s = userdata;
static int process_aliases(char *argv[], char *tempdir, char ***ret) {
_cleanup_strv_free_ char **filenames = NULL;
char **filename;
int r;
assert(userdata);
assert(unit);
if (level > LOG_WARNING)
return;
if (*s == POINTER_MAX)
return;
r = set_put_strdup(s, unit);
if (r < 0) {
set_free_free(*s);
*s = POINTER_MAX;
}
}
int verify_prepare_filename(const char *filename, char **ret) {
int r;
const char *name;
_cleanup_free_ char *abspath = NULL;
_cleanup_free_ char *dir = NULL;
_cleanup_free_ char *with_instance = NULL;
char *c;
assert(filename);
assert(argv);
assert(tempdir);
assert(ret);
r = path_make_absolute_cwd(filename, &abspath);
if (r < 0)
return r;
STRV_FOREACH(filename, strv_skip(argv, 1)) {
_cleanup_free_ char *src = NULL, *dst = NULL, *base = NULL;
const char *parse_arg;
name = basename(abspath);
if (!unit_name_is_valid(name, UNIT_NAME_ANY))
return -EINVAL;
if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) {
r = unit_name_replace_instance(name, "i", &with_instance);
parse_arg = *filename;
r = extract_first_word(&parse_arg, &src, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_ESCAPE);
if (r < 0)
return r;
}
dir = dirname_malloc(abspath);
if (!dir)
return -ENOMEM;
if (!parse_arg) {
r = strv_consume(&filenames, TAKE_PTR(src));
if (r < 0)
return r;
c = path_join(dir, with_instance ?: name);
if (!c)
return -ENOMEM;
continue;
}
*ret = c;
return 0;
}
r = path_extract_filename(parse_arg, &base);
if (r < 0)
return r;
int verify_generate_path(char **var, char **filenames) {
const char *old;
char **filename;
_cleanup_strv_free_ char **ans = NULL;
int r;
STRV_FOREACH(filename, filenames) {
char *t;
t = dirname_malloc(*filename);
if (!t)
dst = path_join(tempdir, base);
if (!dst)
return -ENOMEM;
r = strv_consume(&ans, t);
r = copy_file(src, dst, 0, 0644, 0, 0, COPY_REFLINK);
if (r < 0)
return r;
r = strv_consume(&filenames, TAKE_PTR(dst));
if (r < 0)
return r;
}
assert_se(strv_uniq(ans));
/* First, prepend our directories. Second, if some path was specified, use that, and
* otherwise use the defaults. Any duplicates will be filtered out in path-lookup.c.
* Treat explicit empty path to mean that nothing should be appended.
*/
old = getenv("SYSTEMD_UNIT_PATH");
if (!streq_ptr(old, "")) {
if (!old)
old = ":";
r = strv_extend(&ans, old);
if (r < 0)
return r;
}
*var = strv_join(ans, ":");
if (!*var)
return -ENOMEM;
*ret = TAKE_PTR(filenames);
return 0;
}
static int verify_socket(Unit *u) {
Unit *service;
int verb_verify(int argc, char *argv[], void *userdata) {
_cleanup_strv_free_ char **filenames = NULL;
_cleanup_(rm_rf_physical_and_freep) char *tempdir = NULL;
int r;
assert(u);
if (u->type != UNIT_SOCKET)
return 0;
r = socket_load_service_unit(SOCKET(u), -1, &service);
r = mkdtemp_malloc("/tmp/systemd-analyze-XXXXXX", &tempdir);
if (r < 0)
return log_unit_error_errno(u, r, "service unit for the socket cannot be loaded: %m");
return log_error_errno(r, "Failed to setup working directory: %m");
if (service->load_state != UNIT_LOADED)
return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOENT),
"service %s not loaded, socket cannot be started.", service->id);
log_unit_debug(u, "using service unit %s.", service->id);
return 0;
}
int verify_executable(Unit *u, const ExecCommand *exec, const char *root) {
int r;
if (!exec)
return 0;
if (exec->flags & EXEC_COMMAND_IGNORE_FAILURE)
return 0;
r = find_executable_full(exec->path, root, NULL, false, NULL, NULL);
r = process_aliases(argv, tempdir, &filenames);
if (r < 0)
return log_unit_error_errno(u, r, "Command %s is not executable: %m", exec->path);
return log_error_errno(r, "Couldn't process aliases: %m");
return 0;
return verify_units(filenames, arg_scope, arg_man, arg_generators, arg_recursive_errors, arg_root);
}
static int verify_executables(Unit *u, const char *root) {
ExecCommand *exec;
int r = 0, k;
unsigned i;
assert(u);
exec = u->type == UNIT_SOCKET ? SOCKET(u)->control_command :
u->type == UNIT_MOUNT ? MOUNT(u)->control_command :
u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL;
k = verify_executable(u, exec, root);
if (k < 0 && r == 0)
r = k;
if (u->type == UNIT_SERVICE)
for (i = 0; i < ELEMENTSOF(SERVICE(u)->exec_command); i++) {
k = verify_executable(u, SERVICE(u)->exec_command[i], root);
if (k < 0 && r == 0)
r = k;
}
if (u->type == UNIT_SOCKET)
for (i = 0; i < ELEMENTSOF(SOCKET(u)->exec_command); i++) {
k = verify_executable(u, SOCKET(u)->exec_command[i], root);
if (k < 0 && r == 0)
r = k;
}
return r;
}
static int verify_documentation(Unit *u, bool check_man) {
char **p;
int r = 0, k;
STRV_FOREACH(p, u->documentation) {
log_unit_debug(u, "Found documentation item: %s", *p);
if (check_man && startswith(*p, "man:")) {
k = show_man_page(*p + 4, true);
if (k != 0) {
if (k < 0)
log_unit_error_errno(u, k, "Can't show %s: %m", *p + 4);
else {
log_unit_error(u, "Command 'man %s' failed with code %d", *p + 4, k);
k = -ENOEXEC;
}
if (r == 0)
r = k;
}
}
}
/* Check remote URLs? */
return r;
}
static int verify_unit(Unit *u, bool check_man, const char *root) {
_cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL;
int r, k;
assert(u);
if (DEBUG_LOGGING)
unit_dump(u, stdout, "\t");
log_unit_debug(u, "Creating %s/start job", u->id);
r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, NULL, &err, NULL);
if (r < 0)
log_unit_error_errno(u, r, "Failed to create %s/start: %s", u->id, bus_error_message(&err, r));
k = verify_socket(u);
if (k < 0 && r == 0)
r = k;
k = verify_executables(u, root);
if (k < 0 && r == 0)
r = k;
k = verify_documentation(u, check_man);
if (k < 0 && r == 0)
r = k;
return r;
}
static void set_destroy_ignore_pointer_max(Set** s) {
if (*s == POINTER_MAX)
return;
set_free_free(*s);
}
int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run_generators, RecursiveErrors recursive_errors, const char *root) {
const ManagerTestRunFlags flags =
MANAGER_TEST_RUN_MINIMAL |
MANAGER_TEST_RUN_ENV_GENERATORS |
(recursive_errors == RECURSIVE_ERRORS_NO) * MANAGER_TEST_RUN_IGNORE_DEPENDENCIES |
run_generators * MANAGER_TEST_RUN_GENERATORS;
_cleanup_(manager_freep) Manager *m = NULL;
_cleanup_(set_destroy_ignore_pointer_max) Set *s = NULL;
_unused_ _cleanup_(clear_log_syntax_callback) dummy_t dummy;
Unit *units[strv_length(filenames)];
_cleanup_free_ char *var = NULL;
int r, k, i, count = 0;
char **filename;
if (strv_isempty(filenames))
return 0;
/* Allow systemd-analyze to hook in a callback function so that it can get
* all the required log data from the function itself without having to rely
* on a global set variable for the same */
set_log_syntax_callback(log_syntax_callback, &s);
/* set the path */
r = verify_generate_path(&var, filenames);
if (r < 0)
return log_error_errno(r, "Failed to generate unit load path: %m");
assert_se(set_unit_path(var) >= 0);
r = manager_new(scope, flags, &m);
if (r < 0)
return log_error_errno(r, "Failed to initialize manager: %m");
log_debug("Starting manager...");
r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, root);
if (r < 0)
return r;
manager_clear_jobs(m);
log_debug("Loading remaining units from the command line...");
STRV_FOREACH(filename, filenames) {
_cleanup_free_ char *prepared = NULL;
log_debug("Handling %s...", *filename);
k = verify_prepare_filename(*filename, &prepared);
if (k < 0) {
log_error_errno(k, "Failed to prepare filename %s: %m", *filename);
if (r == 0)
r = k;
continue;
}
k = manager_load_startable_unit_or_warn(m, NULL, prepared, &units[count]);
if (k < 0) {
if (r == 0)
r = k;
continue;
}
count++;
}
for (i = 0; i < count; i++) {
k = verify_unit(units[i], check_man, root);
if (k < 0 && r == 0)
r = k;
}
if (s == POINTER_MAX)
return log_oom();
if (set_isempty(s) || r != 0)
return r;
/* If all previous verifications succeeded, then either the recursive parsing of all the
* associated dependencies with RECURSIVE_ERRORS_YES or the parsing of the specified unit file
* with RECURSIVE_ERRORS_NO must have yielded a syntax warning and hence, a non-empty set. */
if (IN_SET(recursive_errors, RECURSIVE_ERRORS_YES, RECURSIVE_ERRORS_NO))
return -ENOTRECOVERABLE;
/* If all previous verifications succeeded, then the non-empty set could have resulted from
* a syntax warning encountered during the recursive parsing of the specified unit file and
* its direct dependencies. Hence, search for any of the filenames in the set and if found,
* return a non-zero process exit status. */
if (recursive_errors == RECURSIVE_ERRORS_ONE)
STRV_FOREACH(filename, filenames)
if (set_contains(s, basename(*filename)))
return -ENOTRECOVERABLE;
return 0;
}
static const char* const recursive_errors_table[_RECURSIVE_ERRORS_MAX] = {
[RECURSIVE_ERRORS_NO] = "no",
[RECURSIVE_ERRORS_YES] = "yes",
[RECURSIVE_ERRORS_ONE] = "one",
};
DEFINE_STRING_TABLE_LOOKUP(recursive_errors, RecursiveErrors);

View File

@ -1,23 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <stdbool.h>
#include "execute.h"
#include "path-lookup.h"
typedef enum RecursiveErrors {
RECURSIVE_ERRORS_YES, /* Look for errors in all associated units */
RECURSIVE_ERRORS_NO, /* Don't look for errors in any but the selected unit */
RECURSIVE_ERRORS_ONE, /* Look for errors in the selected unit and its direct dependencies */
_RECURSIVE_ERRORS_MAX,
_RECURSIVE_ERRORS_INVALID = -EINVAL,
} RecursiveErrors;
int verify_generate_path(char **var, char **filenames);
int verify_prepare_filename(const char *filename, char **ret);
int verify_executable(Unit *u, const ExecCommand *exec, const char *root);
int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run_generators, RecursiveErrors recursive_errors, const char *root);
const char* recursive_errors_to_string(RecursiveErrors i) _const_;
RecursiveErrors recursive_errors_from_string(const char *s) _pure_;
int verb_verify(int argc, char *argv[], void *userdata);

File diff suppressed because it is too large Load Diff

44
src/analyze/analyze.h Normal file
View File

@ -0,0 +1,44 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <stdbool.h>
#include "analyze-verify-util.h"
#include "bus-util.h"
#include "json.h"
#include "pager.h"
#include "time-util.h"
#include "unit-file.h"
typedef enum DotMode {
DEP_ALL,
DEP_ORDER,
DEP_REQUIRE,
} DotMode;
extern DotMode arg_dot;
extern char **arg_dot_from_patterns, **arg_dot_to_patterns;
extern usec_t arg_fuzz;
extern PagerFlags arg_pager_flags;
extern BusTransport arg_transport;
extern const char *arg_host;
extern UnitFileScope arg_scope;
extern RecursiveErrors arg_recursive_errors;
extern bool arg_man;
extern bool arg_generators;
extern char *arg_root;
extern char *arg_security_policy;
extern bool arg_offline;
extern unsigned arg_threshold;
extern unsigned arg_iterations;
extern usec_t arg_base_time;
extern char *arg_unit;
extern JsonFormatFlags arg_json_format_flags;
extern bool arg_quiet;
extern char *arg_profile;
int acquire_bus(sd_bus **bus, bool *use_full_bus);
int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv);
void time_parsing_hint(const char *p, bool calendar, bool timestamp, bool timespan);

View File

@ -1,21 +1,61 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
systemd_analyze_sources = files('''
analyze.c
analyze-blame.c
analyze-blame.h
analyze-calendar.c
analyze-calendar.h
analyze-capability.c
analyze-capability.h
analyze-cat-config.c
analyze-cat-config.h
analyze-condition.c
analyze-condition.h
analyze-elf.c
analyze-elf.h
analyze-verify.c
analyze-verify.h
analyze-critical-chain.c
analyze-critical-chain.h
analyze-dot.c
analyze-dot.h
analyze-dump.c
analyze-dump.h
analyze-exit-status.c
analyze-exit-status.h
analyze-filesystems.c
analyze-filesystems.h
analyze-inspect-elf.c
analyze-inspect-elf.h
analyze-log-control.c
analyze-log-control.h
analyze-plot.c
analyze-plot.h
analyze-security.c
analyze-security.h
analyze-service-watchdogs.c
analyze-service-watchdogs.h
analyze-syscall-filter.c
analyze-syscall-filter.h
analyze-time.c
analyze-time.h
analyze-time-data.c
analyze-time-data.h
analyze-timespan.c
analyze-timespan.h
analyze-timestamp.c
analyze-timestamp.h
analyze-unit-files.c
analyze-unit-files.h
analyze-unit-paths.c
analyze-unit-paths.h
analyze-verify.c
analyze-verify.h
analyze-verify-util.c
analyze-verify-util.h
analyze.c
'''.split())
tests += [
[files('test-verify.c',
'analyze-verify.c',
'analyze-verify.h'),
'analyze-verify-util.c',
'analyze-verify-util.h'),
[libcore,
libshared],
[],

View File

@ -1,5 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze-verify.h"
#include "analyze-verify-util.h"
#include "tests.h"
static void test_verify_nonexistent(void) {