libvirt/src/util/virqemu.c
Peter Krempa 4f33b817b2 qemu: command: Use JSON for QAPIfied -object directly
Skip the lossy conversion to legacy commandline arguments by using the
JSON props directly when -object is QAPIfied. This avoids issues with
conversion of bitmaps and also allows validation of the generated JSON
against the QMP schema in the tests.

Since the new approach is triggered by a qemu capability the code
from 'virQEMUBuildObjectCommandlineFromJSON' in util/virqemu.c was moved
to 'qemuBuildObjectCommandlineFromJSON' in qemu/qemu_command.c which has
the virQEMUCaps type.

Some functions needed to be modified to propagate qemuCaps.

Signed-off-by: Peter Krempa <pkrempa@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2021-03-23 14:06:31 +01:00

332 lines
10 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;
virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc;
};
static int
virQEMUBuildCommandLineJSONRecurse(const char *key,
virJSONValuePtr value,
virBufferPtr buf,
const char *skipKey,
virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc,
bool nested);
int
virQEMUBuildCommandLineJSONArrayBitmap(const char *key,
virJSONValuePtr array,
virBufferPtr buf,
const char *skipKey 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)
{
virJSONValuePtr member;
size_t i;
for (i = 0; i < virJSONValueArraySize(array); i++) {
g_autofree char *prefix = NULL;
member = virJSONValueArrayGet((virJSONValuePtr) array, i);
prefix = g_strdup_printf("%s.%zu", key, i);
if (virQEMUBuildCommandLineJSONRecurse(prefix, member, buf, skipKey,
virQEMUBuildCommandLineJSONArrayNumbered,
true) < 0)
return 0;
}
return 0;
}
/**
* This array converter 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)
{
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->arrayFunc, false);
} else {
return virQEMUBuildCommandLineJSONRecurse(key, value, data->buf,
data->skipKey,
data->arrayFunc, false);
}
}
static int
virQEMUBuildCommandLineJSONRecurse(const char *key,
virJSONValuePtr value,
virBufferPtr buf,
const char *skipKey,
virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc,
bool nested)
{
struct virQEMUCommandLineJSONIteratorData data = { key, buf, skipKey, 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 (tmp)
virBufferAsprintf(buf, "%s=on,", key);
else
virBufferAsprintf(buf, "%s=off,", 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) < 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,
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
* @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,
virQEMUBuildCommandLineJSONArrayFormatFunc array)
{
if (virQEMUBuildCommandLineJSONRecurse(NULL, value, buf, skipKey, 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",
virQEMUBuildCommandLineJSONArrayObjectsStr) < 0)
return NULL;
return virBufferContentAndReset(&buf);
}
char *
virQEMUBuildDriveCommandlineFromJSON(virJSONValuePtr srcdef)
{
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
if (virQEMUBuildCommandLineJSON(srcdef, &buf, NULL,
virQEMUBuildCommandLineJSONArrayNumbered) < 0)
return NULL;
return virBufferContentAndReset(&buf);
}
/**
* 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);
}