diff --git a/src/basic/argv-util.c b/src/basic/argv-util.c index 20587f66ad..b0381a6096 100644 --- a/src/basic/argv-util.c +++ b/src/basic/argv-util.c @@ -13,7 +13,6 @@ #include "path-util.h" #include "process-util.h" #include "string-util.h" -#include "strv.h" int saved_argc = 0; char **saved_argv = NULL; @@ -69,32 +68,6 @@ bool invoked_by_systemd(void) { return cached; } -bool argv_looks_like_help(int argc, char **argv) { - char **l; - - /* Scans the command line for indications the user asks for help. This is supposed to be called by - * tools that do not implement getopt()-style command line parsing because they are not primarily - * user-facing. Detects four ways of asking for help: - * - * 1. Passing zero arguments - * 2. Passing "help" as first argument - * 3. Passing --help as any argument - * 4. Passing -h as any argument - */ - assert(argc == 0 || argv); - - if (argc <= 1) - return true; - - if (streq_ptr(argv[1], "help")) - return true; - - l = strv_skip(argv, 1); - - return strv_contains(l, "--help") || - strv_contains(l, "-h"); -} - static int update_argv(const char name[], size_t l) { static int can_do = -1; int r; diff --git a/src/basic/argv-util.h b/src/basic/argv-util.h index a9526ef936..9d598929e7 100644 --- a/src/basic/argv-util.h +++ b/src/basic/argv-util.h @@ -10,7 +10,6 @@ void save_argc_argv(int argc, char **argv); bool invoked_as(char *argv[], const char *token); bool invoked_by_systemd(void) _const_; -bool argv_looks_like_help(int argc, char **argv); int rename_process_full(const char *comm, const char *invocation); static inline int rename_process(const char *name) { diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index 4de3480ff0..5b8f3c954b 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -37,6 +37,7 @@ int proc_cmdline_filter_pid1_args(char **argv, char ***ret) { { "no-pager", no_argument, }, { "help", no_argument, }, { "version", no_argument, }, + { "introspect-cli", no_argument, }, { "dump-configuration-items", no_argument, }, { "dump-bus-properties", no_argument, }, { "bus-introspect", required_argument }, diff --git a/src/core/executor.c b/src/core/executor.c index 0c104e5c31..6b96ea3f9c 100644 --- a/src/core/executor.c +++ b/src/core/executor.c @@ -2,9 +2,9 @@ #include +#include "sd-json.h" #include "sd-messages.h" -#include "alloc-util.h" #include "argv-util.h" #include "build.h" #include "capability-util.h" @@ -18,45 +18,24 @@ #include "exit-status.h" #include "fd-util.h" #include "fdset.h" -#include "format-table.h" #include "label-util.h" #include "log.h" #include "options.h" #include "parse-util.h" -#include "pretty-print.h" #include "selinux-util.h" #include "static-destruct.h" +#include "verbs.h" static FILE *arg_serialization = NULL; STATIC_DESTRUCTOR_REGISTER(arg_serialization, fclosep); -static int help(void) { - _cleanup_free_ char *link = NULL; - _cleanup_(table_unrefp) Table *options = NULL; - int r; - - r = terminal_urlify_man("systemd", "1", &link); - if (r < 0) - return log_oom(); - - r = option_parser_get_help_table_ns("systemd-executor", &options); - if (r < 0) - return r; - - printf("%s [OPTIONS...]\n\n" - "%sSandbox and execute processes.%s\n\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal()); - - r = table_print_or_warn(options); - if (r < 0) - return r; - - printf("\nSee the %s for details.\n", link); - return 0; -} +COMMAND( + "systemd-executor\0", + "Sandbox and execute processes.", + .man_pages = "systemd.1\0", + .option_namespace = "systemd-executor", +); static int parse_argv(int argc, char *argv[]) { int r; @@ -72,7 +51,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_NAMESPACE("systemd-executor"): {} OPTION_COMMON_HELP: - return help(); + return command_print_help_name("systemd-executor"); OPTION_COMMON_VERSION: return version(); @@ -128,6 +107,9 @@ static int parse_argv(int argc, char *argv[]) { arg_serialization = f; break; } + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); } if (!arg_serialization) diff --git a/src/core/main.c b/src/core/main.c index d27156ec3f..fe7e5d8498 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -51,13 +51,11 @@ #include "fd-util.h" #include "fdset.h" #include "fileio.h" -#include "format-table.h" #include "format-util.h" #include "fs-util.h" #include "glyph-util.h" #include "hash-funcs.h" #include "hashmap.h" -#include "help-util.h" #include "hexdecoct.h" #include "hostname-setup.h" #include "id128-util.h" @@ -119,6 +117,7 @@ #include "unit-name.h" #include "user-util.h" #include "utf8.h" +#include "verbs.h" #include "version.h" #include "virt.h" #include "watchdog.h" @@ -131,6 +130,7 @@ static enum { ACTION_RUN, ACTION_HELP, ACTION_VERSION, + ACTION_INTROSPECT_CLI, ACTION_TEST, ACTION_DUMP_CONFIGURATION_ITEMS, ACTION_DUMP_BUS_PROPERTIES, @@ -1039,6 +1039,14 @@ static int redirect_telinit(char *argv[], char **args) { strnull(a), strnull(b), strnull(c)); } +COMMAND( + "systemd\0", + "Start and monitor system and user services.", + .man_pages = "systemd.1\0", + .option_namespace = "systemd", + .pager_flags = &arg_pager_flags, +); + static int parse_argv(int argc, char *argv[]) { bool user_arg_seen = false; int r; @@ -1068,6 +1076,10 @@ static int parse_argv(int argc, char *argv[]) { arg_action = ACTION_VERSION; break; + OPTION_COMMON_INTROSPECT_CLI: + arg_action = ACTION_INTROSPECT_CLI; + break; + OPTION_LONG("test", NULL, "Determine initial transaction, dump it and exit"): arg_action = ACTION_TEST; break; @@ -1268,6 +1280,7 @@ static int parse_argv(int argc, char *argv[]) { "Switch -%c is only supported when running as PID 1.", opts.opt->short_code); /* In PID1, do nothing and continue with option parsing. */ break; + OPTION_ERROR: if (getpid_cached() != 1) return -EINVAL; @@ -1290,28 +1303,6 @@ static int parse_argv(int argc, char *argv[]) { return 0; } -static int help(void) { - _cleanup_(table_unrefp) Table *options = NULL; - int r; - - r = option_parser_get_help_table_ns("systemd", &options); - if (r < 0) - return r; - - help_cmdline("[OPTIONS...]"); - help_abstract("Starts and monitors system and user services."); - - printf("\nThis program takes no positional arguments.\n"); - - help_section("Options"); - r = table_print_or_warn(options); - if (r < 0) - return r; - - help_man_page_reference("systemd", "1"); - return 0; -} - static int prepare_reexecute( Manager *m, FILE **ret_f, @@ -3745,18 +3736,23 @@ static int run_systemd(int argc, char *argv[]) { if (r < 0) goto finish; - if (IN_SET(arg_action, ACTION_TEST, ACTION_HELP, ACTION_DUMP_CONFIGURATION_ITEMS, ACTION_DUMP_BUS_PROPERTIES, ACTION_BUS_INTROSPECT)) + if (IN_SET(arg_action, ACTION_TEST, ACTION_DUMP_CONFIGURATION_ITEMS, ACTION_DUMP_BUS_PROPERTIES, ACTION_BUS_INTROSPECT)) pager_open(arg_pager_flags); if (arg_action != ACTION_RUN) skip_setup = true; if (arg_action == ACTION_HELP) { - retval = help() < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + r = command_print_help_name("systemd"); + retval = r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; goto finish; } else if (arg_action == ACTION_VERSION) { retval = version(); goto finish; + } else if (arg_action == ACTION_INTROSPECT_CLI) { + r = introspect_cli(SD_JSON_FORMAT_OFF); + retval = r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + goto finish; } else if (arg_action == ACTION_DUMP_CONFIGURATION_ITEMS) { unit_dump_config_items(stdout); retval = EXIT_SUCCESS; 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 5c190b373c..c748758fed 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" @@ -2662,6 +2663,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 */ @@ -2672,9 +2683,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 b77515a264..274c9f8add 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; @@ -2080,6 +2081,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; @@ -2089,9 +2101,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/integritysetup/integritysetup.c b/src/integritysetup/integritysetup.c index bf2f40b0d4..461be60905 100644 --- a/src/integritysetup/integritysetup.c +++ b/src/integritysetup/integritysetup.c @@ -1,22 +1,20 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include +#include "sd-json.h" + #include "alloc-util.h" -#include "argv-util.h" +#include "build.h" #include "cryptsetup-util.h" #include "dlopen-note.h" #include "fileio.h" -#include "format-table.h" -#include "help-util.h" #include "integrity-util.h" #include "log.h" #include "main-func.h" #include "path-util.h" #include "string-table.h" #include "string-util.h" -#include "strv.h" #include "time-util.h" #include "verbs.h" @@ -43,25 +41,11 @@ static const char* const dm_integrity_algorithm_table[_INTEGRITY_ALGORITHM_MAX] DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(dm_integrity_algorithm, IntegrityAlgorithm); -static int help(void) { - _cleanup_(table_unrefp) Table *verbs = NULL; - int r; - - r = verbs_get_help_table(&verbs); - if (r < 0) - return r; - - help_cmdline("COMMAND ..."); - help_abstract("Attach or detach an integrity protected block device."); - - help_section("Commands"); - r = table_print_or_warn(verbs); - if (r < 0) - return r; - - help_man_page_reference("systemd-integritysetup@.service", "8"); - return 0; -} +COMMAND( + "systemd-integritysetup\0", + "Attach or detach an integrity protected block device.", + .man_pages = "systemd-integritysetup@.service.8\0", +); static int load_key_file( const char *key_file, @@ -197,23 +181,51 @@ static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) return 0; } +VERB_COMMON_HELP_AUTO_HIDDEN(); + +static int parse_argv(int argc, char *argv[], char ***ret_args) { + assert(argc >= 0); + assert(argv); + assert(ret_args); + + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) + switch (c) { + + OPTION_COMMON_HELP: + return command_print_help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); + } + + *ret_args = option_parser_get_args(&opts); + return 1; +} + static int run(int argc, char *argv[]) { int r; - LIBCRYPTSETUP_NOTE(required); - - if (argv_looks_like_help(argc, argv)) - return help(); + char **args = NULL; + r = parse_argv(argc, argv, &args); + if (r <= 0) + return r; log_setup(); + LIBCRYPTSETUP_NOTE(required); + r = dlopen_cryptsetup(LOG_ERR); if (r < 0) return r; umask(0022); - return dispatch_verb(strv_skip(argv, 1), /* userdata= */ NULL); + return dispatch_verb(args, /* userdata= */ NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/integritysetup/meson.build b/src/integritysetup/meson.build index 7f29c031b8..b8d23eeafa 100644 --- a/src/integritysetup/meson.build +++ b/src/integritysetup/meson.build @@ -7,6 +7,7 @@ endif executables += [ libexec_template + { 'name' : 'systemd-integritysetup', + 'public' : true, 'sources' : files('integritysetup.c'), 'export' : files('integrity-util.c'), 'dependencies' : libcryptsetup_cflags, diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index d35ff931b2..943640bd41 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -1624,7 +1624,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { switch (c) { OPTION_COMMON_HELP: - return command_print_help_full("kernel-install", /* footer_ansi_seq= */ NULL); + return command_print_help_name("kernel-install"); OPTION_COMMON_VERSION: return version(); 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/mount/mount-tool.c b/src/mount/mount-tool.c index 5ecd7c097a..5d989188c8 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -163,7 +163,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION_GROUP("Common options"): {} OPTION_COMMON_HELP: - return command_print_help_full(cmd->names, /* footer_ansi_seq= */ NULL); + return command_print_help_name(cmd->names); OPTION_COMMON_VERSION: return version(); 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/resolvconf-compat.c b/src/resolve/resolvconf-compat.c index 8a07124573..018908bcc0 100644 --- a/src/resolve/resolvconf-compat.c +++ b/src/resolve/resolvconf-compat.c @@ -181,7 +181,7 @@ int resolvconf_parse_argv(int argc, char *argv[]) { OPTION_NAMESPACE("resolvconf"): {} OPTION_COMMON_HELP: - return command_print_help_full("resolvconf", /* footer_ansi_seq= */ NULL); + return command_print_help_name("resolvconf"); OPTION_COMMON_VERSION: return version(); diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index 2762d3d764..1c87c9fd33 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -3515,7 +3515,7 @@ static int native_parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION_NAMESPACE("resolvectl"): {} OPTION_COMMON_HELP: - return command_print_help_full("resolvectl", /* footer_ansi_seq= */ NULL); + return command_print_help_name("resolvectl"); OPTION_COMMON_VERSION: return version(); 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/run/run.c b/src/run/run.c index 6aa58f8b5c..cdf15fb41f 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -208,7 +208,7 @@ static int parse_argv(int argc, char *argv[]) { OPTION_NAMESPACE("systemd-run"): {} OPTION_COMMON_HELP: - return command_print_help_full("systemd-run", /* footer_ansi_seq= */ NULL); + return command_print_help_name("systemd-run"); OPTION_COMMON_VERSION: return version(); @@ -784,7 +784,7 @@ static int parse_argv_sudo_mode(int argc, char *argv[]) { OPTION_NAMESPACE("run0"): {} OPTION_COMMON_HELP: - return command_print_help_full("run0", /* footer_ansi_seq= */ NULL); + return command_print_help_name("run0"); OPTION('V', "version", NULL, "Show package version"): return version(); 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/shared/verbs.h b/src/shared/verbs.h index 673ef5870b..72b6d70f4f 100644 --- a/src/shared/verbs.h +++ b/src/shared/verbs.h @@ -162,10 +162,11 @@ int _command_print_help_full( __start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, \ __start_SYSTEMD_OPTIONS, __stop_SYSTEMD_OPTIONS, \ name, footer_ansi_seq) -#define command_print_help() command_print_help_full(/* name= */ NULL, /* footer_ansi_seq= */ NULL) +#define command_print_help_name(name) command_print_help_full(name, /* footer_ansi_seq= */ NULL) +#define command_print_help() command_print_help_name(/* name= */ NULL) static inline int verb_help_auto(int argc, char **argv, uintptr_t data, void *userdata) { - return command_print_help_full((const char*) data, /* footer_ansi_seq= */ NULL); + return command_print_help_name((const char*) data); } /* Print a machine-readable description of the program's commands, verbs, and options in the format diff --git a/src/shutdown/meson.build b/src/shutdown/meson.build index 1545db92e5..397c67098f 100644 --- a/src/shutdown/meson.build +++ b/src/shutdown/meson.build @@ -12,6 +12,7 @@ shutdown_detach_sources = files( executables += [ libexec_template + { 'name' : 'systemd-shutdown', + 'public' : true, 'export' : files( 'detach-dm.c', 'detach-loopback.c', diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index ebf011f7a7..44fddbde7b 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -16,6 +16,7 @@ #include "alloc-util.h" #include "async.h" #include "binfmt-util.h" +#include "build.h" #include "cgroup-setup.h" #include "cgroup-util.h" #include "constants.h" @@ -34,6 +35,7 @@ #include "killall.h" #include "log.h" #include "luo-util.h" +#include "main-func.h" #include "options.h" #include "parse-util.h" #include "pidref.h" @@ -48,6 +50,7 @@ #include "terminal-util.h" #include "time-util.h" #include "umount.h" +#include "verbs.h" #include "virt.h" #include "watchdog.h" @@ -59,6 +62,13 @@ static const char *arg_verb = NULL; static uint8_t arg_exit_code = 0; static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC; +COMMAND( + "systemd-shutdown\0", + "Execute the final phase of system shutdown.", + .argspec = "reboot|poweroff|halt|kexec|exit\0", + .man_pages = "systemd-shutdown.8\0", +); + static int parse_argv(int argc, char *argv[]) { assert(argc >= 1); assert(argv); @@ -74,6 +84,15 @@ static int parse_argv(int argc, char *argv[]) { FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { + OPTION_COMMON_HELP: + return command_print_help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION_COMMON_INTROSPECT_CLI: + return introspect_cli(SD_JSON_FORMAT_OFF); + OPTION_COMMON_LOG_LEVEL: r = log_set_max_level_from_string(opts.arg); if (r < 0) @@ -148,7 +167,7 @@ static int parse_argv(int argc, char *argv[]) { if (!arg_verb) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Verb argument missing."); - return 0; + return 1; /* Further action */ } static int switch_root_initramfs(void) { @@ -366,7 +385,7 @@ static void sleep_until_minimum_uptime(void) { } } -int main(int argc, char *argv[]) { +static int run(int argc, char *argv[]) { static const char* const dirs[] = { SYSTEM_SHUTDOWN_PATH, NULL @@ -402,9 +421,16 @@ int main(int argc, char *argv[]) { log_set_prohibit_ipc(true); log_parse_environment(); + r = parse_argv(argc, argv); if (getpid_cached() != 1) { - log_error("Not executed by init (PID 1). Refusing to operate."); - return EXIT_FAILURE; + /* If we're a normal user space process, our work is done. Either we hit an error, or were + * called with --help, --version, or another option that was handled by parse_argv(), or it + * looks like we're being asked to do some real work, but we should do that only as part of + * the real shutdown sequence. */ + if (r <= 0) + return r; + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Not executed by init (PID 1). Refusing to operate."); } log_set_always_reopen_console(true); @@ -414,8 +440,7 @@ int main(int argc, char *argv[]) { * through kernel. */ (void) reboot(RB_ENABLE_CAD); - r = parse_argv(argc, argv); - if (r < 0) + if (r <= 0) goto error; log_open(); @@ -668,7 +693,6 @@ int main(int argc, char *argv[]) { switch (cmd) { case LINUX_REBOOT_CMD_KEXEC: - if (!in_container) { /* Preserve fd stores via the kernel Live Update Orchestrator before kexec. * The session fd must stay open until the kexec syscall. */ @@ -706,14 +730,21 @@ int main(int argc, char *argv[]) { /* If we are in a container, and we lacked CAP_SYS_BOOT just exit, this will kill our * container for good. */ log_info("Exiting container."); - return EXIT_SUCCESS; + return 0; } r = log_error_errno(errno, "Failed to invoke reboot(): %m"); error: - log_struct_errno(LOG_EMERG, r, - LOG_MESSAGE("Critical error while doing system shutdown: %m"), - LOG_MESSAGE_ID(SD_MESSAGE_SHUTDOWN_ERROR_STR)); + if (r == 0) + log_struct(LOG_EMERG, + LOG_MESSAGE("Non-shutdown operation requested, cannot operate."), + LOG_MESSAGE_ID(SD_MESSAGE_SHUTDOWN_ERROR_STR)); + else + log_struct_errno(LOG_EMERG, r, + LOG_MESSAGE("Critical error while doing system shutdown: %m"), + LOG_MESSAGE_ID(SD_MESSAGE_SHUTDOWN_ERROR_STR)); freeze(); } + +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index b0e668a38a..7722c458cb 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -3138,9 +3138,8 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl VERB_FULL(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, /* dat= */ 0u, /* help= */ NULL); static int verb_help(int argc, char **argv, uintptr_t data, void *userdata) { - return command_print_help_full( - arg_image_class == IMAGE_SYSEXT ? "systemd-sysext" : "systemd-confext", - /* footer_ansi_seq= */ NULL); + return command_print_help_name( + arg_image_class == IMAGE_SYSEXT ? "systemd-sysext" : "systemd-confext"); } static int parse_argv(int argc, char *argv[], char ***ret_args) { @@ -3156,9 +3155,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { switch (c) { OPTION_COMMON_HELP: - return command_print_help_full( - arg_image_class == IMAGE_SYSEXT ? "systemd-sysext" : "systemd-confext", - /* footer_ansi_seq= */ NULL); + return verb_help(argc, argv, /* data= */ 0u, /* userdata= */ NULL); OPTION_COMMON_VERSION: return version(); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 6a36afc6d3..63fdfbc064 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -351,7 +351,7 @@ static int systemctl_parse_argv(int argc, char *argv[], int log_level_shift, cha OPTION_NAMESPACE("systemctl"): {} OPTION_COMMON_HELP: - return command_print_help_full("systemctl", /* footer_ansi_seq= */ NULL); + return command_print_help_name("systemctl"); OPTION_COMMON_VERSION: return version(); 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/test/test-argv-util.c b/src/test/test-argv-util.c index df844db30a..dfe813a1eb 100644 --- a/src/test/test-argv-util.c +++ b/src/test/test-argv-util.c @@ -99,19 +99,6 @@ TEST(rename_process) { test_rename_process_one("1234567", 1); /* should always fit */ } -TEST(argv_help) { - assert_se(argv_looks_like_help(1, STRV_MAKE("program"))); - assert_se(argv_looks_like_help(2, STRV_MAKE("program", "help"))); - assert_se(argv_looks_like_help(3, STRV_MAKE("program", "arg1", "--help"))); - assert_se(argv_looks_like_help(4, STRV_MAKE("program", "arg1", "arg2", "-h"))); - assert_se(!argv_looks_like_help(2, STRV_MAKE("program", "arg1"))); - assert_se(!argv_looks_like_help(4, STRV_MAKE("program", "arg1", "arg2", "--h"))); - assert_se(!argv_looks_like_help(3, STRV_MAKE("program", "Help", "arg2"))); - assert_se(argv_looks_like_help(5, STRV_MAKE("program", "--help", "arg1", "-h", "--help"))); - assert_se(!argv_looks_like_help(4, STRV_MAKE("program","arg1", "arg2", "-H"))); - assert_se(!argv_looks_like_help(3, STRV_MAKE("program", "--Help", "arg2"))); -} - static int intro(void) { log_show_color(true); return EXIT_SUCCESS; diff --git a/src/test/test-proc-cmdline.c b/src/test/test-proc-cmdline.c index 69ad4e212f..c43e7fab1d 100644 --- a/src/test/test-proc-cmdline.c +++ b/src/test/test-proc-cmdline.c @@ -271,9 +271,10 @@ TEST(proc_cmdline_filter_pid1_args) { "--log-color\0--log-level\0drop3\0" /* optional argument ("--log-level" is handled as another option) */ "--log-color\0accept5\0" /* optional argument (separated with space) */ "--log-color=drop4\0accept6\0" /* optional argument (concatenated with '=') */ + "--introspect-cli\0accept7\0" /* no argument */ "--log-color\0--\0" /* "--" is _not_ handled as argument, and remaining strings are accepted */ "remaining\0-x\0--foo\0", - STRV_MAKE("accept1", "accept2", "accept3", "accept4", "accept5", "accept6", "remaining", "-x", "--foo")); + STRV_MAKE("accept1", "accept2", "accept3", "accept4", "accept5", "accept6", "accept7", "remaining", "-x", "--foo")); /* test for "--" */ test_proc_cmdline_filter_pid1_args_one("systemd\0" 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 ce9124696b..cfdbe5c5ef 100755 --- a/test/units/TEST-74-AUX-UTILS.introspect-cli.sh +++ b/test/units/TEST-74-AUX-UTILS.introspect-cli.sh @@ -40,6 +40,7 @@ INTROSPECTABLE=( shutdown storagectl systemctl + systemd systemd-ac-power systemd-analyze systemd-ask-password @@ -61,29 +62,38 @@ INTROSPECTABLE=( systemd-detect-virt systemd-dissect systemd-escape + systemd-executor systemd-export systemd-factory-reset 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 @@ -91,6 +101,7 @@ INTROSPECTABLE=( systemd-path systemd-pcrextend systemd-pcrlock + systemd-portabled systemd-pty-forward systemd-pull systemd-random-seed @@ -101,8 +112,10 @@ INTROSPECTABLE=( systemd-report-files systemd-report-sign-plain systemd-report-sign-tsm + systemd-resolved systemd-run systemd-sbsign + systemd-shutdown systemd-sleep systemd-socket-activate systemd-socket-proxyd @@ -115,7 +128,10 @@ INTROSPECTABLE=( systemd-sysext systemd-sysinstall systemd-sysupdate + systemd-sysupdated systemd-sysusers + systemd-timedated + systemd-timesyncd systemd-tmpfiles systemd-tpm2-clear systemd-tpm2-setup @@ -180,9 +196,23 @@ 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"])' +# systemd is optionally a multicall binary that also provides systemd-executor +if [[ "$(readlink "$(command -v systemd-executor)" 2>/dev/null)" == systemd ]]; then + systemd --introspect-cli | jq -e \ + '[.commands[].names[0]] | sort == ["systemd", "systemd-executor"]' +fi + if command -v systemd-mount >/dev/null; then systemd-mount --introspect-cli | jq -e \ '[.commands[].names[0]] | sort == ["systemd-mount", "systemd-umount"]'