From patchwork Wed Oct 7 10:32:21 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Denis V. Lunev" X-Patchwork-Id: 527247 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3A2FD140D88 for ; Wed, 7 Oct 2015 21:36:53 +1100 (AEDT) Received: from localhost ([::1]:56880 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Zjm3d-0003YS-Kk for incoming@patchwork.ozlabs.org; Wed, 07 Oct 2015 06:34:49 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:41750) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Zjm1V-0000Ym-Mh for qemu-devel@nongnu.org; Wed, 07 Oct 2015 06:32:39 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Zjm1U-0005ZH-5m for qemu-devel@nongnu.org; Wed, 07 Oct 2015 06:32:37 -0400 Received: from mailhub.sw.ru ([195.214.232.25]:20279 helo=relay.sw.ru) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Zjm1T-0005Yt-Jt for qemu-devel@nongnu.org; Wed, 07 Oct 2015 06:32:36 -0400 Received: from irbis.sw.ru ([10.30.2.139]) by relay.sw.ru (8.13.4/8.13.4) with ESMTP id t97AWLAH024822; Wed, 7 Oct 2015 13:32:30 +0300 (MSK) From: "Denis V. Lunev" To: Date: Wed, 7 Oct 2015 13:32:21 +0300 Message-Id: <1444213941-11128-6-git-send-email-den@openvz.org> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1444213941-11128-1-git-send-email-den@openvz.org> References: <1444213941-11128-1-git-send-email-den@openvz.org> X-detected-operating-system: by eggs.gnu.org: OpenBSD 3.x X-Received-From: 195.214.232.25 Cc: "Denis V. Lunev" , Yuri Pudgorodskiy , mdroth@linux.vnet.ibm.com, v.tolstov@selfip.ru, qemu-devel@nongnu.org Subject: [Qemu-devel] [PATCH 5/5] qga: guest-exec simple stdin/stdout/stderr redirection X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: Yuri Pudgorodskiy Implemented with base64-encoded strings in qga json protocol. Glib portable GIOChannel is used for data I/O. Optinal stdin parameter of guest-exec command is now used as stdin content for spawned subprocess. If capture-output bool flag is specified, guest-exec redirects out/err file descriptiors internally to pipes and collects subprocess output. Guest-exe-status is modified to return this collected data to requestor in base64 encoding. Signed-off-by: Yuri Pudgorodskiy Signed-off-by: Denis V. Lunev CC: Michael Roth --- qga/commands.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++++--- qga/qapi-schema.json | 11 +++- 2 files changed, 175 insertions(+), 11 deletions(-) diff --git a/qga/commands.c b/qga/commands.c index b487324..740423f 100644 --- a/qga/commands.c +++ b/qga/commands.c @@ -15,6 +15,11 @@ #include "qga-qmp-commands.h" #include "qapi/qmp/qerror.h" +/* Maximum captured guest-exec out_data/err_data - 16MB */ +#define GUEST_EXEC_MAX_OUTPUT (16*1024*1024) +/* Allocation and I/O buffer for reading guest-exec out_data/err_data - 4KB */ +#define GUEST_EXEC_IO_SIZE (4*1024) + /* Note: in some situations, like with the fsfreeze, logging may be * temporarilly disabled. if it is necessary that a command be able * to log for accounting purposes, check ga_logging_enabled() beforehand, @@ -71,10 +76,23 @@ struct GuestAgentInfo *qmp_guest_info(Error **errp) return info; } +struct GuestExecIOData { + guchar *data; + gsize size; + gsize length; + gint closed; + bool truncated; + const char *name; +}; + struct GuestExecInfo { GPid pid; gint status; - bool finished; + bool has_output; + gint finished; + struct GuestExecIOData in; + struct GuestExecIOData out; + struct GuestExecIOData err; QTAILQ_ENTRY(GuestExecInfo) next; }; typedef struct GuestExecInfo GuestExecInfo; @@ -123,9 +141,18 @@ GuestExecStatus *qmp_guest_exec_status(int64_t pid, Error **err) } ges = g_new0(GuestExecStatus, 1); - ges->exited = gei->finished; - if (gei->finished) { + bool finished = g_atomic_int_get(&gei->finished); + + /* need to wait till output channels are closed + * to be sure we captured all output at this point */ + if (gei->has_output) { + finished = finished && g_atomic_int_get(&gei->out.closed); + finished = finished && g_atomic_int_get(&gei->err.closed); + } + + ges->exited = finished; + if (finished) { /* Glib has no portable way to parse exit status. * On UNIX, we can get either exit code from normal termination * or signal number. @@ -160,6 +187,20 @@ GuestExecStatus *qmp_guest_exec_status(int64_t pid, Error **err) ges->signal = WTERMSIG(gei->status); } #endif + if (gei->out.length > 0) { + ges->has_out_data = true; + ges->out_data = g_base64_encode(gei->out.data, gei->out.length); + g_free(gei->out.data); + ges->has_out_truncated = gei->out.truncated; + } + + if (gei->err.length > 0) { + ges->has_err_data = true; + ges->err_data = g_base64_encode(gei->err.data, gei->err.length); + g_free(gei->err.data); + ges->has_err_truncated = gei->err.truncated; + } + QTAILQ_REMOVE(&guest_exec_state.processes, gei, next); g_free(gei); } @@ -230,6 +271,85 @@ static void guest_exec_task_setup(gpointer data) #endif } +static gboolean guest_exec_input_watch(GIOChannel *ch, + GIOCondition cond, gpointer p_) +{ + struct GuestExecIOData *p = (struct GuestExecIOData *)p_; + gsize bytes_written = 0; + GIOStatus status; + GError *gerr = NULL; + + /* nothing left to write */ + if (p->size == p->length) { + goto done; + } + + status = g_io_channel_write_chars(ch, (gchar *)p->data + p->length, + p->size - p->length, &bytes_written, &gerr); + + /* can be not 0 even if not G_IO_STATUS_NORMAL */ + if (bytes_written != 0) { + p->length += bytes_written; + } + + /* continue write, our callback will be called again */ + if (status == G_IO_STATUS_NORMAL || status == G_IO_STATUS_AGAIN) { + return TRUE; + } + + if (gerr) { + g_warning("qga: i/o error writing to inp_data channel: %s", + gerr->message); + g_error_free(gerr); + } + +done: + g_io_channel_shutdown(ch, true, NULL); + g_io_channel_unref(ch); + g_atomic_int_set(&p->closed, 1); + g_free(p->data); + + return FALSE; +} + +static gboolean guest_exec_output_watch(GIOChannel *ch, + GIOCondition cond, gpointer p_) +{ + struct GuestExecIOData *p = (struct GuestExecIOData *)p_; + gsize bytes_read = 0; + + if (cond == G_IO_HUP) { + g_io_channel_unref(ch); + g_atomic_int_set(&p->closed, 1); + return FALSE; + } + + if (p->size == p->length) { + gpointer t = NULL; + if (!p->truncated && p->size < GUEST_EXEC_MAX_OUTPUT) { + t = g_try_realloc(p->data, p->size + GUEST_EXEC_IO_SIZE); + } + if (t == NULL) { + p->truncated = true; + /* ignore truncated output */ + gchar buf[GUEST_EXEC_IO_SIZE]; + g_io_channel_read_chars(ch, buf, sizeof(buf), &bytes_read, NULL); + return TRUE; + } + p->size += GUEST_EXEC_IO_SIZE; + p->data = t; + } + + /* Calling read API once. + * On next available data our callback will be called again */ + g_io_channel_read_chars(ch, (gchar *)p->data + p->length, + p->size - p->length, &bytes_read, NULL); + + p->length += bytes_read; + + return TRUE; +} + GuestExec *qmp_guest_exec(const char *path, bool has_arg, strList *arg, bool has_env, strList *env, @@ -244,6 +364,10 @@ GuestExec *qmp_guest_exec(const char *path, strList arglist; gboolean ret; GError *gerr = NULL; + gint in_fd, out_fd, err_fd; + GIOChannel *in_ch, *out_ch, *err_ch; + GSpawnFlags flags; + bool has_output = (has_capture_output && capture_output); arglist.value = (char *)path; arglist.next = has_arg ? arg : NULL; @@ -251,11 +375,14 @@ GuestExec *qmp_guest_exec(const char *path, argv = guest_exec_get_args(&arglist, true); envp = guest_exec_get_args(has_env ? env : NULL, false); - ret = g_spawn_async_with_pipes(NULL, argv, envp, - G_SPAWN_SEARCH_PATH | - G_SPAWN_DO_NOT_REAP_CHILD | - G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, - guest_exec_task_setup, NULL, &pid, NULL, NULL, NULL, &gerr); + flags = G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD; + if (!has_output) { + flags |= G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL; + } + + ret = g_spawn_async_with_pipes(NULL, argv, envp, flags, + guest_exec_task_setup, NULL, &pid, has_inp_data ? &in_fd : NULL, + has_output ? &out_fd : NULL, has_output ? &err_fd : NULL, &gerr); if (!ret) { error_setg(err, QERR_QGA_COMMAND_FAILED, gerr->message); g_error_free(gerr); @@ -266,8 +393,40 @@ GuestExec *qmp_guest_exec(const char *path, ge->pid = (int64_t)pid; gei = guest_exec_info_add(pid); + gei->has_output = has_output; g_child_watch_add(pid, guest_exec_child_watch, gei); + if (has_inp_data) { + gei->in.data = g_base64_decode(inp_data, &gei->in.size); +#ifdef G_OS_WIN32 + in_ch = g_io_channel_win32_new_fd(in_ch); +#else + in_ch = g_io_channel_unix_new(in_fd); +#endif + g_io_channel_set_encoding(in_ch, NULL, NULL); + g_io_channel_set_buffered(in_ch, false); + g_io_channel_set_flags(in_ch, G_IO_FLAG_NONBLOCK, NULL); + g_io_add_watch(in_ch, G_IO_OUT, guest_exec_input_watch, &gei->in); + } + + if (has_output) { +#ifdef G_OS_WIN32 + out_ch = g_io_channel_win32_new_fd(out_fd); + err_ch = g_io_channel_win32_new_fd(err_fd); +#else + out_ch = g_io_channel_unix_new(out_fd); + err_ch = g_io_channel_unix_new(err_fd); +#endif + g_io_channel_set_encoding(out_ch, NULL, NULL); + g_io_channel_set_encoding(err_ch, NULL, NULL); + g_io_channel_set_buffered(out_ch, false); + g_io_channel_set_buffered(err_ch, false); + g_io_add_watch(out_ch, G_IO_IN | G_IO_HUP, + guest_exec_output_watch, &gei->out); + g_io_add_watch(err_ch, G_IO_IN | G_IO_HUP, + guest_exec_output_watch, &gei->err); + } + done: g_free(argv); g_free(envp); diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index da6b64f..6f5ea50 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -941,12 +941,17 @@ # @err-data: #optional base64-encoded stderr of the process # Note: @out-data and @err-data are present only # if 'capture-output' was specified for 'guest-exec' +# @out-truncated: #optional true if stdout was not fully captured +# due to size limitation. +# @err-truncated: #optional true if stderr was not fully captured +# due to size limitation. # # Since: 2.5 ## { 'struct': 'GuestExecStatus', 'data': { 'exited': 'bool', '*exitcode': 'int', '*signal': 'int', - '*out-data': 'str', '*err-data': 'str' }} + '*out-data': 'str', '*err-data': 'str', + '*out-truncated': 'bool', '*err-truncated': 'bool' }} ## # @guest-exec-status # @@ -955,9 +960,9 @@ # # @pid: pid returned from guest-exec # -# Returns: @GuestExecStatus on success. +# Returns: GuestExecStatus on success. # -# Since 2.3 +# Since 2.5 ## { 'command': 'guest-exec-status', 'data': { 'pid': 'int' },