1From bcc63f775e265df69963a4ad7805b8678ace68f0 Mon Sep 17 00:00:00 2001 2From: Alistair Francis <alistair.francis@xilinx.com> 3Date: Thu, 21 Dec 2017 11:35:16 -0800 4Subject: [PATCH] chardev: connect socket to a spawned command 5 6The command is started in a shell (sh -c) with stdin connect to QEMU 7via a Unix domain stream socket. QEMU then exchanges data via its own 8end of the socket, just like it normally does. 9 10"-chardev socket" supports some ways of connecting via protocols like 11telnet, but that is only a subset of the functionality supported by 12tools socat. To use socat instead, for example to connect via a socks 13proxy, 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 18Beware that commas in the command must be escaped as double commas. 19 20Or 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 28Another usage is starting swtpm from inside QEMU. swtpm will 29automatically shut down once it looses the connection to the parent 30QEMU, 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 36The patch was discussed upstream, but QEMU developers believe that the 37code calling QEMU should be responsible for managing additional 38processes. In OE-core, that would imply enhancing runqemu and 39oeqa. This patch is a simpler solution. 40 41Because it is not going upstream, the patch was written so that it is 42as simple as possible. 43 44Upstream-Status: Inappropriate [embedded specific] 45 46Signed-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 54Index: qemu-6.2.0/chardev/char-socket.c 55=================================================================== 56--- qemu-6.2.0.orig/chardev/char-socket.c 57+++ qemu-6.2.0/chardev/char-socket.c 58@@ -1362,6 +1362,67 @@ static bool qmp_chardev_validate_socket( 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, 126@@ -1370,6 +1431,9 @@ static void qmp_chardev_open_socket(Char 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; 136@@ -1440,6 +1504,14 @@ static void qmp_chardev_open_socket(Char 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) { 151@@ -1459,6 +1531,9 @@ static void qemu_chr_parse_socket(QemuOp 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 158 #ifdef CONFIG_LINUX 159 bool tight = qemu_opt_get_bool(opts, "tight", true); 160 bool abstract = qemu_opt_get_bool(opts, "abstract", false); 161@@ -1466,6 +1541,20 @@ static void qemu_chr_parse_socket(QemuOp 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 178+ 179 if ((!!path + !!fd + !!host) != 1) { 180 error_setg(errp, 181 "Exactly one of 'path', 'fd' or 'host' required"); 182@@ -1516,13 +1605,24 @@ static void qemu_chr_parse_socket(QemuOp 183 sock->tls_creds = g_strdup(qemu_opt_get(opts, "tls-creds")); 184 sock->has_tls_authz = qemu_opt_get(opts, "tls-authz"); 185 sock->tls_authz = g_strdup(qemu_opt_get(opts, "tls-authz")); 186+#ifndef _WIN32 187+ sock->cmd = g_strdup(cmd); 188+#endif 189 190 addr = g_new0(SocketAddressLegacy, 1); 191+#ifndef _WIN32 192+ if (path || cmd) { 193+#else 194 if (path) { 195+#endif 196 UnixSocketAddress *q_unix; 197 addr->type = SOCKET_ADDRESS_TYPE_UNIX; 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 204 #ifdef CONFIG_LINUX 205 q_unix->has_tight = true; 206 q_unix->tight = tight; 207Index: qemu-6.2.0/chardev/char.c 208=================================================================== 209--- qemu-6.2.0.orig/chardev/char.c 210+++ qemu-6.2.0/chardev/char.c 211@@ -836,6 +836,9 @@ QemuOptsList qemu_chardev_opts = { 212 .name = "path", 213 .type = QEMU_OPT_STRING, 214 },{ 215+ .name = "cmd", 216+ .type = QEMU_OPT_STRING, 217+ },{ 218 .name = "host", 219 .type = QEMU_OPT_STRING, 220 },{ 221Index: qemu-6.2.0/qapi/char.json 222=================================================================== 223--- qemu-6.2.0.orig/qapi/char.json 224+++ qemu-6.2.0/qapi/char.json 225@@ -250,6 +250,10 @@ 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 236@@ -276,6 +280,7 @@ 237 ## 238 { 'struct': 'ChardevSocket', 239 'data': { 'addr': 'SocketAddressLegacy', 240+ '*cmd': 'str', 241 '*tls-creds': 'str', 242 '*tls-authz' : 'str', 243 '*server': 'bool', 244