[libvirt] [PATCH 10/13] virsh: Split cmds to manage domain snapshot from virsh.c

Osier Yang jyang at redhat.com
Tue Jul 24 09:18:31 UTC 2012


Commands to manage domain snapshot are moved from virsh.c to
virsh-snapshot.c.

* virsh.c: Remove domain snapshot commands.
* virsh-snapshot.c: New file, filled with domain snapshot commands.
---
 tools/virsh-snapshot.c | 1604 ++++++++++++++++++++++++++++++++++++++++++++++++
 tools/virsh.c          | 1582 +-----------------------------------------------
 2 files changed, 1606 insertions(+), 1580 deletions(-)
 create mode 100644 tools/virsh-snapshot.c

diff --git a/tools/virsh-snapshot.c b/tools/virsh-snapshot.c
new file mode 100644
index 0000000..c0a2d9d
--- /dev/null
+++ b/tools/virsh-snapshot.c
@@ -0,0 +1,1604 @@
+/*
+ * virsh-domain.c: Commands to manage domain snapshot
+ *
+ * Copyright (C) 2005, 2007-2012 Red Hat, Inc
+ *
+ * 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/>.
+ *
+ *  Daniel Veillard <veillard at redhat.com>
+ *  Karel Zak <kzak at redhat.com>
+ *  Daniel P. Berrange <berrange at redhat.com>
+ *
+ */
+
+/* Helper for snapshot-create and snapshot-create-as */
+static bool
+vshSnapshotCreate(vshControl *ctl, virDomainPtr dom, const char *buffer,
+                  unsigned int flags, const char *from)
+{
+    bool ret = false;
+    virDomainSnapshotPtr snapshot;
+    bool halt = false;
+    char *doc = NULL;
+    xmlDocPtr xml = NULL;
+    xmlXPathContextPtr ctxt = NULL;
+    const char *name = NULL;
+
+    snapshot = virDomainSnapshotCreateXML(dom, buffer, flags);
+
+    /* Emulate --halt on older servers.  */
+    if (!snapshot && last_error->code == VIR_ERR_INVALID_ARG &&
+        (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT)) {
+        int persistent;
+
+        virFreeError(last_error);
+        last_error = NULL;
+        persistent = virDomainIsPersistent(dom);
+        if (persistent < 0) {
+            virshReportError(ctl);
+            goto cleanup;
+        }
+        if (!persistent) {
+            vshError(ctl, "%s",
+                     _("cannot halt after snapshot of transient domain"));
+            goto cleanup;
+        }
+        if (virDomainIsActive(dom) == 1)
+            halt = true;
+        flags &= ~VIR_DOMAIN_SNAPSHOT_CREATE_HALT;
+        snapshot = virDomainSnapshotCreateXML(dom, buffer, flags);
+    }
+
+    if (snapshot == NULL)
+        goto cleanup;
+
+    if (halt && virDomainDestroy(dom) < 0) {
+        virshReportError(ctl);
+        goto cleanup;
+    }
+
+    name = virDomainSnapshotGetName(snapshot);
+    if (!name) {
+        vshError(ctl, "%s", _("Could not get snapshot name"));
+        goto cleanup;
+    }
+
+    if (from)
+        vshPrint(ctl, _("Domain snapshot %s created from '%s'"), name, from);
+    else
+        vshPrint(ctl, _("Domain snapshot %s created"), name);
+
+    ret = true;
+
+cleanup:
+    xmlXPathFreeContext(ctxt);
+    xmlFreeDoc(xml);
+    if (snapshot)
+        virDomainSnapshotFree(snapshot);
+    VIR_FREE(doc);
+    return ret;
+}
+
+/*
+ * "snapshot-create" command
+ */
+static const vshCmdInfo info_snapshot_create[] = {
+    {"help", N_("Create a snapshot from XML")},
+    {"desc", N_("Create a snapshot (disk and RAM) from XML")},
+    {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_snapshot_create[] = {
+    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
+    {"xmlfile", VSH_OT_DATA, 0, N_("domain snapshot XML")},
+    {"redefine", VSH_OT_BOOL, 0, N_("redefine metadata for existing snapshot")},
+    {"current", VSH_OT_BOOL, 0, N_("with redefine, set current snapshot")},
+    {"no-metadata", VSH_OT_BOOL, 0, N_("take snapshot but create no metadata")},
+    {"halt", VSH_OT_BOOL, 0, N_("halt domain after snapshot is created")},
+    {"disk-only", VSH_OT_BOOL, 0, N_("capture disk state but not vm state")},
+    {"reuse-external", VSH_OT_BOOL, 0, N_("reuse any existing external files")},
+    {"quiesce", VSH_OT_BOOL, 0, N_("quiesce guest's file systems")},
+    {"atomic", VSH_OT_BOOL, 0, N_("require atomic operation")},
+    {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdSnapshotCreate(vshControl *ctl, const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    const char *from = NULL;
+    char *buffer = NULL;
+    unsigned int flags = 0;
+
+    if (vshCommandOptBool(cmd, "redefine"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE;
+    if (vshCommandOptBool(cmd, "current"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT;
+    if (vshCommandOptBool(cmd, "no-metadata"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA;
+    if (vshCommandOptBool(cmd, "halt"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT;
+    if (vshCommandOptBool(cmd, "disk-only"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
+    if (vshCommandOptBool(cmd, "reuse-external"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT;
+    if (vshCommandOptBool(cmd, "quiesce"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE;
+    if (vshCommandOptBool(cmd, "atomic"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC;
+
+    if (!vshConnectionUsability(ctl, ctl->conn))
+        goto cleanup;
+
+    dom = vshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        goto cleanup;
+
+    if (vshCommandOptString(cmd, "xmlfile", &from) <= 0)
+        buffer = vshStrdup(ctl, "<domainsnapshot/>");
+    else {
+        if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) {
+            /* we have to report the error here because during cleanup
+             * we'll run through virDomainFree(), which loses the
+             * last error
+             */
+            virshReportError(ctl);
+            goto cleanup;
+        }
+    }
+    if (buffer == NULL) {
+        vshError(ctl, "%s", _("Out of memory"));
+        goto cleanup;
+    }
+
+    ret = vshSnapshotCreate(ctl, dom, buffer, flags, from);
+
+cleanup:
+    VIR_FREE(buffer);
+    if (dom)
+        virDomainFree(dom);
+
+    return ret;
+}
+
+/*
+ * "snapshot-create-as" command
+ */
+static int
+vshParseSnapshotDiskspec(vshControl *ctl, virBufferPtr buf, const char *str)
+{
+    int ret = -1;
+    char *name = NULL;
+    char *snapshot = NULL;
+    char *driver = NULL;
+    char *file = NULL;
+    char *spec = vshStrdup(ctl, str);
+    char *tmp = spec;
+    size_t len = strlen(str);
+
+    if (*str == ',')
+        goto cleanup;
+    name = tmp;
+    while ((tmp = strchr(tmp, ','))) {
+        if (tmp[1] == ',') {
+            /* Recognize ,, as an escape for a literal comma */
+            memmove(&tmp[1], &tmp[2], len - (tmp - spec) - 2 + 1);
+            len--;
+            tmp++;
+            continue;
+        }
+        /* Terminate previous string, look for next recognized one */
+        *tmp++ = '\0';
+        if (!snapshot && STRPREFIX(tmp, "snapshot="))
+            snapshot = tmp + strlen("snapshot=");
+        else if (!driver && STRPREFIX(tmp, "driver="))
+            driver = tmp + strlen("driver=");
+        else if (!file && STRPREFIX(tmp, "file="))
+            file = tmp + strlen("file=");
+        else
+            goto cleanup;
+    }
+
+    virBufferEscapeString(buf, "    <disk name='%s'", name);
+    if (snapshot)
+        virBufferAsprintf(buf, " snapshot='%s'", snapshot);
+    if (driver || file) {
+        virBufferAddLit(buf, ">\n");
+        if (driver)
+            virBufferAsprintf(buf, "      <driver type='%s'/>\n", driver);
+        if (file)
+            virBufferEscapeString(buf, "      <source file='%s'/>\n", file);
+        virBufferAddLit(buf, "    </disk>\n");
+    } else {
+        virBufferAddLit(buf, "/>\n");
+    }
+    ret = 0;
+cleanup:
+    if (ret < 0)
+        vshError(ctl, _("unable to parse diskspec: %s"), str);
+    VIR_FREE(spec);
+    return ret;
+}
+
+static const vshCmdInfo info_snapshot_create_as[] = {
+    {"help", N_("Create a snapshot from a set of args")},
+    {"desc", N_("Create a snapshot (disk and RAM) from arguments")},
+    {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_snapshot_create_as[] = {
+    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
+    {"name", VSH_OT_DATA, 0, N_("name of snapshot")},
+    {"description", VSH_OT_DATA, 0, N_("description of snapshot")},
+    {"print-xml", VSH_OT_BOOL, 0, N_("print XML document rather than create")},
+    {"no-metadata", VSH_OT_BOOL, 0, N_("take snapshot but create no metadata")},
+    {"halt", VSH_OT_BOOL, 0, N_("halt domain after snapshot is created")},
+    {"disk-only", VSH_OT_BOOL, 0, N_("capture disk state but not vm state")},
+    {"reuse-external", VSH_OT_BOOL, 0, N_("reuse any existing external files")},
+    {"quiesce", VSH_OT_BOOL, 0, N_("quiesce guest's file systems")},
+    {"atomic", VSH_OT_BOOL, 0, N_("require atomic operation")},
+    {"diskspec", VSH_OT_ARGV, 0,
+     N_("disk attributes: disk[,snapshot=type][,driver=type][,file=name]")},
+    {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdSnapshotCreateAs(vshControl *ctl, const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    char *buffer = NULL;
+    const char *name = NULL;
+    const char *desc = NULL;
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+    unsigned int flags = 0;
+    const vshCmdOpt *opt = NULL;
+
+    if (vshCommandOptBool(cmd, "no-metadata"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA;
+    if (vshCommandOptBool(cmd, "halt"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT;
+    if (vshCommandOptBool(cmd, "disk-only"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
+    if (vshCommandOptBool(cmd, "reuse-external"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT;
+    if (vshCommandOptBool(cmd, "quiesce"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE;
+    if (vshCommandOptBool(cmd, "atomic"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC;
+
+    if (!vshConnectionUsability(ctl, ctl->conn))
+        goto cleanup;
+
+    dom = vshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        goto cleanup;
+
+    if (vshCommandOptString(cmd, "name", &name) < 0 ||
+        vshCommandOptString(cmd, "description", &desc) < 0) {
+        vshError(ctl, _("argument must not be empty"));
+        goto cleanup;
+    }
+
+    virBufferAddLit(&buf, "<domainsnapshot>\n");
+    if (name)
+        virBufferEscapeString(&buf, "  <name>%s</name>\n", name);
+    if (desc)
+        virBufferEscapeString(&buf, "  <description>%s</description>\n", desc);
+    if (vshCommandOptBool(cmd, "diskspec")) {
+        virBufferAddLit(&buf, "  <disks>\n");
+        while ((opt = vshCommandOptArgv(cmd, opt))) {
+            if (vshParseSnapshotDiskspec(ctl, &buf, opt->data) < 0) {
+                virBufferFreeAndReset(&buf);
+                goto cleanup;
+            }
+        }
+        virBufferAddLit(&buf, "  </disks>\n");
+    }
+    virBufferAddLit(&buf, "</domainsnapshot>\n");
+
+    buffer = virBufferContentAndReset(&buf);
+    if (buffer == NULL) {
+        vshError(ctl, "%s", _("Out of memory"));
+        goto cleanup;
+    }
+
+    if (vshCommandOptBool(cmd, "print-xml")) {
+        vshPrint(ctl, "%s\n",  buffer);
+        ret = true;
+        goto cleanup;
+    }
+
+    ret = vshSnapshotCreate(ctl, dom, buffer, flags, NULL);
+
+cleanup:
+    VIR_FREE(buffer);
+    if (dom)
+        virDomainFree(dom);
+
+    return ret;
+}
+
+/* Helper for resolving {--current | --ARG name} into a snapshot
+ * belonging to DOM.  If EXCLUSIVE, fail if both --current and arg are
+ * present.  On success, populate *SNAP and *NAME, before returning 0.
+ * On failure, return -1 after issuing an error message.  */
+static int
+vshLookupSnapshot(vshControl *ctl, const vshCmd *cmd,
+                  const char *arg, bool exclusive, virDomainPtr dom,
+                  virDomainSnapshotPtr *snap, const char **name)
+{
+    bool current = vshCommandOptBool(cmd, "current");
+    const char *snapname = NULL;
+
+    if (vshCommandOptString(cmd, arg, &snapname) < 0) {
+        vshError(ctl, _("invalid argument for --%s"), arg);
+        return -1;
+    }
+
+    if (exclusive && current && snapname) {
+        vshError(ctl, _("--%s and --current are mutually exclusive"), arg);
+        return -1;
+    }
+
+    if (snapname) {
+        *snap = virDomainSnapshotLookupByName(dom, snapname, 0);
+    } else if (current) {
+        *snap = virDomainSnapshotCurrent(dom, 0);
+    } else {
+        vshError(ctl, _("--%s or --current is required"), arg);
+        return -1;
+    }
+    if (!*snap) {
+        virshReportError(ctl);
+        return -1;
+    }
+
+    *name = virDomainSnapshotGetName(*snap);
+    return 0;
+}
+
+/*
+ * "snapshot-edit" command
+ */
+static const vshCmdInfo info_snapshot_edit[] = {
+    {"help", N_("edit XML for a snapshot")},
+    {"desc", N_("Edit the domain snapshot XML for a named snapshot")},
+    {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_snapshot_edit[] = {
+    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
+    {"snapshotname", VSH_OT_DATA, 0, N_("snapshot name")},
+    {"current", VSH_OT_BOOL, 0, N_("also set edited snapshot as current")},
+    {"rename", VSH_OT_BOOL, 0, N_("allow renaming an existing snapshot")},
+    {"clone", VSH_OT_BOOL, 0, N_("allow cloning to new name")},
+    {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdSnapshotEdit(vshControl *ctl, const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    virDomainSnapshotPtr snapshot = NULL;
+    virDomainSnapshotPtr edited = NULL;
+    const char *name;
+    const char *edited_name;
+    bool ret = false;
+    unsigned int getxml_flags = VIR_DOMAIN_XML_SECURE;
+    unsigned int define_flags = VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE;
+    bool rename_okay = vshCommandOptBool(cmd, "rename");
+    bool clone_okay = vshCommandOptBool(cmd, "clone");
+
+    if (rename_okay && clone_okay) {
+        vshError(ctl, "%s",
+                 _("--rename and --clone are mutually exclusive"));
+        return false;
+    }
+
+    if (vshCommandOptBool(cmd, "current") &&
+        vshCommandOptBool(cmd, "snapshotname"))
+        define_flags |= VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT;
+
+    if (!vshConnectionUsability(ctl, ctl->conn))
+        return false;
+
+    dom = vshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        goto cleanup;
+
+    if (vshLookupSnapshot(ctl, cmd, "snapshotname", false, dom,
+                          &snapshot, &name) < 0)
+        goto cleanup;
+
+#define EDIT_GET_XML \
+    virDomainSnapshotGetXMLDesc(snapshot, getxml_flags)
+#define EDIT_NOT_CHANGED \
+    /* Depending on flags, we re-edit even if XML is unchanged.  */ \
+    if (!(define_flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT)) {     \
+        vshPrint(ctl,                                               \
+                 _("Snapshot %s XML configuration not changed.\n"), \
+                 name);                                             \
+        ret = true;                                                 \
+        goto cleanup;                                               \
+    }
+#define EDIT_DEFINE \
+    (strstr(doc, "<state>disk-snapshot</state>") ? \
+    define_flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY : 0), \
+    edited = virDomainSnapshotCreateXML(dom, doc_edited, define_flags)
+#define EDIT_FREE \
+    if (edited) \
+        virDomainSnapshotFree(edited);
+#include "virsh-edit.c"
+
+    edited_name = virDomainSnapshotGetName(edited);
+    if (STREQ(name, edited_name)) {
+        vshPrint(ctl, _("Snapshot %s edited.\n"), name);
+    } else if (clone_okay) {
+        vshPrint(ctl, _("Snapshot %s cloned to %s.\n"), name,
+                 edited_name);
+    } else {
+        unsigned int delete_flags;
+
+        delete_flags = VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY;
+        if (virDomainSnapshotDelete(rename_okay ? snapshot : edited,
+                                    delete_flags) < 0) {
+            virshReportError(ctl);
+            vshError(ctl, _("Failed to clean up %s"),
+                     rename_okay ? name : edited_name);
+            goto cleanup;
+        }
+        if (!rename_okay) {
+            vshError(ctl, _("Must use --rename or --clone to change %s to %s"),
+                     name, edited_name);
+            goto cleanup;
+        }
+    }
+
+    ret = true;
+
+cleanup:
+    if (edited)
+        virDomainSnapshotFree(edited);
+    else
+        vshError(ctl, _("Failed to update %s"), name);
+    if (snapshot)
+        virDomainSnapshotFree(snapshot);
+    if (dom)
+        virDomainFree(dom);
+    return ret;
+}
+
+/*
+ * "snapshot-current" command
+ */
+static const vshCmdInfo info_snapshot_current[] = {
+    {"help", N_("Get or set the current snapshot")},
+    {"desc", N_("Get or set the current snapshot")},
+    {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_snapshot_current[] = {
+    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
+    {"name", VSH_OT_BOOL, 0, N_("list the name, rather than the full xml")},
+    {"security-info", VSH_OT_BOOL, 0,
+     N_("include security sensitive information in XML dump")},
+    {"snapshotname", VSH_OT_DATA, 0,
+     N_("name of existing snapshot to make current")},
+    {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdSnapshotCurrent(vshControl *ctl, const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    int current;
+    virDomainSnapshotPtr snapshot = NULL;
+    char *xml = NULL;
+    const char *snapshotname = NULL;
+    unsigned int flags = 0;
+    const char *domname;
+
+    if (vshCommandOptBool(cmd, "security-info"))
+        flags |= VIR_DOMAIN_XML_SECURE;
+
+    if (!vshConnectionUsability(ctl, ctl->conn))
+        goto cleanup;
+
+    dom = vshCommandOptDomain(ctl, cmd, &domname);
+    if (dom == NULL)
+        goto cleanup;
+
+    if (vshCommandOptString(cmd, "snapshotname", &snapshotname) < 0) {
+        vshError(ctl, _("invalid snapshotname argument '%s'"), snapshotname);
+        goto cleanup;
+    }
+    if (snapshotname) {
+        virDomainSnapshotPtr snapshot2 = NULL;
+        flags = (VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE |
+                 VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT);
+
+        if (vshCommandOptBool(cmd, "name")) {
+            vshError(ctl, "%s",
+                     _("--name and snapshotname are mutually exclusive"));
+            goto cleanup;
+        }
+        snapshot = virDomainSnapshotLookupByName(dom, snapshotname, 0);
+        if (snapshot == NULL)
+            goto cleanup;
+        xml = virDomainSnapshotGetXMLDesc(snapshot, VIR_DOMAIN_XML_SECURE);
+        if (!xml)
+            goto cleanup;
+        /* strstr is safe here, since xml came from libvirt API and not user */
+        if (strstr(xml, "<state>disk-snapshot</state>"))
+            flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
+        snapshot2 = virDomainSnapshotCreateXML(dom, xml, flags);
+        if (snapshot2 == NULL)
+            goto cleanup;
+        virDomainSnapshotFree(snapshot2);
+        vshPrint(ctl, _("Snapshot %s set as current"), snapshotname);
+        ret = true;
+        goto cleanup;
+    }
+
+    current = virDomainHasCurrentSnapshot(dom, 0);
+    if (current < 0) {
+        goto cleanup;
+    } else if (!current) {
+        vshError(ctl, _("domain '%s' has no current snapshot"), domname);
+        goto cleanup;
+    } else {
+        const char *name = NULL;
+
+        if (!(snapshot = virDomainSnapshotCurrent(dom, 0)))
+            goto cleanup;
+
+        if (vshCommandOptBool(cmd, "name")) {
+            name = virDomainSnapshotGetName(snapshot);
+            if (!name)
+                goto cleanup;
+        } else {
+            xml = virDomainSnapshotGetXMLDesc(snapshot, flags);
+            if (!xml)
+                goto cleanup;
+        }
+
+        vshPrint(ctl, "%s", name ? name : xml);
+    }
+
+    ret = true;
+
+cleanup:
+    if (!ret)
+        virshReportError(ctl);
+    VIR_FREE(xml);
+    if (snapshot)
+        virDomainSnapshotFree(snapshot);
+    if (dom)
+        virDomainFree(dom);
+
+    return ret;
+}
+
+/* Helper function to get the name of a snapshot's parent.  Caller
+ * must free the result.  Returns 0 on success (including when it was
+ * proven no parent exists), and -1 on failure with error reported
+ * (such as no snapshot support or domain deleted in meantime).  */
+static int
+vshGetSnapshotParent(vshControl *ctl, virDomainSnapshotPtr snapshot,
+                     char **parent_name)
+{
+    virDomainSnapshotPtr parent = NULL;
+    char *xml = NULL;
+    xmlDocPtr xmldoc = NULL;
+    xmlXPathContextPtr ctxt = NULL;
+    int ret = -1;
+
+    *parent_name = NULL;
+
+    /* Try new API, since it is faster. */
+    if (!ctl->useSnapshotOld) {
+        parent = virDomainSnapshotGetParent(snapshot, 0);
+        if (parent) {
+            /* API works, and virDomainSnapshotGetName will succeed */
+            *parent_name = vshStrdup(ctl, virDomainSnapshotGetName(parent));
+            ret = 0;
+            goto cleanup;
+        }
+        if (last_error->code == VIR_ERR_NO_DOMAIN_SNAPSHOT) {
+            /* API works, and we found a root with no parent */
+            ret = 0;
+            goto cleanup;
+        }
+        /* API didn't work, fall back to XML scraping. */
+        ctl->useSnapshotOld = true;
+    }
+
+    xml = virDomainSnapshotGetXMLDesc(snapshot, 0);
+    if (!xml)
+        goto cleanup;
+
+    xmldoc = virXMLParseStringCtxt(xml, _("(domain_snapshot)"), &ctxt);
+    if (!xmldoc)
+        goto cleanup;
+
+    *parent_name = virXPathString("string(/domainsnapshot/parent/name)", ctxt);
+    ret = 0;
+
+cleanup:
+    if (ret < 0) {
+        virshReportError(ctl);
+        vshError(ctl, "%s", _("unable to determine if snapshot has parent"));
+    } else {
+        virFreeError(last_error);
+        last_error = NULL;
+    }
+    if (parent)
+        virDomainSnapshotFree(parent);
+    xmlXPathFreeContext(ctxt);
+    xmlFreeDoc(xmldoc);
+    VIR_FREE(xml);
+    return ret;
+}
+
+/*
+ * "snapshot-info" command
+ */
+static const vshCmdInfo info_snapshot_info[] = {
+    {"help", N_("snapshot information")},
+    {"desc", N_("Returns basic information about a snapshot.")},
+    {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_snapshot_info[] = {
+    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
+    {"snapshotname", VSH_OT_DATA, 0, N_("snapshot name")},
+    {"current", VSH_OT_BOOL, 0, N_("info on current snapshot")},
+    {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdSnapshotInfo(vshControl *ctl, const vshCmd *cmd)
+{
+    virDomainPtr dom;
+    virDomainSnapshotPtr snapshot = NULL;
+    const char *name;
+    char *doc = NULL;
+    char *tmp;
+    char *parent = NULL;
+    bool ret = false;
+    int count;
+    unsigned int flags;
+    int current;
+    int metadata;
+
+    if (!vshConnectionUsability(ctl, ctl->conn))
+        return false;
+
+    dom = vshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        return false;
+
+    if (vshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
+                          &snapshot, &name) < 0)
+        goto cleanup;
+
+    vshPrint(ctl, "%-15s %s\n", _("Name:"), name);
+    vshPrint(ctl, "%-15s %s\n", _("Domain:"), virDomainGetName(dom));
+
+    /* Determine if snapshot is current; this is useful enough that we
+     * attempt a fallback.  */
+    current = virDomainSnapshotIsCurrent(snapshot, 0);
+    if (current < 0) {
+        virDomainSnapshotPtr other = virDomainSnapshotCurrent(dom, 0);
+
+        virResetLastError();
+        current = 0;
+        if (other) {
+            if (STREQ(name, virDomainSnapshotGetName(other)))
+                current = 1;
+            virDomainSnapshotFree(other);
+        }
+    }
+    vshPrint(ctl, "%-15s %s\n", _("Current:"),
+             current > 0 ? _("yes") : _("no"));
+
+    /* Get the XML configuration of the snapshot to determine the
+     * state of the machine at the time of the snapshot.  */
+    doc = virDomainSnapshotGetXMLDesc(snapshot, 0);
+    if (!doc)
+        goto cleanup;
+
+    tmp = strstr(doc, "<state>");
+    if (!tmp) {
+        vshError(ctl, "%s",
+                 _("unexpected problem reading snapshot xml"));
+        goto cleanup;
+    }
+    tmp += strlen("<state>");
+    vshPrint(ctl, "%-15s %.*s\n", _("State:"),
+             (int) (strchr(tmp, '<') - tmp), tmp);
+
+    if (vshGetSnapshotParent(ctl, snapshot, &parent) < 0)
+        goto cleanup;
+    vshPrint(ctl, "%-15s %s\n", _("Parent:"), parent ? parent : "-");
+
+    /* Children, Descendants.  After this point, the fallback to
+     * compute children is too expensive, so we gracefully quit if the
+     * APIs don't exist.  */
+    if (ctl->useSnapshotOld) {
+        ret = true;
+        goto cleanup;
+    }
+    flags = 0;
+    count = virDomainSnapshotNumChildren(snapshot, flags);
+    if (count < 0)
+        goto cleanup;
+    vshPrint(ctl, "%-15s %d\n", _("Children:"), count);
+    flags = VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
+    count = virDomainSnapshotNumChildren(snapshot, flags);
+    if (count < 0)
+        goto cleanup;
+    vshPrint(ctl, "%-15s %d\n", _("Descendants:"), count);
+
+    /* Metadata; the fallback here relies on the fact that metadata
+     * used to have an all-or-nothing effect on snapshot count.  */
+    metadata = virDomainSnapshotHasMetadata(snapshot, 0);
+    if (metadata < 0) {
+        metadata = virDomainSnapshotNum(dom,
+                                        VIR_DOMAIN_SNAPSHOT_LIST_METADATA);
+        virResetLastError();
+    }
+    if (metadata >= 0)
+        vshPrint(ctl, "%-15s %s\n", _("Metadata:"),
+                 metadata ? _("yes") : _("no"));
+
+    ret = true;
+
+cleanup:
+    VIR_FREE(doc);
+    VIR_FREE(parent);
+    if (snapshot)
+        virDomainSnapshotFree(snapshot);
+    virDomainFree(dom);
+    return ret;
+}
+
+/* Helpers for collecting a list of snapshots.  */
+struct vshSnap {
+    virDomainSnapshotPtr snap;
+    char *parent;
+};
+struct vshSnapshotList {
+    struct vshSnap *snaps;
+    int nsnaps;
+};
+typedef struct vshSnapshotList *vshSnapshotListPtr;
+
+static void
+vshSnapshotListFree(vshSnapshotListPtr snaplist)
+{
+    int i;
+
+    if (!snaplist)
+        return;
+    if (snaplist->snaps) {
+        for (i = 0; i < snaplist->nsnaps; i++) {
+            if (snaplist->snaps[i].snap)
+                virDomainSnapshotFree(snaplist->snaps[i].snap);
+            VIR_FREE(snaplist->snaps[i].parent);
+        }
+        VIR_FREE(snaplist->snaps);
+    }
+    VIR_FREE(snaplist);
+}
+
+static int
+vshSnapSorter(const void *a, const void *b)
+{
+    const struct vshSnap *sa = a;
+    const struct vshSnap *sb = b;
+
+    if (sa->snap && !sb->snap)
+        return -1;
+    if (!sa->snap)
+        return sb->snap != NULL;
+
+    /* User visible sort, so we want locale-specific case comparison.  */
+    return strcasecmp(virDomainSnapshotGetName(sa->snap),
+                      virDomainSnapshotGetName(sb->snap));
+}
+
+/* Compute a list of snapshots from DOM.  If FROM is provided, the
+ * list is limited to descendants of the given snapshot.  If FLAGS is
+ * given, the list is filtered.  If TREE is specified, then all but
+ * FROM or the roots will also have parent information.  */
+static vshSnapshotListPtr
+vshSnapshotListCollect(vshControl *ctl, virDomainPtr dom,
+                       virDomainSnapshotPtr from,
+                       unsigned int flags, bool tree)
+{
+    int i;
+    char **names = NULL;
+    int count = -1;
+    bool descendants = false;
+    bool roots = false;
+    virDomainSnapshotPtr *snaps;
+    vshSnapshotListPtr snaplist = vshMalloc(ctl, sizeof(*snaplist));
+    vshSnapshotListPtr ret = NULL;
+    const char *fromname = NULL;
+    int start_index = -1;
+    int deleted = 0;
+
+    /* Try the interface available in 0.9.13 and newer.  */
+    if (!ctl->useSnapshotOld) {
+        if (from)
+            count = virDomainSnapshotListAllChildren(from, &snaps, flags);
+        else
+            count = virDomainListAllSnapshots(dom, &snaps, flags);
+    }
+    if (count >= 0) {
+        /* When mixing --from and --tree, we also want a copy of from
+         * in the list, but with no parent for that one entry.  */
+        snaplist->snaps = vshCalloc(ctl, count + (tree && from),
+                                    sizeof(*snaplist->snaps));
+        snaplist->nsnaps = count;
+        for (i = 0; i < count; i++)
+            snaplist->snaps[i].snap = snaps[i];
+        VIR_FREE(snaps);
+        if (tree) {
+            for (i = 0; i < count; i++) {
+                if (vshGetSnapshotParent(ctl, snaplist->snaps[i].snap,
+                                         &snaplist->snaps[i].parent) < 0)
+                    goto cleanup;
+            }
+            if (from) {
+                snaps[snaplist->nsnaps++] = from;
+                virDomainSnapshotRef(from);
+            }
+        }
+        goto success;
+    }
+
+    /* Assume that if we got this far, then the --no-leaves and
+     * --no-metadata flags were not supported.  Disable groups that
+     * have no impact.  */
+    /* XXX should we emulate --no-leaves?  */
+    if (flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES &&
+        flags & VIR_DOMAIN_SNAPSHOT_LIST_LEAVES)
+        flags &= ~(VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES |
+                   VIR_DOMAIN_SNAPSHOT_LIST_LEAVES);
+    if (flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA &&
+        flags & VIR_DOMAIN_SNAPSHOT_LIST_METADATA)
+        flags &= ~(VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA |
+                   VIR_DOMAIN_SNAPSHOT_LIST_METADATA);
+    if (flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA) {
+        /* We can emulate --no-metadata if --metadata was supported,
+         * since it was an all-or-none attribute on old servers.  */
+        count = virDomainSnapshotNum(dom,
+                                     VIR_DOMAIN_SNAPSHOT_LIST_METADATA);
+        if (count < 0)
+            goto cleanup;
+        if (count > 0)
+            return snaplist;
+        flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA;
+    }
+
+    /* This uses the interfaces available in 0.8.0-0.9.6
+     * (virDomainSnapshotListNames, global list only) and in
+     * 0.9.7-0.9.12 (addition of virDomainSnapshotListChildrenNames
+     * for child listing, and new flags), as follows, with [*] by the
+     * combinations that need parent info (either for filtering
+     * purposes or for the resulting tree listing):
+     *                              old               new
+     * list                         global as-is      global as-is
+     * list --roots                *global + filter   global + flags
+     * list --from                 *global + filter   child as-is
+     * list --from --descendants   *global + filter   child + flags
+     * list --tree                 *global as-is     *global as-is
+     * list --tree --from          *global + filter  *child + flags
+     *
+     * Additionally, when --tree and --from are both used, from is
+     * added to the final list as the only element without a parent.
+     * Otherwise, --from does not appear in the final list.
+     */
+    if (from) {
+        fromname = virDomainSnapshotGetName(from);
+        if (!fromname) {
+            vshError(ctl, "%s", _("Could not get snapshot name"));
+            goto cleanup;
+        }
+        descendants = (flags & VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS) || tree;
+        if (tree)
+            flags |= VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
+
+        /* Determine if we can use the new child listing API.  */
+        if (ctl->useSnapshotOld ||
+            ((count = virDomainSnapshotNumChildren(from, flags)) < 0 &&
+             last_error->code == VIR_ERR_NO_SUPPORT)) {
+            /* We can emulate --from.  */
+            /* XXX can we also emulate --leaves? */
+            virFreeError(last_error);
+            last_error = NULL;
+            ctl->useSnapshotOld = true;
+            flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
+            goto global;
+        }
+        if (tree && count >= 0)
+            count++;
+    } else {
+    global:
+        /* Global listing (including fallback when --from failed with
+         * child listing).  */
+        count = virDomainSnapshotNum(dom, flags);
+
+        /* Fall back to simulation if --roots was unsupported. */
+        /* XXX can we also emulate --leaves? */
+        if (!from && count < 0 && last_error->code == VIR_ERR_INVALID_ARG &&
+            (flags & VIR_DOMAIN_SNAPSHOT_LIST_ROOTS)) {
+            virFreeError(last_error);
+            last_error = NULL;
+            roots = true;
+            flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_ROOTS;
+            count = virDomainSnapshotNum(dom, flags);
+        }
+    }
+
+    if (count < 0) {
+        if (!last_error)
+            vshError(ctl, _("failed to collect snapshot list"));
+        goto cleanup;
+    }
+
+    if (!count)
+        goto success;
+
+    names = vshCalloc(ctl, sizeof(*names), count);
+
+    /* Now that we have a count, collect the list.  */
+    if (from && !ctl->useSnapshotOld) {
+        if (tree) {
+            if (count)
+                count = virDomainSnapshotListChildrenNames(from, names + 1,
+                                                           count - 1, flags);
+            if (count >= 0) {
+                count++;
+                names[0] = vshStrdup(ctl, fromname);
+            }
+        } else {
+            count = virDomainSnapshotListChildrenNames(from, names,
+                                                       count, flags);
+        }
+    } else {
+        count = virDomainSnapshotListNames(dom, names, count, flags);
+    }
+    if (count < 0)
+        goto cleanup;
+
+    snaplist->snaps = vshCalloc(ctl, sizeof(*snaplist->snaps), count);
+    snaplist->nsnaps = count;
+    for (i = 0; i < count; i++) {
+        snaplist->snaps[i].snap = virDomainSnapshotLookupByName(dom,
+                                                                names[i], 0);
+        if (!snaplist->snaps[i].snap)
+            goto cleanup;
+    }
+
+    /* Collect parents when needed.  With the new API, --tree and
+     * --from together put from as the first element without a parent;
+     * with the old API we still need to do a post-process filtering
+     * based on all parent information.  */
+    if (tree || (from && ctl->useSnapshotOld) || roots) {
+        for (i = (from && !ctl->useSnapshotOld); i < count; i++) {
+            if (from && ctl->useSnapshotOld && STREQ(names[i], fromname)) {
+                start_index = i;
+                if (tree)
+                    continue;
+            }
+            if (vshGetSnapshotParent(ctl, snaplist->snaps[i].snap,
+                                     &snaplist->snaps[i].parent) < 0)
+                goto cleanup;
+            if ((from && ((tree && !snaplist->snaps[i].parent) ||
+                          (!descendants &&
+                           STRNEQ_NULLABLE(fromname,
+                                           snaplist->snaps[i].parent)))) ||
+                (roots && snaplist->snaps[i].parent)) {
+                virDomainSnapshotFree(snaplist->snaps[i].snap);
+                snaplist->snaps[i].snap = NULL;
+                VIR_FREE(snaplist->snaps[i].parent);
+                deleted++;
+            }
+        }
+    }
+    if (tree)
+        goto success;
+
+    if (ctl->useSnapshotOld && descendants) {
+        bool changed = false;
+        bool remaining = false;
+
+        /* Make multiple passes over the list - first pass finds
+         * direct children and NULLs out all roots and from, remaining
+         * passes NULL out any undecided entry whose parent is not
+         * still in list.  We mark known descendants by clearing
+         * snaps[i].parents.  Sorry, this is O(n^3) - hope your
+         * hierarchy isn't huge.  XXX Is it worth making O(n^2 log n)
+         * by using qsort and bsearch?  */
+        if (start_index < 0) {
+            vshError(ctl, _("snapshot %s disappeared from list"), fromname);
+            goto cleanup;
+        }
+        for (i = 0; i < count; i++) {
+            if (i == start_index || !snaplist->snaps[i].parent) {
+                VIR_FREE(names[i]);
+                virDomainSnapshotFree(snaplist->snaps[i].snap);
+                snaplist->snaps[i].snap = NULL;
+                VIR_FREE(snaplist->snaps[i].parent);
+                deleted++;
+            } else if (STREQ(snaplist->snaps[i].parent, fromname)) {
+                VIR_FREE(snaplist->snaps[i].parent);
+                changed = true;
+            } else {
+                remaining = true;
+            }
+        }
+        if (!changed) {
+            ret = vshMalloc(ctl, sizeof(*snaplist));
+            goto cleanup;
+        }
+        while (changed && remaining) {
+            changed = remaining = false;
+            for (i = 0; i < count; i++) {
+                bool found_parent = false;
+                int j;
+
+                if (!names[i] || !snaplist->snaps[i].parent)
+                    continue;
+                for (j = 0; j < count; j++) {
+                    if (!names[j] || i == j)
+                        continue;
+                    if (STREQ(snaplist->snaps[i].parent, names[j])) {
+                        found_parent = true;
+                        if (!snaplist->snaps[j].parent)
+                            VIR_FREE(snaplist->snaps[i].parent);
+                        else
+                            remaining = true;
+                        break;
+                    }
+                }
+                if (!found_parent) {
+                    changed = true;
+                    VIR_FREE(names[i]);
+                    virDomainSnapshotFree(snaplist->snaps[i].snap);
+                    snaplist->snaps[i].snap = NULL;
+                    VIR_FREE(snaplist->snaps[i].parent);
+                    deleted++;
+                }
+            }
+        }
+    }
+
+success:
+    qsort(snaplist->snaps, snaplist->nsnaps, sizeof(*snaplist->snaps),
+          vshSnapSorter);
+    snaplist->nsnaps -= deleted;
+
+    ret = snaplist;
+    snaplist = NULL;
+
+cleanup:
+    vshSnapshotListFree(snaplist);
+    if (names)
+        for (i = 0; i < count; i++)
+            VIR_FREE(names[i]);
+    VIR_FREE(names);
+    return ret;
+}
+
+static const char *
+vshSnapshotListLookup(int id, bool parent, void *opaque)
+{
+    vshSnapshotListPtr snaplist = opaque;
+    if (parent)
+        return snaplist->snaps[id].parent;
+    return virDomainSnapshotGetName(snaplist->snaps[id].snap);
+}
+
+/*
+ * "snapshot-list" command
+ */
+static const vshCmdInfo info_snapshot_list[] = {
+    {"help", N_("List snapshots for a domain")},
+    {"desc", N_("Snapshot List")},
+    {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_snapshot_list[] = {
+    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
+    {"parent", VSH_OT_BOOL, 0, N_("add a column showing parent snapshot")},
+    {"roots", VSH_OT_BOOL, 0, N_("list only snapshots without parents")},
+    {"leaves", VSH_OT_BOOL, 0, N_("list only snapshots without children")},
+    {"no-leaves", VSH_OT_BOOL, 0,
+     N_("list only snapshots that are not leaves (with children)")},
+    {"metadata", VSH_OT_BOOL, 0,
+     N_("list only snapshots that have metadata that would prevent undefine")},
+    {"no-metadata", VSH_OT_BOOL, 0,
+     N_("list only snapshots that have no metadata managed by libvirt")},
+    {"tree", VSH_OT_BOOL, 0, N_("list snapshots in a tree")},
+    {"from", VSH_OT_DATA, 0, N_("limit list to children of given snapshot")},
+    {"current", VSH_OT_BOOL, 0,
+     N_("limit list to children of current snapshot")},
+    {"descendants", VSH_OT_BOOL, 0, N_("with --from, list all descendants")},
+    {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdSnapshotList(vshControl *ctl, const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    unsigned int flags = 0;
+    bool show_parent = false;
+    int i;
+    xmlDocPtr xml = NULL;
+    xmlXPathContextPtr ctxt = NULL;
+    char *doc = NULL;
+    virDomainSnapshotPtr snapshot = NULL;
+    char *state = NULL;
+    char *parent = NULL;
+    long long creation_longlong;
+    time_t creation_time_t;
+    char timestr[100];
+    struct tm time_info;
+    bool tree = vshCommandOptBool(cmd, "tree");
+    bool leaves = vshCommandOptBool(cmd, "leaves");
+    bool no_leaves = vshCommandOptBool(cmd, "no-leaves");
+    const char *from = NULL;
+    virDomainSnapshotPtr start = NULL;
+    vshSnapshotListPtr snaplist = NULL;
+
+    if (!vshConnectionUsability(ctl, ctl->conn))
+        goto cleanup;
+
+    dom = vshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        goto cleanup;
+
+    if ((vshCommandOptBool(cmd, "from") ||
+         vshCommandOptBool(cmd, "current")) &&
+        vshLookupSnapshot(ctl, cmd, "from", true, dom, &start, &from) < 0)
+        goto cleanup;
+
+    if (vshCommandOptBool(cmd, "parent")) {
+        if (vshCommandOptBool(cmd, "roots")) {
+            vshError(ctl, "%s",
+                     _("--parent and --roots are mutually exclusive"));
+            goto cleanup;
+        }
+        if (tree) {
+            vshError(ctl, "%s",
+                     _("--parent and --tree are mutually exclusive"));
+            goto cleanup;
+        }
+        show_parent = true;
+    } else if (vshCommandOptBool(cmd, "roots")) {
+        if (tree) {
+            vshError(ctl, "%s",
+                     _("--roots and --tree are mutually exclusive"));
+            goto cleanup;
+        }
+        if (from) {
+            vshError(ctl, "%s",
+                     _("--roots and --from are mutually exclusive"));
+            goto cleanup;
+        }
+        flags |= VIR_DOMAIN_SNAPSHOT_LIST_ROOTS;
+    }
+    if (leaves) {
+        if (tree) {
+            vshError(ctl, "%s",
+                     _("--leaves and --tree are mutually exclusive"));
+            goto cleanup;
+        }
+        flags |= VIR_DOMAIN_SNAPSHOT_LIST_LEAVES;
+    }
+    if (no_leaves) {
+        if (tree) {
+            vshError(ctl, "%s",
+                     _("--no-leaves and --tree are mutually exclusive"));
+            goto cleanup;
+        }
+        flags |= VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES;
+    }
+
+    if (vshCommandOptBool(cmd, "metadata")) {
+        flags |= VIR_DOMAIN_SNAPSHOT_LIST_METADATA;
+    }
+    if (vshCommandOptBool(cmd, "no-metadata")) {
+        flags |= VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA;
+    }
+
+    if (vshCommandOptBool(cmd, "descendants")) {
+        if (!from) {
+            vshError(ctl, "%s",
+                     _("--descendants requires either --from or --current"));
+            goto cleanup;
+        }
+        flags |= VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
+    }
+
+    if ((snaplist = vshSnapshotListCollect(ctl, dom, start, flags,
+                                           tree)) == NULL)
+        goto cleanup;
+
+    if (!tree) {
+        if (show_parent)
+            vshPrintExtra(ctl, " %-20s %-25s %-15s %s",
+                          _("Name"), _("Creation Time"), _("State"),
+                          _("Parent"));
+        else
+            vshPrintExtra(ctl, " %-20s %-25s %s",
+                          _("Name"), _("Creation Time"), _("State"));
+        vshPrintExtra(ctl, "\n"
+"------------------------------------------------------------\n");
+    }
+
+    if (!snaplist->nsnaps) {
+        ret = true;
+        goto cleanup;
+    }
+
+    if (tree) {
+        for (i = 0; i < snaplist->nsnaps; i++) {
+            if (!snaplist->snaps[i].parent &&
+                vshTreePrint(ctl, vshSnapshotListLookup, snaplist,
+                             snaplist->nsnaps, i) < 0)
+                goto cleanup;
+        }
+        ret = true;
+        goto cleanup;
+    }
+
+    for (i = 0; i < snaplist->nsnaps; i++) {
+        const char *name;
+
+        /* free up memory from previous iterations of the loop */
+        VIR_FREE(parent);
+        VIR_FREE(state);
+        xmlXPathFreeContext(ctxt);
+        xmlFreeDoc(xml);
+        VIR_FREE(doc);
+
+        snapshot = snaplist->snaps[i].snap;
+        name = virDomainSnapshotGetName(snapshot);
+        assert(name);
+
+        doc = virDomainSnapshotGetXMLDesc(snapshot, 0);
+        if (!doc)
+            continue;
+
+        xml = virXMLParseStringCtxt(doc, _("(domain_snapshot)"), &ctxt);
+        if (!xml)
+            continue;
+
+        if (show_parent)
+            parent = virXPathString("string(/domainsnapshot/parent/name)",
+                                    ctxt);
+
+        state = virXPathString("string(/domainsnapshot/state)", ctxt);
+        if (state == NULL)
+            continue;
+        if (virXPathLongLong("string(/domainsnapshot/creationTime)", ctxt,
+                             &creation_longlong) < 0)
+            continue;
+        creation_time_t = creation_longlong;
+        if (creation_time_t != creation_longlong) {
+            vshError(ctl, "%s", _("time_t overflow"));
+            continue;
+        }
+        localtime_r(&creation_time_t, &time_info);
+        strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S %z",
+                 &time_info);
+
+        if (parent)
+            vshPrint(ctl, " %-20s %-25s %-15s %s\n",
+                     name, timestr, state, parent);
+        else
+            vshPrint(ctl, " %-20s %-25s %s\n", name, timestr, state);
+    }
+
+    ret = true;
+
+cleanup:
+    /* this frees up memory from the last iteration of the loop */
+    vshSnapshotListFree(snaplist);
+    VIR_FREE(parent);
+    VIR_FREE(state);
+    if (start)
+        virDomainSnapshotFree(start);
+    xmlXPathFreeContext(ctxt);
+    xmlFreeDoc(xml);
+    VIR_FREE(doc);
+    if (dom)
+        virDomainFree(dom);
+
+    return ret;
+}
+
+/*
+ * "snapshot-dumpxml" command
+ */
+static const vshCmdInfo info_snapshot_dumpxml[] = {
+    {"help", N_("Dump XML for a domain snapshot")},
+    {"desc", N_("Snapshot Dump XML")},
+    {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_snapshot_dumpxml[] = {
+    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
+    {"snapshotname", VSH_OT_DATA, VSH_OFLAG_REQ, N_("snapshot name")},
+    {"security-info", VSH_OT_BOOL, 0,
+     N_("include security sensitive information in XML dump")},
+    {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdSnapshotDumpXML(vshControl *ctl, const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    const char *name = NULL;
+    virDomainSnapshotPtr snapshot = NULL;
+    char *xml = NULL;
+    unsigned int flags = 0;
+
+    if (vshCommandOptBool(cmd, "security-info"))
+        flags |= VIR_DOMAIN_XML_SECURE;
+
+    if (!vshConnectionUsability(ctl, ctl->conn))
+        goto cleanup;
+
+    dom = vshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        goto cleanup;
+
+    if (vshCommandOptString(cmd, "snapshotname", &name) <= 0)
+        goto cleanup;
+
+    snapshot = virDomainSnapshotLookupByName(dom, name, 0);
+    if (snapshot == NULL)
+        goto cleanup;
+
+    xml = virDomainSnapshotGetXMLDesc(snapshot, flags);
+    if (!xml)
+        goto cleanup;
+
+    vshPrint(ctl, "%s", xml);
+
+    ret = true;
+
+cleanup:
+    VIR_FREE(xml);
+    if (snapshot)
+        virDomainSnapshotFree(snapshot);
+    if (dom)
+        virDomainFree(dom);
+
+    return ret;
+}
+
+/*
+ * "snapshot-parent" command
+ */
+static const vshCmdInfo info_snapshot_parent[] = {
+    {"help", N_("Get the name of the parent of a snapshot")},
+    {"desc", N_("Extract the snapshot's parent, if any")},
+    {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_snapshot_parent[] = {
+    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
+    {"snapshotname", VSH_OT_DATA, 0, N_("find parent of snapshot name")},
+    {"current", VSH_OT_BOOL, 0, N_("find parent of current snapshot")},
+    {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdSnapshotParent(vshControl *ctl, const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    const char *name = NULL;
+    virDomainSnapshotPtr snapshot = NULL;
+    char *parent = NULL;
+
+    if (!vshConnectionUsability(ctl, ctl->conn))
+        goto cleanup;
+
+    dom = vshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        goto cleanup;
+
+    if (vshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
+                          &snapshot, &name) < 0)
+        goto cleanup;
+
+    if (vshGetSnapshotParent(ctl, snapshot, &parent) < 0)
+        goto cleanup;
+    if (!parent) {
+        vshError(ctl, _("snapshot '%s' has no parent"), name);
+        goto cleanup;
+    }
+
+    vshPrint(ctl, "%s", parent);
+
+    ret = true;
+
+cleanup:
+    VIR_FREE(parent);
+    if (snapshot)
+        virDomainSnapshotFree(snapshot);
+    if (dom)
+        virDomainFree(dom);
+
+    return ret;
+}
+
+/*
+ * "snapshot-revert" command
+ */
+static const vshCmdInfo info_snapshot_revert[] = {
+    {"help", N_("Revert a domain to a snapshot")},
+    {"desc", N_("Revert domain to snapshot")},
+    {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_snapshot_revert[] = {
+    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
+    {"snapshotname", VSH_OT_DATA, 0, N_("snapshot name")},
+    {"current", VSH_OT_BOOL, 0, N_("revert to current snapshot")},
+    {"running", VSH_OT_BOOL, 0, N_("after reverting, change state to running")},
+    {"paused", VSH_OT_BOOL, 0, N_("after reverting, change state to paused")},
+    {"force", VSH_OT_BOOL, 0, N_("try harder on risky reverts")},
+    {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdDomainSnapshotRevert(vshControl *ctl, const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    const char *name = NULL;
+    virDomainSnapshotPtr snapshot = NULL;
+    unsigned int flags = 0;
+    bool force = false;
+    int result;
+
+    if (vshCommandOptBool(cmd, "running"))
+        flags |= VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING;
+    if (vshCommandOptBool(cmd, "paused"))
+        flags |= VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED;
+    /* We want virsh snapshot-revert --force to work even when talking
+     * to older servers that did the unsafe revert by default but
+     * reject the flag, so we probe without the flag, and only use it
+     * when the error says it will make a difference.  */
+    if (vshCommandOptBool(cmd, "force"))
+        force = true;
+
+    if (!vshConnectionUsability(ctl, ctl->conn))
+        goto cleanup;
+
+    dom = vshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        goto cleanup;
+
+    if (vshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
+                          &snapshot, &name) < 0)
+        goto cleanup;
+
+    result = virDomainRevertToSnapshot(snapshot, flags);
+    if (result < 0 && force &&
+        last_error->code == VIR_ERR_SNAPSHOT_REVERT_RISKY) {
+        flags |= VIR_DOMAIN_SNAPSHOT_REVERT_FORCE;
+        virFreeError(last_error);
+        last_error = NULL;
+        result = virDomainRevertToSnapshot(snapshot, flags);
+    }
+    if (result < 0)
+        goto cleanup;
+
+    ret = true;
+
+cleanup:
+    if (snapshot)
+        virDomainSnapshotFree(snapshot);
+    if (dom)
+        virDomainFree(dom);
+
+    return ret;
+}
+
+/*
+ * "snapshot-delete" command
+ */
+static const vshCmdInfo info_snapshot_delete[] = {
+    {"help", N_("Delete a domain snapshot")},
+    {"desc", N_("Snapshot Delete")},
+    {NULL, NULL}
+};
+
+static const vshCmdOptDef opts_snapshot_delete[] = {
+    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
+    {"snapshotname", VSH_OT_DATA, 0, N_("snapshot name")},
+    {"current", VSH_OT_BOOL, 0, N_("delete current snapshot")},
+    {"children", VSH_OT_BOOL, 0, N_("delete snapshot and all children")},
+    {"children-only", VSH_OT_BOOL, 0, N_("delete children but not snapshot")},
+    {"metadata", VSH_OT_BOOL, 0,
+     N_("delete only libvirt metadata, leaving snapshot contents behind")},
+    {NULL, 0, 0, NULL}
+};
+
+static bool
+cmdSnapshotDelete(vshControl *ctl, const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    const char *name = NULL;
+    virDomainSnapshotPtr snapshot = NULL;
+    unsigned int flags = 0;
+
+    if (!vshConnectionUsability(ctl, ctl->conn))
+        goto cleanup;
+
+    dom = vshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        goto cleanup;
+
+    if (vshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
+                          &snapshot, &name) < 0)
+        goto cleanup;
+
+    if (vshCommandOptBool(cmd, "children"))
+        flags |= VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN;
+    if (vshCommandOptBool(cmd, "children-only"))
+        flags |= VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY;
+    if (vshCommandOptBool(cmd, "metadata"))
+        flags |= VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY;
+
+    /* XXX If we wanted, we could emulate DELETE_CHILDREN_ONLY even on
+     * older servers that reject the flag, by manually computing the
+     * list of descendants.  But that's a lot of code to maintain.  */
+    if (virDomainSnapshotDelete(snapshot, flags) == 0) {
+        if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)
+            vshPrint(ctl, _("Domain snapshot %s children deleted\n"), name);
+        else
+            vshPrint(ctl, _("Domain snapshot %s deleted\n"), name);
+    } else {
+        vshError(ctl, _("Failed to delete snapshot %s"), name);
+        goto cleanup;
+    }
+
+    ret = true;
+
+cleanup:
+    if (snapshot)
+        virDomainSnapshotFree(snapshot);
+    if (dom)
+        virDomainFree(dom);
+
+    return ret;
+}
diff --git a/tools/virsh.c b/tools/virsh.c
index cddb6e3..96e9b34 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -2149,1586 +2149,6 @@ cmdQuit(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)
     return true;
 }
 
-/* Helper for snapshot-create and snapshot-create-as */
-static bool
-vshSnapshotCreate(vshControl *ctl, virDomainPtr dom, const char *buffer,
-                  unsigned int flags, const char *from)
-{
-    bool ret = false;
-    virDomainSnapshotPtr snapshot;
-    bool halt = false;
-    char *doc = NULL;
-    xmlDocPtr xml = NULL;
-    xmlXPathContextPtr ctxt = NULL;
-    const char *name = NULL;
-
-    snapshot = virDomainSnapshotCreateXML(dom, buffer, flags);
-
-    /* Emulate --halt on older servers.  */
-    if (!snapshot && last_error->code == VIR_ERR_INVALID_ARG &&
-        (flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT)) {
-        int persistent;
-
-        virFreeError(last_error);
-        last_error = NULL;
-        persistent = virDomainIsPersistent(dom);
-        if (persistent < 0) {
-            virshReportError(ctl);
-            goto cleanup;
-        }
-        if (!persistent) {
-            vshError(ctl, "%s",
-                     _("cannot halt after snapshot of transient domain"));
-            goto cleanup;
-        }
-        if (virDomainIsActive(dom) == 1)
-            halt = true;
-        flags &= ~VIR_DOMAIN_SNAPSHOT_CREATE_HALT;
-        snapshot = virDomainSnapshotCreateXML(dom, buffer, flags);
-    }
-
-    if (snapshot == NULL)
-        goto cleanup;
-
-    if (halt && virDomainDestroy(dom) < 0) {
-        virshReportError(ctl);
-        goto cleanup;
-    }
-
-    name = virDomainSnapshotGetName(snapshot);
-    if (!name) {
-        vshError(ctl, "%s", _("Could not get snapshot name"));
-        goto cleanup;
-    }
-
-    if (from)
-        vshPrint(ctl, _("Domain snapshot %s created from '%s'"), name, from);
-    else
-        vshPrint(ctl, _("Domain snapshot %s created"), name);
-
-    ret = true;
-
-cleanup:
-    xmlXPathFreeContext(ctxt);
-    xmlFreeDoc(xml);
-    if (snapshot)
-        virDomainSnapshotFree(snapshot);
-    VIR_FREE(doc);
-    return ret;
-}
-
-/*
- * "snapshot-create" command
- */
-static const vshCmdInfo info_snapshot_create[] = {
-    {"help", N_("Create a snapshot from XML")},
-    {"desc", N_("Create a snapshot (disk and RAM) from XML")},
-    {NULL, NULL}
-};
-
-static const vshCmdOptDef opts_snapshot_create[] = {
-    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
-    {"xmlfile", VSH_OT_DATA, 0, N_("domain snapshot XML")},
-    {"redefine", VSH_OT_BOOL, 0, N_("redefine metadata for existing snapshot")},
-    {"current", VSH_OT_BOOL, 0, N_("with redefine, set current snapshot")},
-    {"no-metadata", VSH_OT_BOOL, 0, N_("take snapshot but create no metadata")},
-    {"halt", VSH_OT_BOOL, 0, N_("halt domain after snapshot is created")},
-    {"disk-only", VSH_OT_BOOL, 0, N_("capture disk state but not vm state")},
-    {"reuse-external", VSH_OT_BOOL, 0, N_("reuse any existing external files")},
-    {"quiesce", VSH_OT_BOOL, 0, N_("quiesce guest's file systems")},
-    {"atomic", VSH_OT_BOOL, 0, N_("require atomic operation")},
-    {NULL, 0, 0, NULL}
-};
-
-static bool
-cmdSnapshotCreate(vshControl *ctl, const vshCmd *cmd)
-{
-    virDomainPtr dom = NULL;
-    bool ret = false;
-    const char *from = NULL;
-    char *buffer = NULL;
-    unsigned int flags = 0;
-
-    if (vshCommandOptBool(cmd, "redefine"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE;
-    if (vshCommandOptBool(cmd, "current"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT;
-    if (vshCommandOptBool(cmd, "no-metadata"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA;
-    if (vshCommandOptBool(cmd, "halt"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT;
-    if (vshCommandOptBool(cmd, "disk-only"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
-    if (vshCommandOptBool(cmd, "reuse-external"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT;
-    if (vshCommandOptBool(cmd, "quiesce"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE;
-    if (vshCommandOptBool(cmd, "atomic"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC;
-
-    if (!vshConnectionUsability(ctl, ctl->conn))
-        goto cleanup;
-
-    dom = vshCommandOptDomain(ctl, cmd, NULL);
-    if (dom == NULL)
-        goto cleanup;
-
-    if (vshCommandOptString(cmd, "xmlfile", &from) <= 0)
-        buffer = vshStrdup(ctl, "<domainsnapshot/>");
-    else {
-        if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) {
-            /* we have to report the error here because during cleanup
-             * we'll run through virDomainFree(), which loses the
-             * last error
-             */
-            virshReportError(ctl);
-            goto cleanup;
-        }
-    }
-    if (buffer == NULL) {
-        vshError(ctl, "%s", _("Out of memory"));
-        goto cleanup;
-    }
-
-    ret = vshSnapshotCreate(ctl, dom, buffer, flags, from);
-
-cleanup:
-    VIR_FREE(buffer);
-    if (dom)
-        virDomainFree(dom);
-
-    return ret;
-}
-
-/*
- * "snapshot-create-as" command
- */
-static int
-vshParseSnapshotDiskspec(vshControl *ctl, virBufferPtr buf, const char *str)
-{
-    int ret = -1;
-    char *name = NULL;
-    char *snapshot = NULL;
-    char *driver = NULL;
-    char *file = NULL;
-    char *spec = vshStrdup(ctl, str);
-    char *tmp = spec;
-    size_t len = strlen(str);
-
-    if (*str == ',')
-        goto cleanup;
-    name = tmp;
-    while ((tmp = strchr(tmp, ','))) {
-        if (tmp[1] == ',') {
-            /* Recognize ,, as an escape for a literal comma */
-            memmove(&tmp[1], &tmp[2], len - (tmp - spec) - 2 + 1);
-            len--;
-            tmp++;
-            continue;
-        }
-        /* Terminate previous string, look for next recognized one */
-        *tmp++ = '\0';
-        if (!snapshot && STRPREFIX(tmp, "snapshot="))
-            snapshot = tmp + strlen("snapshot=");
-        else if (!driver && STRPREFIX(tmp, "driver="))
-            driver = tmp + strlen("driver=");
-        else if (!file && STRPREFIX(tmp, "file="))
-            file = tmp + strlen("file=");
-        else
-            goto cleanup;
-    }
-
-    virBufferEscapeString(buf, "    <disk name='%s'", name);
-    if (snapshot)
-        virBufferAsprintf(buf, " snapshot='%s'", snapshot);
-    if (driver || file) {
-        virBufferAddLit(buf, ">\n");
-        if (driver)
-            virBufferAsprintf(buf, "      <driver type='%s'/>\n", driver);
-        if (file)
-            virBufferEscapeString(buf, "      <source file='%s'/>\n", file);
-        virBufferAddLit(buf, "    </disk>\n");
-    } else {
-        virBufferAddLit(buf, "/>\n");
-    }
-    ret = 0;
-cleanup:
-    if (ret < 0)
-        vshError(ctl, _("unable to parse diskspec: %s"), str);
-    VIR_FREE(spec);
-    return ret;
-}
-
-static const vshCmdInfo info_snapshot_create_as[] = {
-    {"help", N_("Create a snapshot from a set of args")},
-    {"desc", N_("Create a snapshot (disk and RAM) from arguments")},
-    {NULL, NULL}
-};
-
-static const vshCmdOptDef opts_snapshot_create_as[] = {
-    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
-    {"name", VSH_OT_DATA, 0, N_("name of snapshot")},
-    {"description", VSH_OT_DATA, 0, N_("description of snapshot")},
-    {"print-xml", VSH_OT_BOOL, 0, N_("print XML document rather than create")},
-    {"no-metadata", VSH_OT_BOOL, 0, N_("take snapshot but create no metadata")},
-    {"halt", VSH_OT_BOOL, 0, N_("halt domain after snapshot is created")},
-    {"disk-only", VSH_OT_BOOL, 0, N_("capture disk state but not vm state")},
-    {"reuse-external", VSH_OT_BOOL, 0, N_("reuse any existing external files")},
-    {"quiesce", VSH_OT_BOOL, 0, N_("quiesce guest's file systems")},
-    {"atomic", VSH_OT_BOOL, 0, N_("require atomic operation")},
-    {"diskspec", VSH_OT_ARGV, 0,
-     N_("disk attributes: disk[,snapshot=type][,driver=type][,file=name]")},
-    {NULL, 0, 0, NULL}
-};
-
-static bool
-cmdSnapshotCreateAs(vshControl *ctl, const vshCmd *cmd)
-{
-    virDomainPtr dom = NULL;
-    bool ret = false;
-    char *buffer = NULL;
-    const char *name = NULL;
-    const char *desc = NULL;
-    virBuffer buf = VIR_BUFFER_INITIALIZER;
-    unsigned int flags = 0;
-    const vshCmdOpt *opt = NULL;
-
-    if (vshCommandOptBool(cmd, "no-metadata"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA;
-    if (vshCommandOptBool(cmd, "halt"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_HALT;
-    if (vshCommandOptBool(cmd, "disk-only"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
-    if (vshCommandOptBool(cmd, "reuse-external"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT;
-    if (vshCommandOptBool(cmd, "quiesce"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE;
-    if (vshCommandOptBool(cmd, "atomic"))
-        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_ATOMIC;
-
-    if (!vshConnectionUsability(ctl, ctl->conn))
-        goto cleanup;
-
-    dom = vshCommandOptDomain(ctl, cmd, NULL);
-    if (dom == NULL)
-        goto cleanup;
-
-    if (vshCommandOptString(cmd, "name", &name) < 0 ||
-        vshCommandOptString(cmd, "description", &desc) < 0) {
-        vshError(ctl, _("argument must not be empty"));
-        goto cleanup;
-    }
-
-    virBufferAddLit(&buf, "<domainsnapshot>\n");
-    if (name)
-        virBufferEscapeString(&buf, "  <name>%s</name>\n", name);
-    if (desc)
-        virBufferEscapeString(&buf, "  <description>%s</description>\n", desc);
-    if (vshCommandOptBool(cmd, "diskspec")) {
-        virBufferAddLit(&buf, "  <disks>\n");
-        while ((opt = vshCommandOptArgv(cmd, opt))) {
-            if (vshParseSnapshotDiskspec(ctl, &buf, opt->data) < 0) {
-                virBufferFreeAndReset(&buf);
-                goto cleanup;
-            }
-        }
-        virBufferAddLit(&buf, "  </disks>\n");
-    }
-    virBufferAddLit(&buf, "</domainsnapshot>\n");
-
-    buffer = virBufferContentAndReset(&buf);
-    if (buffer == NULL) {
-        vshError(ctl, "%s", _("Out of memory"));
-        goto cleanup;
-    }
-
-    if (vshCommandOptBool(cmd, "print-xml")) {
-        vshPrint(ctl, "%s\n",  buffer);
-        ret = true;
-        goto cleanup;
-    }
-
-    ret = vshSnapshotCreate(ctl, dom, buffer, flags, NULL);
-
-cleanup:
-    VIR_FREE(buffer);
-    if (dom)
-        virDomainFree(dom);
-
-    return ret;
-}
-
-/* Helper for resolving {--current | --ARG name} into a snapshot
- * belonging to DOM.  If EXCLUSIVE, fail if both --current and arg are
- * present.  On success, populate *SNAP and *NAME, before returning 0.
- * On failure, return -1 after issuing an error message.  */
-static int
-vshLookupSnapshot(vshControl *ctl, const vshCmd *cmd,
-                  const char *arg, bool exclusive, virDomainPtr dom,
-                  virDomainSnapshotPtr *snap, const char **name)
-{
-    bool current = vshCommandOptBool(cmd, "current");
-    const char *snapname = NULL;
-
-    if (vshCommandOptString(cmd, arg, &snapname) < 0) {
-        vshError(ctl, _("invalid argument for --%s"), arg);
-        return -1;
-    }
-
-    if (exclusive && current && snapname) {
-        vshError(ctl, _("--%s and --current are mutually exclusive"), arg);
-        return -1;
-    }
-
-    if (snapname) {
-        *snap = virDomainSnapshotLookupByName(dom, snapname, 0);
-    } else if (current) {
-        *snap = virDomainSnapshotCurrent(dom, 0);
-    } else {
-        vshError(ctl, _("--%s or --current is required"), arg);
-        return -1;
-    }
-    if (!*snap) {
-        virshReportError(ctl);
-        return -1;
-    }
-
-    *name = virDomainSnapshotGetName(*snap);
-    return 0;
-}
-
-/*
- * "snapshot-edit" command
- */
-static const vshCmdInfo info_snapshot_edit[] = {
-    {"help", N_("edit XML for a snapshot")},
-    {"desc", N_("Edit the domain snapshot XML for a named snapshot")},
-    {NULL, NULL}
-};
-
-static const vshCmdOptDef opts_snapshot_edit[] = {
-    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
-    {"snapshotname", VSH_OT_DATA, 0, N_("snapshot name")},
-    {"current", VSH_OT_BOOL, 0, N_("also set edited snapshot as current")},
-    {"rename", VSH_OT_BOOL, 0, N_("allow renaming an existing snapshot")},
-    {"clone", VSH_OT_BOOL, 0, N_("allow cloning to new name")},
-    {NULL, 0, 0, NULL}
-};
-
-static bool
-cmdSnapshotEdit(vshControl *ctl, const vshCmd *cmd)
-{
-    virDomainPtr dom = NULL;
-    virDomainSnapshotPtr snapshot = NULL;
-    virDomainSnapshotPtr edited = NULL;
-    const char *name;
-    const char *edited_name;
-    bool ret = false;
-    unsigned int getxml_flags = VIR_DOMAIN_XML_SECURE;
-    unsigned int define_flags = VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE;
-    bool rename_okay = vshCommandOptBool(cmd, "rename");
-    bool clone_okay = vshCommandOptBool(cmd, "clone");
-
-    if (rename_okay && clone_okay) {
-        vshError(ctl, "%s",
-                 _("--rename and --clone are mutually exclusive"));
-        return false;
-    }
-
-    if (vshCommandOptBool(cmd, "current") &&
-        vshCommandOptBool(cmd, "snapshotname"))
-        define_flags |= VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT;
-
-    if (!vshConnectionUsability(ctl, ctl->conn))
-        return false;
-
-    dom = vshCommandOptDomain(ctl, cmd, NULL);
-    if (dom == NULL)
-        goto cleanup;
-
-    if (vshLookupSnapshot(ctl, cmd, "snapshotname", false, dom,
-                          &snapshot, &name) < 0)
-        goto cleanup;
-
-#define EDIT_GET_XML \
-    virDomainSnapshotGetXMLDesc(snapshot, getxml_flags)
-#define EDIT_NOT_CHANGED \
-    /* Depending on flags, we re-edit even if XML is unchanged.  */ \
-    if (!(define_flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT)) {     \
-        vshPrint(ctl,                                               \
-                 _("Snapshot %s XML configuration not changed.\n"), \
-                 name);                                             \
-        ret = true;                                                 \
-        goto cleanup;                                               \
-    }
-#define EDIT_DEFINE \
-    (strstr(doc, "<state>disk-snapshot</state>") ? \
-    define_flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY : 0), \
-    edited = virDomainSnapshotCreateXML(dom, doc_edited, define_flags)
-#define EDIT_FREE \
-    if (edited) \
-        virDomainSnapshotFree(edited);
-#include "virsh-edit.c"
-
-    edited_name = virDomainSnapshotGetName(edited);
-    if (STREQ(name, edited_name)) {
-        vshPrint(ctl, _("Snapshot %s edited.\n"), name);
-    } else if (clone_okay) {
-        vshPrint(ctl, _("Snapshot %s cloned to %s.\n"), name,
-                 edited_name);
-    } else {
-        unsigned int delete_flags;
-
-        delete_flags = VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY;
-        if (virDomainSnapshotDelete(rename_okay ? snapshot : edited,
-                                    delete_flags) < 0) {
-            virshReportError(ctl);
-            vshError(ctl, _("Failed to clean up %s"),
-                     rename_okay ? name : edited_name);
-            goto cleanup;
-        }
-        if (!rename_okay) {
-            vshError(ctl, _("Must use --rename or --clone to change %s to %s"),
-                     name, edited_name);
-            goto cleanup;
-        }
-    }
-
-    ret = true;
-
-cleanup:
-    if (edited)
-        virDomainSnapshotFree(edited);
-    else
-        vshError(ctl, _("Failed to update %s"), name);
-    if (snapshot)
-        virDomainSnapshotFree(snapshot);
-    if (dom)
-        virDomainFree(dom);
-    return ret;
-}
-
-/*
- * "snapshot-current" command
- */
-static const vshCmdInfo info_snapshot_current[] = {
-    {"help", N_("Get or set the current snapshot")},
-    {"desc", N_("Get or set the current snapshot")},
-    {NULL, NULL}
-};
-
-static const vshCmdOptDef opts_snapshot_current[] = {
-    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
-    {"name", VSH_OT_BOOL, 0, N_("list the name, rather than the full xml")},
-    {"security-info", VSH_OT_BOOL, 0,
-     N_("include security sensitive information in XML dump")},
-    {"snapshotname", VSH_OT_DATA, 0,
-     N_("name of existing snapshot to make current")},
-    {NULL, 0, 0, NULL}
-};
-
-static bool
-cmdSnapshotCurrent(vshControl *ctl, const vshCmd *cmd)
-{
-    virDomainPtr dom = NULL;
-    bool ret = false;
-    int current;
-    virDomainSnapshotPtr snapshot = NULL;
-    char *xml = NULL;
-    const char *snapshotname = NULL;
-    unsigned int flags = 0;
-    const char *domname;
-
-    if (vshCommandOptBool(cmd, "security-info"))
-        flags |= VIR_DOMAIN_XML_SECURE;
-
-    if (!vshConnectionUsability(ctl, ctl->conn))
-        goto cleanup;
-
-    dom = vshCommandOptDomain(ctl, cmd, &domname);
-    if (dom == NULL)
-        goto cleanup;
-
-    if (vshCommandOptString(cmd, "snapshotname", &snapshotname) < 0) {
-        vshError(ctl, _("invalid snapshotname argument '%s'"), snapshotname);
-        goto cleanup;
-    }
-    if (snapshotname) {
-        virDomainSnapshotPtr snapshot2 = NULL;
-        flags = (VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE |
-                 VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT);
-
-        if (vshCommandOptBool(cmd, "name")) {
-            vshError(ctl, "%s",
-                     _("--name and snapshotname are mutually exclusive"));
-            goto cleanup;
-        }
-        snapshot = virDomainSnapshotLookupByName(dom, snapshotname, 0);
-        if (snapshot == NULL)
-            goto cleanup;
-        xml = virDomainSnapshotGetXMLDesc(snapshot, VIR_DOMAIN_XML_SECURE);
-        if (!xml)
-            goto cleanup;
-        /* strstr is safe here, since xml came from libvirt API and not user */
-        if (strstr(xml, "<state>disk-snapshot</state>"))
-            flags |= VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY;
-        snapshot2 = virDomainSnapshotCreateXML(dom, xml, flags);
-        if (snapshot2 == NULL)
-            goto cleanup;
-        virDomainSnapshotFree(snapshot2);
-        vshPrint(ctl, _("Snapshot %s set as current"), snapshotname);
-        ret = true;
-        goto cleanup;
-    }
-
-    current = virDomainHasCurrentSnapshot(dom, 0);
-    if (current < 0) {
-        goto cleanup;
-    } else if (!current) {
-        vshError(ctl, _("domain '%s' has no current snapshot"), domname);
-        goto cleanup;
-    } else {
-        const char *name = NULL;
-
-        if (!(snapshot = virDomainSnapshotCurrent(dom, 0)))
-            goto cleanup;
-
-        if (vshCommandOptBool(cmd, "name")) {
-            name = virDomainSnapshotGetName(snapshot);
-            if (!name)
-                goto cleanup;
-        } else {
-            xml = virDomainSnapshotGetXMLDesc(snapshot, flags);
-            if (!xml)
-                goto cleanup;
-        }
-
-        vshPrint(ctl, "%s", name ? name : xml);
-    }
-
-    ret = true;
-
-cleanup:
-    if (!ret)
-        virshReportError(ctl);
-    VIR_FREE(xml);
-    if (snapshot)
-        virDomainSnapshotFree(snapshot);
-    if (dom)
-        virDomainFree(dom);
-
-    return ret;
-}
-
-/* Helper function to get the name of a snapshot's parent.  Caller
- * must free the result.  Returns 0 on success (including when it was
- * proven no parent exists), and -1 on failure with error reported
- * (such as no snapshot support or domain deleted in meantime).  */
-static int
-vshGetSnapshotParent(vshControl *ctl, virDomainSnapshotPtr snapshot,
-                     char **parent_name)
-{
-    virDomainSnapshotPtr parent = NULL;
-    char *xml = NULL;
-    xmlDocPtr xmldoc = NULL;
-    xmlXPathContextPtr ctxt = NULL;
-    int ret = -1;
-
-    *parent_name = NULL;
-
-    /* Try new API, since it is faster. */
-    if (!ctl->useSnapshotOld) {
-        parent = virDomainSnapshotGetParent(snapshot, 0);
-        if (parent) {
-            /* API works, and virDomainSnapshotGetName will succeed */
-            *parent_name = vshStrdup(ctl, virDomainSnapshotGetName(parent));
-            ret = 0;
-            goto cleanup;
-        }
-        if (last_error->code == VIR_ERR_NO_DOMAIN_SNAPSHOT) {
-            /* API works, and we found a root with no parent */
-            ret = 0;
-            goto cleanup;
-        }
-        /* API didn't work, fall back to XML scraping. */
-        ctl->useSnapshotOld = true;
-    }
-
-    xml = virDomainSnapshotGetXMLDesc(snapshot, 0);
-    if (!xml)
-        goto cleanup;
-
-    xmldoc = virXMLParseStringCtxt(xml, _("(domain_snapshot)"), &ctxt);
-    if (!xmldoc)
-        goto cleanup;
-
-    *parent_name = virXPathString("string(/domainsnapshot/parent/name)", ctxt);
-    ret = 0;
-
-cleanup:
-    if (ret < 0) {
-        virshReportError(ctl);
-        vshError(ctl, "%s", _("unable to determine if snapshot has parent"));
-    } else {
-        virFreeError(last_error);
-        last_error = NULL;
-    }
-    if (parent)
-        virDomainSnapshotFree(parent);
-    xmlXPathFreeContext(ctxt);
-    xmlFreeDoc(xmldoc);
-    VIR_FREE(xml);
-    return ret;
-}
-
-/*
- * "snapshot-info" command
- */
-static const vshCmdInfo info_snapshot_info[] = {
-    {"help", N_("snapshot information")},
-    {"desc", N_("Returns basic information about a snapshot.")},
-    {NULL, NULL}
-};
-
-static const vshCmdOptDef opts_snapshot_info[] = {
-    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
-    {"snapshotname", VSH_OT_DATA, 0, N_("snapshot name")},
-    {"current", VSH_OT_BOOL, 0, N_("info on current snapshot")},
-    {NULL, 0, 0, NULL}
-};
-
-static bool
-cmdSnapshotInfo(vshControl *ctl, const vshCmd *cmd)
-{
-    virDomainPtr dom;
-    virDomainSnapshotPtr snapshot = NULL;
-    const char *name;
-    char *doc = NULL;
-    char *tmp;
-    char *parent = NULL;
-    bool ret = false;
-    int count;
-    unsigned int flags;
-    int current;
-    int metadata;
-
-    if (!vshConnectionUsability(ctl, ctl->conn))
-        return false;
-
-    dom = vshCommandOptDomain(ctl, cmd, NULL);
-    if (dom == NULL)
-        return false;
-
-    if (vshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
-                          &snapshot, &name) < 0)
-        goto cleanup;
-
-    vshPrint(ctl, "%-15s %s\n", _("Name:"), name);
-    vshPrint(ctl, "%-15s %s\n", _("Domain:"), virDomainGetName(dom));
-
-    /* Determine if snapshot is current; this is useful enough that we
-     * attempt a fallback.  */
-    current = virDomainSnapshotIsCurrent(snapshot, 0);
-    if (current < 0) {
-        virDomainSnapshotPtr other = virDomainSnapshotCurrent(dom, 0);
-
-        virResetLastError();
-        current = 0;
-        if (other) {
-            if (STREQ(name, virDomainSnapshotGetName(other)))
-                current = 1;
-            virDomainSnapshotFree(other);
-        }
-    }
-    vshPrint(ctl, "%-15s %s\n", _("Current:"),
-             current > 0 ? _("yes") : _("no"));
-
-    /* Get the XML configuration of the snapshot to determine the
-     * state of the machine at the time of the snapshot.  */
-    doc = virDomainSnapshotGetXMLDesc(snapshot, 0);
-    if (!doc)
-        goto cleanup;
-
-    tmp = strstr(doc, "<state>");
-    if (!tmp) {
-        vshError(ctl, "%s",
-                 _("unexpected problem reading snapshot xml"));
-        goto cleanup;
-    }
-    tmp += strlen("<state>");
-    vshPrint(ctl, "%-15s %.*s\n", _("State:"),
-             (int) (strchr(tmp, '<') - tmp), tmp);
-
-    if (vshGetSnapshotParent(ctl, snapshot, &parent) < 0)
-        goto cleanup;
-    vshPrint(ctl, "%-15s %s\n", _("Parent:"), parent ? parent : "-");
-
-    /* Children, Descendants.  After this point, the fallback to
-     * compute children is too expensive, so we gracefully quit if the
-     * APIs don't exist.  */
-    if (ctl->useSnapshotOld) {
-        ret = true;
-        goto cleanup;
-    }
-    flags = 0;
-    count = virDomainSnapshotNumChildren(snapshot, flags);
-    if (count < 0)
-        goto cleanup;
-    vshPrint(ctl, "%-15s %d\n", _("Children:"), count);
-    flags = VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
-    count = virDomainSnapshotNumChildren(snapshot, flags);
-    if (count < 0)
-        goto cleanup;
-    vshPrint(ctl, "%-15s %d\n", _("Descendants:"), count);
-
-    /* Metadata; the fallback here relies on the fact that metadata
-     * used to have an all-or-nothing effect on snapshot count.  */
-    metadata = virDomainSnapshotHasMetadata(snapshot, 0);
-    if (metadata < 0) {
-        metadata = virDomainSnapshotNum(dom,
-                                        VIR_DOMAIN_SNAPSHOT_LIST_METADATA);
-        virResetLastError();
-    }
-    if (metadata >= 0)
-        vshPrint(ctl, "%-15s %s\n", _("Metadata:"),
-                 metadata ? _("yes") : _("no"));
-
-    ret = true;
-
-cleanup:
-    VIR_FREE(doc);
-    VIR_FREE(parent);
-    if (snapshot)
-        virDomainSnapshotFree(snapshot);
-    virDomainFree(dom);
-    return ret;
-}
-
-/* Helpers for collecting a list of snapshots.  */
-struct vshSnap {
-    virDomainSnapshotPtr snap;
-    char *parent;
-};
-struct vshSnapshotList {
-    struct vshSnap *snaps;
-    int nsnaps;
-};
-typedef struct vshSnapshotList *vshSnapshotListPtr;
-
-static void
-vshSnapshotListFree(vshSnapshotListPtr snaplist)
-{
-    int i;
-
-    if (!snaplist)
-        return;
-    if (snaplist->snaps) {
-        for (i = 0; i < snaplist->nsnaps; i++) {
-            if (snaplist->snaps[i].snap)
-                virDomainSnapshotFree(snaplist->snaps[i].snap);
-            VIR_FREE(snaplist->snaps[i].parent);
-        }
-        VIR_FREE(snaplist->snaps);
-    }
-    VIR_FREE(snaplist);
-}
-
-static int
-vshSnapSorter(const void *a, const void *b)
-{
-    const struct vshSnap *sa = a;
-    const struct vshSnap *sb = b;
-
-    if (sa->snap && !sb->snap)
-        return -1;
-    if (!sa->snap)
-        return sb->snap != NULL;
-
-    /* User visible sort, so we want locale-specific case comparison.  */
-    return strcasecmp(virDomainSnapshotGetName(sa->snap),
-                      virDomainSnapshotGetName(sb->snap));
-}
-
-/* Compute a list of snapshots from DOM.  If FROM is provided, the
- * list is limited to descendants of the given snapshot.  If FLAGS is
- * given, the list is filtered.  If TREE is specified, then all but
- * FROM or the roots will also have parent information.  */
-static vshSnapshotListPtr
-vshSnapshotListCollect(vshControl *ctl, virDomainPtr dom,
-                       virDomainSnapshotPtr from,
-                       unsigned int flags, bool tree)
-{
-    int i;
-    char **names = NULL;
-    int count = -1;
-    bool descendants = false;
-    bool roots = false;
-    virDomainSnapshotPtr *snaps;
-    vshSnapshotListPtr snaplist = vshMalloc(ctl, sizeof(*snaplist));
-    vshSnapshotListPtr ret = NULL;
-    const char *fromname = NULL;
-    int start_index = -1;
-    int deleted = 0;
-
-    /* Try the interface available in 0.9.13 and newer.  */
-    if (!ctl->useSnapshotOld) {
-        if (from)
-            count = virDomainSnapshotListAllChildren(from, &snaps, flags);
-        else
-            count = virDomainListAllSnapshots(dom, &snaps, flags);
-    }
-    if (count >= 0) {
-        /* When mixing --from and --tree, we also want a copy of from
-         * in the list, but with no parent for that one entry.  */
-        snaplist->snaps = vshCalloc(ctl, count + (tree && from),
-                                    sizeof(*snaplist->snaps));
-        snaplist->nsnaps = count;
-        for (i = 0; i < count; i++)
-            snaplist->snaps[i].snap = snaps[i];
-        VIR_FREE(snaps);
-        if (tree) {
-            for (i = 0; i < count; i++) {
-                if (vshGetSnapshotParent(ctl, snaplist->snaps[i].snap,
-                                         &snaplist->snaps[i].parent) < 0)
-                    goto cleanup;
-            }
-            if (from) {
-                snaps[snaplist->nsnaps++] = from;
-                virDomainSnapshotRef(from);
-            }
-        }
-        goto success;
-    }
-
-    /* Assume that if we got this far, then the --no-leaves and
-     * --no-metadata flags were not supported.  Disable groups that
-     * have no impact.  */
-    /* XXX should we emulate --no-leaves?  */
-    if (flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES &&
-        flags & VIR_DOMAIN_SNAPSHOT_LIST_LEAVES)
-        flags &= ~(VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES |
-                   VIR_DOMAIN_SNAPSHOT_LIST_LEAVES);
-    if (flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA &&
-        flags & VIR_DOMAIN_SNAPSHOT_LIST_METADATA)
-        flags &= ~(VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA |
-                   VIR_DOMAIN_SNAPSHOT_LIST_METADATA);
-    if (flags & VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA) {
-        /* We can emulate --no-metadata if --metadata was supported,
-         * since it was an all-or-none attribute on old servers.  */
-        count = virDomainSnapshotNum(dom,
-                                     VIR_DOMAIN_SNAPSHOT_LIST_METADATA);
-        if (count < 0)
-            goto cleanup;
-        if (count > 0)
-            return snaplist;
-        flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA;
-    }
-
-    /* This uses the interfaces available in 0.8.0-0.9.6
-     * (virDomainSnapshotListNames, global list only) and in
-     * 0.9.7-0.9.12 (addition of virDomainSnapshotListChildrenNames
-     * for child listing, and new flags), as follows, with [*] by the
-     * combinations that need parent info (either for filtering
-     * purposes or for the resulting tree listing):
-     *                              old               new
-     * list                         global as-is      global as-is
-     * list --roots                *global + filter   global + flags
-     * list --from                 *global + filter   child as-is
-     * list --from --descendants   *global + filter   child + flags
-     * list --tree                 *global as-is     *global as-is
-     * list --tree --from          *global + filter  *child + flags
-     *
-     * Additionally, when --tree and --from are both used, from is
-     * added to the final list as the only element without a parent.
-     * Otherwise, --from does not appear in the final list.
-     */
-    if (from) {
-        fromname = virDomainSnapshotGetName(from);
-        if (!fromname) {
-            vshError(ctl, "%s", _("Could not get snapshot name"));
-            goto cleanup;
-        }
-        descendants = (flags & VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS) || tree;
-        if (tree)
-            flags |= VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
-
-        /* Determine if we can use the new child listing API.  */
-        if (ctl->useSnapshotOld ||
-            ((count = virDomainSnapshotNumChildren(from, flags)) < 0 &&
-             last_error->code == VIR_ERR_NO_SUPPORT)) {
-            /* We can emulate --from.  */
-            /* XXX can we also emulate --leaves? */
-            virFreeError(last_error);
-            last_error = NULL;
-            ctl->useSnapshotOld = true;
-            flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
-            goto global;
-        }
-        if (tree && count >= 0)
-            count++;
-    } else {
-    global:
-        /* Global listing (including fallback when --from failed with
-         * child listing).  */
-        count = virDomainSnapshotNum(dom, flags);
-
-        /* Fall back to simulation if --roots was unsupported. */
-        /* XXX can we also emulate --leaves? */
-        if (!from && count < 0 && last_error->code == VIR_ERR_INVALID_ARG &&
-            (flags & VIR_DOMAIN_SNAPSHOT_LIST_ROOTS)) {
-            virFreeError(last_error);
-            last_error = NULL;
-            roots = true;
-            flags &= ~VIR_DOMAIN_SNAPSHOT_LIST_ROOTS;
-            count = virDomainSnapshotNum(dom, flags);
-        }
-    }
-
-    if (count < 0) {
-        if (!last_error)
-            vshError(ctl, _("failed to collect snapshot list"));
-        goto cleanup;
-    }
-
-    if (!count)
-        goto success;
-
-    names = vshCalloc(ctl, sizeof(*names), count);
-
-    /* Now that we have a count, collect the list.  */
-    if (from && !ctl->useSnapshotOld) {
-        if (tree) {
-            if (count)
-                count = virDomainSnapshotListChildrenNames(from, names + 1,
-                                                           count - 1, flags);
-            if (count >= 0) {
-                count++;
-                names[0] = vshStrdup(ctl, fromname);
-            }
-        } else {
-            count = virDomainSnapshotListChildrenNames(from, names,
-                                                       count, flags);
-        }
-    } else {
-        count = virDomainSnapshotListNames(dom, names, count, flags);
-    }
-    if (count < 0)
-        goto cleanup;
-
-    snaplist->snaps = vshCalloc(ctl, sizeof(*snaplist->snaps), count);
-    snaplist->nsnaps = count;
-    for (i = 0; i < count; i++) {
-        snaplist->snaps[i].snap = virDomainSnapshotLookupByName(dom,
-                                                                names[i], 0);
-        if (!snaplist->snaps[i].snap)
-            goto cleanup;
-    }
-
-    /* Collect parents when needed.  With the new API, --tree and
-     * --from together put from as the first element without a parent;
-     * with the old API we still need to do a post-process filtering
-     * based on all parent information.  */
-    if (tree || (from && ctl->useSnapshotOld) || roots) {
-        for (i = (from && !ctl->useSnapshotOld); i < count; i++) {
-            if (from && ctl->useSnapshotOld && STREQ(names[i], fromname)) {
-                start_index = i;
-                if (tree)
-                    continue;
-            }
-            if (vshGetSnapshotParent(ctl, snaplist->snaps[i].snap,
-                                     &snaplist->snaps[i].parent) < 0)
-                goto cleanup;
-            if ((from && ((tree && !snaplist->snaps[i].parent) ||
-                          (!descendants &&
-                           STRNEQ_NULLABLE(fromname,
-                                           snaplist->snaps[i].parent)))) ||
-                (roots && snaplist->snaps[i].parent)) {
-                virDomainSnapshotFree(snaplist->snaps[i].snap);
-                snaplist->snaps[i].snap = NULL;
-                VIR_FREE(snaplist->snaps[i].parent);
-                deleted++;
-            }
-        }
-    }
-    if (tree)
-        goto success;
-
-    if (ctl->useSnapshotOld && descendants) {
-        bool changed = false;
-        bool remaining = false;
-
-        /* Make multiple passes over the list - first pass finds
-         * direct children and NULLs out all roots and from, remaining
-         * passes NULL out any undecided entry whose parent is not
-         * still in list.  We mark known descendants by clearing
-         * snaps[i].parents.  Sorry, this is O(n^3) - hope your
-         * hierarchy isn't huge.  XXX Is it worth making O(n^2 log n)
-         * by using qsort and bsearch?  */
-        if (start_index < 0) {
-            vshError(ctl, _("snapshot %s disappeared from list"), fromname);
-            goto cleanup;
-        }
-        for (i = 0; i < count; i++) {
-            if (i == start_index || !snaplist->snaps[i].parent) {
-                VIR_FREE(names[i]);
-                virDomainSnapshotFree(snaplist->snaps[i].snap);
-                snaplist->snaps[i].snap = NULL;
-                VIR_FREE(snaplist->snaps[i].parent);
-                deleted++;
-            } else if (STREQ(snaplist->snaps[i].parent, fromname)) {
-                VIR_FREE(snaplist->snaps[i].parent);
-                changed = true;
-            } else {
-                remaining = true;
-            }
-        }
-        if (!changed) {
-            ret = vshMalloc(ctl, sizeof(*snaplist));
-            goto cleanup;
-        }
-        while (changed && remaining) {
-            changed = remaining = false;
-            for (i = 0; i < count; i++) {
-                bool found_parent = false;
-                int j;
-
-                if (!names[i] || !snaplist->snaps[i].parent)
-                    continue;
-                for (j = 0; j < count; j++) {
-                    if (!names[j] || i == j)
-                        continue;
-                    if (STREQ(snaplist->snaps[i].parent, names[j])) {
-                        found_parent = true;
-                        if (!snaplist->snaps[j].parent)
-                            VIR_FREE(snaplist->snaps[i].parent);
-                        else
-                            remaining = true;
-                        break;
-                    }
-                }
-                if (!found_parent) {
-                    changed = true;
-                    VIR_FREE(names[i]);
-                    virDomainSnapshotFree(snaplist->snaps[i].snap);
-                    snaplist->snaps[i].snap = NULL;
-                    VIR_FREE(snaplist->snaps[i].parent);
-                    deleted++;
-                }
-            }
-        }
-    }
-
-success:
-    qsort(snaplist->snaps, snaplist->nsnaps, sizeof(*snaplist->snaps),
-          vshSnapSorter);
-    snaplist->nsnaps -= deleted;
-
-    ret = snaplist;
-    snaplist = NULL;
-
-cleanup:
-    vshSnapshotListFree(snaplist);
-    if (names)
-        for (i = 0; i < count; i++)
-            VIR_FREE(names[i]);
-    VIR_FREE(names);
-    return ret;
-}
-
-static const char *
-vshSnapshotListLookup(int id, bool parent, void *opaque)
-{
-    vshSnapshotListPtr snaplist = opaque;
-    if (parent)
-        return snaplist->snaps[id].parent;
-    return virDomainSnapshotGetName(snaplist->snaps[id].snap);
-}
-
-/*
- * "snapshot-list" command
- */
-static const vshCmdInfo info_snapshot_list[] = {
-    {"help", N_("List snapshots for a domain")},
-    {"desc", N_("Snapshot List")},
-    {NULL, NULL}
-};
-
-static const vshCmdOptDef opts_snapshot_list[] = {
-    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
-    {"parent", VSH_OT_BOOL, 0, N_("add a column showing parent snapshot")},
-    {"roots", VSH_OT_BOOL, 0, N_("list only snapshots without parents")},
-    {"leaves", VSH_OT_BOOL, 0, N_("list only snapshots without children")},
-    {"no-leaves", VSH_OT_BOOL, 0,
-     N_("list only snapshots that are not leaves (with children)")},
-    {"metadata", VSH_OT_BOOL, 0,
-     N_("list only snapshots that have metadata that would prevent undefine")},
-    {"no-metadata", VSH_OT_BOOL, 0,
-     N_("list only snapshots that have no metadata managed by libvirt")},
-    {"tree", VSH_OT_BOOL, 0, N_("list snapshots in a tree")},
-    {"from", VSH_OT_DATA, 0, N_("limit list to children of given snapshot")},
-    {"current", VSH_OT_BOOL, 0,
-     N_("limit list to children of current snapshot")},
-    {"descendants", VSH_OT_BOOL, 0, N_("with --from, list all descendants")},
-    {NULL, 0, 0, NULL}
-};
-
-static bool
-cmdSnapshotList(vshControl *ctl, const vshCmd *cmd)
-{
-    virDomainPtr dom = NULL;
-    bool ret = false;
-    unsigned int flags = 0;
-    bool show_parent = false;
-    int i;
-    xmlDocPtr xml = NULL;
-    xmlXPathContextPtr ctxt = NULL;
-    char *doc = NULL;
-    virDomainSnapshotPtr snapshot = NULL;
-    char *state = NULL;
-    char *parent = NULL;
-    long long creation_longlong;
-    time_t creation_time_t;
-    char timestr[100];
-    struct tm time_info;
-    bool tree = vshCommandOptBool(cmd, "tree");
-    bool leaves = vshCommandOptBool(cmd, "leaves");
-    bool no_leaves = vshCommandOptBool(cmd, "no-leaves");
-    const char *from = NULL;
-    virDomainSnapshotPtr start = NULL;
-    vshSnapshotListPtr snaplist = NULL;
-
-    if (!vshConnectionUsability(ctl, ctl->conn))
-        goto cleanup;
-
-    dom = vshCommandOptDomain(ctl, cmd, NULL);
-    if (dom == NULL)
-        goto cleanup;
-
-    if ((vshCommandOptBool(cmd, "from") ||
-         vshCommandOptBool(cmd, "current")) &&
-        vshLookupSnapshot(ctl, cmd, "from", true, dom, &start, &from) < 0)
-        goto cleanup;
-
-    if (vshCommandOptBool(cmd, "parent")) {
-        if (vshCommandOptBool(cmd, "roots")) {
-            vshError(ctl, "%s",
-                     _("--parent and --roots are mutually exclusive"));
-            goto cleanup;
-        }
-        if (tree) {
-            vshError(ctl, "%s",
-                     _("--parent and --tree are mutually exclusive"));
-            goto cleanup;
-        }
-        show_parent = true;
-    } else if (vshCommandOptBool(cmd, "roots")) {
-        if (tree) {
-            vshError(ctl, "%s",
-                     _("--roots and --tree are mutually exclusive"));
-            goto cleanup;
-        }
-        if (from) {
-            vshError(ctl, "%s",
-                     _("--roots and --from are mutually exclusive"));
-            goto cleanup;
-        }
-        flags |= VIR_DOMAIN_SNAPSHOT_LIST_ROOTS;
-    }
-    if (leaves) {
-        if (tree) {
-            vshError(ctl, "%s",
-                     _("--leaves and --tree are mutually exclusive"));
-            goto cleanup;
-        }
-        flags |= VIR_DOMAIN_SNAPSHOT_LIST_LEAVES;
-    }
-    if (no_leaves) {
-        if (tree) {
-            vshError(ctl, "%s",
-                     _("--no-leaves and --tree are mutually exclusive"));
-            goto cleanup;
-        }
-        flags |= VIR_DOMAIN_SNAPSHOT_LIST_NO_LEAVES;
-    }
-
-    if (vshCommandOptBool(cmd, "metadata")) {
-        flags |= VIR_DOMAIN_SNAPSHOT_LIST_METADATA;
-    }
-    if (vshCommandOptBool(cmd, "no-metadata")) {
-        flags |= VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA;
-    }
-
-    if (vshCommandOptBool(cmd, "descendants")) {
-        if (!from) {
-            vshError(ctl, "%s",
-                     _("--descendants requires either --from or --current"));
-            goto cleanup;
-        }
-        flags |= VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
-    }
-
-    if ((snaplist = vshSnapshotListCollect(ctl, dom, start, flags,
-                                           tree)) == NULL)
-        goto cleanup;
-
-    if (!tree) {
-        if (show_parent)
-            vshPrintExtra(ctl, " %-20s %-25s %-15s %s",
-                          _("Name"), _("Creation Time"), _("State"),
-                          _("Parent"));
-        else
-            vshPrintExtra(ctl, " %-20s %-25s %s",
-                          _("Name"), _("Creation Time"), _("State"));
-        vshPrintExtra(ctl, "\n"
-"------------------------------------------------------------\n");
-    }
-
-    if (!snaplist->nsnaps) {
-        ret = true;
-        goto cleanup;
-    }
-
-    if (tree) {
-        for (i = 0; i < snaplist->nsnaps; i++) {
-            if (!snaplist->snaps[i].parent &&
-                vshTreePrint(ctl, vshSnapshotListLookup, snaplist,
-                             snaplist->nsnaps, i) < 0)
-                goto cleanup;
-        }
-        ret = true;
-        goto cleanup;
-    }
-
-    for (i = 0; i < snaplist->nsnaps; i++) {
-        const char *name;
-
-        /* free up memory from previous iterations of the loop */
-        VIR_FREE(parent);
-        VIR_FREE(state);
-        xmlXPathFreeContext(ctxt);
-        xmlFreeDoc(xml);
-        VIR_FREE(doc);
-
-        snapshot = snaplist->snaps[i].snap;
-        name = virDomainSnapshotGetName(snapshot);
-        assert(name);
-
-        doc = virDomainSnapshotGetXMLDesc(snapshot, 0);
-        if (!doc)
-            continue;
-
-        xml = virXMLParseStringCtxt(doc, _("(domain_snapshot)"), &ctxt);
-        if (!xml)
-            continue;
-
-        if (show_parent)
-            parent = virXPathString("string(/domainsnapshot/parent/name)",
-                                    ctxt);
-
-        state = virXPathString("string(/domainsnapshot/state)", ctxt);
-        if (state == NULL)
-            continue;
-        if (virXPathLongLong("string(/domainsnapshot/creationTime)", ctxt,
-                             &creation_longlong) < 0)
-            continue;
-        creation_time_t = creation_longlong;
-        if (creation_time_t != creation_longlong) {
-            vshError(ctl, "%s", _("time_t overflow"));
-            continue;
-        }
-        localtime_r(&creation_time_t, &time_info);
-        strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S %z",
-                 &time_info);
-
-        if (parent)
-            vshPrint(ctl, " %-20s %-25s %-15s %s\n",
-                     name, timestr, state, parent);
-        else
-            vshPrint(ctl, " %-20s %-25s %s\n", name, timestr, state);
-    }
-
-    ret = true;
-
-cleanup:
-    /* this frees up memory from the last iteration of the loop */
-    vshSnapshotListFree(snaplist);
-    VIR_FREE(parent);
-    VIR_FREE(state);
-    if (start)
-        virDomainSnapshotFree(start);
-    xmlXPathFreeContext(ctxt);
-    xmlFreeDoc(xml);
-    VIR_FREE(doc);
-    if (dom)
-        virDomainFree(dom);
-
-    return ret;
-}
-
-/*
- * "snapshot-dumpxml" command
- */
-static const vshCmdInfo info_snapshot_dumpxml[] = {
-    {"help", N_("Dump XML for a domain snapshot")},
-    {"desc", N_("Snapshot Dump XML")},
-    {NULL, NULL}
-};
-
-static const vshCmdOptDef opts_snapshot_dumpxml[] = {
-    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
-    {"snapshotname", VSH_OT_DATA, VSH_OFLAG_REQ, N_("snapshot name")},
-    {"security-info", VSH_OT_BOOL, 0,
-     N_("include security sensitive information in XML dump")},
-    {NULL, 0, 0, NULL}
-};
-
-static bool
-cmdSnapshotDumpXML(vshControl *ctl, const vshCmd *cmd)
-{
-    virDomainPtr dom = NULL;
-    bool ret = false;
-    const char *name = NULL;
-    virDomainSnapshotPtr snapshot = NULL;
-    char *xml = NULL;
-    unsigned int flags = 0;
-
-    if (vshCommandOptBool(cmd, "security-info"))
-        flags |= VIR_DOMAIN_XML_SECURE;
-
-    if (!vshConnectionUsability(ctl, ctl->conn))
-        goto cleanup;
-
-    dom = vshCommandOptDomain(ctl, cmd, NULL);
-    if (dom == NULL)
-        goto cleanup;
-
-    if (vshCommandOptString(cmd, "snapshotname", &name) <= 0)
-        goto cleanup;
-
-    snapshot = virDomainSnapshotLookupByName(dom, name, 0);
-    if (snapshot == NULL)
-        goto cleanup;
-
-    xml = virDomainSnapshotGetXMLDesc(snapshot, flags);
-    if (!xml)
-        goto cleanup;
-
-    vshPrint(ctl, "%s", xml);
-
-    ret = true;
-
-cleanup:
-    VIR_FREE(xml);
-    if (snapshot)
-        virDomainSnapshotFree(snapshot);
-    if (dom)
-        virDomainFree(dom);
-
-    return ret;
-}
-
-/*
- * "snapshot-parent" command
- */
-static const vshCmdInfo info_snapshot_parent[] = {
-    {"help", N_("Get the name of the parent of a snapshot")},
-    {"desc", N_("Extract the snapshot's parent, if any")},
-    {NULL, NULL}
-};
-
-static const vshCmdOptDef opts_snapshot_parent[] = {
-    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
-    {"snapshotname", VSH_OT_DATA, 0, N_("find parent of snapshot name")},
-    {"current", VSH_OT_BOOL, 0, N_("find parent of current snapshot")},
-    {NULL, 0, 0, NULL}
-};
-
-static bool
-cmdSnapshotParent(vshControl *ctl, const vshCmd *cmd)
-{
-    virDomainPtr dom = NULL;
-    bool ret = false;
-    const char *name = NULL;
-    virDomainSnapshotPtr snapshot = NULL;
-    char *parent = NULL;
-
-    if (!vshConnectionUsability(ctl, ctl->conn))
-        goto cleanup;
-
-    dom = vshCommandOptDomain(ctl, cmd, NULL);
-    if (dom == NULL)
-        goto cleanup;
-
-    if (vshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
-                          &snapshot, &name) < 0)
-        goto cleanup;
-
-    if (vshGetSnapshotParent(ctl, snapshot, &parent) < 0)
-        goto cleanup;
-    if (!parent) {
-        vshError(ctl, _("snapshot '%s' has no parent"), name);
-        goto cleanup;
-    }
-
-    vshPrint(ctl, "%s", parent);
-
-    ret = true;
-
-cleanup:
-    VIR_FREE(parent);
-    if (snapshot)
-        virDomainSnapshotFree(snapshot);
-    if (dom)
-        virDomainFree(dom);
-
-    return ret;
-}
-
-/*
- * "snapshot-revert" command
- */
-static const vshCmdInfo info_snapshot_revert[] = {
-    {"help", N_("Revert a domain to a snapshot")},
-    {"desc", N_("Revert domain to snapshot")},
-    {NULL, NULL}
-};
-
-static const vshCmdOptDef opts_snapshot_revert[] = {
-    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
-    {"snapshotname", VSH_OT_DATA, 0, N_("snapshot name")},
-    {"current", VSH_OT_BOOL, 0, N_("revert to current snapshot")},
-    {"running", VSH_OT_BOOL, 0, N_("after reverting, change state to running")},
-    {"paused", VSH_OT_BOOL, 0, N_("after reverting, change state to paused")},
-    {"force", VSH_OT_BOOL, 0, N_("try harder on risky reverts")},
-    {NULL, 0, 0, NULL}
-};
-
-static bool
-cmdDomainSnapshotRevert(vshControl *ctl, const vshCmd *cmd)
-{
-    virDomainPtr dom = NULL;
-    bool ret = false;
-    const char *name = NULL;
-    virDomainSnapshotPtr snapshot = NULL;
-    unsigned int flags = 0;
-    bool force = false;
-    int result;
-
-    if (vshCommandOptBool(cmd, "running"))
-        flags |= VIR_DOMAIN_SNAPSHOT_REVERT_RUNNING;
-    if (vshCommandOptBool(cmd, "paused"))
-        flags |= VIR_DOMAIN_SNAPSHOT_REVERT_PAUSED;
-    /* We want virsh snapshot-revert --force to work even when talking
-     * to older servers that did the unsafe revert by default but
-     * reject the flag, so we probe without the flag, and only use it
-     * when the error says it will make a difference.  */
-    if (vshCommandOptBool(cmd, "force"))
-        force = true;
-
-    if (!vshConnectionUsability(ctl, ctl->conn))
-        goto cleanup;
-
-    dom = vshCommandOptDomain(ctl, cmd, NULL);
-    if (dom == NULL)
-        goto cleanup;
-
-    if (vshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
-                          &snapshot, &name) < 0)
-        goto cleanup;
-
-    result = virDomainRevertToSnapshot(snapshot, flags);
-    if (result < 0 && force &&
-        last_error->code == VIR_ERR_SNAPSHOT_REVERT_RISKY) {
-        flags |= VIR_DOMAIN_SNAPSHOT_REVERT_FORCE;
-        virFreeError(last_error);
-        last_error = NULL;
-        result = virDomainRevertToSnapshot(snapshot, flags);
-    }
-    if (result < 0)
-        goto cleanup;
-
-    ret = true;
-
-cleanup:
-    if (snapshot)
-        virDomainSnapshotFree(snapshot);
-    if (dom)
-        virDomainFree(dom);
-
-    return ret;
-}
-
-/*
- * "snapshot-delete" command
- */
-static const vshCmdInfo info_snapshot_delete[] = {
-    {"help", N_("Delete a domain snapshot")},
-    {"desc", N_("Snapshot Delete")},
-    {NULL, NULL}
-};
-
-static const vshCmdOptDef opts_snapshot_delete[] = {
-    {"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name, id or uuid")},
-    {"snapshotname", VSH_OT_DATA, 0, N_("snapshot name")},
-    {"current", VSH_OT_BOOL, 0, N_("delete current snapshot")},
-    {"children", VSH_OT_BOOL, 0, N_("delete snapshot and all children")},
-    {"children-only", VSH_OT_BOOL, 0, N_("delete children but not snapshot")},
-    {"metadata", VSH_OT_BOOL, 0,
-     N_("delete only libvirt metadata, leaving snapshot contents behind")},
-    {NULL, 0, 0, NULL}
-};
-
-static bool
-cmdSnapshotDelete(vshControl *ctl, const vshCmd *cmd)
-{
-    virDomainPtr dom = NULL;
-    bool ret = false;
-    const char *name = NULL;
-    virDomainSnapshotPtr snapshot = NULL;
-    unsigned int flags = 0;
-
-    if (!vshConnectionUsability(ctl, ctl->conn))
-        goto cleanup;
-
-    dom = vshCommandOptDomain(ctl, cmd, NULL);
-    if (dom == NULL)
-        goto cleanup;
-
-    if (vshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
-                          &snapshot, &name) < 0)
-        goto cleanup;
-
-    if (vshCommandOptBool(cmd, "children"))
-        flags |= VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN;
-    if (vshCommandOptBool(cmd, "children-only"))
-        flags |= VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY;
-    if (vshCommandOptBool(cmd, "metadata"))
-        flags |= VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY;
-
-    /* XXX If we wanted, we could emulate DELETE_CHILDREN_ONLY even on
-     * older servers that reject the flag, by manually computing the
-     * list of descendants.  But that's a lot of code to maintain.  */
-    if (virDomainSnapshotDelete(snapshot, flags) == 0) {
-        if (flags & VIR_DOMAIN_SNAPSHOT_DELETE_CHILDREN_ONLY)
-            vshPrint(ctl, _("Domain snapshot %s children deleted\n"), name);
-        else
-            vshPrint(ctl, _("Domain snapshot %s deleted\n"), name);
-    } else {
-        vshError(ctl, _("Failed to delete snapshot %s"), name);
-        goto cleanup;
-    }
-
-    ret = true;
-
-cleanup:
-    if (snapshot)
-        virDomainSnapshotFree(snapshot);
-    if (dom)
-        virDomainFree(dom);
-
-    return ret;
-}
-
 /*
  * "qemu-monitor-command" command
  */
@@ -6183,6 +4603,8 @@ static const vshCmdDef virshCmds[] = {
     {NULL, NULL, NULL, NULL, 0}
 };
 
+#include "virsh-snapshot.c"
+
 static const vshCmdDef snapshotCmds[] = {
     {"snapshot-create", cmdSnapshotCreate, opts_snapshot_create,
      info_snapshot_create, 0},
-- 
1.7.7.3




More information about the libvir-list mailing list