diff --git a/src/home/homed.c b/src/home/homed.c index f36e61d04e..a1b0d991de 100644 --- a/src/home/homed.c +++ b/src/home/homed.c @@ -13,6 +13,17 @@ #include "log.h" #include "main-func.h" #include "service-util.h" +#include "verbs.h" + +COMMAND( + "systemd-homed\0", + "Create, remove, change or inspect home areas.", + .man_pages = "systemd-homed.service.8\0", + .option_namespace = "service", + .option_groups = + "Options\0" + "Bus introspection\0", +); static int run(int argc, char *argv[]) { _cleanup_(manager_freep) Manager *m = NULL; @@ -25,9 +36,7 @@ static int run(int argc, char *argv[]) { log_setup(); - r = service_parse_argv("systemd-homed.service", - "A service to create, remove, change or inspect home areas.", - BUS_IMPLEMENTATIONS(&manager_object, + r = service_parse_argv(BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), /* runtime_scope= */ NULL, argc, argv); diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index 98ade098a7..ffb98fab05 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -46,6 +46,7 @@ #include "varlink-io.systemd.Hostname.h" #include "varlink-io.systemd.service.h" #include "varlink-util.h" +#include "verbs.h" #include "virt.h" #include "vsock-util.h" @@ -2654,6 +2655,16 @@ static bool context_check_idle(void *userdata) { hashmap_isempty(c->polkit_registry); } +COMMAND( + "systemd-hostnamed\0", + "Manage the system hostname and related metadata.", + .man_pages = "systemd-hostnamed.service.8\0", + .option_namespace = "service", + .option_groups = + "Options\0" + "Bus introspection\0", +); + static int run(int argc, char *argv[]) { _cleanup_(context_destroy) Context context = { .hostname_source = _HOSTNAME_INVALID, /* appropriate value will be set later */ @@ -2664,9 +2675,7 @@ static int run(int argc, char *argv[]) { log_setup(); - r = service_parse_argv("systemd-hostnamed.service", - "Manage the system hostname and related metadata.", - BUS_IMPLEMENTATIONS(&manager_object, + r = service_parse_argv(BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), /* runtime_scope= */ NULL, argc, argv); diff --git a/src/import/importd.c b/src/import/importd.c index 3a1ad8b502..1f72d6b5fe 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -48,6 +48,7 @@ #include "varlink-io.systemd.Import.h" #include "varlink-io.systemd.service.h" #include "varlink-util.h" +#include "verbs.h" #include "web-util.h" typedef struct Manager Manager; @@ -2079,6 +2080,17 @@ static void manager_parse_env(Manager *m) { log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_BTRFS_QUOTA: %m"); } +COMMAND( + "systemd-importd\0", + "Import and export VM and container images.", + .man_pages = "systemd-importd.service.8\0", + .option_namespace = "service", + .option_groups = + "Options\0" + "Bus introspection\0" + "Runtime scope\0", +); + static int run(int argc, char *argv[]) { _cleanup_(manager_unrefp) Manager *m = NULL; RuntimeScope scope = RUNTIME_SCOPE_SYSTEM; @@ -2088,9 +2100,7 @@ static int run(int argc, char *argv[]) { log_setup(); - r = service_parse_argv("systemd-importd.service", - "VM and container image import and export service.", - BUS_IMPLEMENTATIONS(&manager_object, + r = service_parse_argv(BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), &scope, argc, argv); diff --git a/src/locale/localed.c b/src/locale/localed.c index a6ff82ffbc..a4c01a3d41 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -24,6 +24,7 @@ #include "service-util.h" #include "string-util.h" #include "strv.h" +#include "verbs.h" static int vconsole_reload(sd_bus *bus) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -627,6 +628,16 @@ static bool context_check_idle(void *userdata) { return hashmap_isempty(c->polkit_registry); } +COMMAND( + "systemd-localed\0", + "Manage system locale settings and key mappings.", + .man_pages = "systemd-localed.service.8\0", + .option_namespace = "service", + .option_groups = + "Options\0" + "Bus introspection\0", +); + static int run(int argc, char *argv[]) { _cleanup_(context_clear) Context context = {}; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -637,9 +648,7 @@ static int run(int argc, char *argv[]) { log_setup(); - r = service_parse_argv("systemd-localed.service", - "Manage system locale settings and key mappings.", - BUS_IMPLEMENTATIONS(&manager_object, + r = service_parse_argv(BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), /* runtime_scope= */ NULL, argc, argv); diff --git a/src/login/logind.c b/src/login/logind.c index 9fcf57ab91..3552ed2eed 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -46,6 +46,7 @@ #include "terminal-util.h" #include "udev-util.h" #include "user-util.h" +#include "verbs.h" static Manager* manager_free(Manager *m); DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); @@ -1347,6 +1348,16 @@ static int manager_run(Manager *m) { } } +COMMAND( + "systemd-logind\0", + "Manage user logins and devices and privileged operations.", + .man_pages = "systemd-logind.service.8\0", + .option_namespace = "service", + .option_groups = + "Options\0" + "Bus introspection\0", +); + static int run(int argc, char *argv[]) { _cleanup_(manager_freep) Manager *m = NULL; _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = NULL; @@ -1359,9 +1370,7 @@ static int run(int argc, char *argv[]) { log_set_facility(LOG_AUTH); log_setup(); - r = service_parse_argv("systemd-logind.service", - "Manager for user logins and devices and privileged operations.", - BUS_IMPLEMENTATIONS(&manager_object, + r = service_parse_argv(BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), /* runtime_scope= */ NULL, argc, argv); diff --git a/src/machine/machined.c b/src/machine/machined.c index 47b7650caf..58edc628a6 100644 --- a/src/machine/machined.c +++ b/src/machine/machined.c @@ -35,6 +35,7 @@ #include "signal-util.h" #include "special.h" #include "string-util.h" +#include "verbs.h" #include "vsock-util.h" static Manager* manager_unref(Manager *m); @@ -343,6 +344,17 @@ static bool check_idle(void *userdata) { return hashmap_isempty(m->machines); } +COMMAND( + "systemd-machined\0", + "Manage registrations of local VMs and containers.", + .man_pages = "systemd-machined.service.8\0", + .option_namespace = "service", + .option_groups = + "Options\0" + "Bus introspection\0" + "Runtime scope\0", +); + static int run(int argc, char *argv[]) { _cleanup_(manager_unrefp) Manager *m = NULL; RuntimeScope scope = RUNTIME_SCOPE_SYSTEM; @@ -357,9 +369,7 @@ static int run(int argc, char *argv[]) { log_set_facility(LOG_AUTH); log_setup(); - r = service_parse_argv("systemd-machined.service", - "Manage registrations of local VMs and containers.", - BUS_IMPLEMENTATIONS(&manager_object, + r = service_parse_argv(BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), &scope, argc, argv); diff --git a/src/network/networkd.c b/src/network/networkd.c index cbdf3896b2..e6b2d3e227 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -19,6 +19,17 @@ #include "service-util.h" #include "strv.h" #include "user-util.h" +#include "verbs.h" + +COMMAND( + "systemd-networkd\0", + "Manage and configure network devices, create virtual network devices.", + .man_pages = "systemd-networkd.service.8\0", + .option_namespace = "service", + .option_groups = + "Options\0" + "Bus introspection\0", +); static int run(int argc, char *argv[]) { _cleanup_(manager_freep) Manager *m = NULL; @@ -31,9 +42,7 @@ static int run(int argc, char *argv[]) { log_setup(); - r = service_parse_argv("systemd-networkd.service", - "Manage and configure network devices, create virtual network devices", - BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), + r = service_parse_argv(BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), /* runtime_scope= */ NULL, argc, argv); if (r <= 0) diff --git a/src/portable/portabled.c b/src/portable/portabled.c index b79491e2d8..09197aa651 100644 --- a/src/portable/portabled.c +++ b/src/portable/portabled.c @@ -20,6 +20,7 @@ #include "portabled.h" #include "service-util.h" #include "signal-util.h" +#include "verbs.h" static Manager* manager_unref(Manager *m); DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref); @@ -139,6 +140,17 @@ static bool check_idle(void *userdata) { hashmap_isempty(m->polkit_registry); } +COMMAND( + "systemd-portabled\0", + "Manage registrations of portable images.", + .man_pages = "systemd-portabled.service.8\0", + .option_namespace = "service", + .option_groups = + "Options\0" + "Bus introspection\0" + "Runtime scope\0", +); + static int run(int argc, char *argv[]) { _cleanup_(manager_unrefp) Manager *m = NULL; RuntimeScope scope = RUNTIME_SCOPE_SYSTEM; @@ -152,9 +164,7 @@ static int run(int argc, char *argv[]) { log_setup(); - r = service_parse_argv("systemd-portabled.service", - "Manage registrations of portable images.", - BUS_IMPLEMENTATIONS(&manager_object, + r = service_parse_argv(BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), &scope, argc, argv); diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index 0b6e3e983d..c2d3248a34 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -19,6 +19,17 @@ #include "resolved-resolv-conf.h" #include "service-util.h" #include "user-util.h" +#include "verbs.h" + +COMMAND( + "systemd-resolved\0", + "Provide name resolution with caching using DNS, mDNS, LLMNR.", + .man_pages = "systemd-resolved.service.8\0", + .option_namespace = "service", + .option_groups = + "Options\0" + "Bus introspection\0", +); static int run(int argc, char *argv[]) { _cleanup_(manager_freep) Manager *m = NULL; @@ -32,9 +43,7 @@ static int run(int argc, char *argv[]) { log_setup(); - r = service_parse_argv("systemd-resolved.service", - "Provide name resolution with caching using DNS, mDNS, LLMNR.", - BUS_IMPLEMENTATIONS(&manager_object, + r = service_parse_argv(BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), /* runtime_scope= */ NULL, argc, argv); diff --git a/src/shared/service-util.c b/src/shared/service-util.c index 7c1df8e73a..9726bbf701 100644 --- a/src/shared/service-util.c +++ b/src/shared/service-util.c @@ -1,59 +1,19 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include +#include "sd-json.h" #include "build.h" #include "bus-object.h" -#include "format-table.h" -#include "help-util.h" #include "log.h" +#include "nulstr-util.h" #include "options.h" #include "runtime-scope.h" #include "service-util.h" +#include "string-util.h" -static int help(const char *service, - const char *description, - bool with_bus_introspect, - bool with_runtime_scope) { - - static const char* const groups[] = { - NULL, - "Bus introspection", - "Runtime scope", - }; - - bool conds[ELEMENTSOF(groups)] = { true, with_bus_introspect, with_runtime_scope }; - Table* tables[ELEMENTSOF(groups)] = {}; - CLEANUP_ELEMENTS(tables, table_unref_array_clear); - int r; - - for (size_t i = 0; i < ELEMENTSOF(groups); i++) - if (conds[i]) { - r = option_parser_get_help_table_group(groups[i], &tables[i]); - if (r < 0) - return r; - } - - (void) table_sync_column_widths(0, tables[0], tables[1] ?: tables[2], tables[1] ? tables[2] : NULL); - - help_cmdline("[OPTIONS...]"); - help_abstract(description); - - help_section("Options"); - for (size_t i = 0; i < ELEMENTSOF(groups); i++) - if (conds[i]) { - r = table_print_or_warn(tables[i]); - if (r < 0) - return r; - } - - help_man_page_reference(service, "8"); - return 0; /* No further action */ -} - -int service_parse_argv( - const char *service, - const char *description, +int _service_parse_argv( + const Verb *verbs, + const Verb *verbs_end, const BusObjectImplementation* const* bus_objects, RuntimeScope *runtime_scope, int argc, char *argv[]) { @@ -61,30 +21,50 @@ int service_parse_argv( assert(argc >= 0); assert(argv); - OptionParser opts = { argc, argv }; + const CommandDescription *cmd = NULL; + assert_se(_verbs_find_command(verbs, verbs_end, /* name= */ NULL, &cmd)); + + /* The COMMAND description must reference the option namespace defined below, and list + * exactly the option groups matching the features the service supports. Options outside of + * the listed groups are treated as unknown. */ + assert(streq_ptr(cmd->option_namespace, "service")); + assert(nulstr_contains(cmd->option_groups, "Options")); + assert(!!bus_objects == nulstr_contains(cmd->option_groups, "Bus introspection")); + assert(!!runtime_scope == nulstr_contains(cmd->option_groups, "Runtime scope")); + + OptionParser opts = { + argc, argv, + .namespace = "service", + .option_groups = cmd->option_groups, + }; FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { + OPTION_NAMESPACE("service"): {} + + OPTION_GROUP("Options"): {} + OPTION_COMMON_HELP: - return help(service, - description, - /* with_bus_introspect= */ bus_objects, - /* with_runtime_scope= */ runtime_scope); + return _command_print_help_full( + verbs, verbs_end, + __start_SYSTEMD_OPTIONS, __stop_SYSTEMD_OPTIONS, + cmd->names, + /* footer_ansi_seq= */ NULL); OPTION_COMMON_VERSION: return version(); + OPTION_COMMON_INTROSPECT_CLI: + return _introspect_cli( + verbs, verbs_end, + __start_SYSTEMD_OPTIONS, __stop_SYSTEMD_OPTIONS, + SD_JSON_FORMAT_OFF); + OPTION_GROUP("Bus introspection"): {} OPTION_LONG("bus-introspect", "PATH", "Write D-Bus XML introspection data"): - /* The option is defined in the shared option table, but it's not supported in this binary, - * so we pretend it doesn't exist. */ - if (!bus_objects) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "This service does not support the --bus-introspect= option."); - - return bus_introspect_implementations(stdout, opts.arg, bus_objects); + return bus_introspect_implementations(stdout, opts.arg, ASSERT_PTR(bus_objects)); OPTION_GROUP("Runtime scope"): {} @@ -92,11 +72,7 @@ int service_parse_argv( "Start service in system mode"): {} OPTION_LONG_DATA("user", NULL, /* data= */ RUNTIME_SCOPE_USER, "Start service in user mode"): - if (!runtime_scope) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "This service does not support the --system/--user options."); - - *runtime_scope = opts.opt->data; + *ASSERT_PTR(runtime_scope) = opts.opt->data; break; } diff --git a/src/shared/service-util.h b/src/shared/service-util.h index 462ba2b3fa..e63f8fe9b0 100644 --- a/src/shared/service-util.h +++ b/src/shared/service-util.h @@ -2,10 +2,21 @@ #pragma once #include "forward.h" +#include "verbs.h" -int service_parse_argv( - const char *service, - const char *description, +int _service_parse_argv( + const Verb *verbs, + const Verb *verbs_end, const BusObjectImplementation* const* bus_objects, RuntimeScope *runtime_scope, int argc, char *argv[]); + +/* The service is expected to define a COMMAND() with .option_namespace = "service" and the option + * groups matching the features it supports: "Options" always, "Bus introspection" iff bus_objects + * is passed, "Runtime scope" iff runtime_scope is passed. The macro captures the calling binary's + * verb section, where that COMMAND is found, while the options are defined in + * service_parse_argv_full() itself. */ +#define service_parse_argv(bus_objects, runtime_scope, argc, argv) \ + _service_parse_argv( \ + __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, \ + bus_objects, runtime_scope, argc, argv) diff --git a/src/sysupdate/sysupdated.c b/src/sysupdate/sysupdated.c index d6f77edb81..ef4c1b579a 100644 --- a/src/sysupdate/sysupdated.c +++ b/src/sysupdate/sysupdated.c @@ -41,6 +41,7 @@ #include "strv.h" #include "sysupdate-target.h" #include "sysupdate-util.h" +#include "verbs.h" #define FEATURES_DROPIN_NAME "systemd-sysupdate-enabled" @@ -2160,15 +2161,23 @@ static int manager_run(Manager *m) { m); } +COMMAND( + "systemd-sysupdated\0", + "Manage system updates.", + .man_pages = "systemd-sysupdated.service.8\0", + .option_namespace = "service", + .option_groups = + "Options\0" + "Bus introspection\0", +); + static int run(int argc, char *argv[]) { _cleanup_(manager_freep) Manager *m = NULL; int r; log_setup(); - r = service_parse_argv("systemd-sysupdated.service", - "System update management service.", - BUS_IMPLEMENTATIONS(&manager_object, + r = service_parse_argv(BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), /* runtime_scope= */ NULL, argc, argv); diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c index 25e6ddccdf..667e4262b3 100644 --- a/src/timedate/timedated.c +++ b/src/timedate/timedated.c @@ -46,6 +46,7 @@ #include "time-util.h" #include "unit-def.h" #include "unit-name.h" +#include "verbs.h" #define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n" #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n" @@ -1167,6 +1168,16 @@ static bool context_check_idle(void *userdata) { return hashmap_isempty(c->polkit_registry); } +COMMAND( + "systemd-timedated\0", + "Manage the system clock and timezone and NTP enablement.", + .man_pages = "systemd-timedated.service.8\0", + .option_namespace = "service", + .option_groups = + "Options\0" + "Bus introspection\0", +); + static int run(int argc, char *argv[]) { _cleanup_(context_clear) Context context = {}; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -1177,9 +1188,7 @@ static int run(int argc, char *argv[]) { log_setup(); - r = service_parse_argv("systemd-timedated.service", - "Manage the system clock and timezone and NTP enablement.", - BUS_IMPLEMENTATIONS(&manager_object, + r = service_parse_argv(BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), /* runtime_scope= */ NULL, argc, argv); diff --git a/src/timesync/timesyncd.c b/src/timesync/timesyncd.c index 5e0d13023a..b5a4455a64 100644 --- a/src/timesync/timesyncd.c +++ b/src/timesync/timesyncd.c @@ -22,6 +22,7 @@ #include "timesyncd-bus.h" #include "timesyncd-conf.h" #include "timesyncd-manager.h" +#include "verbs.h" static int advance_tstamp(int fd, usec_t epoch) { assert(fd >= 0); @@ -122,6 +123,16 @@ static int load_clock_timestamp(void) { return 0; } +COMMAND( + "systemd-timesyncd\0", + "Synchronize the system clock across the network.", + .man_pages = "systemd-timesyncd.service.8\0", + .option_namespace = "service", + .option_groups = + "Options\0" + "Bus introspection\0", +); + static int run(int argc, char *argv[]) { _cleanup_(manager_freep) Manager *m = NULL; _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = NULL; @@ -130,9 +141,7 @@ static int run(int argc, char *argv[]) { log_set_facility(LOG_CRON); log_setup(); - r = service_parse_argv("systemd-timesyncd.service", - "Network time synchronization", - BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), + r = service_parse_argv(BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), /* runtime_scope= */ NULL, argc, argv); if (r <= 0) diff --git a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh index 8cc37d3559..8d85dc9412 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -66,25 +66,32 @@ INTROSPECTABLE=( systemd-firstboot systemd-growfs systemd-hibernate-resume + systemd-homed + systemd-hostnamed systemd-hwdb systemd-id128 systemd-imds systemd-imdsd systemd-import systemd-import-fs + systemd-importd systemd-inhibit systemd-integritysetup systemd-journal-gatewayd systemd-journal-remote systemd-journal-upload systemd-keyutil + systemd-localed + systemd-logind systemd-machine-id-setup + systemd-machined systemd-measure systemd-modules-load systemd-mount systemd-mstack systemd-mute-console systemd-network-generator + systemd-networkd systemd-networkd-wait-online systemd-notify systemd-nspawn @@ -92,6 +99,7 @@ INTROSPECTABLE=( systemd-path systemd-pcrextend systemd-pcrlock + systemd-portabled systemd-pty-forward systemd-pull systemd-random-seed @@ -102,6 +110,7 @@ INTROSPECTABLE=( systemd-report-files systemd-report-sign-plain systemd-report-sign-tsm + systemd-resolved systemd-run systemd-sbsign systemd-sleep @@ -116,7 +125,10 @@ INTROSPECTABLE=( systemd-sysext systemd-sysinstall systemd-sysupdate + systemd-sysupdated systemd-sysusers + systemd-timedated + systemd-timesyncd systemd-tmpfiles systemd-tpm2-clear systemd-tpm2-setup @@ -181,6 +193,14 @@ systemd-clonesetup --introspect-cli | jq -e \ systemd-dissect --introspect-cli | jq -e \ '[.commands[].names[0]] | sort == ["mount.ddi", "systemd-dissect"]' +# systemd-hostnamed does not support --system/--user: options from groups not listed by the +# command must be rejected as unknown and hidden from the introspection (option group filtering) +if command -v systemd-hostnamed >/dev/null; then + (! systemd-hostnamed --system 2>&1) | grep "unrecognized option" >/dev/null + systemd-hostnamed --introspect-cli | jq -e \ + 'all(.commands[]; all(.options[]; .names[-1] != "--system"))' +fi + systemd-id128 --introspect-cli | jq -e \ '.commands[0].verbs | map(.names[0]) | contains(["new", "machine-id", "show", "help"])'