common: Add a simple mini-library for handling qemu command and config files.

This commit is contained in:
Richard W.M. Jones
2017-04-26 15:34:06 +01:00
parent b4728fd004
commit 581c4bcc1d
7 changed files with 1313 additions and 1 deletions

1
.gitignore vendored
View File

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

View File

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

View File

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

View File

@@ -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);
}

991
common/qemuopts/qemuopts.c Normal file
View File

@@ -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<key> is really a tree/array
* specifier. The way this works is complicated but hinted at
* here:
* L<http://git.qemu.org/?p=qemu.git;a=blob;f=util/keyval.c;h=93d5db6b590427e412dfb172f1c406d6dd8958c1;hb=HEAD>
*
* =item *
*
* Some options are syntactic sugar. eg. C<-kernel foo> is sugar
* for C<-machine kernel=foo>.
*
* =back
*/
#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;
}

View File

@@ -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_ */

View File

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