diff --git a/src/qemu/meson.build b/src/qemu/meson.build index 57356451e4..1d904bbc68 100644 --- a/src/qemu/meson.build +++ b/src/qemu/meson.build @@ -6,6 +6,7 @@ qemu_driver_sources = [ 'qemu_blockjob.c', 'qemu_capabilities.c', 'qemu_cgroup.c', + 'qemu_chardev.c', 'qemu_checkpoint.c', 'qemu_command.c', 'qemu_conf.c', diff --git a/src/qemu/qemu_chardev.c b/src/qemu/qemu_chardev.c new file mode 100644 index 0000000000..b3ed11233b --- /dev/null +++ b/src/qemu/qemu_chardev.c @@ -0,0 +1,488 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include + +#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; +} diff --git a/src/qemu/qemu_chardev.h b/src/qemu/qemu_chardev.h new file mode 100644 index 0000000000..0381e0df65 --- /dev/null +++ b/src/qemu/qemu_chardev.h @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include "domain_conf.h" +#include "qemu_capabilities.h" +#include "vircommand.h" + +int +qemuChardevBuildCommandline(virCommand *cmd, + const virDomainChrSourceDef *dev, + const char *charAlias, + virQEMUCaps *qemuCaps); + +int +qemuChardevGetBackendProps(const virDomainChrSourceDef *chr, + bool commandline, + const char *alias, + const char **backendType, + virJSONValue **props); diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 1b992d8eed..c2d65645c4 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -31,6 +31,7 @@ #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" @@ -1292,203 +1293,6 @@ qemuBuildTLSx509CommandLine(virCommand *cmd, } -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, @@ -1564,11 +1368,9 @@ qemuBuildChardevCommand(virCommand *cmd, 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;