Andrew Geissler | 82c905d | 2020-04-13 13:39:40 -0500 | [diff] [blame] | 1 | From bcc63f775e265df69963a4ad7805b8678ace68f0 Mon Sep 17 00:00:00 2001 |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -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 | |
| 48 | --- |
| 49 | chardev/char-socket.c | 101 ++++++++++++++++++++++++++++++++++++++++++ |
| 50 | chardev/char.c | 3 ++ |
| 51 | qapi/char.json | 5 +++ |
| 52 | 3 files changed, 109 insertions(+) |
| 53 | |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 54 | Index: qemu-5.1.0/chardev/char-socket.c |
| 55 | =================================================================== |
| 56 | --- qemu-5.1.0.orig/chardev/char-socket.c |
| 57 | +++ qemu-5.1.0/chardev/char-socket.c |
| 58 | @@ -1292,6 +1292,67 @@ static bool qmp_chardev_validate_socket( |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 59 | return true; |
| 60 | } |
| 61 | |
| 62 | +#ifndef _WIN32 |
| 63 | +static void chardev_open_socket_cmd(Chardev *chr, |
| 64 | + const char *cmd, |
| 65 | + Error **errp) |
| 66 | +{ |
| 67 | + int fds[2] = { -1, -1 }; |
| 68 | + QIOChannelSocket *sioc = NULL; |
| 69 | + pid_t pid = -1; |
| 70 | + const char *argv[] = { "/bin/sh", "-c", cmd, NULL }; |
| 71 | + |
| 72 | + /* |
| 73 | + * We need a Unix domain socket for commands like swtpm and a single |
| 74 | + * connection, therefore we cannot use qio_channel_command_new_spawn() |
| 75 | + * without patching it first. Duplicating the functionality is easier. |
| 76 | + */ |
| 77 | + if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, fds)) { |
| 78 | + error_setg_errno(errp, errno, "Error creating socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC)"); |
| 79 | + goto error; |
| 80 | + } |
| 81 | + |
| 82 | + pid = qemu_fork(errp); |
| 83 | + if (pid < 0) { |
| 84 | + goto error; |
| 85 | + } |
| 86 | + |
| 87 | + if (!pid) { |
| 88 | + /* child */ |
| 89 | + dup2(fds[1], STDIN_FILENO); |
| 90 | + execv(argv[0], (char * const *)argv); |
| 91 | + _exit(1); |
| 92 | + } |
| 93 | + |
| 94 | + /* |
| 95 | + * Hand over our end of the socket pair to the qio channel. |
| 96 | + * |
| 97 | + * We don't reap the child because it is expected to keep |
| 98 | + * running. We also don't support the "reconnect" option for the |
| 99 | + * same reason. |
| 100 | + */ |
| 101 | + sioc = qio_channel_socket_new_fd(fds[0], errp); |
| 102 | + if (!sioc) { |
| 103 | + goto error; |
| 104 | + } |
| 105 | + fds[0] = -1; |
| 106 | + |
| 107 | + g_free(chr->filename); |
| 108 | + chr->filename = g_strdup_printf("cmd:%s", cmd); |
| 109 | + tcp_chr_new_client(chr, sioc); |
| 110 | + |
| 111 | + error: |
| 112 | + if (fds[0] >= 0) { |
| 113 | + close(fds[0]); |
| 114 | + } |
| 115 | + if (fds[1] >= 0) { |
| 116 | + close(fds[1]); |
| 117 | + } |
| 118 | + if (sioc) { |
| 119 | + object_unref(OBJECT(sioc)); |
| 120 | + } |
| 121 | +} |
| 122 | +#endif |
| 123 | |
| 124 | static void qmp_chardev_open_socket(Chardev *chr, |
| 125 | ChardevBackend *backend, |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 126 | @@ -1300,6 +1361,9 @@ static void qmp_chardev_open_socket(Char |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -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; |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 136 | @@ -1365,6 +1429,14 @@ static void qmp_chardev_open_socket(Char |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 137 | |
| 138 | update_disconnected_filename(s); |
| 139 | |
| 140 | +#ifndef _WIN32 |
| 141 | + if (cmd) { |
| 142 | + chardev_open_socket_cmd(chr, cmd, errp); |
| 143 | + |
| 144 | + /* everything ready (or failed permanently) before we return */ |
| 145 | + *be_opened = true; |
| 146 | + } else |
| 147 | +#endif |
| 148 | if (s->is_listen) { |
| 149 | if (qmp_chardev_open_socket_server(chr, is_telnet || is_tn3270, |
| 150 | is_waitconnect, errp) < 0) { |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 151 | @@ -1384,11 +1456,27 @@ static void qemu_chr_parse_socket(QemuOp |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 152 | const char *host = qemu_opt_get(opts, "host"); |
| 153 | const char *port = qemu_opt_get(opts, "port"); |
| 154 | const char *fd = qemu_opt_get(opts, "fd"); |
| 155 | +#ifndef _WIN32 |
| 156 | + const char *cmd = qemu_opt_get(opts, "cmd"); |
| 157 | +#endif |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 158 | bool tight = qemu_opt_get_bool(opts, "tight", true); |
| 159 | bool abstract = qemu_opt_get_bool(opts, "abstract", false); |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 160 | SocketAddressLegacy *addr; |
| 161 | ChardevSocket *sock; |
| 162 | |
| 163 | +#ifndef _WIN32 |
| 164 | + if (cmd) { |
| 165 | + /* |
| 166 | + * Here we have to ensure that no options are set which are incompatible with |
| 167 | + * spawning a command, otherwise unmodified code that doesn't know about |
| 168 | + * command spawning (like socket_reconnect_timeout()) might get called. |
| 169 | + */ |
| 170 | + if (path || sock->server || sock->has_telnet || sock->has_tn3270 || sock->reconnect || host || port || sock->tls_creds) { |
| 171 | + error_setg(errp, "chardev: socket: cmd does not support any additional options"); |
| 172 | + return; |
| 173 | + } |
| 174 | + } else |
| 175 | +#endif |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 176 | if ((!!path + !!fd + !!host) != 1) { |
| 177 | error_setg(errp, |
| 178 | "Exactly one of 'path', 'fd' or 'host' required"); |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 179 | @@ -1431,12 +1519,24 @@ static void qemu_chr_parse_socket(QemuOp |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 180 | sock->has_tls_authz = qemu_opt_get(opts, "tls-authz"); |
| 181 | sock->tls_authz = g_strdup(qemu_opt_get(opts, "tls-authz")); |
| 182 | |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 183 | - addr = g_new0(SocketAddressLegacy, 1); |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 184 | +#ifndef _WIN32 |
| 185 | + sock->cmd = g_strdup(cmd); |
| 186 | +#endif |
| 187 | + |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 188 | + addr = g_new0(SocketAddressLegacy, 1); |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 189 | +#ifndef _WIN32 |
| 190 | + if (path || cmd) { |
| 191 | +#else |
| 192 | if (path) { |
| 193 | +#endif |
| 194 | UnixSocketAddress *q_unix; |
| 195 | addr->type = SOCKET_ADDRESS_LEGACY_KIND_UNIX; |
| 196 | q_unix = addr->u.q_unix.data = g_new0(UnixSocketAddress, 1); |
| 197 | +#ifndef _WIN32 |
| 198 | + q_unix->path = cmd ? g_strdup_printf("cmd:%s", cmd) : g_strdup(path); |
| 199 | +#else |
| 200 | q_unix->path = g_strdup(path); |
| 201 | +#endif |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 202 | q_unix->tight = tight; |
| 203 | q_unix->abstract = abstract; |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 204 | } else if (host) { |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 205 | Index: qemu-5.1.0/chardev/char.c |
| 206 | =================================================================== |
| 207 | --- qemu-5.1.0.orig/chardev/char.c |
| 208 | +++ qemu-5.1.0/chardev/char.c |
| 209 | @@ -826,6 +826,9 @@ QemuOptsList qemu_chardev_opts = { |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 210 | .name = "path", |
| 211 | .type = QEMU_OPT_STRING, |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 212 | },{ |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 213 | + .name = "cmd", |
| 214 | + .type = QEMU_OPT_STRING, |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 215 | + },{ |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 216 | .name = "host", |
| 217 | .type = QEMU_OPT_STRING, |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 218 | },{ |
| 219 | Index: qemu-5.1.0/qapi/char.json |
| 220 | =================================================================== |
| 221 | --- qemu-5.1.0.orig/qapi/char.json |
| 222 | +++ qemu-5.1.0/qapi/char.json |
| 223 | @@ -250,6 +250,10 @@ |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 224 | # |
| 225 | # @addr: socket address to listen on (server=true) |
| 226 | # or connect to (server=false) |
| 227 | +# @cmd: command to run via "sh -c" with stdin as one end of |
| 228 | +# a AF_UNIX SOCK_DSTREAM socket pair. The other end |
| 229 | +# is used by the chardev. Either an addr or a cmd can |
| 230 | +# be specified, but not both. |
| 231 | # @tls-creds: the ID of the TLS credentials object (since 2.6) |
| 232 | # @tls-authz: the ID of the QAuthZ authorization object against which |
| 233 | # the client's x509 distinguished name will be validated. This |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 234 | @@ -276,6 +280,7 @@ |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 235 | ## |
| 236 | { 'struct': 'ChardevSocket', |
| 237 | 'data': { 'addr': 'SocketAddressLegacy', |
| 238 | + '*cmd': 'str', |
| 239 | '*tls-creds': 'str', |
| 240 | '*tls-authz' : 'str', |
| 241 | '*server': 'bool', |