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