]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysupdate: convert to the new option and verb parsers 41797/head
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 23 Apr 2026 20:41:04 +0000 (22:41 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Fri, 24 Apr 2026 07:25:27 +0000 (09:25 +0200)
Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>
src/sysupdate/sysupdate.c

index 254b6bc121e3332d4a5126cc6ac5016505770140..a4bf835108ab605a92470e488d9926ccc0d2782d 100644 (file)
@@ -1,6 +1,5 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include <getopt.h>
 #include <unistd.h>
 
 #include "sd-daemon.h"
@@ -16,6 +15,7 @@
 #include "loop-util.h"
 #include "main-func.h"
 #include "mount-util.h"
+#include "options.h"
 #include "os-util.h"
 #include "pager.h"
 #include "parse-argument.h"
@@ -1275,6 +1275,8 @@ static int process_image(
         return 0;
 }
 
+VERB(verb_list, "list", "[VERSION]", VERB_ANY, 2, VERB_DEFAULT,
+     "Show installed and available versions");
 static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
         _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
@@ -1343,6 +1345,8 @@ static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
         }
 }
 
+VERB(verb_features, "features", "[FEATURE]", VERB_ANY, 2, 0,
+     "Show optional features");
 static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
         _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
@@ -1477,6 +1481,8 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata
         return 0;
 }
 
+VERB_NOARG(verb_check_new, "check-new",
+           "Check if there's a new version available");
 static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
         _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
@@ -1590,6 +1596,8 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag
         return 0;
 }
 
+VERB(verb_update, "update", "[VERSION]", VERB_ANY, 2, 0,
+     "Install new version now");
 static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata) {
         UpdateActionFlags flags = UPDATE_ACTION_INSTALL;
 
@@ -1599,10 +1607,14 @@ static int verb_update(int argc, char *argv[], uintptr_t _data, void *userdata)
         return verb_update_impl(argc, argv, flags);
 }
 
+VERB(verb_acquire, "acquire", "[VERSION]", VERB_ANY, 2, 0,
+     "Acquire (download) new version now");
 static int verb_acquire(int argc, char *argv[], uintptr_t _data, void *userdata) {
         return verb_update_impl(argc, argv, UPDATE_ACTION_ACQUIRE);
 }
 
+VERB_NOARG(verb_vacuum, "vacuum",
+           "Make room, by deleting old versions");
 static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
         _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
@@ -1626,6 +1638,10 @@ static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata)
         return context_vacuum(context, 0, NULL);
 }
 
+VERB(verb_pending_or_reboot, "pending", NULL, 1, 1, 0,
+     "Report whether a newer version is installed than currently booted");
+VERB(verb_pending_or_reboot, "reboot", NULL, 1, 1, 0,
+     "Reboot if a newer version is installed than booted");
 static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_(context_freep) Context* context = NULL;
         _cleanup_free_ char *booted_version = NULL;
@@ -1700,6 +1716,8 @@ static int component_name_valid(const char *c) {
         return filename_is_valid(j);
 }
 
+VERB_NOARG(verb_components, "components",
+           "Show list of components");
 static int verb_components(int argc, char *argv[], uintptr_t _data, void *userdata) {
         _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
         _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
@@ -1792,178 +1810,149 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda
 
 static int help(void) {
         _cleanup_free_ char *link = NULL;
+        _cleanup_(table_unrefp) Table *common_options = NULL, *options = NULL, *verbs = NULL;
         int r;
 
         r = terminal_urlify_man("systemd-sysupdate", "8", &link);
         if (r < 0)
                 return log_oom();
 
-        printf("%1$s [OPTIONS...] [VERSION]\n"
-               "\n%5$sUpdate OS images.%6$s\n"
-               "\n%3$sCommands:%4$s\n"
-               "  list [VERSION]          Show installed and available versions\n"
-               "  features [FEATURE]      Show optional features\n"
-               "  check-new               Check if there's a new version available\n"
-               "  update [VERSION]        Install new version now\n"
-               "  acquire [VERSION]       Acquire (download) new version now\n"
-               "  vacuum                  Make room, by deleting old versions\n"
-               "  pending                 Report whether a newer version is installed than\n"
-               "                          currently booted\n"
-               "  reboot                  Reboot if a newer version is installed than booted\n"
-               "  components              Show list of components\n"
-               "  -h --help               Show this help\n"
-               "     --version            Show package version\n"
-               "\n%3$sOptions:%4$s\n"
-               "  -C --component=NAME     Select component to update\n"
-               "     --definitions=DIR    Find transfer definitions in specified directory\n"
-               "     --root=PATH          Operate on an alternate filesystem root\n"
-               "     --image=PATH         Operate on disk image as filesystem root\n"
-               "     --image-policy=POLICY\n"
-               "                          Specify disk image dissection policy\n"
-               "     --transfer-source=PATH\n"
-               "                          Specify the directory to transfer sources from\n"
-               "  -m --instances-max=INT  How many instances to maintain\n"
-               "     --sync=BOOL          Controls whether to sync data to disk\n"
-               "     --verify=BOOL        Force signature verification on or off\n"
-               "     --reboot             Reboot after updating to newer version\n"
-               "     --offline            Do not fetch metadata from the network\n"
-               "     --no-pager           Do not pipe output into a pager\n"
-               "     --no-legend          Do not show the headers and footers\n"
-               "     --json=pretty|short|off\n"
-               "                          Generate JSON output\n"
-               "\nSee the %2$s for details.\n",
+        r = verbs_get_help_table(&verbs);
+        if (r < 0)
+                return r;
+
+        r = option_parser_get_help_table(&common_options);
+        if (r < 0)
+                return r;
+
+        r = option_parser_get_help_table_group("Options", &options);
+        if (r < 0)
+                return r;
+
+        (void) table_sync_column_widths(0, verbs, common_options, options);
+
+        printf("%s [OPTIONS...] [VERSION]\n"
+               "\n%sUpdate OS images.%s\n"
+               "\n%sCommands:%s\n",
                program_invocation_short_name,
-               link,
-               ansi_underline(),
-               ansi_normal(),
-               ansi_highlight(),
-               ansi_normal());
+               ansi_highlight(), ansi_normal(),
+               ansi_underline(), ansi_normal());
 
-        return 0;
-}
+        r = table_print_or_warn(verbs);
+        if (r < 0)
+                return r;
 
-static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        return help();
+        r = table_print_or_warn(common_options);
+        if (r < 0)
+                return r;
+
+        printf("\n%sOptions:%s\n", ansi_underline(), ansi_normal());
+        r = table_print_or_warn(options);
+        if (r < 0)
+                return r;
+
+        printf("\nSee the %s for details.\n", link);
+        return 0;
 }
 
-static int parse_argv(int argc, char *argv[]) {
-        enum {
-                ARG_VERSION = 0x100,
-                ARG_NO_PAGER,
-                ARG_NO_LEGEND,
-                ARG_SYNC,
-                ARG_DEFINITIONS,
-                ARG_JSON,
-                ARG_ROOT,
-                ARG_IMAGE,
-                ARG_IMAGE_POLICY,
-                ARG_REBOOT,
-                ARG_VERIFY,
-                ARG_OFFLINE,
-                ARG_TRANSFER_SOURCE,
-        };
-
-        static const struct option options[] = {
-                { "help",              no_argument,       NULL, 'h'                   },
-                { "version",           no_argument,       NULL, ARG_VERSION           },
-                { "component",         required_argument, NULL, 'C'                   },
-                { "definitions",       required_argument, NULL, ARG_DEFINITIONS       },
-                { "root",              required_argument, NULL, ARG_ROOT              },
-                { "image",             required_argument, NULL, ARG_IMAGE             },
-                { "image-policy",      required_argument, NULL, ARG_IMAGE_POLICY      },
-                { "transfer-source",   required_argument, NULL, ARG_TRANSFER_SOURCE   },
-                { "instances-max",     required_argument, NULL, 'm'                   },
-                { "sync",              required_argument, NULL, ARG_SYNC              },
-                { "verify",            required_argument, NULL, ARG_VERIFY            },
-                { "reboot",            no_argument,       NULL, ARG_REBOOT            },
-                { "offline",           no_argument,       NULL, ARG_OFFLINE           },
-                { "no-pager",          no_argument,       NULL, ARG_NO_PAGER          },
-                { "no-legend",         no_argument,       NULL, ARG_NO_LEGEND         },
-                { "json",              required_argument, NULL, ARG_JSON              },
-                {}
-        };
-
-        int c, r;
+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, "hm:C:", options, NULL)) >= 0) {
+        OptionParser state = { argc, argv };
+        const char *arg;
+        int r;
 
+        FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c)
                 switch (c) {
 
-                case 'h':
+                OPTION_COMMON_HELP:
                         return help();
 
-                case ARG_VERSION:
+                OPTION_COMMON_VERSION:
                         return version();
 
-                case 'C':
-                        if (isempty(optarg)) {
+                OPTION_GROUP("Options"):
+                        break;
+
+                OPTION('C', "component", "NAME",
+                       "Select component to update"):
+                        if (isempty(arg)) {
                                 arg_component = mfree(arg_component);
                                 break;
                         }
 
-                        r = component_name_valid(optarg);
+                        r = component_name_valid(arg);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to determine if component name is valid: %m");
                         if (r == 0)
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", optarg);
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", arg);
 
-                        r = free_and_strdup_warn(&arg_component, optarg);
+                        r = free_and_strdup_warn(&arg_component, arg);
                         if (r < 0)
                                 return r;
 
                         break;
 
-                case ARG_DEFINITIONS:
-                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_definitions);
+                OPTION_LONG("definitions", "DIR",
+                            "Find transfer definitions in specified directory"):
+                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_definitions);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_ROOT:
-                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
+                OPTION_LONG("root", "PATH",
+                            "Operate on an alternate filesystem root"):
+                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_IMAGE:
-                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
+                OPTION_LONG("image", "PATH",
+                            "Operate on disk image as filesystem root"):
+                        r = parse_path_argument(arg, /* suppress_root= */ 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(arg, &arg_image_policy);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_TRANSFER_SOURCE:
-                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_transfer_source);
+                OPTION_LONG("transfer-source", "PATH",
+                            "Specify the directory to transfer sources from"):
+                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_transfer_source);
                         if (r < 0)
                                 return r;
 
                         break;
 
-                case 'm':
-                        r = safe_atou64(optarg, &arg_instances_max);
+                OPTION('m', "instances-max", "INT",
+                       "How many instances to maintain"):
+                        r = safe_atou64(arg, &arg_instances_max);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", optarg);
+                                return log_error_errno(r, "Failed to parse --instances-max= parameter: %s", arg);
 
                         break;
 
-                case ARG_SYNC:
-                        r = parse_boolean_argument("--sync=", optarg, &arg_sync);
+                OPTION_LONG("sync", "BOOL",
+                            "Controls whether to sync data to disk"):
+                        r = parse_boolean_argument("--sync=", arg, &arg_sync);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_VERIFY: {
+                OPTION_LONG("verify", "BOOL",
+                            "Force signature verification on or off"): {
                         bool b;
 
-                        r = parse_boolean_argument("--verify=", optarg, &b);
+                        r = parse_boolean_argument("--verify=", arg, &b);
                         if (r < 0)
                                 return r;
 
@@ -1971,36 +1960,31 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
                 }
 
-                case ARG_REBOOT:
+                OPTION_LONG("reboot", NULL,
+                            "Reboot after updating to newer version"):
                         arg_reboot = true;
                         break;
 
-                case ARG_OFFLINE:
+                OPTION_LONG("offline", NULL,
+                            "Do not fetch metadata from the network"):
                         arg_offline = 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 ARG_JSON:
-                        r = parse_json_argument(optarg, &arg_json_format_flags);
+                OPTION_COMMON_JSON:
+                        r = parse_json_argument(arg, &arg_json_format_flags);
                         if (r <= 0)
                                 return r;
 
                         break;
-
-                case '?':
-                        return -EINVAL;
-
-                default:
-                        assert_not_reached();
                 }
-        }
 
         if (arg_image && arg_root)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
@@ -2011,38 +1995,21 @@ static int parse_argv(int argc, char *argv[]) {
         if (arg_definitions && arg_component)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined.");
 
+        *remaining_args = option_parser_get_args(&state);
         return 1;
 }
 
-static int sysupdate_main(int argc, char *argv[]) {
-
-        static const Verb verbs[] = {
-                { "list",       VERB_ANY, 2, VERB_DEFAULT, verb_list              },
-                { "components", VERB_ANY, 1, 0,            verb_components        },
-                { "features",   VERB_ANY, 2, 0,            verb_features          },
-                { "check-new",  VERB_ANY, 1, 0,            verb_check_new         },
-                { "update",     VERB_ANY, 2, 0,            verb_update            },
-                { "acquire",    VERB_ANY, 2, 0,            verb_acquire           },
-                { "vacuum",     VERB_ANY, 1, 0,            verb_vacuum            },
-                { "reboot",     1,        1, 0,            verb_pending_or_reboot },
-                { "pending",    1,        1, 0,            verb_pending_or_reboot },
-                { "help",       VERB_ANY, 1, 0,            verb_help              },
-                {}
-        };
-
-        return dispatch_verb(argc, argv, verbs, NULL);
-}
-
 static int run(int argc, char *argv[]) {
         int r;
 
         log_setup();
 
-        r = parse_argv(argc, argv);
+        char **args = NULL;
+        r = parse_argv(argc, argv, &args);
         if (r <= 0)
                 return r;
 
-        return sysupdate_main(argc, argv);
+        return dispatch_verb_with_args(args, NULL);
 }
 
 DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);