]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
mstack-tool: convert to OPTION macros
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Wed, 29 Apr 2026 22:07:29 +0000 (00:07 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Tue, 5 May 2026 11:49:51 +0000 (13:49 +0200)
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 <noreply@anthropic.com>
src/mstack/mstack-tool.c

index 2e8946ab72a7e3e90cc60167fedd96c3e359940a..244e7dc682dcd76af9e9312a57514208c11f3f29 100644 (file)
@@ -1,7 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <fcntl.h>
-#include <getopt.h>
 #include <unistd.h>
 
 #include "argv-util.h"
 #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;