Andrew Geissler | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 1 | From 14cd62607c9de232edf0a9b8503bd02783e03411 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 |
Andrew Geissler | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 4 | Subject: [PATCH 02/12] chardev: connect socket to a spawned command |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 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 | --- |
Andrew Geissler | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 49 | chardev/char-socket.c | 100 ++++++++++++++++++++++++++++++++++++++++++ |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 50 | chardev/char.c | 3 ++ |
| 51 | qapi/char.json | 5 +++ |
Andrew Geissler | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 52 | 3 files changed, 108 insertions(+) |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 53 | |
Andrew Geissler | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 54 | diff --git a/chardev/char-socket.c b/chardev/char-socket.c |
| 55 | index fab2d791d..c79641f24 100644 |
| 56 | --- a/chardev/char-socket.c |
| 57 | +++ b/chardev/char-socket.c |
| 58 | @@ -1315,6 +1315,67 @@ static bool qmp_chardev_validate_socket(ChardevSocket *sock, |
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 | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 126 | @@ -1323,6 +1384,9 @@ static void qmp_chardev_open_socket(Chardev *chr, |
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 | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 136 | @@ -1393,6 +1457,14 @@ static void qmp_chardev_open_socket(Chardev *chr, |
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 | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 151 | @@ -1412,6 +1484,9 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, |
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 | d1e8949 | 2021-02-12 15:35:20 -0600 | [diff] [blame] | 158 | #ifdef CONFIG_LINUX |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 159 | bool tight = qemu_opt_get_bool(opts, "tight", true); |
| 160 | bool abstract = qemu_opt_get_bool(opts, "abstract", false); |
Andrew Geissler | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 161 | @@ -1419,6 +1494,20 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 162 | SocketAddressLegacy *addr; |
| 163 | ChardevSocket *sock; |
| 164 | |
| 165 | +#ifndef _WIN32 |
| 166 | + if (cmd) { |
| 167 | + /* |
| 168 | + * Here we have to ensure that no options are set which are incompatible with |
| 169 | + * spawning a command, otherwise unmodified code that doesn't know about |
| 170 | + * command spawning (like socket_reconnect_timeout()) might get called. |
| 171 | + */ |
| 172 | + if (path || sock->server || sock->has_telnet || sock->has_tn3270 || sock->reconnect || host || port || sock->tls_creds) { |
| 173 | + error_setg(errp, "chardev: socket: cmd does not support any additional options"); |
| 174 | + return; |
| 175 | + } |
| 176 | + } else |
| 177 | +#endif |
Andrew Geissler | d1e8949 | 2021-02-12 15:35:20 -0600 | [diff] [blame] | 178 | + |
Andrew Geissler | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 179 | if ((!!path + !!fd + !!host) > 1) { |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 180 | error_setg(errp, |
Andrew Geissler | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 181 | "None or one of 'path', 'fd' or 'host' option required."); |
| 182 | @@ -1469,13 +1558,24 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, |
Andrew Geissler | d1e8949 | 2021-02-12 15:35:20 -0600 | [diff] [blame] | 183 | sock->tls_creds = g_strdup(qemu_opt_get(opts, "tls-creds")); |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 184 | sock->has_tls_authz = qemu_opt_get(opts, "tls-authz"); |
| 185 | sock->tls_authz = g_strdup(qemu_opt_get(opts, "tls-authz")); |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 186 | +#ifndef _WIN32 |
| 187 | + sock->cmd = g_strdup(cmd); |
| 188 | +#endif |
Andrew Geissler | d1e8949 | 2021-02-12 15:35:20 -0600 | [diff] [blame] | 189 | |
| 190 | addr = g_new0(SocketAddressLegacy, 1); |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 191 | +#ifndef _WIN32 |
| 192 | + if (path || cmd) { |
| 193 | +#else |
| 194 | if (path) { |
| 195 | +#endif |
| 196 | UnixSocketAddress *q_unix; |
Andrew Geissler | 595f630 | 2022-01-24 19:11:47 +0000 | [diff] [blame] | 197 | addr->type = SOCKET_ADDRESS_TYPE_UNIX; |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 198 | q_unix = addr->u.q_unix.data = g_new0(UnixSocketAddress, 1); |
| 199 | +#ifndef _WIN32 |
| 200 | + q_unix->path = cmd ? g_strdup_printf("cmd:%s", cmd) : g_strdup(path); |
| 201 | +#else |
| 202 | q_unix->path = g_strdup(path); |
| 203 | +#endif |
Andrew Geissler | d1e8949 | 2021-02-12 15:35:20 -0600 | [diff] [blame] | 204 | #ifdef CONFIG_LINUX |
| 205 | q_unix->has_tight = true; |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 206 | q_unix->tight = tight; |
Andrew Geissler | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 207 | diff --git a/chardev/char.c b/chardev/char.c |
| 208 | index 0169d8dde..ce9a21f41 100644 |
| 209 | --- a/chardev/char.c |
| 210 | +++ b/chardev/char.c |
| 211 | @@ -835,6 +835,9 @@ QemuOptsList qemu_chardev_opts = { |
| 212 | },{ |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 213 | .name = "path", |
| 214 | .type = QEMU_OPT_STRING, |
Andrew Geissler | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 215 | + },{ |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 216 | + .name = "cmd", |
| 217 | + .type = QEMU_OPT_STRING, |
Andrew Geissler | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 218 | },{ |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 219 | .name = "host", |
| 220 | .type = QEMU_OPT_STRING, |
Andrew Geissler | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 221 | diff --git a/qapi/char.json b/qapi/char.json |
| 222 | index 7b4215157..37feabdac 100644 |
| 223 | --- a/qapi/char.json |
| 224 | +++ b/qapi/char.json |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 225 | @@ -250,6 +250,10 @@ |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 226 | # |
| 227 | # @addr: socket address to listen on (server=true) |
| 228 | # or connect to (server=false) |
| 229 | +# @cmd: command to run via "sh -c" with stdin as one end of |
| 230 | +# a AF_UNIX SOCK_DSTREAM socket pair. The other end |
| 231 | +# is used by the chardev. Either an addr or a cmd can |
| 232 | +# be specified, but not both. |
| 233 | # @tls-creds: the ID of the TLS credentials object (since 2.6) |
| 234 | # @tls-authz: the ID of the QAuthZ authorization object against which |
| 235 | # the client's x509 distinguished name will be validated. This |
Andrew Geissler | 635e0e4 | 2020-08-21 15:58:33 -0500 | [diff] [blame] | 236 | @@ -276,6 +280,7 @@ |
Brad Bishop | c342db3 | 2019-05-15 21:57:59 -0400 | [diff] [blame] | 237 | ## |
| 238 | { 'struct': 'ChardevSocket', |
| 239 | 'data': { 'addr': 'SocketAddressLegacy', |
| 240 | + '*cmd': 'str', |
| 241 | '*tls-creds': 'str', |
| 242 | '*tls-authz' : 'str', |
| 243 | '*server': 'bool', |
Andrew Geissler | d583833 | 2022-05-27 11:33:10 -0500 | [diff] [blame^] | 244 | -- |
| 245 | 2.30.2 |
| 246 | |