summaryrefslogtreecommitdiffstats
path: root/qemu/qga/commands.c
blob: b653a460b98485708e0221e500d0b9a5910c726a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122

@media only all and (prefers-color-scheme: dark) {
.highlight .hll { background-color: #49483e }
.highlight .c { color: #75715e } /* Comment */
.highlight .err { color: #960050; background-color: #1e0010 } /* Error */
.highlight .k { color: #66d9ef } /* Keyword */
.highlight .l { color: #ae81ff } /* Literal */
.highlight .n { color: #f8f8f2 } /* Name */
.highlight .o { color: #f92672 } /* Operator */
.highlight .p { color: #f8f8f2 } /* Punctuation */
.highlight .ch { color: #75715e } /* Comment.Hashbang */
.highlight .cm { color: #75715e } /* Comment.Multiline */
.highlight .cp { color: #75715e } /* Comment.Preproc */
.highlight .cpf { color: #75715e } /* Comment.PreprocFile */
.highlight .c1 { color: #75715e } /* Comment.Single */
.highlight .cs { color: #75715e } /* Comment.Special */
.highlight .gd { color: #f92672 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gi { color: #a6e22e } /* Generic.Inserted */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #75715e } /* Generic.Subheading */
.highlight .kc { color: #66d9ef } /* Keyword.Constant */
.highlight .kd { color: #66d9ef } /* Keyword.Declaration */
.highlight .kn { color: #f92672 } /* Keyword.Namespace */
.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */
.highlight .kr { color: #66d9ef } /* Keyword.Reserved */
.highlight .kt { color: #66d9ef } /* Keyword.Type */
.highlight .ld { color: #e6db74 } /* Literal.Date */
.highlight .m { color: #ae81ff } /* Literal.Number */
.highlight .s { color: #e6db74 } /* Literal.String */
.highlight .na { color: #a6e22e } /* Name.Attribute */
.highlight .nb { color: #f8f8f2 } /* Name.Builtin */
.highlight .nc { color: #a6e22e } /* Name.Class */
.highlight .no { color: #66d9ef } /* Name.Constant */
.highlight .nd { color: #a6e22e } /* Name.Decorator */
.highlight .ni { color: #f8f8f2 } /* Name.Entity */
.highlight .ne { color: #a6e22e } /* Name.Exception */
.highlight .nf { color: #a6e22e } /* Name.Function */
.highlight .nl { color: #f8f8f2 } /* Name.Label */
.highlight .nn { color: #f8f8f2 } /* Name.Namespace */
.highlight .nx { color: #a6e22e } /* Name.Other */
.highlight .py { color: #f8f8f2 } /* Name.Property */
.highlight .nt { color: #f92672 } /* Name.Tag */
.highlight .nv { color: #f8f8f2 } /* Name.Variable */
.highlight .ow { color: #f92672 } /* Operator.Word */
.highlight .w { color: #f8f8f2 } /* Text.Whitespace */
.highlight .mb { color: #ae81ff } /* Literal.Number.Bin */
.highlight .mf { color: #ae81ff } /* Literal.Number.Float */
.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */
.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */
.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */
.highlight .sa { color: #e6db74 } /* Literal.String.Affix */
.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */
.highlight .sc { color: #e6db74 } /* Literal.String.Char */
.highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */
.highlight .sd { color: #e6db74 } /* Literal.String.Doc */
.highlight .s2 { color: #e6db74 } /* Literal.String.Double */
.highlight .se { color: #ae81ff } /* Literal.String.Escape */
.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */
.highlight .si { color: #e6db74 } /* Literal.String.Interpol */
.highlight .sx { color: #e6db74 } /* Literal.String.Other */
.highlight .sr { color: #e6db74 } /* Literal.String.Regex */
.highlight .s1 { color: #e6db74 } /* Literal.String.Single */
.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */
.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #a6e22e } /* Name.Function.Magic */
.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */
.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */
.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */
.highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */
.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */
}
@media (prefers-color-scheme: light) {
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
}
###############################################################################
# Role: ControllerOpenstack                                                   #
###############################################################################
- name: ControllerOpenstack
  description: |
    Controller role that does not contain the database, messaging and networking
    components. Use in combination with the Database, Messaging and Networker
    roles.
  tags:
    - primary
    - controller
  networks:
    - External
    - InternalApi
    - Storage
    - StorageMgmt
    - Tenant
  HostnameFormatDefault: '%stackname%-controller-%index%'
  ServicesDefault:
    - OS::TripleO::Services::AodhApi
    - OS::TripleO::Services::AodhEvaluator
    - OS::TripleO::Services::AodhListener
    - OS::TripleO::Services::AodhNotifier
    - OS::TripleO::Services::AuditD
    - OS::TripleO::Services::BarbicanApi
    - OS::TripleO::Services::CACerts
    - OS::TripleO::Services::CeilometerAgentCentral
    - OS::TripleO::Services::CeilometerAgentNotification
    - OS::TripleO::Services::CeilometerApi
    - OS::TripleO::Services::CeilometerExpirer
    - OS::TripleO::Services::CephExternal
    - OS::TripleO::Services::CephMds
    - OS::TripleO::Services::CephMon
    - OS::TripleO::Services::CephRbdMirror
    - OS::TripleO::Services::CephRgw
    - OS::TripleO::Services::CinderApi
    - OS::TripleO::Services::CinderBackup
    
/*
 * QEMU Guest Agent common/cross-platform command implementations
 *
 * Copyright IBM Corp. 2012
 *
 * Authors:
 *  Michael Roth      <mdroth@linux.vnet.ibm.com>
 *
 * This work is licensed under the terms of the GNU GPL, version 2 or later.
 * See the COPYING file in the top-level directory.
 */

#include "qemu/osdep.h"
#include <glib.h>
#include "qga/guest-agent-core.h"
#include "qga-qmp-commands.h"
#include "qapi/qmp/qerror.h"
#include "qemu/base64.h"
#include "qemu/cutils.h"

/* Maximum captured guest-exec out_data/err_data - 16MB */
#define GUEST_EXEC_MAX_OUTPUT (16*1024*1024)
/* Allocation and I/O buffer for reading guest-exec out_data/err_data - 4KB */
#define GUEST_EXEC_IO_SIZE (4*1024)

/* Note: in some situations, like with the fsfreeze, logging may be
 * temporarilly disabled. if it is necessary that a command be able
 * to log for accounting purposes, check ga_logging_enabled() beforehand,
 * and use the QERR_QGA_LOGGING_DISABLED to generate an error
 */
void slog(const gchar *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    g_logv("syslog", G_LOG_LEVEL_INFO, fmt, ap);
    va_end(ap);
}

int64_t qmp_guest_sync_delimited(int64_t id, Error **errp)
{
    ga_set_response_delimited(ga_state);
    return id;
}

int64_t qmp_guest_sync(int64_t id, Error **errp)
{
    return id;
}

void qmp_guest_ping(Error **errp)
{
    slog("guest-ping called");
}

static void qmp_command_info(QmpCommand *cmd, void *opaque)
{
    GuestAgentInfo *info = opaque;
    GuestAgentCommandInfo *cmd_info;
    GuestAgentCommandInfoList *cmd_info_list;

    cmd_info = g_new0(GuestAgentCommandInfo, 1);
    cmd_info->name = g_strdup(qmp_command_name(cmd));
    cmd_info->enabled = qmp_command_is_enabled(cmd);
    cmd_info->success_response = qmp_has_success_response(cmd);

    cmd_info_list = g_new0(GuestAgentCommandInfoList, 1);
    cmd_info_list->value = cmd_info;
    cmd_info_list->next = info->supported_commands;
    info->supported_commands = cmd_info_list;
}

struct GuestAgentInfo *qmp_guest_info(Error **errp)
{
    GuestAgentInfo *info = g_new0(GuestAgentInfo, 1);

    info->version = g_strdup(QEMU_VERSION);
    qmp_for_each_command(qmp_command_info, info);
    return info;
}

struct GuestExecIOData {
    guchar *data;
    gsize size;
    gsize length;
    gint closed;
    bool truncated;
    const char *name;
};
typedef struct GuestExecIOData GuestExecIOData;

struct GuestExecInfo {
    GPid pid;
    int64_t pid_numeric;
    gint status;
    bool has_output;
    gint finished;
    GuestExecIOData in;
    GuestExecIOData out;
    GuestExecIOData err;
    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 int64_t gpid_to_int64(GPid pid)
{
#ifdef G_OS_WIN32
    return GetProcessId(pid);
#else
    return (int64_t)pid;
#endif
}

static GuestExecInfo *guest_exec_info_add(GPid pid)
{
    GuestExecInfo *gei;

    gei = g_new0(GuestExecInfo, 1);
    gei->pid = pid;
    gei->pid_numeric = gpid_to_int64(pid);
    QTAILQ_INSERT_TAIL(&guest_exec_state.processes, gei, next);

    return gei;
}

static GuestExecInfo *guest_exec_info_find(int64_t pid_numeric)
{
    GuestExecInfo *gei;

    QTAILQ_FOREACH(gei, &guest_exec_state.processes, next) {
        if (gei->pid_numeric == pid_numeric) {
            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(pid);
    if (gei == NULL) {
        error_setg(err, QERR_INVALID_PARAMETER, "pid");
        return NULL;
    }

    ges = g_new0(GuestExecStatus, 1);

    bool finished = g_atomic_int_get(&gei->finished);

    /* need to wait till output channels are closed
     * to be sure we captured all output at this point */
    if (gei->has_output) {
        finished = finished && g_atomic_int_get(&gei->out.closed);
        finished = finished && g_atomic_int_get(&gei->err.closed);
    }

    ges->exited = finished;
    if (finished) {
        /* Glib has no portable way to parse exit status.
         * On UNIX, we can get either exit code from normal termination
         * or signal number.
         * On Windows, it is either the same exit code or the exception
         * value for an unhandled exception that caused the process
         * to terminate.
         * See MSDN for GetExitCodeProcess() and ntstatus.h for possible
         * well-known codes, e.g. C0000005 ACCESS_DENIED - analog of SIGSEGV
         * References:
         *   https://msdn.microsoft.com/en-us/library/windows/desktop/ms683189(v=vs.85).aspx
         *   https://msdn.microsoft.com/en-us/library/aa260331(v=vs.60).aspx
         */
#ifdef G_OS_WIN32
        /* Additionally WIN32 does not provide any additional information
         * on whetherthe child exited or terminated via signal.
         * We use this simple range check to distingish application exit code
         * (usually value less then 256) and unhandled exception code with
         * ntstatus (always value greater then 0xC0000005). */
        if ((uint32_t)gei->status < 0xC0000000U) {
            ges->has_exitcode = true;
            ges->exitcode = gei->status;
        } else {
            ges->has_signal = true;
            ges->signal = gei->status;
        }
#else
        if (WIFEXITED(gei->status)) {
            ges->has_exitcode = true;
            ges->exitcode = WEXITSTATUS(gei->status);
        } else if (WIFSIGNALED(gei->status)) {
            ges->has_signal = true;
            ges->signal = WTERMSIG(gei->status);
        }
#endif
        if (gei->out.length > 0) {
            ges->has_out_data = true;
            ges->out_data = g_base64_encode(gei->out.data, gei->out.length);
            g_free(gei->out.data);
            ges->has_out_truncated = gei->out.truncated;
        }

        if (gei->err.length > 0) {
            ges->has_err_data = true;
            ges->err_data = g_base64_encode(gei->err.data, gei->err.length);
            g_free(gei->err.data);
            ges->has_err_truncated = gei->err.truncated;
        }

        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: %d, status: %u",
            (int32_t)gpid_to_int64(pid), (uint32_t)status);

    gei->status = status;
    gei->finished = true;

    g_spawn_close_pid(pid);
}

/** Reset ignored signals back to default. */
static void guest_exec_task_setup(gpointer data)
{
#if !defined(G_OS_WIN32)
    struct sigaction sigact;

    memset(&sigact, 0, sizeof(struct sigaction));
    sigact.sa_handler = SIG_DFL;

    if (sigaction(SIGPIPE, &sigact, NULL) != 0) {
        slog("sigaction() failed to reset child process's SIGPIPE: %s",
             strerror(errno));
    }
#endif
}

static gboolean guest_exec_input_watch(GIOChannel *ch,
        GIOCondition cond, gpointer p_)
{
    GuestExecIOData *p = (GuestExecIOData *)p_;
    gsize bytes_written = 0;
    GIOStatus status;
    GError *gerr = NULL;

    /* nothing left to write */
    if (p->size == p->length) {
        goto done;
    }

    status = g_io_channel_write_chars(ch, (gchar *)p->data + p->length,
            p->size - p->length, &bytes_written, &gerr);

    /* can be not 0 even if not G_IO_STATUS_NORMAL */
    if (bytes_written != 0) {
        p->length += bytes_written;
    }

    /* continue write, our callback will be called again */
    if (status == G_IO_STATUS_NORMAL || status == G_IO_STATUS_AGAIN) {
        return true;
    }

    if (gerr) {
        g_warning("qga: i/o error writing to input_data channel: %s",
                gerr->message);
        g_error_free(gerr);
    }

done:
    g_io_channel_shutdown(ch, true, NULL);
    g_io_channel_unref(ch);
    g_atomic_int_set(&p->closed, 1);
    g_free(p->data);

    return false;
}

static gboolean guest_exec_output_watch(GIOChannel *ch,
        GIOCondition cond, gpointer p_)
{
    GuestExecIOData *p = (GuestExecIOData *)p_;
    gsize bytes_read;
    GIOStatus gstatus;

    if (cond == G_IO_HUP || cond == G_IO_ERR) {
        goto close;
    }

    if (p->size == p->length) {
        gpointer t = NULL;
        if (!p->truncated && p->size < GUEST_EXEC_MAX_OUTPUT) {
            t = g_try_realloc(p->data, p->size + GUEST_EXEC_IO_SIZE);
        }
        if (t == NULL) {
            /* ignore truncated output */
            gchar buf[GUEST_EXEC_IO_SIZE];

            p->truncated = true;
            gstatus = g_io_channel_read_chars(ch, buf, sizeof(buf),
                                              &bytes_read, NULL);
            if (gstatus == G_IO_STATUS_EOF || gstatus == G_IO_STATUS_ERROR) {
                goto close;
            }

            return true;
        }
        p->size += GUEST_EXEC_IO_SIZE;
        p->data = t;
    }

    /* Calling read API once.
     * On next available data our callback will be called again */
    gstatus = g_io_channel_read_chars(ch, (gchar *)p->data + p->length,
            p->size - p->length, &bytes_read, NULL);
    if (gstatus == G_IO_STATUS_EOF || gstatus == G_IO_STATUS_ERROR) {
        goto close;
    }

    p->length += bytes_read;

    return true;

close:
    g_io_channel_shutdown(ch, true, NULL);
    g_io_channel_unref(ch);
    g_atomic_int_set(&p->closed, 1);
    return false;
}

GuestExec *qmp_guest_exec(const char *path,
                       bool has_arg, strList *arg,
                       bool has_env, strList *env,
                       bool has_input_data, const char *input_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;
    gint in_fd, out_fd, err_fd;
    GIOChannel *in_ch, *out_ch, *err_ch;
    GSpawnFlags flags;
    bool has_output = (has_capture_output && capture_output);
    uint8_t *input = NULL;
    size_t ninput = 0;

    arglist.value = (char *)path;
    arglist.next = has_arg ? arg : NULL;

    if (has_input_data) {
        input = qbase64_decode(input_data, -1, &ninput, err);
        if (!input) {
            return NULL;
        }
    }

    argv = guest_exec_get_args(&arglist, true);
    envp = has_env ? guest_exec_get_args(env, false) : NULL;

    flags = G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD;
#if GLIB_CHECK_VERSION(2, 33, 2)
    flags |= G_SPAWN_SEARCH_PATH_FROM_ENVP;
#endif
    if (!has_output) {
        flags |= G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL;
    }

    ret = g_spawn_async_with_pipes(NULL, argv, envp, flags,
            guest_exec_task_setup, NULL, &pid, has_input_data ? &in_fd : NULL,
            has_output ? &out_fd : NULL, has_output ? &err_fd : NULL, &gerr);
    if (!ret) {
        error_setg(err, QERR_QGA_COMMAND_FAILED, gerr->message);
        g_error_free(gerr);
        goto done;
    }

    ge = g_new0(GuestExec, 1);
    ge->pid = gpid_to_int64(pid);

    gei = guest_exec_info_add(pid);
    gei->has_output = has_output;
    g_child_watch_add(pid, guest_exec_child_watch, gei);

    if (has_input_data) {
        gei->in.data = input;
        gei->in.size = ninput;
#ifdef G_OS_WIN32
        in_ch = g_io_channel_win32_new_fd(in_fd);
#else
        in_ch = g_io_channel_unix_new(in_fd);
#endif
        g_io_channel_set_encoding(in_ch, NULL, NULL);
        g_io_channel_set_buffered(in_ch, false);
        g_io_channel_set_flags(in_ch, G_IO_FLAG_NONBLOCK, NULL);
        g_io_channel_set_close_on_unref(in_ch, true);
        g_io_add_watch(in_ch, G_IO_OUT, guest_exec_input_watch, &gei->in);
    }

    if (has_output) {
#ifdef G_OS_WIN32
        out_ch = g_io_channel_win32_new_fd(out_fd);
        err_ch = g_io_channel_win32_new_fd(err_fd);
#else
        out_ch = g_io_channel_unix_new(out_fd);
        err_ch = g_io_channel_unix_new(err_fd);
#endif
        g_io_channel_set_encoding(out_ch, NULL, NULL);
        g_io_channel_set_encoding(err_ch, NULL, NULL);
        g_io_channel_set_buffered(out_ch, false);
        g_io_channel_set_buffered(err_ch, false);
        g_io_channel_set_close_on_unref(out_ch, true);
        g_io_channel_set_close_on_unref(err_ch, true);
        g_io_add_watch(out_ch, G_IO_IN | G_IO_HUP,
                guest_exec_output_watch, &gei->out);
        g_io_add_watch(err_ch, G_IO_IN | G_IO_HUP,
                guest_exec_output_watch, &gei->err);
    }

done:
    g_free(argv);
    g_free(envp);

    return ge;
}

/* Convert GuestFileWhence (either a raw integer or an enum value) into
 * the guest's SEEK_ constants.  */
int ga_parse_whence(GuestFileWhence *whence, Error **errp)
{
    /* Exploit the fact that we picked values to match QGA_SEEK_*. */
    if (whence->type == QTYPE_QSTRING) {
        whence->type = QTYPE_QINT;
        whence->u.value = whence->u.name;
    }
    switch (whence->u.value) {
    case QGA_SEEK_SET:
        return SEEK_SET;
    case QGA_SEEK_CUR:
        return SEEK_CUR;
    case QGA_SEEK_END:
        return SEEK_END;
    }
    error_setg(errp, "invalid whence code %"PRId64, whence->u.value);
    return -1;
}