From 5a41d43e1b33746fb59114b5dabd3ad1d5409af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 6 May 2026 12:54:12 +0200 Subject: [PATCH 1/7] portablectl: reorder verb functions The order in --help is changed to move have 'list', 'inspect' (query operations), 'attach'/'detach'/'reattach' (main ops), and then the other more specialized verbs. The functions are then reordered to match this. --- src/portable/portablectl.c | 332 ++++++++++++++++++------------------- 1 file changed, 166 insertions(+), 166 deletions(-) diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 2c555ee359..0c0f03ff16 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -251,6 +251,80 @@ static int maybe_reload(sd_bus **bus) { return bus_service_manager_reload(*bus); } +static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(table_unrefp) Table *table = NULL; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + r = bus_call_method(bus, bus_portable_mgr, "ListImages", &error, &reply, NULL); + if (r < 0) + return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r)); + + table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state"); + if (!table) + return log_oom(); + + r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + const char *name, *type, *state; + uint64_t crtime, mtime, usage; + int ro_int; + + r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + r = table_add_many(table, + TABLE_STRING, name, + TABLE_STRING, type, + TABLE_BOOLEAN, ro_int, + TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL, + TABLE_TIMESTAMP, crtime, + TABLE_TIMESTAMP, mtime, + TABLE_SIZE, usage, + TABLE_STRING, state, + TABLE_SET_COLOR, !streq(state, "detached") ? ansi_highlight_green() : NULL); + if (r < 0) + return table_log_add_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (!table_isempty(table)) { + r = table_set_sort(table, (size_t) 0); + if (r < 0) + return table_log_sort_error(r); + + table_set_header(table, arg_legend); + + r = table_print_or_warn(table); + if (r < 0) + return r; + } + + if (arg_legend) { + if (table_isempty(table)) + printf("No images.\n"); + else + printf("\n%zu images listed.\n", table_get_rows(table) - 1); + } + + return 0; +} + static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd_bus_message **reply) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -960,10 +1034,6 @@ static int verb_attach_image(int argc, char *argv[], uintptr_t _data, void *user return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "AttachImage" : "AttachImageWithExtensions"); } -static int verb_reattach_image(int argc, char *argv[], uintptr_t _data, void *userdata) { - return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "ReattachImage" : "ReattachImageWithExtensions"); -} - static int verb_detach_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -1018,166 +1088,8 @@ static int verb_detach_image(int argc, char *argv[], uintptr_t _data, void *user return 0; } -static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(table_unrefp) Table *table = NULL; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - r = bus_call_method(bus, bus_portable_mgr, "ListImages", &error, &reply, NULL); - if (r < 0) - return log_error_errno(r, "Failed to list images: %s", bus_error_message(&error, r)); - - table = table_new("name", "type", "ro", "crtime", "mtime", "usage", "state"); - if (!table) - return log_oom(); - - r = sd_bus_message_enter_container(reply, 'a', "(ssbtttso)"); - if (r < 0) - return bus_log_parse_error(r); - - for (;;) { - const char *name, *type, *state; - uint64_t crtime, mtime, usage; - int ro_int; - - r = sd_bus_message_read(reply, "(ssbtttso)", &name, &type, &ro_int, &crtime, &mtime, &usage, &state, NULL); - if (r < 0) - return bus_log_parse_error(r); - if (r == 0) - break; - - r = table_add_many(table, - TABLE_STRING, name, - TABLE_STRING, type, - TABLE_BOOLEAN, ro_int, - TABLE_SET_COLOR, ro_int ? ansi_highlight_red() : NULL, - TABLE_TIMESTAMP, crtime, - TABLE_TIMESTAMP, mtime, - TABLE_SIZE, usage, - TABLE_STRING, state, - TABLE_SET_COLOR, !streq(state, "detached") ? ansi_highlight_green() : NULL); - if (r < 0) - return table_log_add_error(r); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (!table_isempty(table)) { - r = table_set_sort(table, (size_t) 0); - if (r < 0) - return table_log_sort_error(r); - - table_set_header(table, arg_legend); - - r = table_print_or_warn(table); - if (r < 0) - return r; - } - - if (arg_legend) { - if (table_isempty(table)) - printf("No images.\n"); - else - printf("\n%zu images listed.\n", table_get_rows(table) - 1); - } - - return 0; -} - -static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r, i; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - for (i = 1; i < argc; i++) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "RemoveImage"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", argv[i]); - if (r < 0) - return bus_log_create_error(r); - - /* This is a slow operation, hence turn off any method call timeouts */ - r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); - if (r < 0) - return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r)); - } - - return 0; -} - -static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int b = true, r; - - if (argc > 2) { - b = parse_boolean(argv[2]); - if (b < 0) - return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]); - } - - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - r = bus_call_method(bus, bus_portable_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b); - if (r < 0) - return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r)); - - return 0; -} - -static int verb_set_limit(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - uint64_t limit; - int r; - - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - if (STR_IN_SET(argv[argc-1], "-", "none", "infinity")) - limit = UINT64_MAX; - else { - r = parse_size(argv[argc-1], 1024, &limit); - if (r < 0) - return log_error_errno(r, "Failed to parse size: %s", argv[argc-1]); - } - - if (argc > 2) - /* With two arguments changes the quota limit of the specified image */ - r = bus_call_method(bus, bus_portable_mgr, "SetImageLimit", &error, NULL, "st", argv[1], limit); - else - /* With one argument changes the pool quota limit */ - r = bus_call_method(bus, bus_portable_mgr, "SetPoolLimit", &error, NULL, "t", limit); - - if (r < 0) - return log_error_errno(r, "Could not set limit: %s", bus_error_message(&error, r)); - - return 0; +static int verb_reattach_image(int argc, char *argv[], uintptr_t _data, void *userdata) { + return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "ReattachImage" : "ReattachImageWithExtensions"); } static int verb_is_image_attached(int argc, char *argv[], uintptr_t _data, void *userdata) { @@ -1230,6 +1142,94 @@ static int verb_is_image_attached(int argc, char *argv[], uintptr_t _data, void return streq(state, "detached"); } +static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int b = true, r; + + if (argc > 2) { + b = parse_boolean(argv[2]); + if (b < 0) + return log_error_errno(b, "Failed to parse boolean argument: %s", argv[2]); + } + + r = acquire_bus(&bus); + if (r < 0) + return r; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + r = bus_call_method(bus, bus_portable_mgr, "MarkImageReadOnly", &error, NULL, "sb", argv[1], b); + if (r < 0) + return log_error_errno(r, "Could not mark image read-only: %s", bus_error_message(&error, r)); + + return 0; +} + +static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r, i; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + for (i = 1; i < argc; i++) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = bus_message_new_method_call(bus, &m, bus_portable_mgr, "RemoveImage"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", argv[i]); + if (r < 0) + return bus_log_create_error(r); + + /* This is a slow operation, hence turn off any method call timeouts */ + r = sd_bus_call(bus, m, USEC_INFINITY, &error, NULL); + if (r < 0) + return log_error_errno(r, "Could not remove image: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int verb_set_limit(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + uint64_t limit; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + if (STR_IN_SET(argv[argc-1], "-", "none", "infinity")) + limit = UINT64_MAX; + else { + r = parse_size(argv[argc-1], 1024, &limit); + if (r < 0) + return log_error_errno(r, "Failed to parse size: %s", argv[argc-1]); + } + + if (argc > 2) + /* With two arguments changes the quota limit of the specified image */ + r = bus_call_method(bus, bus_portable_mgr, "SetImageLimit", &error, NULL, "st", argv[1], limit); + else + /* With one argument changes the pool quota limit */ + r = bus_call_method(bus, bus_portable_mgr, "SetPoolLimit", &error, NULL, "t", limit); + + if (r < 0) + return log_error_errno(r, "Could not set limit: %s", bus_error_message(&error, r)); + + return 0; +} + static int dump_profiles(void) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1269,14 +1269,14 @@ static int help(void) { "%sAttach or detach portable services from the local system.%s\n" "\nCommands:\n" " list List available portable service images\n" + " inspect NAME|PATH [PREFIX...]\n" + " Show details of specified portable service image\n" " attach NAME|PATH [PREFIX...]\n" " Attach the specified portable service image\n" " detach NAME|PATH [PREFIX...]\n" " Detach the specified portable service image\n" " reattach NAME|PATH [PREFIX...]\n" " Reattach the specified portable service image\n" - " inspect NAME|PATH [PREFIX...]\n" - " Show details of specified portable service image\n" " is-attached NAME|PATH Query if portable service image is attached\n" " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n" " remove NAME|PATH... Remove a portable service image\n" From 0731b6c4d947d5eb6cb6f1cfd7c4890965ee00c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 5 May 2026 14:36:12 +0200 Subject: [PATCH 2/7] portablectl: convert to OPTION and VERB macros s|attach/detach from the local system|in the local system|, because "attach from" doesn't work. The synopses for 'list' and 'set-limit' are fixed. Co-developed-by: Claude Opus 4.7 --- src/portable/portablectl.c | 273 ++++++++++++++----------------------- 1 file changed, 106 insertions(+), 167 deletions(-) diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 0c0f03ff16..575ab4149a 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -1,10 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "bus-error.h" #include "bus-locator.h" @@ -15,8 +14,11 @@ #include "env-file.h" #include "format-table.h" #include "fs-util.h" +#include "glyph-util.h" +#include "help-util.h" #include "install.h" #include "main-func.h" +#include "options.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" @@ -24,7 +26,6 @@ #include "path-util.h" #include "polkit-agent.h" #include "portable.h" -#include "pretty-print.h" #include "string-util.h" #include "strv.h" #include "verbs.h" @@ -155,8 +156,9 @@ static int extract_prefix(const char *path, char **ret) { if (!name) return -ENOMEM; - /* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as well as '_' - * which we use as delimiter for the second part of the image string, which we ignore for now. */ + /* A slightly reduced version of what's permitted in unit names. With ':' and '\' are removed, as + * well as '_' which we use as delimiter for the second part of the image string, which we ignore for + * now. */ if (!in_charset(name, ALPHANUMERICAL "-.")) return -EINVAL; @@ -171,9 +173,9 @@ static int determine_matches(const char *image, char **l, bool allow_any, char * _cleanup_strv_free_ char **k = NULL; int r; - /* Determine the matches to apply. If the list is empty we derive the match from the image name. If the list - * contains exactly the "-" we return a wildcard list (which is the empty list), but only if this is expressly - * permitted. */ + /* Determine the matches to apply. If the list is empty we derive the match from the image name. If + * the list contains exactly the "-" we return a wildcard list (which is the empty list), but only if + * this is expressly permitted. */ if (strv_isempty(l)) { char *prefix; @@ -251,6 +253,8 @@ static int maybe_reload(sd_bus **bus) { return bus_service_manager_reload(*bus); } +VERB(verb_list_images, "list", NULL, VERB_ANY, 1, VERB_DEFAULT, + "List available portable service images (default)"); static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; @@ -366,6 +370,8 @@ static int get_image_metadata(sd_bus *bus, const char *image, char **matches, sd return 0; } +VERB(verb_inspect_image, "inspect", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0, + "Show details of specified portable service image"); static int verb_inspect_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1030,10 +1036,14 @@ static int attach_reattach_image(int argc, char *argv[], const char *method) { return 0; } +VERB(verb_attach_image, "attach", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0, + "Attach the specified portable service image"); static int verb_attach_image(int argc, char *argv[], uintptr_t _data, void *userdata) { return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "AttachImage" : "AttachImageWithExtensions"); } +VERB(verb_detach_image, "detach", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0, + "Detach the specified portable service image"); static int verb_detach_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -1088,10 +1098,14 @@ static int verb_detach_image(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB(verb_reattach_image, "reattach", "NAME|PATH [PREFIX…]", 2, VERB_ANY, 0, + "Reattach the specified portable service image"); static int verb_reattach_image(int argc, char *argv[], uintptr_t _data, void *userdata) { return attach_reattach_image(argc, argv, strv_isempty(arg_extension_images) && !arg_force ? "ReattachImage" : "ReattachImageWithExtensions"); } +VERB(verb_is_image_attached, "is-attached", "NAME|PATH", 2, 2, 0, + "Query if portable service image is attached"); static int verb_is_image_attached(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -1142,6 +1156,8 @@ static int verb_is_image_attached(int argc, char *argv[], uintptr_t _data, void return streq(state, "detached"); } +VERB(verb_read_only_image, "read-only", "NAME|PATH [BOOL]", 2, 3, 0, + "Mark or unmark portable service image read-only"); static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1166,6 +1182,8 @@ static int verb_read_only_image(int argc, char *argv[], uintptr_t _data, void *u return 0; } +VERB(verb_remove_image, "remove", "NAME|PATH…", 2, VERB_ANY, 0, + "Remove a portable service image"); static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, i; @@ -1197,6 +1215,8 @@ static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *user return 0; } +VERB(verb_set_limit, "set-limit", "[NAME|PATH] LIMIT", 3, 3, 0, + "Set image or pool size limit (disk quota)"); static int verb_set_limit(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -1256,176 +1276,104 @@ static int dump_profiles(void) { } static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; pager_open(arg_pager_flags); - r = terminal_urlify_man("portablectl", "1", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...] COMMAND ...\n\n" - "%sAttach or detach portable services from the local system.%s\n" - "\nCommands:\n" - " list List available portable service images\n" - " inspect NAME|PATH [PREFIX...]\n" - " Show details of specified portable service image\n" - " attach NAME|PATH [PREFIX...]\n" - " Attach the specified portable service image\n" - " detach NAME|PATH [PREFIX...]\n" - " Detach the specified portable service image\n" - " reattach NAME|PATH [PREFIX...]\n" - " Reattach the specified portable service image\n" - " is-attached NAME|PATH Query if portable service image is attached\n" - " read-only NAME|PATH [BOOL] Mark or unmark portable service image read-only\n" - " remove NAME|PATH... Remove a portable service image\n" - " set-limit [NAME|PATH] Set image or pool size limit (disk quota)\n" - "\nOptions:\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --no-ask-password Do not ask for system passwords\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " -q --quiet Suppress informational messages\n" - " -p --profile=PROFILE Pick security profile for portable service\n" - " --copy=copy|auto|symlink|mixed\n" - " Pick copying or symlinking of resources\n" - " --runtime Attach portable service until next reboot only\n" - " --no-reload Don't reload the system and service manager\n" - " --cat When inspecting include unit and os-release file\n" - " contents\n" - " --enable Immediately enable/disable the portable service\n" - " after attach/detach\n" - " --now Immediately start/stop the portable service after\n" - " attach/before detach\n" - " --no-block Don't block waiting for attach --now to complete\n" - " --extension=PATH Extend the image with an overlay\n" - " --force Skip 'already active' check when attaching or\n" - " detaching an image (with extensions)\n" - " --clean When detaching, also remove configuration, state,\n" - " cache, logs or runtime data of the portable\n" - " service(s)\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - link); + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + (void) table_sync_column_widths(0, verbs, options); + + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("Attach or detach portable services in the local system."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("portablectl", "1"); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_NO_ASK_PASSWORD, - ARG_COPY, - ARG_RUNTIME, - ARG_NO_RELOAD, - ARG_CAT, - ARG_ENABLE, - ARG_NOW, - ARG_NO_BLOCK, - ARG_EXTENSION, - ARG_FORCE, - ARG_CLEAN, - ARG_USER, - ARG_SYSTEM, - }; - - 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 }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "quiet", no_argument, NULL, 'q' }, - { "profile", required_argument, NULL, 'p' }, - { "copy", required_argument, NULL, ARG_COPY }, - { "runtime", no_argument, NULL, ARG_RUNTIME }, - { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, - { "cat", no_argument, NULL, ARG_CAT }, - { "enable", no_argument, NULL, ARG_ENABLE }, - { "now", no_argument, NULL, ARG_NOW }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "extension", required_argument, NULL, ARG_EXTENSION }, - { "force", no_argument, NULL, ARG_FORCE }, - { "clean", no_argument, NULL, ARG_CLEAN }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - {} - }; - - int r, c; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***remaining_args) { assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "hH:M:qp:", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + int r; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + 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 ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress informational messages"): arg_quiet = true; break; - case 'p': - if (streq(optarg, "help")) + OPTION('p', "profile", "PROFILE", + "Pick security profile for portable service"): + if (streq(opts.arg, "help")) return dump_profiles(); - if (!filename_is_valid(optarg)) + if (!filename_is_valid(opts.arg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unit profile name not valid: %s", optarg); + "Unit profile name not valid: %s", opts.arg); - arg_profile = optarg; + arg_profile = opts.arg; break; - case ARG_COPY: - if (streq(optarg, "auto")) + OPTION_LONG("copy", "MODE", + "Pick copying or symlinking of resources " + "(copy, auto, symlink, mixed)"): + if (streq(opts.arg, "auto")) arg_copy_mode = NULL; - else if (STR_IN_SET(optarg, "copy", "symlink", "mixed")) - arg_copy_mode = optarg; - else if (streq(optarg, "help")) { + else if (STR_IN_SET(opts.arg, "copy", "symlink", "mixed")) + arg_copy_mode = opts.arg; + else if (streq(opts.arg, "help")) { puts("auto\n" "copy\n" "symlink\n" @@ -1433,90 +1381,81 @@ static int parse_argv(int argc, char *argv[]) { return 0; } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse --copy= argument: %s", optarg); - + "Failed to parse --copy= argument: %s", opts.arg); break; - case ARG_RUNTIME: + OPTION_LONG("runtime", NULL, + "Attach portable service until next reboot only"): arg_runtime = true; break; - case ARG_NO_RELOAD: + OPTION_LONG("no-reload", NULL, + "Don't reload the system and service manager"): arg_reload = false; break; - case ARG_CAT: + OPTION_LONG("cat", NULL, + "When inspecting include unit and os-release file contents"): arg_cat = true; break; - case ARG_ENABLE: + OPTION_LONG("enable", NULL, + "Immediately enable/disable the portable service after attach/detach"): arg_enable = true; break; - case ARG_NOW: + OPTION_LONG("now", NULL, + "Immediately start/stop the portable service after attach/before detach"): arg_now = true; break; - case ARG_NO_BLOCK: + OPTION_LONG("no-block", NULL, + "Don't block waiting for attach --now to complete"): arg_no_block = true; break; - case ARG_EXTENSION: - r = strv_extend(&arg_extension_images, optarg); + OPTION_LONG("extension", "PATH", + "Extend the image with an overlay"): + r = strv_extend(&arg_extension_images, opts.arg); if (r < 0) return log_oom(); break; - case ARG_FORCE: + OPTION_LONG("force", NULL, + "Skip 'already active' check when attaching or detaching an image (with extensions)"): arg_force = true; break; - case ARG_CLEAN: + OPTION_LONG("clean", NULL, + "When detaching, also remove configuration, state, " + "cache, logs or runtime data of the portable service(s)"): arg_clean = true; break; - case ARG_USER: + OPTION_LONG("user", NULL, /* help= */ NULL): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, /* help= */ NULL): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *remaining_args = option_parser_get_args(&opts); return 1; } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "list", VERB_ANY, 1, VERB_DEFAULT, verb_list_images }, - { "attach", 2, VERB_ANY, 0, verb_attach_image }, - { "detach", 2, VERB_ANY, 0, verb_detach_image }, - { "inspect", 2, VERB_ANY, 0, verb_inspect_image }, - { "is-attached", 2, 2, 0, verb_is_image_attached }, - { "read-only", 2, 3, 0, verb_read_only_image }, - { "remove", 2, VERB_ANY, 0, verb_remove_image }, - { "set-limit", 3, 3, 0, verb_set_limit }, - { "reattach", 2, VERB_ANY, 0, verb_reattach_image }, - {} - }; - + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); From de29e618434ad53fee7e60cfc2e3d186968b21cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 7 May 2026 13:07:52 +0200 Subject: [PATCH 3/7] portablectl: actually allow set-limit with one arg In the man page and in the actual code, the first arg is optional. But the arg limit in the verbs table did not allow only one arg to be specified. Fixes: 61d0578b07b97cbffebfd350bac481274e310d39 --- src/portable/portablectl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 575ab4149a..c70d506346 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -1215,7 +1215,7 @@ static int verb_remove_image(int argc, char *argv[], uintptr_t _data, void *user return 0; } -VERB(verb_set_limit, "set-limit", "[NAME|PATH] LIMIT", 3, 3, 0, +VERB(verb_set_limit, "set-limit", "[NAME|PATH] LIMIT", 2, 3, 0, "Set image or pool size limit (disk quota)"); static int verb_set_limit(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; From a42f1ebb94062a9c11c072b6ca8ff84c509f9445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 5 May 2026 18:23:55 +0200 Subject: [PATCH 4/7] report-cgroup-server: convert to OPTION macros Co-developed-by: Claude Opus 4.7 --- src/report/report-cgroup-server.c | 60 +++++++++++-------------------- 1 file changed, 20 insertions(+), 40 deletions(-) diff --git a/src/report/report-cgroup-server.c b/src/report/report-cgroup-server.c index eef2ec05fc..9f5ac93706 100644 --- a/src/report/report-cgroup-server.c +++ b/src/report/report-cgroup-server.c @@ -1,15 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-varlink.h" #include "alloc-util.h" -#include "ansi-color.h" #include "build.h" +#include "format-table.h" +#include "help-util.h" #include "log.h" #include "main-func.h" -#include "pretty-print.h" +#include "options.h" #include "report-cgroup.h" #include "varlink-io.systemd.Metrics.h" #include "varlink-util.h" @@ -46,63 +45,44 @@ static int vl_server(void) { } static int help(void) { - _cleanup_free_ char *url = NULL; + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd-report-cgroup", "8", &url); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; - printf("%s [OPTIONS...]\n" - "\n%sReport cgroup metrics.%s\n" - "\n%sOptions:%s\n" - " -h --help Show this help\n" - " --version Show package version\n" - "\nSee the %s for details.\n", - program_invocation_short_name, - ansi_highlight(), - ansi_normal(), - ansi_underline(), - ansi_normal(), - url); + help_cmdline("[OPTIONS...]"); + help_abstract("Report cgroup metrics."); + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-report-cgroup", "8"); return 0; } static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c, r; + int r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind < argc) + if (option_parser_get_n_args(&opts) > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); From b49a4daa1b118cdb55bf0389dea09375b2a4e577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 6 May 2026 13:39:25 +0200 Subject: [PATCH 5/7] coredumpctl: convert to OPTION and VERB macros The order of options is changed (to what was present in parse_argv). I don't the order in --help was mostly random, as is the new one, so I didn't try to preserve the old order. Some help strings are reworded/adjusted. Co-developed-by: Claude Opus 4.7 --- src/coredump/coredumpctl.c | 230 ++++++++++++++----------------------- 1 file changed, 85 insertions(+), 145 deletions(-) diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index b6ca0f8b7d..d89da78755 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -14,6 +13,7 @@ #include "sd-messages.h" #include "alloc-util.h" +#include "ansi-color.h" #include "build.h" #include "bus-error.h" #include "bus-locator.h" @@ -29,6 +29,7 @@ #include "format-util.h" #include "fs-util.h" #include "glob-util.h" +#include "help-util.h" #include "image-policy.h" #include "io-util.h" #include "journal-internal.h" @@ -39,12 +40,12 @@ #include "loop-util.h" #include "main-func.h" #include "mount-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" #include "pidref.h" -#include "pretty-print.h" #include "process-util.h" #include "signal-util.h" #include "string-util.h" @@ -177,224 +178,166 @@ static int acquire_journal(sd_journal **ret, char **matches) { } static int help(void) { - _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL; int r; - r = terminal_urlify_man("coredumpctl", "1", &link); + r = verbs_get_help_table(&verbs); if (r < 0) - return log_oom(); + return r; - printf("%1$s [OPTIONS...] COMMAND ...\n\n" - "%5$sList or retrieve coredumps from the journal.%6$s\n" - "\n%3$sCommands:%4$s\n" - " list [MATCHES...] List available coredumps (default)\n" - " info [MATCHES...] Show detailed information about one or more coredumps\n" - " dump [MATCHES...] Print first matching coredump to stdout\n" - " debug [MATCHES...] Start a debugger for the first matching coredump\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version string\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" - " --debugger=DEBUGGER Use the given debugger\n" - " -A --debugger-arguments=ARGS Pass the given arguments to the debugger\n" - " -n INT Show maximum number of rows\n" - " -1 Show information about most recent entry only\n" - " -S --since=DATE Only print coredumps since the date\n" - " -U --until=DATE Only print coredumps until the date\n" - " -r --reverse Show the newest entries first\n" - " -F --field=FIELD List all values a certain field takes\n" - " -o --output=FILE Write output to FILE\n" - " --file=PATH Use journal file\n" - " -D --directory=DIR Use journal files from directory\n\n" - " -q --quiet Do not show info messages and privilege warning\n" - " --all Look at all journal files instead of local ones\n" - " --root=PATH Operate on an alternate filesystem root\n" - " --image=PATH Operate on disk image as filesystem root\n" - " --image-policy=POLICY Specify disk image dissection policy\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(&options); + if (r < 0) + return r; + (void) table_sync_column_widths(0, verbs, options); + + help_cmdline("[OPTIONS…] COMMAND …"); + help_abstract("List or retrieve coredumps from the journal."); + + help_section("Commands"); + r = table_print_or_warn(verbs); + if (r < 0) + return r; + + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("coredumpctl", "1"); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} +VERB_COMMON_HELP_HIDDEN(help); -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_JSON, - ARG_DEBUGGER, - ARG_FILE, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_ALL, - }; - - int c, r; - - 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 }, - { "debugger", required_argument, NULL, ARG_DEBUGGER }, - { "debugger-arguments", required_argument, NULL, 'A' }, - { "output", required_argument, NULL, 'o' }, - { "field", required_argument, NULL, 'F' }, - { "file", required_argument, NULL, ARG_FILE }, - { "directory", required_argument, NULL, 'D' }, - { "reverse", no_argument, NULL, 'r' }, - { "since", required_argument, NULL, 'S' }, - { "until", required_argument, NULL, 'U' }, - { "quiet", no_argument, NULL, 'q' }, - { "json", required_argument, NULL, ARG_JSON }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "all", no_argument, NULL, ARG_ALL }, - {} - }; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); - while ((c = getopt_long(argc, argv, "hA:o:F:1D:rS:U:qn:", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': + + 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 ARG_DEBUGGER: - arg_debugger = optarg; + OPTION_LONG("debugger", "DEBUGGER", "Use the given debugger"): + arg_debugger = opts.arg; break; - case 'A': { + OPTION('A', "debugger-arguments", "…", "Pass the given arguments to the debugger"): { _cleanup_strv_free_ char **l = NULL; - r = strv_split_full(&l, optarg, WHITESPACE, EXTRACT_UNQUOTE); + r = strv_split_full(&l, opts.arg, WHITESPACE, EXTRACT_UNQUOTE); if (r < 0) - return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", optarg); + return log_error_errno(r, "Failed to parse debugger arguments '%s': %m", opts.arg); strv_free_and_replace(arg_debugger_args, l); break; } - case ARG_FILE: - r = glob_extend(&arg_file, optarg, GLOB_NOCHECK); + OPTION_LONG("file", "PATH", "Use journal file"): + r = glob_extend(&arg_file, opts.arg, GLOB_NOCHECK); if (r < 0) return log_error_errno(r, "Failed to add paths: %m"); break; - case 'o': + OPTION('o', "output", "FILE", "Write output to FILE"): if (arg_output) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot set output more than once."); - arg_output = optarg; + arg_output = opts.arg; break; - case 'S': - r = parse_timestamp(optarg, &arg_since); + OPTION('S', "since", "DATE", "Only print coredumps since the date"): + r = parse_timestamp(opts.arg, &arg_since); if (r < 0) - return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg); + return log_error_errno(r, "Failed to parse timestamp '%s': %m", opts.arg); break; - case 'U': - r = parse_timestamp(optarg, &arg_until); + OPTION('U', "until", "DATE", "Only print coredumps until the date"): + r = parse_timestamp(opts.arg, &arg_until); if (r < 0) - return log_error_errno(r, "Failed to parse timestamp '%s': %m", optarg); + return log_error_errno(r, "Failed to parse timestamp '%s': %m", opts.arg); break; - case 'F': + OPTION('F', "field", "FIELD", "List all values a certain field takes"): if (arg_field) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --field/-F more than once."); - arg_field = optarg; + arg_field = opts.arg; break; - case '1': + OPTION_SHORT('1', NULL, "Show information about most recent entry only"): arg_rows_max = 1; arg_reverse = true; break; - case 'n': { + OPTION_SHORT('n', "INT", "Show at most this many rows"): { unsigned n; - r = safe_atou(optarg, &n); + r = safe_atou(opts.arg, &n); if (r < 0 || n < 1) return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), - "Invalid numeric parameter to -n: %s", optarg); + "Invalid numeric parameter to -n: %s", opts.arg); arg_rows_max = n; break; } - case 'D': - arg_directory = optarg; + OPTION('D', "directory", "DIR", "Use journal files from directory"): + arg_directory = opts.arg; break; - case ARG_ROOT: - r = parse_path_argument(optarg, false, &arg_root); + OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): + r = parse_path_argument(opts.arg, false, &arg_root); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, false, &arg_image); + OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"): + r = parse_path_argument(opts.arg, false, &arg_image); if (r < 0) return r; break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"): + r = parse_image_policy_argument(opts.arg, &arg_image_policy); if (r < 0) return r; break; - case 'r': + OPTION('r', "reverse", NULL, "Show the newest entries first"): arg_reverse = true; break; - case 'q': + OPTION('q', "quiet", NULL, "Do not show info messages and privilege warning"): arg_quiet = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case ARG_ALL: + OPTION_LONG("all", NULL, "Look at all journal files instead of local ones"): arg_all = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_since != USEC_INFINITY && arg_until != USEC_INFINITY && @@ -405,6 +348,7 @@ static int parse_argv(int argc, char *argv[]) { if ((!!arg_directory + !!arg_image + !!arg_root) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root=, --image= or -D/--directory=, the combination of these options is not supported."); + *remaining_args = option_parser_get_args(&opts); return 1; } @@ -902,6 +846,10 @@ static int print_entry( return print_info(stdout, j, n_found > 0); } +VERB(verb_dump_list, "list", "[MATCHES…]", VERB_ANY, VERB_ANY, VERB_DEFAULT, + "List available coredumps"); +VERB(verb_dump_list, "info", "[MATCHES…]", VERB_ANY, VERB_ANY, 0, + "Show detailed information about one or more coredumps"); static int verb_dump_list(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; _cleanup_(table_unrefp) Table *t = NULL; @@ -1146,6 +1094,8 @@ error: return r; } +VERB(verb_dump_core, "dump", "[MATCHES…]", VERB_ANY, VERB_ANY, 0, + "Print first matching coredump to stdout"); static int verb_dump_core(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -1182,6 +1132,10 @@ static int verb_dump_core(int argc, char *argv[], uintptr_t _data, void *userdat return 0; } +VERB(verb_run_debug, "debug", "[MATCHES…]", VERB_ANY, VERB_ANY, 0, + "Start a debugger for the first matching coredump"); +VERB(verb_run_debug, "gdb", "[MATCHES…]", VERB_ANY, VERB_ANY, 0, + /* help= */ NULL); static int verb_run_debug(int argc, char *argv[], uintptr_t _data, void *userdata) { static const struct sigaction sa = { .sa_sigaction = sigterm_process_group_handler, @@ -1372,30 +1326,16 @@ static int check_units_active(void) { return c; } -static int coredumpctl_main(int argc, char *argv[]) { - - static const Verb verbs[] = { - { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT, verb_dump_list }, - { "info", VERB_ANY, VERB_ANY, 0, verb_dump_list }, - { "dump", VERB_ANY, VERB_ANY, 0, verb_dump_core }, - { "debug", VERB_ANY, VERB_ANY, 0, verb_run_debug }, - { "gdb", VERB_ANY, VERB_ANY, 0, verb_run_debug }, - { "help", VERB_ANY, 1, 0, verb_help }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - static int run(int argc, char *argv[]) { _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(umount_and_freep) char *mounted_dir = NULL; + char **args = NULL; int r, units_active; setlocale(LC_ALL, ""); log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -1425,7 +1365,7 @@ static int run(int argc, char *argv[]) { return log_oom(); } - r = coredumpctl_main(argc, argv); + r = dispatch_verb_with_args(args, NULL); if (units_active > 0) printf("%s-- Notice: %d systemd-coredump@.service %s, output may be incomplete.%s\n", From 561f3a65da050dedaf96854c460bbe97258e3d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 6 May 2026 13:59:44 +0200 Subject: [PATCH 6/7] mount: convert to OPTION macros Previously, we'd show a partial synopsis for systemd-mount in --help for systemd-umount. I don't think it makes sense to do that. So now the --help for systemd-umount is separate, with just its syntax and a new blurb. "transiently" is dropped from the description. Mount points generally are transient, so no need to say that. (E.g. the man page for mount just says "attach" and "detach".) Co-developed-by: Claude Opus 4.7 --- src/mount/mount-tool.c | 300 +++++++++++++++-------------------------- 1 file changed, 106 insertions(+), 194 deletions(-) diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index f1c4c90d76..80846c1c2a 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -1,10 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "sd-bus.h" #include "sd-device.h" +#include "ansi-color.h" #include "argv-util.h" #include "build.h" #include "bus-error.h" @@ -20,15 +19,16 @@ #include "format-table.h" #include "format-util.h" #include "fstab-util.h" +#include "help-util.h" #include "libmount-util.h" #include "main-func.h" #include "mountpoint-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" #include "polkit-agent.h" -#include "pretty-print.h" #include "process-util.h" #include "runtime-scope.h" #include "stat-util.h" @@ -108,296 +108,206 @@ static int parse_where(const char *input, char **ret_where) { return 0; } -static int help(void) { - _cleanup_free_ char *link = NULL; +static int help(char *argv[]) { + _cleanup_(table_unrefp) Table *options = NULL; int r; - r = terminal_urlify_man("systemd-mount", "1", &link); + r = option_parser_get_help_table(&options); if (r < 0) - return log_oom(); + return r; - printf("systemd-mount [OPTIONS...] WHAT [WHERE]\n" - "systemd-mount [OPTIONS...] --tmpfs [NAME] WHERE\n" - "systemd-mount [OPTIONS...] --list\n" - "%1$s [OPTIONS...] %7$sWHAT|WHERE...\n" - "\n%5$sEstablish a mount or auto-mount point transiently.%6$s\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-block Do not wait until operation finished\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers\n" - " -l --full Do not ellipsize output\n" - " --no-ask-password Do not prompt for password\n" - " -q --quiet Suppress information messages during runtime\n" - " --json=pretty|short|off Generate JSON output\n" - " --user Run as user unit\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --discover Discover mount device metadata\n" - " -t --type=TYPE File system type\n" - " -o --options=OPTIONS Mount options\n" - " --owner=USER Add uid= and gid= options for USER\n" - " --fsck=no Don't run file system check before mount\n" - " --description=TEXT Description for unit\n" - " -p --property=NAME=VALUE Set mount unit property\n" - " --automount=BOOL Create an automount point\n" - " -A Same as --automount=yes\n" - " --timeout-idle-sec=SEC Specify automount idle timeout\n" - " --automount-property=NAME=VALUE\n" - " Set automount unit property\n" - " --bind-device Bind automount unit to device\n" - " --list List mountable block devices\n" - " -u --umount Unmount mount points\n" - " -G --collect Unload unit after it stopped, even when failed\n" - " -T --tmpfs Create a new tmpfs on the mount point\n" - " --canonicalize=BOOL Controls whether to canonicalize path before\n" - " operation\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - streq(program_invocation_short_name, "systemd-umount") ? "" : "--umount "); + if (invoked_as(argv, "systemd-umount")) { + help_cmdline("[OPTIONS…] WHAT|WHERE…"); + help_abstract("Unmount one or more mount points."); + } else { + help_cmdline("[OPTIONS…] WHAT [WHERE]"); + help_cmdline("[OPTIONS…] --tmpfs [NAME] WHERE"); + help_cmdline("[OPTIONS…] --list"); + help_cmdline("[OPTIONS…] --umount WHAT|WHERE…"); + help_abstract("Establish a mount or auto-mount point."); + } + help_section("Options"); + r = table_print_or_warn(options); + if (r < 0) + return r; + + help_man_page_reference("systemd-mount", "1"); return 0; } -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_BLOCK, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_NO_ASK_PASSWORD, - ARG_USER, - ARG_SYSTEM, - ARG_DISCOVER, - ARG_MOUNT_TYPE, - ARG_MOUNT_OPTIONS, - ARG_OWNER, - ARG_FSCK, - ARG_DESCRIPTION, - ARG_TIMEOUT_IDLE, - ARG_AUTOMOUNT, - ARG_AUTOMOUNT_PROPERTY, - ARG_BIND_DEVICE, - ARG_LIST, - ARG_JSON, - ARG_CANONICALIZE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "full", no_argument, NULL, 'l' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "quiet", no_argument, NULL, 'q' }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "discover", no_argument, NULL, ARG_DISCOVER }, - { "type", required_argument, NULL, 't' }, - { "options", required_argument, NULL, 'o' }, - { "owner", required_argument, NULL, ARG_OWNER }, - { "fsck", required_argument, NULL, ARG_FSCK }, - { "description", required_argument, NULL, ARG_DESCRIPTION }, - { "property", required_argument, NULL, 'p' }, - { "automount", required_argument, NULL, ARG_AUTOMOUNT }, - { "timeout-idle-sec", required_argument, NULL, ARG_TIMEOUT_IDLE }, - { "automount-property", required_argument, NULL, ARG_AUTOMOUNT_PROPERTY }, - { "bind-device", no_argument, NULL, ARG_BIND_DEVICE }, - { "list", no_argument, NULL, ARG_LIST }, - { "umount", no_argument, NULL, 'u' }, - { "unmount", no_argument, NULL, 'u' }, /* Compat spelling */ - { "collect", no_argument, NULL, 'G' }, - { "tmpfs", no_argument, NULL, 'T' }, - { "json", required_argument, NULL, ARG_JSON }, - { "canonicalize", required_argument, NULL, ARG_CANONICALIZE }, - {}, - }; - - int r, c; +static int parse_argv(int argc, char *argv[], char ***remaining_args) { + int r; assert(argc >= 0); assert(argv); + assert(remaining_args); if (invoked_as(argv, "systemd-umount")) arg_action = ACTION_UMOUNT; - while ((c = getopt_long(argc, argv, "hqH:M:t:o:p:AuGlT", options, NULL)) >= 0) + OptionParser opts = { argc, argv }; + FOREACH_OPTION_OR_RETURN(c, &opts) switch (c) { - case 'h': - return help(); + OPTION_COMMON_HELP: + return help(argv); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_BLOCK: + OPTION_LONG("no-block", NULL, "Do not wait until operation finished"): arg_no_block = true; break; - 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 'l': + OPTION('l', "full", NULL, "Do not ellipsize output"): arg_full = true; break; - case ARG_NO_ASK_PASSWORD: + OPTION_COMMON_NO_ASK_PASSWORD: arg_ask_password = false; break; - case 'q': + OPTION('q', "quiet", NULL, "Suppress information messages during runtime"): arg_quiet = true; break; - case ARG_USER: + OPTION_LONG("user", NULL, "Run as user unit"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, /* help= */ NULL): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case 'H': + OPTION_COMMON_HOST: arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; + arg_host = opts.arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_host, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); if (r < 0) return r; break; - case ARG_DISCOVER: + OPTION_LONG("discover", NULL, "Discover mount device metadata"): arg_discover = true; break; - case 't': - r = free_and_strdup_warn(&arg_mount_type, optarg); + OPTION('t', "type", "TYPE", "File system type"): + r = free_and_strdup_warn(&arg_mount_type, opts.arg); if (r < 0) return r; break; - case 'o': - r = free_and_strdup_warn(&arg_mount_options, optarg); + OPTION('o', "options", "OPTIONS", "Mount options"): + r = free_and_strdup_warn(&arg_mount_options, opts.arg); if (r < 0) return r; break; - case ARG_OWNER: { - const char *user = optarg; + OPTION_LONG("owner", "USER", "Add uid= and gid= options for USER"): { + const char *user = opts.arg; r = get_user_creds(&user, &arg_uid, &arg_gid, NULL, NULL, 0); if (r < 0) return log_error_errno(r, r == -EBADMSG ? "UID or GID of user %s are invalid." : "Cannot use \"%s\" as owner: %m", - optarg); + opts.arg); break; } - case ARG_FSCK: - r = parse_boolean_argument("--fsck=", optarg, &arg_fsck); + OPTION_LONG("fsck", "BOOL", "Run a file system check before mount"): + r = parse_boolean_argument("--fsck=", opts.arg, &arg_fsck); if (r < 0) return r; break; - case ARG_DESCRIPTION: - r = free_and_strdup_warn(&arg_description, optarg); + OPTION_LONG("description", "TEXT", "Description for unit"): + r = free_and_strdup_warn(&arg_description, opts.arg); if (r < 0) return r; break; - case 'p': - if (strv_extend(&arg_property, optarg) < 0) + OPTION('p', "property", "NAME=VALUE", "Set mount unit property"): + if (strv_extend(&arg_property, opts.arg) < 0) return log_oom(); - break; - case 'A': + OPTION_SHORT('A', NULL, "Same as --automount=yes"): arg_action = ACTION_AUTOMOUNT; break; - case ARG_AUTOMOUNT: - r = parse_boolean_argument("--automount=", optarg, NULL); + OPTION_LONG("automount", "BOOL", "Create an automount point"): + r = parse_boolean_argument("--automount=", opts.arg, NULL); if (r < 0) return r; arg_action = r ? ACTION_AUTOMOUNT : ACTION_MOUNT; break; - case ARG_TIMEOUT_IDLE: - r = parse_sec(optarg, &arg_timeout_idle); + OPTION_LONG("timeout-idle-sec", "SEC", "Specify automount idle timeout"): + r = parse_sec(opts.arg, &arg_timeout_idle); if (r < 0) - return log_error_errno(r, "Failed to parse timeout: %s", optarg); + return log_error_errno(r, "Failed to parse timeout: %s", opts.arg); arg_timeout_idle_set = true; break; - case ARG_AUTOMOUNT_PROPERTY: - if (strv_extend(&arg_automount_property, optarg) < 0) + OPTION_LONG("automount-property", "NAME=VALUE", "Set automount unit property"): + if (strv_extend(&arg_automount_property, opts.arg) < 0) return log_oom(); - break; - case ARG_BIND_DEVICE: + OPTION_LONG("bind-device", NULL, "Bind automount unit to device"): arg_bind_device = true; break; - case ARG_LIST: + OPTION_LONG("list", NULL, "List mountable block devices"): arg_action = ACTION_LIST; break; - case 'u': + OPTION('u', "umount", NULL, "Unmount mount points"): {} + OPTION_LONG("unmount", NULL, /* help= */ NULL): /* compat spelling */ arg_action = ACTION_UMOUNT; break; - case 'G': + OPTION('G', "collect", NULL, "Unload unit after it stopped, even when failed"): arg_aggressive_gc = true; break; - case 'T': + OPTION('T', "tmpfs", NULL, "Create a new tmpfs on the mount point"): arg_tmpfs = true; break; - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); if (r <= 0) return r; - break; - case ARG_CANONICALIZE: - r = parse_boolean_argument("--canonicalize=", optarg, &arg_canonicalize); + OPTION_LONG("canonicalize", "BOOL", + "Controls whether to canonicalize path before operation"): + r = parse_boolean_argument("--canonicalize=", opts.arg, &arg_canonicalize); if (r < 0) return r; - 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); + if (arg_runtime_scope == RUNTIME_SCOPE_USER) { arg_ask_password = false; @@ -407,7 +317,7 @@ static int parse_argv(int argc, char *argv[]) { } if (arg_action == ACTION_LIST) { - if (optind < argc) + if (n_args > 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments."); @@ -415,22 +325,22 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Listing devices only supported locally."); } else if (arg_action == ACTION_UMOUNT) { - if (optind >= argc) + if (n_args == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument required."); if (arg_transport != BUS_TRANSPORT_LOCAL || !arg_canonicalize) - for (int i = optind; i < argc; i++) - if (!path_is_absolute(argv[i])) + STRV_FOREACH(a, args) + if (!path_is_absolute(*a)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path must be absolute when operating remotely or when canonicalization is turned off: %s", - argv[i]); + *a); } else { - if (optind >= argc) + if (n_args == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument required."); - if (argc > optind+2) + if (n_args > 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "More than two arguments are not allowed."); @@ -439,16 +349,16 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--discover cannot be used in conjunction with --tmpfs."); - if (argc <= optind+1) { + if (n_args == 1) { arg_mount_what = strdup("tmpfs"); if (!arg_mount_what) return log_oom(); - r = parse_where(argv[optind], &arg_mount_where); + r = parse_where(args[0], &arg_mount_where); if (r < 0) return r; } else { - arg_mount_what = strdup(argv[optind]); + arg_mount_what = strdup(args[0]); if (!arg_mount_what) return log_oom(); } @@ -463,12 +373,12 @@ static int parse_argv(int argc, char *argv[]) { arg_mount_type); } else { if (arg_mount_type && !fstype_is_blockdev_backed(arg_mount_type)) { - arg_mount_what = strdup(argv[optind]); + arg_mount_what = strdup(args[0]); if (!arg_mount_what) return log_oom(); } else { _cleanup_free_ char *u = NULL; - const char *p = argv[optind]; + const char *p = args[0]; if (arg_canonicalize) { u = fstab_node_to_udev_node(p); @@ -494,8 +404,8 @@ static int parse_argv(int argc, char *argv[]) { } } - if (argc > optind+1) { - r = parse_where(argv[optind+1], &arg_mount_where); + if (n_args >= 2) { + r = parse_where(args[1], &arg_mount_where); if (r < 0) return r; } else if (!arg_tmpfs) @@ -524,6 +434,7 @@ static int parse_argv(int argc, char *argv[]) { } } + *remaining_args = args; return 1; } @@ -1077,18 +988,18 @@ static int umount_loop(sd_bus *bus, const char *backing_file) { return umount_by_device(bus, dev); } -static int action_umount(sd_bus *bus, int argc, char **argv) { +static int action_umount(sd_bus *bus, char **args) { int r, ret = 0; assert(bus); - assert(argv); - assert(argc > optind); + assert(args); + assert(!strv_isempty(args)); if (arg_transport != BUS_TRANSPORT_LOCAL || !arg_canonicalize) { - for (int i = optind; i < argc; i++) { + STRV_FOREACH(arg, args) { _cleanup_free_ char *p = NULL; - r = path_simplify_alloc(argv[i], &p); + r = path_simplify_alloc(*arg, &p); if (r < 0) return r; @@ -1097,10 +1008,10 @@ static int action_umount(sd_bus *bus, int argc, char **argv) { return ret; } - for (int i = optind; i < argc; i++) { + STRV_FOREACH(arg, args) { _cleanup_free_ char *u = NULL, *p = NULL; - u = fstab_node_to_udev_node(argv[i]); + u = fstab_node_to_udev_node(*arg); if (!u) return log_oom(); @@ -1113,7 +1024,7 @@ static int action_umount(sd_bus *bus, int argc, char **argv) { struct stat st; if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Can't stat '%s' (from %s): %m", p, argv[i]); + return log_error_errno(errno, "Can't stat '%s' (from %s): %m", p, *arg); r = is_mount_point_at(fd, /* path= */ NULL, /* flags= */ 0); fd = safe_close(fd); /* before continuing make sure the dir is not keeping anything busy */ @@ -1135,7 +1046,7 @@ static int action_umount(sd_bus *bus, int argc, char **argv) { else r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown file type for unmounting: %s (from %s)", - p, argv[i]); + p, *arg); RET_GATHER(ret, r); } } @@ -1552,11 +1463,12 @@ static int list_devices(void) { static int run(int argc, char* argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + char **args = NULL; int r; log_setup(); - r = parse_argv(argc, argv); + r = parse_argv(argc, argv, &args); if (r <= 0) return r; @@ -1570,7 +1482,7 @@ static int run(int argc, char* argv[]) { (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); if (arg_action == ACTION_UMOUNT) - return action_umount(bus, argc, argv); + return action_umount(bus, args); if ((!arg_mount_type || fstype_is_blockdev_backed(arg_mount_type)) && !path_is_normalized(arg_mount_what)) From 355f2bec79c40e276a8dc3c089875399a9d3a123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 6 May 2026 14:24:37 +0200 Subject: [PATCH 7/7] mount: stop showing mount options for systemd-unmount This only serves to confuse the user. --- src/mount/mount-tool.c | 80 +++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index 80846c1c2a..636930d277 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -109,10 +109,10 @@ static int parse_where(const char *input, char **ret_where) { } static int help(char *argv[]) { - _cleanup_(table_unrefp) Table *options = NULL; + _cleanup_(table_unrefp) Table *options_common = NULL, *options_mount = NULL; int r; - r = option_parser_get_help_table(&options); + r = option_parser_get_help_table(&options_common); if (r < 0) return r; @@ -125,13 +125,25 @@ static int help(char *argv[]) { help_cmdline("[OPTIONS…] --list"); help_cmdline("[OPTIONS…] --umount WHAT|WHERE…"); help_abstract("Establish a mount or auto-mount point."); + + r = option_parser_get_help_table_group("Mount options", &options_mount); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, options_common, options_mount); } help_section("Options"); - r = table_print_or_warn(options); + r = table_print_or_warn(options_common); if (r < 0) return r; + if (options_mount) { + r = table_print_or_warn(options_mount); + if (r < 0) + return r; + } + help_man_page_reference("systemd-mount", "1"); return 0; } @@ -157,6 +169,32 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION_COMMON_VERSION: return version(); + OPTION_LONG("user", NULL, "Run as user unit"): + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + + OPTION_LONG("system", NULL, /* help= */ NULL): + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + OPTION_COMMON_HOST: + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = opts.arg; + break; + + OPTION_COMMON_MACHINE: + r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); + if (r < 0) + return r; + break; + + OPTION_LONG("canonicalize", "BOOL", + "Whether to canonicalize path before operation"): + r = parse_boolean_argument("--canonicalize=", opts.arg, &arg_canonicalize); + if (r < 0) + return r; + break; + OPTION_LONG("no-block", NULL, "Do not wait until operation finished"): arg_no_block = true; break; @@ -177,29 +215,18 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { arg_ask_password = false; break; - OPTION('q', "quiet", NULL, "Suppress information messages during runtime"): + OPTION('q', "quiet", NULL, "Suppress informational messages during runtime"): arg_quiet = true; break; - OPTION_LONG("user", NULL, "Run as user unit"): - arg_runtime_scope = RUNTIME_SCOPE_USER; - break; - - OPTION_LONG("system", NULL, /* help= */ NULL): - arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; - break; - - OPTION_COMMON_HOST: - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = opts.arg; - break; - - OPTION_COMMON_MACHINE: - r = parse_machine_argument(opts.arg, &arg_host, &arg_transport); - if (r < 0) + OPTION_COMMON_JSON: + r = parse_json_argument(opts.arg, &arg_json_format_flags); + if (r <= 0) return r; break; + OPTION_GROUP("Mount options"): {} + OPTION_LONG("discover", NULL, "Discover mount device metadata"): arg_discover = true; break; @@ -290,19 +317,6 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { OPTION('T', "tmpfs", NULL, "Create a new tmpfs on the mount point"): arg_tmpfs = true; break; - - OPTION_COMMON_JSON: - r = parse_json_argument(opts.arg, &arg_json_format_flags); - if (r <= 0) - return r; - break; - - OPTION_LONG("canonicalize", "BOOL", - "Controls whether to canonicalize path before operation"): - r = parse_boolean_argument("--canonicalize=", opts.arg, &arg_canonicalize); - if (r < 0) - return r; - break; } char **args = option_parser_get_args(&opts);