/* 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 {
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;
}
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);
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;