From patchwork Wed Dec 31 13:06:54 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: 424787 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 6406B1400B7 for ; Thu, 1 Jan 2015 00:07:38 +1100 (AEDT) Received: from localhost ([::1]:40105 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Y6Izv-00082v-K9 for incoming@patchwork.ozlabs.org; Wed, 31 Dec 2014 08:07:35 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:48267) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Y6IzX-0007ii-CF for qemu-devel@nongnu.org; Wed, 31 Dec 2014 08:07:15 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Y6IzS-0007PL-Rm for qemu-devel@nongnu.org; Wed, 31 Dec 2014 08:07:11 -0500 Received: from mailhub.sw.ru ([195.214.232.25]:35297 helo=relay.sw.ru) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Y6IzS-0007Oo-Bu for qemu-devel@nongnu.org; Wed, 31 Dec 2014 08:07:06 -0500 Received: from hades.sw.ru ([10.30.8.132]) by relay.sw.ru (8.13.4/8.13.4) with ESMTP id sBVD6nVH032343; Wed, 31 Dec 2014 16:07:01 +0300 (MSK) From: "Denis V. Lunev" To: Date: Wed, 31 Dec 2014 16:06:54 +0300 Message-Id: <1420031214-6053-9-git-send-email-den@openvz.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1420031214-6053-1-git-send-email-den@openvz.org> References: <1420031214-6053-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 8/8] guest agent: add guest-exec and guest-exec-status interfaces on Windows 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 guest-exec command executes a new process on a guest machine. Command-line arguments, environment, stdin/stdout/stderr handles can be passed to a new process. guest-exec-status command gets the status of process executed by 'guest-exec'. Signed-off-by: Simon Zolin Signed-off-by: Denis V. Lunev CC: Michael Roth --- qga/commands-win32.c | 356 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 346 insertions(+), 10 deletions(-) diff --git a/qga/commands-win32.c b/qga/commands-win32.c index 4160c2b..85c300b 100644 --- a/qga/commands-win32.c +++ b/qga/commands-win32.c @@ -224,9 +224,10 @@ static int guest_pipe_close_other_end(GuestFileHandle *gfh) return 0; } +static bool guest_exec_file_busy(GuestFileHandle *gfh); + void qmp_guest_file_close(int64_t handle, Error **errp) { - bool ret; GuestFileHandle *gfh = guest_file_handle_find(handle, errp); slog("guest-file-close called, handle: %" PRId64, handle); if (gfh == NULL) { @@ -238,9 +239,19 @@ void qmp_guest_file_close(int64_t handle, Error **errp) return; } - ret = CloseHandle(gfh->fh); - if (!ret) { - error_setg_errno(errp, GetLastError(), "failed close handle"); + if (gfh->fh != INVALID_HANDLE_VALUE) { + if (!CloseHandle(gfh->fh)) { + error_setg_errno(errp, GetLastError(), "failed to close handle"); + return; + } + + gfh->fh = INVALID_HANDLE_VALUE; + } + + if (guest_exec_file_busy(gfh)) { + error_setg_errno(errp, GetLastError(), + "can't close handle %" PRId64 ", " + "because it's used by guest-exec", handle); return; } @@ -458,10 +469,252 @@ 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; +} + +/* Return TRUE if the specified file is used in guest-exec command. + * We must not free the memory associated with GuestFileHandle* until + * guest-exec-status is called. */ +static bool guest_exec_file_busy(GuestFileHandle *gfh) +{ + GuestExecInfo *gei; + + QTAILQ_FOREACH(gei, &guest_exec_state.processes, next) { + + if (gei->gfh_stdin == gfh || + gei->gfh_stdout == gfh || + gei->gfh_stderr == gfh) { + + return true; + } + } + + return false; +} + 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_errno(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, char **argv_str, + Error **errp) +{ + const strList *it; + bool with_spaces; + size_t cap = 0; + WCHAR *wargs; + char *pargv; + + *wpath = NULL; + + 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, (*argv_str + 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, (*argv_str + 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 mbtowc_failed; + } + + cap = strlen(path) + 1; + *wpath = g_malloc(cap * sizeof(WCHAR)); + if (utf8_to_ucs2(*wpath, cap, path, -1) == 0) { + goto mbtowc_failed; + } + + return wargs; + +mbtowc_failed: + error_setg_errno(errp, GetLastError(), "MultiByteToWideChar() failed"); + g_free(*argv_str); + g_free(wargs); + if (*wpath != NULL) { + g_free(*wpath); + } + return NULL; +} + +/* Prepare environment string for CreateProcess(). */ +static char *guest_exec_get_env(strList *env, Error **errp) +{ + const strList *it; + size_t cap = 0; + char *env_str, *s; + + for (it = env; it != NULL; it = it->next) { + cap += strlen(it->value) + 1; + } + cap++; + + env_str = g_malloc(cap); + s = env_str; + + for (it = env; it != NULL; it = it->next) { + + pstrcpy(s, (env_str + cap) - s, it->value); + s += strlen(it->value); + + *s++ = '\0'; + } + *s = '\0'; + + return env_str; +} + +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; } int64_t qmp_guest_exec(const char *path, @@ -472,8 +725,91 @@ int64_t 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; + BOOL b; + PROCESS_INFORMATION info; + STARTUPINFOW si; + char *argv_str, *env_str = NULL; + WCHAR *wpath, *wargs; + GuestFileHandle *gfh_stdin = NULL, *gfh_stdout = NULL, *gfh_stderr = NULL; + + wargs = guest_exec_get_args(path, &wpath, has_params ? params : NULL, + &argv_str, errp); + if (wargs == NULL) { + return -1; + } + slog("guest-exec called: %s", argv_str); + g_free(argv_str); + + env_str = guest_exec_get_env(has_env ? env : NULL, errp); + if (env_str == NULL) { + return -1; + } + + 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; + } + } + + memset(&si, 0, sizeof(STARTUPINFOW)); + si.cb = sizeof(STARTUPINFOW); + si.hStdInput = INVALID_HANDLE_VALUE; + si.hStdOutput = INVALID_HANDLE_VALUE; + si.hStdError = INVALID_HANDLE_VALUE; + + 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*/, + DETACHED_PROCESS, + env_str, NULL /*inherited current dir*/, &si, &info); + if (!b) { + error_setg_errno(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; + +done: + g_free(wpath); + g_free(wargs); + g_free(env_str); + return pid; } GuestFilesystemInfoList *qmp_guest_get_fsinfo(Error **errp) @@ -777,8 +1113,7 @@ 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-exec-status", - "guest-exec", NULL}; + "guest-fstrim", NULL}; char **p = (char **)list_unsupported; while (*p) { @@ -806,4 +1141,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); }