@@ -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;