--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-varlink-idl.h"
+
+#include "varlink-io.systemd.SysInstall.h"
+#include "varlink-io.systemd.Repart.h"
+
+static SD_VARLINK_DEFINE_ENUM_TYPE(
+ ProgressPhase,
+
+ SD_VARLINK_DEFINE_ENUM_VALUE(encrypt_credentials),
+ SD_VARLINK_DEFINE_ENUM_VALUE(install_partitions),
+ SD_VARLINK_DEFINE_ENUM_VALUE(mount_partitions),
+ SD_VARLINK_DEFINE_ENUM_VALUE(install_kernel),
+ SD_VARLINK_DEFINE_ENUM_VALUE(install_bootloader),
+ SD_VARLINK_DEFINE_ENUM_VALUE(unmount_partitions));
+
+static SD_VARLINK_DEFINE_ENUM_TYPE(
+ DeviceFit,
+
+ SD_VARLINK_DEFINE_ENUM_VALUE(enough_free_space),
+ SD_VARLINK_DEFINE_ENUM_VALUE(insufficent_free_space),
+ SD_VARLINK_DEFINE_ENUM_VALUE(disk_too_small),
+ SD_VARLINK_DEFINE_ENUM_VALUE(conflicting_disk_label_present));
+
+static SD_VARLINK_DEFINE_STRUCT_TYPE(
+ Credential,
+ SD_VARLINK_FIELD_COMMENT("The id of the credential."),
+ SD_VARLINK_DEFINE_FIELD(id, SD_VARLINK_STRING, 0),
+ SD_VARLINK_FIELD_COMMENT("The value of the credential in base64 encoding."),
+ SD_VARLINK_DEFINE_FIELD(value, SD_VARLINK_STRING, 0));
+
+static SD_VARLINK_DEFINE_METHOD_FULL(
+ Run,
+ SD_VARLINK_SUPPORTS_MORE,
+ SD_VARLINK_FIELD_COMMENT("Full path to the block device node to operate on."),
+ SD_VARLINK_DEFINE_INPUT(node, SD_VARLINK_STRING, 0),
+ SD_VARLINK_FIELD_COMMENT("Path to directory containing definition files."),
+ SD_VARLINK_DEFINE_INPUT(definitions, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("If true, fully erase the target block device."),
+ SD_VARLINK_DEFINE_INPUT(erase, SD_VARLINK_BOOL, 0),
+ SD_VARLINK_FIELD_COMMENT("If true, EFI variables are modified to register the installed boot loader in the firmware's boot options database."),
+ SD_VARLINK_DEFINE_INPUT(variables, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("The path to a kernel image, if missing the current kernel is dedected and used."),
+ SD_VARLINK_DEFINE_INPUT(kernelImagePath, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+
+ SD_VARLINK_FIELD_COMMENT("If true, the current locale is copied to target system"),
+ SD_VARLINK_DEFINE_INPUT(copyLocale, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("If true, the current keymap is copied to target system"),
+ SD_VARLINK_DEFINE_INPUT(copyKeymap, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("If true, the current timezone is copied to target system"),
+ SD_VARLINK_DEFINE_INPUT(copyTimezone, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+
+ SD_VARLINK_FIELD_COMMENT("A list of credentials to be installed to target system."),
+ SD_VARLINK_DEFINE_INPUT_BY_TYPE(credentials, Credential, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+
+ SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, a phase identifier is sent in progress updates."),
+ SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(phase, ProgressPhase, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, an object identifier string is sent in progress updates."),
+ SD_VARLINK_DEFINE_OUTPUT(object, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, a progress percentage (specific to the work done for the specified phase+object is sent in progress updates)."),
+ SD_VARLINK_DEFINE_OUTPUT(progress, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
+
+static SD_VARLINK_DEFINE_METHOD_FULL(
+ ListCandidateDevices,
+ SD_VARLINK_REQUIRES_MORE,
+ SD_VARLINK_FIELD_COMMENT("Path to directory containing definition files, used to evaluate the fit of the target OS for each block device."),
+ SD_VARLINK_DEFINE_INPUT(definitions, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("If true, keep the call open after the initial enumeration and stream live add/remove notifications as block-subsystem uevents arrive. The end of the initial enumeration is marked by exactly one notification with action='ready' and no other fields. Defaults to false."),
+ SD_VARLINK_DEFINE_INPUT(subscribe, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("Discriminator field. Only set in subscribe mode. 'add' carries the full device record, 'remove' carries only node, 'ready' carries no other fields and is sent once after the initial enumeration."),
+ SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(action, BlockDeviceAction, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("The device node path of the block device."),
+ SD_VARLINK_DEFINE_OUTPUT(node, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("List of symlinks pointing to the device node, if any."),
+ SD_VARLINK_DEFINE_OUTPUT(symlinks, SD_VARLINK_STRING, SD_VARLINK_ARRAY|SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("The Linux kernel disk sequence number identifying the medium."),
+ SD_VARLINK_DEFINE_OUTPUT(diskseq, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("The size of the block device in bytes."),
+ SD_VARLINK_DEFINE_OUTPUT(sizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("The device vendor string if known."),
+ SD_VARLINK_DEFINE_OUTPUT(vendor, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("The device model string if known."),
+ SD_VARLINK_DEFINE_OUTPUT(model, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("The subsystem the block device belongs to if known."),
+ SD_VARLINK_DEFINE_OUTPUT(subsystem, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("The fit indicating whether the OS would have enough available space on the device."),
+ SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(fit, DeviceFit, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("Minimal size of the disk required for the installation."),
+ SD_VARLINK_DEFINE_OUTPUT(minimalSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("Additional free space needed for the installation."),
+ SD_VARLINK_DEFINE_OUTPUT(needFreeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("Current allocated size of this block device."),
+ SD_VARLINK_DEFINE_OUTPUT(currentSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE));
+
+SD_VARLINK_DEFINE_INTERFACE(
+ io_systemd_SysInstall,
+ "io.systemd.SysInstall",
+ SD_VARLINK_INTERFACE_COMMENT("API for installing the OS to another block device."),
+
+ SD_VARLINK_SYMBOL_COMMENT("Progress phase identifiers. Note that we might add more phases here, and thus identifiers. Frontends can choose to display the phase to the user in some human readable form, or not do that, but if they do it and they receive a notification for a so far unknown phase, they should just ignore it."),
+ &vl_type_ProgressPhase,
+
+ SD_VARLINK_SYMBOL_COMMENT("Description about the fit of an OS on a block device."),
+ &vl_type_DeviceFit,
+
+ SD_VARLINK_SYMBOL_COMMENT("Discriminator on streamed ListCandidateDevices replies in subscribe mode."),
+ &vl_type_BlockDeviceAction,
+
+ SD_VARLINK_SYMBOL_COMMENT("A credential to install to target OS."),
+ &vl_type_Credential,
+
+ SD_VARLINK_SYMBOL_COMMENT("Invoke the actual installation of the OS. If invoked with 'more' enabled will report progress, otherwise will just report completion."),
+ &vl_method_Run,
+ SD_VARLINK_SYMBOL_COMMENT("An incompatible disk label present, and not told to erase it."),
+ &vl_error_ConflictingDiskLabelPresent,
+ SD_VARLINK_SYMBOL_COMMENT("The target disk has insufficient free space to fit all requested partitions. (But the disk would fit, if emptied.)"),
+ &vl_error_InsufficientFreeSpace,
+ SD_VARLINK_SYMBOL_COMMENT("The target disk is too small to fit the installation. (Regardless if emptied or not.)"),
+ &vl_error_DiskTooSmall,
+
+ SD_VARLINK_SYMBOL_COMMENT("Return a list of candidate block devices, i.e. that support partition scanning and other requirements for successful operation."),
+ &vl_method_ListCandidateDevices,
+ SD_VARLINK_SYMBOL_COMMENT("Not a single candidate block device could be found."),
+ &vl_error_NoCandidateDevices);
#include "blockdev-list.h"
#include "build.h"
#include "build-path.h"
+#include "bus-polkit.h"
#include "chase.h"
#include "conf-files.h"
#include "constants.h"
#include "format-util.h"
#include "fs-util.h"
#include "glyph-util.h"
+#include "hashmap.h"
#include "help-util.h"
#include "image-policy.h"
#include "json-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "prompt-util.h"
+#include "string-table.h"
#include "strv.h"
#include "terminal-util.h"
+#include "varlink-io.systemd.SysInstall.h"
#include "varlink-util.h"
static char *arg_node = NULL;
static bool arg_copy_timezone = true;
static bool arg_chrome = true;
static bool arg_mute_console = false;
+static bool arg_varlink = false;
STATIC_DESTRUCTOR_REGISTER(arg_node, freep);
STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_kernel_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done);
+typedef enum ProgressPhase {
+ PROGRESS_ENCRYPT_CREDENTIALS,
+ PROGRESS_INSTALL_PARTITIONS,
+ PROGRESS_MOUNT_PARTITIONS,
+ PROGRESS_INSTALL_KERNEL,
+ PROGRESS_INSTALL_BOOTLOADER,
+ PROGRESS_UNMOUNT_PARTITIONS,
+ _PROGRESS_PHASE_MAX,
+ _PROGRESS_PHASE_INVALID = -EINVAL,
+} ProgressPhase;
+
+static const char *progress_phase_table[_PROGRESS_PHASE_MAX] = {
+ [PROGRESS_ENCRYPT_CREDENTIALS] = "encrypt-credentials",
+ [PROGRESS_INSTALL_PARTITIONS] = "install-partitions",
+ [PROGRESS_MOUNT_PARTITIONS] = "mount-partitions",
+ [PROGRESS_INSTALL_KERNEL] = "install-kernel",
+ [PROGRESS_INSTALL_BOOTLOADER] = "install-bootloader",
+ [PROGRESS_UNMOUNT_PARTITIONS] = "unmount-partitions",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase, ProgressPhase);
+
+static const char *progress_phase_log_table[_PROGRESS_PHASE_MAX] = {
+ [PROGRESS_ENCRYPT_CREDENTIALS] = "Encrypting credentials...",
+ [PROGRESS_INSTALL_PARTITIONS] = "Installing partitions...",
+ [PROGRESS_MOUNT_PARTITIONS] = "Mounting partitions...",
+ [PROGRESS_INSTALL_KERNEL] = "Installing kernel...",
+ [PROGRESS_INSTALL_BOOTLOADER] = "Installing boot loader...",
+ [PROGRESS_UNMOUNT_PARTITIONS] = "Unmounting partitions...",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase_log, ProgressPhase);
+
+typedef enum DeviceFit {
+ DEVICE_FIT_ENOUGH_FREE_SPACE,
+ DEVICE_FIT_INSUFFICIENT_FREE_SPACE,
+ DEVICE_FIT_DISK_TOO_SMALL,
+ DEVICE_FIT_CONFLICTING_DISK_LABEL_PRESENT,
+ _DEVICE_FIT_MAX,
+ _DEVICE_FIT_INVALID = -EINVAL,
+} DeviceFit;
+
+static const char *device_fit_table[_DEVICE_FIT_MAX] = {
+ [DEVICE_FIT_ENOUGH_FREE_SPACE] = "enough-free-space",
+ [DEVICE_FIT_INSUFFICIENT_FREE_SPACE] = "insufficient-free-space",
+ [DEVICE_FIT_DISK_TOO_SMALL] = "disk-too-small",
+ [DEVICE_FIT_CONFLICTING_DISK_LABEL_PRESENT] = "conflicting-disk-label-present",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(device_fit, DeviceFit);
+
+typedef struct SysInstallContext {
+ bool copy_locale;
+ bool copy_keymap;
+ bool copy_timezone;
+ MachineCredentialContext credentials;
+ char **definitions;
+ bool erase;
+ char *node;
+ char *kernel_filename;
+ int kernel_fd;
+ bool touch_variables;
+
+ sd_varlink *repart_link;
+
+ sd_varlink *link; /* If 'more' is used on the Varlink call, we'll send progress info over this link */
+} SysInstallContext;
+
+static void sysinstall_context_done(SysInstallContext *c) {
+ assert(c);
+
+ strv_free(c->definitions);
+
+ free(c->node);
+
+ free(c->kernel_filename);
+ safe_close(c->kernel_fd);
+
+ machine_credential_context_done(&c->credentials);
+
+ sd_varlink_flush_close_unref(c->repart_link);
+
+ sd_varlink_unref(c->link);
+}
+
static int help(void) {
int r;
return log_oom();
}
+ r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
+ if (r < 0)
+ return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
+ if (r > 0)
+ arg_varlink = true;
+
return 1;
}
return 0;
}
+static int sysinstall_context_notify(
+ SysInstallContext *context,
+ ProgressPhase phase,
+ const char *object,
+ unsigned percent) {
+
+ int r;
+
+ assert(context);
+ assert(phase >= 0);
+ assert(phase < _PROGRESS_PHASE_MAX);
+
+ log_notice("%s%s%s",
+ emoji_enabled() ? phase == PROGRESS_ENCRYPT_CREDENTIALS ? glyph(GLYPH_LOCK_AND_KEY) : glyph(GLYPH_COMPUTER_DISK) : "",
+ emoji_enabled() ? " " : "",
+ progress_phase_log_to_string(phase));
+
+ if (context->link) {
+ r = sd_varlink_notifybo(
+ context->link,
+ JSON_BUILD_PAIR_ENUM("phase", progress_phase_to_string(phase)),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object),
+ JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("progress", percent, UINT_MAX));
+ if (r < 0)
+ log_debug_errno(r, "Failed to send varlink notify progress notification, ignoring: %m");
+
+ r = sd_varlink_flush(context->link);
+ if (r < 0)
+ log_debug_errno(r, "Failed to flush varlink notify progress notification, ignoring: %m");
+ }
+
+ return 0;
+}
+
+typedef struct RepartResult {
+ int ret;
+ SysInstallContext *context;
+} RepartResult;
+
+static int handle_repart_reply(
+ sd_varlink *link,
+ sd_json_variant *reply,
+ const char *error_id,
+ sd_varlink_reply_flags_t flags,
+ void *userdata) {
+
+ RepartResult *result = userdata;
+ int r;
+
+ assert(result);
+
+ struct {
+ uint64_t min_size;
+ uint64_t current_size;
+ uint64_t need_free;
+
+ const char *phase;
+ const char *object;
+ unsigned progress;
+ } p = {
+ .min_size = UINT64_MAX,
+ .current_size = UINT64_MAX,
+ .need_free = UINT64_MAX,
+ .progress = UINT_MAX,
+ };
+
+ static const sd_json_dispatch_field dispatch_table[] = {
+ { "minimalSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, min_size), 0 },
+ { "currentSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, current_size), 0 },
+ { "needFreeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, need_free), 0 },
+ { "phase", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, voffsetof(p, phase), 0 },
+ { "object", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_const_string, voffsetof(p, object), 0 },
+ { "progress", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, voffsetof(p, progress), 0 },
+ {}
+ };
+
+ r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
+ if (r < 0)
+ return result->ret = r;
+
+ if (error_id) {
+ const char *sysinstall_error_id = NULL;
+
+ if (streq(error_id, "io.systemd.Repart.InsufficientFreeSpace")) {
+ sysinstall_error_id = "io.systemd.SysInstall.InsufficientFreeSpace";
+ result->ret = log_error_errno(SYNTHETIC_ERRNO(ENOSPC), "Not enough free space on disk, cannot install.");
+ } else if (streq(error_id, "io.systemd.Repart.DiskTooSmall")) {
+ sysinstall_error_id = "io.systemd.SysInstall.DiskTooSmall";
+
+ result->ret = log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Disk too small for installation, cannot install.");
+ } else if (streq(error_id, "io.systemd.Repart.ConflictingDiskLabelPresent")) {
+ sysinstall_error_id = "io.systemd.SysInstall.ConflictingDiskLabelPresent";
+
+ result->ret = log_error_errno(
+ SYNTHETIC_ERRNO(EHWPOISON),
+ "A conflicting disk label is already present on the target disk, cannot install unless disk is erased.");
+ }
+
+ if (sysinstall_error_id && result->context->link) {
+ r = sd_varlink_errorbo(
+ result->context->link,
+ sysinstall_error_id,
+ JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("currentSizeBytes", p.current_size, UINT64_MAX),
+ JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("needFreeBytes", p.need_free, UINT64_MAX),
+ JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("minimalSizeBytes", p.min_size, UINT64_MAX));
+
+ if (r < 0)
+ return result->ret = r;
+
+ return result->ret;
+ }
+
+ r = sd_varlink_error_to_errno(error_id, reply); /* If this is a system errno style error, output it with %m */
+ if (r != -EBADR)
+ return result->ret = log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m");
+
+ return result->ret = log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %s", error_id);
+ }
+
+ if ((p.progress != UINT_MAX || p.object) && result->context->link)
+ (void) sysinstall_context_notify(result->context, PROGRESS_INSTALL_PARTITIONS, p.object, p.progress);
+
+ return result->ret = 0;
+}
+
+static int sysinstall_context_invoke_repart_run(SysInstallContext *context) {
+
+ int r;
+
+ assert(context);
+
+ r = connect_to_repart(&context->repart_link);
+ if (r < 0)
+ return r;
+
+ /* Seeding the partitions might be very slow, disable timeout */
+ r = sd_varlink_set_relative_timeout(context->repart_link, UINT64_MAX);
+ if (r < 0)
+ return log_error_errno(r, "Failed to disable IPC timeout: %m");
+
+ RepartResult result = {
+ .context = context,
+ };
+
+ sd_varlink_set_userdata(context->repart_link, &result);
+
+ r = sd_varlink_bind_reply(context->repart_link, handle_repart_reply);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind repart reply callback: %m");
+
+ r = sd_varlink_observebo(
+ context->repart_link,
+ "io.systemd.Repart.Run",
+ SD_JSON_BUILD_PAIR_STRING("node", context->node),
+ SD_JSON_BUILD_PAIR_STRING("empty", context->erase ? "force" : "allow"),
+ SD_JSON_BUILD_PAIR_BOOLEAN("dryRun", false),
+ SD_JSON_BUILD_PAIR_CONDITION(!!context->definitions, "definitions", SD_JSON_BUILD_STRV(context->definitions)),
+ SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsEmpty", true),
+ SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsFactoryReset", true));
+ if (r < 0)
+ return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m");
+
+ for (;;) {
+ r = sd_varlink_is_idle(context->repart_link);
+ if (r < 0)
+ return log_error_errno(r, "Failed to check if varlink connection is idle: %m");
+ if (r > 0)
+ break;
+
+ r = sd_varlink_process(context->repart_link);
+ if (r < 0)
+ return log_error_errno(r, "Failed to process varlink connection: %m");
+ if (r != 0)
+ continue;
+
+ r = sd_varlink_wait(context->repart_link, USEC_INFINITY);
+ if (r < 0)
+ return log_error_errno(r, "Failed to wait for varlink connection events: %m");
+ }
+
+ sd_varlink_set_userdata(context->repart_link, NULL);
+
+ r = sd_varlink_bind_reply(context->repart_link, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to unbind repart reply callback: %m");
+
+ return result.ret;
+}
+
static int read_space_metrics(
sd_json_variant *v,
uint64_t *min_size,
const char *node,
bool erase,
bool dry_run,
+ char **definitions,
uint64_t *min_size, /* initialized both on success and error */
uint64_t *current_size, /* ditto */
uint64_t *need_free) { /* ditto */
SD_JSON_BUILD_PAIR_STRING("node", node),
SD_JSON_BUILD_PAIR_STRING("empty", erase ? "force" : "allow"),
SD_JSON_BUILD_PAIR_BOOLEAN("dryRun", dry_run),
- SD_JSON_BUILD_PAIR_CONDITION(!!arg_definitions, "definitions", SD_JSON_BUILD_STRV(arg_definitions)),
+ SD_JSON_BUILD_PAIR_CONDITION(!!definitions, "definitions", SD_JSON_BUILD_STRV(definitions)),
SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsEmpty", true),
SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsFactoryReset", true));
if (r < 0) {
node,
try_erase,
/* dry_run= */ true,
+ arg_definitions,
&min_size,
¤t_size,
&need_free);
}
}
-static int show_summary(void) {
+static int sysinstall_context_show_summary(SysInstallContext *context) {
int r;
- if (!arg_summary)
- return 0;
-
printf("\n"
"%sSummary:%s\n", ansi_underline(), ansi_normal());
r = table_add_many(
table,
TABLE_FIELD, "Selected Disk",
- TABLE_STRING, arg_node,
+ TABLE_STRING, context->node,
TABLE_FIELD, "Erase Disk",
- TABLE_BOOLEAN, arg_erase,
- TABLE_SET_COLOR, arg_erase ? ansi_highlight_red() : NULL,
+ TABLE_BOOLEAN, context->erase,
+ TABLE_SET_COLOR, context->erase ? ansi_highlight_red() : NULL,
TABLE_FIELD, "Register in Firmware",
- TABLE_BOOLEAN, arg_touch_variables);
+ TABLE_BOOLEAN, context->touch_variables);
if (r < 0)
return table_log_add_error(r);
};
STRV_FOREACH_PAIR(id, text, map) {
- MachineCredential *c = machine_credential_find(&arg_credentials, *id);
+ MachineCredential *c = machine_credential_find(&context->credentials, *id);
if (!c)
continue;
}
unsigned n_extra_credentials = 0;
- FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
+ FOREACH_ARRAY(cred, context->credentials.credentials, context->credentials.n_credentials) {
bool covered = false;
STRV_FOREACH_PAIR(id, text, map)
static int invoke_bootctl_install(
sd_varlink **link,
+ bool variables,
const char *root_dir,
int root_fd) {
int r;
SD_JSON_BUILD_PAIR_STRING("operation", "new"),
SD_JSON_BUILD_PAIR_INTEGER("rootFileDescriptor", fd_idx),
SD_JSON_BUILD_PAIR_STRING("rootDirectory", root_dir),
- SD_JSON_BUILD_PAIR_BOOLEAN("touchVariables", arg_touch_variables));
+ SD_JSON_BUILD_PAIR_BOOLEAN("touchVariables", variables));
if (r < 0)
return r;
return 0;
}
-static int read_credential_locale(void) {
+static int read_credential_locale(MachineCredentialContext *credentials) {
int r;
- if (!arg_copy_locale)
- return 0;
-
- if (machine_credential_find(&arg_credentials, "firstboot.locale") ||
- machine_credential_find(&arg_credentials, "firstboot.locale-messages"))
+ if (machine_credential_find(credentials, "firstboot.locale") ||
+ machine_credential_find(credentials, "firstboot.locale-messages"))
return 0;
/* For the main locale we check the two env vars, and if neither is there, we use LC_NUMERIC, since
* separate setting after all */
const char *l = getenv("LC_ALL") ?: getenv("LANG") ?: setlocale(LC_NUMERIC, NULL);
if (l) {
- r = machine_credential_add(&arg_credentials, "firstboot.locale", l, /* size= */ SIZE_MAX);
+ r = machine_credential_add(credentials, "firstboot.locale", l, /* size= */ SIZE_MAX);
if (r < 0)
return log_oom();
}
const char *m = setlocale(LC_MESSAGES, NULL);
if (m && !streq_ptr(m, l)) {
- r = machine_credential_add(&arg_credentials, "firstboot.locale-messages", m, /* size= */ SIZE_MAX);
+ r = machine_credential_add(credentials, "firstboot.locale-messages", m, /* size= */ SIZE_MAX);
if (r < 0)
return log_oom();
}
return 0;
}
-static int read_credential_keymap(void) {
+static int read_credential_keymap(MachineCredentialContext *credentials) {
int r;
- if (!arg_copy_keymap)
- return 0;
-
- if (machine_credential_find(&arg_credentials, "firstboot.keymap"))
+ if (machine_credential_find(credentials, "firstboot.keymap"))
return 0;
_cleanup_free_ char *keymap = NULL;
return log_error_errno(r, "Failed to parse '%s': %m", etc_vconsole_conf());
if (!isempty(keymap)) {
- r = machine_credential_add(&arg_credentials, "firstboot.keymap", keymap, /* size= */ SIZE_MAX);
+ r = machine_credential_add(credentials, "firstboot.keymap", keymap, /* size= */ SIZE_MAX);
if (r < 0)
return log_oom();
}
return 0;
}
-static int read_credential_timezone(void) {
+static int read_credential_timezone(MachineCredentialContext *credentials) {
int r;
- if (!arg_copy_timezone)
- return 0;
-
- if (machine_credential_find(&arg_credentials, "firstboot.timezone"))
+ if (machine_credential_find(credentials, "firstboot.timezone"))
return 0;
_cleanup_free_ char *tz = NULL;
if (r < 0)
log_warning_errno(r, "Failed to read timezone, skipping timezone propagation: %m");
else {
- r = machine_credential_add(&arg_credentials, "firstboot.timezone", tz, /* size= */ SIZE_MAX);
+ r = machine_credential_add(credentials, "firstboot.timezone", tz, /* size= */ SIZE_MAX);
if (r < 0)
return log_oom();
}
return 0;
}
-static int read_credentials(void) {
+static int sysinstall_context_read_credentials(SysInstallContext *context) {
int r;
- r = read_credential_locale();
- if (r < 0)
- return r;
+ if (context->copy_locale) {
+ r = read_credential_locale(&context->credentials);
+ if (r < 0)
+ return r;
+ }
- r = read_credential_keymap();
- if (r < 0)
- return r;
+ if (context->copy_keymap) {
+ r = read_credential_keymap(&context->credentials);
+ if (r < 0)
+ return r;
+ }
- r = read_credential_timezone();
- if (r < 0)
- return r;
+ if (context->copy_timezone) {
+ r = read_credential_timezone(&context->credentials);
+ if (r < 0)
+ return r;
+ }
return 0;
}
return 0;
}
-static int encrypt_credentials(sd_varlink **link, char ***encrypted) {
+static int encrypt_credentials(sd_varlink **link, MachineCredentialContext *credentials, char ***encrypted) {
int r;
assert(link);
assert(encrypted);
- FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) {
+ FOREACH_ARRAY(cred, credentials->credentials, credentials->n_credentials) {
r = encrypt_one_credential(link, cred, encrypted);
if (r < 0)
return r;
.default_flags = PARTITION_POLICY_IGNORE,
};
-static int settle_definitions(void) {
+static int settle_definitions(char **definitions, char ***ret_definitions) {
+
+ _cleanup_strv_free_ char **d = NULL;
int r;
- if (arg_definitions)
+ assert(ret_definitions);
+
+ if (definitions) {
+ d = strv_copy(definitions);
+ if (!d)
+ return log_oom();
+
+ *ret_definitions = TAKE_PTR(d);
+
return 0;
+ }
/* If /usr/lib/repart.sysinstall.d/ is populated, use it, otherwise use the regular definition
* files */
return log_error_errno(r, "Failed to enumerate *.conf files: %m");
if (!strv_isempty(files)) {
- arg_definitions = strv_copy(CONF_PATHS_STRV("repart.sysinstall.d"));
- if (!arg_definitions)
+ d = strv_copy(CONF_PATHS_STRV("repart.sysinstall.d"));
+ if (!d)
return log_oom();
+
+ *ret_definitions = TAKE_PTR(d);
+ }
+
+ return 0;
+}
+
+static int sysinstall_context_settle_definitions(SysInstallContext *context,
+ char **definitions) {
+
+ return settle_definitions(definitions, &context->definitions);
+}
+
+static int sysinstall_context_settle_kernel_image(SysInstallContext *context,
+ const char *kernel_image) {
+
+ _cleanup_free_ char *kernel_filename = NULL;
+ _cleanup_close_ int kernel_fd = -EBADF;
+ int r;
+
+ assert(context->kernel_fd < 0);
+ assert(!context->kernel_filename);
+
+ if (kernel_image) {
+ r = path_extract_filename(kernel_image, &kernel_filename);
+ if (r < 0)
+ return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", kernel_image);
+ if (r == O_DIRECTORY)
+ return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Kernel path '%s' refers to directory, must be regular file, refusing.", kernel_image);
+
+ kernel_fd = xopenat_full(XAT_FDROOT, kernel_image, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID);
+ if (kernel_fd < 0)
+ return log_error_errno(kernel_fd, "Failed to open kernel image '%s': %m", kernel_image);
+
+ } else {
+ r = find_current_kernel(&kernel_filename, &kernel_fd);
+ if (r < 0)
+ return r;
}
+ context->kernel_filename = TAKE_PTR(kernel_filename);
+ context->kernel_fd = TAKE_FD(kernel_fd);
+
+ return 0;
+}
+
+static int sysinstall_context_run(SysInstallContext *context) {
+
+ int r;
+
+ assert(context);
+
+ (void) sysinstall_context_notify(context, PROGRESS_ENCRYPT_CREDENTIALS, NULL, UINT_MAX);
+
+ _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *creds_link = NULL;
+ _cleanup_strv_free_ char **encrypted_credentials = NULL;
+ r = encrypt_credentials(&creds_link, &context->credentials, &encrypted_credentials);
+ if (r < 0)
+ return r;
+
+ (void) sysinstall_context_notify(context, PROGRESS_INSTALL_PARTITIONS, NULL, UINT_MAX);
+
+ /* Do the main part of the installation */
+
+ r = sysinstall_context_invoke_repart_run(context);
+ if (r < 0)
+ return r;
+
+ (void) sysinstall_context_notify(context, PROGRESS_MOUNT_PARTITIONS, NULL, UINT_MAX);
+
+ _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
+ _cleanup_(umount_and_freep) char *root_dir = NULL;
+ _cleanup_close_ int root_fd = -EBADF;
+ r = mount_image_privately_interactively(
+ context->node,
+ &image_policy,
+ DISSECT_IMAGE_REQUIRE_ROOT |
+ DISSECT_IMAGE_RELAX_VAR_CHECK |
+ DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
+ DISSECT_IMAGE_DISCARD_ANY |
+ DISSECT_IMAGE_GPT_ONLY |
+ DISSECT_IMAGE_FSCK |
+ DISSECT_IMAGE_USR_NO_ROOT |
+ DISSECT_IMAGE_ADD_PARTITION_DEVICES |
+ DISSECT_IMAGE_PIN_PARTITION_DEVICES,
+ &root_dir,
+ &root_fd,
+ &loop_device);
+ if (r < 0)
+ return log_error_errno(r, "Failed to mount new image: %m");
+
+ (void) sysinstall_context_notify(context, PROGRESS_INSTALL_KERNEL, NULL, UINT_MAX);
+
+ _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *bootctl_link = NULL;
+ r = invoke_bootctl_link(&bootctl_link, root_dir, root_fd, context->kernel_filename, context->kernel_fd, encrypted_credentials);
+ if (r < 0)
+ return r;
+
+ (void) sysinstall_context_notify(context, PROGRESS_INSTALL_BOOTLOADER, NULL, UINT_MAX);
+
+ r = invoke_bootctl_install(&bootctl_link, context->touch_variables, root_dir, root_fd);
+ if (r < 0)
+ return r;
+
+ (void) sysinstall_context_notify(context, PROGRESS_UNMOUNT_PARTITIONS, NULL, UINT_MAX);
+
+ root_fd = safe_close(root_fd);
+ r = umount_recursive(root_dir, /* flags= */ 0);
+ if (r < 0)
+ log_warning_errno(r, "Failed to unmount target disk, proceeding anyway: %m");
+ loop_device = loop_device_unref(loop_device);
+ sync();
+
+ return 0;
+}
+
+typedef struct ListCandidateDevicesContext {
+ char **definitions;
+ bool subscribe;
+
+ sd_varlink *repart_link; /* A repart connection to get candidate devices */
+ sd_varlink *dry_run_repart_link; /* A second repart connection to perform a dry run on each node */
+
+ sd_varlink *link;
+} ListCandidateDevicesContext;
+
+static ListCandidateDevicesContext* list_candidate_devices_context_new(void) {
+ ListCandidateDevicesContext *context = new(ListCandidateDevicesContext, 1);
+
+ if (!context)
+ return NULL;
+
+ *context = (ListCandidateDevicesContext) {};
+
+ return context;
+}
+
+static ListCandidateDevicesContext* list_candidate_devices_context_free(ListCandidateDevicesContext *context) {
+ if (!context)
+ return NULL;
+
+ strv_free(context->definitions);
+
+ context->repart_link = sd_varlink_flush_close_unref(context->repart_link);
+ context->dry_run_repart_link = sd_varlink_flush_close_unref(context->dry_run_repart_link);
+ context->link = sd_varlink_unref(context->link);
+
+ return mfree(context);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(ListCandidateDevicesContext*, list_candidate_devices_context_free);
+
+static int list_candidate_devices_context_settle_definitions(ListCandidateDevicesContext *context,
+ char **definitions) {
+
+ return settle_definitions(definitions, &context->definitions);
+}
+
+static void vl_on_disconnect(sd_varlink_server *server, sd_varlink *link, void *userdata) {
+ assert(server);
+ assert(link);
+
+ list_candidate_devices_context_free(sd_varlink_set_userdata(link, NULL));
+}
+
+typedef struct DevicesResponse {
+ const char *node;
+ char **symlinks;
+ uint64_t diskseq;
+ uint64_t size;
+ const char *model;
+ const char *vendor;
+ const char *subsystem;
+ const char *action;
+} DevicesResponse;
+
+static void devices_response_done(DevicesResponse *p) {
+ assert(p);
+
+ p->symlinks = strv_free(p->symlinks);
+}
+
+static int fetch_candidate_devices_reply(
+ sd_varlink *repart_link,
+ sd_json_variant *reply,
+ const char *error_id,
+ sd_varlink_reply_flags_t flags,
+ void *userdata) {
+
+ int r;
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+ ListCandidateDevicesContext *context = ASSERT_PTR(userdata);
+
+ if (error_id) {
+ if (streq(error_id, "io.systemd.Repart.NoCandidateDevices"))
+ return sd_varlink_error(context->link, "io.systemd.SysInstall.NoCandidateDevices", NULL);
+
+ return sd_varlink_error(context->link, error_id, NULL);
+ }
+
+ static const sd_json_dispatch_field dispatch_table[] = {
+ { "node", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(DevicesResponse, node), 0 },
+ { "symlinks", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(DevicesResponse, symlinks), 0 },
+ { "diskseq", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(DevicesResponse, diskseq), 0 },
+ { "sizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(DevicesResponse, size), 0 },
+ { "model", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(DevicesResponse, model), 0 },
+ { "vendor", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(DevicesResponse, vendor), 0 },
+ { "subsystem", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(DevicesResponse, subsystem), 0 },
+ { "action", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(DevicesResponse, action), 0 },
+ {}
+ };
+
+ _cleanup_(devices_response_done) DevicesResponse p = {
+ .diskseq = UINT64_MAX,
+ .size = UINT64_MAX,
+ };
+ r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
+ if (r < 0)
+ return r;
+
+ if (context->subscribe) {
+ /* The action needs to be ready, remove or add else we don't support the action */
+ if (streq(p.action, "ready"))
+ return sd_varlink_notifybo(context->link, SD_JSON_BUILD_PAIR("action", JSON_BUILD_CONST_STRING("ready")));
+
+ if (streq(p.action, "remove"))
+ return sd_varlink_notifybo(context->link,
+ SD_JSON_BUILD_PAIR("action", JSON_BUILD_CONST_STRING("remove")),
+ SD_JSON_BUILD_PAIR_STRING("node", p.node));
+
+ if (!streq(p.action, "add")) {
+ log_debug("Skip unsupported action '%s' while fetching candidate devices.", p.action);
+ return 0;
+ }
+ }
+
+ uint64_t min_size = UINT64_MAX, current_size = UINT64_MAX, need_free = UINT64_MAX;
+ r = invoke_repart(
+ &context->dry_run_repart_link,
+ p.node,
+ /* erase= */ false,
+ /* dry_run= */ true,
+ context->definitions,
+ &min_size,
+ ¤t_size,
+ &need_free);
+
+ DeviceFit fit;
+ if (r < 0) {
+ if (r == -ENOSPC)
+ fit = DEVICE_FIT_INSUFFICIENT_FREE_SPACE;
+ else if (r == -E2BIG)
+ fit = DEVICE_FIT_DISK_TOO_SMALL;
+ else if (r == -EHWPOISON)
+ fit = DEVICE_FIT_CONFLICTING_DISK_LABEL_PRESENT;
+ else
+ return r;
+ } else
+ fit = DEVICE_FIT_ENOUGH_FREE_SPACE;
+
+ r = sd_json_buildo(&v,
+ SD_JSON_BUILD_PAIR_STRING("node", p.node),
+ JSON_BUILD_PAIR_STRV_NON_EMPTY("symlinks", p.symlinks),
+ JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("diskseq", p.diskseq, UINT64_MAX),
+ JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("sizeBytes", p.size, UINT64_MAX),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("model", p.model),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("vendor", p.vendor),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("subsystem", p.subsystem),
+ JSON_BUILD_PAIR_ENUM("fit", device_fit_to_string(fit)),
+ JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("currentSizeBytes", current_size, UINT64_MAX),
+ JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("needFreeBytes", need_free, UINT64_MAX),
+ JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("minimalSizeBytes", min_size, UINT64_MAX),
+ SD_JSON_BUILD_PAIR_CONDITION(context->subscribe, "action", JSON_BUILD_CONST_STRING("add")));
+ if (r < 0)
+ return r;
+
+ if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
+ return sd_varlink_notify(context->link, v);
+
+ return sd_varlink_reply(context->link, v);
+}
+
+typedef struct ListCandidateDevicesParameters {
+ char **definitions;
+ bool subscribe;
+} ListCandidateDevicesParameters;
+
+static void list_candidate_devices_parameters_done(ListCandidateDevicesParameters *p) {
+ assert(p);
+
+ p->definitions = strv_free(p->definitions);
+}
+
+static int vl_method_list_candidate_devices(
+ sd_varlink *link,
+ sd_json_variant *parameters,
+ sd_varlink_method_flags_t flags,
+ void *userdata) {
+ int r;
+
+ assert(link);
+
+ sd_varlink_server *varlink_server = sd_varlink_get_server(link);
+ sd_event *event = sd_varlink_server_get_event(varlink_server);
+ Hashmap **polkit_registry = ASSERT_PTR(sd_varlink_server_get_userdata(varlink_server));
+
+ assert(FLAGS_SET(flags, SD_VARLINK_METHOD_MORE));
+
+ r = varlink_verify_polkit_async(
+ link,
+ /* bus= */ NULL,
+ "io.systemd.sysinstall.ListCandidateDevices",
+ /* details= */ NULL,
+ polkit_registry);
+ if (r <= 0)
+ return r;
+
+ static const sd_json_dispatch_field dispatch_table[] = {
+ { "definitions", SD_JSON_VARIANT_ARRAY, json_dispatch_strv_path, offsetof(ListCandidateDevicesParameters, definitions), SD_JSON_STRICT },
+ { "subscribe", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(ListCandidateDevicesParameters, subscribe), 0 },
+ {}
+ };
+
+ _cleanup_(list_candidate_devices_parameters_done) ListCandidateDevicesParameters p = {};
+ r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
+ if (r != 0)
+ return r;
+
+ _cleanup_(list_candidate_devices_context_freep) ListCandidateDevicesContext* context = list_candidate_devices_context_new();
+ if (!context)
+ return log_oom();
+
+ context->subscribe = p.subscribe;
+ r = list_candidate_devices_context_settle_definitions(context, p.definitions);
+ if (r < 0)
+ return r;
+
+ context->link = sd_varlink_ref(link);
+
+ r = connect_to_repart(&context->repart_link);
+ if (r < 0)
+ return r;
+
+ r = sd_varlink_attach_event(context->repart_link, event, SD_EVENT_PRIORITY_NORMAL);
+ if (r < 0)
+ return log_error_errno(
+ r,
+ "Failed to attach io.systemd.Repart.ListCandidateDevices() varlink connection to event loop: %m");
+
+ r = sd_varlink_bind_reply(context->repart_link, fetch_candidate_devices_reply);
+ if (r < 0)
+ return r;
+
+ r = sd_varlink_observebo(
+ context->repart_link,
+ "io.systemd.Repart.ListCandidateDevices",
+ SD_JSON_BUILD_PAIR_BOOLEAN("subscribe", context->subscribe),
+ SD_JSON_BUILD_PAIR_BOOLEAN("ignoreRoot", true));
+
+ if (r < 0)
+ return log_error_errno(
+ r,
+ "Failed to issue io.systemd.Repart.ListCandidateDevices() varlink call: %m");
+
+ r = sd_varlink_server_bind_disconnect(varlink_server, vl_on_disconnect);
+ if (r < 0)
+ return r;
+
+ /* The context is freed in vl_on_disconnect() */
+ sd_varlink_set_userdata(context->repart_link, context);
+ sd_varlink_set_userdata(link, TAKE_PTR(context));
+
+ return 0;
+}
+
+typedef struct RunParameters {
+ char *node;
+ char **definitions;
+ bool erase;
+ bool variables;
+ char *kernel_image;
+ bool copy_locale;
+ bool copy_keymap;
+ bool copy_timezone;
+ sd_json_variant *credentials;
+} RunParameters;
+
+static void run_parameters_done(RunParameters *p) {
+ assert(p);
+
+ p->node = mfree(p->node);
+ p->definitions = strv_free(p->definitions);
+ p->kernel_image = mfree(p->kernel_image);
+ p->credentials = sd_json_variant_unref(p->credentials);
+}
+
+typedef struct CredentialParameters {
+ const char *id;
+ struct iovec value;
+} CredentialParameters;
+
+static void credential_parameters_done(CredentialParameters *p) {
+ assert(p);
+
+ iovec_done_erase(&p->value);
+}
+
+static int credentials_from_json_array(MachineCredentialContext *credentials, sd_json_variant *v) {
+
+ int r;
+ sd_json_variant *credential;
+
+ assert(credentials);
+
+ static const sd_json_dispatch_field dispatch_table[] = {
+ { "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(CredentialParameters, id), SD_JSON_MANDATORY },
+ { "value", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(CredentialParameters, value), SD_JSON_MANDATORY },
+ {}
+ };
+
+ JSON_VARIANT_ARRAY_FOREACH(credential, v) {
+ _cleanup_(credential_parameters_done) CredentialParameters p = {};
+
+ r = sd_json_dispatch(credential, dispatch_table, /* flags= */ 0, &p);
+ if (r < 0)
+ return r;
+
+ r = machine_credential_add(credentials, p.id, p.value.iov_base, p.value.iov_len);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int vl_method_run(
+ sd_varlink *link,
+ sd_json_variant *parameters,
+ sd_varlink_method_flags_t flags,
+ void *userdata) {
+
+ static const sd_json_dispatch_field dispatch_table[] = {
+ { "node", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(RunParameters, node), SD_JSON_MANDATORY | SD_JSON_STRICT },
+ { "definitions", SD_JSON_VARIANT_ARRAY, json_dispatch_strv_path, offsetof(RunParameters, definitions), SD_JSON_NULLABLE | SD_JSON_STRICT },
+ { "erase", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, erase), SD_JSON_MANDATORY },
+ { "variables", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, variables), SD_JSON_NULLABLE },
+ { "kernelImagePath", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(RunParameters, kernel_image), SD_JSON_NULLABLE | SD_JSON_STRICT },
+ { "copyLocale", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, copy_locale), SD_JSON_NULLABLE },
+ { "copyKeymap", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, copy_keymap), SD_JSON_NULLABLE },
+ { "copyTimezone", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RunParameters, copy_timezone), SD_JSON_NULLABLE },
+ { "credentials", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_variant, offsetof(RunParameters, credentials), SD_JSON_NULLABLE },
+ {}
+ };
+
+ int r;
+
+ assert(link);
+
+ sd_varlink_server *varlink_server = sd_varlink_get_server(link);
+ Hashmap **polkit_registry = ASSERT_PTR(sd_varlink_server_get_userdata(varlink_server));
+
+ r = varlink_verify_polkit_async(
+ link,
+ /* bus= */ NULL,
+ "io.systemd.sysinstall.Run",
+ /* details= */ NULL,
+ polkit_registry);
+ if (r <= 0)
+ return r;
+
+ _cleanup_(run_parameters_done) RunParameters p = {};
+ r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
+ if (r != 0)
+ return r;
+
+ _cleanup_(sysinstall_context_done) SysInstallContext context = (SysInstallContext) {
+ .copy_locale = p.copy_locale,
+ .copy_keymap = p.copy_keymap,
+ .copy_timezone = p.copy_timezone,
+ .erase = p.erase,
+ .touch_variables = p.variables,
+ .node = TAKE_PTR(p.node),
+ .kernel_fd = -EBADF,
+ };
+
+ r = sysinstall_context_settle_definitions(&context, p.definitions);
+ if (r < 0)
+ return r;
+
+ if (FLAGS_SET(flags, SD_VARLINK_METHOD_MORE))
+ context.link = sd_varlink_ref(link);
+
+ r = credentials_from_json_array(&context.credentials, p.credentials);
+ if (r < 0)
+ return r;
+
+ r = sysinstall_context_read_credentials(&context);
+ if (r < 0)
+ return r;
+
+ r = sysinstall_context_settle_kernel_image(&context, p.kernel_image);
+ if (r < 0)
+ return r;
+
+ r = sysinstall_context_run(&context);
+ if (r < 0)
+ return r;
+
+ return sd_varlink_reply(link, NULL);
+}
+
+static int vl_server(void) {
+ _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
+ _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL;
+ int r;
+
+ /* Invocation as Varlink service */
+
+ r = varlink_server_new(
+ &varlink_server,
+ 0,
+ /* userdata= */ &polkit_registry);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate Varlink server: %m");
+
+ r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_SysInstall);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add Varlink interface: %m");
+
+ r = sd_varlink_server_bind_method_many(
+ varlink_server,
+ "io.systemd.SysInstall.ListCandidateDevices", vl_method_list_candidate_devices,
+ "io.systemd.SysInstall.Run", vl_method_run);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind Varlink methods: %m");
+
+ r = sd_varlink_server_loop_auto(varlink_server);
+ if (r < 0)
+ return log_error_errno(r, "Failed to run Varlink event loop: %m");
+
return 0;
}
}
static int run(int argc, char *argv[]) {
- _cleanup_free_ char *kernel_filename = NULL;
- _cleanup_close_ int kernel_fd = -EBADF;
int r;
setlocale(LC_ALL, "");
log_setup();
- r = settle_definitions();
+ if (arg_varlink)
+ return vl_server();
+
+ _cleanup_(sysinstall_context_done) SysInstallContext context = (SysInstallContext) {
+ .copy_locale = arg_copy_locale,
+ .copy_keymap = arg_copy_keymap,
+ .copy_timezone = arg_copy_timezone,
+ .credentials = TAKE_STRUCT(arg_credentials),
+ .kernel_fd = -EBADF,
+ };
+
+ r = sysinstall_context_settle_definitions(&context, arg_definitions);
if (r < 0)
return r;
/* node= */ NULL,
/* erase= */ true,
/* dry_run= */ true,
+ arg_definitions,
&min_size,
/* current_size= */ NULL,
/* need_free= */ NULL);
if (r < 0)
return r;
- r = read_credentials();
+ r = sysinstall_context_read_credentials(&context);
if (r < 0)
return r;
- if (arg_kernel_image) {
- r = path_extract_filename(arg_kernel_image, &kernel_filename);
- if (r < 0)
- return log_error_errno(r, "Failed to extract filename from kernel path '%s': %m", arg_kernel_image);
- if (r == O_DIRECTORY)
- return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Kernel path '%s' refers to directory, must be regular file, refusing.", arg_kernel_image);
-
- kernel_fd = xopenat_full(XAT_FDROOT, arg_kernel_image, O_RDONLY|O_CLOEXEC, XO_REGULAR, MODE_INVALID);
- if (kernel_fd < 0)
- return log_error_errno(kernel_fd, "Failed to open kernel image '%s': %m", arg_kernel_image);
- } else {
- r = find_current_kernel(&kernel_filename, &kernel_fd);
- if (r < 0)
- return r;
- }
+ r = sysinstall_context_settle_kernel_image(&context, arg_kernel_image);
+ if (r < 0)
+ return r;
/* Verify we have everything we need */
assert(arg_node);
assert(arg_erase >= 0);
assert(arg_touch_variables >= 0);
- r = show_summary();
- if (r < 0)
- return r;
+ context.node = TAKE_PTR(arg_node);
+ context.touch_variables = arg_touch_variables;
+ context.erase = arg_erase;
+
+ if (arg_summary) {
+ r = sysinstall_context_show_summary(&context);
+ if (r < 0)
+ return r;
+ }
r = prompt_confirm();
if (r < 0)
putchar('\n');
- log_notice("%s%sEncrypting credentials...",
- emoji_enabled() ? glyph(GLYPH_LOCK_AND_KEY) : "", emoji_enabled() ? " " : "");
-
- _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *creds_link = NULL;
- _cleanup_strv_free_ char **encrypted_credentials = NULL;
- r = encrypt_credentials(&creds_link, &encrypted_credentials);
- if (r < 0)
- return r;
-
- log_notice("%s%sInstalling partitions...",
- emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
-
- /* Do the main part of the installation */
- r = invoke_repart(
- &repart_link,
- arg_node,
- arg_erase,
- /* dry_run= */ false,
- /* min_size= */ NULL,
- /* current_size= */ NULL,
- /* need_free= */ NULL);
- if (r < 0)
- return r;
-
- log_notice("%s%sMounting partitions...",
- emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
-
- _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
- _cleanup_(umount_and_freep) char *root_dir = NULL;
- _cleanup_close_ int root_fd = -EBADF;
- r = mount_image_privately_interactively(
- arg_node,
- &image_policy,
- DISSECT_IMAGE_REQUIRE_ROOT |
- DISSECT_IMAGE_RELAX_VAR_CHECK |
- DISSECT_IMAGE_ALLOW_USERSPACE_VERITY |
- DISSECT_IMAGE_DISCARD_ANY |
- DISSECT_IMAGE_GPT_ONLY |
- DISSECT_IMAGE_FSCK |
- DISSECT_IMAGE_USR_NO_ROOT |
- DISSECT_IMAGE_ADD_PARTITION_DEVICES |
- DISSECT_IMAGE_PIN_PARTITION_DEVICES,
- &root_dir,
- &root_fd,
- &loop_device);
- if (r < 0)
- return log_error_errno(r, "Failed to mount new image: %m");
-
- log_notice("%s%sInstalling kernel...",
- emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
-
- _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *bootctl_link = NULL;
- r = invoke_bootctl_link(&bootctl_link, root_dir, root_fd, kernel_filename, kernel_fd, encrypted_credentials);
+ r = sysinstall_context_run(&context);
if (r < 0)
return r;
- log_notice("%s%sInstalling boot loader...",
- emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
-
- r = invoke_bootctl_install(&bootctl_link, root_dir, root_fd);
- if (r < 0)
- return r;
-
- log_notice("%s%sUnmounting partitions...",
- emoji_enabled() ? glyph(GLYPH_COMPUTER_DISK) : "", emoji_enabled() ? " " : "");
-
- root_fd = safe_close(root_fd);
- r = umount_recursive(root_dir, /* flags= */ 0);
- if (r < 0)
- log_warning_errno(r, "Failed to unmount target disk, proceeding anyway: %m");
- loop_device = loop_device_unref(loop_device);
- sync();
-
log_notice("%s%sInstallation succeeded.",
emoji_enabled() ? glyph(GLYPH_SPARKLES) : "", emoji_enabled() ? " " : "");