[libvirt] [PATCH 08/13] backup: Implement virsh support for checkpoints

Eric Blake eblake at redhat.com
Wed Jun 19 03:47:49 UTC 2019


Introduce a bunch of new virsh commands for managing checkpoints
in isolation. More commands are needed for performing incremental
backups, but these commands were easy to implement by modeling
heavily after virsh-snapshot.c (no need for checkpoint-revert,
and checkpoint-list was a lot easier since we don't have to cater
to older libvirt API).

Signed-off-by: Eric Blake <eblake at redhat.com>
---
 tools/virsh-checkpoint.h     |   26 +
 tools/virsh-completer.h      |    4 +
 tools/virsh-util.h           |    3 +
 tools/virsh.h                |    1 +
 po/POTFILES                  |    1 +
 tools/Makefile.am            |    1 +
 tools/virsh-checkpoint.c     | 1342 ++++++++++++++++++++++++++++++++++
 tools/virsh-completer.c      |   51 ++
 tools/virsh-domain-monitor.c |   23 +
 tools/virsh-domain.c         |    9 +
 tools/virsh-util.c           |   11 +
 tools/virsh.c                |    2 +
 tools/virsh.pod              |  238 +++++-
 13 files changed, 1705 insertions(+), 7 deletions(-)
 create mode 100644 tools/virsh-checkpoint.h
 create mode 100644 tools/virsh-checkpoint.c

diff --git a/tools/virsh-checkpoint.h b/tools/virsh-checkpoint.h
new file mode 100644
index 0000000000..7cc998638f
--- /dev/null
+++ b/tools/virsh-checkpoint.h
@@ -0,0 +1,26 @@
+/*
+ * virsh-checkpoint.h: Commands to manage domain checkpoints
+ *
+ * Copyright (C) 2005-2019 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/>.
+ *
+ */
+
+#pragma once
+
+#include "virsh.h"
+
+extern const vshCmdDef checkpointCmds[];
diff --git a/tools/virsh-completer.h b/tools/virsh-completer.h
index cd3cc9ecae..9792eb8af0 100644
--- a/tools/virsh-completer.h
+++ b/tools/virsh-completer.h
@@ -79,6 +79,10 @@ char ** virshSecretUUIDCompleter(vshControl *ctl,
                                  const vshCmd *cmd,
                                  unsigned int flags);

+char ** virshCheckpointNameCompleter(vshControl *ctl,
+                                     const vshCmd *cmd,
+                                     unsigned int flags);
+
 char ** virshSnapshotNameCompleter(vshControl *ctl,
                                    const vshCmd *cmd,
                                    unsigned int flags);
diff --git a/tools/virsh-util.h b/tools/virsh-util.h
index fb2ed277af..f814558144 100644
--- a/tools/virsh-util.h
+++ b/tools/virsh-util.h
@@ -43,6 +43,9 @@ virshCommandOptDomain(vshControl *ctl,
 void
 virshDomainFree(virDomainPtr dom);

+void
+virshDomainCheckpointFree(virDomainCheckpointPtr chk);
+
 void
 virshDomainSnapshotFree(virDomainSnapshotPtr snap);

diff --git a/tools/virsh.h b/tools/virsh.h
index 254ce3289e..da157d6caa 100644
--- a/tools/virsh.h
+++ b/tools/virsh.h
@@ -41,6 +41,7 @@
 /*
  * Command group types
  */
+# define VIRSH_CMD_GRP_CHECKPOINT       "Checkpoint"
 # define VIRSH_CMD_GRP_DOM_MANAGEMENT   "Domain Management"
 # define VIRSH_CMD_GRP_DOM_MONITORING   "Domain Monitoring"
 # define VIRSH_CMD_GRP_STORAGE_POOL     "Storage Pool"
diff --git a/po/POTFILES b/po/POTFILES
index 57c55fb35f..70417cd01b 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -300,6 +300,7 @@ src/xenconfig/xen_xl.c
 src/xenconfig/xen_xm.c
 tests/virpolkittest.c
 tools/libvirt-guests.sh.in
+tools/virsh-checkpoint.c
 tools/virsh-console.c
 tools/virsh-domain-monitor.c
 tools/virsh-domain.c
diff --git a/tools/Makefile.am b/tools/Makefile.am
index c6064dee08..2807b9f6fd 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -216,6 +216,7 @@ virt_login_shell_CFLAGS = \

 virsh_SOURCES = \
 		virsh.c virsh.h \
+		virsh-checkpoint.c virsh-checkpoint.h \
 		virsh-completer.c virsh-completer.h \
 		virsh-console.c virsh-console.h \
 		virsh-domain.c virsh-domain.h \
diff --git a/tools/virsh-checkpoint.c b/tools/virsh-checkpoint.c
new file mode 100644
index 0000000000..41b07e5452
--- /dev/null
+++ b/tools/virsh-checkpoint.c
@@ -0,0 +1,1342 @@
+/*
+ * virsh-checkpoint.c: Commands to manage domain checkpoints
+ *
+ * Copyright (C) 2005-2019 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>
+ *
+ */
+
+#include <config.h>
+#include "virsh-checkpoint.h"
+
+#include <assert.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include <libxml/xmlsave.h>
+
+#include "internal.h"
+#include "virbuffer.h"
+#include "viralloc.h"
+#include "virfile.h"
+#include "virsh-util.h"
+#include "virstring.h"
+#include "virxml.h"
+#include "conf/checkpoint_conf.h"
+
+/* Helper for checkpoint-create and checkpoint-create-as */
+static bool
+virshCheckpointCreate(vshControl *ctl,
+                      virDomainPtr dom,
+                      const char *buffer,
+                      unsigned int flags,
+                      const char *from)
+{
+    bool ret = false;
+    virDomainCheckpointPtr checkpoint;
+    const char *name = NULL;
+
+    checkpoint = virDomainCheckpointCreateXML(dom, buffer, flags);
+
+    if (checkpoint == NULL)
+        goto cleanup;
+
+    name = virDomainCheckpointGetName(checkpoint);
+    if (!name) {
+        vshError(ctl, "%s", _("Could not get snapshot name"));
+        goto cleanup;
+    }
+
+    if (from)
+        vshPrintExtra(ctl, _("Domain checkpoint %s created from '%s'"),
+                      name, from);
+    else
+        vshPrintExtra(ctl, _("Domain checkpoint %s created"), name);
+
+    ret = true;
+
+ cleanup:
+    virshDomainCheckpointFree(checkpoint);
+    return ret;
+}
+
+
+/*
+ * "checkpoint-create" command
+ */
+static const vshCmdInfo info_checkpoint_create[] = {
+    {.name = "help",
+     .data = N_("Create a checkpoint from XML")
+    },
+    {.name = "desc",
+     .data = N_("Create a checkpoint from XML for use in "
+                "future incremental backups")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_checkpoint_create[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+    {.name = "xmlfile",
+     .type = VSH_OT_STRING,
+     .help = N_("domain checkpoint XML")
+    },
+    {.name = "redefine",
+     .type = VSH_OT_BOOL,
+     .help = N_("redefine metadata for existing checkpoint")
+    },
+    VIRSH_COMMON_OPT_CURRENT(N_("with redefine, set current checkpoint")),
+    {.name = "no-metadata",
+     .type = VSH_OT_BOOL,
+     .help = N_("create checkpoint but create no metadata")
+    },
+    {.name = "quiesce",
+     .type = VSH_OT_BOOL,
+     .help = N_("quiesce guest's file systems")
+    },
+    {.name = NULL}
+};
+
+static bool
+cmdCheckpointCreate(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_CHECKPOINT_CREATE_REDEFINE;
+    if (vshCommandOptBool(cmd, "current"))
+        flags |= VIR_DOMAIN_CHECKPOINT_CREATE_CURRENT;
+    if (vshCommandOptBool(cmd, "no-metadata"))
+        flags |= VIR_DOMAIN_CHECKPOINT_CREATE_NO_METADATA;
+    if (vshCommandOptBool(cmd, "quiesce"))
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE;
+
+    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+        goto cleanup;
+
+    if (vshCommandOptStringReq(ctl, cmd, "xmlfile", &from) < 0)
+        goto cleanup;
+    if (!from) {
+        buffer = vshStrdup(ctl, "<domaincheckpoint/>");
+    } else {
+        if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) {
+            vshSaveLibvirtError();
+            goto cleanup;
+        }
+    }
+
+    ret = virshCheckpointCreate(ctl, dom, buffer, flags, from);
+
+ cleanup:
+    VIR_FREE(buffer);
+    virshDomainFree(dom);
+
+    return ret;
+}
+
+
+/*
+ * "checkpoint-create-as" command
+ */
+static int
+virshParseCheckpointDiskspec(vshControl *ctl,
+                             virBufferPtr buf,
+                             const char *str)
+{
+    int ret = -1;
+    const char *name = NULL;
+    const char *checkpoint = NULL;
+    const char *bitmap = NULL;
+    char **array = NULL;
+    int narray;
+    size_t i;
+
+    narray = vshStringToArray(str, &array);
+    if (narray <= 0)
+        goto cleanup;
+
+    name = array[0];
+    for (i = 1; i < narray; i++) {
+        if (!checkpoint && STRPREFIX(array[i], "checkpoint="))
+            checkpoint = array[i] + strlen("checkpoint=");
+        else if (!bitmap && STRPREFIX(array[i], "bitmap="))
+            bitmap = array[i] + strlen("bitmap=");
+        else
+            goto cleanup;
+    }
+
+    virBufferEscapeString(buf, "<disk name='%s'", name);
+    if (checkpoint)
+        virBufferAsprintf(buf, " checkpoint='%s'", checkpoint);
+    if (bitmap)
+        virBufferAsprintf(buf, " bitmap='%s'", bitmap);
+    virBufferAddLit(buf, "/>\n");
+    ret = 0;
+ cleanup:
+    if (ret < 0)
+        vshError(ctl, _("unable to parse diskspec: %s"), str);
+    virStringListFree(array);
+    return ret;
+}
+
+static const vshCmdInfo info_checkpoint_create_as[] = {
+    {.name = "help",
+     .data = N_("Create a checkpoint from a set of args")
+    },
+    {.name = "desc",
+     .data = N_("Create a checkpoint from arguments for use in "
+                "future incremental backups")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_checkpoint_create_as[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+    {.name = "name",
+     .type = VSH_OT_STRING,
+     .help = N_("name of checkpoint")
+    },
+    {.name = "description",
+     .type = VSH_OT_STRING,
+     .help = N_("description of checkpoint")
+    },
+    {.name = "print-xml",
+     .type = VSH_OT_BOOL,
+     .help = N_("print XML document rather than create")
+    },
+    {.name = "no-metadata",
+     .type = VSH_OT_BOOL,
+     .help = N_("take checkpoint but create no metadata")
+    },
+    {.name = "quiesce",
+     .type = VSH_OT_BOOL,
+     .help = N_("quiesce guest's file systems")
+    },
+    {.name = "diskspec",
+     .type = VSH_OT_ARGV,
+     .help = N_("disk attributes: disk[,checkpoint=type][,bitmap=name]")
+    },
+    {.name = NULL}
+};
+
+
+static bool
+cmdCheckpointCreateAs(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")) {
+        if (vshCommandOptBool(cmd, "print-xml")) {
+            vshError(ctl, "%s",
+                     _("--print-xml is incompatible with --no-metadata"));
+            return false;
+        }
+        flags |= VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA;
+    }
+    if (vshCommandOptBool(cmd, "quiesce"))
+        flags |= VIR_DOMAIN_CHECKPOINT_CREATE_QUIESCE;
+
+    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+        return false;
+
+    if (vshCommandOptStringReq(ctl, cmd, "name", &name) < 0 ||
+        vshCommandOptStringReq(ctl, cmd, "description", &desc) < 0)
+        goto cleanup;
+
+    virBufferAddLit(&buf, "<domaincheckpoint>\n");
+    virBufferAdjustIndent(&buf, 2);
+    virBufferEscapeString(&buf, "<name>%s</name>\n", name);
+    virBufferEscapeString(&buf, "<description>%s</description>\n", desc);
+
+    if (vshCommandOptBool(cmd, "diskspec")) {
+        virBufferAddLit(&buf, "<disks>\n");
+        virBufferAdjustIndent(&buf, 2);
+        while ((opt = vshCommandOptArgv(ctl, cmd, opt))) {
+            if (virshParseCheckpointDiskspec(ctl, &buf, opt->data) < 0)
+                goto cleanup;
+        }
+        virBufferAdjustIndent(&buf, -2);
+        virBufferAddLit(&buf, "</disks>\n");
+    }
+    virBufferAdjustIndent(&buf, -2);
+    virBufferAddLit(&buf, "</domaincheckpoint>\n");
+
+    if (virBufferError(&buf)) {
+        vshError(ctl, "%s", _("Out of memory"));
+        goto cleanup;
+    }
+
+    buffer = virBufferContentAndReset(&buf);
+
+    if (vshCommandOptBool(cmd, "print-xml")) {
+        vshPrint(ctl, "%s\n",  buffer);
+        ret = true;
+        goto cleanup;
+    }
+
+    ret = virshCheckpointCreate(ctl, dom, buffer, flags, NULL);
+
+ cleanup:
+    virBufferFreeAndReset(&buf);
+    VIR_FREE(buffer);
+    virshDomainFree(dom);
+
+    return ret;
+}
+
+
+/* Helper for resolving {--current | --ARG name} into a checkpoint
+ * belonging to DOM.  If EXCLUSIVE, fail if both --current and arg are
+ * present.  On success, populate *CHK and *NAME, before returning 0.
+ * On failure, return -1 after issuing an error message.  */
+static int
+virshLookupCheckpoint(vshControl *ctl,
+                      const vshCmd *cmd,
+                      const char *arg,
+                      bool exclusive,
+                      virDomainPtr dom,
+                      virDomainCheckpointPtr *chk,
+                      const char **name)
+{
+    bool current = vshCommandOptBool(cmd, "current");
+    const char *chkname = NULL;
+
+    if (vshCommandOptStringReq(ctl, cmd, arg, &chkname) < 0)
+        return -1;
+
+    if (exclusive && current && chkname) {
+        vshError(ctl, _("--%s and --current are mutually exclusive"), arg);
+        return -1;
+    }
+
+    if (chkname) {
+        *chk = virDomainCheckpointLookupByName(dom, chkname, 0);
+    } else if (current) {
+        *chk = virDomainCheckpointCurrent(dom, 0);
+    } else {
+        vshError(ctl, _("--%s or --current is required"), arg);
+        return -1;
+    }
+    if (!*chk) {
+        vshReportError(ctl);
+        return -1;
+    }
+
+    *name = virDomainCheckpointGetName(*chk);
+    return 0;
+}
+
+
+/*
+ * "checkpoint-edit" command
+ */
+static const vshCmdInfo info_checkpoint_edit[] = {
+    {.name = "help",
+     .data = N_("edit XML for a checkpoint")
+    },
+    {.name = "desc",
+     .data = N_("Edit the domain checkpoint XML for a named checkpoint")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_checkpoint_edit[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+    {.name = "checkpointname",
+     .type = VSH_OT_STRING,
+     .help = N_("checkpoint name"),
+     .completer = virshCheckpointNameCompleter,
+    },
+    VIRSH_COMMON_OPT_CURRENT(N_("also set edited checkpoint as current")),
+    {.name = "rename",
+     .type = VSH_OT_BOOL,
+     .help = N_("allow renaming an existing checkpoint")
+    },
+    {.name = "clone",
+     .type = VSH_OT_BOOL,
+     .help = N_("allow cloning to new name")
+    },
+    {.name = NULL}
+};
+
+static bool
+cmdCheckpointEdit(vshControl *ctl,
+                  const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    virDomainCheckpointPtr checkpoint = NULL;
+    virDomainCheckpointPtr edited = NULL;
+    const char *name = NULL;
+    const char *edited_name;
+    bool ret = false;
+    unsigned int getxml_flags = VIR_DOMAIN_CHECKPOINT_XML_SECURE;
+    unsigned int define_flags = VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE;
+    bool rename_okay = vshCommandOptBool(cmd, "rename");
+    bool clone_okay = vshCommandOptBool(cmd, "clone");
+
+    VSH_EXCLUSIVE_OPTIONS_EXPR("rename", rename_okay, "clone", clone_okay)
+
+    if (vshCommandOptBool(cmd, "current") &&
+        vshCommandOptBool(cmd, "checkpointname"))
+        define_flags |= VIR_DOMAIN_CHECKPOINT_CREATE_CURRENT;
+
+    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+        return false;
+
+    if (virshLookupCheckpoint(ctl, cmd, "checkpointname", false, dom,
+                              &checkpoint, &name) < 0)
+        goto cleanup;
+
+#define EDIT_GET_XML \
+    virDomainCheckpointGetXMLDesc(checkpoint, getxml_flags)
+#define EDIT_NOT_CHANGED \
+    do { \
+        /* Depending on flags, we re-edit even if XML is unchanged.  */ \
+        if (!(define_flags & VIR_DOMAIN_CHECKPOINT_CREATE_CURRENT)) { \
+            vshPrintExtra(ctl, \
+                          _("Checkpoint %s XML configuration not changed.\n"), \
+                          name); \
+            ret = true; \
+            goto edit_cleanup; \
+        } \
+    } while (0)
+#define EDIT_DEFINE \
+    edited = virDomainCheckpointCreateXML(dom, doc_edited, define_flags)
+#include "virsh-edit.c"
+
+    edited_name = virDomainCheckpointGetName(edited);
+    if (STREQ(name, edited_name)) {
+        vshPrintExtra(ctl, _("Checkpoint %s edited.\n"), name);
+    } else if (clone_okay) {
+        vshPrintExtra(ctl, _("Checkpoint %s cloned to %s.\n"), name,
+                      edited_name);
+    } else {
+        unsigned int delete_flags;
+
+        delete_flags = VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY;
+        if (virDomainCheckpointDelete(rename_okay ? checkpoint : edited,
+                                      delete_flags) < 0) {
+            vshReportError(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 (!ret && name)
+        vshError(ctl, _("Failed to update %s"), name);
+    virshDomainCheckpointFree(edited);
+    virshDomainCheckpointFree(checkpoint);
+    virshDomainFree(dom);
+    return ret;
+}
+
+
+/*
+ * "checkpoint-current" command
+ */
+static const vshCmdInfo info_checkpoint_current[] = {
+    {.name = "help",
+     .data = N_("Get or set the current checkpoint")
+    },
+    {.name = "desc",
+     .data = N_("Get or set the current checkpoint")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_checkpoint_current[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+    {.name = "name",
+     .type = VSH_OT_BOOL,
+     .help = N_("list the name, rather than the full xml")
+    },
+    {.name = "security-info",
+     .type = VSH_OT_BOOL,
+     .help = N_("include security sensitive information in XML dump")
+    },
+    {.name = "no-domain",
+     .type = VSH_OT_BOOL,
+     .help = N_("exclude <domain> from XML")
+    },
+    {.name = "size",
+     .type = VSH_OT_BOOL,
+     .help = N_("include backup size estimate in XML dump")
+    },
+    {.name = "checkpointname",
+     .type = VSH_OT_STRING,
+     .help = N_("name of existing checkpoint to make current"),
+     .completer = virshCheckpointNameCompleter,
+    },
+    {.name = NULL}
+};
+
+static bool
+cmdCheckpointCurrent(vshControl *ctl,
+                     const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    virDomainCheckpointPtr checkpoint = NULL;
+    char *xml = NULL;
+    const char *checkpointname = NULL;
+    unsigned int flags = 0;
+    const char *domname;
+
+    if (vshCommandOptBool(cmd, "security-info"))
+        flags |= VIR_DOMAIN_CHECKPOINT_XML_SECURE;
+    if (vshCommandOptBool(cmd, "no-domain"))
+        flags |= VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN;
+    if (vshCommandOptBool(cmd, "size"))
+        flags |= VIR_DOMAIN_CHECKPOINT_XML_SIZE;
+
+    VSH_EXCLUSIVE_OPTIONS("name", "checkpointname");
+
+    if (!(dom = virshCommandOptDomain(ctl, cmd, &domname)))
+        return false;
+
+    if (vshCommandOptStringReq(ctl, cmd, "checkpointname", &checkpointname) < 0)
+        goto cleanup;
+
+    if (checkpointname) {
+        virDomainCheckpointPtr checkpoint2 = NULL;
+        flags = (VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE |
+                 VIR_DOMAIN_CHECKPOINT_CREATE_CURRENT);
+
+        if (!(checkpoint = virDomainCheckpointLookupByName(dom,
+                                                           checkpointname, 0)))
+            goto cleanup;
+
+        xml = virDomainCheckpointGetXMLDesc(checkpoint,
+                                            VIR_DOMAIN_CHECKPOINT_XML_SECURE);
+        if (!xml)
+            goto cleanup;
+
+        if (!(checkpoint2 = virDomainCheckpointCreateXML(dom, xml, flags)))
+            goto cleanup;
+
+        virshDomainCheckpointFree(checkpoint2);
+        vshPrintExtra(ctl, _("Checkpoint %s set as current"), checkpointname);
+        ret = true;
+        goto cleanup;
+    }
+
+    if (!(checkpoint = virDomainCheckpointCurrent(dom, 0))) {
+        vshError(ctl, _("domain '%s' has no current checkpoint"), domname);
+        goto cleanup;
+    }
+
+    if (vshCommandOptBool(cmd, "name")) {
+        const char *name;
+        if (!(name = virDomainCheckpointGetName(checkpoint)))
+            goto cleanup;
+
+        vshPrint(ctl, "%s", name);
+    } else {
+        if (!(xml = virDomainCheckpointGetXMLDesc(checkpoint, flags)))
+            goto cleanup;
+
+        vshPrint(ctl, "%s", xml);
+    }
+
+    ret = true;
+
+ cleanup:
+    if (!ret)
+        vshReportError(ctl);
+    VIR_FREE(xml);
+    virshDomainCheckpointFree(checkpoint);
+    virshDomainFree(dom);
+
+    return ret;
+}
+
+
+/* Helper function to get the name of a checkpoint'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 checkpoint support or domain deleted in meantime).  */
+static int
+virshGetCheckpointParent(vshControl *ctl,
+                         virDomainCheckpointPtr checkpoint,
+                         char **parent_name)
+{
+    virDomainCheckpointPtr parent = NULL;
+    int ret = -1;
+
+    *parent_name = NULL;
+
+    parent = virDomainCheckpointGetParent(checkpoint, 0);
+    if (parent) {
+        /* API works, and virDomainCheckpointGetName will succeed */
+        *parent_name = vshStrdup(ctl, virDomainCheckpointGetName(parent));
+        ret = 0;
+    } else if (last_error->code == VIR_ERR_NO_DOMAIN_CHECKPOINT) {
+        /* API works, and we found a root with no parent */
+        ret = 0;
+    }
+
+    if (ret < 0) {
+        vshReportError(ctl);
+        vshError(ctl, "%s", _("unable to determine if checkpoint has parent"));
+    } else {
+        vshResetLibvirtError();
+    }
+    virshDomainCheckpointFree(parent);
+    return ret;
+}
+
+
+/*
+ * "checkpoint-info" command
+ */
+static const vshCmdInfo info_checkpoint_info[] = {
+    {.name = "help",
+     .data = N_("checkpoint information")
+    },
+    {.name = "desc",
+     .data = N_("Returns basic information about a checkpoint.")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_checkpoint_info[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+    {.name = "checkpointname",
+     .type = VSH_OT_STRING,
+     .help = N_("checkpoint name"),
+     .completer = virshCheckpointNameCompleter,
+    },
+    VIRSH_COMMON_OPT_CURRENT(N_("info on current checkpoint")),
+    {.name = NULL}
+};
+
+
+static bool
+cmdCheckpointInfo(vshControl *ctl,
+                  const vshCmd *cmd)
+{
+    virDomainPtr dom;
+    virDomainCheckpointPtr checkpoint = NULL;
+    const char *name;
+    char *parent = NULL;
+    bool ret = false;
+    int count;
+    unsigned int flags;
+    int current;
+
+    dom = virshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        return false;
+
+    if (virshLookupCheckpoint(ctl, cmd, "checkpointname", true, dom,
+                              &checkpoint, &name) < 0)
+        goto cleanup;
+
+    vshPrint(ctl, "%-15s %s\n", _("Name:"), name);
+    vshPrint(ctl, "%-15s %s\n", _("Domain:"), virDomainGetName(dom));
+
+    /* Determine if checkpoint is current.  */
+    current = virDomainCheckpointIsCurrent(checkpoint, 0);
+    if (current < 0) {
+        vshError(ctl, "%s",
+                 _("unexpected problem querying checkpoint state"));
+        goto cleanup;
+    }
+    vshPrint(ctl, "%-15s %s\n", _("Current:"),
+             current > 0 ? _("yes") : _("no"));
+
+    if (virshGetCheckpointParent(ctl, checkpoint, &parent) < 0) {
+        vshError(ctl, "%s",
+                 _("unexpected problem querying checkpoint state"));
+        goto cleanup;
+    }
+    vshPrint(ctl, "%-15s %s\n", _("Parent:"), parent ? parent : "-");
+
+    /* Children, Descendants.  */
+    flags = 0;
+    count = virDomainCheckpointListAllChildren(checkpoint, NULL, flags);
+    if (count < 0) {
+        if (last_error->code == VIR_ERR_NO_SUPPORT) {
+            vshResetLibvirtError();
+            ret = true;
+        }
+        goto cleanup;
+    }
+    vshPrint(ctl, "%-15s %d\n", _("Children:"), count);
+    flags = VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS;
+    count = virDomainCheckpointListAllChildren(checkpoint, NULL, flags);
+    if (count < 0)
+        goto cleanup;
+    vshPrint(ctl, "%-15s %d\n", _("Descendants:"), count);
+
+    ret = true;
+
+ cleanup:
+    VIR_FREE(parent);
+    virshDomainCheckpointFree(checkpoint);
+    virshDomainFree(dom);
+    return ret;
+}
+
+
+/* Helpers for collecting a list of checkpoints.  */
+struct virshChk {
+    virDomainCheckpointPtr chk;
+    char *parent;
+};
+struct virshCheckpointList {
+    struct virshChk *chks;
+    int nchks;
+};
+typedef struct virshCheckpointList *virshCheckpointListPtr;
+
+static void
+virshCheckpointListFree(virshCheckpointListPtr chklist)
+{
+    size_t i;
+
+    if (!chklist)
+        return;
+    if (chklist->chks) {
+        for (i = 0; i < chklist->nchks; i++) {
+            virshDomainCheckpointFree(chklist->chks[i].chk);
+            VIR_FREE(chklist->chks[i].parent);
+        }
+        VIR_FREE(chklist->chks);
+    }
+    VIR_FREE(chklist);
+}
+
+
+static int
+virshChkSorter(const void *a,
+               const void *b)
+{
+    const struct virshChk *sa = a;
+    const struct virshChk *sb = b;
+
+    if (sa->chk && !sb->chk)
+        return -1;
+    if (!sa->chk)
+        return sb->chk != NULL;
+
+    return vshStrcasecmp(virDomainCheckpointGetName(sa->chk),
+                         virDomainCheckpointGetName(sb->chk));
+}
+
+
+/* Compute a list of checkpoints from DOM.  If FROM is provided, the
+ * list is limited to descendants of the given checkpoint.  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 virshCheckpointListPtr
+virshCheckpointListCollect(vshControl *ctl,
+                           virDomainPtr dom,
+                           virDomainCheckpointPtr from,
+                           unsigned int orig_flags,
+                           bool tree)
+{
+    size_t i;
+    char **names = NULL;
+    int count = -1;
+    virDomainCheckpointPtr *chks;
+    virshCheckpointListPtr chklist = vshMalloc(ctl, sizeof(*chklist));
+    virshCheckpointListPtr ret = NULL;
+    unsigned int flags = orig_flags;
+
+    if (from)
+        count = virDomainCheckpointListAllChildren(from, &chks, flags);
+    else
+        count = virDomainListAllCheckpoints(dom, &chks, flags);
+    if (count < 0) {
+        vshError(ctl, "%s",
+                 _("unexpected problem querying checkpoints"));
+        goto cleanup;
+    }
+
+    /* When mixing --from and --tree, we also want a copy of from
+     * in the list, but with no parent for that one entry.  */
+    chklist->chks = vshCalloc(ctl, count + (tree && from),
+                              sizeof(*chklist->chks));
+    chklist->nchks = count;
+    for (i = 0; i < count; i++)
+        chklist->chks[i].chk = chks[i];
+    VIR_FREE(chks);
+    if (tree) {
+        for (i = 0; i < count; i++) {
+            if (virshGetCheckpointParent(ctl, chklist->chks[i].chk,
+                                         &chklist->chks[i].parent) < 0)
+                goto cleanup;
+        }
+        if (from) {
+            chklist->chks[chklist->nchks++].chk = from;
+            virDomainCheckpointRef(from);
+        }
+    }
+
+    qsort(chklist->chks, chklist->nchks, sizeof(*chklist->chks),
+          virshChkSorter);
+
+    ret = chklist;
+    chklist = NULL;
+
+ cleanup:
+    virshCheckpointListFree(chklist);
+    if (names && count > 0)
+        for (i = 0; i < count; i++)
+            VIR_FREE(names[i]);
+    VIR_FREE(names);
+    return ret;
+}
+
+
+static const char *
+virshCheckpointListLookup(int id,
+                          bool parent,
+                          void *opaque)
+{
+    virshCheckpointListPtr chklist = opaque;
+    if (parent)
+        return chklist->chks[id].parent;
+    return virDomainCheckpointGetName(chklist->chks[id].chk);
+}
+
+
+/*
+ * "checkpoint-list" command
+ */
+static const vshCmdInfo info_checkpoint_list[] = {
+    {.name = "help",
+     .data = N_("List checkpoints for a domain")
+    },
+    {.name = "desc",
+     .data = N_("Checkpoint List")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_checkpoint_list[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+    {.name = "parent",
+     .type = VSH_OT_BOOL,
+     .help = N_("add a column showing parent checkpoint")
+    },
+    {.name = "roots",
+     .type = VSH_OT_BOOL,
+     .help = N_("list only checkpoints without parents")
+    },
+    {.name = "leaves",
+     .type = VSH_OT_BOOL,
+     .help = N_("list only checkpoints without children")
+    },
+    {.name = "no-leaves",
+     .type = VSH_OT_BOOL,
+     .help = N_("list only checkpoints that are not leaves (with children)")
+    },
+    {.name = "tree",
+     .type = VSH_OT_BOOL,
+     .help = N_("list checkpoints in a tree")
+    },
+    {.name = "from",
+     .type = VSH_OT_STRING,
+     .help = N_("limit list to children of given checkpoint"),
+     .completer = virshCheckpointNameCompleter,
+    },
+    VIRSH_COMMON_OPT_CURRENT(N_("limit list to children of current checkpoint")),
+    {.name = "descendants",
+     .type = VSH_OT_BOOL,
+     .help = N_("with --from, list all descendants")
+    },
+    {.name = "name",
+     .type = VSH_OT_BOOL,
+     .help = N_("list checkpoint names only")
+    },
+    {.name = "topological",
+     .type = VSH_OT_BOOL,
+     .help = N_("sort list topologically rather than by name"),
+    },
+
+    {.name = NULL}
+};
+
+static bool
+cmdCheckpointList(vshControl *ctl,
+                  const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    unsigned int flags = 0;
+    size_t i;
+    xmlDocPtr xml = NULL;
+    xmlXPathContextPtr ctxt = NULL;
+    char *doc = NULL;
+    virDomainCheckpointPtr checkpoint = NULL;
+    long long creation_longlong;
+    time_t creation_time_t;
+    char timestr[100];
+    struct tm time_info;
+    bool tree = vshCommandOptBool(cmd, "tree");
+    bool name = vshCommandOptBool(cmd, "name");
+    bool from = vshCommandOptBool(cmd, "from");
+    bool parent = vshCommandOptBool(cmd, "parent");
+    bool roots = vshCommandOptBool(cmd, "roots");
+    bool current = vshCommandOptBool(cmd, "current");
+    const char *from_chk = NULL;
+    char *parent_chk = NULL;
+    virDomainCheckpointPtr start = NULL;
+    virshCheckpointListPtr chklist = NULL;
+
+    VSH_EXCLUSIVE_OPTIONS_VAR(tree, name);
+    VSH_EXCLUSIVE_OPTIONS_VAR(parent, roots);
+    VSH_EXCLUSIVE_OPTIONS_VAR(parent, tree);
+    VSH_EXCLUSIVE_OPTIONS_VAR(roots, tree);
+    VSH_EXCLUSIVE_OPTIONS_VAR(roots, from);
+    VSH_EXCLUSIVE_OPTIONS_VAR(roots, current);
+
+#define FILTER(option, flag) \
+    do { \
+        if (vshCommandOptBool(cmd, option)) { \
+            if (tree) { \
+                vshError(ctl, \
+                         _("--%s and --tree are mutually exclusive"), \
+                         option); \
+                return false; \
+            } \
+            flags |= VIR_DOMAIN_CHECKPOINT_LIST_ ## flag; \
+        } \
+    } while (0)
+
+    FILTER("leaves", LEAVES);
+    FILTER("no-leaves", NO_LEAVES);
+#undef FILTER
+
+    if (vshCommandOptBool(cmd, "topological"))
+        flags |= VIR_DOMAIN_CHECKPOINT_LIST_TOPOLOGICAL;
+
+    if (roots)
+        flags |= VIR_DOMAIN_CHECKPOINT_LIST_ROOTS;
+
+    if (vshCommandOptBool(cmd, "descendants")) {
+        if (!from && !current) {
+            vshError(ctl, "%s",
+                     _("--descendants requires either --from or --current"));
+            return false;
+        }
+        flags |= VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS;
+    }
+
+    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+        return false;
+
+    if ((from || current) &&
+        virshLookupCheckpoint(ctl, cmd, "from", true, dom, &start, &from_chk) < 0)
+        goto cleanup;
+
+    if (!(chklist = virshCheckpointListCollect(ctl, dom, start, flags, tree)))
+        goto cleanup;
+
+    if (!tree && !name) {
+        if (parent)
+            vshPrintExtra(ctl, " %-20s %-25s %s",
+                          _("Name"), _("Creation Time"), _("Parent"));
+        else
+            vshPrintExtra(ctl, " %-20s %-25s",
+                          _("Name"), _("Creation Time"));
+        vshPrintExtra(ctl, "\n"
+                           "------------------------------"
+                           "--------------\n");
+    }
+
+    if (tree) {
+        for (i = 0; i < chklist->nchks; i++) {
+            if (!chklist->chks[i].parent &&
+                vshTreePrint(ctl, virshCheckpointListLookup, chklist,
+                             chklist->nchks, i) < 0)
+                goto cleanup;
+        }
+        ret = true;
+        goto cleanup;
+    }
+
+    for (i = 0; i < chklist->nchks; i++) {
+        const char *chk_name;
+
+        /* free up memory from previous iterations of the loop */
+        VIR_FREE(parent_chk);
+        xmlXPathFreeContext(ctxt);
+        xmlFreeDoc(xml);
+        VIR_FREE(doc);
+
+        checkpoint = chklist->chks[i].chk;
+        chk_name = virDomainCheckpointGetName(checkpoint);
+        assert(chk_name);
+
+        if (name) {
+            /* just print the checkpoint name */
+            vshPrint(ctl, "%s\n", chk_name);
+            continue;
+        }
+
+        if (!(doc = virDomainCheckpointGetXMLDesc(checkpoint, 0)))
+            continue;
+
+        if (!(xml = virXMLParseStringCtxt(doc, _("(domain_checkpoint)"), &ctxt)))
+            continue;
+
+        if (parent)
+            parent_chk = virXPathString("string(/domaincheckpoint/parent/name)",
+                                        ctxt);
+
+        if (virXPathLongLong("string(/domaincheckpoint/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 %s\n",
+                     chk_name, timestr, parent_chk ?: "-");
+        else
+            vshPrint(ctl, " %-20s %-25s\n", chk_name, timestr);
+    }
+
+    ret = true;
+
+ cleanup:
+    /* this frees up memory from the last iteration of the loop */
+    virshCheckpointListFree(chklist);
+    VIR_FREE(parent_chk);
+    virshDomainCheckpointFree(start);
+    xmlXPathFreeContext(ctxt);
+    xmlFreeDoc(xml);
+    VIR_FREE(doc);
+    virshDomainFree(dom);
+
+    return ret;
+}
+
+
+/*
+ * "checkpoint-dumpxml" command
+ */
+static const vshCmdInfo info_checkpoint_dumpxml[] = {
+    {.name = "help",
+     .data = N_("Dump XML for a domain checkpoint")
+    },
+    {.name = "desc",
+     .data = N_("Checkpoint Dump XML")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_checkpoint_dumpxml[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+    {.name = "checkpointname",
+     .type = VSH_OT_DATA,
+     .flags = VSH_OFLAG_REQ,
+     .help = N_("checkpoint name"),
+     .completer = virshCheckpointNameCompleter,
+    },
+    {.name = "security-info",
+     .type = VSH_OT_BOOL,
+     .help = N_("include security sensitive information in XML dump")
+    },
+    {.name = "no-domain",
+     .type = VSH_OT_BOOL,
+     .help = N_("exclude <domain> from XML")
+    },
+    {.name = "size",
+     .type = VSH_OT_BOOL,
+     .help = N_("include backup size estimate in XML dump")
+    },
+    {.name = NULL}
+};
+
+static bool
+cmdCheckpointDumpXML(vshControl *ctl,
+                     const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    const char *name = NULL;
+    virDomainCheckpointPtr checkpoint = NULL;
+    char *xml = NULL;
+    unsigned int flags = 0;
+
+    if (vshCommandOptBool(cmd, "security-info"))
+        flags |= VIR_DOMAIN_CHECKPOINT_XML_SECURE;
+    if (vshCommandOptBool(cmd, "no-domain"))
+        flags |= VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN;
+    if (vshCommandOptBool(cmd, "size"))
+        flags |= VIR_DOMAIN_CHECKPOINT_XML_SIZE;
+
+    if (vshCommandOptStringReq(ctl, cmd, "checkpointname", &name) < 0)
+        return false;
+
+    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+        return false;
+
+    if (!(checkpoint = virDomainCheckpointLookupByName(dom, name, 0)))
+        goto cleanup;
+
+    if (!(xml = virDomainCheckpointGetXMLDesc(checkpoint, flags)))
+        goto cleanup;
+
+    vshPrint(ctl, "%s", xml);
+    ret = true;
+
+ cleanup:
+    VIR_FREE(xml);
+    virshDomainCheckpointFree(checkpoint);
+    virshDomainFree(dom);
+
+    return ret;
+}
+
+
+/*
+ * "checkpoint-parent" command
+ */
+static const vshCmdInfo info_checkpoint_parent[] = {
+    {.name = "help",
+     .data = N_("Get the name of the parent of a checkpoint")
+    },
+    {.name = "desc",
+     .data = N_("Extract the checkpoint's parent, if any")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_checkpoint_parent[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+    {.name = "checkpointname",
+     .type = VSH_OT_STRING,
+     .help = N_("find parent of checkpoint name"),
+     .completer = virshCheckpointNameCompleter,
+    },
+    VIRSH_COMMON_OPT_CURRENT(N_("find parent of current checkpoint")),
+    {.name = NULL}
+};
+
+static bool
+cmdCheckpointParent(vshControl *ctl,
+                    const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    const char *name = NULL;
+    virDomainCheckpointPtr checkpoint = NULL;
+    char *parent = NULL;
+
+    dom = virshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        goto cleanup;
+
+    if (virshLookupCheckpoint(ctl, cmd, "checkpointname", true, dom,
+                              &checkpoint, &name) < 0)
+        goto cleanup;
+
+    if (virshGetCheckpointParent(ctl, checkpoint, &parent) < 0)
+        goto cleanup;
+    if (!parent) {
+        vshError(ctl, _("checkpoint '%s' has no parent"), name);
+        goto cleanup;
+    }
+
+    vshPrint(ctl, "%s", parent);
+
+    ret = true;
+
+ cleanup:
+    VIR_FREE(parent);
+    virshDomainCheckpointFree(checkpoint);
+    virshDomainFree(dom);
+
+    return ret;
+}
+
+
+/*
+ * "checkpoint-delete" command
+ */
+static const vshCmdInfo info_checkpoint_delete[] = {
+    {.name = "help",
+     .data = N_("Delete a domain checkpoint")
+    },
+    {.name = "desc",
+     .data = N_("Checkpoint Delete")
+    },
+    {.name = NULL}
+};
+
+static const vshCmdOptDef opts_checkpoint_delete[] = {
+    VIRSH_COMMON_OPT_DOMAIN_FULL(0),
+    {.name = "checkpointname",
+     .type = VSH_OT_STRING,
+     .help = N_("checkpoint name"),
+     .completer = virshCheckpointNameCompleter,
+    },
+    VIRSH_COMMON_OPT_CURRENT(N_("delete current checkpoint")),
+    {.name = "children",
+     .type = VSH_OT_BOOL,
+     .help = N_("delete checkpoint and all children")
+    },
+    {.name = "children-only",
+     .type = VSH_OT_BOOL,
+     .help = N_("delete children but not checkpoint")
+    },
+    {.name = "metadata",
+     .type = VSH_OT_BOOL,
+     .help = N_("delete only libvirt metadata, leaving checkpoint contents behind")
+    },
+    {.name = NULL}
+};
+
+static bool
+cmdCheckpointDelete(vshControl *ctl,
+                    const vshCmd *cmd)
+{
+    virDomainPtr dom = NULL;
+    bool ret = false;
+    const char *name = NULL;
+    virDomainCheckpointPtr checkpoint = NULL;
+    unsigned int flags = 0;
+
+    dom = virshCommandOptDomain(ctl, cmd, NULL);
+    if (dom == NULL)
+        goto cleanup;
+
+    if (virshLookupCheckpoint(ctl, cmd, "checkpointname", true, dom,
+                              &checkpoint, &name) < 0)
+        goto cleanup;
+
+    if (vshCommandOptBool(cmd, "children"))
+        flags |= VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN;
+    if (vshCommandOptBool(cmd, "children-only"))
+        flags |= VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY;
+    if (vshCommandOptBool(cmd, "metadata"))
+        flags |= VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY;
+
+    if (virDomainCheckpointDelete(checkpoint, flags) == 0) {
+        if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY)
+            vshPrintExtra(ctl, _("Domain checkpoint %s children deleted\n"), name);
+        else
+            vshPrintExtra(ctl, _("Domain checkpoint %s deleted\n"), name);
+    } else {
+        vshError(ctl, _("Failed to delete checkpoint %s"), name);
+        goto cleanup;
+    }
+
+    ret = true;
+
+ cleanup:
+    virshDomainCheckpointFree(checkpoint);
+    virshDomainFree(dom);
+
+    return ret;
+}
+
+
+const vshCmdDef checkpointCmds[] = {
+    {.name = "checkpoint-create",
+     .handler = cmdCheckpointCreate,
+     .opts = opts_checkpoint_create,
+     .info = info_checkpoint_create,
+     .flags = 0
+    },
+    {.name = "checkpoint-create-as",
+     .handler = cmdCheckpointCreateAs,
+     .opts = opts_checkpoint_create_as,
+     .info = info_checkpoint_create_as,
+     .flags = 0
+    },
+    {.name = "checkpoint-current",
+     .handler = cmdCheckpointCurrent,
+     .opts = opts_checkpoint_current,
+     .info = info_checkpoint_current,
+     .flags = 0
+    },
+    {.name = "checkpoint-delete",
+     .handler = cmdCheckpointDelete,
+     .opts = opts_checkpoint_delete,
+     .info = info_checkpoint_delete,
+     .flags = 0
+    },
+    {.name = "checkpoint-dumpxml",
+     .handler = cmdCheckpointDumpXML,
+     .opts = opts_checkpoint_dumpxml,
+     .info = info_checkpoint_dumpxml,
+     .flags = 0
+    },
+    {.name = "checkpoint-edit",
+     .handler = cmdCheckpointEdit,
+     .opts = opts_checkpoint_edit,
+     .info = info_checkpoint_edit,
+     .flags = 0
+    },
+    {.name = "checkpoint-info",
+     .handler = cmdCheckpointInfo,
+     .opts = opts_checkpoint_info,
+     .info = info_checkpoint_info,
+     .flags = 0
+    },
+    {.name = "checkpoint-list",
+     .handler = cmdCheckpointList,
+     .opts = opts_checkpoint_list,
+     .info = info_checkpoint_list,
+     .flags = 0
+    },
+    {.name = "checkpoint-parent",
+     .handler = cmdCheckpointParent,
+     .opts = opts_checkpoint_parent,
+     .info = info_checkpoint_parent,
+     .flags = 0
+    },
+    {.name = NULL}
+};
diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c
index 7d5cf8cb90..b54699fafa 100644
--- a/tools/virsh-completer.c
+++ b/tools/virsh-completer.c
@@ -619,6 +619,57 @@ virshSecretUUIDCompleter(vshControl *ctl,
 }


+char **
+virshCheckpointNameCompleter(vshControl *ctl,
+                             const vshCmd *cmd,
+                             unsigned int flags)
+{
+    virshControlPtr priv = ctl->privData;
+    virDomainPtr dom = NULL;
+    virDomainCheckpointPtr *checkpoints = NULL;
+    int ncheckpoints = 0;
+    size_t i = 0;
+    char **ret = NULL;
+
+    virCheckFlags(0, NULL);
+
+    if (!priv->conn || virConnectIsAlive(priv->conn) <= 0)
+        return NULL;
+
+    if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
+        return NULL;
+
+    if ((ncheckpoints = virDomainListAllCheckpoints(dom, &checkpoints,
+                                                    flags)) < 0)
+        goto error;
+
+    if (VIR_ALLOC_N(ret, ncheckpoints + 1) < 0)
+        goto error;
+
+    for (i = 0; i < ncheckpoints; i++) {
+        const char *name = virDomainCheckpointGetName(checkpoints[i]);
+
+        if (VIR_STRDUP(ret[i], name) < 0)
+            goto error;
+
+        virshDomainCheckpointFree(checkpoints[i]);
+    }
+    VIR_FREE(checkpoints);
+    virshDomainFree(dom);
+
+    return ret;
+
+ error:
+    for (; i < ncheckpoints; i++)
+        virshDomainCheckpointFree(checkpoints[i]);
+    VIR_FREE(checkpoints);
+    for (i = 0; i < ncheckpoints; i++)
+        VIR_FREE(ret[i]);
+    VIR_FREE(ret);
+    virshDomainFree(dom);
+    return NULL;
+}
+
 char **
 virshSnapshotNameCompleter(vshControl *ctl,
                            const vshCmd *cmd,
diff --git a/tools/virsh-domain-monitor.c b/tools/virsh-domain-monitor.c
index e399195deb..0e2c4191d7 100644
--- a/tools/virsh-domain-monitor.c
+++ b/tools/virsh-domain-monitor.c
@@ -1621,6 +1621,7 @@ virshDomainListCollect(vshControl *ctl, unsigned int flags)
     int autostart;
     int state;
     int nsnap;
+    int nchk;
     int mansave;
     virshControlPtr priv = ctl->privData;

@@ -1788,6 +1789,17 @@ virshDomainListCollect(vshControl *ctl, unsigned int flags)
                 goto remove_entry;
         }

+        /* checkpoint filter */
+        if (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_FILTERS_CHECKPOINT)) {
+            if ((nchk = virDomainListAllCheckpoints(dom, NULL, 0)) < 0) {
+                vshError(ctl, "%s", _("Failed to get checkpoint count"));
+                goto cleanup;
+            }
+            if (!((VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT) && nchk > 0) ||
+                  (VSH_MATCH(VIR_CONNECT_LIST_DOMAINS_NO_CHECKPOINT) && nchk == 0)))
+                goto remove_entry;
+        }
+
         /* the domain matched all filters, it may stay */
         continue;

@@ -1849,6 +1861,14 @@ static const vshCmdOptDef opts_list[] = {
      .type = VSH_OT_BOOL,
      .help = N_("list domains without a snapshot")
     },
+    {.name = "with-checkpoint",
+     .type = VSH_OT_BOOL,
+     .help = N_("list domains with existing checkpoint")
+    },
+    {.name = "without-checkpoint",
+     .type = VSH_OT_BOOL,
+     .help = N_("list domains without a checkpoint")
+    },
     {.name = "state-running",
      .type = VSH_OT_BOOL,
      .help = N_("list domains in running state")
@@ -1948,6 +1968,9 @@ cmdList(vshControl *ctl, const vshCmd *cmd)
     FILTER("with-snapshot",    VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT);
     FILTER("without-snapshot", VIR_CONNECT_LIST_DOMAINS_NO_SNAPSHOT);

+    FILTER("with-checkpoint",    VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT);
+    FILTER("without-checkpoint", VIR_CONNECT_LIST_DOMAINS_NO_CHECKPOINT);
+
     FILTER("state-running", VIR_CONNECT_LIST_DOMAINS_RUNNING);
     FILTER("state-paused",  VIR_CONNECT_LIST_DOMAINS_PAUSED);
     FILTER("state-shutoff", VIR_CONNECT_LIST_DOMAINS_SHUTOFF);
diff --git a/tools/virsh-domain.c b/tools/virsh-domain.c
index 828ae30789..1d6a34de4b 100644
--- a/tools/virsh-domain.c
+++ b/tools/virsh-domain.c
@@ -3633,6 +3633,10 @@ static const vshCmdOptDef opts_undefine[] = {
      .type = VSH_OT_BOOL,
      .help = N_("remove all domain snapshot metadata (vm must be inactive)")
     },
+    {.name = "checkpoints-metadata",
+     .type = VSH_OT_BOOL,
+     .help = N_("remove all domain checkpoint metadata, if inactive")
+    },
     {.name = "nvram",
      .type = VSH_OT_BOOL,
      .help = N_("remove nvram file, if inactive")
@@ -3662,6 +3666,7 @@ cmdUndefine(vshControl *ctl, const vshCmd *cmd)
     /* User-requested actions.  */
     bool managed_save = vshCommandOptBool(cmd, "managed-save");
     bool snapshots_metadata = vshCommandOptBool(cmd, "snapshots-metadata");
+    bool checkpoints_metadata = vshCommandOptBool(cmd, "checkpoints-metadata");
     bool wipe_storage = vshCommandOptBool(cmd, "wipe-storage");
     bool remove_all_storage = vshCommandOptBool(cmd, "remove-all-storage");
     bool delete_snapshots = vshCommandOptBool(cmd, "delete-snapshots");
@@ -3716,6 +3721,8 @@ cmdUndefine(vshControl *ctl, const vshCmd *cmd)
         flags |= VIR_DOMAIN_UNDEFINE_SNAPSHOTS_METADATA;
         snapshots_safe = true;
     }
+    if (checkpoints_metadata)
+        flags |= VIR_DOMAIN_UNDEFINE_CHECKPOINTS_METADATA;
     if (nvram)
         flags |= VIR_DOMAIN_UNDEFINE_NVRAM;
     if (keep_nvram)
@@ -3765,6 +3772,8 @@ cmdUndefine(vshControl *ctl, const vshCmd *cmd)
             }
         }
     }
+    /* Clear flags which older servers might reject, if they would
+     * otherwise have no effect. */
     if (!has_managed_save) {
         flags &= ~VIR_DOMAIN_UNDEFINE_MANAGED_SAVE;
         managed_save_safe = true;
diff --git a/tools/virsh-util.c b/tools/virsh-util.c
index aa88397d61..933d1c825d 100644
--- a/tools/virsh-util.c
+++ b/tools/virsh-util.c
@@ -228,6 +228,17 @@ virshDomainFree(virDomainPtr dom)
 }


+void
+virshDomainCheckpointFree(virDomainCheckpointPtr chk)
+{
+    if (!chk)
+        return;
+
+    vshSaveLibvirtHelperError();
+    virDomainCheckpointFree(chk); /* sc_prohibit_obj_free_apis_in_virsh */
+}
+
+
 void
 virshDomainSnapshotFree(virDomainSnapshotPtr snap)
 {
diff --git a/tools/virsh.c b/tools/virsh.c
index b41304a888..0de41e33b8 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -50,6 +50,7 @@
 #include "virstring.h"
 #include "virgettext.h"

+#include "virsh-checkpoint.h"
 #include "virsh-console.h"
 #include "virsh-domain.h"
 #include "virsh-domain-monitor.h"
@@ -832,6 +833,7 @@ static const vshCmdGrp cmdGroups[] = {
     {VIRSH_CMD_GRP_DOM_MANAGEMENT, "domain", domManagementCmds},
     {VIRSH_CMD_GRP_DOM_MONITORING, "monitor", domMonitoringCmds},
     {VIRSH_CMD_GRP_HOST_AND_HV, "host", hostAndHypervisorCmds},
+    {VIRSH_CMD_GRP_CHECKPOINT, "checkpoint", checkpointCmds},
     {VIRSH_CMD_GRP_IFACE, "interface", ifaceCmds},
     {VIRSH_CMD_GRP_NWFILTER, "filter", nwfilterCmds},
     {VIRSH_CMD_GRP_NETWORK, "network", networkCmds},
diff --git a/tools/virsh.pod b/tools/virsh.pod
index 2e70b68a66..a5db2507cd 100644
--- a/tools/virsh.pod
+++ b/tools/virsh.pod
@@ -409,6 +409,7 @@ Inject NMI to the guest.
               [I<--with-managed-save>] [I<--without-managed-save>]
               [I<--autostart>] [I<--no-autostart>]
               [I<--with-snapshot>] [I<--without-snapshot>]
+              [I<--with-checkpoint>] [I<--without-checkpoint>]
               [I<--state-running>] [I<--state-paused>]
               [I<--state-shutoff>] [I<--state-other>]

@@ -514,6 +515,11 @@ this feature disabled use I<--no-autostart>.
 Domains that have snapshot images can be listed using flag I<--with-snapshot>,
 domains without a snapshot I<--without-snapshot>.

+=item B<Checkpoint existence>
+
+Domains that have checkpoints can be listed using flag I<--with-checkpoint>,
+domains without a checkpoint I<--without-checkpoint>.
+
 =back

 When talking to older servers, this command is forced to use a series of API
@@ -809,7 +815,8 @@ can be restarted later.
 If I<domain> is transient, then the metadata of any snapshots will
 be lost once the guest stops running, but the snapshot contents still
 exist, and a new domain with the same name and UUID can restore the
-snapshot metadata with B<snapshot-create>.
+snapshot metadata with B<snapshot-create>.  Similarly, the metadata of
+any checkpoints will be lost, but can be restored with B<checkpoint-create>.

 If I<--graceful> is specified, don't resort to extreme measures
 (e.g. SIGKILL) when the guest doesn't stop after a reasonable timeout;
@@ -1574,7 +1581,7 @@ Convert a domain Id (or UUID) to domain name
 Rename a domain. This command changes current domain name to the new name
 specified in the second argument.

-B<Note>: Domain must be inactive and without snapshots.
+B<Note>: Domain must be inactive and without snapshots or checkpoints.

 =item B<domstate> I<domain> [I<--reason>]

@@ -2815,10 +2822,11 @@ services must be shutdown in the domain.
 The exact behavior of a domain when it shuts down is set by the
 I<on_poweroff> parameter in the domain's XML definition.

-If I<domain> is transient, then the metadata of any snapshots will
-be lost once the guest stops running, but the snapshot contents still
-exist, and a new domain with the same name and UUID can restore the
-snapshot metadata with B<snapshot-create>.
+If I<domain> is transient, then the metadata of any snapshots and
+checkpoints will be lost once the guest stops running, but the underlying
+contents still exist, and a new domain with the same name and UUID can
+restore the snapshot metadata with B<snapshot-create>, and the checkpoint
+metadata with B<checkpoint-create>.

 By default the hypervisor will try to pick a suitable shutdown
 method. To specify an alternative method, the I<--mode> parameter
@@ -2895,7 +2903,7 @@ Output the device used for the TTY console of the domain. If the information
 is not available the processes will provide an exit code of 1.

 =item B<undefine> I<domain> [I<--managed-save>] [I<--snapshots-metadata>]
-[I<--nvram>] [I<--keep-nvram>]
+[I<--checkpoints-metadata>] [I<--nvram>] [I<--keep-nvram>]
 [ {I<--storage> B<volumes> | I<--remove-all-storage>
 [I<--delete-storage-volume-snapshots>]} I<--wipe-storage>]

@@ -2913,6 +2921,12 @@ domain.  Without the flag, attempts to undefine an inactive domain with
 snapshot metadata will fail.  If the domain is active, this flag is
 ignored.

+The I<--checkpoints-metadata> flag guarantees that any checkpoints (see the
+B<checkpoint-list> command) are also cleaned up when undefining an inactive
+domain.  Without the flag, attempts to undefine an inactive domain with
+checkpoint metadata will fail.  If the domain is active, this flag is
+ignored.
+
 I<--nvram> and I<--keep-nvram> specify accordingly to delete or keep nvram
 (/domain/os/nvram/) file. If the domain has an nvram file and the flags are
 omitted, the undefine will fail.
@@ -4884,6 +4898,216 @@ the data contents from that point in time.

 =back

+=head1 CHECKPOINT COMMANDS
+
+The following commands manipulate domain checkpoints.  Checkpoints serve as
+a point in time to identify which portions of a guest's disks have changed
+after that time, making it possible to perform incremental and differential
+backups.  Checkpoints are identified with a unique name.  See
+L<https://libvirt.org/formatcheckpoint.html> for documentation of the XML
+format used to represent properties of checkpoints.
+
+=over 4
+
+=item B<checkpoint-create> I<domain> [I<xmlfile>] {[I<--redefine>
+{[I<--current>] | [I<--redefine-list>]}] | [I<--no-metadata>] [I<--quiesce>]}
+
+Create a checkpoint for domain I<domain> with the properties specified
+in I<xmlfile> describing a <domaincheckpoint> top-level element. If
+I<xmlfile> is completely omitted, then libvirt will create a
+checkpoint with a name based on the current time. The new checkpoint
+will become current, as listed by B<checkpoint-current>.
+
+If I<--redefine> is specified, then all XML elements produced by
+B<checkpoint-dumpxml> are valid; this can be used to migrate
+checkpoint hierarchy from one machine to another, to recreate
+hierarchy for the case of a transient domain that goes away and is
+later recreated with the same name and UUID, or to make slight
+alterations in the checkpoint metadata (such as host-specific aspects
+of the domain XML embedded in the checkpoint).  When this flag is
+supplied, the I<xmlfile> argument is mandatory, and the domain's
+current snapshot will not be altered unless the I<--current> flag is
+also given.  If I<--redefine-list> is specified, I<--redefine> is
+implied, I<--current> is rejected, and the XML changes from being a
+single <domaincheckpoint> to instead being a <checkpoints> element
+describing a list of checkpoints. List form only works if the domain
+has no currently-defined checkpoint metadata, and can be obtained as a
+subset of I<dumpxml --checkpoints> output.
+
+If I<--no-metadata> is specified, then the checkpoint data is created,
+but any metadata is immediately discarded (that is, libvirt does not
+treat the checkpoint as current, and cannot use the checkpoint for an
+incremental backup unless I<--redefine> is later used to teach libvirt
+about the metadata again).
+
+If I<--quiesce> is specified, libvirt will try to use guest agent
+to freeze and unfreeze domain's mounted file systems. However,
+if domain has no guest agent, checkpoint creation will fail.
+
+Existence of checkpoint metadata will prevent attempts to B<undefine>
+a persistent domain.  However, for transient domains, checkpoint
+metadata is silently lost when the domain quits running (whether
+by command such as B<destroy> or by internal guest action).
+
+=item B<checkpoint-create-as> I<domain> {[I<--print-xml>]
+| [I<--no-metadata>]} [I<name>] [I<description>] [I<--quiesce>]
+[I<--diskspec>] B<diskspec>]...
+
+Create a checkpoint for domain I<domain> with the given <name> and
+<description>; if either value is omitted, libvirt will choose a
+value.  If I<--print-xml> is specified, then XML appropriate for
+I<checkpoint-create> is output, rather than actually creating a
+checkpoint.
+
+The I<--diskspec> option can be used to control which guest disks participate in the checkpoint. This option can occur
+multiple times, according to the number of <disk> elements in the domain
+xml.  Each <diskspec> is in the
+form B<disk[,checkpoint=type][,bitmap=name]>. A literal I<--diskspec> must precede each B<diskspec> unless
+all three of I<domain>, I<name>, and I<description> are also present.
+For example, a diskspec of "vda,checkpoint=bitmap,bitmap=map1"
+results in the following XML:
+  <disk name='vda' checkpoint='bitmap' bitmap='map1'/>
+
+If I<--quiesce> is specified, libvirt will try to use guest agent
+to freeze and unfreeze domain's mounted file systems. However,
+if domain has no guest agent, checkpoint creation will fail.
+
+If I<--no-metadata> is specified, then the checkpoint data is created,
+but any metadata is immediately discarded (that is, libvirt does not
+treat the checkpoint as current, and cannot use the checkpoint for an
+incremental backup unless I<--redefine> is later used to teach libvirt
+about the metadata again).
+
+=item B<checkpoint-current> I<domain> {[I<--name>] | [I<--security-info>]
+[I<--no-domain>] [I<--size>] | [I<checkpointname>]}
+
+Without I<checkpointname>, this will output the checkpoint XML for the
+domain's current checkpoint (if any).  If I<--name> is specified,
+output just the current checkpoint name instead of the full xml.
+Otherwise, using I<--security-info> will also include security
+sensitive information in the XML, using I<--size> will add XML
+indicating roughly how much guest data has changed since the
+checkpoint was created, and using I<--no-domain> will omit the
+<domain> element from the output for a more compact view.
+
+With I<checkpointname>, this is a request to make the existing named
+checkpoint become the current checkpoint.
+
+=item B<checkpoint-edit> I<domain> [I<checkpointname>] [I<--current>]
+{[I<--rename>] | [I<--clone>]}
+
+Edit the XML configuration file for I<checkpointname> of a domain.  If
+both I<checkpointname> and I<--current> are specified, also force the
+edited checkpoint to become the current snapshot.  If
+I<checkpointname> is omitted, then I<--current> must be supplied, to
+edit the current checkpoint.
+
+This is equivalent to:
+
+ virsh checkpoint-dumpxml dom name > checkpoint.xml
+ vi checkpoint.xml (or make changes with your other text editor)
+ virsh checkpoint-create dom checkpoint.xml --redefine [--current]
+
+except that it does some error checking.
+
+The editor used can be supplied by the C<$VISUAL> or C<$EDITOR> environment
+variables, and defaults to C<vi>.
+
+If I<--rename> is specified, then the edits can change the checkpoint
+name.  If I<--clone> is specified, then changing the snapshot name
+will create a clone of the checkpoint metadata.  If neither is
+specified, then the edits must not change the checkpoint name.  Note
+that changing a checkpoint name must be done with care, since some
+drivers may require the original checkpoint name for actually
+accessing changes since a point in time.
+
+=item B<checkpoint-info> I<domain> {I<checkpoint> | I<--current>}
+
+Output basic information about a named <checkpoint>, or the current
+checkpoint with I<--current>.
+
+=item B<checkpoint-list> I<domain> [I<--metadata>] [I<--no-metadata>]
+[{I<--parent> | I<--roots> | [{I<--tree> | I<--name>}]}] [I<--topological>]
+[{[I<--from>] B<checkpoint> | I<--current>} [I<--descendants>]]
+[I<--leaves>] [I<--no-leaves>]
+
+List all of the available checkpoints for the given domain, defaulting
+to show columns for the checkpoint name and creation time.
+
+Normally, table form output is sorted by checkpoint name; using
+I<--topological> instead sorts so that no child is listed before its
+ancestors (although there may be more than one possible ordering with
+this property).
+
+If I<--parent> is specified, add a column to the output table giving
+the name of the parent of each checkpoint.  If I<--roots> is
+specified, the list will be filtered to just checkpoints that have no
+parents.  If I<--tree> is specified, the output will be in a tree
+format, listing just checkpoint names.  These three options are
+mutually exclusive. If I<--name> is specified only the checkpoint name
+is printed. This option is mutually exclusive with I<--tree>.
+
+If I<--from> is provided, filter the list to checkpoints which are
+children of the given B<checkpoint>; or if I<--current> is provided,
+start at the current checkpoint.  When used in isolation or with
+I<--parent>, the list is limited to direct children unless
+I<--descendants> is also present.  When used with I<--tree>, the use
+of I<--descendants> is implied.  This option is not compatible with
+I<--roots>.  Note that the starting point of I<--from> or I<--current>
+is not included in the list unless the I<--tree> option is also
+present.
+
+If I<--leaves> is specified, the list will be filtered to just
+checkpoints that have no children.  Likewise, if I<--no-leaves> is
+specified, the list will be filtered to just checkpoints with
+children.  (Note that omitting both options does no filtering, while
+providing both options will either produce the same list or error out
+depending on whether the server recognizes the flags).  Filtering
+options are not compatible with I<--tree>.
+
+If I<--metadata> is specified, the list will be filtered to just
+checkpoints that involve libvirt metadata, and thus would prevent
+B<undefine> of a persistent domain, or be lost on B<destroy> of a
+transient domain.  Likewise, if I<--no-metadata> is specified, the
+list will be filtered to just checkpoints that exist without the need
+for libvirt metadata.
+
+=item B<checkpoint-dumpxml> I<domain> I<snapshot> [I<--security-info>]
+[I<--no-domain>] [I<--size>]
+
+Output the snapshot XML for the domain's checkpoint named
+I<checkpoint>.  Using I<--security-info> will also include security
+sensitive information, using I<--size> will add XML indicating roughly
+how much guest data has changed since the checkpoint was created, and
+using I<--no-domain> will omit the <domain> element from the output
+for a more compact view.  Use B<checkpoint-current> to easily access
+the XML of the current snapshot.  To grab the XML for all checkpoints
+at once, use B<dumpxml --checkpoints>.
+
+=item B<checkpoint-parent> I<domain> {I<checkpoint> | I<--current>}
+
+Output the name of the parent checkpoint, if any, for the given
+I<checkpoint>, or for the current checkpoint with I<--current>.
+
+=item B<checkpoint-delete> I<domain> {I<checkpoint> | I<--current>}
+[I<--metadata>] [{I<--children> | I<--children-only>}]
+
+Delete the checkpoint for the domain named I<checkpoint>, or the
+current checkpoint with I<--current>.  The record of which portions of
+the disk changed since the checkpoint are merged into the parent
+checkpoint (if any). If I<--children> is passed, then delete this
+checkpoint and any children of this checkpoint.  If I<--children-only>
+is passed, then delete any children of this checkpoint, but leave this
+checkpoint intact. These two flags are mutually exclusive.
+
+If I<--metadata> is specified, then only delete the checkpoint
+metadata maintained by libvirt, while leaving the checkpoint contents
+intact for access by external tools; otherwise deleting a checkpoint
+also removes the ability to perform an incremental backup from that
+point in time.
+
+=back
+
 =head1 NWFILTER COMMANDS

 The following commands manipulate network filters. Network filters allow
-- 
2.20.1




More information about the libvir-list mailing list