Define options and verbs through "magic macros" (#40880)

This is an another alternative for #40656, based on @poettering's
suggestion to use an ELF section.

The output of --help is generated using format-table, but the details of
the formatting a bit off: different sections in the table (verbs, option
groups) are not aligned to the same column. Our current table formatting
doesn't make this easy. If somebody has an idea how to do this without
too much pain, please make suggestions.

Another thing that I didn't know how to do nicely, was to use the two
columns of separation. But maybe this doesn't matter. If we switch to
one column everywhere, I don't think people will care.

Example output:
```console
$ build/systemd-id128 -h | cat    
systemd-id128 [OPTIONS...] COMMAND

Generate and print 128-bit identifiers.

Commands:
  new                Generate a new ID
  machine-id         Print the ID of current machine
  boot-id            Print the ID of current boot
  invocation-id      Print the ID of current invocation
  var-partition-uuid Print the UUID for the /var/ partition
  show [NAME|UUID]   Print one or more UUIDs
  help               Show this help

Options:
  -h --help            Show this help
     --version         Show package version
     --no-pager        Do not start a pager
     --no-legend       Do not show headers and footers
     --json=FORMAT     Output inspection data in JSON (takes one of pretty,
                       short, off)
  -j                   Equivalent to --json=pretty (on TTY) or --json=short
                       (otherwise)
  -p --pretty          Generate samples of program code
  -P --value           Only print the value
  -a --app-specific=ID Generate app-specific IDs
  -u --uuid            Output in UUID format

See the systemd-id128(1) man page for details.
```

The output wraps automatically with terminal width. If the terminal is
extremely narrow, ellipsization occurs. I think this doesn't matter
and/or is actually a feature. Such narrow terminals mostly occur in
testing, so it doesn't matter what exactly we do, as long as it is
something somewhat reasonable.

Anyway, I think this is enough as PoC. Please compare this with the
previous approach. /cc @behrmann, @YHNdnzj, @poettering

The three binaries that are converted have options, verbs, optional
arguments, and options terminate option parsing, so most of the
functionality is there. One thing that I didn't implement that was
present in previous PRs is "namespaces", i.e. multiple parsers in the
same source file. I expect that we can handle this similarly to option
groups.
This commit is contained in:
Lennart Poettering
2026-03-23 22:17:05 +01:00
committed by GitHub
15 changed files with 2017 additions and 632 deletions
+229 -384
View File
@@ -42,6 +42,7 @@
#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"
@@ -122,6 +123,7 @@ 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);
@@ -130,6 +132,17 @@ static int help(void) {
if (r < 0)
return log_oom();
r = option_parser_get_help_table(&options);
if (r < 0)
return r;
r = option_parser_get_help_table_group("Commands", &commands);
if (r < 0)
return r;
/* Make the 1st column same width in both tables */
(void) table_sync_column_width(options, 0, commands, 0);
printf("%1$s [OPTIONS...] IMAGE\n"
"%1$s [OPTIONS...] --mount IMAGE PATH\n"
"%1$s [OPTIONS...] --umount PATH\n"
@@ -144,112 +157,24 @@ static int help(void) {
"%1$s [OPTIONS...] --discover\n"
"%1$s [OPTIONS...] --validate IMAGE\n"
"%1$s [OPTIONS...] --shift IMAGE UIDBASE\n"
"\n%5$sDissect a Discoverable Disk Image (DDI).%6$s\n\n"
"%3$sOptions:%4$s\n"
" --no-pager Do not pipe output into a pager\n"
" --no-legend Do not show the headers and footers\n"
" -r --read-only Mount read-only\n"
" --fsck=BOOL Run fsck before mounting\n"
" --growfs=BOOL Grow file system to partition size, if marked\n"
" --mkdir Make mount directory before mounting, if missing\n"
" --rmdir Remove mount directory after unmounting\n"
" --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n"
" --in-memory Copy image into memory\n"
" --root-hash=HASH Specify root hash for verity\n"
" --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n"
" as a DER encoded PKCS7, either as a path to a file\n"
" or as an ASCII base64 encoded string prefixed by\n"
" 'base64:'\n"
" --verity-data=PATH Specify data file with hash tree for verity if it is\n"
" not embedded in IMAGE\n"
" --image-policy=POLICY\n"
" Specify image dissection policy\n"
" --image-filter=FILTER\n"
" Specify image dissection filter\n"
" --json=pretty|short|off\n"
" Generate JSON output\n"
" --loop-ref=NAME Set reference string for loopback device\n"
" --loop-ref-auto Derive reference string from image file name\n"
" --mtree-hash=BOOL Whether to include SHA256 hash in the mtree output\n"
" --copy-ownership=BOOL\n"
" Whether to copy ownership when copying files\n"
" --user Discover user images\n"
" --system Discover system images\n"
" --all Show hidden images too\n"
"\n%3$sCommands:%4$s\n"
" -h --help Show this help\n"
" --version Show package version\n"
" -m --mount Mount the image to the specified directory\n"
" -M Shortcut for --mount --mkdir\n"
" -u --umount Unmount the image from the specified directory\n"
" -U Shortcut for --umount --rmdir\n"
" --attach Attach the disk image to a loopback block device\n"
" --detach Detach a loopback block device again\n"
" -l --list List all the files and directories of the specified\n"
" OS image\n"
" --mtree Show BSD mtree manifest of OS image\n"
" --with Mount, run command, unmount\n"
" -x --copy-from Copy files from image to host\n"
" -a --copy-to Copy files from host to image\n"
" --make-archive Convert the DDI to an archive file\n"
" --discover Discover DDIs in well known directories\n"
" --validate Validate image and image policy\n"
" --shift Shift UID range to selected base\n"
" -q --quiet Suppress output of chosen loopback block device\n"
"\nSee the %2$s for details.\n",
"\n%2$sDissect a Discoverable Disk Image (DDI).%3$s\n"
"\n%4$sOptions:%5$s\n",
program_invocation_short_name,
link,
ansi_underline(),
ansi_normal(),
ansi_highlight(),
ansi_normal(),
ansi_underline(),
ansi_normal());
table_print(options, stdout);
printf("\n%sCommands:%s\n", ansi_underline(), ansi_normal());
table_print(commands, stdout);
printf("\nSee the %s for details.\n", link);
return 0;
}
static int patch_argv(int *argc, char ***argv, char ***buf) {
_cleanup_free_ char **l = NULL;
char **e;
assert(argc);
assert(*argc >= 0);
assert(argv);
assert(*argv);
assert(buf);
/* Ugly hack: if --with is included in command line, also insert "--" immediately after it, to make
* getopt_long() stop processing switches */
for (e = *argv + 1; e < *argv + *argc; e++) {
assert(*e);
if (streq(*e, "--with"))
break;
}
if (e >= *argv + *argc || streq_ptr(e[1], "--")) {
/* No --with used? Or already followed by "--"? Then don't do anything */
*buf = NULL;
return 0;
}
/* Insert the extra "--" right after the --with */
l = new(char*, *argc + 2);
if (!l)
return log_oom();
size_t idx = e - *argv + 1;
memcpy(l, *argv, sizeof(char*) * idx); /* copy everything up to and including the --with */
l[idx] = (char*) "--"; /* insert "--" */
memcpy(l + idx + 1, e + 1, sizeof(char*) * (*argc - idx + 1)); /* copy the rest, including trailing NULL entry */
(*argc)++;
(*argv) = l;
*buf = TAKE_PTR(l);
return 1;
}
static int parse_image_path_argument(const char *path, char **ret_root, char **ret_image) {
_cleanup_free_ char *p = NULL;
struct stat st;
@@ -276,190 +201,51 @@ static int parse_image_path_argument(const char *path, char **ret_root, char **r
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_NO_PAGER,
ARG_NO_LEGEND,
ARG_WITH,
ARG_DISCARD,
ARG_FSCK,
ARG_GROWFS,
ARG_ROOT_HASH,
ARG_ROOT_HASH_SIG,
ARG_USR_HASH,
ARG_USR_HASH_SIG,
ARG_VERITY_DATA,
ARG_MKDIR,
ARG_RMDIR,
ARG_IN_MEMORY,
ARG_JSON,
ARG_MTREE,
ARG_DISCOVER,
ARG_ATTACH,
ARG_DETACH,
ARG_LOOP_REF,
ARG_LOOP_REF_AUTO,
ARG_IMAGE_POLICY,
ARG_VALIDATE,
ARG_MTREE_HASH,
ARG_MAKE_ARCHIVE,
ARG_SHIFT,
ARG_SYSTEM,
ARG_USER,
ARG_ALL,
ARG_IMAGE_FILTER,
ARG_COPY_OWNERSHIP,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "mount", no_argument, NULL, 'm' },
{ "umount", no_argument, NULL, 'u' },
{ "attach", no_argument, NULL, ARG_ATTACH },
{ "detach", no_argument, NULL, ARG_DETACH },
{ "with", no_argument, NULL, ARG_WITH },
{ "read-only", no_argument, NULL, 'r' },
{ "discard", required_argument, NULL, ARG_DISCARD },
{ "fsck", required_argument, NULL, ARG_FSCK },
{ "growfs", required_argument, NULL, ARG_GROWFS },
{ "root-hash", required_argument, NULL, ARG_ROOT_HASH },
{ "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG },
{ "usr-hash", required_argument, NULL, ARG_USR_HASH },
{ "usr-hash-sig", required_argument, NULL, ARG_USR_HASH_SIG },
{ "verity-data", required_argument, NULL, ARG_VERITY_DATA },
{ "mkdir", no_argument, NULL, ARG_MKDIR },
{ "rmdir", no_argument, NULL, ARG_RMDIR },
{ "in-memory", no_argument, NULL, ARG_IN_MEMORY },
{ "list", no_argument, NULL, 'l' },
{ "mtree", no_argument, NULL, ARG_MTREE },
{ "copy-from", no_argument, NULL, 'x' },
{ "copy-to", no_argument, NULL, 'a' },
{ "json", required_argument, NULL, ARG_JSON },
{ "discover", no_argument, NULL, ARG_DISCOVER },
{ "loop-ref", required_argument, NULL, ARG_LOOP_REF },
{ "loop-ref-auto", no_argument, NULL, ARG_LOOP_REF_AUTO },
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
{ "validate", no_argument, NULL, ARG_VALIDATE },
{ "mtree-hash", required_argument, NULL, ARG_MTREE_HASH },
{ "make-archive", no_argument, NULL, ARG_MAKE_ARCHIVE },
{ "shift", no_argument, NULL, ARG_SHIFT },
{ "system", no_argument, NULL, ARG_SYSTEM },
{ "user", no_argument, NULL, ARG_USER },
{ "all", no_argument, NULL, ARG_ALL },
{ "quiet", no_argument, NULL, 'q' },
{ "image-filter", required_argument, NULL, ARG_IMAGE_FILTER },
{ "copy-ownership", required_argument, NULL, ARG_COPY_OWNERSHIP },
{}
};
_cleanup_free_ char **buf = NULL; /* we use free(), not strv_free() here, as we don't copy the strings here */
bool system_scope_requested = false, user_scope_requested = false;
int c, r;
int r;
assert(argc >= 0);
assert(argv);
r = patch_argv(&argc, &argv, &buf);
if (r < 0)
return r;
while ((c = getopt_long(argc, argv, "hmurMUlxaq", options, NULL)) >= 0) {
OptionParser state = {};
const Option *current;
const char *arg;
FOREACH_OPTION_FULL(&state, c, argc, argv, &current, &arg, /* on_error= */ return c)
switch (c) {
case 'h':
return help();
case ARG_VERSION:
return version();
case ARG_NO_PAGER:
OPTION_COMMON_NO_PAGER:
arg_pager_flags |= PAGER_DISABLE;
break;
case ARG_NO_LEGEND:
OPTION_COMMON_NO_LEGEND:
arg_legend = false;
break;
case 'm':
arg_action = ACTION_MOUNT;
break;
case ARG_MKDIR:
OPTION_LONG("mkdir", NULL, "Make mount directory before mounting, if missing"):
arg_flags |= DISSECT_IMAGE_MKDIR;
break;
case 'M':
/* Shortcut combination of the above two */
arg_action = ACTION_MOUNT;
arg_flags |= DISSECT_IMAGE_MKDIR;
break;
case 'u':
arg_action = ACTION_UMOUNT;
break;
case ARG_RMDIR:
OPTION_LONG("rmdir", NULL, "Remove mount directory after unmounting"):
arg_rmdir = true;
break;
case 'U':
/* Shortcut combination of the above two */
arg_action = ACTION_UMOUNT;
arg_rmdir = true;
break;
case ARG_ATTACH:
arg_action = ACTION_ATTACH;
break;
case ARG_DETACH:
arg_action = ACTION_DETACH;
break;
case 'l':
arg_action = ACTION_LIST;
OPTION('r', "read-only", NULL, "Mount read-only"):
arg_flags |= DISSECT_IMAGE_READ_ONLY;
break;
case ARG_MTREE:
arg_action = ACTION_MTREE;
arg_flags |= DISSECT_IMAGE_READ_ONLY;
break;
case ARG_WITH:
arg_action = ACTION_WITH;
break;
case 'x':
arg_action = ACTION_COPY_FROM;
arg_flags |= DISSECT_IMAGE_READ_ONLY;
break;
case 'a':
arg_action = ACTION_COPY_TO;
break;
case 'r':
arg_flags |= DISSECT_IMAGE_READ_ONLY;
break;
case ARG_DISCARD: {
OPTION_LONG("discard", "MODE", "Choose discard mode (disabled, loop, all, crypto)"): {
DissectImageFlags flags;
if (streq(optarg, "disabled"))
if (streq(arg, "disabled"))
flags = 0;
else if (streq(optarg, "loop"))
else if (streq(arg, "loop"))
flags = DISSECT_IMAGE_DISCARD_ON_LOOP;
else if (streq(optarg, "all"))
else if (streq(arg, "all"))
flags = DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD;
else if (streq(optarg, "crypt"))
else if (streq(arg, "crypt"))
flags = DISSECT_IMAGE_DISCARD_ANY;
else if (streq(optarg, "list")) {
else if (streq(arg, "list")) {
puts("disabled\n"
"all\n"
"crypt\n"
@@ -467,32 +253,32 @@ static int parse_argv(int argc, char *argv[]) {
return 0;
} else
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Unknown --discard= parameter: %s",
optarg);
"Unknown --discard= parameter: %s", arg);
arg_flags = (arg_flags & ~DISSECT_IMAGE_DISCARD_ANY) | flags;
break;
}
case ARG_IN_MEMORY:
OPTION_LONG("in-memory", NULL, "Copy image into memory"):
arg_in_memory = true;
break;
case ARG_ROOT_HASH:
case ARG_USR_HASH: {
OPTION_LONG("root-hash", "HASH", "Specify root hash for verity"): {}
OPTION_LONG("usr-hash", "HASH", "Same, but for the usr partition"): {
_cleanup_(iovec_done) struct iovec roothash = {};
PartitionDesignator d = c == ARG_USR_HASH ? PARTITION_USR : PARTITION_ROOT;
PartitionDesignator d = streq(current->long_code, "root-hash") ? PARTITION_ROOT : PARTITION_USR;
if (arg_verity_settings.designator >= 0 &&
arg_verity_settings.designator != d)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --root-hash=/--root-hash-sig= and --usr-hash=/--usr-hash-sig= options.");
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Cannot combine --root-hash=/--root-hash-sig= and --usr-hash=/--usr-hash-sig= options.");
r = unhexmem(optarg, &roothash.iov_base, &roothash.iov_len);
r = unhexmem(arg, &roothash.iov_base, &roothash.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash '%s': %m", optarg);
return log_error_errno(r, "Failed to parse root hash '%s': %m", arg);
if (roothash.iov_len < sizeof(sd_id128_t))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Root hash must be at least 128-bit long: %s", optarg);
"Root hash must be at least 128-bit long: %s", arg);
iovec_done(&arg_verity_settings.root_hash);
arg_verity_settings.root_hash = TAKE_STRUCT(roothash);
@@ -500,24 +286,28 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_ROOT_HASH_SIG:
case ARG_USR_HASH_SIG: {
char *value;
OPTION_LONG("root-hash-sig", "SIG",
"Specify signature of root hash for verity as DER-encoded PKCS7, "
"either as a path to a file or as an ASCII base64-encoded string "
"prefixed by 'base64:'"): {}
OPTION_LONG("usr-hash-sig", "SIG", "Same, but for the usr partition"): {
const char *value;
_cleanup_(iovec_done) struct iovec sig = {};
PartitionDesignator d = c == ARG_USR_HASH_SIG ? PARTITION_USR : PARTITION_ROOT;
PartitionDesignator d = streq(current->long_code, "root-hash-sig") ? PARTITION_ROOT : PARTITION_USR;
if (arg_verity_settings.designator >= 0 &&
arg_verity_settings.designator != d)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot combine --root-hash=/--root-hash-sig= and --usr-hash=/--usr-hash-sig= options.");
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Cannot combine --root-hash=/--root-hash-sig= and --usr-hash=/--usr-hash-sig= options.");
if ((value = startswith(optarg, "base64:"))) {
if ((value = startswith(arg, "base64:"))) {
r = unbase64mem(value, &sig.iov_base, &sig.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg);
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg);
} else {
r = read_full_file(optarg, (char**) &sig.iov_base, &sig.iov_len);
r = read_full_file(arg, (char**) &sig.iov_base, &sig.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to read root hash signature file '%s': %m", optarg);
return log_error_errno(r, "Failed to read root hash signature file '%s': %m", arg);
}
iovec_done(&arg_verity_settings.root_hash_sig);
@@ -526,78 +316,161 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_VERITY_DATA:
r = parse_path_argument(optarg, false, &arg_verity_settings.data_path);
OPTION_LONG("verity-data", "PATH",
"Specify data file with hash tree for verity if it is not embedded in IMAGE"):
r = parse_path_argument(arg, false, &arg_verity_settings.data_path);
if (r < 0)
return r;
break;
case ARG_FSCK:
r = parse_boolean(optarg);
OPTION_LONG("fsck", "BOOL", "Run fsck before mounting"):
r = parse_boolean(arg);
if (r < 0)
return log_error_errno(r, "Failed to parse --fsck= parameter: %s", optarg);
return log_error_errno(r, "Failed to parse --fsck= parameter: %s", arg);
SET_FLAG(arg_flags, DISSECT_IMAGE_FSCK, r);
break;
case ARG_GROWFS:
r = parse_boolean(optarg);
OPTION_LONG("growfs", "BOOL", "Grow file system to partition size, if marked"):
r = parse_boolean(arg);
if (r < 0)
return log_error_errno(r, "Failed to parse --growfs= parameter: %s", optarg);
return log_error_errno(r, "Failed to parse --growfs= parameter: %s", arg);
SET_FLAG(arg_flags, DISSECT_IMAGE_GROWFS, r);
break;
case ARG_JSON:
r = parse_json_argument(optarg, &arg_json_format_flags);
OPTION_COMMON_JSON:
r = parse_json_argument(arg, &arg_json_format_flags);
if (r <= 0)
return r;
break;
case ARG_DISCOVER:
arg_action = ACTION_DISCOVER;
break;
case ARG_LOOP_REF:
if (isempty(optarg)) {
OPTION_LONG("loop-ref", "NAME", "Set reference string for loopback device"):
if (isempty(arg)) {
arg_loop_ref = mfree(arg_loop_ref);
arg_loop_ref_auto = false;
break;
}
if (strlen(optarg) >= sizeof_field(struct loop_info64, lo_file_name))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Loop device ref string '%s' is too long.", optarg);
if (strlen(arg) >= sizeof_field(struct loop_info64, lo_file_name))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Loop device ref string '%s' is too long.", arg);
r = free_and_strdup_warn(&arg_loop_ref, optarg);
r = free_and_strdup_warn(&arg_loop_ref, arg);
if (r < 0)
return r;
arg_loop_ref_auto = false;
break;
case ARG_LOOP_REF_AUTO:
OPTION_LONG("loop-ref-auto", NULL, "Derive reference string from image file name"):
arg_loop_ref = mfree(arg_loop_ref);
arg_loop_ref_auto = true;
break;
case ARG_IMAGE_POLICY:
r = parse_image_policy_argument(optarg, &arg_image_policy);
OPTION_LONG("image-policy", "POLICY", "Specify image dissection policy"):
r = parse_image_policy_argument(arg, &arg_image_policy);
if (r < 0)
return r;
break;
case ARG_VALIDATE:
arg_action = ACTION_VALIDATE;
break;
case ARG_MTREE_HASH:
r = parse_boolean_argument("--mtree-hash=", optarg, &arg_mtree_hash);
OPTION_LONG("mtree-hash", "BOOL", "Whether to include SHA256 hash in the mtree output"):
r = parse_boolean_argument("--mtree-hash=", arg, &arg_mtree_hash);
if (r < 0)
return r;
break;
case ARG_MAKE_ARCHIVE:
OPTION_LONG("system", NULL, "Discover system images"):
system_scope_requested = true;
break;
OPTION_LONG("user", NULL, "Discover user images"):
user_scope_requested = true;
break;
OPTION_LONG("all", NULL, "Show hidden images too"):
arg_all = true;
break;
OPTION('q', "quiet", NULL, "Suppress output of chosen loopback block device"):
arg_quiet = true;
break;
OPTION_LONG("image-filter", "FILTER", "Specify image dissection filter"): {
_cleanup_(image_filter_freep) ImageFilter *f = NULL;
r = image_filter_parse(arg, &f);
if (r < 0)
return log_error_errno(r, "Failed to parse image filter expression: %s", arg);
image_filter_free(arg_image_filter);
arg_image_filter = TAKE_PTR(f);
break;
}
OPTION_LONG("copy-ownership", "BOOL", "Whether to copy ownership when copying files"):
r = parse_tristate_argument_with_auto("--copy-ownership=", arg, &arg_copy_ownership);
if (r < 0)
return r;
break;
/************************************ Commands ***************************************/
OPTION_GROUP("Commands"): {}
OPTION_COMMON_HELP:
return help();
OPTION_COMMON_VERSION:
return version();
OPTION('m', "mount", NULL, "Mount the image to the specified directory"):
arg_action = ACTION_MOUNT;
break;
OPTION_SHORT('M', NULL, "Shortcut for --mount --mkdir"):
arg_action = ACTION_MOUNT;
arg_flags |= DISSECT_IMAGE_MKDIR;
break;
OPTION('u', "umount", NULL, "Unmount the image from the specified directory"):
arg_action = ACTION_UMOUNT;
break;
OPTION_SHORT('U', NULL, "Shortcut for --umount --rmdir"):
arg_action = ACTION_UMOUNT;
arg_rmdir = true;
break;
OPTION_LONG("attach", NULL, "Attach the disk image to a loopback block device"):
arg_action = ACTION_ATTACH;
break;
OPTION_LONG("detach", NULL, "Detach a loopback block device again"):
arg_action = ACTION_DETACH;
break;
OPTION('l', "list", NULL, "List all the files and directories of the specified OS image"):
arg_action = ACTION_LIST;
arg_flags |= DISSECT_IMAGE_READ_ONLY;
break;
OPTION_LONG("mtree", NULL, "Show BSD mtree manifest of OS image"):
arg_action = ACTION_MTREE;
arg_flags |= DISSECT_IMAGE_READ_ONLY;
break;
OPTION_FULL(OPTION_STOPS_PARSING, /* sc= */ 0, "with", NULL, "Mount, run command, unmount"):
arg_action = ACTION_WITH;
break;
OPTION('x', "copy-from", NULL, "Copy files from image to host"):
arg_action = ACTION_COPY_FROM;
arg_flags |= DISSECT_IMAGE_READ_ONLY;
break;
OPTION('a', "copy-to", NULL, "Copy files from host to image"):
arg_action = ACTION_COPY_TO;
break;
OPTION_LONG("make-archive", NULL, "Convert the DDI to an archive file"):
r = dlopen_libarchive();
if (r < 0)
return log_error_errno(r, "Archive support not available (compiled without libarchive, or libarchive not installed?).");
@@ -605,63 +478,33 @@ static int parse_argv(int argc, char *argv[]) {
arg_action = ACTION_MAKE_ARCHIVE;
break;
case ARG_SHIFT:
OPTION_LONG("discover", NULL, "Discover DDIs in well known directories"):
arg_action = ACTION_DISCOVER;
break;
OPTION_LONG("validate", NULL, "Validate image and image policy"):
arg_action = ACTION_VALIDATE;
break;
OPTION_LONG("shift", NULL, "Shift UID range to selected base"):
arg_action = ACTION_SHIFT;
break;
case ARG_SYSTEM:
system_scope_requested = true;
break;
case ARG_USER:
user_scope_requested = true;
break;
case ARG_ALL:
arg_all = true;
break;
case 'q':
arg_quiet = true;
break;
case ARG_IMAGE_FILTER: {
_cleanup_(image_filter_freep) ImageFilter *f = NULL;
r = image_filter_parse(optarg, &f);
if (r < 0)
return log_error_errno(r, "Failed to parse image filter expression: %s", optarg);
image_filter_free(arg_image_filter);
arg_image_filter = TAKE_PTR(f);
break;
}
case ARG_COPY_OWNERSHIP:
r = parse_tristate_argument_with_auto("--copy-ownership=", optarg, &arg_copy_ownership);
if (r < 0)
return r;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
}
if (system_scope_requested || user_scope_requested)
arg_runtime_scope = system_scope_requested && user_scope_requested ? _RUNTIME_SCOPE_INVALID :
system_scope_requested ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER;
char **args = option_parser_get_args(&state, argc, argv);
switch (arg_action) {
case ACTION_DISSECT:
if (optind + 1 != argc)
if (strv_length(args) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file path as only argument.");
r = parse_image_path_argument(argv[optind], NULL, &arg_image);
r = parse_image_path_argument(args[0], NULL, &arg_image);
if (r < 0)
return r;
@@ -670,15 +513,15 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ACTION_MOUNT:
if (optind + 2 != argc)
if (strv_length(args) != 2)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file path and mount point path as only arguments.");
r = parse_image_path_argument(argv[optind], NULL, &arg_image);
r = parse_image_path_argument(args[0], NULL, &arg_image);
if (r < 0)
return r;
r = parse_path_argument(argv[optind+1], /* suppress_root= */ false, &arg_path);
r = parse_path_argument(args[1], /* suppress_root= */ false, &arg_path);
if (r < 0)
return r;
@@ -686,42 +529,42 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ACTION_UMOUNT:
if (optind + 1 != argc)
if (strv_length(args) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected a mount point path as only argument.");
r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_path);
r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_path);
if (r < 0)
return r;
break;
case ACTION_ATTACH:
if (optind + 1 != argc)
if (strv_length(args) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file path as only argument.");
r = parse_image_path_argument(argv[optind], NULL, &arg_image);
r = parse_image_path_argument(args[0], NULL, &arg_image);
if (r < 0)
return r;
break;
case ACTION_DETACH:
if (optind + 1 != argc)
if (strv_length(args) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file path or loopback device as only argument.");
r = parse_image_path_argument(argv[optind], NULL, &arg_image);
r = parse_image_path_argument(args[0], NULL, &arg_image);
if (r < 0)
return r;
break;
case ACTION_LIST:
case ACTION_MTREE:
if (optind + 1 != argc)
if (strv_length(args) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file or directory path as only argument.");
r = parse_image_path_argument(argv[optind], &arg_root, &arg_image);
r = parse_image_path_argument(args[0], &arg_root, &arg_image);
if (r < 0)
return r;
@@ -729,63 +572,63 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ACTION_MAKE_ARCHIVE:
if (argc < optind + 1 || argc > optind + 2)
if (!IN_SET(strv_length(args), 1, 2))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file, and an optional target path as only arguments.");
r = parse_image_path_argument(argv[optind], &arg_root, &arg_image);
r = parse_image_path_argument(args[0], &arg_root, &arg_image);
if (r < 0)
return r;
arg_target = argc > optind + 1 ? empty_or_dash_to_null(argv[optind + 1]) : NULL;
arg_target = empty_or_dash_to_null(args[1]);
arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT;
break;
case ACTION_COPY_FROM:
if (argc < optind + 2 || argc > optind + 3)
if (!IN_SET(strv_length(args), 2, 3))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file or directory path, a source path and an optional destination path as only arguments.");
r = parse_image_path_argument(argv[optind], &arg_root, &arg_image);
r = parse_image_path_argument(args[0], &arg_root, &arg_image);
if (r < 0)
return r;
arg_source = argv[optind + 1];
arg_target = argc > optind + 2 ? argv[optind + 2] : "-" /* this means stdout */ ;
arg_source = args[1];
arg_target = args[2] ?: "-"; /* this means stdout */ ;
arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT;
break;
case ACTION_COPY_TO:
if (argc < optind + 2 || argc > optind + 3)
if (!IN_SET(strv_length(args), 2, 3))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file or directory path, an optional source path and a destination path as only arguments.");
r = parse_image_path_argument(argv[optind], &arg_root, &arg_image);
r = parse_image_path_argument(args[0], &arg_root, &arg_image);
if (r < 0)
return r;
if (argc > optind + 2) {
arg_source = argv[optind + 1];
arg_target = argv[optind + 2];
if (args[2]) {
arg_source = args[1];
arg_target = args[2];
} else {
arg_source = "-"; /* this means stdin */
arg_target = argv[optind + 1];
arg_target = args[1];
}
arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
break;
case ACTION_WITH:
if (optind >= argc)
if (strv_isempty(args))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file path and an optional command line.");
r = parse_image_path_argument(argv[optind], NULL, &arg_image);
r = parse_image_path_argument(args[0], NULL, &arg_image);
if (r < 0)
return r;
if (argc > optind + 1) {
arg_argv = strv_copy(argv + optind + 1);
if (args[1]) {
arg_argv = strv_copy(args + 1);
if (!arg_argv)
return log_oom();
}
@@ -793,17 +636,17 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ACTION_DISCOVER:
if (optind != argc)
if (!strv_isempty(args))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected no argument.");
break;
case ACTION_VALIDATE:
if (optind + 1 != argc)
if (strv_length(args) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image file path as only argument.");
r = parse_image_path_argument(argv[optind], NULL, &arg_image);
r = parse_image_path_argument(args[0], NULL, &arg_image);
if (r < 0)
return r;
@@ -812,27 +655,29 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ACTION_SHIFT:
if (optind + 2 != argc)
if (strv_length(args) != 2)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Expected an image path and a UID base as only argument.");
r = parse_image_path_argument(argv[optind], &arg_root, &arg_image);
r = parse_image_path_argument(args[0], &arg_root, &arg_image);
if (r < 0)
return r;
if (streq(argv[optind + 1], "foreign"))
if (streq(args[1], "foreign"))
arg_uid_base = FOREIGN_UID_BASE;
else {
r = parse_uid(argv[optind + 1], &arg_uid_base);
r = parse_uid(args[1], &arg_uid_base);
if (r < 0)
return log_error_errno(r, "Failed to parse UID base: %s", argv[optind + 1]);
return log_error_errno(r, "Failed to parse UID base: %s", args[1]);
if ((arg_uid_base & 0xFFFF) != 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected UID base not a multiple of 64K: " UID_FMT, arg_uid_base);
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Selected UID base not a multiple of 64K: " UID_FMT, arg_uid_base);
if (arg_uid_base != 0 &&
!uid_is_container(arg_uid_base) &&
!uid_is_foreign(arg_uid_base))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected UID range is not in the container range, nor the foreign one, refusing.");
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Selected UID range is not in the container range, nor the foreign one, refusing.");
}
arg_flags |= DISSECT_IMAGE_REQUIRE_ROOT;
+56 -91
View File
@@ -1,6 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include <stdio.h>
#include "alloc-util.h"
@@ -11,6 +10,7 @@
#include "id128-util.h"
#include "log.h"
#include "main-func.h"
#include "options.h"
#include "parse-argument.h"
#include "pretty-print.h"
#include "string-util.h"
@@ -24,10 +24,12 @@ 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;
VERB_NOARG(verb_new, "new", "Generate a new ID");
static int verb_new(int argc, char *argv[], uintptr_t _data, void *userdata) {
return id128_print_new(arg_mode);
}
VERB_NOARG(verb_machine_id, "machine-id", "Print the ID of current machine");
static int verb_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) {
sd_id128_t id;
int r;
@@ -43,6 +45,7 @@ static int verb_machine_id(int argc, char *argv[], uintptr_t _data, void *userda
return id128_pretty_print(id, arg_mode);
}
VERB_NOARG(verb_boot_id, "boot-id", "Print the ID of current boot");
static int verb_boot_id(int argc, char *argv[], uintptr_t _data, void *userdata) {
sd_id128_t id;
int r;
@@ -58,6 +61,7 @@ static int verb_boot_id(int argc, char *argv[], uintptr_t _data, void *userdata)
return id128_pretty_print(id, arg_mode);
}
VERB_NOARG(verb_invocation_id, "invocation-id", "Print the ID of current invocation");
static int verb_invocation_id(int argc, char *argv[], uintptr_t _data, void *userdata) {
sd_id128_t id;
int r;
@@ -73,6 +77,7 @@ static int verb_invocation_id(int argc, char *argv[], uintptr_t _data, void *use
return id128_pretty_print(id, arg_mode);
}
VERB_NOARG(verb_var_uuid, "var-partition-uuid", "Print the UUID for the /var/ partition");
static int verb_var_uuid(int argc, char *argv[], uintptr_t _data, void *userdata) {
sd_id128_t id;
int r;
@@ -130,6 +135,7 @@ static int show_one(Table **table, const char *name, sd_id128_t uuid, bool first
arg_mode == ID128_PRINT_ID128 ? TABLE_ID128 : TABLE_UUID, uuid);
}
VERB(verb_show, "show", "[NAME|UUID]", VERB_ANY, VERB_ANY, 0, "Print one or more UUIDs");
static int verb_show(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(table_unrefp) Table *table = NULL;
int r;
@@ -186,158 +192,117 @@ static int verb_show(int argc, char *argv[], uintptr_t _data, void *userdata) {
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_width(options, 0, verbs, 0);
printf("%s [OPTIONS...] COMMAND\n\n"
"%sGenerate and print 128-bit identifiers.%s\n"
"\nCommands:\n"
" new Generate a new ID\n"
" machine-id Print the ID of current machine\n"
" boot-id Print the ID of current boot\n"
" invocation-id Print the ID of current invocation\n"
" var-partition-uuid Print the UUID for the /var/ partition\n"
" show [NAME|UUID] Print one or more UUIDs\n"
" help Show this help\n"
"\nOptions:\n"
" -h --help Show this help\n"
" --no-pager Do not pipe output into a pager\n"
" --no-legend Do not show the headers and footers\n"
" --json=FORMAT Output inspection data in JSON (takes one of\n"
" pretty, short, off)\n"
" -j Equivalent to --json=pretty (on TTY) or\n"
" --json=short (otherwise)\n"
" -p --pretty Generate samples of program code\n"
" -P --value Only print the value\n"
" -a --app-specific=ID Generate app-specific IDs\n"
" -u --uuid Output in UUID format\n"
"\nSee the %s for details.\n",
"\nCommands:\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal(),
link);
ansi_normal());
table_print(verbs, stdout);
printf("\nOptions:\n");
table_print(options, stdout);
printf("\nSee the %s for details.\n", link);
return 0;
}
static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) {
return help();
}
VERB_COMMON_HELP(help);
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_NO_PAGER,
ARG_NO_LEGEND,
ARG_JSON,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
{ "json", required_argument, NULL, ARG_JSON },
{ "pretty", no_argument, NULL, 'p' },
{ "value", no_argument, NULL, 'P' },
{ "app-specific", required_argument, NULL, 'a' },
{ "uuid", no_argument, NULL, 'u' },
{},
};
int c, r;
static int parse_argv(int argc, char *argv[], char ***ret_args) {
int r;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "hpa:uPj", options, NULL)) >= 0)
switch (c) {
OptionParser state = {};
const char *arg;
case 'h':
FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c)
switch (c) {
OPTION_COMMON_HELP:
return help();
case ARG_VERSION:
OPTION_COMMON_VERSION:
return version();
case ARG_NO_PAGER:
OPTION_COMMON_NO_PAGER:
arg_pager_flags |= PAGER_DISABLE;
break;
case ARG_NO_LEGEND:
OPTION_COMMON_NO_LEGEND:
arg_legend = false;
break;
case 'j':
OPTION_LONG("json", "FORMAT",
"Output inspection data in JSON (takes one of pretty, short, off)"):
r = parse_json_argument(arg, &arg_json_format_flags);
if (r <= 0)
return r;
break;
OPTION_SHORT('j', NULL,
"Equivalent to --json=pretty (on TTY) or --json=short (otherwise)"):
arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO;
break;
case ARG_JSON:
r = parse_json_argument(optarg, &arg_json_format_flags);
if (r <= 0)
return r;
break;
case 'p':
OPTION('p', "pretty", NULL, "Generate samples of program code"):
arg_mode = ID128_PRINT_PRETTY;
arg_value = false;
break;
case 'P':
OPTION('P', "value", NULL, "Only print the value"):
arg_value = true;
if (arg_mode == ID128_PRINT_PRETTY)
arg_mode = ID128_PRINT_ID128;
break;
case 'a':
r = id128_from_string_nonzero(optarg, &arg_app);
OPTION('a', "app-specific", "ID", "Generate app-specific IDs"):
r = id128_from_string_nonzero(arg, &arg_app);
if (r == -ENXIO)
return log_error_errno(r, "Application ID cannot be all zeros.");
if (r < 0)
return log_error_errno(r, "Failed to parse \"%s\" as application-ID: %m", optarg);
return log_error_errno(r, "Failed to parse \"%s\" as application ID: %m", arg);
break;
case 'u':
OPTION('u', "uuid", NULL, "Output in UUID format"):
arg_mode = ID128_PRINT_UUID;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
*ret_args = option_parser_get_args(&state, argc, argv);
return 1;
}
static int id128_main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "new", VERB_ANY, 1, 0, verb_new },
{ "machine-id", VERB_ANY, 1, 0, verb_machine_id },
{ "boot-id", VERB_ANY, 1, 0, verb_boot_id },
{ "invocation-id", VERB_ANY, 1, 0, verb_invocation_id },
{ "var-partition-uuid", VERB_ANY, 1, 0, verb_var_uuid },
{ "show", VERB_ANY, VERB_ANY, 0, verb_show },
{ "help", VERB_ANY, VERB_ANY, 0, verb_help },
{}
};
return dispatch_verb(argc, argv, verbs, NULL);
}
static int run(int argc, char *argv[]) {
int r;
log_setup();
r = parse_argv(argc, argv);
char **args = NULL; /* unnecessary initialization to appease gcc <= 13 */
r = parse_argv(argc, argv, &args);
if (r <= 0)
return r;
return id128_main(argc, argv);
return dispatch_verb_with_args(args, NULL);
}
DEFINE_MAIN_FUNCTION(run);
+1 -1
View File
@@ -47,7 +47,7 @@ const char* _bus_error_message(const sd_bus_error *e, int error, char buf[static
static const sd_bus_error_map * const CONCATENATE(errors ## _copy_, __COUNTER__) = errors;
/* We use something exotic as end marker, to ensure people build the
* maps using the macsd-ros. */
* maps using the macros. */
#define BUS_ERROR_MAP_END_MARKER -'x'
BUS_ERROR_MAP_ELF_USE(bus_standard_errors);
+79 -125
View File
@@ -1,6 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@@ -16,10 +15,12 @@
#include "exit-status.h"
#include "fd-util.h"
#include "fdset.h"
#include "format-table.h"
#include "format-util.h"
#include "log.h"
#include "main-func.h"
#include "notify-recv.h"
#include "options.h"
#include "parse-util.h"
#include "pidref.h"
#include "pretty-print.h"
@@ -57,40 +58,28 @@ STATIC_DESTRUCTOR_REGISTER(arg_fdname, freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
_cleanup_(table_unrefp) Table *options = NULL;
int r;
r = terminal_urlify_man("systemd-notify", "1", &link);
if (r < 0)
return log_oom();
printf("%s [OPTIONS...] [VARIABLE=VALUE...]\n"
"%s [OPTIONS...] --exec [VARIABLE=VALUE...] ; -- CMDLINE...\n"
"%s [OPTIONS...] --fork -- CMDLINE...\n"
"\n%sNotify the init system about service status updates.%s\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --ready Inform the service manager about service start-up/reload\n"
" completion\n"
" --reloading Inform the service manager about configuration reloading\n"
" --stopping Inform the service manager about service shutdown\n"
" --pid[=PID] Set main PID of daemon\n"
" --uid=USER Set user to send from\n"
" --status=TEXT Set status text\n"
" --booted Check if the system was booted up with systemd\n"
" --no-block Do not wait until operation finished\n"
" --exec Execute command line separated by ';' once done\n"
" --fd=FD Pass specified file descriptor with along with message\n"
" --fdname=NAME Name to assign to passed file descriptor(s)\n"
" --fork Receive notifications from child rather than sending them\n"
" -q --quiet Do not show PID of child when forking\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
program_invocation_short_name,
r = option_parser_get_help_table(&options);
if (r < 0)
return r;
printf("%1$s [OPTIONS...] [VARIABLE=VALUE...]\n"
"%1$s [OPTIONS...] --exec [VARIABLE=VALUE...] ; -- CMDLINE...\n"
"%1$s [OPTIONS...] --fork -- CMDLINE...\n"
"\n%2$sNotify the init system about service status updates.%3$s\n\n",
program_invocation_short_name,
ansi_highlight(),
ansi_normal(),
link);
ansi_normal());
table_print(options, stdout);
printf("\nSee the %s for details.\n", link);
return 0;
}
@@ -161,123 +150,87 @@ from_self:
return pidref_set_self(ret);
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_READY = 0x100,
ARG_RELOADING,
ARG_STOPPING,
ARG_VERSION,
ARG_PID,
ARG_STATUS,
ARG_BOOTED,
ARG_UID,
ARG_NO_BLOCK,
ARG_EXEC,
ARG_FD,
ARG_FDNAME,
ARG_FORK,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "ready", no_argument, NULL, ARG_READY },
{ "reloading", no_argument, NULL, ARG_RELOADING },
{ "stopping", no_argument, NULL, ARG_STOPPING },
{ "pid", optional_argument, NULL, ARG_PID },
{ "status", required_argument, NULL, ARG_STATUS },
{ "booted", no_argument, NULL, ARG_BOOTED },
{ "uid", required_argument, NULL, ARG_UID },
{ "no-block", no_argument, NULL, ARG_NO_BLOCK },
{ "exec", no_argument, NULL, ARG_EXEC },
{ "fd", required_argument, NULL, ARG_FD },
{ "fdname", required_argument, NULL, ARG_FDNAME },
{ "fork", no_argument, NULL, ARG_FORK },
{ "quiet", no_argument, NULL, 'q' },
{}
};
static int parse_argv(int argc, char *argv[], char ***ret_args) {
_cleanup_fdset_free_ FDSet *passed = NULL;
bool do_exec = false;
int c, r;
int r;
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0) {
OptionParser state = {};
const char *arg;
FOREACH_OPTION(&state, c, argc, argv, &arg, /* on_error= */ return c)
switch (c) {
case 'h':
OPTION_COMMON_HELP:
return help();
case ARG_VERSION:
OPTION_COMMON_VERSION:
return version();
case ARG_READY:
OPTION_LONG("ready", NULL,
"Inform the service manager about service start-up/reload completion"):
arg_ready = true;
break;
case ARG_RELOADING:
OPTION_LONG("reloading", NULL,
"Inform the service manager about configuration reloading"):
arg_reloading = true;
break;
case ARG_STOPPING:
OPTION_LONG("stopping", NULL,
"Inform the service manager about service shutdown"):
arg_stopping = true;
break;
case ARG_PID:
OPTION_FULL(OPTION_OPTIONAL_ARG, /* sc= */ 0, "pid", "PID",
"Set main PID of daemon"):
pidref_done(&arg_pid);
if (isempty(optarg) || streq(optarg, "auto"))
if (isempty(arg) || streq(arg, "auto"))
r = pidref_parent_if_applicable(&arg_pid);
else if (streq(optarg, "parent"))
else if (streq(arg, "parent"))
r = pidref_set_parent(&arg_pid);
else if (streq(optarg, "self"))
else if (streq(arg, "self"))
r = pidref_set_self(&arg_pid);
else
r = pidref_set_pidstr(&arg_pid, optarg);
r = pidref_set_pidstr(&arg_pid, arg);
if (r < 0)
return log_error_errno(r, "Failed to refer to --pid='%s': %m", optarg);
return log_error_errno(r, "Failed to refer to --pid='%s': %m", arg);
break;
case ARG_STATUS:
arg_status = optarg;
OPTION_LONG("uid", "USER", "Set user to send from"):
r = get_user_creds(&arg, &arg_uid, &arg_gid, NULL, NULL, 0);
if (r == -ESRCH) /* If the user doesn't exist, then accept it anyway as numeric */
r = parse_uid(arg, &arg_uid);
if (r < 0)
return log_error_errno(r, "Can't resolve user %s: %m", arg);
break;
case ARG_BOOTED:
OPTION_LONG("status", "TEXT", "Set status text"):
arg_status = arg;
break;
OPTION_LONG("booted", NULL, "Check if the system was booted up with systemd"):
arg_action = ACTION_BOOTED;
break;
case ARG_UID: {
const char *u = optarg;
r = get_user_creds(&u, &arg_uid, &arg_gid, NULL, NULL, 0);
if (r == -ESRCH) /* If the user doesn't exist, then accept it anyway as numeric */
r = parse_uid(u, &arg_uid);
if (r < 0)
return log_error_errno(r, "Can't resolve user %s: %m", optarg);
break;
}
case ARG_NO_BLOCK:
OPTION_LONG("no-block", NULL, "Do not wait until operation finished"):
arg_no_block = true;
break;
case ARG_EXEC:
OPTION_LONG("exec", NULL, "Execute command line separated by ';' once done"):
do_exec = true;
break;
case ARG_FD: {
OPTION_LONG("fd", "FD", "Pass specified file descriptor along with the message"): {
_cleanup_close_ int owned_fd = -EBADF;
int fdnr;
fdnr = parse_fd(optarg);
int fdnr = parse_fd(arg);
if (fdnr < 0)
return log_error_errno(fdnr, "Failed to parse file descriptor: %s", optarg);
return log_error_errno(fdnr, "Failed to parse file descriptor: %s", arg);
if (!passed) {
/* Take possession of all passed fds */
@@ -310,33 +263,28 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_FDNAME:
if (!fdname_is_valid(optarg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File descriptor name invalid: %s", optarg);
OPTION_LONG("fdname", "NAME", "Name to assign to passed file descriptors"):
if (!fdname_is_valid(arg))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File descriptor name invalid: %s", arg);
if (free_and_strdup(&arg_fdname, optarg) < 0)
if (free_and_strdup(&arg_fdname, arg) < 0)
return log_oom();
break;
case ARG_FORK:
OPTION_LONG("fork", NULL, "Receive notifications from child rather than sending them"):
arg_action = ACTION_FORK;
break;
case 'q':
OPTION('q', "quiet", NULL, "Do not show PID of child when forking"):
arg_quiet = true;
break;
case '?':
return -EINVAL;
default:
assert_not_reached();
}
}
bool have_env = arg_ready || arg_stopping || arg_reloading || arg_status || pidref_is_set(&arg_pid) || !fdset_isempty(arg_fds);
char **args = option_parser_get_args(&state, argc, argv);
switch (arg_action) {
case ACTION_NOTIFY: {
@@ -348,22 +296,22 @@ static int parse_argv(int argc, char *argv[]) {
if (do_exec) {
int i;
for (i = optind; i < argc; i++)
if (streq(argv[i], ";"))
for (i = 0; args[i]; i++)
if (streq(args[i], ";"))
break;
if (i >= argc)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "If --exec is used argument list must contain ';' separator, refusing.");
if (i+1 == argc)
if (!args[i])
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "If --exec is used, argument list must contain ';' separator, refusing.");
if (!args[i + 1])
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty command line specified after ';' separator, refusing.");
arg_exec = strv_copy_n(argv + i + 1, argc - i - 1);
arg_exec = strv_copy(args + i + 1);
if (!arg_exec)
return log_oom();
n_arg_env = i - optind;
n_arg_env = i;
} else
n_arg_env = argc - optind;
n_arg_env = strv_length(args);
have_env = have_env || n_arg_env > 0;
if (!have_env) {
@@ -376,7 +324,7 @@ static int parse_argv(int argc, char *argv[]) {
}
if (n_arg_env > 0) {
arg_env = strv_copy_n(argv + optind, n_arg_env);
arg_env = strv_copy_n(args, n_arg_env);
if (!arg_env)
return log_oom();
}
@@ -388,13 +336,13 @@ static int parse_argv(int argc, char *argv[]) {
}
case ACTION_BOOTED:
if (argc > optind)
if (!strv_isempty(args))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--booted takes no parameters, refusing.");
break;
case ACTION_FORK:
if (optind >= argc)
if (strv_isempty(args))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--fork requires a command to be specified, refusing.");
break;
@@ -404,7 +352,10 @@ static int parse_argv(int argc, char *argv[]) {
}
if (have_env && arg_action != ACTION_NOTIFY)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--ready, --reloading, --stopping, --pid=, --status=, --fd= may not be combined with --fork or --booted, refusing.");
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"--ready, --reloading, --stopping, --pid=, --status=, --fd= may not be combined with --fork or --booted, refusing.");
*ret_args = args;
return 1;
}
@@ -577,16 +528,18 @@ static int run(int argc, char* argv[]) {
_cleanup_strv_free_ char **final_env = NULL;
const char *our_env[10];
size_t i = 0;
char **args = NULL; /* unnecessary initialization to appease gcc */
int r;
log_setup();
r = parse_argv(argc, argv);
r = parse_argv(argc, argv, &args);
if (r <= 0)
return r;
assert(args);
if (arg_action == ACTION_FORK)
return action_fork(argv + optind);
return action_fork(args);
if (arg_action == ACTION_BOOTED) {
r = sd_booted();
@@ -652,6 +605,7 @@ static int run(int argc, char* argv[]) {
}
our_env[i++] = NULL;
assert(i <= ELEMENTSOF(our_env));
final_env = strv_env_merge((char**) our_env, arg_env);
if (!final_env)
+69
View File
@@ -2139,6 +2139,75 @@ static const char* table_data_rgap_underline(const TableData *d) {
return NULL;
}
int table_data_requested_width(Table *table, size_t column, size_t *ret) {
size_t width = 0;
int r;
assert(table);
assert(ret);
for (size_t row = 0; row < table_get_rows(table); row++) {
TableCell *cell = table_get_cell(table, row, column);
if (!cell)
continue;
TableData *data = table_get_data(table, cell);
if (!data)
continue;
size_t w;
r = table_data_requested_width_height(
table, data, SIZE_MAX, &w, /* ret_height= */ NULL, /* have_soft= */ NULL);
if (r < 0)
return r;
width = MAX(width, w);
}
*ret = width;
return 0;
}
int table_set_column_width(Table *t, size_t column, size_t width) {
int r = 0;
assert(t);
for (size_t row = 0; row < table_get_rows(t); row++) {
TableCell *cell = table_get_cell(t, row, column);
if (!cell)
continue;
RET_GATHER(r, table_set_minimum_width(t, cell, width));
}
return r;
}
int table_sync_column_width(Table *a, size_t column_a, Table *b, size_t column_b) {
size_t w1, w2;
int r;
assert(a);
assert(b);
/* Make both tables have specified columns of same width */
r = table_data_requested_width(a, column_a, &w1);
if (r < 0)
return log_error_errno(r, "Failed to query table column width: %m");
r = table_data_requested_width(b, column_b, &w2);
if (r < 0)
return log_error_errno(r, "Failed to query table column width: %m");
r = 0;
RET_GATHER(r, table_set_column_width(a, column_a, MAX(w1, w2)));
RET_GATHER(r, table_set_column_width(b, column_b, MAX(w1, w2)));
return r;
}
int table_print(Table *t, FILE *f) {
size_t n_rows, *minimum_width, *maximum_width, display_columns, *requested_width,
table_minimum_width, table_maximum_width, table_requested_width, table_effective_width,
+5
View File
@@ -141,6 +141,11 @@ int table_set_reverse(Table *t, size_t column, bool b);
int table_hide_column_from_display_internal(Table *t, ...);
#define table_hide_column_from_display(t, ...) table_hide_column_from_display_internal(t, __VA_ARGS__, SIZE_MAX)
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_width(Table *a, size_t column_a, Table *b, size_t column_b);
int table_print(Table *t, FILE *f);
int table_format(Table *t, char **ret);
+1
View File
@@ -143,6 +143,7 @@ shared_sources = files(
'numa-util.c',
'open-file.c',
'openssl-util.c',
'options.c',
'osc-context.c',
'output-mode.c',
'pager.c',
+323
View File
@@ -0,0 +1,323 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "format-table.h"
#include "log.h"
#include "options.h"
#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
static bool option_takes_arg(const Option *opt) {
return ASSERT_PTR(opt)->metavar;
}
static bool option_arg_optional(const Option *opt) {
return option_takes_arg(opt) && FLAGS_SET(opt->flags, OPTION_OPTIONAL_ARG);
}
static bool option_arg_required(const Option *opt) {
return option_takes_arg(opt) && !FLAGS_SET(opt->flags, OPTION_OPTIONAL_ARG);
}
static bool option_is_metadata(const Option *opt) {
/* A metadata entry that is not a real option, like the group marker */
return FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_GROUP_MARKER);
}
static void kill_arg(char* argv[], int argc, int index) {
assert(index < argc);
assert(!argv[argc]);
/* Eliminate argv[index] */
memmove(argv + index, argv + index + 1, (argc - index) * sizeof(char*));
}
static void shift_arg(char* argv[], int target, int source) {
assert(argv);
assert(target <= source);
/* Move argv[source] before argv[target], shifting arguments inbetween */
char *saved = argv[source];
memmove(argv + target + 1, argv + target, (source - target) * sizeof(char*));
argv[target] = saved;
}
static int partial_match_error(
const Option options[],
const Option options_end[],
const char *optname,
unsigned n_partial_matches) {
int r;
assert(startswith(ASSERT_PTR(optname), "--"));
assert(n_partial_matches >= 2);
/* Find options that match the prefix */
_cleanup_strv_free_ char **s = NULL;
for (const Option* option = options; option < options_end; option++)
if (!option_is_metadata(option) &&
option->long_code &&
startswith(option->long_code, optname + 2)) {
r = strv_extendf(&s, "--%s", option->long_code);
if (r < 0)
return log_error_errno(r, "Failed to format message: %m");
}
assert(strv_length(s) == n_partial_matches);
_cleanup_free_ char *p = strv_join_full(s, ", ", /* prefix= */ NULL, /* escape_separator= */ false);
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: option '%s' is ambiguous; possibilities: %s",
program_invocation_short_name, optname, strnull(p));
}
int option_parse(
const Option options[],
const Option options_end[],
OptionParser *state,
int argc, char *argv[],
const Option **ret_option,
const char **ret_arg) {
assert(ret_arg);
/* Check and initialize */
if (state->optind == 0) {
if (argc < 1 || strv_isempty(argv))
return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "argv cannot be empty");
*state = (OptionParser) {
.optind = 1,
.positional_offset = 1,
};
}
/* Look for the next option */
const Option *option = NULL; /* initialization to appease gcc 13 */
const char *optname = NULL, *optval = NULL;
_cleanup_free_ char *_optname = NULL; /* allocated option name */
bool separate_optval = false;
if (state->short_option_offset == 0) {
/* Skip over non-option parameters */
for (;;) {
if (state->optind == argc)
return 0;
if (streq(argv[state->optind], "--")) {
/* No more options. Eliminate "--" so that the list of positional args is clean. */
kill_arg(argv, argc, state->optind);
return 0;
}
if (state->parsing_stopped)
return 0;
if (argv[state->optind][0] == '-' &&
argv[state->optind][1] != '\0')
/* Looks like we found an option parameter */
break;
state->optind++;
}
/* Find matching option entry.
* First, figure out if we have a long option or a short option. */
assert(argv[state->optind][0] == '-');
if (argv[state->optind][1] == '-') {
/* We have a long option. */
char *eq = strchr(argv[state->optind], '=');
if (eq) {
optname = _optname = strndup(argv[state->optind], eq - argv[state->optind]);
if (!_optname)
return log_oom();
/* joined argument */
optval = eq + 1;
} else
/* argument (if any) is separate */
optname = argv[state->optind];
const Option *last_partial = NULL;
unsigned n_partial_matches = 0; /* The commandline option matches a defined prefix. */
for (option = options;; option++) {
if (option >= options_end) {
if (n_partial_matches == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: unrecognized option '%s'",
program_invocation_short_name, optname);
if (n_partial_matches > 1)
return partial_match_error(options, options_end, optname, n_partial_matches);
/* just one partial — good */
option = last_partial;
break;
}
if (option_is_metadata(option) || !option->long_code)
continue;
/* Check if the parameter forms a prefix of the option name */
const char *rest = startswith(option->long_code, optname + 2);
if (!rest)
continue;
if (isempty(rest))
/* exact match */
break;
/* partial match */
last_partial = option;
n_partial_matches++;
}
} else
/* We have a short option */
state->short_option_offset = 1;
}
if (state->short_option_offset > 0) {
char optchar = argv[state->optind][state->short_option_offset];
if (asprintf(&_optname, "-%c", optchar) < 0)
return log_oom();
optname = _optname;
for (option = options;; option++) {
if (option >= options_end)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: unrecognized option '%s'",
program_invocation_short_name, optname);
if (option_is_metadata(option) || optchar != option->short_code)
continue;
const char *rest = argv[state->optind] + state->short_option_offset + 1;
if (option_takes_arg(option) && !isempty(rest)) {
/* The rest of this parameter is the value. */
optval = rest;
state->short_option_offset = 0;
} else if (isempty(rest))
state->short_option_offset = 0;
else
state->short_option_offset++;
break;
}
}
assert(option);
if (optval && !option_takes_arg(option))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: option '%s' doesn't allow an argument",
program_invocation_short_name, optname);
if (!optval && option_arg_required(option)) {
if (!argv[state->optind + 1])
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"%s: option '%s' requires an argument",
program_invocation_short_name, optname);
optval = argv[state->optind + 1];
separate_optval = true;
}
if (state->short_option_offset == 0) {
/* We're done with this option. Adjust the array and position. */
shift_arg(argv, state->positional_offset++, state->optind++);
if (separate_optval)
shift_arg(argv, state->positional_offset++, state->optind++);
}
if (FLAGS_SET(option->flags, OPTION_STOPS_PARSING))
state->parsing_stopped = true;
if (ret_option)
/* Return the matched Option structure to allow the caller to "know" what was matched */
*ret_option = option;
*ret_arg = optval;
return option->id;
}
char** option_parser_get_args(OptionParser *state, int argc, char *argv[]) {
/* Returns positional args as a strv.
* If "--" was found, it has been removed. */
assert(state->optind > 0);
return argv + state->positional_offset;
}
int _option_parser_get_help_table(
const Option options[],
const Option options_end[],
const char *group,
Table **ret) {
int r;
assert(ret);
_cleanup_(table_unrefp) Table *table = table_new("names", "help");
if (!table)
return log_oom();
bool in_group = group == NULL; /* Are we currently in the section on the array that forms
* group <group>? The first part is the default group, so
* the group was not specified, we are in. */
for (const Option *opt = options; opt < options_end; opt++) {
bool group_marker = FLAGS_SET(opt->flags, OPTION_GROUP_MARKER);
if (!in_group) {
in_group = group_marker && streq(group, opt->long_code);
continue;
}
if (group_marker)
break; /* End of group */
assert(!option_is_metadata(opt));
if (!opt->help)
/* No help string — we do not show the option */
continue;
char sc[3] = " ";
if (opt->short_code != 0)
xsprintf(sc, "-%c", opt->short_code);
/* 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.
*
* "=" is shown only when a long option is defined: -l --long=ARG, --long=ARG, -s ARG.
*/
bool need_eq = option_takes_arg(opt) && opt->long_code;
_cleanup_free_ char *s = strjoin(
" ",
sc,
" ",
opt->long_code ? "--" : "",
strempty(opt->long_code),
option_arg_optional(opt) ? "[" : "",
need_eq ? "=" : "",
strempty(opt->metavar),
option_arg_optional(opt) ? "]" : "");
if (!s)
return log_oom();
r = table_add_many(table, TABLE_STRING, s);
if (r < 0)
return table_log_add_error(r);
_cleanup_strv_free_ char **t = strv_split(opt->help, /* separators= */ NULL);
if (!t)
return log_oom();
r = table_add_many(table, TABLE_STRV_WRAPPED, t);
if (r < 0)
return table_log_add_error(r);
}
table_set_header(table, false);
*ret = TAKE_PTR(table);
return 0;
}
+101
View File
@@ -0,0 +1,101 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "shared-forward.h"
typedef enum OptionFlags {
OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */
OPTION_STOPS_PARSING = 1U << 1, /* This option acts like "--" */
OPTION_GROUP_MARKER = 1U << 2, /* Fake option entry to separate groups */
} OptionFlags;
typedef struct Option {
int id;
OptionFlags flags;
char short_code;
const char *long_code;
const char *metavar;
const char *help;
} Option;
#define _OPTION(counter, fl, sc, lc, mv, h) \
_section_("SYSTEMD_OPTIONS") \
_alignptr_ \
_used_ \
_retain_ \
_variable_no_sanitize_address_ \
static const Option CONCATENATE(option, counter) = { \
.id = 0x100 + counter, \
.flags = fl, \
.short_code = sc, \
.long_code = lc, \
.metavar = mv, \
.help = h, \
}; \
case (0x100 + counter)
/* Magic entry in the table (which will not be returned) that designates the start of the group <gr>.
* The define is structured as 'case' so that it can be followed by ':' and indented appropriately.
*/
#define OPTION_GROUP(gr) \
_OPTION(__COUNTER__, OPTION_GROUP_MARKER, /* sc= */ 0, /* lc= */ gr, /* mv= */ NULL, /* h= */ NULL)
#define OPTION_FULL(fl, sc, lc, mv, h) _OPTION(__COUNTER__, fl, sc, lc, mv, h)
#define OPTION(sc, lc, mv, h) OPTION_FULL(/* fl= */ 0, sc, lc, mv, h)
#define OPTION_LONG(lc, mv, h) OPTION(/* sc= */ 0, lc, mv, h)
#define OPTION_SHORT(sc, mv, h) OPTION(sc, /* lc= */ NULL, mv, h)
#define OPTION_COMMON_HELP \
OPTION('h', "help", NULL, "Show this help")
#define OPTION_COMMON_VERSION \
OPTION_LONG("version", NULL, "Show package version")
#define OPTION_COMMON_NO_PAGER \
OPTION_LONG("no-pager", NULL, "Do not start a pager")
#define OPTION_COMMON_NO_LEGEND \
OPTION_LONG("no-legend", NULL, "Do not show headers and footers")
#define OPTION_COMMON_JSON \
OPTION_LONG("json", "FORMAT", "Generate JSON output (pretty, short, or off)")
/* This is magically mapped to the beginning and end of the section */
extern const Option __start_SYSTEMD_OPTIONS[];
extern const Option __stop_SYSTEMD_OPTIONS[];
typedef struct OptionParser {
int optind; /* Position of the parameter being handled.
* 0 → option parsing hasn't been started yet. */
int short_option_offset; /* Set when we're parsing an argument with one or more short options.
* 0 → we're not parsing short options. */
int positional_offset; /* Offset to where positional parameters are. After processing has been
* finished, all options and their args are to the left of this offset. */
bool parsing_stopped; /* We processed "--" or an option that terminates option parsing. */
} OptionParser;
int option_parse(
const Option options[],
const Option options_end[],
OptionParser *state,
int argc, char *argv[],
const Option **ret_option,
const char **ret_arg);
/* Iterate over options. */
#define FOREACH_OPTION_FULL(parser, opt, argc, argv, ret_o, ret_a, on_error) \
for (int opt; (opt = option_parse(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, parser, argc, argv, ret_o, ret_a)) != 0; ) \
if (opt < 0) { \
on_error; \
break; \
} else
#define FOREACH_OPTION(parser, opt, argc, argv, ret_a, on_error) \
FOREACH_OPTION_FULL(parser, opt, argc, argv, /* ret_o= */ NULL, ret_a, on_error)
char** option_parser_get_args(OptionParser *state, int argc, char *argv[]);
int _option_parser_get_help_table(
const Option options[],
const Option options_end[],
const char *group,
Table **ret);
#define option_parser_get_help_table_group(group, ret) \
_option_parser_get_help_table(ALIGN_PTR(__start_SYSTEMD_OPTIONS), __stop_SYSTEMD_OPTIONS, group, ret)
#define option_parser_get_help_table(ret) \
option_parser_get_help_table_group(/* group= */ NULL, ret)
+69 -23
View File
@@ -3,6 +3,7 @@
#include <getopt.h>
#include "env-util.h"
#include "format-table.h"
#include "log.h"
#include "string-util.h"
#include "strv.h"
@@ -57,40 +58,34 @@ bool should_bypass(const char *env_prefix) {
return true;
}
const Verb* verbs_find_verb(const char *name, const Verb verbs[]) {
const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]) {
assert(verbs);
for (size_t i = 0; verbs[i].dispatch; i++)
if (name ? streq(name, verbs[i].verb) : FLAGS_SET(verbs[i].flags, VERB_DEFAULT))
return verbs + i;
for (const Verb *verb = verbs; verb < verbs_end; verb++)
if (name ? streq(name, verb->verb) : FLAGS_SET(verb->flags, VERB_DEFAULT))
return verb;
/* At the end of the list? */
return NULL;
}
int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
const Verb *verb;
const char *name;
int r, left;
int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata) {
int r;
assert(verbs);
assert(verbs_end > verbs);
assert(verbs[0].dispatch);
assert(verbs[0].verb);
assert(argc >= 0);
assert(argv);
assert(argc >= optind);
left = argc - optind;
argv += optind;
optind = 0;
name = argv[0];
const char *name = args ? args[0] : NULL;
size_t left = strv_length(args);
verb = verbs_find_verb(name, verbs);
const Verb *verb = verbs_find_verb(name, verbs, verbs_end);
if (!verb) {
_cleanup_strv_free_ char **verb_strv = NULL;
for (size_t i = 0; verbs[i].dispatch; i++) {
r = strv_extend(&verb_strv, verbs[i].verb);
for (verb = verbs; verb < verbs_end; verb++) {
r = strv_extend(&verb_strv, verb->verb);
if (r < 0)
return log_oom();
}
@@ -120,12 +115,10 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
if (!name)
left = 1;
if (verb->min_args != VERB_ANY &&
(unsigned) left < verb->min_args)
if (verb->min_args != VERB_ANY && left < verb->min_args)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments.");
if (verb->max_args != VERB_ANY &&
(unsigned) left > verb->max_args)
if (verb->max_args != VERB_ANY && left > verb->max_args)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments.");
if ((verb->flags & VERB_ONLINE_ONLY) && running_in_chroot_or_offline()) {
@@ -136,5 +129,58 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
if (!name)
return verb->dispatch(1, STRV_MAKE(verb->verb), verb->data, userdata);
return verb->dispatch(left, argv, verb->data, userdata);
assert(left < INT_MAX); /* args are derived from argc+argv, so their size must fit in an int. */
return verb->dispatch(left, args, verb->data, userdata);
}
int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) {
/* getopt wrapper for _dispatch_verb_with_args.
* TBD: remove this function when all programs with verbs have been converted. */
assert(argc >= 0);
assert(argv);
assert(argc >= optind);
size_t n = 0;
while (verbs[n].dispatch)
n++;
return _dispatch_verb_with_args(strv_skip(argv, optind), verbs, verbs + n, userdata);
}
int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **ret) {
int r;
assert(ret);
_cleanup_(table_unrefp) Table *table = table_new("verb", "help");
if (!table)
return log_oom();
for (const Verb *verb = verbs; verb < verbs_end; verb++) {
assert(verb->dispatch);
/* 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. */
r = table_add_cell_stringf(table, NULL, " %s%s%s",
verb->verb,
verb->argspec ? " " : "",
strempty(verb->argspec));
if (r < 0)
return table_log_add_error(r);
const char *help = verb->help ?: "FIXME";
_cleanup_strv_free_ char **s = strv_split(help, /* separators= */ NULL);
if (!s)
return log_oom();
r = table_add_many(table, TABLE_STRV_WRAPPED, s);
if (r < 0)
return table_log_add_error(r);
}
table_set_header(table, false);
*ret = TAKE_PTR(table);
return 0;
}
+48 -1
View File
@@ -16,11 +16,58 @@ typedef struct {
VerbFlags flags;
int (* const dispatch)(int argc, char *argv[], uintptr_t data, void *userdata);
uintptr_t data;
const char *argspec;
const char *help;
} Verb;
#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \
static int d(int, char**, uintptr_t, void*); \
_section_("SYSTEMD_VERBS") \
_alignptr_ \
_used_ \
_retain_ \
_variable_no_sanitize_address_ \
static const Verb CONCATENATE(d, _data) = { \
.verb = v, \
.min_args = amin, \
.max_args = amax, \
.flags = f, \
.dispatch = d, \
.data = dat, \
.argspec = a, \
.help = h, \
}
/* The same as VERB_FULL, but without the data argument */
#define VERB(d, v, a, amin, amax, f, h) \
VERB_FULL(d, v, a, amin, amax, f, /* dat= */ 0, h)
/* Simplified VERB for parameters that take no argument */
#define VERB_NOARG(d, v, h) \
VERB(d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h)
/* This is magically mapped to the beginning and end of the section */
extern const Verb __start_SYSTEMD_VERBS[];
extern const Verb __stop_SYSTEMD_VERBS[];
bool running_in_chroot_or_offline(void);
bool should_bypass(const char *env_prefix);
const Verb* verbs_find_verb(const char *name, const Verb verbs[]);
const Verb* verbs_find_verb(const char *name, const Verb verbs[], const Verb verbs_end[]);
int _dispatch_verb_with_args(char **args, const Verb verbs[], const Verb verbs_end[], void *userdata);
#define dispatch_verb_with_args(args, userdata) \
_dispatch_verb_with_args(args, ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, userdata)
int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata);
int _verbs_get_help_table(const Verb verbs[], const Verb verbs_end[], Table **ret);
#define verbs_get_help_table(ret) \
_verbs_get_help_table(ALIGN_PTR(__start_SYSTEMD_VERBS), __stop_SYSTEMD_VERBS, ret)
#define VERB_COMMON_HELP(impl) \
VERB(verb_help, "help", NULL, VERB_ANY, VERB_ANY, 0, "Show this help"); \
static int verb_help(int argc, char **argv, uintptr_t data, void *userdata) { \
return impl(); \
}
+1 -1
View File
@@ -135,7 +135,7 @@ static int systemctl_main(int argc, char *argv[]) {
{}
};
const Verb *verb = verbs_find_verb(argv[optind], verbs);
const Verb *verb = verbs_find_verb(argv[optind], verbs, verbs + ELEMENTSOF(verbs) - 1);
if (verb && (verb->flags & VERB_ONLINE_ONLY) && arg_root)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Verb '%s' cannot be used with --root= or --image=.",
+1
View File
@@ -156,6 +156,7 @@ simple_tests += files(
'test-nsresource.c',
'test-nulstr-util.c',
'test-open-file.c',
'test-options.c',
'test-ordered-set.c',
'test-os-util.c',
'test-osc-context.c',
+6 -6
View File
@@ -90,27 +90,27 @@ TEST(getopt_long) {
test_getopt_long_one(STRV_MAKE("arg0",
"--",
"string1",
"string2",
"string3",
"--help",
"-h",
"string4"),
"hr:o::", options,
NULL,
STRV_MAKE("string1",
"string2",
"string3",
"--help",
"-h",
"string4"));
test_getopt_long_one(STRV_MAKE("arg0",
"string1",
"string2",
"--",
"string3",
"--",
"string4"),
"hr:o::", options,
NULL,
STRV_MAKE("string1",
"string2",
"string3",
"--",
"string4"));
test_getopt_long_one(STRV_MAKE("arg0",
File diff suppressed because it is too large Load Diff