diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml
index d5ece4acea..b84cc9c062 100644
--- a/man/systemd-vmspawn.xml
+++ b/man/systemd-vmspawn.xml
@@ -35,6 +35,12 @@
project='man-pages'>systemd-nspawn1, but it
launches a full virtual machine instead of using namespaces.
+ File descriptors for /dev/kvm and /dev/vhost-vsock can be
+ passed to systemd-vmspawn via systemd's native socket passing interface (see
+ sd_listen_fds3 for
+ details about the precise protocol used and the order in which the file descriptors are passed), these
+ fds must be passed with the names kvm and vhost-vsock respectively.
+
Note: on Ubuntu/Debian derivatives systemd-vmspawn requires the user to be in the kvm group to use the VSock options.
@@ -132,6 +138,42 @@
+
+ BOOL
+
+
+ Configure whether to use qemu with a virtual TPM or not.
+ If the option is not specified vmspawn will detect the presence of
+ swtpm8 and use it if available.
+ If yes is specified swtpm8
+ is always used, and vice versa if no is set swtpm
+ 8 is never used.
+ Note: the virtual TPM used may change in future.
+
+
+
+
+
+ PATH
+
+
+ Set the linux kernel image to use for direct kernel boot.
+ If no kernel was installed into the image then the image will fail to boot.
+
+
+
+
+
+ PATH
+
+
+ Set the initrd to use for direct kernel boot.
+ If the linux kernel supplied is a UKI then this argument is not required.
+ If no initrd was installed into the image then the image will fail to boot.
+
+
+
+
@@ -141,12 +183,26 @@
- BOOL
+
+
- Configure whether to search for firmware which supports Secure Boot.
- If the option is not specified the first firmware which is detected will be used.
- If the option is set to yes then the first firmware with Secure Boot support will be selected.
- If no is specified then the first firmware without Secure Boot will be selected.
+
+ Create a TAP device to network with the virtual machine.
+
+ Note: root privileges are required to use TAP networking.
+ Additionally requires a correctly setup
+ systemd-networkd8
+ to be running on the host to ensure the host interface is correctly configured.
+ The relevant .network file can be found at /usr/lib/systemd/network/80-vm-vt.network.
+
+
+
+
+
+
+
+
+ Use user mode networking with QEMU.
@@ -161,9 +217,21 @@
-
-
+
+ BOOL
+
+ Configure whether to search for firmware which supports Secure Boot.
+ If the option is not specified the first firmware which is detected will be used.
+ If the option is set to yes then the first firmware with Secure Boot support will be selected.
+ If no is specified then the first firmware without Secure Boot will be selected.
+
+
+
+
+
+
+
System Identity Options
diff --git a/src/basic/path-lookup.c b/src/basic/path-lookup.c
index 068054512b..1b31a4e23a 100644
--- a/src/basic/path-lookup.c
+++ b/src/basic/path-lookup.c
@@ -91,6 +91,44 @@ int xdg_user_data_dir(char **ret, const char *suffix) {
return 1;
}
+int runtime_directory(char **ret, RuntimeScope scope, const char *suffix) {
+ _cleanup_free_ char *d = NULL;
+ int r;
+
+ assert(ret);
+ assert(suffix);
+ assert(IN_SET(scope, RUNTIME_SCOPE_SYSTEM, RUNTIME_SCOPE_USER, RUNTIME_SCOPE_GLOBAL));
+
+ /* Accept $RUNTIME_DIRECTORY as authoritative
+ * If its missing apply the suffix to /run or $XDG_RUNTIME_DIR
+ * if we are in a user runtime scope.
+ *
+ * Return value indicates whether the suffix was applied or not */
+
+ const char *e = secure_getenv("RUNTIME_DIRECTORY");
+ if (e) {
+ d = strdup(e);
+ if (!d)
+ return -ENOMEM;
+
+ *ret = TAKE_PTR(d);
+ return false;
+ }
+
+ if (scope == RUNTIME_SCOPE_USER) {
+ r = xdg_user_runtime_dir(&d, suffix);
+ if (r < 0)
+ return r;
+ } else {
+ d = path_join("/run", suffix);
+ if (!d)
+ return -ENOMEM;
+ }
+
+ *ret = TAKE_PTR(d);
+ return true;
+}
+
static const char* const user_data_unit_paths[] = {
"/usr/local/lib/systemd/user",
"/usr/local/share/systemd/user",
diff --git a/src/basic/path-lookup.h b/src/basic/path-lookup.h
index 1601787064..11db264c01 100644
--- a/src/basic/path-lookup.h
+++ b/src/basic/path-lookup.h
@@ -59,6 +59,7 @@ int xdg_user_dirs(char ***ret_config_dirs, char ***ret_data_dirs);
int xdg_user_runtime_dir(char **ret, const char *suffix);
int xdg_user_config_dir(char **ret, const char *suffix);
int xdg_user_data_dir(char **ret, const char *suffix);
+int runtime_directory(char **ret, RuntimeScope scope, const char *suffix);
bool path_is_user_data_dir(const char *path);
bool path_is_user_config_dir(const char *path);
diff --git a/src/vmspawn/meson.build b/src/vmspawn/meson.build
index 800d7c362c..5f54364fac 100644
--- a/src/vmspawn/meson.build
+++ b/src/vmspawn/meson.build
@@ -3,6 +3,7 @@
libvmspawn_core_sources = files(
'vmspawn-settings.c',
'vmspawn-util.c',
+ 'vmspawn-scope.c',
)
libvmspawn_core = static_library(
'vmspawn-core',
@@ -23,5 +24,6 @@ executables += [
'conditions': ['ENABLE_VMSPAWN'],
'sources' : files('vmspawn.c'),
'link_with' : vmspawn_libs,
+ 'dependencies' : [libblkid]
}
]
diff --git a/src/vmspawn/vmspawn-scope.c b/src/vmspawn/vmspawn-scope.c
new file mode 100644
index 0000000000..676dee0592
--- /dev/null
+++ b/src/vmspawn/vmspawn-scope.c
@@ -0,0 +1,302 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+
+#include "sd-bus.h"
+
+#include "bus-error.h"
+#include "bus-locator.h"
+#include "bus-unit-util.h"
+#include "bus-util.h"
+#include "bus-wait-for-jobs.h"
+#include "escape.h"
+#include "macro.h"
+#include "process-util.h"
+#include "random-util.h"
+#include "socket-util.h"
+#include "strv.h"
+#include "unit-def.h"
+#include "unit-name.h"
+#include "vmspawn-scope.h"
+
+int start_transient_scope(sd_bus *bus, const char *machine_name, bool allow_pidfd, char **ret_scope) {
+ _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL;
+ _cleanup_free_ char *scope = NULL, *description = NULL;
+ const char *object;
+ int r;
+
+ assert(bus);
+ assert(machine_name);
+
+ /* Creates a transient scope unit which tracks the lifetime of the current process */
+
+ r = bus_wait_for_jobs_new(bus, &w);
+ if (r < 0)
+ return log_error_errno(r, "Could not watch job: %m");
+
+ if (asprintf(&scope, "machine-%"PRIu64"-%s.scope", random_u64(), machine_name) < 0)
+ return log_oom();
+
+ description = strjoin("Virtual Machine ", machine_name);
+ if (!description)
+ return log_oom();
+
+ r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "ss", /* name */ scope, /* mode */ "fail");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ /* Properties */
+ r = sd_bus_message_open_container(m, 'a', "(sv)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "(sa)(sa)(sa)",
+ "Description", "s", description,
+ "AddRef", "b", 1,
+ "CollectMode", "s", "inactive-or-failed");
+
+ if (allow_pidfd) {
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+ r = pidref_set_pid(&pidref, getpid_cached());
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate PID reference: %m");
+
+ r = bus_append_scope_pidref(m, &pidref);
+ } else
+ r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, getpid_cached());
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ /* No auxiliary units */
+ r = sd_bus_message_append(
+ m,
+ "a(sa(sv))",
+ 0);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_call(bus, m, 0, &error, &reply);
+ if (r < 0) {
+ /* If this failed with a property we couldn't write, this is quite likely because the server
+ * doesn't support PIDFDs yet, let's try without. */
+ if (allow_pidfd &&
+ sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY, SD_BUS_ERROR_PROPERTY_READ_ONLY))
+ return start_transient_scope(bus, machine_name, false, ret_scope);
+
+ return log_error_errno(r, "Failed to start transient scope unit: %s", bus_error_message(&error, r));
+ }
+
+ r = sd_bus_message_read(reply, "o", &object);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ r = bus_wait_for_jobs_one(w, object, /* quiet */ false, NULL);
+ if (r < 0)
+ return r;
+
+ if (ret_scope)
+ *ret_scope = TAKE_PTR(scope);
+
+ return 0;
+}
+
+static int message_add_commands(sd_bus_message *m, const char *exec_type, char ***commands, size_t n_commands) {
+ int r;
+
+ assert(m);
+ assert(exec_type);
+ assert(commands || n_commands == 0);
+
+ /* A small helper for adding an ExecStart / ExecStopPost / etc.. property to an sd_bus_message */
+
+ r = sd_bus_message_open_container(m, 'r', "sv");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "s", exec_type);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'v', "a(sasb)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'a', "(sasb)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ FOREACH_ARRAY(cmd, commands, n_commands) {
+ char **cmdline = *cmd;
+
+ r = sd_bus_message_open_container(m, 'r', "sasb");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "s", cmdline[0]);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append_strv(m, cmdline);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "b", 0);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ return 0;
+}
+
+void socket_service_pair_done(SocketServicePair *p) {
+ p->exec_start = strv_free(p->exec_start);
+ p->exec_stop_post = strv_free(p->exec_stop_post);
+ p->unit_name_prefix = mfree(p->unit_name_prefix);
+ p->runtime_directory = mfree(p->runtime_directory);
+ p->listen_address = mfree(p->listen_address);
+ p->socket_type = 0;
+}
+
+int start_socket_service_pair(sd_bus *bus, const char *scope, SocketServicePair *p) {
+ _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
+ _cleanup_free_ char *service_desc = NULL, *service_name = NULL, *socket_name = NULL;
+ const char *object, *socket_type_str;
+ int r;
+
+ /* Starts a socket/service unit pair bound to the given scope. */
+
+ assert(bus);
+ assert(scope);
+ assert(p);
+ assert(p->unit_name_prefix);
+ assert(p->exec_start);
+ assert(p->listen_address);
+
+ r = bus_wait_for_jobs_new(bus, &w);
+ if (r < 0)
+ return log_error_errno(r, "Could not watch job: %m");
+
+ socket_name = strjoin(p->unit_name_prefix, ".socket");
+ if (!socket_name)
+ return log_oom();
+
+ service_name = strjoin(p->unit_name_prefix, ".service");
+ if (!service_name)
+ return log_oom();
+
+ service_desc = quote_command_line(p->exec_start, SHELL_ESCAPE_EMPTY);
+ if (!service_desc)
+ return log_oom();
+
+ socket_type_str = socket_address_type_to_string(p->socket_type);
+ if (!socket_type_str)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Invalid socket type: %d", p->socket_type);
+
+ r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "StartTransientUnit");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "ssa(sv)",
+ /* ss - name, mode */
+ socket_name, "fail",
+ /* a(sv) - Properties */
+ 5,
+ "Description", "s", p->listen_address,
+ "AddRef", "b", 1,
+ "BindsTo", "as", 1, scope,
+ "Listen", "a(ss)", 1, socket_type_str, p->listen_address,
+ "CollectMode", "s", "inactive-or-failed");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ /* aux */
+ r = sd_bus_message_open_container(m, 'a', "(sa(sv))");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'r', "sa(sv)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "s", service_name);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_open_container(m, 'a', "(sv)");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_append(m, "(sv)(sv)(sv)(sv)",
+ "Description", "s", service_desc,
+ "AddRef", "b", 1,
+ "BindsTo", "as", 1, scope,
+ "CollectMode", "s", "inactive-or-failed");
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ if (p->runtime_directory) {
+ r = sd_bus_message_append(m, "(sv)", "RuntimeDirectory", "as", 1, p->runtime_directory);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
+ r = message_add_commands(m, "ExecStart", &p->exec_start, 1);
+ if (r < 0)
+ return r;
+
+ if (p->exec_stop_post) {
+ r = message_add_commands(m, "ExecStopPost", &p->exec_stop_post, 1);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_message_close_container(m);
+ if (r < 0)
+ return bus_log_create_error(r);
+
+ r = sd_bus_call(bus, m, 0, &error, &reply);
+ if (r < 0)
+ return log_error_errno(r, "Failed to start %s as transient unit: %s", p->exec_start[0], bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply, "o", &object);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ return bus_wait_for_jobs_one(w, object, /* quiet */ false, NULL);
+}
diff --git a/src/vmspawn/vmspawn-scope.h b/src/vmspawn/vmspawn-scope.h
new file mode 100644
index 0000000000..b807c3b203
--- /dev/null
+++ b/src/vmspawn/vmspawn-scope.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include
+
+#include "sd-bus.h"
+
+#include "macro.h"
+
+typedef struct SocketServicePair {
+ char **exec_start;
+ char **exec_stop_post;
+ char *unit_name_prefix;
+ char *runtime_directory;
+ char *listen_address;
+ int socket_type;
+} SocketServicePair;
+
+void socket_service_pair_done(SocketServicePair *p);
+
+int start_transient_scope(sd_bus *bus, const char *machine_name, bool allow_pidfd, char **ret_scope);
+int start_socket_service_pair(sd_bus *bus, const char *scope, SocketServicePair *p);
diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c
index 79e1e5399a..42c7bbfac6 100644
--- a/src/vmspawn/vmspawn-util.c
+++ b/src/vmspawn/vmspawn-util.c
@@ -20,6 +20,7 @@
#include "siphash24.h"
#include "socket-util.h"
#include "sort-util.h"
+#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "vmspawn-util.h"
@@ -35,6 +36,8 @@ OvmfConfig* ovmf_config_free(OvmfConfig *config) {
return mfree(config);
}
+DEFINE_STRING_TABLE_LOOKUP(qemu_network_stack, QemuNetworkStack);
+
int qemu_check_kvm_support(void) {
if (access("/dev/kvm", F_OK) >= 0)
return true;
@@ -364,7 +367,7 @@ int find_qemu_binary(char **ret_qemu_binary) {
return find_executable(qemu_arch_specific, ret_qemu_binary);
}
-int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_child_sock) {
+int vsock_fix_child_cid(int vhost_device_fd, unsigned *machine_cid, const char *machine) {
/* this is an arbitrary value picked from /dev/urandom */
static const uint8_t sip_key[HASH_KEY_SIZE] = {
0x03, 0xad, 0xf0, 0xa4,
@@ -373,14 +376,13 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi
0xf5, 0x4c, 0x80, 0x52
};
struct siphash machine_hash_state, state;
- _cleanup_close_ int vfd = -EBADF;
int r;
/* uint64_t is required here for the ioctl call, but valid CIDs are only 32 bits */
uint64_t cid = *ASSERT_PTR(machine_cid);
assert(machine);
- assert(ret_child_sock);
+ assert(vhost_device_fd >= 0);
/* Fix the CID of the AF_VSOCK socket passed to qemu
*
@@ -393,16 +395,10 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi
* If after another 64 attempts this hasn't worked then give up and return EADDRNOTAVAIL.
*/
- /* remove O_CLOEXEC before this fd is passed to QEMU */
- vfd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC);
- if (vfd < 0)
- return log_debug_errno(errno, "Failed to open /dev/vhost-vsock as read/write: %m");
-
if (cid != VMADDR_CID_ANY) {
- r = ioctl(vfd, VHOST_VSOCK_SET_GUEST_CID, &cid);
+ r = ioctl(vhost_device_fd, VHOST_VSOCK_SET_GUEST_CID, &cid);
if (r < 0)
return log_debug_errno(errno, "Failed to set CID for child vsock with user provided CID %" PRIu64 ": %m", cid);
- *ret_child_sock = TAKE_FD(vfd);
return 0;
}
@@ -414,10 +410,9 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi
uint64_t hash = siphash24_finalize(&state);
cid = 3 + (hash % (UINT_MAX - 4));
- r = ioctl(vfd, VHOST_VSOCK_SET_GUEST_CID, &cid);
+ r = ioctl(vhost_device_fd, VHOST_VSOCK_SET_GUEST_CID, &cid);
if (r >= 0) {
*machine_cid = cid;
- *ret_child_sock = TAKE_FD(vfd);
return 0;
}
if (errno != EADDRINUSE)
@@ -426,10 +421,9 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi
for (unsigned i = 0; i < 64; i++) {
cid = 3 + random_u64_range(UINT_MAX - 4);
- r = ioctl(vfd, VHOST_VSOCK_SET_GUEST_CID, &cid);
+ r = ioctl(vhost_device_fd, VHOST_VSOCK_SET_GUEST_CID, &cid);
if (r >= 0) {
*machine_cid = cid;
- *ret_child_sock = TAKE_FD(vfd);
return 0;
}
diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h
index f2d1b621e2..e1ce780671 100644
--- a/src/vmspawn/vmspawn-util.h
+++ b/src/vmspawn/vmspawn-util.h
@@ -10,6 +10,12 @@
#define ARCHITECTURE_SUPPORTS_SMBIOS 0
#endif
+#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__)
+#define ARCHITECTURE_SUPPORTS_TPM 1
+#else
+#define ARCHITECTURE_SUPPORTS_TPM 0
+#endif
+
#if defined(__arm__) || defined(__aarch64__)
#define DEFAULT_SERIAL_TTY "ttyAMA0"
#elif defined(__s390__) || defined(__s390x__)
@@ -39,10 +45,27 @@ static inline const char *ovmf_config_vars_format(const OvmfConfig *c) {
OvmfConfig* ovmf_config_free(OvmfConfig *ovmf_config);
DEFINE_TRIVIAL_CLEANUP_FUNC(OvmfConfig*, ovmf_config_free);
+typedef enum QemuNetworkStack {
+ QEMU_NET_TAP,
+ QEMU_NET_USER,
+ QEMU_NET_NONE,
+ _QEMU_NET_MAX,
+ _QEMU_NET_INVALID = -EINVAL,
+} QemuNetworkStack;
+
+static const char* const qemu_network_stack_table[_QEMU_NET_MAX] = {
+ [QEMU_NET_TAP] = "tap",
+ [QEMU_NET_USER] = "user",
+ [QEMU_NET_NONE] = "none",
+};
+
+const char* qemu_network_stack_to_string(QemuNetworkStack type) _const_;
+QemuNetworkStack qemu_network_stack_from_string(const char *s) _pure_;
+
int qemu_check_kvm_support(void);
int qemu_check_vsock_support(void);
int list_ovmf_config(char ***ret);
int load_ovmf_config(const char *path, OvmfConfig **ret);
int find_ovmf_config(int search_sb, OvmfConfig **ret);
int find_qemu_binary(char **ret_qemu_binary);
-int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_child_sock);
+int vsock_fix_child_cid(int vsock_fd, unsigned *machine_cid, const char *machine);
diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c
index cc21efbf44..d5ab2a1a27 100644
--- a/src/vmspawn/vmspawn.c
+++ b/src/vmspawn/vmspawn.c
@@ -3,36 +3,49 @@
#include
#include
#include
-#include
+#include
#include
+#include "sd-daemon.h"
+#include "sd-event.h"
+#include "sd-id128.h"
+
#include "alloc-util.h"
#include "architecture.h"
#include "build.h"
#include "common-signal.h"
#include "copy.h"
#include "creds-util.h"
+#include "dissect-image.h"
#include "escape.h"
#include "event-util.h"
#include "fileio.h"
#include "format-util.h"
#include "fs-util.h"
+#include "gpt.h"
#include "hexdecoct.h"
#include "hostname-util.h"
+#include "kernel-image.h"
#include "log.h"
#include "machine-credential.h"
+#include "macro.h"
#include "main-func.h"
+#include "mkdir.h"
#include "pager.h"
#include "parse-argument.h"
#include "parse-util.h"
+#include "path-lookup.h"
#include "path-util.h"
#include "pretty-print.h"
#include "process-util.h"
-#include "sd-event.h"
+#include "rm-rf.h"
#include "signal-util.h"
#include "socket-util.h"
+#include "string-util.h"
#include "strv.h"
#include "tmpfile-util.h"
+#include "unit-name.h"
+#include "vmspawn-scope.h"
#include "vmspawn-settings.h"
#include "vmspawn-util.h"
@@ -45,19 +58,29 @@ static uint64_t arg_qemu_mem = UINT64_C(2) * U64_GB;
static int arg_qemu_kvm = -1;
static int arg_qemu_vsock = -1;
static unsigned arg_vsock_cid = VMADDR_CID_ANY;
+static int arg_tpm = -1;
+static char *arg_linux = NULL;
+static char *arg_initrd = NULL;
static bool arg_qemu_gui = false;
+static QemuNetworkStack arg_network_stack = QEMU_NET_NONE;
static int arg_secure_boot = -1;
static MachineCredentialContext arg_credentials = {};
static SettingsMask arg_settings_mask = 0;
-static char **arg_parameters = NULL;
static char *arg_firmware = NULL;
+static char *arg_runtime_directory = NULL;
+static bool arg_runtime_directory_created = false;
+static bool arg_privileged = false;
+static char **arg_kernel_cmdline_extra = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_machine, freep);
STATIC_DESTRUCTOR_REGISTER(arg_qemu_smp, freep);
-STATIC_DESTRUCTOR_REGISTER(arg_parameters, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_runtime_directory, freep);
STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done);
STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_linux, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_initrd, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep);
static int help(void) {
_cleanup_free_ char *link = NULL;
@@ -84,7 +107,12 @@ static int help(void) {
" --qemu-kvm=BOOL Configure whether to use KVM or not\n"
" --qemu-vsock=BOOL Configure whether to use qemu with a vsock or not\n"
" --vsock-cid= Specify the CID to use for the qemu guest's vsock\n"
+ " --tpm=BOOL Configure whether to use a virtual TPM or not\n"
+ " --linux=PATH Specify the linux kernel for direct kernel boot\n"
+ " --initrd=PATH Specify the initrd for direct kernel boot\n"
" --qemu-gui Start QEMU in graphical mode\n"
+ " -n --network-tap Create a TAP device for networking with QEMU.\n"
+ " --network-user-mode Use user mode networking with QEMU.\n"
" --secure-boot=BOOL Configure whether to search for firmware which\n"
" supports Secure Boot\n"
" --firmware=PATH|list Select firmware definition file (or list available)\n"
@@ -117,7 +145,11 @@ static int parse_argv(int argc, char *argv[]) {
ARG_QEMU_KVM,
ARG_QEMU_VSOCK,
ARG_VSOCK_CID,
+ ARG_TPM,
+ ARG_LINUX,
+ ARG_INITRD,
ARG_QEMU_GUI,
+ ARG_NETWORK_USER_MODE,
ARG_SECURE_BOOT,
ARG_SET_CREDENTIAL,
ARG_LOAD_CREDENTIAL,
@@ -125,22 +157,27 @@ static int parse_argv(int argc, char *argv[]) {
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "quiet", no_argument, NULL, 'q' },
- { "no-pager", no_argument, NULL, ARG_NO_PAGER },
- { "image", required_argument, NULL, 'i' },
- { "machine", required_argument, NULL, 'M' },
- { "qemu-smp", required_argument, NULL, ARG_QEMU_SMP },
- { "qemu-mem", required_argument, NULL, ARG_QEMU_MEM },
- { "qemu-kvm", required_argument, NULL, ARG_QEMU_KVM },
- { "qemu-vsock", required_argument, NULL, ARG_QEMU_VSOCK },
- { "vsock-cid", required_argument, NULL, ARG_VSOCK_CID },
- { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI },
- { "secure-boot", required_argument, NULL, ARG_SECURE_BOOT },
- { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL },
- { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL },
- { "firmware", required_argument, NULL, ARG_FIRMWARE },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "quiet", no_argument, NULL, 'q' },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "image", required_argument, NULL, 'i' },
+ { "machine", required_argument, NULL, 'M' },
+ { "qemu-smp", required_argument, NULL, ARG_QEMU_SMP },
+ { "qemu-mem", required_argument, NULL, ARG_QEMU_MEM },
+ { "qemu-kvm", required_argument, NULL, ARG_QEMU_KVM },
+ { "qemu-vsock", required_argument, NULL, ARG_QEMU_VSOCK },
+ { "vsock-cid", required_argument, NULL, ARG_VSOCK_CID },
+ { "tpm", required_argument, NULL, ARG_TPM },
+ { "linux", required_argument, NULL, ARG_LINUX },
+ { "initrd", required_argument, NULL, ARG_INITRD },
+ { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI },
+ { "network-tap", no_argument, NULL, 'n' },
+ { "network-user-mode", no_argument, NULL, ARG_NETWORK_USER_MODE },
+ { "secure-boot", required_argument, NULL, ARG_SECURE_BOOT },
+ { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL },
+ { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL },
+ { "firmware", required_argument, NULL, ARG_FIRMWARE },
{}
};
@@ -150,7 +187,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argv);
optind = 0;
- while ((c = getopt_long(argc, argv, "+hi:M:q", options, NULL)) >= 0)
+ while ((c = getopt_long(argc, argv, "+hi:M:nq", options, NULL)) >= 0)
switch (c) {
case 'h':
return help();
@@ -228,10 +265,37 @@ static int parse_argv(int argc, char *argv[]) {
}
break;
+ case ARG_TPM:
+ r = parse_tristate(optarg, &arg_tpm);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --tpm=%s: %m", optarg);
+ break;
+
+ case ARG_LINUX:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_linux);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_INITRD: {
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_initrd);
+ if (r < 0)
+ return r;
+ break;
+ }
+
case ARG_QEMU_GUI:
arg_qemu_gui = true;
break;
+ case 'n':
+ arg_network_stack = QEMU_NET_TAP;
+ break;
+
+ case ARG_NETWORK_USER_MODE:
+ arg_network_stack = QEMU_NET_USER;
+ break;
+
case ARG_SECURE_BOOT:
r = parse_tristate(optarg, &arg_secure_boot);
if (r < 0)
@@ -288,9 +352,8 @@ static int parse_argv(int argc, char *argv[]) {
}
if (argc > optind) {
- strv_free(arg_parameters);
- arg_parameters = strv_copy(argv + optind);
- if (!arg_parameters)
+ arg_kernel_cmdline_extra = strv_copy(argv + optind);
+ if (!arg_kernel_cmdline_extra)
return log_oom();
arg_settings_mask |= SETTING_START_MODE;
@@ -482,12 +545,137 @@ static int cmdline_add_vsock(char ***cmdline, int vsock_fd) {
return 0;
}
-static int run_virtual_machine(void) {
- _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL;
- _cleanup_strv_free_ char **cmdline = NULL;
- _cleanup_free_ char *machine = NULL, *qemu_binary = NULL, *mem = NULL;
+static int start_tpm(sd_bus *bus, const char *scope, const char *tpm, const char **ret_state_tempdir) {
+ _cleanup_(rm_rf_physical_and_freep) char *state_dir = NULL;
+ _cleanup_free_ char *scope_prefix = NULL;
+ _cleanup_(socket_service_pair_done) SocketServicePair ssp = {
+ .socket_type = SOCK_STREAM,
+ };
int r;
- _cleanup_close_ int vsock_fd = -EBADF;
+
+ assert(bus);
+ assert(scope);
+ assert(tpm);
+ assert(ret_state_tempdir);
+
+ r = unit_name_to_prefix(scope, &scope_prefix);
+ if (r < 0)
+ return log_error_errno(r, "Failed to strip .scope suffix from scope: %m");
+
+ ssp.unit_name_prefix = strjoin(scope_prefix, "-tpm");
+ if (!ssp.unit_name_prefix)
+ return log_oom();
+
+ state_dir = path_join(arg_runtime_directory, ssp.unit_name_prefix);
+ if (!state_dir)
+ return log_oom();
+
+ if (arg_runtime_directory_created) {
+ ssp.runtime_directory = path_join("systemd/vmspawn", ssp.unit_name_prefix);
+ if (!ssp.runtime_directory)
+ return log_oom();
+ }
+
+ ssp.listen_address = path_join(state_dir, "sock");
+ if (!ssp.listen_address)
+ return log_oom();
+
+ ssp.exec_start = strv_new(tpm, "socket", "--tpm2", "--tpmstate");
+ if (!ssp.exec_start)
+ return log_oom();
+
+ r = strv_extendf(&ssp.exec_start, "dir=%s", state_dir);
+ if (r < 0)
+ return log_oom();
+
+ r = strv_extend_many(&ssp.exec_start, "--ctrl", "type=unixio,fd=3");
+ if (r < 0)
+ return log_oom();
+
+ r = start_socket_service_pair(bus, scope, &ssp);
+ if (r < 0)
+ return r;
+
+ *ret_state_tempdir = TAKE_PTR(state_dir);
+
+ return 0;
+}
+
+static int discover_root(char **ret) {
+ int r;
+ _cleanup_(dissected_image_unrefp) DissectedImage *image = NULL;
+ _cleanup_free_ char *root = NULL;
+
+ assert(ret);
+
+ r = dissect_image_file_and_warn(
+ arg_image,
+ /* verity= */ NULL,
+ /* mount_options= */ NULL,
+ /* image_policy= */ NULL,
+ /* flags= */ 0,
+ &image);
+ if (r < 0)
+ return r;
+
+ if (image->partitions[PARTITION_ROOT].found)
+ root = strjoin("root=PARTUUID=", SD_ID128_TO_UUID_STRING(image->partitions[PARTITION_ROOT].uuid));
+ else if (image->partitions[PARTITION_USR].found)
+ root = strjoin("mount.usr=PARTUUID=", SD_ID128_TO_UUID_STRING(image->partitions[PARTITION_USR].uuid));
+ else
+ return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Cannot perform a direct kernel boot without a root or usr partition, refusing");
+
+ if (!root)
+ return log_oom();
+
+ *ret = TAKE_PTR(root);
+
+ return 0;
+}
+
+static int kernel_cmdline_maybe_append_root(void) {
+ int r;
+ bool cmdline_contains_root = strv_find_startswith(arg_kernel_cmdline_extra, "root=")
+ || strv_find_startswith(arg_kernel_cmdline_extra, "mount.usr=");
+
+ if (!cmdline_contains_root) {
+ _cleanup_free_ char *root = NULL;
+
+ r = discover_root(&root);
+ if (r < 0)
+ return r;
+
+ log_debug("Determined root file system %s from dissected image", root);
+
+ r = strv_consume(&arg_kernel_cmdline_extra, TAKE_PTR(root));
+ if (r < 0)
+ return log_oom();
+ }
+
+ return 0;
+}
+
+static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
+ _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ _cleanup_free_ char *qemu_binary = NULL, *mem = NULL, *trans_scope = NULL;
+ _cleanup_close_ int notify_sock_fd = -EBADF;
+ _cleanup_strv_free_ char **cmdline = NULL;
+ _cleanup_free_ int *pass_fds = NULL;
+ size_t n_pass_fds = 0;
+ const char *machine, *accel;
+ int r;
+
+ if (arg_privileged)
+ r = sd_bus_default_system(&bus);
+ else
+ r = sd_bus_default_user(&bus);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to systemd bus: %m");
+
+ r = start_transient_scope(bus, arg_machine, /* allow_pidfd= */ true, &trans_scope);
+ if (r < 0)
+ return r;
bool use_kvm = arg_qemu_kvm > 0;
if (arg_qemu_kvm < 0) {
@@ -509,20 +697,17 @@ static int run_virtual_machine(void) {
log_warning("Couldn't find OVMF firmware blob with Secure Boot support, "
"falling back to OVMF firmware blobs without Secure Boot support.");
- const char *accel = use_kvm ? "kvm" : "tcg";
- if (IN_SET(native_architecture(), ARCHITECTURE_ARM64, ARCHITECTURE_ARM64_BE))
- machine = strjoin("type=virt,accel=", accel);
- else
- machine = strjoin("type=q35,accel=", accel, ",smm=", on_off(ovmf_config->supports_sb));
- if (!machine)
- return log_oom();
-
r = find_qemu_binary(&qemu_binary);
if (r == -EOPNOTSUPP)
return log_error_errno(r, "Native architecture is not supported by qemu.");
if (r < 0)
return log_error_errno(r, "Failed to find QEMU binary: %m");
+ if (IN_SET(native_architecture(), ARCHITECTURE_ARM64, ARCHITECTURE_ARM64_BE))
+ machine = "type=virt";
+ else
+ machine = ovmf_config->supports_sb ? "type=q35,smm=on" : "type=q35,smm=off";
+
if (asprintf(&mem, "%" PRIu64, DIV_ROUND_UP(arg_qemu_mem, U64_MB)) < 0)
return log_oom();
@@ -532,12 +717,65 @@ static int run_virtual_machine(void) {
"-smp", arg_qemu_smp ?: "1",
"-m", mem,
"-object", "rng-random,filename=/dev/urandom,id=rng0",
- "-device", "virtio-rng-pci,rng=rng0,id=rng-device0",
- "-nic", "user,model=virtio-net-pci"
+ "-device", "virtio-rng-pci,rng=rng0,id=rng-device0"
);
if (!cmdline)
return log_oom();
+ /* if we are going to be starting any units with state then create our runtime dir */
+ if (arg_tpm != 0) {
+ r = runtime_directory(&arg_runtime_directory, arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, "systemd/vmspawn");
+ if (r < 0)
+ return log_error_errno(r, "Failed to lookup runtime directory: %m");
+ if (r) {
+ /* r > 0 means we need to create our own runtime dir */
+ r = mkdir_p(arg_runtime_directory, 0755);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create runtime directory: %m");
+ arg_runtime_directory_created = true;
+ }
+ }
+
+ if (arg_network_stack == QEMU_NET_TAP)
+ r = strv_extend_many(&cmdline, "-nic", "tap,script=no,model=virtio-net-pci");
+ else if (arg_network_stack == QEMU_NET_USER)
+ r = strv_extend_many(&cmdline, "-nic", "user,model=virtio-net-pci");
+ else
+ r = strv_extend_many(&cmdline, "-nic", "none");
+ if (r < 0)
+ return log_oom();
+
+ if (!use_kvm && kvm_device_fd >= 0) {
+ log_warning("KVM is disabled but fd for /dev/kvm was passed, closing fd and ignoring");
+ kvm_device_fd = safe_close(kvm_device_fd);
+ }
+
+ if (use_kvm && kvm_device_fd >= 0) {
+ /* /dev/fdset/1 is magic string to tell qemu where to find the fd for /dev/kvm
+ * we use this so that we can take a fd to /dev/kvm and then give qemu that fd */
+ accel = "kvm,device=/dev/fdset/1";
+
+ r = strv_extend(&cmdline, "--add-fd");
+ if (r < 0)
+ return log_oom();
+
+ r = strv_extendf(&cmdline, "fd=%d,set=1,opaque=/dev/kvm", kvm_device_fd);
+ if (r < 0)
+ return log_oom();
+
+ if (!GREEDY_REALLOC(pass_fds, n_pass_fds + 1))
+ return log_oom();
+
+ pass_fds[n_pass_fds++] = kvm_device_fd;
+ } else if (use_kvm)
+ accel = "kvm";
+ else
+ accel = "tcg";
+
+ r = strv_extend_many(&cmdline, "-accel", accel);
+ if (r < 0)
+ return log_oom();
+
bool use_vsock = arg_qemu_vsock > 0 && ARCHITECTURE_SUPPORTS_SMBIOS;
if (arg_qemu_vsock < 0) {
r = qemu_check_vsock_support();
@@ -547,12 +785,20 @@ static int run_virtual_machine(void) {
use_vsock = r;
}
- unsigned child_cid = VMADDR_CID_ANY;
_cleanup_close_ int child_vsock_fd = -EBADF;
if (use_vsock) {
- child_cid = arg_vsock_cid;
+ int device_fd = vhost_device_fd;
+ unsigned child_cid = arg_vsock_cid;
- r = vsock_fix_child_cid(&child_cid, arg_machine, &child_vsock_fd);
+ if (device_fd < 0) {
+ child_vsock_fd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC);
+ if (child_vsock_fd < 0)
+ return log_error_errno(errno, "Failed to open /dev/vhost-vsock as read/write: %m");
+
+ device_fd = child_vsock_fd;
+ }
+
+ r = vsock_fix_child_cid(device_fd, &child_cid, arg_machine);
if (r < 0)
return log_error_errno(r, "Failed to fix CID for the guest vsock socket: %m");
@@ -560,10 +806,14 @@ static int run_virtual_machine(void) {
if (r < 0)
return log_oom();
- log_debug("vhost-vsock-pci,guest-cid=%u,vhostfd=%d", child_cid, child_vsock_fd);
- r = strv_extendf(&cmdline, "vhost-vsock-pci,guest-cid=%u,vhostfd=%d", child_cid, child_vsock_fd);
+ r = strv_extendf(&cmdline, "vhost-vsock-pci,guest-cid=%u,vhostfd=%d", child_cid, device_fd);
if (r < 0)
return log_oom();
+
+ if (!GREEDY_REALLOC(pass_fds, n_pass_fds + 1))
+ return log_oom();
+
+ pass_fds[n_pass_fds++] = device_fd;
}
r = strv_extend_many(&cmdline, "-cpu", "max");
@@ -651,6 +901,20 @@ static int run_virtual_machine(void) {
return log_oom();
}
+ if (arg_linux) {
+ r = strv_extend_many(&cmdline, "-kernel", arg_linux);
+ if (r < 0)
+ return log_oom();
+
+ /* We can't rely on gpt-auto-generator when direct kernel booting so synthesize a root=
+ * kernel argument instead. */
+ if (arg_image) {
+ r = kernel_cmdline_maybe_append_root();
+ if (r < 0)
+ return r;
+ }
+ }
+
r = strv_extend(&cmdline, "-drive");
if (r < 0)
return log_oom();
@@ -666,31 +930,98 @@ static int run_virtual_machine(void) {
if (r < 0)
return log_oom();
- r = strv_prepend(&arg_parameters, "console=" DEFAULT_SERIAL_TTY);
+ r = strv_prepend(&arg_kernel_cmdline_extra, "console=" DEFAULT_SERIAL_TTY);
if (r < 0)
return log_oom();
if (ARCHITECTURE_SUPPORTS_SMBIOS) {
- _cleanup_free_ char *kcl = strv_join(arg_parameters, " ");
+ _cleanup_free_ char *kcl = strv_join(arg_kernel_cmdline_extra, " ");
if (!kcl)
return log_oom();
- r = strv_extend(&cmdline, "-smbios");
- if (r < 0)
- return log_oom();
+ if (arg_linux) {
+ r = strv_extend_many(&cmdline, "-append", kcl);
+ if (r < 0)
+ return log_oom();
+ } else {
+ if (ARCHITECTURE_SUPPORTS_SMBIOS) {
+ r = strv_extend(&cmdline, "-smbios");
+ if (r < 0)
+ return log_oom();
- r = strv_extendf(&cmdline, "type=11,value=io.systemd.stub.kernel-cmdline-extra=%s", kcl);
- if (r < 0)
- return log_oom();
+ r = strv_extendf(&cmdline, "type=11,value=io.systemd.stub.kernel-cmdline-extra=%s", kcl);
+ if (r < 0)
+ return log_oom();
+ } else
+ log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS, ignoring");
+ }
} else
log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS");
- if (use_vsock) {
- vsock_fd = open_vsock();
- if (vsock_fd < 0)
- return log_error_errno(vsock_fd, "Failed to open vsock: %m");
+ /* disable TPM autodetection if the user's hardware doesn't support it */
+ if (!ARCHITECTURE_SUPPORTS_TPM) {
+ if (arg_tpm < 0) {
+ arg_tpm = 0;
+ log_debug("TPM not support on %s, disabling tpm autodetection and continuing", architecture_to_string(native_architecture()));
+ } else if (arg_tpm > 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM not supported on %s, refusing", architecture_to_string(native_architecture()));
+ }
- r = cmdline_add_vsock(&cmdline, vsock_fd);
+ _cleanup_free_ char *swtpm = NULL;
+ if (arg_tpm != 0) {
+ r = find_executable("swtpm", &swtpm);
+ if (r < 0) {
+ /* log if the user asked for swtpm and we cannot find it */
+ if (arg_tpm > 0)
+ return log_error_errno(r, "Failed to find swtpm binary: %m");
+ /* also log if we got an error other than ENOENT from find_executable */
+ if (r != -ENOENT && arg_tpm < 0)
+ return log_error_errno(r, "Error detecting swtpm: %m");
+ }
+ }
+
+ _cleanup_free_ const char *tpm_state_tempdir = NULL;
+ if (swtpm) {
+ r = start_tpm(bus, trans_scope, swtpm, &tpm_state_tempdir);
+ if (r < 0) {
+ /* only bail if the user asked for a tpm */
+ if (arg_tpm > 0)
+ return log_error_errno(r, "Failed to start tpm: %m");
+ log_debug_errno(r, "Failed to start tpm, ignoring: %m");
+ }
+
+ r = strv_extend(&cmdline, "-chardev");
+ if (r < 0)
+ return log_oom();
+
+ r = strv_extendf(&cmdline, "socket,id=chrtpm,path=%s/sock", tpm_state_tempdir);
+ if (r < 0)
+ return log_oom();
+
+ r = strv_extend_many(&cmdline, "-tpmdev", "emulator,id=tpm0,chardev=chrtpm");
+ if (r < 0)
+ return log_oom();
+
+ if (native_architecture() == ARCHITECTURE_X86_64)
+ r = strv_extend_many(&cmdline, "-device", "tpm-tis,tpmdev=tpm0");
+ else if (IN_SET(native_architecture(), ARCHITECTURE_ARM64, ARCHITECTURE_ARM64_BE))
+ r = strv_extend_many(&cmdline, "-device", "tpm-tis-device,tpmdev=tpm0");
+ if (r < 0)
+ return log_oom();
+ }
+
+ if (arg_initrd) {
+ r = strv_extend_many(&cmdline, "-initrd", arg_initrd);
+ if (r < 0)
+ return log_oom();
+ }
+
+ if (use_vsock) {
+ notify_sock_fd = open_vsock();
+ if (notify_sock_fd < 0)
+ return log_error_errno(notify_sock_fd, "Failed to open vsock: %m");
+
+ r = cmdline_add_vsock(&cmdline, notify_sock_fd);
if (r == -ENOMEM)
return log_oom();
if (r < 0)
@@ -741,7 +1072,7 @@ static int run_virtual_machine(void) {
int exit_status = INT_MAX;
if (use_vsock) {
- r = setup_notify_parent(event, vsock_fd, &exit_status, ¬ify_event_source);
+ r = setup_notify_parent(event, notify_sock_fd, &exit_status, ¬ify_event_source);
if (r < 0)
return log_error_errno(r, "Failed to setup event loop to handle vsock notify events: %m");
}
@@ -798,11 +1129,21 @@ static int determine_names(void) {
return 0;
}
+static int verify_arguments(void) {
+ if (arg_network_stack == QEMU_NET_TAP && !arg_privileged)
+ return log_error_errno(SYNTHETIC_ERRNO(EPERM), "--network-tap requires root privileges, refusing.");
+
+ return 0;
+}
+
static int run(int argc, char *argv[]) {
- int r;
+ int r, kvm_device_fd = -EBADF, vhost_device_fd = -EBADF;
+ _cleanup_strv_free_ char **names = NULL;
log_setup();
+ arg_privileged = getuid() == 0;
+
r = parse_argv(argc, argv);
if (r <= 0)
return r;
@@ -811,6 +1152,10 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
+ r = verify_arguments();
+ if (r < 0)
+ return r;
+
if (!arg_quiet) {
_cleanup_free_ char *u = NULL;
(void) terminal_urlify_path(arg_image, arg_image, &u);
@@ -821,9 +1166,25 @@ static int run(int argc, char *argv[]) {
special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal());
}
+ r = sd_listen_fds_with_names(true, &names);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get passed file descriptors: %m");
+
+ for (int i = 0; i < r; i++) {
+ int fd = SD_LISTEN_FDS_START + i;
+ if (streq(names[i], "kvm"))
+ kvm_device_fd = fd;
+ else if (streq(names[i], "vhost-vsock"))
+ vhost_device_fd = fd;
+ else {
+ log_notice("Couldn't recognize passed fd %d (%s), closing fd and ignoring...", fd, names[i]);
+ safe_close(fd);
+ }
+ }
+
assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0);
- return run_virtual_machine();
+ return run_virtual_machine(kvm_device_fd, vhost_device_fd);
}
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);