/*
 * snapshot_conf.c: domain snapshot XML processing
 *
 * Copyright (C) 2006-2019 Red Hat, Inc.
 * Copyright (C) 2006-2008 Daniel P. Berrange
 *
 * This library 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; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

#include "configmake.h"
#include "internal.h"
#include "virbitmap.h"
#include "virbuffer.h"
#include "datatypes.h"
#include "domain_conf.h"
#include "virlog.h"
#include "viralloc.h"
#include "netdev_bandwidth_conf.h"
#include "netdev_vport_profile_conf.h"
#include "nwfilter_conf.h"
#include "secret_conf.h"
#include "snapshot_conf.h"
#include "virstoragefile.h"
#include "viruuid.h"
#include "virfile.h"
#include "virerror.h"
#include "virxml.h"
#include "virstring.h"
#include "virdomainsnapshotobjlist.h"

#define LIBVIRT_SNAPSHOT_CONF_PRIV_H_ALLOW
#include "snapshot_conf_priv.h"

#define VIR_FROM_THIS VIR_FROM_DOMAIN_SNAPSHOT

VIR_LOG_INIT("conf.snapshot_conf");

static virClassPtr virDomainSnapshotDefClass;
static void virDomainSnapshotDefDispose(void *obj);

static int
virDomainSnapshotOnceInit(void)
{
    if (!VIR_CLASS_NEW(virDomainSnapshotDef, virClassForDomainMomentDef()))
        return -1;

    return 0;
}

VIR_ONCE_GLOBAL_INIT(virDomainSnapshot);

VIR_ENUM_IMPL(virDomainSnapshotLocation,
              VIR_DOMAIN_SNAPSHOT_LOCATION_LAST,
              "default",
              "no",
              "internal",
              "external",
);

/* virDomainSnapshotState is really virDomainState plus one extra state */
VIR_ENUM_IMPL(virDomainSnapshotState,
              VIR_DOMAIN_SNAPSHOT_LAST,
              "nostate",
              "running",
              "blocked",
              "paused",
              "shutdown",
              "shutoff",
              "crashed",
              "pmsuspended",
              "disk-snapshot",
);

/* Snapshot Def functions */
static void
virDomainSnapshotDiskDefClear(virDomainSnapshotDiskDefPtr disk)
{
    VIR_FREE(disk->name);
    virObjectUnref(disk->src);
    disk->src = NULL;
}

void
virDomainSnapshotDiskDefFree(virDomainSnapshotDiskDefPtr disk)
{
    if (!disk)
        return;

    virDomainSnapshotDiskDefClear(disk);
    VIR_FREE(disk);
}


/* Allocate a new virDomainSnapshotDef; free with virObjectUnref() */
virDomainSnapshotDefPtr
virDomainSnapshotDefNew(void)
{
    if (virDomainSnapshotInitialize() < 0)
        return NULL;

    return virObjectNew(virDomainSnapshotDefClass);
}

static void
virDomainSnapshotDefDispose(void *obj)
{
    virDomainSnapshotDefPtr def = obj;
    size_t i;

    VIR_FREE(def->file);
    for (i = 0; i < def->ndisks; i++)
        virDomainSnapshotDiskDefClear(&def->disks[i]);
    VIR_FREE(def->disks);
    virObjectUnref(def->cookie);
}

int
virDomainSnapshotDiskDefParseXML(xmlNodePtr node,
                                 xmlXPathContextPtr ctxt,
                                 virDomainSnapshotDiskDefPtr def,
                                 unsigned int flags,
                                 virDomainXMLOptionPtr xmlopt)
{
    int ret = -1;
    char *snapshot = NULL;
    char *type = NULL;
    char *driver = NULL;
    xmlNodePtr cur;
    VIR_XPATH_NODE_AUTORESTORE(ctxt);

    ctxt->node = node;

    if (!(def->src = virStorageSourceNew()))
        goto cleanup;

    def->name = virXMLPropString(node, "name");
    if (!def->name) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("missing name from disk snapshot element"));
        goto cleanup;
    }

    snapshot = virXMLPropString(node, "snapshot");
    if (snapshot) {
        def->snapshot = virDomainSnapshotLocationTypeFromString(snapshot);
        if (def->snapshot <= 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("unknown disk snapshot setting '%s'"),
                           snapshot);
            goto cleanup;
        }
    }

    if ((type = virXMLPropString(node, "type"))) {
        if ((def->src->type = virStorageTypeFromString(type)) <= 0 ||
            def->src->type == VIR_STORAGE_TYPE_VOLUME ||
            def->src->type == VIR_STORAGE_TYPE_DIR) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("unknown disk snapshot type '%s'"), type);
            goto cleanup;
        }
    } else {
        def->src->type = VIR_STORAGE_TYPE_FILE;
    }

    if ((cur = virXPathNode("./source", ctxt)) &&
        virDomainStorageSourceParse(cur, ctxt, def->src, flags, xmlopt) < 0)
        goto cleanup;

    if ((driver = virXPathString("string(./driver/@type)", ctxt)) &&
        (def->src->format = virStorageFileFormatTypeFromString(driver)) <= 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("unknown disk snapshot driver '%s'"), driver);
            goto cleanup;
    }

    /* validate that the passed path is absolute */
    if (virStorageSourceIsRelative(def->src)) {
        virReportError(VIR_ERR_XML_ERROR,
                       _("disk snapshot image path '%s' must be absolute"),
                       def->src->path);
        goto cleanup;
    }

    if (!def->snapshot && (def->src->path || def->src->format))
        def->snapshot = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;

    ret = 0;
 cleanup:

    VIR_FREE(driver);
    VIR_FREE(snapshot);
    VIR_FREE(type);
    if (ret < 0)
        virDomainSnapshotDiskDefClear(def);
    return ret;
}

/* flags is bitwise-or of virDomainSnapshotParseFlags.
 * If flags does not include
 * VIR_DOMAIN_SNAPSHOT_PARSE_INTERNAL, then current is ignored.
 */
static virDomainSnapshotDefPtr
virDomainSnapshotDefParse(xmlXPathContextPtr ctxt,
                          virDomainXMLOptionPtr xmlopt,
                          void *parseOpaque,
                          bool *current,
                          unsigned int flags)
{
    virDomainSnapshotDefPtr def = NULL;
    virDomainSnapshotDefPtr ret = NULL;
    xmlNodePtr *nodes = NULL;
    xmlNodePtr inactiveDomNode = NULL;
    size_t i;
    int n;
    char *state = NULL;
    int active;
    char *tmp;
    char *memorySnapshot = NULL;
    char *memoryFile = NULL;
    bool offline = !!(flags & VIR_DOMAIN_SNAPSHOT_PARSE_OFFLINE);
    virSaveCookieCallbacksPtr saveCookie = virDomainXMLOptionGetSaveCookie(xmlopt);
    int domainflags = VIR_DOMAIN_DEF_PARSE_INACTIVE |
                      VIR_DOMAIN_DEF_PARSE_SKIP_VALIDATE;

    if (!(def = virDomainSnapshotDefNew()))
        return NULL;

    def->parent.name = virXPathString("string(./name)", ctxt);
    if (def->parent.name == NULL) {
        if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE) {
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("a redefined snapshot must have a name"));
            goto cleanup;
        }
    }

    def->parent.description = virXPathString("string(./description)", ctxt);

    if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE) {
        if (virXPathLongLong("string(./creationTime)", ctxt,
                             &def->parent.creationTime) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("missing creationTime from existing snapshot"));
            goto cleanup;
        }

        def->parent.parent_name = virXPathString("string(./parent/name)", ctxt);

        state = virXPathString("string(./state)", ctxt);
        if (state == NULL) {
            /* there was no state in an existing snapshot; this
             * should never happen
             */
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("missing state from existing snapshot"));
            goto cleanup;
        }
        def->state = virDomainSnapshotStateTypeFromString(state);
        if (def->state <= 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("Invalid state '%s' in domain snapshot XML"),
                           state);
            goto cleanup;
        }
        offline = (def->state == VIR_DOMAIN_SNAPSHOT_SHUTOFF ||
                   def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT);

        /* Older snapshots were created with just <domain>/<uuid>, and
         * lack domain/@type.  In that case, leave dom NULL, and
         * clients will have to decide between best effort
         * initialization or outright failure.  */
        if ((tmp = virXPathString("string(./domain/@type)", ctxt))) {
            xmlNodePtr domainNode = virXPathNode("./domain", ctxt);

            VIR_FREE(tmp);
            if (!domainNode) {
                virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                               _("missing domain in snapshot"));
                goto cleanup;
            }
            def->parent.dom = virDomainDefParseNode(ctxt->node->doc, domainNode,
                                                    xmlopt, parseOpaque,
                                                    domainflags);
            if (!def->parent.dom)
                goto cleanup;
        } else {
            VIR_WARN("parsing older snapshot that lacks domain");
        }

        /* /inactiveDomain entry saves the config XML present in a running
         * VM. In case of absent, leave parent.inactiveDom NULL and use
         * parent.dom for config and live XML. */
        if ((inactiveDomNode = virXPathNode("./inactiveDomain", ctxt))) {
            def->parent.inactiveDom = virDomainDefParseNode(ctxt->node->doc, inactiveDomNode,
                                                            xmlopt, NULL, domainflags);
            if (!def->parent.inactiveDom)
                goto cleanup;
        }
    } else if (virDomainXMLOptionRunMomentPostParse(xmlopt, &def->parent) < 0) {
        goto cleanup;
    }

    memorySnapshot = virXPathString("string(./memory/@snapshot)", ctxt);
    memoryFile = virXPathString("string(./memory/@file)", ctxt);
    if (memorySnapshot) {
        def->memory = virDomainSnapshotLocationTypeFromString(memorySnapshot);
        if (def->memory <= 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("unknown memory snapshot setting '%s'"),
                           memorySnapshot);
            goto cleanup;
        }
        if (memoryFile &&
            def->memory != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
            virReportError(VIR_ERR_XML_ERROR,
                           _("memory filename '%s' requires external snapshot"),
                           memoryFile);
            goto cleanup;
        }
        if (!memoryFile &&
            def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
            virReportError(VIR_ERR_XML_ERROR, "%s",
                           _("external memory snapshots require a filename"));
            goto cleanup;
        }
    } else if (memoryFile) {
        def->memory = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
    } else if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_REDEFINE) {
        def->memory = (offline ?
                       VIR_DOMAIN_SNAPSHOT_LOCATION_NONE :
                       VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL);
    }
    if (offline && def->memory &&
        def->memory != VIR_DOMAIN_SNAPSHOT_LOCATION_NONE) {
        virReportError(VIR_ERR_XML_ERROR, "%s",
                       _("memory state cannot be saved with offline or "
                         "disk-only snapshot"));
        goto cleanup;
    }
    def->file = g_steal_pointer(&memoryFile);

    /* verify that memory path is absolute */
    if (def->file && def->file[0] != '/') {
        virReportError(VIR_ERR_XML_ERROR,
                       _("memory snapshot file path (%s) must be absolute"),
                       def->file);
        goto cleanup;
    }

    if ((n = virXPathNodeSet("./disks/*", ctxt, &nodes)) < 0)
        goto cleanup;
    if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_DISKS) {
        if (n && VIR_ALLOC_N(def->disks, n) < 0)
            goto cleanup;
        def->ndisks = n;
        for (i = 0; i < def->ndisks; i++) {
            if (virDomainSnapshotDiskDefParseXML(nodes[i], ctxt, &def->disks[i],
                                                 flags, xmlopt) < 0)
                goto cleanup;
        }
        VIR_FREE(nodes);
    } else if (n) {
        virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s",
                       _("unable to handle disk requests in snapshot"));
        goto cleanup;
    }

    if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_INTERNAL) {
        if (!current) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("internal parse requested with NULL current"));
            goto cleanup;
        }
        if (virXPathInt("string(./active)", ctxt, &active) < 0) {
            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                           _("Could not find 'active' element"));
            goto cleanup;
        }
        *current = active != 0;
    }

    if (!offline && virSaveCookieParse(ctxt, &def->cookie, saveCookie) < 0)
        goto cleanup;

    ret = g_steal_pointer(&def);

 cleanup:
    VIR_FREE(state);
    VIR_FREE(nodes);
    VIR_FREE(memorySnapshot);
    VIR_FREE(memoryFile);
    virObjectUnref(def);

    return ret;
}

virDomainSnapshotDefPtr
virDomainSnapshotDefParseNode(xmlDocPtr xml,
                              xmlNodePtr root,
                              virDomainXMLOptionPtr xmlopt,
                              void *parseOpaque,
                              bool *current,
                              unsigned int flags)
{
    g_autoptr(xmlXPathContext) ctxt = NULL;

    if (!virXMLNodeNameEqual(root, "domainsnapshot")) {
        virReportError(VIR_ERR_XML_ERROR, "%s", _("domainsnapshot"));
        return NULL;
    }

    if (flags & VIR_DOMAIN_SNAPSHOT_PARSE_VALIDATE) {
        g_autofree char *schema = NULL;

        schema = virFileFindResource("domainsnapshot.rng",
                                     abs_top_srcdir "/docs/schemas",
                                     PKGDATADIR "/schemas");
        if (!schema)
            return NULL;
        if (virXMLValidateAgainstSchema(schema, xml) < 0)
            return NULL;
    }

    if (!(ctxt = virXMLXPathContextNew(xml)))
        return NULL;

    ctxt->node = root;
    return virDomainSnapshotDefParse(ctxt, xmlopt, parseOpaque, current, flags);
}

virDomainSnapshotDefPtr
virDomainSnapshotDefParseString(const char *xmlStr,
                                virDomainXMLOptionPtr xmlopt,
                                void *parseOpaque,
                                bool *current,
                                unsigned int flags)
{
    virDomainSnapshotDefPtr ret = NULL;
    xmlDocPtr xml;
    int keepBlanksDefault = xmlKeepBlanksDefault(0);

    if ((xml = virXMLParse(NULL, xmlStr, _("(domain_snapshot)")))) {
        xmlKeepBlanksDefault(keepBlanksDefault);
        ret = virDomainSnapshotDefParseNode(xml, xmlDocGetRootElement(xml),
                                            xmlopt, parseOpaque,
                                            current, flags);
        xmlFreeDoc(xml);
    }
    xmlKeepBlanksDefault(keepBlanksDefault);

    return ret;
}


/* Perform sanity checking on a redefined snapshot definition. If
 * @other is non-NULL, this may include swapping def->parent.dom from other
 * into def. */
int
virDomainSnapshotRedefineValidate(virDomainSnapshotDefPtr def,
                                  const unsigned char *domain_uuid,
                                  virDomainMomentObjPtr other,
                                  virDomainXMLOptionPtr xmlopt,
                                  unsigned int flags)
{
    int align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_INTERNAL;
    bool align_match = true;
    bool external = def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT ||
        virDomainSnapshotDefIsExternal(def);

    if ((flags & VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY) && !external) {
        virReportError(VIR_ERR_INVALID_ARG,
                       _("disk-only flag for snapshot %s requires "
                         "disk-snapshot state"),
                       def->parent.name);
        return -1;
    }
    if (def->parent.dom && memcmp(def->parent.dom->uuid, domain_uuid,
                                  VIR_UUID_BUFLEN)) {
        char uuidstr[VIR_UUID_STRING_BUFLEN];

        virUUIDFormat(domain_uuid, uuidstr);
        virReportError(VIR_ERR_INVALID_ARG,
                       _("definition for snapshot %s must use uuid %s"),
                       def->parent.name, uuidstr);
        return -1;
    }

    if (other) {
        virDomainSnapshotDefPtr otherdef = virDomainSnapshotObjGetDef(other);

        if ((otherdef->state == VIR_DOMAIN_SNAPSHOT_RUNNING ||
             otherdef->state == VIR_DOMAIN_SNAPSHOT_PAUSED) !=
            (def->state == VIR_DOMAIN_SNAPSHOT_RUNNING ||
             def->state == VIR_DOMAIN_SNAPSHOT_PAUSED)) {
            virReportError(VIR_ERR_INVALID_ARG,
                           _("cannot change between online and offline "
                             "snapshot state in snapshot %s"),
                           def->parent.name);
            return -1;
        }

        if ((otherdef->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT) !=
            (def->state == VIR_DOMAIN_SNAPSHOT_DISK_SNAPSHOT)) {
            virReportError(VIR_ERR_INVALID_ARG,
                           _("cannot change between disk only and "
                             "full system in snapshot %s"),
                           def->parent.name);
            return -1;
        }

        if (otherdef->parent.dom) {
            if (def->parent.dom) {
                if (!virDomainDefCheckABIStability(otherdef->parent.dom,
                                                   def->parent.dom, xmlopt))
                    return -1;
            } else {
                /* Transfer the domain def */
                def->parent.dom = g_steal_pointer(&otherdef->parent.dom);
            }
        }
    }

    if (def->parent.dom) {
        if (external) {
            align_location = VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL;
            align_match = false;
        }
        if (virDomainSnapshotAlignDisks(def, align_location,
                                        align_match) < 0)
            return -1;
    }


    return 0;
}


/**
 * virDomainSnapshotDefAssignExternalNames:
 * @def: snapshot def object
 *
 * Generate default external file names for snapshot targets. Returns 0 on
 * success, -1 on error.
 */
static int
virDomainSnapshotDefAssignExternalNames(virDomainSnapshotDefPtr def)
{
    const char *origpath;
    char *tmppath;
    char *tmp;
    struct stat sb;
    size_t i;
    size_t j;

    for (i = 0; i < def->ndisks; i++) {
        virDomainSnapshotDiskDefPtr disk = &def->disks[i];

        if (disk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL ||
            disk->src->path)
            continue;

        if (disk->src->type != VIR_STORAGE_TYPE_FILE) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("cannot generate external snapshot name "
                             "for disk '%s' on a '%s' device"),
                           disk->name, virStorageTypeToString(disk->src->type));
            return -1;
        }

        if (!(origpath = virDomainDiskGetSource(def->parent.dom->disks[i]))) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("cannot generate external snapshot name "
                             "for disk '%s' without source"),
                           disk->name);
            return -1;
        }

        if (stat(origpath, &sb) < 0 || !S_ISREG(sb.st_mode)) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("source for disk '%s' is not a regular "
                             "file; refusing to generate external "
                             "snapshot name"),
                           disk->name);
            return -1;
        }

        tmppath = g_strdup(origpath);

        /* drop suffix of the file name */
        if ((tmp = strrchr(tmppath, '.')) && !strchr(tmp, '/'))
            *tmp = '\0';

        disk->src->path = g_strdup_printf("%s.%s", tmppath, def->parent.name);

        VIR_FREE(tmppath);

        /* verify that we didn't generate a duplicate name */
        for (j = 0; j < i; j++) {
            if (STREQ_NULLABLE(disk->src->path, def->disks[j].src->path)) {
                virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                               _("cannot generate external snapshot name for "
                                 "disk '%s': collision with disk '%s'"),
                               disk->name, def->disks[j].name);
                return -1;
            }
        }
    }

    return 0;
}


static int
virDomainSnapshotCompareDiskIndex(const void *a, const void *b)
{
    const virDomainSnapshotDiskDef *diska = a;
    const virDomainSnapshotDiskDef *diskb = b;

    /* Integer overflow shouldn't be a problem here.  */
    return diska->idx - diskb->idx;
}

/* Align def->disks to def->parent.dom.  Sort the list of def->disks,
 * filling in any missing disks or snapshot state defaults given by
 * the domain, with a fallback to a passed in default.  Convert paths
 * to disk targets for uniformity.  Issue an error and return -1 if
 * any def->disks[n]->name appears more than once or does not map to
 * dom->disks.  If require_match, also ensure that there is no
 * conflicting requests for both internal and external snapshots.  */
int
virDomainSnapshotAlignDisks(virDomainSnapshotDefPtr def,
                            int default_snapshot,
                            bool require_match)
{
    int ret = -1;
    virBitmapPtr map = NULL;
    size_t i;
    int ndisks;

    if (!def->parent.dom) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("missing domain in snapshot"));
        goto cleanup;
    }

    if (def->ndisks > def->parent.dom->ndisks) {
        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
                       _("too many disk snapshot requests for domain"));
        goto cleanup;
    }

    /* Unlikely to have a guest without disks but technically possible.  */
    if (!def->parent.dom->ndisks) {
        ret = 0;
        goto cleanup;
    }

    if (!(map = virBitmapNew(def->parent.dom->ndisks)))
        goto cleanup;

    /* Double check requested disks.  */
    for (i = 0; i < def->ndisks; i++) {
        virDomainSnapshotDiskDefPtr disk = &def->disks[i];
        int idx = virDomainDiskIndexByName(def->parent.dom, disk->name, false);
        int disk_snapshot;

        if (idx < 0) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("no disk named '%s'"), disk->name);
            goto cleanup;
        }

        if (virBitmapIsBitSet(map, idx)) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("disk '%s' specified twice"),
                           disk->name);
            goto cleanup;
        }
        ignore_value(virBitmapSetBit(map, idx));
        disk->idx = idx;

        disk_snapshot = def->parent.dom->disks[idx]->snapshot;
        if (!disk->snapshot) {
            if (disk_snapshot &&
                (!require_match ||
                 disk_snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE))
                disk->snapshot = disk_snapshot;
            else
                disk->snapshot = default_snapshot;
        } else if (require_match &&
                   disk->snapshot != default_snapshot &&
                   !(disk->snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE &&
                     disk_snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_NONE)) {
            const char *tmp;

            tmp = virDomainSnapshotLocationTypeToString(default_snapshot);
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("disk '%s' must use snapshot mode '%s'"),
                           disk->name, tmp);
            goto cleanup;
        }
        if (disk->src->path &&
            disk->snapshot != VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL) {
            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                           _("file '%s' for disk '%s' requires "
                             "use of external snapshot mode"),
                           disk->src->path, disk->name);
            goto cleanup;
        }
        if (STRNEQ(disk->name, def->parent.dom->disks[idx]->dst)) {
            VIR_FREE(disk->name);
            disk->name = g_strdup(def->parent.dom->disks[idx]->dst);
        }
    }

    /* Provide defaults for all remaining disks.  */
    ndisks = def->ndisks;
    if (VIR_EXPAND_N(def->disks, def->ndisks,
                     def->parent.dom->ndisks - def->ndisks) < 0)
        goto cleanup;

    for (i = 0; i < def->parent.dom->ndisks; i++) {
        virDomainSnapshotDiskDefPtr disk;

        if (virBitmapIsBitSet(map, i))
            continue;
        disk = &def->disks[ndisks++];
        if (!(disk->src = virStorageSourceNew()))
            goto cleanup;
        disk->name = g_strdup(def->parent.dom->disks[i]->dst);
        disk->idx = i;

        /* Don't snapshot empty drives */
        if (virStorageSourceIsEmpty(def->parent.dom->disks[i]->src))
            disk->snapshot = VIR_DOMAIN_SNAPSHOT_LOCATION_NONE;
        else
            disk->snapshot = def->parent.dom->disks[i]->snapshot;

        disk->src->type = VIR_STORAGE_TYPE_FILE;
        if (!disk->snapshot)
            disk->snapshot = default_snapshot;
    }

    qsort(&def->disks[0], def->ndisks, sizeof(def->disks[0]),
          virDomainSnapshotCompareDiskIndex);

    /* Generate default external file names for external snapshot locations */
    if (virDomainSnapshotDefAssignExternalNames(def) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    virBitmapFree(map);
    return ret;
}


/* Converts public VIR_DOMAIN_SNAPSHOT_XML_* into
 * VIR_DOMAIN_SNAPSHOT_FORMAT_* flags, and silently ignores any other
 * flags. */
unsigned int
virDomainSnapshotFormatConvertXMLFlags(unsigned int flags)
{
    unsigned int formatFlags = 0;

    if (flags & VIR_DOMAIN_SNAPSHOT_XML_SECURE)
        formatFlags |= VIR_DOMAIN_SNAPSHOT_FORMAT_SECURE;

    return formatFlags;
}


static int
virDomainSnapshotDiskDefFormat(virBufferPtr buf,
                               virDomainSnapshotDiskDefPtr disk,
                               virDomainXMLOptionPtr xmlopt)
{
    int type = disk->src->type;

    if (!disk->name)
        return 0;

    virBufferEscapeString(buf, "<disk name='%s'", disk->name);
    if (disk->snapshot > 0)
        virBufferAsprintf(buf, " snapshot='%s'",
                          virDomainSnapshotLocationTypeToString(disk->snapshot));

    if (!disk->src->path && disk->src->format == 0) {
        virBufferAddLit(buf, "/>\n");
        return 0;
    }

    virBufferAsprintf(buf, " type='%s'>\n", virStorageTypeToString(type));
    virBufferAdjustIndent(buf, 2);

    if (disk->src->format > 0)
        virBufferEscapeString(buf, "<driver type='%s'/>\n",
                              virStorageFileFormatTypeToString(disk->src->format));
    if (virDomainDiskSourceFormat(buf, disk->src, "source", 0, false, 0,
                                  false, false, xmlopt) < 0)
        return -1;

    virBufferAdjustIndent(buf, -2);
    virBufferAddLit(buf, "</disk>\n");
    return 0;
}


/* Append XML describing def into buf. Return 0 on success, or -1 on
 * failure with buf cleared. */
static int
virDomainSnapshotDefFormatInternal(virBufferPtr buf,
                                   const char *uuidstr,
                                   virDomainSnapshotDefPtr def,
                                   virDomainXMLOptionPtr xmlopt,
                                   unsigned int flags)
{
    size_t i;
    int domainflags = VIR_DOMAIN_DEF_FORMAT_INACTIVE;

    if (flags & VIR_DOMAIN_SNAPSHOT_FORMAT_SECURE)
        domainflags |= VIR_DOMAIN_DEF_FORMAT_SECURE;

    virBufferAddLit(buf, "<domainsnapshot>\n");
    virBufferAdjustIndent(buf, 2);

    virBufferEscapeString(buf, "<name>%s</name>\n", def->parent.name);
    if (def->parent.description)
        virBufferEscapeString(buf, "<description>%s</description>\n",
                              def->parent.description);
    if (def->state)
        virBufferAsprintf(buf, "<state>%s</state>\n",
                          virDomainSnapshotStateTypeToString(def->state));

    if (def->parent.parent_name) {
        virBufferAddLit(buf, "<parent>\n");
        virBufferAdjustIndent(buf, 2);
        virBufferEscapeString(buf, "<name>%s</name>\n",
                              def->parent.parent_name);
        virBufferAdjustIndent(buf, -2);
        virBufferAddLit(buf, "</parent>\n");
    }

    if (def->parent.creationTime)
        virBufferAsprintf(buf, "<creationTime>%lld</creationTime>\n",
                          def->parent.creationTime);

    if (def->memory) {
        virBufferAsprintf(buf, "<memory snapshot='%s'",
                          virDomainSnapshotLocationTypeToString(def->memory));
        virBufferEscapeString(buf, " file='%s'", def->file);
        virBufferAddLit(buf, "/>\n");
    }

    if (def->ndisks) {
        virBufferAddLit(buf, "<disks>\n");
        virBufferAdjustIndent(buf, 2);
        for (i = 0; i < def->ndisks; i++) {
            if (virDomainSnapshotDiskDefFormat(buf, &def->disks[i], xmlopt) < 0)
                return -1;
        }
        virBufferAdjustIndent(buf, -2);
        virBufferAddLit(buf, "</disks>\n");
    }

    if (def->parent.dom) {
        if (virDomainDefFormatInternal(def->parent.dom, xmlopt,
                                       buf, domainflags) < 0)
            return -1;
    } else if (uuidstr) {
        virBufferAddLit(buf, "<domain>\n");
        virBufferAdjustIndent(buf, 2);
        virBufferAsprintf(buf, "<uuid>%s</uuid>\n", uuidstr);
        virBufferAdjustIndent(buf, -2);
        virBufferAddLit(buf, "</domain>\n");
    }

    if (def->parent.inactiveDom) {
        if (virDomainDefFormatInternalSetRootName(def->parent.inactiveDom, xmlopt,
                                                  buf, "inactiveDomain",
                                                  domainflags) < 0)
            return -1;
    }

    if (virSaveCookieFormatBuf(buf, def->cookie,
                               virDomainXMLOptionGetSaveCookie(xmlopt)) < 0)
        return -1;

    if (flags & VIR_DOMAIN_SNAPSHOT_FORMAT_INTERNAL)
        virBufferAsprintf(buf, "<active>%d</active>\n",
                          !!(flags & VIR_DOMAIN_SNAPSHOT_FORMAT_CURRENT));

    virBufferAdjustIndent(buf, -2);
    virBufferAddLit(buf, "</domainsnapshot>\n");

    return 0;
}


char *
virDomainSnapshotDefFormat(const char *uuidstr,
                           virDomainSnapshotDefPtr def,
                           virDomainXMLOptionPtr xmlopt,
                           unsigned int flags)
{
    g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;

    virCheckFlags(VIR_DOMAIN_SNAPSHOT_FORMAT_SECURE |
                  VIR_DOMAIN_SNAPSHOT_FORMAT_INTERNAL |
                  VIR_DOMAIN_SNAPSHOT_FORMAT_CURRENT, NULL);
    if (virDomainSnapshotDefFormatInternal(&buf, uuidstr, def,
                                           xmlopt, flags) < 0)
        return NULL;

    return virBufferContentAndReset(&buf);
}


bool
virDomainSnapshotDefIsExternal(virDomainSnapshotDefPtr def)
{
    size_t i;

    if (def->memory == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
        return true;

    for (i = 0; i < def->ndisks; i++) {
        if (def->disks[i].snapshot == VIR_DOMAIN_SNAPSHOT_LOCATION_EXTERNAL)
            return true;
    }

    return false;
}

bool
virDomainSnapshotIsExternal(virDomainMomentObjPtr snap)
{
    virDomainSnapshotDefPtr def = virDomainSnapshotObjGetDef(snap);

    return virDomainSnapshotDefIsExternal(def);
}

int
virDomainSnapshotRedefinePrep(virDomainObjPtr vm,
                              virDomainSnapshotDefPtr *defptr,
                              virDomainMomentObjPtr *snap,
                              virDomainXMLOptionPtr xmlopt,
                              unsigned int flags)
{
    virDomainSnapshotDefPtr def = *defptr;
    virDomainMomentObjPtr other;
    virDomainSnapshotDefPtr otherdef = NULL;
    bool check_if_stolen;

    if (virDomainSnapshotCheckCycles(vm->snapshots, def, vm->def->name) < 0)
        return -1;

    other = virDomainSnapshotFindByName(vm->snapshots, def->parent.name);
    if (other)
        otherdef = virDomainSnapshotObjGetDef(other);
    check_if_stolen = other && otherdef->parent.dom;
    if (virDomainSnapshotRedefineValidate(def, vm->def->uuid, other, xmlopt,
                                          flags) < 0) {
        /* revert any stealing of the snapshot domain definition */
        if (check_if_stolen && def->parent.dom && !otherdef->parent.dom)
            otherdef->parent.dom = g_steal_pointer(&def->parent.dom);
        return -1;
    }
    if (other) {
        /* Drop and rebuild the parent relationship, but keep all
         * child relations by reusing snap. */
        virDomainMomentDropParent(other);
        virObjectUnref(otherdef);
        other->def = &(*defptr)->parent;
        *defptr = NULL;
        *snap = other;
    }

    return 0;
}
