]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
vmspawn: convert to the new option parser
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Tue, 7 Apr 2026 14:22:21 +0000 (16:22 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Tue, 7 Apr 2026 21:55:51 +0000 (23:55 +0200)
Uses stop_at_first_nonoption for POSIX-style option parsing.

--help output is the same, apart from whitespace differences
and common strings.

The error message for --ssh-key-type= is fixed.

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

index 1b3558b47e75ee5e86a15aa9431bcb95eb14ad12..93c8fbbbbd2ed09c1e4f5169bf2016b305c75a04 100644 (file)
@@ -1,6 +1,5 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
-#include <getopt.h>
 #include <poll.h>
 #include <sched.h>
 #include <stdio.h>
@@ -39,6 +38,7 @@
 #include "fd-util.h"
 #include "fileio.h"
 #include "fork-notify.h"
+#include "format-table.h"
 #include "format-util.h"
 #include "fs-util.h"
 #include "gpt.h"
@@ -58,6 +58,7 @@
 #include "netif-util.h"
 #include "nsresource.h"
 #include "osc-context.h"
+#include "options.h"
 #include "pager.h"
 #include "parse-argument.h"
 #include "parse-util.h"
@@ -202,6 +203,11 @@ STATIC_DESTRUCTOR_REGISTER(arg_bind_user, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_bind_user_shell, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_bind_user_groups, strv_freep);
 
+static void unref_many_tables(Table* (*tablesp)[]) {
+        for (Table **t = *ASSERT_PTR(tablesp); *t; t++)
+                *t = table_unref(*t);
+}
+
 static int help(void) {
         _cleanup_free_ char *link = NULL;
         int r;
@@ -212,107 +218,46 @@ static int help(void) {
         if (r < 0)
                 return log_oom();
 
-        printf("%1$s [OPTIONS...] [ARGUMENTS...]\n\n"
-               "%5$sSpawn a command or OS in a virtual machine.%6$s\n\n"
-               "  -h --help                Show this help\n"
-               "     --version             Print version string\n"
-               "  -q --quiet               Do not show status information\n"
-               "     --no-pager            Do not pipe output into a pager\n"
-               "     --no-ask-password     Do not prompt for password\n"
-               "     --system              Run in the system service manager scope\n"
-               "     --user                Run in the user service manager scope\n"
-               "\n%3$sImage:%4$s\n"
-               "  -D --directory=PATH      Root directory for the VM\n"
-               "  -x --ephemeral           Run VM with snapshot of the disk or directory\n"
-               "  -i --image=FILE|DEVICE   Root file system disk image or device for the VM\n"
-               "     --image-format=FORMAT Specify disk image format (raw, qcow2; default: raw)\n"
-               "     --image-disk-type=TYPE\n"
-               "                           Specify disk type (virtio-blk, virtio-scsi, nvme,\n"
-               "                           scsi-cd; default: virtio-blk)\n"
-               "\n%3$sHost Configuration:%4$s\n"
-               "     --cpus=CPUS           Configure number of CPUs in guest\n"
-               "     --ram=BYTES[:MAXBYTES[:SLOTS]]\n"
-               "                           Configure guest's RAM size (and max/slots for\n"
-               "                           hotplug)\n"
-               "     --kvm=BOOL            Enable use of KVM\n"
-               "     --vsock=BOOL          Override autodetection of VSOCK support\n"
-               "     --vsock-cid=CID       Specify the CID to use for the guest's VSOCK support\n"
-               "     --tpm=BOOL            Enable use of a virtual TPM\n"
-               "     --tpm-state=off|auto|PATH\n"
-               "                           Where to store TPM state\n"
-               "     --efi-nvram-template=PATH\n"
-               "                           Set the path to the EFI NVRAM template file to use\n"
-               "     --efi-nvram-state=off|auto|PATH\n"
-               "                           Where to store EFI Variable NVRAM state\n"
-               "     --linux=PATH          Specify the linux kernel for direct kernel boot\n"
-               "     --initrd=PATH         Specify the initrd for direct kernel boot\n"
-               "  -n --network-tap         Create a TAP device for networking\n"
-               "     --network-user-mode   Use user mode networking\n"
-               "     --secure-boot=BOOL|auto\n"
-               "                           Enable searching for firmware supporting SecureBoot\n"
-               "     --firmware=PATH|list|describe\n"
-               "                           Select firmware definition file (or list/describe\n"
-               "                           available)\n"
-               "     --firmware-features=FEATURE[,FEATURE...]|list\n"
-               "                           Require/exclude specific firmware features\n"
-               "     --discard-disk=BOOL   Control processing of discard requests\n"
-               "  -G --grow-image=BYTES    Grow image file to specified size in bytes\n"
-               "\n%3$sExecution:%4$s\n"
-               "  -s --smbios11=STRING     Pass an arbitrary SMBIOS Type #11 string to the VM\n"
-               "     --notify-ready=BOOL   Wait for ready notification from the VM\n"
-               "\n%3$sSystem Identity:%4$s\n"
-               "  -M --machine=NAME        Set the machine name for the VM\n"
-               "     --uuid=UUID           Set a specific machine UUID for the VM\n"
-               "\n%3$sProperties:%4$s\n"
-               "  -S --slice=SLICE         Place the VM in the specified slice\n"
-               "     --property=NAME=VALUE Set scope unit property\n"
-               "     --register=BOOLEAN    Register VM as machine\n"
-               "     --keep-unit           Do not register a scope for the machine, reuse\n"
-               "                           the service unit vmspawn is running in\n"
-               "\n%3$sUser Namespacing:%4$s\n"
-               "     --private-users=UIDBASE[:NUIDS]\n"
-               "                           Configure the UID/GID range to map into the\n"
-               "                           virtiofsd namespace\n"
-               "\n%3$sMounts:%4$s\n"
-               "     --bind=SOURCE[:TARGET]\n"
-               "                           Mount a file or directory from the host into the VM\n"
-               "     --bind-ro=SOURCE[:TARGET]\n"
-               "                           Mount a file or directory, but read-only\n"
-               "     --extra-drive=[FORMAT:][DISKTYPE:]PATH\n"
-               "                           Adds an additional disk to the VM\n"
-               "                           FORMAT: raw, qcow2\n"
-               "                           DISKTYPE: virtio-blk, virtio-scsi, nvme,\n"
-               "                           scsi-cd\n"
-               "     --bind-user=NAME       Bind user from host to virtual machine\n"
-               "     --bind-user-shell=BOOL|PATH\n"
-               "                            Configure the shell to use for --bind-user= users\n"
-               "     --bind-user-group=GROUP\n"
-               "                            Add an auxiliary group to --bind-user= users\n"
-               "\n%3$sIntegration:%4$s\n"
-               "     --forward-journal=FILE|DIR\n"
-               "                           Forward the VM's journal to the host\n"
-               "     --pass-ssh-key=BOOL   Create an SSH key to access the VM\n"
-               "     --ssh-key-type=TYPE   Choose what type of SSH key to pass\n"
-               "\n%3$sInput/Output:%4$s\n"
-               "     --console=MODE        Console mode (interactive, native, gui, read-only\n"
-               "                           or headless)\n"
-               "     --console-transport=TRANSPORT\n"
-               "                           Console transport (virtio or serial)\n"
-               "     --background=COLOR    Set ANSI color for background\n"
-               "\n%3$sCredentials:%4$s\n"
-               "     --set-credential=ID:VALUE\n"
-               "                           Pass a credential with literal value to the VM\n"
-               "     --load-credential=ID:PATH\n"
-               "                           Load credential for the VM from file or AF_UNIX\n"
-               "                           stream socket.\n"
-               "\nSee the %2$s for details.\n",
+        static const char *groups[] = {
+                NULL,
+                "Image",
+                "Host Configuration",
+                "Execution",
+                "System Identity",
+                "Properties",
+                "User Namespacing",
+                "Mounts",
+                "Integration",
+                "Input/Output",
+                "Credentials",
+        };
+
+        _cleanup_(unref_many_tables) Table* tables[ELEMENTSOF(groups) + 1] = {};
+
+        for (size_t i = 0; i < ELEMENTSOF(groups); i++) {
+                r = option_parser_get_help_table_group(groups[i], &tables[i]);
+                if (r < 0)
+                        return r;
+        }
+
+        (void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3], tables[4],
+                                        tables[5], tables[6], tables[7], tables[8], tables[9], tables[10]);
+
+        printf("%s [OPTIONS...] [ARGUMENTS...]\n\n"
+               "%sSpawn a command or OS in a virtual machine.%s\n",
                program_invocation_short_name,
-               link,
-               ansi_underline(),
-               ansi_normal(),
                ansi_highlight(),
                ansi_normal());
 
+        for (size_t i = 0; i < ELEMENTSOF(groups); i++) {
+                printf("\n%s%s:%s\n", ansi_underline(), groups[i] ?: "Options", ansi_normal());
+
+                r = table_print_or_warn(tables[i]);
+                if (r < 0)
+                        return r;
+        }
+
+        printf("\nSee the %s for details.\n", link);
         return 0;
 }
 
@@ -374,216 +319,117 @@ static int parse_argv(int argc, char *argv[]) {
         if (r < 0)
                 return log_oom();
 
-        enum {
-                ARG_VERSION = 0x100,
-                ARG_NO_PAGER,
-                ARG_CPUS,
-                ARG_RAM,
-                ARG_KVM,
-                ARG_VSOCK,
-                ARG_VSOCK_CID,
-                ARG_TPM,
-                ARG_LINUX,
-                ARG_INITRD,
-                ARG_QEMU_GUI,
-                ARG_NETWORK_USER_MODE,
-                ARG_UUID,
-                ARG_REGISTER,
-                ARG_KEEP_UNIT,
-                ARG_BIND,
-                ARG_BIND_RO,
-                ARG_EXTRA_DRIVE,
-                ARG_SECURE_BOOT,
-                ARG_PRIVATE_USERS,
-                ARG_FORWARD_JOURNAL,
-                ARG_PASS_SSH_KEY,
-                ARG_SSH_KEY_TYPE,
-                ARG_SET_CREDENTIAL,
-                ARG_LOAD_CREDENTIAL,
-                ARG_FIRMWARE,
-                ARG_FIRMWARE_FEATURES,
-                ARG_DISCARD_DISK,
-                ARG_CONSOLE,
-                ARG_BACKGROUND,
-                ARG_TPM_STATE,
-                ARG_EFI_NVRAM_TEMPLATE,
-                ARG_EFI_NVRAM_STATE,
-                ARG_NO_ASK_PASSWORD,
-                ARG_PROPERTY,
-                ARG_NOTIFY_READY,
-                ARG_BIND_USER,
-                ARG_BIND_USER_SHELL,
-                ARG_BIND_USER_GROUP,
-                ARG_SYSTEM,
-                ARG_USER,
-                ARG_IMAGE_FORMAT,
-                ARG_IMAGE_DISK_TYPE,
-                ARG_CONSOLE_TRANSPORT,
-        };
-
-        static const struct option options[] = {
-                { "help",              no_argument,       NULL, 'h'                   },
-                { "version",           no_argument,       NULL, ARG_VERSION           },
-                { "quiet",             no_argument,       NULL, 'q'                   },
-                { "no-pager",          no_argument,       NULL, ARG_NO_PAGER          },
-                { "image",             required_argument, NULL, 'i'                   },
-                { "image-format",      required_argument, NULL, ARG_IMAGE_FORMAT      },
-                { "image-disk-type",   required_argument, NULL, ARG_IMAGE_DISK_TYPE   },
-                { "ephemeral",         no_argument,       NULL, 'x'                   },
-                { "directory",         required_argument, NULL, 'D'                   },
-                { "machine",           required_argument, NULL, 'M'                   },
-                { "slice",             required_argument, NULL, 'S'                   },
-                { "cpus",              required_argument, NULL, ARG_CPUS              },
-                { "qemu-smp",          required_argument, NULL, ARG_CPUS              }, /* Compat alias */
-                { "ram",               required_argument, NULL, ARG_RAM               },
-                { "qemu-mem",          required_argument, NULL, ARG_RAM               }, /* Compat alias */
-                { "kvm",               required_argument, NULL, ARG_KVM               },
-                { "qemu-kvm",          required_argument, NULL, ARG_KVM               }, /* Compat alias */
-                { "vsock",             required_argument, NULL, ARG_VSOCK             },
-                { "qemu-vsock",        required_argument, NULL, ARG_VSOCK             }, /* Compat alias */
-                { "vsock-cid",         required_argument, NULL, ARG_VSOCK_CID         },
-                { "tpm",               required_argument, NULL, ARG_TPM               },
-                { "linux",             required_argument, NULL, ARG_LINUX             },
-                { "initrd",            required_argument, NULL, ARG_INITRD            },
-                { "console",           required_argument, NULL, ARG_CONSOLE           },
-                { "console-transport", required_argument, NULL, ARG_CONSOLE_TRANSPORT },
-                { "qemu-gui",          no_argument,       NULL, ARG_QEMU_GUI          }, /* compat option */
-                { "network-tap",       no_argument,       NULL, 'n'                   },
-                { "network-user-mode", no_argument,       NULL, ARG_NETWORK_USER_MODE },
-                { "uuid",              required_argument, NULL, ARG_UUID              },
-                { "register",          required_argument, NULL, ARG_REGISTER          },
-                { "keep-unit",         no_argument,       NULL, ARG_KEEP_UNIT         },
-                { "bind",              required_argument, NULL, ARG_BIND              },
-                { "bind-ro",           required_argument, NULL, ARG_BIND_RO           },
-                { "extra-drive",       required_argument, NULL, ARG_EXTRA_DRIVE       },
-                { "secure-boot",       required_argument, NULL, ARG_SECURE_BOOT       },
-                { "private-users",     required_argument, NULL, ARG_PRIVATE_USERS     },
-                { "forward-journal",   required_argument, NULL, ARG_FORWARD_JOURNAL   },
-                { "pass-ssh-key",      required_argument, NULL, ARG_PASS_SSH_KEY      },
-                { "ssh-key-type",      required_argument, NULL, ARG_SSH_KEY_TYPE      },
-                { "set-credential",    required_argument, NULL, ARG_SET_CREDENTIAL    },
-                { "load-credential",   required_argument, NULL, ARG_LOAD_CREDENTIAL   },
-                { "firmware",          required_argument, NULL, ARG_FIRMWARE          },
-                { "firmware-features", required_argument, NULL, ARG_FIRMWARE_FEATURES  },
-                { "discard-disk",      required_argument, NULL, ARG_DISCARD_DISK      },
-                { "background",        required_argument, NULL, ARG_BACKGROUND        },
-                { "smbios11",          required_argument, NULL, 's'                   },
-                { "grow-image",        required_argument, NULL, 'G'                   },
-                { "tpm-state",         required_argument, NULL, ARG_TPM_STATE         },
-                { "efi-nvram-template", required_argument, NULL, ARG_EFI_NVRAM_TEMPLATE },
-                { "efi-nvram-state",   required_argument, NULL, ARG_EFI_NVRAM_STATE   },
-                { "no-ask-password",   no_argument,       NULL, ARG_NO_ASK_PASSWORD   },
-                { "property",          required_argument, NULL, ARG_PROPERTY          },
-                { "notify-ready",      required_argument, NULL, ARG_NOTIFY_READY      },
-                { "bind-user",         required_argument, NULL, ARG_BIND_USER         },
-                { "bind-user-shell",   required_argument, NULL, ARG_BIND_USER_SHELL   },
-                { "bind-user-group",   required_argument, NULL, ARG_BIND_USER_GROUP   },
-                { "system",            no_argument,       NULL, ARG_SYSTEM            },
-                { "user",              no_argument,       NULL, ARG_USER              },
-                {}
-        };
-
-        int c;
-
         assert(argc >= 0);
         assert(argv);
 
-        optind = 0;
-        while ((c = getopt_long(argc, argv, "+hD:i:xM:nqs:G:S:", options, NULL)) >= 0)
+        OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true };
+        const Option *current;
+        const char *arg;
+
+        FOREACH_OPTION_FULL(&state, c, &current, &arg, /* on_error= */ return c)
                 switch (c) {
-                case 'h':
+
+                OPTION_COMMON_HELP:
                         return help();
 
-                case ARG_VERSION:
+                OPTION_COMMON_VERSION:
                         return version();
 
-                case 'q':
+                OPTION('q', "quiet", NULL, "Do not show status information"):
                         arg_quiet = true;
                         break;
 
-                case 'D':
-                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_directory);
+                OPTION_COMMON_NO_PAGER:
+                        arg_pager_flags |= PAGER_DISABLE;
+                        break;
+
+                OPTION_COMMON_NO_ASK_PASSWORD:
+                        arg_ask_password = false;
+                        break;
+
+                OPTION_LONG("system", NULL, "Run in the system service manager scope"):
+                        arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
+                        break;
+
+                OPTION_LONG("user", NULL, "Run in the user service manager scope"):
+                        arg_runtime_scope = RUNTIME_SCOPE_USER;
+                        break;
+
+                OPTION_GROUP("Image"):
+                        break;
+
+                OPTION('D', "directory", "PATH", "Root directory for the VM"):
+                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_directory);
                         if (r < 0)
                                 return r;
+                        break;
 
+                OPTION('x', "ephemeral", NULL, "Run VM with snapshot of the disk or directory"):
+                        arg_ephemeral = true;
                         break;
 
-                case 'i':
-                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
+                OPTION('i', "image", "FILE|DEVICE", "Root file system disk image or device for the VM"):
+                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image);
                         if (r < 0)
                                 return r;
-
                         break;
 
-                case ARG_IMAGE_FORMAT:
-                        arg_image_format = image_format_from_string(optarg);
+                OPTION_LONG("image-format", "FORMAT", "Specify disk image format (raw, qcow2; default: raw)"):
+                        arg_image_format = image_format_from_string(arg);
                         if (arg_image_format < 0)
                                 return log_error_errno(arg_image_format,
-                                                       "Invalid image format: %s", optarg);
+                                                       "Invalid image format: %s", arg);
                         break;
 
-                case ARG_IMAGE_DISK_TYPE:
-                        arg_image_disk_type = disk_type_from_string(optarg);
+                OPTION_LONG("image-disk-type", "TYPE",
+                            "Specify disk type (virtio-blk, virtio-scsi, nvme, scsi-cd; default: virtio-blk)"):
+                        arg_image_disk_type = disk_type_from_string(arg);
                         if (arg_image_disk_type < 0)
                                 return log_error_errno(arg_image_disk_type,
-                                                       "Invalid image disk type: %s", optarg);
-                        break;
-
-                case 'M':
-                        if (isempty(optarg))
-                                arg_machine = mfree(arg_machine);
-                        else {
-                                if (!hostname_is_valid(optarg, 0))
-                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                                               "Invalid machine name: %s", optarg);
-
-                                r = free_and_strdup(&arg_machine, optarg);
-                                if (r < 0)
-                                        return log_oom();
-                        }
-                        break;
-
-                case 'x':
-                        arg_ephemeral = true;
+                                                       "Invalid image disk type: %s", arg);
                         break;
 
-                case ARG_NO_PAGER:
-                        arg_pager_flags |= PAGER_DISABLE;
+                OPTION_GROUP("Host Configuration"):
                         break;
 
-                case ARG_CPUS:
-                        r = free_and_strdup_warn(&arg_cpus, optarg);
+                OPTION_LONG("cpus", "CPUS", "Configure number of CPUs in guest"): {}
+                OPTION_LONG("qemu-smp", "CPUS", /* help= */ NULL):  /* Compat alias */
+                        r = free_and_strdup_warn(&arg_cpus, arg);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_RAM:
-                        r = parse_ram(optarg);
+                OPTION_LONG("ram", "BYTES[:MAXBYTES[:SLOTS]]",
+                            "Configure guest's RAM size (and max/slots for hotplug)"): {}
+                OPTION_LONG("qemu-mem", "BYTES", /* help= */ NULL):  /* Compat alias */
+                        r = parse_ram(arg);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_KVM:
-                        r = parse_tristate_argument_with_auto("--kvm=", optarg, &arg_kvm);
+                OPTION_LONG("kvm", "BOOL", "Enable use of KVM"): {}
+                OPTION_LONG("qemu-kvm", "BOOL", /* help= */ NULL):  /* Compat alias */
+                        r = parse_tristate_argument_with_auto("--kvm=", arg, &arg_kvm);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_VSOCK:
-                        r = parse_tristate_argument_with_auto("--vsock=", optarg, &arg_vsock);
+                OPTION_LONG("vsock", "BOOL", "Override autodetection of VSOCK support"): {}
+                OPTION_LONG("qemu-vsock", "BOOL", /* help= */ NULL):  /* Compat alias */
+                        r = parse_tristate_argument_with_auto("--vsock=", arg, &arg_vsock);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_VSOCK_CID:
-                        if (isempty(optarg))
+                OPTION_LONG("vsock-cid", "CID", "Specify the CID to use for the guest's VSOCK support"):
+                        if (isempty(arg))
                                 arg_vsock_cid = VMADDR_CID_ANY;
                         else {
                                 unsigned cid;
 
-                                r = vsock_parse_cid(optarg, &cid);
+                                r = vsock_parse_cid(arg, &cid);
                                 if (r < 0)
-                                        return log_error_errno(r, "Failed to parse --vsock-cid: %s", optarg);
+                                        return log_error_errno(r, "Failed to parse --vsock-cid: %s", arg);
                                 if (!VSOCK_CID_IS_REGULAR(cid))
                                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified CID is not regular, refusing: %u", cid);
 
@@ -591,140 +437,97 @@ static int parse_argv(int argc, char *argv[]) {
                         }
                         break;
 
-                case ARG_TPM:
-                        r = parse_tristate_argument_with_auto("--tpm=", optarg, &arg_tpm);
-                        if (r < 0)
-                                return r;
-                        break;
-
-                case ARG_LINUX:
-                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_linux);
+                OPTION_LONG("tpm", "BOOL", "Enable use of a virtual TPM"):
+                        r = parse_tristate_argument_with_auto("--tpm=", arg, &arg_tpm);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_INITRD: {
-                        _cleanup_free_ char *initrd_path = NULL;
-                        r = parse_path_argument(optarg, /* suppress_root= */ false, &initrd_path);
-                        if (r < 0)
-                                return r;
-
-                        r = strv_consume(&arg_initrds, TAKE_PTR(initrd_path));
-                        if (r < 0)
-                                return log_oom();
-
-                        break;
-                }
+                OPTION_LONG("tpm-state", "off|auto|PATH", "Where to store TPM state"):
+                        r = isempty(arg) ? false :
+                                streq(arg, "auto") ? true :
+                                parse_boolean(arg);
+                        if (r >= 0) {
+                                arg_tpm_state_mode = r ? STATE_AUTO : STATE_OFF;
+                                arg_tpm_state_path = mfree(arg_tpm_state_path);
+                                break;
+                        }
 
-                case ARG_CONSOLE:
-                        arg_console_mode = console_mode_from_string(optarg);
-                        if (arg_console_mode < 0)
-                                return log_error_errno(arg_console_mode, "Failed to parse specified console mode: %s", optarg);
+                        if (!path_is_valid(arg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --tpm-state= parameter: %s", arg);
 
-                        break;
+                        if (!path_is_absolute(arg) && !startswith(arg, "./"))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --tpm-state= parameter must be absolute or start with './': %s", arg);
 
-                case ARG_CONSOLE_TRANSPORT:
-                        arg_console_transport = console_transport_from_string(optarg);
-                        if (arg_console_transport < 0)
-                                return log_error_errno(arg_console_transport, "Failed to parse specified console transport: %s", optarg);
+                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm_state_path);
+                        if (r < 0)
+                                return r;
 
+                        arg_tpm_state_mode = STATE_PATH;
                         break;
 
-                case ARG_QEMU_GUI:
-                        arg_console_mode = CONSOLE_GUI;
-                        break;
+                OPTION_LONG("efi-nvram-template", "PATH", "Set the path to the EFI NVRAM template file to use"):
+                        if (!isempty(arg) && !path_is_absolute(arg) && !startswith(arg, "./"))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required.");
 
-                case 'n':
-                        arg_network_stack = NETWORK_STACK_TAP;
+                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_efi_nvram_template);
+                        if (r < 0)
+                                return r;
                         break;
 
-                case ARG_NETWORK_USER_MODE:
-                        arg_network_stack = NETWORK_STACK_USER;
-                        break;
+                OPTION_LONG("efi-nvram-state", "off|auto|PATH", "Where to store EFI Variable NVRAM state"):
+                        r = isempty(arg) ? false :
+                                streq(arg, "auto") ? true :
+                                parse_boolean(arg);
+                        if (r >= 0) {
+                                arg_efi_nvram_state_mode = r ? STATE_AUTO : STATE_OFF;
+                                arg_efi_nvram_state_path = mfree(arg_efi_nvram_state_path);
+                                break;
+                        }
 
-                case ARG_UUID:
-                        r = id128_from_string_nonzero(optarg, &arg_uuid);
-                        if (r == -ENXIO)
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine UUID may not be all zeroes.");
-                        if (r < 0)
-                                return log_error_errno(r, "Invalid UUID: %s", optarg);
+                        if (!path_is_valid(arg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --efi-nvram-state= parameter: %s", arg);
 
-                        break;
+                        if (!path_is_absolute(arg) && !startswith(arg, "./"))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --efi-nvram-state= parameter must be absolute or start with './': %s", arg);
 
-                case ARG_REGISTER:
-                        r = parse_tristate_argument_with_auto("--register=", optarg, &arg_register);
+                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_efi_nvram_state_path);
                         if (r < 0)
                                 return r;
 
+                        arg_efi_nvram_state_mode = STATE_PATH;
                         break;
 
-                case ARG_KEEP_UNIT:
-                        arg_keep_unit = true;
-                        break;
-
-                case ARG_BIND:
-                case ARG_BIND_RO:
-                        r = runtime_mount_parse(&arg_runtime_mounts, optarg, c == ARG_BIND_RO);
+                OPTION_LONG("linux", "PATH", "Specify the linux kernel for direct kernel boot"):
+                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_linux);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", optarg);
-
+                                return r;
                         break;
 
-                case ARG_EXTRA_DRIVE: {
-                        ImageFormat format = IMAGE_FORMAT_RAW;
-                        DiskType extra_disk_type = _DISK_TYPE_INVALID;
-                        const char *dp = optarg;
-
-                        /* Parse optional colon-separated prefixes. The format and disk type
-                         * value sets don't overlap, so they can appear in any order. */
-                        for (;;) {
-                                const char *colon = strchr(dp, ':');
-                                if (!colon)
-                                        break;
-
-                                _cleanup_free_ char *prefix = strndup(dp, colon - dp);
-                                if (!prefix)
-                                        return log_oom();
-
-                                ImageFormat f = image_format_from_string(prefix);
-                                if (f >= 0) {
-                                        format = f;
-                                        dp = colon + 1;
-                                        continue;
-                                }
-
-                                DiskType dt = disk_type_from_string(prefix);
-                                if (dt >= 0) {
-                                        extra_disk_type = dt;
-                                        dp = colon + 1;
-                                        continue;
-                                }
-
-                                /* Not a recognized prefix, treat the rest as the path */
-                                break;
-                        }
-
-                        _cleanup_free_ char *drive_path = NULL;
-                        r = parse_path_argument(dp, /* suppress_root= */ false, &drive_path);
+                OPTION_LONG("initrd", "PATH", "Specify the initrd for direct kernel boot"): {
+                        _cleanup_free_ char *initrd_path = NULL;
+                        r = parse_path_argument(arg, /* suppress_root= */ false, &initrd_path);
                         if (r < 0)
                                 return r;
 
-                        if (!GREEDY_REALLOC(arg_extra_drives.drives, arg_extra_drives.n_drives + 1))
+                        r = strv_consume(&arg_initrds, TAKE_PTR(initrd_path));
+                        if (r < 0)
                                 return log_oom();
+                        break;
+                }
 
-                        arg_extra_drives.drives[arg_extra_drives.n_drives++] = (ExtraDrive) {
-                                .path = TAKE_PTR(drive_path),
-                                .format = format,
-                                .disk_type = extra_disk_type,
-                        };
+                OPTION('n', "network-tap", NULL, "Create a TAP device for networking"):
+                        arg_network_stack = NETWORK_STACK_TAP;
+                        break;
 
+                OPTION_LONG("network-user-mode", NULL, "Use user mode networking"):
+                        arg_network_stack = NETWORK_STACK_USER;
                         break;
-                }
 
-                case ARG_SECURE_BOOT: {
+                OPTION_LONG("secure-boot", "BOOL|auto", "Enable searching for firmware supporting SecureBoot"): {
                         int b;
 
-                        r = parse_tristate_argument_with_auto("--secure-boot=", optarg, &b);
+                        r = parse_tristate_argument_with_auto("--secure-boot=", arg, &b);
                         if (r < 0)
                                 return r;
 
@@ -736,54 +539,12 @@ static int parse_argv(int argc, char *argv[]) {
                                 if (r < 0)
                                         return log_oom();
                         }
-
-                        break;
-                }
-
-                case ARG_PRIVATE_USERS:
-                        r = parse_userns_uid_range(optarg, &arg_uid_shift, &arg_uid_range);
-                        if (r < 0)
-                                return r;
-                        break;
-
-                case ARG_FORWARD_JOURNAL:
-                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_forward_journal);
-                        if (r < 0)
-                                return r;
-                        break;
-
-                case ARG_PASS_SSH_KEY:
-                        r = parse_boolean_argument("--pass-ssh-key=", optarg, &arg_pass_ssh_key);
-                        if (r < 0)
-                                return r;
-                        break;
-
-                case ARG_SSH_KEY_TYPE:
-                        if (!string_is_safe(optarg))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value for --arg-ssh-key-type=: %s", optarg);
-
-                        r = free_and_strdup_warn(&arg_ssh_key_type, optarg);
-                        if (r < 0)
-                                return r;
-                        break;
-
-                case ARG_SET_CREDENTIAL: {
-                        r = machine_credential_set(&arg_credentials, optarg);
-                        if (r < 0)
-                                return r;
-                        break;
-                }
-
-                case ARG_LOAD_CREDENTIAL: {
-                        r = machine_credential_load(&arg_credentials, optarg);
-                        if (r < 0)
-                                return r;
-
                         break;
                 }
 
-                case ARG_FIRMWARE:
-                        if (streq(optarg, "list")) {
+                OPTION_LONG("firmware", "PATH|list|describe",
+                            "Select firmware definition file (or list/describe available)"):
+                        if (streq(arg, "list")) {
                                 _cleanup_strv_free_ char **l = NULL;
 
                                 r = list_ovmf_config(&l);
@@ -798,7 +559,7 @@ static int parse_argv(int argc, char *argv[]) {
                                 return 0;
                         }
 
-                        if (streq(optarg, "describe")) {
+                        if (streq(arg, "describe")) {
                                 /* Handled after argument parsing so that --firmware-features= is
                                  * taken into account. */
                                 arg_firmware = mfree(arg_firmware);
@@ -808,23 +569,23 @@ static int parse_argv(int argc, char *argv[]) {
 
                         arg_firmware_describe = false;
 
-                        if (!isempty(optarg) && !path_is_absolute(optarg) && !startswith(optarg, "./"))
+                        if (!isempty(arg) && !path_is_absolute(arg) && !startswith(arg, "./"))
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required.");
 
-                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_firmware);
+                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_firmware);
                         if (r < 0)
                                 return r;
-
                         break;
 
-                case ARG_FIRMWARE_FEATURES: {
-                        if (isempty(optarg)) {
+                OPTION_LONG("firmware-features", "FEATURE,...|list",
+                            "Require/exclude specific firmware features"): {
+                        if (isempty(arg)) {
                                 arg_firmware_features_include = set_free(arg_firmware_features_include);
                                 arg_firmware_features_exclude = set_free(arg_firmware_features_exclude);
                                 break;
                         }
 
-                        if (streq(optarg, "list")) {
+                        if (streq(arg, "list")) {
                                 _cleanup_strv_free_ char **l = NULL;
 
                                 r = list_ovmf_firmware_features(&l);
@@ -839,7 +600,7 @@ static int parse_argv(int argc, char *argv[]) {
                                 return 0;
                         }
 
-                        _cleanup_strv_free_ char **features = strv_split(optarg, ",");
+                        _cleanup_strv_free_ char **features = strv_split(arg, ",");
                         if (!features)
                                 return log_oom();
 
@@ -849,178 +610,271 @@ static int parse_argv(int argc, char *argv[]) {
                                 if (r < 0)
                                         return log_oom();
                         }
-
                         break;
                 }
 
-                case ARG_DISCARD_DISK:
-                        r = parse_boolean_argument("--discard-disk=", optarg, &arg_discard_disk);
+                OPTION_LONG("discard-disk", "BOOL", "Control processing of discard requests"):
+                        r = parse_boolean_argument("--discard-disk=", arg, &arg_discard_disk);
                         if (r < 0)
                                 return r;
                         break;
 
-                case ARG_BACKGROUND:
-                        r = parse_background_argument(optarg, &arg_background);
+                OPTION('G', "grow-image", "BYTES", "Grow image file to specified size in bytes"):
+                        if (isempty(arg)) {
+                                arg_grow_image = 0;
+                                break;
+                        }
+
+                        r = parse_size(arg, 1024, &arg_grow_image);
                         if (r < 0)
-                                return r;
+                                return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", arg);
+                        break;
+
+                OPTION_GROUP("Execution"):
                         break;
 
-                case 's':
-                        if (isempty(optarg)) {
+                OPTION('s', "smbios11", "STRING", "Pass an arbitrary SMBIOS Type #11 string to the VM"):
+                        if (isempty(arg)) {
                                 arg_smbios11 = strv_free(arg_smbios11);
                                 break;
                         }
 
-                        if (!utf8_is_valid(optarg))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SMBIOS Type 11 string is not UTF-8 clean, refusing: %s", optarg);
+                        if (!utf8_is_valid(arg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SMBIOS Type 11 string is not UTF-8 clean, refusing: %s", arg);
 
-                        if (strv_extend(&arg_smbios11, optarg) < 0)
+                        if (strv_extend(&arg_smbios11, arg) < 0)
                                 return log_oom();
-
                         break;
 
-                case 'G':
-                        if (isempty(optarg)) {
-                                arg_grow_image = 0;
-                                break;
-                        }
-
-                        r = parse_size(optarg, 1024, &arg_grow_image);
+                OPTION_LONG("notify-ready", "BOOL", "Wait for ready notification from the VM"):
+                        r = parse_boolean_argument("--notify-ready=", arg, &arg_notify_ready);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", optarg);
+                                return r;
+                        break;
 
+                OPTION_GROUP("System Identity"):
                         break;
 
-                case ARG_TPM_STATE:
-                        r = isempty(optarg) ? false :
-                                streq(optarg, "auto") ? true :
-                                parse_boolean(optarg);
-                        if (r >= 0) {
-                                arg_tpm_state_mode = r ? STATE_AUTO : STATE_OFF;
-                                arg_tpm_state_path = mfree(arg_tpm_state_path);
-                                break;
+                OPTION('M', "machine", "NAME", "Set the machine name for the VM"):
+                        if (isempty(arg))
+                                arg_machine = mfree(arg_machine);
+                        else {
+                                if (!hostname_is_valid(arg, 0))
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                               "Invalid machine name: %s", arg);
+
+                                r = free_and_strdup(&arg_machine, arg);
+                                if (r < 0)
+                                        return log_oom();
                         }
+                        break;
+
+                OPTION_LONG("uuid", "UUID", "Set a specific machine UUID for the VM"):
+                        r = id128_from_string_nonzero(arg, &arg_uuid);
+                        if (r == -ENXIO)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine UUID may not be all zeroes.");
+                        if (r < 0)
+                                return log_error_errno(r, "Invalid UUID: %s", arg);
+                        break;
 
-                        if (!path_is_valid(optarg))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --tpm-state= parameter: %s", optarg);
+                OPTION_GROUP("Properties"):
+                        break;
 
-                        if (!path_is_absolute(optarg) && !startswith(optarg, "./"))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --tpm-state= parameter must be absolute or start with './': %s", optarg);
+                OPTION('S', "slice", "SLICE", "Place the VM in the specified slice"): {
+                        _cleanup_free_ char *mangled = NULL;
 
-                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm_state_path);
+                        r = unit_name_mangle_with_suffix(arg, /* operation= */ NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled);
                         if (r < 0)
-                                return r;
+                                return log_error_errno(r, "Failed to turn '%s' into unit name: %m", arg);
 
-                        arg_tpm_state_mode = STATE_PATH;
+                        free_and_replace(arg_slice, mangled);
                         break;
+                }
 
-                case ARG_EFI_NVRAM_TEMPLATE:
-                        if (!isempty(optarg) && !path_is_absolute(optarg) && !startswith(optarg, "./"))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required.");
+                OPTION_LONG("property", "NAME=VALUE", "Set scope unit property"):
+                        if (strv_extend(&arg_property, arg) < 0)
+                                return log_oom();
+                        break;
 
-                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_efi_nvram_template);
+                OPTION_LONG("register", "BOOLEAN", "Register VM as machine"):
+                        r = parse_tristate_argument_with_auto("--register=", arg, &arg_register);
                         if (r < 0)
                                 return r;
-
                         break;
 
-                case ARG_EFI_NVRAM_STATE:
-                        r = isempty(optarg) ? false :
-                                streq(optarg, "auto") ? true :
-                                parse_boolean(optarg);
-                        if (r >= 0) {
-                                arg_efi_nvram_state_mode = r ? STATE_AUTO : STATE_OFF;
-                                arg_efi_nvram_state_path = mfree(arg_efi_nvram_state_path);
-                                break;
-                        }
-
-                        if (!path_is_valid(optarg))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path in --efi-nvram-state= parameter: %s", optarg);
+                OPTION_LONG("keep-unit", NULL,
+                            "Do not register a scope for the machine, reuse the service unit vmspawn is running in"):
+                        arg_keep_unit = true;
+                        break;
 
-                        if (!path_is_absolute(optarg) && !startswith(optarg, "./"))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path in --efi-nvram-state= parameter must be absolute or start with './': %s", optarg);
+                OPTION_GROUP("User Namespacing"):
+                        break;
 
-                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_efi_nvram_state_path);
+                OPTION_LONG("private-users", "UIDBASE[:NUIDS]",
+                            "Configure the UID/GID range to map into the virtiofsd namespace"):
+                        r = parse_userns_uid_range(arg, &arg_uid_shift, &arg_uid_range);
                         if (r < 0)
                                 return r;
-
-                        arg_efi_nvram_state_mode = STATE_PATH;
                         break;
 
-                case ARG_NO_ASK_PASSWORD:
-                        arg_ask_password = false;
+                OPTION_GROUP("Mounts"):
                         break;
 
-                case 'S': {
-                        _cleanup_free_ char *mangled = NULL;
-
-                        r = unit_name_mangle_with_suffix(optarg, /* operation= */ NULL, UNIT_NAME_MANGLE_WARN, ".slice", &mangled);
+                OPTION_LONG("bind", "SOURCE[:TARGET]", "Mount a file or directory from the host into the VM"): {}
+                OPTION_LONG("bind-ro", "SOURCE[:TARGET]", "Mount a file or directory, but read-only"): {
+                        bool read_only = streq(current->long_code, "bind-ro");
+                        r = runtime_mount_parse(&arg_runtime_mounts, arg, read_only);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to turn '%s' into unit name: %m", optarg);
-
-                        free_and_replace(arg_slice, mangled);
+                                return log_error_errno(r, "Failed to parse --%s= argument %s: %m", current->long_code, arg);
                         break;
                 }
 
-                case ARG_PROPERTY:
-                        if (strv_extend(&arg_property, optarg) < 0)
-                                return log_oom();
+                OPTION_LONG("extra-drive", "[FORMAT:][DISKTYPE:]PATH", "Adds an additional disk to the VM"): {
+                        ImageFormat format = IMAGE_FORMAT_RAW;
+                        DiskType extra_disk_type = _DISK_TYPE_INVALID;
+                        const char *dp = arg;
 
-                        break;
+                        /* Parse optional colon-separated prefixes. The format and disk type
+                         * value sets don't overlap, so they can appear in any order. */
+                        for (;;) {
+                                const char *colon = strchr(dp, ':');
+                                if (!colon)
+                                        break;
+
+                                _cleanup_free_ char *prefix = strndup(dp, colon - dp);
+                                if (!prefix)
+                                        return log_oom();
+
+                                ImageFormat f = image_format_from_string(prefix);
+                                if (f >= 0) {
+                                        format = f;
+                                        dp = colon + 1;
+                                        continue;
+                                }
+
+                                DiskType dt = disk_type_from_string(prefix);
+                                if (dt >= 0) {
+                                        extra_disk_type = dt;
+                                        dp = colon + 1;
+                                        continue;
+                                }
 
-                case ARG_NOTIFY_READY:
-                        r = parse_boolean_argument("--notify-ready=", optarg, &arg_notify_ready);
+                                /* Not a recognized prefix, treat the rest as the path */
+                                break;
+                        }
+
+                        _cleanup_free_ char *drive_path = NULL;
+                        r = parse_path_argument(dp, /* suppress_root= */ false, &drive_path);
                         if (r < 0)
                                 return r;
 
+                        if (!GREEDY_REALLOC(arg_extra_drives.drives, arg_extra_drives.n_drives + 1))
+                                return log_oom();
+
+                        arg_extra_drives.drives[arg_extra_drives.n_drives++] = (ExtraDrive) {
+                                .path = TAKE_PTR(drive_path),
+                                .format = format,
+                                .disk_type = extra_disk_type,
+                        };
                         break;
+                }
 
-                case ARG_BIND_USER:
-                        if (!valid_user_group_name(optarg, /* flags= */ 0))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", optarg);
+                OPTION_LONG("bind-user", "NAME", "Bind user from host to virtual machine"):
+                        if (!valid_user_group_name(arg, /* flags= */ 0))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid user name to bind: %s", arg);
 
-                        if (strv_extend(&arg_bind_user, optarg) < 0)
+                        if (strv_extend(&arg_bind_user, arg) < 0)
                                 return log_oom();
-
                         break;
 
-                case ARG_BIND_USER_SHELL: {
+                OPTION_LONG("bind-user-shell", "BOOL|PATH",
+                            "Configure the shell to use for --bind-user= users"): {
                         bool copy = false;
                         char *sh = NULL;
-                        r = parse_user_shell(optarg, &sh, &copy);
+                        r = parse_user_shell(arg, &sh, &copy);
                         if (r == -ENOMEM)
                                 return log_oom();
                         if (r < 0)
-                                return log_error_errno(r, "Invalid user shell to bind: %s", optarg);
+                                return log_error_errno(r, "Invalid user shell to bind: %s", arg);
 
                         free_and_replace(arg_bind_user_shell, sh);
                         arg_bind_user_shell_copy = copy;
-
                         break;
                 }
 
-                case ARG_BIND_USER_GROUP:
-                        if (!valid_user_group_name(optarg, /* flags= */ 0))
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", optarg);
+                OPTION_LONG("bind-user-group", "GROUP", "Add an auxiliary group to --bind-user= users"):
+                        if (!valid_user_group_name(arg, /* flags= */ 0))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind user auxiliary group name: %s", arg);
 
-                        if (strv_extend(&arg_bind_user_groups, optarg) < 0)
+                        if (strv_extend(&arg_bind_user_groups, arg) < 0)
                                 return log_oom();
+                        break;
 
+                OPTION_GROUP("Integration"):
                         break;
 
-                case ARG_SYSTEM:
-                        arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
+                OPTION_LONG("forward-journal", "FILE|DIR", "Forward the VM's journal to the host"):
+                        r = parse_path_argument(arg, /* suppress_root= */ false, &arg_forward_journal);
+                        if (r < 0)
+                                return r;
                         break;
 
-                case ARG_USER:
-                        arg_runtime_scope = RUNTIME_SCOPE_USER;
+                OPTION_LONG("pass-ssh-key", "BOOL", "Create an SSH key to access the VM"):
+                        r = parse_boolean_argument("--pass-ssh-key=", arg, &arg_pass_ssh_key);
+                        if (r < 0)
+                                return r;
                         break;
 
-                case '?':
-                        return -EINVAL;
+                OPTION_LONG("ssh-key-type", "TYPE", "Choose what type of SSH key to pass"):
+                        if (!string_is_safe(arg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value for --ssh-key-type=: %s", arg);
 
-                default:
-                        assert_not_reached();
+                        r = free_and_strdup_warn(&arg_ssh_key_type, arg);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                OPTION_GROUP("Input/Output"):
+                        break;
+
+                OPTION_LONG("console", "MODE",
+                            "Console mode (interactive, native, gui, read-only or headless)"):
+                        arg_console_mode = console_mode_from_string(arg);
+                        if (arg_console_mode < 0)
+                                return log_error_errno(arg_console_mode, "Failed to parse specified console mode: %s", arg);
+                        break;
+
+                OPTION_LONG("console-transport", "TRANSPORT", "Console transport (virtio or serial)"):
+                        arg_console_transport = console_transport_from_string(arg);
+                        if (arg_console_transport < 0)
+                                return log_error_errno(arg_console_transport, "Failed to parse specified console transport: %s", arg);
+                        break;
+
+                OPTION_LONG("qemu-gui", NULL, /* help= */ NULL):  /* Compat alias */
+                        arg_console_mode = CONSOLE_GUI;
+                        break;
+
+                OPTION_LONG("background", "COLOR", "Set ANSI color for background"):
+                        r = parse_background_argument(arg, &arg_background);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                OPTION_GROUP("Credentials"):
+                        break;
+
+                OPTION_LONG("set-credential", "ID:VALUE", "Pass a credential with literal value to the VM"):
+                        r = machine_credential_set(&arg_credentials, arg);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                OPTION_LONG("load-credential", "ID:PATH",
+                            "Load credential for the VM from file or AF_UNIX stream socket"):
+                        r = machine_credential_load(&arg_credentials, arg);
+                        if (r < 0)
+                                return r;
+                        break;
                 }
 
         /* Drop duplicate --bind-user= and --bind-user-group= entries */
@@ -1058,8 +912,9 @@ static int parse_argv(int argc, char *argv[]) {
                 arg_uid_range = 0x10000;
         }
 
-        if (argc > optind) {
-                arg_kernel_cmdline_extra = strv_copy(argv + optind);
+        char **args = option_parser_get_args(&state);
+        if (!strv_isempty(args)) {
+                arg_kernel_cmdline_extra = strv_copy(args);
                 if (!arg_kernel_cmdline_extra)
                         return log_oom();
         }