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:
commit
22049270c0
65
src/analyze/analyze-blame.c
Normal file
65
src/analyze/analyze-blame.c
Normal 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, ×);
|
||||
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);
|
||||
}
|
4
src/analyze/analyze-blame.h
Normal file
4
src/analyze/analyze-blame.h
Normal file
@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_blame(int argc, char *argv[], void *userdata);
|
145
src/analyze/analyze-calendar.c
Normal file
145
src/analyze/analyze-calendar.c
Normal 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;
|
||||
}
|
4
src/analyze/analyze-calendar.h
Normal file
4
src/analyze/analyze-calendar.h
Normal file
@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_calendar(int argc, char *argv[], void *userdata);
|
52
src/analyze/analyze-capability.c
Normal file
52
src/analyze/analyze-capability.c
Normal 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);
|
||||
}
|
4
src/analyze/analyze-capability.h
Normal file
4
src/analyze/analyze-capability.h
Normal file
@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_capabilities(int argc, char *argv[], void *userdata);
|
46
src/analyze/analyze-cat-config.c
Normal file
46
src/analyze/analyze-cat-config.c
Normal 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;
|
||||
}
|
4
src/analyze/analyze-cat-config.h
Normal file
4
src/analyze/analyze-cat-config.h
Normal 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);
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
237
src/analyze/analyze-critical-chain.c
Normal file
237
src/analyze/analyze-critical-chain.c
Normal 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, ×);
|
||||
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;
|
||||
}
|
4
src/analyze/analyze-critical-chain.h
Normal file
4
src/analyze/analyze-critical-chain.h
Normal 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
184
src/analyze/analyze-dot.c
Normal 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;
|
||||
}
|
4
src/analyze/analyze-dot.h
Normal file
4
src/analyze/analyze-dot.h
Normal file
@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_dot(int argc, char *argv[], void *userdata);
|
64
src/analyze/analyze-dump.c
Normal file
64
src/analyze/analyze-dump.c
Normal 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);
|
||||
}
|
4
src/analyze/analyze-dump.h
Normal file
4
src/analyze/analyze-dump.h
Normal file
@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_dump(int argc, char *argv[], void *userdata);
|
@ -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);
|
52
src/analyze/analyze-exit-status.c
Normal file
52
src/analyze/analyze-exit-status.c
Normal 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);
|
||||
}
|
4
src/analyze/analyze-exit-status.h
Normal file
4
src/analyze/analyze-exit-status.h
Normal 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);
|
230
src/analyze/analyze-filesystems.c
Normal file
230
src/analyze/analyze-filesystems.c
Normal 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;
|
||||
}
|
4
src/analyze/analyze-filesystems.h
Normal file
4
src/analyze/analyze-filesystems.h
Normal file
@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_filesystems(int argc, char *argv[], void *userdata);
|
@ -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);
|
||||
}
|
4
src/analyze/analyze-inspect-elf.h
Normal file
4
src/analyze/analyze-inspect-elf.h
Normal 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);
|
18
src/analyze/analyze-log-control.c
Normal file
18
src/analyze/analyze-log-control.c
Normal 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);
|
||||
}
|
4
src/analyze/analyze-log-control.h
Normal file
4
src/analyze/analyze-log-control.h
Normal 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
395
src/analyze/analyze-plot.c
Normal 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, ×);
|
||||
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;
|
||||
}
|
4
src/analyze/analyze-plot.h
Normal file
4
src/analyze/analyze-plot.h
Normal file
@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_plot(int argc, char *argv[], void *userdata);
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
41
src/analyze/analyze-service-watchdogs.c
Normal file
41
src/analyze/analyze-service-watchdogs.c
Normal 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;
|
||||
}
|
4
src/analyze/analyze-service-watchdogs.h
Normal file
4
src/analyze/analyze-service-watchdogs.h
Normal 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);
|
191
src/analyze/analyze-syscall-filter.c
Normal file
191
src/analyze/analyze-syscall-filter.c
Normal 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
|
4
src/analyze/analyze-syscall-filter.h
Normal file
4
src/analyze/analyze-syscall-filter.h
Normal 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);
|
297
src/analyze/analyze-time-data.c
Normal file
297
src/analyze/analyze-time-data.c
Normal 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,
|
||||
×);
|
||||
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(×.finish_time, times.reverse_offset);
|
||||
|
||||
subtract_timestamp(×.generators_start_time, times.reverse_offset);
|
||||
subtract_timestamp(×.generators_finish_time, times.reverse_offset);
|
||||
|
||||
subtract_timestamp(×.unitsload_start_time, times.reverse_offset);
|
||||
subtract_timestamp(×.unitsload_finish_time, times.reverse_offset);
|
||||
}
|
||||
|
||||
cached = true;
|
||||
|
||||
finish:
|
||||
*ret = ×
|
||||
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;
|
||||
}
|
54
src/analyze/analyze-time-data.h
Normal file
54
src/analyze/analyze-time-data.h
Normal 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);
|
22
src/analyze/analyze-time.c
Normal file
22
src/analyze/analyze-time.c
Normal 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;
|
||||
}
|
4
src/analyze/analyze-time.h
Normal file
4
src/analyze/analyze-time.h
Normal file
@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_time(int argc, char *argv[], void *userdata);
|
74
src/analyze/analyze-timespan.c
Normal file
74
src/analyze/analyze-timespan.c
Normal 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;
|
||||
}
|
4
src/analyze/analyze-timespan.h
Normal file
4
src/analyze/analyze-timespan.h
Normal file
@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_timespan(int argc, char *argv[], void *userdata);
|
95
src/analyze/analyze-timestamp.c
Normal file
95
src/analyze/analyze-timestamp.c
Normal 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;
|
||||
}
|
4
src/analyze/analyze-timestamp.h
Normal file
4
src/analyze/analyze-timestamp.h
Normal file
@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int verb_timestamp(int argc, char *argv[], void *userdata);
|
52
src/analyze/analyze-unit-files.c
Normal file
52
src/analyze/analyze-unit-files.c
Normal 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;
|
||||
}
|
4
src/analyze/analyze-unit-files.h
Normal file
4
src/analyze/analyze-unit-files.h
Normal 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);
|
21
src/analyze/analyze-unit-paths.c
Normal file
21
src/analyze/analyze-unit-paths.c
Normal 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;
|
||||
}
|
4
src/analyze/analyze-unit-paths.h
Normal file
4
src/analyze/analyze-unit-paths.h
Normal 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);
|
351
src/analyze/analyze-verify-util.c
Normal file
351
src/analyze/analyze-verify-util.c
Normal 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);
|
23
src/analyze/analyze-verify-util.h
Normal file
23
src/analyze/analyze-verify-util.h
Normal 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_;
|
@ -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);
|
||||
|
@ -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
44
src/analyze/analyze.h
Normal 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);
|
@ -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],
|
||||
[],
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user