bhyve: add cmdline option for user defined fw_cfg items

Some guest allow to configure themself by fw_cfg. E.g. Fedora CoreOs can
be provisioned by adding a JSON file as fw_cfg item.

Reviewed by:		jhb
MFC after:		1 week
Sponsored by:		Beckhoff Automation GmbH & Co. KG
Differential Revision:	https://reviews.freebsd.org/D38338

(cherry picked from commit ca14781c81)

bhyve: error out if fwcfg user file isn't read completely

At the moment, fwcfg reads the file once at startup and passes these
data to the guest. Therefore, we should always read the whole file.
Otherwise we should error out.

Additionally, GCC12 complains that the comparison whether
fwcfg_file->size is lower than 0 is always false due to the limited
range of data type.

Reviewed by:		markj
Fixes:			ca14781c81 ("bhyve: add cmdline option for user defined fw_cfg items")
MFC after:		1 week
Sponsored by:		Beckhoff Automation GmbH & Co. KG
Differential Revision:	https://reviews.freebsd.org/D40076

(cherry picked from commit 26d9f973d8)
This commit is contained in:
Corvin Köhne 2021-09-08 11:31:21 +02:00
parent 8caac07ce9
commit b1fffed683
No known key found for this signature in database
GPG key ID: D854DA56315E026A
4 changed files with 172 additions and 2 deletions

View file

@ -45,6 +45,15 @@
.Op Cm ,threads= Ar n
.Oc
.Sm on
.Oo Fl f
.Sm off
.Ar name Cm \&,
.Oo
.Cm string No | Cm file
.Oc
.Cm \&= Ar data
.Sm on
.Oc
.Oo
.Sm off
.Fl G\~
@ -145,6 +154,16 @@ Force
.Nm
to exit when a guest issues an access to an I/O port that is not emulated.
This is intended for debug purposes.
.It Fl f Ar name Ns Cm \&, Ns Oo Cm string Ns No | Ns Cm file Ns Oc Ns Cm \&= Ns Ar data
Add a fw_cfg file
.Ar name
to the fw_cfg interface.
If a
.Cm string
is specified, the fw_cfg file contains the string as data.
If a
.Cm file
is specified, bhyve reads the file and adds the file content as fw_cfg data.
.It Fl G Xo
.Sm off
.Oo Ar w Oc

View file

@ -1258,9 +1258,9 @@ main(int argc, char *argv[])
progname = basename(argv[0]);
#ifdef BHYVE_SNAPSHOT
optstr = "aehuwxACDHIPSWYk:o:p:G:c:s:m:l:K:U:r:";
optstr = "aehuwxACDHIPSWYk:f:o:p:G:c:s:m:l:K:U:r:";
#else
optstr = "aehuwxACDHIPSWYk:o:p:G:c:s:m:l:K:U:";
optstr = "aehuwxACDHIPSWYk:f:o:p:G:c:s:m:l:K:U:";
#endif
while ((c = getopt(argc, argv, optstr)) != -1) {
switch (c) {
@ -1288,6 +1288,11 @@ main(int argc, char *argv[])
case 'C':
set_config_bool("memory.guest_in_core", true);
break;
case 'f':
if (qemu_fwcfg_parse_cmdline_arg(optarg) != 0) {
errx(EX_USAGE, "invalid fwcfg item '%s'", optarg);
}
break;
case 'G':
parse_gdb_options(optarg);
break;

View file

@ -7,13 +7,18 @@
#include <sys/param.h>
#include <sys/endian.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <machine/vmm.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "acpi_device.h"
#include "bhyverun.h"
@ -98,6 +103,15 @@ struct qemu_fwcfg_softc {
static struct qemu_fwcfg_softc fwcfg_sc;
struct qemu_fwcfg_user_file {
STAILQ_ENTRY(qemu_fwcfg_user_file) chain;
uint8_t name[QEMU_FWCFG_MAX_NAME];
uint32_t size;
void *data;
};
static STAILQ_HEAD(qemu_fwcfg_user_file_list,
qemu_fwcfg_user_file) user_files = STAILQ_HEAD_INITIALIZER(user_files);
static int
qemu_fwcfg_selector_port_handler(struct vmctx *const ctx __unused, const int in,
const int port __unused, const int bytes, uint32_t *const eax,
@ -384,6 +398,22 @@ qemu_fwcfg_add_file(const char *name, const uint32_t size, void *const data)
return (0);
}
static int
qemu_fwcfg_add_user_files(void)
{
const struct qemu_fwcfg_user_file *fwcfg_file;
int error;
STAILQ_FOREACH(fwcfg_file, &user_files, chain) {
error = qemu_fwcfg_add_file(fwcfg_file->name, fwcfg_file->size,
fwcfg_file->data);
if (error)
return (error);
}
return (0);
}
static const struct acpi_device_emul qemu_fwcfg_acpi_device_emul = {
.name = QEMU_FWCFG_ACPI_DEVICE_NAME,
.hid = QEMU_FWCFG_ACPI_HARDWARE_ID,
@ -458,6 +488,11 @@ qemu_fwcfg_init(struct vmctx *const ctx)
}
if ((error = qemu_fwcfg_add_item_file_dir()) != 0) {
warnx("%s: Unable to add file_dir item", __func__);
}
/* add user defined fwcfg files */
if ((error = qemu_fwcfg_add_user_files()) != 0) {
warnx("%s: Unable to add user files", __func__);
goto done;
}
@ -468,3 +503,113 @@ qemu_fwcfg_init(struct vmctx *const ctx)
return (error);
}
static void
qemu_fwcfg_usage(const char *opt)
{
warnx("Invalid fw_cfg option \"%s\"", opt);
warnx("-f [name=]<name>,(string|file)=<value>");
}
/*
* Parses the cmdline argument for user defined fw_cfg items. The cmdline
* argument has the format:
* "-f [name=]<name>,(string|file)=<value>"
*
* E.g.: "-f opt/com.page/example,string=Hello"
*/
int
qemu_fwcfg_parse_cmdline_arg(const char *opt)
{
struct qemu_fwcfg_user_file *fwcfg_file;
struct stat sb;
const char *opt_ptr, *opt_end;
ssize_t bytes_read;
int fd;
fwcfg_file = malloc(sizeof(*fwcfg_file));
if (fwcfg_file == NULL) {
warnx("Unable to allocate fw_cfg_user_file");
return (ENOMEM);
}
/* get pointer to <name> */
opt_ptr = opt;
/* If [name=] is specified, skip it */
if (strncmp(opt_ptr, "name=", sizeof("name=") - 1) == 0) {
opt_ptr += sizeof("name=") - 1;
}
/* get the end of <name> */
opt_end = strchr(opt_ptr, ',');
if (opt_end == NULL) {
qemu_fwcfg_usage(opt);
return (EINVAL);
}
/* check if <name> is too long */
if (opt_end - opt_ptr >= QEMU_FWCFG_MAX_NAME) {
warnx("fw_cfg name too long: \"%s\"", opt);
return (EINVAL);
}
/* save <name> */
strncpy(fwcfg_file->name, opt_ptr, opt_end - opt_ptr);
fwcfg_file->name[opt_end - opt_ptr] = '\0';
/* set opt_ptr and opt_end to <value> */
opt_ptr = opt_end + 1;
opt_end = opt_ptr + strlen(opt_ptr);
if (strncmp(opt_ptr, "string=", sizeof("string=") - 1) == 0) {
opt_ptr += sizeof("string=") - 1;
fwcfg_file->data = strdup(opt_ptr);
if (fwcfg_file->data == NULL) {
warnx("Can't duplicate fw_cfg_user_file string \"%s\"",
opt_ptr);
return (ENOMEM);
}
fwcfg_file->size = strlen(opt_ptr) + 1;
} else if (strncmp(opt_ptr, "file=", sizeof("file=") - 1) == 0) {
opt_ptr += sizeof("file=") - 1;
fd = open(opt_ptr, O_RDONLY);
if (fd < 0) {
warn("Can't open fw_cfg_user_file file \"%s\"",
opt_ptr);
return (EINVAL);
}
if (fstat(fd, &sb) < 0) {
warn("Unable to get size of file \"%s\"", opt_ptr);
close(fd);
return (-1);
}
fwcfg_file->data = malloc(sb.st_size);
if (fwcfg_file->data == NULL) {
warnx(
"Can't allocate fw_cfg_user_file file \"%s\" (size: 0x%16lx)",
opt_ptr, sb.st_size);
close(fd);
return (ENOMEM);
}
bytes_read = read(fd, fwcfg_file->data, sb.st_size);
if (bytes_read < 0 || bytes_read != sb.st_size) {
warn("Unable to read file \"%s\"", opt_ptr);
free(fwcfg_file->data);
close(fd);
return (-1);
}
fwcfg_file->size = bytes_read;
close(fd);
} else {
qemu_fwcfg_usage(opt);
return (EINVAL);
}
STAILQ_INSERT_TAIL(&user_files, fwcfg_file, chain);
return (0);
}

View file

@ -23,3 +23,4 @@ struct qemu_fwcfg_item {
int qemu_fwcfg_add_file(const char *name,
const uint32_t size, void *const data);
int qemu_fwcfg_init(struct vmctx *const ctx);
int qemu_fwcfg_parse_cmdline_arg(const char *opt);