/*
 * Copyright (C) 2010      Citrix Ltd.
 * Author Vincent Hanquez <vincent.hanquez@eu.citrix.com>
 * Author Stefano Stabellini <stefano.stabellini@eu.citrix.com>
 * Author Gianni Tedesco <gianni.tedesco@citrix.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation; version 2.1 only. with the special
 * exception on linking described in file LICENSE.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 */

#include "libxl_osdeps.h" /* must come before any other headers */

#include "libxl_internal.h"
#include "libxl_arch.h"

#include <xc_dom.h>
#include <xenguest.h>
#include <xen/hvm/hvm_info_table.h>
#include <xen/hvm/e820.h>

int libxl__domain_create_info_setdefault(libxl__gc *gc,
                                         libxl_domain_create_info *c_info)
{
    if (!c_info->type)
        return ERROR_INVAL;

    if (c_info->type == LIBXL_DOMAIN_TYPE_HVM) {
        libxl_defbool_setdefault(&c_info->hap, true);
        libxl_defbool_setdefault(&c_info->oos, true);
    } else {
        libxl_defbool_setdefault(&c_info->pvh, false);
        libxl_defbool_setdefault(&c_info->hap, libxl_defbool_val(c_info->pvh));
    }

    libxl_defbool_setdefault(&c_info->run_hotplug_scripts, true);
    libxl_defbool_setdefault(&c_info->driver_domain, false);

    return 0;
}

static int sched_params_valid(libxl__gc *gc,
                              uint32_t domid, libxl_domain_sched_params *scp)
{
    int has_weight = scp->weight != LIBXL_DOMAIN_SCHED_PARAM_WEIGHT_DEFAULT;
    int has_period = scp->period != LIBXL_DOMAIN_SCHED_PARAM_PERIOD_DEFAULT;
    int has_slice = scp->slice != LIBXL_DOMAIN_SCHED_PARAM_SLICE_DEFAULT;
    int has_extratime =
                scp->extratime != LIBXL_DOMAIN_SCHED_PARAM_EXTRATIME_DEFAULT;

    /* The sedf scheduler needs some more consistency checking */
    if (libxl__domain_scheduler(gc, domid) == LIBXL_SCHEDULER_SEDF) {
        if (has_weight && (has_period || has_slice))
            return 0;
        /* If you want a real-time domain, with its own period and
         * slice, please, do provide both! */
        if (has_period != has_slice)
            return 0;

        /*
         * Idea is, if we specify a weight, then both period and
         * slice has to be zero. OTOH, if we do specify a period and
         * slice, it is weight that should be zeroed. See
         * docs/misc/sedf_scheduler_mini-HOWTO.txt for more details
         * on the meaningful combinations and their meanings.
         */
        if (has_weight) {
            scp->slice = 0;
            scp->period = 0;
        }
        else if (!has_period) {
            /* No weight nor slice/period means best effort. Parameters needs
             * some mangling in order to properly ask for that, though. */

            /*
             * Providing no weight does not make any sense if we do not allow
             * the domain to run in extra time. On the other hand, if we have
             * extra time, weight will be ignored (and zeroed) by Xen, but it
             * can't be zero here, or the call for setting the scheduling
             * parameters will fail. So, avoid the latter by setting a random
             * weight (namely, 1), as it will be ignored anyway.
             */

            /* We can setup a proper best effort domain (extra time only)
             * iff we either already have or are asking for some extra time. */
            scp->weight = has_extratime ? scp->extratime : 1;
            scp->period = 0;
        } else {
            /* Real-time domain: will get slice CPU time over every period */
            scp->weight = 0;
        }
    }

    return 1;
}

int libxl__domain_build_info_setdefault(libxl__gc *gc,
                                        libxl_domain_build_info *b_info)
{
    int i;

    if (b_info->type != LIBXL_DOMAIN_TYPE_HVM &&
        b_info->type != LIBXL_DOMAIN_TYPE_PV)
        return ERROR_INVAL;

    libxl_defbool_setdefault(&b_info->device_model_stubdomain, false);

    if (!b_info->device_model_version) {
        if (b_info->type == LIBXL_DOMAIN_TYPE_HVM) {
            if (libxl_defbool_val(b_info->device_model_stubdomain)) {
                b_info->device_model_version =
                    LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL;
            } else {
                b_info->device_model_version = libxl__default_device_model(gc);
            }
        } else {
            b_info->device_model_version =
                LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN;
        }
        if (b_info->device_model_version
                == LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN) {
            const char *dm;
            int rc;

            dm = libxl__domain_device_model(gc, b_info);
            rc = access(dm, X_OK);
            if (rc < 0) {
                /* qemu-xen unavailable, use qemu-xen-traditional */
                if (errno == ENOENT) {
                    LIBXL__LOG_ERRNO(CTX, XTL_VERBOSE, "qemu-xen is unavailable"
                                     ", use qemu-xen-traditional instead");
                    b_info->device_model_version =
                        LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL;
                } else {
                    LIBXL__LOG_ERRNO(CTX, XTL_ERROR, "qemu-xen access error");
                    return ERROR_FAIL;
                }
            }
        }
    }

    if (b_info->blkdev_start == NULL)
        b_info->blkdev_start = libxl__strdup(NOGC, "xvda");

    if (b_info->type == LIBXL_DOMAIN_TYPE_HVM) {
        if (!b_info->u.hvm.bios)
            switch (b_info->device_model_version) {
            case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL:
                b_info->u.hvm.bios = LIBXL_BIOS_TYPE_ROMBIOS; break;
            case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN:
                b_info->u.hvm.bios = LIBXL_BIOS_TYPE_SEABIOS; break;
            default:return ERROR_INVAL;
            }

        /* Enforce BIOS<->Device Model version relationship */
        switch (b_info->device_model_version) {
        case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL:
            if (b_info->u.hvm.bios != LIBXL_BIOS_TYPE_ROMBIOS)
                return ERROR_INVAL;
            break;
        case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN:
            if (b_info->u.hvm.bios == LIBXL_BIOS_TYPE_ROMBIOS)
                return ERROR_INVAL;
            break;
        default:abort();
        }
    }

    if (b_info->type == LIBXL_DOMAIN_TYPE_HVM &&
        b_info->device_model_version !=
            LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL &&
        libxl_defbool_val(b_info->device_model_stubdomain)) {
        LIBXL__LOG(CTX, XTL_ERROR,
            "device model stubdomains require \"qemu-xen-traditional\"");
        return ERROR_INVAL;
    }

    if (!b_info->max_vcpus)
        b_info->max_vcpus = 1;
    if (!b_info->avail_vcpus.size) {
        if (libxl_cpu_bitmap_alloc(CTX, &b_info->avail_vcpus, 1))
            return ERROR_FAIL;
        libxl_bitmap_set(&b_info->avail_vcpus, 0);
    } else if (b_info->avail_vcpus.size > HVM_MAX_VCPUS)
        return ERROR_FAIL;

    /* In libxl internals, we want to deal with vcpu_hard_affinity only! */
    if (b_info->cpumap.size && !b_info->num_vcpu_hard_affinity) {
        b_info->vcpu_hard_affinity = libxl__calloc(gc, b_info->max_vcpus,
                                                   sizeof(libxl_bitmap));
        for (i = 0; i < b_info->max_vcpus; i++) {
            if (libxl_cpu_bitmap_alloc(CTX, &b_info->vcpu_hard_affinity[i], 0))
                return ERROR_FAIL;
            libxl_bitmap_copy(CTX, &b_info->vcpu_hard_affinity[i],
                              &b_info->cpumap);
        }
        b_info->num_vcpu_hard_affinity = b_info->max_vcpus;
    }

    libxl_defbool_setdefault(&b_info->numa_placement, true);

    if (b_info->max_memkb == LIBXL_MEMKB_DEFAULT)
        b_info->max_memkb = 32 * 1024;
    if (b_info->target_memkb == LIBXL_MEMKB_DEFAULT)
        b_info->target_memkb = b_info->max_memkb;

    libxl_defbool_setdefault(&b_info->claim_mode, false);

    libxl_defbool_setdefault(&b_info->localtime, false);

    libxl_defbool_setdefault(&b_info->disable_migrate, false);

    for (i = 0 ; i < b_info->num_iomem; i++)
        if (b_info->iomem[i].gfn == LIBXL_INVALID_GFN)
            b_info->iomem[i].gfn = b_info->iomem[i].start;

    if (!b_info->event_channels)
        b_info->event_channels = 1023;

    switch (b_info->type) {
    case LIBXL_DOMAIN_TYPE_HVM:
        if (b_info->shadow_memkb == LIBXL_MEMKB_DEFAULT)
            b_info->shadow_memkb = 0;
        if (b_info->u.hvm.mmio_hole_memkb == LIBXL_MEMKB_DEFAULT)
            b_info->u.hvm.mmio_hole_memkb = 0;

        if (!b_info->u.hvm.vga.kind)
            b_info->u.hvm.vga.kind = LIBXL_VGA_INTERFACE_TYPE_CIRRUS;

        switch (b_info->device_model_version) {
        case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL:
            switch (b_info->u.hvm.vga.kind) {
            case LIBXL_VGA_INTERFACE_TYPE_NONE:
                if (b_info->video_memkb == LIBXL_MEMKB_DEFAULT)
                    b_info->video_memkb = 0;
                break;
            case LIBXL_VGA_INTERFACE_TYPE_STD:
                if (b_info->video_memkb == LIBXL_MEMKB_DEFAULT)
                    b_info->video_memkb = 8 * 1024;
                if (b_info->video_memkb < 8 * 1024) {
                    LOG(ERROR, "videoram must be at least 8 MB for STDVGA on QEMU_XEN_TRADITIONAL");
                    return ERROR_INVAL;
                }
                break;
            case LIBXL_VGA_INTERFACE_TYPE_CIRRUS:
            default:
                if (b_info->video_memkb == LIBXL_MEMKB_DEFAULT)
                    b_info->video_memkb = 4 * 1024;
                if (b_info->video_memkb != 4 * 1024)
                    LOG(WARN, "ignoring videoram other than 4 MB for CIRRUS on QEMU_XEN_TRADITIONAL");
                break;
            }
            break;
        case LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN:
        default:
            switch (b_info->u.hvm.vga.kind) {
            case LIBXL_VGA_INTERFACE_TYPE_NONE:
                if (b_info->video_memkb == LIBXL_MEMKB_DEFAULT)
                    b_info->video_memkb = 0;
                break;
            case LIBXL_VGA_INTERFACE_TYPE_STD:
                if (b_info->video_memkb == LIBXL_MEMKB_DEFAULT)
                    b_info->video_memkb = 16 * 1024;
                if (b_info->video_memkb < 16 * 1024) {
                    LOG(ERROR, "videoram must be at least 16 MB for STDVGA on QEMU_XEN");
                    return ERROR_INVAL;
                }
                break;
            case LIBXL_VGA_INTERFACE_TYPE_CIRRUS:
            default:
                if (b_info->video_memkb == LIBXL_MEMKB_DEFAULT)
                    b_info->video_memkb = 8 * 1024;
                if (b_info->video_memkb < 8 * 1024) {
                    LOG(ERROR, "videoram must be at least 8 MB for CIRRUS on QEMU_XEN");
                    return ERROR_INVAL;
                }
                break;
            }
            break;
        }

        if (b_info->u.hvm.timer_mode == LIBXL_TIMER_MODE_DEFAULT)
            b_info->u.hvm.timer_mode =
                LIBXL_TIMER_MODE_NO_DELAY_FOR_MISSED_TICKS;

        libxl_defbool_setdefault(&b_info->u.hvm.pae,                true);
        libxl_defbool_setdefault(&b_info->u.hvm.apic,               true);
        libxl_defbool_setdefault(&b_info->u.hvm.acpi,               true);
        libxl_defbool_setdefault(&b_info->u.hvm.acpi_s3,            true);
        libxl_defbool_setdefault(&b_info->u.hvm.acpi_s4,            true);
        libxl_defbool_setdefault(&b_info->u.hvm.nx,                 true);
        libxl_defbool_setdefault(&b_info->u.hvm.viridian,           false);
        libxl_defbool_setdefault(&b_info->u.hvm.hpet,               true);
        libxl_defbool_setdefault(&b_info->u.hvm.vpt_align,          true);
        libxl_defbool_setdefault(&b_info->u.hvm.nested_hvm,         false);
        libxl_defbool_setdefault(&b_info->u.hvm.usb,                false);
        libxl_defbool_setdefault(&b_info->u.hvm.xen_platform_pci,   true);

        libxl_defbool_setdefault(&b_info->u.hvm.spice.enable, false);
        if (!libxl_defbool_val(b_info->u.hvm.spice.enable) &&
            (b_info->u.hvm.spice.usbredirection > 0) ){
            b_info->u.hvm.spice.usbredirection = 0;
            LOG(WARN, "spice disabled, disabling usbredirection");
        }

        if (!b_info->u.hvm.usbversion &&
            (b_info->u.hvm.spice.usbredirection > 0) )
            b_info->u.hvm.usbversion = 2;

        if ((b_info->u.hvm.usbversion || b_info->u.hvm.spice.usbredirection) &&
            ( libxl_defbool_val(b_info->u.hvm.usb)
            || b_info->u.hvm.usbdevice_list
            || b_info->u.hvm.usbdevice) ){
            LOG(ERROR,"usbversion and/or usbredirection cannot be "
            "enabled with usb and/or usbdevice parameters.");
            return ERROR_INVAL;
        }

        if (!b_info->u.hvm.boot) {
            b_info->u.hvm.boot = strdup("cda");
            if (!b_info->u.hvm.boot) return ERROR_NOMEM;
        }

        libxl_defbool_setdefault(&b_info->u.hvm.vnc.enable, true);
        if (libxl_defbool_val(b_info->u.hvm.vnc.enable)) {
            libxl_defbool_setdefault(&b_info->u.hvm.vnc.findunused, true);
            if (!b_info->u.hvm.vnc.listen) {
                b_info->u.hvm.vnc.listen = strdup("127.0.0.1");
                if (!b_info->u.hvm.vnc.listen) return ERROR_NOMEM;
            }
        }

        libxl_defbool_setdefault(&b_info->u.hvm.sdl.enable, false);
        if (libxl_defbool_val(b_info->u.hvm.sdl.enable)) {
            libxl_defbool_setdefault(&b_info->u.hvm.sdl.opengl, false);
        }

        if (libxl_defbool_val(b_info->u.hvm.spice.enable)) {
            libxl_defbool_setdefault(&b_info->u.hvm.spice.disable_ticketing,
                                     false);
            libxl_defbool_setdefault(&b_info->u.hvm.spice.agent_mouse, true);
            libxl_defbool_setdefault(&b_info->u.hvm.spice.vdagent, false);
            libxl_defbool_setdefault(&b_info->u.hvm.spice.clipboard_sharing,
                                     false);
        }

        libxl_defbool_setdefault(&b_info->u.hvm.nographic, false);

        libxl_defbool_setdefault(&b_info->u.hvm.gfx_passthru, false);

        break;
    case LIBXL_DOMAIN_TYPE_PV:
        libxl_defbool_setdefault(&b_info->u.pv.e820_host, false);
        if (b_info->video_memkb == LIBXL_MEMKB_DEFAULT)
            b_info->video_memkb = 0;
        if (b_info->shadow_memkb == LIBXL_MEMKB_DEFAULT)
            b_info->shadow_memkb = 0;
        if (b_info->u.pv.slack_memkb == LIBXL_MEMKB_DEFAULT)
            b_info->u.pv.slack_memkb = 0;

        /* For compatibility, fill in b_info->kernel|ramdisk|cmdline
         * with the value in u.pv, later processing will use
         * b_info->kernel|ramdisk|cmdline only.
         * User with old APIs that passes u.pv.kernel|ramdisk|cmdline
         * is not affected.
         */
        if (!b_info->kernel && b_info->u.pv.kernel) {
            b_info->kernel = b_info->u.pv.kernel;
            b_info->u.pv.kernel = NULL;
        }
        if (!b_info->ramdisk && b_info->u.pv.ramdisk) {
            b_info->ramdisk = b_info->u.pv.ramdisk;
            b_info->u.pv.ramdisk = NULL;
        }
        if (!b_info->cmdline && b_info->u.pv.cmdline) {
            b_info->cmdline = b_info->u.pv.cmdline;
            b_info->u.pv.cmdline = NULL;
        }
        break;
    default:
        LIBXL__LOG(CTX, LIBXL__LOG_ERROR,
                   "invalid domain type %s in create info",
                   libxl_domain_type_to_string(b_info->type));
        return ERROR_INVAL;
    }
    return 0;
}

static void init_console_info(libxl__gc *gc,
                             libxl__device_console *console,
                             int dev_num)
{
    libxl__device_console_init(console);
    console->devid = dev_num;
    console->consback = LIBXL__CONSOLE_BACKEND_XENCONSOLED;
    console->output = libxl__strdup(NOGC, "pty");
    /* console->{name,connection,path} are NULL on normal consoles.
       Only 'channels' when mapped to consoles have a string name. */
}

int libxl__domain_build(libxl__gc *gc,
                        libxl_domain_config *d_config,
                        uint32_t domid,
                        libxl__domain_build_state *state)
{
    libxl_domain_build_info *const info = &d_config->b_info;
    char **vments = NULL, **localents = NULL;
    struct timeval start_time;
    int i, ret;

    ret = libxl__build_pre(gc, domid, d_config, state);
    if (ret)
        goto out;

    gettimeofday(&start_time, NULL);

    switch (info->type) {
    case LIBXL_DOMAIN_TYPE_HVM:
        ret = libxl__build_hvm(gc, domid, info, state);
        if (ret)
            goto out;

        vments = libxl__calloc(gc, 7, sizeof(char *));
        vments[0] = "rtc/timeoffset";
        vments[1] = (info->u.hvm.timeoffset) ? info->u.hvm.timeoffset : "";
        vments[2] = "image/ostype";
        vments[3] = "hvm";
        vments[4] = "start_time";
        vments[5] = libxl__sprintf(gc, "%lu.%02d", start_time.tv_sec,(int)start_time.tv_usec/10000);

        localents = libxl__calloc(gc, 9, sizeof(char *));
        i = 0;
        localents[i++] = "platform/acpi";
        localents[i++] = libxl_defbool_val(info->u.hvm.acpi) ? "1" : "0";
        localents[i++] = "platform/acpi_s3";
        localents[i++] = libxl_defbool_val(info->u.hvm.acpi_s3) ? "1" : "0";
        localents[i++] = "platform/acpi_s4";
        localents[i++] = libxl_defbool_val(info->u.hvm.acpi_s4) ? "1" : "0";
        if (info->u.hvm.mmio_hole_memkb) {
            uint64_t max_ram_below_4g =
                (1ULL << 32) - (info->u.hvm.mmio_hole_memkb << 10);

            if (max_ram_below_4g <= HVM_BELOW_4G_MMIO_START) {
                localents[i++] = "platform/mmio_hole_size";
                localents[i++] =
                    libxl__sprintf(gc, "%"PRIu64,
                                   info->u.hvm.mmio_hole_memkb << 10);
            }
        }

        break;
    case LIBXL_DOMAIN_TYPE_PV:
        state->pvh_enabled = libxl_defbool_val(d_config->c_info.pvh);

        ret = libxl__build_pv(gc, domid, info, state);
        if (ret)
            goto out;

        vments = libxl__calloc(gc, 11, sizeof(char *));
        i = 0;
        vments[i++] = "image/ostype";
        vments[i++] = "linux";
        vments[i++] = "image/kernel";
        vments[i++] = (char *) state->pv_kernel.path;
        vments[i++] = "start_time";
        vments[i++] = libxl__sprintf(gc, "%lu.%02d", start_time.tv_sec,(int)start_time.tv_usec/10000);
        if (state->pv_ramdisk.path) {
            vments[i++] = "image/ramdisk";
            vments[i++] = (char *) state->pv_ramdisk.path;
        }
        if (state->pv_cmdline) {
            vments[i++] = "image/cmdline";
            vments[i++] = (char *) state->pv_cmdline;
        }

        break;
    default:
        ret = ERROR_INVAL;
        goto out;
    }
    ret = libxl__build_post(gc, domid, info, state, vments, localents);
out:
    return ret;
}

int libxl__domain_make(libxl__gc *gc, libxl_domain_create_info *info,
                       uint32_t *domid)
{
    libxl_ctx *ctx = libxl__gc_owner(gc);
    int flags, ret, rc, nb_vm;
    char *uuid_string;
    char *dom_path, *vm_path, *libxl_path;
    struct xs_permissions roperm[2];
    struct xs_permissions rwperm[1];
    struct xs_permissions noperm[1];
    xs_transaction_t t = 0;
    xen_domain_handle_t handle;
    libxl_vminfo *vm_list;


    assert(!libxl_domid_valid_guest(*domid));

    uuid_string = libxl__uuid2string(gc, info->uuid);
    if (!uuid_string) {
        rc = ERROR_NOMEM;
        goto out;
    }

    flags = 0;
    if (info->type == LIBXL_DOMAIN_TYPE_HVM) {
        flags |= XEN_DOMCTL_CDF_hvm_guest;
        flags |= libxl_defbool_val(info->hap) ? XEN_DOMCTL_CDF_hap : 0;
        flags |= libxl_defbool_val(info->oos) ? 0 : XEN_DOMCTL_CDF_oos_off;
    } else if (libxl_defbool_val(info->pvh)) {
        flags |= XEN_DOMCTL_CDF_pvh_guest;
        if (!libxl_defbool_val(info->hap)) {
            LOG(ERROR, "HAP must be on for PVH");
            rc = ERROR_INVAL;
            goto out;
        }
        flags |= XEN_DOMCTL_CDF_hap;
    }
    *domid = -1;

    /* Ultimately, handle is an array of 16 uint8_t, same as uuid */
    libxl_uuid_copy(ctx, (libxl_uuid *)handle, &info->uuid);

    ret = xc_domain_create(ctx->xch, info->ssidref, handle, flags, domid);
    if (ret < 0) {
        LIBXL__LOG_ERRNO(ctx, LIBXL__LOG_ERROR, "domain creation fail");
        rc = ERROR_FAIL;
        goto out;
    }

    ret = xc_cpupool_movedomain(ctx->xch, info->poolid, *domid);
    if (ret < 0) {
        LIBXL__LOG_ERRNO(ctx, LIBXL__LOG_ERROR, "domain move fail");
        rc = ERROR_FAIL;
        goto out;
    }

    dom_path = libxl__xs_get_dompath(gc, *domid);
    if (!dom_path) {
        rc = ERROR_FAIL;
        goto out;
    }

    vm_path = libxl__sprintf(gc, "/vm/%s", uuid_string);
    if (!vm_path) {
        LIBXL__LOG(ctx, LIBXL__LOG_ERROR, "cannot allocate create paths");
        rc = ERROR_FAIL;
        goto out;
    }

    libxl_path = libxl__xs_libxl_path(gc, *domid);
    if (!libxl_path) {
        rc = ERROR_FAIL;
        goto out;
    }

    noperm[0].id = 0;
    noperm[0].perms = XS_PERM_NONE;

    roperm[0].id = 0;
    roperm[0].perms = XS_PERM_NONE;
    roperm[1].id = *domid;
    roperm[1].perms = XS_PERM_READ;

    rwperm[0].id = *domid;
    rwperm[0].perms = XS_PERM_NONE;

retry_transaction:
    t = xs_transaction_start(ctx->xsh);

    xs_rm(ctx->xsh, t, dom_path);
    libxl__xs_mkdir(gc, t, dom_path, roperm, ARRAY_SIZE(roperm));

    xs_rm(ctx->xsh, t, vm_path);
    libxl__xs_mkdir(gc, t, vm_path, roperm, ARRAY_SIZE(roperm));

    xs_rm(ctx->xsh, t, libxl_path);
    libxl__xs_mkdir(gc, t, libxl_path, noperm, ARRAY_SIZE(noperm));

    xs_write(ctx->xsh, t, libxl__sprintf(gc, "%s/vm", dom_path), vm_path, strlen(vm_path));
    rc = libxl__domain_rename(gc, *domid, 0, info->name, t);
    if (rc)
        goto out;

    libxl__xs_mkdir(gc, t,
                    libxl__sprintf(gc, "%s/cpu", dom_path),
                    roperm, ARRAY_SIZE(roperm));
    libxl__xs_mkdir(gc, t,
                    libxl__sprintf(gc, "%s/memory", dom_path),
                    roperm, ARRAY_SIZE(roperm));
    libxl__xs_mkdir(gc, t,
                    libxl__sprintf(gc, "%s/device", dom_path),
                    roperm, ARRAY_SIZE(roperm));
    libxl__xs_mkdir(gc, t,
                    libxl__sprintf(gc, "%s/control", dom_path),
                    roperm, ARRAY_SIZE(roperm));
    if (info->type == LIBXL_DOMAIN_TYPE_HVM)
        libxl__xs_mkdir(gc, t,
                        libxl__sprintf(gc, "%s/hvmloader", dom_path),
                        roperm, ARRAY_SIZE(roperm));

    libxl__xs_mkdir(gc, t,
                    libxl__sprintf(gc, "%s/control/shutdown", dom_path),
                    rwperm, ARRAY_SIZE(rwperm));
    libxl__xs_mkdir(gc, t,
                    libxl__sprintf(gc, "%s/device/suspend/event-channel", dom_path),
                    rwperm, ARRAY_SIZE(rwperm));
    libxl__xs_mkdir(gc, t,
                    libxl__sprintf(gc, "%s/data", dom_path),
                    rwperm, ARRAY_SIZE(rwperm));

    if (libxl_defbool_val(info->driver_domain)) {
        /*
         * Create a local "libxl" directory for each guest, since we might want
         * to use libxl from inside the guest
         */
        libxl__xs_mkdir(gc, t, GCSPRINTF("%s/libxl", dom_path), rwperm,
                        ARRAY_SIZE(rwperm));
        /*
         * Create a local "device-model" directory for each guest, since we
         * might want to use Qemu from inside the guest
         */
        libxl__xs_mkdir(gc, t, GCSPRINTF("%s/device-model", dom_path), rwperm,
                        ARRAY_SIZE(rwperm));
    }

    vm_list = libxl_list_vm(ctx, &nb_vm);
    if (!vm_list) {
        LOG(ERROR, "cannot get number of running guests");
        rc = ERROR_FAIL;
        goto out;
    }
    libxl_vminfo_list_free(vm_list, nb_vm);
    int hotplug_setting = libxl__hotplug_settings(gc, t);
    if (hotplug_setting < 0) {
        LOG(ERROR, "unable to get current hotplug scripts execution setting");
        rc = ERROR_FAIL;
        goto out;
    }
    if (libxl_defbool_val(info->run_hotplug_scripts) != hotplug_setting &&
        (nb_vm - 1)) {
        LOG(ERROR, "cannot change hotplug execution option once set, "
                    "please shutdown all guests before changing it");
        rc = ERROR_FAIL;
        goto out;
    }

    if (libxl_defbool_val(info->run_hotplug_scripts)) {
        rc = libxl__xs_write_checked(gc, t, DISABLE_UDEV_PATH, "1");
        if (rc) {
            LOGE(ERROR, "unable to write %s = 1", DISABLE_UDEV_PATH);
            goto out;
        }
    } else {
        rc = libxl__xs_rm_checked(gc, t, DISABLE_UDEV_PATH);
        if (rc) {
            LOGE(ERROR, "unable to delete %s", DISABLE_UDEV_PATH);
            goto out;
        }
    }

    xs_write(ctx->xsh, t, libxl__sprintf(gc, "%s/uuid", vm_path), uuid_string, strlen(uuid_string));
    xs_write(ctx->xsh, t, libxl__sprintf(gc, "%s/name", vm_path), info->name, strlen(info->name));

    libxl__xs_writev(gc, t, dom_path, info->xsdata);
    libxl__xs_writev(gc, t, libxl__sprintf(gc, "%s/platform", dom_path), info->platformdata);

    xs_write(ctx->xsh, t, libxl__sprintf(gc, "%s/control/platform-feature-multiprocessor-suspend", dom_path), "1", 1);
    xs_write(ctx->xsh, t, libxl__sprintf(gc, "%s/control/platform-feature-xs_reset_watches", dom_path), "1", 1);
    if (!xs_transaction_end(ctx->xsh, t, 0)) {
        if (errno == EAGAIN) {
            t = 0;
            goto retry_transaction;
        }
        LIBXL__LOG_ERRNO(ctx, LIBXL__LOG_ERROR, "domain creation "
                         "xenstore transaction commit failed");
        rc = ERROR_FAIL;
        goto out;
    }
    t = 0;

    rc = 0;
 out:
    if (t) xs_transaction_end(ctx->xsh, t, 1);
    return rc;
}

static int store_libxl_entry(libxl__gc *gc, uint32_t domid,
                             libxl_domain_build_info *b_info)
{
    char *path = NULL;

    path = libxl__xs_libxl_path(gc, domid);
    path = libxl__sprintf(gc, "%s/dm-version", path);
    return libxl__xs_write(gc, XBT_NULL, path, "%s",
        libxl_device_model_version_to_string(b_info->device_model_version));
}

/*----- main domain creation -----*/

/* We have a linear control flow; only one event callback is
 * outstanding at any time.  Each initiation and callback function
 * arranges for the next to be called, as the very last thing it
 * does.  (If that particular sub-operation is not needed, a
 * function will call the next event callback directly.)
 */

/* Event callbacks, in this order: */
static void domcreate_devmodel_started(libxl__egc *egc,
                                       libxl__dm_spawn_state *dmss,
                                       int rc);
static void domcreate_bootloader_console_available(libxl__egc *egc,
                                                   libxl__bootloader_state *bl);
static void domcreate_bootloader_done(libxl__egc *egc,
                                      libxl__bootloader_state *bl,
                                      int rc);

static void domcreate_launch_dm(libxl__egc *egc, libxl__multidev *aodevs,
                                int ret);

static void domcreate_attach_vtpms(libxl__egc *egc, libxl__multidev *multidev,
                                   int ret);
static void domcreate_attach_pci(libxl__egc *egc, libxl__multidev *aodevs,
                                 int ret);

static void domcreate_console_available(libxl__egc *egc,
                                        libxl__domain_create_state *dcs);

static void domcreate_rebuild_done(libxl__egc *egc,
                                   libxl__domain_create_state *dcs,
                                   int ret);

/* Our own function to clean up and call the user's callback.
 * The final call in the sequence. */
static void domcreate_complete(libxl__egc *egc,
                               libxl__domain_create_state *dcs,
                               int rc);

/* If creation is not successful, this callback will be executed
 * when domain destruction is finished */
static void domcreate_destruction_cb(libxl__egc *egc,
                                     libxl__domain_destroy_state *dds,
                                     int rc);

static void initiate_domain_create(libxl__egc *egc,
                                   libxl__domain_create_state *dcs)
{
    STATE_AO_GC(dcs->ao);
    libxl_ctx *ctx = libxl__gc_owner(gc);
    uint32_t domid;
    int i, ret;
    size_t last_devid = -1;
    bool pod_enabled = false;

    /* convenience aliases */
    libxl_domain_config *const d_config = dcs->guest_config;
    const int restore_fd = dcs->restore_fd;
    memset(&dcs->build_state, 0, sizeof(dcs->build_state));

    domid = 0;

    if (d_config->c_info.ssid_label) {
        char *s = d_config->c_info.ssid_label;
        ret = libxl_flask_context_to_sid(ctx, s, strlen(s),
                                         &d_config->c_info.ssidref);
        if (ret) {
            if (errno == ENOSYS) {
                LOG(WARN, "XSM Disabled: init_seclabel not supported");
                ret = 0;
            } else {
                LOG(ERROR, "Invalid init_seclabel: %s", s);
                goto error_out;
            }
        }
    }

    if (d_config->b_info.exec_ssid_label) {
        char *s = d_config->b_info.exec_ssid_label;
        ret = libxl_flask_context_to_sid(ctx, s, strlen(s),
                                         &d_config->b_info.exec_ssidref);
        if (ret) {
            if (errno == ENOSYS) {
                LOG(WARN, "XSM Disabled: seclabel not supported");
                ret = 0;
            } else {
                LOG(ERROR, "Invalid seclabel: %s", s);
                goto error_out;
            }
        }
    }

    if (d_config->b_info.device_model_ssid_label) {
        char *s = d_config->b_info.device_model_ssid_label;
        ret = libxl_flask_context_to_sid(ctx, s, strlen(s),
                                         &d_config->b_info.device_model_ssidref);
        if (ret) {
            if (errno == ENOSYS) {
                LOG(WARN,"XSM Disabled: device_model_stubdomain_seclabel not supported");
                ret = 0;
            } else {
                LOG(ERROR, "Invalid device_model_stubdomain_seclabel: %s", s);
                goto error_out;
            }
        }
    }

    if (d_config->c_info.pool_name) {
        d_config->c_info.poolid = -1;
        libxl_cpupool_qualifier_to_cpupoolid(ctx, d_config->c_info.pool_name,
                                             &d_config->c_info.poolid,
                                             NULL);
    }
    if (!libxl_cpupoolid_is_valid(ctx, d_config->c_info.poolid)) {
        LOG(ERROR, "Illegal pool specified: %s", d_config->c_info.pool_name);
        ret = ERROR_INVAL;
        goto error_out;
    }

    /* If target_memkb is smaller than max_memkb, the subsequent call
     * to libxc when building HVM domain will enable PoD mode.
     */
    pod_enabled = (d_config->c_info.type == LIBXL_DOMAIN_TYPE_HVM) &&
        (d_config->b_info.target_memkb < d_config->b_info.max_memkb);

    /* We cannot have PoD and PCI device assignment at the same time
     * for HVM guest. It was reported that IOMMU cannot work with PoD
     * enabled because it needs to populated entire page table for
     * guest. To stay on the safe side, we disable PCI device
     * assignment when PoD is enabled.
     */
    if (d_config->c_info.type == LIBXL_DOMAIN_TYPE_HVM &&
        d_config->num_pcidevs && pod_enabled) {
        ret = ERROR_INVAL;
        LOG(ERROR, "PCI device assignment for HVM guest failed due to PoD enabled");
        goto error_out;
    }

    ret = libxl__domain_create_info_setdefault(gc, &d_config->c_info);
    if (ret) goto error_out;

    ret = libxl__domain_make(gc, &d_config->c_info, &domid);
    if (ret) {
        LIBXL__LOG(ctx, LIBXL__LOG_ERROR, "cannot make domain: %d", ret);
        dcs->guest_domid = domid;
        ret = ERROR_FAIL;
        goto error_out;
    }

    dcs->guest_domid = domid;
    dcs->dmss.dm.guest_domid = 0; /* means we haven't spawned */

    ret = libxl__domain_build_info_setdefault(gc, &d_config->b_info);
    if (ret) goto error_out;

    if (!sched_params_valid(gc, domid, &d_config->b_info.sched_params)) {
        LOG(ERROR, "Invalid scheduling parameters\n");
        ret = ERROR_INVAL;
        goto error_out;
    }

    for (i = 0; i < d_config->num_disks; i++) {
        ret = libxl__device_disk_setdefault(gc, &d_config->disks[i]);
        if (ret) goto error_out;
    }

    dcs->bl.ao = ao;
    libxl_device_disk *bootdisk =
        d_config->num_disks > 0 ? &d_config->disks[0] : NULL;

    /*
     * The devid has to be set before launching the device model. For the
     * hotplug case this is done in libxl_device_nic_add but on domain
     * creation this is called too late.
     * Make two runs over configured NICs in order to avoid duplicate IDs
     * in case the caller partially assigned IDs.
     */
    for (i = 0; i < d_config->num_nics; i++) {
        /* We have to init the nic here, because we still haven't
         * called libxl_device_nic_add when domcreate_launch_dm gets called,
         * but qemu needs the nic information to be complete.
         */
        ret = libxl__device_nic_setdefault(gc, &d_config->nics[i], domid);
        if (ret) goto error_out;

        if (d_config->nics[i].devid > last_devid)
            last_devid = d_config->nics[i].devid;
    }
    for (i = 0; i < d_config->num_nics; i++) {
        if (d_config->nics[i].devid < 0)
            d_config->nics[i].devid = ++last_devid;
    }

    if (restore_fd >= 0) {
        LOG(DEBUG, "restoring, not running bootloader");
        domcreate_bootloader_done(egc, &dcs->bl, 0);
    } else  {
        LOG(DEBUG, "running bootloader");
        dcs->bl.callback = domcreate_bootloader_done;
        dcs->bl.console_available = domcreate_bootloader_console_available;
        dcs->bl.info = &d_config->b_info;
        dcs->bl.disk = bootdisk;
        dcs->bl.domid = dcs->guest_domid;

        dcs->bl.kernel = &dcs->build_state.pv_kernel;
        dcs->bl.ramdisk = &dcs->build_state.pv_ramdisk;

        libxl__bootloader_run(egc, &dcs->bl);
    }
    return;

error_out:
    assert(ret);
    domcreate_complete(egc, dcs, ret);
}

static void domcreate_bootloader_console_available(libxl__egc *egc,
                                                   libxl__bootloader_state *bl)
{
    libxl__domain_create_state *dcs = CONTAINER_OF(bl, *dcs, bl);
    STATE_AO_GC(bl->ao);
    domcreate_console_available(egc, dcs);
}

static void domcreate_console_available(libxl__egc *egc,
                                        libxl__domain_create_state *dcs) {
    libxl__ao_progress_report(egc, dcs->ao, &dcs->aop_console_how,
                              NEW_EVENT(egc, DOMAIN_CREATE_CONSOLE_AVAILABLE,
                                        dcs->guest_domid,
                                        dcs->aop_console_how.for_event));
}

static void domcreate_bootloader_done(libxl__egc *egc,
                                      libxl__bootloader_state *bl,
                                      int rc)
{
    libxl__domain_create_state *dcs = CONTAINER_OF(bl, *dcs, bl);
    STATE_AO_GC(bl->ao);

    /* convenience aliases */
    const uint32_t domid = dcs->guest_domid;
    libxl_domain_config *const d_config = dcs->guest_config;
    libxl_domain_build_info *const info = &d_config->b_info;
    const int restore_fd = dcs->restore_fd;
    libxl__domain_build_state *const state = &dcs->build_state;
    libxl__srm_restore_autogen_callbacks *const callbacks =
        &dcs->shs.callbacks.restore.a;

    if (rc) {
        domcreate_rebuild_done(egc, dcs, rc);
        return;
    }

    /* consume bootloader outputs. state->pv_{kernel,ramdisk} have
     * been initialised by the bootloader already.
     */
    state->pv_cmdline = bl->cmdline;

    /* We might be going to call libxl__spawn_local_dm, or _spawn_stub_dm.
     * Fill in any field required by either, including both relevant
     * callbacks (_spawn_stub_dm will overwrite our trespass if needed). */
    dcs->dmss.dm.spawn.ao = ao;
    dcs->dmss.dm.guest_config = dcs->guest_config;
    dcs->dmss.dm.build_state = &dcs->build_state;
    dcs->dmss.dm.callback = domcreate_devmodel_started;
    dcs->dmss.callback = domcreate_devmodel_started;

    if ( restore_fd < 0 ) {
        rc = libxl__domain_build(gc, d_config, domid, state);
        domcreate_rebuild_done(egc, dcs, rc);
        return;
    }

    /* Restore */

    rc = libxl__build_pre(gc, domid, d_config, state);
    if (rc)
        goto out;

    /* read signature */
    int hvm, pae, superpages;
    switch (info->type) {
    case LIBXL_DOMAIN_TYPE_HVM:
        hvm = 1;
        superpages = 1;
        pae = libxl_defbool_val(info->u.hvm.pae);
        callbacks->toolstack_restore = libxl__toolstack_restore;
        break;
    case LIBXL_DOMAIN_TYPE_PV:
        hvm = 0;
        superpages = 0;
        pae = 1;
        break;
    default:
        rc = ERROR_INVAL;
        goto out;
    }
    libxl__xc_domain_restore(egc, dcs,
                             hvm, pae, superpages);
    return;

 out:
    libxl__xc_domain_restore_done(egc, dcs, rc, 0, 0);
}

void libxl__srm_callout_callback_restore_results(unsigned long store_mfn,
          unsigned long console_mfn, void *user)
{
    libxl__save_helper_state *shs = user;
    libxl__domain_create_state *dcs = CONTAINER_OF(shs, *dcs, shs);
    STATE_AO_GC(dcs->ao);
    libxl__domain_build_state *const state = &dcs->build_state;

    state->store_mfn =            store_mfn;
    state->console_mfn =          console_mfn;
    shs->need_results =           0;
}

void libxl__xc_domain_restore_done(libxl__egc *egc, void *dcs_void,
                                   int ret, int retval, int errnoval)
{
    libxl__domain_create_state *dcs = dcs_void;
    STATE_AO_GC(dcs->ao);
    libxl_ctx *ctx = libxl__gc_owner(gc);
    char **vments = NULL, **localents = NULL;
    struct timeval start_time;
    int i, esave;

    /* convenience aliases */
    const uint32_t domid = dcs->guest_domid;
    libxl_domain_config *const d_config = dcs->guest_config;
    libxl_domain_build_info *const info = &d_config->b_info;
    libxl__domain_build_state *const state = &dcs->build_state;
    const int fd = dcs->restore_fd;

    if (ret)
        goto out;

    if (retval) {
        LOGEV(ERROR, errnoval, "restoring domain");
        ret = ERROR_FAIL;
        goto out;
    }

    gettimeofday(&start_time, NULL);

    switch (info->type) {
    case LIBXL_DOMAIN_TYPE_HVM:
        vments = libxl__calloc(gc, 7, sizeof(char *));
        vments[0] = "rtc/timeoffset";
        vments[1] = (info->u.hvm.timeoffset) ? info->u.hvm.timeoffset : "";
        vments[2] = "image/ostype";
        vments[3] = "hvm";
        vments[4] = "start_time";
        vments[5] = libxl__sprintf(gc, "%lu.%02d", start_time.tv_sec,(int)start_time.tv_usec/10000);
        break;
    case LIBXL_DOMAIN_TYPE_PV:
        vments = libxl__calloc(gc, 11, sizeof(char *));
        i = 0;
        vments[i++] = "image/ostype";
        vments[i++] = "linux";
        vments[i++] = "image/kernel";
        vments[i++] = (char *) state->pv_kernel.path;
        vments[i++] = "start_time";
        vments[i++] = libxl__sprintf(gc, "%lu.%02d", start_time.tv_sec,(int)start_time.tv_usec/10000);
        if (state->pv_ramdisk.path) {
            vments[i++] = "image/ramdisk";
            vments[i++] = (char *) state->pv_ramdisk.path;
        }
        if (state->pv_cmdline) {
            vments[i++] = "image/cmdline";
            vments[i++] = (char *) state->pv_cmdline;
        }
        break;
    default:
        ret = ERROR_INVAL;
        goto out;
    }
    ret = libxl__build_post(gc, domid, info, state, vments, localents);
    if (ret)
        goto out;

    if (info->type == LIBXL_DOMAIN_TYPE_HVM) {
        state->saved_state = GCSPRINTF(
                       XC_DEVICE_MODEL_RESTORE_FILE".%d", domid);
    }

out:
    if (info->type == LIBXL_DOMAIN_TYPE_PV) {
        libxl__file_reference_unmap(&state->pv_kernel);
        libxl__file_reference_unmap(&state->pv_ramdisk);
    }

    esave = errno;
    libxl_fd_set_nonblock(ctx, fd, 0);
    errno = esave;
    domcreate_rebuild_done(egc, dcs, ret);
}

static void domcreate_rebuild_done(libxl__egc *egc,
                                   libxl__domain_create_state *dcs,
                                   int ret)
{
    STATE_AO_GC(dcs->ao);

    /* convenience aliases */
    const uint32_t domid = dcs->guest_domid;
    libxl_domain_config *const d_config = dcs->guest_config;
    libxl_ctx *const ctx = CTX;

    if (ret) {
        LIBXL__LOG(ctx, LIBXL__LOG_ERROR, "cannot (re-)build domain: %d", ret);
        ret = ERROR_FAIL;
        goto error_out;
    }

    store_libxl_entry(gc, domid, &d_config->b_info);

    libxl__multidev_begin(ao, &dcs->multidev);
    dcs->multidev.callback = domcreate_launch_dm;
    libxl__add_disks(egc, ao, domid, d_config, &dcs->multidev);
    libxl__multidev_prepared(egc, &dcs->multidev, 0);

    return;

 error_out:
    assert(ret);
    domcreate_complete(egc, dcs, ret);
}

static void domcreate_launch_dm(libxl__egc *egc, libxl__multidev *multidev,
                                int ret)
{
    libxl__domain_create_state *dcs = CONTAINER_OF(multidev, *dcs, multidev);
    STATE_AO_GC(dcs->ao);
    int i;

    /* convenience aliases */
    const uint32_t domid = dcs->guest_domid;
    libxl_domain_config *const d_config = dcs->guest_config;
    libxl__domain_build_state *const state = &dcs->build_state;

    if (ret) {
        LOG(ERROR, "unable to add disk devices");
        goto error_out;
    }

    for (i = 0; i < d_config->b_info.num_ioports; i++) {
        libxl_ioport_range *io = &d_config->b_info.ioports[i];

        LOG(DEBUG, "dom%d ioports %"PRIx32"-%"PRIx32,
            domid, io->first, io->first + io->number - 1);

        ret = xc_domain_ioport_permission(CTX->xch, domid,
                                          io->first, io->number, 1);
        if (ret < 0) {
            LOGE(ERROR,
                 "failed give dom%d access to ioports %"PRIx32"-%"PRIx32,
                 domid, io->first, io->first + io->number - 1);
            ret = ERROR_FAIL;
            goto error_out;
        }
    }

    for (i = 0; i < d_config->b_info.num_irqs; i++) {
        int irq = d_config->b_info.irqs[i];

        LOG(DEBUG, "dom%d irq %d", domid, irq);

        ret = irq >= 0 ? xc_physdev_map_pirq(CTX->xch, domid, irq, &irq)
                       : -EOVERFLOW;
        if (!ret)
            ret = xc_domain_irq_permission(CTX->xch, domid, irq, 1);
        if (ret < 0) {
            LOGE(ERROR, "failed give dom%d access to irq %d", domid, irq);
            ret = ERROR_FAIL;
            goto error_out;
        }
    }

    for (i = 0; i < d_config->b_info.num_iomem; i++) {
        libxl_iomem_range *io = &d_config->b_info.iomem[i];

        LOG(DEBUG, "dom%d iomem %"PRIx64"-%"PRIx64,
            domid, io->start, io->start + io->number - 1);

        ret = xc_domain_iomem_permission(CTX->xch, domid,
                                          io->start, io->number, 1);
        if (ret < 0) {
            LOGE(ERROR,
                 "failed give dom%d access to iomem range %"PRIx64"-%"PRIx64,
                 domid, io->start, io->start + io->number - 1);
            ret = ERROR_FAIL;
            goto error_out;
        }
        ret = xc_domain_memory_mapping(CTX->xch, domid,
                                       io->gfn, io->start,
                                       io->number, 1);
        if (ret < 0) {
            LOGE(ERROR,
                 "failed to map to dom%d iomem range %"PRIx64"-%"PRIx64
                 " to guest address %"PRIx64,
                 domid, io->start, io->start + io->number - 1, io->gfn);
            ret = ERROR_FAIL;
            goto error_out;
        }
    }

    /* For both HVM and PV the 0th console is a regular console. We
       map channels to IOEMU consoles starting at 1 */
    for (i = 0; i < d_config->num_channels; i++) {
        libxl__device_console console;
        libxl__device device;
        ret = libxl__init_console_from_channel(gc, &console, i + 1,
                                               &d_config->channels[i]);
        if ( ret ) {
            libxl__device_console_dispose(&console);
            goto error_out;
        }
        libxl__device_console_add(gc, domid, &console, NULL, &device);
        libxl__device_console_dispose(&console);
    }

    switch (d_config->c_info.type) {
    case LIBXL_DOMAIN_TYPE_HVM:
    {
        libxl__device_console console;
        libxl__device device;
        libxl_device_vkb vkb;

        init_console_info(gc, &console, 0);
        console.backend_domid = state->console_domid;
        libxl__device_console_add(gc, domid, &console, state, &device);
        libxl__device_console_dispose(&console);

        libxl_device_vkb_init(&vkb);
        libxl__device_vkb_add(gc, domid, &vkb);
        libxl_device_vkb_dispose(&vkb);

        dcs->dmss.dm.guest_domid = domid;
        if (libxl_defbool_val(d_config->b_info.device_model_stubdomain))
            libxl__spawn_stub_dm(egc, &dcs->dmss);
        else
            libxl__spawn_local_dm(egc, &dcs->dmss.dm);

        /*
         * Handle the domain's (and the related stubdomain's) access to
         * the VGA framebuffer.
         */
        ret = libxl__grant_vga_iomem_permission(gc, domid, d_config);
        if ( ret )
            goto error_out;

        return;
    }
    case LIBXL_DOMAIN_TYPE_PV:
    {
        int need_qemu = 0;
        libxl__device_console console;
        libxl__device device;

        for (i = 0; i < d_config->num_vfbs; i++) {
            libxl__device_vfb_add(gc, domid, &d_config->vfbs[i]);
            libxl__device_vkb_add(gc, domid, &d_config->vkbs[i]);
        }

        init_console_info(gc, &console, 0);

        need_qemu = libxl__need_xenpv_qemu(gc, 1, &console,
                d_config->num_vfbs, d_config->vfbs,
                d_config->num_disks, &d_config->disks[0],
                d_config->num_channels, &d_config->channels[0]);

        console.backend_domid = state->console_domid;
        libxl__device_console_add(gc, domid, &console, state, &device);
        libxl__device_console_dispose(&console);

        if (need_qemu) {
            dcs->dmss.dm.guest_domid = domid;
            libxl__spawn_local_dm(egc, &dcs->dmss.dm);
            return;
        } else {
            assert(!dcs->dmss.dm.guest_domid);
            domcreate_devmodel_started(egc, &dcs->dmss.dm, 0);
            return;
        }
    }
    default:
        ret = ERROR_INVAL;
        goto error_out;
    }
    abort(); /* not reached */

 error_out:
    assert(ret);
    domcreate_complete(egc, dcs, ret);
}

static void domcreate_devmodel_started(libxl__egc *egc,
                                       libxl__dm_spawn_state *dmss,
                                       int ret)
{
    libxl__domain_create_state *dcs = CONTAINER_OF(dmss, *dcs, dmss.dm);
    STATE_AO_GC(dmss->spawn.ao);
    libxl_ctx *ctx = CTX;
    int domid = dcs->guest_domid;

    /* convenience aliases */
    libxl_domain_config *const d_config = dcs->guest_config;

    if (ret) {
        LIBXL__LOG(ctx, LIBXL__LOG_ERROR,
                   "device model did not start: %d", ret);
        goto error_out;
    }

    if (dcs->dmss.dm.guest_domid) {
        if (d_config->b_info.device_model_version
            == LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN) {
            libxl__qmp_initializations(gc, domid, d_config);
        }
    }

    /* Plug nic interfaces */
    if (d_config->num_nics > 0) {
        /* Attach nics */
        libxl__multidev_begin(ao, &dcs->multidev);
        dcs->multidev.callback = domcreate_attach_vtpms;
        libxl__add_nics(egc, ao, domid, d_config, &dcs->multidev);
        libxl__multidev_prepared(egc, &dcs->multidev, 0);
        return;
    }

    domcreate_attach_vtpms(egc, &dcs->multidev, 0);
    return;

error_out:
    assert(ret);
    domcreate_complete(egc, dcs, ret);
}

static void domcreate_attach_vtpms(libxl__egc *egc,
                                   libxl__multidev *multidev,
                                   int ret)
{
   libxl__domain_create_state *dcs = CONTAINER_OF(multidev, *dcs, multidev);
   STATE_AO_GC(dcs->ao);
   int domid = dcs->guest_domid;

   libxl_domain_config* const d_config = dcs->guest_config;

   if(ret) {
       LOG(ERROR, "unable to add nic devices");
       goto error_out;
   }

    /* Plug vtpm devices */
   if (d_config->num_vtpms > 0) {
       /* Attach vtpms */
       libxl__multidev_begin(ao, &dcs->multidev);
       dcs->multidev.callback = domcreate_attach_pci;
       libxl__add_vtpms(egc, ao, domid, d_config, &dcs->multidev);
       libxl__multidev_prepared(egc, &dcs->multidev, 0);
       return;
   }

   domcreate_attach_pci(egc, multidev, 0);
   return;

error_out:
   assert(ret);
   domcreate_complete(egc, dcs, ret);
}

static void domcreate_attach_pci(libxl__egc *egc, libxl__multidev *multidev,
                                 int ret)
{
    libxl__domain_create_state *dcs = CONTAINER_OF(multidev, *dcs, multidev);
    STATE_AO_GC(dcs->ao);
    int i;
    libxl_ctx *ctx = CTX;
    int domid = dcs->guest_domid;

    /* convenience aliases */
    libxl_domain_config *const d_config = dcs->guest_config;

    if (ret) {
        LOG(ERROR, "unable to add vtpm devices");
        goto error_out;
    }

    for (i = 0; i < d_config->num_pcidevs; i++) {
        ret = libxl__device_pci_add(gc, domid, &d_config->pcidevs[i], 1);
        if (ret < 0) {
            LIBXL__LOG(ctx, LIBXL__LOG_ERROR,
                       "libxl_device_pci_add failed: %d", ret);
            goto error_out;
        }
    }

    if (d_config->num_pcidevs > 0) {
        ret = libxl__create_pci_backend(gc, domid, d_config->pcidevs,
            d_config->num_pcidevs);
        if (ret < 0) {
            LIBXL__LOG(ctx, LIBXL__LOG_ERROR,
                "libxl_create_pci_backend failed: %d", ret);
            goto error_out;
        }
    }

    domcreate_console_available(egc, dcs);

    domcreate_complete(egc, dcs, 0);
    return;

error_out:
    assert(ret);
    domcreate_complete(egc, dcs, ret);
}

static void domcreate_complete(libxl__egc *egc,
                               libxl__domain_create_state *dcs,
                               int rc)
{
    STATE_AO_GC(dcs->ao);
    libxl_domain_config *const d_config = dcs->guest_config;
    libxl_domain_config *d_config_saved = &dcs->guest_config_saved;

    if (!rc && d_config->b_info.exec_ssidref)
        rc = xc_flask_relabel_domain(CTX->xch, dcs->guest_domid, d_config->b_info.exec_ssidref);

    if (!rc) {
        libxl__domain_userdata_lock *lock;

        /* Note that we hold CTX lock at this point so only need to
         * take data store lock
         */
        lock = libxl__lock_domain_userdata(gc, dcs->guest_domid);
        if (!lock) {
            rc = ERROR_LOCK_FAIL;
        } else {
            libxl__update_domain_configuration(gc, d_config_saved, d_config);
            rc = libxl__set_domain_configuration(gc, dcs->guest_domid,
                                                 d_config_saved);
            libxl__unlock_domain_userdata(lock);
        }
    }

    libxl_domain_config_dispose(d_config_saved);

    if (rc) {
        if (dcs->guest_domid) {
            dcs->dds.ao = ao;
            dcs->dds.domid = dcs->guest_domid;
            dcs->dds.callback = domcreate_destruction_cb;
            libxl__domain_destroy(egc, &dcs->dds);
            return;
        }
        dcs->guest_domid = -1;
    }
    dcs->callback(egc, dcs, rc, dcs->guest_domid);
}

static void domcreate_destruction_cb(libxl__egc *egc,
                                     libxl__domain_destroy_state *dds,
                                     int rc)
{
    STATE_AO_GC(dds->ao);
    libxl__domain_create_state *dcs = CONTAINER_OF(dds, *dcs, dds);

    if (rc)
        LOG(ERROR, "unable to destroy domain %u following failed creation",
                   dds->domid);

    dcs->callback(egc, dcs, ERROR_FAIL, dcs->guest_domid);
}

/*----- application-facing domain creation interface -----*/

typedef struct {
    libxl__domain_create_state dcs;
    uint32_t *domid_out;
} libxl__app_domain_create_state;

static void domain_create_cb(libxl__egc *egc,
                             libxl__domain_create_state *dcs,
                             int rc, uint32_t domid);

static int do_domain_create(libxl_ctx *ctx, libxl_domain_config *d_config,
                            uint32_t *domid,
                            int restore_fd, int checkpointed_stream,
                            const libxl_asyncop_how *ao_how,
                            const libxl_asyncprogress_how *aop_console_how)
{
    AO_CREATE(ctx, 0, ao_how);
    libxl__app_domain_create_state *cdcs;

    GCNEW(cdcs);
    cdcs->dcs.ao = ao;
    cdcs->dcs.guest_config = d_config;
    libxl_domain_config_init(&cdcs->dcs.guest_config_saved);
    libxl_domain_config_copy(ctx, &cdcs->dcs.guest_config_saved, d_config);
    cdcs->dcs.restore_fd = restore_fd;
    cdcs->dcs.callback = domain_create_cb;
    cdcs->dcs.checkpointed_stream = checkpointed_stream;
    libxl__ao_progress_gethow(&cdcs->dcs.aop_console_how, aop_console_how);
    cdcs->domid_out = domid;

    initiate_domain_create(egc, &cdcs->dcs);

    return AO_INPROGRESS;
}

static void domain_create_cb(libxl__egc *egc,
                             libxl__domain_create_state *dcs,
                             int rc, uint32_t domid)
{
    libxl__app_domain_create_state *cdcs = CONTAINER_OF(dcs, *cdcs, dcs);
    STATE_AO_GC(cdcs->dcs.ao);

    if (!rc)
        *cdcs->domid_out = domid;

    libxl__ao_complete(egc, ao, rc);
}
    
int libxl_domain_create_new(libxl_ctx *ctx, libxl_domain_config *d_config,
                            uint32_t *domid,
                            const libxl_asyncop_how *ao_how,
                            const libxl_asyncprogress_how *aop_console_how)
{
    return do_domain_create(ctx, d_config, domid, -1, 0,
                            ao_how, aop_console_how);
}

int libxl_domain_create_restore(libxl_ctx *ctx, libxl_domain_config *d_config,
                                uint32_t *domid, int restore_fd,
                                const libxl_domain_restore_params *params,
                                const libxl_asyncop_how *ao_how,
                                const libxl_asyncprogress_how *aop_console_how)
{
    return do_domain_create(ctx, d_config, domid, restore_fd,
                            params->checkpointed_stream, ao_how, aop_console_how);
}

/*
 * Local variables:
 * mode: C
 * c-basic-offset: 4
 * indent-tabs-mode: nil
 * End:
 */
