qemu-devel
[Top][All Lists]
Advanced

[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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]