vmspawn: add disk type selection for root and extra drives (#41301)
vmspawn previously hardcoded virtio-blk for all drives. This adds --image-disk-type= to select the root disk type (virtio-blk, virtio-scsi, or nvme) and allows per-drive overrides via a colon-separated prefix on --extra-drive=. The format and disk type prefixes can appear in any order since their value sets don't overlap. For virtio-scsi, a single shared controller is created with drives attached as scsi-hd devices. For nvme, each drive gets its own controller. Both have serial number length limits (30 and 20 characters respectively), so long filenames are replaced with a truncated SHA-256 hex digest.
This commit is contained in:
+18
-5
@@ -125,6 +125,18 @@
|
||||
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--image-disk-type=<replaceable>TYPE</replaceable></option></term>
|
||||
|
||||
<listitem><para>Specifies the disk type to use for the root disk passed to <option>--image=</option>.
|
||||
Extra drives added via <option>--extra-drive=</option> inherit this disk type unless overridden
|
||||
with an explicit disk type prefix. Takes one of <literal>virtio-blk</literal>,
|
||||
<literal>virtio-scsi</literal>, or <literal>nvme</literal>. Defaults to
|
||||
<literal>virtio-blk</literal>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>-x</option></term>
|
||||
<term><option>--ephemeral</option></term>
|
||||
@@ -506,13 +518,14 @@
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--extra-drive=[<replaceable>FORMAT</replaceable>:]<replaceable>PATH</replaceable></option></term>
|
||||
<term><option>--extra-drive=[<replaceable>FORMAT</replaceable>:][<replaceable>DISKTYPE</replaceable>:]<replaceable>PATH</replaceable></option></term>
|
||||
|
||||
<listitem><para>Takes a disk image or block device on the host and supplies it to the virtual
|
||||
machine as another drive. Optionally, the image format can be specified by prefixing the path with
|
||||
<literal>raw</literal> or <literal>qcow2</literal> and a colon. The format defaults to
|
||||
<literal>raw</literal>. Note that <literal>qcow2</literal> is only supported for regular files, not
|
||||
block devices.</para>
|
||||
machine as another drive. Optionally, the image format and/or disk type can be specified by prefixing
|
||||
the path with their values separated by colons. The format and disk type prefixes can appear in any
|
||||
order. The format defaults to <literal>raw</literal> and the disk type defaults to the value of
|
||||
<option>--image-disk-type=</option> (which itself defaults to <literal>virtio-blk</literal>).
|
||||
Note that <literal>qcow2</literal> is only supported for regular files, not block devices.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
@@ -38,6 +38,7 @@ _systemd_vmspawn() {
|
||||
[CONSOLE]='--console'
|
||||
[ARG]='--cpus --ram --vsock-cid -M --machine --uuid --private-users --background --set-credential --load-credential'
|
||||
[IMAGE_FORMAT]='--image-format'
|
||||
[IMAGE_DISK_TYPE]='--image-disk-type'
|
||||
)
|
||||
|
||||
_init_completion || return
|
||||
@@ -59,6 +60,8 @@ _systemd_vmspawn() {
|
||||
comps='interactive native gui read-only headless'
|
||||
elif __contains_word "$prev" ${OPTS[IMAGE_FORMAT]}; then
|
||||
comps='raw qcow2'
|
||||
elif __contains_word "$prev" ${OPTS[IMAGE_DISK_TYPE]}; then
|
||||
comps='virtio-blk virtio-scsi nvme'
|
||||
elif __contains_word "$prev" ${OPTS[ARG]}; then
|
||||
comps=''
|
||||
else
|
||||
|
||||
@@ -10,6 +10,14 @@ static const char *const image_format_table[_IMAGE_FORMAT_MAX] = {
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(image_format, ImageFormat);
|
||||
|
||||
static const char *const disk_type_table[_DISK_TYPE_MAX] = {
|
||||
[DISK_TYPE_VIRTIO_BLK] = "virtio-blk",
|
||||
[DISK_TYPE_VIRTIO_SCSI] = "virtio-scsi",
|
||||
[DISK_TYPE_NVME] = "nvme",
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(disk_type, DiskType);
|
||||
|
||||
void extra_drive_context_done(ExtraDriveContext *ctx) {
|
||||
assert(ctx);
|
||||
|
||||
|
||||
@@ -10,9 +10,18 @@ typedef enum ImageFormat {
|
||||
_IMAGE_FORMAT_INVALID = -EINVAL,
|
||||
} ImageFormat;
|
||||
|
||||
typedef enum DiskType {
|
||||
DISK_TYPE_VIRTIO_BLK,
|
||||
DISK_TYPE_VIRTIO_SCSI,
|
||||
DISK_TYPE_NVME,
|
||||
_DISK_TYPE_MAX,
|
||||
_DISK_TYPE_INVALID = -EINVAL,
|
||||
} DiskType;
|
||||
|
||||
typedef struct ExtraDrive {
|
||||
char *path;
|
||||
ImageFormat format;
|
||||
DiskType disk_type;
|
||||
} ExtraDrive;
|
||||
|
||||
typedef struct ExtraDriveContext {
|
||||
@@ -43,4 +52,5 @@ typedef enum SettingsMask {
|
||||
} SettingsMask;
|
||||
|
||||
DECLARE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode);
|
||||
DECLARE_STRING_TABLE_LOOKUP(disk_type, DiskType);
|
||||
DECLARE_STRING_TABLE_LOOKUP(image_format, ImageFormat);
|
||||
|
||||
+134
-15
@@ -65,6 +65,7 @@
|
||||
#include "ptyfwd.h"
|
||||
#include "random-util.h"
|
||||
#include "rm-rf.h"
|
||||
#include "sha256.h"
|
||||
#include "signal-util.h"
|
||||
#include "snapshot-util.h"
|
||||
#include "socket-util.h"
|
||||
@@ -143,6 +144,7 @@ static char *arg_background = NULL;
|
||||
static bool arg_pass_ssh_key = true;
|
||||
static char *arg_ssh_key_type = NULL;
|
||||
static bool arg_discard_disk = true;
|
||||
static DiskType arg_image_disk_type = DISK_TYPE_VIRTIO_BLK;
|
||||
static struct ether_addr arg_network_provided_mac = {};
|
||||
static char **arg_smbios11 = NULL;
|
||||
static uint64_t arg_grow_image = 0;
|
||||
@@ -206,6 +208,8 @@ static int help(void) {
|
||||
" -x --ephemeral Run VM with snapshot of the disk or directory\n"
|
||||
" -i --image=FILE|DEVICE Root file system disk image or device for the VM\n"
|
||||
" --image-format=FORMAT Specify disk image format (raw, qcow2; default: raw)\n"
|
||||
" --image-disk-type=TYPE\n"
|
||||
" Specify disk type (virtio-blk, virtio-scsi, nvme; default: virtio-blk)\n"
|
||||
"\n%3$sHost Configuration:%4$s\n"
|
||||
" --cpus=CPUS Configure number of CPUs in guest\n"
|
||||
" --ram=BYTES Configure guest's RAM size\n"
|
||||
@@ -246,9 +250,10 @@ static int help(void) {
|
||||
" Mount a file or directory from the host into the VM\n"
|
||||
" --bind-ro=SOURCE[:TARGET]\n"
|
||||
" Mount a file or directory, but read-only\n"
|
||||
" --extra-drive=[FORMAT:]PATH\n"
|
||||
" Adds an additional disk to the virtual machine\n"
|
||||
" (FORMAT: raw, qcow2; default: raw)\n"
|
||||
" --extra-drive=[FORMAT:][DISKTYPE:]PATH\n"
|
||||
" Adds an additional disk to the VM\n"
|
||||
" FORMAT: raw, qcow2\n"
|
||||
" DISKTYPE: virtio-blk, virtio-scsi, nvme\n"
|
||||
" --bind-user=NAME Bind user from host to virtual machine\n"
|
||||
" --bind-user-shell=BOOL|PATH\n"
|
||||
" Configure the shell to use for --bind-user= users\n"
|
||||
@@ -335,6 +340,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_SYSTEM,
|
||||
ARG_USER,
|
||||
ARG_IMAGE_FORMAT,
|
||||
ARG_IMAGE_DISK_TYPE,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@@ -344,6 +350,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
||||
{ "image", required_argument, NULL, 'i' },
|
||||
{ "image-format", required_argument, NULL, ARG_IMAGE_FORMAT },
|
||||
{ "image-disk-type", required_argument, NULL, ARG_IMAGE_DISK_TYPE },
|
||||
{ "ephemeral", no_argument, NULL, 'x' },
|
||||
{ "directory", required_argument, NULL, 'D' },
|
||||
{ "machine", required_argument, NULL, 'M' },
|
||||
@@ -434,6 +441,13 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
"Invalid image format: %s", optarg);
|
||||
break;
|
||||
|
||||
case ARG_IMAGE_DISK_TYPE:
|
||||
arg_image_disk_type = disk_type_from_string(optarg);
|
||||
if (arg_image_disk_type < 0)
|
||||
return log_error_errno(arg_image_disk_type,
|
||||
"Invalid image disk type: %s", optarg);
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
if (isempty(optarg))
|
||||
arg_machine = mfree(arg_machine);
|
||||
@@ -570,21 +584,36 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
case ARG_EXTRA_DRIVE: {
|
||||
ImageFormat format = IMAGE_FORMAT_RAW;
|
||||
DiskType extra_disk_type = _DISK_TYPE_INVALID;
|
||||
const char *dp = optarg;
|
||||
|
||||
const char *colon = strchr(dp, ':');
|
||||
if (colon) {
|
||||
_cleanup_free_ char *fs = strndup(optarg, colon - optarg);
|
||||
if (!fs)
|
||||
/* Parse optional colon-separated prefixes. The format and disk type
|
||||
* value sets don't overlap, so they can appear in any order. */
|
||||
for (;;) {
|
||||
const char *colon = strchr(dp, ':');
|
||||
if (!colon)
|
||||
break;
|
||||
|
||||
_cleanup_free_ char *prefix = strndup(dp, colon - dp);
|
||||
if (!prefix)
|
||||
return log_oom();
|
||||
|
||||
ImageFormat f = image_format_from_string(fs);
|
||||
if (f < 0)
|
||||
log_debug_errno(f, "Cannot parse '%s' as an image format, assuming it is a part of path, ignoring.", fs);
|
||||
else {
|
||||
ImageFormat f = image_format_from_string(prefix);
|
||||
if (f >= 0) {
|
||||
format = f;
|
||||
dp = colon + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
DiskType dt = disk_type_from_string(prefix);
|
||||
if (dt >= 0) {
|
||||
extra_disk_type = dt;
|
||||
dp = colon + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Not a recognized prefix, treat the rest as the path */
|
||||
break;
|
||||
}
|
||||
|
||||
_cleanup_free_ char *drive_path = NULL;
|
||||
@@ -598,6 +627,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
arg_extra_drives.drives[arg_extra_drives.n_drives++] = (ExtraDrive) {
|
||||
.path = TAKE_PTR(drive_path),
|
||||
.format = format,
|
||||
.disk_type = extra_disk_type,
|
||||
};
|
||||
|
||||
break;
|
||||
@@ -1965,6 +1995,31 @@ static int make_sidecar_path(const char *suffix, char **ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Device serial numbers have length limits (e.g. 20 for NVMe, 30 for SCSI).
|
||||
* If the filename fits, use it directly; otherwise hash it with SHA-256 and
|
||||
* take the first max_len hex characters. max_len must be even and <= 64.
|
||||
* The filename should already be QEMU-escaped (commas doubled) so that the
|
||||
* result can be embedded directly in a -device argument. */
|
||||
static int disk_serial(const char *filename, size_t max_len, char **ret) {
|
||||
assert(filename);
|
||||
assert(ret);
|
||||
assert(max_len % 2 == 0);
|
||||
assert(max_len <= SHA256_DIGEST_SIZE * 2);
|
||||
|
||||
if (strlen(filename) <= max_len)
|
||||
return strdup_to(ret, filename);
|
||||
|
||||
uint8_t hash[SHA256_DIGEST_SIZE];
|
||||
sha256_direct(filename, strlen(filename), hash);
|
||||
|
||||
_cleanup_free_ char *serial = hexmem(hash, max_len / 2);
|
||||
if (!serial)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = TAKE_PTR(serial);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
_cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL;
|
||||
_cleanup_free_ char *qemu_binary = NULL, *mem = NULL, *kernel = NULL;
|
||||
@@ -2483,6 +2538,22 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
}
|
||||
}
|
||||
|
||||
bool need_scsi_controller =
|
||||
arg_image_disk_type == DISK_TYPE_VIRTIO_SCSI && arg_image;
|
||||
if (!need_scsi_controller)
|
||||
FOREACH_ARRAY(drive, arg_extra_drives.drives, arg_extra_drives.n_drives) {
|
||||
DiskType dt = drive->disk_type >= 0 ? drive->disk_type : arg_image_disk_type;
|
||||
if (dt == DISK_TYPE_VIRTIO_SCSI) {
|
||||
need_scsi_controller = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (need_scsi_controller) {
|
||||
if (strv_extend_many(&cmdline, "-device", "virtio-scsi-pci,id=vmspawn_scsi") < 0)
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
if (arg_image) {
|
||||
assert(!arg_directory);
|
||||
|
||||
@@ -2517,8 +2588,30 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
if (strv_extend(&cmdline, "-device") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (strv_extend_joined(&cmdline, "virtio-blk-pci,drive=vmspawn,bootindex=1,serial=", escaped_image_fn) < 0)
|
||||
return log_oom();
|
||||
switch (arg_image_disk_type) {
|
||||
case DISK_TYPE_VIRTIO_BLK:
|
||||
if (strv_extend_joined(&cmdline, "virtio-blk-pci,drive=vmspawn,bootindex=1,serial=", escaped_image_fn) < 0)
|
||||
return log_oom();
|
||||
break;
|
||||
case DISK_TYPE_VIRTIO_SCSI: {
|
||||
_cleanup_free_ char *serial = NULL;
|
||||
if (disk_serial(escaped_image_fn, 30, &serial) < 0)
|
||||
return log_oom();
|
||||
if (strv_extend_joined(&cmdline, "scsi-hd,bus=vmspawn_scsi.0,drive=vmspawn,bootindex=1,serial=", serial) < 0)
|
||||
return log_oom();
|
||||
break;
|
||||
}
|
||||
case DISK_TYPE_NVME: {
|
||||
_cleanup_free_ char *serial = NULL;
|
||||
if (disk_serial(escaped_image_fn, 20, &serial) < 0)
|
||||
return log_oom();
|
||||
if (strv_extend_joined(&cmdline, "nvme,drive=vmspawn,bootindex=1,serial=", serial) < 0)
|
||||
return log_oom();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
r = grow_image(arg_image, arg_grow_image);
|
||||
if (r < 0)
|
||||
@@ -2644,8 +2737,34 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
if (strv_extend(&cmdline, "-device") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (strv_extendf(&cmdline, "virtio-blk-pci,drive=vmspawn_extra_%zu,serial=%s", i++, escaped_drive_fn) < 0)
|
||||
return log_oom();
|
||||
DiskType dt = drive->disk_type >= 0 ? drive->disk_type : arg_image_disk_type;
|
||||
|
||||
switch (dt) {
|
||||
case DISK_TYPE_VIRTIO_BLK:
|
||||
if (strv_extendf(&cmdline, "virtio-blk-pci,drive=vmspawn_extra_%zu,serial=%s", i++, escaped_drive_fn) < 0)
|
||||
return log_oom();
|
||||
break;
|
||||
case DISK_TYPE_VIRTIO_SCSI: {
|
||||
_cleanup_free_ char *serial = NULL;
|
||||
r = disk_serial(escaped_drive_fn, 30, &serial);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
if (strv_extendf(&cmdline, "scsi-hd,bus=vmspawn_scsi.0,drive=vmspawn_extra_%zu,serial=%s", i++, serial) < 0)
|
||||
return log_oom();
|
||||
break;
|
||||
}
|
||||
case DISK_TYPE_NVME: {
|
||||
_cleanup_free_ char *serial = NULL;
|
||||
r = disk_serial(escaped_drive_fn, 20, &serial);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
if (strv_extendf(&cmdline, "nvme,drive=vmspawn_extra_%zu,serial=%s", i++, serial) < 0)
|
||||
return log_oom();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
if (!IN_SET(arg_console_mode, CONSOLE_GUI, CONSOLE_HEADLESS)) {
|
||||
|
||||
Reference in New Issue
Block a user