From bcc63f775e265df69963a4ad7805b8678ace68f0 Mon Sep 17 00:00:00 2001
|
From: Alistair Francis <alistair.francis@xilinx.com>
|
Date: Thu, 21 Dec 2017 11:35:16 -0800
|
Subject: [PATCH] chardev: connect socket to a spawned command
|
|
The command is started in a shell (sh -c) with stdin connect to QEMU
|
via a Unix domain stream socket. QEMU then exchanges data via its own
|
end of the socket, just like it normally does.
|
|
"-chardev socket" supports some ways of connecting via protocols like
|
telnet, but that is only a subset of the functionality supported by
|
tools socat. To use socat instead, for example to connect via a socks
|
proxy, use:
|
|
-chardev 'socket,id=socat,cmd=exec socat FD:0 SOCKS4A:socks-proxy.localdomain:example.com:9999,,socksuser=nobody' \
|
-device usb-serial,chardev=socat
|
|
Beware that commas in the command must be escaped as double commas.
|
|
Or interactively in the console:
|
(qemu) chardev-add socket,id=cat,cmd=cat
|
(qemu) device_add usb-serial,chardev=cat
|
^ac
|
# cat >/dev/ttyUSB0
|
hello
|
hello
|
|
Another usage is starting swtpm from inside QEMU. swtpm will
|
automatically shut down once it looses the connection to the parent
|
QEMU, so there is no risk of lingering processes:
|
|
-chardev 'socket,id=chrtpm0,cmd=exec swtpm socket --terminate --ctrl type=unixio,,clientfd=0 --tpmstate dir=... --log file=swtpm.log' \
|
-tpmdev emulator,id=tpm0,chardev=chrtpm0 \
|
-device tpm-tis,tpmdev=tpm0
|
|
The patch was discussed upstream, but QEMU developers believe that the
|
code calling QEMU should be responsible for managing additional
|
processes. In OE-core, that would imply enhancing runqemu and
|
oeqa. This patch is a simpler solution.
|
|
Because it is not going upstream, the patch was written so that it is
|
as simple as possible.
|
|
Upstream-Status: Inappropriate [embedded specific]
|
|
Signed-off-by: Patrick Ohly <patrick.ohly@intel.com>
|
|
---
|
chardev/char-socket.c | 101 ++++++++++++++++++++++++++++++++++++++++++
|
chardev/char.c | 3 ++
|
qapi/char.json | 5 +++
|
3 files changed, 109 insertions(+)
|
|
Index: qemu-6.0.0/chardev/char-socket.c
|
===================================================================
|
--- qemu-6.0.0.orig/chardev/char-socket.c
|
+++ qemu-6.0.0/chardev/char-socket.c
|
@@ -1362,6 +1362,67 @@ static bool qmp_chardev_validate_socket(
|
return true;
|
}
|
|
+#ifndef _WIN32
|
+static void chardev_open_socket_cmd(Chardev *chr,
|
+ const char *cmd,
|
+ Error **errp)
|
+{
|
+ int fds[2] = { -1, -1 };
|
+ QIOChannelSocket *sioc = NULL;
|
+ pid_t pid = -1;
|
+ const char *argv[] = { "/bin/sh", "-c", cmd, NULL };
|
+
|
+ /*
|
+ * We need a Unix domain socket for commands like swtpm and a single
|
+ * connection, therefore we cannot use qio_channel_command_new_spawn()
|
+ * without patching it first. Duplicating the functionality is easier.
|
+ */
|
+ if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, fds)) {
|
+ error_setg_errno(errp, errno, "Error creating socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC)");
|
+ goto error;
|
+ }
|
+
|
+ pid = qemu_fork(errp);
|
+ if (pid < 0) {
|
+ goto error;
|
+ }
|
+
|
+ if (!pid) {
|
+ /* child */
|
+ dup2(fds[1], STDIN_FILENO);
|
+ execv(argv[0], (char * const *)argv);
|
+ _exit(1);
|
+ }
|
+
|
+ /*
|
+ * Hand over our end of the socket pair to the qio channel.
|
+ *
|
+ * We don't reap the child because it is expected to keep
|
+ * running. We also don't support the "reconnect" option for the
|
+ * same reason.
|
+ */
|
+ sioc = qio_channel_socket_new_fd(fds[0], errp);
|
+ if (!sioc) {
|
+ goto error;
|
+ }
|
+ fds[0] = -1;
|
+
|
+ g_free(chr->filename);
|
+ chr->filename = g_strdup_printf("cmd:%s", cmd);
|
+ tcp_chr_new_client(chr, sioc);
|
+
|
+ error:
|
+ if (fds[0] >= 0) {
|
+ close(fds[0]);
|
+ }
|
+ if (fds[1] >= 0) {
|
+ close(fds[1]);
|
+ }
|
+ if (sioc) {
|
+ object_unref(OBJECT(sioc));
|
+ }
|
+}
|
+#endif
|
|
static void qmp_chardev_open_socket(Chardev *chr,
|
ChardevBackend *backend,
|
@@ -1370,6 +1431,9 @@ static void qmp_chardev_open_socket(Char
|
{
|
SocketChardev *s = SOCKET_CHARDEV(chr);
|
ChardevSocket *sock = backend->u.socket.data;
|
+#ifndef _WIN32
|
+ const char *cmd = sock->cmd;
|
+#endif
|
bool do_nodelay = sock->has_nodelay ? sock->nodelay : false;
|
bool is_listen = sock->has_server ? sock->server : true;
|
bool is_telnet = sock->has_telnet ? sock->telnet : false;
|
@@ -1446,6 +1510,14 @@ static void qmp_chardev_open_socket(Char
|
|
update_disconnected_filename(s);
|
|
+#ifndef _WIN32
|
+ if (cmd) {
|
+ chardev_open_socket_cmd(chr, cmd, errp);
|
+
|
+ /* everything ready (or failed permanently) before we return */
|
+ *be_opened = true;
|
+ } else
|
+#endif
|
if (s->is_listen) {
|
if (qmp_chardev_open_socket_server(chr, is_telnet || is_tn3270,
|
is_waitconnect, errp) < 0) {
|
@@ -1465,6 +1537,9 @@ static void qemu_chr_parse_socket(QemuOp
|
const char *host = qemu_opt_get(opts, "host");
|
const char *port = qemu_opt_get(opts, "port");
|
const char *fd = qemu_opt_get(opts, "fd");
|
+#ifndef _WIN32
|
+ const char *cmd = qemu_opt_get(opts, "cmd");
|
+#endif
|
#ifdef CONFIG_LINUX
|
bool tight = qemu_opt_get_bool(opts, "tight", true);
|
bool abstract = qemu_opt_get_bool(opts, "abstract", false);
|
@@ -1472,6 +1547,20 @@ static void qemu_chr_parse_socket(QemuOp
|
SocketAddressLegacy *addr;
|
ChardevSocket *sock;
|
|
+#ifndef _WIN32
|
+ if (cmd) {
|
+ /*
|
+ * Here we have to ensure that no options are set which are incompatible with
|
+ * spawning a command, otherwise unmodified code that doesn't know about
|
+ * command spawning (like socket_reconnect_timeout()) might get called.
|
+ */
|
+ if (path || sock->server || sock->has_telnet || sock->has_tn3270 || sock->reconnect || host || port || sock->tls_creds) {
|
+ error_setg(errp, "chardev: socket: cmd does not support any additional options");
|
+ return;
|
+ }
|
+ } else
|
+#endif
|
+
|
if ((!!path + !!fd + !!host) != 1) {
|
error_setg(errp,
|
"Exactly one of 'path', 'fd' or 'host' required");
|
@@ -1522,13 +1611,24 @@ static void qemu_chr_parse_socket(QemuOp
|
sock->tls_creds = g_strdup(qemu_opt_get(opts, "tls-creds"));
|
sock->has_tls_authz = qemu_opt_get(opts, "tls-authz");
|
sock->tls_authz = g_strdup(qemu_opt_get(opts, "tls-authz"));
|
+#ifndef _WIN32
|
+ sock->cmd = g_strdup(cmd);
|
+#endif
|
|
addr = g_new0(SocketAddressLegacy, 1);
|
+#ifndef _WIN32
|
+ if (path || cmd) {
|
+#else
|
if (path) {
|
+#endif
|
UnixSocketAddress *q_unix;
|
addr->type = SOCKET_ADDRESS_LEGACY_KIND_UNIX;
|
q_unix = addr->u.q_unix.data = g_new0(UnixSocketAddress, 1);
|
+#ifndef _WIN32
|
+ q_unix->path = cmd ? g_strdup_printf("cmd:%s", cmd) : g_strdup(path);
|
+#else
|
q_unix->path = g_strdup(path);
|
+#endif
|
#ifdef CONFIG_LINUX
|
q_unix->has_tight = true;
|
q_unix->tight = tight;
|
Index: qemu-6.0.0/chardev/char.c
|
===================================================================
|
--- qemu-6.0.0.orig/chardev/char.c
|
+++ qemu-6.0.0/chardev/char.c
|
@@ -840,6 +840,9 @@ QemuOptsList qemu_chardev_opts = {
|
.name = "path",
|
.type = QEMU_OPT_STRING,
|
},{
|
+ .name = "cmd",
|
+ .type = QEMU_OPT_STRING,
|
+ },{
|
.name = "host",
|
.type = QEMU_OPT_STRING,
|
},{
|
Index: qemu-6.0.0/qapi/char.json
|
===================================================================
|
--- qemu-6.0.0.orig/qapi/char.json
|
+++ qemu-6.0.0/qapi/char.json
|
@@ -250,6 +250,10 @@
|
#
|
# @addr: socket address to listen on (server=true)
|
# or connect to (server=false)
|
+# @cmd: command to run via "sh -c" with stdin as one end of
|
+# a AF_UNIX SOCK_DSTREAM socket pair. The other end
|
+# is used by the chardev. Either an addr or a cmd can
|
+# be specified, but not both.
|
# @tls-creds: the ID of the TLS credentials object (since 2.6)
|
# @tls-authz: the ID of the QAuthZ authorization object against which
|
# the client's x509 distinguished name will be validated. This
|
@@ -276,6 +280,7 @@
|
##
|
{ 'struct': 'ChardevSocket',
|
'data': { 'addr': 'SocketAddressLegacy',
|
+ '*cmd': 'str',
|
'*tls-creds': 'str',
|
'*tls-authz' : 'str',
|
'*server': 'bool',
|