1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
|
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',
|