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'],