]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
firstboot: convert to the new option parser
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Tue, 14 Apr 2026 14:27:35 +0000 (16:27 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 16 Apr 2026 14:55:14 +0000 (16:55 +0200)
The descriptions for boolean options are adjusted to work better
with BOOL as the metavar.

Description of --copy is fixed, fixup for
f649325ba73a7165a14c1f1134b30b12a96d3718.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
src/firstboot/firstboot.c

index 4720af570746b316a8ad9a3908a58f523794d89e..3cfe6ed7f2523b7fe693d3de1d213172b400e66b 100644 (file)
@@ -1,7 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <fcntl.h>
-#include <getopt.h>
 #include <unistd.h>
 
 #include "sd-bus.h"
@@ -24,6 +23,7 @@
 #include "errno-util.h"
 #include "fd-util.h"
 #include "fileio.h"
+#include "format-table.h"
 #include "fs-util.h"
 #include "glyph-util.h"
 #include "hostname-util.h"
@@ -38,6 +38,7 @@
 #include "main-func.h"
 #include "memory-util.h"
 #include "mount-util.h"
+#include "options.h"
 #include "os-util.h"
 #include "parse-argument.h"
 #include "parse-util.h"
@@ -1240,380 +1241,254 @@ static int process_reset(int rfd) {
 
 static int help(void) {
         _cleanup_free_ char *link = NULL;
+        _cleanup_(table_unrefp) Table *options = NULL;
         int r;
 
         r = terminal_urlify_man("systemd-firstboot", "1", &link);
         if (r < 0)
                 return log_oom();
 
-        printf("%1$s [OPTIONS...]\n"
-               "\n%3$sConfigures basic settings of the system.%4$s\n\n"
-               "  -h --help                       Show this help\n"
-               "     --version                    Show package version\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"
-               "     --locale=LOCALE              Set primary locale (LANG=)\n"
-               "     --locale-messages=LOCALE     Set message locale (LC_MESSAGES=)\n"
-               "     --keymap=KEYMAP              Set keymap\n"
-               "     --timezone=TIMEZONE          Set timezone\n"
-               "     --hostname=NAME              Set hostname\n"
-               "     --setup-machine-id           Set a random machine ID\n"
-               "     --machine-id=ID              Set specified machine ID\n"
-               "     --root-password=PASSWORD     Set root password from plaintext password\n"
-               "     --root-password-file=FILE    Set root password from file\n"
-               "     --root-password-hashed=HASH  Set root password from hashed password\n"
-               "     --root-shell=SHELL           Set root shell\n"
-               "     --kernel-command-line=CMDLINE\n"
-               "                                  Set kernel command line\n"
-               "     --prompt-locale              Prompt the user for locale settings\n"
-               "     --prompt-keymap              Prompt the user for keymap settings\n"
-               "     --prompt-keymap-auto         Prompt the user for keymap settings if invoked\n"
-               "                                  on local console\n"
-               "     --prompt-timezone            Prompt the user for timezone\n"
-               "     --prompt-hostname            Prompt the user for hostname\n"
-               "     --prompt-root-password       Prompt the user for root password\n"
-               "     --prompt-root-shell          Prompt the user for root shell\n"
-               "     --prompt                     Prompt for all of the above\n"
-               "     --copy-locale                Copy locale from host\n"
-               "     --copy-keymap                Copy keymap from host\n"
-               "     --copy-timezone              Copy timezone from host\n"
-               "     --copy-root-password         Copy root password from host\n"
-               "     --copy-root-shell            Copy root shell from host\n"
-               "     --copy                       Copy locale, keymap, timezone, root password\n"
-               "     --force                      Overwrite existing files\n"
-               "     --delete-root-password       Delete root password\n"
-               "     --welcome=no                 Disable the welcome text\n"
-               "     --chrome=no                  Don't show color bar at top and bottom of\n"
-               "                                  terminal\n"
-               "     --mute-console=yes           Tell kernel/PID 1 to not write to the console\n"
-               "                                  while running\n"
-               "     --reset                      Remove existing files\n"
-               "\nSee the %2$s for details.\n",
+        r = option_parser_get_help_table(&options);
+        if (r < 0)
+                return r;
+
+        printf("%s [OPTIONS...]\n\n"
+               "%sConfigures basic settings of the system.%s\n\n",
                program_invocation_short_name,
-               link,
                ansi_highlight(),
                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_ROOT,
-                ARG_IMAGE,
-                ARG_IMAGE_POLICY,
-                ARG_LOCALE,
-                ARG_LOCALE_MESSAGES,
-                ARG_KEYMAP,
-                ARG_TIMEZONE,
-                ARG_HOSTNAME,
-                ARG_SETUP_MACHINE_ID,
-                ARG_MACHINE_ID,
-                ARG_ROOT_PASSWORD,
-                ARG_ROOT_PASSWORD_FILE,
-                ARG_ROOT_PASSWORD_HASHED,
-                ARG_ROOT_SHELL,
-                ARG_KERNEL_COMMAND_LINE,
-                ARG_PROMPT,
-                ARG_PROMPT_LOCALE,
-                ARG_PROMPT_KEYMAP,
-                ARG_PROMPT_KEYMAP_AUTO,
-                ARG_PROMPT_TIMEZONE,
-                ARG_PROMPT_HOSTNAME,
-                ARG_PROMPT_ROOT_PASSWORD,
-                ARG_PROMPT_ROOT_SHELL,
-                ARG_COPY,
-                ARG_COPY_LOCALE,
-                ARG_COPY_KEYMAP,
-                ARG_COPY_TIMEZONE,
-                ARG_COPY_ROOT_PASSWORD,
-                ARG_COPY_ROOT_SHELL,
-                ARG_FORCE,
-                ARG_DELETE_ROOT_PASSWORD,
-                ARG_WELCOME,
-                ARG_CHROME,
-                ARG_RESET,
-                ARG_MUTE_CONSOLE,
-        };
-
-        static const struct option options[] = {
-                { "help",                    no_argument,       NULL, 'h'                         },
-                { "version",                 no_argument,       NULL, ARG_VERSION                 },
-                { "root",                    required_argument, NULL, ARG_ROOT                    },
-                { "image",                   required_argument, NULL, ARG_IMAGE                   },
-                { "image-policy",            required_argument, NULL, ARG_IMAGE_POLICY            },
-                { "locale",                  required_argument, NULL, ARG_LOCALE                  },
-                { "locale-messages",         required_argument, NULL, ARG_LOCALE_MESSAGES         },
-                { "keymap",                  required_argument, NULL, ARG_KEYMAP                  },
-                { "timezone",                required_argument, NULL, ARG_TIMEZONE                },
-                { "hostname",                required_argument, NULL, ARG_HOSTNAME                },
-                { "setup-machine-id",        no_argument,       NULL, ARG_SETUP_MACHINE_ID        },
-                { "machine-id",              required_argument, NULL, ARG_MACHINE_ID              },
-                { "root-password",           required_argument, NULL, ARG_ROOT_PASSWORD           },
-                { "root-password-file",      required_argument, NULL, ARG_ROOT_PASSWORD_FILE      },
-                { "root-password-hashed",    required_argument, NULL, ARG_ROOT_PASSWORD_HASHED    },
-                { "root-shell",              required_argument, NULL, ARG_ROOT_SHELL              },
-                { "kernel-command-line",     required_argument, NULL, ARG_KERNEL_COMMAND_LINE     },
-                { "prompt",                  no_argument,       NULL, ARG_PROMPT                  },
-                { "prompt-locale",           no_argument,       NULL, ARG_PROMPT_LOCALE           },
-                { "prompt-keymap",           no_argument,       NULL, ARG_PROMPT_KEYMAP           },
-                { "prompt-keymap-auto",      no_argument,       NULL, ARG_PROMPT_KEYMAP_AUTO      },
-                { "prompt-timezone",         no_argument,       NULL, ARG_PROMPT_TIMEZONE         },
-                { "prompt-hostname",         no_argument,       NULL, ARG_PROMPT_HOSTNAME         },
-                { "prompt-root-password",    no_argument,       NULL, ARG_PROMPT_ROOT_PASSWORD    },
-                { "prompt-root-shell",       no_argument,       NULL, ARG_PROMPT_ROOT_SHELL       },
-                { "copy",                    no_argument,       NULL, ARG_COPY                    },
-                { "copy-locale",             no_argument,       NULL, ARG_COPY_LOCALE             },
-                { "copy-keymap",             no_argument,       NULL, ARG_COPY_KEYMAP             },
-                { "copy-timezone",           no_argument,       NULL, ARG_COPY_TIMEZONE           },
-                { "copy-root-password",      no_argument,       NULL, ARG_COPY_ROOT_PASSWORD      },
-                { "copy-root-shell",         no_argument,       NULL, ARG_COPY_ROOT_SHELL         },
-                { "force",                   no_argument,       NULL, ARG_FORCE                   },
-                { "delete-root-password",    no_argument,       NULL, ARG_DELETE_ROOT_PASSWORD    },
-                { "welcome",                 required_argument, NULL, ARG_WELCOME                 },
-                { "chrome",                  required_argument, NULL, ARG_CHROME                  },
-                { "reset",                   no_argument,       NULL, ARG_RESET                   },
-                { "mute-console",            required_argument, NULL, ARG_MUTE_CONSOLE            },
-                {}
-        };
-
-        int r, c;
-
         assert(argc >= 0);
         assert(argv);
 
-        while ((c = getopt_long(argc, argv, "h", 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 ARG_ROOT:
-                        r = parse_path_argument(optarg, true, &arg_root);
+                OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"):
+                        r = parse_path_argument(arg, true, &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(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(arg, &arg_image_policy);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_LOCALE:
-                        r = free_and_strdup(&arg_locale, optarg);
+                OPTION_LONG("locale", "LOCALE", "Set primary locale (LANG=)"):
+                        r = free_and_strdup(&arg_locale, arg);
                         if (r < 0)
                                 return log_oom();
-
                         break;
 
-                case ARG_LOCALE_MESSAGES:
-                        r = free_and_strdup(&arg_locale_messages, optarg);
+                OPTION_LONG("locale-messages", "LOCALE", "Set message locale (LC_MESSAGES=)"):
+                        r = free_and_strdup(&arg_locale_messages, arg);
                         if (r < 0)
                                 return log_oom();
-
                         break;
 
-                case ARG_KEYMAP:
-                        if (!keymap_is_valid(optarg))
+                OPTION_LONG("keymap", "KEYMAP", "Set keymap"):
+                        if (!keymap_is_valid(arg))
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                       "Keymap %s is not valid.", optarg);
+                                                       "Keymap %s is not valid.", arg);
 
-                        r = free_and_strdup(&arg_keymap, optarg);
+                        r = free_and_strdup(&arg_keymap, arg);
                         if (r < 0)
                                 return log_oom();
-
                         break;
 
-                case ARG_TIMEZONE:
-                        if (!timezone_is_valid(optarg, LOG_ERR))
+                OPTION_LONG("timezone", "TIMEZONE", "Set timezone"):
+                        if (!timezone_is_valid(arg, LOG_ERR))
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                       "Timezone %s is not valid.", optarg);
+                                                       "Timezone %s is not valid.", arg);
 
-                        r = free_and_strdup(&arg_timezone, optarg);
+                        r = free_and_strdup(&arg_timezone, arg);
                         if (r < 0)
                                 return log_oom();
-
                         break;
 
-                case ARG_HOSTNAME:
-                        if (!hostname_is_valid(optarg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK))
+                OPTION_LONG("hostname", "NAME", "Set hostname"):
+                        if (!hostname_is_valid(arg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK))
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                       "Host name %s is not valid.", optarg);
+                                                       "Host name %s is not valid.", arg);
 
-                        r = free_and_strdup(&arg_hostname, optarg);
+                        r = free_and_strdup(&arg_hostname, arg);
                         if (r < 0)
                                 return log_oom();
 
                         hostname_cleanup(arg_hostname);
                         break;
 
-                case ARG_SETUP_MACHINE_ID:
+                OPTION_LONG("setup-machine-id", NULL, "Set a random machine ID"):
                         r = sd_id128_randomize(&arg_machine_id);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to generate randomized machine ID: %m");
-
                         break;
 
-                case ARG_MACHINE_ID:
-                        r = sd_id128_from_string(optarg, &arg_machine_id);
+                OPTION_LONG("machine-id", "ID", "Set specified machine ID"):
+                        r = sd_id128_from_string(arg, &arg_machine_id);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to parse machine id %s.", optarg);
-
+                                return log_error_errno(r, "Failed to parse machine id %s.", arg);
                         break;
 
-                case ARG_ROOT_PASSWORD:
-                        r = free_and_strdup(&arg_root_password, optarg);
+                OPTION_LONG("root-password", "PASSWORD", "Set root password from plaintext password"):
+                        r = free_and_strdup(&arg_root_password, arg);
                         if (r < 0)
                                 return log_oom();
 
                         arg_root_password_is_hashed = false;
                         break;
 
-                case ARG_ROOT_PASSWORD_FILE:
+                OPTION_LONG("root-password-file", "FILE", "Set root password from file"):
                         arg_root_password = mfree(arg_root_password);
 
-                        r = read_one_line_file(optarg, &arg_root_password);
+                        r = read_one_line_file(arg, &arg_root_password);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to read %s: %m", optarg);
+                                return log_error_errno(r, "Failed to read %s: %m", arg);
 
                         arg_root_password_is_hashed = false;
                         break;
 
-                case ARG_ROOT_PASSWORD_HASHED:
-                        r = free_and_strdup(&arg_root_password, optarg);
+                OPTION_LONG("root-password-hashed", "HASH", "Set root password from hashed password"):
+                        r = free_and_strdup(&arg_root_password, arg);
                         if (r < 0)
                                 return log_oom();
 
                         arg_root_password_is_hashed = true;
                         break;
 
-                case ARG_ROOT_SHELL:
-                        r = free_and_strdup(&arg_root_shell, optarg);
+                OPTION_LONG("root-shell", "SHELL", "Set root shell"):
+                        r = free_and_strdup(&arg_root_shell, arg);
                         if (r < 0)
                                 return log_oom();
-
                         break;
 
-                case ARG_KERNEL_COMMAND_LINE:
-                        r = free_and_strdup(&arg_kernel_cmdline, optarg);
+                OPTION_LONG("kernel-command-line", "CMDLINE", "Set kernel command line"):
+                        r = free_and_strdup(&arg_kernel_cmdline, arg);
                         if (r < 0)
                                 return log_oom();
-
                         break;
 
-                case ARG_PROMPT_LOCALE:
+                OPTION_LONG("prompt-locale", NULL, "Prompt the user for locale settings"):
                         arg_prompt_locale = true;
                         break;
 
-                case ARG_PROMPT_KEYMAP:
+                OPTION_LONG("prompt-keymap", NULL, "Prompt the user for keymap settings"):
                         arg_prompt_keymap = true;
                         arg_prompt_keymap_auto = false;
                         break;
 
-                case ARG_PROMPT_KEYMAP_AUTO:
+                OPTION_LONG("prompt-keymap-auto", NULL,
+                            "Prompt the user for keymap settings if invoked on local console"):
                         arg_prompt_keymap_auto = true;
                         break;
 
-                case ARG_PROMPT_TIMEZONE:
+                OPTION_LONG("prompt-timezone", NULL, "Prompt the user for timezone"):
                         arg_prompt_timezone = true;
                         break;
 
-                case ARG_PROMPT_HOSTNAME:
+                OPTION_LONG("prompt-hostname", NULL, "Prompt the user for hostname"):
                         arg_prompt_hostname = true;
                         break;
 
-                case ARG_PROMPT_ROOT_PASSWORD:
+                OPTION_LONG("prompt-root-password", NULL, "Prompt the user for root password"):
                         arg_prompt_root_password = true;
                         break;
 
-                case ARG_PROMPT_ROOT_SHELL:
+                OPTION_LONG("prompt-root-shell", NULL, "Prompt the user for root shell"):
                         arg_prompt_root_shell = true;
                         break;
 
-                case ARG_PROMPT:
+                OPTION_LONG("prompt", NULL, "Prompt for all of the above"):
                         arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname =
                                 arg_prompt_root_password = arg_prompt_root_shell = true;
                         arg_prompt_keymap_auto = false;
                         break;
 
-                case ARG_COPY_LOCALE:
+                OPTION_LONG("copy-locale", NULL, "Copy locale from host"):
                         arg_copy_locale = true;
                         break;
 
-                case ARG_COPY_KEYMAP:
+                OPTION_LONG("copy-keymap", NULL, "Copy keymap from host"):
                         arg_copy_keymap = true;
                         break;
 
-                case ARG_COPY_TIMEZONE:
+                OPTION_LONG("copy-timezone", NULL, "Copy timezone from host"):
                         arg_copy_timezone = true;
                         break;
 
-                case ARG_COPY_ROOT_PASSWORD:
+                OPTION_LONG("copy-root-password", NULL, "Copy root password from host"):
                         arg_copy_root_password = true;
                         break;
 
-                case ARG_COPY_ROOT_SHELL:
+                OPTION_LONG("copy-root-shell", NULL, "Copy root shell from host"):
                         arg_copy_root_shell = true;
                         break;
 
-                case ARG_COPY:
+                OPTION_LONG("copy", NULL, "Copy all of the above"):
                         arg_copy_locale = arg_copy_keymap = arg_copy_timezone = arg_copy_root_password =
                                 arg_copy_root_shell = true;
                         break;
 
-                case ARG_FORCE:
+                OPTION_LONG("force", NULL, "Overwrite existing files"):
                         arg_force = true;
                         break;
 
-                case ARG_DELETE_ROOT_PASSWORD:
+                OPTION_LONG("delete-root-password", NULL, "Delete root password"):
                         arg_delete_root_password = true;
                         break;
 
-                case ARG_WELCOME:
-                        r = parse_boolean(optarg);
+                OPTION_LONG("welcome", "BOOL", "Whether to show the welcome text"):
+                        r = parse_boolean(arg);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to parse --welcome= argument: %s", optarg);
+                                return log_error_errno(r, "Failed to parse --welcome= argument: %s", arg);
 
                         arg_welcome = r;
                         break;
 
-                case ARG_CHROME:
-                        r = parse_boolean_argument("--chrome=", optarg, &arg_chrome);
+                OPTION_LONG("chrome", "BOOL",
+                            "Whether to show a color bar at top and bottom of terminal"):
+                        r = parse_boolean_argument("--chrome=", arg, &arg_chrome);
                         if (r < 0)
                                 return r;
-
                         break;
 
-                case ARG_MUTE_CONSOLE:
-                        r = parse_boolean_argument("--mute-console=", optarg, &arg_mute_console);
+                OPTION_LONG("mute-console", "BOOL",
+                            "Whether to disallow kernel/PID 1 writes to the console while running"):
+                        r = parse_boolean_argument("--mute-console=", arg, &arg_mute_console);
                         if (r < 0)
                                 return r;
-
                         break;
 
-                case ARG_RESET:
+                OPTION_LONG("reset", NULL, "Remove existing files"):
                         arg_reset = true;
                         break;
-
-                case '?':
-                        return -EINVAL;
-
-                default:
-                        assert_not_reached();
                 }
 
         if (arg_delete_root_password && (arg_copy_root_password || arg_root_password || arg_prompt_root_password))