* [Qemu-devel] [RFC PATCH 1/2] fw_cfg: Add -guestenv qemu command line option
2015-02-24 19:43 [Qemu-devel] [RFC PATCH 0/2] host->guest environment variables via fw_cfg Gabriel L. Somlo
@ 2015-02-24 19:43 ` Gabriel L. Somlo
2015-02-24 19:43 ` [Qemu-devel] [RFC PATCH 2/2] qga: add --getenv option to get env. vars from fw_cfg Gabriel L. Somlo
2015-02-24 23:07 ` [Qemu-devel] [RFC PATCH 0/2] host->guest environment variables via fw_cfg Laszlo Ersek
2 siblings, 0 replies; 10+ messages in thread
From: Gabriel L. Somlo @ 2015-02-24 19:43 UTC (permalink / raw)
To: qemu-devel; +Cc: mdroth, lersek, rjones
Introduce "-guestenv" command line option, which allows passing of
environment variables to the guest in a "fire-and-forget", asynchronous
way. The guest may retrieve this data at its convenience, by accessing
the provided fw_cfg device from the inside.
The new "etc/guestenv" blob will be a set of concatenated null-terminated
ascii strings: "key1=val1\0key2=val2\0...\0keyN=valN\0", each string being
added by its own separate "-guestenv <line>" command line argument to qemu.
I'm currently not checking for the presence of an '=' character in each
entry, nor am I currently checking for duplicate keys.
Right now, I'm only inserting the new fw_cfg file from i386/pc.c, since
this is a proof-of-concept/RFC, but this should work easily on any platform
on which fw_cfg is supported.
Signed-off-by: Gabriel Somlo <somlo@cmu.edu>
---
hw/i386/pc.c | 4 ++++
include/sysemu/sysemu.h | 3 +++
qemu-options.hx | 9 +++++++++
vl.c | 14 ++++++++++++++
4 files changed, 30 insertions(+)
diff --git a/hw/i386/pc.c b/hw/i386/pc.c
index c7af6aa..4133b21 100644
--- a/hw/i386/pc.c
+++ b/hw/i386/pc.c
@@ -715,6 +715,10 @@ static FWCfgState *bochs_bios_init(void)
(1 + apic_id_limit + nb_numa_nodes) *
sizeof(*numa_fw_cfg));
+ if (guestenv) {
+ fw_cfg_add_file(fw_cfg, "etc/guestenv", guestenv, guestenv_len);
+ }
+
return fw_cfg;
}
diff --git a/include/sysemu/sysemu.h b/include/sysemu/sysemu.h
index 748d059..fd00266 100644
--- a/include/sysemu/sysemu.h
+++ b/include/sysemu/sysemu.h
@@ -14,6 +14,9 @@
/* vl.c */
+extern char *guestenv;
+extern int guestenv_len;
+
extern const char *bios_name;
extern const char *qemu_name;
diff --git a/qemu-options.hx b/qemu-options.hx
index ee4b223..b9d5565 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2646,6 +2646,15 @@ STEXI
@table @option
ETEXI
+DEF("guestenv", HAS_ARG, QEMU_OPTION_guestenv, \
+ "-guestenv entry add 'entry' to environment passed to guest\n",
+ QEMU_ARCH_ALL)
+STEXI
+@item -guestenv @var{entry}
+@findex -guestenv
+Add @var{entry} to environment passed to guest
+ETEXI
+
DEF("serial", HAS_ARG, QEMU_OPTION_serial, \
"-serial dev redirect the serial port to char device 'dev'\n",
QEMU_ARCH_ALL)
diff --git a/vl.c b/vl.c
index 8c8f142..a94dcb5 100644
--- a/vl.c
+++ b/vl.c
@@ -187,6 +187,9 @@ int nb_numa_nodes;
int max_numa_nodeid;
NodeInfo numa_info[MAX_NODES];
+char *guestenv;
+int guestenv_len;
+
/* The bytes in qemu_uuid[] are in the order specified by RFC4122, _not_ in the
* little-endian "wire format" described in the SMBIOS 2.6 specification.
*/
@@ -2249,6 +2252,14 @@ struct device_config {
static QTAILQ_HEAD(, device_config) device_configs =
QTAILQ_HEAD_INITIALIZER(device_configs);
+static void add_guestenv_entry(const char *entry)
+{
+ int entry_len = strlen(entry) + 1;
+ guestenv = g_realloc(guestenv, guestenv_len + entry_len);
+ strcpy(guestenv + guestenv_len, entry);
+ guestenv_len += entry_len;
+}
+
static void add_device_config(int type, const char *cmdline)
{
struct device_config *conf;
@@ -3320,6 +3331,9 @@ int main(int argc, char **argv, char **envp)
qemu_opt_set(device, "mount_tag", "v_synth");
break;
}
+ case QEMU_OPTION_guestenv:
+ add_guestenv_entry(optarg);
+ break;
case QEMU_OPTION_serial:
add_device_config(DEV_SERIAL, optarg);
default_serial = 0;
--
2.1.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [Qemu-devel] [RFC PATCH 2/2] qga: add --getenv option to get env. vars from fw_cfg
2015-02-24 19:43 [Qemu-devel] [RFC PATCH 0/2] host->guest environment variables via fw_cfg Gabriel L. Somlo
2015-02-24 19:43 ` [Qemu-devel] [RFC PATCH 1/2] fw_cfg: Add -guestenv qemu command line option Gabriel L. Somlo
@ 2015-02-24 19:43 ` Gabriel L. Somlo
2015-02-25 9:09 ` Daniel P. Berrange
2015-02-24 23:07 ` [Qemu-devel] [RFC PATCH 0/2] host->guest environment variables via fw_cfg Laszlo Ersek
2 siblings, 1 reply; 10+ messages in thread
From: Gabriel L. Somlo @ 2015-02-24 19:43 UTC (permalink / raw)
To: qemu-devel; +Cc: mdroth, lersek, rjones
The new "-g" (or "--getenv") command line option causes qemu-ga to extract
and parse the "etc/guestenv" blob from fw_cfg, and return the value of
the requested key (if available) on stdout.
Warnings and error messages are printed to stderr, and only the actual
value portion of a "key=value" string matching the "--getenv key" argument
will be printed to stdout. Strings are searched in reverse order to
implement a sort of "last dupe wins" policy -- but I expect to refine
things quite a bit after receiving some feedback.
I've added a flag to qemu-ga instead of creating a completely separate
binary, but I don't feel strongly about keeping it that way. I just need
something that would end up tightly integrated with the "qemu guest tools"
package...
I also haven't built this on cygwin yet, so it's only tested on Linux
and gcc 4.9.2 (which stubbornly optimizes away one of my "true" if branches
with -O2, if I let it, but that's something else I'm planning to have figured
out by the time the dust settles on this :)
Signed-off-by: Gabriel Somlo <somlo@cmu.edu>
---
qga/Makefile.objs | 1 +
qga/getenv.c | 140 +++++++++++++++++++++++++++++++++++++++++++++++++
qga/guest-agent-core.h | 2 +
qga/main.c | 7 ++-
4 files changed, 149 insertions(+), 1 deletion(-)
create mode 100644 qga/getenv.c
diff --git a/qga/Makefile.objs b/qga/Makefile.objs
index 1c5986c..6392b93 100644
--- a/qga/Makefile.objs
+++ b/qga/Makefile.objs
@@ -4,5 +4,6 @@ qga-obj-$(CONFIG_WIN32) += commands-win32.o channel-win32.o service-win32.o
qga-obj-$(CONFIG_WIN32) += vss-win32.o
qga-obj-y += qapi-generated/qga-qapi-types.o qapi-generated/qga-qapi-visit.o
qga-obj-y += qapi-generated/qga-qmp-marshal.o
+qga-obj-y += getenv.o
qga-vss-dll-obj-$(CONFIG_QGA_VSS) += vss-win32/
diff --git a/qga/getenv.c b/qga/getenv.c
new file mode 100644
index 0000000..9fecb9b
--- /dev/null
+++ b/qga/getenv.c
@@ -0,0 +1,140 @@
+/*
+ * QEMU Guest Agent: host->guest environment variable retrieval
+ *
+ * Copyright Carnegie Mellon University 2015
+ *
+ * Author:
+ * Gabriel L. Somlo <somlo@cmu.edu>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/io.h>
+#include "qemu/bswap.h"
+#include "hw/nvram/fw_cfg.h"
+#include "qga/guest-agent-core.h"
+
+#define PORT_FW_CFG_CTL 0x0510
+#define PORT_FW_CFG_DATA 0x0511
+
+#define GUESTENV_FW_CFG_FILE "etc/guestenv"
+
+static char *ge_raw;
+
+static struct {
+ char *key;
+ char *val;
+} *ge_var;
+static int ge_var_cnt;
+
+static void
+fw_cfg_select(uint16_t f)
+{
+ outw(f, PORT_FW_CFG_CTL);
+}
+
+static void
+fw_cfg_read(void *buf, int len)
+{
+ insb(PORT_FW_CFG_DATA, buf, len);
+}
+
+static void
+fw_cfg_read_entry(void *buf, int e, int len)
+{
+ fw_cfg_select(e);
+ fw_cfg_read(buf, len);
+}
+
+static int
+__attribute__((optimize("O0"))) //FIXME: gcc -O2 nukes "true" if branch below!!!
+fw_cfg_grab_guestenv(void)
+{
+ int i;
+ uint32_t count, len = 0;
+ uint16_t sel;
+ uint8_t sig[] = "QEMU";
+ FWCfgFile fcfile;
+
+ /* ensure access to the fw_cfg device */
+ if (ioperm(PORT_FW_CFG_CTL, 2, 1) != 0) {
+ perror("ioperm failed");
+ return EXIT_FAILURE;
+ }
+
+ /* verify presence of fw_cfg device */
+ fw_cfg_select(FW_CFG_SIGNATURE);
+ for (i = 0; i < sizeof(sig) - 1; i++) {
+ sig[i] = inb(PORT_FW_CFG_DATA);
+ }
+ if (memcmp(sig, "QEMU", sizeof(sig)) != 0) {
+ fprintf(stderr, "fw_cfg signature not found!\n");
+ return EXIT_FAILURE;
+ }
+
+ /* read number of fw_cfg entries, then scan for guestenv entry */
+ fw_cfg_read_entry(&count, FW_CFG_FILE_DIR, sizeof(count));
+ count = be32_to_cpu(count);
+ for (i = 0; i < count; i++) {
+ fw_cfg_read(&fcfile, sizeof(fcfile));
+ //FIXME: why does gcc -O2 optimize away the whole if {} block below?!?
+ if (!strcmp(fcfile.name, GUESTENV_FW_CFG_FILE)) {
+ len = be32_to_cpu(fcfile.size);
+ sel = be16_to_cpu(fcfile.select);
+ ge_raw = g_malloc(len);
+ fw_cfg_read_entry(ge_raw, sel, len);
+ break;
+ }
+ }
+
+ if (i == count) {
+ /* guestenv entry not present in fw_cfg */
+ fprintf(stderr, "File %s not found in fw_cfg!\n", GUESTENV_FW_CFG_FILE);
+ return EXIT_FAILURE;
+ }
+
+ /* guestenv entry (concatenation of null-terminated ascii strings)
+ * found and copied to ge_raw; Expected string format is "key=val",
+ * and we attempt to extract them into the ge_var table */
+ for (i = 0; i < len;) {
+ ge_var = g_realloc(ge_var, (ge_var_cnt + 1) * sizeof(ge_var[0]));
+ ge_var[ge_var_cnt].key = ge_raw + i;
+ i += strlen(ge_raw + i) + 1;
+ ge_var[ge_var_cnt].val = strchr(ge_var[ge_var_cnt].key, '=');
+ if (ge_var[ge_var_cnt].val != NULL) {
+ ge_var[ge_var_cnt].val[0] = '\0';
+ ge_var[ge_var_cnt].val++;
+ ge_var_cnt++;
+ } else {
+ fprintf(stderr, "warning: skipping non-assignment line: \"%s\"\n",
+ ge_var[ge_var_cnt].key);
+ }
+ }
+ return 0;
+}
+
+
+int
+ga_getenv(const char *key)
+{
+ int i;
+
+ if (fw_cfg_grab_guestenv() != 0) {
+ return EXIT_FAILURE;
+ }
+
+ /* last value for key "wins" */
+ for (i = ge_var_cnt - 1; i >= 0; i--) {
+ if (strcmp(key, ge_var[i].key) == 0) {
+ printf("%s\n", ge_var[i].val);
+ return 0;
+ }
+ }
+
+ fprintf(stderr, "Key \"%s\" not found\n", key);
+ return EXIT_FAILURE;;
+}
diff --git a/qga/guest-agent-core.h b/qga/guest-agent-core.h
index e92c6ab..ef54b45 100644
--- a/qga/guest-agent-core.h
+++ b/qga/guest-agent-core.h
@@ -41,3 +41,5 @@ int64_t ga_get_fd_handle(GAState *s, Error **errp);
#ifndef _WIN32
void reopen_fd_to_null(int fd);
#endif
+
+int ga_getenv(const char *key);
diff --git a/qga/main.c b/qga/main.c
index 9939a2b..9159244 100644
--- a/qga/main.c
+++ b/qga/main.c
@@ -215,6 +215,8 @@ static void usage(const char *cmd)
#endif
" -b, --blacklist comma-separated list of RPCs to disable (no spaces, \"?\"\n"
" to list available RPCs)\n"
+" -g, --getenv display the value of a given guest environment variable\n"
+" (passed into the guest via the -guestenv \"name=value\" option\n"
" -h, --help display this help and exit\n"
"\n"
"Report bugs to <mdroth@linux.vnet.ibm.com>\n"
@@ -923,7 +925,7 @@ static void ga_print_cmd(QmpCommand *cmd, void *opaque)
int main(int argc, char **argv)
{
- const char *sopt = "hVvdm:p:l:f:F::b:s:t:";
+ const char *sopt = "hVvdm:p:l:f:F::b:s:t:g:";
const char *method = NULL, *path = NULL;
const char *log_filepath = NULL;
const char *pid_filepath;
@@ -951,6 +953,7 @@ int main(int argc, char **argv)
{ "service", 1, NULL, 's' },
#endif
{ "statedir", 1, NULL, 't' },
+ { "getenv", 1, NULL, 'g' },
{ NULL, 0, NULL, 0 }
};
int opt_ind = 0, ch, daemonize = 0, i, j, len;
@@ -1042,6 +1045,8 @@ int main(int argc, char **argv)
}
break;
#endif
+ case 'g':
+ return ga_getenv(optarg);
case 'h':
usage(argv[0]);
return 0;
--
2.1.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [Qemu-devel] [RFC PATCH 0/2] host->guest environment variables via fw_cfg
2015-02-24 19:43 [Qemu-devel] [RFC PATCH 0/2] host->guest environment variables via fw_cfg Gabriel L. Somlo
2015-02-24 19:43 ` [Qemu-devel] [RFC PATCH 1/2] fw_cfg: Add -guestenv qemu command line option Gabriel L. Somlo
2015-02-24 19:43 ` [Qemu-devel] [RFC PATCH 2/2] qga: add --getenv option to get env. vars from fw_cfg Gabriel L. Somlo
@ 2015-02-24 23:07 ` Laszlo Ersek
2015-02-25 22:40 ` Matt Fleming
2 siblings, 1 reply; 10+ messages in thread
From: Laszlo Ersek @ 2015-02-24 23:07 UTC (permalink / raw)
To: Gabriel L. Somlo, qemu-devel; +Cc: Matt Fleming, mdroth, rjones
On 02/24/15 20:43, Gabriel L. Somlo wrote:
> Following up on a conversation I started a few weeks ago, regarding the
> ability to pass "environment variables" into a guest from the host, via
> the qemu command line, and without further requirements for guest- or
> host-initiated synchronization at a later time, after the act of firing
> up the guest.
>
> I remember we had discussed SMBIOS as one of the options for "transport"
> of environment data; but then I thought that's still not "out-of-band"
> enough for my taste. Seeing how SMBIOS is passed in via fw_cfg, I thought
> the latter would be the perfect OOB transport mechanism for this type of
> data. Except instead of being consumed by the BIOS on the booting guest,
> the actual guest OS can read the data from fw_cfg directly.
>
> So, I added a -guestenv qemu command line option on the host side (1/2)
> which results in the creation of a fw_cfg blob named "etc/guestenv",
> and added --getenv to qga on the guest side (2/2), to query fw_cfg and
> return the value of such an environment variable, if available.
>
> Right now it's only used with i386, but there's no reason why it couldn't
> work with any guest type that uses fw_cfg.
>
> Currently it's pretty bare bones (e.g. I'm not checking for duplicate keys
> on the host side, in fact I'm not even checking whether there's an '='
> character present in each -guestenv argument). I just wanted to get something
> out there for folks to look at, and get feedback on whether this might be
> of interest.
>
> The most important aspect of this feature is its asynchronous nature, i.e.
> we can pass data into the guest without having to bring up a subsequent
> connection (host->guest or guest-host) at a later time, with all the
> failure-mode handling and coordination that would entail.
>
> A couple of extra random thoughts:
>
> On the host side, it might be interesting/useful to allow a random file
> to be inserted into fw_cfg with a given fw_cfg name (something like
> "-fwcfgblob name=foo,file=./bar"), where ./bar could be anything, even
> a shell script or a text file with key=value pairs :)
>
> On the guest side, I think it might be cool to write a device driver for
> fw_cfg, something that would allow reading fw_cfg blobs via
> /sys/firmware/fw_cfg/... I think just reading the fw_cfg data port as root
> is more portable (userspace code shipping with qga vs. kernel device driver),
> which is why I haven't started coding on this already :) Not to mention I
> don't know where I'd start with the equivalent driver for Windows :)
>
>
> Anyhow, please let me know what you all think, and how it could be made
> more compelling to the qemu community at large.
>
>
> Thanks,
> --Gabriel
>
> Gabriel L. Somlo (2):
> fw_cfg: Add -guestenv qemu command line option
> qga: add --getenv option to get env. vars from fw_cfg
>
> hw/i386/pc.c | 4 ++
> include/sysemu/sysemu.h | 3 ++
> qemu-options.hx | 9 ++++
> qga/Makefile.objs | 1 +
> qga/getenv.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++++
> qga/guest-agent-core.h | 2 +
> qga/main.c | 7 ++-
> vl.c | 14 +++++
> 8 files changed, 179 insertions(+), 1 deletion(-)
> create mode 100644 qga/getenv.c
>
Matt Fleming was thinking about something like this, I'm CC'ing him.
Thanks
Laszlo
^ permalink raw reply [flat|nested] 10+ messages in thread