diff --git a/po/POTFILES b/po/POTFILES index 2c27049d55..c62bc32bb2 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -296,6 +296,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..08f8fded86 --- /dev/null +++ b/tools/virsh-checkpoint.c @@ -0,0 +1,1148 @@ +/* + * 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 + * . + */ + +#include +#include "virsh-checkpoint.h" + +#include + +#include +#include +#include +#include + +#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" +#include "vsh-table.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 checkpoint 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") + }, + {.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, "quiesce")) + flags |= VIR_DOMAIN_CHECKPOINT_CREATE_QUIESCE; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + goto cleanup; + + if (vshCommandOptStringReq(ctl, cmd, "xmlfile", &from) < 0) + goto cleanup; + if (!from) { + buffer = vshStrdup(ctl, ""); + } 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, "\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 = "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, "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, "\n"); + virBufferAdjustIndent(&buf, 2); + virBufferEscapeString(&buf, "%s\n", name); + virBufferEscapeString(&buf, "%s\n", desc); + + if (vshCommandOptBool(cmd, "diskspec")) { + virBufferAddLit(&buf, "\n"); + virBufferAdjustIndent(&buf, 2); + while ((opt = vshCommandOptArgv(ctl, cmd, opt))) { + if (virshParseCheckpointDiskspec(ctl, &buf, opt->data) < 0) + goto cleanup; + } + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "\n"); + } + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "\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 --ARG name into a checkpoint + * belonging to DOM. 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, + virDomainPtr dom, + virDomainCheckpointPtr *chk, + const char **name) +{ + const char *chkname = NULL; + + if (vshCommandOptStringReq(ctl, cmd, arg, &chkname) < 0) + return -1; + + if (chkname) { + *chk = virDomainCheckpointLookupByName(dom, chkname, 0); + } else { + vshError(ctl, _("--%s 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, + }, + {.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; + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (virshLookupCheckpoint(ctl, cmd, "checkpointname", dom, + &checkpoint, &name) < 0) + goto cleanup; + +#define EDIT_GET_XML \ + virDomainCheckpointGetXMLDesc(checkpoint, getxml_flags) +#define EDIT_NOT_CHANGED \ + do { \ + 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 { + unsigned int delete_flags = VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY; + + if (virDomainCheckpointDelete(edited, delete_flags) < 0) { + vshReportError(ctl); + vshError(ctl, _("Failed to clean up %s"), edited_name); + goto cleanup; + } + vshError(ctl, _("Cannot rename checkpoint %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; +} + + +/* 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, + }, + {.name = NULL} +}; + + +static bool +cmdCheckpointInfo(vshControl *ctl, + const vshCmd *cmd) +{ + virDomainPtr dom; + virDomainCheckpointPtr checkpoint = NULL; + const char *name; + char *parent = NULL; + char *xml = NULL; + xmlDocPtr xmldoc = NULL; + xmlXPathContextPtr ctxt = NULL; + bool ret = false; + int count; + unsigned int flags; + + dom = virshCommandOptDomain(ctl, cmd, NULL); + if (dom == NULL) + return false; + + if (virshLookupCheckpoint(ctl, cmd, "checkpointname", dom, + &checkpoint, &name) < 0) + goto cleanup; + + vshPrint(ctl, "%-15s %s\n", _("Name:"), name); + vshPrint(ctl, "%-15s %s\n", _("Domain:"), virDomainGetName(dom)); + + 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: + xmlXPathFreeContext(ctxt); + xmlFreeDoc(xmldoc); + VIR_FREE(xml); + 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 checkpointlist) +{ + size_t i; + + if (!checkpointlist) + return; + if (checkpointlist->chks) { + for (i = 0; i < checkpointlist->nchks; i++) { + virshDomainCheckpointFree(checkpointlist->chks[i].chk); + VIR_FREE(checkpointlist->chks[i].parent); + } + VIR_FREE(checkpointlist->chks); + } + VIR_FREE(checkpointlist); +} + + +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 checkpointlist = vshMalloc(ctl, + sizeof(*checkpointlist)); + 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. */ + checkpointlist->chks = vshCalloc(ctl, count + (tree && from), + sizeof(*checkpointlist->chks)); + checkpointlist->nchks = count; + for (i = 0; i < count; i++) + checkpointlist->chks[i].chk = chks[i]; + VIR_FREE(chks); + if (tree) { + for (i = 0; i < count; i++) { + if (virshGetCheckpointParent(ctl, checkpointlist->chks[i].chk, + &checkpointlist->chks[i].parent) < 0) + goto cleanup; + } + if (from) { + checkpointlist->chks[checkpointlist->nchks++].chk = from; + virDomainCheckpointRef(from); + } + } + + if (!(orig_flags & VIR_DOMAIN_CHECKPOINT_LIST_TOPOLOGICAL)) + qsort(checkpointlist->chks, checkpointlist->nchks, + sizeof(*checkpointlist->chks), virshChkSorter); + + ret = checkpointlist; + checkpointlist = NULL; + + cleanup: + virshCheckpointListFree(checkpointlist); + 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 checkpointlist = opaque; + if (parent) + return checkpointlist->chks[id].parent; + return virDomainCheckpointGetName(checkpointlist->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, + }, + {.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"); + const char *from_chk = NULL; + char *parent_chk = NULL; + virDomainCheckpointPtr start = NULL; + virshCheckpointListPtr checkpointlist = NULL; + vshTablePtr table = 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); + +#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) { + vshError(ctl, "%s", + _("--descendants requires --from")); + return false; + } + flags |= VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS; + } + + if (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (from && + virshLookupCheckpoint(ctl, cmd, "from", dom, &start, &from_chk) < 0) + goto cleanup; + + if (!(checkpointlist = virshCheckpointListCollect(ctl, dom, start, flags, + tree))) + goto cleanup; + + if (!tree && !name) { + if (parent) + table = vshTableNew(_("Name"), _("Creation Time"), _("Parent"), + NULL); + else + table = vshTableNew(_("Name"), _("Creation Time"), NULL); + if (!table) + goto cleanup; + } + + if (tree) { + for (i = 0; i < checkpointlist->nchks; i++) { + if (!checkpointlist->chks[i].parent && + vshTreePrint(ctl, virshCheckpointListLookup, checkpointlist, + checkpointlist->nchks, i) < 0) + goto cleanup; + } + ret = true; + goto cleanup; + } + + for (i = 0; i < checkpointlist->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 = checkpointlist->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) { + if (vshTableRowAppend(table, chk_name, timestr, + NULLSTR_EMPTY(parent_chk), NULL) < 0) + goto cleanup; + } else { + if (vshTableRowAppend(table, chk_name, timestr, NULL) < 0) + goto cleanup; + } + } + + if (table) + vshTablePrintToStdout(table, ctl); + + ret = true; + + cleanup: + /* this frees up memory from the last iteration of the loop */ + virshCheckpointListFree(checkpointlist); + VIR_FREE(parent_chk); + virshDomainCheckpointFree(start); + xmlXPathFreeContext(ctxt); + xmlFreeDoc(xml); + VIR_FREE(doc); + virshDomainFree(dom); + vshTableFree(table); + + 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_STRING, + .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 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 (!(dom = virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (virshLookupCheckpoint(ctl, cmd, "checkpointname", dom, + &checkpoint, &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, + }, + {.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", 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, + }, + {.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", 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-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-checkpoint.h b/tools/virsh-checkpoint.h new file mode 100644 index 0000000000..f288f9aece --- /dev/null +++ b/tools/virsh-checkpoint.h @@ -0,0 +1,25 @@ +/* + * 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 + * . + */ + +#pragma once + +#include "virsh.h" + +extern const vshCmdDef checkpointCmds[]; diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c index 37f946d4b6..4fa6b19225 100644 --- a/tools/virsh-completer.c +++ b/tools/virsh-completer.c @@ -693,6 +693,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-completer.h b/tools/virsh-completer.h index cb7aafcc8c..2e54dddc05 100644 --- a/tools/virsh-completer.h +++ b/tools/virsh-completer.h @@ -78,6 +78,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-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 2ad73959ec..e79dc75342 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 (vm must be 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) 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-util.h b/tools/virsh-util.h index 55520302ff..9005aa9d36 100644 --- a/tools/virsh-util.h +++ b/tools/virsh-util.h @@ -42,6 +42,9 @@ virshCommandOptDomain(vshControl *ctl, void virshDomainFree(virDomainPtr dom); +void +virshDomainCheckpointFree(virDomainCheckpointPtr chk); + void virshDomainSnapshotFree(virDomainSnapshotPtr snap); diff --git a/tools/virsh.c b/tools/virsh.c index c758e38cbd..f09ce1ec98 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -47,6 +47,7 @@ #include "virstring.h" #include "virgettext.h" +#include "virsh-checkpoint.h" #include "virsh-console.h" #include "virsh-domain.h" #include "virsh-domain-monitor.h" @@ -829,6 +830,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.h b/tools/virsh.h index 847ed25151..b4e610b2a4 100644 --- a/tools/virsh.h +++ b/tools/virsh.h @@ -40,6 +40,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/tools/virsh.pod b/tools/virsh.pod index 40c7b638f7..f58578e3b3 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 + +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 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 metadata with B. Similarly, the metadata of +any checkpoints will be lost, but can be restored with B. 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: Domain must be inactive and without snapshots. +B: Domain must be inactive and without snapshots or checkpoints. =item B I [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 parameter in the domain's XML definition. -If I 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. +If I 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, and the checkpoint +metadata with B. 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 I [I<--managed-save>] [I<--snapshots-metadata>] -[I<--nvram>] [I<--keep-nvram>] +[I<--checkpoints-metadata>] [I<--nvram>] [I<--keep-nvram>] [ {I<--storage> B | 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 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. @@ -4890,6 +4904,163 @@ 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 for documentation of the XML +format used to represent properties of checkpoints. + +=over 4 + +=item B I [I] { I<--redefine> +| [I<--quiesce>]} + +Create a checkpoint for domain I with the properties specified +in I describing a top-level element. The +format of the input XML file will be validated against an internal RNG +schema (idential to using the L tool). If +I is completely omitted, then libvirt will create a +checkpoint with a name based on the current time. + +If I<--redefine> is specified, then all XML elements produced by +B 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 argument is mandatory. + +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 +a persistent domain. However, for transient domains, checkpoint +metadata is silently lost when the domain quits running (whether +by command such as B or by internal guest action). + +=item B I [I<--print-xml>] +[I] [I] [I<--quiesce>] [I<--diskspec>] B]... + +Create a checkpoint for domain I with the given and +; if either value is omitted, libvirt will choose a +value. If I<--print-xml> is specified, then XML appropriate for +I 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 elements in the domain xml. Each + is in the form B. A +literal I<--diskspec> must precede each B unless +all three of I, I, and I are also present. +For example, a diskspec of "vda,checkpoint=bitmap,bitmap=map1" +results in the following XML: + + +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. + +=item B I I + +Edit the XML configuration file for I of a domain. + +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 + +except that it does some error checking, including that the edits +should not attempt to change the checkpoint name. + +The editor used can be supplied by the C<$VISUAL> or C<$EDITOR> environment +variables, and defaults to C. + +=item B I I + +Output basic information about a named . + +=item B I [{I<--parent> | I<--roots> | +[{I<--tree> | I<--name>}]}] [I<--topological>] +[[I<--from>] B | [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. 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> +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>. + +=item B I I +[I<--security-info>] [I<--no-domain>] [I<--size>] + +Output the checkpoint XML for the domain's checkpoint named +I. Using +I<--security-info> will also include security sensitive information. +Using I<--size> will add XML indicating the current size in bytes of +guest data that has changed since the checkpoint was created (although +remember that guest activity between a size check and actually +creating a backup can result in the backup needing slightly more +space). Using I<--no-domain> will omit the element from the +output for a more compact view. + +=item B I I + +Output the name of the parent checkpoint, if any, for the given +I. + +=item B I I +[I<--metadata>] [{I<--children> | I<--children-only>}] + +Delete the checkpoint for the domain named I. 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