[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH 2/2] guest agent: add guest-exec and guest-exec-stat
From: |
Michael Roth |
Subject: |
[Qemu-devel] [PATCH 2/2] guest agent: add guest-exec and guest-exec-status interfaces |
Date: |
Tue, 6 Dec 2011 08:34:08 -0600 |
Interfaces to execute/manage processes in the guest. Child process'
stdin/stdout/stderr can be associated with handles for communication
via read/write interfaces.
Signed-off-by: Michael Roth <address@hidden>
---
qapi-schema-guest.json | 55 ++++++++
qga/guest-agent-commands.c | 299 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 354 insertions(+), 0 deletions(-)
diff --git a/qapi-schema-guest.json b/qapi-schema-guest.json
index 4c9f063..7bf3086 100644
--- a/qapi-schema-guest.json
+++ b/qapi-schema-guest.json
@@ -237,3 +237,58 @@
##
{ 'command': 'guest-fsfreeze-thaw',
'returns': 'int' }
+
+##
+# @guest-exec-status
+#
+# Check status of process associated with PID retrieved via guest-exec.
+# Reap the process and associated metadata if it has exited.
+#
+# @pid: pid returned from guest-exec
+# @wait: #optional whether to wait for completion or simply poll once.
+# Waiting is disallowed if a stdin/stdout/stderr handle was supplied
+# to guest-exec
+#
+# Returns: GuestExecStatus on success
+#
+{ 'type': 'GuestExecStatus',
+ 'data': { 'pid': 'int', 'exited': 'bool', 'exit-code': 'int',
+ 'handle_stdin': 'int', 'handle_stdout': 'int',
+ 'handle_stderr': 'int' } }
+{ 'command': 'guest-exec-status',
+ 'data': { 'pid': 'int', '*wait': 'bool' },
+ 'returns': 'GuestExecStatus' }
+
+##
+# @guest-exec:
+#
+# Execute a command in the guest
+#
+# If a pipe is associated with the resulting process, the
+# read/write/write sides of the process' stdin/stdout/stderr will
+# be transferred automatically, so no need to close them from the
+# client. If no handle is passed in for stdin/stdout/stderr, they
+# will be closed before executing the command.
+#
+# @path: path or executable name to execute
+# @params: #optional parameter list to pass to executable
+# @handle_stdin: #optional handle to associate with process' stdin.
+# @handle_stdout: #optional handle to associate with process' stdout
+# @handle_stderr: #optional handle to associate with process' stderr
+# @detach: #optional whether to detach the process or execute it
+# synchronously. If synchronous, passing of stdin/stdout/stderr handles
+# will be disallowed, since process can block on closing them and cause
+# a deadlock in the guest agent.
+#
+# Returns: GuestExecStatus on success.
+#
+# Since: 1.0.50
+##
+{ 'type': 'GuestExecParam',
+ 'data': { 'param': 'str' } }
+{ 'command': 'guest-exec',
+ 'data': { 'path': 'str', '*params': ['GuestExecParam'],
+ '*handle_stdin': 'int', '*handle_stdout': 'int',
+ '*handle_stderr': 'int',
+ '*detach': 'bool' },
+ 'returns': 'GuestExecStatus' }
diff --git a/qga/guest-agent-commands.c b/qga/guest-agent-commands.c
index ae77ee4..11f9d00 100644
--- a/qga/guest-agent-commands.c
+++ b/qga/guest-agent-commands.c
@@ -450,6 +450,304 @@ static void guest_file_init(void)
QTAILQ_INIT(&guest_file_state.filehandles);
}
+typedef struct GuestExecInfo {
+ pid_t pid;
+ char **params;
+ GuestFileHandle *gfh_stdin;
+ GuestFileHandle *gfh_stdout;
+ GuestFileHandle *gfh_stderr;
+ QTAILQ_ENTRY(GuestExecInfo) next;
+} GuestExecInfo;
+
+static struct {
+ QTAILQ_HEAD(, GuestExecInfo) processes;
+} guest_exec_state;
+
+static void guest_exec_info_add(pid_t pid, char **params,
+ GuestFileHandle *in, GuestFileHandle *out,
+ GuestFileHandle *error, Error **err)
+{
+ GuestExecInfo *gei;
+
+ gei = g_malloc0(sizeof(*gei));
+ gei->pid = pid;
+ gei->params = params;
+ gei->gfh_stdin = in;
+ gei->gfh_stdout = out;
+ gei->gfh_stderr = error;
+ QTAILQ_INSERT_TAIL(&guest_exec_state.processes, gei, next);
+}
+
+static GuestExecInfo *guest_exec_info_find(pid_t pid)
+{
+ GuestExecInfo *gei;
+
+ QTAILQ_FOREACH(gei, &guest_exec_state.processes, next)
+ {
+ if (gei->pid == pid) {
+ return gei;
+ }
+ }
+
+ return NULL;
+}
+
+#include <sys/wait.h>
+#include <sys/types.h>
+GuestExecStatus *qmp_guest_exec_status(int64_t pid, bool has_wait,
+ bool wait, Error **err)
+{
+ GuestExecInfo *gei;
+ GuestExecStatus *ges;
+ int status, ret;
+ char **ptr;
+
+ slog("guest-exec-status called");
+
+ gei = guest_exec_info_find(pid);
+ if (gei == NULL) {
+ error_set(err, QERR_INVALID_PARAMETER, "pid");
+ return NULL;
+ }
+
+ ret = waitpid(gei->pid, &status, WNOHANG);
+ if (ret == -1) {
+ error_set(err, QERR_UNDEFINED_ERROR);
+ return NULL;
+ }
+
+ ges = g_malloc0(sizeof(*ges));
+ ges->handle_stdin = gei->gfh_stdin ? gei->gfh_stdin->id : -1;
+ ges->handle_stdout = gei->gfh_stdout ? gei->gfh_stdout->id : -1;
+ ges->handle_stderr = gei->gfh_stderr ? gei->gfh_stderr->id : -1;
+ ges->pid = gei->pid;
+ if (ret == 0) {
+ ges->exited = false;
+ } else {
+ ges->exited = true;
+ /* reap child info once user has successfully wait()'d */
+ QTAILQ_REMOVE(&guest_exec_state.processes, gei, next);
+ for (ptr = gei->params; ptr && *ptr != NULL; ptr++) {
+ g_free(*ptr);
+ }
+ g_free(gei->params);
+ g_free(gei);
+ }
+ return ges;
+}
+
+static char **extract_param_array(const char *path,
+ const GuestExecParamList *entry)
+{
+ const GuestExecParamList *head;
+ GuestExecParam *param_container;
+ int count = 2; /* reserve 2 for path and NULL terminator */
+ int i = 0;
+ char **param_array;
+
+ for (head = entry; head != NULL; head = head->next) {
+ param_container = head->value;
+ printf("value: %s\n", param_container->param);
+ count++;
+ }
+
+ param_array = g_malloc0((count) * sizeof(*param_array));
+ param_array[i++] = strdup(path);
+
+ for (head = entry; head != NULL; head = head->next) {
+ param_container = head->value;
+ /* NULL-terminated list, so if they passed us a NULL param dup
+ * in an emptry string to avoid client-induced memory leak
+ */
+ if (param_container->param) {
+ param_array[i] = strdup(param_container->param);
+ } else {
+ param_array[i] = strdup("");
+ }
+ i++;
+ }
+
+ return param_array;
+}
+
+GuestExecStatus *qmp_guest_exec(const char *path,
+ bool has_params,
+ GuestExecParamList *params,
+ bool has_handle_stdin,
+ int64_t handle_stdin,
+ bool has_handle_stdout,
+ int64_t handle_stdout,
+ bool has_handle_stderr,
+ int64_t handle_stderr,
+ bool has_detach, bool detach,
+ Error **err)
+{
+ int ret, fd;
+ Error *local_err = NULL;
+ GuestExecStatus *ges;
+ GuestFileHandle *gfh_stdin = NULL;
+ GuestFileHandle *gfh_stdout = NULL;
+ GuestFileHandle *gfh_stderr = NULL;
+ char **param_array;
+
+ slog("guest-exec called");
+
+ if (!has_detach) {
+ detach = false;
+ }
+
+ param_array = extract_param_array(path, has_params ? params : NULL);
+
+ /* processes can block writing to a pipe, so don't allow redirect
+ * to a pipe unless we're doing detached/asynchronous exec()
+ */
+ if (has_handle_stdin) {
+ gfh_stdin = guest_file_handle_find(handle_stdin);
+ if (!gfh_stdin) {
+ error_set(err, QERR_FD_NOT_FOUND, "handle_stdin");
+ return NULL;
+ }
+ if (!detach && gfh_stdin->is_pipe) {
+ error_set(err, QERR_QGA_COMMAND_FAILED,
+ "can't use stdin pipe for non-detached exec()");
+ return NULL;
+ }
+ } else {
+ handle_stdin = -1;
+ }
+ if (has_handle_stdout) {
+ gfh_stdout = guest_file_handle_find(handle_stdout);
+ if (!gfh_stdout) {
+ error_set(err, QERR_FD_NOT_FOUND, "handle_stdout");
+ return NULL;
+ }
+ if (!detach && gfh_stdout->is_pipe) {
+ error_set(err, QERR_QGA_COMMAND_FAILED,
+ "can't use stdout pipe for non-detached exec()");
+ return NULL;
+ }
+ } else {
+ handle_stdin = -1;
+ }
+ if (has_handle_stderr) {
+ gfh_stderr = guest_file_handle_find(handle_stderr);
+ if (!gfh_stderr) {
+ error_set(err, QERR_FD_NOT_FOUND, "handle_stderr");
+ return NULL;
+ }
+ if (!detach && gfh_stderr->is_pipe) {
+ error_set(err, QERR_QGA_COMMAND_FAILED,
+ "can't use stderr pipe for non-detached exec()");
+ return NULL;
+ }
+ } else {
+ handle_stderr = -1;
+ }
+
+ ret = fork();
+ if (ret < 0) {
+ error_set(err, QERR_UNDEFINED_ERROR);
+ return NULL;
+ } else if (ret == 0) {
+ slog("guest-exec child spawned");
+ /* exec the command */
+ setsid();
+ if (has_handle_stdin) {
+ if (gfh_stdin->is_pipe) {
+ fclose(gfh_stdin->stream.pipe.in); /* parent writes to this */
+ fd = fileno(gfh_stdin->stream.pipe.out);
+ } else {
+ fd = fileno(gfh_stdin->stream.fh);
+ }
+ dup2(fd, STDIN_FILENO);
+ /* processes don't seem to like O_NONBLOCK std in/out/err */
+ toggle_flags(fd, O_NONBLOCK, false, err);
+ if (error_is_set(err)) {
+ return NULL;
+ }
+ } else {
+ fclose(stdin);
+ }
+ if (has_handle_stdout) {
+ if (gfh_stdout->is_pipe) {
+ fclose(gfh_stdout->stream.pipe.out); /* parent reads this end
*/
+ fd = fileno(gfh_stdout->stream.pipe.in);
+ } else {
+ fd = fileno(gfh_stdout->stream.fh);
+ }
+ dup2(fd, STDOUT_FILENO);
+ toggle_flags(fd, O_NONBLOCK, false, err);
+ if (error_is_set(err)) {
+ return NULL;
+ }
+ } else {
+ fclose(stdout);
+ }
+ if (has_handle_stderr) {
+ if (gfh_stderr->is_pipe) {
+ fclose(gfh_stderr->stream.pipe.out); /* parent reads this end
*/
+ fd = fileno(gfh_stderr->stream.pipe.in);
+ } else {
+ fd = fileno(gfh_stderr->stream.fh);
+ }
+ dup2(fd, STDERR_FILENO);
+ toggle_flags(fd, O_NONBLOCK, false, err);
+ if (error_is_set(err)) {
+ return NULL;
+ }
+ } else {
+ fclose(stderr);
+ }
+
+ ret = execvp(path, (char * const*)param_array);
+ if (ret) {
+ slog("guest-exec child failed: %s", strerror(errno));
+ }
+ exit(!!ret);
+ }
+
+ if (has_handle_stdin && gfh_stdin->is_pipe) {
+ /* child reads from this end, not us */
+ qmp_guest_file_close(gfh_stdin->id, true, GUEST_FILE_PIPE_END_R, err);
+ if (error_is_set(err)) {
+ return NULL;
+ }
+ }
+ if (has_handle_stdout && gfh_stdout->is_pipe) {
+ /* child writes to this end, not us */
+ qmp_guest_file_close(gfh_stdout->id, true, GUEST_FILE_PIPE_END_W, err);
+ if (error_is_set(err)) {
+ return NULL;
+ }
+ }
+ if (has_handle_stderr && gfh_stderr->is_pipe) {
+ /* child writes to this end, not us */
+ qmp_guest_file_close(gfh_stderr->id, true, GUEST_FILE_PIPE_END_W, err);
+ if (error_is_set(err)) {
+ return NULL;
+ }
+ }
+ guest_exec_info_add(ret, param_array, gfh_stdin, gfh_stdout, gfh_stderr,
+ &local_err);
+ if (local_err) {
+ error_propagate(err, local_err);
+ return NULL;
+ }
+
+ /* return initial status, or wait for completion if !detach */
+ if (!detach) {
+ ges = qmp_guest_exec_status(ret, true, true, err);
+ } else {
+ ges = qmp_guest_exec_status(ret, true, false, err);
+ }
+ return ges;
+}
+
+static void guest_exec_init(void)
+{
+ QTAILQ_INIT(&guest_exec_state.processes);
+}
+
#if defined(CONFIG_FSFREEZE)
static void disable_logging(void)
{
@@ -687,4 +985,5 @@ void ga_command_state_init(GAState *s, GACommandState *cs)
ga_command_state_add(cs, guest_fsfreeze_init, guest_fsfreeze_cleanup);
#endif
ga_command_state_add(cs, guest_file_init, NULL);
+ ga_command_state_add(cs, guest_exec_init, NULL);
}
--
1.7.4.1