git: ca14781c8170 - main - bhyve: add cmdline option for user defined fw_cfg items

From: Corvin Köhne <corvink_at_FreeBSD.org>
Date: Thu, 11 May 2023 12:20:54 UTC
The branch main has been updated by corvink:

URL: https://cgit.FreeBSD.org/src/commit/?id=ca14781c8170f3517ae79e198c0c880dbc3142dd

commit ca14781c8170f3517ae79e198c0c880dbc3142dd
Author:     Corvin Köhne <corvink@FreeBSD.org>
AuthorDate: 2021-09-08 09:31:21 +0000
Commit:     Corvin Köhne <corvink@FreeBSD.org>
CommitDate: 2023-05-11 12:20:32 +0000

    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
---
 usr.sbin/bhyve/bhyve.8      |  19 ++++++
 usr.sbin/bhyve/bhyverun.c   |   9 ++-
 usr.sbin/bhyve/qemu_fwcfg.c | 146 ++++++++++++++++++++++++++++++++++++++++++++
 usr.sbin/bhyve/qemu_fwcfg.h |   1 +
 4 files changed, 173 insertions(+), 2 deletions(-)

diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8
index 321c014395fb..38891ab33c40 100644
--- a/usr.sbin/bhyve/bhyve.8
+++ b/usr.sbin/bhyve/bhyve.8
@@ -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
diff --git a/usr.sbin/bhyve/bhyverun.c b/usr.sbin/bhyve/bhyverun.c
index 627a86d71e0e..5c5d4608b10d 100644
--- a/usr.sbin/bhyve/bhyverun.c
+++ b/usr.sbin/bhyve/bhyverun.c
@@ -1259,9 +1259,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) {
@@ -1289,6 +1289,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;
diff --git a/usr.sbin/bhyve/qemu_fwcfg.c b/usr.sbin/bhyve/qemu_fwcfg.c
index af819dc4a952..73a10e0e61c9 100644
--- a/usr.sbin/bhyve/qemu_fwcfg.c
+++ b/usr.sbin/bhyve/qemu_fwcfg.c
@@ -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,114 @@ done:
 
 	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;
+	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);
+		}
+		fwcfg_file->size = read(fd, fwcfg_file->data, sb.st_size);
+		if ((ssize_t)fwcfg_file->size < 0) {
+			warn("Unable to read file \"%s\"", opt_ptr);
+			free(fwcfg_file->data);
+			close(fd);
+			return (-1);
+		} else if (fwcfg_file->size < sb.st_size) {
+			warnx("Only read %u bytes of file \"%s\"",
+			    fwcfg_file->size, opt_ptr);
+		}
+
+		close(fd);
+	} else {
+		qemu_fwcfg_usage(opt);
+		return (EINVAL);
+	}
+
+	STAILQ_INSERT_TAIL(&user_files, fwcfg_file, chain);
+
+	return (0);
+}
diff --git a/usr.sbin/bhyve/qemu_fwcfg.h b/usr.sbin/bhyve/qemu_fwcfg.h
index d318d0434285..98881b4ab79d 100644
--- a/usr.sbin/bhyve/qemu_fwcfg.h
+++ b/usr.sbin/bhyve/qemu_fwcfg.h
@@ -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);