diff options
Diffstat (limited to 'meta/recipes-devtools/qemu/qemu/chardev-connect-socket-to-a-spawned-command.patch')
-rw-r--r-- | meta/recipes-devtools/qemu/qemu/chardev-connect-socket-to-a-spawned-command.patch | 227 |
1 files changed, 227 insertions, 0 deletions
diff --git a/meta/recipes-devtools/qemu/qemu/chardev-connect-socket-to-a-spawned-command.patch b/meta/recipes-devtools/qemu/qemu/chardev-connect-socket-to-a-spawned-command.patch new file mode 100644 index 0000000000..49d4af2e5e --- /dev/null +++ b/meta/recipes-devtools/qemu/qemu/chardev-connect-socket-to-a-spawned-command.patch | |||
@@ -0,0 +1,227 @@ | |||
1 | From aa3aef4cf5f4dd98f9133df085e825ff5da7dcbd Mon Sep 17 00:00:00 2001 | ||
2 | From: Patrick Ohly <patrick.ohly@intel.com> | ||
3 | Date: Fri, 27 Oct 2017 15:23:35 +0200 | ||
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 | 86 ++++++++++++++++++++++++++++++++++++++++++++++++--- | ||
50 | chardev/char.c | 3 ++ | ||
51 | qapi-schema.json | 5 +++ | ||
52 | 3 files changed, 90 insertions(+), 4 deletions(-) | ||
53 | |||
54 | diff --git a/chardev/char-socket.c b/chardev/char-socket.c | ||
55 | index 1ae730a4..c366a02a 100644 | ||
56 | --- a/chardev/char-socket.c | ||
57 | +++ b/chardev/char-socket.c | ||
58 | @@ -854,6 +854,66 @@ static gboolean socket_reconnect_timeout(gpointer opaque) | ||
59 | return false; | ||
60 | } | ||
61 | |||
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 | + | ||
122 | static void qmp_chardev_open_socket(Chardev *chr, | ||
123 | ChardevBackend *backend, | ||
124 | bool *be_opened, | ||
125 | @@ -861,6 +921,7 @@ static void qmp_chardev_open_socket(Chardev *chr, | ||
126 | { | ||
127 | SocketChardev *s = SOCKET_CHARDEV(chr); | ||
128 | ChardevSocket *sock = backend->u.socket.data; | ||
129 | + const char *cmd = sock->cmd; | ||
130 | bool do_nodelay = sock->has_nodelay ? sock->nodelay : false; | ||
131 | bool is_listen = sock->has_server ? sock->server : true; | ||
132 | bool is_telnet = sock->has_telnet ? sock->telnet : false; | ||
133 | @@ -928,7 +989,12 @@ static void qmp_chardev_open_socket(Chardev *chr, | ||
134 | s->reconnect_time = reconnect; | ||
135 | } | ||
136 | |||
137 | - if (s->reconnect_time) { | ||
138 | + if (cmd) { | ||
139 | + chardev_open_socket_cmd(chr, cmd, errp); | ||
140 | + | ||
141 | + /* everything ready (or failed permanently) before we return */ | ||
142 | + *be_opened = true; | ||
143 | + } else if (s->reconnect_time) { | ||
144 | sioc = qio_channel_socket_new(); | ||
145 | tcp_chr_set_client_ioc_name(chr, sioc); | ||
146 | qio_channel_socket_connect_async(sioc, s->addr, | ||
147 | @@ -987,11 +1053,22 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, | ||
148 | const char *host = qemu_opt_get(opts, "host"); | ||
149 | const char *port = qemu_opt_get(opts, "port"); | ||
150 | const char *tls_creds = qemu_opt_get(opts, "tls-creds"); | ||
151 | + const char *cmd = qemu_opt_get(opts, "cmd"); | ||
152 | SocketAddressLegacy *addr; | ||
153 | ChardevSocket *sock; | ||
154 | |||
155 | backend->type = CHARDEV_BACKEND_KIND_SOCKET; | ||
156 | - if (!path) { | ||
157 | + if (cmd) { | ||
158 | + /* | ||
159 | + * Here we have to ensure that no options are set which are incompatible with | ||
160 | + * spawning a command, otherwise unmodified code that doesn't know about | ||
161 | + * command spawning (like socket_reconnect_timeout()) might get called. | ||
162 | + */ | ||
163 | + if (path || is_listen || is_telnet || is_tn3270 || reconnect || host || port || tls_creds) { | ||
164 | + error_setg(errp, "chardev: socket: cmd does not support any additional options"); | ||
165 | + return; | ||
166 | + } | ||
167 | + } else if (!path) { | ||
168 | if (!host) { | ||
169 | error_setg(errp, "chardev: socket: no host given"); | ||
170 | return; | ||
171 | @@ -1023,13 +1100,14 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, | ||
172 | sock->has_reconnect = true; | ||
173 | sock->reconnect = reconnect; | ||
174 | sock->tls_creds = g_strdup(tls_creds); | ||
175 | + sock->cmd = g_strdup(cmd); | ||
176 | |||
177 | addr = g_new0(SocketAddressLegacy, 1); | ||
178 | - if (path) { | ||
179 | + if (path || cmd) { | ||
180 | UnixSocketAddress *q_unix; | ||
181 | addr->type = SOCKET_ADDRESS_LEGACY_KIND_UNIX; | ||
182 | q_unix = addr->u.q_unix.data = g_new0(UnixSocketAddress, 1); | ||
183 | - q_unix->path = g_strdup(path); | ||
184 | + q_unix->path = cmd ? g_strdup_printf("cmd:%s", cmd) : g_strdup(path); | ||
185 | } else { | ||
186 | addr->type = SOCKET_ADDRESS_LEGACY_KIND_INET; | ||
187 | addr->u.inet.data = g_new(InetSocketAddress, 1); | ||
188 | diff --git a/chardev/char.c b/chardev/char.c | ||
189 | index 5d283b90..ccb329d4 100644 | ||
190 | --- a/chardev/char.c | ||
191 | +++ b/chardev/char.c | ||
192 | @@ -782,6 +782,9 @@ QemuOptsList qemu_chardev_opts = { | ||
193 | .name = "path", | ||
194 | .type = QEMU_OPT_STRING, | ||
195 | },{ | ||
196 | + .name = "cmd", | ||
197 | + .type = QEMU_OPT_STRING, | ||
198 | + },{ | ||
199 | .name = "host", | ||
200 | .type = QEMU_OPT_STRING, | ||
201 | },{ | ||
202 | diff --git a/qapi-schema.json b/qapi-schema.json | ||
203 | index 78a00bc8..790b026d 100644 | ||
204 | --- a/qapi-schema.json | ||
205 | +++ b/qapi-schema.json | ||
206 | @@ -5004,6 +5004,10 @@ | ||
207 | # | ||
208 | # @addr: socket address to listen on (server=true) | ||
209 | # or connect to (server=false) | ||
210 | +# @cmd: command to run via "sh -c" with stdin as one end of | ||
211 | +# a AF_UNIX SOCK_DSTREAM socket pair. The other end | ||
212 | +# is used by the chardev. Either an addr or a cmd can | ||
213 | +# be specified, but not both. | ||
214 | # @tls-creds: the ID of the TLS credentials object (since 2.6) | ||
215 | # @server: create server socket (default: true) | ||
216 | # @wait: wait for incoming connection on server | ||
217 | @@ -5021,6 +5025,7 @@ | ||
218 | # Since: 1.4 | ||
219 | ## | ||
220 | { 'struct': 'ChardevSocket', 'data': { 'addr' : 'SocketAddressLegacy', | ||
221 | + '*cmd' : 'str', | ||
222 | '*tls-creds' : 'str', | ||
223 | '*server' : 'bool', | ||
224 | '*wait' : 'bool', | ||
225 | -- | ||
226 | 2.11.0 | ||
227 | |||