diff --git a/man/systemd-import-generator.xml b/man/systemd-import-generator.xml index 1faa2f78a2..f140873552 100644 --- a/man/systemd-import-generator.xml +++ b/man/systemd-import-generator.xml @@ -117,6 +117,16 @@ + + + blockdev + + If this option is specified the downloaded image is attached to a loopback block + device (via systemd-loop@.service) after completion. This permits booting + from downloaded disk images. This is only supported for raw disk images. + + + @@ -183,6 +193,16 @@ validation. This is useful for development purposes in virtual machines and containers. Warning: do not deploy a system with validation disabled like this! + + + Download root disk image (raw) into memory, for booting into it + + rd.systemd.pull=raw,machine,verify=no,blockdev:image:https://example.com/image.raw.xz root=/dev/disk/by-loop-ref/image.raw-part2 + + This downloads the specified disk image, saving it locally under the name + image, and attaches it to a loopback block device on completion. It then boots from + the 2nd partition in the image. + @@ -193,6 +213,7 @@ kernel-command-line7 systemd.system-credentials7 importctl1 + systemd-loop@.service8 diff --git a/man/systemd.special.xml b/man/systemd.special.xml index 7bf9b63f0a..ca3bf463ae 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -386,6 +386,16 @@ directly. + + imports.target + + A target unit that pulls in all disk image download jobs to execute on system boot. This is + used by + systemd-import-generator8. + + + + init.scope @@ -1075,6 +1085,16 @@ + + imports-pre.target + + A passive unit that is ordered before all disk image download jobs to execute on system + boot. This is used by + systemd-import-generator8. + + + + local-fs-pre.target diff --git a/src/import/import-generator.c b/src/import/import-generator.c index 24da2f1a26..a778d064a9 100644 --- a/src/import/import-generator.c +++ b/src/import/import-generator.c @@ -8,20 +8,42 @@ #include "fileio.h" #include "generator.h" #include "import-util.h" +#include "initrd-util.h" #include "json-util.h" #include "proc-cmdline.h" +#include "special.h" #include "specifier.h" +#include "unit-name.h" #include "web-util.h" +typedef struct Transfer { + ImageClass class; + ImportType type; + char *local; + char *remote; + bool blockdev; + sd_json_variant *json; +} Transfer; + static const char *arg_dest = NULL; static char *arg_success_action = NULL; static char *arg_failure_action = NULL; -static sd_json_variant **arg_transfers = NULL; +static Transfer *arg_transfers = NULL; static size_t arg_n_transfers = 0; +static void transfer_destroy_many(Transfer *transfers, size_t n) { + FOREACH_ARRAY(t, transfers, n) { + free(t->local); + free(t->remote); + sd_json_variant_unref(t->json); + } + + free(transfers); +} + STATIC_DESTRUCTOR_REGISTER(arg_success_action, freep); STATIC_DESTRUCTOR_REGISTER(arg_failure_action, freep); -STATIC_ARRAY_DESTRUCTOR_REGISTER(arg_transfers, arg_n_transfers, sd_json_variant_unref_many); +STATIC_ARRAY_DESTRUCTOR_REGISTER(arg_transfers, arg_n_transfers, transfer_destroy_many); static int parse_pull_expression(const char *v) { const char *p = v; @@ -57,7 +79,7 @@ static int parse_pull_expression(const char *v) { ImportType type = _IMPORT_TYPE_INVALID; ImageClass class = _IMAGE_CLASS_INVALID; ImportVerify verify = IMPORT_VERIFY_SIGNATURE; - bool ro = false; + bool ro = false, blockdev = false; const char *o = options; for (;;) { @@ -75,6 +97,8 @@ static int parse_pull_expression(const char *v) { ro = true; else if (streq(opt, "rw")) ro = false; + else if (streq(opt, "blockdev")) + blockdev = true; else if ((suffix = startswith(opt, "verify="))) { ImportVerify w = import_verify_from_string(suffix); @@ -105,6 +129,35 @@ static int parse_pull_expression(const char *v) { if (class < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No image class (machine, portable, sysext, confext) specified in pull expression, refusing: %s", v); + if (!local) { + _cleanup_free_ char *c = NULL; + r = import_url_last_component(remote, &c); + if (r < 0) + return log_error_errno(r, "Failed to generate local name from URL '%s': %m", remote); + + switch (type) { + + case IMPORT_RAW: + r = raw_strip_suffixes(c, &local); + break; + + case IMPORT_TAR: + r = tar_strip_suffixes(c, &local); + break; + + default: + assert_not_reached(); + break; + } + if (r < 0) + return log_error_errno(r, "Failed to strip suffix from URL '%s': %m", remote); + + log_info("Saving downloaded file under local name '%s'.", local); + } + + if (blockdev && type != IMPORT_RAW) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option 'blockdev' only available for raw images, refusing: %s", v); + if (!GREEDY_REALLOC(arg_transfers, arg_n_transfers + 1)) return log_oom(); @@ -113,7 +166,7 @@ static int parse_pull_expression(const char *v) { r = sd_json_buildo( &j, SD_JSON_BUILD_PAIR("remote", SD_JSON_BUILD_STRING(remote)), - SD_JSON_BUILD_PAIR_CONDITION(!!local, "local", SD_JSON_BUILD_STRING(local)), + SD_JSON_BUILD_PAIR("local", SD_JSON_BUILD_STRING(local)), SD_JSON_BUILD_PAIR("class", JSON_BUILD_STRING_UNDERSCORIFY(image_class_to_string(class))), SD_JSON_BUILD_PAIR("type", JSON_BUILD_STRING_UNDERSCORIFY(import_type_to_string(type))), SD_JSON_BUILD_PAIR("readOnly", SD_JSON_BUILD_BOOLEAN(ro)), @@ -121,7 +174,15 @@ static int parse_pull_expression(const char *v) { if (r < 0) return log_error_errno(r, "Failed to build import JSON object: %m"); - arg_transfers[arg_n_transfers++] = TAKE_PTR(j); + arg_transfers[arg_n_transfers++] = (Transfer) { + .class = class, + .type = type, + .local = TAKE_PTR(local), + .remote = TAKE_PTR(remote), + .json = TAKE_PTR(j), + .blockdev = blockdev, + }; + return 0; } @@ -191,10 +252,10 @@ static int parse_credentials(void) { return 0; } -static int transfer_generate(sd_json_variant *v, size_t c) { +static int transfer_generate(const Transfer *t, size_t c) { int r; - assert(v); + assert(t); _cleanup_free_ char *service = NULL; if (asprintf(&service, "import%zu.service", c) < 0) @@ -205,19 +266,17 @@ static int transfer_generate(sd_json_variant *v, size_t c) { if (r < 0) return r; - const char *remote = sd_json_variant_string(sd_json_variant_by_key(v, "remote")); - fprintf(f, "[Unit]\n" "Description=Download of %s\n" "Documentation=man:systemd-import-generator(8)\n" "SourcePath=/proc/cmdline\n" "Requires=systemd-importd.socket\n" - "After=systemd-importd.socket\n" + "After=imports-pre.target systemd-importd.socket\n" "Conflicts=shutdown.target\n" - "Before=shutdown.target\n" + "Before=imports.target shutdown.target\n" "DefaultDependencies=no\n", - remote); + t->remote); if (arg_success_action) fprintf(f, "SuccessAction=%s\n", @@ -227,24 +286,39 @@ static int transfer_generate(sd_json_variant *v, size_t c) { fprintf(f, "FailureAction=%s\n", arg_failure_action); - const char *class = sd_json_variant_string(sd_json_variant_by_key(v, "class")); - if (streq_ptr(class, "sysext")) + if (t->class == IMAGE_SYSEXT) fputs("Before=systemd-sysext.service\n", f); - else if (streq_ptr(class, "confext")) + else if (t->class == IMAGE_CONFEXT) fputs("Before=systemd-confext.service\n", f); /* Assume network resource unless URL is file:// */ - if (!file_url_is_valid(remote)) + if (!file_url_is_valid(t->remote)) fputs("Wants=network-online.target\n" "After=network-online.target\n", f); + _cleanup_free_ char *local_path = NULL, *loop_service = NULL; + if (t->blockdev) { + assert(t->type == IMPORT_RAW); + + local_path = strjoin(image_root_to_string(t->class), "/", t->local, ".raw"); + if (!local_path) + return log_oom(); + + r = unit_name_from_path_instance("systemd-loop", local_path, ".service", &loop_service); + if (r < 0) + return log_error_errno(r, "Failed to build systemd-loop@.service instance name from path '%s': %m", local_path); + + /* Make sure download completes before the loopback service is activated */ + fprintf(f, "Before=%s\n", loop_service); + } + fputs("\n" "[Service]\n" "Type=oneshot\n" "NotifyAccess=main\n", f); _cleanup_free_ char *formatted = NULL; - r = sd_json_variant_format(v, /* flags= */ 0, &formatted); + r = sd_json_variant_format(t->json, /* flags= */ 0, &formatted); if (r < 0) return log_error_errno(r, "Failed to format import JSON data: %m"); @@ -259,7 +333,17 @@ static int transfer_generate(sd_json_variant *v, size_t c) { if (r < 0) return log_error_errno(r, "Failed to write unit %s: %m", service); - return generator_add_symlink(arg_dest, "multi-user.target", "wants", service); + r = generator_add_symlink(arg_dest, "imports.target", "wants", service); + if (r < 0) + return r; + + if (loop_service) { + r = generator_add_symlink(arg_dest, "imports.target", "wants", loop_service); + if (r < 0) + return r; + } + + return 0; } static int generate(void) { @@ -267,7 +351,7 @@ static int generate(void) { int r = 0; FOREACH_ARRAY(i, arg_transfers, arg_n_transfers) - RET_GATHER(r, transfer_generate(*i, c++)); + RET_GATHER(r, transfer_generate(i, c++)); return r; } diff --git a/units/imports-pre.target b/units/imports-pre.target new file mode 100644 index 0000000000..1bd3f0d778 --- /dev/null +++ b/units/imports-pre.target @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Image Downloads (Pre) +Documentation=man:systemd.special(7) +Before=imports.target +RefuseManualStart=yes diff --git a/units/imports.target b/units/imports.target new file mode 100644 index 0000000000..e07a8a3832 --- /dev/null +++ b/units/imports.target @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Image Downloads +Documentation=man:systemd.special(7) diff --git a/units/meson.build b/units/meson.build index 3ae3ef8311..edf09b7989 100644 --- a/units/meson.build +++ b/units/meson.build @@ -382,6 +382,15 @@ units = [ 'conditions' : ['ENABLE_IMPORTD'], 'symlinks' : ['sockets.target.wants/'], }, + { + 'file' : 'imports-pre.target', + 'conditions' : ['ENABLE_IMPORTD'], + }, + { + 'file' : 'imports.target', + 'conditions' : ['ENABLE_IMPORTD'], + 'symlinks' : ['sysinit.target.wants/'], + }, { 'file' : 'systemd-initctl.service.in', 'conditions' : ['HAVE_SYSV_COMPAT'],