From patchwork Mon Nov 6 17:11:43 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Patrick Ohly X-Patchwork-Id: 10043897 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 098B8602BF for ; Mon, 6 Nov 2017 17:12:41 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id DFB4829F08 for ; Mon, 6 Nov 2017 17:12:40 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id D48F929F17; Mon, 6 Nov 2017 17:12:40 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id D579D29F08 for ; Mon, 6 Nov 2017 17:12:39 +0000 (UTC) Received: from localhost ([::1]:49371 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1eBkww-0004Kp-GW for patchwork-qemu-devel@patchwork.kernel.org; Mon, 06 Nov 2017 12:12:38 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:38313) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1eBkwG-0004Kg-U3 for qemu-devel@nongnu.org; Mon, 06 Nov 2017 12:11:58 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1eBkwD-0001Rn-Fm for qemu-devel@nongnu.org; Mon, 06 Nov 2017 12:11:56 -0500 Received: from mail-wr0-x236.google.com ([2a00:1450:400c:c0c::236]:51093) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1eBkwD-0001Pc-0v for qemu-devel@nongnu.org; Mon, 06 Nov 2017 12:11:53 -0500 Received: by mail-wr0-x236.google.com with SMTP id p96so9285640wrb.7 for ; Mon, 06 Nov 2017 09:11:46 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=intel-com.20150623.gappssmtp.com; s=20150623; h=message-id:subject:from:to:cc:date:organization:mime-version; bh=eEFpC0FFIxEDh0Y8DFfMn/4+AA025Z4i56foypWSYnw=; b=bTYC2xd62Bz06nRVLyc3siElYiUAsOqP7Kg5UH+S6GPUyfk6Kpqq/ggW4X1GWx4bfr Y5KXNmKXwpgP3k087bA/JzK5acA1DJOn2qzPzgVI+/hYwpPsKRSnPlPKYxCMizDTXn/S r9olvwDDGeQCkHo+IoQoboWPN/Hl6mP8CzjpAG64EVznUc4cYa47ayLl8GdQOoV2svsf nVYyt+Z/0P+1Z0TZt5tnWoLrZ60lYVMYp0j6sXbI4XNXktLjRtjQYvBGMMkGwTBm0ZUw 2NKqefb2a2FuUdZo4L5vxs+KSKa789CquSrChFyAUyIggq9n+jnLdQKLrtjAm2AWU9Ho 8+BA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:message-id:subject:from:to:cc:date:organization :mime-version; bh=eEFpC0FFIxEDh0Y8DFfMn/4+AA025Z4i56foypWSYnw=; b=dPesusHTwpruGHw+5XDiUZMW0o6vczGch8aqWMHWnSrXFP1xFVvR89f5/z/wy3CM7D EvoEQwbe+vVa2hQdr7INoTDssI6BFJWBJgYZkPYlI85aN6/GQ53QSizkbAmHSutCOB5C 9HIxyWAFQK4F/Pbo7tNGDEXF3+N2IgVwxlHqqE4w0Ddjn/9G9yw5h8RteMhPoHDTCLDR hhEOvzEyfm40fnexfXNecQj4jqsgHgsOCyQq9ev6+2wxd7lEETeGxN7EiRaQQmeGSb7Z 7KCNc5SDGaMbiItCzmQvvdgrAR/x58y3sXemQK4XBKyDruU0QzIe7yw4tJq/EPSIiPXu 6yGg== X-Gm-Message-State: AMCzsaU+eFDvFhIRoKdnLCu4Kzw1J8dHlEK/lInWGD2WuTWh3RRR6Io/ RtsvRHKOGnOy0p2VlmB4RZIMHsc= X-Google-Smtp-Source: ABhQp+RvOK1BudUAl25rKyeOzwe1wAGqcqv32worB2/OLDOM5iDZLP0METws2ywXhwy86Dr/YMu/iA== X-Received: by 10.223.162.152 with SMTP id s24mr14421944wra.173.1509988305194; Mon, 06 Nov 2017 09:11:45 -0800 (PST) Received: from pohly-mobl1 (p54BD5858.dip0.t-ipconnect.de. [84.189.88.88]) by smtp.gmail.com with ESMTPSA id f140sm8582822wmd.27.2017.11.06.09.11.44 (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Mon, 06 Nov 2017 09:11:44 -0800 (PST) Message-ID: <1509988303.22094.8.camel@intel.com> From: Patrick Ohly To: qemu-devel@nongnu.org Date: Mon, 06 Nov 2017 18:11:43 +0100 Organization: Intel GmbH, Dornacher Strasse 1, D-85622 Feldkirchen/Munich X-Mailer: Evolution 3.22.6-1+deb9u1 Mime-Version: 1.0 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 2a00:1450:400c:c0c::236 Subject: [Qemu-devel] RFC: connecting chardev to a command forked by qemu X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Amarnath Valluri Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP Hello! When Amarnath proposed the initial patch set for interacting with swtpm without relying on CUSE, letting QEMU invoke swtpm was part of the patch series. When using some virtual machine management framework like virt-manager that might not be necessary. But our goal was to use QEMU+swtpm to test TPM support in Yocto, and there QEMU is started by a simple wrapper script where handling additional processes would be awkward. To achieve that goal without having to make that script more complex, I'd like to propose that with some minor (?) changes, a chardev could also be configured to use an external command. As a proof of concept I'm attaching a patch which modifies char-socket.c accordingly. Because it's now implemented using the chardev concept, this feature could also be useful for other purposes besides connecting to swtpm. For example, it could also be used to connect a serial port to anything that socat supports, which is more than the builtin code can handle. Is that something that has a chance of getting merged? The specific implementation may need more work. I'm not entirely happy about adding the "cmd" option to the existing char-socket.c. It might be cleaner to have a separate char-cmd.c. I'm just not sure how the common code (for example, everything related to read/write, like tcp_chr_read) then could be shared. Or perhaps it shouldn't be shared? Introducing yet another base class for SocketChardev and a future SocketCmd looks like overkill to me. Regarding SocketChardev, for what are the "addr" and "connected" properties used? Would it be useful to also have something like that in a hypothetical char-cmd.c? When SocketChardev is configured to establish a connection as client, does it do that only on demand? I haven't checked what triggers that code. It might be useful to only spawn the command when actually needed by some frontend. Perhaps then the command can also be spawned multiple times. Right now it gets spawned exactly once when creating thechardev. From 85e2456983e8c284d304b8fcd7e9af8d37820cff Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Fri, 27 Oct 2017 15:23:35 +0200 Subject: [PATCH 1/1] 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,cmd=exec socat FD:0 SOCKS4A:socks-proxy.localdomain:example.com:9999,,socksuser=nobody' Beware that commas in the command must be escaped as double commas. 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 Signed-off-by: Patrick Ohly %% original patch: chardev-connect-socket-to-a-spawned-command.patch --- chardev/char-socket.c | 88 ++++++++++++++++++++++++++++++++++++++++++++------- chardev/char.c | 3 ++ qapi-schema.json | 5 +++ 3 files changed, 84 insertions(+), 12 deletions(-) diff --git a/chardev/char-socket.c b/chardev/char-socket.c index 1ae730a4..42a67f88 100644 --- a/chardev/char-socket.c +++ b/chardev/char-socket.c @@ -854,6 +854,58 @@ static gboolean socket_reconnect_timeout(gpointer opaque) return false; } +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 }; + + 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. + * TODO: We don't reap the child at the moment. + */ + 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)); + } +} + static void qmp_chardev_open_socket(Chardev *chr, ChardevBackend *backend, bool *be_opened, @@ -861,12 +913,13 @@ static void qmp_chardev_open_socket(Chardev *chr, { SocketChardev *s = SOCKET_CHARDEV(chr); ChardevSocket *sock = backend->u.socket.data; - 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; - bool is_tn3270 = sock->has_tn3270 ? sock->tn3270 : false; - bool is_waitconnect = sock->has_wait ? sock->wait : false; - int64_t reconnect = sock->has_reconnect ? sock->reconnect : 0; + const char *cmd = sock->cmd; + bool do_nodelay = cmd ? false : sock->has_nodelay ? sock->nodelay : false; + bool is_listen = cmd ? false : sock->has_server ? sock->server : true; + bool is_telnet = cmd ? false : sock->has_telnet ? sock->telnet : false; + bool is_tn3270 = cmd ? false : sock->has_tn3270 ? sock->tn3270 : false; + bool is_waitconnect = cmd ? false : sock->has_wait ? sock->wait : false; + int64_t reconnect = cmd ? 0 : sock->has_reconnect ? sock->reconnect : 0; QIOChannelSocket *sioc = NULL; SocketAddress *addr; @@ -911,11 +964,11 @@ static void qmp_chardev_open_socket(Chardev *chr, qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_RECONNECTABLE); /* TODO SOCKET_ADDRESS_FD where fd has AF_UNIX */ - if (addr->type == SOCKET_ADDRESS_TYPE_UNIX) { + if (cmd || addr->type == SOCKET_ADDRESS_TYPE_UNIX) { qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_FD_PASS); } - /* be isn't opened until we get a connection */ + /* be isn't opened until we get a connection or fork a command */ *be_opened = false; update_disconnected_filename(s); @@ -928,7 +981,12 @@ static void qmp_chardev_open_socket(Chardev *chr, s->reconnect_time = reconnect; } - if (s->reconnect_time) { + if (cmd) { + chardev_open_socket_cmd(chr, cmd, errp); + + /* everything ready (or failed permanently) before we return */ + *be_opened = true; + } else if (s->reconnect_time) { sioc = qio_channel_socket_new(); tcp_chr_set_client_ioc_name(chr, sioc); qio_channel_socket_connect_async(sioc, s->addr, @@ -987,11 +1045,16 @@ 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 *tls_creds = qemu_opt_get(opts, "tls-creds"); + const char *cmd = qemu_opt_get(opts, "cmd"); SocketAddressLegacy *addr; ChardevSocket *sock; backend->type = CHARDEV_BACKEND_KIND_SOCKET; - if (!path) { + if (cmd && path) { + error_setg(errp, "chardev: socket: cmd and path are mutually exclusive"); + return; + } + if (!path && !cmd) { if (!host) { error_setg(errp, "chardev: socket: no host given"); return; @@ -1023,13 +1086,14 @@ static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, sock->has_reconnect = true; sock->reconnect = reconnect; sock->tls_creds = g_strdup(tls_creds); + sock->cmd = g_strdup(cmd); addr = g_new0(SocketAddressLegacy, 1); - if (path) { + if (path || cmd) { UnixSocketAddress *q_unix; addr->type = SOCKET_ADDRESS_LEGACY_KIND_UNIX; q_unix = addr->u.q_unix.data = g_new0(UnixSocketAddress, 1); - q_unix->path = g_strdup(path); + q_unix->path = cmd ? g_strdup_printf("cmd:%s", cmd) : g_strdup(path); } else { 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 5d283b90..ccb329d4 100644 --- a/chardev/char.c +++ b/chardev/char.c @@ -782,6 +782,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-schema.json b/qapi-schema.json index 78a00bc8..790b026d 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -5004,6 +5004,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) # @server: create server socket (default: true) # @wait: wait for incoming connection on server @@ -5021,6 +5025,7 @@ # Since: 1.4 ## { 'struct': 'ChardevSocket', 'data': { 'addr' : 'SocketAddressLegacy', + '*cmd' : 'str', '*tls-creds' : 'str', '*server' : 'bool', '*wait' : 'bool', -- 2.11.0