diff --git a/libvirt.spec b/libvirt.spec index 40da7e1c6856effa8b7f4290cb1a92be0bab723e..10d96fc6ec06d46b0ff643e8bc8db6fd97751ab6 100644 --- a/libvirt.spec +++ b/libvirt.spec @@ -101,7 +101,7 @@ Summary: Library providing a simple virtualization API Name: libvirt Version: 6.2.0 -Release: 31 +Release: 32 License: LGPLv2+ URL: https://libvirt.org/ @@ -197,6 +197,24 @@ Patch0084: tests-Replace-deprecated-ASN1-code.patch Patch0085: tests-disabale-storage-tests.patch Patch0086: Revert-tests-disabale-storage-tests.patch Patch0087: hotpatch-virsh-support-autoload-mode.patch +Patch0088: virQEMUBuildCommandLineJSON-Allow-skipping-certain-k.patch +Patch0089: virQEMUBuildCommandLineJSON-Add-possibility-for-usin.patch +Patch0090: util-virqemu-Introduce-virQEMUBuildNetdevCommandline.patch +Patch0091: util-json-Introduce-virJSONValueObjectAppendStringPr.patch +Patch0092: qemuBuildChannelsCommandLine-Use-typecasted-switch-f.patch +Patch0093: qemuBuildChannelsCommandLine-Extract-common-formatti.patch +Patch0094: qemuBuildChannelChrDeviceStr-Remove-formatting-of-pr.patch +Patch0095: qemuMonitorJSON-Add-Remove-Netdev-Refactor-cleanup.patch +Patch0096: qemuBuildHostNetStr-Stop-using-ipv6-net-convenience-.patch +Patch0097: qemu-command-Generate-netdev-command-line-via-JSON-c.patch +Patch0098: qemuBuildChannelGuestfwdNetdevProps-Convert-to-gener.patch +Patch0099: qemuMonitorAddNetdev-Convert-to-the-native-JSON-prop.patch +Patch0100: virCommand-Introduce-virCommandGetArgList.patch +Patch0101: testutilsqemuschema-Introduce-testQEMUSchemaValidate.patch +Patch0102: testCompareXMLToArgv-Split-out-preparation-and-comma.patch +Patch0103: virQEMUBuildNetdevCommandlineFromJSON-Prepare-for-qu.patch +Patch0104: qemu-Prepare-for-testing-of-netdev_add-props-via-qem.patch +Patch0105: qemuxml2argvtest-Add-QAPI-QMP-schema-validation-for-.patch Requires: libvirt-daemon = %{version}-%{release} Requires: libvirt-daemon-config-network = %{version}-%{release} @@ -1931,6 +1949,26 @@ exit 0 %changelog +* Mon Feb 21 2022 imxcc +- virQEMUBuildCommandLineJSON: Allow skipping certain keys +- virQEMUBuildCommandLineJSON: Add possibility for using 'on/off' instead of 'yes/no' +- util: virqemu: Introduce virQEMUBuildNetdevCommandlineFromJSON +- util: json: Introduce virJSONValueObjectAppendStringPrintf +- qemuBuildChannelsCommandLine: Use typecasted switch for channel type +- qemuBuildChannelsCommandLine: Extract common formatting of 'chardev' +- qemuBuildChannelChrDeviceStr: Remove formatting of properties for -netdev +- qemuMonitorJSON(Add|Remove)Netdev: Refactor cleanup +- qemuBuildHostNetStr: Stop using 'ipv6-net' convenience argument +- qemu: command: Generate -netdev command line via JSON->cmdline conversion +- qemuBuildChannelGuestfwdNetdevProps: Convert to generating JSON props +- qemuMonitorAddNetdev: Convert to the native JSON props object +- virCommand: Introduce virCommandGetArgList +- testutilsqemuschema: Introduce testQEMUSchemaValidateCommand +- testCompareXMLToArgv: Split out preparation and command formatting +- virQEMUBuildNetdevCommandlineFromJSON: Prepare for quirky 'guestfwd' +- qemu: Prepare for testing of 'netdev_add' props via qemuxml2argvtest +- qemuxml2argvtest: Add QAPI/QMP schema validation for -blockdev and -netdev + * Sat Feb 12 2022 imxcc - hotpatch: virsh support autoload mode diff --git a/qemu-Prepare-for-testing-of-netdev_add-props-via-qem.patch b/qemu-Prepare-for-testing-of-netdev_add-props-via-qem.patch new file mode 100644 index 0000000000000000000000000000000000000000..07bd69d4bdfe7d6b3f602c939bf6e3eb68b9ebad --- /dev/null +++ b/qemu-Prepare-for-testing-of-netdev_add-props-via-qem.patch @@ -0,0 +1,305 @@ +From fb869a0d3a28591d17a9f047eafd3b77513d6d77 Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Fri, 15 May 2020 14:24:21 +0200 +Subject: [PATCH 17/18] qemu: Prepare for testing of 'netdev_add' props via + qemuxml2argvtest + +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 +Reviewed-by: Eric Blake +--- + src/qemu/qemu_command.c | 28 +++++++++++++++++----------- + src/qemu/qemu_command.h | 7 ++++++- + src/qemu/qemu_driver.c | 2 +- + src/qemu/qemu_process.c | 11 +++++++++-- + src/qemu/qemu_process.h | 1 + + src/util/virqemu.c | 9 ++++++++- + src/util/virqemu.h | 3 ++- + tests/qemuxml2argvtest.c | 6 ++++-- + 8 files changed, 48 insertions(+), 19 deletions(-) + +diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c +index bd78bfb31b..dae6b5a7f9 100644 +--- a/src/qemu/qemu_command.c ++++ b/src/qemu/qemu_command.c +@@ -8027,7 +8027,8 @@ qemuBuildInterfaceCommandLine(virQEMUDriverPtr driver, + virNetDevVPortProfileOp vmop, + bool standalone, + size_t *nnicindexes, +- int **nicindexes) ++ int **nicindexes, ++ unsigned int flags) + { + virDomainDefPtr def = vm->def; + int ret = -1; +@@ -8256,7 +8257,8 @@ qemuBuildInterfaceCommandLine(virQEMUDriverPtr driver, + slirpfdName))) + goto cleanup; + +- if (!(host = virQEMUBuildNetdevCommandlineFromJSON(hostnetprops))) ++ if (!(host = virQEMUBuildNetdevCommandlineFromJSON(hostnetprops, ++ (flags & QEMU_BUILD_COMMANDLINE_VALIDATE_KEEP_JSON)))) + goto cleanup; + + virCommandAddArgList(cmd, "-netdev", host, NULL); +@@ -8332,7 +8334,8 @@ qemuBuildNetCommandLine(virQEMUDriverPtr driver, + bool standalone, + size_t *nnicindexes, + int **nicindexes, +- unsigned int *bootHostdevNet) ++ unsigned int *bootHostdevNet, ++ unsigned int flags) + { + size_t i; + int last_good_net = -1; +@@ -8356,7 +8359,7 @@ qemuBuildNetCommandLine(virQEMUDriverPtr driver, + if (qemuBuildInterfaceCommandLine(driver, vm, logManager, secManager, cmd, net, + qemuCaps, bootNet, vmop, + standalone, nnicindexes, +- nicindexes) < 0) ++ nicindexes, flags) < 0) + goto error; + + last_good_net = i; +@@ -8892,7 +8895,8 @@ qemuBuildChannelsCommandLine(virLogManagerPtr logManager, + virQEMUDriverConfigPtr cfg, + const virDomainDef *def, + virQEMUCapsPtr qemuCaps, +- bool chardevStdioLogd) ++ bool chardevStdioLogd, ++ unsigned int flags) + { + size_t i; + unsigned int cdevflags = QEMU_BUILD_CHARDEV_TCP_NOWAIT | +@@ -8921,7 +8925,8 @@ qemuBuildChannelsCommandLine(virLogManagerPtr logManager, + if (!(netdevprops = qemuBuildChannelGuestfwdNetdevProps(channel))) + return -1; + +- if (!(netdevstr = virQEMUBuildNetdevCommandlineFromJSON(netdevprops))) ++ if (!(netdevstr = virQEMUBuildNetdevCommandlineFromJSON(netdevprops, ++ (flags & QEMU_BUILD_COMMANDLINE_VALIDATE_KEEP_JSON)))) + return -1; + + virCommandAddArgList(cmd, "-netdev", netdevstr, NULL); +@@ -9857,7 +9862,8 @@ qemuBuildCommandLine(virQEMUDriverPtr driver, + bool standalone, + bool enableFips, + size_t *nnicindexes, +- int **nicindexes) ++ int **nicindexes, ++ unsigned int flags) + { + size_t i; + char uuid[VIR_UUID_STRING_BUFLEN]; +@@ -9870,9 +9876,9 @@ qemuBuildCommandLine(virQEMUDriverPtr driver, + bool chardevStdioLogd = priv->chardevStdioLogd; + + VIR_DEBUG("driver=%p def=%p mon=%p " +- "qemuCaps=%p migrateURI=%s snapshot=%p vmop=%d", ++ "qemuCaps=%p migrateURI=%s snapshot=%p vmop=%d flags=0x%x", + driver, def, priv->monConfig, +- qemuCaps, migrateURI, snapshot, vmop); ++ qemuCaps, migrateURI, snapshot, vmop, flags); + + if (qemuBuildCommandLineValidate(driver, def) < 0) + return NULL; +@@ -10017,7 +10023,7 @@ qemuBuildCommandLine(virQEMUDriverPtr driver, + + if (qemuBuildNetCommandLine(driver, vm, logManager, secManager, cmd, + qemuCaps, vmop, standalone, +- nnicindexes, nicindexes, &bootHostdevNet) < 0) ++ nnicindexes, nicindexes, &bootHostdevNet, flags) < 0) + return NULL; + + if (qemuBuildSmartcardCommandLine(logManager, secManager, cmd, cfg, def, qemuCaps, +@@ -10033,7 +10039,7 @@ qemuBuildCommandLine(virQEMUDriverPtr driver, + return NULL; + + if (qemuBuildChannelsCommandLine(logManager, secManager, cmd, cfg, def, qemuCaps, +- chardevStdioLogd) < 0) ++ chardevStdioLogd, flags) < 0) + return NULL; + + if (qemuBuildConsoleCommandLine(logManager, secManager, cmd, cfg, def, qemuCaps, +diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h +index 1e6b23ba05..4b1c2103c5 100644 +--- a/src/qemu/qemu_command.h ++++ b/src/qemu/qemu_command.h +@@ -43,6 +43,10 @@ + VIR_ENUM_DECL(qemuVideo); + VIR_ENUM_DECL(qemuSoundCodec); + ++typedef enum { ++ QEMU_BUILD_COMMANDLINE_VALIDATE_KEEP_JSON = 1 << 0, ++} qemuBuildCommandLineFlags; ++ + virCommandPtr qemuBuildCommandLine(virQEMUDriverPtr driver, + virLogManagerPtr logManager, + virSecurityManagerPtr secManager, +@@ -53,7 +57,8 @@ virCommandPtr qemuBuildCommandLine(virQEMUDriverPtr driver, + bool standalone, + bool enableFips, + size_t *nnicindexes, +- int **nicindexes); ++ int **nicindexes, ++ unsigned int flags); + + /* Generate the object properties for pr-manager */ + virJSONValuePtr qemuBuildPRManagerInfoProps(virStorageSourcePtr src); +diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c +index 2b24881f75..b3ebf3aa9b 100644 +--- a/src/qemu/qemu_driver.c ++++ b/src/qemu/qemu_driver.c +@@ -7449,7 +7449,7 @@ static char *qemuConnectDomainXMLToNative(virConnectPtr conn, + } + + if (!(cmd = qemuProcessCreatePretendCmd(driver, vm, NULL, +- qemuCheckFips(), true, ++ qemuCheckFips(), true, false, + VIR_QEMU_PROCESS_START_COLD))) + goto cleanup; + +diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c +index 818a72d8f9..db564cae36 100644 +--- a/src/qemu/qemu_process.c ++++ b/src/qemu/qemu_process.c +@@ -6750,7 +6750,7 @@ qemuProcessLaunch(virConnectPtr conn, + snapshot, vmop, + false, + qemuCheckFips(), +- &nnicindexes, &nicindexes))) ++ &nnicindexes, &nicindexes, 0))) + goto cleanup; + + if (incoming && incoming->fd != -1) +@@ -7193,8 +7193,11 @@ qemuProcessCreatePretendCmd(virQEMUDriverPtr driver, + const char *migrateURI, + bool enableFips, + bool standalone, ++ bool jsonPropsValidation, + unsigned int flags) + { ++ unsigned int buildflags = 0; ++ + virCheckFlags(VIR_QEMU_PROCESS_START_COLD | + VIR_QEMU_PROCESS_START_PAUSED | + VIR_QEMU_PROCESS_START_AUTODESTROY, NULL); +@@ -7204,6 +7207,9 @@ qemuProcessCreatePretendCmd(virQEMUDriverPtr driver, + if (standalone) + flags |= VIR_QEMU_PROCESS_START_STANDALONE; + ++ if (jsonPropsValidation) ++ buildflags = QEMU_BUILD_COMMANDLINE_VALIDATE_KEEP_JSON; ++ + if (qemuProcessInit(driver, vm, NULL, QEMU_ASYNC_JOB_NONE, + !!migrateURI, flags) < 0) + return NULL; +@@ -7222,7 +7228,8 @@ qemuProcessCreatePretendCmd(virQEMUDriverPtr driver, + standalone, + enableFips, + NULL, +- NULL); ++ NULL, ++ buildflags); + } + + +diff --git a/src/qemu/qemu_process.h b/src/qemu/qemu_process.h +index 3077d3ef9e..15e67b9762 100644 +--- a/src/qemu/qemu_process.h ++++ b/src/qemu/qemu_process.h +@@ -101,6 +101,7 @@ virCommandPtr qemuProcessCreatePretendCmd(virQEMUDriverPtr driver, + const char *migrateURI, + bool enableFips, + bool standalone, ++ bool jsonPropsValidation, + unsigned int flags); + + int qemuProcessInit(virQEMUDriverPtr driver, +diff --git a/src/util/virqemu.c b/src/util/virqemu.c +index 9823ebc14d..321ddeb7e3 100644 +--- a/src/util/virqemu.c ++++ b/src/util/virqemu.c +@@ -292,16 +292,23 @@ virQEMUBuildCommandLineJSON(virJSONValuePtr value, + /** + * 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) ++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, +diff --git a/src/util/virqemu.h b/src/util/virqemu.h +index 22f47851df..b1296cb657 100644 +--- a/src/util/virqemu.h ++++ b/src/util/virqemu.h +@@ -50,7 +50,8 @@ int virQEMUBuildCommandLineJSON(virJSONValuePtr value, + virQEMUBuildCommandLineJSONArrayFormatFunc array); + + char * +-virQEMUBuildNetdevCommandlineFromJSON(virJSONValuePtr props); ++virQEMUBuildNetdevCommandlineFromJSON(virJSONValuePtr props, ++ bool rawjson); + + int virQEMUBuildObjectCommandlineFromJSON(virBufferPtr buf, + virJSONValuePtr objprops); +diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c +index 762d65f521..2217e2d81e 100644 +--- a/tests/qemuxml2argvtest.c ++++ b/tests/qemuxml2argvtest.c +@@ -400,7 +400,8 @@ testCompareXMLToArgvCreateArgs(virQEMUDriverPtr drv, + virDomainObjPtr vm, + const char *migrateURI, + struct testQemuInfo *info, +- unsigned int flags) ++ unsigned int flags, ++ bool jsonPropsValidation) + { + size_t i; + +@@ -475,6 +476,7 @@ testCompareXMLToArgvCreateArgs(virQEMUDriverPtr drv, + + return qemuProcessCreatePretendCmd(drv, vm, migrateURI, + (flags & FLAG_FIPS), false, ++ jsonPropsValidation, + VIR_QEMU_PROCESS_START_COLD); + } + +@@ -570,7 +572,7 @@ testCompareXMLToArgv(const void *data) + virResetLastError(); + + if (!(cmd = testCompareXMLToArgvCreateArgs(&driver, vm, migrateURI, info, +- flags))) { ++ flags, false))) { + if (flags & FLAG_EXPECT_FAILURE) + goto ok; + goto cleanup; +-- +2.23.0.windows.1 + diff --git a/qemu-command-Generate-netdev-command-line-via-JSON-c.patch b/qemu-command-Generate-netdev-command-line-via-JSON-c.patch new file mode 100644 index 0000000000000000000000000000000000000000..90b8a68fd80b1bbfddfc0d95253d76c9de38eadf --- /dev/null +++ b/qemu-command-Generate-netdev-command-line-via-JSON-c.patch @@ -0,0 +1,354 @@ +From 0ab9df24cbe5b6cfd2aa259aaaef2f8fb2696ce2 Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Thu, 14 May 2020 22:50:59 +0200 +Subject: [PATCH 10/18] qemu: command: Generate -netdev command line via + JSON->cmdline conversion + +The 'netdev_add' command was recently formally described in qemu via the +QMP schema. This means that it also requires the arguments to be +properly formatted. Our current approach is to generate the command line +and then use qemuMonitorJSONKeywordStringToJSON to get the JSON +properties for the monitor. This will not work if we need to pass some +fields as numbers or booleans. + +In this step we re-do internals of qemuBuildHostNetStr to format a JSON +object which is converted back via virQEMUBuildNetdevCommandlineFromJSON +to the equivalent command line. This will later allow fixing of the +monitor code to use the JSON object directly rather than rely on the +conversion. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/qemu/qemu_command.c | 163 +++++++++++++++++++++++++--------------- + src/qemu/qemu_command.h | 12 +-- + src/qemu/qemu_hotplug.c | 13 +++- + 3 files changed, 119 insertions(+), 69 deletions(-) + +diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c +index 688c9db851..141657b3e4 100644 +--- a/src/qemu/qemu_command.c ++++ b/src/qemu/qemu_command.c +@@ -3910,7 +3910,7 @@ qemuBuildNicDevStr(virDomainDefPtr def, + } + + +-char * ++virJSONValuePtr + qemuBuildHostNetStr(virDomainNetDefPtr net, + char **tapfd, + size_t tapfdSize, +@@ -3919,10 +3919,11 @@ qemuBuildHostNetStr(virDomainNetDefPtr net, + const char *slirpfd) + { + bool is_tap = false; +- g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; + virDomainNetType netType = virDomainNetGetActualType(net); + size_t i; + ++ g_autoptr(virJSONValue) netprops = NULL; ++ + if (net->script && netType != VIR_DOMAIN_NET_TYPE_ETHERNET) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("scripts are not supported on interfaces of type %s"), +@@ -3940,54 +3941,75 @@ qemuBuildHostNetStr(virDomainNetDefPtr net, + case VIR_DOMAIN_NET_TYPE_NETWORK: + case VIR_DOMAIN_NET_TYPE_DIRECT: + case VIR_DOMAIN_NET_TYPE_ETHERNET: +- virBufferAddLit(&buf, "tap,"); ++ if (virJSONValueObjectCreate(&netprops, "s:type", "tap", NULL) < 0) ++ return NULL; ++ + /* for one tapfd 'fd=' shall be used, + * for more than one 'fds=' is the right choice */ + if (tapfdSize == 1) { +- virBufferAsprintf(&buf, "fd=%s,", tapfd[0]); ++ if (virJSONValueObjectAdd(netprops, "s:fd", tapfd[0], NULL) < 0) ++ return NULL; + } else { +- virBufferAddLit(&buf, "fds="); +- for (i = 0; i < tapfdSize; i++) { +- if (i) +- virBufferAddChar(&buf, ':'); +- virBufferAdd(&buf, tapfd[i], -1); +- } +- virBufferAddChar(&buf, ','); ++ g_auto(virBuffer) fdsbuf = VIR_BUFFER_INITIALIZER; ++ ++ for (i = 0; i < tapfdSize; i++) ++ virBufferAsprintf(&fdsbuf, "%s:", tapfd[i]); ++ ++ virBufferTrim(&fdsbuf, ":"); ++ ++ if (virJSONValueObjectAdd(netprops, ++ "s:fds", virBufferCurrentContent(&fdsbuf), ++ NULL) < 0) ++ return NULL; + } ++ + is_tap = true; + break; + + case VIR_DOMAIN_NET_TYPE_CLIENT: +- virBufferAsprintf(&buf, "socket,connect=%s:%d,", +- net->data.socket.address, +- net->data.socket.port); ++ if (virJSONValueObjectCreate(&netprops, "s:type", "socket", NULL) < 0 || ++ virJSONValueObjectAppendStringPrintf(netprops, "connect", "%s:%d", ++ net->data.socket.address, ++ net->data.socket.port) < 0) ++ return NULL; + break; + + case VIR_DOMAIN_NET_TYPE_SERVER: +- virBufferAsprintf(&buf, "socket,listen=%s:%d,", +- NULLSTR_EMPTY(net->data.socket.address), +- net->data.socket.port); ++ if (virJSONValueObjectCreate(&netprops, "s:type", "socket", NULL) < 0 || ++ virJSONValueObjectAppendStringPrintf(netprops, "listen", "%s:%d", ++ NULLSTR_EMPTY(net->data.socket.address), ++ net->data.socket.port) < 0) ++ return NULL; + break; + + case VIR_DOMAIN_NET_TYPE_MCAST: +- virBufferAsprintf(&buf, "socket,mcast=%s:%d,", +- net->data.socket.address, +- net->data.socket.port); ++ if (virJSONValueObjectCreate(&netprops, "s:type", "socket", NULL) < 0 || ++ virJSONValueObjectAppendStringPrintf(netprops, "mcast", "%s:%d", ++ net->data.socket.address, ++ net->data.socket.port) < 0) ++ return NULL; + break; + + case VIR_DOMAIN_NET_TYPE_UDP: +- virBufferAsprintf(&buf, "socket,udp=%s:%d,localaddr=%s:%d,", +- net->data.socket.address, +- net->data.socket.port, +- net->data.socket.localaddr, +- net->data.socket.localport); ++ if (virJSONValueObjectCreate(&netprops, "s:type", "socket", NULL) < 0 || ++ virJSONValueObjectAppendStringPrintf(netprops, "udp", "%s:%d", ++ net->data.socket.address, ++ net->data.socket.port) < 0 || ++ virJSONValueObjectAppendStringPrintf(netprops, "localaddr", "%s:%d", ++ net->data.socket.localaddr, ++ net->data.socket.localport) < 0) ++ return NULL; + break; + + case VIR_DOMAIN_NET_TYPE_USER: + if (slirpfd) { +- virBufferAsprintf(&buf, "socket,fd=%s,", slirpfd); ++ if (virJSONValueObjectCreate(&netprops, "s:type", "socket", NULL) < 0 || ++ virJSONValueObjectAppendString(netprops, "fd", slirpfd) < 0) ++ return NULL; + } else { +- virBufferAddLit(&buf, "user,"); ++ if (virJSONValueObjectCreate(&netprops, "s:type", "user", NULL) < 0) ++ return NULL; ++ + for (i = 0; i < net->guestIP.nips; i++) { + const virNetDevIPAddr *ip = net->guestIP.ips[i]; + g_autofree char *addr = NULL; +@@ -3996,29 +4018,40 @@ qemuBuildHostNetStr(virDomainNetDefPtr net, + return NULL; + + if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET)) { +- virBufferAsprintf(&buf, "net=%s", addr); ++ g_autofree char *ipv4netaddr = NULL; ++ + if (ip->prefix) +- virBufferAsprintf(&buf, "/%u", ip->prefix); +- virBufferAddChar(&buf, ','); ++ ipv4netaddr = g_strdup_printf("%s/%u", addr, ip->prefix); ++ else ++ ipv4netaddr = g_strdup(addr); ++ ++ if (virJSONValueObjectAppendString(netprops, "net", ipv4netaddr) < 0) ++ return NULL; + } else if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET6)) { +- virBufferAsprintf(&buf, "ipv6-prefix=%s,", addr); +- if (ip->prefix) +- virBufferAsprintf(&buf, "ipv6-prefixlen=%u,", ip->prefix); ++ if (virJSONValueObjectAppendString(netprops, "ipv6-prefix", addr) < 0) ++ return NULL; ++ if (ip->prefix && ++ virJSONValueObjectAppendNumberUlong(netprops, "ipv6-prefixlen", ++ ip->prefix) < 0) ++ return NULL; + } + } + } + break; + + case VIR_DOMAIN_NET_TYPE_INTERNAL: +- virBufferAddLit(&buf, "user,"); ++ if (virJSONValueObjectCreate(&netprops, "s:type", "user", NULL) < 0) ++ return NULL; + break; + + case VIR_DOMAIN_NET_TYPE_VHOSTUSER: +- virBufferAsprintf(&buf, "vhost-user,chardev=char%s,", +- net->info.alias); +- if (net->driver.virtio.queues > 1) +- virBufferAsprintf(&buf, "queues=%u,", +- net->driver.virtio.queues); ++ if (virJSONValueObjectCreate(&netprops, "s:type", "vhost-user", NULL) < 0 || ++ virJSONValueObjectAppendStringPrintf(netprops, "chardev", "char%s", net->info.alias) < 0) ++ return NULL; ++ ++ if (net->driver.virtio.queues > 1 && ++ virJSONValueObjectAppendNumberUlong(netprops, "queues", net->driver.virtio.queues) < 0) ++ return NULL; + break; + + case VIR_DOMAIN_NET_TYPE_HOSTDEV: +@@ -4027,31 +4060,38 @@ qemuBuildHostNetStr(virDomainNetDefPtr net, + break; + } + +- virBufferAsprintf(&buf, "id=host%s,", net->info.alias); ++ if (virJSONValueObjectAppendStringPrintf(netprops, "id", "host%s", net->info.alias) < 0) ++ return NULL; + + if (is_tap) { + if (vhostfdSize) { +- virBufferAddLit(&buf, "vhost=on,"); ++ if (virJSONValueObjectAppendBoolean(netprops, "vhost", true) < 0) ++ return NULL; ++ + if (vhostfdSize == 1) { +- virBufferAsprintf(&buf, "vhostfd=%s,", vhostfd[0]); ++ if (virJSONValueObjectAdd(netprops, "s:vhostfd", vhostfd[0], NULL) < 0) ++ return NULL; + } else { +- virBufferAddLit(&buf, "vhostfds="); +- for (i = 0; i < vhostfdSize; i++) { +- if (i) +- virBufferAddChar(&buf, ':'); +- virBufferAdd(&buf, vhostfd[i], -1); +- } +- virBufferAddChar(&buf, ','); ++ g_auto(virBuffer) fdsbuf = VIR_BUFFER_INITIALIZER; ++ ++ for (i = 0; i < vhostfdSize; i++) ++ virBufferAsprintf(&fdsbuf, "%s:", vhostfd[i]); ++ ++ virBufferTrim(&fdsbuf, ":"); ++ ++ if (virJSONValueObjectAdd(netprops, ++ "s:vhostfds", virBufferCurrentContent(&fdsbuf), ++ NULL) < 0) ++ return NULL; + } + } +- if (net->tune.sndbuf_specified) +- virBufferAsprintf(&buf, "sndbuf=%lu,", net->tune.sndbuf); +- } +- + +- virBufferTrim(&buf, ","); ++ if (net->tune.sndbuf_specified && ++ virJSONValueObjectAppendNumberUlong(netprops, "sndbuf", net->tune.sndbuf) < 0) ++ return NULL; ++ } + +- return virBufferContentAndReset(&buf); ++ return g_steal_pointer(&netprops); + } + + +@@ -8006,6 +8046,7 @@ qemuBuildInterfaceCommandLine(virQEMUDriverPtr driver, + bool requireNicdev = false; + qemuSlirpPtr slirp; + size_t i; ++ g_autoptr(virJSONValue) hostnetprops = NULL; + + + if (!bootindex) +@@ -8209,11 +8250,15 @@ qemuBuildInterfaceCommandLine(virQEMUDriverPtr driver, + if (chardev) + virCommandAddArgList(cmd, "-chardev", chardev, NULL); + +- if (!(host = qemuBuildHostNetStr(net, +- tapfdName, tapfdSize, +- vhostfdName, vhostfdSize, +- slirpfdName))) ++ if (!(hostnetprops = qemuBuildHostNetStr(net, ++ tapfdName, tapfdSize, ++ vhostfdName, vhostfdSize, ++ slirpfdName))) + goto cleanup; ++ ++ if (!(host = virQEMUBuildNetdevCommandlineFromJSON(hostnetprops))) ++ goto cleanup; ++ + virCommandAddArgList(cmd, "-netdev", host, NULL); + + /* Possible combinations: +diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h +index 274084821b..00d9a9da8f 100644 +--- a/src/qemu/qemu_command.h ++++ b/src/qemu/qemu_command.h +@@ -89,12 +89,12 @@ qemuBuildChrDeviceStr(char **deviceStr, + char * + qemuBuildChannelGuestfwdNetdevProps(virDomainChrDefPtr chr); + +-char *qemuBuildHostNetStr(virDomainNetDefPtr net, +- char **tapfd, +- size_t tapfdSize, +- char **vhostfd, +- size_t vhostfdSize, +- const char *slirpfd); ++virJSONValuePtr qemuBuildHostNetStr(virDomainNetDefPtr net, ++ char **tapfd, ++ size_t tapfdSize, ++ char **vhostfd, ++ size_t vhostfdSize, ++ const char *slirpfd); + + /* Current, best practice */ + char *qemuBuildNicDevStr(virDomainDefPtr def, +diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c +index ee97397235..1877aef1b0 100644 +--- a/src/qemu/qemu_hotplug.c ++++ b/src/qemu/qemu_hotplug.c +@@ -54,6 +54,7 @@ + #include "virstoragefile.h" + #include "virstring.h" + #include "virtime.h" ++#include "virqemu.h" + + #define VIR_FROM_THIS VIR_FROM_QEMU + +@@ -1157,6 +1158,7 @@ qemuDomainAttachNetDevice(virQEMUDriverPtr driver, + size_t vhostfdSize = 0; + size_t queueSize = 0; + g_autofree char *nicstr = NULL; ++ g_autoptr(virJSONValue) netprops = NULL; + g_autofree char *netstr = NULL; + int ret = -1; + bool releaseaddr = false; +@@ -1382,10 +1384,13 @@ qemuDomainAttachNetDevice(virQEMUDriverPtr driver, + for (i = 0; i < vhostfdSize; i++) + vhostfdName[i] = g_strdup_printf("vhostfd-%s%zu", net->info.alias, i); + +- if (!(netstr = qemuBuildHostNetStr(net, +- tapfdName, tapfdSize, +- vhostfdName, vhostfdSize, +- slirpfdName))) ++ if (!(netprops = qemuBuildHostNetStr(net, ++ tapfdName, tapfdSize, ++ vhostfdName, vhostfdSize, ++ slirpfdName))) ++ goto cleanup; ++ ++ if (!(netstr = virQEMUBuildNetdevCommandlineFromJSON(netprops))) + goto cleanup; + + qemuDomainObjEnterMonitor(driver, vm); +-- +2.23.0.windows.1 + diff --git a/qemuBuildChannelChrDeviceStr-Remove-formatting-of-pr.patch b/qemuBuildChannelChrDeviceStr-Remove-formatting-of-pr.patch new file mode 100644 index 0000000000000000000000000000000000000000..6f175ece3e639b36099f3d44827a0ad8e15ead7c --- /dev/null +++ b/qemuBuildChannelChrDeviceStr-Remove-formatting-of-pr.patch @@ -0,0 +1,152 @@ +From f0b20bfdd5e0967da8b98a1c66649c23a94b3d3c Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Thu, 14 May 2020 13:01:59 +0200 +Subject: [PATCH 07/18] qemuBuildChannelChrDeviceStr: Remove formatting of + properties for -netdev + +The output of the function is fed as argument to '-device' command line +argument or 'device_add' monitor command except for 'guestfwd' channels +where it needs to be fed to -netdev/netdev_add. This is confusing and +error prone. Split it up since the caller needs to know which +command/option to use anyways, so the caller can call the appropriate +function without any magic. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/qemu/qemu_command.c | 36 ++++++++++++++++++++---------------- + src/qemu/qemu_command.h | 3 +++ + src/qemu/qemu_hotplug.c | 18 +++++++++++++----- + 3 files changed, 36 insertions(+), 21 deletions(-) + +diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c +index 579e78ea75..056a74fb60 100644 +--- a/src/qemu/qemu_command.c ++++ b/src/qemu/qemu_command.c +@@ -8872,7 +8872,7 @@ qemuBuildChannelsCommandLine(virLogManagerPtr logManager, + + switch ((virDomainChrChannelTargetType) channel->targetType) { + case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD: +- if (qemuBuildChrDeviceStr(&netdevstr, def, channel, qemuCaps) < 0) ++ if (!(netdevstr = qemuBuildChannelGuestfwdNetdevProps(channel))) + return -1; + virCommandAddArgList(cmd, "-netdev", netdevstr, NULL); + break; +@@ -10140,36 +10140,40 @@ qemuBuildParallelChrDeviceStr(char **deviceStr, + return 0; + } + +-static int +-qemuBuildChannelChrDeviceStr(char **deviceStr, +- const virDomainDef *def, +- virDomainChrDefPtr chr) ++ ++char * ++qemuBuildChannelGuestfwdNetdevProps(virDomainChrDefPtr chr) + { +- int ret = -1; + g_autofree char *addr = NULL; + int port; + +- switch ((virDomainChrChannelTargetType)chr->targetType) { +- case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD: ++ if (!(addr = virSocketAddrFormat(chr->target.addr))) ++ return NULL; + +- addr = virSocketAddrFormat(chr->target.addr); +- if (!addr) +- return ret; +- port = virSocketAddrGetPort(chr->target.addr); ++ port = virSocketAddrGetPort(chr->target.addr); ++ ++ return g_strdup_printf("user,guestfwd=tcp:%s:%i-chardev:char%s,id=%s", ++ addr, port, chr->info.alias, chr->info.alias); ++} + +- *deviceStr = g_strdup_printf("user,guestfwd=tcp:%s:%i-chardev:char%s,id=%s", +- addr, port, chr->info.alias, chr->info.alias); +- break; + ++static int ++qemuBuildChannelChrDeviceStr(char **deviceStr, ++ const virDomainDef *def, ++ virDomainChrDefPtr chr) ++{ ++ switch ((virDomainChrChannelTargetType)chr->targetType) { + case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO: + if (!(*deviceStr = qemuBuildVirtioSerialPortDevStr(def, chr))) + return -1; + break; + ++ case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD: ++ /* guestfwd is as a netdev handled separately */ + case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_XEN: + case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_NONE: + case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_LAST: +- return ret; ++ return -1; + } + + return 0; +diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h +index bc3ba44fb3..274084821b 100644 +--- a/src/qemu/qemu_command.h ++++ b/src/qemu/qemu_command.h +@@ -86,6 +86,9 @@ qemuBuildChrDeviceStr(char **deviceStr, + virDomainChrDefPtr chr, + virQEMUCapsPtr qemuCaps); + ++char * ++qemuBuildChannelGuestfwdNetdevProps(virDomainChrDefPtr chr); ++ + char *qemuBuildHostNetStr(virDomainNetDefPtr net, + char **tapfd, + size_t tapfdSize, +diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c +index 60d0729f1e..ee97397235 100644 +--- a/src/qemu/qemu_hotplug.c ++++ b/src/qemu/qemu_hotplug.c +@@ -2108,6 +2108,7 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, + virErrorPtr orig_err; + virDomainDefPtr vmdef = vm->def; + g_autofree char *devstr = NULL; ++ g_autofree char *netdevstr = NULL; + virDomainChrSourceDefPtr dev = chr->source; + g_autofree char *charAlias = NULL; + bool chardevAttached = false; +@@ -2146,8 +2147,13 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, + goto cleanup; + teardowncgroup = true; + +- if (qemuBuildChrDeviceStr(&devstr, vmdef, chr, priv->qemuCaps) < 0) +- goto cleanup; ++ if (guestfwd) { ++ if (!(netdevstr = qemuBuildChannelGuestfwdNetdevProps(chr))) ++ goto cleanup; ++ } else { ++ if (qemuBuildChrDeviceStr(&devstr, vmdef, chr, priv->qemuCaps) < 0) ++ goto cleanup; ++ } + + if (!(charAlias = qemuAliasChardevFromDevAlias(chr->info.alias))) + goto cleanup; +@@ -2166,11 +2172,13 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, + goto exit_monitor; + chardevAttached = true; + +- if (guestfwd) { +- if (qemuMonitorAddNetdev(priv->mon, devstr, ++ if (netdevstr) { ++ if (qemuMonitorAddNetdev(priv->mon, netdevstr, + NULL, NULL, 0, NULL, NULL, 0, -1, NULL) < 0) + goto exit_monitor; +- } else { ++ } ++ ++ if (devstr) { + if (qemuMonitorAddDevice(priv->mon, devstr) < 0) + goto exit_monitor; + } +-- +2.23.0.windows.1 + diff --git a/qemuBuildChannelGuestfwdNetdevProps-Convert-to-gener.patch b/qemuBuildChannelGuestfwdNetdevProps-Convert-to-gener.patch new file mode 100644 index 0000000000000000000000000000000000000000..b3608a17f76f41bbb2c0910c26987a928c6550ef --- /dev/null +++ b/qemuBuildChannelGuestfwdNetdevProps-Convert-to-gener.patch @@ -0,0 +1,128 @@ +From 17e751be222dd86959f71afda2fca8b0ed09f55b Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Fri, 15 May 2020 09:46:08 +0200 +Subject: [PATCH 11/18] qemuBuildChannelGuestfwdNetdevProps: Convert to + generating JSON props + +Syntax of guestfwd channel also needs to be modified to conform to the +QAPI schema. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/qemu/qemu_command.c | 37 +++++++++++++++++++++++++++++++------ + src/qemu/qemu_command.h | 2 +- + src/qemu/qemu_hotplug.c | 6 +++++- + 3 files changed, 37 insertions(+), 8 deletions(-) + +diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c +index 141657b3e4..bd78bfb31b 100644 +--- a/src/qemu/qemu_command.c ++++ b/src/qemu/qemu_command.c +@@ -8903,6 +8903,7 @@ qemuBuildChannelsCommandLine(virLogManagerPtr logManager, + for (i = 0; i < def->nchannels; i++) { + virDomainChrDefPtr channel = def->channels[i]; + g_autofree char *chardevstr = NULL; ++ g_autoptr(virJSONValue) netdevprops = NULL; + g_autofree char *netdevstr = NULL; + + if (!(chardevstr = qemuBuildChrChardevStr(logManager, secManager, +@@ -8917,8 +8918,12 @@ qemuBuildChannelsCommandLine(virLogManagerPtr logManager, + + switch ((virDomainChrChannelTargetType) channel->targetType) { + case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD: +- if (!(netdevstr = qemuBuildChannelGuestfwdNetdevProps(channel))) ++ if (!(netdevprops = qemuBuildChannelGuestfwdNetdevProps(channel))) + return -1; ++ ++ if (!(netdevstr = virQEMUBuildNetdevCommandlineFromJSON(netdevprops))) ++ return -1; ++ + virCommandAddArgList(cmd, "-netdev", netdevstr, NULL); + break; + +@@ -10186,19 +10191,39 @@ qemuBuildParallelChrDeviceStr(char **deviceStr, + } + + +-char * ++virJSONValuePtr + qemuBuildChannelGuestfwdNetdevProps(virDomainChrDefPtr chr) + { ++ g_autoptr(virJSONValue) guestfwdarr = virJSONValueNewArray(); ++ g_autoptr(virJSONValue) guestfwdstrobj = virJSONValueNewObject(); + g_autofree char *addr = NULL; +- int port; ++ virJSONValuePtr ret = NULL; + + if (!(addr = virSocketAddrFormat(chr->target.addr))) + return NULL; + +- port = virSocketAddrGetPort(chr->target.addr); ++ /* this may seem weird, but qemu indeed decided that 'guestfwd' parameter ++ * is an array of objects which have just one member named 'str' which ++ * contains the description */ ++ if (virJSONValueObjectAppendStringPrintf(guestfwdstrobj, "str", ++ "tcp:%s:%i-chardev:char%s", ++ addr, ++ virSocketAddrGetPort(chr->target.addr), ++ chr->info.alias) < 0) ++ return NULL; ++ ++ if (virJSONValueArrayAppend(guestfwdarr, guestfwdstrobj) < 0) ++ return NULL; ++ guestfwdstrobj = NULL; + +- return g_strdup_printf("user,guestfwd=tcp:%s:%i-chardev:char%s,id=%s", +- addr, port, chr->info.alias, chr->info.alias); ++ if (virJSONValueObjectCreate(&ret, ++ "s:type", "user", ++ "a:guestfwd", &guestfwdarr, ++ "s:id", chr->info.alias, ++ NULL) < 0) ++ return NULL; ++ ++ return ret; + } + + +diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h +index 00d9a9da8f..1e6b23ba05 100644 +--- a/src/qemu/qemu_command.h ++++ b/src/qemu/qemu_command.h +@@ -86,7 +86,7 @@ qemuBuildChrDeviceStr(char **deviceStr, + virDomainChrDefPtr chr, + virQEMUCapsPtr qemuCaps); + +-char * ++virJSONValuePtr + qemuBuildChannelGuestfwdNetdevProps(virDomainChrDefPtr chr); + + virJSONValuePtr qemuBuildHostNetStr(virDomainNetDefPtr net, +diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c +index 1877aef1b0..24a898166d 100644 +--- a/src/qemu/qemu_hotplug.c ++++ b/src/qemu/qemu_hotplug.c +@@ -2113,6 +2113,7 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, + virErrorPtr orig_err; + virDomainDefPtr vmdef = vm->def; + g_autofree char *devstr = NULL; ++ g_autoptr(virJSONValue) netdevprops = NULL; + g_autofree char *netdevstr = NULL; + virDomainChrSourceDefPtr dev = chr->source; + g_autofree char *charAlias = NULL; +@@ -2153,7 +2154,10 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, + teardowncgroup = true; + + if (guestfwd) { +- if (!(netdevstr = qemuBuildChannelGuestfwdNetdevProps(chr))) ++ if (!(netdevprops = qemuBuildChannelGuestfwdNetdevProps(chr))) ++ goto cleanup; ++ ++ if (!(netdevstr = virQEMUBuildNetdevCommandlineFromJSON(netdevprops))) + goto cleanup; + } else { + if (qemuBuildChrDeviceStr(&devstr, vmdef, chr, priv->qemuCaps) < 0) +-- +2.23.0.windows.1 + diff --git a/qemuBuildChannelsCommandLine-Extract-common-formatti.patch b/qemuBuildChannelsCommandLine-Extract-common-formatti.patch new file mode 100644 index 0000000000000000000000000000000000000000..8fe7f9358eece44d909434a15aca18333030ee9e --- /dev/null +++ b/qemuBuildChannelsCommandLine-Extract-common-formatti.patch @@ -0,0 +1,72 @@ +From 31c32f8d76a447899ff4a5c551c06a2888814111 Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Thu, 14 May 2020 12:51:21 +0200 +Subject: [PATCH 06/18] qemuBuildChannelsCommandLine: Extract common formatting + of 'chardev' + +Both active branches create the same backend chardev. Since there is no +other case, extract it before the switch so that we don't have to +duplicate it. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/qemu/qemu_command.c | 32 +++++++++++--------------------- + 1 file changed, 11 insertions(+), 21 deletions(-) + +diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c +index 7c4db11ae2..579e78ea75 100644 +--- a/src/qemu/qemu_command.c ++++ b/src/qemu/qemu_command.c +@@ -8857,37 +8857,27 @@ qemuBuildChannelsCommandLine(virLogManagerPtr logManager, + + for (i = 0; i < def->nchannels; i++) { + virDomainChrDefPtr channel = def->channels[i]; +- char *devstr; ++ g_autofree char *chardevstr = NULL; ++ g_autofree char *netdevstr = NULL; + +- switch ((virDomainChrChannelTargetType) channel->targetType) { +- case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD: +- if (!(devstr = qemuBuildChrChardevStr(logManager, secManager, ++ if (!(chardevstr = qemuBuildChrChardevStr(logManager, secManager, + cmd, cfg, def, + channel->source, + channel->info.alias, + qemuCaps, cdevflags))) +- return -1; +- virCommandAddArg(cmd, "-chardev"); +- virCommandAddArg(cmd, devstr); +- VIR_FREE(devstr); ++ return -1; + +- if (qemuBuildChrDeviceStr(&devstr, def, channel, qemuCaps) < 0) ++ virCommandAddArg(cmd, "-chardev"); ++ virCommandAddArg(cmd, chardevstr); ++ ++ switch ((virDomainChrChannelTargetType) channel->targetType) { ++ case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD: ++ if (qemuBuildChrDeviceStr(&netdevstr, def, channel, qemuCaps) < 0) + return -1; +- virCommandAddArgList(cmd, "-netdev", devstr, NULL); +- VIR_FREE(devstr); ++ virCommandAddArgList(cmd, "-netdev", netdevstr, NULL); + break; + + case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_VIRTIO: +- if (!(devstr = qemuBuildChrChardevStr(logManager, secManager, +- cmd, cfg, def, +- channel->source, +- channel->info.alias, +- qemuCaps, cdevflags))) +- return -1; +- virCommandAddArg(cmd, "-chardev"); +- virCommandAddArg(cmd, devstr); +- VIR_FREE(devstr); +- + if (qemuBuildChrDeviceCommandLine(cmd, def, channel, qemuCaps) < 0) + return -1; + break; +-- +2.23.0.windows.1 + diff --git a/qemuBuildChannelsCommandLine-Use-typecasted-switch-f.patch b/qemuBuildChannelsCommandLine-Use-typecasted-switch-f.patch new file mode 100644 index 0000000000000000000000000000000000000000..44dbd6c9c7967a0f02a941bf5323cba4d1da79b4 --- /dev/null +++ b/qemuBuildChannelsCommandLine-Use-typecasted-switch-f.patch @@ -0,0 +1,42 @@ +From 2aac11e296b60464c0ef4870879b6e75d5e0ee46 Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Thu, 14 May 2020 12:32:55 +0200 +Subject: [PATCH 05/18] qemuBuildChannelsCommandLine: Use typecasted switch for + channel type + +Cover all cases of the enum. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/qemu/qemu_command.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c +index c3ad041959..7c4db11ae2 100644 +--- a/src/qemu/qemu_command.c ++++ b/src/qemu/qemu_command.c +@@ -8859,7 +8859,7 @@ qemuBuildChannelsCommandLine(virLogManagerPtr logManager, + virDomainChrDefPtr channel = def->channels[i]; + char *devstr; + +- switch (channel->targetType) { ++ switch ((virDomainChrChannelTargetType) channel->targetType) { + case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_GUESTFWD: + if (!(devstr = qemuBuildChrChardevStr(logManager, secManager, + cmd, cfg, def, +@@ -8891,6 +8891,11 @@ qemuBuildChannelsCommandLine(virLogManagerPtr logManager, + if (qemuBuildChrDeviceCommandLine(cmd, def, channel, qemuCaps) < 0) + return -1; + break; ++ ++ case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_XEN: ++ case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_NONE: ++ case VIR_DOMAIN_CHR_CHANNEL_TARGET_TYPE_LAST: ++ return -1; + } + } + +-- +2.23.0.windows.1 + diff --git a/qemuBuildHostNetStr-Stop-using-ipv6-net-convenience-.patch b/qemuBuildHostNetStr-Stop-using-ipv6-net-convenience-.patch new file mode 100644 index 0000000000000000000000000000000000000000..eb150b05992f19b99924f67fe76bbd55b7cf52e8 --- /dev/null +++ b/qemuBuildHostNetStr-Stop-using-ipv6-net-convenience-.patch @@ -0,0 +1,68 @@ +From ed5210b67f18478af5669976abb6b61c3bd576e0 Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Thu, 14 May 2020 12:02:55 +0200 +Subject: [PATCH 09/18] qemuBuildHostNetStr: Stop using 'ipv6-net' convenience + argument + +In qemu the argument of 'ipv6-net' is split up into 'ipv6-prefix' and +'ipv6-prefixlen'. Additionally now that 'netdev_add' was qapified, only +the real properties are allowed. Switch to using them explicitly. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/qemu/qemu_command.c | 20 ++++++++++---------- + tests/qemuxml2argvdata/net-user-addr.args | 3 ++- + 2 files changed, 12 insertions(+), 11 deletions(-) + +diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c +index 056a74fb60..688c9db851 100644 +--- a/src/qemu/qemu_command.c ++++ b/src/qemu/qemu_command.c +@@ -3991,20 +3991,20 @@ qemuBuildHostNetStr(virDomainNetDefPtr net, + for (i = 0; i < net->guestIP.nips; i++) { + const virNetDevIPAddr *ip = net->guestIP.ips[i]; + g_autofree char *addr = NULL; +- const char *prefix = ""; + + if (!(addr = virSocketAddrFormat(&ip->address))) + return NULL; + +- if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET)) +- prefix = "net="; +- if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET6)) +- prefix = "ipv6-net="; +- +- virBufferAsprintf(&buf, "%s%s", prefix, addr); +- if (ip->prefix) +- virBufferAsprintf(&buf, "/%u", ip->prefix); +- virBufferAddChar(&buf, ','); ++ if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET)) { ++ virBufferAsprintf(&buf, "net=%s", addr); ++ if (ip->prefix) ++ virBufferAsprintf(&buf, "/%u", ip->prefix); ++ virBufferAddChar(&buf, ','); ++ } else if (VIR_SOCKET_ADDR_IS_FAMILY(&ip->address, AF_INET6)) { ++ virBufferAsprintf(&buf, "ipv6-prefix=%s,", addr); ++ if (ip->prefix) ++ virBufferAsprintf(&buf, "ipv6-prefixlen=%u,", ip->prefix); ++ } + } + } + break; +diff --git a/tests/qemuxml2argvdata/net-user-addr.args b/tests/qemuxml2argvdata/net-user-addr.args +index 6cc82d9e62..5f1de305e0 100644 +--- a/tests/qemuxml2argvdata/net-user-addr.args ++++ b/tests/qemuxml2argvdata/net-user-addr.args +@@ -27,6 +27,7 @@ server,nowait \ + -usb \ + -drive file=/dev/HostVG/QEMUGuest1,format=raw,if=none,id=drive-ide0-0-0 \ + -device ide-hd,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0,bootindex=1 \ +--netdev user,net=172.17.2.0/24,ipv6-net=2001:db8:ac10:fd01::/64,id=hostnet0 \ ++-netdev user,net=172.17.2.0/24,ipv6-prefix=2001:db8:ac10:fd01::,\ ++ipv6-prefixlen=64,id=hostnet0 \ + -device rtl8139,netdev=hostnet0,id=net0,mac=00:11:22:33:44:55,bus=pci.0,\ + addr=0x3 +-- +2.23.0.windows.1 + diff --git a/qemuMonitorAddNetdev-Convert-to-the-native-JSON-prop.patch b/qemuMonitorAddNetdev-Convert-to-the-native-JSON-prop.patch new file mode 100644 index 0000000000000000000000000000000000000000..a7c69b15ec82b7d3dcea2eaa6242631b85b2448b --- /dev/null +++ b/qemuMonitorAddNetdev-Convert-to-the-native-JSON-prop.patch @@ -0,0 +1,198 @@ +From 2d555c8e864c8ba7dbc7a22e37712b70b59e5293 Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Fri, 15 May 2020 11:36:28 +0200 +Subject: [PATCH 12/18] qemuMonitorAddNetdev: Convert to the native JSON props + object + +Now that all code paths generate JSON props we can remove the conversion +to command line arguments and back in the monitor code. + +Note that the test which is removed in this commit will be replaced by a +stronger testsuite later. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/qemu/qemu_hotplug.c | 14 +++----------- + src/qemu/qemu_monitor.c | 8 ++++---- + src/qemu/qemu_monitor.h | 2 +- + src/qemu/qemu_monitor_json.c | 15 +++------------ + src/qemu/qemu_monitor_json.h | 2 +- + tests/qemumonitorjsontest.c | 2 -- + 6 files changed, 12 insertions(+), 31 deletions(-) + +diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c +index 24a898166d..d7bcb6a99f 100644 +--- a/src/qemu/qemu_hotplug.c ++++ b/src/qemu/qemu_hotplug.c +@@ -1159,7 +1159,6 @@ qemuDomainAttachNetDevice(virQEMUDriverPtr driver, + size_t queueSize = 0; + g_autofree char *nicstr = NULL; + g_autoptr(virJSONValue) netprops = NULL; +- g_autofree char *netstr = NULL; + int ret = -1; + bool releaseaddr = false; + bool iface_connected = false; +@@ -1390,9 +1389,6 @@ qemuDomainAttachNetDevice(virQEMUDriverPtr driver, + slirpfdName))) + goto cleanup; + +- if (!(netstr = virQEMUBuildNetdevCommandlineFromJSON(netprops))) +- goto cleanup; +- + qemuDomainObjEnterMonitor(driver, vm); + + if (actualType == VIR_DOMAIN_NET_TYPE_VHOSTUSER) { +@@ -1404,7 +1400,7 @@ qemuDomainAttachNetDevice(virQEMUDriverPtr driver, + charDevPlugged = true; + } + +- if (qemuMonitorAddNetdev(priv->mon, netstr, ++ if (qemuMonitorAddNetdev(priv->mon, &netprops, + tapfd, tapfdName, tapfdSize, + vhostfd, vhostfdName, vhostfdSize, + slirpfd, slirpfdName) < 0) { +@@ -2114,7 +2110,6 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, + virDomainDefPtr vmdef = vm->def; + g_autofree char *devstr = NULL; + g_autoptr(virJSONValue) netdevprops = NULL; +- g_autofree char *netdevstr = NULL; + virDomainChrSourceDefPtr dev = chr->source; + g_autofree char *charAlias = NULL; + bool chardevAttached = false; +@@ -2156,9 +2151,6 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, + if (guestfwd) { + if (!(netdevprops = qemuBuildChannelGuestfwdNetdevProps(chr))) + goto cleanup; +- +- if (!(netdevstr = virQEMUBuildNetdevCommandlineFromJSON(netdevprops))) +- goto cleanup; + } else { + if (qemuBuildChrDeviceStr(&devstr, vmdef, chr, priv->qemuCaps) < 0) + goto cleanup; +@@ -2181,8 +2173,8 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, + goto exit_monitor; + chardevAttached = true; + +- if (netdevstr) { +- if (qemuMonitorAddNetdev(priv->mon, netdevstr, ++ if (netdevprops) { ++ if (qemuMonitorAddNetdev(priv->mon, &netdevprops, + NULL, NULL, 0, NULL, NULL, 0, -1, NULL) < 0) + goto exit_monitor; + } +diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c +index cc62948c9c..9f4432ab3a 100644 +--- a/src/qemu/qemu_monitor.c ++++ b/src/qemu/qemu_monitor.c +@@ -2667,7 +2667,7 @@ qemuMonitorCloseFileHandle(qemuMonitorPtr mon, + + int + qemuMonitorAddNetdev(qemuMonitorPtr mon, +- const char *netdevstr, ++ virJSONValuePtr *props, + int *tapfd, char **tapfdName, int tapfdSize, + int *vhostfd, char **vhostfdName, int vhostfdSize, + int slirpfd, char *slirpfdName) +@@ -2675,10 +2675,10 @@ qemuMonitorAddNetdev(qemuMonitorPtr mon, + int ret = -1; + size_t i = 0, j = 0; + +- VIR_DEBUG("netdevstr=%s tapfd=%p tapfdName=%p tapfdSize=%d" ++ VIR_DEBUG("props=%p tapfd=%p tapfdName=%p tapfdSize=%d" + "vhostfd=%p vhostfdName=%p vhostfdSize=%d" + "slirpfd=%d slirpfdName=%s", +- netdevstr, tapfd, tapfdName, tapfdSize, ++ props, tapfd, tapfdName, tapfdSize, + vhostfd, vhostfdName, vhostfdSize, slirpfd, slirpfdName); + + QEMU_CHECK_MONITOR(mon); +@@ -2696,7 +2696,7 @@ qemuMonitorAddNetdev(qemuMonitorPtr mon, + qemuMonitorSendFileHandle(mon, slirpfdName, slirpfd) < 0) + goto cleanup; + +- ret = qemuMonitorJSONAddNetdev(mon, netdevstr); ++ ret = qemuMonitorJSONAddNetdev(mon, props); + + cleanup: + if (ret < 0) { +diff --git a/src/qemu/qemu_monitor.h b/src/qemu/qemu_monitor.h +index 68e21dcaee..7a3240f00a 100644 +--- a/src/qemu/qemu_monitor.h ++++ b/src/qemu/qemu_monitor.h +@@ -881,7 +881,7 @@ int qemuMonitorCloseFileHandle(qemuMonitorPtr mon, + const char *fdname); + + int qemuMonitorAddNetdev(qemuMonitorPtr mon, +- const char *netdevstr, ++ virJSONValuePtr *props, + int *tapfd, char **tapfdName, int tapfdSize, + int *vhostfd, char **vhostfdName, int vhostfdSize, + int slirpfd, char *slirpfdName); +diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c +index e6b5c7140e..c1d92b2009 100644 +--- a/src/qemu/qemu_monitor_json.c ++++ b/src/qemu/qemu_monitor_json.c +@@ -3993,23 +3993,14 @@ int qemuMonitorJSONCloseFileHandle(qemuMonitorPtr mon, + + int + qemuMonitorJSONAddNetdev(qemuMonitorPtr mon, +- const char *netdevstr) ++ virJSONValuePtr *props) + { + g_autoptr(virJSONValue) cmd = NULL; + g_autoptr(virJSONValue) reply = NULL; +- g_autoptr(virJSONValue) args = NULL; +- +- cmd = qemuMonitorJSONMakeCommand("netdev_add", NULL); +- if (!cmd) +- return -1; +- +- args = qemuMonitorJSONKeywordStringToJSON(netdevstr, "type"); +- if (!args) +- return -1; ++ virJSONValuePtr pr = g_steal_pointer(props); + +- if (virJSONValueObjectAppend(cmd, "arguments", args) < 0) ++ if (!(cmd = qemuMonitorJSONMakeCommandInternal("netdev_add", pr))) + return -1; +- args = NULL; /* obj owns reference to args now */ + + if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0) + return -1; +diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h +index 05a46b4fe2..dd25e5d6e6 100644 +--- a/src/qemu/qemu_monitor_json.h ++++ b/src/qemu/qemu_monitor_json.h +@@ -210,7 +210,7 @@ int qemuMonitorJSONCloseFileHandle(qemuMonitorPtr mon, + const char *fdname); + + int qemuMonitorJSONAddNetdev(qemuMonitorPtr mon, +- const char *netdevstr); ++ virJSONValuePtr *props); + + int qemuMonitorJSONRemoveNetdev(qemuMonitorPtr mon, + const char *alias); +diff --git a/tests/qemumonitorjsontest.c b/tests/qemumonitorjsontest.c +index 472775a3cf..2ec143f476 100644 +--- a/tests/qemumonitorjsontest.c ++++ b/tests/qemumonitorjsontest.c +@@ -1308,7 +1308,6 @@ GEN_TEST_FUNC(qemuMonitorJSONDump, "dummy_protocol", "elf", + true) + GEN_TEST_FUNC(qemuMonitorJSONGraphicsRelocate, VIR_DOMAIN_GRAPHICS_TYPE_SPICE, + "localhost", 12345, 12346, "certsubjectval") +-GEN_TEST_FUNC(qemuMonitorJSONAddNetdev, "id=net0,type=test") + GEN_TEST_FUNC(qemuMonitorJSONRemoveNetdev, "net0") + GEN_TEST_FUNC(qemuMonitorJSONDelDevice, "ide0") + GEN_TEST_FUNC(qemuMonitorJSONAddDevice, "some_dummy_devicestr") +@@ -3192,7 +3191,6 @@ mymain(void) + DO_TEST_GEN(qemuMonitorJSONMigrate); + DO_TEST_GEN(qemuMonitorJSONDump); + DO_TEST_GEN(qemuMonitorJSONGraphicsRelocate); +- DO_TEST_GEN(qemuMonitorJSONAddNetdev); + DO_TEST_GEN(qemuMonitorJSONRemoveNetdev); + DO_TEST_GEN(qemuMonitorJSONDelDevice); + DO_TEST_GEN(qemuMonitorJSONAddDevice); +-- +2.23.0.windows.1 + diff --git a/qemuMonitorJSON-Add-Remove-Netdev-Refactor-cleanup.patch b/qemuMonitorJSON-Add-Remove-Netdev-Refactor-cleanup.patch new file mode 100644 index 0000000000000000000000000000000000000000..a851f75f418c8362bd218f0ce5c8fdc683c45b60 --- /dev/null +++ b/qemuMonitorJSON-Add-Remove-Netdev-Refactor-cleanup.patch @@ -0,0 +1,107 @@ +From 3e1026fb3619aae6b1be4879797474d10d22a54c Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Fri, 15 May 2020 11:16:32 +0200 +Subject: [PATCH 08/18] qemuMonitorJSON(Add|Remove)Netdev: Refactor cleanup + +Use automatic pointer cleanup for virJSONValuePtrs to get rid of the +cleanup label and ret variable. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/qemu/qemu_monitor_json.c | 52 +++++++++++++++--------------------- + 1 file changed, 22 insertions(+), 30 deletions(-) + +diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c +index ef25764a98..e6b5c7140e 100644 +--- a/src/qemu/qemu_monitor_json.c ++++ b/src/qemu/qemu_monitor_json.c +@@ -3991,13 +3991,13 @@ int qemuMonitorJSONCloseFileHandle(qemuMonitorPtr mon, + } + + +-int qemuMonitorJSONAddNetdev(qemuMonitorPtr mon, +- const char *netdevstr) ++int ++qemuMonitorJSONAddNetdev(qemuMonitorPtr mon, ++ const char *netdevstr) + { +- int ret = -1; +- virJSONValuePtr cmd = NULL; +- virJSONValuePtr reply = NULL; +- virJSONValuePtr args = NULL; ++ g_autoptr(virJSONValue) cmd = NULL; ++ g_autoptr(virJSONValue) reply = NULL; ++ g_autoptr(virJSONValue) args = NULL; + + cmd = qemuMonitorJSONMakeCommand("netdev_add", NULL); + if (!cmd) +@@ -4005,49 +4005,41 @@ int qemuMonitorJSONAddNetdev(qemuMonitorPtr mon, + + args = qemuMonitorJSONKeywordStringToJSON(netdevstr, "type"); + if (!args) +- goto cleanup; ++ return -1; + + if (virJSONValueObjectAppend(cmd, "arguments", args) < 0) +- goto cleanup; ++ return -1; + args = NULL; /* obj owns reference to args now */ + + if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0) +- goto cleanup; ++ return -1; + + if (qemuMonitorJSONCheckError(cmd, reply) < 0) +- goto cleanup; ++ return -1; + +- ret = 0; +- cleanup: +- virJSONValueFree(args); +- virJSONValueFree(cmd); +- virJSONValueFree(reply); +- return ret; ++ return 0; + } + + +-int qemuMonitorJSONRemoveNetdev(qemuMonitorPtr mon, +- const char *alias) ++int ++qemuMonitorJSONRemoveNetdev(qemuMonitorPtr mon, ++ const char *alias) + { +- int ret = -1; +- virJSONValuePtr cmd = qemuMonitorJSONMakeCommand("netdev_del", +- "s:id", alias, +- NULL); +- virJSONValuePtr reply = NULL; ++ g_autoptr(virJSONValue) cmd = qemuMonitorJSONMakeCommand("netdev_del", ++ "s:id", alias, ++ NULL); ++ g_autoptr(virJSONValue) reply = NULL; ++ + if (!cmd) + return -1; + + if (qemuMonitorJSONCommand(mon, cmd, &reply) < 0) +- goto cleanup; ++ return -1; + + if (qemuMonitorJSONCheckError(cmd, reply) < 0) +- goto cleanup; ++ return -1; + +- ret = 0; +- cleanup: +- virJSONValueFree(cmd); +- virJSONValueFree(reply); +- return ret; ++ return 0; + } + + +-- +2.23.0.windows.1 + diff --git a/qemuxml2argvtest-Add-QAPI-QMP-schema-validation-for-.patch b/qemuxml2argvtest-Add-QAPI-QMP-schema-validation-for-.patch new file mode 100644 index 0000000000000000000000000000000000000000..c61e2ac7442f428f7ba27105114c7d0a82d9f79b --- /dev/null +++ b/qemuxml2argvtest-Add-QAPI-QMP-schema-validation-for-.patch @@ -0,0 +1,181 @@ +From 63e167446b11121a0e375ad28375b6f5a44a5d95 Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Fri, 15 May 2020 14:33:10 +0200 +Subject: [PATCH 18/18] qemuxml2argvtest: Add QAPI/QMP schema validation for + -blockdev and -netdev + +Our hotplug test cases are weak in comparison to the qemuxml2argvtest. +Use all the the input data to also validate the arguments for -netdev +and -blockdev against the appropriate commands of the QMP schema. + +Note that currently it's done just for the _CAPS versions of tests but +commenting out a line in the test file allows to validate even cases +which don't use real capabilities. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + tests/Makefile.am | 2 +- + tests/qemuxml2argvtest.c | 76 ++++++++++++++++++++++++++++++++++++++++ + tests/testutilsqemu.c | 5 +++ + tests/testutilsqemu.h | 1 + + 4 files changed, 83 insertions(+), 1 deletion(-) + +diff --git a/tests/Makefile.am b/tests/Makefile.am +index ada5b8fc57..e5440906d1 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -569,7 +569,7 @@ qemuxml2argvtest_SOURCES = \ + testutils.c testutils.h \ + virfilewrapper.c virfilewrapper.h \ + $(NULL) +-qemuxml2argvtest_LDADD = libqemutestdriver.la \ ++qemuxml2argvtest_LDADD = libqemutestdriver.la libqemumonitortestutils.la \ + $(LDADDS) $(LIBXML_LIBS) + + libqemuxml2argvmock_la_SOURCES = \ +diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c +index 2217e2d81e..dc871d5698 100644 +--- a/tests/qemuxml2argvtest.c ++++ b/tests/qemuxml2argvtest.c +@@ -18,6 +18,7 @@ + # include "qemu/qemu_migration.h" + # include "qemu/qemu_process.h" + # include "qemu/qemu_slirp.h" ++# include "qemu/qemu_qapi.h" + # include "datatypes.h" + # include "conf/storage_conf.h" + # include "cpu/cpu_map.h" +@@ -26,6 +27,8 @@ + # include "virmock.h" + # include "virfilewrapper.h" + # include "configmake.h" ++# include "testutilsqemuschema.h" ++# include "qemu/qemu_monitor_json.h" + + # define LIBVIRT_QEMU_CAPSPRIV_H_ALLOW + # include "qemu/qemu_capspriv.h" +@@ -481,6 +484,76 @@ testCompareXMLToArgvCreateArgs(virQEMUDriverPtr drv, + } + + ++static int ++testCompareXMLToArgvValidateSchema(virQEMUDriverPtr drv, ++ virDomainObjPtr vm, ++ const char *migrateURI, ++ struct testQemuInfo *info, ++ unsigned int flags) ++{ ++ VIR_AUTOSTRINGLIST args = NULL; ++ size_t nargs = 0; ++ size_t i; ++ g_autoptr(virHashTable) schema = NULL; ++ g_autoptr(virCommand) cmd = NULL; ++ ++ if (info->schemafile) ++ schema = testQEMUSchemaLoad(info->schemafile); ++ ++ /* comment out with line comment to enable schema checking for non _CAPS tests ++ if (!schema) ++ schema = testQEMUSchemaLoadLatest(virArchToString(info->arch)); ++ // */ ++ ++ if (!schema) ++ return 0; ++ ++ if (!(cmd = testCompareXMLToArgvCreateArgs(drv, vm, migrateURI, info, flags, ++ true))) ++ return -1; ++ ++ if (virCommandGetArgList(cmd, &args, &nargs) < 0) ++ return -1; ++ ++ for (i = 0; i < nargs; i++) { ++ g_auto(virBuffer) debug = VIR_BUFFER_INITIALIZER; ++ g_autoptr(virJSONValue) jsonargs = NULL; ++ ++ if (STREQ(args[i], "-blockdev")) { ++ if (!(jsonargs = virJSONValueFromString(args[i + 1]))) ++ return -1; ++ ++ if (testQEMUSchemaValidateCommand("blockdev-add", jsonargs, ++ schema, &debug) < 0) { ++ VIR_TEST_VERBOSE("failed to validate -blockdev '%s' against QAPI schema: %s", ++ args[i + 1], virBufferCurrentContent(&debug)); ++ return -1; ++ } ++ ++ i++; ++ } else if (STREQ(args[i], "-netdev")) { ++ if (!(jsonargs = virJSONValueFromString(args[i + 1]))) ++ return -1; ++ ++ /* skip the validation for pre-QAPIfication cases */ ++ if (virQEMUQAPISchemaPathExists("netdev_add/arg-type/type/!string", schema)) ++ continue; ++ ++ if (testQEMUSchemaValidateCommand("netdev_add", jsonargs, ++ schema, &debug) < 0) { ++ VIR_TEST_VERBOSE("failed to validate -netdev '%s' against QAPI schema: %s", ++ args[i + 1], virBufferCurrentContent(&debug)); ++ return -1; ++ } ++ ++ i++; ++ } ++ } ++ ++ return 0; ++} ++ ++ + static int + testCompareXMLToArgv(const void *data) + { +@@ -582,6 +655,9 @@ testCompareXMLToArgv(const void *data) + goto cleanup; + } + ++ if (testCompareXMLToArgvValidateSchema(&driver, vm, migrateURI, info, flags) < 0) ++ goto cleanup; ++ + if (!(actualargv = virCommandToString(cmd, false))) + goto cleanup; + +diff --git a/tests/testutilsqemu.c b/tests/testutilsqemu.c +index f3b4e2b3b2..bae1fa828b 100644 +--- a/tests/testutilsqemu.c ++++ b/tests/testutilsqemu.c +@@ -767,6 +767,10 @@ testQemuInfoSetArgs(struct testQemuInfo *info, + if (stripmachinealiases) + virQEMUCapsStripMachineAliases(qemuCaps); + info->flags |= FLAG_REAL_CAPS; ++ ++ /* provide path to the replies file for schema testing */ ++ capsfile[strlen(capsfile) - 3] = '\0'; ++ info->schemafile = g_strdup_printf("%sreplies", capsfile); + } + + if (!qemuCaps) { +@@ -793,5 +797,6 @@ testQemuInfoClear(struct testQemuInfo *info) + { + VIR_FREE(info->infile); + VIR_FREE(info->outfile); ++ VIR_FREE(info->schemafile); + virObjectUnref(info->qemuCaps); + } +diff --git a/tests/testutilsqemu.h b/tests/testutilsqemu.h +index edee6e450c..e7c5032012 100644 +--- a/tests/testutilsqemu.h ++++ b/tests/testutilsqemu.h +@@ -64,6 +64,7 @@ struct testQemuInfo { + unsigned int flags; + unsigned int parseFlags; + virArch arch; ++ char *schemafile; + }; + + virCapsPtr testQemuCapsInit(void); +-- +2.23.0.windows.1 + diff --git a/testCompareXMLToArgv-Split-out-preparation-and-comma.patch b/testCompareXMLToArgv-Split-out-preparation-and-comma.patch new file mode 100644 index 0000000000000000000000000000000000000000..b81e2fadd8bda89c1da5fbcf8ebda33eee42d6bd --- /dev/null +++ b/testCompareXMLToArgv-Split-out-preparation-and-comma.patch @@ -0,0 +1,204 @@ +From fb0de8df975557e74af01a0eee0d25c08981c02a Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Fri, 15 May 2020 08:50:31 +0200 +Subject: [PATCH 15/18] testCompareXMLToArgv: Split out preparation and command + formatting + +There are multiple steps of setting up the domain definition prior to +formatting the command line for the tests. Extract it to a separate +function so that it's self-contained and also will allow re-running the +command line formatting which will be necessary for QMP schema +validation tests. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + tests/qemuxml2argvtest.c | 158 +++++++++++++++++++++------------------ + 1 file changed, 86 insertions(+), 72 deletions(-) + +diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c +index 2153e44c5a..762d65f521 100644 +--- a/tests/qemuxml2argvtest.c ++++ b/tests/qemuxml2argvtest.c +@@ -395,6 +395,90 @@ testCheckExclusiveFlags(int flags) + } + + ++static virCommandPtr ++testCompareXMLToArgvCreateArgs(virQEMUDriverPtr drv, ++ virDomainObjPtr vm, ++ const char *migrateURI, ++ struct testQemuInfo *info, ++ unsigned int flags) ++{ ++ size_t i; ++ ++ for (i = 0; i < vm->def->nhostdevs; i++) { ++ virDomainHostdevDefPtr hostdev = vm->def->hostdevs[i]; ++ ++ if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS && ++ hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI && ++ hostdev->source.subsys.u.pci.backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT) { ++ hostdev->source.subsys.u.pci.backend = VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO; ++ } ++ } ++ ++ for (i = 0; i < vm->def->nfss; i++) { ++ virDomainFSDefPtr fs = vm->def->fss[i]; ++ char *s; ++ ++ if (fs->fsdriver != VIR_DOMAIN_FS_DRIVER_TYPE_VIRTIOFS || ++ QEMU_DOMAIN_FS_PRIVATE(fs)->vhostuser_fs_sock) ++ continue; ++ ++ s = g_strdup_printf("/tmp/lib/domain--1-guest/fs%zu.vhost-fs.sock", i); ++ QEMU_DOMAIN_FS_PRIVATE(fs)->vhostuser_fs_sock = s; ++ } ++ ++ if (vm->def->vsock) { ++ virDomainVsockDefPtr vsock = vm->def->vsock; ++ qemuDomainVsockPrivatePtr vsockPriv = ++ (qemuDomainVsockPrivatePtr)vsock->privateData; ++ ++ if (vsock->auto_cid == VIR_TRISTATE_BOOL_YES) ++ vsock->guest_cid = 42; ++ ++ vsockPriv->vhostfd = 6789; ++ } ++ ++ if (vm->def->tpm) { ++ switch (vm->def->tpm->type) { ++ case VIR_DOMAIN_TPM_TYPE_EMULATOR: ++ VIR_FREE(vm->def->tpm->data.emulator.source.data.file.path); ++ vm->def->tpm->data.emulator.source.data.file.path = g_strdup("/dev/test"); ++ vm->def->tpm->data.emulator.source.type = VIR_DOMAIN_CHR_TYPE_FILE; ++ break; ++ case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: ++ case VIR_DOMAIN_TPM_TYPE_LAST: ++ break; ++ } ++ } ++ ++ for (i = 0; i < vm->def->nvideos; i++) { ++ virDomainVideoDefPtr video = vm->def->videos[i]; ++ ++ if (video->backend == VIR_DOMAIN_VIDEO_BACKEND_TYPE_VHOSTUSER) { ++ qemuDomainVideoPrivatePtr vpriv = QEMU_DOMAIN_VIDEO_PRIVATE(video); ++ ++ vpriv->vhost_user_fd = 1729; ++ } ++ } ++ ++ if (flags & FLAG_SLIRP_HELPER) { ++ for (i = 0; i < vm->def->nnets; i++) { ++ virDomainNetDefPtr net = vm->def->nets[i]; ++ ++ if (net->type == VIR_DOMAIN_NET_TYPE_USER && ++ virQEMUCapsGet(info->qemuCaps, QEMU_CAPS_DBUS_VMSTATE)) { ++ qemuSlirpPtr slirp = qemuSlirpNew(); ++ slirp->fd[0] = 42; ++ QEMU_DOMAIN_NETWORK_PRIVATE(net)->slirp = slirp; ++ } ++ } ++ } ++ ++ return qemuProcessCreatePretendCmd(drv, vm, migrateURI, ++ (flags & FLAG_FIPS), false, ++ VIR_QEMU_PROCESS_START_COLD); ++} ++ ++ + static int + testCompareXMLToArgv(const void *data) + { +@@ -409,7 +493,6 @@ testCompareXMLToArgv(const void *data) + virConnectPtr conn; + char *log = NULL; + virCommandPtr cmd = NULL; +- size_t i; + qemuDomainObjPrivatePtr priv = NULL; + + if (info->arch != VIR_ARCH_NONE && info->arch != VIR_ARCH_X86_64) +@@ -486,77 +569,8 @@ testCompareXMLToArgv(const void *data) + VIR_FREE(log); + virResetLastError(); + +- for (i = 0; i < vm->def->nhostdevs; i++) { +- virDomainHostdevDefPtr hostdev = vm->def->hostdevs[i]; +- +- if (hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS && +- hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI && +- hostdev->source.subsys.u.pci.backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_DEFAULT) { +- hostdev->source.subsys.u.pci.backend = VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO; +- } +- } +- +- for (i = 0; i < vm->def->nfss; i++) { +- virDomainFSDefPtr fs = vm->def->fss[i]; +- char *s; +- +- if (fs->fsdriver != VIR_DOMAIN_FS_DRIVER_TYPE_VIRTIOFS) +- continue; +- +- s = g_strdup_printf("/tmp/lib/domain--1-guest/fs%zu.vhost-fs.sock", i); +- QEMU_DOMAIN_FS_PRIVATE(fs)->vhostuser_fs_sock = s; +- } +- +- if (vm->def->vsock) { +- virDomainVsockDefPtr vsock = vm->def->vsock; +- qemuDomainVsockPrivatePtr vsockPriv = +- (qemuDomainVsockPrivatePtr)vsock->privateData; +- +- if (vsock->auto_cid == VIR_TRISTATE_BOOL_YES) +- vsock->guest_cid = 42; +- +- vsockPriv->vhostfd = 6789; +- } +- +- if (vm->def->tpm) { +- switch (vm->def->tpm->type) { +- case VIR_DOMAIN_TPM_TYPE_EMULATOR: +- VIR_FREE(vm->def->tpm->data.emulator.source.data.file.path); +- vm->def->tpm->data.emulator.source.data.file.path = g_strdup("/dev/test"); +- vm->def->tpm->data.emulator.source.type = VIR_DOMAIN_CHR_TYPE_FILE; +- break; +- case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH: +- case VIR_DOMAIN_TPM_TYPE_LAST: +- break; +- } +- } +- +- for (i = 0; i < vm->def->nvideos; i++) { +- virDomainVideoDefPtr video = vm->def->videos[i]; +- +- if (video->backend == VIR_DOMAIN_VIDEO_BACKEND_TYPE_VHOSTUSER) { +- qemuDomainVideoPrivatePtr vpriv = QEMU_DOMAIN_VIDEO_PRIVATE(video); +- +- vpriv->vhost_user_fd = 1729; +- } +- } +- +- if (flags & FLAG_SLIRP_HELPER) { +- for (i = 0; i < vm->def->nnets; i++) { +- virDomainNetDefPtr net = vm->def->nets[i]; +- +- if (net->type == VIR_DOMAIN_NET_TYPE_USER && +- virQEMUCapsGet(info->qemuCaps, QEMU_CAPS_DBUS_VMSTATE)) { +- qemuSlirpPtr slirp = qemuSlirpNew(); +- slirp->fd[0] = 42; +- QEMU_DOMAIN_NETWORK_PRIVATE(net)->slirp = slirp; +- } +- } +- } +- +- if (!(cmd = qemuProcessCreatePretendCmd(&driver, vm, migrateURI, +- (flags & FLAG_FIPS), false, +- VIR_QEMU_PROCESS_START_COLD))) { ++ if (!(cmd = testCompareXMLToArgvCreateArgs(&driver, vm, migrateURI, info, ++ flags))) { + if (flags & FLAG_EXPECT_FAILURE) + goto ok; + goto cleanup; +-- +2.23.0.windows.1 + diff --git a/testutilsqemuschema-Introduce-testQEMUSchemaValidate.patch b/testutilsqemuschema-Introduce-testQEMUSchemaValidate.patch new file mode 100644 index 0000000000000000000000000000000000000000..11f423b515edf32f2482dedc9772fbea1a7891fe --- /dev/null +++ b/testutilsqemuschema-Introduce-testQEMUSchemaValidate.patch @@ -0,0 +1,94 @@ +From 740581f96fa4007d6549e82131a9ef48a6478149 Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Wed, 4 Mar 2020 10:22:19 +0100 +Subject: [PATCH 14/18] testutilsqemuschema: Introduce + testQEMUSchemaValidateCommand +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The new helper splits out all steps necessary to validate a QMP command +against the schema. + +Signed-off-by: Peter Krempa +Reviewed-by: Ján Tomko +--- + tests/testutilsqemuschema.c | 44 +++++++++++++++++++++++++++++++++++++ + tests/testutilsqemuschema.h | 6 +++++ + 2 files changed, 50 insertions(+) + +diff --git a/tests/testutilsqemuschema.c b/tests/testutilsqemuschema.c +index 7b82ff27b2..fe8501f3a0 100644 +--- a/tests/testutilsqemuschema.c ++++ b/tests/testutilsqemuschema.c +@@ -517,6 +517,50 @@ testQEMUSchemaValidate(virJSONValuePtr obj, + } + + ++/** ++ * testQEMUSchemaValidateCommand: ++ * @command: command to validate ++ * @arguments: arguments of @command to validate ++ * @schema: hash table containing schema entries ++ * @debug: a virBuffer which will be filled with debug information if provided ++ * ++ * Validates whether @command and its @arguments conform to the QAPI schema ++ * passed in via @schema. Returns 0, if the command and args match @schema, ++ * -1 if it does not and -2 if there is a problem with the schema or with ++ * internals. ++ * ++ * @debug is filled with information regarding the validation process ++ */ ++int ++testQEMUSchemaValidateCommand(const char *command, ++ virJSONValuePtr arguments, ++ virHashTablePtr schema, ++ virBufferPtr debug) ++{ ++ g_autofree char *schemapatharguments = g_strdup_printf("%s/arg-type", command); ++ g_autoptr(virJSONValue) emptyargs = NULL; ++ virJSONValuePtr schemarootcommand; ++ virJSONValuePtr schemarootarguments; ++ ++ if (virQEMUQAPISchemaPathGet(command, schema, &schemarootcommand) < 0 || ++ !schemarootcommand) { ++ virBufferAsprintf(debug, "ERROR: command '%s' not found in the schema", command); ++ return -1; ++ } ++ ++ if (!arguments) ++ arguments = emptyargs = virJSONValueNewObject(); ++ ++ if (virQEMUQAPISchemaPathGet(schemapatharguments, schema, &schemarootarguments) < 0 || ++ !schemarootarguments) { ++ virBufferAsprintf(debug, "ERROR: failed to look up 'arg-type' of '%s'", command); ++ return -1; ++ } ++ ++ return testQEMUSchemaValidateRecurse(arguments, schemarootarguments, schema, debug); ++} ++ ++ + /** + * testQEMUSchemaGetLatest: + * +diff --git a/tests/testutilsqemuschema.h b/tests/testutilsqemuschema.h +index 84ee9a9670..e3a375b038 100644 +--- a/tests/testutilsqemuschema.h ++++ b/tests/testutilsqemuschema.h +@@ -28,6 +28,12 @@ testQEMUSchemaValidate(virJSONValuePtr obj, + virHashTablePtr schema, + virBufferPtr debug); + ++int ++testQEMUSchemaValidateCommand(const char *command, ++ virJSONValuePtr arguments, ++ virHashTablePtr schema, ++ virBufferPtr debug); ++ + virJSONValuePtr + testQEMUSchemaGetLatest(const char* arch); + +-- +2.23.0.windows.1 + diff --git a/util-json-Introduce-virJSONValueObjectAppendStringPr.patch b/util-json-Introduce-virJSONValueObjectAppendStringPr.patch new file mode 100644 index 0000000000000000000000000000000000000000..7f63b0e99d993bec1109ff8a95ac4b5d2beb1549 --- /dev/null +++ b/util-json-Introduce-virJSONValueObjectAppendStringPr.patch @@ -0,0 +1,73 @@ +From 0924902c3814677f9f328f521a95d4a79635d81a Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Thu, 14 May 2020 08:09:56 +0200 +Subject: [PATCH 04/18] util: json: Introduce + virJSONValueObjectAppendStringPrintf + +Add a variant similar to virJSONValueObjectAppendString which also +formats more complex value strings with printf syntax. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/libvirt_private.syms | 1 + + src/util/virjson.c | 17 +++++++++++++++++ + src/util/virjson.h | 2 ++ + 3 files changed, 20 insertions(+) + +diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms +index 84f3bd57ca..726e7a21f9 100644 +--- a/src/libvirt_private.syms ++++ b/src/libvirt_private.syms +@@ -2349,6 +2349,7 @@ virJSONValueObjectAppendNumberLong; + virJSONValueObjectAppendNumberUint; + virJSONValueObjectAppendNumberUlong; + virJSONValueObjectAppendString; ++virJSONValueObjectAppendStringPrintf; + virJSONValueObjectCreate; + virJSONValueObjectCreateVArgs; + virJSONValueObjectDeflatten; +diff --git a/src/util/virjson.c b/src/util/virjson.c +index dc662bf8e9..6921eccb60 100644 +--- a/src/util/virjson.c ++++ b/src/util/virjson.c +@@ -649,6 +649,23 @@ virJSONValueObjectAppendString(virJSONValuePtr object, + } + + ++int ++virJSONValueObjectAppendStringPrintf(virJSONValuePtr object, ++ const char *key, ++ const char *fmt, ++ ...) ++{ ++ va_list ap; ++ g_autofree char *str = NULL; ++ ++ va_start(ap, fmt); ++ str = g_strdup_vprintf(fmt, ap); ++ va_end(ap); ++ ++ return virJSONValueObjectInsertString(object, key, str, false); ++} ++ ++ + int + virJSONValueObjectPrependString(virJSONValuePtr object, + const char *key, +diff --git a/src/util/virjson.h b/src/util/virjson.h +index 0894e91b59..588c977650 100644 +--- a/src/util/virjson.h ++++ b/src/util/virjson.h +@@ -127,6 +127,8 @@ int virJSONValueObjectGetBoolean(virJSONValuePtr object, const char *key, bool * + int virJSONValueObjectIsNull(virJSONValuePtr object, const char *key); + + int virJSONValueObjectAppendString(virJSONValuePtr object, const char *key, const char *value); ++int virJSONValueObjectAppendStringPrintf(virJSONValuePtr object, const char *key, const char *fmt, ...) ++ G_GNUC_PRINTF(3, 4); + int virJSONValueObjectPrependString(virJSONValuePtr object, const char *key, const char *value); + int virJSONValueObjectAppendNumberInt(virJSONValuePtr object, const char *key, int number); + int virJSONValueObjectAppendNumberUint(virJSONValuePtr object, const char *key, unsigned int number); +-- +2.23.0.windows.1 + diff --git a/util-virqemu-Introduce-virQEMUBuildNetdevCommandline.patch b/util-virqemu-Introduce-virQEMUBuildNetdevCommandline.patch new file mode 100644 index 0000000000000000000000000000000000000000..299725dedc3d47e5c71b0fead1eeeb8c52342bcd --- /dev/null +++ b/util-virqemu-Introduce-virQEMUBuildNetdevCommandline.patch @@ -0,0 +1,81 @@ +From b9ff1a5a81df23f0a138a7f6d7d3ab0654f669e2 Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Thu, 14 May 2020 10:10:16 +0200 +Subject: [PATCH 03/18] util: virqemu: Introduce + virQEMUBuildNetdevCommandlineFromJSON + +In preparation for converting the generator of -netdev to generate JSON +which will be used to do the command line rather than the other way +around we need to introduce a convertor which properly configures +virQEMUBuildCommandLineJSON for the quirks of -netdev. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/libvirt_private.syms | 1 + + src/util/virqemu.c | 22 ++++++++++++++++++++++ + src/util/virqemu.h | 3 +++ + 3 files changed, 26 insertions(+) + +diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms +index bac96e140e..84f3bd57ca 100644 +--- a/src/libvirt_private.syms ++++ b/src/libvirt_private.syms +@@ -2902,6 +2902,7 @@ virQEMUBuildCommandLineJSON; + virQEMUBuildCommandLineJSONArrayBitmap; + virQEMUBuildCommandLineJSONArrayNumbered; + virQEMUBuildDriveCommandlineFromJSON; ++virQEMUBuildNetdevCommandlineFromJSON; + virQEMUBuildObjectCommandlineFromJSON; + virQEMUBuildQemuImgKeySecretOpts; + +diff --git a/src/util/virqemu.c b/src/util/virqemu.c +index 549f88fcd5..0f8cab29df 100644 +--- a/src/util/virqemu.c ++++ b/src/util/virqemu.c +@@ -252,6 +252,28 @@ virQEMUBuildCommandLineJSON(virJSONValuePtr value, + } + + ++/** ++ * virQEMUBuildNetdevCommandlineFromJSON: ++ * @props: JSON properties describing a netdev ++ * ++ * Converts @props into arguments for -netdev including all the quirks and ++ * differences between the monitor and command line syntax. ++ */ ++char * ++virQEMUBuildNetdevCommandlineFromJSON(virJSONValuePtr props) ++{ ++ const char *type = virJSONValueObjectGetString(props, "type"); ++ g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; ++ ++ virBufferAsprintf(&buf, "%s,", type); ++ ++ if (virQEMUBuildCommandLineJSON(props, &buf, "type", true, NULL) < 0) ++ return NULL; ++ ++ return virBufferContentAndReset(&buf); ++} ++ ++ + static int + virQEMUBuildObjectCommandlineFromJSONInternal(virBufferPtr buf, + const char *type, +diff --git a/src/util/virqemu.h b/src/util/virqemu.h +index 67a5711613..22f47851df 100644 +--- a/src/util/virqemu.h ++++ b/src/util/virqemu.h +@@ -49,6 +49,9 @@ int virQEMUBuildCommandLineJSON(virJSONValuePtr value, + bool onOff, + virQEMUBuildCommandLineJSONArrayFormatFunc array); + ++char * ++virQEMUBuildNetdevCommandlineFromJSON(virJSONValuePtr props); ++ + int virQEMUBuildObjectCommandlineFromJSON(virBufferPtr buf, + virJSONValuePtr objprops); + +-- +2.23.0.windows.1 + diff --git a/virCommand-Introduce-virCommandGetArgList.patch b/virCommand-Introduce-virCommandGetArgList.patch new file mode 100644 index 0000000000000000000000000000000000000000..997ededa162c96c0d2a0db26102b97e915dac9c1 --- /dev/null +++ b/virCommand-Introduce-virCommandGetArgList.patch @@ -0,0 +1,77 @@ +From 548ea1fcfcfbfc40fab04301e780d28ebe4320a4 Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Wed, 13 May 2020 17:55:21 +0200 +Subject: [PATCH 13/18] virCommand: Introduce virCommandGetArgList + +The helper returns a list of arguments of a virCommand. This will be +useful in tests where we'll inspect certain already formatted arguments. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/libvirt_private.syms | 1 + + src/util/vircommand.c | 23 +++++++++++++++++++++++ + src/util/vircommand.h | 1 + + 3 files changed, 25 insertions(+) + +diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms +index 726e7a21f9..861a4892be 100644 +--- a/src/libvirt_private.syms ++++ b/src/libvirt_private.syms +@@ -1829,6 +1829,7 @@ virCommandDaemonize; + virCommandDoAsyncIO; + virCommandExec; + virCommandFree; ++virCommandGetArgList; + virCommandGetGID; + virCommandGetUID; + virCommandHandshakeNotify; +diff --git a/src/util/vircommand.c b/src/util/vircommand.c +index b84fb40948..86e4c5cd39 100644 +--- a/src/util/vircommand.c ++++ b/src/util/vircommand.c +@@ -2167,6 +2167,29 @@ virCommandToString(virCommandPtr cmd, bool linebreaks) + } + + ++int ++virCommandGetArgList(virCommandPtr cmd, ++ char ***args, ++ size_t *nargs) ++{ ++ size_t i; ++ ++ if (cmd->has_error) { ++ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", ++ _("invalid use of command API")); ++ return -1; ++ } ++ ++ *args = g_new0(char *, cmd->nargs); ++ *nargs = cmd->nargs - 1; ++ ++ for (i = 1; i < cmd->nargs; i++) ++ (*args)[i - 1] = g_strdup(cmd->args[i]); ++ ++ return 0; ++} ++ ++ + #ifndef WIN32 + /* + * Manage input and output to the child process. +diff --git a/src/util/vircommand.h b/src/util/vircommand.h +index 4e6cb0ac0d..854bfe6576 100644 +--- a/src/util/vircommand.h ++++ b/src/util/vircommand.h +@@ -171,6 +171,7 @@ void virCommandWriteArgLog(virCommandPtr cmd, + int logfd); + + char *virCommandToString(virCommandPtr cmd, bool linebreaks) G_GNUC_WARN_UNUSED_RESULT; ++int virCommandGetArgList(virCommandPtr cmd, char ***args, size_t *nargs); + + int virCommandExec(virCommandPtr cmd, gid_t *groups, int ngroups) G_GNUC_WARN_UNUSED_RESULT; + +-- +2.23.0.windows.1 + diff --git a/virQEMUBuildCommandLineJSON-Add-possibility-for-usin.patch b/virQEMUBuildCommandLineJSON-Add-possibility-for-usin.patch new file mode 100644 index 0000000000000000000000000000000000000000..06aa3aaa7ecd1f718ac9c28c20d44e2fa7247eb5 --- /dev/null +++ b/virQEMUBuildCommandLineJSON-Add-possibility-for-usin.patch @@ -0,0 +1,218 @@ +From b80d9efd8af5de335e3c1de24b74d2e647832458 Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Thu, 14 May 2020 22:45:24 +0200 +Subject: [PATCH 02/18] virQEMUBuildCommandLineJSON: Add possibility for using + 'on/off' instead of 'yes/no' + +In some cases we use 'on/off' for command line arguments. Add a switch +which will select the preferred spelling for a specific usage. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/util/virqemu.c | 44 ++++++++++++++++++++++++------------- + src/util/virqemu.h | 10 ++++++--- + tests/qemucommandutiltest.c | 2 +- + 3 files changed, 37 insertions(+), 19 deletions(-) + +diff --git a/src/util/virqemu.c b/src/util/virqemu.c +index 0e6fa412bc..549f88fcd5 100644 +--- a/src/util/virqemu.c ++++ b/src/util/virqemu.c +@@ -37,6 +37,7 @@ struct virQEMUCommandLineJSONIteratorData { + const char *prefix; + virBufferPtr buf; + const char *skipKey; ++ bool onOff; + virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc; + }; + +@@ -46,6 +47,7 @@ virQEMUBuildCommandLineJSONRecurse(const char *key, + virJSONValuePtr value, + virBufferPtr buf, + const char *skipKey, ++ bool onOff, + virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc, + bool nested); + +@@ -55,7 +57,8 @@ int + virQEMUBuildCommandLineJSONArrayBitmap(const char *key, + virJSONValuePtr array, + virBufferPtr buf, +- const char *skipKey G_GNUC_UNUSED) ++ const char *skipKey G_GNUC_UNUSED, ++ bool onOff G_GNUC_UNUSED) + { + ssize_t pos = -1; + ssize_t end; +@@ -84,7 +87,8 @@ int + virQEMUBuildCommandLineJSONArrayNumbered(const char *key, + virJSONValuePtr array, + virBufferPtr buf, +- const char *skipKey) ++ const char *skipKey, ++ bool onOff) + { + virJSONValuePtr member; + size_t i; +@@ -95,7 +99,7 @@ virQEMUBuildCommandLineJSONArrayNumbered(const char *key, + + prefix = g_strdup_printf("%s.%zu", key, i); + +- if (virQEMUBuildCommandLineJSONRecurse(prefix, member, buf, skipKey, ++ if (virQEMUBuildCommandLineJSONRecurse(prefix, member, buf, skipKey, onOff, + virQEMUBuildCommandLineJSONArrayNumbered, + true) < 0) + return 0; +@@ -122,11 +126,11 @@ virQEMUBuildCommandLineJSONIterate(const char *key, + tmpkey = g_strdup_printf("%s.%s", data->prefix, key); + + return virQEMUBuildCommandLineJSONRecurse(tmpkey, value, data->buf, +- data->skipKey, ++ data->skipKey, data->onOff, + data->arrayFunc, false); + } else { + return virQEMUBuildCommandLineJSONRecurse(key, value, data->buf, +- data->skipKey, ++ data->skipKey, data->onOff, + data->arrayFunc, false); + } + } +@@ -137,10 +141,11 @@ virQEMUBuildCommandLineJSONRecurse(const char *key, + virJSONValuePtr value, + virBufferPtr buf, + const char *skipKey, ++ bool onOff, + virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc, + bool nested) + { +- struct virQEMUCommandLineJSONIteratorData data = { key, buf, skipKey, arrayFunc }; ++ struct virQEMUCommandLineJSONIteratorData data = { key, buf, skipKey, onOff, arrayFunc }; + virJSONType type = virJSONValueGetType(value); + virJSONValuePtr elem; + bool tmp; +@@ -165,10 +170,17 @@ virQEMUBuildCommandLineJSONRecurse(const char *key, + + case VIR_JSON_TYPE_BOOLEAN: + virJSONValueGetBoolean(value, &tmp); +- if (tmp) +- virBufferAsprintf(buf, "%s=yes,", key); +- else +- virBufferAsprintf(buf, "%s=no,", key); ++ 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; + +@@ -180,7 +192,7 @@ virQEMUBuildCommandLineJSONRecurse(const char *key, + return -1; + } + +- if (!arrayFunc || arrayFunc(key, value, buf, skipKey) < 0) { ++ 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++) { +@@ -188,7 +200,7 @@ virQEMUBuildCommandLineJSONRecurse(const char *key, + + /* recurse to avoid duplicating code */ + if (virQEMUBuildCommandLineJSONRecurse(key, elem, buf, skipKey, +- arrayFunc, true) < 0) ++ onOff, arrayFunc, true) < 0) + return -1; + } + } +@@ -216,6 +228,7 @@ virQEMUBuildCommandLineJSONRecurse(const char *key, + * @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 +@@ -227,9 +240,10 @@ int + virQEMUBuildCommandLineJSON(virJSONValuePtr value, + virBufferPtr buf, + const char *skipKey, ++ bool onOff, + virQEMUBuildCommandLineJSONArrayFormatFunc array) + { +- if (virQEMUBuildCommandLineJSONRecurse(NULL, value, buf, skipKey, array, false) < 0) ++ if (virQEMUBuildCommandLineJSONRecurse(NULL, value, buf, skipKey, onOff, array, false) < 0) + return -1; + + virBufferTrim(buf, ","); +@@ -255,7 +269,7 @@ virQEMUBuildObjectCommandlineFromJSONInternal(virBufferPtr buf, + + if (props) { + virBufferAddLit(buf, ","); +- if (virQEMUBuildCommandLineJSON(props, buf, NULL, ++ if (virQEMUBuildCommandLineJSON(props, buf, NULL, false, + virQEMUBuildCommandLineJSONArrayBitmap) < 0) + return -1; + } +@@ -282,7 +296,7 @@ virQEMUBuildDriveCommandlineFromJSON(virJSONValuePtr srcdef) + virBuffer buf = VIR_BUFFER_INITIALIZER; + char *ret = NULL; + +- if (virQEMUBuildCommandLineJSON(srcdef, &buf, NULL, ++ if (virQEMUBuildCommandLineJSON(srcdef, &buf, NULL, false, + virQEMUBuildCommandLineJSONArrayNumbered) < 0) + goto cleanup; + +diff --git a/src/util/virqemu.h b/src/util/virqemu.h +index 9d3db7c2a2..67a5711613 100644 +--- a/src/util/virqemu.h ++++ b/src/util/virqemu.h +@@ -30,19 +30,23 @@ + typedef int (*virQEMUBuildCommandLineJSONArrayFormatFunc)(const char *key, + virJSONValuePtr array, + virBufferPtr buf, +- const char *skipKey); ++ const char *skipKey, ++ bool onOff); + int virQEMUBuildCommandLineJSONArrayBitmap(const char *key, + virJSONValuePtr array, + virBufferPtr buf, +- const char *skipKey); ++ const char *skipKey, ++ bool onOff); + int virQEMUBuildCommandLineJSONArrayNumbered(const char *key, + virJSONValuePtr array, + virBufferPtr buf, +- const char *skipKey); ++ const char *skipKey, ++ bool onOff); + + int virQEMUBuildCommandLineJSON(virJSONValuePtr value, + virBufferPtr buf, + const char *skipKey, ++ bool onOff, + virQEMUBuildCommandLineJSONArrayFormatFunc array); + + int virQEMUBuildObjectCommandlineFromJSON(virBufferPtr buf, +diff --git a/tests/qemucommandutiltest.c b/tests/qemucommandutiltest.c +index 923776e642..049fd2f0b0 100644 +--- a/tests/qemucommandutiltest.c ++++ b/tests/qemucommandutiltest.c +@@ -47,7 +47,7 @@ testQemuCommandBuildFromJSON(const void *opaque) + return -1; + } + +- if (virQEMUBuildCommandLineJSON(val, &buf, NULL, data->arrayfunc) < 0) { ++ if (virQEMUBuildCommandLineJSON(val, &buf, NULL, false, data->arrayfunc) < 0) { + fprintf(stderr, + "\nvirQEMUBuildCommandlineJSON failed process JSON:\n%s\n", + data->props); +-- +2.23.0.windows.1 + diff --git a/virQEMUBuildCommandLineJSON-Allow-skipping-certain-k.patch b/virQEMUBuildCommandLineJSON-Allow-skipping-certain-k.patch new file mode 100644 index 0000000000000000000000000000000000000000..e67e949cb9aaa2de93870bae12dbfd409da055c4 --- /dev/null +++ b/virQEMUBuildCommandLineJSON-Allow-skipping-certain-k.patch @@ -0,0 +1,202 @@ +From b1e8b606813b1097cdb3a0d9e9f04c629d7bcf29 Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Thu, 14 May 2020 09:41:48 +0200 +Subject: [PATCH 01/18] virQEMUBuildCommandLineJSON: Allow skipping certain + keys + +Allow reusing this for formatting of netdev_add arguments into -netdev. +We need to be able to skip the 'type' property as it's used without the +prefix by our generator. + +Add infrastructure which allows skipping property with a specific name. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/util/virqemu.c | 30 +++++++++++++++++++++--------- + src/util/virqemu.h | 10 +++++++--- + tests/qemucommandutiltest.c | 2 +- + 3 files changed, 29 insertions(+), 13 deletions(-) + +diff --git a/src/util/virqemu.c b/src/util/virqemu.c +index 78a9e0480b..0e6fa412bc 100644 +--- a/src/util/virqemu.c ++++ b/src/util/virqemu.c +@@ -36,6 +36,7 @@ VIR_LOG_INIT("util.qemu"); + struct virQEMUCommandLineJSONIteratorData { + const char *prefix; + virBufferPtr buf; ++ const char *skipKey; + virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc; + }; + +@@ -44,6 +45,7 @@ static int + virQEMUBuildCommandLineJSONRecurse(const char *key, + virJSONValuePtr value, + virBufferPtr buf, ++ const char *skipKey, + virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc, + bool nested); + +@@ -52,7 +54,8 @@ virQEMUBuildCommandLineJSONRecurse(const char *key, + int + virQEMUBuildCommandLineJSONArrayBitmap(const char *key, + virJSONValuePtr array, +- virBufferPtr buf) ++ virBufferPtr buf, ++ const char *skipKey G_GNUC_UNUSED) + { + ssize_t pos = -1; + ssize_t end; +@@ -80,7 +83,8 @@ virQEMUBuildCommandLineJSONArrayBitmap(const char *key, + int + virQEMUBuildCommandLineJSONArrayNumbered(const char *key, + virJSONValuePtr array, +- virBufferPtr buf) ++ virBufferPtr buf, ++ const char *skipKey) + { + virJSONValuePtr member; + size_t i; +@@ -91,7 +95,7 @@ virQEMUBuildCommandLineJSONArrayNumbered(const char *key, + + prefix = g_strdup_printf("%s.%zu", key, i); + +- if (virQEMUBuildCommandLineJSONRecurse(prefix, member, buf, ++ if (virQEMUBuildCommandLineJSONRecurse(prefix, member, buf, skipKey, + virQEMUBuildCommandLineJSONArrayNumbered, + true) < 0) + return 0; +@@ -109,15 +113,20 @@ virQEMUBuildCommandLineJSONIterate(const char *key, + { + 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); + } + } +@@ -127,10 +136,11 @@ static int + virQEMUBuildCommandLineJSONRecurse(const char *key, + virJSONValuePtr value, + virBufferPtr buf, ++ const char *skipKey, + virQEMUBuildCommandLineJSONArrayFormatFunc arrayFunc, + bool nested) + { +- struct virQEMUCommandLineJSONIteratorData data = { key, buf, arrayFunc }; ++ struct virQEMUCommandLineJSONIteratorData data = { key, buf, skipKey, arrayFunc }; + virJSONType type = virJSONValueGetType(value); + virJSONValuePtr elem; + bool tmp; +@@ -170,14 +180,14 @@ virQEMUBuildCommandLineJSONRecurse(const char *key, + return -1; + } + +- if (!arrayFunc || arrayFunc(key, value, buf) < 0) { ++ 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, ++ if (virQEMUBuildCommandLineJSONRecurse(key, elem, buf, skipKey, + arrayFunc, true) < 0) + return -1; + } +@@ -205,6 +215,7 @@ virQEMUBuildCommandLineJSONRecurse(const char *key, + * 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 +@@ -215,9 +226,10 @@ virQEMUBuildCommandLineJSONRecurse(const char *key, + int + virQEMUBuildCommandLineJSON(virJSONValuePtr value, + virBufferPtr buf, ++ const char *skipKey, + virQEMUBuildCommandLineJSONArrayFormatFunc array) + { +- if (virQEMUBuildCommandLineJSONRecurse(NULL, value, buf, array, false) < 0) ++ if (virQEMUBuildCommandLineJSONRecurse(NULL, value, buf, skipKey, array, false) < 0) + return -1; + + virBufferTrim(buf, ","); +@@ -243,7 +255,7 @@ virQEMUBuildObjectCommandlineFromJSONInternal(virBufferPtr buf, + + if (props) { + virBufferAddLit(buf, ","); +- if (virQEMUBuildCommandLineJSON(props, buf, ++ if (virQEMUBuildCommandLineJSON(props, buf, NULL, + virQEMUBuildCommandLineJSONArrayBitmap) < 0) + return -1; + } +@@ -270,7 +282,7 @@ virQEMUBuildDriveCommandlineFromJSON(virJSONValuePtr srcdef) + virBuffer buf = VIR_BUFFER_INITIALIZER; + char *ret = NULL; + +- if (virQEMUBuildCommandLineJSON(srcdef, &buf, ++ if (virQEMUBuildCommandLineJSON(srcdef, &buf, NULL, + virQEMUBuildCommandLineJSONArrayNumbered) < 0) + goto cleanup; + +diff --git a/src/util/virqemu.h b/src/util/virqemu.h +index 227325e80e..9d3db7c2a2 100644 +--- a/src/util/virqemu.h ++++ b/src/util/virqemu.h +@@ -29,16 +29,20 @@ + + typedef int (*virQEMUBuildCommandLineJSONArrayFormatFunc)(const char *key, + virJSONValuePtr array, +- virBufferPtr buf); ++ virBufferPtr buf, ++ const char *skipKey); + int virQEMUBuildCommandLineJSONArrayBitmap(const char *key, + virJSONValuePtr array, +- virBufferPtr buf); ++ virBufferPtr buf, ++ const char *skipKey); + int virQEMUBuildCommandLineJSONArrayNumbered(const char *key, + virJSONValuePtr array, +- virBufferPtr buf); ++ virBufferPtr buf, ++ const char *skipKey); + + int virQEMUBuildCommandLineJSON(virJSONValuePtr value, + virBufferPtr buf, ++ const char *skipKey, + virQEMUBuildCommandLineJSONArrayFormatFunc array); + + int virQEMUBuildObjectCommandlineFromJSON(virBufferPtr buf, +diff --git a/tests/qemucommandutiltest.c b/tests/qemucommandutiltest.c +index c5b3e7b735..923776e642 100644 +--- a/tests/qemucommandutiltest.c ++++ b/tests/qemucommandutiltest.c +@@ -47,7 +47,7 @@ testQemuCommandBuildFromJSON(const void *opaque) + return -1; + } + +- if (virQEMUBuildCommandLineJSON(val, &buf, data->arrayfunc) < 0) { ++ if (virQEMUBuildCommandLineJSON(val, &buf, NULL, data->arrayfunc) < 0) { + fprintf(stderr, + "\nvirQEMUBuildCommandlineJSON failed process JSON:\n%s\n", + data->props); +-- +2.23.0.windows.1 + diff --git a/virQEMUBuildNetdevCommandlineFromJSON-Prepare-for-qu.patch b/virQEMUBuildNetdevCommandlineFromJSON-Prepare-for-qu.patch new file mode 100644 index 0000000000000000000000000000000000000000..1b2a1bd656660fbe58bec7bd5508557eaad94642 --- /dev/null +++ b/virQEMUBuildNetdevCommandlineFromJSON-Prepare-for-qu.patch @@ -0,0 +1,88 @@ +From 9df86be1d8d974dc6bcf174c63814cd4492ddd5e Mon Sep 17 00:00:00 2001 +From: Peter Krempa +Date: Fri, 15 May 2020 10:59:40 +0200 +Subject: [PATCH 16/18] virQEMUBuildNetdevCommandlineFromJSON: Prepare for + quirky 'guestfwd' + +QEMU models guestfwd as: + + 'guestfwd': [ + { "str": "tcp:10.0.2.1:4600-chardev:charchannel0" }, + { "str": "...."}, + ] + +but the command line as: + +guestfwd=tcp:10.0.2.1:4600-chardev:charchannel0,guestfwd=... + +I guess the original idea was to make it extensible while not worrying +about adding another object for it. Either way it requires us to add yet +another JSON->cmdline convertor for arrays. + +Signed-off-by: Peter Krempa +Reviewed-by: Eric Blake +--- + src/util/virqemu.c | 40 +++++++++++++++++++++++++++++++++++++++- + 1 file changed, 39 insertions(+), 1 deletion(-) + +diff --git a/src/util/virqemu.c b/src/util/virqemu.c +index 0f8cab29df..9823ebc14d 100644 +--- a/src/util/virqemu.c ++++ b/src/util/virqemu.c +@@ -109,6 +109,43 @@ virQEMUBuildCommandLineJSONArrayNumbered(const char *key, + } + + ++/** ++ * 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, +@@ -267,7 +304,8 @@ virQEMUBuildNetdevCommandlineFromJSON(virJSONValuePtr props) + + virBufferAsprintf(&buf, "%s,", type); + +- if (virQEMUBuildCommandLineJSON(props, &buf, "type", true, NULL) < 0) ++ if (virQEMUBuildCommandLineJSON(props, &buf, "type", true, ++ virQEMUBuildCommandLineJSONArrayObjectsStr) < 0) + return NULL; + + return virBufferContentAndReset(&buf); +-- +2.23.0.windows.1 +