From patchwork Fri Jun 19 16:51:26 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Denis V. Lunev" X-Patchwork-Id: 486801 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 73C061401E7 for ; Sat, 20 Jun 2015 02:50:44 +1000 (AEST) Received: from localhost ([::1]:59160 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z5zV4-0003Py-Eu for incoming@patchwork.ozlabs.org; Fri, 19 Jun 2015 12:50:42 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:41580) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z5zSf-0006fw-Fd for qemu-devel@nongnu.org; Fri, 19 Jun 2015 12:48:18 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Z5zSb-0007Yo-Jk for qemu-devel@nongnu.org; Fri, 19 Jun 2015 12:48:13 -0400 Received: from mailhub.sw.ru ([195.214.232.25]:22390 helo=relay.sw.ru) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z5zSa-0007VM-Vs for qemu-devel@nongnu.org; Fri, 19 Jun 2015 12:48:09 -0400 Received: from hades.sw.ru ([10.30.8.132]) by relay.sw.ru (8.13.4/8.13.4) with ESMTP id t5JGlp1f026593; Fri, 19 Jun 2015 19:47:57 +0300 (MSK) From: "Denis V. Lunev" To: Date: Fri, 19 Jun 2015 19:51:26 +0300 Message-Id: <1434732693-24127-4-git-send-email-den@openvz.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1434732693-24127-1-git-send-email-den@openvz.org> References: <1434732693-24127-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: Michael Roth , Olga Krishtal , qemu-devel@nongnu.org, "Denis V. Lunev" Subject: [Qemu-devel] [PATCH 03/10] qga: guest exec functionality for Unix guests 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: Olga Krishtal Child process' stdin/stdout/stderr can be associated with handles for communication via read/write interfaces. The workflow should be something like this: * Open an anonymous pipe through guest-pipe-open * Execute a binary or a script in the guest. Arbitrary arguments and environment to a new child process could be passed through options * Read/pass information from/to executed process using guest-file-read/write * Collect the status of a child process Signed-off-by: Olga Krishtal Acked-by: Roman Kagan Signed-off-by: Denis V. Lunev CC: Eric Blake CC: Michael Roth --- qga/commands-posix.c | 237 +++++++++++++++++++++++++++++++++++++++++++++++++++ qga/commands-win32.c | 21 ++++- qga/qapi-schema.json | 56 ++++++++++++ 3 files changed, 313 insertions(+), 1 deletion(-) diff --git a/qga/commands-posix.c b/qga/commands-posix.c index a616996..aa11932 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -828,6 +828,242 @@ 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 or arguments array for execve(). */ +static char **guest_exec_get_args(const strList *entry) +{ + const strList *it; + int count = 1, i = 0; /* reserve for NULL terminator */ + size_t argv_str_size = 0; + char **args; + char *argv_str; /* for array of arguments */ + + if (entry != NULL) { + argv_str_size = strlen(entry->value) + 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); + args = g_malloc(count * sizeof(char *)); + for (it = entry; it != NULL; it = it->next) { + args[i++] = it->value; + pstrcat(argv_str, argv_str_size, it->value); + pstrcat(argv_str, argv_str_size, " "); + } + + slog("guest-exec called: \"%s\"", argv_str); + g_free(argv_str); + + args[i] = NULL; + return args; + +} + +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; +} + +GuestExec *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; + GuestExec *ge = NULL; + GuestFileHandle *gfh_stdin = NULL, *gfh_stdout = NULL, *gfh_stderr = NULL; + char **argv, **envp; + strList path_arg = { + .value = (char *)path, + .next = has_params ? params : NULL + }; + + argv = guest_exec_get_args(&path_arg); + envp = guest_exec_get_args(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); + ge = g_malloc(sizeof(*ge)); + ge->pid = pid; + +done: + g_free(argv); + g_free(envp); + return ge; +} + +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) @@ -2520,4 +2756,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 685dd0f..3db7255 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -388,6 +388,24 @@ static void guest_file_init(void) QTAILQ_INIT(&guest_file_state.filehandles); } +GuestExecStatus *qmp_guest_exec_status(int64_t pid, Error **errp) +{ + error_set(errp, QERR_UNSUPPORTED); + return 0; +} + +GuestExec *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); @@ -719,7 +737,8 @@ GList *ga_command_blacklist_init(GList *blacklist) "guest-get-memory-blocks", "guest-set-memory-blocks", "guest-get-memory-block-size", "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 8081213..21997cf 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -935,3 +935,59 @@ ## { 'command': 'guest-get-memory-block-info', 'returns': 'GuestMemoryBlockInfo' } + +## +# @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. If a guest cannot +# reliably detect exit signals, "signal" will be -1. +# +# Since: 2.4 +## +{ 'struct': 'GuestExecStatus', + 'data': { 'exit': 'int', 'signal': 'int', + 'handle-stdin': 'int', 'handle-stdout': 'int', + 'handle-stderr': 'int' } } + +{ 'command': 'guest-exec-status', + 'data': { 'pid': 'int' }, + 'returns': 'GuestExecStatus' } + +## +# @GuestExec: +# @pid: pid of child process in guest OS +# +#Since: 2.4 +## +{ 'struct': 'GuestExec', + 'data': { 'pid': 'int'} } + +## +# @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.4 +## +{ 'command': 'guest-exec', + 'data': { 'path': 'str', '*params': ['str'], '*env': ['str'], + '*handle-stdin': 'int', '*handle-stdout': 'int', + '*handle-stderr': 'int' }, + 'returns': 'GuestExec' }