| From ad853601e75f6d0dd09672bcca05fbe4fac766a4 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(+) |
| |
| diff --git a/chardev/char-socket.c b/chardev/char-socket.c |
| index 7ca5d97a..207fae4a 100644 |
| --- a/chardev/char-socket.c |
| +++ b/chardev/char-socket.c |
| @@ -1278,6 +1278,67 @@ static bool qmp_chardev_validate_socket(ChardevSocket *sock, |
| 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, |
| @@ -1286,6 +1347,9 @@ static void qmp_chardev_open_socket(Chardev *chr, |
| { |
| 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; |
| @@ -1351,6 +1415,14 @@ static void qmp_chardev_open_socket(Chardev *chr, |
| |
| 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) { |
| @@ -1370,9 +1442,26 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, |
| 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 |
| 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"); |
| @@ -1415,12 +1504,24 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, |
| 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 |
| } else if (host) { |
| addr->type = SOCKET_ADDRESS_LEGACY_KIND_INET; |
| addr->u.inet.data = g_new(InetSocketAddress, 1); |
| diff --git a/chardev/char.c b/chardev/char.c |
| index 7b6b2cb1..0c2ca64b 100644 |
| --- a/chardev/char.c |
| +++ b/chardev/char.c |
| @@ -837,6 +837,9 @@ QemuOptsList qemu_chardev_opts = { |
| },{ |
| .name = "path", |
| .type = QEMU_OPT_STRING, |
| + },{ |
| + .name = "cmd", |
| + .type = QEMU_OPT_STRING, |
| },{ |
| .name = "host", |
| .type = QEMU_OPT_STRING, |
| diff --git a/qapi/char.json b/qapi/char.json |
| index a6e81ac7..517962c6 100644 |
| --- a/qapi/char.json |
| +++ b/qapi/char.json |
| @@ -247,6 +247,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 |
| @@ -272,6 +276,7 @@ |
| ## |
| { 'struct': 'ChardevSocket', |
| 'data': { 'addr': 'SocketAddressLegacy', |
| + '*cmd': 'str', |
| '*tls-creds': 'str', |
| '*tls-authz' : 'str', |
| '*server': 'bool', |