qga: guest-exec simple stdin/stdout/stderr redirection
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-off-by: Michael Roth <mdroth@linux.vnet.ibm.com> * switch from 'struct GuestIOExecData' to 'GuestIOExecData' * s/TRUE/true/g, s/FALSE/false/g for gboolean return values * s/inp_data/input_data/ Signed-off-by: Michael Roth <mdroth@linux.vnet.ibm.com>
This commit is contained in:
parent
f74df9bfce
commit
a1853dca74
189
qga/commands.c
189
qga/commands.c
@ -15,6 +15,11 @@
|
|||||||
#include "qga-qmp-commands.h"
|
#include "qga-qmp-commands.h"
|
||||||
#include "qapi/qmp/qerror.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
|
/* Note: in some situations, like with the fsfreeze, logging may be
|
||||||
* temporarilly disabled. if it is necessary that a command be able
|
* temporarilly disabled. if it is necessary that a command be able
|
||||||
* to log for accounting purposes, check ga_logging_enabled() beforehand,
|
* to log for accounting purposes, check ga_logging_enabled() beforehand,
|
||||||
@ -71,11 +76,25 @@ struct GuestAgentInfo *qmp_guest_info(Error **errp)
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct GuestExecIOData {
|
||||||
|
guchar *data;
|
||||||
|
gsize size;
|
||||||
|
gsize length;
|
||||||
|
gint closed;
|
||||||
|
bool truncated;
|
||||||
|
const char *name;
|
||||||
|
};
|
||||||
|
typedef struct GuestExecIOData GuestExecIOData;
|
||||||
|
|
||||||
struct GuestExecInfo {
|
struct GuestExecInfo {
|
||||||
GPid pid;
|
GPid pid;
|
||||||
int64_t pid_numeric;
|
int64_t pid_numeric;
|
||||||
gint status;
|
gint status;
|
||||||
bool finished;
|
bool has_output;
|
||||||
|
gint finished;
|
||||||
|
GuestExecIOData in;
|
||||||
|
GuestExecIOData out;
|
||||||
|
GuestExecIOData err;
|
||||||
QTAILQ_ENTRY(GuestExecInfo) next;
|
QTAILQ_ENTRY(GuestExecInfo) next;
|
||||||
};
|
};
|
||||||
typedef struct GuestExecInfo GuestExecInfo;
|
typedef struct GuestExecInfo GuestExecInfo;
|
||||||
@ -134,9 +153,18 @@ GuestExecStatus *qmp_guest_exec_status(int64_t pid, Error **err)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ges = g_new0(GuestExecStatus, 1);
|
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.
|
/* Glib has no portable way to parse exit status.
|
||||||
* On UNIX, we can get either exit code from normal termination
|
* On UNIX, we can get either exit code from normal termination
|
||||||
* or signal number.
|
* or signal number.
|
||||||
@ -171,6 +199,20 @@ GuestExecStatus *qmp_guest_exec_status(int64_t pid, Error **err)
|
|||||||
ges->signal = WTERMSIG(gei->status);
|
ges->signal = WTERMSIG(gei->status);
|
||||||
}
|
}
|
||||||
#endif
|
#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);
|
QTAILQ_REMOVE(&guest_exec_state.processes, gei, next);
|
||||||
g_free(gei);
|
g_free(gei);
|
||||||
}
|
}
|
||||||
@ -241,6 +283,98 @@ static void guest_exec_task_setup(gpointer data)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean guest_exec_input_watch(GIOChannel *ch,
|
||||||
|
GIOCondition cond, gpointer p_)
|
||||||
|
{
|
||||||
|
GuestExecIOData *p = (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 input_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_)
|
||||||
|
{
|
||||||
|
GuestExecIOData *p = (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,
|
GuestExec *qmp_guest_exec(const char *path,
|
||||||
bool has_arg, strList *arg,
|
bool has_arg, strList *arg,
|
||||||
bool has_env, strList *env,
|
bool has_env, strList *env,
|
||||||
@ -255,6 +389,10 @@ GuestExec *qmp_guest_exec(const char *path,
|
|||||||
strList arglist;
|
strList arglist;
|
||||||
gboolean ret;
|
gboolean ret;
|
||||||
GError *gerr = NULL;
|
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.value = (char *)path;
|
||||||
arglist.next = has_arg ? arg : NULL;
|
arglist.next = has_arg ? arg : NULL;
|
||||||
@ -262,11 +400,14 @@ GuestExec *qmp_guest_exec(const char *path,
|
|||||||
argv = guest_exec_get_args(&arglist, true);
|
argv = guest_exec_get_args(&arglist, true);
|
||||||
envp = guest_exec_get_args(has_env ? env : NULL, false);
|
envp = guest_exec_get_args(has_env ? env : NULL, false);
|
||||||
|
|
||||||
ret = g_spawn_async_with_pipes(NULL, argv, envp,
|
flags = G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD;
|
||||||
G_SPAWN_SEARCH_PATH |
|
if (!has_output) {
|
||||||
G_SPAWN_DO_NOT_REAP_CHILD |
|
flags |= G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL;
|
||||||
G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
|
}
|
||||||
guest_exec_task_setup, NULL, &pid, NULL, NULL, NULL, &gerr);
|
|
||||||
|
ret = g_spawn_async_with_pipes(NULL, argv, envp, flags,
|
||||||
|
guest_exec_task_setup, NULL, &pid, has_input_data ? &in_fd : NULL,
|
||||||
|
has_output ? &out_fd : NULL, has_output ? &err_fd : NULL, &gerr);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
error_setg(err, QERR_QGA_COMMAND_FAILED, gerr->message);
|
error_setg(err, QERR_QGA_COMMAND_FAILED, gerr->message);
|
||||||
g_error_free(gerr);
|
g_error_free(gerr);
|
||||||
@ -277,8 +418,40 @@ GuestExec *qmp_guest_exec(const char *path,
|
|||||||
ge->pid = gpid_to_int64(pid);
|
ge->pid = gpid_to_int64(pid);
|
||||||
|
|
||||||
gei = guest_exec_info_add(pid);
|
gei = guest_exec_info_add(pid);
|
||||||
|
gei->has_output = has_output;
|
||||||
g_child_watch_add(pid, guest_exec_child_watch, gei);
|
g_child_watch_add(pid, guest_exec_child_watch, gei);
|
||||||
|
|
||||||
|
if (has_input_data) {
|
||||||
|
gei->in.data = g_base64_decode(input_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:
|
done:
|
||||||
g_free(argv);
|
g_free(argv);
|
||||||
g_free(envp);
|
g_free(envp);
|
||||||
|
@ -941,12 +941,17 @@
|
|||||||
# @err-data: #optional base64-encoded stderr of the process
|
# @err-data: #optional base64-encoded stderr of the process
|
||||||
# Note: @out-data and @err-data are present only
|
# Note: @out-data and @err-data are present only
|
||||||
# if 'capture-output' was specified for 'guest-exec'
|
# 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
|
# Since: 2.5
|
||||||
##
|
##
|
||||||
{ 'struct': 'GuestExecStatus',
|
{ 'struct': 'GuestExecStatus',
|
||||||
'data': { 'exited': 'bool', '*exitcode': 'int', '*signal': 'int',
|
'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
|
# @guest-exec-status
|
||||||
#
|
#
|
||||||
|
Loading…
Reference in New Issue
Block a user