[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH 03/10] qga: guest exec functionality for Unix guests
From: |
Denis V. Lunev |
Subject: |
[Qemu-devel] [PATCH 03/10] qga: guest exec functionality for Unix guests |
Date: |
Fri, 19 Jun 2015 19:51:26 +0300 |
From: Olga Krishtal <address@hidden>
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 <address@hidden>
Acked-by: Roman Kagan <address@hidden>
Signed-off-by: Denis V. Lunev <address@hidden>
CC: Eric Blake <address@hidden>
CC: Michael Roth <address@hidden>
---
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' }
--
1.9.1
- [Qemu-devel] [PATCH v4 0/10] QGA: disk and volume info for Windows & guest exec, Denis V. Lunev, 2015/06/19
- [Qemu-devel] [PATCH 01/10] util, qga: drop guest_file_toggle_flags, Denis V. Lunev, 2015/06/19
- [Qemu-devel] [PATCH 04/10] qga: handle possible SIGPIPE in guest-file-write, Denis V. Lunev, 2015/06/19
- [Qemu-devel] [PATCH 02/10] qga: implement guest-pipe-open command, Denis V. Lunev, 2015/06/19
- [Qemu-devel] [PATCH 05/10] qga: guest-pipe-open for Windows guest, Denis V. Lunev, 2015/06/19
- [Qemu-devel] [PATCH 03/10] qga: guest exec functionality for Unix guests,
Denis V. Lunev <=
- [Qemu-devel] [PATCH 08/10] qga: added mountpoint and filesystem type for single volume, Denis V. Lunev, 2015/06/19
- [Qemu-devel] [PATCH 10/10] qga: added GuestPCIAddress information, Denis V. Lunev, 2015/06/19
- [Qemu-devel] [PATCH 06/10] qga: guest exec functionality for Windows guests, Denis V. Lunev, 2015/06/19
- [Qemu-devel] [PATCH 07/10] qga: added empty qmp_quest_get_fsinfo functionality., Denis V. Lunev, 2015/06/19
- [Qemu-devel] [PATCH 09/10] qga: added bus type and disk location path, Denis V. Lunev, 2015/06/19