[Libguestfs] [PATCH 1/4] common: Add a simple mini-library for handling qemu command and config files.

Richard W.M. Jones rjones at redhat.com
Thu Apr 27 15:03:24 UTC 2017


---
 .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




More information about the Libguestfs mailing list