From patchwork Thu Oct 1 07:38:01 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: 524997 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 B188A140D70 for ; Fri, 2 Oct 2015 00:14:32 +1000 (AEST) Received: from localhost ([::1]:50692 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Zhecq-0000XJ-0W for incoming@patchwork.ozlabs.org; Thu, 01 Oct 2015 10:14:24 -0400 Received: from eggs.gnu.org ([2001:4830:134:3::10]:33016) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZhYSM-0005et-9D for qemu-devel@nongnu.org; Thu, 01 Oct 2015 03:39:11 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1ZhYSH-0002li-I1 for qemu-devel@nongnu.org; Thu, 01 Oct 2015 03:39:10 -0400 Received: from mailhub.sw.ru ([195.214.232.25]:27100 helo=relay.sw.ru) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZhYSH-00025I-3X for qemu-devel@nongnu.org; Thu, 01 Oct 2015 03:39:05 -0400 Received: from irbis.sw.ru ([10.30.2.139]) by relay.sw.ru (8.13.4/8.13.4) with ESMTP id t917gc2b024530; Thu, 1 Oct 2015 10:42:43 +0300 (MSK) From: "Denis V. Lunev" To: Date: Thu, 1 Oct 2015 10:38:01 +0300 Message-Id: <1443685083-6242-4-git-send-email-den@openvz.org> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1443685083-6242-1-git-send-email-den@openvz.org> References: <1443685083-6242-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" , Yuri Pudgorodskiy , qemu-devel@nongnu.org, Michael Roth Subject: [Qemu-devel] [PATCH 3/5] qga: guest exec functionality 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: Yuri Pudgorodskiy Guest-exec rewriten in platform-independant style with glib spawn. Child process is spawn asynchroneously and exit status can later be picked up by guest-exec-status command. stdin/stdout/stderr of the child now is redirected to /dev/null Later we will add ability to specify stdin in guest-exec command and to get collected stdout/stderr with guest-exec-status. Signed-off-by: Yuri Pudgorodskiy Signed-off-by: Denis V. Lunev CC: Michael Roth --- qga/commands.c | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++ qga/qapi-schema.json | 57 +++++++++++++++++ 2 files changed, 225 insertions(+) diff --git a/qga/commands.c b/qga/commands.c index 7834967..6efd6aa 100644 --- a/qga/commands.c +++ b/qga/commands.c @@ -70,3 +70,171 @@ struct GuestAgentInfo *qmp_guest_info(Error **errp) qmp_for_each_command(qmp_command_info, info); return info; } + +struct GuestExecInfo { + GPid pid; + gint status; + bool finished; + QTAILQ_ENTRY(GuestExecInfo) next; +}; +typedef struct GuestExecInfo GuestExecInfo; + +static struct { + QTAILQ_HEAD(, GuestExecInfo) processes; +} guest_exec_state = { + .processes = QTAILQ_HEAD_INITIALIZER(guest_exec_state.processes), +}; + +static GuestExecInfo *guest_exec_info_add(GPid pid) +{ + GuestExecInfo *gei; + + gei = g_malloc0(sizeof(*gei)); + gei->pid = pid; + QTAILQ_INSERT_TAIL(&guest_exec_state.processes, gei, next); + + return gei; +} + +static GuestExecInfo *guest_exec_info_find(GPid 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; + + slog("guest-exec-status called, pid: %u", (uint32_t)pid); + + gei = guest_exec_info_find((GPid)pid); + if (gei == NULL) { + error_setg(err, QERR_INVALID_PARAMETER, "pid"); + return NULL; + } + + ges = g_malloc0(sizeof(GuestExecStatus)); + ges->exit = ges->signal = -1; + + if (gei->finished) { + /* glib has no platform independent way to parse exit status */ +#ifdef G_OS_WIN32 + if ((uint32_t)gei->status < 0xC0000000U) { + ges->exit = gei->status; + } else { + ges->signal = gei->status; + } +#else + if (WIFEXITED(gei->status)) { + ges->exit = WEXITSTATUS(gei->status); + } else if (WIFSIGNALED(gei->status)) { + ges->signal = WTERMSIG(gei->status); + } +#endif + 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, bool log) +{ + const strList *it; + int count = 1, i = 0; /* reserve for NULL terminator */ + char **args; + char *str; /* for logging array of arguments */ + size_t str_size = 1; + + for (it = entry; it != NULL; it = it->next) { + count++; + str_size += 1 + strlen(it->value); + } + + str = g_malloc(str_size); + *str = 0; + args = g_malloc(count * sizeof(char *)); + for (it = entry; it != NULL; it = it->next) { + args[i++] = it->value; + pstrcat(str, str_size, it->value); + if (it->next) { + pstrcat(str, str_size, " "); + } + } + args[i] = NULL; + + if (log) { + slog("guest-exec called: \"%s\"", str); + } + g_free(str); + + return args; +} + +static void guest_exec_child_watch(GPid pid, gint status, gpointer data) +{ + GuestExecInfo *gei = (GuestExecInfo *)data; + + g_debug("guest_exec_child_watch called, pid: %u, status: %u", + (uint32_t)pid, (uint32_t)status); + + gei->status = status; + gei->finished = true; + + g_spawn_close_pid(pid); +} + +GuestExec *qmp_guest_exec(const char *path, + bool has_arg, strList *arg, + bool has_env, strList *env, + bool has_inp_data, const char *inp_data, + bool has_capture_output, bool capture_output, + Error **err) +{ + GPid pid; + GuestExec *ge = NULL; + GuestExecInfo *gei; + char **argv, **envp; + strList arglist; + gboolean ret; + GError *gerr = NULL; + + arglist.value = (char *)path; + arglist.next = has_arg ? arg : NULL; + + argv = guest_exec_get_args(&arglist, true); + envp = guest_exec_get_args(has_env ? env : NULL, false); + + ret = g_spawn_async_with_pipes(NULL, argv, envp, + G_SPAWN_SEARCH_PATH | + G_SPAWN_DO_NOT_REAP_CHILD | + G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, &pid, NULL, NULL, NULL, &gerr); + if (!ret) { + error_setg(err, QERR_QGA_COMMAND_FAILED, gerr->message); + g_error_free(gerr); + goto done; + } + + ge = g_malloc(sizeof(*ge)); + ge->pid = (int64_t)pid; + + gei = guest_exec_info_add(pid); + g_child_watch_add(pid, guest_exec_child_watch, gei); + +done: + g_free(argv); + g_free(envp); + + return ge; +} diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index 82894c6..ca9a633 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -930,3 +930,60 @@ ## { '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. For Windows guest, "signal" is +# actually an unhandled exception code. 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. +# 'out-data' and 'err-data' contains captured data when guest-exec was +# called with 'capture-output' flag. +# +# Since: 2.5 +## +{ 'struct': 'GuestExecStatus', + 'data': { 'exit': 'int', 'signal': 'int', + '*out-data': 'str', '*err-data': 'str' }} + +{ 'command': 'guest-exec-status', + 'data': { 'pid': 'int' }, + 'returns': 'GuestExecStatus' } + +## +# @GuestExec: +# @pid: pid of child process in guest OS +# +#Since: 2.5 +## +{ 'struct': 'GuestExec', + 'data': { 'pid': 'int'} } + +## +# @guest-exec: +# +# Execute a command in the guest +# +# @path: path or executable name to execute +# @arg: #optional argument list to pass to executable +# @env: #optional environment variables to pass to executable +# @inp-data: #optional data to be passed to process stdin (base64 encoded) +# @capture-output: #optional bool flags to enable capture of +# stdout/stderr of running process +# +# Returns: PID on success. +# +# Since: 2.5 +## +{ 'command': 'guest-exec', + 'data': { 'path': 'str', '*arg': ['str'], '*env': ['str'], + '*inp-data': 'str', '*capture-output': 'bool' }, + 'returns': 'GuestExec' }