Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 1 | From 20a09bb18907e67565c54fc505a741cbbef53f7f Mon Sep 17 00:00:00 2001 |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 2 | From: Alistair Francis <alistair.francis@xilinx.com> |
| 3 | Date: Thu, 21 Dec 2017 11:35:16 -0800 |
| 4 | Subject: [PATCH] chardev: connect socket to a spawned command |
| 5 | |
| 6 | The command is started in a shell (sh -c) with stdin connect to QEMU |
| 7 | via a Unix domain stream socket. QEMU then exchanges data via its own |
| 8 | end of the socket, just like it normally does. |
| 9 | |
| 10 | "-chardev socket" supports some ways of connecting via protocols like |
| 11 | telnet, but that is only a subset of the functionality supported by |
| 12 | tools socat. To use socat instead, for example to connect via a socks |
| 13 | proxy, use: |
| 14 | |
| 15 | -chardev 'socket,id=socat,cmd=exec socat FD:0 SOCKS4A:socks-proxy.localdomain:example.com:9999,,socksuser=nobody' \ |
| 16 | -device usb-serial,chardev=socat |
| 17 | |
| 18 | Beware that commas in the command must be escaped as double commas. |
| 19 | |
| 20 | Or interactively in the console: |
| 21 | (qemu) chardev-add socket,id=cat,cmd=cat |
| 22 | (qemu) device_add usb-serial,chardev=cat |
| 23 | ^ac |
| 24 | # cat >/dev/ttyUSB0 |
| 25 | hello |
| 26 | hello |
| 27 | |
| 28 | Another usage is starting swtpm from inside QEMU. swtpm will |
| 29 | automatically shut down once it looses the connection to the parent |
| 30 | QEMU, so there is no risk of lingering processes: |
| 31 | |
| 32 | -chardev 'socket,id=chrtpm0,cmd=exec swtpm socket --terminate --ctrl type=unixio,,clientfd=0 --tpmstate dir=... --log file=swtpm.log' \ |
| 33 | -tpmdev emulator,id=tpm0,chardev=chrtpm0 \ |
| 34 | -device tpm-tis,tpmdev=tpm0 |
| 35 | |
| 36 | The patch was discussed upstream, but QEMU developers believe that the |
| 37 | code calling QEMU should be responsible for managing additional |
| 38 | processes. In OE-core, that would imply enhancing runqemu and |
| 39 | oeqa. This patch is a simpler solution. |
| 40 | |
| 41 | Because it is not going upstream, the patch was written so that it is |
| 42 | as simple as possible. |
| 43 | |
| 44 | Upstream-Status: Inappropriate [embedded specific] |
| 45 | |
| 46 | Signed-off-by: Patrick Ohly <patrick.ohly@intel.com> |
| 47 | --- |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 48 | chardev/char-socket.c | 102 ++++++++++++++++++++++++++++++++++++++++++ |
| 49 | chardev/char.c | 3 ++ |
| 50 | qapi/char.json | 5 +++ |
| 51 | 3 files changed, 110 insertions(+) |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 52 | |
| 53 | diff --git a/chardev/char-socket.c b/chardev/char-socket.c |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 54 | index 159e69c3b1..84778cf31a 100644 |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 55 | --- a/chardev/char-socket.c |
| 56 | +++ b/chardev/char-socket.c |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 57 | @@ -934,6 +934,68 @@ static gboolean socket_reconnect_timeout(gpointer opaque) |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 58 | return false; |
| 59 | } |
| 60 | |
| 61 | +#ifndef _WIN32 |
| 62 | +static void chardev_open_socket_cmd(Chardev *chr, |
| 63 | + const char *cmd, |
| 64 | + Error **errp) |
| 65 | +{ |
| 66 | + int fds[2] = { -1, -1 }; |
| 67 | + QIOChannelSocket *sioc = NULL; |
| 68 | + pid_t pid = -1; |
| 69 | + const char *argv[] = { "/bin/sh", "-c", cmd, NULL }; |
| 70 | + |
| 71 | + /* |
| 72 | + * We need a Unix domain socket for commands like swtpm and a single |
| 73 | + * connection, therefore we cannot use qio_channel_command_new_spawn() |
| 74 | + * without patching it first. Duplicating the functionality is easier. |
| 75 | + */ |
| 76 | + if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, fds)) { |
| 77 | + error_setg_errno(errp, errno, "Error creating socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC)"); |
| 78 | + goto error; |
| 79 | + } |
| 80 | + |
| 81 | + pid = qemu_fork(errp); |
| 82 | + if (pid < 0) { |
| 83 | + goto error; |
| 84 | + } |
| 85 | + |
| 86 | + if (!pid) { |
| 87 | + /* child */ |
| 88 | + dup2(fds[1], STDIN_FILENO); |
| 89 | + execv(argv[0], (char * const *)argv); |
| 90 | + _exit(1); |
| 91 | + } |
| 92 | + |
| 93 | + /* |
| 94 | + * Hand over our end of the socket pair to the qio channel. |
| 95 | + * |
| 96 | + * We don't reap the child because it is expected to keep |
| 97 | + * running. We also don't support the "reconnect" option for the |
| 98 | + * same reason. |
| 99 | + */ |
| 100 | + sioc = qio_channel_socket_new_fd(fds[0], errp); |
| 101 | + if (!sioc) { |
| 102 | + goto error; |
| 103 | + } |
| 104 | + fds[0] = -1; |
| 105 | + |
| 106 | + g_free(chr->filename); |
| 107 | + chr->filename = g_strdup_printf("cmd:%s", cmd); |
| 108 | + tcp_chr_new_client(chr, sioc); |
| 109 | + |
| 110 | + error: |
| 111 | + if (fds[0] >= 0) { |
| 112 | + close(fds[0]); |
| 113 | + } |
| 114 | + if (fds[1] >= 0) { |
| 115 | + close(fds[1]); |
| 116 | + } |
| 117 | + if (sioc) { |
| 118 | + object_unref(OBJECT(sioc)); |
| 119 | + } |
| 120 | +} |
| 121 | +#endif |
| 122 | + |
| 123 | static void qmp_chardev_open_socket(Chardev *chr, |
| 124 | ChardevBackend *backend, |
| 125 | bool *be_opened, |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 126 | @@ -941,6 +1003,9 @@ static void qmp_chardev_open_socket(Chardev *chr, |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 127 | { |
| 128 | SocketChardev *s = SOCKET_CHARDEV(chr); |
| 129 | ChardevSocket *sock = backend->u.socket.data; |
| 130 | +#ifndef _WIN32 |
| 131 | + const char *cmd = sock->cmd; |
| 132 | +#endif |
| 133 | bool do_nodelay = sock->has_nodelay ? sock->nodelay : false; |
| 134 | bool is_listen = sock->has_server ? sock->server : true; |
| 135 | bool is_telnet = sock->has_telnet ? sock->telnet : false; |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 136 | @@ -1008,6 +1073,14 @@ static void qmp_chardev_open_socket(Chardev *chr, |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 137 | s->reconnect_time = reconnect; |
| 138 | } |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 139 | |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 140 | +#ifndef _WIN32 |
| 141 | + if (cmd) { |
| 142 | + chardev_open_socket_cmd(chr, cmd, errp); |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 143 | + |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 144 | + /* everything ready (or failed permanently) before we return */ |
| 145 | + *be_opened = true; |
| 146 | + } else |
| 147 | +#endif |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 148 | /* If reconnect_time is set, will do that in chr_machine_done. */ |
| 149 | if (!s->reconnect_time) { |
| 150 | if (s->is_listen) { |
| 151 | @@ -1065,9 +1138,26 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 152 | const char *port = qemu_opt_get(opts, "port"); |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 153 | const char *fd = qemu_opt_get(opts, "fd"); |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 154 | const char *tls_creds = qemu_opt_get(opts, "tls-creds"); |
| 155 | +#ifndef _WIN32 |
| 156 | + const char *cmd = qemu_opt_get(opts, "cmd"); |
| 157 | +#endif |
| 158 | SocketAddressLegacy *addr; |
| 159 | ChardevSocket *sock; |
| 160 | |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 161 | +#ifndef _WIN32 |
| 162 | + if (cmd) { |
| 163 | + /* |
| 164 | + * Here we have to ensure that no options are set which are incompatible with |
| 165 | + * spawning a command, otherwise unmodified code that doesn't know about |
| 166 | + * command spawning (like socket_reconnect_timeout()) might get called. |
| 167 | + */ |
| 168 | + if (path || is_listen || is_telnet || is_tn3270 || reconnect || host || port || tls_creds) { |
| 169 | + error_setg(errp, "chardev: socket: cmd does not support any additional options"); |
| 170 | + return; |
| 171 | + } |
| 172 | + } else |
| 173 | +#endif |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 174 | + |
| 175 | if ((!!path + !!fd + !!host) != 1) { |
| 176 | error_setg(errp, |
| 177 | "Exactly one of 'path', 'fd' or 'host' required"); |
| 178 | @@ -1112,12 +1202,24 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 179 | sock->reconnect = reconnect; |
| 180 | sock->tls_creds = g_strdup(tls_creds); |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 181 | |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 182 | +#ifndef _WIN32 |
| 183 | + sock->cmd = g_strdup(cmd); |
| 184 | +#endif |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 185 | + |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 186 | addr = g_new0(SocketAddressLegacy, 1); |
| 187 | +#ifndef _WIN32 |
| 188 | + if (path || cmd) { |
| 189 | +#else |
| 190 | if (path) { |
| 191 | +#endif |
| 192 | UnixSocketAddress *q_unix; |
| 193 | addr->type = SOCKET_ADDRESS_LEGACY_KIND_UNIX; |
| 194 | q_unix = addr->u.q_unix.data = g_new0(UnixSocketAddress, 1); |
| 195 | +#ifndef _WIN32 |
| 196 | + q_unix->path = cmd ? g_strdup_printf("cmd:%s", cmd) : g_strdup(path); |
| 197 | +#else |
| 198 | q_unix->path = g_strdup(path); |
| 199 | +#endif |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 200 | } else if (host) { |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 201 | addr->type = SOCKET_ADDRESS_LEGACY_KIND_INET; |
| 202 | addr->u.inet.data = g_new(InetSocketAddress, 1); |
| 203 | diff --git a/chardev/char.c b/chardev/char.c |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 204 | index 76d866e6fe..9747d51d7c 100644 |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 205 | --- a/chardev/char.c |
| 206 | +++ b/chardev/char.c |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 207 | @@ -792,6 +792,9 @@ QemuOptsList qemu_chardev_opts = { |
| 208 | },{ |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 209 | .name = "path", |
| 210 | .type = QEMU_OPT_STRING, |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 211 | + },{ |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 212 | + .name = "cmd", |
| 213 | + .type = QEMU_OPT_STRING, |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 214 | },{ |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 215 | .name = "host", |
| 216 | .type = QEMU_OPT_STRING, |
| 217 | diff --git a/qapi/char.json b/qapi/char.json |
Brad Bishop | 1a4b7ee | 2018-12-16 17:11:34 -0800 | [diff] [blame^] | 218 | index ae19dcd1ed..6de0f29bcd 100644 |
Brad Bishop | 316dfdd | 2018-06-25 12:45:53 -0400 | [diff] [blame] | 219 | --- a/qapi/char.json |
| 220 | +++ b/qapi/char.json |
| 221 | @@ -241,6 +241,10 @@ |
| 222 | # |
| 223 | # @addr: socket address to listen on (server=true) |
| 224 | # or connect to (server=false) |
| 225 | +# @cmd: command to run via "sh -c" with stdin as one end of |
| 226 | +# a AF_UNIX SOCK_DSTREAM socket pair. The other end |
| 227 | +# is used by the chardev. Either an addr or a cmd can |
| 228 | +# be specified, but not both. |
| 229 | # @tls-creds: the ID of the TLS credentials object (since 2.6) |
| 230 | # @server: create server socket (default: true) |
| 231 | # @wait: wait for incoming connection on server |
| 232 | @@ -258,6 +262,7 @@ |
| 233 | # Since: 1.4 |
| 234 | ## |
| 235 | { 'struct': 'ChardevSocket', 'data': { 'addr' : 'SocketAddressLegacy', |
| 236 | + '*cmd' : 'str', |
| 237 | '*tls-creds' : 'str', |
| 238 | '*server' : 'bool', |
| 239 | '*wait' : 'bool', |