mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-11-01 10:51:12 +00:00
78d30aa0bf
qemuxml2argv test suite is way more comprehensive than the hotplug suite. Since we share the code paths for monitor and command line hotplug we can easily test the properties of devices against the QAPI schema. To achieve this we'll need to skip the JSON->commandline conversion for the test run so that we can analyze the pure properties. This patch adds flags for the comand line generator and hook them into the JSON->commandline convertor for -netdev. An upcoming patch will make use of this new infrastructure. Signed-off-by: Peter Krempa <pkrempa@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com>
449 lines
14 KiB
C
449 lines
14 KiB
C
/*
|
|
* virqemu.c: utilities for working with qemu and its tools
|
|
*
|
|
* Copyright (C) 2016 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.1 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, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
|
|
#include <config.h>
|
|
|
|
#include "virbuffer.h"
|
|
#include "virerror.h"
|
|
#include "virlog.h"
|
|
#include "virqemu.h"
|
|
#include "virstring.h"
|
|
#include "viralloc.h"
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_NONE
|
|
|
|
VIR_LOG_INIT("util.qemu");
|
|
|
|
struct virQEMUCommandLineJSONIteratorData {
|
|
const char *prefix;
|
|
virBufferPtr buf;
|
|
const char *skipKey;
|
|
bool onOff;
|
|
virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc;
|
|
};
|
|
|
|
|
|
static int
|
|
virQEMUBuildCommandLineJSONRecurse(const char *key,
|
|
virJSONValuePtr value,
|
|
virBufferPtr buf,
|
|
const char *skipKey,
|
|
bool onOff,
|
|
virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc,
|
|
bool nested);
|
|
|
|
|
|
|
|
int
|
|
virQEMUBuildCommandLineJSONArrayBitmap(const char *key,
|
|
virJSONValuePtr array,
|
|
virBufferPtr buf,
|
|
const char *skipKey G_GNUC_UNUSED,
|
|
bool onOff G_GNUC_UNUSED)
|
|
{
|
|
ssize_t pos = -1;
|
|
ssize_t end;
|
|
g_autoptr(virBitmap) bitmap = NULL;
|
|
|
|
if (virJSONValueGetArrayAsBitmap(array, &bitmap) < 0)
|
|
return -1;
|
|
|
|
while ((pos = virBitmapNextSetBit(bitmap, pos)) > -1) {
|
|
if ((end = virBitmapNextClearBit(bitmap, pos)) < 0)
|
|
end = virBitmapLastSetBit(bitmap) + 1;
|
|
|
|
if (end - 1 > pos) {
|
|
virBufferAsprintf(buf, "%s=%zd-%zd,", key, pos, end - 1);
|
|
pos = end;
|
|
} else {
|
|
virBufferAsprintf(buf, "%s=%zd,", key, pos);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virQEMUBuildCommandLineJSONArrayNumbered(const char *key,
|
|
virJSONValuePtr array,
|
|
virBufferPtr buf,
|
|
const char *skipKey,
|
|
bool onOff)
|
|
{
|
|
virJSONValuePtr member;
|
|
size_t i;
|
|
|
|
for (i = 0; i < virJSONValueArraySize(array); i++) {
|
|
member = virJSONValueArrayGet((virJSONValuePtr) array, i);
|
|
g_autofree char *prefix = NULL;
|
|
|
|
prefix = g_strdup_printf("%s.%zu", key, i);
|
|
|
|
if (virQEMUBuildCommandLineJSONRecurse(prefix, member, buf, skipKey, onOff,
|
|
virQEMUBuildCommandLineJSONArrayNumbered,
|
|
true) < 0)
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* This array convertor is for quirky cases where the QMP schema mandates an
|
|
* array of objects with only one attribute 'str' which needs to be formatted as
|
|
* repeated key-value pairs without the 'str' being printed:
|
|
*
|
|
* 'guestfwd': [
|
|
* { "str": "tcp:10.0.2.1:4600-chardev:charchannel0" },
|
|
* { "str": "...."},
|
|
* ]
|
|
*
|
|
* guestfwd=tcp:10.0.2.1:4600-chardev:charchannel0,guestfwd=...
|
|
*/
|
|
static int
|
|
virQEMUBuildCommandLineJSONArrayObjectsStr(const char *key,
|
|
virJSONValuePtr array,
|
|
virBufferPtr buf,
|
|
const char *skipKey G_GNUC_UNUSED,
|
|
bool onOff G_GNUC_UNUSED)
|
|
{
|
|
g_auto(virBuffer) tmp = VIR_BUFFER_INITIALIZER;
|
|
size_t i;
|
|
|
|
for (i = 0; i < virJSONValueArraySize(array); i++) {
|
|
virJSONValuePtr member = virJSONValueArrayGet(array, i);
|
|
const char *str = virJSONValueObjectGetString(member, "str");
|
|
|
|
if (!str)
|
|
return -1;
|
|
|
|
virBufferAsprintf(&tmp, "%s=%s,", key, str);
|
|
}
|
|
|
|
virBufferAddBuffer(buf, &tmp);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* internal iterator to handle nested object formatting */
|
|
static int
|
|
virQEMUBuildCommandLineJSONIterate(const char *key,
|
|
virJSONValuePtr value,
|
|
void *opaque)
|
|
{
|
|
struct virQEMUCommandLineJSONIteratorData *data = opaque;
|
|
|
|
if (STREQ_NULLABLE(key, data->skipKey))
|
|
return 0;
|
|
|
|
if (data->prefix) {
|
|
g_autofree char *tmpkey = NULL;
|
|
|
|
tmpkey = g_strdup_printf("%s.%s", data->prefix, key);
|
|
|
|
return virQEMUBuildCommandLineJSONRecurse(tmpkey, value, data->buf,
|
|
data->skipKey, data->onOff,
|
|
data->arrayFunc, false);
|
|
} else {
|
|
return virQEMUBuildCommandLineJSONRecurse(key, value, data->buf,
|
|
data->skipKey, data->onOff,
|
|
data->arrayFunc, false);
|
|
}
|
|
}
|
|
|
|
|
|
static int
|
|
virQEMUBuildCommandLineJSONRecurse(const char *key,
|
|
virJSONValuePtr value,
|
|
virBufferPtr buf,
|
|
const char *skipKey,
|
|
bool onOff,
|
|
virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc,
|
|
bool nested)
|
|
{
|
|
struct virQEMUCommandLineJSONIteratorData data = { key, buf, skipKey, onOff, arrayFunc };
|
|
virJSONType type = virJSONValueGetType(value);
|
|
virJSONValuePtr elem;
|
|
bool tmp;
|
|
size_t i;
|
|
|
|
if (!key && type != VIR_JSON_TYPE_OBJECT) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("only JSON objects can be top level"));
|
|
return -1;
|
|
}
|
|
|
|
switch (type) {
|
|
case VIR_JSON_TYPE_STRING:
|
|
virBufferAsprintf(buf, "%s=", key);
|
|
virQEMUBuildBufferEscapeComma(buf, virJSONValueGetString(value));
|
|
virBufferAddLit(buf, ",");
|
|
break;
|
|
|
|
case VIR_JSON_TYPE_NUMBER:
|
|
virBufferAsprintf(buf, "%s=%s,", key, virJSONValueGetNumberString(value));
|
|
break;
|
|
|
|
case VIR_JSON_TYPE_BOOLEAN:
|
|
virJSONValueGetBoolean(value, &tmp);
|
|
if (onOff) {
|
|
if (tmp)
|
|
virBufferAsprintf(buf, "%s=on,", key);
|
|
else
|
|
virBufferAsprintf(buf, "%s=off,", key);
|
|
} else {
|
|
if (tmp)
|
|
virBufferAsprintf(buf, "%s=yes,", key);
|
|
else
|
|
virBufferAsprintf(buf, "%s=no,", key);
|
|
}
|
|
|
|
break;
|
|
|
|
case VIR_JSON_TYPE_ARRAY:
|
|
if (nested) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("nested JSON array to commandline conversion is "
|
|
"not supported"));
|
|
return -1;
|
|
}
|
|
|
|
if (!arrayFunc || arrayFunc(key, value, buf, skipKey, onOff) < 0) {
|
|
/* fallback, treat the array as a non-bitmap, adding the key
|
|
* for each member */
|
|
for (i = 0; i < virJSONValueArraySize(value); i++) {
|
|
elem = virJSONValueArrayGet((virJSONValuePtr)value, i);
|
|
|
|
/* recurse to avoid duplicating code */
|
|
if (virQEMUBuildCommandLineJSONRecurse(key, elem, buf, skipKey,
|
|
onOff, arrayFunc, true) < 0)
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VIR_JSON_TYPE_OBJECT:
|
|
if (virJSONValueObjectForeachKeyValue(value,
|
|
virQEMUBuildCommandLineJSONIterate,
|
|
&data) < 0)
|
|
return -1;
|
|
break;
|
|
|
|
case VIR_JSON_TYPE_NULL:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("NULL JSON type can't be converted to commandline"));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virQEMUBuildCommandLineJSON:
|
|
* @value: json object containing the value
|
|
* @buf: otuput buffer
|
|
* @skipKey: name of key that will be handled separately by caller
|
|
* @onOff: Use 'on' and 'off' for boolean values rather than 'yes' and 'no'
|
|
* @arrayFunc: array formatter function to allow for different syntax
|
|
*
|
|
* Formats JSON value object into command line parameters suitable for use with
|
|
* qemu.
|
|
*
|
|
* Returns 0 on success -1 on error.
|
|
*/
|
|
int
|
|
virQEMUBuildCommandLineJSON(virJSONValuePtr value,
|
|
virBufferPtr buf,
|
|
const char *skipKey,
|
|
bool onOff,
|
|
virQEMUBuildCommandLineJSONArrayFormatFunc array)
|
|
{
|
|
if (virQEMUBuildCommandLineJSONRecurse(NULL, value, buf, skipKey, onOff, array, false) < 0)
|
|
return -1;
|
|
|
|
virBufferTrim(buf, ",");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virQEMUBuildNetdevCommandlineFromJSON:
|
|
* @props: JSON properties describing a netdev
|
|
* @rawjson: don't transform to commandline args, but just stringify json
|
|
*
|
|
* Converts @props into arguments for -netdev including all the quirks and
|
|
* differences between the monitor and command line syntax.
|
|
*
|
|
* @rawjson is meant for testing of the schema in the xml2argvtest
|
|
*/
|
|
char *
|
|
virQEMUBuildNetdevCommandlineFromJSON(virJSONValuePtr props,
|
|
bool rawjson)
|
|
{
|
|
const char *type = virJSONValueObjectGetString(props, "type");
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
if (rawjson)
|
|
return virJSONValueToString(props, false);
|
|
|
|
virBufferAsprintf(&buf, "%s,", type);
|
|
|
|
if (virQEMUBuildCommandLineJSON(props, &buf, "type", true,
|
|
virQEMUBuildCommandLineJSONArrayObjectsStr) < 0)
|
|
return NULL;
|
|
|
|
return virBufferContentAndReset(&buf);
|
|
}
|
|
|
|
|
|
static int
|
|
virQEMUBuildObjectCommandlineFromJSONInternal(virBufferPtr buf,
|
|
const char *type,
|
|
const char *alias,
|
|
virJSONValuePtr props)
|
|
{
|
|
if (!type || !alias) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("missing 'type'(%s) or 'alias'(%s) field of QOM 'object'"),
|
|
NULLSTR(type), NULLSTR(alias));
|
|
return -1;
|
|
}
|
|
|
|
virBufferAsprintf(buf, "%s,id=%s", type, alias);
|
|
|
|
if (props) {
|
|
virBufferAddLit(buf, ",");
|
|
if (virQEMUBuildCommandLineJSON(props, buf, NULL, false,
|
|
virQEMUBuildCommandLineJSONArrayBitmap) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
virQEMUBuildObjectCommandlineFromJSON(virBufferPtr buf,
|
|
virJSONValuePtr objprops)
|
|
{
|
|
const char *type = virJSONValueObjectGetString(objprops, "qom-type");
|
|
const char *alias = virJSONValueObjectGetString(objprops, "id");
|
|
virJSONValuePtr props = virJSONValueObjectGetObject(objprops, "props");
|
|
|
|
return virQEMUBuildObjectCommandlineFromJSONInternal(buf, type, alias, props);
|
|
}
|
|
|
|
|
|
char *
|
|
virQEMUBuildDriveCommandlineFromJSON(virJSONValuePtr srcdef)
|
|
{
|
|
virBuffer buf = VIR_BUFFER_INITIALIZER;
|
|
char *ret = NULL;
|
|
|
|
if (virQEMUBuildCommandLineJSON(srcdef, &buf, NULL, false,
|
|
virQEMUBuildCommandLineJSONArrayNumbered) < 0)
|
|
goto cleanup;
|
|
|
|
ret = virBufferContentAndReset(&buf);
|
|
|
|
cleanup:
|
|
virBufferFreeAndReset(&buf);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* virQEMUBuildBufferEscapeComma:
|
|
* @buf: buffer to append the escaped string
|
|
* @str: the string to escape
|
|
*
|
|
* qemu requires that any values passed on the command line which contain
|
|
* a ',' must escape it using an extra ',' as the escape character
|
|
*/
|
|
void
|
|
virQEMUBuildBufferEscapeComma(virBufferPtr buf, const char *str)
|
|
{
|
|
virBufferEscape(buf, ',', ",", "%s", str);
|
|
}
|
|
|
|
|
|
/**
|
|
* virQEMUBuildQemuImgKeySecretOpts:
|
|
* @buf: buffer to build the string into
|
|
* @encinfo: pointer to encryption info
|
|
* @alias: alias to use
|
|
*
|
|
* Generate the string for id=$alias and any encryption options for
|
|
* into the buffer.
|
|
*
|
|
* Important note, a trailing comma (",") is built into the return since
|
|
* it's expected other arguments are appended after the id=$alias string.
|
|
* So either turn something like:
|
|
*
|
|
* "key-secret=$alias,"
|
|
*
|
|
* or
|
|
* "key-secret=$alias,cipher-alg=twofish-256,cipher-mode=cbc,
|
|
* hash-alg=sha256,ivgen-alg=plain64,igven-hash-alg=sha256,"
|
|
*
|
|
*/
|
|
void
|
|
virQEMUBuildQemuImgKeySecretOpts(virBufferPtr buf,
|
|
virStorageEncryptionInfoDefPtr encinfo,
|
|
const char *alias)
|
|
{
|
|
virBufferAsprintf(buf, "key-secret=%s,", alias);
|
|
|
|
if (!encinfo->cipher_name)
|
|
return;
|
|
|
|
virBufferAddLit(buf, "cipher-alg=");
|
|
virQEMUBuildBufferEscapeComma(buf, encinfo->cipher_name);
|
|
virBufferAsprintf(buf, "-%u,", encinfo->cipher_size);
|
|
if (encinfo->cipher_mode) {
|
|
virBufferAddLit(buf, "cipher-mode=");
|
|
virQEMUBuildBufferEscapeComma(buf, encinfo->cipher_mode);
|
|
virBufferAddLit(buf, ",");
|
|
}
|
|
if (encinfo->cipher_hash) {
|
|
virBufferAddLit(buf, "hash-alg=");
|
|
virQEMUBuildBufferEscapeComma(buf, encinfo->cipher_hash);
|
|
virBufferAddLit(buf, ",");
|
|
}
|
|
if (!encinfo->ivgen_name)
|
|
return;
|
|
|
|
virBufferAddLit(buf, "ivgen-alg=");
|
|
virQEMUBuildBufferEscapeComma(buf, encinfo->ivgen_name);
|
|
virBufferAddLit(buf, ",");
|
|
|
|
if (encinfo->ivgen_hash) {
|
|
virBufferAddLit(buf, "ivgen-hash-alg=");
|
|
virQEMUBuildBufferEscapeComma(buf, encinfo->ivgen_hash);
|
|
virBufferAddLit(buf, ",");
|
|
}
|
|
}
|