Another batch of option+verb conversions (#41586)
This commit is contained in:
+240
-286
@@ -1,6 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "sd-varlink.h"
|
||||
@@ -23,12 +22,14 @@
|
||||
#include "efivars.h"
|
||||
#include "escape.h"
|
||||
#include "find-esp.h"
|
||||
#include "format-table.h"
|
||||
#include "image-policy.h"
|
||||
#include "log.h"
|
||||
#include "loop-util.h"
|
||||
#include "main-func.h"
|
||||
#include "mount-util.h"
|
||||
#include "openssl-util.h"
|
||||
#include "options.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "path-util.h"
|
||||
@@ -254,296 +255,270 @@ static int help(void) {
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...] COMMAND ...\n"
|
||||
"\n%5$sControl EFI firmware boot settings and manage boot loader.%6$s\n"
|
||||
"\n%3$sGeneric EFI Firmware/Boot Loader Commands:%4$s\n"
|
||||
" status Show status of installed boot loader and EFI variables\n"
|
||||
" reboot-to-firmware [BOOL]\n"
|
||||
" Query or set reboot-to-firmware EFI flag\n"
|
||||
"\n%3$sBoot Loader Specification Commands:%4$s\n"
|
||||
" list List boot loader entries\n"
|
||||
" unlink ID Remove boot loader entry\n"
|
||||
" cleanup Remove files in ESP not referenced in any boot entry\n"
|
||||
"\n%3$sBoot Loader Interface Commands:%4$s\n"
|
||||
" set-default ID Set default boot loader entry\n"
|
||||
" set-oneshot ID Set default boot loader entry, for next boot only\n"
|
||||
" set-sysfail ID Set boot loader entry used in case of a system failure\n"
|
||||
" set-timeout SECONDS Set the menu timeout\n"
|
||||
" set-timeout-oneshot SECONDS\n"
|
||||
" Set the menu timeout for the next boot only\n"
|
||||
"\n%3$ssystemd-boot Commands:%4$s\n"
|
||||
" install Install systemd-boot to the ESP and EFI variables\n"
|
||||
" update Update systemd-boot in the ESP and EFI variables\n"
|
||||
" remove Remove systemd-boot from the ESP and EFI variables\n"
|
||||
" is-installed Test whether systemd-boot is installed in the ESP\n"
|
||||
" random-seed Initialize or refresh random seed in ESP and EFI\n"
|
||||
" variables\n"
|
||||
"\n%3$sKernel Image Commands:%4$s\n"
|
||||
" kernel-identify KERNEL-IMAGE\n"
|
||||
" Identify kernel image type\n"
|
||||
" kernel-inspect KERNEL-IMAGE\n"
|
||||
" Prints details about the kernel image\n"
|
||||
"\n%3$sBlock Device Discovery Commands:%4$s\n"
|
||||
" -p --print-esp-path Print path to the EFI System Partition mount point\n"
|
||||
" -x --print-boot-path Print path to the $BOOT partition mount point\n"
|
||||
" --print-loader-path\n"
|
||||
" Print path to currently booted boot loader binary\n"
|
||||
" --print-stub-path Print path to currently booted unified kernel binary\n"
|
||||
" -R --print-root-device\n"
|
||||
" Print path to the block device node backing the\n"
|
||||
" root file system (returns e.g. /dev/nvme0n1p5)\n"
|
||||
" -RR Print path to the whole disk block device node\n"
|
||||
" backing the root FS (returns e.g. /dev/nvme0n1)\n"
|
||||
"\n%3$sOptions:%4$s\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Print version\n"
|
||||
" --esp-path=PATH Path to the EFI System Partition (ESP)\n"
|
||||
" --boot-path=PATH Path to the $BOOT partition\n"
|
||||
" --root=PATH Operate on an alternate filesystem root\n"
|
||||
" --image=PATH Operate on disk image as filesystem root\n"
|
||||
" --image-policy=POLICY\n"
|
||||
" Specify disk image dissection policy\n"
|
||||
" --install-source=auto|image|host\n"
|
||||
" Where to pick files when using --root=/--image=\n"
|
||||
" --variables=yes|no\n"
|
||||
" Whether to modify EFI variables\n"
|
||||
" --random-seed=yes|no\n"
|
||||
" Whether to create random-seed file during install\n"
|
||||
" --no-pager Do not pipe output into a pager\n"
|
||||
" --graceful Don't fail when the ESP cannot be found or EFI\n"
|
||||
" variables cannot be written\n"
|
||||
" -q --quiet Suppress output\n"
|
||||
" --make-entry-directory=yes|no|auto\n"
|
||||
" Create $BOOT/ENTRY-TOKEN/ directory\n"
|
||||
" --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n"
|
||||
" Entry token to use for this installation\n"
|
||||
" --json=pretty|short|off\n"
|
||||
" Generate JSON output\n"
|
||||
" --all-architectures\n"
|
||||
" Install all supported EFI architectures\n"
|
||||
" --efi-boot-option-description=DESCRIPTION\n"
|
||||
" Description of the entry in the boot option list\n"
|
||||
" --efi-boot-option-description-with-device=yes\n"
|
||||
" Suffix description with disk vendor/model/serial\n"
|
||||
" --dry-run Dry run (unlink and cleanup)\n"
|
||||
" --secure-boot-auto-enroll=yes|no\n"
|
||||
" Set up secure boot auto-enrollment\n"
|
||||
" --private-key=PATH|URI\n"
|
||||
" Private key to use when setting up secure boot\n"
|
||||
" auto-enrollment 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 setting\n"
|
||||
" up secure boot auto-enrollment\n"
|
||||
" --certificate=PATH|URI\n"
|
||||
" PEM certificate to use when setting up Secure Boot\n"
|
||||
" auto-enrollment, or a provider specific designation\n"
|
||||
" 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"
|
||||
"\nSee the %2$s for details.\n",
|
||||
static const char *const verb_groups[] = {
|
||||
"Generic EFI Firmware/Boot Loader Commands",
|
||||
"Boot Loader Specification Commands",
|
||||
"Boot Loader Interface Commands",
|
||||
"systemd-boot Commands",
|
||||
"Kernel Image Commands",
|
||||
};
|
||||
|
||||
static const char *const option_groups[] = {
|
||||
"Block Device Discovery Commands",
|
||||
"Options",
|
||||
};
|
||||
|
||||
_cleanup_(table_unref_many) Table *verb_tables[ELEMENTSOF(verb_groups) + 1] = {};
|
||||
_cleanup_(table_unref_many) Table *option_tables[ELEMENTSOF(option_groups) + 1] = {};
|
||||
|
||||
for (size_t i = 0; i < ELEMENTSOF(verb_groups); i++) {
|
||||
r = verbs_get_help_table_group(verb_groups[i], &verb_tables[i]);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
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,
|
||||
verb_tables[0], verb_tables[1], verb_tables[2],
|
||||
verb_tables[3], verb_tables[4],
|
||||
option_tables[0], option_tables[1]);
|
||||
|
||||
printf("%s [OPTIONS...] COMMAND ...\n"
|
||||
"\n%sControl EFI firmware boot settings and manage boot loader.%s\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
ansi_underline(),
|
||||
ansi_normal(),
|
||||
ansi_highlight(),
|
||||
ansi_normal());
|
||||
|
||||
for (size_t i = 0; i < ELEMENTSOF(verb_groups); i++) {
|
||||
printf("\n%s%s:%s\n", ansi_underline(), verb_groups[i], ansi_normal());
|
||||
|
||||
r = table_print_or_warn(verb_tables[i]);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
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 verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
return help();
|
||||
}
|
||||
VERB_COMMON_HELP(help);
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
enum {
|
||||
ARG_ESP_PATH = 0x100,
|
||||
ARG_BOOT_PATH,
|
||||
ARG_ROOT,
|
||||
ARG_IMAGE,
|
||||
ARG_IMAGE_POLICY,
|
||||
ARG_INSTALL_SOURCE,
|
||||
ARG_VERSION,
|
||||
ARG_VARIABLES,
|
||||
ARG_NO_VARIABLES,
|
||||
ARG_RANDOM_SEED,
|
||||
ARG_NO_PAGER,
|
||||
ARG_GRACEFUL,
|
||||
ARG_MAKE_ENTRY_DIRECTORY,
|
||||
ARG_ENTRY_TOKEN,
|
||||
ARG_JSON,
|
||||
ARG_ARCH_ALL,
|
||||
ARG_EFI_BOOT_OPTION_DESCRIPTION,
|
||||
ARG_EFI_BOOT_OPTION_DESCRIPTION_WITH_DEVICE,
|
||||
ARG_DRY_RUN,
|
||||
ARG_PRINT_LOADER_PATH,
|
||||
ARG_PRINT_STUB_PATH,
|
||||
ARG_SECURE_BOOT_AUTO_ENROLL,
|
||||
ARG_CERTIFICATE,
|
||||
ARG_CERTIFICATE_SOURCE,
|
||||
ARG_PRIVATE_KEY,
|
||||
ARG_PRIVATE_KEY_SOURCE,
|
||||
};
|
||||
VERB_GROUP("Generic EFI Firmware/Boot Loader Commands");
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "esp-path", required_argument, NULL, ARG_ESP_PATH },
|
||||
{ "path", required_argument, NULL, ARG_ESP_PATH }, /* Compatibility alias */
|
||||
{ "boot-path", required_argument, NULL, ARG_BOOT_PATH },
|
||||
{ "root", required_argument, NULL, ARG_ROOT },
|
||||
{ "image", required_argument, NULL, ARG_IMAGE },
|
||||
{ "image-policy", required_argument, NULL, ARG_IMAGE_POLICY },
|
||||
{ "install-source", required_argument, NULL, ARG_INSTALL_SOURCE },
|
||||
{ "print-esp-path", no_argument, NULL, 'p' },
|
||||
{ "print-path", no_argument, NULL, 'p' }, /* Compatibility alias */
|
||||
{ "print-boot-path", no_argument, NULL, 'x' },
|
||||
{ "print-loader-path", no_argument, NULL, ARG_PRINT_LOADER_PATH },
|
||||
{ "print-stub-path", no_argument, NULL, ARG_PRINT_STUB_PATH },
|
||||
{ "print-root-device", no_argument, NULL, 'R' },
|
||||
{ "variables", required_argument, NULL, ARG_VARIABLES },
|
||||
{ "no-variables", no_argument, NULL, ARG_NO_VARIABLES }, /* Compatibility alias */
|
||||
{ "random-seed", required_argument, NULL, ARG_RANDOM_SEED },
|
||||
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
||||
{ "graceful", no_argument, NULL, ARG_GRACEFUL },
|
||||
{ "quiet", no_argument, NULL, 'q' },
|
||||
{ "make-entry-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY },
|
||||
{ "make-machine-id-directory", required_argument, NULL, ARG_MAKE_ENTRY_DIRECTORY }, /* Compatibility alias */
|
||||
{ "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN },
|
||||
{ "json", required_argument, NULL, ARG_JSON },
|
||||
{ "all-architectures", no_argument, NULL, ARG_ARCH_ALL },
|
||||
{ "efi-boot-option-description", required_argument, NULL, ARG_EFI_BOOT_OPTION_DESCRIPTION },
|
||||
{ "efi-boot-option-description-with-device", required_argument, NULL, ARG_EFI_BOOT_OPTION_DESCRIPTION_WITH_DEVICE },
|
||||
{ "dry-run", no_argument, NULL, ARG_DRY_RUN },
|
||||
{ "secure-boot-auto-enroll", required_argument, NULL, ARG_SECURE_BOOT_AUTO_ENROLL },
|
||||
{ "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 },
|
||||
{}
|
||||
};
|
||||
VERB_SCOPE(, verb_status, "status", NULL, VERB_ANY, 1, VERB_DEFAULT,
|
||||
"Show status of installed boot loader and EFI variables");
|
||||
|
||||
int c, r;
|
||||
VERB_SCOPE(, verb_reboot_to_firmware, "reboot-to-firmware", "[BOOL]", VERB_ANY, 2, 0,
|
||||
"Query or set reboot-to-firmware EFI flag");
|
||||
|
||||
VERB_GROUP("Boot Loader Specification Commands");
|
||||
|
||||
VERB_SCOPE_NOARG(, verb_list, "list",
|
||||
"List boot loader entries");
|
||||
|
||||
VERB_SCOPE(, verb_unlink, "unlink", "ID", 2, 2, 0,
|
||||
"Remove boot loader entry");
|
||||
|
||||
VERB_SCOPE_NOARG(, verb_cleanup, "cleanup",
|
||||
"Remove files in ESP not referenced in any boot entry");
|
||||
|
||||
VERB_GROUP("Boot Loader Interface Commands");
|
||||
|
||||
VERB_SCOPE(, verb_set_efivar, "set-default", "ID", 2, 2, 0,
|
||||
"Set default boot loader entry");
|
||||
|
||||
VERB_SCOPE(, verb_set_efivar, "set-oneshot", "ID", 2, 2, 0,
|
||||
"Set default boot loader entry, for next boot only");
|
||||
|
||||
VERB_SCOPE(, verb_set_efivar, "set-sysfail", "ID", 2, 2, 0,
|
||||
"Set boot loader entry used in case of a system failure");
|
||||
|
||||
VERB_SCOPE(, verb_set_efivar, "set-timeout", "SECONDS", 2, 2, 0,
|
||||
"Set the menu timeout");
|
||||
|
||||
VERB_SCOPE(, verb_set_efivar, "set-timeout-oneshot", "SECONDS", 2, 2, 0,
|
||||
"Set the menu timeout for the next boot only");
|
||||
|
||||
VERB_SCOPE(, verb_set_efivar, "set-preferred", "ID", 2, 2, 0,
|
||||
/* help= */ NULL);
|
||||
|
||||
VERB_GROUP("systemd-boot Commands");
|
||||
|
||||
VERB_SCOPE(, verb_install, "install", NULL, VERB_ANY, 1, 0,
|
||||
"Install systemd-boot to the ESP and EFI variables");
|
||||
|
||||
VERB_SCOPE(, verb_install, "update", NULL, VERB_ANY, 1, 0,
|
||||
"Update systemd-boot in the ESP and EFI variables");
|
||||
|
||||
VERB_SCOPE_NOARG(, verb_remove, "remove",
|
||||
"Remove systemd-boot from the ESP and EFI variables");
|
||||
|
||||
VERB_SCOPE_NOARG(, verb_is_installed, "is-installed",
|
||||
"Test whether systemd-boot is installed in the ESP");
|
||||
|
||||
VERB_SCOPE_NOARG(, verb_random_seed, "random-seed",
|
||||
"Initialize or refresh random seed in ESP and EFI variables");
|
||||
|
||||
VERB_GROUP("Kernel Image Commands");
|
||||
|
||||
VERB_SCOPE(, verb_kernel_identify, "kernel-identify", "KERNEL-IMAGE", 2, 2, 0,
|
||||
"Identify kernel image type");
|
||||
|
||||
VERB_SCOPE(, verb_kernel_inspect, "kernel-inspect", "KERNEL-IMAGE", 2, 2, 0,
|
||||
"Prints details about the kernel image");
|
||||
|
||||
static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
int r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hpxRq", options, NULL)) >= 0)
|
||||
OptionParser state = { argc, argv };
|
||||
const char *arg;
|
||||
|
||||
FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c)
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
OPTION_GROUP("Block Device Discovery Commands"):
|
||||
break;
|
||||
|
||||
OPTION('p', "print-esp-path", NULL, "Print path to the EFI System Partition mount point"): {}
|
||||
OPTION_LONG("print-path", NULL, /* help= */ NULL): /* Compatibility alias */
|
||||
arg_print_esp_path = true;
|
||||
break;
|
||||
|
||||
OPTION('x', "print-boot-path", NULL, "Print path to the $BOOT partition mount point"):
|
||||
arg_print_dollar_boot_path = true;
|
||||
break;
|
||||
|
||||
OPTION_LONG("print-loader-path", NULL, "Print path to currently booted boot loader binary"):
|
||||
arg_print_loader_path = true;
|
||||
break;
|
||||
|
||||
OPTION_LONG("print-stub-path", NULL, "Print path to currently booted unified kernel binary"):
|
||||
arg_print_stub_path = true;
|
||||
break;
|
||||
|
||||
OPTION('R', "print-root-device", NULL,
|
||||
"Print path to the block device node backing the root file system"
|
||||
" (returns e.g. /dev/nvme0n1p5)"): {}
|
||||
OPTION_HELP_VERBATIM("-RR",
|
||||
"Print path to the whole disk block device node backing the root FS"
|
||||
" (returns e.g. /dev/nvme0n1)"):
|
||||
arg_print_root_device++;
|
||||
break;
|
||||
|
||||
OPTION_GROUP("Options"):
|
||||
break;
|
||||
|
||||
OPTION_COMMON_HELP:
|
||||
return help();
|
||||
|
||||
case ARG_VERSION:
|
||||
OPTION_COMMON_VERSION:
|
||||
return version();
|
||||
|
||||
case ARG_ESP_PATH:
|
||||
r = free_and_strdup(&arg_esp_path, optarg);
|
||||
OPTION_LONG("esp-path", "PATH", "Path to the EFI System Partition (ESP)"): {}
|
||||
OPTION_LONG("path", "PATH", /* help= */ NULL): /* Compatibility alias */
|
||||
r = free_and_strdup(&arg_esp_path, arg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
break;
|
||||
|
||||
case ARG_BOOT_PATH:
|
||||
r = free_and_strdup(&arg_xbootldr_path, optarg);
|
||||
OPTION_LONG("boot-path", "PATH", "Path to the $BOOT partition"):
|
||||
r = free_and_strdup(&arg_xbootldr_path, arg);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
break;
|
||||
|
||||
case ARG_ROOT:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root);
|
||||
OPTION_LONG("root", "PATH", "Operate on an alternate filesystem root"):
|
||||
r = parse_path_argument(arg, /* suppress_root= */ true, &arg_root);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_IMAGE:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
|
||||
OPTION_LONG("image", "PATH", "Operate on disk image as filesystem root"):
|
||||
r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_IMAGE_POLICY:
|
||||
r = parse_image_policy_argument(optarg, &arg_image_policy);
|
||||
OPTION_LONG("image-policy", "POLICY", "Specify disk image dissection policy"):
|
||||
r = parse_image_policy_argument(arg, &arg_image_policy);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_INSTALL_SOURCE: {
|
||||
InstallSource is = install_source_from_string(optarg);
|
||||
OPTION_LONG("install-source", "SOURCE",
|
||||
"Where to pick files when using --root=/--image= (auto, image, host)"): {
|
||||
InstallSource is = install_source_from_string(arg);
|
||||
if (is < 0)
|
||||
return log_error_errno(is, "Unexpected parameter for --install-source=: %s", optarg);
|
||||
return log_error_errno(is, "Unexpected parameter for --install-source=: %s", arg);
|
||||
|
||||
arg_install_source = is;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p':
|
||||
arg_print_esp_path = true;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
arg_print_dollar_boot_path = true;
|
||||
break;
|
||||
|
||||
case ARG_PRINT_LOADER_PATH:
|
||||
arg_print_loader_path = true;
|
||||
break;
|
||||
|
||||
case ARG_PRINT_STUB_PATH:
|
||||
arg_print_stub_path = true;
|
||||
break;
|
||||
|
||||
case 'R':
|
||||
arg_print_root_device++;
|
||||
break;
|
||||
|
||||
case ARG_VARIABLES:
|
||||
r = parse_tristate_argument_with_auto("--variables=", optarg, &arg_touch_variables);
|
||||
OPTION_LONG("variables", "BOOL", "Whether to modify EFI variables"):
|
||||
r = parse_tristate_argument_with_auto("--variables=", arg, &arg_touch_variables);
|
||||
if (r < 0)
|
||||
return r;
|
||||
#if !ENABLE_EFI
|
||||
if (arg_touch_variables > 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"Compiled without support for EFI, --variables=%s cannot be specified.", optarg);
|
||||
"Compiled without support for EFI, --variables=%s cannot be specified.", arg);
|
||||
#endif
|
||||
break;
|
||||
|
||||
case ARG_NO_VARIABLES:
|
||||
OPTION_LONG("no-variables", NULL, /* help= */ NULL): /* Compatibility alias */
|
||||
arg_touch_variables = false;
|
||||
break;
|
||||
|
||||
case ARG_RANDOM_SEED:
|
||||
r = parse_boolean_argument("--random-seed=", optarg, &arg_install_random_seed);
|
||||
OPTION_LONG("random-seed", "BOOL", "Whether to create random-seed file during install"):
|
||||
r = parse_boolean_argument("--random-seed=", arg, &arg_install_random_seed);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_NO_PAGER:
|
||||
OPTION_COMMON_NO_PAGER:
|
||||
arg_pager_flags |= PAGER_DISABLE;
|
||||
break;
|
||||
|
||||
case ARG_GRACEFUL:
|
||||
OPTION_LONG("graceful", NULL,
|
||||
"Don't fail when the ESP cannot be found or EFI variables cannot be written"):
|
||||
_arg_graceful = ARG_GRACEFUL_YES;
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
OPTION('q', "quiet", NULL, "Suppress output"):
|
||||
arg_quiet = true;
|
||||
break;
|
||||
|
||||
case ARG_ENTRY_TOKEN:
|
||||
r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token);
|
||||
OPTION_LONG("entry-token", "TOKEN",
|
||||
"Entry token to use for this installation"
|
||||
" (machine-id, os-id, os-image-id, auto, literal:…)"):
|
||||
r = parse_boot_entry_token_type(arg, &arg_entry_token_type, &arg_entry_token);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_MAKE_ENTRY_DIRECTORY:
|
||||
if (streq(optarg, "auto")) /* retained for backwards compatibility */
|
||||
OPTION_LONG("make-entry-directory", "yes|no|auto", "Create $BOOT/ENTRY-TOKEN/ directory"): {}
|
||||
OPTION_LONG("make-machine-id-directory", "BOOL", /* help= */ NULL): /* Compatibility alias */
|
||||
if (streq(arg, "auto")) /* retained for backwards compatibility */
|
||||
arg_make_entry_directory = -1; /* yes if machine-id is permanent */
|
||||
else {
|
||||
r = parse_boolean_argument("--make-entry-directory=", optarg, NULL);
|
||||
r = parse_boolean_argument("--make-entry-directory=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -551,95 +526,99 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
}
|
||||
break;
|
||||
|
||||
case ARG_JSON:
|
||||
r = parse_json_argument(optarg, &arg_json_format_flags);
|
||||
OPTION_COMMON_JSON:
|
||||
r = parse_json_argument(arg, &arg_json_format_flags);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_ARCH_ALL:
|
||||
OPTION_LONG("all-architectures", NULL, "Install all supported EFI architectures"):
|
||||
arg_arch_all = true;
|
||||
break;
|
||||
|
||||
case ARG_EFI_BOOT_OPTION_DESCRIPTION:
|
||||
if (isempty(optarg) || !(string_is_safe(optarg) && utf8_is_valid(optarg))) {
|
||||
_cleanup_free_ char *escaped = cescape(optarg);
|
||||
OPTION_LONG("efi-boot-option-description", "DESCRIPTION",
|
||||
"Description of the entry in the boot option list"):
|
||||
if (isempty(arg) || !(string_is_safe(arg) && utf8_is_valid(arg))) {
|
||||
_cleanup_free_ char *escaped = cescape(arg);
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Invalid --efi-boot-option-description=: %s", strna(escaped));
|
||||
}
|
||||
if (strlen(optarg) > EFI_BOOT_OPTION_DESCRIPTION_MAX)
|
||||
if (strlen(arg) > EFI_BOOT_OPTION_DESCRIPTION_MAX)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"--efi-boot-option-description= too long: %zu > %zu",
|
||||
strlen(optarg), EFI_BOOT_OPTION_DESCRIPTION_MAX);
|
||||
r = free_and_strdup_warn(&arg_efi_boot_option_description, optarg);
|
||||
strlen(arg), EFI_BOOT_OPTION_DESCRIPTION_MAX);
|
||||
r = free_and_strdup_warn(&arg_efi_boot_option_description, arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_EFI_BOOT_OPTION_DESCRIPTION_WITH_DEVICE:
|
||||
r = parse_boolean_argument("--efi-boot-option-description-with-device=", optarg, &arg_efi_boot_option_description_with_device);
|
||||
OPTION_LONG("efi-boot-option-description-with-device", "BOOL",
|
||||
"Suffix description with disk vendor/model/serial"):
|
||||
r = parse_boolean_argument("--efi-boot-option-description-with-device=", arg,
|
||||
&arg_efi_boot_option_description_with_device);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_DRY_RUN:
|
||||
OPTION_LONG("dry-run", NULL, "Dry run (unlink and cleanup)"):
|
||||
arg_dry_run = true;
|
||||
break;
|
||||
|
||||
case ARG_SECURE_BOOT_AUTO_ENROLL:
|
||||
r = parse_boolean_argument("--secure-boot-auto-enroll=", optarg, &arg_secure_boot_auto_enroll);
|
||||
OPTION_LONG("secure-boot-auto-enroll", "BOOL", "Set up secure boot auto-enrollment"):
|
||||
r = parse_boolean_argument("--secure-boot-auto-enroll=", arg,
|
||||
&arg_secure_boot_auto_enroll);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_CERTIFICATE:
|
||||
r = free_and_strdup_warn(&arg_certificate, optarg);
|
||||
OPTION_LONG("private-key", "PATH|URI",
|
||||
"Private key for Secure Boot auto-enrollment"):
|
||||
r = free_and_strdup_warn(&arg_private_key, arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_CERTIFICATE_SOURCE:
|
||||
r = parse_openssl_certificate_source_argument(
|
||||
optarg,
|
||||
&arg_certificate_source,
|
||||
&arg_certificate_source_type);
|
||||
OPTION_LONG("private-key-source", "SOURCE",
|
||||
"Specify how to use the private key "
|
||||
"(file, provider:PROVIDER, engine:ENGINE)"):
|
||||
r = parse_openssl_key_source_argument(arg,
|
||||
&arg_private_key_source,
|
||||
&arg_private_key_source_type);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_PRIVATE_KEY: {
|
||||
r = free_and_strdup_warn(&arg_private_key, optarg);
|
||||
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"):
|
||||
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)"):
|
||||
r = parse_openssl_certificate_source_argument(arg,
|
||||
&arg_certificate_source,
|
||||
&arg_certificate_source_type);
|
||||
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 '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
char **args = option_parser_get_args(&state);
|
||||
|
||||
if (!!arg_print_esp_path + !!arg_print_dollar_boot_path + (arg_print_root_device > 0) + arg_print_loader_path + arg_print_stub_path > 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"--print-esp-path/-p, --print-boot-path/-x, --print-root-device=/-R, --print-loader-path, --print-stub-path cannot be combined.");
|
||||
|
||||
if ((arg_root || arg_image) && argv[optind] && !STR_IN_SET(argv[optind], "status", "list",
|
||||
if ((arg_root || arg_image) && args[0] && !STR_IN_SET(args[0], "status", "list",
|
||||
"install", "update", "remove", "is-installed", "random-seed", "unlink", "cleanup"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Options --root= and --image= are not supported with verb %s.",
|
||||
argv[optind]);
|
||||
args[0]);
|
||||
|
||||
if (arg_root && arg_image)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
|
||||
@@ -647,7 +626,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
if (arg_install_source != INSTALL_SOURCE_AUTO && !arg_root && !arg_image)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--install-from-host is only supported with --root= or --image=.");
|
||||
|
||||
if (arg_dry_run && argv[optind] && !STR_IN_SET(argv[optind], "unlink", "cleanup"))
|
||||
if (arg_dry_run && args[0] && !STR_IN_SET(args[0], "unlink", "cleanup"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--dry-run is only supported with --unlink or --cleanup");
|
||||
|
||||
if (arg_secure_boot_auto_enroll) {
|
||||
@@ -670,36 +649,10 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
arg_pager_flags |= PAGER_DISABLE;
|
||||
}
|
||||
|
||||
*ret_args = args;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int bootctl_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 },
|
||||
{ "install", VERB_ANY, 1, 0, verb_install },
|
||||
{ "update", VERB_ANY, 1, 0, verb_install },
|
||||
{ "remove", VERB_ANY, 1, 0, verb_remove },
|
||||
{ "is-installed", VERB_ANY, 1, 0, verb_is_installed },
|
||||
{ "kernel-identify", 2, 2, 0, verb_kernel_identify },
|
||||
{ "kernel-inspect", 2, 2, 0, verb_kernel_inspect },
|
||||
{ "list", VERB_ANY, 1, 0, verb_list },
|
||||
{ "unlink", 2, 2, 0, verb_unlink },
|
||||
{ "cleanup", VERB_ANY, 1, 0, verb_cleanup },
|
||||
{ "set-default", 2, 2, 0, verb_set_efivar },
|
||||
{ "set-preferred", 2, 2, 0, verb_set_efivar },
|
||||
{ "set-oneshot", 2, 2, 0, verb_set_efivar },
|
||||
{ "set-timeout", 2, 2, 0, verb_set_efivar },
|
||||
{ "set-timeout-oneshot", 2, 2, 0, verb_set_efivar },
|
||||
{ "set-sysfail", 2, 2, 0, verb_set_efivar },
|
||||
{ "random-seed", VERB_ANY, 1, 0, verb_random_seed },
|
||||
{ "reboot-to-firmware", VERB_ANY, 2, 0, verb_reboot_to_firmware },
|
||||
{}
|
||||
};
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, NULL);
|
||||
}
|
||||
|
||||
static int vl_server(void) {
|
||||
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
|
||||
int r;
|
||||
@@ -740,7 +693,8 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
char **args = NULL;
|
||||
r = parse_argv(argc, argv, &args);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
@@ -800,7 +754,7 @@ static int run(int argc, char *argv[]) {
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
return bootctl_main(argc, argv);
|
||||
return dispatch_verb_with_args(args, NULL);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
|
||||
|
||||
+177
-250
@@ -1,6 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "sd-device.h"
|
||||
@@ -19,9 +18,11 @@
|
||||
#include "cryptsetup-util.h"
|
||||
#include "extract-word.h"
|
||||
#include "fileio.h"
|
||||
#include "format-table.h"
|
||||
#include "libfido2-util.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "options.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
@@ -30,6 +31,7 @@
|
||||
#include "process-util.h"
|
||||
#include "string-table.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "tpm2-pcr.h"
|
||||
#include "tpm2-util.h"
|
||||
|
||||
@@ -231,207 +233,96 @@ static int help(void) {
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...] [BLOCK-DEVICE]\n\n"
|
||||
"%5$sEnroll a security token or authentication credential to a LUKS volume.%6$s\n\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" --no-pager Do not spawn a pager\n"
|
||||
" --list-devices List candidate block devices to operate on\n"
|
||||
" --wipe-slot=SLOT1,SLOT2,…\n"
|
||||
" Wipe specified slots\n"
|
||||
"\n%3$sUnlocking:%4$s\n"
|
||||
" --unlock-key-file=PATH\n"
|
||||
" Use a file to unlock the volume\n"
|
||||
" --unlock-fido2-device=PATH\n"
|
||||
" Use a FIDO2 device to unlock the volume\n"
|
||||
" --unlock-tpm2-device=PATH\n"
|
||||
" Use a TPM2 device to unlock the volume\n"
|
||||
"\n%3$sSimple Enrollment:%4$s\n"
|
||||
" --password Enroll a user-supplied password\n"
|
||||
" --recovery-key Enroll a recovery key\n"
|
||||
"\n%3$sPKCS#11 Enrollment:%4$s\n"
|
||||
" --pkcs11-token-uri=URI|auto|list\n"
|
||||
" Enroll a PKCS#11 security token or list them\n"
|
||||
"\n%3$sFIDO2 Enrollment:%4$s\n"
|
||||
" --fido2-device=PATH|auto|list\n"
|
||||
" Enroll a FIDO2-HMAC security token or list them\n"
|
||||
" --fido2-salt-file=PATH\n"
|
||||
" Use salt from a file instead of generating one\n"
|
||||
" --fido2-parameters-in-header=BOOL\n"
|
||||
" Whether to store FIDO2 parameters in the LUKS2 header\n"
|
||||
" --fido2-credential-algorithm=STRING\n"
|
||||
" Specify COSE algorithm for FIDO2 credential\n"
|
||||
" --fido2-with-client-pin=BOOL\n"
|
||||
" Whether to require entering a PIN to unlock the volume\n"
|
||||
" --fido2-with-user-presence=BOOL\n"
|
||||
" Whether to require user presence to unlock the volume\n"
|
||||
" --fido2-with-user-verification=BOOL\n"
|
||||
" Whether to require user verification to unlock the volume\n"
|
||||
"\n%3$sTPM2 Enrollment:%4$s\n"
|
||||
" --tpm2-device=PATH|auto|list\n"
|
||||
" Enroll a TPM2 device or list them\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"
|
||||
" Specify TPM2 PCRs to seal against\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-signature=PATH\n"
|
||||
" Validate public key enrollment works with JSON signature\n"
|
||||
" file\n"
|
||||
" --tpm2-pcrlock=PATH\n"
|
||||
" Specify pcrlock policy to lock against\n"
|
||||
" --tpm2-with-pin=BOOL\n"
|
||||
" Whether to require entering a PIN to unlock the volume\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
static const char* const groups[] = {
|
||||
NULL,
|
||||
"Unlocking",
|
||||
"Simple Enrollment",
|
||||
"PKCS#11 Enrollment",
|
||||
"FIDO2 Enrollment",
|
||||
"TPM2 Enrollment",
|
||||
};
|
||||
|
||||
_cleanup_(table_unref_many) Table *tables[ELEMENTSOF(groups) + 1] = {};
|
||||
|
||||
for (size_t i = 0; i < ELEMENTSOF(groups); i++) {
|
||||
r = option_parser_get_help_table_group(groups[i], &tables[i]);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
(void) table_sync_column_widths(0, tables[0], tables[1], tables[2], tables[3], tables[4], tables[5]);
|
||||
|
||||
printf("%s [OPTIONS...] [BLOCK-DEVICE]\n\n"
|
||||
"%sEnroll a security token or authentication credential to a LUKS volume.%s\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
ansi_underline(),
|
||||
ansi_normal(),
|
||||
ansi_highlight(),
|
||||
ansi_normal());
|
||||
|
||||
for (size_t i = 0; i < ELEMENTSOF(groups); i++) {
|
||||
printf("\n%s%s:%s\n", ansi_underline(), groups[i] ?: "Options", ansi_normal());
|
||||
|
||||
r = table_print_or_warn(tables[i]);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
printf("\nSee the %s for details.\n", link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_NO_PAGER,
|
||||
ARG_PASSWORD,
|
||||
ARG_RECOVERY_KEY,
|
||||
ARG_UNLOCK_KEYFILE,
|
||||
ARG_UNLOCK_FIDO2_DEVICE,
|
||||
ARG_UNLOCK_TPM2_DEVICE,
|
||||
ARG_PKCS11_TOKEN_URI,
|
||||
ARG_FIDO2_DEVICE,
|
||||
ARG_FIDO2_SALT_FILE,
|
||||
ARG_FIDO2_PARAMETERS_IN_HEADER,
|
||||
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_SIGNATURE,
|
||||
ARG_TPM2_PCRLOCK,
|
||||
ARG_TPM2_WITH_PIN,
|
||||
ARG_WIPE_SLOT,
|
||||
ARG_FIDO2_WITH_PIN,
|
||||
ARG_FIDO2_WITH_UP,
|
||||
ARG_FIDO2_WITH_UV,
|
||||
ARG_FIDO2_CRED_ALG,
|
||||
ARG_LIST_DEVICES,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
||||
{ "password", no_argument, NULL, ARG_PASSWORD },
|
||||
{ "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY },
|
||||
{ "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE },
|
||||
{ "unlock-fido2-device", required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE },
|
||||
{ "unlock-tpm2-device", required_argument, NULL, ARG_UNLOCK_TPM2_DEVICE },
|
||||
{ "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI },
|
||||
{ "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG },
|
||||
{ "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE },
|
||||
{ "fido2-salt-file", required_argument, NULL, ARG_FIDO2_SALT_FILE },
|
||||
{ "fido2-parameters-in-header", required_argument, NULL, ARG_FIDO2_PARAMETERS_IN_HEADER },
|
||||
{ "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN },
|
||||
{ "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP },
|
||||
{ "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV },
|
||||
{ "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-signature", required_argument, NULL, ARG_TPM2_SIGNATURE },
|
||||
{ "tpm2-pcrlock", required_argument, NULL, ARG_TPM2_PCRLOCK },
|
||||
{ "tpm2-with-pin", required_argument, NULL, ARG_TPM2_WITH_PIN },
|
||||
{ "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT },
|
||||
{ "list-devices", no_argument, NULL, ARG_LIST_DEVICES },
|
||||
{}
|
||||
};
|
||||
|
||||
bool auto_public_key_pcr_mask = true, auto_pcrlock = true;
|
||||
int c, r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
|
||||
OptionParser state = { argc, argv };
|
||||
const char *arg;
|
||||
int r;
|
||||
|
||||
FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c)
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
OPTION_COMMON_HELP:
|
||||
return help();
|
||||
|
||||
case ARG_VERSION:
|
||||
OPTION_COMMON_VERSION:
|
||||
return version();
|
||||
|
||||
case ARG_NO_PAGER:
|
||||
OPTION_COMMON_NO_PAGER:
|
||||
arg_pager_flags |= PAGER_DISABLE;
|
||||
break;
|
||||
|
||||
case ARG_FIDO2_WITH_PIN:
|
||||
r = parse_boolean_argument("--fido2-with-client-pin=", optarg, NULL);
|
||||
OPTION_LONG("list-devices", NULL,
|
||||
"List candidate block devices to operate on"):
|
||||
return blockdev_list(BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_REQUIRE_LUKS,
|
||||
/* ret_devices= */ NULL,
|
||||
/* ret_n_devices= */ NULL);
|
||||
|
||||
OPTION_LONG("wipe-slot", "SLOT1,SLOT2,…",
|
||||
"Wipe specified slots"):
|
||||
r = parse_wipe_slot(arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r);
|
||||
break;
|
||||
|
||||
case ARG_FIDO2_WITH_UP:
|
||||
r = parse_boolean_argument("--fido2-with-user-presence=", optarg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
OPTION_GROUP("Unlocking"): {}
|
||||
|
||||
SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r);
|
||||
break;
|
||||
|
||||
case ARG_FIDO2_WITH_UV:
|
||||
r = parse_boolean_argument("--fido2-with-user-verification=", optarg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r);
|
||||
break;
|
||||
|
||||
case ARG_PASSWORD:
|
||||
if (arg_enroll_type >= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Multiple operations specified at once, refusing.");
|
||||
|
||||
arg_enroll_type = ENROLL_PASSWORD;
|
||||
break;
|
||||
|
||||
case ARG_RECOVERY_KEY:
|
||||
if (arg_enroll_type >= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Multiple operations specified at once, refusing.");
|
||||
|
||||
arg_enroll_type = ENROLL_RECOVERY;
|
||||
break;
|
||||
|
||||
case ARG_UNLOCK_KEYFILE:
|
||||
OPTION_LONG("unlock-key-file", "PATH",
|
||||
"Use a file to unlock the volume"):
|
||||
if (arg_unlock_type != UNLOCK_PASSWORD)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Multiple unlock methods specified at once, refusing.");
|
||||
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_unlock_keyfile);
|
||||
r = parse_path_argument(arg, /* suppress_root= */ true, &arg_unlock_keyfile);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
arg_unlock_type = UNLOCK_KEYFILE;
|
||||
break;
|
||||
|
||||
case ARG_UNLOCK_FIDO2_DEVICE: {
|
||||
OPTION_LONG("unlock-fido2-device", "PATH",
|
||||
"Use a FIDO2 device to unlock the volume"): {
|
||||
_cleanup_free_ char *device = NULL;
|
||||
|
||||
if (arg_unlock_type != UNLOCK_PASSWORD)
|
||||
@@ -440,8 +331,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
assert(!arg_unlock_fido2_device);
|
||||
|
||||
if (!streq(optarg, "auto")) {
|
||||
device = strdup(optarg);
|
||||
if (!streq(arg, "auto")) {
|
||||
device = strdup(arg);
|
||||
if (!device)
|
||||
return log_oom();
|
||||
}
|
||||
@@ -451,7 +342,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_UNLOCK_TPM2_DEVICE: {
|
||||
OPTION_LONG("unlock-tpm2-device", "PATH",
|
||||
"Use a TPM2 device to unlock the volume"): {
|
||||
_cleanup_free_ char *device = NULL;
|
||||
|
||||
if (arg_unlock_type != UNLOCK_PASSWORD)
|
||||
@@ -460,8 +352,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
assert(!arg_unlock_tpm2_device);
|
||||
|
||||
if (!streq(optarg, "auto")) {
|
||||
device = strdup(optarg);
|
||||
if (!streq(arg, "auto")) {
|
||||
device = strdup(arg);
|
||||
if (!device)
|
||||
return log_oom();
|
||||
}
|
||||
@@ -471,25 +363,48 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_PKCS11_TOKEN_URI: {
|
||||
OPTION_GROUP("Simple Enrollment"): {}
|
||||
|
||||
OPTION_LONG("password", NULL,
|
||||
"Enroll a user-supplied password"):
|
||||
if (arg_enroll_type >= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Multiple operations specified at once, refusing.");
|
||||
|
||||
arg_enroll_type = ENROLL_PASSWORD;
|
||||
break;
|
||||
|
||||
OPTION_LONG("recovery-key", NULL,
|
||||
"Enroll a recovery key"):
|
||||
if (arg_enroll_type >= 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Multiple operations specified at once, refusing.");
|
||||
|
||||
arg_enroll_type = ENROLL_RECOVERY;
|
||||
break;
|
||||
|
||||
OPTION_GROUP("PKCS#11 Enrollment"): {}
|
||||
|
||||
OPTION_LONG("pkcs11-token-uri", "URI|auto|list",
|
||||
"Enroll a PKCS#11 security token or list them"): {
|
||||
_cleanup_free_ char *uri = NULL;
|
||||
|
||||
if (streq(optarg, "list"))
|
||||
if (streq(arg, "list"))
|
||||
return pkcs11_list_tokens();
|
||||
|
||||
if (arg_enroll_type >= 0 || arg_pkcs11_token_uri)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Multiple operations specified at once, refusing.");
|
||||
|
||||
if (streq(optarg, "auto")) {
|
||||
if (streq(arg, "auto")) {
|
||||
r = pkcs11_find_token_auto(&uri);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else {
|
||||
if (!pkcs11_uri_valid(optarg))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg);
|
||||
if (!pkcs11_uri_valid(arg))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", arg);
|
||||
|
||||
uri = strdup(optarg);
|
||||
uri = strdup(arg);
|
||||
if (!uri)
|
||||
return log_oom();
|
||||
}
|
||||
@@ -499,24 +414,21 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_FIDO2_CRED_ALG:
|
||||
r = parse_fido2_algorithm(optarg, &arg_fido2_cred_alg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse COSE algorithm: %s", optarg);
|
||||
break;
|
||||
OPTION_GROUP("FIDO2 Enrollment"): {}
|
||||
|
||||
case ARG_FIDO2_DEVICE: {
|
||||
OPTION_LONG("fido2-device", "PATH|auto|list",
|
||||
"Enroll a FIDO2-HMAC security token or list them"): {
|
||||
_cleanup_free_ char *device = NULL;
|
||||
|
||||
if (streq(optarg, "list"))
|
||||
if (streq(arg, "list"))
|
||||
return fido2_list_devices();
|
||||
|
||||
if (arg_enroll_type >= 0 || arg_fido2_device)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Multiple operations specified at once, refusing.");
|
||||
|
||||
if (!streq(optarg, "auto")) {
|
||||
device = strdup(optarg);
|
||||
if (!streq(arg, "auto")) {
|
||||
device = strdup(arg);
|
||||
if (!device)
|
||||
return log_oom();
|
||||
}
|
||||
@@ -526,32 +438,66 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_FIDO2_SALT_FILE:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_fido2_salt_file);
|
||||
OPTION_LONG("fido2-salt-file", "PATH",
|
||||
"Use salt from a file instead of generating one"):
|
||||
r = parse_path_argument(arg, /* suppress_root= */ true, &arg_fido2_salt_file);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_FIDO2_PARAMETERS_IN_HEADER:
|
||||
r = parse_boolean_argument("--fido2-parameters-in-header=", optarg, &arg_fido2_parameters_in_header);
|
||||
OPTION_LONG("fido2-parameters-in-header", "BOOL",
|
||||
"Whether to store FIDO2 parameters in the LUKS2 header"):
|
||||
r = parse_boolean_argument("--fido2-parameters-in-header=", arg, &arg_fido2_parameters_in_header);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_TPM2_DEVICE: {
|
||||
OPTION_LONG("fido2-credential-algorithm", "STRING",
|
||||
"Specify COSE algorithm for FIDO2 credential"):
|
||||
r = parse_fido2_algorithm(arg, &arg_fido2_cred_alg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse COSE algorithm: %s", arg);
|
||||
break;
|
||||
|
||||
OPTION_LONG("fido2-with-client-pin", "BOOL",
|
||||
"Whether to require entering a PIN to unlock the volume"):
|
||||
r = parse_boolean_argument("--fido2-with-client-pin=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_PIN, r);
|
||||
break;
|
||||
|
||||
OPTION_LONG("fido2-with-user-presence", "BOOL",
|
||||
"Whether to require user presence to unlock the volume"):
|
||||
r = parse_boolean_argument("--fido2-with-user-presence=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UP, r);
|
||||
break;
|
||||
|
||||
OPTION_LONG("fido2-with-user-verification", "BOOL",
|
||||
"Whether to require user verification to unlock the volume"):
|
||||
r = parse_boolean_argument("--fido2-with-user-verification=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
SET_FLAG(arg_fido2_lock_with, FIDO2ENROLL_UV, r);
|
||||
break;
|
||||
|
||||
OPTION_GROUP("TPM2 Enrollment"): {}
|
||||
|
||||
OPTION_LONG("tpm2-device", "PATH|auto|list",
|
||||
"Enroll a TPM2 device or list them"): {
|
||||
_cleanup_free_ char *device = NULL;
|
||||
|
||||
if (streq(optarg, "list"))
|
||||
if (streq(arg, "list"))
|
||||
return tpm2_list_devices(/* legend= */ true, /* quiet= */ false);
|
||||
|
||||
if (arg_enroll_type >= 0 || arg_tpm2_device)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Multiple operations specified at once, refusing.");
|
||||
|
||||
if (!streq(optarg, "auto")) {
|
||||
device = strdup(optarg);
|
||||
if (!streq(arg, "auto")) {
|
||||
device = strdup(arg);
|
||||
if (!device)
|
||||
return log_oom();
|
||||
}
|
||||
@@ -561,115 +507,96 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_TPM2_DEVICE_KEY:
|
||||
OPTION_LONG("tpm2-device-key", "PATH",
|
||||
"Enroll a TPM2 device using its public key"):
|
||||
if (arg_enroll_type >= 0 || arg_tpm2_device_key)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Multiple operations specified at once, refusing.");
|
||||
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_device_key);
|
||||
r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_device_key);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
arg_enroll_type = ENROLL_TPM2;
|
||||
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+PCR3+…",
|
||||
"Specify TPM2 PCRs to seal against"):
|
||||
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:
|
||||
OPTION_LONG("tpm2-public-key", "PATH",
|
||||
"Enroll signed TPM2 PCR policy against PEM public key"):
|
||||
/* an empty argument disables loading a public key */
|
||||
if (isempty(optarg)) {
|
||||
if (isempty(arg)) {
|
||||
arg_tpm2_load_public_key = false;
|
||||
arg_tpm2_public_key = mfree(arg_tpm2_public_key);
|
||||
break;
|
||||
}
|
||||
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key);
|
||||
r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_public_key);
|
||||
if (r < 0)
|
||||
return r;
|
||||
arg_tpm2_load_public_key = true;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_TPM2_PUBLIC_KEY_PCRS:
|
||||
OPTION_LONG("tpm2-public-key-pcrs", "PCR1+PCR2+PCR3+…",
|
||||
"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_SIGNATURE:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature);
|
||||
OPTION_LONG("tpm2-signature", "PATH",
|
||||
"Validate public key enrollment works with JSON signature file"):
|
||||
r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tpm2_signature);
|
||||
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_TPM2_WITH_PIN:
|
||||
r = parse_boolean_argument("--tpm2-with-pin=", optarg, &arg_tpm2_pin);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_WIPE_SLOT:
|
||||
r = parse_wipe_slot(optarg);
|
||||
OPTION_LONG("tpm2-with-pin", "BOOL",
|
||||
"Whether to require entering a PIN to unlock the volume"):
|
||||
r = parse_boolean_argument("--tpm2-with-pin=", arg, &arg_tpm2_pin);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_LIST_DEVICES:
|
||||
return blockdev_list(BLOCKDEV_LIST_SHOW_SYMLINKS|BLOCKDEV_LIST_REQUIRE_LUKS,
|
||||
/* ret_devices= */ NULL,
|
||||
/* ret_n_devices= */ NULL);
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
if (argc > optind+1)
|
||||
char **args = option_parser_get_args(&state);
|
||||
|
||||
if (strv_length(args) > 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Too many arguments, refusing.");
|
||||
|
||||
if (optind < argc) {
|
||||
r = parse_path_argument(argv[optind], false, &arg_node);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else {
|
||||
if (wipe_requested())
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Wiping requested and no block device node specified, refusing.");
|
||||
|
||||
if (args[0])
|
||||
r = parse_path_argument(args[0], false, &arg_node);
|
||||
else if (!wipe_requested())
|
||||
r = determine_default_node();
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Wiping requested and no block device node specified, refusing.");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (arg_enroll_type == ENROLL_FIDO2) {
|
||||
|
||||
if (arg_unlock_type == UNLOCK_FIDO2 && !(arg_fido2_device && arg_unlock_fido2_device))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"When both enrolling and unlocking with FIDO2 tokens, automatic discovery is unsupported. "
|
||||
|
||||
+47
-40
@@ -1,6 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
@@ -11,7 +10,6 @@
|
||||
#include "sd-messages.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "argv-util.h"
|
||||
#include "ask-password-api.h"
|
||||
#include "build.h"
|
||||
#include "cryptsetup-fido2.h"
|
||||
@@ -27,6 +25,7 @@
|
||||
#include "escape.h"
|
||||
#include "extract-word.h"
|
||||
#include "fileio.h"
|
||||
#include "format-table.h"
|
||||
#include "fs-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "json-util.h"
|
||||
@@ -36,6 +35,7 @@
|
||||
#include "main-func.h"
|
||||
#include "memory-util.h"
|
||||
#include "nulstr-util.h"
|
||||
#include "options.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "pkcs11-util.h"
|
||||
@@ -2451,61 +2451,69 @@ static int attach_luks_or_plain_or_bitlk(
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
_cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-cryptsetup", "8", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s attach VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG]\n"
|
||||
"%1$s detach VOLUME\n\n"
|
||||
"%2$sAttach or detach an encrypted block device.%3$s\n\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
"\nSee the %4$s for details.\n",
|
||||
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"
|
||||
"%sAttach or detach an encrypted block device.%s\n"
|
||||
"\n%sCommands:%s\n",
|
||||
program_invocation_short_name,
|
||||
ansi_highlight(),
|
||||
ansi_normal(),
|
||||
link);
|
||||
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 parse_argv(int argc, char *argv[]) {
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{}
|
||||
};
|
||||
|
||||
int c;
|
||||
VERB_COMMON_HELP_HIDDEN(help);
|
||||
|
||||
static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
assert(ret_args);
|
||||
|
||||
if (argv_looks_like_help(argc, argv))
|
||||
return help();
|
||||
OptionParser state = { argc, argv };
|
||||
const char *arg;
|
||||
|
||||
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
|
||||
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 '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
*ret_args = option_parser_get_args(&state);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -2588,6 +2596,8 @@ static int discover_key(const char *key_file, const char *volume, TokenType toke
|
||||
return r;
|
||||
}
|
||||
|
||||
VERB(verb_attach, "attach", "VOLUME SOURCE-DEVICE [KEY-FILE] [CONFIG]", 3, 5, 0,
|
||||
"Attach an encrypted block device");
|
||||
static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
|
||||
_unused_ _cleanup_(remove_and_erasep) const char *destroy_key_file = NULL;
|
||||
@@ -2828,6 +2838,8 @@ static int verb_attach(int argc, char *argv[], uintptr_t _data, void *userdata)
|
||||
return 0;
|
||||
}
|
||||
|
||||
VERB(verb_detach, "detach", "VOLUME", 2, 2, 0,
|
||||
"Detach an encrypted block device");
|
||||
static int verb_detach(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(crypt_freep) struct crypt_device *cd = NULL;
|
||||
const char *volume = ASSERT_PTR(argv[1]);
|
||||
@@ -2862,19 +2874,14 @@ 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;
|
||||
|
||||
cryptsetup_enable_logging(NULL);
|
||||
|
||||
static const Verb verbs[] = {
|
||||
{ "attach", 3, 5, 0, verb_attach },
|
||||
{ "detach", 2, 2, 0, verb_detach },
|
||||
{}
|
||||
};
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, NULL);
|
||||
return dispatch_verb_with_args(args, NULL);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
|
||||
+38
-55
@@ -1,6 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
@@ -10,12 +9,14 @@
|
||||
#include "errno-util.h"
|
||||
#include "extract-word.h"
|
||||
#include "fd-util.h"
|
||||
#include "format-table.h"
|
||||
#include "fs-util.h"
|
||||
#include "glyph-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "nulstr-util.h"
|
||||
#include "options.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
#include "path-util.h"
|
||||
@@ -459,23 +460,26 @@ static int process_suffix_chop(const char *arg) {
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
_cleanup_(table_unrefp) Table *options = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-delta", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%s [OPTIONS...] [SUFFIX...]\n\n"
|
||||
"Find overridden configuration files.\n\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" --no-pager Do not pipe output into a pager\n"
|
||||
" --diff[=1|0] Show a diff when overridden files differ\n"
|
||||
" -t --type=LIST... Only display a selected set of override types\n"
|
||||
"\nSee the %s for details.\n",
|
||||
program_invocation_short_name,
|
||||
link);
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("%s [OPTIONS...] [SUFFIX...]\n\n"
|
||||
"Find overridden configuration files.\n\n",
|
||||
program_invocation_short_name);
|
||||
|
||||
r = table_print_or_warn(options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("\nSee the %s for details.\n", link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -509,66 +513,45 @@ static int parse_flags(const char *flag_str, int flags) {
|
||||
}
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_NO_PAGER = 0x100,
|
||||
ARG_DIFF,
|
||||
ARG_VERSION
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
||||
{ "diff", optional_argument, NULL, ARG_DIFF },
|
||||
{ "type", required_argument, NULL, 't' },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
|
||||
assert(argc >= 1);
|
||||
static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "ht:", 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_NO_PAGER:
|
||||
OPTION_COMMON_NO_PAGER:
|
||||
arg_pager_flags |= PAGER_DISABLE;
|
||||
break;
|
||||
|
||||
case 't': {
|
||||
int f;
|
||||
f = parse_flags(optarg, arg_flags);
|
||||
if (f < 0)
|
||||
OPTION('t', "type", "TYPE...", "Only display a selected set of override types"):
|
||||
r = parse_flags(arg, arg_flags);
|
||||
if (r < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Failed to parse flags field.");
|
||||
arg_flags = f;
|
||||
arg_flags = r;
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_DIFF:
|
||||
r = parse_boolean_argument("--diff", optarg, NULL);
|
||||
OPTION_LONG_FLAGS(OPTION_OPTIONAL_ARG, "diff", "yes|no",
|
||||
"Show a diff when overridden files differ"):
|
||||
r = parse_boolean_argument("--diff", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
arg_diff = r;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
*ret_args = option_parser_get_args(&state);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -577,7 +560,8 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
char **args = NULL;
|
||||
r = parse_argv(argc, argv, &args);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
@@ -591,17 +575,16 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
pager_open(arg_pager_flags);
|
||||
|
||||
if (optind < argc) {
|
||||
for (int i = optind; i < argc; i++) {
|
||||
path_simplify(argv[i]);
|
||||
if (!strv_isempty(args)) {
|
||||
STRV_FOREACH(i, args) {
|
||||
path_simplify(*i);
|
||||
|
||||
k = process_suffix_chop(argv[i]);
|
||||
k = process_suffix_chop(*i);
|
||||
if (k < 0)
|
||||
r = k;
|
||||
else
|
||||
n_found += k;
|
||||
}
|
||||
|
||||
} else {
|
||||
k = process_suffixes(NULL);
|
||||
if (k < 0)
|
||||
|
||||
+46
-67
@@ -1,12 +1,13 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "build.h"
|
||||
#include "format-table.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "options.h"
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "string-util.h"
|
||||
@@ -26,107 +27,83 @@ static bool arg_instance = false;
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
_cleanup_(table_unrefp) Table *options = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-escape", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...] [NAME...]\n\n"
|
||||
"%3$sEscape strings for usage in systemd unit names.%4$s\n\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" --suffix=SUFFIX Unit suffix to append to escaped strings\n"
|
||||
" --template=TEMPLATE Insert strings as instance into template\n"
|
||||
" --instance With --unescape, show just the instance part\n"
|
||||
" -u --unescape Unescape strings\n"
|
||||
" -m --mangle Mangle strings\n"
|
||||
" -p --path When escaping/unescaping assume the string is a path\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("%s [OPTIONS...] [NAME...]\n\n"
|
||||
"%sEscape strings for usage in systemd unit names.%s\n\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
ansi_highlight(),
|
||||
ansi_normal());
|
||||
|
||||
r = table_print_or_warn(options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("\nSee the %s for details.\n", link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_SUFFIX,
|
||||
ARG_TEMPLATE
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "suffix", required_argument, NULL, ARG_SUFFIX },
|
||||
{ "template", required_argument, NULL, ARG_TEMPLATE },
|
||||
{ "unescape", no_argument, NULL, 'u' },
|
||||
{ "mangle", no_argument, NULL, 'm' },
|
||||
{ "path", no_argument, NULL, 'p' },
|
||||
{ "instance", no_argument, NULL, 'i' },
|
||||
{}
|
||||
};
|
||||
|
||||
int c;
|
||||
|
||||
static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hump", 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_SUFFIX: {
|
||||
UnitType t = unit_type_from_string(optarg);
|
||||
OPTION_LONG("suffix", "SUFFIX", "Unit suffix to append to escaped strings"): {
|
||||
UnitType t = unit_type_from_string(arg);
|
||||
if (t < 0)
|
||||
return log_error_errno(t, "Invalid unit suffix type \"%s\".", optarg);
|
||||
return log_error_errno(t, "Invalid unit suffix type \"%s\".", arg);
|
||||
|
||||
arg_suffix = optarg;
|
||||
arg_suffix = arg;
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_TEMPLATE:
|
||||
if (!unit_name_is_valid(optarg, UNIT_NAME_TEMPLATE))
|
||||
OPTION_LONG("template", "TEMPLATE", "Insert strings as instance into template"):
|
||||
if (!unit_name_is_valid(arg, UNIT_NAME_TEMPLATE))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Template name %s is not valid.", optarg);
|
||||
"Template name %s is not valid.", arg);
|
||||
|
||||
arg_template = optarg;
|
||||
arg_template = arg;
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
arg_action = ACTION_UNESCAPE;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
arg_action = ACTION_MANGLE;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
arg_path = true;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
OPTION_LONG("instance", NULL, "With --unescape, show just the instance part"):
|
||||
arg_instance = true;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
OPTION('u', "unescape", NULL, "Unescape strings"):
|
||||
arg_action = ACTION_UNESCAPE;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
OPTION('m', "mangle", NULL, "Mangle strings"):
|
||||
arg_action = ACTION_MANGLE;
|
||||
break;
|
||||
|
||||
OPTION('p', "path", NULL,
|
||||
"When escaping/unescaping assume the string is a path"):
|
||||
arg_path = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (optind >= argc)
|
||||
if (option_parser_get_n_args(&state) == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Not enough arguments.");
|
||||
|
||||
@@ -154,6 +131,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"--instance may not be combined with --template.");
|
||||
|
||||
*ret_args = option_parser_get_args(&state);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -162,11 +140,12 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
char **args = NULL;
|
||||
r = parse_argv(argc, argv, &args);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
STRV_FOREACH(i, argv + optind) {
|
||||
STRV_FOREACH(i, args) {
|
||||
_cleanup_free_ char *e = NULL;
|
||||
|
||||
switch (arg_action) {
|
||||
@@ -267,7 +246,7 @@ static int run(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (i != argv + optind)
|
||||
if (i != args)
|
||||
fputc(' ', stdout);
|
||||
|
||||
fputs(e, stdout);
|
||||
|
||||
+78
-125
@@ -1,6 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <locale.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -12,12 +11,14 @@
|
||||
#include "discover-image.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "format-table.h"
|
||||
#include "import-raw.h"
|
||||
#include "import-tar.h"
|
||||
#include "import-util.h"
|
||||
#include "io-util.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "options.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
@@ -138,7 +139,8 @@ static void on_tar_finished(TarImport *import, int error, void *userdata) {
|
||||
sd_event_exit(event, ABS(error));
|
||||
}
|
||||
|
||||
static int verb_import_tar(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
VERB(verb_tar, "tar", "FILE [NAME]", 2, 3, 0, "Import a TAR image");
|
||||
static int verb_tar(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(tar_import_unrefp) TarImport *import = NULL;
|
||||
_cleanup_free_ char *ll = NULL, *normalized = NULL;
|
||||
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
|
||||
@@ -207,7 +209,8 @@ static void on_raw_finished(RawImport *import, int error, void *userdata) {
|
||||
sd_event_exit(event, ABS(error));
|
||||
}
|
||||
|
||||
static int verb_import_raw(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
VERB(verb_raw, "raw", "FILE [NAME]", 2, 3, 0, "Import a RAW image");
|
||||
static int verb_raw(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(raw_import_unrefp) RawImport *import = NULL;
|
||||
_cleanup_free_ char *ll = NULL, *normalized = NULL;
|
||||
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
|
||||
@@ -269,193 +272,152 @@ static int verb_import_raw(int argc, char *argv[], uintptr_t _data, void *userda
|
||||
}
|
||||
|
||||
static int help(void) {
|
||||
printf("%1$s [OPTIONS...] {COMMAND} ...\n"
|
||||
"\n%4$sImport disk images.%5$s\n"
|
||||
"\n%2$sCommands:%3$s\n"
|
||||
" tar FILE [NAME] Import a TAR image\n"
|
||||
" raw FILE [NAME] Import a RAW image\n"
|
||||
"\n%2$sOptions:%3$s\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" --force Force creation of image\n"
|
||||
" --image-root=PATH Image root directory\n"
|
||||
" --read-only Create a read-only image\n"
|
||||
" --direct Import directly to specified file\n"
|
||||
" --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n"
|
||||
" instead of a directory\n"
|
||||
" --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n"
|
||||
" subvolume\n"
|
||||
" --convert-qcow2=BOOL Controls whether to convert QCOW2 images to\n"
|
||||
" regular disk images\n"
|
||||
" --sync=BOOL Controls whether to sync() before completing\n"
|
||||
" --offset=BYTES Offset to seek to in destination\n"
|
||||
" --size-max=BYTES Maximum number of bytes to write to destination\n"
|
||||
" --class=CLASS Select image class (machine, sysext, confext,\n"
|
||||
" portable)\n"
|
||||
" --system Operate in per-system mode\n"
|
||||
" --user Operate in per-user mode\n",
|
||||
_cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL;
|
||||
int r;
|
||||
|
||||
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"
|
||||
"%sImport disk images.%s\n"
|
||||
"\n%sCommands:%s\n",
|
||||
program_invocation_short_name,
|
||||
ansi_underline(),
|
||||
ansi_normal(),
|
||||
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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
return help();
|
||||
}
|
||||
VERB_COMMON_HELP_HIDDEN(help);
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_FORCE,
|
||||
ARG_IMAGE_ROOT,
|
||||
ARG_READ_ONLY,
|
||||
ARG_DIRECT,
|
||||
ARG_BTRFS_SUBVOL,
|
||||
ARG_BTRFS_QUOTA,
|
||||
ARG_CONVERT_QCOW2,
|
||||
ARG_SYNC,
|
||||
ARG_OFFSET,
|
||||
ARG_SIZE_MAX,
|
||||
ARG_CLASS,
|
||||
ARG_SYSTEM,
|
||||
ARG_USER,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "force", no_argument, NULL, ARG_FORCE },
|
||||
{ "image-root", required_argument, NULL, ARG_IMAGE_ROOT },
|
||||
{ "read-only", no_argument, NULL, ARG_READ_ONLY },
|
||||
{ "direct", no_argument, NULL, ARG_DIRECT },
|
||||
{ "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL },
|
||||
{ "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA },
|
||||
{ "convert-qcow2", required_argument, NULL, ARG_CONVERT_QCOW2 },
|
||||
{ "sync", required_argument, NULL, ARG_SYNC },
|
||||
{ "offset", required_argument, NULL, ARG_OFFSET },
|
||||
{ "size-max", required_argument, NULL, ARG_SIZE_MAX },
|
||||
{ "class", required_argument, NULL, ARG_CLASS },
|
||||
{ "system", no_argument, NULL, ARG_SYSTEM },
|
||||
{ "user", no_argument, NULL, ARG_USER },
|
||||
{}
|
||||
};
|
||||
|
||||
int r, c;
|
||||
static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
int r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
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_FORCE:
|
||||
OPTION_LONG("force", NULL, "Force creation of image"):
|
||||
arg_import_flags |= IMPORT_FORCE;
|
||||
break;
|
||||
|
||||
case ARG_IMAGE_ROOT:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root);
|
||||
OPTION_LONG("image-root", "PATH", "Image root directory"):
|
||||
r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image_root);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_READ_ONLY:
|
||||
OPTION_LONG("read-only", NULL, "Create a read-only image"):
|
||||
arg_import_flags |= IMPORT_READ_ONLY;
|
||||
break;
|
||||
|
||||
case ARG_DIRECT:
|
||||
OPTION_LONG("direct", NULL, "Import directly to specified file"):
|
||||
arg_import_flags |= IMPORT_DIRECT;
|
||||
break;
|
||||
|
||||
case ARG_BTRFS_SUBVOL:
|
||||
r = parse_boolean_argument("--btrfs-subvol=", optarg, NULL);
|
||||
OPTION_LONG("btrfs-subvol", "BOOL",
|
||||
"Controls whether to create a btrfs subvolume instead of a directory"):
|
||||
r = parse_boolean_argument("--btrfs-subvol=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r);
|
||||
break;
|
||||
|
||||
case ARG_BTRFS_QUOTA:
|
||||
r = parse_boolean_argument("--btrfs-quota=", optarg, NULL);
|
||||
OPTION_LONG("btrfs-quota", "BOOL",
|
||||
"Controls whether to set up quota for btrfs subvolume"):
|
||||
r = parse_boolean_argument("--btrfs-quota=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r);
|
||||
break;
|
||||
|
||||
case ARG_CONVERT_QCOW2:
|
||||
r = parse_boolean_argument("--convert-qcow2=", optarg, NULL);
|
||||
OPTION_LONG("convert-qcow2", "BOOL",
|
||||
"Controls whether to convert QCOW2 images to regular disk images"):
|
||||
r = parse_boolean_argument("--convert-qcow2=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
SET_FLAG(arg_import_flags, IMPORT_CONVERT_QCOW2, r);
|
||||
break;
|
||||
|
||||
case ARG_SYNC:
|
||||
r = parse_boolean_argument("--sync=", optarg, NULL);
|
||||
OPTION_LONG("sync", "BOOL", "Controls whether to sync() before completing"):
|
||||
r = parse_boolean_argument("--sync=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
SET_FLAG(arg_import_flags, IMPORT_SYNC, r);
|
||||
break;
|
||||
|
||||
case ARG_OFFSET: {
|
||||
OPTION_LONG("offset", "BYTES", "Offset to seek to in destination"): {
|
||||
uint64_t u;
|
||||
|
||||
r = safe_atou64(optarg, &u);
|
||||
r = safe_atou64(arg, &u);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --offset= argument: %s", optarg);
|
||||
return log_error_errno(r, "Failed to parse --offset= argument: %s", arg);
|
||||
if (!FILE_SIZE_VALID(u))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", optarg);
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", arg);
|
||||
|
||||
arg_offset = u;
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_SIZE_MAX: {
|
||||
OPTION_LONG("size-max", "BYTES", "Maximum number of bytes to write to destination"): {
|
||||
uint64_t u;
|
||||
|
||||
r = parse_size(optarg, 1024, &u);
|
||||
r = parse_size(arg, 1024, &u);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --size-max= argument: %s", optarg);
|
||||
return log_error_errno(r, "Failed to parse --size-max= argument: %s", arg);
|
||||
if (!FILE_SIZE_VALID(u))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", optarg);
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", arg);
|
||||
|
||||
arg_size_max = u;
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_CLASS:
|
||||
arg_class = image_class_from_string(optarg);
|
||||
OPTION_LONG("class", "CLASS",
|
||||
"Select image class (machine, sysext, confext, portable)"):
|
||||
arg_class = image_class_from_string(arg);
|
||||
if (arg_class < 0)
|
||||
return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg);
|
||||
|
||||
return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg);
|
||||
break;
|
||||
|
||||
case ARG_SYSTEM:
|
||||
OPTION_LONG("system", NULL, "Operate in per-system mode"):
|
||||
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
break;
|
||||
|
||||
case ARG_USER:
|
||||
OPTION_LONG("user", NULL, "Operate in per-user mode"):
|
||||
arg_runtime_scope = RUNTIME_SCOPE_USER;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
/* Make sure offset+size is still in the valid range if both set */
|
||||
@@ -476,20 +438,10 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
if (arg_runtime_scope == RUNTIME_SCOPE_USER)
|
||||
arg_import_flags |= IMPORT_FOREIGN_UID;
|
||||
|
||||
*ret_args = option_parser_get_args(&state);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int import_main(int argc, char *argv[]) {
|
||||
static const Verb verbs[] = {
|
||||
{ "help", VERB_ANY, VERB_ANY, 0, verb_help },
|
||||
{ "tar", 2, 3, 0, verb_import_tar },
|
||||
{ "raw", 2, 3, 0, verb_import_raw },
|
||||
{}
|
||||
};
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, NULL);
|
||||
}
|
||||
|
||||
static void parse_env(void) {
|
||||
int r;
|
||||
|
||||
@@ -523,13 +475,14 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
parse_env();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
char **args = NULL;
|
||||
r = parse_argv(argc, argv, &args);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
(void) ignore_signals(SIGPIPE);
|
||||
|
||||
return import_main(argc, argv);
|
||||
return dispatch_verb_with_args(args, NULL);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
|
||||
+312
-385
@@ -1,6 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <locale.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -20,6 +19,7 @@
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "oci-util.h"
|
||||
#include "options.h"
|
||||
#include "os-util.h"
|
||||
#include "pager.h"
|
||||
#include "parse-argument.h"
|
||||
@@ -261,6 +261,211 @@ static int transfer_image_common(sd_bus *bus, sd_bus_message *m) {
|
||||
return -r;
|
||||
}
|
||||
|
||||
VERB(verb_pull_tar, "pull-tar", "URL [NAME]", 2, 3, 0, "Download a TAR container image");
|
||||
static int verb_pull_tar(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_free_ char *l = NULL, *ll = NULL;
|
||||
const char *local, *remote;
|
||||
sd_bus *bus = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
r = settle_image_class();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
remote = argv[1];
|
||||
if (!http_url_is_valid(remote) && !file_url_is_valid(remote))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"URL '%s' is not valid.", remote);
|
||||
|
||||
if (argc >= 3)
|
||||
local = argv[2];
|
||||
else {
|
||||
r = import_url_last_component(remote, &l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get final component of URL: %m");
|
||||
|
||||
local = l;
|
||||
}
|
||||
|
||||
local = empty_or_dash_to_null(local);
|
||||
|
||||
if (local) {
|
||||
r = tar_strip_suffixes(local, &ll);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
local = ll;
|
||||
|
||||
if (!image_name_is_valid(local))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Local name %s is not a suitable image name.",
|
||||
local);
|
||||
}
|
||||
|
||||
if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) {
|
||||
r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTar");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(
|
||||
m,
|
||||
"sssb",
|
||||
remote,
|
||||
local,
|
||||
import_verify_to_string(arg_verify),
|
||||
FLAGS_SET(arg_import_flags, IMPORT_FORCE));
|
||||
} else {
|
||||
r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTarEx");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(
|
||||
m,
|
||||
"sssst",
|
||||
remote,
|
||||
local,
|
||||
image_class_to_string(arg_image_class),
|
||||
import_verify_to_string(arg_verify),
|
||||
(uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD));
|
||||
}
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
return transfer_image_common(bus, m);
|
||||
}
|
||||
|
||||
VERB(verb_pull_raw, "pull-raw", "URL [NAME]", 2, 3, 0, "Download a RAW container or VM image");
|
||||
static int verb_pull_raw(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_free_ char *l = NULL, *ll = NULL;
|
||||
const char *local, *remote;
|
||||
sd_bus *bus = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
r = settle_image_class();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
remote = argv[1];
|
||||
if (!http_url_is_valid(remote) && !file_url_is_valid(remote))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"URL '%s' is not valid.", remote);
|
||||
|
||||
if (argc >= 3)
|
||||
local = argv[2];
|
||||
else {
|
||||
r = import_url_last_component(remote, &l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get final component of URL: %m");
|
||||
|
||||
local = l;
|
||||
}
|
||||
|
||||
local = empty_or_dash_to_null(local);
|
||||
|
||||
if (local) {
|
||||
r = raw_strip_suffixes(local, &ll);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
local = ll;
|
||||
|
||||
if (!image_name_is_valid(local))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Local name %s is not a suitable image name.",
|
||||
local);
|
||||
}
|
||||
|
||||
if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) {
|
||||
r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRaw");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(
|
||||
m,
|
||||
"sssb",
|
||||
remote,
|
||||
local,
|
||||
import_verify_to_string(arg_verify),
|
||||
FLAGS_SET(arg_import_flags, IMPORT_FORCE));
|
||||
} else {
|
||||
r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRawEx");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(
|
||||
m,
|
||||
"sssst",
|
||||
remote,
|
||||
local,
|
||||
image_class_to_string(arg_image_class),
|
||||
import_verify_to_string(arg_verify),
|
||||
(uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD));
|
||||
}
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
return transfer_image_common(bus, m);
|
||||
}
|
||||
|
||||
VERB(verb_pull_oci, "pull-oci", "REF [NAME]", 2, 3, 0, "Download an OCI container image");
|
||||
static int verb_pull_oci(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_free_ char *l = NULL;
|
||||
const char *local, *remote;
|
||||
sd_bus *bus = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
r = settle_image_class();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
remote = argv[1];
|
||||
_cleanup_free_ char *image = NULL;
|
||||
r = oci_ref_parse(remote, /* ret_registry= */ NULL, &image, /* ret_tag= */ NULL);
|
||||
if (r == -EINVAL)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Ref '%s' is not valid.", remote);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine if ref '%s' is valid.", remote);
|
||||
|
||||
if (argc >= 3)
|
||||
local = argv[2];
|
||||
else {
|
||||
r = path_extract_filename(image, &l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get final component of reference: %m");
|
||||
|
||||
local = l;
|
||||
}
|
||||
|
||||
local = empty_or_dash_to_null(local);
|
||||
|
||||
if (local) {
|
||||
if (!image_name_is_valid(local))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Local name %s is not a suitable image name.",
|
||||
local);
|
||||
}
|
||||
|
||||
r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullOci");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(
|
||||
m,
|
||||
"ssst",
|
||||
remote,
|
||||
local,
|
||||
image_class_to_string(arg_image_class),
|
||||
(uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY));
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
return transfer_image_common(bus, m);
|
||||
}
|
||||
|
||||
VERB(verb_import_tar, "import-tar", "FILE [NAME]", 2, 3, 0, "Import a local TAR container image");
|
||||
static int verb_import_tar(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_free_ char *ll = NULL, *fn = NULL;
|
||||
@@ -340,6 +545,7 @@ static int verb_import_tar(int argc, char *argv[], uintptr_t _data, void *userda
|
||||
return transfer_image_common(bus, m);
|
||||
}
|
||||
|
||||
VERB(verb_import_raw, "import-raw", "FILE [NAME]", 2, 3, 0, "Import a local RAW container or VM image");
|
||||
static int verb_import_raw(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_free_ char *ll = NULL, *fn = NULL;
|
||||
@@ -419,6 +625,7 @@ static int verb_import_raw(int argc, char *argv[], uintptr_t _data, void *userda
|
||||
return transfer_image_common(bus, m);
|
||||
}
|
||||
|
||||
VERB(verb_import_fs, "import-fs", "DIRECTORY [NAME]", 2, 3, 0, "Import a local directory container image");
|
||||
static int verb_import_fs(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
const char *local = NULL, *path = NULL;
|
||||
@@ -506,6 +713,7 @@ static void determine_compression_from_filename(const char *p) {
|
||||
arg_format = "zstd";
|
||||
}
|
||||
|
||||
VERB(verb_export_tar, "export-tar", "NAME [FILE]", 2, 3, 0, "Export a TAR container image locally");
|
||||
static int verb_export_tar(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
@@ -565,6 +773,7 @@ static int verb_export_tar(int argc, char *argv[], uintptr_t _data, void *userda
|
||||
return transfer_image_common(bus, m);
|
||||
}
|
||||
|
||||
VERB(verb_export_raw, "export-raw", "NAME [FILE]", 2, 3, 0, "Export a RAW container or VM image locally");
|
||||
static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
@@ -624,207 +833,7 @@ static int verb_export_raw(int argc, char *argv[], uintptr_t _data, void *userda
|
||||
return transfer_image_common(bus, m);
|
||||
}
|
||||
|
||||
static int verb_pull_tar(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_free_ char *l = NULL, *ll = NULL;
|
||||
const char *local, *remote;
|
||||
sd_bus *bus = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
r = settle_image_class();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
remote = argv[1];
|
||||
if (!http_url_is_valid(remote) && !file_url_is_valid(remote))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"URL '%s' is not valid.", remote);
|
||||
|
||||
if (argc >= 3)
|
||||
local = argv[2];
|
||||
else {
|
||||
r = import_url_last_component(remote, &l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get final component of URL: %m");
|
||||
|
||||
local = l;
|
||||
}
|
||||
|
||||
local = empty_or_dash_to_null(local);
|
||||
|
||||
if (local) {
|
||||
r = tar_strip_suffixes(local, &ll);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
local = ll;
|
||||
|
||||
if (!image_name_is_valid(local))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Local name %s is not a suitable image name.",
|
||||
local);
|
||||
}
|
||||
|
||||
if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) {
|
||||
r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTar");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(
|
||||
m,
|
||||
"sssb",
|
||||
remote,
|
||||
local,
|
||||
import_verify_to_string(arg_verify),
|
||||
FLAGS_SET(arg_import_flags, IMPORT_FORCE));
|
||||
} else {
|
||||
r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTarEx");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(
|
||||
m,
|
||||
"sssst",
|
||||
remote,
|
||||
local,
|
||||
image_class_to_string(arg_image_class),
|
||||
import_verify_to_string(arg_verify),
|
||||
(uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD));
|
||||
}
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
return transfer_image_common(bus, m);
|
||||
}
|
||||
|
||||
static int verb_pull_raw(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_free_ char *l = NULL, *ll = NULL;
|
||||
const char *local, *remote;
|
||||
sd_bus *bus = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
r = settle_image_class();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
remote = argv[1];
|
||||
if (!http_url_is_valid(remote) && !file_url_is_valid(remote))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"URL '%s' is not valid.", remote);
|
||||
|
||||
if (argc >= 3)
|
||||
local = argv[2];
|
||||
else {
|
||||
r = import_url_last_component(remote, &l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get final component of URL: %m");
|
||||
|
||||
local = l;
|
||||
}
|
||||
|
||||
local = empty_or_dash_to_null(local);
|
||||
|
||||
if (local) {
|
||||
r = raw_strip_suffixes(local, &ll);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
local = ll;
|
||||
|
||||
if (!image_name_is_valid(local))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Local name %s is not a suitable image name.",
|
||||
local);
|
||||
}
|
||||
|
||||
if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) {
|
||||
r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRaw");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(
|
||||
m,
|
||||
"sssb",
|
||||
remote,
|
||||
local,
|
||||
import_verify_to_string(arg_verify),
|
||||
FLAGS_SET(arg_import_flags, IMPORT_FORCE));
|
||||
} else {
|
||||
r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRawEx");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(
|
||||
m,
|
||||
"sssst",
|
||||
remote,
|
||||
local,
|
||||
image_class_to_string(arg_image_class),
|
||||
import_verify_to_string(arg_verify),
|
||||
(uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD));
|
||||
}
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
return transfer_image_common(bus, m);
|
||||
}
|
||||
|
||||
static int verb_pull_oci(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
|
||||
_cleanup_free_ char *l = NULL;
|
||||
const char *local, *remote;
|
||||
sd_bus *bus = ASSERT_PTR(userdata);
|
||||
int r;
|
||||
|
||||
r = settle_image_class();
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
remote = argv[1];
|
||||
_cleanup_free_ char *image = NULL;
|
||||
r = oci_ref_parse(remote, /* ret_registry= */ NULL, &image, /* ret_tag= */ NULL);
|
||||
if (r == -EINVAL)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Ref '%s' is not valid.", remote);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine if ref '%s' is valid.", remote);
|
||||
|
||||
if (argc >= 3)
|
||||
local = argv[2];
|
||||
else {
|
||||
r = path_extract_filename(image, &l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get final component of reference: %m");
|
||||
|
||||
local = l;
|
||||
}
|
||||
|
||||
local = empty_or_dash_to_null(local);
|
||||
|
||||
if (local) {
|
||||
if (!image_name_is_valid(local))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Local name %s is not a suitable image name.",
|
||||
local);
|
||||
}
|
||||
|
||||
r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullOci");
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
r = sd_bus_message_append(
|
||||
m,
|
||||
"ssst",
|
||||
remote,
|
||||
local,
|
||||
image_class_to_string(arg_image_class),
|
||||
(uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY));
|
||||
if (r < 0)
|
||||
return bus_log_create_error(r);
|
||||
|
||||
return transfer_image_common(bus, m);
|
||||
}
|
||||
|
||||
VERB(verb_list_transfers, "list-transfers", NULL, VERB_ANY, 1, VERB_DEFAULT, "Show list of transfers in progress");
|
||||
static int verb_list_transfers(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
||||
@@ -929,6 +938,7 @@ static int verb_list_transfers(int argc, char *argv[], uintptr_t _data, void *us
|
||||
return 0;
|
||||
}
|
||||
|
||||
VERB(verb_cancel_transfer, "cancel-transfer", "[ID...]", 2, VERB_ANY, 0, "Cancel a transfer");
|
||||
static int verb_cancel_transfer(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
sd_bus *bus = ASSERT_PTR(userdata);
|
||||
@@ -951,6 +961,7 @@ static int verb_cancel_transfer(int argc, char *argv[], uintptr_t _data, void *u
|
||||
return 0;
|
||||
}
|
||||
|
||||
VERB_NOARG(verb_list_images, "list-images", "Show list of installed images");
|
||||
static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
||||
@@ -1050,6 +1061,7 @@ static int verb_list_images(int argc, char *argv[], uintptr_t _data, void *userd
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
_cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL;
|
||||
int r;
|
||||
|
||||
pager_open(arg_pager_flags);
|
||||
@@ -1058,265 +1070,179 @@ static int help(void) {
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...] COMMAND ...\n\n"
|
||||
"%5$sDownload, import or export disk images%6$s\n"
|
||||
"\n%3$sCommands:%4$s\n"
|
||||
" pull-tar URL [NAME] Download a TAR container image\n"
|
||||
" pull-raw URL [NAME] Download a RAW container or VM image\n"
|
||||
" pull-oci REF [NAME] Download an OCI container image\n"
|
||||
" import-tar FILE [NAME] Import a local TAR container image\n"
|
||||
" import-raw FILE [NAME] Import a local RAW container or VM image\n"
|
||||
" import-fs DIRECTORY [NAME] Import a local directory container image\n"
|
||||
" export-tar NAME [FILE] Export a TAR container image locally\n"
|
||||
" export-raw NAME [FILE] Export a RAW container or VM image locally\n"
|
||||
" list-transfers Show list of transfers in progress\n"
|
||||
" cancel-transfer [ID...] Cancel a transfer\n"
|
||||
" list-images Show list of installed images\n"
|
||||
"\n%3$sOptions:%4$s\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" --no-pager Do not pipe output into a pager\n"
|
||||
" --no-legend Do not show the headers and footers\n"
|
||||
" --no-ask-password Do not ask for system passwords\n"
|
||||
" -H --host=[USER@]HOST Operate on remote host\n"
|
||||
" -M --machine=CONTAINER Operate on local container\n"
|
||||
" --system Connect to system machine manager\n"
|
||||
" --user Connect to user machine manager\n"
|
||||
" --read-only Create read-only image\n"
|
||||
" -q --quiet Suppress output\n"
|
||||
" --json=pretty|short|off Generate JSON output\n"
|
||||
" -j Equvilant to --json=pretty on TTY, --json=short\n"
|
||||
" otherwise\n"
|
||||
" --verify=MODE Verification mode for downloaded images (no,\n"
|
||||
" checksum, signature)\n"
|
||||
" --format=xz|gzip|bzip2|zstd\n"
|
||||
" Desired output format for export\n"
|
||||
" --force Install image even if already exists\n"
|
||||
" --class=TYPE Install as the specified TYPE\n"
|
||||
" -m Install as --class=machine, machine image\n"
|
||||
" -P Install as --class=portable,\n"
|
||||
" portable service image\n"
|
||||
" -S Install as --class=sysext, system extension image\n"
|
||||
" -C Install as --class=confext,\n"
|
||||
" configuration extension image\n"
|
||||
" --keep-download=BOOL Control whether to keep pristine copy of download\n"
|
||||
" -N Same as --keep-download=no\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
r = verbs_get_help_table(&verbs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(void) table_sync_column_widths(0, verbs, options);
|
||||
|
||||
printf("%s [OPTIONS...] COMMAND ...\n\n"
|
||||
"%sDownload, import or export disk images%s\n"
|
||||
"\n%sCommands:%s\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
ansi_underline(),
|
||||
ansi_normal(),
|
||||
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();
|
||||
}
|
||||
VERB_COMMON_HELP(help);
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_NO_PAGER,
|
||||
ARG_NO_LEGEND,
|
||||
ARG_NO_ASK_PASSWORD,
|
||||
ARG_READ_ONLY,
|
||||
ARG_JSON,
|
||||
ARG_VERIFY,
|
||||
ARG_FORCE,
|
||||
ARG_FORMAT,
|
||||
ARG_CLASS,
|
||||
ARG_KEEP_DOWNLOAD,
|
||||
ARG_SYSTEM,
|
||||
ARG_USER,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
||||
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
|
||||
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
|
||||
{ "host", required_argument, NULL, 'H' },
|
||||
{ "machine", required_argument, NULL, 'M' },
|
||||
{ "read-only", no_argument, NULL, ARG_READ_ONLY },
|
||||
{ "json", required_argument, NULL, ARG_JSON },
|
||||
{ "quiet", no_argument, NULL, 'q' },
|
||||
{ "verify", required_argument, NULL, ARG_VERIFY },
|
||||
{ "force", no_argument, NULL, ARG_FORCE },
|
||||
{ "format", required_argument, NULL, ARG_FORMAT },
|
||||
{ "class", required_argument, NULL, ARG_CLASS },
|
||||
{ "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD },
|
||||
{ "system", no_argument, NULL, ARG_SYSTEM },
|
||||
{ "user", no_argument, NULL, ARG_USER },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
int r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
for (;;) {
|
||||
c = getopt_long(argc, argv, "hH:M:jqmPSCN", options, NULL);
|
||||
if (c < 0)
|
||||
break;
|
||||
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_NO_PAGER:
|
||||
OPTION_COMMON_NO_PAGER:
|
||||
arg_pager_flags |= PAGER_DISABLE;
|
||||
break;
|
||||
|
||||
case ARG_NO_LEGEND:
|
||||
OPTION_COMMON_NO_LEGEND:
|
||||
arg_legend = false;
|
||||
break;
|
||||
|
||||
case ARG_NO_ASK_PASSWORD:
|
||||
OPTION_COMMON_NO_ASK_PASSWORD:
|
||||
arg_ask_password = false;
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
OPTION_COMMON_HOST:
|
||||
arg_transport = BUS_TRANSPORT_REMOTE;
|
||||
arg_host = optarg;
|
||||
arg_host = arg;
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
r = parse_machine_argument(optarg, &arg_host, &arg_transport);
|
||||
OPTION_COMMON_MACHINE:
|
||||
r = parse_machine_argument(arg, &arg_host, &arg_transport);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_READ_ONLY:
|
||||
OPTION_LONG("system", NULL, "Connect to system machine manager"):
|
||||
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
break;
|
||||
|
||||
OPTION_LONG("user", NULL, "Connect to user machine manager"):
|
||||
arg_runtime_scope = RUNTIME_SCOPE_USER;
|
||||
break;
|
||||
|
||||
OPTION_LONG("read-only", NULL, "Create read-only image"):
|
||||
arg_import_flags |= IMPORT_READ_ONLY;
|
||||
arg_import_flags_mask |= IMPORT_READ_ONLY;
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
OPTION('q', "quiet", NULL, "Suppress output"):
|
||||
arg_quiet = true;
|
||||
break;
|
||||
|
||||
case ARG_VERIFY:
|
||||
if (streq(optarg, "help"))
|
||||
return DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX);
|
||||
|
||||
r = import_verify_from_string(optarg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --verify= setting: %s", optarg);
|
||||
arg_verify = r;
|
||||
break;
|
||||
|
||||
case ARG_FORCE:
|
||||
arg_import_flags |= IMPORT_FORCE;
|
||||
arg_import_flags_mask |= IMPORT_FORCE;
|
||||
break;
|
||||
|
||||
case ARG_FORMAT:
|
||||
if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2", "zstd"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Unknown format: %s", optarg);
|
||||
|
||||
arg_format = optarg;
|
||||
break;
|
||||
|
||||
case ARG_JSON:
|
||||
r = parse_json_argument(optarg, &arg_json_format_flags);
|
||||
OPTION_COMMON_JSON:
|
||||
r = parse_json_argument(arg, &arg_json_format_flags);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
arg_legend = false;
|
||||
break;
|
||||
|
||||
case 'j':
|
||||
OPTION_COMMON_LOWERCASE_J:
|
||||
arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO;
|
||||
arg_legend = false;
|
||||
break;
|
||||
|
||||
case ARG_CLASS:
|
||||
arg_image_class = image_class_from_string(optarg);
|
||||
if (arg_image_class < 0)
|
||||
return log_error_errno(arg_image_class, "Failed to parse --class= parameter: %s", optarg);
|
||||
OPTION_LONG("verify", "MODE",
|
||||
"Verification mode for downloaded images (no, checksum, signature)"):
|
||||
if (streq(arg, "help"))
|
||||
return DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX);
|
||||
|
||||
r = import_verify_from_string(arg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --verify= setting: %s", arg);
|
||||
arg_verify = r;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
OPTION_LONG("format", "FORMAT",
|
||||
"Desired output format for export (zstd, xz, gzip, bzip2)"):
|
||||
if (!STR_IN_SET(arg, "uncompressed", "xz", "gzip", "bzip2", "zstd"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Unknown format: %s", arg);
|
||||
arg_format = arg;
|
||||
break;
|
||||
|
||||
OPTION_LONG("force", NULL, "Install image even if already exists"):
|
||||
arg_import_flags |= IMPORT_FORCE;
|
||||
arg_import_flags_mask |= IMPORT_FORCE;
|
||||
break;
|
||||
|
||||
OPTION_LONG("class", "TYPE", "Install as the specified TYPE"):
|
||||
arg_image_class = image_class_from_string(arg);
|
||||
if (arg_image_class < 0)
|
||||
return log_error_errno(arg_image_class, "Failed to parse --class= parameter: %s", arg);
|
||||
break;
|
||||
|
||||
OPTION_SHORT('m', NULL, "Install as --class=machine, machine image"):
|
||||
arg_image_class = IMAGE_MACHINE;
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
OPTION_SHORT('P', NULL, "Install as --class=portable, portable service image"):
|
||||
arg_image_class = IMAGE_PORTABLE;
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
OPTION_SHORT('S', NULL, "Install as --class=sysext, system extension image"):
|
||||
arg_image_class = IMAGE_SYSEXT;
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
OPTION_SHORT('C', NULL, "Install as --class=confext, configuration extension image"):
|
||||
arg_image_class = IMAGE_CONFEXT;
|
||||
break;
|
||||
|
||||
case ARG_KEEP_DOWNLOAD:
|
||||
r = parse_boolean(optarg);
|
||||
OPTION_LONG("keep-download", "BOOL",
|
||||
"Control whether to keep pristine copy of download"):
|
||||
r = parse_boolean(arg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --keep-download= value: %s", optarg);
|
||||
return log_error_errno(r, "Failed to parse --keep-download= value: %s", arg);
|
||||
|
||||
SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r);
|
||||
arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD;
|
||||
break;
|
||||
|
||||
case 'N':
|
||||
arg_import_flags_mask &= ~IMPORT_PULL_KEEP_DOWNLOAD;
|
||||
OPTION_SHORT('N', NULL, "Same as --keep-download=no"):
|
||||
arg_import_flags &= ~IMPORT_PULL_KEEP_DOWNLOAD;
|
||||
arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD;
|
||||
break;
|
||||
|
||||
case ARG_USER:
|
||||
arg_runtime_scope = RUNTIME_SCOPE_USER;
|
||||
break;
|
||||
|
||||
case ARG_SYSTEM:
|
||||
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
*ret_args = option_parser_get_args(&state);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int importctl_main(int argc, char *argv[], sd_bus *bus) {
|
||||
|
||||
static const Verb verbs[] = {
|
||||
{ "help", VERB_ANY, VERB_ANY, 0, verb_help },
|
||||
{ "import-tar", 2, 3, 0, verb_import_tar },
|
||||
{ "import-raw", 2, 3, 0, verb_import_raw },
|
||||
{ "import-fs", 2, 3, 0, verb_import_fs },
|
||||
{ "export-tar", 2, 3, 0, verb_export_tar },
|
||||
{ "export-raw", 2, 3, 0, verb_export_raw },
|
||||
{ "pull-tar", 2, 3, 0, verb_pull_tar },
|
||||
{ "pull-oci", 2, 3, 0, verb_pull_oci },
|
||||
{ "pull-raw", 2, 3, 0, verb_pull_raw },
|
||||
{ "list-transfers", VERB_ANY, 1, VERB_DEFAULT, verb_list_transfers },
|
||||
{ "cancel-transfer", 2, VERB_ANY, 0, verb_cancel_transfer },
|
||||
{ "list-images", VERB_ANY, 1, 0, verb_list_images },
|
||||
{}
|
||||
};
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, bus);
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
int r;
|
||||
@@ -1324,7 +1250,8 @@ static int run(int argc, char *argv[]) {
|
||||
setlocale(LC_ALL, "");
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
char **args = NULL;
|
||||
r = parse_argv(argc, argv, &args);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
@@ -1334,7 +1261,7 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
(void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password);
|
||||
|
||||
return importctl_main(argc, argv, bus);
|
||||
return dispatch_verb_with_args(args, bus);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
|
||||
+98
-164
@@ -1,6 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
|
||||
@@ -11,6 +10,7 @@
|
||||
#include "build.h"
|
||||
#include "discover-image.h"
|
||||
#include "env-util.h"
|
||||
#include "format-table.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "import-common.h"
|
||||
#include "import-util.h"
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "oci-util.h"
|
||||
#include "options.h"
|
||||
#include "parse-argument.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
@@ -117,7 +118,8 @@ static void on_tar_finished(TarPull *pull, int error, void *userdata) {
|
||||
sd_event_exit(event, ABS(error));
|
||||
}
|
||||
|
||||
static int verb_pull_tar(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
VERB(verb_tar, "tar", "URL [NAME]", 2, 3, 0, "Download a TAR image");
|
||||
static int verb_tar(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_free_ char *ll = NULL, *normalized = NULL;
|
||||
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
|
||||
_cleanup_(tar_pull_unrefp) TarPull *pull = NULL;
|
||||
@@ -187,7 +189,8 @@ static void on_raw_finished(RawPull *pull, int error, void *userdata) {
|
||||
sd_event_exit(event, ABS(error));
|
||||
}
|
||||
|
||||
static int verb_pull_raw(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
VERB(verb_raw, "raw", "URL [NAME]", 2, 3, 0, "Download a RAW image");
|
||||
static int verb_raw(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
_cleanup_free_ char *ll = NULL, *normalized = NULL;
|
||||
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
|
||||
_cleanup_(raw_pull_unrefp) RawPull *pull = NULL;
|
||||
@@ -256,7 +259,8 @@ static void on_oci_finished(OciPull *pull, int error, void *userdata) {
|
||||
sd_event_exit(event, ABS(error));
|
||||
}
|
||||
|
||||
static int verb_pull_oci(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
VERB(verb_oci, "oci", "REF [NAME]", 2, 3, 0, "Download an OCI image");
|
||||
static int verb_oci(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
int r;
|
||||
|
||||
const char *ref = argv[1];
|
||||
@@ -312,134 +316,79 @@ static int verb_pull_oci(int argc, char *argv[], uintptr_t _data, void *userdata
|
||||
}
|
||||
|
||||
static int help(void) {
|
||||
printf("%1$s [OPTIONS...] {COMMAND} ...\n"
|
||||
"\n%4$sDownload disk images.%5$s\n"
|
||||
"\n%2$sCommands:%3$s\n"
|
||||
" tar URL [NAME] Download a TAR image\n"
|
||||
" raw URL [NAME] Download a RAW image\n"
|
||||
" oci REF [NAME] Download an OCI image\n"
|
||||
"\n%2$sOptions:%3$s\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" --force Force creation of image\n"
|
||||
" --verify=MODE Verify downloaded image, one of: 'no',\n"
|
||||
" 'checksum', 'signature' or literal SHA256 hash\n"
|
||||
" --settings=BOOL Download settings file with image\n"
|
||||
" --roothash=BOOL Download root hash file with image\n"
|
||||
" --roothash-signature=BOOL\n"
|
||||
" Download root hash signature file with image\n"
|
||||
" --verity=BOOL Download verity file with image\n"
|
||||
" --image-root=PATH Image root directory\n"
|
||||
" --read-only Create a read-only image\n"
|
||||
" --direct Download directly to specified file\n"
|
||||
" --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n"
|
||||
" instead of a directory\n"
|
||||
" --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n"
|
||||
" subvolume\n"
|
||||
" --convert-qcow2=BOOL Controls whether to convert QCOW2 images to\n"
|
||||
" regular disk images\n"
|
||||
" --sync=BOOL Controls whether to sync() before completing\n"
|
||||
" --offset=BYTES Offset to seek to in destination\n"
|
||||
" --size-max=BYTES Maximum number of bytes to write to destination\n"
|
||||
" --class=CLASS Select image class (machine, sysext, confext,\n"
|
||||
" portable)\n"
|
||||
" --keep-download=BOOL Keep a copy pristine copy of the downloaded file\n"
|
||||
" around\n"
|
||||
" --system Operate in per-system mode\n"
|
||||
" --user Operate in per-user mode\n",
|
||||
_cleanup_(table_unrefp) Table *options = NULL, *verbs = NULL;
|
||||
int r;
|
||||
|
||||
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"
|
||||
"%sDownload disk images.%s\n"
|
||||
"\n%sCommands:%s\n",
|
||||
program_invocation_short_name,
|
||||
ansi_underline(),
|
||||
ansi_normal(),
|
||||
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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
return help();
|
||||
}
|
||||
VERB_COMMON_HELP_HIDDEN(help);
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_FORCE,
|
||||
ARG_IMAGE_ROOT,
|
||||
ARG_VERIFY,
|
||||
ARG_SETTINGS,
|
||||
ARG_ROOTHASH,
|
||||
ARG_ROOTHASH_SIGNATURE,
|
||||
ARG_VERITY,
|
||||
ARG_READ_ONLY,
|
||||
ARG_DIRECT,
|
||||
ARG_BTRFS_SUBVOL,
|
||||
ARG_BTRFS_QUOTA,
|
||||
ARG_CONVERT_QCOW2,
|
||||
ARG_SYNC,
|
||||
ARG_OFFSET,
|
||||
ARG_SIZE_MAX,
|
||||
ARG_CLASS,
|
||||
ARG_KEEP_DOWNLOAD,
|
||||
ARG_SYSTEM,
|
||||
ARG_USER,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "force", no_argument, NULL, ARG_FORCE },
|
||||
{ "image-root", required_argument, NULL, ARG_IMAGE_ROOT },
|
||||
{ "verify", required_argument, NULL, ARG_VERIFY },
|
||||
{ "settings", required_argument, NULL, ARG_SETTINGS },
|
||||
{ "roothash", required_argument, NULL, ARG_ROOTHASH },
|
||||
{ "roothash-signature", required_argument, NULL, ARG_ROOTHASH_SIGNATURE },
|
||||
{ "verity", required_argument, NULL, ARG_VERITY },
|
||||
{ "read-only", no_argument, NULL, ARG_READ_ONLY },
|
||||
{ "direct", no_argument, NULL, ARG_DIRECT },
|
||||
{ "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL },
|
||||
{ "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA },
|
||||
{ "convert-qcow2", required_argument, NULL, ARG_CONVERT_QCOW2 },
|
||||
{ "sync", required_argument, NULL, ARG_SYNC },
|
||||
{ "offset", required_argument, NULL, ARG_OFFSET },
|
||||
{ "size-max", required_argument, NULL, ARG_SIZE_MAX },
|
||||
{ "class", required_argument, NULL, ARG_CLASS },
|
||||
{ "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD },
|
||||
{ "system", no_argument, NULL, ARG_SYSTEM },
|
||||
{ "user", no_argument, NULL, ARG_USER },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
int r;
|
||||
bool auto_settings = true, auto_keep_download = true;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
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_FORCE:
|
||||
OPTION_LONG("force", NULL, "Force creation of image"):
|
||||
arg_import_flags |= IMPORT_FORCE;
|
||||
break;
|
||||
|
||||
case ARG_IMAGE_ROOT:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root);
|
||||
OPTION_LONG("image-root", "PATH", "Image root directory"):
|
||||
r = parse_path_argument(arg, /* suppress_root= */ false, &arg_image_root);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_VERIFY: {
|
||||
OPTION_LONG("verify", "MODE",
|
||||
"Verify downloaded image, one of: 'no', 'checksum', 'signature' or literal SHA256 hash"): {
|
||||
ImportVerify v;
|
||||
|
||||
v = import_verify_from_string(optarg);
|
||||
v = import_verify_from_string(arg);
|
||||
if (v < 0) {
|
||||
_cleanup_free_ void *h = NULL;
|
||||
size_t n;
|
||||
@@ -447,10 +396,10 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
/* If this is not a valid verification mode, maybe it's a literally specified
|
||||
* SHA256 hash? We can handle that too... */
|
||||
|
||||
r = unhexmem(optarg, &h, &n);
|
||||
r = unhexmem(arg, &h, &n);
|
||||
if (r < 0 || n == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Invalid verification setting: %s", optarg);
|
||||
"Invalid verification setting: %s", arg);
|
||||
if (n != 32)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"64 hex character SHA256 hash required when specifying explicit checksum, %zu specified", n * 2);
|
||||
@@ -466,8 +415,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_SETTINGS:
|
||||
r = parse_boolean_argument("--settings=", optarg, NULL);
|
||||
OPTION_LONG("settings", "BOOL", "Download settings file with image"):
|
||||
r = parse_boolean_argument("--settings=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -475,8 +424,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
auto_settings = false;
|
||||
break;
|
||||
|
||||
case ARG_ROOTHASH:
|
||||
r = parse_boolean_argument("--roothash=", optarg, NULL);
|
||||
OPTION_LONG("roothash", "BOOL", "Download root hash file with image"):
|
||||
r = parse_boolean_argument("--roothash=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -487,118 +436,113 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
SET_FLAG(arg_import_flags, IMPORT_PULL_ROOTHASH_SIGNATURE, false);
|
||||
break;
|
||||
|
||||
case ARG_ROOTHASH_SIGNATURE:
|
||||
r = parse_boolean_argument("--roothash-signature=", optarg, NULL);
|
||||
OPTION_LONG("roothash-signature", "BOOL",
|
||||
"Download root hash signature file with image"):
|
||||
r = parse_boolean_argument("--roothash-signature=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
SET_FLAG(arg_import_flags, IMPORT_PULL_ROOTHASH_SIGNATURE, r);
|
||||
break;
|
||||
|
||||
case ARG_VERITY:
|
||||
r = parse_boolean_argument("--verity=", optarg, NULL);
|
||||
OPTION_LONG("verity", "BOOL", "Download verity file with image"):
|
||||
r = parse_boolean_argument("--verity=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
SET_FLAG(arg_import_flags, IMPORT_PULL_VERITY, r);
|
||||
break;
|
||||
|
||||
case ARG_READ_ONLY:
|
||||
OPTION_LONG("read-only", NULL, "Create a read-only image"):
|
||||
arg_import_flags |= IMPORT_READ_ONLY;
|
||||
break;
|
||||
|
||||
case ARG_DIRECT:
|
||||
OPTION_LONG("direct", NULL, "Download directly to specified file"):
|
||||
arg_import_flags |= IMPORT_DIRECT;
|
||||
arg_import_flags &= ~(IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY);
|
||||
break;
|
||||
|
||||
case ARG_BTRFS_SUBVOL:
|
||||
r = parse_boolean_argument("--btrfs-subvol=", optarg, NULL);
|
||||
OPTION_LONG("btrfs-subvol", "BOOL",
|
||||
"Controls whether to create a btrfs subvolume instead of a directory"):
|
||||
r = parse_boolean_argument("--btrfs-subvol=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r);
|
||||
break;
|
||||
|
||||
case ARG_BTRFS_QUOTA:
|
||||
r = parse_boolean_argument("--btrfs-quota=", optarg, NULL);
|
||||
OPTION_LONG("btrfs-quota", "BOOL",
|
||||
"Controls whether to set up quota for btrfs subvolume"):
|
||||
r = parse_boolean_argument("--btrfs-quota=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r);
|
||||
break;
|
||||
|
||||
case ARG_CONVERT_QCOW2:
|
||||
r = parse_boolean_argument("--convert-qcow2=", optarg, NULL);
|
||||
OPTION_LONG("convert-qcow2", "BOOL",
|
||||
"Controls whether to convert QCOW2 images to regular disk images"):
|
||||
r = parse_boolean_argument("--convert-qcow2=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
SET_FLAG(arg_import_flags, IMPORT_CONVERT_QCOW2, r);
|
||||
break;
|
||||
|
||||
case ARG_SYNC:
|
||||
r = parse_boolean_argument("--sync=", optarg, NULL);
|
||||
OPTION_LONG("sync", "BOOL", "Controls whether to sync() before completing"):
|
||||
r = parse_boolean_argument("--sync=", arg, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
SET_FLAG(arg_import_flags, IMPORT_SYNC, r);
|
||||
break;
|
||||
|
||||
case ARG_OFFSET: {
|
||||
OPTION_LONG("offset", "BYTES", "Offset to seek to in destination"): {
|
||||
uint64_t u;
|
||||
|
||||
r = safe_atou64(optarg, &u);
|
||||
r = safe_atou64(arg, &u);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --offset= argument: %s", optarg);
|
||||
return log_error_errno(r, "Failed to parse --offset= argument: %s", arg);
|
||||
if (!FILE_SIZE_VALID(u))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", optarg);
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", arg);
|
||||
|
||||
arg_offset = u;
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_SIZE_MAX: {
|
||||
OPTION_LONG("size-max", "BYTES", "Maximum number of bytes to write to destination"): {
|
||||
uint64_t u;
|
||||
|
||||
r = parse_size(optarg, 1024, &u);
|
||||
r = parse_size(arg, 1024, &u);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --size-max= argument: %s", optarg);
|
||||
return log_error_errno(r, "Failed to parse --size-max= argument: %s", arg);
|
||||
if (!FILE_SIZE_VALID(u))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", optarg);
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", arg);
|
||||
|
||||
arg_size_max = u;
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_CLASS:
|
||||
arg_class = image_class_from_string(optarg);
|
||||
OPTION_LONG("class", "CLASS",
|
||||
"Select image class (machine, sysext, confext, portable)"):
|
||||
arg_class = image_class_from_string(arg);
|
||||
if (arg_class < 0)
|
||||
return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg);
|
||||
|
||||
return log_error_errno(arg_class, "Failed to parse --class= argument: %s", arg);
|
||||
break;
|
||||
|
||||
case ARG_KEEP_DOWNLOAD:
|
||||
r = parse_boolean(optarg);
|
||||
OPTION_LONG("keep-download", "BOOL",
|
||||
"Keep a pristine copy of the downloaded file around"):
|
||||
r = parse_boolean(arg);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --keep-download= argument: %s", optarg);
|
||||
return log_error_errno(r, "Failed to parse --keep-download= argument: %s", arg);
|
||||
|
||||
SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r);
|
||||
auto_keep_download = false;
|
||||
break;
|
||||
|
||||
case ARG_SYSTEM:
|
||||
OPTION_LONG("system", NULL, "Operate in per-system mode"):
|
||||
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
break;
|
||||
|
||||
case ARG_USER:
|
||||
OPTION_LONG("user", NULL, "Operate in per-user mode"):
|
||||
arg_runtime_scope = RUNTIME_SCOPE_USER;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
/* Make sure offset+size is still in the valid range if both set */
|
||||
@@ -631,6 +575,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
if (arg_runtime_scope == RUNTIME_SCOPE_USER)
|
||||
arg_import_flags |= IMPORT_FOREIGN_UID;
|
||||
|
||||
*ret_args = option_parser_get_args(&state);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -659,18 +604,6 @@ static void parse_env(void) {
|
||||
log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_SYNC: %m");
|
||||
}
|
||||
|
||||
static int pull_main(int argc, char *argv[]) {
|
||||
static const Verb verbs[] = {
|
||||
{ "help", VERB_ANY, VERB_ANY, 0, verb_help },
|
||||
{ "tar", 2, 3, 0, verb_pull_tar },
|
||||
{ "raw", 2, 3, 0, verb_pull_raw },
|
||||
{ "oci", 2, 3, 0, verb_pull_oci },
|
||||
{}
|
||||
};
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, NULL);
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
int r;
|
||||
|
||||
@@ -679,13 +612,14 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
parse_env();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
char **args = NULL;
|
||||
r = parse_argv(argc, argv, &args);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
(void) ignore_signals(SIGPIPE);
|
||||
|
||||
return pull_main(argc, argv);
|
||||
return dispatch_verb_with_args(args, NULL);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
|
||||
+29
-45
@@ -1,6 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
#include <linux/vt.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/ioctl.h>
|
||||
@@ -11,10 +10,12 @@
|
||||
#include "build.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "format-table.h"
|
||||
#include "io-util.h"
|
||||
#include "log.h"
|
||||
#include "logs-show.h"
|
||||
#include "main-func.h"
|
||||
#include "options.h"
|
||||
#include "parse-argument.h"
|
||||
#include "pretty-print.h"
|
||||
#include "qrcode-util.h"
|
||||
@@ -29,29 +30,32 @@ STATIC_DESTRUCTOR_REGISTER(arg_tty, freep);
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
_cleanup_(table_unrefp) Table *options = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-bsod", "8", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...]\n\n"
|
||||
"%5$sFilter the journal to fetch the first message from the current boot with an%6$s\n"
|
||||
"%5$semergency log level and display it as a string and a QR code.%6$s\n"
|
||||
"\n%3$sOptions:%4$s\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" -c --continuous Make systemd-bsod wait continuously\n"
|
||||
" for changes in the journal\n"
|
||||
" --tty=TTY Specify path to TTY to use\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("%s [OPTIONS...]\n\n"
|
||||
"%sFilter the journal to fetch the first message from the current boot with an\n"
|
||||
"emergency log level and display it as a string and a QR code.%s\n"
|
||||
"\n%sOptions:%s\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
ansi_underline(),
|
||||
ansi_normal(),
|
||||
ansi_highlight(),
|
||||
ansi_normal(),
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -239,55 +243,35 @@ cleanup:
|
||||
return r;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char * argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_TTY,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "continuous", no_argument, NULL, 'c' },
|
||||
{ "tty", required_argument, NULL, ARG_TTY },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hc", options, NULL)) >= 0)
|
||||
OptionParser state = { argc, argv };
|
||||
const char *arg;
|
||||
int r;
|
||||
|
||||
FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c)
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
OPTION_COMMON_HELP:
|
||||
return help();
|
||||
|
||||
case ARG_VERSION:
|
||||
OPTION_COMMON_VERSION:
|
||||
return version();
|
||||
|
||||
case 'c':
|
||||
OPTION('c', "continuous", NULL, "Continuously wait for changes in the journal"):
|
||||
arg_continuous = true;
|
||||
break;
|
||||
|
||||
case ARG_TTY:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tty);
|
||||
OPTION_LONG("tty", "TTY", "Specify path to TTY to use"):
|
||||
r = parse_path_argument(arg, /* suppress_root= */ false, &arg_tty);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
if (optind < argc)
|
||||
if (option_parser_get_n_args(&state) > 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"%s takes no argument.",
|
||||
program_invocation_short_name);
|
||||
|
||||
+42
-62
@@ -1,7 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
@@ -12,12 +11,15 @@
|
||||
#include "build.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "format-table.h"
|
||||
#include "format-util.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "options.h"
|
||||
#include "parse-argument.h"
|
||||
#include "pretty-print.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "syslog-util.h"
|
||||
|
||||
static const char *arg_identifier = NULL;
|
||||
@@ -28,104 +30,81 @@ static bool arg_level_prefix = true;
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
_cleanup_(table_unrefp) Table *options = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-cat", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%s [OPTIONS...] COMMAND ...\n"
|
||||
"\n%sExecute process with stdout/stderr connected to the journal.%s\n\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" -t --identifier=STRING Set syslog identifier\n"
|
||||
" -p --priority=PRIORITY Set priority value (0..7)\n"
|
||||
" --stderr-priority=PRIORITY Set priority value (0..7) used for stderr\n"
|
||||
" --level-prefix=BOOL Control whether level prefix shall be parsed\n"
|
||||
" --namespace=NAMESPACE Connect to specified journal namespace\n"
|
||||
"\nSee the %s for details.\n",
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("%s [OPTIONS...] COMMAND ...\n\n"
|
||||
"%sExecute process with stdout/stderr connected to the journal.%s\n\n",
|
||||
program_invocation_short_name,
|
||||
ansi_highlight(),
|
||||
ansi_normal(),
|
||||
link);
|
||||
ansi_normal());
|
||||
|
||||
r = table_print_or_warn(options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("\nSee the %s for details.\n", link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_STDERR_PRIORITY,
|
||||
ARG_LEVEL_PREFIX,
|
||||
ARG_NAMESPACE,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "identifier", required_argument, NULL, 't' },
|
||||
{ "priority", required_argument, NULL, 'p' },
|
||||
{ "stderr-priority", required_argument, NULL, ARG_STDERR_PRIORITY },
|
||||
{ "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX },
|
||||
{ "namespace", required_argument, NULL, ARG_NAMESPACE },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
|
||||
static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
assert(argc >= 0);
|
||||
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;
|
||||
while ((c = getopt_long(argc, argv, "+ht:p:", options, NULL)) >= 0)
|
||||
OptionParser state = { argc, argv, /* stop_at_first_nonoption= */ true };
|
||||
const char *arg;
|
||||
int r;
|
||||
|
||||
FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c)
|
||||
switch (c) {
|
||||
|
||||
case 'h':
|
||||
help();
|
||||
return 0;
|
||||
OPTION_COMMON_HELP:
|
||||
return help();
|
||||
|
||||
case ARG_VERSION:
|
||||
OPTION_COMMON_VERSION:
|
||||
return version();
|
||||
|
||||
case 't':
|
||||
arg_identifier = empty_to_null(optarg);
|
||||
OPTION('t', "identifier", "STRING", "Set syslog identifier"):
|
||||
arg_identifier = empty_to_null(arg);
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
arg_priority = log_level_from_string(optarg);
|
||||
OPTION('p', "priority", "PRIORITY", "Set priority value (0..7)"):
|
||||
arg_priority = log_level_from_string(arg);
|
||||
if (arg_priority < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Failed to parse priority value.");
|
||||
break;
|
||||
|
||||
case ARG_STDERR_PRIORITY:
|
||||
arg_stderr_priority = log_level_from_string(optarg);
|
||||
OPTION_LONG("stderr-priority", "PRIORITY",
|
||||
"Set priority value (0..7) used for stderr"):
|
||||
arg_stderr_priority = log_level_from_string(arg);
|
||||
if (arg_stderr_priority < 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Failed to parse stderr priority value.");
|
||||
break;
|
||||
|
||||
case ARG_LEVEL_PREFIX:
|
||||
r = parse_boolean_argument("--level-prefix=", optarg, &arg_level_prefix);
|
||||
OPTION_LONG("level-prefix", "BOOL",
|
||||
"Control whether level prefix shall be parsed"):
|
||||
r = parse_boolean_argument("--level-prefix=", arg, &arg_level_prefix);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
case ARG_NAMESPACE:
|
||||
arg_namespace = empty_to_null(optarg);
|
||||
OPTION_LONG("namespace", "NAMESPACE",
|
||||
"Connect to specified journal namespace"):
|
||||
arg_namespace = empty_to_null(arg);
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
*ret_args = option_parser_get_args(&state);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -135,7 +114,8 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
char **args = NULL;
|
||||
r = parse_argv(argc, argv, &args);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
@@ -157,7 +137,7 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to rearrange stdout/stderr: %m");
|
||||
|
||||
if (argc <= optind)
|
||||
if (strv_isempty(args))
|
||||
(void) execlp("cat", "cat", NULL);
|
||||
else {
|
||||
struct stat st;
|
||||
@@ -171,7 +151,7 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set environment variable JOURNAL_STREAM: %m");
|
||||
|
||||
(void) execvp(argv[optind], argv + optind);
|
||||
(void) execvp(args[0], args);
|
||||
}
|
||||
r = -errno;
|
||||
|
||||
|
||||
+34
-25
@@ -21,8 +21,9 @@ static bool option_arg_required(const Option *opt) {
|
||||
|
||||
static bool option_is_metadata(const Option *opt) {
|
||||
/* A metadata entry that is not a real option, like the group marker */
|
||||
return FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_GROUP_MARKER) ||
|
||||
FLAGS_SET(ASSERT_PTR(opt)->flags, OPTION_HELP_ENTRY);
|
||||
return ASSERT_PTR(opt)->flags & (OPTION_GROUP_MARKER |
|
||||
OPTION_HELP_ENTRY |
|
||||
OPTION_HELP_ENTRY_VERBATIM);
|
||||
}
|
||||
|
||||
static void shift_arg(char* argv[], int target, int source) {
|
||||
@@ -313,30 +314,38 @@ int _option_parser_get_help_table(
|
||||
/* No help string — we do not show the option */
|
||||
continue;
|
||||
|
||||
char sc[3] = " ";
|
||||
if (opt->short_code != 0)
|
||||
xsprintf(sc, "-%c", opt->short_code);
|
||||
_cleanup_free_ char *s = NULL;
|
||||
|
||||
/* We indent the option string by two spaces. We could set the minimum cell width and
|
||||
* right-align for a similar result, but that'd be more work. This is only used for
|
||||
* display.
|
||||
*
|
||||
* "=" is shown only when a long option is defined: -l --long=ARG, --long=ARG, -s ARG.
|
||||
*/
|
||||
bool need_eq = option_takes_arg(opt) && opt->long_code;
|
||||
bool need_quote = opt->metavar && strchr(opt->metavar, ' ');
|
||||
_cleanup_free_ char *s = strjoin(
|
||||
" ",
|
||||
sc,
|
||||
" ",
|
||||
opt->long_code ? "--" : "",
|
||||
strempty(opt->long_code),
|
||||
option_arg_optional(opt) ? "[" : "",
|
||||
need_eq ? "=" : "",
|
||||
need_quote ? "'" : "",
|
||||
strempty(opt->metavar),
|
||||
need_quote ? "'" : "",
|
||||
option_arg_optional(opt) ? "]" : "");
|
||||
if (FLAGS_SET(opt->flags, OPTION_HELP_ENTRY_VERBATIM)) {
|
||||
assert(opt->long_code);
|
||||
|
||||
s = strjoin(" ",
|
||||
opt->long_code);
|
||||
} else {
|
||||
char sc[3] = " ";
|
||||
if (opt->short_code != 0)
|
||||
xsprintf(sc, "-%c", opt->short_code);
|
||||
|
||||
/* We indent the option string by two spaces. We could set the minimum cell width and
|
||||
* right-align for a similar result, but that'd be more work. This is only used for
|
||||
* display.
|
||||
*
|
||||
* "=" is shown only when a long option is defined: -l --long=ARG, --long=ARG, -s ARG.
|
||||
*/
|
||||
bool need_eq = option_takes_arg(opt) && opt->long_code;
|
||||
bool need_quote = opt->metavar && strchr(opt->metavar, ' ');
|
||||
s = strjoin(" ",
|
||||
sc,
|
||||
" ",
|
||||
opt->long_code ? "--" : "",
|
||||
strempty(opt->long_code),
|
||||
option_arg_optional(opt) ? "[" : "",
|
||||
need_eq ? "=" : "",
|
||||
need_quote ? "'" : "",
|
||||
strempty(opt->metavar),
|
||||
need_quote ? "'" : "",
|
||||
option_arg_optional(opt) ? "]" : "");
|
||||
}
|
||||
if (!s)
|
||||
return log_oom();
|
||||
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
#include "shared-forward.h"
|
||||
|
||||
typedef enum OptionFlags {
|
||||
OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */
|
||||
OPTION_STOPS_PARSING = 1U << 1, /* This option acts like "--" */
|
||||
OPTION_GROUP_MARKER = 1U << 2, /* Fake option entry to separate groups */
|
||||
OPTION_HELP_ENTRY = 1U << 3, /* Fake option entry to insert an additional help line */
|
||||
OPTION_OPTIONAL_ARG = 1U << 0, /* Same as optional_argument in getopt */
|
||||
OPTION_STOPS_PARSING = 1U << 1, /* This option acts like "--" */
|
||||
OPTION_GROUP_MARKER = 1U << 2, /* Fake option entry to separate groups */
|
||||
OPTION_HELP_ENTRY = 1U << 3, /* Fake option entry to insert an additional help line */
|
||||
OPTION_HELP_ENTRY_VERBATIM = 1U << 4, /* Same, but use the long_code in the first column as written */
|
||||
} OptionFlags;
|
||||
|
||||
typedef struct Option {
|
||||
@@ -49,6 +50,7 @@ typedef struct Option {
|
||||
#define OPTION_LONG_FLAGS(fl, lc, mv, h) OPTION_FULL(fl, /* sc= */ 0, lc, mv, 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_HELP_VERBATIM(lc, h) OPTION_FULL(OPTION_HELP_ENTRY_VERBATIM, /* sc= */ 0, lc, /* mv= */ NULL, h)
|
||||
|
||||
#define OPTION_COMMON_HELP \
|
||||
OPTION('h', "help", NULL, "Show this help")
|
||||
|
||||
+14
-6
@@ -39,19 +39,27 @@ typedef struct {
|
||||
.help = h, \
|
||||
}
|
||||
|
||||
#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \
|
||||
/* Forward-define function d. scope specifies the scope, e.g. static. */
|
||||
#define VERB_SCOPE_FULL(scope, d, v, a, amin, amax, f, dat, h) \
|
||||
DISABLE_WARNING_REDUNDANT_DECLS \
|
||||
static int d(int, char**, uintptr_t, void*); \
|
||||
scope int d(int, char**, uintptr_t, void*); \
|
||||
REENABLE_WARNING \
|
||||
_VERB_DATA(d, v, a, amin, amax, f, dat, h)
|
||||
/* The same as VERB_SCOPE_FULL with scope hardwired to 'static'. */
|
||||
#define VERB_FULL(d, v, a, amin, amax, f, dat, h) \
|
||||
VERB_SCOPE_FULL(static, d, v, a, amin, amax, f, dat, h)
|
||||
|
||||
/* The same as VERB_FULL, but without the data argument */
|
||||
/* The same as VERB_SCOPE_FULL/VERB_FULL, but without the data argument. */
|
||||
#define VERB_SCOPE(scope, d, v, a, amin, amax, f, h) \
|
||||
VERB_SCOPE_FULL(scope, d, v, a, amin, amax, f, /* dat= */ 0, h)
|
||||
#define VERB(d, v, a, amin, amax, f, h) \
|
||||
VERB_FULL(d, v, a, amin, amax, f, /* dat= */ 0, h)
|
||||
VERB_SCOPE(static, d, v, a, amin, amax, f, h)
|
||||
|
||||
/* Simplified VERB for parameters that take no argument */
|
||||
/* Simplified VERB_SCOPE/VERB for verbs that take no argument. */
|
||||
#define VERB_SCOPE_NOARG(scope, d, v, h) \
|
||||
VERB_SCOPE(scope, d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h)
|
||||
#define VERB_NOARG(d, v, h) \
|
||||
VERB(d, v, /* a= */ NULL, /* amin= */ VERB_ANY, /* amax= */ 1, /* f= */ 0, h)
|
||||
VERB_SCOPE_NOARG(static, d, v, h)
|
||||
|
||||
/* Magic entry in the table (which will not be returned) that designates the start of the group <gr>.
|
||||
* The macro works as a separator between groups and must be between other VERB* stanzas. */
|
||||
|
||||
Reference in New Issue
Block a user