From patchwork Fri Jun 19 16:51:29 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: 486805 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 CE2071401DE for ; Sat, 20 Jun 2015 02:52:43 +1000 (AEST) Received: from localhost ([::1]:59174 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z5zX0-0006xt-1J for incoming@patchwork.ozlabs.org; Fri, 19 Jun 2015 12:52:42 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:41695) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z5zSr-000737-OW for qemu-devel@nongnu.org; Fri, 19 Jun 2015 12:48:30 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Z5zSo-0007gl-UC for qemu-devel@nongnu.org; Fri, 19 Jun 2015 12:48:25 -0400 Received: from mailhub.sw.ru ([195.214.232.25]:2558 helo=relay.sw.ru) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Z5zSo-0007gB-Bs for qemu-devel@nongnu.org; Fri, 19 Jun 2015 12:48:22 -0400 Received: from hades.sw.ru ([10.30.8.132]) by relay.sw.ru (8.13.4/8.13.4) with ESMTP id t5JGlp1i026593; Fri, 19 Jun 2015 19:48:02 +0300 (MSK) From: "Denis V. Lunev" To: Date: Fri, 19 Jun 2015 19:51:29 +0300 Message-Id: <1434732693-24127-7-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 06/10] qga: guest exec functionality for Windows 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-win32.c | 309 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 303 insertions(+), 6 deletions(-) diff --git a/qga/commands-win32.c b/qga/commands-win32.c index b216628..e633126 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -454,10 +454,231 @@ static void guest_file_init(void) QTAILQ_INIT(&guest_file_state.filehandles); } + +typedef struct GuestExecInfo { + int pid; + HANDLE phandle; + 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_init(void) +{ + QTAILQ_INIT(&guest_exec_state.processes); +} + +static void guest_exec_info_add(int pid, HANDLE phandle, + GuestFileHandle *in, GuestFileHandle *out, + GuestFileHandle *error) +{ + GuestExecInfo *gei; + + gei = g_malloc0(sizeof(GuestExecInfo)); + gei->pid = pid; + gei->phandle = phandle; + 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(int64_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 **errp) { - error_set(errp, QERR_UNSUPPORTED); - return 0; + GuestExecInfo *gei; + GuestExecStatus *ges; + int r; + DWORD exit_code; + + slog("guest-exec-status called, pid: %" PRId64, pid); + + gei = guest_exec_info_find(pid); + if (gei == NULL) { + error_set(errp, QERR_INVALID_PARAMETER, "pid"); + return NULL; + } + + r = WaitForSingleObject(gei->phandle, 0); + if (r != WAIT_OBJECT_0 && r != WAIT_TIMEOUT) { + error_setg_win32(errp, GetLastError(), + "WaitForSingleObject() failed, pid: %u", gei->pid); + return NULL; + } + + ges = g_malloc0(sizeof(GuestExecStatus)); + ges->handle_stdin = (gei->gfh_stdin != NULL) ? gei->gfh_stdin->id : -1; + ges->handle_stdout = (gei->gfh_stdout != NULL) ? gei->gfh_stdout->id : -1; + ges->handle_stderr = (gei->gfh_stderr != NULL) ? gei->gfh_stderr->id : -1; + ges->exit = -1; + ges->signal = -1; + + if (r == WAIT_OBJECT_0) { + GetExitCodeProcess(gei->phandle, &exit_code); + CloseHandle(gei->phandle); + + ges->exit = (int)exit_code; + + QTAILQ_REMOVE(&guest_exec_state.processes, gei, next); + g_free(gei); + } + + return ges; +} + +/* Convert UTF-8 to wide string. */ +#define utf8_to_ucs2(dst, dst_cap, src, src_len) \ + MultiByteToWideChar(CP_UTF8, 0, src, (int)(src_len), dst, (int)(dst_cap)) + +/* Get command-line arguments for CreateProcess(). + * Path or arguments containing double quotes are prohibited. + * Arguments containing spaces are enclosed in double quotes. + * @wpath: @path that was converted to wchar. + * @argv_str: arguments in one line separated by space. */ +static WCHAR *guest_exec_get_args(const char *path, WCHAR **wpath, + const strList *params, Error **errp) +{ + const strList *it; + bool with_spaces; + size_t cap = 0; + char *argv_str; + WCHAR *wargs; + char *pargv; + + if (strchr(path, '"') != NULL) { + error_setg(errp, "path or arguments can't contain \" quotes"); + return NULL; + } + + for (it = params; it != NULL; it = it->next) { + if (strchr(it->value, '"') != NULL) { + error_setg(errp, "path or arguments can't contain \" quotes"); + return NULL; + } + } + + cap += strlen(path) + sizeof("\"\"") - 1; + for (it = params; it != NULL; it = it->next) { + cap += strlen(it->value) + sizeof(" \"\"") - 1; + } + cap++; + + argv_str = g_malloc(cap); + pargv = argv_str; + + *pargv++ = '"'; + pstrcpy(pargv, (pargv + cap) - pargv, path); + *pargv++ = '"'; + + for (it = params; it != NULL; it = it->next) { + with_spaces = (strchr(it->value, ' ') != NULL); + + *pargv++ = ' '; + + if (with_spaces) { + *pargv++ = '"'; + } + + pstrcpy(pargv, (pargv + cap) - pargv, it->value); + pargv += strlen(it->value); + + if (with_spaces) { + *pargv++ = '"'; + } + } + *pargv = '\0'; + + wargs = g_malloc(cap * sizeof(WCHAR)); + if (utf8_to_ucs2(wargs, cap, argv_str, -1) == 0) { + goto fail; + } + + cap = strlen(path) + 1; + *wpath = g_malloc(cap * sizeof(WCHAR)); + if (utf8_to_ucs2(*wpath, cap, path, -1) == 0) { + g_free(*wpath); + goto fail; + } + slog("guest-exec called: %s", argv_str); + g_free(argv_str); + return wargs; + +fail: + error_setg_win32(errp, GetLastError(), "MultiByteToWideChar() failed"); + g_free(argv_str); + g_free(wargs); + return NULL; +} + +/* Prepare environment string for CreateProcess(). */ +static WCHAR *guest_exec_get_env(strList *env, Error **errp) +{ + const strList *it; + size_t r, cap = 0; + WCHAR *wenv, *pwenv; + + for (it = env; it != NULL; it = it->next) { + cap += strlen(it->value) + 1; + } + cap++; + + wenv = g_malloc(cap * sizeof(WCHAR)); + pwenv = wenv; + + for (it = env; it != NULL; it = it->next) { + r = utf8_to_ucs2(pwenv, (wenv + cap) - pwenv, it->value, -1); + if (r == 0) { + error_setg_win32(errp, GetLastError(), + "MultiByteToWideChar() failed"); + g_free(wenv); + return NULL; + } + pwenv += r - 1; + + *pwenv++ = L'\0'; + } + *pwenv = L'\0'; + + return wenv; +} + +static HANDLE guest_exec_get_stdhandle(GuestFileHandle *gfh) +{ + HANDLE fd; + + if (gfh == NULL) { + return INVALID_HANDLE_VALUE; + } + + if (gfh->pipe_other_end_fd != INVALID_HANDLE_VALUE) { + fd = gfh->pipe_other_end_fd; + } else { + fd = gfh->fh; + } + + if (!SetHandleInformation(fd, HANDLE_FLAG_INHERIT, 1)) { + slog("guest-exec: SetHandleInformation() failed to set inherit flag: " + "%lu", GetLastError()); + } + + return fd; } GuestExec *qmp_guest_exec(const char *path, @@ -468,8 +689,84 @@ GuestExec *qmp_guest_exec(const char *path, bool has_handle_stderr, int64_t handle_stderr, Error **errp) { - error_set(errp, QERR_UNSUPPORTED); - return 0; + int64_t pid = -1; + GuestExec *ge = NULL; + BOOL b; + PROCESS_INFORMATION info; + STARTUPINFOW si = {0}; + WCHAR *wpath = NULL, *wargs, *wenv = NULL; + GuestFileHandle *gfh_stdin = NULL, *gfh_stdout = NULL, *gfh_stderr = NULL; + + wargs = guest_exec_get_args(path, &wpath, has_params ? params : NULL, errp); + wenv = guest_exec_get_env(has_env ? env : NULL, errp); + if (wargs == NULL || wenv == NULL) { + return NULL; + } + + if (has_handle_stdin) { + gfh_stdin = guest_file_handle_find(handle_stdin, errp); + if (gfh_stdin == NULL) { + goto done; + } + } + + if (has_handle_stdout) { + gfh_stdout = guest_file_handle_find(handle_stdout, errp); + if (gfh_stdout == NULL) { + goto done; + } + } + + if (has_handle_stderr) { + gfh_stderr = guest_file_handle_find(handle_stderr, errp); + if (gfh_stderr == NULL) { + goto done; + } + } + + si.cb = sizeof(STARTUPINFOW); + + if (has_handle_stdin || has_handle_stdout || has_handle_stderr) { + si.dwFlags = STARTF_USESTDHANDLES; + + si.hStdInput = guest_exec_get_stdhandle(gfh_stdin); + si.hStdOutput = guest_exec_get_stdhandle(gfh_stdout); + si.hStdError = guest_exec_get_stdhandle(gfh_stderr); + } + + b = CreateProcessW(wpath, wargs, NULL, NULL, 1 /*inherit handles*/, + CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS, + wenv, NULL /*inherited current dir*/, &si, &info); + if (!b) { + error_setg_win32(errp, GetLastError(), + "CreateProcessW() failed"); + goto done; + } + + if (gfh_stdin != NULL && guest_pipe_close_other_end(gfh_stdin) != 0) { + slog("failed to close stdin pipe handle. error: %lu", GetLastError()); + } + + if (gfh_stdout != NULL && guest_pipe_close_other_end(gfh_stdout) != 0) { + slog("failed to close stdout pipe handle. error: %lu", GetLastError()); + } + + if (gfh_stderr != NULL && guest_pipe_close_other_end(gfh_stderr) != 0) { + slog("failed to close stderr pipe handle. error: %lu", GetLastError()); + } + + CloseHandle(info.hThread); + guest_exec_info_add(info.dwProcessId, info.hProcess, gfh_stdin, gfh_stdout, + gfh_stderr); + pid = info.dwProcessId; + ge = g_malloc(sizeof(*ge)); + ge->pid = pid; + +done: + g_free(wpath); + g_free(wargs); + g_free(wenv); + return ge; } GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp) @@ -803,8 +1100,7 @@ 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-exec-status", - "guest-exec", NULL}; + "guest-fstrim", NULL}; char **p = (char **)list_unsupported; while (*p) { @@ -832,4 +1128,5 @@ void ga_command_state_init(GAState *s, GACommandState *cs) ga_command_state_add(cs, NULL, guest_fsfreeze_cleanup); } ga_command_state_add(cs, guest_file_init, NULL); + ga_command_state_add(cs, guest_exec_init, NULL); }