From d9fbe513a9700a52845dd2ebbf0f583bab7519f4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 30 Apr 2026 00:07:29 +0200 Subject: [PATCH] mstack-tool: convert to OPTION macros Both the main parser and the util-linux mount-helper-mode parser (invoked as mount.mstack) are converted with "systmed-mstack" and "mount.mstack" as namespaces. The latter has no help. For systemd-mstack, Commands are listed first, and then Options. And --no-pager, --no-legend, --json= are moved to the end. Co-developed-by: Claude Opus 4.7 --- src/mstack/mstack-tool.c | 232 +++++++++++++++++---------------------- 1 file changed, 99 insertions(+), 133 deletions(-) diff --git a/src/mstack/mstack-tool.c b/src/mstack/mstack-tool.c index 2e8946ab72a..244e7dc682d 100644 --- a/src/mstack/mstack-tool.c +++ b/src/mstack/mstack-tool.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "argv-util.h" @@ -11,13 +10,14 @@ #include "extract-word.h" #include "fd-util.h" #include "format-table.h" +#include "help-util.h" #include "image-policy.h" #include "main-func.h" #include "mount-util.h" #include "mountpoint-util.h" #include "mstack.h" +#include "options.h" #include "parse-argument.h" -#include "pretty-print.h" #include "string-util.h" static enum { @@ -41,191 +41,155 @@ 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; - r = terminal_urlify_man("systemd-mstack", "1", &link); + r = option_parser_get_help_table_ns("systemd-mstack", &options); if (r < 0) - return log_oom(); + return r; - printf("%1$s [OPTIONS...] WHAT\n" - "%1$s [OPTIONS...] --mount WHAT WHERE\n" - "%1$s [OPTIONS...] --umount WHERE\n" - "\n%5$sInspect or apply mount stack.%6$s\n\n" - "%3$sOptions:%4$s\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not print the column headers\n" - " --json=pretty|short|off Generate JSON output\n" - " -r --read-only Mount read-only\n" - " --mkdir Make mount directory before mounting, if missing\n" - " --rmdir Remove mount directory after unmounting\n" - " --image-policy=POLICY\n" - " Specify image dissection policy\n" - " --image-filter=FILTER\n" - " Specify image dissection filter\n" - "\n%3$sCommands:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -m --mount Mount the mstack 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" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), ansi_normal(), - ansi_highlight(), ansi_normal()); + r = option_parser_get_help_table_full("systemd-mstack", "Commands", &commands); + if (r < 0) + return r; + (void) table_sync_column_widths(0, options, commands); + + help_cmdline("[OPTIONS...] WHAT"); + help_cmdline("[OPTIONS...] --mount WHAT WHERE"); + help_cmdline("[OPTIONS...] --umount WHERE"); + help_abstract("Inspect or apply mount stack."); + + help_section("Commands"); + r = table_print_or_warn(commands); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-mstack", "1"); return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - ARG_MKDIR, - ARG_RMDIR, - ARG_IMAGE_POLICY, - ARG_IMAGE_FILTER, - }; - - 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' }, - { "json", required_argument, NULL, ARG_JSON }, - { "read-only", no_argument, NULL, 'r' }, - { "mkdir", no_argument, NULL, ARG_MKDIR }, - { "rmdir", no_argument, NULL, ARG_RMDIR }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "image-filter", required_argument, NULL, ARG_IMAGE_FILTER }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hmMuUr", options, NULL)) >= 0) { + OptionParser opts = { argc, argv, .namespace = "systemd-mstack" }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: - arg_pager_flags |= PAGER_DISABLE; - break; + OPTION_NAMESPACE("systemd-mstack"): {} - case ARG_NO_LEGEND: - arg_legend = false; + OPTION('r', "read-only", NULL, "Mount read-only"): + arg_mstack_flags |= MSTACK_RDONLY; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - + OPTION_LONG("mkdir", NULL, "Make mount directory before mounting, if missing"): + arg_mstack_flags |= MSTACK_MKDIR; break; - case 'r': - arg_mstack_flags |= MSTACK_RDONLY; + OPTION_LONG("rmdir", NULL, "Remove mount directory after unmounting"): + arg_rmdir = 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(opts.arg, &arg_image_policy); if (r < 0) return r; break; - case ARG_IMAGE_FILTER: { + OPTION_LONG("image-filter", "FILTER", "Specify image dissection filter"): { _cleanup_(image_filter_freep) ImageFilter *f = NULL; - r = image_filter_parse(optarg, &f); + r = image_filter_parse(opts.arg, &f); if (r < 0) - return log_error_errno(r, "Failed to parse image filter expression: %s", optarg); + return log_error_errno(r, "Failed to parse image filter expression: %s", opts.arg); image_filter_free(arg_image_filter); arg_image_filter = TAKE_PTR(f); break; } - case ARG_MKDIR: - arg_mstack_flags |= MSTACK_MKDIR; + OPTION_COMMON_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; break; - case ARG_RMDIR: - arg_rmdir = true; + OPTION_COMMON_NO_LEGEND: + arg_legend = false; break; - case 'm': + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); + if (r <= 0) + return r; + break; + + OPTION_GROUP("Commands"): {} + + OPTION_COMMON_HELP: + return help(); + + OPTION_COMMON_VERSION: + return version(); + + OPTION('m', "mount", NULL, "Mount the mstack to the specified directory"): arg_action = ACTION_MOUNT; break; - case 'M': - /* Shortcut combination of --mkdir + --mount */ + OPTION_SHORT('M', NULL, "Shortcut for --mount --mkdir"): arg_action = ACTION_MOUNT; arg_mstack_flags |= MSTACK_MKDIR; break; - case 'u': + OPTION('u', "umount", NULL, "Unmount the image from the specified directory"): arg_action = ACTION_UMOUNT; break; - case 'U': - /* Shortcut combination of --rmdir + --umount */ + OPTION_SHORT('U', NULL, "Shortcut for --umount --rmdir"): arg_action = ACTION_UMOUNT; arg_rmdir = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - } + + char **args = option_parser_get_args(&opts); + size_t n_args = option_parser_get_n_args(&opts); switch (arg_action) { case ACTION_INSPECT: - if (optind + 1 != argc) + if (n_args != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected one argument."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_what); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_what); if (r < 0) return r; break; case ACTION_MOUNT: - if (optind + 2 != argc) + if (n_args != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected two arguments."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_what); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_what); if (r < 0) return r; - r = parse_path_argument(argv[optind+1], /* suppress_root= */ false, &arg_where); + r = parse_path_argument(args[1], /* suppress_root= */ false, &arg_where); if (r < 0) return r; break; case ACTION_UMOUNT: - if (optind + 1 != argc) + if (n_args != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected one argument."); - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_where); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_where); if (r < 0) return r; @@ -239,47 +203,49 @@ static int parse_argv(int argc, char *argv[]) { } static int parse_argv_as_mount_helper(int argc, char *argv[]) { - const char *options = NULL; + const char *mount_options = NULL; bool fake = false; - int c, r; + int r; /* Implements util-linux "external helper" command line interface, as per mount(8) man page. */ - while ((c = getopt(argc, argv, "sfnvN:o:t:")) >= 0) { + OptionParser opts = { argc, argv, .namespace = "mount.mstack" }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'f': + OPTION_NAMESPACE("mount.mstack"): {} + + OPTION_SHORT('f', NULL, NULL): fake = true; break; - case 'o': - options = optarg; + OPTION_SHORT('o', "OPTIONS", NULL): + mount_options = opts.arg; break; - case 't': - if (!streq(optarg, "mstack")) - log_debug("Unexpected file system type '%s', ignoring.", optarg); + OPTION_SHORT('t', "TYPE", NULL): + if (!streq(opts.arg, "mstack")) + log_debug("Unexpected file system type '%s', ignoring.", opts.arg); break; - case 's': /* sloppy mount options */ - case 'n': /* aka --no-mtab */ - case 'v': /* aka --verbose */ - log_debug("Ignoring option -%c, not implemented.", c); + OPTION_SHORT('s', NULL, NULL): {} /* sloppy mount options, fall-through */ + OPTION_SHORT('n', NULL, NULL): {} /* aka --no-mtab, fall-through */ + OPTION_SHORT('v', NULL, NULL): /* aka --verbose */ + log_debug("Ignoring option -%c, not implemented.", opts.opt->short_code); break; - case 'N': /* aka --namespace= */ - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Option -%c is not implemented, refusing.", c); - - case '?': - return -EINVAL; + OPTION_SHORT('N', "NAMESPACE", NULL): /* aka --namespace= */ + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Option -%c is not implemented, refusing.", opts.opt->short_code); } - } - if (optind + 2 != argc) + char **args = option_parser_get_args(&opts); + if (option_parser_get_n_args(&opts) != 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Expected an image file path and target directory as only argument."); + "Expected an image file path and target directory as arguments."); - for (const char *p = options;;) { + for (const char *p = mount_options;;) { _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, ",", EXTRACT_KEEP_QUOTE); @@ -300,11 +266,11 @@ static int parse_argv_as_mount_helper(int argc, char *argv[]) { if (fake) return 0; - r = parse_path_argument(argv[optind], /* suppress_root= */ false, &arg_what); + r = parse_path_argument(args[0], /* suppress_root= */ false, &arg_what); if (r < 0) return r; - r = parse_path_argument(argv[optind+1], /* suppress_root= */ false, &arg_where); + r = parse_path_argument(args[1], /* suppress_root= */ false, &arg_where); if (r < 0) return r; -- 2.47.3