diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 6127541f8b..9fb215a136 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -162,6 +162,8 @@ node /org/freedesktop/systemd1 { Subscribe(); Unsubscribe(); Dump(out s output); + DumpPatterns(in as patterns, + out s output); DumpByFileDescriptor(out h fd); Reload(); @org.freedesktop.DBus.Method.NoReply("true") @@ -868,6 +870,8 @@ node /org/freedesktop/systemd1 { + + @@ -1338,7 +1342,9 @@ node /org/freedesktop/systemd1 { string guaranteed, and new fields may be added any time, and old fields removed. The general structure may be rearranged drastically between releases. This is exposed by systemd-analyze1's - dump command. The DumpByFileDescriptor() method is identical to + dump command. Similarly, DumpPatterns() returns the internal + state of units whose names match the glob expressions specified in the patterns + argument. The DumpByFileDescriptor() method is identical to Dump() but returns the data serialized into a file descriptor (the client should read the text data from it until hitting EOF). Given the size limits on D-Bus messages and the possibly large size of the returned string, DumpByFileDescriptor() is usually the diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index 13b881cfc7..cce08fa121 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -43,6 +43,7 @@ systemd-analyze OPTIONS dump + PATTERN @@ -243,10 +244,12 @@ multi-user.target @47.820s - <command>systemd-analyze dump</command> + <command>systemd-analyze dump [<replaceable>pattern</replaceable>…]</command> - This command outputs a (usually very long) human-readable serialization of the complete server - state. Its format is subject to change without notice and should not be parsed by applications. + Without any parameter, this command outputs a (usually very long) human-readable serialization of + the complete service manager state. Optional glob pattern may be specified, causing the output to be + limited to units whose names match one of the patterns. The output format is subject to change without + notice and should not be parsed by applications. Show the internal state of user manager diff --git a/shell-completion/bash/systemd-analyze b/shell-completion/bash/systemd-analyze index 9fe984d87c..fe2c1d122c 100644 --- a/shell-completion/bash/systemd-analyze +++ b/shell-completion/bash/systemd-analyze @@ -60,9 +60,10 @@ _systemd_analyze() { ) local -A VERBS=( - [STANDALONE]='time blame plot dump unit-paths exit-status calendar timestamp timespan' + [STANDALONE]='time blame plot unit-paths exit-status calendar timestamp timespan' [CRITICAL_CHAIN]='critical-chain' [DOT]='dot' + [DUMP]='dump' [VERIFY]='verify' [SECCOMP_FILTER]='syscall-filter' [CAT_CONFIG]='cat-config' @@ -125,6 +126,13 @@ _systemd_analyze() { comps='--help --version --system --user --global --from-pattern --to-pattern --order --require' fi + elif __contains_word "$verb" ${VERBS[DUMP]}; then + if [[ $cur = -* ]]; then + comps='--help --version --system --user --no-pager' + else + comps=$( __get_units_all ) + fi + elif __contains_word "$verb" ${VERBS[SECCOMP_FILTER]}; then if [[ $cur = -* ]]; then comps='--help --version --no-pager' diff --git a/src/analyze/analyze-dump.c b/src/analyze/analyze-dump.c index 448ce09bd7..220218e2fe 100644 --- a/src/analyze/analyze-dump.c +++ b/src/analyze/analyze-dump.c @@ -29,6 +29,46 @@ static int dump_fallback(sd_bus *bus) { return 0; } +static int dump_patterns(sd_bus *bus, char **patterns) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + _cleanup_strv_free_ char **mangled = NULL; + const char *text; + int r; + + r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "DumpPatterns"); + if (r < 0) + return bus_log_create_error(r); + + STRV_FOREACH(pattern, patterns) { + char *t; + + r = unit_name_mangle_with_suffix(*pattern, NULL, UNIT_NAME_MANGLE_GLOB, ".service", &t); + if (r < 0) + return log_error_errno(r, "Failed to mangle name: %m"); + + r = strv_consume(&mangled, t); + if (r < 0) + return log_oom(); + } + + r = sd_bus_message_append_strv(m, mangled); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to issue method call DumpPatterns: %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 r; +} + 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; @@ -42,6 +82,9 @@ int verb_dump(int argc, char *argv[], void *userdata) { pager_open(arg_pager_flags); + if (argc > 1) + return dump_patterns(bus, strv_skip(argv, 1)); + if (!sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD)) return dump_fallback(bus); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 6215f50fac..4a276f66ba 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -190,7 +190,7 @@ static int help(int argc, char *argv[], void *userdata) { " plot Output SVG graphic showing service\n" " initialization\n" " dot [UNIT...] Output dependency graph in %s format\n" - " dump Output state serialization of service\n" + " dump [PATTERN...] Output state serialization of service\n" " manager\n" " cat-config Show configuration file and drop-ins\n" " unit-files List files and symlinks for units\n" @@ -557,7 +557,7 @@ static int run(int argc, char *argv[]) { { "get-log-target", VERB_ANY, 1, 0, verb_log_control }, { "service-watchdogs", VERB_ANY, 2, 0, verb_service_watchdogs }, /* ↑ … until here ↑ */ - { "dump", VERB_ANY, 1, 0, verb_dump }, + { "dump", VERB_ANY, VERB_ANY, 0, verb_dump }, { "cat-config", 2, VERB_ANY, 0, verb_cat_config }, { "unit-files", VERB_ANY, VERB_ANY, 0, verb_unit_files }, { "unit-paths", 1, 1, 0, verb_unit_paths }, diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 473c0bc5ab..73f739b12d 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1344,7 +1344,13 @@ static int method_unsubscribe(sd_bus_message *message, void *userdata, sd_bus_er return sd_bus_reply_method_return(message, NULL); } -static int dump_impl(sd_bus_message *message, void *userdata, sd_bus_error *error, int (*reply)(sd_bus_message *, char *)) { +static int dump_impl( + sd_bus_message *message, + void *userdata, + sd_bus_error *error, + char **patterns, + int (*reply)(sd_bus_message *, char *)) { + _cleanup_free_ char *dump = NULL; Manager *m = ASSERT_PTR(userdata); int r; @@ -1357,7 +1363,7 @@ static int dump_impl(sd_bus_message *message, void *userdata, sd_bus_error *erro if (r < 0) return r; - r = manager_get_dump_string(m, &dump); + r = manager_get_dump_string(m, patterns, &dump); if (r < 0) return r; @@ -1369,7 +1375,7 @@ static int reply_dump(sd_bus_message *message, char *dump) { } static int method_dump(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return dump_impl(message, userdata, error, reply_dump); + return dump_impl(message, userdata, error, NULL, reply_dump); } static int reply_dump_by_fd(sd_bus_message *message, char *dump) { @@ -1383,7 +1389,18 @@ static int reply_dump_by_fd(sd_bus_message *message, char *dump) { } static int method_dump_by_fd(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return dump_impl(message, userdata, error, reply_dump_by_fd); + return dump_impl(message, userdata, error, NULL, reply_dump_by_fd); +} + +static int method_dump_patterns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **patterns = NULL; + int r; + + r = sd_bus_message_read_strv(message, &patterns); + if (r < 0) + return r; + + return dump_impl(message, userdata, error, patterns, reply_dump); } static int method_refuse_snapshot(sd_bus_message *message, void *userdata, sd_bus_error *error) { @@ -3010,6 +3027,11 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_RESULT("s", output), method_dump, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("DumpPatterns", + SD_BUS_ARGS("as", patterns), + SD_BUS_RESULT("s", output), + method_dump_patterns, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("DumpByFileDescriptor", SD_BUS_NO_ARGS, SD_BUS_RESULT("h", fd), diff --git a/src/core/execute.c b/src/core/execute.c index 86d238b57d..b5b7de6d2a 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -919,7 +919,7 @@ static int ask_for_confirmation(const ExecContext *context, const char *vc, Unit u->id, u->description, cmdline); continue; /* ask again */ case 'j': - manager_dump_jobs(u->manager, stdout, " "); + manager_dump_jobs(u->manager, stdout, /* patterns= */ NULL, " "); continue; /* ask again */ case 'n': /* 'n' was removed in favor of 'f'. */ diff --git a/src/core/fuzz-unit-file.c b/src/core/fuzz-unit-file.c index 058be6aa74..7b393386ff 100644 --- a/src/core/fuzz-unit-file.c +++ b/src/core/fuzz-unit-file.c @@ -83,7 +83,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { assert_se(g); unit_dump(u, g, ""); - manager_dump(m, g, ">>>"); + manager_dump(m, g, /* patterns= */ NULL, ">>>"); return 0; } diff --git a/src/core/manager-dump.c b/src/core/manager-dump.c index 61717d8006..5a92356d48 100644 --- a/src/core/manager-dump.c +++ b/src/core/manager-dump.c @@ -7,31 +7,40 @@ #include "manager-dump.h" #include "unit-serialize.h" -void manager_dump_jobs(Manager *s, FILE *f, const char *prefix) { +void manager_dump_jobs(Manager *s, FILE *f, char **patterns, const char *prefix) { Job *j; assert(s); assert(f); - HASHMAP_FOREACH(j, s->jobs) + HASHMAP_FOREACH(j, s->jobs) { + + if (!strv_fnmatch_or_empty(patterns, j->unit->id, FNM_NOESCAPE)) + continue; + job_dump(j, f, prefix); + } } -void manager_dump_units(Manager *s, FILE *f, const char *prefix) { +void manager_dump_units(Manager *s, FILE *f, char **patterns, const char *prefix) { Unit *u; const char *t; assert(s); assert(f); - HASHMAP_FOREACH_KEY(u, t, s->units) - if (u->id == t) - unit_dump(u, f, prefix); + HASHMAP_FOREACH_KEY(u, t, s->units) { + if (u->id != t) + continue; + + if (!strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE)) + continue; + + unit_dump(u, f, prefix); + } } -void manager_dump(Manager *m, FILE *f, const char *prefix) { - assert(m); - assert(f); +static void manager_dump_header(Manager *m, FILE *f, const char *prefix) { /* NB: this is a debug interface for developers. It's not supposed to be machine readable or be * stable between versions. We take the liberty to restructure it entirely between versions and @@ -50,12 +59,22 @@ void manager_dump(Manager *m, FILE *f, const char *prefix) { timestamp_is_set(t->realtime) ? FORMAT_TIMESTAMP(t->realtime) : FORMAT_TIMESPAN(t->monotonic, 1)); } - - manager_dump_units(m, f, prefix); - manager_dump_jobs(m, f, prefix); } -int manager_get_dump_string(Manager *m, char **ret) { +void manager_dump(Manager *m, FILE *f, char **patterns, const char *prefix) { + assert(m); + assert(f); + + /* If no pattern is provided, dump the full manager state including the manager version, features and + * so on. Otherwise limit the dump to the units/jobs matching the specified patterns. */ + if (!patterns) + manager_dump_header(m, f, prefix); + + manager_dump_units(m, f, patterns, prefix); + manager_dump_jobs(m, f, patterns, prefix); +} + +int manager_get_dump_string(Manager *m, char **patterns, char **ret) { _cleanup_free_ char *dump = NULL; _cleanup_fclose_ FILE *f = NULL; size_t size; @@ -68,7 +87,7 @@ int manager_get_dump_string(Manager *m, char **ret) { if (!f) return -errno; - manager_dump(m, f, NULL); + manager_dump(m, f, patterns, NULL); r = fflush_and_check(f); if (r < 0) @@ -85,8 +104,8 @@ void manager_test_summary(Manager *m) { assert(m); printf("-> By units:\n"); - manager_dump_units(m, stdout, "\t"); + manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); printf("-> By jobs:\n"); - manager_dump_jobs(m, stdout, "\t"); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); } diff --git a/src/core/manager-dump.h b/src/core/manager-dump.h index 317a4b641c..714a1f0c8e 100644 --- a/src/core/manager-dump.h +++ b/src/core/manager-dump.h @@ -5,8 +5,8 @@ #include "manager.h" -void manager_dump_jobs(Manager *s, FILE *f, const char *prefix); -void manager_dump_units(Manager *s, FILE *f, const char *prefix); -void manager_dump(Manager *s, FILE *f, const char *prefix); -int manager_get_dump_string(Manager *m, char **ret); +void manager_dump_jobs(Manager *s, FILE *f, char **patterns, const char *prefix); +void manager_dump_units(Manager *s, FILE *f, char **patterns, const char *prefix); +void manager_dump(Manager *s, FILE *f, char **patterns, const char *prefix); +int manager_get_dump_string(Manager *m, char **patterns, char **ret); void manager_test_summary(Manager *m); diff --git a/src/core/manager.c b/src/core/manager.c index 90e126840a..22c79bed17 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -2823,7 +2823,7 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t case SIGUSR2: { _cleanup_free_ char *dump = NULL; - r = manager_get_dump_string(m, &dump); + r = manager_get_dump_string(m, /* patterns= */ NULL, &dump); if (r < 0) { log_warning_errno(errno, "Failed to acquire manager dump: %m"); break; diff --git a/src/core/org.freedesktop.systemd1.conf b/src/core/org.freedesktop.systemd1.conf index 4a798c7870..8aa1b03a33 100644 --- a/src/core/org.freedesktop.systemd1.conf +++ b/src/core/org.freedesktop.systemd1.conf @@ -120,6 +120,10 @@ send_interface="org.freedesktop.systemd1.Manager" send_member="DumpByFileDescriptor"/> + + diff --git a/src/test/test-engine.c b/src/test/test-engine.c index d430076056..600391094c 100644 --- a/src/test/test-engine.c +++ b/src/test/test-engine.c @@ -103,86 +103,86 @@ int main(int argc, char *argv[]) { assert_se(manager_load_startable_unit_or_warn(m, "a.service", NULL, &a) >= 0); assert_se(manager_load_startable_unit_or_warn(m, "b.service", NULL, &b) >= 0); assert_se(manager_load_startable_unit_or_warn(m, "c.service", NULL, &c) >= 0); - manager_dump_units(m, stdout, "\t"); + manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); printf("Test1: (Trivial)\n"); r = manager_add_job(m, JOB_START, c, JOB_REPLACE, NULL, &err, &j); if (sd_bus_error_is_set(&err)) log_error("error: %s: %s", err.name, err.message); assert_se(r == 0); - manager_dump_jobs(m, stdout, "\t"); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); printf("Load2:\n"); manager_clear_jobs(m); assert_se(manager_load_startable_unit_or_warn(m, "d.service", NULL, &d) >= 0); assert_se(manager_load_startable_unit_or_warn(m, "e.service", NULL, &e) >= 0); - manager_dump_units(m, stdout, "\t"); + manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); printf("Test2: (Cyclic Order, Unfixable)\n"); assert_se(manager_add_job(m, JOB_START, d, JOB_REPLACE, NULL, NULL, &j) == -EDEADLK); - manager_dump_jobs(m, stdout, "\t"); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); printf("Test3: (Cyclic Order, Fixable, Garbage Collector)\n"); assert_se(manager_add_job(m, JOB_START, e, JOB_REPLACE, NULL, NULL, &j) == 0); - manager_dump_jobs(m, stdout, "\t"); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); printf("Test4: (Identical transaction)\n"); assert_se(manager_add_job(m, JOB_START, e, JOB_FAIL, NULL, NULL, &j) == 0); - manager_dump_jobs(m, stdout, "\t"); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); printf("Load3:\n"); assert_se(manager_load_startable_unit_or_warn(m, "g.service", NULL, &g) >= 0); - manager_dump_units(m, stdout, "\t"); + manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); printf("Test5: (Colliding transaction, fail)\n"); assert_se(manager_add_job(m, JOB_START, g, JOB_FAIL, NULL, NULL, &j) == -EDEADLK); printf("Test6: (Colliding transaction, replace)\n"); assert_se(manager_add_job(m, JOB_START, g, JOB_REPLACE, NULL, NULL, &j) == 0); - manager_dump_jobs(m, stdout, "\t"); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); printf("Test7: (Unmergeable job type, fail)\n"); assert_se(manager_add_job(m, JOB_STOP, g, JOB_FAIL, NULL, NULL, &j) == -EDEADLK); printf("Test8: (Mergeable job type, fail)\n"); assert_se(manager_add_job(m, JOB_RESTART, g, JOB_FAIL, NULL, NULL, &j) == 0); - manager_dump_jobs(m, stdout, "\t"); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); printf("Test9: (Unmergeable job type, replace)\n"); assert_se(manager_add_job(m, JOB_STOP, g, JOB_REPLACE, NULL, NULL, &j) == 0); - manager_dump_jobs(m, stdout, "\t"); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); printf("Load4:\n"); assert_se(manager_load_startable_unit_or_warn(m, "h.service", NULL, &h) >= 0); - manager_dump_units(m, stdout, "\t"); + manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); printf("Test10: (Unmergeable job type of auxiliary job, fail)\n"); assert_se(manager_add_job(m, JOB_START, h, JOB_FAIL, NULL, NULL, &j) == 0); - manager_dump_jobs(m, stdout, "\t"); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); printf("Load5:\n"); manager_clear_jobs(m); assert_se(manager_load_startable_unit_or_warn(m, "i.service", NULL, &i) >= 0); SERVICE(a)->state = SERVICE_RUNNING; SERVICE(d)->state = SERVICE_RUNNING; - manager_dump_units(m, stdout, "\t"); + manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); printf("Test11: (Start/stop job ordering, execution cycle)\n"); assert_se(manager_add_job(m, JOB_START, i, JOB_FAIL, NULL, NULL, &j) == 0); assert_se(unit_has_job_type(a, JOB_STOP)); assert_se(unit_has_job_type(d, JOB_STOP)); assert_se(unit_has_job_type(b, JOB_START)); - manager_dump_jobs(m, stdout, "\t"); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); printf("Load6:\n"); manager_clear_jobs(m); assert_se(manager_load_startable_unit_or_warn(m, "a-conj.service", NULL, &a_conj) >= 0); SERVICE(a)->state = SERVICE_DEAD; - manager_dump_units(m, stdout, "\t"); + manager_dump_units(m, stdout, /* patterns= */ NULL, "\t"); printf("Test12: (Trivial cycle, Unfixable)\n"); assert_se(manager_add_job(m, JOB_START, a_conj, JOB_REPLACE, NULL, NULL, &j) == -EDEADLK); - manager_dump_jobs(m, stdout, "\t"); + manager_dump_jobs(m, stdout, /* patterns= */ NULL, "\t"); assert_se(!hashmap_get(unit_get_dependencies(a, UNIT_PROPAGATES_RELOAD_TO), b)); assert_se(!hashmap_get(unit_get_dependencies(b, UNIT_RELOAD_PROPAGATED_FROM), a));