From: "Denis V. Lunev" <den@openvz.org>
Cc: "Denis V. Lunev" <den@openvz.org>,
Yuri Pudgorodskiy <yur@virtuozzo.com>,
mdroth@linux.vnet.ibm.com, qemu-devel@nongnu.org
Subject: [Qemu-devel] [PATCH v4 5/5] qga: guest-exec simple stdin/stdout/stderr redirection
Date: Tue, 13 Oct 2015 18:28:46 +0300 [thread overview]
Message-ID: <1444750126-12414-1-git-send-email-den@openvz.org> (raw)
In-Reply-To: <561D10A7.6050409@openvz.org>
From: Yuri Pudgorodskiy <yur@virtuozzo.com>
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 <yur@virtuozzo.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
Signed-by: Michael Roth <mdroth@linux.vnet.ibm.com>
---
Changes from v3:
- fixed bug in WIN32 EOF processing
- fixed typo with in_ch/in_fd for WIN32 code
- refactored output watcher function, merged similar branches
qga/commands.c | 188 ++++++++++++++++++++++++++++++++++++++++++++++++---
qga/qapi-schema.json | 11 ++-
2 files changed, 188 insertions(+), 11 deletions(-)
diff --git a/qga/commands.c b/qga/commands.c
index b487324..2f88026 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,98 @@ 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;
+ GIOStatus gstatus;
+
+ if (cond == G_IO_HUP || cond == G_IO_ERR) {
+ goto close;
+ }
+
+ 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) {
+ /* ignore truncated output */
+ gchar buf[GUEST_EXEC_IO_SIZE];
+
+ p->truncated = true;
+ gstatus = g_io_channel_read_chars(ch, buf, sizeof(buf),
+ &bytes_read, NULL);
+ if (gstatus == G_IO_STATUS_EOF || gstatus == G_IO_STATUS_ERROR) {
+ goto close;
+ }
+
+ 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 */
+ gstatus = g_io_channel_read_chars(ch, (gchar *)p->data + p->length,
+ p->size - p->length, &bytes_read, NULL);
+ if (gstatus == G_IO_STATUS_EOF || gstatus == G_IO_STATUS_ERROR) {
+ goto close;
+ }
+
+ p->length += bytes_read;
+
+ return TRUE;
+
+close:
+ g_io_channel_unref(ch);
+ g_atomic_int_set(&p->closed, 1);
+ return FALSE;
+}
+
GuestExec *qmp_guest_exec(const char *path,
bool has_arg, strList *arg,
bool has_env, strList *env,
@@ -244,6 +377,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 +388,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 +406,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_fd);
+#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' },
--
2.1.4
next prev parent reply other threads:[~2015-10-13 15:29 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-10-07 10:32 [Qemu-devel] [PATCH v3 0/5] simplified QEMU guest exec Denis V. Lunev
2015-10-07 10:32 ` [Qemu-devel] [PATCH 1/5] qga: drop guest_file_init helper and replace it with static initializers Denis V. Lunev
2015-10-07 10:32 ` [Qemu-devel] [PATCH 2/5] qga: guest exec functionality Denis V. Lunev
2015-10-07 10:32 ` [Qemu-devel] [PATCH 3/5] qga: handle possible SIGPIPE in guest-file-write Denis V. Lunev
2015-10-07 10:32 ` [Qemu-devel] [PATCH 4/5] qga: handle G_IO_STATUS_AGAIN in ga_channel_write_all() Denis V. Lunev
2015-10-07 10:32 ` [Qemu-devel] [PATCH 5/5] qga: guest-exec simple stdin/stdout/stderr redirection Denis V. Lunev
2015-10-13 4:05 ` Michael Roth
2015-10-13 14:09 ` Denis V. Lunev
2015-10-13 15:28 ` Denis V. Lunev [this message]
2015-10-12 6:35 ` [Qemu-devel] [PATCH v3 0/5] simplified QEMU guest exec Denis V. Lunev
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1444750126-12414-1-git-send-email-den@openvz.org \
--to=den@openvz.org \
--cc=mdroth@linux.vnet.ibm.com \
--cc=qemu-devel@nongnu.org \
--cc=yur@virtuozzo.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).