qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 8/8] guest agent: add guest-exec and guest-exec-stat


From: Denis V. Lunev
Subject: [Qemu-devel] [PATCH 8/8] guest agent: add guest-exec and guest-exec-status interfaces on Windows
Date: Wed, 31 Dec 2014 16:06:54 +0300

From: Simon Zolin <address@hidden>

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 <address@hidden>
Signed-off-by: Denis V. Lunev <address@hidden>
CC: Michael Roth <address@hidden>
---
 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);
 }
-- 
1.9.1




reply via email to

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