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 <so...@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 <so...@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 <mdr...@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