---
.gitignore | 1 +
Makefile.am | 2 +-
common/qemuopts/Makefile.am | 46 ++
common/qemuopts/qemuopts-tests.c | 226 ++++++++++
common/qemuopts/qemuopts.c | 952 +++++++++++++++++++++++++++++++++++++++
common/qemuopts/qemuopts.h | 47 ++
configure.ac | 1 +
7 files changed, 1274 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 152a400..d9a3d6d 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 6c072a1..53014e2 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 0000000..ff643be
--- /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 0000000..b4e7bcc
--- /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 <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#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 0000000..acdfda8
--- /dev/null
+++ b/common/qemuopts/qemuopts.c
@@ -0,0 +1,952 @@
+/* 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#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<qemuopts_free>.
+ *
+ * Returns C<NULL> on error, setting C<errno>.
+ */
+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<errno>.
+ */
+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<qemuopts_add_arg_list>.
+ *
+ * Returns C<0> on success. Returns C<-1> on error, setting C<errno>.
+ */
+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<qemuopts_add_arg_list>.
+ *
+ * Returns C<0> on success. Returns C<-1> on error, setting C<errno>.
+ */
+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<qemuopts_add_arg> except that no quoting is done on
+ * the value.
+ *
+ * For C<qemuopts_to_script> and C<qemuopts_to_channel>, this
+ * means that neither shell quoting nor qemu comma quoting is done
+ * on the value.
+ *
+ * For C<qemuopts_to_argv> this means that qemu comma quoting is
+ * not done.
+ *
+ * C<qemuopts_to_config*> 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<qemuopts_add_arg_list> for a way to do simple cases in
+ * one call.
+ *
+ * Returns C<0> on success. Returns C<-1> on error, setting C<errno>.
+ */
+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<errno>.
+ */
+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<errno>.
+ */
+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<qemu-system-[arch]>.
+ *
+ * As a special case if C<arch> is C<NULL>, 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<errno>.
+ */
+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<qemuopts_set_binary*> 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<errno>.
+ */
+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<str> to C<fp>, 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<str> to C<fp> 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<FILE *fp>.
+ *
+ * C<qemuopts_set_binary*> 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<errno>.
+ */
+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<execv(3)>.
+ *
+ * C<qemuopts_set_binary*> must be called first. It will be
+ * returned as C<argv[0]> in the returned list.
+ *
+ * The list of strings and the strings themselves must be freed by the
+ * caller.
+ *
+ * Returns C<NULL> on error, setting C<errno>.
+ */
+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<qemu -readconfig filename>.
+ *
+ * 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<https://habkost.net/posts/2016/12/qemu-apis-qemuopts.html>
+ *
L<https://bugs.launchpad.net/qemu/+bug/1686364>
+ *
+ * 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<not> 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<errno>.
+ */
+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<qemuopts_to_config_file>, but this writes to a C<FILE *fp>.
+ */
+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<qemuopts_to_config_file> 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 0000000..7a7818b
--- /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 <stdarg.h>
+
+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 d47fe48..4b466f5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -187,6 +187,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
--
2.9.3