Add a "declarative program header" and introspection of options and verbs (#43152)

This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-08-14 11:52:42 +02:00
committed by GitHub
22 changed files with 1029 additions and 590 deletions
+12 -23
View File
@@ -1,13 +1,13 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-json.h"
#include "battery-util.h"
#include "build.h"
#include "format-table.h"
#include "help-util.h"
#include "log.h"
#include "main-func.h"
#include "options.h"
#include "string-util.h"
#include "verbs.h"
static bool arg_verbose = false;
@@ -16,25 +16,11 @@ static enum {
ACTION_LOW,
} arg_action = ACTION_AC_POWER;
static int help(void) {
int r;
_cleanup_(table_unrefp) Table *options = NULL;
r = option_parser_get_help_table(&options);
if (r < 0)
return r;
help_cmdline("[OPTIONS...]");
help_abstract("Report whether we are connected to an external power source.");
help_section("Options");
r = table_print_or_warn(options);
if (r < 0)
return r;
help_man_page_reference("systemd-ac-power", "1");
return 0;
}
COMMAND(
"systemd-ac-power\0",
"Report whether we are connected to an external power source.",
.man_pages = "systemd-ac-power.1\0",
);
static int parse_argv(int argc, char *argv[]) {
@@ -46,7 +32,7 @@ static int parse_argv(int argc, char *argv[]) {
FOREACH_OPTION_OR_RETURN(c, &opts)
switch (c) {
OPTION_COMMON_HELP:
return help();
return command_print_help("systemd-ac-power");
OPTION_COMMON_VERSION:
return version();
@@ -58,6 +44,9 @@ static int parse_argv(int argc, char *argv[]) {
OPTION_LONG("low", NULL, "Check if battery is discharging and low"):
arg_action = ACTION_LOW;
break;
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(SD_JSON_FORMAT_OFF);
}
if (option_parser_get_n_args(&opts) > 0)
+11 -58
View File
@@ -57,8 +57,6 @@
#include "dissect-image.h"
#include "dlopen-note.h"
#include "extract-word.h"
#include "format-table.h"
#include "help-util.h"
#include "image-policy.h"
#include "log.h"
#include "loop-util.h"
@@ -198,62 +196,14 @@ static int verb_transient_settings(int argc, char *argv[], uintptr_t _data, void
return 0;
}
static int help(void) {
static const char *const vgroups[] = {
"Boot Analysis",
"Dependency Analysis",
"Configuration Files and Search Paths",
"Enumerate OS Concepts",
"Expression Evaluation",
"Clock & Time",
"Unit & Service Analysis",
"Executable Analysis",
"TPM Operations",
};
COMMAND(
"systemd-analyze\0",
.abstract = "Profile systemd, show unit dependencies, check unit files.",
.man_pages = "systemd-analyze.1\0",
.pager_flags = &arg_pager_flags,
);
Table *vtables[ELEMENTSOF(vgroups)] = {};
CLEANUP_ELEMENTS(vtables, table_unref_array_clear);
_cleanup_(table_unrefp) Table *options = NULL;
int r;
pager_open(arg_pager_flags);
for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) {
r = verbs_get_help_table_group(vgroups[i], &vtables[i]);
if (r < 0)
return r;
}
r = option_parser_get_help_table(&options);
if (r < 0)
return r;
assert_cc(ELEMENTSOF(vtables) == 9);
(void) table_sync_column_widths(0, options, vtables[0], vtables[1], vtables[2],
vtables[3], vtables[4], vtables[5], vtables[6],
vtables[7], vtables[8]);
help_cmdline("[OPTIONS...] COMMAND ...");
help_abstract("Profile systemd, show unit dependencies, check unit files.");
for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) {
help_section(vgroups[i]);
r = table_print_or_warn(vtables[i]);
if (r < 0)
return r;
}
help_section("Options");
r = table_print_or_warn(options);
if (r < 0)
return r;
help_man_page_reference("systemd-analyze", "1");
return 0;
}
VERB_COMMON_HELP_HIDDEN(help);
VERB_COMMON_HELP_AUTO_HIDDEN("systemd-analyze");
/* When updating this list, including descriptions, apply changes to
* shell-completion/bash/systemd-analyze and shell-completion/zsh/_systemd-analyze too. */
@@ -386,7 +336,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
break;
OPTION_COMMON_HELP:
return help();
return command_print_help("systemd-analyze");
OPTION_COMMON_VERSION:
return version();
@@ -610,6 +560,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
strv_free_and_replace(arg_debugger_args, l);
break;
}
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(arg_json_format_flags);
}
_cleanup_strv_free_ char **args = strv_copy(option_parser_get_args(&opts)); /* args is [arg1, arg2, …] */
+11 -31
View File
@@ -9,20 +9,18 @@
#include "build.h"
#include "bus-polkit.h"
#include "constants.h"
#include "format-table.h"
#include "hashmap.h"
#include "json-util.h"
#include "log.h"
#include "main-func.h"
#include "options.h"
#include "parse-argument.h"
#include "pretty-print.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
#include "varlink-io.systemd.AskPassword.h"
#include "varlink-util.h"
#include "verbs.h"
static const char *arg_icon = NULL;
static const char *arg_id = NULL; /* identifier for 'ask-password' protocol */
@@ -38,35 +36,14 @@ static bool arg_varlink = false;
STATIC_DESTRUCTOR_REGISTER(arg_message, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
_cleanup_(table_unrefp) Table *options = NULL;
int r;
r = terminal_urlify_man("systemd-ask-password", "1", &link);
if (r < 0)
return log_oom();
r = option_parser_get_help_table(&options);
if (r < 0)
return r;
printf("%s [OPTIONS...] MESSAGE\n"
"\n%sQuery the user for a passphrase, via the TTY or a UI agent.%s\n"
"\nOptions:\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-ask-password\0",
"Query the user for a passphrase, via the TTY or a UI agent.",
.argspec = "MESSAGE\0",
.man_pages = "systemd-ask-password.1\0",
);
static int parse_argv(int argc, char *argv[]) {
const char *emoji = NULL;
int r;
@@ -78,7 +55,7 @@ static int parse_argv(int argc, char *argv[]) {
FOREACH_OPTION_OR_RETURN(c, &opts)
switch (c) {
OPTION_COMMON_HELP:
return help();
return command_print_help("systemd-ask-password");
OPTION_COMMON_VERSION:
return version();
@@ -159,6 +136,9 @@ static int parse_argv(int argc, char *argv[]) {
OPTION_SHORT('n', NULL, "Do not suffix password written to standard output with newline"):
arg_newline = false;
break;
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(SD_JSON_FORMAT_OFF);
}
if (isempty(emoji) || streq(emoji, "auto"))
+43 -27
View File
@@ -4,15 +4,14 @@
#include <unistd.h>
#include "sd-device.h"
#include "sd-json.h"
#include "alloc-util.h"
#include "argv-util.h"
#include "build.h"
#include "device-private.h"
#include "device-util.h"
#include "escape.h"
#include "fileio.h"
#include "format-table.h"
#include "help-util.h"
#include "main-func.h"
#include "parse-util.h"
#include "percent-util.h"
@@ -23,25 +22,12 @@
#define PCI_CLASS_GRAPHICS_CARD 0x30000
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 [backlight|leds]:DEVICE");
help_abstract("Save and restore backlight brightness at shutdown and boot.");
help_section("Commands");
r = table_print_or_warn(verbs);
if (r < 0)
return r;
help_man_page_reference("systemd-backlight", "8");
return 0;
}
COMMAND(
"systemd-backlight\0",
"Save and restore backlight brightness at shutdown and boot.",
.argspec = "COMMAND [backlight|leds]:DEVICE\0",
.man_pages = "systemd-backlight.8\0",
);
static int has_multiple_graphics_cards(void) {
_cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
@@ -639,15 +625,45 @@ static int verb_save(int argc, char *argv[], uintptr_t _data, void *userdata) {
return 0;
}
static int run(int argc, char *argv[]) {
log_setup();
VERB_COMMON_HELP_AUTO_HIDDEN("systemd-backlight");
if (argv_looks_like_help(argc, argv))
return help();
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("systemd-backlight");
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;
log_setup();
umask(0022);
return dispatch_verb(strv_skip(argv, 1), /* userdata= */ NULL);
char **args = NULL;
r = parse_argv(argc, argv, &args);
if (r <= 0)
return r;
return dispatch_verb(args, /* userdata= */ NULL);
}
DEFINE_MAIN_FUNCTION(run);
+27 -59
View File
@@ -42,7 +42,6 @@
#include "mountpoint-util.h"
#include "namespace-util.h"
#include "nsresource.h"
#include "options.h"
#include "parse-argument.h"
#include "parse-util.h"
#include "path-util.h"
@@ -60,6 +59,7 @@
#include "tmpfile-util.h"
#include "uid-classification.h"
#include "user-util.h"
#include "verbs.h"
#include "vpick.h"
static enum {
@@ -121,63 +121,28 @@ STATIC_DESTRUCTOR_REGISTER(arg_loop_ref, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_filter, image_filter_freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
_cleanup_(table_unrefp) Table *options = NULL, *commands = NULL;
int r;
pager_open(arg_pager_flags);
r = terminal_urlify_man("systemd-dissect", "1", &link);
if (r < 0)
return log_oom();
r = option_parser_get_help_table_ns("systemd-dissect", &options);
if (r < 0)
return r;
r = option_parser_get_help_table_full("systemd-dissect", "Commands", &commands);
if (r < 0)
return r;
/* Make the 1st column same width in both tables */
(void) table_sync_column_widths(0, options, commands);
printf("%1$s [OPTIONS...] IMAGE\n"
"%1$s [OPTIONS...] --mount IMAGE PATH\n"
"%1$s [OPTIONS...] --umount PATH\n"
"%1$s [OPTIONS...] --attach IMAGE\n"
"%1$s [OPTIONS...] --detach PATH\n"
"%1$s [OPTIONS...] --list IMAGE\n"
"%1$s [OPTIONS...] --mtree IMAGE\n"
"%1$s [OPTIONS...] --with IMAGE [COMMAND…]\n"
"%1$s [OPTIONS...] --copy-from IMAGE PATH [TARGET]\n"
"%1$s [OPTIONS...] --copy-to IMAGE [SOURCE] PATH\n"
"%1$s [OPTIONS...] --make-archive IMAGE [TARGET]\n"
"%1$s [OPTIONS...] --discover\n"
"%1$s [OPTIONS...] --validate IMAGE\n"
"%1$s [OPTIONS...] --shift IMAGE UIDBASE\n"
"\n%2$sDissect a Discoverable Disk Image (DDI).%3$s\n"
"\n%4$sOptions:%5$s\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal(),
ansi_underline(),
ansi_normal());
r = table_print_or_warn(options);
if (r < 0)
return r;
printf("\n%sCommands:%s\n", ansi_underline(), ansi_normal());
r = table_print_or_warn(commands);
if (r < 0)
return r;
printf("\nSee the %s for details.\n", link);
return 0;
}
COMMAND(
"systemd-dissect\0",
"Dissect a Discoverable Disk Image (DDI).",
.argspec =
"IMAGE\0"
"--mount IMAGE PATH\0"
"--umount PATH\0"
"--attach IMAGE\0"
"--detach PATH\0"
"--list IMAGE\0"
"--mtree IMAGE\0"
"--with IMAGE [COMMAND…]\0"
"--copy-from IMAGE PATH [TARGET]\0"
"--copy-to IMAGE [SOURCE] PATH\0"
"--make-archive IMAGE [TARGET]\0"
"--discover\0"
"--validate IMAGE\0"
"--shift IMAGE UIDBASE\0",
.man_pages = "systemd-dissect.1\0",
.option_namespace = "systemd-dissect",
.pager_flags = &arg_pager_flags,
);
static int parse_image_path_argument(const char *path, char **ret_root, char **ret_image) {
_cleanup_free_ char *p = NULL;
@@ -439,7 +404,7 @@ static int parse_argv(int argc, char *argv[]) {
OPTION_GROUP("Commands"): {}
OPTION_COMMON_HELP:
return help();
return command_print_help("systemd-dissect");
OPTION_COMMON_VERSION:
return version();
@@ -512,6 +477,9 @@ static int parse_argv(int argc, char *argv[]) {
OPTION_LONG("shift", NULL, "Shift UID range to selected base"):
arg_action = ACTION_SHIFT;
break;
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(arg_json_format_flags);
}
if (system_scope_requested || user_scope_requested)
+13 -86
View File
@@ -32,7 +32,6 @@
#include "fs-util.h"
#include "glyph-util.h"
#include "hashmap.h"
#include "help-util.h"
#include "hexdecoct.h"
#include "home-util.h"
#include "homectl-fido2.h"
@@ -180,6 +179,14 @@ static int acquire_bus(sd_bus **bus) {
return 0;
}
COMMAND(
"homectl\0",
"Create, manipulate or inspect home directories.",
.man_pages = "homectl.1\0",
.pager_flags = &arg_pager_flags,
.flags = COMMAND_HELP_SEPARATE, /* the option table is very wide */
);
VERB_GROUP("Basic User Manipulation Commands");
VERB(verb_list_homes, "list", /* argspec= */ NULL, VERB_ANY, 1, VERB_DEFAULT,
"List home areas");
@@ -4002,90 +4009,7 @@ static int parse_fido2_device_field(const char *arg) {
return 1;
}
static int help(void) {
static const char* const vgroups[] = {
"Basic User Manipulation Commands",
"Advanced User Manipulation Commands",
"User Migration Commands",
"Signing Keys Commands",
"Lock/Unlock Commands",
"Other Commands",
};
static const char* const ogroups[] = {
NULL,
"General User Record Properties",
"Authentication User Record Properties",
"Blob Directory User Record Properties",
"Account Management User Record Properties",
"Password Policy User Record Properties",
"Resource Management User Record Properties",
"Storage User Record Properties",
"LUKS Storage User Record Properties",
"Mounting User Record Properties",
"CIFS User Record Properties",
"Login Behaviour User Record Properties",
};
Table *vtables[ELEMENTSOF(vgroups)] = {};
CLEANUP_ELEMENTS(vtables, table_unref_array_clear);
Table *otables[ELEMENTSOF(ogroups)] = {};
CLEANUP_ELEMENTS(otables, table_unref_array_clear);
int r;
for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) {
r = verbs_get_help_table_group(vgroups[i], &vtables[i]);
if (r < 0)
return r;
}
for (size_t i = 0; i < ELEMENTSOF(ogroups); i++) {
r = option_parser_get_help_table_group(ogroups[i], &otables[i]);
if (r < 0)
return r;
}
/* The two groups are not synchronized because the option table is very wide. */
assert_cc(ELEMENTSOF(vtables) == 6);
(void) table_sync_column_widths(0, vtables[0], vtables[1], vtables[2],
vtables[3], vtables[4], vtables[5]);
assert_cc(ELEMENTSOF(otables) == 12);
(void) table_sync_column_widths(0, otables[0], otables[1], otables[2], otables[3],
otables[4], otables[5], otables[6], otables[7],
otables[8], otables[9], otables[10], otables[11]);
pager_open(arg_pager_flags);
help_cmdline("[OPTIONS…] COMMAND …");
help_abstract("Create, manipulate or inspect home directories.");
for (size_t i = 0; i < ELEMENTSOF(vgroups); i++) {
help_section(vgroups[i]);
r = table_print_or_warn(vtables[i]);
if (r < 0)
return r;
}
help_section("Options");
r = table_print_or_warn(otables[0]);
if (r < 0)
return r;
for (size_t i = 1; i < ELEMENTSOF(ogroups); i++) {
help_section(ogroups[i]);
r = table_print_or_warn(otables[i]);
if (r < 0)
return r;
}
help_man_page_reference("homectl", "1");
return 0;
}
VERB_COMMON_HELP_HIDDEN(help);
VERB_COMMON_HELP_AUTO_HIDDEN("homectl");
static int parse_argv(int argc, char *argv[], char ***remaining_args) {
_cleanup_strv_free_ char **arg_languages = NULL;
@@ -4114,7 +4038,7 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
switch (c) {
OPTION_COMMON_HELP:
return help();
return command_print_help("homectl");
OPTION_COMMON_VERSION:
return version();
@@ -4929,6 +4853,9 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
OPTION_SHORT('N', NULL, /* help= */ NULL):
match_identity = &arg_identity_extra_other_machines;
break;
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(arg_json_format_flags);
}
if (!strv_isempty(arg_languages)) {
+12 -42
View File
@@ -14,7 +14,6 @@
#include "main-func.h"
#include "options.h"
#include "parse-argument.h"
#include "pretty-print.h"
#include "string-util.h"
#include "strv.h"
#include "verbs.h"
@@ -26,6 +25,13 @@ static PagerFlags arg_pager_flags = 0;
static bool arg_legend = true;
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
COMMAND(
"systemd-id128\0",
"Generate and print 128-bit identifiers.",
.man_pages = "systemd-id128.1\0",
.pager_flags = &arg_pager_flags,
);
static int print_id(sd_id128_t id) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
int r;
@@ -228,46 +234,7 @@ static int verb_show(int argc, char *argv[], uintptr_t _data, void *userdata) {
return 0;
}
static int help(void) {
_cleanup_free_ char *link = NULL;
_cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL;
int r;
r = terminal_urlify_man("systemd-id128", "1", &link);
if (r < 0)
return log_oom();
r = option_parser_get_help_table(&options);
if (r < 0)
return r;
r = verbs_get_help_table(&verbs);
if (r < 0)
return r;
/* Make the 1st column same width in both tables */
(void) table_sync_column_widths(0, options, verbs);
printf("%s [OPTIONS...] COMMAND\n\n"
"%sGenerate and print 128-bit identifiers.%s\n"
"\nCommands:\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal());
r = table_print_or_warn(verbs);
if (r < 0)
return r;
printf("\nOptions:\n");
r = table_print_or_warn(options);
if (r < 0)
return r;
printf("\nSee the %s for details.\n", link);
return 0;
}
VERB_COMMON_HELP(help);
VERB_COMMON_HELP_AUTO("systemd-id128");
static int parse_argv(int argc, char *argv[], char ***ret_args) {
int r;
@@ -280,7 +247,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
FOREACH_OPTION_OR_RETURN(c, &opts)
switch (c) {
OPTION_COMMON_HELP:
return help();
return command_print_help("systemd-id128");
OPTION_COMMON_VERSION:
return version();
@@ -326,6 +293,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
OPTION('u', "uuid", NULL, "Output in UUID format"):
arg_mode = ID128_PRINT_UUID;
break;
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(arg_json_format_flags);
}
if (arg_mode == ID128_PRINT_PRETTY && sd_json_format_enabled(arg_json_format_flags))
+20 -35
View File
@@ -6,6 +6,7 @@
#include "sd-daemon.h"
#include "sd-event.h"
#include "sd-json.h"
#include "alloc-util.h"
#include "build.h"
@@ -15,13 +16,10 @@
#include "exit-status.h"
#include "fd-util.h"
#include "fdset.h"
#include "format-table.h"
#include "format-util.h"
#include "help-util.h"
#include "log.h"
#include "main-func.h"
#include "notify-recv.h"
#include "options.h"
#include "parse-util.h"
#include "pidref.h"
#include "process-util.h"
@@ -30,6 +28,7 @@
#include "strv.h"
#include "time-util.h"
#include "user-util.h"
#include "verbs.h"
static enum {
ACTION_NOTIFY,
@@ -56,27 +55,15 @@ STATIC_DESTRUCTOR_REGISTER(arg_exec, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_fds, fdset_freep);
STATIC_DESTRUCTOR_REGISTER(arg_fdname, freep);
static int help(void) {
int r;
_cleanup_(table_unrefp) Table *options = NULL;
r = option_parser_get_help_table(&options);
if (r < 0)
return r;
help_cmdline("[OPTIONS...] [VARIABLE=VALUE...]");
help_cmdline("[OPTIONS...] --exec [VARIABLE=VALUE...] ; -- CMDLINE...");
help_cmdline("[OPTIONS...] --fork -- CMDLINE...");
help_abstract("Notify the service manager about service status updates.");
help_section("Options");
r = table_print_or_warn(options);
if (r < 0)
return r;
help_man_page_reference("systemd-notify", "1");
return 0;
}
COMMAND(
"systemd-notify\0",
"Notify the service manager about service status updates.",
.argspec =
"[VARIABLE=VALUE…]\0"
"--exec [VARIABLE=VALUE…] ; -- CMDLINE…\0"
"--fork -- CMDLINE…\0",
.man_pages = "systemd-notify.1\0",
);
static int get_manager_pid(PidRef *ret) {
int r;
@@ -159,7 +146,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
switch (c) {
OPTION_COMMON_HELP:
return help();
return command_print_help("systemd-notify");
OPTION_COMMON_VERSION:
return version();
@@ -273,6 +260,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
OPTION('q', "quiet", NULL, "Do not show PID of child when forking"):
arg_quiet = true;
break;
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(SD_JSON_FORMAT_OFF);
}
bool have_env = arg_ready || arg_stopping || arg_reloading || arg_status || pidref_is_set(&arg_pid) || !fdset_isempty(arg_fds);
@@ -283,7 +273,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
case ACTION_NOTIFY: {
if (arg_fdname && fdset_isempty(arg_fds))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No file descriptors passed, but --fdname= set, refusing.");
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No file descriptors passed, but --fdname= used, refusing.");
size_t n_arg_env;
@@ -308,14 +298,10 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
n_arg_env = strv_length(args);
have_env = have_env || n_arg_env > 0;
if (!have_env) {
if (do_exec)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No notify message specified while --exec, refusing.");
/* No argument at all? */
help();
return -EINVAL;
}
if (!have_env)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
do_exec ? "No notify message specified with --exec, refusing." :
"Nothing to notify about was specified.");
if (n_arg_env > 0) {
arg_env = strv_copy_n(args, n_arg_env);
@@ -417,7 +403,6 @@ static int on_child(sd_event_source *s, const siginfo_t *si, void *userdata) {
}
static int action_fork(char *const *_command) {
static const int forward_signals[] = {
SIGHUP,
SIGTERM,
+23 -33
View File
@@ -2,18 +2,18 @@
#include <stdlib.h>
#include "sd-json.h"
#include "alloc-util.h"
#include "build.h"
#include "extract-word.h"
#include "fileio.h"
#include "format-table.h"
#include "help-util.h"
#include "log.h"
#include "options.h"
#include "resolvconf-compat.h"
#include "resolvectl.h"
#include "string-util.h"
#include "strv.h"
#include "verbs.h"
typedef enum LookupType {
LOOKUP_TYPE_REGULAR,
@@ -21,35 +21,22 @@ typedef enum LookupType {
LOOKUP_TYPE_EXCLUSIVE, /* -x */
} LookupType;
static int resolvconf_help(void) {
_cleanup_(table_unrefp) Table *options = NULL;
int r;
r = option_parser_get_help_table_ns("resolvconf", &options);
if (r < 0)
return r;
help_cmdline("-a INTERFACE <FILE");
help_cmdline("-d INTERFACE");
help_abstract("Register DNS server and domain configuration with systemd-resolved.");
help_section("Options");
r = table_print_or_warn(options);
if (r < 0)
return r;
printf("\n"
"This is a compatibility alias for the resolvectl(1) tool, providing native\n"
"command line compatibility with the resolvconf(8) tool of various Linux\n"
"distributions and BSD systems. Some options supported by other implementations\n"
"are not supported and are ignored: -m, -u. Various options supported by other\n"
"implementations are not supported and will cause the invocation to fail:\n"
"-I, -i, -l, -R, -r, -v, -V, --enable-updates, --disable-updates,\n"
"--updates-are-enabled.\n");
help_man_page_reference("resolvectl", "1");
return 0;
}
COMMAND(
"resolvconf\0",
"Register DNS server and domain configuration with systemd-resolved.",
.man_pages = "resolvectl.1\0",
.argspec = "-a INTERFACE <FILE\0"
"-d INTERFACE\0",
.footer =
"This is a compatibility alias for the resolvectl(1) tool, providing native "
"command line compatibility with the resolvconf(8) tool of various Linux "
"distributions and BSD systems. Some options supported by other implementations "
"are not supported and are ignored: -m, -u. Various options supported by other "
"implementations are not supported and will cause the invocation to fail: "
"-I, -i, -l, -R, -r, -v, -V, --enable-updates, --disable-updates, "
"--updates-are-enabled.",
.option_namespace = "resolvconf",
);
static int parse_nameserver(const char *string) {
int r;
@@ -194,7 +181,7 @@ int resolvconf_parse_argv(int argc, char *argv[]) {
OPTION_NAMESPACE("resolvconf"): {}
OPTION_COMMON_HELP:
return resolvconf_help();
return command_print_help("resolvconf");
OPTION_COMMON_VERSION:
return version();
@@ -253,6 +240,9 @@ int resolvconf_parse_argv(int argc, char *argv[]) {
OPTION_LONG("updates-are-enabled", NULL, /* help= */ NULL):
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Switch --updates-are-enabled not supported.");
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(SD_JSON_FORMAT_OFF);
}
if (arg_mode == _MODE_INVALID)
+26 -63
View File
@@ -30,7 +30,6 @@
#include "format-ifname.h"
#include "format-table.h"
#include "glyph-util.h"
#include "help-util.h"
#include "hostname-util.h"
#include "json-util.h"
#include "main-func.h"
@@ -97,6 +96,15 @@ STATIC_DESTRUCTOR_REGISTER(arg_set_dns, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_set_domain, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_set_nta, strv_freep);
COMMAND(
"resolvectl\0",
"Send control commands to the network name resolution manager, or "
"resolve domain names, IPv4 and IPv6 addresses, DNS records, and services.",
.man_pages = "resolvectl.1\0",
.option_namespace = "resolvectl",
.pager_flags = &arg_pager_flags,
);
typedef enum StatusMode {
STATUS_ALL,
STATUS_DNS,
@@ -3224,67 +3232,15 @@ static void help_dns_classes(void) {
DUMP_STRING_TABLE(dns_class, int, _DNS_CLASS_MAX);
}
static int compat_help(void) {
_cleanup_(table_unrefp) Table *options = NULL;
int r;
VERB_COMMON_HELP_AUTO_HIDDEN("resolvectl");
r = option_parser_get_help_table_ns("systemd-resolve", &options);
if (r < 0)
return r;
pager_open(arg_pager_flags);
help_cmdline("[OPTIONS…] HOSTNAME|ADDRESS…");
help_cmdline("[OPTIONS…] --service [[NAME] TYPE] DOMAIN");
help_cmdline("[OPTIONS…] --openpgp EMAIL@DOMAIN…");
help_cmdline("[OPTIONS…] --statistics");
help_cmdline("[OPTIONS…] --reset-statistics");
help_abstract("Resolve domain names, IPv4 and IPv6 addresses, DNS records, and services.");
help_section("Options");
r = table_print_or_warn(options);
if (r < 0)
return r;
help_man_page_reference("resolvectl", "1");
return 0;
}
static int native_help(void) {
_cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL;
int r;
r = verbs_get_help_table(&verbs);
if (r < 0)
return r;
r = option_parser_get_help_table_ns("resolvectl", &options);
if (r < 0)
return r;
(void) table_sync_column_widths(0, verbs, options);
pager_open(arg_pager_flags);
help_cmdline("[OPTIONS…] COMMAND …");
help_abstract("Send control commands to the network name resolution manager, or\n"
"resolve domain names, IPv4 and IPv6 addresses, DNS records, and services.");
help_section("Commands");
r = table_print_or_warn(verbs);
if (r < 0)
return r;
help_section("Options");
r = table_print_or_warn(options);
if (r < 0)
return r;
help_man_page_reference("resolvectl", "1");
return 0;
}
VERB_COMMON_HELP_HIDDEN(native_help);
COMMAND(
"systemd-resolve\0",
"This command is deprecated. Use resolvectl.1 instead.",
.man_pages = "resolvectl.1\0",
.option_namespace = "systemd-resolve",
.pager_flags = &arg_pager_flags,
);
static int compat_parse_argv(int argc, char *argv[], char ***remaining_args) {
int r;
@@ -3301,7 +3257,8 @@ static int compat_parse_argv(int argc, char *argv[], char ***remaining_args) {
OPTION_NAMESPACE("systemd-resolve"): {}
OPTION_COMMON_HELP:
return compat_help();
printf("systemd-resolve is deprecated. Call resolvectl instead.\n");
return 0;
OPTION_COMMON_VERSION:
return version();
@@ -3499,6 +3456,9 @@ static int compat_parse_argv(int argc, char *argv[], char ***remaining_args) {
if (r < 0)
return r;
break;
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(arg_json_format_flags);
}
if (arg_type == 0 && arg_class != 0)
@@ -3541,7 +3501,7 @@ static int native_parse_argv(int argc, char *argv[], char ***remaining_args) {
OPTION_NAMESPACE("resolvectl"): {}
OPTION_COMMON_HELP:
return native_help();
return command_print_help("resolvectl");
OPTION_COMMON_VERSION:
return version();
@@ -3726,6 +3686,9 @@ static int native_parse_argv(int argc, char *argv[], char ***remaining_args) {
OPTION_COMMON_LOWERCASE_J:
arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO;
break;
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(arg_json_format_flags);
}
if (arg_raw != RAW_NONE && sd_json_format_enabled(arg_json_format_flags))
+23
View File
@@ -2250,6 +2250,29 @@ int _table_sync_column_widths(size_t column, Table *a, ...) {
return r;
}
int table_sync_all_column_widths(size_t column, Table **a) {
size_t max = 0;
int r;
/* Make the specified column have the same width in the tables. */
for (Table **t = a; t && *t; t++) {
size_t w;
r = table_data_requested_width(*t, column, &w);
if (r < 0)
return log_error_errno(r, "Failed to query table column width: %m");
max = MAX(max, w);
}
r = 0;
for (Table **t = a; t && *t; t++)
RET_GATHER(r, table_set_column_width(*t, column, max));
return r;
}
int table_print_full(Table *t, FILE *f, bool flush) {
_cleanup_free_ size_t *sorted = NULL;
int r;
+1
View File
@@ -149,6 +149,7 @@ int table_data_requested_width(Table *table, size_t column, size_t *ret);
int table_set_column_width(Table *t, size_t column, size_t width);
int _table_sync_column_widths(size_t column, Table *a, ...);
#define table_sync_column_widths(column, a, ...) _table_sync_column_widths(column, a, __VA_ARGS__, NULL)
int table_sync_all_column_widths(size_t column, Table **a);
int table_print_full(Table *t, FILE *f, bool flush);
static inline int table_print(Table *t) {
+2
View File
@@ -85,6 +85,7 @@ typedef struct MachineCredentialContext MachineCredentialContext;
typedef struct MountOptions MountOptions;
typedef struct MStack MStack;
typedef struct OpenFile OpenFile;
typedef struct Option Option;
typedef struct Pkcs11EncryptedKey Pkcs11EncryptedKey;
typedef struct QmpClient QmpClient;
typedef struct QmpSlot QmpSlot;
@@ -94,4 +95,5 @@ typedef struct Tpm2Handle Tpm2Handle;
typedef struct Tpm2PCRValue Tpm2PCRValue;
typedef struct UnitInfo UnitInfo;
typedef struct UserRecord UserRecord;
typedef struct Verb Verb;
typedef struct VeritySettings VeritySettings;
+184 -1
View File
@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-json.h"
#include "format-table.h"
#include "log.h"
#include "options.h"
@@ -470,6 +472,24 @@ char* option_get_synopsis(const Option *opt, const char *joiner, bool show_metav
option_arg_optional(opt) ? "]" : "");
}
const Option* options_find_namespace(
const Option options[],
const Option options_end[],
const char *namespace) {
if (!namespace)
/* The first part is the default unnamed namespace, so
* if the namespace was not specified, we are in it. */
return options;
for (const Option *opt = options; opt < options_end; opt++)
if (FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER) &&
streq(namespace, ASSERT_PTR(opt->long_code)))
return opt + 1;
return NULL; /* not found :/ */
}
int _option_parser_get_help_table_full(
const Option options[],
const Option options_end[],
@@ -478,6 +498,8 @@ int _option_parser_get_help_table_full(
Table **ret) {
int r;
// TODO: drop this function and all its wrappers
assert(ret);
_cleanup_(table_unrefp) Table *table = table_new("names", "help");
@@ -492,7 +514,8 @@ int _option_parser_get_help_table_full(
* <group>? The first part is the default group, so if the group was
* not specified, we are in it. */
for (const Option *opt = options; opt < options_end; opt++) {
const Option *opt;
for (opt = options; opt < options_end; opt++) {
bool ns_marker = FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER);
if (!in_ns) {
in_ns = ns_marker && streq(namespace, opt->long_code);
@@ -543,5 +566,165 @@ int _option_parser_get_help_table_full(
table_set_header(table, false);
*ret = TAKE_PTR(table);
assert(opt - options < INT_MAX);
return opt - options;
}
int options_get_help_table_group(
const Option options[],
const Option options_end[],
Table **ret,
const char **ret_group) {
int r;
assert(ret);
assert(ret_group);
_cleanup_(table_unrefp) Table *table = table_new("names", "help");
if (!table)
return log_oom();
assert(options_end > options);
bool group_marker = FLAGS_SET(options[0].flags, OPTION_GROUP_MARKER);
const char *group = group_marker ? ASSERT_PTR(options[0].long_code) : NULL;
const Option *opt;
for (opt = options + group_marker; opt < options_end; opt++) {
if (FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER))
/* End of our namespace */
break;
if (FLAGS_SET(opt->flags, OPTION_GROUP_MARKER))
/* End of the group */
break;
if (!opt->help)
/* No help string — we do not show the option */
continue;
_cleanup_free_ char *s = option_get_synopsis(opt, " ", /* show_metavar= */ true);
if (!s)
return log_oom();
/* We indent the option string by two spaces. We could set the minimum cell width and
* right-align for a similar result, but that'd be more work. This is only used for
* display. */
bool shift_left = opt->short_code != 0 || FLAGS_SET(opt->flags, OPTION_HELP_ENTRY_VERBATIM);
const char *prefix = shift_left ? " " : " ";
_cleanup_free_ char *t = strjoin(prefix, s);
if (!t)
return log_oom();
r = table_add_many(table, TABLE_STRING, t);
if (r < 0)
return table_log_add_error(r);
_cleanup_strv_free_ char **split = strv_split(opt->help, /* separators= */ NULL);
if (!split)
return log_oom();
r = table_add_many(table, TABLE_STRV_WRAPPED, split);
if (r < 0)
return table_log_add_error(r);
}
assert(!table_isempty(table)); /* Empty group? Something is off. */
table_set_header(table, false);
*ret = TAKE_PTR(table);
*ret_group = group;
assert(opt - options < INT_MAX);
return opt - options;
}
static int option_build_json(const Option *opt, const char *group, sd_json_variant **ret) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *names = NULL;
int r;
assert(opt);
assert(opt->short_code != 0 || opt->long_code);
assert(ret);
if (opt->short_code != 0) {
char s[3] = { '-', opt->short_code };
r = sd_json_variant_append_arrayb(&names, SD_JSON_BUILD_STRING(s));
if (r < 0)
return r;
}
if (opt->long_code) {
_cleanup_free_ char *s = strjoin("--", opt->long_code);
if (!s)
return -ENOMEM;
r = sd_json_variant_append_arrayb(&names, SD_JSON_BUILD_STRING(s));
if (r < 0)
return r;
}
const char *argtype =
option_arg_required(opt) ? "required_argument" :
option_arg_optional(opt) ? "optional_argument" :
"no_argument";
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_VARIANT("names", names),
SD_JSON_BUILD_PAIR_STRING("argument", argtype),
SD_JSON_BUILD_PAIR_CONDITION(
!!opt->metavar,
"metavar", SD_JSON_BUILD_STRING(opt->metavar)),
SD_JSON_BUILD_PAIR_CONDITION(!!opt->help, "help", SD_JSON_BUILD_STRING(opt->help)),
SD_JSON_BUILD_PAIR_CONDITION(
!!group,
"group", SD_JSON_BUILD_STRV(STRV_MAKE(group))));
}
int options_build_json(
const Option options[],
const Option options_end[],
const char *namespace,
sd_json_variant **ret) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
const char *group = NULL;
int r;
assert(ret);
const Option *start = options_find_namespace(options, options_end, namespace);
if (!start)
return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN),
"Option namespace %s not found.",
namespace ?: "(unnamed)");
for (const Option *opt = start; opt < options_end; opt++) {
if (FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER))
break; /* End of our namespace */
if (FLAGS_SET(opt->flags, OPTION_GROUP_MARKER)) {
group = opt->long_code;
continue;
}
if (option_is_metadata(opt))
/* Positional and help entries are only used for display */
continue;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *o = NULL;
r = option_build_json(opt, group, &o);
if (r < 0)
return log_error_errno(r, "Failed to build JSON object for option '%s': %m",
opt->long_code ?: CHAR_TO_STR(opt->short_code));
r = sd_json_variant_append_array(&array, o);
if (r < 0)
return log_error_errno(r, "Failed to append JSON object to array: %m");
}
*ret = TAKE_PTR(array);
return 0;
}
+27
View File
@@ -95,6 +95,14 @@ assert_cc(sizeof(Option) % sizeof(void*) == 0);
#define OPTION_COMMON_VERSION \
OPTION_LONG("version", NULL, "Show package version")
#define OPTION_COMMON_INTROSPECT_CLI \
/* This option is internal-only for now and not shown in --help */ \
OPTION_LONG("introspect-cli", NULL, /* help= */ NULL)
/* For programs which do not do option parsing, but check the argv array directly. */
#define OPTION_COMMON_INTROSPECT_CLI_ENTRY \
_OPTION_ENTRY(__COUNTER__, /* fl= */ 0, /* sc= */ 0, "introspect-cli", /* mv= */ NULL, /* d= */ 0u, /* h= */ NULL)
#define OPTION_COMMON_NO_PAGER \
OPTION_LONG("no-pager", NULL, "Do not start a pager")
@@ -266,6 +274,11 @@ char* option_parser_get_arg(const OptionParser *state, size_t i);
char* option_get_synopsis(const Option *opt, const char *joiner, bool show_metavar);
const Option* options_find_namespace(
const Option options[],
const Option options_end[],
const char *namespace);
int _option_parser_get_help_table_full(
const Option options[],
const Option options_end[],
@@ -280,3 +293,17 @@ int _option_parser_get_help_table_full(
option_parser_get_help_table_full(/* namespace= */ NULL, group, ret)
#define option_parser_get_help_table(ret) \
option_parser_get_help_table_group(/* group= */ NULL, ret)
int options_get_help_table_group(
const Option options[],
const Option options_end[],
Table **ret,
const char **ret_group);
/* Build a JSON array of CLI-introspection option objects for the options in the given namespace.
* Returns NULL in *ret if the namespace defines no options. */
int options_build_json(
const Option options[],
const Option options_end[],
const char *namespace,
sd_json_variant **ret);
+7 -2
View File
@@ -180,8 +180,13 @@ int terminal_urlify_path(const char *path, const char *text, char **ret) {
int terminal_urlify_man_full(const char *page, const char *section, const char *suffix, char **ret) {
const char *url, *text;
url = strjoina("man:", page, "(", section, ")");
text = strjoina(page, "(", section, ")", suffix);
if (section) {
url = strjoina("man:", page, "(", section, ")");
text = strjoina(page, "(", section, ")", suffix);
} else {
url = strjoina("man:", page);
text = strjoina(page, suffix);
}
return terminal_urlify(url, text, ret);
}
+420 -5
View File
@@ -1,8 +1,17 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-json.h"
#include "build.h"
#include "env-util.h"
#include "extract-word.h"
#include "format-table.h"
#include "help-util.h"
#include "log.h"
#include "nulstr-util.h"
#include "options.h"
#include "pager.h"
#include "pretty-print.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
@@ -58,15 +67,16 @@ bool should_bypass(const char *env_prefix) {
}
static bool verb_is_metadata(const Verb *verb) {
/* A metadata entry that is not a real verb, like the group marker */
return FLAGS_SET(ASSERT_PTR(verb)->flags, VERB_GROUP_MARKER);
/* A metadata entry that is not a real verb, i.e. either the command and group markers. */
return FLAGS_SET(ASSERT_PTR(verb)->flags, VERB_COMMAND_MARKER) ||
FLAGS_SET(ASSERT_PTR(verb)->flags, VERB_GROUP_MARKER);
}
const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]) {
assert(verbs);
assert(verbs_end > verbs);
assert((uintptr_t) verbs % sizeof(void*) == 0);
assert(verbs[0].verb);
assert(FLAGS_SET(verbs[0].flags, VERB_COMMAND_MARKER) || verbs[0].verb);
for (const Verb *verb = verbs; verb < verbs_end; verb++) {
if (verb_is_metadata(verb))
@@ -86,7 +96,7 @@ int _dispatch_verb(char **args, const Verb verbs[], const Verb verbs_end[], void
assert(verbs);
assert(verbs_end > verbs);
assert((uintptr_t) verbs % sizeof(void*) == 0);
assert(verbs[0].verb);
assert(FLAGS_SET(verbs[0].flags, VERB_COMMAND_MARKER) || verbs[0].verb);
const char *name = args ? args[0] : NULL;
size_t left = strv_length(args);
@@ -214,6 +224,26 @@ static int verb_add_help_one(Table *table, const Verb *verb) {
return 0;
}
static const Verb* verbs_get_command(const Verb verbs[], const Verb verbs_end[], const char *name) {
assert(verbs);
assert(verbs_end > verbs);
assert((uintptr_t) verbs % sizeof(void*) == 0);
assert(FLAGS_SET(verbs[0].flags, VERB_COMMAND_MARKER) || verbs[0].verb);
assert(name);
for (const Verb *verb = verbs; verb < verbs_end; verb++) {
if (!FLAGS_SET(verb->flags, VERB_COMMAND_MARKER))
continue;
const CommandDescription *cmd = (const CommandDescription*) ASSERT_PTR(verb->data);
if (nulstr_contains(cmd->names, name))
return verb;
}
/* At the end of the list? */
return NULL;
}
int _verbs_get_help_table(
const Verb verbs[],
const Verb verbs_end[],
@@ -234,7 +264,18 @@ int _verbs_get_help_table(
* group <group>? The first part is the default group, so
* if the group was not specified, we are in. */
for (const Verb *verb = verbs; verb < verbs_end; verb++) {
const Verb *verb;
for (verb = verbs; verb < verbs_end; verb++) {
/* Binaries with one or more VERB_COMMAND entries call this function
* through print_verb_option_help() which provides a verbs slice that
* starts after the VERB_COMMAND entry.
*
* In unconverted binaries, this function is called directly. */
// TODO: make this function static after everything has been converted.
if (FLAGS_SET(verb->flags, VERB_COMMAND_MARKER))
break;
assert(verb->verb);
bool group_marker = FLAGS_SET(verb->flags, VERB_GROUP_MARKER);
@@ -256,5 +297,379 @@ int _verbs_get_help_table(
table_set_header(table, false);
*ret = TAKE_PTR(table);
assert(verb - verbs < INT_MAX);
return verb - verbs; /* Return the count of verb entries "consumed". */
}
static void table_array_freep(Table ***tables) {
assert(tables);
for (Table **t = *tables; t && *t; t++)
*t = table_unref(*t);
free(*tables);
}
static int print_verb_option_help(
const CommandDescription *cmd,
const Verb verbs[],
const Verb verbs_end[],
const Option options[],
const Option options_end[]) {
assert(verbs);
assert(verbs_end >= verbs);
assert((uintptr_t) verbs % sizeof(void*) == 0);
_cleanup_free_ const char **groups = NULL;
_cleanup_(table_array_freep) Table **tables = NULL;
size_t n_verbs = 0, n_opts = 0;
int r;
for (const Verb *verb = verbs; verb < verbs_end;) {
if (FLAGS_SET(verb->flags, VERB_COMMAND_MARKER))
/* Start of entries for another command */
break;
assert(verb->verb);
/* What is the the first group? */
const char *group = FLAGS_SET(verb->flags, VERB_GROUP_MARKER) ? verb->verb : NULL;
_cleanup_(table_unrefp) Table *table = NULL;
r = _verbs_get_help_table(verb, verbs_end, group, &table);
if (r < 0)
return r;
if (!GREEDY_REALLOC0(tables, n_verbs + 2))
return -ENOMEM;
if (!GREEDY_REALLOC0(groups, n_verbs + 2))
return -ENOMEM;
tables[n_verbs] = TAKE_PTR(table);
groups[n_verbs++] = group ?: "Commands";
verb += r; /* Skip over the whole verb group */
}
if (cmd->flags & COMMAND_HELP_SEPARATE)
(void) table_sync_all_column_widths(0, tables);
const Option *optns = options_find_namespace(options, options_end, cmd->option_namespace);
if (!optns)
return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN),
"Option namespace %s not found.",
cmd->option_namespace ?: "(unnamed)");
for (const Option *opt = optns; opt < options_end;) {
_cleanup_(table_unrefp) Table *table = NULL;
const char *group;
if (FLAGS_SET(opt->flags, OPTION_NAMESPACE_MARKER))
/* End of our namespace */
break;
r = options_get_help_table_group(opt, options_end, &table, &group);
if (r < 0)
return r;
if (!GREEDY_REALLOC0(tables, n_verbs + n_opts + 2))
return -ENOMEM;
if (!GREEDY_REALLOC0(groups, n_verbs + n_opts + 2))
return -ENOMEM;
tables[n_verbs + n_opts] = TAKE_PTR(table);
groups[n_verbs + n_opts++] = group ?: "Options";
opt += r; /* Skip over the whole option group */
}
if (cmd->flags & COMMAND_HELP_SEPARATE)
(void) table_sync_all_column_widths(0, tables + n_verbs);
else
(void) table_sync_all_column_widths(0, tables);
for (size_t i = 0; i < n_verbs + n_opts; i++) {
if (table_isempty(tables[i]))
continue;
help_section(groups[i]);
r = table_print_or_warn(tables[i]);
if (r < 0)
return r;
}
return 0;
}
static int print_man_links(const char *pages) {
int r;
NULSTR_FOREACH(page, pages) {
_cleanup_free_ char *link = NULL;
r = terminal_urlify_man(page, /* section= */ NULL, &link);
if (r < 0)
return r;
printf("\nSee the %s for details.\n", link);
}
return 0;
}
static bool verbs_array_has_verbs(const Verb verbs[], const Verb verbs_end[]) {
for (const Verb *verb = verbs; verb < verbs_end; verb++) {
if (FLAGS_SET(verb->flags, VERB_COMMAND_MARKER)) /* start of another command section */
break;
if (!verb->help) /* a hidden verb */
continue;
return true;
}
return false;
}
static int print_wrapped(const char *text, bool abstract) {
/* Print the text wrapped at spaces to the specified width. Newlines embedded in the text
* are preserved and each line is wrapped independently, so explicit line breaks (e.g.
* before an example command line) can be used where needed. */
if (!text)
return 0;
_cleanup_strv_free_ char **lines = NULL, **lines2 = NULL;
int r;
size_t width = MIN(columns(), 80U); /* This is a reasonable default. */
r = strv_split_full(&lines, text, "\n", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return r;
r = strv_rebreak_lines(lines, width, &lines2);
if (r < 0)
return r;
/* Seperate this block from the previous input. */
putchar('\n');
STRV_FOREACH(line, lines2)
if (abstract)
printf("%s%s%s%s\n",
ansi_highlight(),
ansi_add_italics(),
*line,
ansi_normal());
else
puts(*line);
return 0;
}
int _command_print_help(
const Verb verbs[],
const Verb verbs_end[],
const Option options[],
const Option options_end[],
const char *name) {
int r;
const Verb *cmdverb = verbs_get_command(verbs, verbs_end, name);
if (!cmdverb)
return log_error_errno(SYNTHETIC_ERRNO(ENODATA),
"Command %s not known", name);
const CommandDescription *cmd = (const CommandDescription*) ASSERT_PTR(cmdverb->data);
if (cmd->pager_flags)
pager_open(*cmd->pager_flags);
if (cmd->argspec)
NULSTR_FOREACH(spec, cmd->argspec) {
_cleanup_free_ char *line = strjoin("[OPTION…] ", spec);
if (!line)
return log_oom();
help_cmdline(line);
}
else {
bool have_verbs = verbs_array_has_verbs(cmdverb + 1, verbs_end);
help_cmdline(have_verbs ? "[OPTION…] COMMAND …" : "[OPTION…]");
}
r = print_wrapped(cmd->abstract, /* abstract= */ true);
if (r < 0)
return r;
r = print_verb_option_help(cmd, cmdverb + 1, verbs_end, options, options_end);
if (r < 0)
return log_error_errno(r, "Failed to print verb&option help: %m");
r = print_wrapped(cmd->footer, /* abstract= */ false);
if (r < 0)
return r;
r = print_man_links(cmd->man_pages);
if (r < 0)
return log_error_errno(r, "Failed to print man page links: %m");
return 0;
}
static int verb_build_json(const Verb *verb, sd_json_variant **ret) {
int r;
assert(verb);
assert(verb->verb);
assert(ret);
/* Verbs are represented as command objects, as described in the CLI-Introspection
* Specification (https://uapi-group.org/specifications/specs/cli_introspection/).
*
* The "minArguments", "maxArguments", "isDefault", and "isOnlineOnly" fields
* are extensions not (yet) covered by the specification. Note that the argument
* counts include the verb itself. */
r = sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_STRV("names", STRV_MAKE(verb->verb)),
SD_JSON_BUILD_PAIR_CONDITION(
!!verb->help,
"abstract", SD_JSON_BUILD_STRV(STRV_MAKE(verb->help))),
SD_JSON_BUILD_PAIR_CONDITION(
verb->min_args != VERB_ANY,
"minArguments", SD_JSON_BUILD_UNSIGNED(verb->min_args)),
SD_JSON_BUILD_PAIR_CONDITION(
verb->max_args != VERB_ANY,
"maxArguments", SD_JSON_BUILD_UNSIGNED(verb->max_args)),
SD_JSON_BUILD_PAIR_CONDITION(
FLAGS_SET(verb->flags, VERB_DEFAULT),
"isDefault", SD_JSON_BUILD_BOOLEAN(true)),
SD_JSON_BUILD_PAIR_CONDITION(
FLAGS_SET(verb->flags, VERB_ONLINE_ONLY),
"isOnlineOnly", SD_JSON_BUILD_BOOLEAN(true)));
if (r < 0)
return log_error_errno(r, "Failed to build JSON object: %m");
return 0;
}
static int command_build_json(
const Verb *cmdverb,
const Verb verbs_end[],
const Option options[],
const Option options_end[],
sd_json_variant **ret) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *names = NULL, *opts = NULL, *cmds = NULL;
int r;
assert(cmdverb);
assert(FLAGS_SET(cmdverb->flags, VERB_COMMAND_MARKER));
assert(ret);
const CommandDescription *cmd = (const CommandDescription*) ASSERT_PTR(cmdverb->data);
NULSTR_FOREACH(name, cmd->names) {
r = sd_json_variant_append_arrayb(&names, SD_JSON_BUILD_STRING(name));
if (r < 0)
return log_error_errno(r, "Failed to append JSON string to array: %m");
}
assert(names); /* At least the primary name must be defined */
r = options_build_json(options, options_end, cmd->option_namespace, &opts);
if (r < 0)
return r;
for (const Verb *verb = cmdverb + 1; verb < verbs_end; verb++) {
if (FLAGS_SET(verb->flags, VERB_COMMAND_MARKER))
break; /* Start of entries for another command */
if (FLAGS_SET(verb->flags, VERB_GROUP_MARKER))
continue;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *o = NULL;
r = verb_build_json(verb, &o);
if (r < 0)
return r;
r = sd_json_variant_append_array(&cmds, o);
if (r < 0)
return log_error_errno(r, "Failed to append JSON object to array: %m");
}
_cleanup_strv_free_ char **features = strv_split(systemd_features, /* separators= */ NULL);
if (!features)
return log_oom();
r = sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_VARIANT("names", names),
SD_JSON_BUILD_PAIR_STRING("version", PROJECT_VERSION_FULL),
SD_JSON_BUILD_PAIR_STRV("features", features),
SD_JSON_BUILD_PAIR_CONDITION(
!!cmd->abstract,
"abstract", SD_JSON_BUILD_STRV(STRV_MAKE(cmd->abstract))),
SD_JSON_BUILD_PAIR_CONDITION(
!!cmd->footer,
"footer", SD_JSON_BUILD_STRV(STRV_MAKE(cmd->footer))),
SD_JSON_BUILD_PAIR_CONDITION(!!opts, "options", SD_JSON_BUILD_VARIANT(opts)),
SD_JSON_BUILD_PAIR_CONDITION(!!cmds, "verbs", SD_JSON_BUILD_VARIANT(cmds)));
if (r < 0)
return log_error_errno(r, "Failed to build JSON object: %m");
return 0;
}
int _introspect_cli(
const Verb verbs[],
const Verb verbs_end[],
const Option options[],
const Option options_end[],
sd_json_format_flags_t flags) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *commands = NULL, *w = NULL;
int r;
assert(verbs);
assert(verbs_end > verbs);
assert((uintptr_t) verbs % sizeof(void*) == 0);
if (flags == SD_JSON_FORMAT_OFF)
flags = SD_JSON_FORMAT_PRETTY_AUTO;
for (const Verb *verb = verbs; verb < verbs_end; verb++) {
if (!FLAGS_SET(verb->flags, VERB_COMMAND_MARKER))
continue;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *c = NULL;
r = command_build_json(verb, verbs_end, options, options_end, &c);
if (r < 0)
return r;
r = sd_json_variant_append_array(&commands, c);
if (r < 0)
return log_error_errno(r, "Failed to append JSON object to array: %m");
}
if (!commands)
return log_error_errno(SYNTHETIC_ERRNO(ENODATA),
"No command description defined, cannot introspect.");
r = sd_json_buildo(
&w,
SD_JSON_BUILD_PAIR_STRING(
"mediaType",
"application/vnd.io.systemd.cli-introspection-0"),
SD_JSON_BUILD_PAIR_VARIANT("commands", commands));
if (r < 0)
return log_error_errno(r, "Failed to build JSON object: %m");
r = sd_json_variant_dump(w, flags, stdout, /* prefix= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to print JSON object: %m");
return 0;
}
+64 -4
View File
@@ -2,17 +2,34 @@
#pragma once
#include "forward.h"
#include "options.h"
#define VERB_ANY (UINT_MAX)
typedef enum CommandFlags {
COMMAND_HELP_SEPARATE = 1 << 0, /* Do not synchronize the width between verbs and options */
} CommandFlags;
typedef struct CommandDescription {
const char *names; /* nulstr with the main command name and potentially aliases */
const char *abstract; /* concrete abstract */
const char *argspec; /* optional specification of positional args in synopsis */
const char *footer; /* optional footer to print right above man page links */
const char *man_pages; /* nulstr with man page names */
const char *option_namespace; /* to be used when the options are in a namespace */
const PagerFlags *pager_flags; /* optional pointer to the runtime pager flags variable */
CommandFlags flags;
} CommandDescription;
typedef enum VerbFlags {
VERB_DEFAULT = 1 << 0, /* The verb to run if no verb is specified */
VERB_ONLINE_ONLY = 1 << 1, /* Just do nothing when running in chroot or offline */
VERB_GROUP_MARKER = 1 << 2, /* Fake verb entry to separate groups */
VERB_DEFAULT = 1 << 0, /* The verb to run if no verb is specified */
VERB_ONLINE_ONLY = 1 << 1, /* Just do nothing when running in chroot or offline */
VERB_COMMAND_MARKER = 1 << 2, /* Header entry with command description */
VERB_GROUP_MARKER = 1 << 3, /* Fake verb entry to separate groups */
} VerbFlags;
/* Note: see the comment on struct Option in options.h for why _alignptr_ is required here. */
typedef struct _alignptr_ {
typedef struct _alignptr_ Verb {
const char *verb;
unsigned min_args, max_args;
VerbFlags flags;
@@ -71,6 +88,12 @@ assert_cc(sizeof(Verb) % sizeof(void*) == 0);
_VERB_DATA(/* d= */ NULL, /* v= */ gr, /* a= */ NULL, /* amin= */ 0, /* amax= */ 0, \
/* f= */ VERB_GROUP_MARKER, /* dat= */ 0, /* h= */ NULL)
#define _COMMAND(u, ...) \
static const CommandDescription UNIQ_T(description, u) = { __VA_ARGS__ }; \
_VERB_DATA(/* d= */ NULL, /* v= */ NULL, /* a= */ NULL, /* amin= */ 0, /* amax= */ 0, \
/* f= */ VERB_COMMAND_MARKER, /* dat= */ (uintptr_t) &UNIQ_T(description, u), /* h= */ NULL)
#define COMMAND(...) _COMMAND(UNIQ, __VA_ARGS__)
/* This is magically mapped to the beginning and end of the section */
extern const Verb __start_SYSTEMD_VERBS[];
extern const Verb __stop_SYSTEMD_VERBS[];
@@ -107,3 +130,40 @@ int _verbs_get_help_table(
#define VERB_COMMON_HELP_HIDDEN(impl) \
VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, NULL); \
_VERB_COMMON_HELP_IMPL(impl)
#define VERB_COMMON_HELP_AUTO(program) \
VERB_FULL(verb_help_auto, "help", NULL, VERB_ANY, VERB_ANY, 0, (uintptr_t) program, "Show this help")
#define VERB_COMMON_HELP_AUTO_HIDDEN(program) \
VERB_FULL(verb_help_auto, "help", NULL, VERB_ANY, VERB_ANY, 0, (uintptr_t) program, NULL)
int _command_print_help(
const Verb verbs[],
const Verb verbs_end[],
const Option options[],
const Option options_end[],
const char *name);
#define command_print_help(name) \
_command_print_help( \
__start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, \
__start_SYSTEMD_OPTIONS, __stop_SYSTEMD_OPTIONS, \
name)
static inline int verb_help_auto(int argc, char **argv, uintptr_t data, void *userdata) {
return command_print_help((const char*) ASSERT_PTR(data));
}
/* Print a machine-readable description of the program's commands, verbs, and options in the format
* defined by the CLI-Introspection Specification. One command object is emitted for each
* VERB_COMMAND() in the verb table, so multicall binaries are described in full. */
int _introspect_cli(
const Verb verbs[],
const Verb verbs_end[],
const Option options[],
const Option options_end[],
sd_json_format_flags_t flags);
#define introspect_cli(flags) \
_introspect_cli( \
__start_SYSTEMD_VERBS, __stop_SYSTEMD_VERBS, \
__start_SYSTEMD_OPTIONS, __stop_SYSTEMD_OPTIONS, \
flags)
+12 -50
View File
@@ -24,7 +24,6 @@
#include "parse-argument.h"
#include "parse-util.h"
#include "polkit-agent.h"
#include "pretty-print.h"
#include "runtime-scope.h"
#include "sparse-endian.h"
#include "string-table.h"
@@ -180,6 +179,13 @@ static int print_status_info(const StatusInfo *i) {
return 0;
}
COMMAND(
"timedatectl\0",
.abstract = "Query or change system time and date settings.",
.man_pages = "timedatectl.1\0",
.pager_flags = &arg_pager_flags,
);
VERB_DEFAULT_NOARG(verb_status, "status", "Show current time settings");
static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) {
StatusInfo info = {};
@@ -893,54 +899,7 @@ static int verb_revert(int argc, char *argv[], uintptr_t _data, void *userdata)
return 0;
}
static int help(void) {
_cleanup_free_ char *link = NULL;
_cleanup_(table_unrefp) Table *verbs = NULL, *verbs2 = NULL, *options = NULL;
int r;
r = terminal_urlify_man("timedatectl", "1", &link);
if (r < 0)
return log_oom();
r = verbs_get_help_table(&verbs);
if (r < 0)
return r;
r = verbs_get_help_table_group("systemd-timesyncd Commands", &verbs2);
if (r < 0)
return r;
r = option_parser_get_help_table(&options);
if (r < 0)
return r;
(void) table_sync_column_widths(0, verbs, verbs2, options);
printf("%s [OPTIONS...] COMMAND ...\n"
"\n%sQuery or change system time and date settings.%s\n"
"\nCommands:\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal());
r = table_print_or_warn(verbs);
if (r < 0)
return r;
printf("\nsystemd-timesyncd Commands:\n");
r = table_print_or_warn(verbs2);
if (r < 0)
return r;
printf("\nOptions:\n");
r = table_print_or_warn(options);
if (r < 0)
return r;
printf("\nSee the %s for details.\n", link);
return 0;
}
VERB_COMMON_HELP_HIDDEN(help);
VERB_COMMON_HELP_AUTO_HIDDEN("timedatectl");
static int parse_argv(int argc, char *argv[], char ***ret_args) {
int r;
@@ -953,7 +912,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
FOREACH_OPTION_OR_RETURN(c, &opts)
switch (c) {
OPTION_COMMON_HELP:
return help();
return command_print_help("timedatectl");
OPTION_COMMON_VERSION:
return version();
@@ -1005,6 +964,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
OPTION('a', "all", NULL, "Show all properties, including empty ones"):
SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true);
break;
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(SD_JSON_FORMAT_OFF);
}
*ret_args = option_parser_get_args(&opts);
+15 -43
View File
@@ -23,7 +23,6 @@
#include "format-table.h"
#include "format-util.h"
#include "fs-util.h"
#include "help-util.h"
#include "log.h"
#include "main-func.h"
#include "memfd-util.h"
@@ -81,47 +80,6 @@ static void push_fds_done(PushFds *p) {
STATIC_DESTRUCTOR_REGISTER(arg_graceful, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_push_fds, push_fds_done);
static int help(void) {
_cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL;
int r;
r = option_parser_get_help_table(&options);
if (r < 0)
return r;
r = verbs_get_help_table(&verbs);
if (r < 0)
return r;
(void) table_sync_column_widths(0, options, verbs);
pager_open(arg_pager_flags);
help_cmdline("[OPTIONS...] COMMAND ...");
help_abstract("Introspect Varlink Services.");
help_section("Commands");
r = table_print_or_warn(verbs);
if (r < 0)
return r;
help_section("Options");
r = table_print_or_warn(options);
if (r < 0)
return r;
printf("\nWith --exec, specify the command to invoke:\n"
" %s --exec call ADDRESS METHOD PARAMS -- CMDLINE…\n",
program_invocation_short_name);
help_man_page_reference("varlinkctl", "1");
return 0;
}
VERB_COMMON_HELP(help);
static int parse_argv(int argc, char *argv[], char ***ret_args) {
int r;
@@ -134,7 +92,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
switch (c) {
OPTION_COMMON_HELP:
return help();
return command_print_help("varlinkctl");
OPTION_COMMON_VERSION:
return version();
@@ -248,6 +206,9 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) {
arg_push_fds.fds[arg_push_fds.n_fds++] = TAKE_FD(add_fd);
break;
}
OPTION_COMMON_INTROSPECT_CLI:
return introspect_cli(arg_json_format_flags);
}
/* If more than one reply is expected, imply JSON-SEQ output, and set SD_JSON_FORMAT_FLUSH */
@@ -325,6 +286,15 @@ static void get_info_data_done(GetInfoData *d) {
d->interfaces = strv_free(d->interfaces);
}
COMMAND(
"varlinkctl\0",
"Introspect Varlink Services.",
.man_pages = "varlinkctl.1\0",
.footer = "With --exec, specify the command to invoke:\n"
" varlinkctl --exec call ADDRESS METHOD PARAMS -- CMDLINE…",
.pager_flags = &arg_pager_flags,
);
VERB(verb_info, "info", "ADDRESS", 2, 2, 0, "Show service information");
VERB(verb_info, "list-interfaces", "ADDRESS", 2, 2, 0, "List interfaces implemented by service");
static int verb_info(int argc, char *argv[], uintptr_t _data, void *userdata) {
@@ -1488,6 +1458,8 @@ static int verb_serve(int argc, char *argv[], uintptr_t _data, void *userdata) {
return 0;
}
VERB_COMMON_HELP_AUTO("varlinkctl");
static int run(int argc, char *argv[]) {
int r;
+41 -28
View File
@@ -4,16 +4,15 @@
#include <sys/stat.h>
#include "sd-device.h"
#include "sd-json.h"
#include "alloc-util.h"
#include "argv-util.h"
#include "build.h"
#include "cryptsetup-util.h"
#include "dlopen-note.h"
#include "extract-word.h"
#include "fileio.h"
#include "format-table.h"
#include "fstab-util.h"
#include "help-util.h"
#include "hexdecoct.h"
#include "log.h"
#include "main-func.h"
@@ -51,26 +50,6 @@ STATIC_DESTRUCTOR_REGISTER(arg_fec_what, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_hash_signature, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_nvpcr, freep);
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 a verity protected block device.");
help_section("Commands");
r = table_print_or_warn(verbs);
if (r < 0)
return r;
help_man_page_reference("systemd-veritysetup@.service", "8");
return 0;
}
static int parse_roothashsig_option(const char *option, bool strict) {
_cleanup_free_ void *rhs = NULL;
size_t rhss = 0;
@@ -318,8 +297,14 @@ static int parse_options(const char *options) {
return r;
}
COMMAND(
"systemd-veritysetup\0",
"Attach or detach a verity-protected block device.",
.man_pages = "systemd-veritysetup@.service.8\0",
);
VERB(verb_attach, "attach", "VOLUME DATADEVICE HASHDEVICE ROOTHASH [OPTIONS]", 5, 6, 0,
"Attach a verity protected block device");
"Attach a verity-protected block device");
static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
_cleanup_free_ void *rh = NULL;
@@ -457,7 +442,7 @@ static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata)
}
VERB(verb_detach, "detach", "VOLUME", 2, 2, 0,
"Detach a verity protected block device");
"Detach a verity-protected block device");
static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
int r;
@@ -486,11 +471,39 @@ static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata)
return 0;
}
VERB_COMMON_HELP_AUTO_HIDDEN("systemd-veritysetup");
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("systemd-veritysetup");
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;
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();
@@ -503,7 +516,7 @@ static int run(int argc, char *argv[]) {
umask(0022);
return dispatch_verb(strv_skip(argv, 1), /* userdata= */ NULL);
return dispatch_verb(args, /* userdata= */ NULL);
}
DEFINE_MAIN_FUNCTION(run);
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
# Just a smoke test for the introspection code
for i in systemd-ac-power systemd-analyze systemd-ask-password systemd-dissect \
systemd-id128 systemd-notify homectl resolvectl timedatectl varlinkctl; do
command -v "$i" >/dev/null || continue
$i --introspect-cli | jq -e \
'.mediaType == "application/vnd.io.systemd.cli-introspection-0"'
$i --introspect-cli | jq -e --arg name "$i" \
'any(.commands[]; .names[0] == $name)'
$i --introspect-cli | jq -e \
'any(.commands[]; [.options[].names[-1]] | contains(["--help", "--version", "--introspect-cli"]))'
$i --intro | grep -e --help
done
# Those are not in $PATH and do not do regular option parsing, so the option must
# be given verbatim
for i in /usr/lib/systemd/systemd-backlight /usr/lib/systemd/systemd-veritysetup; do
[[ -x "$i" ]] || continue
$i --introspect-cli | jq -e \
'.mediaType == "application/vnd.io.systemd.cli-introspection-0"'
done
# systemd-id128 defines verbs, check that they are described
systemd-id128 --introspect-cli | jq -e \
'.commands[0].verbs | map(.names[0]) | contains(["new", "machine-id", "show", "help"])'
# resolvectl is a multicall binary, check that both commands are described
resolvectl --introspect-cli | jq -e \
'[.commands[].names[0]] | sort == ["resolvconf", "resolvectl", "systemd-resolve"]'