diff options
Diffstat (limited to 'qga/commands-posix.c')
-rw-r--r-- | qga/commands-posix.c | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/qga/commands-posix.c b/qga/commands-posix.c index 7df2d72..9b1bdf1 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -76,6 +76,156 @@ static void ga_wait_child(pid_t pid, int *status, Error **errp) g_assert(rpid == pid); } +static ssize_t ga_pipe_read_str(int fd[2], char **str) +{ + ssize_t n, len = 0; + char buf[1024]; + + close(fd[1]); + fd[1] = -1; + while ((n = read(fd[0], buf, sizeof(buf))) != 0) { + if (n < 0) { + if (errno == EINTR) { + continue; + } else { + len = -errno; + break; + } + } + *str = g_realloc(*str, len + n + 1); + memcpy(*str + len, buf, n); + len += n; + *str[len] = '\0'; + } + close(fd[0]); + fd[0] = -1; + + return len; +} + +/* + * Helper to run command with input/output redirection, + * sending string to stdin and taking error message from + * stdout/err. + */ +G_GNUC_UNUSED +static int ga_run_command(const char *argv[], const char *in_str, + const char *action, Error **errp) +{ + pid_t pid; + int status; + int retcode = -1; + int infd[2] = { -1, -1 }; + int outfd[2] = { -1, -1 }; + char *str = NULL; + ssize_t len = 0; + + if ((in_str && !g_unix_open_pipe(infd, FD_CLOEXEC, NULL)) || + !g_unix_open_pipe(outfd, FD_CLOEXEC, NULL)) { + error_setg(errp, "cannot create pipe FDs"); + goto out; + } + + pid = fork(); + if (pid == 0) { + char *cherr = NULL; + + setsid(); + + if (in_str) { + /* Redirect stdin to infd. */ + close(infd[1]); + dup2(infd[0], 0); + close(infd[0]); + } else { + reopen_fd_to_null(0); + } + + /* Redirect stdout/stderr to outfd. */ + close(outfd[0]); + dup2(outfd[1], 1); + dup2(outfd[1], 2); + close(outfd[1]); + + execvp(argv[0], (char *const *)argv); + + /* Write the cause of failed exec to pipe for the parent to read it. */ + cherr = g_strdup_printf("failed to exec '%s'", argv[0]); + perror(cherr); + g_free(cherr); + _exit(EXIT_FAILURE); + } else if (pid < 0) { + error_setg_errno(errp, errno, "failed to create child process"); + goto out; + } + + if (in_str) { + close(infd[0]); + infd[0] = -1; + if (qemu_write_full(infd[1], in_str, strlen(in_str)) != + strlen(in_str)) { + error_setg_errno(errp, errno, "%s: cannot write to stdin pipe", + action); + goto out; + } + close(infd[1]); + infd[1] = -1; + } + + len = ga_pipe_read_str(outfd, &str); + if (len < 0) { + error_setg_errno(errp, -len, "%s: cannot read from stdout/stderr pipe", + action); + goto out; + } + + ga_wait_child(pid, &status, errp); + if (*errp) { + goto out; + } + + if (!WIFEXITED(status)) { + if (len) { + error_setg(errp, "child process has terminated abnormally: %s", + str); + } else { + error_setg(errp, "child process has terminated abnormally"); + } + goto out; + } + + retcode = WEXITSTATUS(status); + + if (WEXITSTATUS(status)) { + if (len) { + error_setg(errp, "child process has failed to %s: %s", + action, str); + } else { + error_setg(errp, "child process has failed to %s: exit status %d", + action, WEXITSTATUS(status)); + } + goto out; + } + +out: + g_free(str); + + if (infd[0] != -1) { + close(infd[0]); + } + if (infd[1] != -1) { + close(infd[1]); + } + if (outfd[0] != -1) { + close(outfd[0]); + } + if (outfd[1] != -1) { + close(outfd[1]); + } + + return retcode; +} + void qmp_guest_shutdown(const char *mode, Error **errp) { const char *shutdown_flag; |