From 581c4bcc1d07e601b690877489eb3add3397be38 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Wed, 26 Apr 2017 15:34:06 +0100 Subject: [PATCH] common: Add a simple mini-library for handling qemu command and config files. --- .gitignore | 1 + Makefile.am | 2 +- common/qemuopts/Makefile.am | 46 ++ common/qemuopts/qemuopts-tests.c | 226 +++++++ common/qemuopts/qemuopts.c | 991 +++++++++++++++++++++++++++++++ common/qemuopts/qemuopts.h | 47 ++ configure.ac | 1 + 7 files changed, 1313 insertions(+), 1 deletion(-) create mode 100644 common/qemuopts/Makefile.am create mode 100644 common/qemuopts/qemuopts-tests.c create mode 100644 common/qemuopts/qemuopts.c create mode 100644 common/qemuopts/qemuopts.h diff --git a/.gitignore b/.gitignore index 89638f51b..69e1ae160 100644 --- a/.gitignore +++ b/.gitignore @@ -127,6 +127,7 @@ Makefile.in /common/protocol/guestfs_protocol.c /common/protocol/guestfs_protocol.h /common/protocol/guestfs_protocol.x +/common/qemuopts/qemuopts-tests /common/utils/guestfs-internal-frontend-cleanups.h /common/utils/structs-cleanup.c /common/utils/structs-print.c diff --git a/Makefile.am b/Makefile.am index 209e3b511..ae77cdda2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -38,7 +38,7 @@ SUBDIRS += gnulib/tests endif # Basic source for the library. -SUBDIRS += common/errnostring common/protocol common/utils +SUBDIRS += common/errnostring common/protocol common/qemuopts common/utils SUBDIRS += lib docs examples po # The daemon and the appliance. diff --git a/common/qemuopts/Makefile.am b/common/qemuopts/Makefile.am new file mode 100644 index 000000000..ff643bef7 --- /dev/null +++ b/common/qemuopts/Makefile.am @@ -0,0 +1,46 @@ +# libguestfs +# Copyright (C) 2017 Red Hat Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +include $(top_srcdir)/subdir-rules.mk + +noinst_LTLIBRARIES = libqemuopts.la + +libqemuopts_la_SOURCES = \ + qemuopts.c \ + qemuopts.h +libqemuopts_la_CPPFLAGS = \ + -I$(srcdir) -I. +libqemuopts_la_CFLAGS = \ + $(WARN_CFLAGS) $(WERROR_CFLAGS) \ + $(GCC_VISIBILITY_HIDDEN) + +TESTS_ENVIRONMENT = $(top_builddir)/run --test +LOG_COMPILER = $(VG) +TESTS = qemuopts-tests + +check_PROGRAMS = qemuopts-tests + +qemuopts_tests_SOURCES = qemuopts-tests.c +qemuopts_tests_CPPFLAGS = \ + -I$(srcdir) -I. +qemuopts_tests_CFLAGS = \ + $(WARN_CFLAGS) $(WERROR_CFLAGS) +qemuopts_tests_LDADD = \ + libqemuopts.la + +check-valgrind: + make VG="@VG@" check diff --git a/common/qemuopts/qemuopts-tests.c b/common/qemuopts/qemuopts-tests.c new file mode 100644 index 000000000..b4e7bcc48 --- /dev/null +++ b/common/qemuopts/qemuopts-tests.c @@ -0,0 +1,226 @@ +/* libguestfs + * Copyright (C) 2014-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * Unit tests of internal functions. + * + * These tests may use a libguestfs handle, but must not launch the + * handle. Also, avoid long-running tests. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "qemuopts.h" + +#define CHECK_ERROR(r,call,expr) \ + do { \ + if ((expr) == (r)) { \ + perror (call); \ + exit (EXIT_FAILURE); \ + } \ + } while (0) + +int +main (int argc, char *argv[]) +{ + struct qemuopts *qopts; + FILE *fp; + char *actual; + size_t i, len; + char **actual_argv; + + qopts = qemuopts_create (); + + if (qemuopts_set_binary_by_arch (qopts, NULL) == -1) { + if (errno == ENXIO) { + fprintf (stderr, "qemuopts: This architecture does not support KVM.\n"); + fprintf (stderr, "If this architecture *does* support KVM, then please modify qemuopts.c\n"); + fprintf (stderr, "and send us a patch.\n"); + exit (77); /* Skip the test. */ + } + perror ("qemuopts_set_binary_by_arch"); + exit (EXIT_FAILURE); + } + /* ... but for the purposes of testing, it's easier if we + * set this to a known string. + */ + CHECK_ERROR (-1, "qemuopts_set_binary", + qemuopts_set_binary (qopts, "qemu-system-x86_64")); + + CHECK_ERROR (-1, "qemuopts_add_flag", + qemuopts_add_flag (qopts, "-nodefconfig")); + CHECK_ERROR (-1, "qemuopts_add_arg", + qemuopts_add_arg (qopts, "-m", "1024")); + CHECK_ERROR (-1, "qemuopts_add_arg_format", + qemuopts_add_arg_format (qopts, "-smp", "%d", 4)); + + CHECK_ERROR (-1, "qemuopts_start_arg_list", + qemuopts_start_arg_list (qopts, "-drive")); + CHECK_ERROR (-1, "qemuopts_append_arg_list", + qemuopts_append_arg_list (qopts, "file=/tmp/foo")); + CHECK_ERROR (-1, "qemuopts_append_arg_list_format", + qemuopts_append_arg_list_format (qopts, "if=%s", "ide")); + CHECK_ERROR (-1, "qemuopts_end_arg_list", + qemuopts_end_arg_list (qopts)); + CHECK_ERROR (-1, "qemuopts_add_arg_list", + qemuopts_add_arg_list (qopts, "-drive", + "file=/tmp/bar", "serial=123", + NULL)); + + /* Test qemu comma-quoting. */ + CHECK_ERROR (-1, "qemuopts_add_arg", + qemuopts_add_arg (qopts, "-name", "foo,bar")); + CHECK_ERROR (-1, "qemuopts_add_arg_list", + qemuopts_add_arg_list (qopts, "-drive", + "file=comma,in,name", + "serial=$dollar$", + NULL)); + + /* Test shell quoting. */ + CHECK_ERROR (-1, "qemuopts_add_arg", + qemuopts_add_arg (qopts, "-cdrom", "\"$quoted\".iso")); + + fp = open_memstream (&actual, &len); + if (fp == NULL) { + perror ("open_memstream"); + exit (EXIT_FAILURE); + } + CHECK_ERROR (-1, "qemuopts_to_channel", + qemuopts_to_channel (qopts, fp)); + if (fclose (fp) == EOF) { + perror ("fclose"); + exit (EXIT_FAILURE); + } + + const char *expected = + "qemu-system-x86_64 \\\n" + " -nodefconfig \\\n" + " -m 1024 \\\n" + " -smp 4 \\\n" + " -drive file=/tmp/foo,if=ide \\\n" + " -drive file=/tmp/bar,serial=123 \\\n" + " -name \"foo,,bar\" \\\n" + " -drive \"file=comma,,in,,name\",\"serial=\\$dollar\\$\" \\\n" + " -cdrom \"\\\"\\$quoted\\\".iso\"\n"; + + if (strcmp (actual, expected) != 0) { + fprintf (stderr, "qemuopts: Serialized qemu command line does not match expected\n"); + fprintf (stderr, "Actual:\n%s", actual); + fprintf (stderr, "Expected:\n%s", expected); + exit (EXIT_FAILURE); + } + + free (actual); + + /* Test qemuopts_to_argv. */ + CHECK_ERROR (NULL, "qemuopts_to_argv", + actual_argv = qemuopts_to_argv (qopts)); + const char *expected_argv[] = { + "qemu-system-x86_64", + "-nodefconfig", + "-m", "1024", + "-smp", "4", + "-drive", "file=/tmp/foo,if=ide", + "-drive", "file=/tmp/bar,serial=123", + "-name", "foo,,bar", + "-drive", "file=comma,,in,,name,serial=$dollar$", + "-cdrom", "\"$quoted\".iso", + NULL + }; + + for (i = 0; actual_argv[i] != NULL; ++i) { + if (expected_argv[i] == NULL || + strcmp (actual_argv[i], expected_argv[i])) { + fprintf (stderr, "qemuopts: actual != expected argv at position %zu, %s != %s\n", + i, actual_argv[i], expected_argv[i]); + exit (EXIT_FAILURE); + } + } + assert (expected_argv[i] == NULL); + + for (i = 0; actual_argv[i] != NULL; ++i) + free (actual_argv[i]); + free (actual_argv); + + qemuopts_free (qopts); + + /* Test qemuopts_to_config_channel. */ + qopts = qemuopts_create (); + + CHECK_ERROR (-1, "qemuopts_start_arg_list", + qemuopts_start_arg_list (qopts, "-drive")); + CHECK_ERROR (-1, "qemuopts_append_arg_list", + qemuopts_append_arg_list (qopts, "file=/tmp/foo")); + CHECK_ERROR (-1, "qemuopts_append_arg_list", + qemuopts_append_arg_list (qopts, "id=id")); + CHECK_ERROR (-1, "qemuopts_append_arg_list_format", + qemuopts_append_arg_list_format (qopts, "if=%s", "ide")); + CHECK_ERROR (-1, "qemuopts_append_arg_list_format", + qemuopts_append_arg_list_format (qopts, "bool")); + CHECK_ERROR (-1, "qemuopts_end_arg_list", + qemuopts_end_arg_list (qopts)); + CHECK_ERROR (-1, "qemuopts_add_arg_list", + qemuopts_add_arg_list (qopts, "-drive", + "file=/tmp/bar", "serial=123", + NULL)); + + fp = open_memstream (&actual, &len); + if (fp == NULL) { + perror ("open_memstream"); + exit (EXIT_FAILURE); + } + CHECK_ERROR (-1, "qemuopts_to_config_channel", + qemuopts_to_config_channel (qopts, fp)); + if (fclose (fp) == EOF) { + perror ("fclose"); + exit (EXIT_FAILURE); + } + + const char *expected2 = + "# qemu config file\n" + "\n" + "[drive \"id\"]\n" + " file = \"/tmp/foo\"\n" + " if = \"ide\"\n" + " bool = \"on\"\n" + "\n" + "[drive]\n" + " file = \"/tmp/bar\"\n" + " serial = \"123\"\n" + "\n"; + + if (strcmp (actual, expected2) != 0) { + fprintf (stderr, "qemuopts: Serialized qemu command line does not match expected\n"); + fprintf (stderr, "Actual:\n%s", actual); + fprintf (stderr, "Expected:\n%s", expected2); + exit (EXIT_FAILURE); + } + + free (actual); + + qemuopts_free (qopts); + + exit (EXIT_SUCCESS); +} diff --git a/common/qemuopts/qemuopts.c b/common/qemuopts/qemuopts.c new file mode 100644 index 000000000..5d6ac1006 --- /dev/null +++ b/common/qemuopts/qemuopts.c @@ -0,0 +1,991 @@ +/* libguestfs + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * Mini-library for writing qemu command lines and qemu config files. + * + * There are some shortcomings with the model used for qemu options + * which aren't clear until you try to convert options into a + * configuration file. However if we attempted to model the options + * in more detail then this library would be both very difficult to + * use and incompatible with older versions of qemu. Hopefully the + * current model is a decent compromise. + * + * For reference here are the problems: + * + * =over 4 + * + * =item * + * + * There's inconsistency in qemu between options and config file, eg. + * C<-smp 4> becomes: + * + * [smp-opts] + * cpus = "4" + * + * =item * + * + * Similar to the previous point, you can write either C<-smp 4> or + * C<-smp cpus=4> (although this won't work in very old qemu). When + * generating a config file you need to know the implicit key name. + * + * =item * + * + * In C<-opt key=value,...> the C is really a tree/array + * specifier. The way this works is complicated but hinted at + * here: + * L + * + * =item * + * + * Some options are syntactic sugar. eg. C<-kernel foo> is sugar + * for C<-machine kernel=foo>. + * + * =back + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qemuopts.h" + +enum qopt_type { + QOPT_FLAG, + QOPT_ARG, + QOPT_ARG_NOQUOTE, + QOPT_ARG_LIST, +}; + +struct qopt { + enum qopt_type type; + char *flag; /* eg. "-m" */ + char *value; /* Value, for QOPT_ARG, QOPT_ARG_NOQUOTE. */ + char **values; /* List of values, for QOPT_ARG_LIST. */ +}; + +struct qemuopts { + char *binary; /* NULL = qemuopts_set_binary not called yet */ + struct qopt *options; + size_t nr_options, nr_alloc; +}; + +/** + * Create an empty list of qemu options. + * + * The caller must eventually free the list by calling + * C. + * + * Returns C on error, setting C. + */ +struct qemuopts * +qemuopts_create (void) +{ + struct qemuopts *qopts; + + qopts = malloc (sizeof *qopts); + if (qopts == NULL) + return NULL; + + qopts->binary = NULL; + qopts->options = NULL; + qopts->nr_options = qopts->nr_alloc = 0; + + return qopts; +} + +static void +free_string_list (char **argv) +{ + size_t i; + + if (argv == NULL) + return; + + for (i = 0; argv[i] != NULL; ++i) + free (argv[i]); + free (argv); +} + +static size_t +count_strings (char **argv) +{ + size_t i; + + for (i = 0; argv[i] != NULL; ++i) + ; + return i; +} + +/** + * Free the list of qemu options. + */ +void +qemuopts_free (struct qemuopts *qopts) +{ + size_t i; + + for (i = 0; i < qopts->nr_options; ++i) { + free (qopts->options[i].flag); + free (qopts->options[i].value); + free_string_list (qopts->options[i].values); + } + free (qopts->options); + free (qopts->binary); + free (qopts); +} + +static struct qopt * +extend_options (struct qemuopts *qopts) +{ + struct qopt *new_options; + struct qopt *ret; + + if (qopts->nr_options >= qopts->nr_alloc) { + if (qopts->nr_alloc == 0) + qopts->nr_alloc = 1; + else + qopts->nr_alloc *= 2; + new_options = qopts->options; + new_options = realloc (new_options, + qopts->nr_alloc * sizeof (struct qopt)); + if (new_options == NULL) + return NULL; + qopts->options = new_options; + } + + ret = &qopts->options[qopts->nr_options]; + qopts->nr_options++; + + ret->type = 0; + ret->flag = NULL; + ret->value = NULL; + ret->values = NULL; + + return ret; +} + +static struct qopt * +last_option (struct qemuopts *qopts) +{ + assert (qopts->nr_options > 0); + return &qopts->options[qopts->nr_options-1]; +} + +/** + * Add a command line flag which has no argument. eg: + * + * qemuopts_add_flag (qopts, "-nodefconfig"); + * + * Returns C<0> on success. Returns C<-1> on error, setting C. + */ +int +qemuopts_add_flag (struct qemuopts *qopts, const char *flag) +{ + struct qopt *qopt; + char *flag_copy; + + if (flag[0] != '-') { + errno = EINVAL; + return -1; + } + + flag_copy = strdup (flag); + if (flag_copy == NULL) + return -1; + + if ((qopt = extend_options (qopts)) == NULL) { + free (flag_copy); + return -1; + } + + qopt->type = QOPT_FLAG; + qopt->flag = flag_copy; + return 0; +} + +/** + * Add a command line flag which has a single argument. eg: + * + * qemuopts_add_arg (qopts, "-m", "1024"); + * + * Don't use this if the argument is a comma-separated list, since + * quoting will not be done properly. See C. + * + * Returns C<0> on success. Returns C<-1> on error, setting C. + */ +int +qemuopts_add_arg (struct qemuopts *qopts, const char *flag, const char *value) +{ + struct qopt *qopt; + char *flag_copy; + char *value_copy; + + if (flag[0] != '-') { + errno = EINVAL; + return -1; + } + + flag_copy = strdup (flag); + if (flag_copy == NULL) + return -1; + + value_copy = strdup (value); + if (value_copy == NULL) { + free (flag_copy); + return -1; + } + + if ((qopt = extend_options (qopts)) == NULL) { + free (flag_copy); + free (value_copy); + return -1; + } + + qopt->type = QOPT_ARG; + qopt->flag = flag_copy; + qopt->value = value_copy; + return 0; +} + +/** + * Add a command line flag which has a single formatted argument. eg: + * + * qemuopts_add_arg_format (qopts, "-m", "%d", 1024); + * + * Don't use this if the argument is a comma-separated list, since + * quoting will not be done properly. See C. + * + * Returns C<0> on success. Returns C<-1> on error, setting C. + */ +int +qemuopts_add_arg_format (struct qemuopts *qopts, const char *flag, + const char *fs, ...) +{ + char *value; + int r; + va_list args; + + if (flag[0] != '-') { + errno = EINVAL; + return -1; + } + + va_start (args, fs); + r = vasprintf (&value, fs, args); + va_end (args); + if (r == -1) + return -1; + + r = qemuopts_add_arg (qopts, flag, value); + free (value); + return r; +} + +/** + * This is like C except that no quoting is done on + * the value. + * + * For C and C, this + * means that neither shell quoting nor qemu comma quoting is done + * on the value. + * + * For C this means that qemu comma quoting is + * not done. + * + * C will fail. + * + * You should use this with great care. + */ +int +qemuopts_add_arg_noquote (struct qemuopts *qopts, const char *flag, + const char *value) +{ + struct qopt *qopt; + char *flag_copy; + char *value_copy; + + if (flag[0] != '-') { + errno = EINVAL; + return -1; + } + + flag_copy = strdup (flag); + if (flag_copy == NULL) + return -1; + + value_copy = strdup (value); + if (value_copy == NULL) { + free (flag_copy); + return -1; + } + + if ((qopt = extend_options (qopts)) == NULL) { + free (flag_copy); + free (value_copy); + return -1; + } + + qopt->type = QOPT_ARG_NOQUOTE; + qopt->flag = flag_copy; + qopt->value = value_copy; + return 0; +} + +/** + * Start an argument that takes a comma-separated list of fields. + * + * Typical usage is like this (with error handling omitted): + * + * qemuopts_start_arg_list (qopts, "-drive"); + * qemuopts_append_arg_list (qopts, "file=foo"); + * qemuopts_append_arg_list_format (qopts, "if=%s", "ide"); + * qemuopts_end_arg_list (qopts); + * + * which would construct C<-drive file=foo,if=ide> + * + * See also C for a way to do simple cases in + * one call. + * + * Returns C<0> on success. Returns C<-1> on error, setting C. + */ +int +qemuopts_start_arg_list (struct qemuopts *qopts, const char *flag) +{ + struct qopt *qopt; + char *flag_copy; + char **values; + + if (flag[0] != '-') { + errno = EINVAL; + return -1; + } + + flag_copy = strdup (flag); + if (flag_copy == NULL) + return -1; + + values = calloc (1, sizeof (char *)); + if (values == NULL) { + free (flag_copy); + return -1; + } + + if ((qopt = extend_options (qopts)) == NULL) { + free (flag_copy); + free (values); + return -1; + } + + qopt->type = QOPT_ARG_LIST; + qopt->flag = flag_copy; + qopt->values = values; + return 0; +} + +int +qemuopts_append_arg_list (struct qemuopts *qopts, const char *value) +{ + struct qopt *qopt; + char **new_values; + char *value_copy; + size_t len; + + qopt = last_option (qopts); + assert (qopt->type == QOPT_ARG_LIST); + len = count_strings (qopt->values); + + value_copy = strdup (value); + if (value_copy == NULL) + return -1; + + new_values = qopt->values; + new_values = realloc (new_values, (len+2) * sizeof (char *)); + if (new_values == NULL) { + free (value_copy); + return -1; + } + qopt->values = new_values; + qopt->values[len] = value_copy; + qopt->values[len+1] = NULL; + return 0; +} + +int +qemuopts_append_arg_list_format (struct qemuopts *qopts, + const char *fs, ...) +{ + char *value; + int r; + va_list args; + + va_start (args, fs); + r = vasprintf (&value, fs, args); + va_end (args); + if (r == -1) + return -1; + + r = qemuopts_append_arg_list (qopts, value); + free (value); + return r; +} + +int +qemuopts_end_arg_list (struct qemuopts *qopts) +{ + /* Nothing to do, the list is already well-formed. */ + return 0; +} + +/** + * Add a command line flag which has a list of arguments. eg: + * + * qemuopts_add_arg_list (qopts, "-drive", "file=foo", "if=ide", NULL); + * + * This is turned into a comma-separated list, like: + * C<-drive file=foo,if=ide>. Note that this handles qemu quoting + * properly, so individual elements may contain commas and this will + * do the right thing. + * + * Returns C<0> on success. Returns C<-1> on error, setting C. + */ +int +qemuopts_add_arg_list (struct qemuopts *qopts, const char *flag, + const char *elem0, ...) +{ + va_list args; + const char *elem; + + if (qemuopts_start_arg_list (qopts, flag) == -1) + return -1; + if (qemuopts_append_arg_list (qopts, elem0) == -1) + return -1; + va_start (args, elem0); + elem = va_arg (args, const char *); + while (elem != NULL) { + if (qemuopts_append_arg_list (qopts, elem) == -1) { + va_end (args); + return -1; + } + elem = va_arg (args, const char *); + } + va_end (args); + if (qemuopts_end_arg_list (qopts) == -1) + return -1; + return 0; +} + +/** + * Set the qemu binary name. + * + * Returns C<0> on success. Returns C<-1> on error, setting C. + */ +int +qemuopts_set_binary (struct qemuopts *qopts, const char *binary) +{ + char *binary_copy; + + binary_copy = strdup (binary); + if (binary_copy == NULL) + return -1; + + free (qopts->binary); + qopts->binary = binary_copy; + return 0; +} + +/** + * Set the qemu binary name to C. + * + * As a special case if C is C, the binary is set to the + * KVM binary for the current host architecture: + * + * qemuopts_set_binary_by_arch (qopts, NULL); + * + * Returns C<0> on success. Returns C<-1> on error, setting C. + */ +int +qemuopts_set_binary_by_arch (struct qemuopts *qopts, const char *arch) +{ + char *binary; + + free (qopts->binary); + qopts->binary = NULL; + + if (arch) { + if (asprintf (&binary, "qemu-system-%s", arch) == -1) + return -1; + qopts->binary = binary; + } + else { +#if defined(__i386__) || defined(__x86_64__) + binary = strdup ("qemu-system-x86_64"); +#elif defined(__aarch64__) + binary = strdup ("qemu-system-aarch64"); +#elif defined(__arm__) + binary = strdup ("qemu-system-arm"); +#elif defined(__powerpc64__) || defined(__powerpc64le__) + binary = strdup ("qemu-system-ppc64"); +#elif defined(__s390x__) + binary = strdup ("qemu-system-s390x"); +#else + /* There is no KVM capability on this architecture. */ + errno = ENXIO; + binary = NULL; +#endif + if (binary == NULL) + return -1; + qopts->binary = binary; + } + + return 0; +} + +/** + * Write the qemu options to a script. + * + * C must be called first. + * + * The script file will start with C<#!/bin/sh> and will be chmod to + * mode C<0755>. + * + * Returns C<0> on success. Returns C<-1> on error, setting C. + */ +int +qemuopts_to_script (struct qemuopts *qopts, const char *filename) +{ + FILE *fp; + int saved_errno; + + fp = fopen (filename, "w"); + if (fp == NULL) + return -1; + + fprintf (fp, "#!/bin/sh -\n\n"); + if (qemuopts_to_channel (qopts, fp) == -1) { + error: + saved_errno = errno; + fclose (fp); + unlink (filename); + errno = saved_errno; + return -1; + } + + if (fchmod (fileno (fp), 0755) == -1) + goto error; + + if (fclose (fp) == EOF) { + saved_errno = errno; + unlink (filename); + errno = saved_errno; + return -1; + } + + return 0; +} + +/** + * Print C to C, shell-quoting it if necessary. + */ +static void +shell_quote (const char *str, FILE *fp) +{ + const char *safe_chars = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_=,:/"; + size_t i, len; + + /* If the string consists only of safe characters, output it as-is. */ + len = strlen (str); + if (len == strspn (str, safe_chars)) { + fputs (str, fp); + return; + } + + /* Double-quote the string. */ + fputc ('"', fp); + for (i = 0; i < len; ++i) { + switch (str[i]) { + case '$': case '`': case '\\': case '"': + fputc ('\\', fp); + /*FALLTHROUGH*/ + default: + fputc (str[i], fp); + } + } + fputc ('"', fp); +} + +/** + * Print C to C doing both shell and qemu comma quoting. + */ +static void +shell_and_comma_quote (const char *str, FILE *fp) +{ + const char *safe_chars = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_=:/"; + size_t i, len; + + /* If the string consists only of safe characters, output it as-is. */ + len = strlen (str); + if (len == strspn (str, safe_chars)) { + fputs (str, fp); + return; + } + + fputc ('"', fp); + for (i = 0; i < len; ++i) { + switch (str[i]) { + case ',': + /* qemu comma-quoting doubles commas. */ + fputs (",,", fp); + break; + case '$': case '`': case '\\': case '"': + fputc ('\\', fp); + /*FALLTHROUGH*/ + default: + fputc (str[i], fp); + } + } + fputc ('"', fp); +} + +/** + * Write the qemu options to a C. + * + * C must be called first. + * + * Only the qemu command line is written. The caller may need to add + * C<#!/bin/sh> and may need to chmod the resulting file to C<0755>. + * + * Returns C<0> on success. Returns C<-1> on error, setting C. + */ +int +qemuopts_to_channel (struct qemuopts *qopts, FILE *fp) +{ + size_t i, j; + const char *nl = " \\\n "; + + if (qopts->binary == NULL) { + errno = ENOENT; + return -1; + } + + shell_quote (qopts->binary, fp); + for (i = 0; i < qopts->nr_options; ++i) { + switch (qopts->options[i].type) { + case QOPT_FLAG: + fprintf (fp, "%s%s", nl, qopts->options[i].flag); + break; + + case QOPT_ARG_NOQUOTE: + fprintf (fp, "%s%s %s", + nl, qopts->options[i].flag, qopts->options[i].value); + break; + + case QOPT_ARG: + fprintf (fp, "%s%s ", + nl, qopts->options[i].flag); + shell_and_comma_quote (qopts->options[i].value, fp); + break; + + case QOPT_ARG_LIST: + fprintf (fp, "%s%s ", + nl, qopts->options[i].flag); + for (j = 0; qopts->options[i].values[j] != NULL; ++j) { + if (j > 0) fputc (',', fp); + shell_and_comma_quote (qopts->options[i].values[j], fp); + } + break; + } + } + fputc ('\n', fp); + + return 0; +} + +/** + * Return a NULL-terminated argument list, of the kind that can be + * passed directly to L. + * + * C must be called first. It will be + * returned as C in the returned list. + * + * The list of strings and the strings themselves must be freed by the + * caller. + * + * Returns C on error, setting C. + */ +char ** +qemuopts_to_argv (struct qemuopts *qopts) +{ + char **ret, **values; + size_t n, i, j, k, len; + + if (qopts->binary == NULL) { + errno = ENOENT; + return NULL; + } + + /* Count how many arguments we will return. It's not the same as + * the number of options because some options are flags (returning a + * single string) and others have a parameter (two strings). + */ + n = 1; /* for the qemu binary */ + for (i = 0; i < qopts->nr_options; ++i) { + switch (qopts->options[i].type) { + case QOPT_FLAG: + n++; + break; + + case QOPT_ARG_NOQUOTE: + case QOPT_ARG: + case QOPT_ARG_LIST: + n += 2; + } + } + + ret = calloc (n+1, sizeof (char *)); + if (ret == NULL) + return NULL; + + n = 0; + ret[n] = strdup (qopts->binary); + if (ret[n] == NULL) { + error: + for (i = 0; i < n; ++i) + free (ret[i]); + free (ret); + return NULL; + } + n++; + + for (i = 0; i < qopts->nr_options; ++i) { + ret[n] = strdup (qopts->options[i].flag); + if (ret[n] == NULL) goto error; + n++; + + switch (qopts->options[i].type) { + case QOPT_FLAG: + /* nothing */ + break; + + case QOPT_ARG_NOQUOTE: + ret[n] = strdup (qopts->options[i].value); + if (ret[n] == NULL) goto error; + n++; + break; + + case QOPT_ARG: + /* We only have to do comma-quoting here. */ + len = 0; + for (k = 0; k < strlen (qopts->options[i].value); ++k) { + if (qopts->options[i].value[k] == ',') len++; + len++; + } + ret[n] = malloc (len+1); + if (ret[n] == NULL) goto error; + len = 0; + for (k = 0; k < strlen (qopts->options[i].value); ++k) { + if (qopts->options[i].value[k] == ',') ret[n][len++] = ','; + ret[n][len++] = qopts->options[i].value[k]; + } + ret[n][len] = '\0'; + n++; + break; + + case QOPT_ARG_LIST: + /* We only have to do comma-quoting here. */ + values = qopts->options[i].values; + len = count_strings (values) - 1 /* one for each comma */; + for (j = 0; values[j] != NULL; ++j) { + for (k = 0; k < strlen (values[j]); ++k) { + if (values[j][k] == ',') len++; + len++; + } + } + ret[n] = malloc (len+1); + if (ret[n] == NULL) goto error; + len = 0; + for (j = 0; values[j] != NULL; ++j) { + if (j > 0) ret[n][len++] = ','; + for (k = 0; k < strlen (values[j]); ++k) { + if (values[j][k] == ',') ret[n][len++] = ','; + ret[n][len++] = values[j][k]; + } + } + ret[n][len] = '\0'; + n++; + } + } + + return ret; +} + +/** + * Write the qemu options to a qemu config file, suitable for reading + * in using C. + * + * Note that qemu config files have limitations on content and + * quoting, so not all qemuopts structs can be written (this function + * returns an error in these cases). For more information see + * L + * L + * + * Also, command line argument names and config file sections + * sometimes have different names. For example the equivalent of + * C<-m 1024> is: + * + * [memory] + * size = "1024" + * + * This code does I attempt to convert between the two forms. + * You just need to know how to do that yourself. + * + * Returns C<0> on success. Returns C<-1> on error, setting C. + */ +int +qemuopts_to_config_file (struct qemuopts *qopts, const char *filename) +{ + FILE *fp; + int saved_errno; + + fp = fopen (filename, "w"); + if (fp == NULL) + return -1; + + if (qemuopts_to_config_channel (qopts, fp) == -1) { + saved_errno = errno; + fclose (fp); + unlink (filename); + errno = saved_errno; + return -1; + } + + if (fclose (fp) == EOF) { + saved_errno = errno; + unlink (filename); + errno = saved_errno; + return -1; + } + + return 0; +} + +/** + * Same as C, but this writes to a C. + */ +int +qemuopts_to_config_channel (struct qemuopts *qopts, FILE *fp) +{ + size_t i, j, k; + ssize_t id_param; + char **values; + + /* Before starting, try to detect some illegal options which + * cannot be translated into a qemu config file. + */ + for (i = 0; i < qopts->nr_options; ++i) { + switch (qopts->options[i].type) { + case QOPT_FLAG: + /* Single flags cannot be written to a config file. It seems + * as if the file format simply does not support this notion. + */ + errno = EINVAL; + return -1; + + case QOPT_ARG_NOQUOTE: + /* arg_noquote is incompatible with this function. */ + errno = EINVAL; + return -1; + + case QOPT_ARG: + /* Single arguments can be expressed, but we would have to do + * special translation as outlined in the description of + * C above. + */ + errno = EINVAL; + return -1; + + case QOPT_ARG_LIST: + /* If any value contains a double quote character, then qemu + * cannot parse it. See + * https://bugs.launchpad.net/qemu/+bug/1686364. + */ + values = qopts->options[i].values; + for (j = 0; values[j] != NULL; ++j) { + if (strchr (values[j], '"') != NULL) { + errno = EINVAL; + return -1; + } + } + break; + } + } + + /* Write the output. */ + fprintf (fp, "# qemu config file\n\n"); + + for (i = 0; i < qopts->nr_options; ++i) { + switch (qopts->options[i].type) { + case QOPT_FLAG: + case QOPT_ARG_NOQUOTE: + case QOPT_ARG: + abort (); + + case QOPT_ARG_LIST: + values = qopts->options[i].values; + /* The id=... parameter is special. */ + id_param = -1; + for (j = 0; values[j] != NULL; ++j) { + if (strncmp (values[j], "id=", 2) == 0) { + id_param = j; + break; + } + } + + if (id_param >= 0) + fprintf (fp, "[%s \"%s\"]\n", + &qopts->options[i].flag[1], + &values[id_param][3]); + else + fprintf (fp, "[%s]\n", &qopts->options[i].flag[1]); + + for (j = 0; values[j] != NULL; ++j) { + if ((ssize_t) j != id_param) { + k = strcspn (values[j], "="); + if (k < strlen (values[j])) { + fprintf (fp, " %.*s = ", (int) k, values[j]); + fprintf (fp, "\"%s\"\n", &values[j][k+1]); + } + else + fprintf (fp, " %s = \"on\"\n", values[j]); + } + } + } + fprintf (fp, "\n"); + } + + return 0; +} diff --git a/common/qemuopts/qemuopts.h b/common/qemuopts/qemuopts.h new file mode 100644 index 000000000..7a7818b65 --- /dev/null +++ b/common/qemuopts/qemuopts.h @@ -0,0 +1,47 @@ +/* libguestfs + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* See qemuopts.c for documentation on how to use the library. */ + +#ifndef QEMUOPTS_H_ +#define QEMUOPTS_H_ + +#include + +struct qemuopts; + +extern struct qemuopts *qemuopts_create (void); +extern void qemuopts_free (struct qemuopts *qopts); +extern int qemuopts_add_flag (struct qemuopts *qopts, const char *flag); +extern int qemuopts_add_arg (struct qemuopts *qopts, const char *flag, const char *value); +extern int qemuopts_add_arg_format (struct qemuopts *qopts, const char *flag, const char *fs, ...) __attribute__((format (printf,3,4))); +extern int qemuopts_add_arg_noquote (struct qemuopts *qopts, const char *flag, const char *value); +extern int qemuopts_start_arg_list (struct qemuopts *qopts, const char *flag); +extern int qemuopts_append_arg_list (struct qemuopts *qopts, const char *value); +extern int qemuopts_append_arg_list_format (struct qemuopts *qopts, const char *fs, ...) __attribute__((format (printf,2,3))); +extern int qemuopts_end_arg_list (struct qemuopts *qopts); +extern int qemuopts_add_arg_list (struct qemuopts *qopts, const char *flag, const char *elem0, ...) __attribute__((sentinel)); +extern int qemuopts_set_binary (struct qemuopts *qopts, const char *binary); +extern int qemuopts_set_binary_by_arch (struct qemuopts *qopts, const char *arch); +extern int qemuopts_to_script (struct qemuopts *qopts, const char *filename); +extern int qemuopts_to_channel (struct qemuopts *qopts, FILE *fp); +extern char **qemuopts_to_argv (struct qemuopts *qopts); +extern int qemuopts_to_config_file (struct qemuopts *qopts, const char *filename); +extern int qemuopts_to_config_channel (struct qemuopts *qopts, FILE *fp); + +#endif /* QEMUOPTS_H_ */ diff --git a/configure.ac b/configure.ac index a8d812714..17668ff0b 100644 --- a/configure.ac +++ b/configure.ac @@ -189,6 +189,7 @@ AC_CONFIG_FILES([Makefile common/parallel/Makefile common/progress/Makefile common/protocol/Makefile + common/qemuopts/Makefile common/utils/Makefile common/visit/Makefile common/windows/Makefile