From patchwork Mon Dec 22 09:06:36 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Denis V. Lunev" X-Patchwork-Id: 423314 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 60BC11400E2 for ; Mon, 22 Dec 2014 20:08:34 +1100 (AEDT) Received: from localhost ([::1]:39710 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Y2yye-0006la-HC for incoming@patchwork.ozlabs.org; Mon, 22 Dec 2014 04:08:32 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:45247) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Y2ywH-0002N4-S3 for qemu-devel@nongnu.org; Mon, 22 Dec 2014 04:06:10 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Y2ywD-0001gT-C3 for qemu-devel@nongnu.org; Mon, 22 Dec 2014 04:06:05 -0500 Received: from mailhub.sw.ru ([195.214.232.25]:26627 helo=relay.sw.ru) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Y2ywC-0001X7-Pd for qemu-devel@nongnu.org; Mon, 22 Dec 2014 04:06:01 -0500 Received: from hades.sw.ru ([10.30.8.132]) by relay.sw.ru (8.13.4/8.13.4) with ESMTP id sBM95DL5026630; Mon, 22 Dec 2014 13:05:19 +0400 (MSK) From: "Denis V. Lunev" To: Date: Mon, 22 Dec 2014 12:06:36 +0300 Message-Id: <1419239196-6371-4-git-send-email-den@openvz.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1419239196-6371-1-git-send-email-den@openvz.org> References: <1419239196-6371-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" , qemu-devel@nongnu.org, Simon Zolin , Michael Roth Subject: [Qemu-devel] [PATCH 3/3] guest agent: add guest-exec and guest-exec-status interfaces 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: Simon Zolin 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: Simon Zolin Acked-by: Roman Kagan Signed-off-by: Denis V. Lunev CC: Michael Roth --- qga/commands-posix.c | 259 +++++++++++++++++++++++++++++++++++++++++++++++++++ qga/commands-win32.c | 21 ++++- qga/qapi-schema.json | 47 ++++++++++ 3 files changed, 326 insertions(+), 1 deletion(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index bf14518..5872196 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -842,6 +842,264 @@ static void build_fs_mount_list(FsMountList *mounts, Error **errp) } #endif +typedef struct GuestExecInfo { + pid_t pid; + 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, + GuestFileHandle *in, GuestFileHandle *out, + GuestFileHandle *error) +{ + GuestExecInfo *gei; + + gei = g_malloc0(sizeof(*gei)); + gei->pid = pid; + 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; +} + +GuestExecStatus *qmp_guest_exec_status(int64_t pid, Error **err) +{ + GuestExecInfo *gei; + GuestExecStatus *ges; + int status, ret; + + slog("guest-exec-status called, pid: %u", (uint32_t)pid); + + 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_setg_errno(err, errno, "waitpid() failed, pid: %u", gei->pid); + return NULL; + } + + ges = g_malloc0(sizeof(GuestExecStatus)); + 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->exit = -1; + ges->signal = -1; + + if (ret != 0) { + if (WIFEXITED(status)) { + ges->exit = WEXITSTATUS(status); + + } else if (WIFSIGNALED(status)) { + ges->signal = WTERMSIG(status); + } + + QTAILQ_REMOVE(&guest_exec_state.processes, gei, next); + g_free(gei); + } + + return ges; +} + +/* Get environment variables array for execve(). */ +static char **guest_exec_get_envp(const strList *env) +{ + const strList *it; + char **envp; + size_t i = 0, count = 1; + + for (it = env; it != NULL; it = it->next) { + count++; + } + + envp = g_malloc(count * sizeof(char *)); + + for (it = env; it != NULL; it = it->next) { + envp[i++] = it->value; + } + + envp[i] = NULL; + return envp; +} + +/* Get array of arguments for execve(). + * @argv_str: arguments in one line separated by space. */ +static char **guest_exec_get_argv(const char *path, const strList *entry, + char **argv_str) +{ + const strList *it; + int i = 0, count = 2; /* reserve 2 for path and NULL terminator */ + size_t argv_str_size; + char **argv; + + argv_str_size = strlen(path) + 1; + for (it = entry; it != NULL; it = it->next) { + count++; + argv_str_size += sizeof(" ") - 1 + strlen(it->value); + } + + *argv_str = g_malloc(argv_str_size); + pstrcpy(*argv_str, argv_str_size, path); + + argv = g_malloc(count * sizeof(char *)); + argv[i++] = (char *)path; + + for (it = entry; it != NULL; it = it->next) { + argv[i++] = it->value; + pstrcat(*argv_str, argv_str_size, " "); + pstrcat(*argv_str, argv_str_size, it->value); + } + + argv[i] = NULL; + return argv; +} + +static int guest_exec_set_std(GuestFileHandle *gfh, int std_fd, int fd_null) +{ + int fd; + + if (gfh == NULL) { + fd = fd_null; + + } else if (gfh->pipe_other_end_fd != -1) { + fd = gfh->pipe_other_end_fd; + + } else { + fd = fileno(gfh->fh); + } + + if (dup2(fd, std_fd) == -1) { + slog("dup2() failed: %s", strerror(errno)); + return 1; + } + + return 0; +} + +int64_t qmp_guest_exec(const char *path, + bool has_params, strList *params, + bool has_env, strList *env, + bool has_handle_stdin, int64_t handle_stdin, + bool has_handle_stdout, int64_t handle_stdout, + bool has_handle_stderr, int64_t handle_stderr, + Error **err) +{ + pid_t pid = -1; + int fd_null; + GuestFileHandle *gfh_stdin = NULL, *gfh_stdout = NULL, *gfh_stderr = NULL; + char **argv, *argv_str, **envp; + + argv = guest_exec_get_argv(path, has_params ? params : NULL, &argv_str); + + slog("guest-exec called: \"%s\"", argv_str); + g_free(argv_str); + + envp = guest_exec_get_envp(has_env ? env : NULL); + + if (has_handle_stdin) { + gfh_stdin = guest_file_handle_find(handle_stdin, err); + if (gfh_stdin == NULL) { + goto done; + } + } + + if (has_handle_stdout) { + gfh_stdout = guest_file_handle_find(handle_stdout, err); + if (gfh_stdout == NULL) { + goto done; + } + } + + if (has_handle_stderr) { + gfh_stderr = guest_file_handle_find(handle_stderr, err); + if (gfh_stderr == NULL) { + goto done; + } + } + + pid = fork(); + if (pid < 0) { + error_set(err, QERR_UNDEFINED_ERROR); + goto done; + + } else if (pid == 0) { + + setsid(); + + fd_null = -1; + if (!has_handle_stdin || !has_handle_stdout || !has_handle_stderr) { + fd_null = open("/dev/null", O_RDWR); + if (fd_null == -1) { + slog("guest-exec: couldn't open /dev/null: %s", + strerror(errno)); + exit(1); + } + } + + if (guest_exec_set_std(gfh_stdin, STDIN_FILENO, fd_null) != 0 || + guest_exec_set_std(gfh_stdout, STDOUT_FILENO, fd_null) != 0 || + guest_exec_set_std(gfh_stderr, STDERR_FILENO, fd_null) != 0) { + + exit(1); + } + + if (fd_null != -1 && close(fd_null) != 0) { + slog("guest-exec: couldn't close /dev/null: %s", strerror(errno)); + /* exit(1); */ + } + + execvpe(path, (char * const *)argv, (char * const *)envp); + slog("guest-exec child failed: %s", strerror(errno)); + exit(1); + } + + if (gfh_stdin != NULL && guest_pipe_close_other_end(gfh_stdin) != 0) { + slog("close() failed to close stdin pipe handle: %s", strerror(errno)); + } + + if (gfh_stdout != NULL && guest_pipe_close_other_end(gfh_stdout) != 0) { + slog("close() failed to close stdout pipe handle: %s", strerror(errno)); + } + + if (gfh_stderr != NULL && guest_pipe_close_other_end(gfh_stderr) != 0) { + slog("close() failed to close stderr pipe handle: %s", strerror(errno)); + } + + guest_exec_info_add(pid, gfh_stdin, gfh_stdout, gfh_stderr); + +done: + g_free(argv); + g_free(envp); + return pid; +} + +static void guest_exec_init(void) +{ + QTAILQ_INIT(&guest_exec_state.processes); +} + #if defined(CONFIG_FSFREEZE) static char *get_pci_driver(char const *syspath, int pathlen, Error **errp) @@ -2096,4 +2354,5 @@ void ga_command_state_init(GAState *s, GACommandState *cs) ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup); #endif ga_command_state_add(cs, guest_file_init, NULL); + ga_command_state_add(cs, guest_exec_init, NULL); } diff --git a/qga/commands-win32.c b/qga/commands-win32.c index ae4c98a..b2a6647 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -158,6 +158,24 @@ void qmp_guest_file_flush(int64_t handle, Error **errp) error_set(errp, QERR_UNSUPPORTED); } +GuestExecStatus *qmp_guest_exec_status(int64_t pid, Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return 0; +} + +int64_t qmp_guest_exec(const char *path, + bool has_params, strList *params, + bool has_env, strList *env, + bool has_handle_stdin, int64_t handle_stdin, + bool has_handle_stdout, int64_t handle_stdout, + bool has_handle_stderr, int64_t handle_stderr, + Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return 0; +} + GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp) { error_set(errp, QERR_UNSUPPORTED); @@ -461,7 +479,8 @@ GList *ga_command_blacklist_init(GList *blacklist) "guest-suspend-hybrid", "guest-network-get-interfaces", "guest-get-vcpus", "guest-set-vcpus", "guest-fsfreeze-freeze-list", "guest-get-fsinfo", - "guest-fstrim", "guest-pipe-open", NULL}; + "guest-fstrim", "guest-pipe-open", "guest-exec-status", + "guest-exec", NULL}; char **p = (char **)list_unsupported; while (*p) { diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index 67d3b72..ea09565 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -759,3 +759,50 @@ ## { 'command': 'guest-get-fsinfo', 'returns': ['GuestFilesystemInfo'] } + +## +# @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 +# +# Returns: GuestExecStatus on success. If a child process exited, "exit" is set +# to the exit code. If a child process was killed by a signal, +# "signal" is set to the signal number. If a child process is still +# running, both "exit" and "signal" are set to -1. On Windows, +# "signal" is always set to -1. +# +# Since: 2.3 +## +{ 'type': 'GuestExecStatus', + 'data': { 'exit': 'int', 'signal': 'int', + 'handle_stdin': 'int', 'handle_stdout': 'int', + 'handle_stderr': 'int' } } + +{ 'command': 'guest-exec-status', + 'data': { 'pid': 'int' }, + 'returns': 'GuestExecStatus' } + +## +# @guest-exec: +# +# Execute a command in the guest +# +# @path: path or executable name to execute +# @params: #optional parameter list to pass to executable +# @env: #optional environment variables 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 +# +# Returns: PID on success. +# +# Since: 2.3 +## +{ 'command': 'guest-exec', + 'data': { 'path': 'str', '*params': ['str'], '*env': ['str'], + '*handle_stdin': 'int', '*handle_stdout': 'int', + '*handle_stderr': 'int' }, + 'returns': 'int' }