diff --git a/TODO.md b/TODO.md index a178e8b9fb..d77225c572 100644 --- a/TODO.md +++ b/TODO.md @@ -2411,10 +2411,12 @@ SPDX-License-Identifier: LGPL-2.1-or-later - **systemd-measure tool:** - pre-calculate PCR 12 (command line) + PCR 13 (sysext) the same way we can precalculate PCR 11 - -- systemd-measure: add --pcrpkey-auto as an alternative to --pcrpkey=, where it - would just use the same public key specified with --public-key= (or the one - automatically derived from --private-key=). + - add --pcrpkey-auto as an alternative to --pcrpkey=, where it would just use + the same public key specified with --public-key= (or the one automatically + derived from --private-key=). + - allow multiple --initrd=, --efifw=, --dtbauto=, etc., params and pad and + concatenate the contents in the same way that ukify does, so we end up with + the expected measurement. - systemd-mount should only consider modern file systems when mounting, similar to systemd-dissect diff --git a/src/bootctl/bootctl.c b/src/bootctl/bootctl.c index 00ae512668..59a93d07c3 100644 --- a/src/bootctl/bootctl.c +++ b/src/bootctl/bootctl.c @@ -422,8 +422,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - OPTION_GROUP("Block Device Discovery Commands"): - break; + OPTION_GROUP("Block Device Discovery Commands"): {} OPTION('p', "print-esp-path", NULL, "Print path to the EFI System Partition mount point"): {} OPTION_LONG("print-path", NULL, /* help= */ NULL): /* Compatibility alias */ @@ -455,8 +454,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { arg_print_efi_architecture = true; break; - OPTION_GROUP("Options"): - break; + OPTION_GROUP("Options"): {} OPTION_COMMON_HELP: return help(); @@ -605,16 +603,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return r; break; - OPTION_LONG("private-key", "PATH|URI", - "Private key for Secure Boot auto-enrollment"): + OPTION_COMMON_PRIVATE_KEY("Private key for Secure Boot auto-enrollment"): r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; break; - OPTION_LONG("private-key-source", "SOURCE", - "Specify how to use the private key " - "(file, provider:PROVIDER, engine:ENGINE)"): + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument(arg, &arg_private_key_source, &arg_private_key_source_type); @@ -622,18 +617,13 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { return r; break; - OPTION_LONG("certificate", "PATH|URI", - "PEM certificate to use when setting up Secure Boot auto-enrollment, " - "or a provider specific designation if --certificate-source= is used"): + OPTION_COMMON_CERTIFICATE("PEM certificate to use when setting up Secure Boot auto-enrollment"): r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - OPTION_LONG("certificate-source", "SOURCE", - "Specify how to interpret the certificate from --certificate=. " - "Allows the certificate to be loaded from an OpenSSL provider " - "(file, provider:PROVIDER)"): + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument(arg, &arg_certificate_source, &arg_certificate_source_type); diff --git a/src/fundamental/uki.h b/src/fundamental/uki.h index 8d67b13b8b..627538a0eb 100644 --- a/src/fundamental/uki.h +++ b/src/fundamental/uki.h @@ -15,7 +15,7 @@ typedef enum UnifiedSection { UNIFIED_SECTION_DTB, UNIFIED_SECTION_UNAME, UNIFIED_SECTION_SBAT, - UNIFIED_SECTION_PCRSIG, + UNIFIED_SECTION_PCRSIG, /* This is is not measured actually */ UNIFIED_SECTION_PCRPKEY, UNIFIED_SECTION_PROFILE, UNIFIED_SECTION_DTBAUTO, diff --git a/src/imds/imdsd.c b/src/imds/imdsd.c index 8ab3498656..b3d1771161 100644 --- a/src/imds/imdsd.c +++ b/src/imds/imdsd.c @@ -2348,8 +2348,7 @@ static int parse_argv(int argc, char *argv[]) { break; /* The following all configure endpoint information explicitly */ - OPTION_GROUP("Manual Endpoint Configuration"): - break; + OPTION_GROUP("Manual Endpoint Configuration"): {} OPTION_LONG("vendor", "VENDOR", "Specify IMDS vendor literally"): if (isempty(arg)) { diff --git a/src/libudev/test-libudev.c b/src/libudev/test-libudev.c index f15cbc3a91..48d5ac4d9d 100644 --- a/src/libudev/test-libudev.c +++ b/src/libudev/test-libudev.c @@ -1,15 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include #include "devnum-util.h" #include "fd-util.h" +#include "format-table.h" #include "libudev-list-internal.h" #include "log.h" #include "main-func.h" #include "libudev-util.h" +#include "options.h" #include "string-util.h" #include "tests.h" #include "version.h" @@ -404,52 +405,57 @@ static void test_list(void) { assert_se(!udev_list_entry_get_by_name(e, "ccc")); } -static int parse_args(int argc, char *argv[], const char **syspath, const char **subsystem) { - static const struct option options[] = { - { "syspath", required_argument, NULL, 'p' }, - { "subsystem", required_argument, NULL, 's' }, - { "debug", no_argument, NULL, 'd' }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "monitor", no_argument, NULL, 'm' }, - {} - }; - int c; +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n\n", program_invocation_short_name); + + r = table_print_or_warn(options); + if (r < 0) + return r; + + return 0; +} + +static int parse_args(int argc, char *argv[], const char **syspath, const char **subsystem) { + assert(argc >= 0); + assert(argv); assert(syspath); assert(subsystem); - while ((c = getopt_long(argc, argv, "p:s:dhVm", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'p': - *syspath = optarg; - break; - case 's': - *subsystem = optarg; - break; + OPTION_COMMON_HELP: + return help(); - case 'd': - log_set_max_level(LOG_DEBUG); - break; - - case 'h': - printf("--debug --syspath= --subsystem= --help\n"); - return 0; - - case 'V': + OPTION('V', "version", NULL, "Show package version"): printf("%s\n", GIT_VERSION); return 0; - case 'm': - arg_monitor = true; + OPTION('p', "syspath", "PATH", "Syspath to test"): + *syspath = arg; break; - case '?': - return -EINVAL; + OPTION('s', "subsystem", "SUBSYSTEM", "Subsystem to enumerate"): + *subsystem = arg; + break; - default: - assert_not_reached(); + OPTION('d', "debug", NULL, "Enable debug logging"): + log_set_max_level(LOG_DEBUG); + break; + + OPTION('m', "monitor", NULL, "Run monitor test"): + arg_monitor = true; + break; } return 1; diff --git a/src/measure/measure-tool.c b/src/measure/measure-tool.c index 6a2e2994f9..09c04d888c 100644 --- a/src/measure/measure-tool.c +++ b/src/measure/measure-tool.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "sd-json.h" @@ -12,10 +11,12 @@ #include "efivars.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "hexdecoct.h" #include "log.h" #include "main-func.h" #include "openssl-util.h" +#include "options.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -65,71 +66,55 @@ STATIC_DESTRUCTOR_REGISTER(arg_sections, free_sections); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL, *options2 = NULL; int r; r = terminal_urlify_man("systemd-measure", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sPre-calculate and sign PCR hash for a unified kernel image (UKI).%6$s\n" - "\n%3$sCommands:%4$s\n" - " status Show current PCR values\n" - " calculate Calculate expected PCR values\n" - " sign Calculate and sign expected PCR values\n" - " policy-digest Calculate expected TPM2 policy digests\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --no-pager Do not pipe output into a pager\n" - " -c --current Use current PCR values\n" - " --phase=PHASE Specify a boot phase to sign for\n" - " --bank=DIGEST Select TPM bank (SHA1, SHA256, SHA384, SHA512)\n" - " --tpm2-device=PATH Use specified TPM2 device\n" - " --private-key=KEY Private key (PEM) to sign with\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used for signing\n" - " --public-key=KEY Public key (PEM) to validate against\n" - " --certificate=PATH|URI\n" - " PEM certificate to use for signing, or a provider\n" - " specific designation if --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - " --json=MODE Output as JSON\n" - " -j Same as --json=pretty on tty, --json=short otherwise\n" - " --append=PATH Load specified JSON signature, and append new signature to it\n" - "\n%3$sUKI PE Section Options:%4$s %3$sUKI PE Section%4$s\n" - " --linux=PATH Path to Linux kernel image file %7$s .linux\n" - " --osrel=PATH Path to os-release file %7$s .osrel\n" - " --cmdline=PATH Path to file with kernel command line %7$s .cmdline\n" - " --initrd=PATH Path to initrd image file %7$s .initrd\n" - " --ucode=PATH Path to microcode image file %7$s .ucode\n" - " --splash=PATH Path to splash bitmap file %7$s .splash\n" - " --dtb=PATH Path to DeviceTree file %7$s .dtb\n" - " --dtbauto=PATH Path to DeviceTree file for auto selection %7$s .dtbauto\n" - " --uname=PATH Path to 'uname -r' file %7$s .uname\n" - " --sbat=PATH Path to SBAT file %7$s .sbat\n" - " --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n" - " --profile=PATH Path to profile file %7$s .profile\n" - " --hwids=PATH Path to HWIDs file %7$s .hwids\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal(), - glyph(GLYPH_ARROW_RIGHT)); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + r = option_parser_get_help_table_group("UKI PE Section Options", &options2); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options, options2); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sPre-calculate and sign PCR hash for a unified kernel image (UKI).%s\n" + "\n%sCommands:%s\n", + program_invocation_short_name, + ansi_highlight(), ansi_normal(), + ansi_underline(), ansi_normal()); + + r = table_print_or_warn(verbs); + 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("\n%sUKI PE Section Options:%s\n", ansi_underline(), ansi_normal()); + + r = table_print_or_warn(options2); + if (r < 0) + return r; + + printf("\nSee the %s for details.\n", link); return 0; } -static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} +VERB_COMMON_HELP_HIDDEN(help); static char *normalize_phase(const char *s) { _cleanup_strv_free_ char **l = NULL; @@ -146,187 +131,39 @@ static char *normalize_phase(const char *s) { return strv_join(strv_remove(l, ""), ":"); } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - _ARG_SECTION_FIRST, - ARG_LINUX = _ARG_SECTION_FIRST, - ARG_OSREL, - ARG_CMDLINE, - ARG_INITRD, - ARG_UCODE, - ARG_SPLASH, - ARG_DTB, - ARG_UNAME, - ARG_SBAT, - _ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */ - ARG_PCRPKEY, - ARG_PROFILE, - ARG_DTBAUTO, - ARG_HWIDS, - _ARG_SECTION_LAST, - ARG_EFIFW = _ARG_SECTION_LAST, - ARG_BANK, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_PUBLIC_KEY, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_TPM2_DEVICE, - ARG_JSON, - ARG_PHASE, - ARG_APPEND, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "version", no_argument, NULL, ARG_VERSION }, - { "linux", required_argument, NULL, ARG_LINUX }, - { "osrel", required_argument, NULL, ARG_OSREL }, - { "cmdline", required_argument, NULL, ARG_CMDLINE }, - { "initrd", required_argument, NULL, ARG_INITRD }, - { "ucode", required_argument, NULL, ARG_UCODE }, - { "splash", required_argument, NULL, ARG_SPLASH }, - { "dtb", required_argument, NULL, ARG_DTB }, - { "dtbauto", required_argument, NULL, ARG_DTBAUTO }, - { "uname", required_argument, NULL, ARG_UNAME }, - { "sbat", required_argument, NULL, ARG_SBAT }, - { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, - { "profile", required_argument, NULL, ARG_PROFILE }, - { "hwids", required_argument, NULL, ARG_HWIDS }, - { "current", no_argument, NULL, 'c' }, - { "bank", required_argument, NULL, ARG_BANK }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "public-key", required_argument, NULL, ARG_PUBLIC_KEY }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "json", required_argument, NULL, ARG_JSON }, - { "phase", required_argument, NULL, ARG_PHASE }, - { "append", required_argument, NULL, ARG_APPEND }, - {} - }; - - int c, r; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - /* Make sure the arguments list and the section list, stays in sync */ - assert_cc(_ARG_SECTION_FIRST + _UNIFIED_SECTION_MAX == _ARG_SECTION_LAST + 1); + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + int r; - while ((c = getopt_long(argc, argv, "hjc", options, NULL)) >= 0) + FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case _ARG_SECTION_FIRST..._ARG_SECTION_LAST: { - UnifiedSection section = c - _ARG_SECTION_FIRST; - - r = parse_path_argument(optarg, /* suppress_root= */ false, arg_sections + section); - if (r < 0) - return r; - break; - } - - case 'c': + OPTION('c', "current", NULL, + "Use current PCR values"): arg_current = true; break; - case ARG_BANK: { - const EVP_MD *implementation; - - implementation = EVP_get_digestbyname(optarg); - if (!implementation) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", optarg); - - if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0) - return log_oom(); - - break; - } - - case ARG_PRIVATE_KEY: - r = free_and_strdup_warn(&arg_private_key, optarg); - if (r < 0) - return r; - - break; - - case ARG_PRIVATE_KEY_SOURCE: - r = parse_openssl_key_source_argument( - optarg, - &arg_private_key_source, - &arg_private_key_source_type); - if (r < 0) - return r; - - break; - - case ARG_PUBLIC_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_public_key); - if (r < 0) - return r; - - break; - - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); - if (r < 0) - return r; - break; - - case ARG_CERTIFICATE_SOURCE: - r = parse_openssl_certificate_source_argument( - optarg, - &arg_certificate_source, - &arg_certificate_source_type); - if (r < 0) - return r; - break; - - case ARG_TPM2_DEVICE: { - _cleanup_free_ char *device = NULL; - - if (streq(optarg, "list")) - return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - - if (!streq(optarg, "auto")) { - device = strdup(optarg); - if (!device) - return log_oom(); - } - - free_and_replace(arg_tpm2_device, device); - break; - } - - case 'j': - arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; - break; - - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); - if (r <= 0) - return r; - - break; - - case ARG_PHASE: { + OPTION_LONG("phase", "PHASE", + "Specify a boot phase to sign for"): { char *n; - n = normalize_phase(optarg); + n = normalize_phase(arg); if (!n) return log_oom(); @@ -337,18 +174,135 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_APPEND: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_append); + OPTION_LONG("bank", "DIGEST", + "Select TPM bank (SHA1, SHA256, SHA384, SHA512)"): { + const EVP_MD *implementation; + + implementation = EVP_get_digestbyname(arg); + if (!implementation) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", arg); + + if (strv_extend(&arg_banks, EVP_MD_name(implementation)) < 0) + return log_oom(); + + break; + } + + OPTION_LONG("tpm2-device", "PATH", + "Use specified TPM2 device"): { + _cleanup_free_ char *device = NULL; + + if (streq(arg, "list")) + return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); + + if (!streq(arg, "auto")) { + device = strdup(arg); + if (!device) + return log_oom(); + } + + free_and_replace(arg_tpm2_device, device); + break; + } + + OPTION_COMMON_PRIVATE_KEY("Private key (PEM) to sign with"): + r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; break; - case '?': - return -EINVAL; + OPTION_COMMON_PRIVATE_KEY_SOURCE: + r = parse_openssl_key_source_argument( + arg, + &arg_private_key_source, + &arg_private_key_source_type); + if (r < 0) + return r; - default: - assert_not_reached(); + break; + + OPTION_LONG("public-key", "KEY", + "Public key (PEM) to validate against"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_public_key); + if (r < 0) + return r; + + break; + + OPTION_COMMON_CERTIFICATE("PEM certificate to use for signing"): + r = free_and_strdup_warn(&arg_certificate, arg); + if (r < 0) + return r; + break; + + OPTION_COMMON_CERTIFICATE_SOURCE: + r = parse_openssl_certificate_source_argument( + arg, + &arg_certificate_source, + &arg_certificate_source_type); + if (r < 0) + return r; + break; + + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); + if (r <= 0) + return r; + + break; + + OPTION_COMMON_LOWERCASE_J: + arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO; + break; + + OPTION_LONG("append", "PATH", + "Load specified JSON signature, and append new signature to it"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_append); + if (r < 0) + return r; + + break; + + OPTION_GROUP("UKI PE Section Options"): {} + + OPTION_LONG_DATA("linux", "PATH", UNIFIED_SECTION_LINUX, + "Path to Linux kernel image file (→ .linux)"): {} + OPTION_LONG_DATA("osrel", "PATH", UNIFIED_SECTION_OSREL, + "Path to os-release file (→ .osrel)"): {} + OPTION_LONG_DATA("cmdline", "PATH", UNIFIED_SECTION_CMDLINE, + "Path to file with kernel command line (→ .cmdline)"): {} + OPTION_LONG_DATA("initrd", "PATH", UNIFIED_SECTION_INITRD, + "Path to initrd image file (→ .initrd)"): {} + OPTION_LONG_DATA("ucode", "PATH", UNIFIED_SECTION_UCODE, + "Path to microcode image file (→ .ucode)"): {} + OPTION_LONG_DATA("splash", "PATH", UNIFIED_SECTION_SPLASH, + "Path to splash bitmap file (→ .splash)"): {} + OPTION_LONG_DATA("dtb", "PATH", UNIFIED_SECTION_DTB, + "Path to DeviceTree file (→ .dtb)"): {} + OPTION_LONG_DATA("dtbauto", "PATH", UNIFIED_SECTION_DTBAUTO, + "Path to DeviceTree file for auto selection (→ .dtbauto)"): {} + OPTION_LONG_DATA("uname", "PATH", UNIFIED_SECTION_UNAME, + "Path to 'uname -r' file (→ .uname)"): {} + OPTION_LONG_DATA("sbat", "PATH", UNIFIED_SECTION_SBAT, + "Path to SBAT file (→ .sbat)"): {} + /* The .pcrsig section is not input for signing, hence not actually an argument here */ + OPTION_LONG_DATA("pcrpkey", "PATH", UNIFIED_SECTION_PCRPKEY, + "Path to public key for PCR signatures (→ .pcrpkey)"): {} + OPTION_LONG_DATA("profile", "PATH", UNIFIED_SECTION_PROFILE, + "Path to profile file (→ .profile)"): {} + OPTION_LONG_DATA("hwids", "PATH", UNIFIED_SECTION_HWIDS, + "Path to HWIDs file (→ .hwids)"): {} + OPTION_LONG_DATA("efifw", "PATH", UNIFIED_SECTION_EFIFW, + "Path to EFI firmware file (→ .efifw)"): {} + /* Make sure that if new sections are added, the list here is updated. */ + assert_cc(UNIFIED_SECTION_EFIFW + 1 == _UNIFIED_SECTION_MAX); + assert(opt->data < _UNIFIED_SECTION_MAX); + + r = parse_path_argument(arg, /* suppress_root= */ false, arg_sections + opt->data); + if (r < 0) + return r; + break; } if (arg_public_key && arg_certificate) @@ -390,9 +344,160 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); log_debug("Measuring boot phases: %s", j); + + *ret_args = option_parser_get_args(&state); return 1; } +static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) { + _cleanup_free_ char *s = NULL; + uint32_t v; + int r; + + r = efi_get_variable_string(varname, &s); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to read EFI variable '%s': %m", varname); + + r = safe_atou32(s, &v); + if (r < 0) + return log_error_errno(r, "Failed to parse EFI variable '%s': %s", varname, s); + + if (pcr != v) + log_warning("PCR number reported by stub for %s (%" PRIu32 ") different from our expectation (%" PRIu32 ").\n" + "The measurements are likely inconsistent.", description, v, pcr); + + return 0; +} + +static int validate_stub(void) { + uint64_t features; + bool found = false; + int r; + + if (!tpm2_is_fully_supported()) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, system lacks full TPM2 support."); + + r = efi_stub_get_features(&features); + if (r < 0) + return log_error_errno(r, "Unable to get stub features: %m"); + + if (!FLAGS_SET(features, EFI_STUB_FEATURE_THREE_PCRS)) + log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n" + "The PCR measurements seen are unlikely to be valid."); + + r = compare_reported_pcr_nr(TPM2_PCR_KERNEL_BOOT, EFI_LOADER_VARIABLE_STR("StubPcrKernelImage"), "kernel image"); + if (r < 0) + return r; + + STRV_FOREACH(bank, arg_banks) { + _cleanup_free_ char *b = NULL, *p = NULL; + + b = strdup(*bank); + if (!b) + return log_oom(); + + if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/", ascii_strlower(b)) < 0) + return log_oom(); + + if (access(p, F_OK) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to detect if '%s' exists: %m", b); + } else + found = true; + } + + if (!found) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "None of the select PCR banks appear to exist."); + + return 0; +} + +VERB(verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT, + "Show current PCR values"); +static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + r = validate_stub(); + if (r < 0) + return r; + + STRV_FOREACH(bank, arg_banks) { + _cleanup_free_ char *b = NULL, *p = NULL, *s = NULL; + _cleanup_free_ void *h = NULL; + size_t l; + + b = strdup(*bank); + if (!b) + return log_oom(); + + if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%" PRIu32, ascii_strlower(b), (uint32_t) TPM2_PCR_KERNEL_BOOT) < 0) + return log_oom(); + + r = read_virtual_file(p, 4096, &s, NULL); + if (r == -ENOENT) + continue; + if (r < 0) + return log_error_errno(r, "Failed to read '%s': %m", p); + + r = unhexmem(strstrip(s), &h, &l); + if (r < 0) + return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); + + if (!sd_json_format_enabled(arg_json_format_flags)) { + _cleanup_free_ char *f = hexmem(h, l); + if (!f) + return log_oom(); + + if (bank == arg_banks) { + /* before the first line for each PCR, write a short descriptive text to + * stderr, and leave the primary content on stdout */ + fflush(stdout); + fprintf(stderr, "%s# PCR[%" PRIu32 "] %s%s%s\n", + ansi_grey(), + (uint32_t) TPM2_PCR_KERNEL_BOOT, + tpm2_pcr_index_to_string(TPM2_PCR_KERNEL_BOOT), + memeqzero(h, l) ? " (NOT SET!)" : "", + ansi_normal()); + fflush(stderr); + } + + printf("%" PRIu32 ":%s=%s\n", (uint32_t) TPM2_PCR_KERNEL_BOOT, b, f); + + } else { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *bv = NULL, *a = NULL; + + r = sd_json_buildo( + &bv, + SD_JSON_BUILD_PAIR_INTEGER("pcr", TPM2_PCR_KERNEL_BOOT), + SD_JSON_BUILD_PAIR_HEX("hash", h, l)); + if (r < 0) + return log_error_errno(r, "Failed to build JSON object: %m"); + + a = sd_json_variant_ref(sd_json_variant_by_key(v, b)); + + r = sd_json_variant_append_array(&a, bv); + if (r < 0) + return log_error_errno(r, "Failed to append PCR entry to JSON array: %m"); + + r = sd_json_variant_set_field(&v, b, a); + if (r < 0) + return log_error_errno(r, "Failed to add bank info to object: %m"); + } + } + + if (sd_json_format_enabled(arg_json_format_flags)) { + if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) + pager_open(arg_pager_flags); + + sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); + } + + return 0; +} + /* The PCR 11 state for one specific bank */ typedef struct PcrState { char *bank; @@ -711,6 +816,8 @@ static void pcr_states_restore(PcrState *pcr_states, size_t n) { } } +VERB_NOARG(verb_calculate, "calculate", + "Calculate expected PCR values"); static int verb_calculate(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; @@ -992,186 +1099,29 @@ static int build_policy_digest(bool sign) { return 0; } +VERB_NOARG(verb_sign, "sign", + "Calculate and sign expected PCR values"); static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { return build_policy_digest(/* sign= */ true); } +VERB_NOARG(verb_policy_digest, "policy-digest", + "Calculate expected TPM2 policy digests"); static int verb_policy_digest(int argc, char *argv[], uintptr_t _data, void *userdata) { return build_policy_digest(/* sign= */ false); } -static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) { - _cleanup_free_ char *s = NULL; - uint32_t v; - int r; - - r = efi_get_variable_string(varname, &s); - if (r == -ENOENT) - return 0; - if (r < 0) - return log_error_errno(r, "Failed to read EFI variable '%s': %m", varname); - - r = safe_atou32(s, &v); - if (r < 0) - return log_error_errno(r, "Failed to parse EFI variable '%s': %s", varname, s); - - if (pcr != v) - log_warning("PCR number reported by stub for %s (%" PRIu32 ") different from our expectation (%" PRIu32 ").\n" - "The measurements are likely inconsistent.", description, v, pcr); - - return 0; -} - -static int validate_stub(void) { - uint64_t features; - bool found = false; - int r; - - if (!tpm2_is_fully_supported()) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, system lacks full TPM2 support."); - - r = efi_stub_get_features(&features); - if (r < 0) - return log_error_errno(r, "Unable to get stub features: %m"); - - if (!FLAGS_SET(features, EFI_STUB_FEATURE_THREE_PCRS)) - log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n" - "The PCR measurements seen are unlikely to be valid."); - - r = compare_reported_pcr_nr(TPM2_PCR_KERNEL_BOOT, EFI_LOADER_VARIABLE_STR("StubPcrKernelImage"), "kernel image"); - if (r < 0) - return r; - - STRV_FOREACH(bank, arg_banks) { - _cleanup_free_ char *b = NULL, *p = NULL; - - b = strdup(*bank); - if (!b) - return log_oom(); - - if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/", ascii_strlower(b)) < 0) - return log_oom(); - - if (access(p, F_OK) < 0) { - if (errno != ENOENT) - return log_error_errno(errno, "Failed to detect if '%s' exists: %m", b); - } else - found = true; - } - - if (!found) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "None of the select PCR banks appear to exist."); - - return 0; -} - -static int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - int r; - - r = validate_stub(); - if (r < 0) - return r; - - STRV_FOREACH(bank, arg_banks) { - _cleanup_free_ char *b = NULL, *p = NULL, *s = NULL; - _cleanup_free_ void *h = NULL; - size_t l; - - b = strdup(*bank); - if (!b) - return log_oom(); - - if (asprintf(&p, "/sys/class/tpm/tpm0/pcr-%s/%" PRIu32, ascii_strlower(b), (uint32_t) TPM2_PCR_KERNEL_BOOT) < 0) - return log_oom(); - - r = read_virtual_file(p, 4096, &s, NULL); - if (r == -ENOENT) - continue; - if (r < 0) - return log_error_errno(r, "Failed to read '%s': %m", p); - - r = unhexmem(strstrip(s), &h, &l); - if (r < 0) - return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); - - if (!sd_json_format_enabled(arg_json_format_flags)) { - _cleanup_free_ char *f = NULL; - - f = hexmem(h, l); - if (!h) - return log_oom(); - - if (bank == arg_banks) { - /* before the first line for each PCR, write a short descriptive text to - * stderr, and leave the primary content on stdout */ - fflush(stdout); - fprintf(stderr, "%s# PCR[%" PRIu32 "] %s%s%s\n", - ansi_grey(), - (uint32_t) TPM2_PCR_KERNEL_BOOT, - tpm2_pcr_index_to_string(TPM2_PCR_KERNEL_BOOT), - memeqzero(h, l) ? " (NOT SET!)" : "", - ansi_normal()); - fflush(stderr); - } - - printf("%" PRIu32 ":%s=%s\n", (uint32_t) TPM2_PCR_KERNEL_BOOT, b, f); - - } else { - _cleanup_(sd_json_variant_unrefp) sd_json_variant *bv = NULL, *a = NULL; - - r = sd_json_buildo( - &bv, - SD_JSON_BUILD_PAIR_INTEGER("pcr", TPM2_PCR_KERNEL_BOOT), - SD_JSON_BUILD_PAIR_HEX("hash", h, l)); - if (r < 0) - return log_error_errno(r, "Failed to build JSON object: %m"); - - a = sd_json_variant_ref(sd_json_variant_by_key(v, b)); - - r = sd_json_variant_append_array(&a, bv); - if (r < 0) - return log_error_errno(r, "Failed to append PCR entry to JSON array: %m"); - - r = sd_json_variant_set_field(&v, b, a); - if (r < 0) - return log_error_errno(r, "Failed to add bank info to object: %m"); - } - } - - if (sd_json_format_enabled(arg_json_format_flags)) { - if (arg_json_format_flags & (SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_PRETTY_AUTO)) - pager_open(arg_pager_flags); - - sd_json_variant_dump(v, arg_json_format_flags, stdout, NULL); - } - - return 0; -} - -static int measure_main(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status }, - { "calculate", VERB_ANY, 1, 0, verb_calculate }, - { "policy-digest", VERB_ANY, 1, 0, verb_policy_digest }, - { "sign", VERB_ANY, 1, 0, verb_sign }, - {} - }; - - 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 measure_main(argc, argv); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/network/generator/network-generator-main.c b/src/network/generator/network-generator-main.c index d64f65bc44..f9acf40500 100644 --- a/src/network/generator/network-generator-main.c +++ b/src/network/generator/network-generator-main.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" @@ -8,15 +7,18 @@ #include "creds-util.h" #include "errno-util.h" #include "fd-util.h" +#include "format-table.h" #include "fs-util.h" #include "generator.h" #include "log.h" #include "main-func.h" #include "mkdir.h" #include "network-generator.h" +#include "options.h" #include "path-util.h" #include "proc-cmdline.h" #include "string-util.h" +#include "strv.h" #define NETWORK_UNIT_DIRECTORY "/run/systemd/network/" @@ -148,52 +150,47 @@ static int context_save(Context *context) { } static int help(void) { - printf("%s [OPTIONS...] [-- KERNEL_CMDLINE]\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --root=PATH Operate on an alternate filesystem root\n", + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...] [-- KERNEL_CMDLINE]\n\n", program_invocation_short_name); + r = table_print_or_warn(options); + if (r < 0) + return r; + return 0; } -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_ROOT, - }; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "root", required_argument, NULL, ARG_ROOT }, - {}, - }; - int c; - +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + 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: - arg_root = optarg; + OPTION_LONG("root", "PATH", + "Operate on an alternate filesystem root"): + arg_root = arg; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } + *ret_args = option_parser_get_args(&state); return 1; } @@ -205,20 +202,21 @@ static int run(int argc, char *argv[]) { umask(0022); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - if (optind >= argc) { + if (strv_isempty(args)) { r = proc_cmdline_parse(parse_cmdline_item, &context, 0); if (r < 0) return log_warning_errno(r, "Failed to parse kernel command line: %m"); } else { - for (int i = optind; i < argc; i++) { + STRV_FOREACH(a, args) { _cleanup_free_ char *word = NULL; char *value; - word = strdup(argv[i]); + word = strdup(*a); if (!word) return log_oom(); diff --git a/src/repart/repart.c b/src/repart/repart.c index 821868b363..e585313713 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include #include @@ -61,6 +60,7 @@ #include "mountpoint-util.h" #include "nulstr-util.h" #include "openssl-util.h" +#include "options.h" #include "parse-argument.h" #include "parse-helpers.h" #include "parse-util.h" @@ -9627,355 +9627,157 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] [DEVICE]\n" - "\n%5$sGrow and add partitions to a partition table, and generate disk images (DDIs).%6$s\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - "\n%3$sOperation:%4$s\n" - " --dry-run=BOOL Whether to run dry-run operation\n" - " --empty=MODE One of refuse, allow, require, force, create; controls\n" - " how to handle empty disks lacking partition tables\n" - " --offline=BOOL Whether to build the image offline\n" - " --discard=BOOL Whether to discard backing blocks for new partitions\n" - " --sector-size=SIZE Set the logical sector size for the image\n" - " --grain-size=BYTES Set the grain size for partition alignment\n" - " --architecture=ARCH Set the generic architecture for the image\n" - " --size=BYTES Grow loopback file to specified size\n" - " --seed=UUID 128-bit seed UUID to derive all UUIDs from\n" - " --split=BOOL Whether to generate split artifacts\n" - "\n%3$sOutput:%4$s\n" - " --pretty=BOOL Whether to show pretty summary before doing changes\n" - " --json=pretty|short|off\n" - " Generate JSON output\n" - "\n%3$sFactory Reset:%4$s\n" - " --factory-reset=BOOL Whether to remove data partitions before recreating\n" - " them\n" - " --can-factory-reset Test whether factory reset is defined\n" - "\n%3$sConfiguration & Image Control:%4$s\n" - " --root=PATH Operate relative to root path\n" - " --image=PATH Operate relative to image file\n" - " --image-policy=POLICY\n" - " Specify disk image dissection policy\n" - " --definitions=DIR Find partition definitions in specified directory\n" - " --list-devices List candidate block devices to operate on\n" - "\n%3$sVerity:%4$s\n" - " --private-key=PATH|URI\n" - " Private key to use when generating verity roothash\n" - " signatures, or an engine or provider specific\n" - " designation if --private-key-source= is used\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used when generating\n" - " verity roothash signatures\n" - " --certificate=PATH|URI\n" - " PEM certificate to use when generating verity roothash\n" - " signatures, or a provider specific designation if\n" - " --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - " --join-signature=HASH:SIG\n" - " Specify root hash and pkcs7 signature of root hash for\n" - " verity as a tuple of hex encoded hash and a DER\n" - " encoded PKCS7, either as a path to a file or as an\n" - " ASCII base64 encoded string prefixed by 'base64:'\n" - "\n%3$sEncryption:%4$s\n" - " --key-file=PATH Key to use when encrypting partitions\n" - " --tpm2-device=PATH Path to TPM2 device node to use\n" - " --tpm2-device-key=PATH\n" - " Enroll a TPM2 device using its public key\n" - " --tpm2-seal-key-handle=HANDLE\n" - " Specify handle of key to use for sealing\n" - " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" - " TPM2 PCR indexes to use for TPM2 enrollment\n" - " --tpm2-public-key=PATH\n" - " Enroll signed TPM2 PCR policy against PEM public key\n" - " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n" - " Enroll signed TPM2 PCR policy for specified TPM2 PCRs\n" - " --tpm2-pcrlock=PATH\n" - " Specify pcrlock policy to lock against\n" - "\n%3$sPartition Control:%4$s\n" - " --include-partitions=PARTITION1,PARTITION2,PARTITION3,…\n" - " Ignore partitions not of the specified types\n" - " --exclude-partitions=PARTITION1,PARTITION2,PARTITION3,…\n" - " Ignore partitions of the specified types\n" - " --defer-partitions=PARTITION1,PARTITION2,PARTITION3,…\n" - " Take partitions of the specified types into account\n" - " but don't populate them yet\n" - " --defer-partitions-empty=yes\n" - " Defer all partitions marked for formatting as empty\n" - " --defer-partitions-factory-reset=yes\n" - " Defer all partitions marked for factory reset\n" - "\n%3$sCopying:%4$s\n" - " -s --copy-source=PATH Specify the primary source tree to copy files from\n" - " --copy-from=IMAGE Copy partitions from the given image(s)\n" - "\n%3$sDDI Profile:%4$s\n" - " -S --make-ddi=sysext Make a system extension DDI\n" - " -C --make-ddi=confext Make a configuration extension DDI\n" - " -P --make-ddi=portable Make a portable service DDI\n" - "\n%3$sAuxiliary Resource Generation:%4$s\n" - " --append-fstab=MODE One of no, auto, replace; controls how to join the\n" - " content of a pre-existing fstab with the generated one\n" - " --generate-fstab=PATH\n" - " Write fstab configuration to the given path\n" - " --generate-crypttab=PATH\n" - " Write crypttab configuration to the given path\n" - "\n%3$sEl Torito boot catalog:%4$s\n" - " --el-torito=BOOL Whether to add a boot catalog to boot the ESP\n" - " --el-torito-system=STRING\n" - " Set the system identifier in the ISO9660 descriptor\n" - " --el-torito-volume=STRING\n" - " Set the volume identifier in the ISO9660 descriptor\n" - " --el-torito-publisher=STRING\n" - " Set the publisher identifier in the ISO9660 descriptor\n" - "\nSee the %2$s for details.\n", + static const char *const option_groups[] = { + "Options", + "Operation", + "Output", + "Factory Reset", + "Configuration & Image Control", + "Verity", + "Encryption", + "Partition Control", + "Copying", + "DDI Profile", + "Auxiliary Resource Generation", + "El Torito boot catalog", + }; + + _cleanup_(table_unref_many) Table *option_tables[ELEMENTSOF(option_groups) + 1] = {}; + + for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { + r = option_parser_get_help_table_group(option_groups[i], &option_tables[i]); + if (r < 0) + return r; + } + + (void) table_sync_column_widths(0, + option_tables[0], option_tables[1], option_tables[2], + option_tables[3], option_tables[4], option_tables[5], + option_tables[6], option_tables[7], option_tables[8], + option_tables[9], option_tables[10], option_tables[11]); + + printf("%s [OPTIONS...] [DEVICE]\n" + "\n%sGrow and add partitions to a partition table, and generate disk images (DDIs).%s\n", program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), ansi_highlight(), ansi_normal()); + for (size_t i = 0; i < ELEMENTSOF(option_groups); i++) { + printf("\n%s%s:%s\n", ansi_underline(), option_groups[i], ansi_normal()); + + r = table_print_or_warn(option_tables[i]); + 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_DRY_RUN, - ARG_EMPTY, - ARG_DISCARD, - ARG_FACTORY_RESET, - ARG_CAN_FACTORY_RESET, - ARG_ROOT, - ARG_IMAGE, - ARG_IMAGE_POLICY, - ARG_SEED, - ARG_PRETTY, - ARG_DEFINITIONS, - ARG_SIZE, - ARG_JSON, - ARG_KEY_FILE, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_TPM2_DEVICE, - ARG_TPM2_DEVICE_KEY, - ARG_TPM2_SEAL_KEY_HANDLE, - ARG_TPM2_PCRS, - ARG_TPM2_PUBLIC_KEY, - ARG_TPM2_PUBLIC_KEY_PCRS, - ARG_TPM2_PCRLOCK, - ARG_SPLIT, - ARG_INCLUDE_PARTITIONS, - ARG_EXCLUDE_PARTITIONS, - ARG_DEFER_PARTITIONS, - ARG_DEFER_PARTITIONS_EMPTY, - ARG_DEFER_PARTITIONS_FACTORY_RESET, - ARG_SECTOR_SIZE, - ARG_GRAIN_SIZE, - ARG_SKIP_PARTITIONS, - ARG_ARCHITECTURE, - ARG_OFFLINE, - ARG_COPY_FROM, - ARG_MAKE_DDI, - ARG_APPEND_FSTAB, - ARG_GENERATE_FSTAB, - ARG_GENERATE_CRYPTTAB, - ARG_LIST_DEVICES, - ARG_JOIN_SIGNATURE, - ARG_ELTORITO, - ARG_ELTORITO_SYSTEM, - ARG_ELTORITO_VOLUME, - ARG_ELTORITO_PUBLISHER, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "dry-run", required_argument, NULL, ARG_DRY_RUN }, - { "empty", required_argument, NULL, ARG_EMPTY }, - { "discard", required_argument, NULL, ARG_DISCARD }, - { "factory-reset", required_argument, NULL, ARG_FACTORY_RESET }, - { "can-factory-reset", no_argument, NULL, ARG_CAN_FACTORY_RESET }, - { "root", required_argument, NULL, ARG_ROOT }, - { "image", required_argument, NULL, ARG_IMAGE }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "seed", required_argument, NULL, ARG_SEED }, - { "pretty", required_argument, NULL, ARG_PRETTY }, - { "definitions", required_argument, NULL, ARG_DEFINITIONS }, - { "size", required_argument, NULL, ARG_SIZE }, - { "json", required_argument, NULL, ARG_JSON }, - { "key-file", required_argument, NULL, ARG_KEY_FILE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY }, - { "tpm2-seal-key-handle", required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE }, - { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, - { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, - { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, - { "tpm2-pcrlock", required_argument, NULL, ARG_TPM2_PCRLOCK }, - { "split", required_argument, NULL, ARG_SPLIT }, - { "include-partitions", required_argument, NULL, ARG_INCLUDE_PARTITIONS }, - { "exclude-partitions", required_argument, NULL, ARG_EXCLUDE_PARTITIONS }, - { "defer-partitions", required_argument, NULL, ARG_DEFER_PARTITIONS }, - { "defer-partitions-empty", required_argument, NULL, ARG_DEFER_PARTITIONS_EMPTY }, - { "defer-partitions-factory-reset", required_argument, NULL, ARG_DEFER_PARTITIONS_FACTORY_RESET }, - { "sector-size", required_argument, NULL, ARG_SECTOR_SIZE }, - { "grain-size", required_argument, NULL, ARG_GRAIN_SIZE }, - { "architecture", required_argument, NULL, ARG_ARCHITECTURE }, - { "offline", required_argument, NULL, ARG_OFFLINE }, - { "copy-from", required_argument, NULL, ARG_COPY_FROM }, - { "copy-source", required_argument, NULL, 's' }, - { "make-ddi", required_argument, NULL, ARG_MAKE_DDI }, - { "append-fstab", required_argument, NULL, ARG_APPEND_FSTAB }, - { "generate-fstab", required_argument, NULL, ARG_GENERATE_FSTAB }, - { "generate-crypttab", required_argument, NULL, ARG_GENERATE_CRYPTTAB }, - { "list-devices", no_argument, NULL, ARG_LIST_DEVICES }, - { "join-signature", required_argument, NULL, ARG_JOIN_SIGNATURE }, - { "el-torito", required_argument, NULL, ARG_ELTORITO }, - { "el-torito-system", required_argument, NULL, ARG_ELTORITO_SYSTEM }, - { "el-torito-volume", required_argument, NULL, ARG_ELTORITO_VOLUME }, - { "el-torito-publisher", required_argument, NULL, ARG_ELTORITO_PUBLISHER }, - {} - }; - - bool auto_public_key_pcr_mask = true, auto_pcrlock = true; - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hs:SCP", options, NULL)) >= 0) + OptionParser state = { argc, argv }; + const char *arg; + bool auto_public_key_pcr_mask = true, auto_pcrlock = true; + int r; + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case 'h': + OPTION_GROUP("Options"): {} + + OPTION_COMMON_HELP: return help(); - case ARG_VERSION: + OPTION_COMMON_VERSION: return version(); - case ARG_NO_PAGER: + OPTION_COMMON_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; - case ARG_NO_LEGEND: + OPTION_COMMON_NO_LEGEND: arg_legend = false; break; - case ARG_DRY_RUN: - r = parse_boolean_argument("--dry-run=", optarg, &arg_dry_run); + OPTION_GROUP("Operation"): {} + + OPTION_LONG("dry-run", "BOOL", + "Whether to run dry-run operation"): + r = parse_boolean_argument("--dry-run=", arg, &arg_dry_run); if (r < 0) return r; break; - case ARG_EMPTY: - if (isempty(optarg)) { + OPTION_LONG("empty", "MODE", + "How to handle empty disks lacking partition tables (refuse, allow, require, force, create)"): + if (isempty(arg)) { arg_empty = EMPTY_UNSET; break; } - arg_empty = empty_mode_from_string(optarg); + arg_empty = empty_mode_from_string(arg); if (arg_empty < 0) - return log_error_errno(arg_empty, "Failed to parse --empty= parameter: %s", optarg); + return log_error_errno(arg_empty, "Failed to parse --empty= parameter: %s", arg); break; - case ARG_DISCARD: - r = parse_boolean_argument("--discard=", optarg, &arg_discard); + OPTION_LONG("offline", "BOOL", + "Whether to build the image offline"): + r = parse_tristate_argument_with_auto("--offline=", arg, &arg_offline); if (r < 0) return r; break; - case ARG_FACTORY_RESET: - r = parse_boolean_argument("--factory-reset=", optarg, NULL); - if (r < 0) - return r; - arg_factory_reset = r; - break; - - case ARG_CAN_FACTORY_RESET: - arg_can_factory_reset = true; - break; - - case ARG_ROOT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root); + OPTION_LONG("discard", "BOOL", + "Whether to discard backing blocks for new partitions"): + r = parse_boolean_argument("--discard=", arg, &arg_discard); if (r < 0) return r; break; - case ARG_IMAGE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + OPTION_LONG("sector-size", "SIZE", + "Set the logical sector size for the image"): + r = parse_sector_size(arg, &arg_sector_size); if (r < 0) return r; - arg_relax_copy_block_security = false; - break; - case ARG_IMAGE_POLICY: - r = parse_image_policy_argument(optarg, &arg_image_policy); + OPTION_LONG("grain-size", "BYTES", + "Set the grain size for partition alignment"): + r = parse_size(arg, 1024, &arg_grain_size); if (r < 0) - return r; - break; - - case ARG_SEED: - if (isempty(optarg)) { - arg_seed = SD_ID128_NULL; - arg_randomize = false; - } else if (streq(optarg, "random")) - arg_randomize = true; - else { - r = sd_id128_from_string(optarg, &arg_seed); - if (r < 0) - return log_error_errno(r, "Failed to parse seed: %s", optarg); - - arg_randomize = false; - } + return log_error_errno(r, "Failed to parse --grain-size= parameter: %s", arg); + if (arg_grain_size < 512 || !ISPOWEROF2(arg_grain_size)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Grain size must be a power of 2 >= 512."); break; - case ARG_PRETTY: - r = parse_boolean_argument("--pretty=", optarg, NULL); + OPTION_LONG("architecture", "ARCH", + "Set the generic architecture for the image"): + r = architecture_from_string(arg); if (r < 0) - return r; - arg_pretty = r; + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid architecture '%s'.", arg); + + arg_architecture = r; break; - case ARG_DEFINITIONS: { - _cleanup_free_ char *path = NULL; - r = parse_path_argument(optarg, false, &path); - if (r < 0) - return r; - if (strv_consume(&arg_definitions, TAKE_PTR(path)) < 0) - return log_oom(); - break; - } - - case ARG_SIZE: { + OPTION_LONG("size", "BYTES", + "Grow loopback file to specified size"): { uint64_t parsed, rounded; - if (streq(optarg, "auto")) { + if (streq(arg, "auto")) { arg_size = UINT64_MAX; arg_size_auto = true; break; } - r = parse_size(optarg, 1024, &parsed); + r = parse_size(arg, 1024, &parsed); if (r < 0) - return log_error_errno(r, "Failed to parse --size= parameter: %s", optarg); + return log_error_errno(r, "Failed to parse --size= parameter: %s", arg); rounded = round_up_size(parsed, 4096); if (rounded == 0) @@ -9992,59 +9794,168 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_JSON: - r = parse_json_argument(optarg, &arg_json_format_flags); + OPTION_LONG("seed", "UUID", + "128-bit seed UUID to derive all UUIDs from"): + if (isempty(arg)) { + arg_seed = SD_ID128_NULL; + arg_randomize = false; + } else if (streq(arg, "random")) + arg_randomize = true; + else { + r = sd_id128_from_string(arg, &arg_seed); + if (r < 0) + return log_error_errno(r, "Failed to parse seed: %s", arg); + + arg_randomize = false; + } + + break; + + OPTION_LONG("split", "BOOL", + "Whether to generate split artifacts"): + r = parse_boolean_argument("--split=", arg, NULL); + if (r < 0) + return r; + + arg_split = r; + break; + + OPTION_GROUP("Output"): {} + + OPTION_LONG("pretty", "BOOL", + "Whether to show pretty summary before doing changes"): + r = parse_boolean_argument("--pretty=", arg, NULL); + if (r < 0) + return r; + arg_pretty = r; + break; + + OPTION_COMMON_JSON: + r = parse_json_argument(arg, &arg_json_format_flags); if (r <= 0) return r; break; - case ARG_KEY_FILE: { - r = parse_key_file(optarg, &arg_key); + OPTION_GROUP("Factory Reset"): {} + + OPTION_LONG("factory-reset", "BOOL", + "Whether to remove data partitions before recreating them"): + r = parse_boolean_argument("--factory-reset=", arg, NULL); + if (r < 0) + return r; + arg_factory_reset = r; + break; + + OPTION_LONG("can-factory-reset", NULL, + "Test whether factory reset is defined"): + arg_can_factory_reset = true; + break; + + OPTION_GROUP("Configuration & Image Control"): {} + + OPTION_LONG("root", "PATH", + "Operate relative to root path"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); if (r < 0) return r; break; - } - case ARG_PRIVATE_KEY: { - r = free_and_strdup_warn(&arg_private_key, optarg); + OPTION_LONG("image", "PATH", + "Operate relative to image file"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image); + if (r < 0) + return r; + + arg_relax_copy_block_security = false; + + break; + + 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; + + OPTION_LONG("definitions", "DIR", + "Find partition definitions in specified directory"): { + _cleanup_free_ char *path = NULL; + r = parse_path_argument(arg, false, &path); + if (r < 0) + return r; + if (strv_consume(&arg_definitions, TAKE_PTR(path)) < 0) + return log_oom(); + break; } - case ARG_PRIVATE_KEY_SOURCE: + OPTION_LONG("list-devices", NULL, + "List candidate block devices to operate on"): + r = blockdev_list(BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING|BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_IGNORE_ZRAM, /* ret_devices= */ NULL, /* ret_n_devices= */ NULL); + if (r < 0) + return r; + + return 0; + + OPTION_GROUP("Verity"): {} + + OPTION_COMMON_PRIVATE_KEY("Private key to use when generating verity roothash signatures"): + r = free_and_strdup_warn(&arg_private_key, arg); + if (r < 0) + return r; + break; + + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - optarg, + arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) return r; break; - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); + OPTION_COMMON_CERTIFICATE("PEM certificate to use when generating verity roothash signatures"): + r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - optarg, + arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) return r; break; - case ARG_TPM2_DEVICE: { + OPTION_LONG("join-signature", "HASH:SIG", + "Specify root hash and pkcs7 signature of root hash for verity as a tuple of " + "hex-encoded hash and a DER-encoded PKCS7, either as a path to a file or as an " + "ASCII base64-encoded string prefixed by 'base64:'"): + r = parse_join_signature(arg, &arg_verity_settings); + if (r < 0) + return r; + break; + + OPTION_GROUP("Encryption"): {} + + OPTION_LONG("key-file", "PATH", + "Key to use when encrypting partitions"): + r = parse_key_file(arg, &arg_key); + if (r < 0) + return r; + break; + + OPTION_LONG("tpm2-device", "PATH", + "Path to TPM2 device node to use"): { _cleanup_free_ char *device = NULL; - if (streq(optarg, "list")) + if (streq(arg, "list")) return tpm2_list_devices(/* legend= */ true, /* quiet= */ false); - if (!streq(optarg, "auto")) { - device = strdup(optarg); + if (!streq(arg, "auto")) { + device = strdup(arg); if (!device) return log_oom(); } @@ -10054,64 +9965,65 @@ static int parse_argv(int argc, char *argv[]) { break; } - case ARG_TPM2_DEVICE_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_device_key); + OPTION_LONG("tpm2-device-key", "PATH", + "Enroll a TPM2 device using its public key"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_device_key); if (r < 0) return r; break; - case ARG_TPM2_SEAL_KEY_HANDLE: - r = safe_atou32_full(optarg, 16, &arg_tpm2_seal_key_handle); + OPTION_LONG("tpm2-seal-key-handle", "HANDLE", + "Specify handle of key to use for sealing"): + r = safe_atou32_full(arg, 16, &arg_tpm2_seal_key_handle); if (r < 0) - return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", optarg); + return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", arg); break; - case ARG_TPM2_PCRS: - r = tpm2_parse_pcr_argument_append(optarg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); + OPTION_LONG("tpm2-pcrs", "PCR1+PCR2+…", + "TPM2 PCR indexes to use for TPM2 enrollment"): + r = tpm2_parse_pcr_argument_append(arg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); if (r < 0) return r; break; - case ARG_TPM2_PUBLIC_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key); + OPTION_LONG("tpm2-public-key", "PATH", + "Enroll signed TPM2 PCR policy against PEM public key"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_public_key); if (r < 0) return r; break; - case ARG_TPM2_PUBLIC_KEY_PCRS: + OPTION_LONG("tpm2-public-key-pcrs", "PCR1+PCR2+…", + "Enroll signed TPM2 PCR policy for specified TPM2 PCRs"): auto_public_key_pcr_mask = false; - r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); + r = tpm2_parse_pcr_argument_to_mask(arg, &arg_tpm2_public_key_pcr_mask); if (r < 0) return r; break; - case ARG_TPM2_PCRLOCK: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_pcrlock); + OPTION_LONG("tpm2-pcrlock", "PATH", + "Specify pcrlock policy to lock against"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_pcrlock); if (r < 0) return r; auto_pcrlock = false; break; - case ARG_SPLIT: - r = parse_boolean_argument("--split=", optarg, NULL); - if (r < 0) - return r; + OPTION_GROUP("Partition Control"): {} - arg_split = r; - break; - - case ARG_INCLUDE_PARTITIONS: + OPTION_LONG("include-partitions", "PART1,PART2…", + "Ignore partitions not of the specified types"): if (arg_filter_partitions_type == FILTER_PARTITIONS_EXCLUDE) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Combination of --include-partitions= and --exclude-partitions= is invalid."); - r = parse_partition_types(optarg, &arg_filter_partitions, &arg_n_filter_partitions); + r = parse_partition_types(arg, &arg_filter_partitions, &arg_n_filter_partitions); if (r < 0) return r; @@ -10119,12 +10031,13 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_EXCLUDE_PARTITIONS: + OPTION_LONG("exclude-partitions", "PART1,PART2…", + "Ignore partitions of the specified types"): if (arg_filter_partitions_type == FILTER_PARTITIONS_INCLUDE) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Combination of --include-partitions= and --exclude-partitions= is invalid."); - r = parse_partition_types(optarg, &arg_filter_partitions, &arg_n_filter_partitions); + r = parse_partition_types(arg, &arg_filter_partitions, &arg_n_filter_partitions); if (r < 0) return r; @@ -10132,68 +10045,44 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_DEFER_PARTITIONS: - r = parse_partition_types(optarg, &arg_defer_partitions, &arg_n_defer_partitions); + OPTION_LONG("defer-partitions", "PART1,PART2…", + "Take partitions of the specified types into account but don't populate them yet"): + r = parse_partition_types(arg, &arg_defer_partitions, &arg_n_defer_partitions); if (r < 0) return r; break; - case ARG_DEFER_PARTITIONS_EMPTY: - r = parse_boolean_argument("--defer-partitions-empty=", optarg, &arg_defer_partitions_empty); + OPTION_LONG("defer-partitions-empty", "BOOL", + "Defer all partitions marked for formatting as empty"): + r = parse_boolean_argument("--defer-partitions-empty=", arg, &arg_defer_partitions_empty); if (r < 0) return r; break; - case ARG_DEFER_PARTITIONS_FACTORY_RESET: - r = parse_boolean_argument("--defer-partitions-factory-reset=", optarg, &arg_defer_partitions_factory_reset); + OPTION_LONG("defer-partitions-factory-reset", "BOOL", + "Defer all partitions marked for factory reset"): + r = parse_boolean_argument("--defer-partitions-factory-reset=", arg, &arg_defer_partitions_factory_reset); if (r < 0) return r; break; - case ARG_SECTOR_SIZE: - r = parse_sector_size(optarg, &arg_sector_size); + OPTION_GROUP("Copying"): {} + + OPTION('s', "copy-source", "PATH", + "Specify the primary source tree to copy files from"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_copy_source); if (r < 0) return r; - break; - case ARG_GRAIN_SIZE: - r = parse_size(optarg, 1024, &arg_grain_size); - if (r < 0) - return log_error_errno(r, "Failed to parse --grain-size= parameter: %s", optarg); - if (arg_grain_size < 512 || !ISPOWEROF2(arg_grain_size)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Grain size must be a power of 2 >= 512."); - - break; - - case ARG_ARCHITECTURE: - r = architecture_from_string(optarg); - if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid architecture '%s'.", optarg); - - arg_architecture = r; - break; - - case ARG_OFFLINE: - if (streq(optarg, "auto")) - arg_offline = -1; - else { - r = parse_boolean_argument("--offline=", optarg, NULL); - if (r < 0) - return r; - - arg_offline = r; - } - - break; - - case ARG_COPY_FROM: { + OPTION_LONG("copy-from", "IMAGE", + "Copy partitions from the given image"): { _cleanup_free_ char *p = NULL; - r = parse_path_argument(optarg, /* suppress_root= */ false, &p); + r = parse_path_argument(arg, /* suppress_root= */ false, &p); if (r < 0) return r; @@ -10203,120 +10092,110 @@ static int parse_argv(int argc, char *argv[]) { break; } - case 's': - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_copy_source); + OPTION_GROUP("DDI Profile"): {} + + OPTION_LONG("make-ddi", "TYPE", + "Create a DDI of the given type"): + if (!filename_is_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid DDI type: %s", arg); + + r = free_and_strdup_warn(&arg_make_ddi, arg); if (r < 0) return r; break; - case ARG_MAKE_DDI: - if (!filename_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid DDI type: %s", optarg); - - r = free_and_strdup_warn(&arg_make_ddi, optarg); - if (r < 0) - return r; - break; - - case 'S': + OPTION_SHORT('S', NULL, "Same as --make-ddi=sysext, make a system extension"): r = free_and_strdup_warn(&arg_make_ddi, "sysext"); if (r < 0) return r; break; - case 'C': + OPTION_SHORT('C', NULL, "Same as --make-ddi=confext, make a configuration extension"): r = free_and_strdup_warn(&arg_make_ddi, "confext"); if (r < 0) return r; break; - case 'P': + OPTION_SHORT('P', NULL, "Same as --make-ddi=portable, make a portable service"): r = free_and_strdup_warn(&arg_make_ddi, "portable"); if (r < 0) return r; break; - case ARG_APPEND_FSTAB: - if (isempty(optarg)) { + OPTION_GROUP("Auxiliary Resource Generation"): {} + + OPTION_LONG("append-fstab", "MODE", + "How to join the content of a pre-existing fstab with the generated one " + "(no, auto, replace)"): + if (isempty(arg)) { arg_append_fstab = APPEND_AUTO; break; } - arg_append_fstab = append_mode_from_string(optarg); + arg_append_fstab = append_mode_from_string(arg); if (arg_append_fstab < 0) - return log_error_errno(arg_append_fstab, "Failed to parse --append-fstab= parameter: %s", optarg); + return log_error_errno(arg_append_fstab, "Failed to parse --append-fstab= parameter: %s", arg); break; - case ARG_GENERATE_FSTAB: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_fstab); + OPTION_LONG("generate-fstab", "PATH", + "Write fstab configuration to the given path"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_generate_fstab); if (r < 0) return r; break; - case ARG_GENERATE_CRYPTTAB: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_crypttab); + OPTION_LONG("generate-crypttab", "PATH", + "Write crypttab configuration to the given path"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_generate_crypttab); if (r < 0) return r; break; - case ARG_LIST_DEVICES: - r = blockdev_list(BLOCKDEV_LIST_REQUIRE_PARTITION_SCANNING|BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_IGNORE_ZRAM, /* ret_devices= */ NULL, /* ret_n_devices= */ NULL); - if (r < 0) - return r; + OPTION_GROUP("El Torito boot catalog"): {} - return 0; - - case ARG_JOIN_SIGNATURE: - r = parse_join_signature(optarg, &arg_verity_settings); - if (r < 0) - return r; - break; - - case ARG_ELTORITO: - r = parse_boolean_argument("--el-torito=", optarg, &arg_eltorito); + OPTION_LONG("el-torito", "BOOL", + "Whether to add a boot catalog to boot the ESP"): + r = parse_boolean_argument("--el-torito=", arg, &arg_eltorito); if (r < 0) return r; break; - case ARG_ELTORITO_SYSTEM: - if (!iso9660_system_name_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-system=.", optarg); + OPTION_LONG("el-torito-system", "STRING", + "Set the system identifier in the ISO9660 descriptor"): + if (!iso9660_system_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-system=.", arg); - r = free_and_strdup_warn(&arg_eltorito_system, optarg); + r = free_and_strdup_warn(&arg_eltorito_system, arg); if (r < 0) return r; break; - case ARG_ELTORITO_VOLUME: - if (!iso9660_volume_name_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-volume=.", optarg); + OPTION_LONG("el-torito-volume", "STRING", + "Set the volume identifier in the ISO9660 descriptor"): + if (!iso9660_volume_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-volume=.", arg); - r = free_and_strdup_warn(&arg_eltorito_volume, optarg); + r = free_and_strdup_warn(&arg_eltorito_volume, arg); if (r < 0) return r; break; - case ARG_ELTORITO_PUBLISHER: - if (!iso9660_publisher_name_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-publisher=.", optarg); + OPTION_LONG("el-torito-publisher", "STRING", + "Set the publisher identifier in the ISO9660 descriptor"): + if (!iso9660_publisher_name_valid(arg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value '%s' for --el-torito-publisher=.", arg); - r = free_and_strdup_warn(&arg_eltorito_publisher, optarg); + r = free_and_strdup_warn(&arg_eltorito_publisher, arg); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (argc - optind > 1) + if (option_parser_get_n_args(&state) > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected at most one argument, the path to the block device or image file."); @@ -10396,11 +10275,12 @@ static int parse_argv(int argc, char *argv[]) { arg_relax_copy_block_security = true; } - if (argc > optind) { - if (empty_or_dash(argv[optind])) + char **args = option_parser_get_args(&state); + if (!strv_isempty(args)) { + if (empty_or_dash(args[0])) arg_node_none = true; else { - arg_node = strdup(argv[optind]); + arg_node = strdup(args[0]); if (!arg_node) return log_oom(); arg_node_none = false; diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index f54dacf65a..a8c554ffcf 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include "alloc-util.h" @@ -11,12 +10,14 @@ #include "efi-fundamental.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "fs-util.h" #include "install-file.h" #include "io-util.h" #include "log.h" #include "main-func.h" #include "openssl-util.h" +#include "options.h" #include "parse-argument.h" #include "pe-binary.h" #include "pretty-print.h" @@ -47,119 +48,96 @@ STATIC_DESTRUCTOR_REGISTER(arg_signed_data_signature, freep); static int help(void) { _cleanup_free_ char *link = NULL; + _cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL; int r; r = terminal_urlify_man("systemd-sbsign", "1", &link); if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] COMMAND ...\n" - "\n%5$sSign binaries for EFI Secure Boot%6$s\n" - "\n%3$sCommands:%4$s\n" - " sign EXEFILE Sign the given binary for EFI Secure Boot\n" - "\n%3$sOptions:%4$s\n" - " -h --help Show this help\n" - " --version Print version\n" - " --output Where to write the signed PE binary\n" - " --certificate=PATH|URI\n" - " PEM certificate to use for signing, or a provider\n" - " specific designation if --certificate-source= is used\n" - " --certificate-source=file|provider:PROVIDER\n" - " Specify how to interpret the certificate from\n" - " --certificate=. Allows the certificate to be loaded\n" - " from an OpenSSL provider\n" - " --private-key=KEY Private key (PEM) to sign with\n" - " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" - " Specify how to use KEY for --private-key=. Allows\n" - " an OpenSSL engine/provider to be used for signing\n" - "\nSee the %2$s for details.\n", - program_invocation_short_name, - link, - ansi_underline(), - ansi_normal(), - ansi_highlight(), - ansi_normal()); + r = verbs_get_help_table(&verbs); + if (r < 0) + return r; + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + (void) table_sync_column_widths(0, verbs, options); + + printf("%s [OPTIONS...] COMMAND ...\n" + "\n%sSign binaries for EFI Secure Boot%s\n" + "\n%sCommands:%s\n", + program_invocation_short_name, + ansi_highlight(), ansi_normal(), + ansi_underline(), ansi_normal()); + + r = table_print_or_warn(verbs); + 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 verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) { - return help(); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_OUTPUT, - ARG_CERTIFICATE, - ARG_CERTIFICATE_SOURCE, - ARG_PRIVATE_KEY, - ARG_PRIVATE_KEY_SOURCE, - ARG_PREPARE_OFFLINE_SIGNING, - ARG_SIGNED_DATA, - ARG_SIGNED_DATA_SIGNATURE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "output", required_argument, NULL, ARG_OUTPUT }, - { "certificate", required_argument, NULL, ARG_CERTIFICATE }, - { "certificate-source", required_argument, NULL, ARG_CERTIFICATE_SOURCE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, - { "prepare-offline-signing", no_argument, NULL, ARG_PREPARE_OFFLINE_SIGNING }, - { "signed-data", required_argument, NULL, ARG_SIGNED_DATA }, - { "signed-data-signature", required_argument, NULL, ARG_SIGNED_DATA_SIGNATURE }, - {} - }; - - int c, r; +VERB_COMMON_HELP_HIDDEN(help); +static int parse_argv(int argc, char *argv[], char ***ret_args) { assert(argc >= 0); assert(argv); + assert(ret_args); - 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_OUTPUT: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_output); + OPTION_LONG("output", "PATH", + "Where to write the signed PE binary"): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_output); if (r < 0) return r; break; - case ARG_CERTIFICATE: - r = free_and_strdup_warn(&arg_certificate, optarg); + OPTION_COMMON_CERTIFICATE("PEM certificate to use for signing"): + r = free_and_strdup_warn(&arg_certificate, arg); if (r < 0) return r; break; - case ARG_CERTIFICATE_SOURCE: + OPTION_COMMON_CERTIFICATE_SOURCE: r = parse_openssl_certificate_source_argument( - optarg, + arg, &arg_certificate_source, &arg_certificate_source_type); if (r < 0) return r; break; - case ARG_PRIVATE_KEY: - r = free_and_strdup_warn(&arg_private_key, optarg); + OPTION_COMMON_PRIVATE_KEY("Private key (PEM) to sign with"): + r = free_and_strdup_warn(&arg_private_key, arg); if (r < 0) return r; break; - case ARG_PRIVATE_KEY_SOURCE: + OPTION_COMMON_PRIVATE_KEY_SOURCE: r = parse_openssl_key_source_argument( - optarg, + arg, &arg_private_key_source, &arg_private_key_source_type); if (r < 0) @@ -167,29 +145,23 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_PREPARE_OFFLINE_SIGNING: + OPTION_LONG("prepare-offline-signing", NULL, /* help= */ NULL): arg_prepare_offline_signing = true; break; - case ARG_SIGNED_DATA: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_signed_data); + OPTION_LONG("signed-data", "PATH", /* help= */ NULL): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_signed_data); if (r < 0) return r; break; - case ARG_SIGNED_DATA_SIGNATURE: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_signed_data_signature); + OPTION_LONG("signed-data-signature", "PATH", /* help= */ NULL): + r = parse_path_argument(arg, /* suppress_root= */ false, &arg_signed_data_signature); if (r < 0) return r; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (arg_private_key_source && !arg_certificate) @@ -201,6 +173,7 @@ static int parse_argv(int argc, char *argv[]) { if (arg_prepare_offline_signing && (arg_private_key || arg_signed_data || arg_signed_data_signature)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--prepare-offline-signing cannot be used with --private-key=, --signed-data= or --signed-data-signature="); + *ret_args = option_parser_get_args(&state); return 1; } @@ -442,6 +415,8 @@ static int pkcs7_add_digest_attribute(PKCS7 *p7, BIO *data, PKCS7_SIGNER_INFO *s return 0; } +VERB(verb_sign, "sign", "EXEFILE", 2, 2, 0, + "Sign the given binary for EFI Secure Boot"); static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { _cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL; _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = NULL; @@ -742,20 +717,16 @@ static int verb_sign(int argc, char *argv[], uintptr_t _data, void *userdata) { } static int run(int argc, char *argv[]) { - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, verb_help }, - { "sign", 2, 2, 0, verb_sign }, - {} - }; int r; log_setup(); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - return dispatch_verb(argc, argv, verbs, NULL); + return dispatch_verb_with_args(args, NULL); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/shared/options.h b/src/shared/options.h index cd9f64fd49..d86766f90e 100644 --- a/src/shared/options.h +++ b/src/shared/options.h @@ -19,10 +19,11 @@ typedef struct Option { char short_code; const char *long_code; const char *metavar; + uintptr_t data; const char *help; } Option; -#define _OPTION(counter, fl, sc, lc, mv, h) \ +#define _OPTION(counter, fl, sc, lc, mv, d, h) \ _section_("SYSTEMD_OPTIONS") \ _alignptr_ \ _used_ \ @@ -35,6 +36,7 @@ typedef struct Option { .short_code = sc, \ .long_code = lc, \ .metavar = mv, \ + .data = d, \ .help = h, \ }; \ case (0x100 + counter) @@ -43,14 +45,17 @@ typedef struct Option { * The define is structured as 'case' so that it can be followed by ':' and indented appropriately. */ #define OPTION_GROUP(gr) \ - _OPTION(__COUNTER__, OPTION_GROUP_MARKER, /* sc= */ 0, /* lc= */ gr, /* mv= */ NULL, /* h= */ NULL) + _OPTION(__COUNTER__, OPTION_GROUP_MARKER, /* sc= */ 0, /* lc= */ gr, /* mv= */ NULL, /* d= */ 0u, /* h= */ NULL) -#define OPTION_FULL(fl, sc, lc, mv, h) _OPTION(__COUNTER__, fl, sc, lc, mv, h) +#define OPTION_FULL_DATA(fl, sc, lc, mv, d, h) _OPTION(__COUNTER__, fl, sc, lc, mv, d, h) +#define OPTION_FULL(fl, sc, lc, mv, h) OPTION_FULL_DATA(fl, sc, lc, mv, /* d= */ 0u, h) #define OPTION(sc, lc, mv, h) OPTION_FULL(/* fl= */ 0, sc, lc, mv, h) #define OPTION_LONG(lc, mv, h) OPTION(/* sc= */ 0, lc, mv, h) #define OPTION_LONG_FLAGS(fl, lc, mv, h) OPTION_FULL(fl, /* sc= */ 0, lc, mv, h) +#define OPTION_LONG_DATA(lc, mv, d, h) OPTION_FULL_DATA(/* fl= */ 0, /* sc= */ 0, lc, mv, d, h) #define OPTION_SHORT(sc, mv, h) OPTION(sc, /* lc= */ NULL, mv, h) #define OPTION_SHORT_FLAGS(fl, sc, mv, h) OPTION_FULL(fl, sc, /* lc= */ NULL, mv, h) +#define OPTION_SHORT_DATA(sc, mv, d, h) OPTION_FULL_DATA(/* fl= */ 0, sc, /* lc= */ NULL, mv, d, h) #define OPTION_POSITIONAL OPTION_FULL(OPTION_POSITIONAL_ENTRY, /* sc= */ 0, "(positional)", /* mv= */ NULL, /* h= */ NULL) #define OPTION_HELP_VERBATIM(lc, h) OPTION_FULL(OPTION_HELP_ENTRY_VERBATIM, /* sc= */ 0, lc, /* mv= */ NULL, h) @@ -89,6 +94,20 @@ typedef struct Option { #define OPTION_COMMON_LOWERCASE_J \ OPTION_SHORT('j', NULL, \ "Equivalent to --json=pretty (on TTY) or --json=short (otherwise)") +#define OPTION_COMMON_PRIVATE_KEY(purpose) \ + OPTION_LONG("private-key", "PATH|URI", purpose) +#define OPTION_COMMON_PRIVATE_KEY_SOURCE \ + OPTION_LONG("private-key-source", "SOURCE", \ + "Specify how to use the private key " \ + "(file, provider:PROVIDER, engine:ENGINE)") +#define OPTION_COMMON_CERTIFICATE(purpose) \ + OPTION_LONG("certificate", "PATH|URI", purpose \ + ", or a provider-specific designation if --certificate-source= is used") +#define OPTION_COMMON_CERTIFICATE_SOURCE \ + OPTION_LONG("certificate-source", "SOURCE", \ + "Specify how to interpret the certificate from --certificate=. " \ + "Allows the certificate to be loaded from an OpenSSL provider " \ + "(file, provider:PROVIDER)") /* This is magically mapped to the beginning and end of the section */ extern const Option __start_SYSTEMD_OPTIONS[]; diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index 1fb0f422e5..5e6ff9f68f 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -3,7 +3,6 @@ Copyright © 2010 ProFUSION embedded systems ***/ -#include #include #include #include @@ -29,10 +28,10 @@ #include "fd-util.h" #include "fileio.h" #include "format-util.h" -#include "getopt-defs.h" #include "initrd-util.h" #include "killall.h" #include "log.h" +#include "options.h" #include "parse-util.h" #include "pidref.h" #include "printk-util.h" @@ -58,103 +57,88 @@ static uint8_t arg_exit_code = 0; static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC; static int parse_argv(int argc, char *argv[]) { - enum { - COMMON_GETOPT_ARGS, - SHUTDOWN_GETOPT_ARGS, - }; - - static const struct option options[] = { - COMMON_GETOPT_OPTIONS, - SHUTDOWN_GETOPT_OPTIONS, - {} - }; - - int c, r; - assert(argc >= 1); assert(argv); - /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long() - * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */ - optind = 0; + /* The interface is: the verb must stay in argv[1]. Any extra positional arguments + * are warned about and ignored. See 4b5d8d0f22ae61ceb45a25391354ba53b43ee992. */ - /* "-" prevents getopt from permuting argv[] and moving the verb away - * from argv[1]. Our interface to initrd promises it'll be there. */ - while ((c = getopt_long(argc, argv, "-", options, NULL)) >= 0) + OptionParser state = { argc, argv, OPTION_PARSER_RETURN_POSITIONAL_ARGS }; + const char *arg; + int r; + + FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c) switch (c) { - case ARG_LOG_LEVEL: - r = log_set_max_level_from_string(optarg); + OPTION_COMMON_LOG_LEVEL: + r = log_set_max_level_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log level %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log level %s, ignoring: %m", arg); break; - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); + OPTION_COMMON_LOG_TARGET: + r = log_set_target_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log target %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log target %s, ignoring: %m", arg); break; - case ARG_LOG_COLOR: - - if (optarg) { - r = log_show_color_from_string(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-color", "BOOL", + "Highlight important messages"): + if (arg) { + r = log_show_color_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log color setting %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log color setting %s, ignoring: %m", arg); } else log_show_color(true); break; - case ARG_LOG_LOCATION: - if (optarg) { - r = log_show_location_from_string(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-location", "BOOL", + "Include code location in messages"): + if (arg) { + r = log_show_location_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log location setting %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log location setting %s, ignoring: %m", arg); } else log_show_location(true); break; - case ARG_LOG_TIME: - - if (optarg) { - r = log_show_time_from_string(optarg); + OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "log-time", "BOOL", + "Prefix messages with current time"): + if (arg) { + r = log_show_time_from_string(arg); if (r < 0) - log_warning_errno(r, "Failed to parse log time setting %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse log time setting %s, ignoring: %m", arg); } else log_show_time(true); break; - case ARG_EXIT_CODE: - r = safe_atou8(optarg, &arg_exit_code); + OPTION_LONG("exit-code", "N", + "Exit code for reboot/kexec"): + r = safe_atou8(arg, &arg_exit_code); if (r < 0) - log_warning_errno(r, "Failed to parse exit code %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse exit code %s, ignoring: %m", arg); break; - case ARG_TIMEOUT: - r = parse_sec(optarg, &arg_timeout); + OPTION_LONG("timeout", "TIME", + "Overall shutdown timeout"): + r = parse_sec(arg, &arg_timeout); if (r < 0) - log_warning_errno(r, "Failed to parse shutdown timeout %s, ignoring: %m", optarg); + log_warning_errno(r, "Failed to parse shutdown timeout %s, ignoring: %m", arg); break; - case '\001': + OPTION_POSITIONAL: if (!arg_verb) - arg_verb = optarg; + arg_verb = arg; else - log_warning("Got extraneous arguments, ignoring."); + log_warning("Got extraneous argument, ignoring."); break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } if (!arg_verb) diff --git a/src/stdio-bridge/stdio-bridge.c b/src/stdio-bridge/stdio-bridge.c index 52c87559a4..42f365ad3e 100644 --- a/src/stdio-bridge/stdio-bridge.c +++ b/src/stdio-bridge/stdio-bridge.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -11,9 +10,11 @@ #include "bus-internal.h" #include "bus-util.h" #include "errno-util.h" +#include "format-table.h" #include "io-util.h" #include "log.h" #include "main-func.h" +#include "options.h" #include "parse-argument.h" #include "time-util.h" @@ -23,84 +24,66 @@ static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; static bool arg_quiet = false; static int help(void) { - printf("%s [OPTIONS...]\n\n" - "Forward messages between a pipe or socket and a D-Bus bus.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -p --bus-path=PATH Path to the bus address (default: %s)\n" - " --system Connect to system bus\n" - " --user Connect to user bus\n" - " -M --machine=CONTAINER Name of local container to connect to\n" - " -q --quiet Fail silently instead of logging errors\n", - program_invocation_short_name, DEFAULT_SYSTEM_BUS_ADDRESS); + _cleanup_(table_unrefp) Table *options = NULL; + int r; + + r = option_parser_get_help_table(&options); + if (r < 0) + return r; + + printf("%s [OPTIONS...]\n" + "\nForward messages between a pipe or socket and a D-Bus bus.\n\n", + program_invocation_short_name); + + r = table_print_or_warn(options); + if (r < 0) + return r; return 0; } static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_USER, - ARG_SYSTEM, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "bus-path", required_argument, NULL, 'p' }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "machine", required_argument, NULL, 'M' }, - { "quiet", no_argument, NULL, 'q' }, - {}, - }; - - int c, r; - assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hp:M:", 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_USER: + OPTION_LONG("user", NULL, "Connect to user bus"): arg_runtime_scope = RUNTIME_SCOPE_USER; break; - case ARG_SYSTEM: + OPTION_LONG("system", NULL, "Connect to system bus"): arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; - case 'p': - arg_bus_path = optarg; + OPTION('p', "bus-path", "PATH", + "Path to the bus address (default: " DEFAULT_SYSTEM_BUS_ADDRESS ")"): + arg_bus_path = arg; break; - case 'M': - r = parse_machine_argument(optarg, &arg_bus_path, &arg_transport); + OPTION_COMMON_MACHINE: + r = parse_machine_argument(arg, &arg_bus_path, &arg_transport); if (r < 0) return r; break; - case 'q': + OPTION('q', "quiet", NULL, "Fail silently instead of logging errors"): arg_quiet = true; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (argc > optind) + if (option_parser_get_n_args(&state) > 0) return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(EINVAL), "%s takes no arguments.", program_invocation_short_name); diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 1f79e2face..ff391cb316 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -2121,8 +2121,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_VERSION: return version(); - OPTION_GROUP("Options"): - break; + OPTION_GROUP("Options"): {} OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"): r = parse_path_argument(arg, /* suppress_root= */ false, &arg_root); diff --git a/src/test/test-chase-manual.c b/src/test/test-chase-manual.c index 376cd12c42..0e760aeb89 100644 --- a/src/test/test-chase-manual.c +++ b/src/test/test-chase-manual.c @@ -1,84 +1,75 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include "chase.h" #include "fd-util.h" +#include "format-table.h" #include "log.h" #include "main-func.h" +#include "options.h" +#include "strv.h" #include "tests.h" -static char *arg_root = NULL; +static const char *arg_root = NULL; static int arg_flags = 0; static bool arg_open = false; -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_ROOT = 0x1000, - ARG_OPEN, - }; +static int help(void) { + _cleanup_(table_unrefp) Table *options = NULL; + int r; - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "open", no_argument, NULL, ARG_OPEN }, + r = option_parser_get_help_table(&options); + if (r < 0) + return r; - { "prefix-root", no_argument, NULL, CHASE_PREFIX_ROOT }, - { "nonexistent", no_argument, NULL, CHASE_NONEXISTENT }, - { "no_autofs", no_argument, NULL, CHASE_NO_AUTOFS }, - { "trigger-autofs", no_argument, NULL, CHASE_TRIGGER_AUTOFS }, - { "safe", no_argument, NULL, CHASE_SAFE }, - { "trail-slash", no_argument, NULL, CHASE_TRAIL_SLASH }, - { "step", no_argument, NULL, CHASE_STEP }, - { "nofollow", no_argument, NULL, CHASE_NOFOLLOW }, - { "warn", no_argument, NULL, CHASE_WARN }, - {} - }; + printf("%s [OPTIONS...] path...\n" + "\nExercise chase() function on specified paths.\n\n", + program_invocation_short_name); - int c; + r = table_print_or_warn(options); + if (r < 0) + return r; - assert_se(argc >= 0); - assert_se(argv); + return 0; +} - while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) +static int parse_argv(int argc, char *argv[], char ***ret_args) { + assert(argc >= 0); + assert(argv); + assert(ret_args); + + OptionParser state = { argc, argv }; + const Option *opt; + const char *arg; + + FOREACH_OPTION_FULL(&state, c, &opt, &arg, /* on_error= */ return c) switch (c) { - case 'h': - printf("Syntax:\n" - " %s [OPTION...] path...\n" - "Options:\n" - , argv[0]); - FOREACH_ARRAY(option, options, ELEMENTSOF(options) - 1) - printf(" --%s\n", option->name); - return 0; + OPTION_COMMON_HELP: + return help(); - case ARG_ROOT: - arg_root = optarg; + OPTION_LONG("root", "PATH", "Operate below specified root directory"): + arg_root = arg; break; - case ARG_OPEN: + OPTION_LONG("open", NULL, "Open the resolved path"): arg_open = true; break; - case CHASE_PREFIX_ROOT: - case CHASE_NONEXISTENT: - case CHASE_NO_AUTOFS: - case CHASE_TRIGGER_AUTOFS: - case CHASE_SAFE: - case CHASE_TRAIL_SLASH: - case CHASE_STEP: - case CHASE_NOFOLLOW: - case CHASE_WARN: - arg_flags |= c; + OPTION_LONG_DATA("prefix-root", NULL, CHASE_PREFIX_ROOT, "Prefix path with --root"): {} + OPTION_LONG_DATA("nonexistent", NULL, CHASE_NONEXISTENT, "Allow path to not exist"): {} + OPTION_LONG_DATA("no-autofs", NULL, CHASE_NO_AUTOFS, "Return -EREMOTE if autofs mount point found"): {} + OPTION_LONG_DATA("trigger-autofs", NULL, CHASE_TRIGGER_AUTOFS, "Trigger autofs mounts"): {} + OPTION_LONG_DATA("safe", NULL, CHASE_SAFE, "Refuse privilege boundary crossings"): {} + OPTION_LONG_DATA("trail-slash", NULL, CHASE_TRAIL_SLASH, "Preserve trailing slash"): {} + OPTION_LONG_DATA("step", NULL, CHASE_STEP, "Execute a single normalization step"): {} + OPTION_LONG_DATA("nofollow", NULL, CHASE_NOFOLLOW, "Do not follow the path's right-most component"): {} + OPTION_LONG_DATA("warn", NULL, CHASE_WARN, "Emit a warning on error"): + arg_flags |= opt->data; break; - - case '?': - return -EINVAL; - - default: - assert_not_reached(); } - if (optind == argc) + *ret_args = option_parser_get_args(&state); + if (strv_isempty(*ret_args)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "At least one argument is required."); return 1; @@ -89,18 +80,19 @@ static int run(int argc, char **argv) { test_setup_logging(LOG_DEBUG); - r = parse_argv(argc, argv); + char **args = NULL; + r = parse_argv(argc, argv, &args); if (r <= 0) return r; - for (int i = optind; i < argc; i++) { + STRV_FOREACH(a, args) { _cleanup_free_ char *p = NULL; _cleanup_close_ int fd = -EBADF; - printf("%s ", argv[i]); + printf("%s ", *a); fflush(stdout); - r = chase(argv[i], arg_root, arg_flags, &p, arg_open ? &fd : NULL); + r = chase(*a, arg_root, arg_flags, &p, arg_open ? &fd : NULL); if (r < 0) log_error_errno(r, "failed: %m"); else { diff --git a/src/test/test-options.c b/src/test/test-options.c index 05fd4bcbe0..1c9073b7a5 100644 --- a/src/test/test-options.c +++ b/src/test/test-options.c @@ -772,6 +772,12 @@ static void test_macros_parse_one( if (entries[i].short_code != 0) ASSERT_EQ(opt->short_code, entries[i].short_code); ASSERT_TRUE(streq_ptr(arg, entries[i].argument)); + + if (streq_ptr(entries[i].long_code, "optional2")) + ASSERT_EQ(opt->data, 666u); + else + ASSERT_EQ(opt->data, 0u); + i++; switch (c) { @@ -796,13 +802,16 @@ static void test_macros_parse_one( OPTION_FULL(OPTION_OPTIONAL_ARG, 'o', "optional", "ARG", "Optional arg option"): break; + /* OPTION_FULL_DATA: optional arg */ + OPTION_FULL_DATA(OPTION_OPTIONAL_ARG, 'O', "optional2", "ARG", 666, "Optional arg option"): + break; + /* OPTION_FULL: stops parsing */ OPTION_FULL(OPTION_STOPS_PARSING, 0, "exec", NULL, "Stop parsing after this"): break; /* OPTION_GROUP: group marker (never returned by parser) */ - OPTION_GROUP("Advanced"): - break; + OPTION_GROUP("Advanced"): {} /* OPTION_LONG: long only, in the "Advanced" group */ OPTION_LONG("debug", NULL, "Enable debug mode"): @@ -1015,6 +1024,7 @@ TEST(option_macros) { "-v", "--required=rval", "--optional=oval", + "--optional2=oval", "--debug", "pos2", "-o", @@ -1025,6 +1035,7 @@ TEST(option_macros) { { .short_code = 'v' }, { "required", "rval" }, { "optional", "oval" }, + { "optional2", "oval" }, { "debug" }, { "optional", NULL }, { "help" }, diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 9975ffca39..0c133b08c8 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -4225,8 +4225,7 @@ static int parse_argv(int argc, char *argv[], char ***ret_args) { OPTION_COMMON_VERSION: return version(); - OPTION_GROUP("Options"): - break; + OPTION_GROUP("Options"): {} OPTION_LONG("user", NULL, "Execute user configuration"): arg_runtime_scope = RUNTIME_SCOPE_USER; diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 8ccd631762..e0ddcee9b1 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -361,8 +361,7 @@ static int parse_argv(int argc, char *argv[]) { arg_runtime_scope = RUNTIME_SCOPE_USER; break; - OPTION_GROUP("Image"): - break; + OPTION_GROUP("Image"): {} OPTION('D', "directory", "PATH", "Root directory for the VM"): r = parse_path_argument(arg, /* suppress_root= */ false, &arg_directory); @@ -395,8 +394,7 @@ static int parse_argv(int argc, char *argv[]) { "Invalid image disk type: %s", arg); break; - OPTION_GROUP("Host Configuration"): - break; + OPTION_GROUP("Host Configuration"): {} OPTION_LONG("cpus", "CPUS", "Configure number of CPUs in guest"): {} OPTION_LONG("qemu-smp", "CPUS", /* help= */ NULL): /* Compat alias */ @@ -657,8 +655,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to parse --grow-image= parameter: %s", arg); break; - OPTION_GROUP("Execution"): - break; + OPTION_GROUP("Execution"): {} OPTION('s', "smbios11", "STRING", "Pass an arbitrary SMBIOS Type #11 string to the VM"): if (isempty(arg)) { @@ -679,8 +676,7 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - OPTION_GROUP("System Identity"): - break; + OPTION_GROUP("System Identity"): {} OPTION('M', "machine", "NAME", "Set the machine name for the VM"): if (isempty(arg)) @@ -704,8 +700,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Invalid UUID: %s", arg); break; - OPTION_GROUP("Properties"): - break; + OPTION_GROUP("Properties"): {} OPTION('S', "slice", "SLICE", "Place the VM in the specified slice"): { _cleanup_free_ char *mangled = NULL; @@ -734,8 +729,7 @@ static int parse_argv(int argc, char *argv[]) { arg_keep_unit = true; break; - OPTION_GROUP("User Namespacing"): - break; + OPTION_GROUP("User Namespacing"): {} OPTION_LONG("private-users", "UIDBASE[:NUIDS]", "Configure the UID/GID range to map into the virtiofsd namespace"): @@ -744,8 +738,7 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - OPTION_GROUP("Mounts"): - break; + OPTION_GROUP("Mounts"): {} 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"): { @@ -837,8 +830,7 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); break; - OPTION_GROUP("Integration"): - break; + OPTION_GROUP("Integration"): {} 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); @@ -890,8 +882,7 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - OPTION_GROUP("Input/Output"): - break; + OPTION_GROUP("Input/Output"): {} OPTION_LONG("console", "MODE", "Console mode (interactive, native, gui, read-only or headless)"): @@ -916,8 +907,7 @@ static int parse_argv(int argc, char *argv[]) { return r; break; - OPTION_GROUP("Credentials"): - break; + OPTION_GROUP("Credentials"): {} OPTION_LONG("set-credential", "ID:VALUE", "Pass a credential with literal value to the VM"): r = machine_credential_set(&arg_credentials, arg); diff --git a/test/units/TEST-70-TPM2.measure.sh b/test/units/TEST-70-TPM2.measure.sh index bf30bd57b3..30fa51e521 100755 --- a/test/units/TEST-70-TPM2.measure.sh +++ b/test/units/TEST-70-TPM2.measure.sh @@ -45,6 +45,12 @@ cat >/tmp/result.json </tmp/result <