--- /dev/null
+/*
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <config.h>
+
+#include "qemu_capabilities.h"
+#include "qemu_domain.h"
+#include "qemu_fd.h"
+
+#include "vircommand.h"
+#include "virlog.h"
+#include "virqemu.h"
+
+#include "domain_conf.h"
+
+#include "qemu_chardev.h"
+
+#define VIR_FROM_THIS VIR_FROM_QEMU
+
+VIR_LOG_INIT("qemu.qemu_command");
+
+static int
+qemuChardevBackendAddSocketAddressInet(virJSONValue **backendData,
+ const char *backendFieldName,
+ bool commandline,
+ const char *commandlinePrefix,
+ const char *host,
+ const char *port)
+{
+ if (commandline) {
+ g_autofree char *hostField = NULL;
+ g_autofree char *portField = NULL;
+
+ if (!commandlinePrefix) {
+ hostField = g_strdup("s:host");
+ portField = g_strdup("s:port");
+ } else {
+ hostField = g_strdup_printf("s:%saddr", commandlinePrefix);
+ portField = g_strdup_printf("s:%sport", commandlinePrefix);
+ }
+
+ if (virJSONValueObjectAdd(backendData,
+ hostField, host,
+ portField, port,
+ NULL) < 0)
+ return -1;
+ } else {
+ g_autoptr(virJSONValue) addr = NULL;
+ g_autoptr(virJSONValue) data = NULL;
+ g_autofree char *datafield = g_strdup_printf("a:%s", backendFieldName);
+
+ if (virJSONValueObjectAdd(&data,
+ "s:host", host,
+ "s:port", port,
+ NULL) < 0)
+ return -1;
+
+ if (virJSONValueObjectAdd(&addr,
+ "s:type", "inet",
+ "a:data", &data,
+ NULL) < 0)
+ return -1;
+
+ if (virJSONValueObjectAdd(backendData,
+ datafield, &addr,
+ NULL) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int
+qemuChardevBackendAddSocketAddressFD(virJSONValue **backendData,
+ const char *backendFieldName,
+ bool commandline,
+ const char *fdname)
+{
+ if (commandline) {
+ if (virJSONValueObjectAdd(backendData,
+ "s:fd", fdname,
+ NULL) < 0)
+ return -1;
+ } else {
+ g_autoptr(virJSONValue) addr = NULL;
+ g_autoptr(virJSONValue) data = NULL;
+ g_autofree char *datafield = g_strdup_printf("a:%s", backendFieldName);
+
+ if (virJSONValueObjectAdd(&data, "s:str", fdname, NULL) < 0)
+ return -1;
+
+ if (virJSONValueObjectAdd(&addr,
+ "s:type", "fd",
+ "a:data", &data, NULL) < 0)
+ return -1;
+
+ if (virJSONValueObjectAdd(backendData,
+ datafield, &addr,
+ NULL) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int
+qemuChardevBackendAddSocketAddressUNIX(virJSONValue **backendData,
+ const char *backendFieldName,
+ bool commandline,
+ const char *path)
+{
+ if (commandline) {
+ if (virJSONValueObjectAdd(backendData,
+ "s:path", path,
+ NULL) < 0)
+ return -1;
+ } else {
+ g_autoptr(virJSONValue) addr = NULL;
+ g_autoptr(virJSONValue) data = NULL;
+ g_autofree char *datafield = g_strdup_printf("a:%s", backendFieldName);
+
+ if (virJSONValueObjectAdd(&data, "s:path", path, NULL) < 0)
+ return -1;
+
+ if (virJSONValueObjectAdd(&addr,
+ "s:type", "unix",
+ "a:data", &data, NULL) < 0)
+ return -1;
+
+ if (virJSONValueObjectAdd(backendData,
+ datafield, &addr,
+ NULL) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int
+qemuChardevGetBackendProps(const virDomainChrSourceDef *chr,
+ bool commandline,
+ const char *alias,
+ const char **backendType,
+ virJSONValue **props)
+{
+ qemuDomainChrSourcePrivate *chrSourcePriv = QEMU_DOMAIN_CHR_SOURCE_PRIVATE(chr);
+ const char *dummy = NULL;
+
+ if (!backendType)
+ backendType = &dummy;
+
+ *props = NULL;
+
+ switch ((virDomainChrType)chr->type) {
+ case VIR_DOMAIN_CHR_TYPE_NULL:
+ case VIR_DOMAIN_CHR_TYPE_VC:
+ case VIR_DOMAIN_CHR_TYPE_PTY:
+ case VIR_DOMAIN_CHR_TYPE_STDIO:
+ *backendType = virDomainChrTypeToString(chr->type);
+ break;
+
+ case VIR_DOMAIN_CHR_TYPE_FILE: {
+ const char *path = chr->data.file.path;
+ virTristateSwitch append = chr->data.file.append;
+ const char *pathfield = "s:out";
+
+ if (commandline)
+ pathfield = "s:path";
+
+ *backendType = "file";
+
+ if (chrSourcePriv->sourcefd) {
+ path = qemuFDPassGetPath(chrSourcePriv->sourcefd);
+ append = VIR_TRISTATE_SWITCH_ON;
+ }
+
+ if (virJSONValueObjectAdd(props,
+ pathfield, path,
+ "T:append", append,
+ NULL) < 0)
+ return -1;
+ }
+ break;
+
+ case VIR_DOMAIN_CHR_TYPE_PIPE:
+ case VIR_DOMAIN_CHR_TYPE_DEV: {
+ const char *pathField = "s:device";
+
+ if (commandline)
+ pathField = "s:path";
+
+ if (chr->type == VIR_DOMAIN_CHR_TYPE_PIPE) {
+ *backendType = "pipe";
+ } else {
+ if (STRPREFIX(alias, "charparallel"))
+ *backendType = "parallel";
+ else
+ *backendType = "serial";
+ }
+
+ if (virJSONValueObjectAdd(props,
+ pathField, chr->data.file.path,
+ NULL) < 0)
+ return -1;
+ }
+ break;
+
+ case VIR_DOMAIN_CHR_TYPE_UNIX: {
+ virTristateBool waitval = VIR_TRISTATE_BOOL_ABSENT;
+ virTristateBool server = VIR_TRISTATE_BOOL_ABSENT;
+ int reconnect = -1;
+
+ *backendType = "socket";
+
+ if (!commandline)
+ server = VIR_TRISTATE_BOOL_NO;
+
+ if (chr->data.nix.listen) {
+ server = VIR_TRISTATE_BOOL_YES;
+
+ if (!chrSourcePriv->wait)
+ waitval = VIR_TRISTATE_BOOL_NO;
+ }
+
+ if (chrSourcePriv->directfd) {
+ if (qemuChardevBackendAddSocketAddressFD(props, "addr",
+ commandline,
+ qemuFDPassDirectGetPath(chrSourcePriv->directfd)) < 0)
+ return -1;
+ } else {
+ if (qemuChardevBackendAddSocketAddressUNIX(props, "addr",
+ commandline,
+ chr->data.nix.path) < 0)
+ return -1;
+
+ if (chr->data.nix.reconnect.enabled == VIR_TRISTATE_BOOL_YES)
+ reconnect = chr->data.nix.reconnect.timeout;
+ else if (chr->data.nix.reconnect.enabled == VIR_TRISTATE_BOOL_NO)
+ reconnect = 0;
+ }
+
+ if (virJSONValueObjectAdd(props,
+ "T:server", server,
+ "T:wait", waitval,
+ "k:reconnect", reconnect,
+ NULL) < 0)
+ return -1;
+ }
+ break;
+
+ case VIR_DOMAIN_CHR_TYPE_TCP: {
+ virTristateBool waitval = VIR_TRISTATE_BOOL_ABSENT;
+ virTristateBool telnet = VIR_TRISTATE_BOOL_ABSENT;
+ virTristateBool server = VIR_TRISTATE_BOOL_ABSENT;
+ int reconnect = -1;
+
+ *backendType = "socket";
+
+ if (!commandline) {
+ server = VIR_TRISTATE_BOOL_NO;
+ telnet = VIR_TRISTATE_BOOL_NO;
+ }
+
+ if (chr->data.tcp.listen) {
+ server = VIR_TRISTATE_BOOL_YES;
+
+ if (!chrSourcePriv->wait)
+ waitval = VIR_TRISTATE_BOOL_NO;
+ }
+
+ if (chr->data.tcp.protocol == VIR_DOMAIN_CHR_TCP_PROTOCOL_TELNET)
+ telnet = VIR_TRISTATE_BOOL_YES;
+
+ if (chr->data.tcp.reconnect.enabled == VIR_TRISTATE_BOOL_YES)
+ reconnect = chr->data.tcp.reconnect.timeout;
+ else if (chr->data.tcp.reconnect.enabled == VIR_TRISTATE_BOOL_NO)
+ reconnect = 0;
+
+ if (qemuChardevBackendAddSocketAddressInet(props, "addr",
+ commandline, NULL,
+ chr->data.tcp.host,
+ chr->data.tcp.service) < 0)
+ return -1;
+
+ if (virJSONValueObjectAdd(props,
+ "T:telnet", telnet,
+ "T:server", server,
+ "T:wait", waitval,
+ "k:reconnect", reconnect,
+ "S:tls-creds", chrSourcePriv->tlsCredsAlias,
+ NULL) < 0)
+ return -1;
+ }
+ break;
+
+ case VIR_DOMAIN_CHR_TYPE_UDP:
+ *backendType = "udp";
+
+ if (qemuChardevBackendAddSocketAddressInet(props, "remote",
+ commandline, NULL,
+ NULLSTR_EMPTY(chr->data.udp.connectHost),
+ chr->data.udp.connectService) < 0)
+ return -1;
+
+ if (commandline || chr->data.udp.bindHost || chr->data.udp.bindService) {
+ const char *bindHost = NULLSTR_EMPTY(chr->data.udp.bindHost);
+ const char *bindService = chr->data.udp.bindService;
+
+ if (!bindService)
+ bindService = "0";
+
+ if (qemuChardevBackendAddSocketAddressInet(props, "local",
+ commandline, "local",
+ bindHost, bindService) < 0)
+ return -1;
+ }
+
+ break;
+
+ case VIR_DOMAIN_CHR_TYPE_SPICEVMC: {
+ const char *typeField = "s:type";
+
+ *backendType = "spicevmc";
+
+ if (commandline)
+ typeField = "s:name";
+
+ if (virJSONValueObjectAdd(props,
+ typeField, virDomainChrSpicevmcTypeToString(chr->data.spicevmc),
+ NULL) < 0)
+ return -1;
+ }
+ break;
+
+ case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT: {
+ virTristateBool mouse = VIR_TRISTATE_BOOL_ABSENT;
+
+ *backendType = "qemu-vdagent";
+
+ switch (chr->data.qemuVdagent.mouse) {
+ case VIR_DOMAIN_MOUSE_MODE_CLIENT:
+ mouse = VIR_TRISTATE_BOOL_YES;
+ break;
+ case VIR_DOMAIN_MOUSE_MODE_SERVER:
+ mouse = VIR_TRISTATE_BOOL_NO;
+ break;
+ case VIR_DOMAIN_MOUSE_MODE_DEFAULT:
+ break;
+ case VIR_DOMAIN_MOUSE_MODE_LAST:
+ default:
+ virReportEnumRangeError(virDomainMouseMode,
+ chr->data.qemuVdagent.mouse);
+ return -1;
+ }
+
+ if (commandline) {
+ if (virJSONValueObjectAdd(props,
+ "s:name", "vdagent",
+ NULL) < 0)
+ return -1;
+ }
+
+ if (virJSONValueObjectAdd(props,
+ "T:clipboard", chr->data.qemuVdagent.clipboard,
+ "T:mouse", mouse,
+ NULL) < 0)
+ return -1;
+ break;
+ }
+
+ case VIR_DOMAIN_CHR_TYPE_DBUS:
+ *backendType = "dbus";
+
+ if (virJSONValueObjectAdd(props,
+ "s:name", chr->data.dbus.channel,
+ NULL) < 0)
+ return -1;
+
+ break;
+
+ case VIR_DOMAIN_CHR_TYPE_SPICEPORT: {
+ const char *channelField = "s:fqdn";
+
+ *backendType = "spiceport";
+
+ if (commandline)
+ channelField = "s:name";
+
+ if (virJSONValueObjectAdd(props,
+ channelField, chr->data.spiceport.channel,
+ NULL) < 0)
+ return -1;
+ }
+ break;
+
+
+ case VIR_DOMAIN_CHR_TYPE_NMDM:
+ case VIR_DOMAIN_CHR_TYPE_LAST:
+ default:
+ virReportEnumRangeError(virDomainChrType, chr->type);
+ return -1;
+ }
+
+ if (chr->logfile) {
+ const char *path = chr->logfile;
+ virTristateSwitch append = chr->logappend;
+
+ if (chrSourcePriv->logfd) {
+ path = qemuFDPassGetPath(chrSourcePriv->logfd);
+ append = VIR_TRISTATE_SWITCH_ON;
+ }
+
+ if (virJSONValueObjectAdd(props,
+ "s:logfile", path,
+ "T:logappend", append,
+ NULL) < 0)
+ return -1;
+ }
+
+ if (!commandline) {
+ /* The 'chardev-add' QMP command uses two extra layers of wrapping in
+ * comparison to what the '-chardev' command syntax has */
+ g_autoptr(virJSONValue) backend = g_steal_pointer(props);
+ g_autoptr(virJSONValue) backendWrap = NULL;
+
+ /* the 'data' field of the wrapper below must be present per QMP schema */
+ if (!backend)
+ backend = virJSONValueNewObject();
+
+ if (virJSONValueObjectAdd(&backendWrap,
+ "s:type", *backendType,
+ "a:data", &backend,
+ NULL) < 0)
+ return -1;
+
+ /* We now replace the value in the variable we're about to return */
+ if (virJSONValueObjectAdd(props,
+ "s:id", alias,
+ "a:backend", &backendWrap,
+ NULL) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int
+qemuChardevBuildCommandline(virCommand *cmd,
+ const virDomainChrSourceDef *dev,
+ const char *charAlias,
+ virQEMUCaps *qemuCaps)
+{
+ g_autoptr(virJSONValue) props = NULL;
+ g_autofree char *arg = NULL;
+ /* BEWARE: '-chardev' is not yet accepting JSON syntax.
+ * QEMU_CAPS_CHARDEV_JSON is asserted just from tests */
+ bool useJSON = virQEMUCapsGet(qemuCaps, QEMU_CAPS_CHARDEV_JSON);
+ const char *backendType = NULL;
+
+ if (qemuChardevGetBackendProps(dev, !useJSON, charAlias, &backendType, &props) < 0)
+ return -1;
+
+ if (useJSON) {
+ if (!(arg = virJSONValueToString(props, false)))
+ return -1;
+ } else {
+ g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
+
+ virBufferAsprintf(&buf, "%s,id=%s", backendType, charAlias);
+
+ if (props) {
+ virBufferAddLit(&buf, ",");
+
+ if (virQEMUBuildCommandLineJSON(props, &buf, NULL, NULL) < 0)
+ return -1;
+ }
+
+ arg = virBufferContentAndReset(&buf);
+ }
+
+ virCommandAddArgList(cmd, "-chardev", arg, NULL);
+ return 0;
+}
#include "qemu_slirp.h"
#include "qemu_block.h"
#include "qemu_fd.h"
+#include "qemu_chardev.h"
#include "viralloc.h"
#include "virlog.h"
#include "virarch.h"
}
-static void
-qemuBuildChrChardevReconnectStr(virBuffer *buf,
- const virDomainChrSourceReconnectDef *def)
-{
- if (def->enabled == VIR_TRISTATE_BOOL_YES) {
- virBufferAsprintf(buf, ",reconnect=%u", def->timeout);
- } else if (def->enabled == VIR_TRISTATE_BOOL_NO) {
- virBufferAddLit(buf, ",reconnect=0");
- }
-}
-
-
-static char *
-qemuBuildChardevStr(const virDomainChrSourceDef *dev,
- const char *charAlias)
-{
-
- qemuDomainChrSourcePrivate *chrSourcePriv = QEMU_DOMAIN_CHR_SOURCE_PRIVATE(dev);
- g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
- const char *path;
- virTristateSwitch append;
-
- switch ((virDomainChrType) dev->type) {
- case VIR_DOMAIN_CHR_TYPE_NULL:
- virBufferAsprintf(&buf, "null,id=%s", charAlias);
- break;
-
- case VIR_DOMAIN_CHR_TYPE_VC:
- virBufferAsprintf(&buf, "vc,id=%s", charAlias);
- break;
-
- case VIR_DOMAIN_CHR_TYPE_PTY:
- virBufferAsprintf(&buf, "pty,id=%s", charAlias);
- break;
-
- case VIR_DOMAIN_CHR_TYPE_DEV: {
- const char *backend = "serial";
-
- if (STRPREFIX(charAlias, "charparallel"))
- backend = "parallel";
-
- virBufferAsprintf(&buf, "%s,id=%s,path=", backend, charAlias);
- virQEMUBuildBufferEscapeComma(&buf, dev->data.file.path);
- break;
- }
-
- case VIR_DOMAIN_CHR_TYPE_FILE:
- virBufferAsprintf(&buf, "file,id=%s", charAlias);
- path = dev->data.file.path;
- append = dev->data.file.append;
-
- if (chrSourcePriv->sourcefd) {
- path = qemuFDPassGetPath(chrSourcePriv->sourcefd);
- append = VIR_TRISTATE_SWITCH_ON;
- }
-
- virBufferAddLit(&buf, ",path=");
- virQEMUBuildBufferEscapeComma(&buf, path);
- if (append != VIR_TRISTATE_SWITCH_ABSENT) {
- virBufferAsprintf(&buf, ",append=%s",
- virTristateSwitchTypeToString(append));
- }
- break;
-
- case VIR_DOMAIN_CHR_TYPE_PIPE:
- virBufferAsprintf(&buf, "pipe,id=%s,path=", charAlias);
- virQEMUBuildBufferEscapeComma(&buf, dev->data.file.path);
- break;
-
- case VIR_DOMAIN_CHR_TYPE_STDIO:
- virBufferAsprintf(&buf, "stdio,id=%s", charAlias);
- break;
-
- case VIR_DOMAIN_CHR_TYPE_UDP: {
- const char *connectHost = dev->data.udp.connectHost;
- const char *bindHost = dev->data.udp.bindHost;
- const char *bindService = dev->data.udp.bindService;
-
- if (connectHost == NULL)
- connectHost = "";
- if (bindHost == NULL)
- bindHost = "";
- if (bindService == NULL)
- bindService = "0";
-
- virBufferAsprintf(&buf,
- "udp,id=%s,host=%s,port=%s,localaddr=%s,"
- "localport=%s",
- charAlias,
- connectHost,
- dev->data.udp.connectService,
- bindHost, bindService);
- break;
- }
-
- case VIR_DOMAIN_CHR_TYPE_TCP:
- virBufferAsprintf(&buf,
- "socket,id=%s,host=%s,port=%s",
- charAlias,
- dev->data.tcp.host,
- dev->data.tcp.service);
-
- if (dev->data.tcp.protocol == VIR_DOMAIN_CHR_TCP_PROTOCOL_TELNET)
- virBufferAddLit(&buf, ",telnet=on");
-
- if (dev->data.tcp.listen) {
- virBufferAddLit(&buf, ",server=on");
- if (!chrSourcePriv->wait)
- virBufferAddLit(&buf, ",wait=off");
- }
-
- qemuBuildChrChardevReconnectStr(&buf, &dev->data.tcp.reconnect);
-
- if (chrSourcePriv->tlsCredsAlias)
- virBufferAsprintf(&buf, ",tls-creds=%s", chrSourcePriv->tlsCredsAlias);
- break;
-
- case VIR_DOMAIN_CHR_TYPE_UNIX:
- virBufferAsprintf(&buf, "socket,id=%s", charAlias);
- if (chrSourcePriv->directfd) {
- virBufferAsprintf(&buf, ",fd=%s", qemuFDPassDirectGetPath(chrSourcePriv->directfd));
- } else {
- virBufferAddLit(&buf, ",path=");
- virQEMUBuildBufferEscapeComma(&buf, dev->data.nix.path);
- }
-
- if (dev->data.nix.listen) {
- virBufferAddLit(&buf, ",server=on");
- if (!chrSourcePriv->wait)
- virBufferAddLit(&buf, ",wait=off");
- }
-
- qemuBuildChrChardevReconnectStr(&buf, &dev->data.nix.reconnect);
- break;
-
- case VIR_DOMAIN_CHR_TYPE_SPICEVMC:
- virBufferAsprintf(&buf, "spicevmc,id=%s,name=%s", charAlias,
- virDomainChrSpicevmcTypeToString(dev->data.spicevmc));
- break;
-
- case VIR_DOMAIN_CHR_TYPE_SPICEPORT:
- virBufferAsprintf(&buf, "spiceport,id=%s,name=%s", charAlias,
- dev->data.spiceport.channel);
- break;
-
- case VIR_DOMAIN_CHR_TYPE_QEMU_VDAGENT:
- virBufferAsprintf(&buf, "qemu-vdagent,id=%s,name=vdagent",
- charAlias);
- if (dev->data.qemuVdagent.clipboard != VIR_TRISTATE_BOOL_ABSENT)
- virBufferAsprintf(&buf, ",clipboard=%s",
- virTristateSwitchTypeToString(dev->data.qemuVdagent.clipboard));
- switch (dev->data.qemuVdagent.mouse) {
- case VIR_DOMAIN_MOUSE_MODE_CLIENT:
- virBufferAddLit(&buf, ",mouse=on");
- break;
- case VIR_DOMAIN_MOUSE_MODE_SERVER:
- virBufferAddLit(&buf, ",mouse=off");
- break;
- case VIR_DOMAIN_MOUSE_MODE_DEFAULT:
- case VIR_DOMAIN_MOUSE_MODE_LAST:
- default:
- break;
- }
- break;
-
- case VIR_DOMAIN_CHR_TYPE_DBUS:
- virBufferAsprintf(&buf, "dbus,id=%s,name=%s", charAlias,
- dev->data.dbus.channel);
- break;
-
- case VIR_DOMAIN_CHR_TYPE_NMDM:
- case VIR_DOMAIN_CHR_TYPE_LAST:
- default:
- break;
- }
-
- if (dev->logfile) {
- path = dev->logfile;
- append = dev->logappend;
-
- if (chrSourcePriv->logfd) {
- path = qemuFDPassGetPath(chrSourcePriv->logfd);
- append = VIR_TRISTATE_SWITCH_ON;
- }
-
- virBufferAddLit(&buf, ",logfile=");
- virQEMUBuildBufferEscapeComma(&buf, path);
- if (append != VIR_TRISTATE_SWITCH_ABSENT) {
- virBufferAsprintf(&buf, ",logappend=%s",
- virTristateSwitchTypeToString(append));
- }
- }
-
- return virBufferContentAndReset(&buf);
-}
-
-
static int
qemuBuildChardevCommand(virCommand *cmd,
const virDomainChrSourceDef *dev,
qemuFDPassTransferCommand(chrSourcePriv->logfd, cmd);
- if (!(charstr = qemuBuildChardevStr(dev, charAlias)))
+ if (qemuChardevBuildCommandline(cmd, dev, charAlias, qemuCaps) < 0)
return -1;
- virCommandAddArgList(cmd, "-chardev", charstr, NULL);
-
qemuDomainChrSourcePrivateClearFDPass(chrSourcePriv);
return 0;