2012-07-23 06:23:00 +00:00
|
|
|
/*
|
2012-08-20 23:29:03 +00:00
|
|
|
* virsh-snapshot.c: Commands to manage domain snapshot
|
2012-07-23 06:23:00 +00:00
|
|
|
*
|
virsh: Add snapshot-list --topological
For snapshots, virsh already has a (shockingly naive [1]) client-side
topological sorter with the --tree option. But as a series of REDEFINE
calls must be presented in topological order, it's worth letting the
server do the work for us, especially since the server can give us a
topological sorting with less effort than our naive client
reconstruction.
[1] The XXX comment in virshSnapshotListCollect() about --tree being
O(n^3) is telling; https://en.wikipedia.org/wiki/Topological_sorting
is an interesting resource describing Kahn's algorithm and other
approaches for O(n) topological sorting for anyone motivated to use a
more elegant algorithm than brute force - but that doesn't affect this
patch.
For now, I am purposefully NOT implementing virsh fallback code to
provide a topological sort when the flag was rejected as unsupported;
we can worry about that down the road if users actually demonstrate
that they use new virsh but old libvirt to even need the fallback.
(The code we use for --tree could be repurposed to be such a fallback,
whether or not we keep it naive or improve it to be faster - but
again, no one should spend time on a fallback without evidence that we
need it.)
The test driver makes it easy to test:
$ virsh -c test:///default '
snapshot-create-as test a
snapshot-create-as test c
snapshot-create-as test b
snapshot-list test
snapshot-list test --topological
snapshot-list test --descendants a
snapshot-list test --descendants a --topological
snapshot-list test --tree
snapshot-list test --tree --topological
'
Without any flags, virsh does client-side sorting alphabetically, and
lists 'b' before 'c' (even though 'c' is the parent of 'b'); with the
flag, virsh skips sorting, and you can now see that the server handed
back data in a correct ordering. As shown here with a simple linear
chain, there isn't any other possible ordering, so --tree mode doesn't
seem to care whether --topological is used. But it is possible to
compose more complicated DAGs with multiple children to a parent
(representing reverting back to a snapshot then creating more
snapshots along those divergent execution timelines), where it is then
possible (but not guaranteed) that adding the --topological flag
changes the --tree output (the client-side --tree algorithm breaks
ties based on alphabetical sorting between two nodes that share the
same parent, while the --topological sort skips the client-side
alphabetical sort and ends up exposing the server's internal order for
siblings, whether that be historical creation order or dependent on a
random hash seed). But even if the results differ, they will still be
topologically correct.
Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2019-03-08 04:12:01 +00:00
|
|
|
* Copyright (C) 2005-2019 Red Hat, Inc.
|
2012-07-23 06:23:00 +00:00
|
|
|
*
|
|
|
|
* 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
|
2012-09-20 22:30:55 +00:00
|
|
|
* License along with this library. If not, see
|
2012-07-23 06:23:00 +00:00
|
|
|
* <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2012-08-20 23:29:03 +00:00
|
|
|
#include <config.h>
|
|
|
|
#include "virsh-snapshot.h"
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
#include <libxml/parser.h>
|
|
|
|
#include <libxml/xpath.h>
|
|
|
|
|
|
|
|
#include "internal.h"
|
2012-12-04 12:04:07 +00:00
|
|
|
#include "virbuffer.h"
|
2012-12-12 18:06:53 +00:00
|
|
|
#include "viralloc.h"
|
2013-05-09 18:59:04 +00:00
|
|
|
#include "virfile.h"
|
2017-04-11 08:18:06 +00:00
|
|
|
#include "virsh-util.h"
|
2015-04-01 15:09:04 +00:00
|
|
|
#include "virstring.h"
|
2012-12-13 18:13:21 +00:00
|
|
|
#include "virxml.h"
|
2019-03-20 02:33:23 +00:00
|
|
|
#include "conf/virdomainsnapshotobjlist.h"
|
2018-09-21 14:17:16 +00:00
|
|
|
#include "vsh-table.h"
|
2012-08-20 23:29:03 +00:00
|
|
|
|
2012-07-23 06:23:00 +00:00
|
|
|
/* Helper for snapshot-create and snapshot-create-as */
|
|
|
|
static bool
|
2015-06-15 16:53:58 +00:00
|
|
|
virshSnapshotCreate(vshControl *ctl, virDomainPtr dom, const char *buffer,
|
|
|
|
unsigned int flags, const char *from)
|
2012-07-23 06:23:00 +00:00
|
|
|
{
|
|
|
|
bool ret = false;
|
|
|
|
virDomainSnapshotPtr snapshot;
|
|
|
|
bool halt = false;
|
|
|
|
const char *name = NULL;
|
|
|
|
|
|
|
|
snapshot = virDomainSnapshotCreateXML(dom, buffer, flags);
|
|
|
|
|
snapshot: Add VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE flag
We've been doing a terrible job of performing XML validation in our
various API that parse XML with a corresponding schema (we started
with domains back in commit dd69a14f, v1.2.12, but didn't catch all
domain-related APIs, didn't document the use of the flag, and didn't
cover other XML). New APIs (like checkpoints) should do the validation
unconditionally, but it doesn't hurt to continue retrofitting existing
APIs to at least allow the option.
While there are many APIs that could be improved, this patch focuses
on wiring up a new snapshot XML creation flag through all the
hypervisors that support snapshots, as well as exposing it in 'virsh
snapshot-create'. For 'virsh snapshot-create-as', we blindly set the
flag without a command-line option, since the XML we create from the
command line should generally always comply (note that validation
might cause failures where it used to succeed, such as if we tighten
the RNG to reject a name of '../\n'); but blindly passing the flag
means we also have to add in fallback code to disable validation if
the server is too old to understand the flag.
Signed-off-by: Eric Blake <eblake@redhat.com>
Acked-by: Peter Krempa <pkrempa@redhat.com>
2019-07-06 03:05:37 +00:00
|
|
|
/* If no source file but validate was not recognized, try again without
|
|
|
|
* that flag. */
|
|
|
|
if (!snapshot && last_error->code == VIR_ERR_NO_SUPPORT && !from) {
|
|
|
|
flags &= ~VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE;
|
|
|
|
snapshot = virDomainSnapshotCreateXML(dom, buffer, flags);
|
|
|
|
}
|
|
|
|
|
2012-07-23 06:23:00 +00:00
|
|
|
/* Emulate --halt on older servers. */
|
|
|
|
if (!snapshot && last_error->code == VIR_ERR_INVALID_ARG &&
|
|
|
|
(flags & VIR_DOMAIN_SNAPSHOT_CREATE_HALT)) {
|
|
|
|
int persistent;
|
|
|
|
|
2012-07-25 11:41:49 +00:00
|
|
|
vshResetLibvirtError();
|
2012-07-23 06:23:00 +00:00
|
|
|
persistent = virDomainIsPersistent(dom);
|
|
|
|
if (persistent < 0) {
|
virsh: use common namespacing
Convert the exported items in virsh.h to use a common 'vsh' prefix.
* tools/virsh.h (VIRSH_MAX_XML_FILE): Rename...
(VSH_MAX_XML_FILE): ...and parenthesize.
(DIFF_MSEC, CTRL_CLOSE_BRACKET): Delete.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Remove prototype.
(editWriteToTempFile, editFile, editReadBackFile, prettyCapacity)
(virshReportError): Rename...
(vshEditWriteToTempFile, vshEditFile, vshEditReadBackFile)
(vshPrettyCapacity, vshReportError): ...into vsh namespace.
(jobWatchTimeoutFunc): Move to virsh-domain.c.
* tools/virsh.c (vshCommandRun): Inline former DIFF_MSEC.
(main): Inline former CTRL_CLOSE_BRACKET.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Make static.
(prettyCapacity, virshReportError, editWriteToTempFile, editFile):
Fix naming, and adjust usage.
(vshAskReedit, vshCommandRun, vshEventLoop, vshInit): Adjust
usage.
* tools/virsh-domain.c (cmdAttachDevice, cmdCPUCompare)
(cmdCPUBaseline, cmdCreate, cmdDefine, cmdDetachDevice)
(cmdUpdateDevice, cmdDesc, cmdUndefine, cmdStart, cmdVcpucount)
(cmdAttachDevice, cmdDomjobinfo): Likewise.
* tools/virsh-edit.c (do): Likewise.
* tools/virsh-interface.c (cmdInterfaceDefine): Likewise.
* tools/virsh-network.c (cmdNetworkCreate, cmdNetworkDefine):
Likewise.
* tools/virsh-nodedev.c (cmdNodeDeviceCreate): Likewise.
* tools/virsh-nwfilter.c (cmdNWFilterDefine): Likewise.
* tools/virsh-pool.c (cmdPoolCreate, cmdPoolDefine)
(cmdPoolDiscoverSources, cmdPoolList): Likewise.
* tools/virsh-secret.c (cmdSecretDefine): Likewise.
* tools/virsh-snapshot.c (cmdSnapshotCreate, vshSnapshotCreate)
(vshLookupSnapshot, cmdSnapshotEdit, cmdSnapshotCurrent)
(vshGetSnapshotParent): Likewise.
* tools/virsh-volume.c (cmdVolCreate, cmdVolCreateFrom)
(cmdVolInfo, cmdVolList): Likewise.
2012-08-19 04:10:17 +00:00
|
|
|
vshReportError(ctl);
|
2012-07-23 06:23:00 +00:00
|
|
|
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) {
|
virsh: use common namespacing
Convert the exported items in virsh.h to use a common 'vsh' prefix.
* tools/virsh.h (VIRSH_MAX_XML_FILE): Rename...
(VSH_MAX_XML_FILE): ...and parenthesize.
(DIFF_MSEC, CTRL_CLOSE_BRACKET): Delete.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Remove prototype.
(editWriteToTempFile, editFile, editReadBackFile, prettyCapacity)
(virshReportError): Rename...
(vshEditWriteToTempFile, vshEditFile, vshEditReadBackFile)
(vshPrettyCapacity, vshReportError): ...into vsh namespace.
(jobWatchTimeoutFunc): Move to virsh-domain.c.
* tools/virsh.c (vshCommandRun): Inline former DIFF_MSEC.
(main): Inline former CTRL_CLOSE_BRACKET.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Make static.
(prettyCapacity, virshReportError, editWriteToTempFile, editFile):
Fix naming, and adjust usage.
(vshAskReedit, vshCommandRun, vshEventLoop, vshInit): Adjust
usage.
* tools/virsh-domain.c (cmdAttachDevice, cmdCPUCompare)
(cmdCPUBaseline, cmdCreate, cmdDefine, cmdDetachDevice)
(cmdUpdateDevice, cmdDesc, cmdUndefine, cmdStart, cmdVcpucount)
(cmdAttachDevice, cmdDomjobinfo): Likewise.
* tools/virsh-edit.c (do): Likewise.
* tools/virsh-interface.c (cmdInterfaceDefine): Likewise.
* tools/virsh-network.c (cmdNetworkCreate, cmdNetworkDefine):
Likewise.
* tools/virsh-nodedev.c (cmdNodeDeviceCreate): Likewise.
* tools/virsh-nwfilter.c (cmdNWFilterDefine): Likewise.
* tools/virsh-pool.c (cmdPoolCreate, cmdPoolDefine)
(cmdPoolDiscoverSources, cmdPoolList): Likewise.
* tools/virsh-secret.c (cmdSecretDefine): Likewise.
* tools/virsh-snapshot.c (cmdSnapshotCreate, vshSnapshotCreate)
(vshLookupSnapshot, cmdSnapshotEdit, cmdSnapshotCurrent)
(vshGetSnapshotParent): Likewise.
* tools/virsh-volume.c (cmdVolCreate, cmdVolCreateFrom)
(cmdVolInfo, cmdVolList): Likewise.
2012-08-19 04:10:17 +00:00
|
|
|
vshReportError(ctl);
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
name = virDomainSnapshotGetName(snapshot);
|
|
|
|
if (!name) {
|
|
|
|
vshError(ctl, "%s", _("Could not get snapshot name"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (from)
|
2016-08-24 14:14:23 +00:00
|
|
|
vshPrintExtra(ctl, _("Domain snapshot %s created from '%s'"), name, from);
|
2012-07-23 06:23:00 +00:00
|
|
|
else
|
2016-08-24 14:14:23 +00:00
|
|
|
vshPrintExtra(ctl, _("Domain snapshot %s created"), name);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
ret = true;
|
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(snapshot);
|
2012-07-23 06:23:00 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "snapshot-create" command
|
|
|
|
*/
|
|
|
|
static const vshCmdInfo info_snapshot_create[] = {
|
2013-02-07 15:25:10 +00:00
|
|
|
{.name = "help",
|
|
|
|
.data = N_("Create a snapshot from XML")
|
|
|
|
},
|
|
|
|
{.name = "desc",
|
|
|
|
.data = N_("Create a snapshot (disk and RAM) from XML")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const vshCmdOptDef opts_snapshot_create[] = {
|
2017-10-31 08:24:21 +00:00
|
|
|
VIRSH_COMMON_OPT_DOMAIN_FULL(0),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "xmlfile",
|
2014-12-11 02:46:15 +00:00
|
|
|
.type = VSH_OT_STRING,
|
2013-01-14 14:44:18 +00:00
|
|
|
.help = N_("domain snapshot XML")
|
|
|
|
},
|
|
|
|
{.name = "redefine",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("redefine metadata for existing snapshot")
|
|
|
|
},
|
2016-01-09 13:36:28 +00:00
|
|
|
VIRSH_COMMON_OPT_CURRENT(N_("with redefine, set current snapshot")),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "no-metadata",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("take snapshot but create no metadata")
|
|
|
|
},
|
|
|
|
{.name = "halt",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("halt domain after snapshot is created")
|
|
|
|
},
|
|
|
|
{.name = "disk-only",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("capture disk state but not vm state")
|
|
|
|
},
|
|
|
|
{.name = "reuse-external",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("reuse any existing external files")
|
|
|
|
},
|
|
|
|
{.name = "quiesce",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("quiesce guest's file systems")
|
|
|
|
},
|
|
|
|
{.name = "atomic",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("require atomic operation")
|
|
|
|
},
|
2016-01-09 13:36:27 +00:00
|
|
|
VIRSH_COMMON_OPT_LIVE(N_("take a live snapshot")),
|
snapshot: Add VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE flag
We've been doing a terrible job of performing XML validation in our
various API that parse XML with a corresponding schema (we started
with domains back in commit dd69a14f, v1.2.12, but didn't catch all
domain-related APIs, didn't document the use of the flag, and didn't
cover other XML). New APIs (like checkpoints) should do the validation
unconditionally, but it doesn't hurt to continue retrofitting existing
APIs to at least allow the option.
While there are many APIs that could be improved, this patch focuses
on wiring up a new snapshot XML creation flag through all the
hypervisors that support snapshots, as well as exposing it in 'virsh
snapshot-create'. For 'virsh snapshot-create-as', we blindly set the
flag without a command-line option, since the XML we create from the
command line should generally always comply (note that validation
might cause failures where it used to succeed, such as if we tighten
the RNG to reject a name of '../\n'); but blindly passing the flag
means we also have to add in fallback code to disable validation if
the server is too old to understand the flag.
Signed-off-by: Eric Blake <eblake@redhat.com>
Acked-by: Peter Krempa <pkrempa@redhat.com>
2019-07-06 03:05:37 +00:00
|
|
|
{.name = "validate",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("validate the XML against the schema"),
|
|
|
|
},
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
2012-10-09 10:11:56 +00:00
|
|
|
if (vshCommandOptBool(cmd, "live"))
|
|
|
|
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_LIVE;
|
snapshot: Add VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE flag
We've been doing a terrible job of performing XML validation in our
various API that parse XML with a corresponding schema (we started
with domains back in commit dd69a14f, v1.2.12, but didn't catch all
domain-related APIs, didn't document the use of the flag, and didn't
cover other XML). New APIs (like checkpoints) should do the validation
unconditionally, but it doesn't hurt to continue retrofitting existing
APIs to at least allow the option.
While there are many APIs that could be improved, this patch focuses
on wiring up a new snapshot XML creation flag through all the
hypervisors that support snapshots, as well as exposing it in 'virsh
snapshot-create'. For 'virsh snapshot-create-as', we blindly set the
flag without a command-line option, since the XML we create from the
command line should generally always comply (note that validation
might cause failures where it used to succeed, such as if we tighten
the RNG to reject a name of '../\n'); but blindly passing the flag
means we also have to add in fallback code to disable validation if
the server is too old to understand the flag.
Signed-off-by: Eric Blake <eblake@redhat.com>
Acked-by: Peter Krempa <pkrempa@redhat.com>
2019-07-06 03:05:37 +00:00
|
|
|
if (vshCommandOptBool(cmd, "validate"))
|
|
|
|
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
2013-03-11 12:22:21 +00:00
|
|
|
if (vshCommandOptStringReq(ctl, cmd, "xmlfile", &from) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
if (!from) {
|
2019-10-18 15:24:02 +00:00
|
|
|
buffer = g_strdup("<domainsnapshot/>");
|
2013-01-21 16:48:26 +00:00
|
|
|
} else {
|
virsh: use common namespacing
Convert the exported items in virsh.h to use a common 'vsh' prefix.
* tools/virsh.h (VIRSH_MAX_XML_FILE): Rename...
(VSH_MAX_XML_FILE): ...and parenthesize.
(DIFF_MSEC, CTRL_CLOSE_BRACKET): Delete.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Remove prototype.
(editWriteToTempFile, editFile, editReadBackFile, prettyCapacity)
(virshReportError): Rename...
(vshEditWriteToTempFile, vshEditFile, vshEditReadBackFile)
(vshPrettyCapacity, vshReportError): ...into vsh namespace.
(jobWatchTimeoutFunc): Move to virsh-domain.c.
* tools/virsh.c (vshCommandRun): Inline former DIFF_MSEC.
(main): Inline former CTRL_CLOSE_BRACKET.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Make static.
(prettyCapacity, virshReportError, editWriteToTempFile, editFile):
Fix naming, and adjust usage.
(vshAskReedit, vshCommandRun, vshEventLoop, vshInit): Adjust
usage.
* tools/virsh-domain.c (cmdAttachDevice, cmdCPUCompare)
(cmdCPUBaseline, cmdCreate, cmdDefine, cmdDetachDevice)
(cmdUpdateDevice, cmdDesc, cmdUndefine, cmdStart, cmdVcpucount)
(cmdAttachDevice, cmdDomjobinfo): Likewise.
* tools/virsh-edit.c (do): Likewise.
* tools/virsh-interface.c (cmdInterfaceDefine): Likewise.
* tools/virsh-network.c (cmdNetworkCreate, cmdNetworkDefine):
Likewise.
* tools/virsh-nodedev.c (cmdNodeDeviceCreate): Likewise.
* tools/virsh-nwfilter.c (cmdNWFilterDefine): Likewise.
* tools/virsh-pool.c (cmdPoolCreate, cmdPoolDefine)
(cmdPoolDiscoverSources, cmdPoolList): Likewise.
* tools/virsh-secret.c (cmdSecretDefine): Likewise.
* tools/virsh-snapshot.c (cmdSnapshotCreate, vshSnapshotCreate)
(vshLookupSnapshot, cmdSnapshotEdit, cmdSnapshotCurrent)
(vshGetSnapshotParent): Likewise.
* tools/virsh-volume.c (cmdVolCreate, cmdVolCreateFrom)
(cmdVolInfo, cmdVolList): Likewise.
2012-08-19 04:10:17 +00:00
|
|
|
if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) {
|
2013-01-21 16:48:26 +00:00
|
|
|
vshSaveLibvirtError();
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
ret = virshSnapshotCreate(ctl, dom, buffer, flags, from);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2012-07-23 06:23:00 +00:00
|
|
|
VIR_FREE(buffer);
|
2017-04-11 10:16:52 +00:00
|
|
|
virshDomainFree(dom);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "snapshot-create-as" command
|
|
|
|
*/
|
2012-11-07 03:55:53 +00:00
|
|
|
static int
|
2015-06-15 16:53:58 +00:00
|
|
|
virshParseSnapshotMemspec(vshControl *ctl, virBufferPtr buf, const char *str)
|
2012-11-07 03:55:53 +00:00
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
const char *snapshot = NULL;
|
|
|
|
const char *file = NULL;
|
|
|
|
char **array = NULL;
|
|
|
|
int narray;
|
Convert 'int i' to 'size_t i' in tools/ files
Convert the type of loop iterators named 'i', 'j', k',
'ii', 'jj', 'kk', to be 'size_t' instead of 'int' or
'unsigned int', also santizing 'ii', 'jj', 'kk' to use
the normal 'i', 'j', 'k' naming
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-07-08 14:09:33 +00:00
|
|
|
size_t i;
|
2012-11-07 03:55:53 +00:00
|
|
|
|
|
|
|
if (!str)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
narray = vshStringToArray(str, &array);
|
|
|
|
if (narray < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
for (i = 0; i < narray; i++) {
|
|
|
|
if (!snapshot && STRPREFIX(array[i], "snapshot="))
|
|
|
|
snapshot = array[i] + strlen("snapshot=");
|
|
|
|
else if (!file && STRPREFIX(array[i], "file="))
|
|
|
|
file = array[i] + strlen("file=");
|
|
|
|
else if (!file && *array[i] == '/')
|
|
|
|
file = array[i];
|
|
|
|
else
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2014-03-12 23:52:07 +00:00
|
|
|
virBufferAddLit(buf, "<memory");
|
2012-11-07 03:55:53 +00:00
|
|
|
virBufferEscapeString(buf, " snapshot='%s'", snapshot);
|
|
|
|
virBufferEscapeString(buf, " file='%s'", file);
|
|
|
|
virBufferAddLit(buf, "/>\n");
|
|
|
|
ret = 0;
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2012-11-07 03:55:53 +00:00
|
|
|
if (ret < 0)
|
|
|
|
vshError(ctl, _("unable to parse memspec: %s"), str);
|
2020-08-02 17:36:03 +00:00
|
|
|
g_strfreev(array);
|
2012-11-07 03:55:53 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-07-23 06:23:00 +00:00
|
|
|
static int
|
2015-06-15 16:53:58 +00:00
|
|
|
virshParseSnapshotDiskspec(vshControl *ctl, virBufferPtr buf, const char *str)
|
2012-07-23 06:23:00 +00:00
|
|
|
{
|
|
|
|
int ret = -1;
|
virsh: make ,, escape parsing common
So far, none of the existing callers of vshStringToArray expected
the user to ever pass a literal comma; meanwhile, snapshot parsing
had rolled its own array parser. Moving the comma escaping into
the common function won't affect any existing callers, and will make
this function reusable for adding memory handling to snapshot parsing.
As a bonus, the testsuite was already testing snapshot parsing, so
the fact that the test still passes means that we are now giving
testsuite exposure to vshStringToArray.
* tools/virsh-snapshot.c (vshParseSnapshotDiskspec): Move ,,
parsing...
* tools/virsh.c (vshStringToArray): ...into common function.
Also, vshStrdup can't fail.
2012-11-07 00:45:09 +00:00
|
|
|
const char *name = NULL;
|
|
|
|
const char *snapshot = NULL;
|
|
|
|
const char *driver = NULL;
|
2019-07-08 09:46:34 +00:00
|
|
|
const char *stype = NULL;
|
virsh: make ,, escape parsing common
So far, none of the existing callers of vshStringToArray expected
the user to ever pass a literal comma; meanwhile, snapshot parsing
had rolled its own array parser. Moving the comma escaping into
the common function won't affect any existing callers, and will make
this function reusable for adding memory handling to snapshot parsing.
As a bonus, the testsuite was already testing snapshot parsing, so
the fact that the test still passes means that we are now giving
testsuite exposure to vshStringToArray.
* tools/virsh-snapshot.c (vshParseSnapshotDiskspec): Move ,,
parsing...
* tools/virsh.c (vshStringToArray): ...into common function.
Also, vshStrdup can't fail.
2012-11-07 00:45:09 +00:00
|
|
|
const char *file = NULL;
|
|
|
|
char **array = NULL;
|
|
|
|
int narray;
|
Convert 'int i' to 'size_t i' in tools/ files
Convert the type of loop iterators named 'i', 'j', k',
'ii', 'jj', 'kk', to be 'size_t' instead of 'int' or
'unsigned int', also santizing 'ii', 'jj', 'kk' to use
the normal 'i', 'j', 'k' naming
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-07-08 14:09:33 +00:00
|
|
|
size_t i;
|
2019-07-08 09:46:34 +00:00
|
|
|
bool isFile = true;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
virsh: make ,, escape parsing common
So far, none of the existing callers of vshStringToArray expected
the user to ever pass a literal comma; meanwhile, snapshot parsing
had rolled its own array parser. Moving the comma escaping into
the common function won't affect any existing callers, and will make
this function reusable for adding memory handling to snapshot parsing.
As a bonus, the testsuite was already testing snapshot parsing, so
the fact that the test still passes means that we are now giving
testsuite exposure to vshStringToArray.
* tools/virsh-snapshot.c (vshParseSnapshotDiskspec): Move ,,
parsing...
* tools/virsh.c (vshStringToArray): ...into common function.
Also, vshStrdup can't fail.
2012-11-07 00:45:09 +00:00
|
|
|
narray = vshStringToArray(str, &array);
|
|
|
|
if (narray <= 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
virsh: make ,, escape parsing common
So far, none of the existing callers of vshStringToArray expected
the user to ever pass a literal comma; meanwhile, snapshot parsing
had rolled its own array parser. Moving the comma escaping into
the common function won't affect any existing callers, and will make
this function reusable for adding memory handling to snapshot parsing.
As a bonus, the testsuite was already testing snapshot parsing, so
the fact that the test still passes means that we are now giving
testsuite exposure to vshStringToArray.
* tools/virsh-snapshot.c (vshParseSnapshotDiskspec): Move ,,
parsing...
* tools/virsh.c (vshStringToArray): ...into common function.
Also, vshStrdup can't fail.
2012-11-07 00:45:09 +00:00
|
|
|
|
|
|
|
name = array[0];
|
|
|
|
for (i = 1; i < narray; i++) {
|
|
|
|
if (!snapshot && STRPREFIX(array[i], "snapshot="))
|
|
|
|
snapshot = array[i] + strlen("snapshot=");
|
|
|
|
else if (!driver && STRPREFIX(array[i], "driver="))
|
|
|
|
driver = array[i] + strlen("driver=");
|
2019-07-08 09:46:34 +00:00
|
|
|
else if (!stype && STRPREFIX(array[i], "stype="))
|
|
|
|
stype = array[i] + strlen("stype=");
|
virsh: make ,, escape parsing common
So far, none of the existing callers of vshStringToArray expected
the user to ever pass a literal comma; meanwhile, snapshot parsing
had rolled its own array parser. Moving the comma escaping into
the common function won't affect any existing callers, and will make
this function reusable for adding memory handling to snapshot parsing.
As a bonus, the testsuite was already testing snapshot parsing, so
the fact that the test still passes means that we are now giving
testsuite exposure to vshStringToArray.
* tools/virsh-snapshot.c (vshParseSnapshotDiskspec): Move ,,
parsing...
* tools/virsh.c (vshStringToArray): ...into common function.
Also, vshStrdup can't fail.
2012-11-07 00:45:09 +00:00
|
|
|
else if (!file && STRPREFIX(array[i], "file="))
|
|
|
|
file = array[i] + strlen("file=");
|
2012-07-23 06:23:00 +00:00
|
|
|
else
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2014-03-12 23:52:07 +00:00
|
|
|
virBufferEscapeString(buf, "<disk name='%s'", name);
|
2012-07-23 06:23:00 +00:00
|
|
|
if (snapshot)
|
|
|
|
virBufferAsprintf(buf, " snapshot='%s'", snapshot);
|
2019-07-08 09:46:34 +00:00
|
|
|
if (stype) {
|
|
|
|
if (STREQ(stype, "block")) {
|
|
|
|
isFile = false;
|
|
|
|
} else if (STRNEQ(stype, "file")) {
|
|
|
|
vshError(ctl, _("Unknown storage type: '%s'"), stype);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
virBufferAsprintf(buf, " type='%s'", stype);
|
|
|
|
}
|
2012-07-23 06:23:00 +00:00
|
|
|
if (driver || file) {
|
|
|
|
virBufferAddLit(buf, ">\n");
|
2014-03-12 23:52:07 +00:00
|
|
|
virBufferAdjustIndent(buf, 2);
|
2012-07-23 06:23:00 +00:00
|
|
|
if (driver)
|
2014-03-12 23:52:07 +00:00
|
|
|
virBufferAsprintf(buf, "<driver type='%s'/>\n", driver);
|
2019-07-08 09:46:34 +00:00
|
|
|
if (file) {
|
|
|
|
if (isFile)
|
|
|
|
virBufferEscapeString(buf, "<source file='%s'/>\n", file);
|
|
|
|
else
|
|
|
|
virBufferEscapeString(buf, "<source dev='%s'/>\n", file);
|
|
|
|
}
|
2014-03-12 23:52:07 +00:00
|
|
|
virBufferAdjustIndent(buf, -2);
|
|
|
|
virBufferAddLit(buf, "</disk>\n");
|
2012-07-23 06:23:00 +00:00
|
|
|
} else {
|
|
|
|
virBufferAddLit(buf, "/>\n");
|
|
|
|
}
|
|
|
|
ret = 0;
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2012-07-23 06:23:00 +00:00
|
|
|
if (ret < 0)
|
|
|
|
vshError(ctl, _("unable to parse diskspec: %s"), str);
|
2020-08-02 17:36:03 +00:00
|
|
|
g_strfreev(array);
|
2012-07-23 06:23:00 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const vshCmdInfo info_snapshot_create_as[] = {
|
2013-02-07 15:25:10 +00:00
|
|
|
{.name = "help",
|
|
|
|
.data = N_("Create a snapshot from a set of args")
|
|
|
|
},
|
|
|
|
{.name = "desc",
|
|
|
|
.data = N_("Create a snapshot (disk and RAM) from arguments")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const vshCmdOptDef opts_snapshot_create_as[] = {
|
2017-10-31 08:24:21 +00:00
|
|
|
VIRSH_COMMON_OPT_DOMAIN_FULL(0),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "name",
|
2014-12-11 02:46:15 +00:00
|
|
|
.type = VSH_OT_STRING,
|
2013-01-14 14:44:18 +00:00
|
|
|
.help = N_("name of snapshot")
|
|
|
|
},
|
|
|
|
{.name = "description",
|
2014-12-11 02:46:15 +00:00
|
|
|
.type = VSH_OT_STRING,
|
2013-01-14 14:44:18 +00:00
|
|
|
.help = N_("description of snapshot")
|
|
|
|
},
|
|
|
|
{.name = "print-xml",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("print XML document rather than create")
|
|
|
|
},
|
|
|
|
{.name = "no-metadata",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("take snapshot but create no metadata")
|
|
|
|
},
|
|
|
|
{.name = "halt",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("halt domain after snapshot is created")
|
|
|
|
},
|
|
|
|
{.name = "disk-only",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("capture disk state but not vm state")
|
|
|
|
},
|
|
|
|
{.name = "reuse-external",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("reuse any existing external files")
|
|
|
|
},
|
|
|
|
{.name = "quiesce",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("quiesce guest's file systems")
|
|
|
|
},
|
|
|
|
{.name = "atomic",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("require atomic operation")
|
|
|
|
},
|
2016-01-09 13:36:27 +00:00
|
|
|
VIRSH_COMMON_OPT_LIVE(N_("take a live snapshot")),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "memspec",
|
2013-03-12 15:34:31 +00:00
|
|
|
.type = VSH_OT_STRING,
|
2013-01-14 14:44:18 +00:00
|
|
|
.flags = VSH_OFLAG_REQ_OPT,
|
|
|
|
.help = N_("memory attributes: [file=]name[,snapshot=type]")
|
|
|
|
},
|
|
|
|
{.name = "diskspec",
|
|
|
|
.type = VSH_OT_ARGV,
|
2019-07-08 09:46:34 +00:00
|
|
|
.help = N_("disk attributes: disk[,snapshot=type][,driver=type][,stype=type][,file=name]")
|
2013-01-14 14:44:18 +00:00
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
2012-11-07 03:55:53 +00:00
|
|
|
const char *memspec = NULL;
|
2020-07-02 23:40:16 +00:00
|
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
snapshot: Add VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE flag
We've been doing a terrible job of performing XML validation in our
various API that parse XML with a corresponding schema (we started
with domains back in commit dd69a14f, v1.2.12, but didn't catch all
domain-related APIs, didn't document the use of the flag, and didn't
cover other XML). New APIs (like checkpoints) should do the validation
unconditionally, but it doesn't hurt to continue retrofitting existing
APIs to at least allow the option.
While there are many APIs that could be improved, this patch focuses
on wiring up a new snapshot XML creation flag through all the
hypervisors that support snapshots, as well as exposing it in 'virsh
snapshot-create'. For 'virsh snapshot-create-as', we blindly set the
flag without a command-line option, since the XML we create from the
command line should generally always comply (note that validation
might cause failures where it used to succeed, such as if we tighten
the RNG to reject a name of '../\n'); but blindly passing the flag
means we also have to add in fallback code to disable validation if
the server is too old to understand the flag.
Signed-off-by: Eric Blake <eblake@redhat.com>
Acked-by: Peter Krempa <pkrempa@redhat.com>
2019-07-06 03:05:37 +00:00
|
|
|
unsigned int flags = VIR_DOMAIN_SNAPSHOT_CREATE_VALIDATE;
|
2012-07-23 06:23:00 +00:00
|
|
|
const vshCmdOpt *opt = NULL;
|
|
|
|
|
2019-06-20 12:44:51 +00:00
|
|
|
if (vshCommandOptBool(cmd, "no-metadata"))
|
2012-07-23 06:23:00 +00:00
|
|
|
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;
|
2012-10-09 10:11:56 +00:00
|
|
|
if (vshCommandOptBool(cmd, "live"))
|
|
|
|
flags |= VIR_DOMAIN_SNAPSHOT_CREATE_LIVE;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
|
2013-02-11 13:00:36 +00:00
|
|
|
return false;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2013-01-21 17:11:41 +00:00
|
|
|
if (vshCommandOptStringReq(ctl, cmd, "name", &name) < 0 ||
|
|
|
|
vshCommandOptStringReq(ctl, cmd, "description", &desc) < 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
virBufferAddLit(&buf, "<domainsnapshot>\n");
|
2014-03-12 23:52:07 +00:00
|
|
|
virBufferAdjustIndent(&buf, 2);
|
|
|
|
virBufferEscapeString(&buf, "<name>%s</name>\n", name);
|
|
|
|
virBufferEscapeString(&buf, "<description>%s</description>\n", desc);
|
2012-11-07 03:55:53 +00:00
|
|
|
|
2013-01-21 17:11:41 +00:00
|
|
|
if (vshCommandOptStringReq(ctl, cmd, "memspec", &memspec) < 0)
|
2012-11-07 03:55:53 +00:00
|
|
|
goto cleanup;
|
2012-11-22 10:00:14 +00:00
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (memspec && virshParseSnapshotMemspec(ctl, &buf, memspec) < 0)
|
2012-11-22 10:00:14 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
2012-07-23 06:23:00 +00:00
|
|
|
if (vshCommandOptBool(cmd, "diskspec")) {
|
2014-03-12 23:52:07 +00:00
|
|
|
virBufferAddLit(&buf, "<disks>\n");
|
|
|
|
virBufferAdjustIndent(&buf, 2);
|
2015-06-02 09:17:28 +00:00
|
|
|
while ((opt = vshCommandOptArgv(ctl, cmd, opt))) {
|
2015-06-15 16:53:58 +00:00
|
|
|
if (virshParseSnapshotDiskspec(ctl, &buf, opt->data) < 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
2014-03-12 23:52:07 +00:00
|
|
|
virBufferAdjustIndent(&buf, -2);
|
|
|
|
virBufferAddLit(&buf, "</disks>\n");
|
2012-07-23 06:23:00 +00:00
|
|
|
}
|
2014-03-12 23:52:07 +00:00
|
|
|
virBufferAdjustIndent(&buf, -2);
|
2012-07-23 06:23:00 +00:00
|
|
|
virBufferAddLit(&buf, "</domainsnapshot>\n");
|
|
|
|
|
2013-02-11 13:00:36 +00:00
|
|
|
buffer = virBufferContentAndReset(&buf);
|
|
|
|
|
2012-07-23 06:23:00 +00:00
|
|
|
if (vshCommandOptBool(cmd, "print-xml")) {
|
|
|
|
vshPrint(ctl, "%s\n", buffer);
|
|
|
|
ret = true;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
ret = virshSnapshotCreate(ctl, dom, buffer, flags, NULL);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2012-07-23 06:23:00 +00:00
|
|
|
VIR_FREE(buffer);
|
2017-04-11 10:16:52 +00:00
|
|
|
virshDomainFree(dom);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
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
|
2015-06-15 16:53:58 +00:00
|
|
|
virshLookupSnapshot(vshControl *ctl, const vshCmd *cmd,
|
|
|
|
const char *arg, bool exclusive, virDomainPtr dom,
|
|
|
|
virDomainSnapshotPtr *snap, const char **name)
|
2012-07-23 06:23:00 +00:00
|
|
|
{
|
|
|
|
bool current = vshCommandOptBool(cmd, "current");
|
|
|
|
const char *snapname = NULL;
|
|
|
|
|
2013-01-21 17:11:41 +00:00
|
|
|
if (vshCommandOptStringReq(ctl, cmd, arg, &snapname) < 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
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) {
|
virsh: use common namespacing
Convert the exported items in virsh.h to use a common 'vsh' prefix.
* tools/virsh.h (VIRSH_MAX_XML_FILE): Rename...
(VSH_MAX_XML_FILE): ...and parenthesize.
(DIFF_MSEC, CTRL_CLOSE_BRACKET): Delete.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Remove prototype.
(editWriteToTempFile, editFile, editReadBackFile, prettyCapacity)
(virshReportError): Rename...
(vshEditWriteToTempFile, vshEditFile, vshEditReadBackFile)
(vshPrettyCapacity, vshReportError): ...into vsh namespace.
(jobWatchTimeoutFunc): Move to virsh-domain.c.
* tools/virsh.c (vshCommandRun): Inline former DIFF_MSEC.
(main): Inline former CTRL_CLOSE_BRACKET.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Make static.
(prettyCapacity, virshReportError, editWriteToTempFile, editFile):
Fix naming, and adjust usage.
(vshAskReedit, vshCommandRun, vshEventLoop, vshInit): Adjust
usage.
* tools/virsh-domain.c (cmdAttachDevice, cmdCPUCompare)
(cmdCPUBaseline, cmdCreate, cmdDefine, cmdDetachDevice)
(cmdUpdateDevice, cmdDesc, cmdUndefine, cmdStart, cmdVcpucount)
(cmdAttachDevice, cmdDomjobinfo): Likewise.
* tools/virsh-edit.c (do): Likewise.
* tools/virsh-interface.c (cmdInterfaceDefine): Likewise.
* tools/virsh-network.c (cmdNetworkCreate, cmdNetworkDefine):
Likewise.
* tools/virsh-nodedev.c (cmdNodeDeviceCreate): Likewise.
* tools/virsh-nwfilter.c (cmdNWFilterDefine): Likewise.
* tools/virsh-pool.c (cmdPoolCreate, cmdPoolDefine)
(cmdPoolDiscoverSources, cmdPoolList): Likewise.
* tools/virsh-secret.c (cmdSecretDefine): Likewise.
* tools/virsh-snapshot.c (cmdSnapshotCreate, vshSnapshotCreate)
(vshLookupSnapshot, cmdSnapshotEdit, cmdSnapshotCurrent)
(vshGetSnapshotParent): Likewise.
* tools/virsh-volume.c (cmdVolCreate, cmdVolCreateFrom)
(cmdVolInfo, cmdVolList): Likewise.
2012-08-19 04:10:17 +00:00
|
|
|
vshReportError(ctl);
|
2012-07-23 06:23:00 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*name = virDomainSnapshotGetName(*snap);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "snapshot-edit" command
|
|
|
|
*/
|
|
|
|
static const vshCmdInfo info_snapshot_edit[] = {
|
2013-02-07 15:25:10 +00:00
|
|
|
{.name = "help",
|
|
|
|
.data = N_("edit XML for a snapshot")
|
|
|
|
},
|
|
|
|
{.name = "desc",
|
|
|
|
.data = N_("Edit the domain snapshot XML for a named snapshot")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const vshCmdOptDef opts_snapshot_edit[] = {
|
2020-09-11 07:13:03 +00:00
|
|
|
VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "snapshotname",
|
2014-12-11 02:46:15 +00:00
|
|
|
.type = VSH_OT_STRING,
|
2018-01-12 14:28:30 +00:00
|
|
|
.help = N_("snapshot name"),
|
|
|
|
.completer = virshSnapshotNameCompleter,
|
2013-01-14 14:44:18 +00:00
|
|
|
},
|
2016-01-09 13:36:28 +00:00
|
|
|
VIRSH_COMMON_OPT_CURRENT(N_("also set edited snapshot as current")),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "rename",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("allow renaming an existing snapshot")
|
|
|
|
},
|
|
|
|
{.name = "clone",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("allow cloning to new name")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static bool
|
|
|
|
cmdSnapshotEdit(vshControl *ctl, const vshCmd *cmd)
|
|
|
|
{
|
|
|
|
virDomainPtr dom = NULL;
|
|
|
|
virDomainSnapshotPtr snapshot = NULL;
|
|
|
|
virDomainSnapshotPtr edited = NULL;
|
2012-11-15 10:37:32 +00:00
|
|
|
const char *name = NULL;
|
2012-07-23 06:23:00 +00:00
|
|
|
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");
|
|
|
|
|
2013-03-07 10:17:48 +00:00
|
|
|
VSH_EXCLUSIVE_OPTIONS_EXPR("rename", rename_okay, "clone", clone_okay)
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
if (vshCommandOptBool(cmd, "current") &&
|
|
|
|
vshCommandOptBool(cmd, "snapshotname"))
|
|
|
|
define_flags |= VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT;
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
|
2013-03-07 10:18:18 +00:00
|
|
|
return false;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (virshLookupSnapshot(ctl, cmd, "snapshotname", false, dom,
|
|
|
|
&snapshot, &name) < 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
#define EDIT_GET_XML \
|
|
|
|
virDomainSnapshotGetXMLDesc(snapshot, getxml_flags)
|
2017-11-03 12:09:47 +00:00
|
|
|
#define EDIT_NOT_CHANGED \
|
|
|
|
do { \
|
|
|
|
/* Depending on flags, we re-edit even if XML is unchanged. */ \
|
|
|
|
if (!(define_flags & VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT)) { \
|
|
|
|
vshPrintExtra(ctl, \
|
2016-11-04 14:22:26 +00:00
|
|
|
_("Snapshot %s XML configuration not changed.\n"), \
|
2017-11-03 12:09:47 +00:00
|
|
|
name); \
|
|
|
|
ret = true; \
|
|
|
|
goto edit_cleanup; \
|
|
|
|
} \
|
2014-11-14 14:57:17 +00:00
|
|
|
} while (0)
|
2012-07-23 06:23:00 +00:00
|
|
|
#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)
|
|
|
|
#include "virsh-edit.c"
|
|
|
|
|
|
|
|
edited_name = virDomainSnapshotGetName(edited);
|
|
|
|
if (STREQ(name, edited_name)) {
|
2016-08-24 14:14:23 +00:00
|
|
|
vshPrintExtra(ctl, _("Snapshot %s edited.\n"), name);
|
2012-07-23 06:23:00 +00:00
|
|
|
} else if (clone_okay) {
|
2016-08-24 14:14:23 +00:00
|
|
|
vshPrintExtra(ctl, _("Snapshot %s cloned to %s.\n"), name,
|
|
|
|
edited_name);
|
2012-07-23 06:23:00 +00:00
|
|
|
} else {
|
|
|
|
unsigned int delete_flags;
|
|
|
|
|
|
|
|
delete_flags = VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY;
|
|
|
|
if (virDomainSnapshotDelete(rename_okay ? snapshot : edited,
|
|
|
|
delete_flags) < 0) {
|
virsh: use common namespacing
Convert the exported items in virsh.h to use a common 'vsh' prefix.
* tools/virsh.h (VIRSH_MAX_XML_FILE): Rename...
(VSH_MAX_XML_FILE): ...and parenthesize.
(DIFF_MSEC, CTRL_CLOSE_BRACKET): Delete.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Remove prototype.
(editWriteToTempFile, editFile, editReadBackFile, prettyCapacity)
(virshReportError): Rename...
(vshEditWriteToTempFile, vshEditFile, vshEditReadBackFile)
(vshPrettyCapacity, vshReportError): ...into vsh namespace.
(jobWatchTimeoutFunc): Move to virsh-domain.c.
* tools/virsh.c (vshCommandRun): Inline former DIFF_MSEC.
(main): Inline former CTRL_CLOSE_BRACKET.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Make static.
(prettyCapacity, virshReportError, editWriteToTempFile, editFile):
Fix naming, and adjust usage.
(vshAskReedit, vshCommandRun, vshEventLoop, vshInit): Adjust
usage.
* tools/virsh-domain.c (cmdAttachDevice, cmdCPUCompare)
(cmdCPUBaseline, cmdCreate, cmdDefine, cmdDetachDevice)
(cmdUpdateDevice, cmdDesc, cmdUndefine, cmdStart, cmdVcpucount)
(cmdAttachDevice, cmdDomjobinfo): Likewise.
* tools/virsh-edit.c (do): Likewise.
* tools/virsh-interface.c (cmdInterfaceDefine): Likewise.
* tools/virsh-network.c (cmdNetworkCreate, cmdNetworkDefine):
Likewise.
* tools/virsh-nodedev.c (cmdNodeDeviceCreate): Likewise.
* tools/virsh-nwfilter.c (cmdNWFilterDefine): Likewise.
* tools/virsh-pool.c (cmdPoolCreate, cmdPoolDefine)
(cmdPoolDiscoverSources, cmdPoolList): Likewise.
* tools/virsh-secret.c (cmdSecretDefine): Likewise.
* tools/virsh-snapshot.c (cmdSnapshotCreate, vshSnapshotCreate)
(vshLookupSnapshot, cmdSnapshotEdit, cmdSnapshotCurrent)
(vshGetSnapshotParent): Likewise.
* tools/virsh-volume.c (cmdVolCreate, cmdVolCreateFrom)
(cmdVolInfo, cmdVolList): Likewise.
2012-08-19 04:10:17 +00:00
|
|
|
vshReportError(ctl);
|
2012-07-23 06:23:00 +00:00
|
|
|
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;
|
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2012-11-15 10:37:32 +00:00
|
|
|
if (!ret && name)
|
2012-08-21 20:37:34 +00:00
|
|
|
vshError(ctl, _("Failed to update %s"), name);
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(edited);
|
|
|
|
virshDomainSnapshotFree(snapshot);
|
2017-04-11 10:16:52 +00:00
|
|
|
virshDomainFree(dom);
|
2012-07-23 06:23:00 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "snapshot-current" command
|
|
|
|
*/
|
|
|
|
static const vshCmdInfo info_snapshot_current[] = {
|
2013-02-07 15:25:10 +00:00
|
|
|
{.name = "help",
|
|
|
|
.data = N_("Get or set the current snapshot")
|
|
|
|
},
|
|
|
|
{.name = "desc",
|
|
|
|
.data = N_("Get or set the current snapshot")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const vshCmdOptDef opts_snapshot_current[] = {
|
2020-09-11 07:13:03 +00:00
|
|
|
VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "name",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("list the name, rather than the full xml")
|
|
|
|
},
|
|
|
|
{.name = "security-info",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("include security sensitive information in XML dump")
|
|
|
|
},
|
|
|
|
{.name = "snapshotname",
|
2014-12-11 02:46:15 +00:00
|
|
|
.type = VSH_OT_STRING,
|
2018-01-12 14:28:30 +00:00
|
|
|
.help = N_("name of existing snapshot to make current"),
|
|
|
|
.completer = virshSnapshotNameCompleter,
|
2013-01-14 14:44:18 +00:00
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2013-03-07 10:07:03 +00:00
|
|
|
VSH_EXCLUSIVE_OPTIONS("name", "snapshotname");
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (!(dom = virshCommandOptDomain(ctl, cmd, &domname)))
|
2013-03-07 10:07:03 +00:00
|
|
|
return false;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2013-01-21 17:11:41 +00:00
|
|
|
if (vshCommandOptStringReq(ctl, cmd, "snapshotname", &snapshotname) < 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
2013-01-21 17:11:41 +00:00
|
|
|
|
2012-07-23 06:23:00 +00:00
|
|
|
if (snapshotname) {
|
|
|
|
virDomainSnapshotPtr snapshot2 = NULL;
|
|
|
|
flags = (VIR_DOMAIN_SNAPSHOT_CREATE_REDEFINE |
|
|
|
|
VIR_DOMAIN_SNAPSHOT_CREATE_CURRENT);
|
|
|
|
|
2013-03-07 10:07:03 +00:00
|
|
|
if (!(snapshot = virDomainSnapshotLookupByName(dom, snapshotname, 0)))
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
2013-03-07 10:07:03 +00:00
|
|
|
|
2012-07-23 06:23:00 +00:00
|
|
|
xml = virDomainSnapshotGetXMLDesc(snapshot, VIR_DOMAIN_XML_SECURE);
|
|
|
|
if (!xml)
|
|
|
|
goto cleanup;
|
2013-03-07 10:07:03 +00:00
|
|
|
|
2012-07-23 06:23:00 +00:00
|
|
|
/* 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;
|
2013-03-07 10:07:03 +00:00
|
|
|
|
|
|
|
if (!(snapshot2 = virDomainSnapshotCreateXML(dom, xml, flags)))
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
2013-03-07 10:07:03 +00:00
|
|
|
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(snapshot2);
|
2016-08-24 14:14:23 +00:00
|
|
|
vshPrintExtra(ctl, _("Snapshot %s set as current"), snapshotname);
|
2012-07-23 06:23:00 +00:00
|
|
|
ret = true;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
2013-03-07 10:07:03 +00:00
|
|
|
if ((current = virDomainHasCurrentSnapshot(dom, 0)) < 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
2013-03-07 10:07:03 +00:00
|
|
|
|
|
|
|
if (!current) {
|
2012-07-23 06:23:00 +00:00
|
|
|
vshError(ctl, _("domain '%s' has no current snapshot"), domname);
|
|
|
|
goto cleanup;
|
|
|
|
} else {
|
|
|
|
if (!(snapshot = virDomainSnapshotCurrent(dom, 0)))
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
if (vshCommandOptBool(cmd, "name")) {
|
2013-03-07 10:07:03 +00:00
|
|
|
const char *name;
|
|
|
|
if (!(name = virDomainSnapshotGetName(snapshot)))
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
2013-03-07 10:07:03 +00:00
|
|
|
|
|
|
|
vshPrint(ctl, "%s", name);
|
2012-07-23 06:23:00 +00:00
|
|
|
} else {
|
2013-03-07 10:07:03 +00:00
|
|
|
if (!(xml = virDomainSnapshotGetXMLDesc(snapshot, flags)))
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
2013-03-07 10:07:03 +00:00
|
|
|
vshPrint(ctl, "%s", xml);
|
|
|
|
}
|
2012-07-23 06:23:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = true;
|
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2012-07-23 06:23:00 +00:00
|
|
|
if (!ret)
|
virsh: use common namespacing
Convert the exported items in virsh.h to use a common 'vsh' prefix.
* tools/virsh.h (VIRSH_MAX_XML_FILE): Rename...
(VSH_MAX_XML_FILE): ...and parenthesize.
(DIFF_MSEC, CTRL_CLOSE_BRACKET): Delete.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Remove prototype.
(editWriteToTempFile, editFile, editReadBackFile, prettyCapacity)
(virshReportError): Rename...
(vshEditWriteToTempFile, vshEditFile, vshEditReadBackFile)
(vshPrettyCapacity, vshReportError): ...into vsh namespace.
(jobWatchTimeoutFunc): Move to virsh-domain.c.
* tools/virsh.c (vshCommandRun): Inline former DIFF_MSEC.
(main): Inline former CTRL_CLOSE_BRACKET.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Make static.
(prettyCapacity, virshReportError, editWriteToTempFile, editFile):
Fix naming, and adjust usage.
(vshAskReedit, vshCommandRun, vshEventLoop, vshInit): Adjust
usage.
* tools/virsh-domain.c (cmdAttachDevice, cmdCPUCompare)
(cmdCPUBaseline, cmdCreate, cmdDefine, cmdDetachDevice)
(cmdUpdateDevice, cmdDesc, cmdUndefine, cmdStart, cmdVcpucount)
(cmdAttachDevice, cmdDomjobinfo): Likewise.
* tools/virsh-edit.c (do): Likewise.
* tools/virsh-interface.c (cmdInterfaceDefine): Likewise.
* tools/virsh-network.c (cmdNetworkCreate, cmdNetworkDefine):
Likewise.
* tools/virsh-nodedev.c (cmdNodeDeviceCreate): Likewise.
* tools/virsh-nwfilter.c (cmdNWFilterDefine): Likewise.
* tools/virsh-pool.c (cmdPoolCreate, cmdPoolDefine)
(cmdPoolDiscoverSources, cmdPoolList): Likewise.
* tools/virsh-secret.c (cmdSecretDefine): Likewise.
* tools/virsh-snapshot.c (cmdSnapshotCreate, vshSnapshotCreate)
(vshLookupSnapshot, cmdSnapshotEdit, cmdSnapshotCurrent)
(vshGetSnapshotParent): Likewise.
* tools/virsh-volume.c (cmdVolCreate, cmdVolCreateFrom)
(cmdVolInfo, cmdVolList): Likewise.
2012-08-19 04:10:17 +00:00
|
|
|
vshReportError(ctl);
|
2012-07-23 06:23:00 +00:00
|
|
|
VIR_FREE(xml);
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(snapshot);
|
2017-04-11 10:16:52 +00:00
|
|
|
virshDomainFree(dom);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
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
|
2015-06-15 16:53:58 +00:00
|
|
|
virshGetSnapshotParent(vshControl *ctl, virDomainSnapshotPtr snapshot,
|
|
|
|
char **parent_name)
|
2012-07-23 06:23:00 +00:00
|
|
|
{
|
|
|
|
virDomainSnapshotPtr parent = NULL;
|
|
|
|
char *xml = NULL;
|
|
|
|
xmlDocPtr xmldoc = NULL;
|
|
|
|
xmlXPathContextPtr ctxt = NULL;
|
|
|
|
int ret = -1;
|
2015-06-15 16:53:58 +00:00
|
|
|
virshControlPtr priv = ctl->privData;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
*parent_name = NULL;
|
|
|
|
|
|
|
|
/* Try new API, since it is faster. */
|
2015-06-15 16:53:58 +00:00
|
|
|
if (!priv->useSnapshotOld) {
|
2012-07-23 06:23:00 +00:00
|
|
|
parent = virDomainSnapshotGetParent(snapshot, 0);
|
|
|
|
if (parent) {
|
|
|
|
/* API works, and virDomainSnapshotGetName will succeed */
|
2019-10-18 15:24:02 +00:00
|
|
|
*parent_name = g_strdup(virDomainSnapshotGetName(parent));
|
2012-07-23 06:23:00 +00:00
|
|
|
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. */
|
2015-06-15 16:53:58 +00:00
|
|
|
priv->useSnapshotOld = true;
|
2012-07-23 06:23:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2012-07-23 06:23:00 +00:00
|
|
|
if (ret < 0) {
|
virsh: use common namespacing
Convert the exported items in virsh.h to use a common 'vsh' prefix.
* tools/virsh.h (VIRSH_MAX_XML_FILE): Rename...
(VSH_MAX_XML_FILE): ...and parenthesize.
(DIFF_MSEC, CTRL_CLOSE_BRACKET): Delete.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Remove prototype.
(editWriteToTempFile, editFile, editReadBackFile, prettyCapacity)
(virshReportError): Rename...
(vshEditWriteToTempFile, vshEditFile, vshEditReadBackFile)
(vshPrettyCapacity, vshReportError): ...into vsh namespace.
(jobWatchTimeoutFunc): Move to virsh-domain.c.
* tools/virsh.c (vshCommandRun): Inline former DIFF_MSEC.
(main): Inline former CTRL_CLOSE_BRACKET.
(vshUsage, vshInit, vshDeinit, vshParseArgv): Make static.
(prettyCapacity, virshReportError, editWriteToTempFile, editFile):
Fix naming, and adjust usage.
(vshAskReedit, vshCommandRun, vshEventLoop, vshInit): Adjust
usage.
* tools/virsh-domain.c (cmdAttachDevice, cmdCPUCompare)
(cmdCPUBaseline, cmdCreate, cmdDefine, cmdDetachDevice)
(cmdUpdateDevice, cmdDesc, cmdUndefine, cmdStart, cmdVcpucount)
(cmdAttachDevice, cmdDomjobinfo): Likewise.
* tools/virsh-edit.c (do): Likewise.
* tools/virsh-interface.c (cmdInterfaceDefine): Likewise.
* tools/virsh-network.c (cmdNetworkCreate, cmdNetworkDefine):
Likewise.
* tools/virsh-nodedev.c (cmdNodeDeviceCreate): Likewise.
* tools/virsh-nwfilter.c (cmdNWFilterDefine): Likewise.
* tools/virsh-pool.c (cmdPoolCreate, cmdPoolDefine)
(cmdPoolDiscoverSources, cmdPoolList): Likewise.
* tools/virsh-secret.c (cmdSecretDefine): Likewise.
* tools/virsh-snapshot.c (cmdSnapshotCreate, vshSnapshotCreate)
(vshLookupSnapshot, cmdSnapshotEdit, cmdSnapshotCurrent)
(vshGetSnapshotParent): Likewise.
* tools/virsh-volume.c (cmdVolCreate, cmdVolCreateFrom)
(cmdVolInfo, cmdVolList): Likewise.
2012-08-19 04:10:17 +00:00
|
|
|
vshReportError(ctl);
|
2012-07-23 06:23:00 +00:00
|
|
|
vshError(ctl, "%s", _("unable to determine if snapshot has parent"));
|
|
|
|
} else {
|
2012-07-25 11:41:49 +00:00
|
|
|
vshResetLibvirtError();
|
2012-07-23 06:23:00 +00:00
|
|
|
}
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(parent);
|
2012-07-23 06:23:00 +00:00
|
|
|
xmlXPathFreeContext(ctxt);
|
|
|
|
xmlFreeDoc(xmldoc);
|
|
|
|
VIR_FREE(xml);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-11-13 16:40:37 +00:00
|
|
|
/* Helper function to filter snapshots according to status and
|
|
|
|
* location portion of flags. Returns 0 if filter excluded snapshot,
|
|
|
|
* 1 if snapshot is okay (or if snapshot is already NULL), and -1 on
|
|
|
|
* failure, with error already reported. */
|
|
|
|
static int
|
2015-06-15 16:53:58 +00:00
|
|
|
virshSnapshotFilter(vshControl *ctl, virDomainSnapshotPtr snapshot,
|
|
|
|
unsigned int flags)
|
2012-11-13 16:40:37 +00:00
|
|
|
{
|
|
|
|
char *xml = NULL;
|
|
|
|
xmlDocPtr xmldoc = NULL;
|
|
|
|
xmlXPathContextPtr ctxt = NULL;
|
|
|
|
int ret = -1;
|
|
|
|
char *state = NULL;
|
|
|
|
|
|
|
|
if (!snapshot)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
xml = virDomainSnapshotGetXMLDesc(snapshot, 0);
|
|
|
|
if (!xml)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
xmldoc = virXMLParseStringCtxt(xml, _("(domain_snapshot)"), &ctxt);
|
|
|
|
if (!xmldoc)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
/* Libvirt 1.0.1 and newer never call this function, because the
|
|
|
|
* filtering is already supported by the listing functions. Older
|
|
|
|
* libvirt lacked /domainsnapshot/memory, but was also limited in
|
|
|
|
* the types of snapshots it could create: if state was disk-only,
|
|
|
|
* the snapshot is external; all other snapshots are internal. */
|
|
|
|
state = virXPathString("string(/domainsnapshot/state)", ctxt);
|
|
|
|
if (!state) {
|
|
|
|
vshError(ctl, "%s", _("unable to perform snapshot filtering"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
if (STREQ(state, "disk-snapshot")) {
|
|
|
|
ret = ((flags & (VIR_DOMAIN_SNAPSHOT_LIST_DISK_ONLY |
|
|
|
|
VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL)) ==
|
|
|
|
(VIR_DOMAIN_SNAPSHOT_LIST_DISK_ONLY |
|
|
|
|
VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL));
|
|
|
|
} else {
|
|
|
|
if (!(flags & VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL))
|
|
|
|
ret = 0;
|
|
|
|
else if (STREQ(state, "shutoff"))
|
|
|
|
ret = !!(flags & VIR_DOMAIN_SNAPSHOT_LIST_INACTIVE);
|
|
|
|
else
|
|
|
|
ret = !!(flags & VIR_DOMAIN_SNAPSHOT_LIST_ACTIVE);
|
|
|
|
}
|
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2012-11-13 16:40:37 +00:00
|
|
|
VIR_FREE(state);
|
|
|
|
xmlXPathFreeContext(ctxt);
|
|
|
|
xmlFreeDoc(xmldoc);
|
|
|
|
VIR_FREE(xml);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-07-23 06:23:00 +00:00
|
|
|
/*
|
|
|
|
* "snapshot-info" command
|
|
|
|
*/
|
|
|
|
static const vshCmdInfo info_snapshot_info[] = {
|
2013-02-07 15:25:10 +00:00
|
|
|
{.name = "help",
|
|
|
|
.data = N_("snapshot information")
|
|
|
|
},
|
|
|
|
{.name = "desc",
|
|
|
|
.data = N_("Returns basic information about a snapshot.")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const vshCmdOptDef opts_snapshot_info[] = {
|
2020-09-11 07:13:03 +00:00
|
|
|
VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "snapshotname",
|
2014-12-11 02:46:15 +00:00
|
|
|
.type = VSH_OT_STRING,
|
2018-01-12 14:28:30 +00:00
|
|
|
.help = N_("snapshot name"),
|
|
|
|
.completer = virshSnapshotNameCompleter,
|
2013-01-14 14:44:18 +00:00
|
|
|
},
|
2016-01-09 13:36:28 +00:00
|
|
|
VIRSH_COMMON_OPT_CURRENT(N_("info on current snapshot")),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static bool
|
|
|
|
cmdSnapshotInfo(vshControl *ctl, const vshCmd *cmd)
|
|
|
|
{
|
|
|
|
virDomainPtr dom;
|
|
|
|
virDomainSnapshotPtr snapshot = NULL;
|
|
|
|
const char *name;
|
|
|
|
char *doc = NULL;
|
2012-11-13 17:40:28 +00:00
|
|
|
xmlDocPtr xmldoc = NULL;
|
|
|
|
xmlXPathContextPtr ctxt = NULL;
|
|
|
|
char *state = NULL;
|
|
|
|
int external;
|
2012-07-23 06:23:00 +00:00
|
|
|
char *parent = NULL;
|
|
|
|
bool ret = false;
|
|
|
|
int count;
|
|
|
|
unsigned int flags;
|
|
|
|
int current;
|
|
|
|
int metadata;
|
2015-06-15 16:53:58 +00:00
|
|
|
virshControlPtr priv = ctl->privData;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
dom = virshCommandOptDomain(ctl, cmd, NULL);
|
2012-07-23 06:23:00 +00:00
|
|
|
if (dom == NULL)
|
|
|
|
return false;
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (virshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
|
|
|
|
&snapshot, &name) < 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
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);
|
|
|
|
|
2012-07-26 09:24:30 +00:00
|
|
|
vshResetLibvirtError();
|
2012-07-23 06:23:00 +00:00
|
|
|
current = 0;
|
|
|
|
if (other) {
|
|
|
|
if (STREQ(name, virDomainSnapshotGetName(other)))
|
|
|
|
current = 1;
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(other);
|
2012-07-23 06:23:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
|
2012-11-13 17:40:28 +00:00
|
|
|
xmldoc = virXMLParseStringCtxt(doc, _("(domain_snapshot)"), &ctxt);
|
|
|
|
if (!xmldoc)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
state = virXPathString("string(/domainsnapshot/state)", ctxt);
|
|
|
|
if (!state) {
|
2012-07-23 06:23:00 +00:00
|
|
|
vshError(ctl, "%s",
|
|
|
|
_("unexpected problem reading snapshot xml"));
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2012-11-13 17:40:28 +00:00
|
|
|
vshPrint(ctl, "%-15s %s\n", _("State:"), state);
|
|
|
|
|
|
|
|
/* In addition to state, location is useful. If the snapshot has
|
|
|
|
* a <memory> element, then the existence of snapshot='external'
|
|
|
|
* prior to <domain> is the deciding factor; for snapshots
|
|
|
|
* created prior to 1.0.1, a state of disk-only is the only
|
|
|
|
* external snapshot. */
|
|
|
|
switch (virXPathBoolean("boolean(/domainsnapshot/memory)", ctxt)) {
|
|
|
|
case 1:
|
2013-02-08 13:14:22 +00:00
|
|
|
external = virXPathBoolean("boolean(/domainsnapshot/memory[@snapshot='external'] "
|
|
|
|
"| /domainsnapshot/disks/disk[@snapshot='external'])",
|
2012-11-13 17:40:28 +00:00
|
|
|
ctxt);
|
|
|
|
break;
|
|
|
|
case 0:
|
|
|
|
external = STREQ(state, "disk-snapshot");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
external = -1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
if (external < 0) {
|
|
|
|
vshError(ctl, "%s",
|
|
|
|
_("unexpected problem reading snapshot xml"));
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
2012-11-13 17:40:28 +00:00
|
|
|
}
|
|
|
|
vshPrint(ctl, "%-15s %s\n", _("Location:"),
|
|
|
|
external ? _("external") : _("internal"));
|
|
|
|
|
|
|
|
/* Since we already have the XML, there's no need to call
|
|
|
|
* virDomainSnapshotGetParent */
|
|
|
|
parent = virXPathString("string(/domainsnapshot/parent/name)", ctxt);
|
2019-02-12 16:09:49 +00:00
|
|
|
vshPrint(ctl, "%-15s %s\n", _("Parent:"), NULLSTR_MINUS(parent));
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
/* Children, Descendants. After this point, the fallback to
|
|
|
|
* compute children is too expensive, so we gracefully quit if the
|
|
|
|
* APIs don't exist. */
|
2015-06-15 16:53:58 +00:00
|
|
|
if (priv->useSnapshotOld) {
|
2012-07-23 06:23:00 +00:00
|
|
|
ret = true;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
flags = 0;
|
|
|
|
count = virDomainSnapshotNumChildren(snapshot, flags);
|
2012-11-13 17:40:28 +00:00
|
|
|
if (count < 0) {
|
|
|
|
if (last_error->code == VIR_ERR_NO_SUPPORT) {
|
|
|
|
vshResetLibvirtError();
|
|
|
|
ret = true;
|
|
|
|
}
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
2012-11-13 17:40:28 +00:00
|
|
|
}
|
2012-07-23 06:23:00 +00:00
|
|
|
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);
|
2012-07-26 09:24:30 +00:00
|
|
|
vshResetLibvirtError();
|
2012-07-23 06:23:00 +00:00
|
|
|
}
|
|
|
|
if (metadata >= 0)
|
|
|
|
vshPrint(ctl, "%-15s %s\n", _("Metadata:"),
|
|
|
|
metadata ? _("yes") : _("no"));
|
|
|
|
|
|
|
|
ret = true;
|
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2012-11-13 17:40:28 +00:00
|
|
|
VIR_FREE(state);
|
|
|
|
xmlXPathFreeContext(ctxt);
|
|
|
|
xmlFreeDoc(xmldoc);
|
2012-07-23 06:23:00 +00:00
|
|
|
VIR_FREE(doc);
|
|
|
|
VIR_FREE(parent);
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(snapshot);
|
2017-04-11 10:16:52 +00:00
|
|
|
virshDomainFree(dom);
|
2012-07-23 06:23:00 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Helpers for collecting a list of snapshots. */
|
2015-06-15 16:53:58 +00:00
|
|
|
struct virshSnap {
|
2012-07-23 06:23:00 +00:00
|
|
|
virDomainSnapshotPtr snap;
|
|
|
|
char *parent;
|
|
|
|
};
|
2015-06-15 16:53:58 +00:00
|
|
|
struct virshSnapshotList {
|
|
|
|
struct virshSnap *snaps;
|
2012-07-23 06:23:00 +00:00
|
|
|
int nsnaps;
|
|
|
|
};
|
2015-06-15 16:53:58 +00:00
|
|
|
typedef struct virshSnapshotList *virshSnapshotListPtr;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
static void
|
2015-06-15 16:53:58 +00:00
|
|
|
virshSnapshotListFree(virshSnapshotListPtr snaplist)
|
2012-07-23 06:23:00 +00:00
|
|
|
{
|
Convert 'int i' to 'size_t i' in tools/ files
Convert the type of loop iterators named 'i', 'j', k',
'ii', 'jj', 'kk', to be 'size_t' instead of 'int' or
'unsigned int', also santizing 'ii', 'jj', 'kk' to use
the normal 'i', 'j', 'k' naming
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-07-08 14:09:33 +00:00
|
|
|
size_t i;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
if (!snaplist)
|
|
|
|
return;
|
|
|
|
if (snaplist->snaps) {
|
|
|
|
for (i = 0; i < snaplist->nsnaps; i++) {
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(snaplist->snaps[i].snap);
|
2012-07-23 06:23:00 +00:00
|
|
|
VIR_FREE(snaplist->snaps[i].parent);
|
|
|
|
}
|
|
|
|
VIR_FREE(snaplist->snaps);
|
|
|
|
}
|
|
|
|
VIR_FREE(snaplist);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2015-06-15 16:53:58 +00:00
|
|
|
virshSnapSorter(const void *a, const void *b)
|
2012-07-23 06:23:00 +00:00
|
|
|
{
|
2015-06-15 16:53:58 +00:00
|
|
|
const struct virshSnap *sa = a;
|
|
|
|
const struct virshSnap *sb = b;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
if (sa->snap && !sb->snap)
|
|
|
|
return -1;
|
|
|
|
if (!sa->snap)
|
|
|
|
return sb->snap != NULL;
|
|
|
|
|
2012-08-14 07:21:44 +00:00
|
|
|
return vshStrcasecmp(virDomainSnapshotGetName(sa->snap),
|
|
|
|
virDomainSnapshotGetName(sb->snap));
|
2012-07-23 06:23:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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. */
|
2015-06-15 16:53:58 +00:00
|
|
|
static virshSnapshotListPtr
|
|
|
|
virshSnapshotListCollect(vshControl *ctl, virDomainPtr dom,
|
|
|
|
virDomainSnapshotPtr from,
|
|
|
|
unsigned int orig_flags, bool tree)
|
2012-07-23 06:23:00 +00:00
|
|
|
{
|
Convert 'int i' to 'size_t i' in tools/ files
Convert the type of loop iterators named 'i', 'j', k',
'ii', 'jj', 'kk', to be 'size_t' instead of 'int' or
'unsigned int', also santizing 'ii', 'jj', 'kk' to use
the normal 'i', 'j', 'k' naming
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-07-08 14:09:33 +00:00
|
|
|
size_t i;
|
2012-07-23 06:23:00 +00:00
|
|
|
char **names = NULL;
|
|
|
|
int count = -1;
|
|
|
|
bool descendants = false;
|
|
|
|
bool roots = false;
|
|
|
|
virDomainSnapshotPtr *snaps;
|
2020-10-05 16:50:09 +00:00
|
|
|
virshSnapshotListPtr snaplist = g_new0(struct virshSnapshotList, 1);
|
2015-06-15 16:53:58 +00:00
|
|
|
virshSnapshotListPtr ret = NULL;
|
2012-07-23 06:23:00 +00:00
|
|
|
const char *fromname = NULL;
|
|
|
|
int start_index = -1;
|
|
|
|
int deleted = 0;
|
2012-11-13 16:40:37 +00:00
|
|
|
bool filter_fallback = false;
|
|
|
|
unsigned int flags = orig_flags;
|
2015-06-15 16:53:58 +00:00
|
|
|
virshControlPtr priv = ctl->privData;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
/* Try the interface available in 0.9.13 and newer. */
|
2015-06-15 16:53:58 +00:00
|
|
|
if (!priv->useSnapshotOld) {
|
2012-07-23 06:23:00 +00:00
|
|
|
if (from)
|
|
|
|
count = virDomainSnapshotListAllChildren(from, &snaps, flags);
|
|
|
|
else
|
|
|
|
count = virDomainListAllSnapshots(dom, &snaps, flags);
|
2012-11-13 16:40:37 +00:00
|
|
|
/* If we failed because of flags added in 1.0.1, we can do
|
|
|
|
* fallback filtering. */
|
|
|
|
if (count < 0 && last_error->code == VIR_ERR_INVALID_ARG &&
|
|
|
|
flags & (VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS |
|
|
|
|
VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION)) {
|
|
|
|
flags &= ~(VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS |
|
|
|
|
VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION);
|
|
|
|
vshResetLibvirtError();
|
|
|
|
filter_fallback = true;
|
|
|
|
if (from)
|
|
|
|
count = virDomainSnapshotListAllChildren(from, &snaps, flags);
|
|
|
|
else
|
|
|
|
count = virDomainListAllSnapshots(dom, &snaps, flags);
|
|
|
|
}
|
2012-07-23 06:23:00 +00:00
|
|
|
}
|
|
|
|
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. */
|
2020-10-05 16:48:21 +00:00
|
|
|
if (tree && from)
|
|
|
|
snaplist->snaps = g_new0(struct virshSnap, count + 1);
|
|
|
|
else
|
|
|
|
snaplist->snaps = g_new0(struct virshSnap, count);
|
2012-07-23 06:23:00 +00:00
|
|
|
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++) {
|
2015-06-15 16:53:58 +00:00
|
|
|
if (virshGetSnapshotParent(ctl, snaplist->snaps[i].snap,
|
|
|
|
&snaplist->snaps[i].parent) < 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
if (from) {
|
2012-10-22 14:28:59 +00:00
|
|
|
snaplist->snaps[snaplist->nsnaps++].snap = from;
|
2012-07-23 06:23:00 +00:00
|
|
|
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;
|
|
|
|
}
|
2012-11-13 16:40:37 +00:00
|
|
|
if (flags & (VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS |
|
|
|
|
VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION)) {
|
|
|
|
flags &= ~(VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS |
|
|
|
|
VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION);
|
|
|
|
filter_fallback = true;
|
|
|
|
}
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
/* 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. */
|
2015-06-15 16:53:58 +00:00
|
|
|
if (priv->useSnapshotOld ||
|
2012-07-23 06:23:00 +00:00
|
|
|
((count = virDomainSnapshotNumChildren(from, flags)) < 0 &&
|
|
|
|
last_error->code == VIR_ERR_NO_SUPPORT)) {
|
|
|
|
/* We can emulate --from. */
|
|
|
|
/* XXX can we also emulate --leaves? */
|
2012-07-25 11:41:49 +00:00
|
|
|
vshResetLibvirtError();
|
2015-06-15 16:53:58 +00:00
|
|
|
priv->useSnapshotOld = true;
|
2012-07-23 06:23:00 +00:00
|
|
|
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)) {
|
2012-07-25 11:41:49 +00:00
|
|
|
vshResetLibvirtError();
|
2012-07-23 06:23:00 +00:00
|
|
|
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;
|
|
|
|
|
2020-10-05 16:50:09 +00:00
|
|
|
names = g_new0(char *, count);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
/* Now that we have a count, collect the list. */
|
2015-06-15 16:53:58 +00:00
|
|
|
if (from && !priv->useSnapshotOld) {
|
2012-07-23 06:23:00 +00:00
|
|
|
if (tree) {
|
2019-02-27 14:03:10 +00:00
|
|
|
count = virDomainSnapshotListChildrenNames(from, names + 1,
|
|
|
|
count - 1, flags);
|
2012-07-23 06:23:00 +00:00
|
|
|
if (count >= 0) {
|
|
|
|
count++;
|
2019-10-18 15:24:02 +00:00
|
|
|
names[0] = g_strdup(fromname);
|
2012-07-23 06:23:00 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
count = virDomainSnapshotListChildrenNames(from, names,
|
|
|
|
count, flags);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
count = virDomainSnapshotListNames(dom, names, count, flags);
|
|
|
|
}
|
|
|
|
if (count < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
2020-10-05 16:50:09 +00:00
|
|
|
snaplist->snaps = g_new0(struct virshSnap, count);
|
2012-07-23 06:23:00 +00:00
|
|
|
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. */
|
2015-06-15 16:53:58 +00:00
|
|
|
if (tree || (from && priv->useSnapshotOld) || roots) {
|
|
|
|
for (i = (from && !priv->useSnapshotOld); i < count; i++) {
|
|
|
|
if (from && priv->useSnapshotOld && STREQ(names[i], fromname)) {
|
2012-07-23 06:23:00 +00:00
|
|
|
start_index = i;
|
|
|
|
if (tree)
|
|
|
|
continue;
|
|
|
|
}
|
2015-06-15 16:53:58 +00:00
|
|
|
if (virshGetSnapshotParent(ctl, snaplist->snaps[i].snap,
|
|
|
|
&snaplist->snaps[i].parent) < 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
if ((from && ((tree && !snaplist->snaps[i].parent) ||
|
|
|
|
(!descendants &&
|
|
|
|
STRNEQ_NULLABLE(fromname,
|
|
|
|
snaplist->snaps[i].parent)))) ||
|
|
|
|
(roots && snaplist->snaps[i].parent)) {
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(snaplist->snaps[i].snap);
|
2012-07-23 06:23:00 +00:00
|
|
|
snaplist->snaps[i].snap = NULL;
|
|
|
|
VIR_FREE(snaplist->snaps[i].parent);
|
|
|
|
deleted++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (tree)
|
|
|
|
goto success;
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (priv->useSnapshotOld && descendants) {
|
2012-07-23 06:23:00 +00:00
|
|
|
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]);
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(snaplist->snaps[i].snap);
|
2012-07-23 06:23:00 +00:00
|
|
|
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) {
|
2020-10-05 16:50:09 +00:00
|
|
|
ret = g_new0(struct virshSnapshotList, 1);
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
while (changed && remaining) {
|
|
|
|
changed = remaining = false;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
|
|
bool found_parent = false;
|
Convert 'int i' to 'size_t i' in tools/ files
Convert the type of loop iterators named 'i', 'j', k',
'ii', 'jj', 'kk', to be 'size_t' instead of 'int' or
'unsigned int', also santizing 'ii', 'jj', 'kk' to use
the normal 'i', 'j', 'k' naming
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-07-08 14:09:33 +00:00
|
|
|
size_t j;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
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]);
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(snaplist->snaps[i].snap);
|
2012-07-23 06:23:00 +00:00
|
|
|
snaplist->snaps[i].snap = NULL;
|
|
|
|
VIR_FREE(snaplist->snaps[i].parent);
|
|
|
|
deleted++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
success:
|
2012-11-13 16:40:37 +00:00
|
|
|
if (filter_fallback) {
|
|
|
|
/* Older API didn't filter on status or location, but the
|
|
|
|
* information is available in domain XML. */
|
|
|
|
if (!(orig_flags & VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS))
|
|
|
|
orig_flags |= VIR_DOMAIN_SNAPSHOT_FILTERS_STATUS;
|
|
|
|
if (!(orig_flags & VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION))
|
|
|
|
orig_flags |= VIR_DOMAIN_SNAPSHOT_FILTERS_LOCATION;
|
|
|
|
for (i = 0; i < snaplist->nsnaps; i++) {
|
2015-06-15 16:53:58 +00:00
|
|
|
switch (virshSnapshotFilter(ctl, snaplist->snaps[i].snap,
|
|
|
|
orig_flags)) {
|
2012-11-13 16:40:37 +00:00
|
|
|
case 1:
|
|
|
|
break;
|
|
|
|
case 0:
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(snaplist->snaps[i].snap);
|
2012-11-13 16:40:37 +00:00
|
|
|
snaplist->snaps[i].snap = NULL;
|
|
|
|
VIR_FREE(snaplist->snaps[i].parent);
|
|
|
|
deleted++;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
virsh: Add snapshot-list --topological
For snapshots, virsh already has a (shockingly naive [1]) client-side
topological sorter with the --tree option. But as a series of REDEFINE
calls must be presented in topological order, it's worth letting the
server do the work for us, especially since the server can give us a
topological sorting with less effort than our naive client
reconstruction.
[1] The XXX comment in virshSnapshotListCollect() about --tree being
O(n^3) is telling; https://en.wikipedia.org/wiki/Topological_sorting
is an interesting resource describing Kahn's algorithm and other
approaches for O(n) topological sorting for anyone motivated to use a
more elegant algorithm than brute force - but that doesn't affect this
patch.
For now, I am purposefully NOT implementing virsh fallback code to
provide a topological sort when the flag was rejected as unsupported;
we can worry about that down the road if users actually demonstrate
that they use new virsh but old libvirt to even need the fallback.
(The code we use for --tree could be repurposed to be such a fallback,
whether or not we keep it naive or improve it to be faster - but
again, no one should spend time on a fallback without evidence that we
need it.)
The test driver makes it easy to test:
$ virsh -c test:///default '
snapshot-create-as test a
snapshot-create-as test c
snapshot-create-as test b
snapshot-list test
snapshot-list test --topological
snapshot-list test --descendants a
snapshot-list test --descendants a --topological
snapshot-list test --tree
snapshot-list test --tree --topological
'
Without any flags, virsh does client-side sorting alphabetically, and
lists 'b' before 'c' (even though 'c' is the parent of 'b'); with the
flag, virsh skips sorting, and you can now see that the server handed
back data in a correct ordering. As shown here with a simple linear
chain, there isn't any other possible ordering, so --tree mode doesn't
seem to care whether --topological is used. But it is possible to
compose more complicated DAGs with multiple children to a parent
(representing reverting back to a snapshot then creating more
snapshots along those divergent execution timelines), where it is then
possible (but not guaranteed) that adding the --topological flag
changes the --tree output (the client-side --tree algorithm breaks
ties based on alphabetical sorting between two nodes that share the
same parent, while the --topological sort skips the client-side
alphabetical sort and ends up exposing the server's internal order for
siblings, whether that be historical creation order or dependent on a
random hash seed). But even if the results differ, they will still be
topologically correct.
Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2019-03-08 04:12:01 +00:00
|
|
|
if (!(orig_flags & VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL))
|
|
|
|
qsort(snaplist->snaps, snaplist->nsnaps, sizeof(*snaplist->snaps),
|
|
|
|
virshSnapSorter);
|
2012-07-23 06:23:00 +00:00
|
|
|
snaplist->nsnaps -= deleted;
|
|
|
|
|
2019-10-16 11:43:36 +00:00
|
|
|
ret = g_steal_pointer(&snaplist);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2015-06-15 16:53:58 +00:00
|
|
|
virshSnapshotListFree(snaplist);
|
Convert 'int i' to 'size_t i' in tools/ files
Convert the type of loop iterators named 'i', 'j', k',
'ii', 'jj', 'kk', to be 'size_t' instead of 'int' or
'unsigned int', also santizing 'ii', 'jj', 'kk' to use
the normal 'i', 'j', 'k' naming
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-07-08 14:09:33 +00:00
|
|
|
if (names && count > 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
for (i = 0; i < count; i++)
|
|
|
|
VIR_FREE(names[i]);
|
|
|
|
VIR_FREE(names);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
2015-06-15 16:53:58 +00:00
|
|
|
virshSnapshotListLookup(int id, bool parent, void *opaque)
|
2012-07-23 06:23:00 +00:00
|
|
|
{
|
2015-06-15 16:53:58 +00:00
|
|
|
virshSnapshotListPtr snaplist = opaque;
|
2012-07-23 06:23:00 +00:00
|
|
|
if (parent)
|
|
|
|
return snaplist->snaps[id].parent;
|
|
|
|
return virDomainSnapshotGetName(snaplist->snaps[id].snap);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "snapshot-list" command
|
|
|
|
*/
|
|
|
|
static const vshCmdInfo info_snapshot_list[] = {
|
2013-02-07 15:25:10 +00:00
|
|
|
{.name = "help",
|
|
|
|
.data = N_("List snapshots for a domain")
|
|
|
|
},
|
|
|
|
{.name = "desc",
|
|
|
|
.data = N_("Snapshot List")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const vshCmdOptDef opts_snapshot_list[] = {
|
2020-09-11 07:13:03 +00:00
|
|
|
VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "parent",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("add a column showing parent snapshot")
|
|
|
|
},
|
|
|
|
{.name = "roots",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("list only snapshots without parents")
|
|
|
|
},
|
|
|
|
{.name = "leaves",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("list only snapshots without children")
|
|
|
|
},
|
|
|
|
{.name = "no-leaves",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("list only snapshots that are not leaves (with children)")
|
|
|
|
},
|
|
|
|
{.name = "metadata",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("list only snapshots that have metadata that would prevent undefine")
|
|
|
|
},
|
|
|
|
{.name = "no-metadata",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("list only snapshots that have no metadata managed by libvirt")
|
|
|
|
},
|
|
|
|
{.name = "inactive",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("filter by snapshots taken while inactive")
|
|
|
|
},
|
|
|
|
{.name = "active",
|
|
|
|
.type = VSH_OT_BOOL,
|
2018-06-11 13:59:10 +00:00
|
|
|
.help = N_("filter by snapshots taken while active (full system snapshots)")
|
2013-01-14 14:44:18 +00:00
|
|
|
},
|
|
|
|
{.name = "disk-only",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("filter by disk-only snapshots")
|
|
|
|
},
|
|
|
|
{.name = "internal",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("filter by internal snapshots")
|
|
|
|
},
|
|
|
|
{.name = "external",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("filter by external snapshots")
|
|
|
|
},
|
|
|
|
{.name = "tree",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("list snapshots in a tree")
|
|
|
|
},
|
|
|
|
{.name = "from",
|
2014-12-11 02:46:15 +00:00
|
|
|
.type = VSH_OT_STRING,
|
2013-01-14 14:44:18 +00:00
|
|
|
.help = N_("limit list to children of given snapshot")
|
|
|
|
},
|
2016-01-09 13:36:28 +00:00
|
|
|
VIRSH_COMMON_OPT_CURRENT(N_("limit list to children of current snapshot")),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "descendants",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("with --from, list all descendants")
|
|
|
|
},
|
2013-03-01 14:42:25 +00:00
|
|
|
{.name = "name",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("list snapshot names only")
|
|
|
|
},
|
virsh: Add snapshot-list --topological
For snapshots, virsh already has a (shockingly naive [1]) client-side
topological sorter with the --tree option. But as a series of REDEFINE
calls must be presented in topological order, it's worth letting the
server do the work for us, especially since the server can give us a
topological sorting with less effort than our naive client
reconstruction.
[1] The XXX comment in virshSnapshotListCollect() about --tree being
O(n^3) is telling; https://en.wikipedia.org/wiki/Topological_sorting
is an interesting resource describing Kahn's algorithm and other
approaches for O(n) topological sorting for anyone motivated to use a
more elegant algorithm than brute force - but that doesn't affect this
patch.
For now, I am purposefully NOT implementing virsh fallback code to
provide a topological sort when the flag was rejected as unsupported;
we can worry about that down the road if users actually demonstrate
that they use new virsh but old libvirt to even need the fallback.
(The code we use for --tree could be repurposed to be such a fallback,
whether or not we keep it naive or improve it to be faster - but
again, no one should spend time on a fallback without evidence that we
need it.)
The test driver makes it easy to test:
$ virsh -c test:///default '
snapshot-create-as test a
snapshot-create-as test c
snapshot-create-as test b
snapshot-list test
snapshot-list test --topological
snapshot-list test --descendants a
snapshot-list test --descendants a --topological
snapshot-list test --tree
snapshot-list test --tree --topological
'
Without any flags, virsh does client-side sorting alphabetically, and
lists 'b' before 'c' (even though 'c' is the parent of 'b'); with the
flag, virsh skips sorting, and you can now see that the server handed
back data in a correct ordering. As shown here with a simple linear
chain, there isn't any other possible ordering, so --tree mode doesn't
seem to care whether --topological is used. But it is possible to
compose more complicated DAGs with multiple children to a parent
(representing reverting back to a snapshot then creating more
snapshots along those divergent execution timelines), where it is then
possible (but not guaranteed) that adding the --topological flag
changes the --tree output (the client-side --tree algorithm breaks
ties based on alphabetical sorting between two nodes that share the
same parent, while the --topological sort skips the client-side
alphabetical sort and ends up exposing the server's internal order for
siblings, whether that be historical creation order or dependent on a
random hash seed). But even if the results differ, they will still be
topologically correct.
Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2019-03-08 04:12:01 +00:00
|
|
|
{.name = "topological",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("sort list topologically rather than by name"),
|
|
|
|
},
|
2013-03-01 14:42:25 +00:00
|
|
|
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static bool
|
|
|
|
cmdSnapshotList(vshControl *ctl, const vshCmd *cmd)
|
|
|
|
{
|
|
|
|
virDomainPtr dom = NULL;
|
|
|
|
bool ret = false;
|
|
|
|
unsigned int flags = 0;
|
Convert 'int i' to 'size_t i' in tools/ files
Convert the type of loop iterators named 'i', 'j', k',
'ii', 'jj', 'kk', to be 'size_t' instead of 'int' or
'unsigned int', also santizing 'ii', 'jj', 'kk' to use
the normal 'i', 'j', 'k' naming
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
2013-07-08 14:09:33 +00:00
|
|
|
size_t i;
|
2012-07-23 06:23:00 +00:00
|
|
|
xmlDocPtr xml = NULL;
|
|
|
|
xmlXPathContextPtr ctxt = NULL;
|
|
|
|
char *doc = NULL;
|
|
|
|
virDomainSnapshotPtr snapshot = NULL;
|
|
|
|
char *state = NULL;
|
|
|
|
long long creation_longlong;
|
2020-01-09 14:07:15 +00:00
|
|
|
g_autoptr(GDateTime) then = NULL;
|
|
|
|
g_autofree gchar *thenstr = NULL;
|
2012-07-23 06:23:00 +00:00
|
|
|
bool tree = vshCommandOptBool(cmd, "tree");
|
2013-03-01 14:42:25 +00:00
|
|
|
bool name = vshCommandOptBool(cmd, "name");
|
2013-03-01 15:27:04 +00:00
|
|
|
bool from = vshCommandOptBool(cmd, "from");
|
|
|
|
bool parent = vshCommandOptBool(cmd, "parent");
|
|
|
|
bool roots = vshCommandOptBool(cmd, "roots");
|
|
|
|
bool current = vshCommandOptBool(cmd, "current");
|
|
|
|
const char *from_snap = NULL;
|
|
|
|
char *parent_snap = NULL;
|
2012-07-23 06:23:00 +00:00
|
|
|
virDomainSnapshotPtr start = NULL;
|
2015-06-15 16:53:58 +00:00
|
|
|
virshSnapshotListPtr snaplist = NULL;
|
2018-09-21 14:17:16 +00:00
|
|
|
vshTablePtr table = NULL;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2013-03-01 15:27:04 +00:00
|
|
|
VSH_EXCLUSIVE_OPTIONS_VAR(tree, name);
|
|
|
|
VSH_EXCLUSIVE_OPTIONS_VAR(parent, roots);
|
|
|
|
VSH_EXCLUSIVE_OPTIONS_VAR(parent, tree);
|
|
|
|
VSH_EXCLUSIVE_OPTIONS_VAR(roots, tree);
|
|
|
|
VSH_EXCLUSIVE_OPTIONS_VAR(roots, from);
|
|
|
|
VSH_EXCLUSIVE_OPTIONS_VAR(roots, current);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2017-11-03 12:09:47 +00:00
|
|
|
#define FILTER(option, flag) \
|
|
|
|
do { \
|
|
|
|
if (vshCommandOptBool(cmd, option)) { \
|
|
|
|
if (tree) { \
|
|
|
|
vshError(ctl, \
|
2012-11-12 17:29:14 +00:00
|
|
|
_("--%s and --tree are mutually exclusive"), \
|
2017-11-03 12:09:47 +00:00
|
|
|
option); \
|
|
|
|
return false; \
|
|
|
|
} \
|
|
|
|
flags |= VIR_DOMAIN_SNAPSHOT_LIST_ ## flag; \
|
|
|
|
} \
|
2012-11-12 17:29:14 +00:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
FILTER("leaves", LEAVES);
|
|
|
|
FILTER("no-leaves", NO_LEAVES);
|
|
|
|
FILTER("inactive", INACTIVE);
|
|
|
|
FILTER("active", ACTIVE);
|
|
|
|
FILTER("disk-only", DISK_ONLY);
|
|
|
|
FILTER("internal", INTERNAL);
|
|
|
|
FILTER("external", EXTERNAL);
|
|
|
|
#undef FILTER
|
2012-07-23 06:23:00 +00:00
|
|
|
|
virsh: Add snapshot-list --topological
For snapshots, virsh already has a (shockingly naive [1]) client-side
topological sorter with the --tree option. But as a series of REDEFINE
calls must be presented in topological order, it's worth letting the
server do the work for us, especially since the server can give us a
topological sorting with less effort than our naive client
reconstruction.
[1] The XXX comment in virshSnapshotListCollect() about --tree being
O(n^3) is telling; https://en.wikipedia.org/wiki/Topological_sorting
is an interesting resource describing Kahn's algorithm and other
approaches for O(n) topological sorting for anyone motivated to use a
more elegant algorithm than brute force - but that doesn't affect this
patch.
For now, I am purposefully NOT implementing virsh fallback code to
provide a topological sort when the flag was rejected as unsupported;
we can worry about that down the road if users actually demonstrate
that they use new virsh but old libvirt to even need the fallback.
(The code we use for --tree could be repurposed to be such a fallback,
whether or not we keep it naive or improve it to be faster - but
again, no one should spend time on a fallback without evidence that we
need it.)
The test driver makes it easy to test:
$ virsh -c test:///default '
snapshot-create-as test a
snapshot-create-as test c
snapshot-create-as test b
snapshot-list test
snapshot-list test --topological
snapshot-list test --descendants a
snapshot-list test --descendants a --topological
snapshot-list test --tree
snapshot-list test --tree --topological
'
Without any flags, virsh does client-side sorting alphabetically, and
lists 'b' before 'c' (even though 'c' is the parent of 'b'); with the
flag, virsh skips sorting, and you can now see that the server handed
back data in a correct ordering. As shown here with a simple linear
chain, there isn't any other possible ordering, so --tree mode doesn't
seem to care whether --topological is used. But it is possible to
compose more complicated DAGs with multiple children to a parent
(representing reverting back to a snapshot then creating more
snapshots along those divergent execution timelines), where it is then
possible (but not guaranteed) that adding the --topological flag
changes the --tree output (the client-side --tree algorithm breaks
ties based on alphabetical sorting between two nodes that share the
same parent, while the --topological sort skips the client-side
alphabetical sort and ends up exposing the server's internal order for
siblings, whether that be historical creation order or dependent on a
random hash seed). But even if the results differ, they will still be
topologically correct.
Signed-off-by: Eric Blake <eblake@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
2019-03-08 04:12:01 +00:00
|
|
|
if (vshCommandOptBool(cmd, "topological"))
|
|
|
|
flags |= VIR_DOMAIN_SNAPSHOT_LIST_TOPOLOGICAL;
|
|
|
|
|
2013-03-01 15:27:04 +00:00
|
|
|
if (roots)
|
|
|
|
flags |= VIR_DOMAIN_SNAPSHOT_LIST_ROOTS;
|
|
|
|
|
|
|
|
if (vshCommandOptBool(cmd, "metadata"))
|
2012-07-23 06:23:00 +00:00
|
|
|
flags |= VIR_DOMAIN_SNAPSHOT_LIST_METADATA;
|
2013-03-01 15:27:04 +00:00
|
|
|
|
|
|
|
if (vshCommandOptBool(cmd, "no-metadata"))
|
2012-07-23 06:23:00 +00:00
|
|
|
flags |= VIR_DOMAIN_SNAPSHOT_LIST_NO_METADATA;
|
|
|
|
|
|
|
|
if (vshCommandOptBool(cmd, "descendants")) {
|
2013-03-01 15:27:04 +00:00
|
|
|
if (!from && !current) {
|
2012-07-23 06:23:00 +00:00
|
|
|
vshError(ctl, "%s",
|
|
|
|
_("--descendants requires either --from or --current"));
|
2013-03-01 15:27:04 +00:00
|
|
|
return false;
|
2012-07-23 06:23:00 +00:00
|
|
|
}
|
|
|
|
flags |= VIR_DOMAIN_SNAPSHOT_LIST_DESCENDANTS;
|
|
|
|
}
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
|
2013-03-01 15:27:04 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if ((from || current) &&
|
2015-06-15 16:53:58 +00:00
|
|
|
virshLookupSnapshot(ctl, cmd, "from", true, dom, &start, &from_snap) < 0)
|
2013-03-01 15:27:04 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (!(snaplist = virshSnapshotListCollect(ctl, dom, start, flags, tree)))
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
2013-03-01 14:42:25 +00:00
|
|
|
if (!tree && !name) {
|
2013-03-01 15:27:04 +00:00
|
|
|
if (parent)
|
2018-09-21 14:17:16 +00:00
|
|
|
table = vshTableNew(_("Name"), _("Creation Time"), _("State"), _("Parent"), NULL);
|
2012-07-23 06:23:00 +00:00
|
|
|
else
|
2018-09-21 14:17:16 +00:00
|
|
|
table = vshTableNew(_("Name"), _("Creation Time"), _("State"), NULL);
|
|
|
|
|
|
|
|
if (!table)
|
|
|
|
goto cleanup;
|
2012-07-23 06:23:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (tree) {
|
|
|
|
for (i = 0; i < snaplist->nsnaps; i++) {
|
|
|
|
if (!snaplist->snaps[i].parent &&
|
2015-06-15 16:53:58 +00:00
|
|
|
vshTreePrint(ctl, virshSnapshotListLookup, snaplist,
|
2012-07-23 06:23:00 +00:00
|
|
|
snaplist->nsnaps, i) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
ret = true;
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < snaplist->nsnaps; i++) {
|
2013-03-01 14:42:25 +00:00
|
|
|
const char *snap_name;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
/* free up memory from previous iterations of the loop */
|
2013-03-01 15:27:04 +00:00
|
|
|
VIR_FREE(parent_snap);
|
2012-07-23 06:23:00 +00:00
|
|
|
VIR_FREE(state);
|
|
|
|
xmlXPathFreeContext(ctxt);
|
|
|
|
xmlFreeDoc(xml);
|
|
|
|
VIR_FREE(doc);
|
|
|
|
|
|
|
|
snapshot = snaplist->snaps[i].snap;
|
2013-03-01 14:42:25 +00:00
|
|
|
snap_name = virDomainSnapshotGetName(snapshot);
|
|
|
|
assert(snap_name);
|
|
|
|
|
|
|
|
if (name) {
|
2013-03-01 15:27:04 +00:00
|
|
|
/* just print the snapshot name */
|
2013-03-01 14:42:25 +00:00
|
|
|
vshPrint(ctl, "%s\n", snap_name);
|
|
|
|
continue;
|
|
|
|
}
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2013-03-01 15:27:04 +00:00
|
|
|
if (!(doc = virDomainSnapshotGetXMLDesc(snapshot, 0)))
|
2012-07-23 06:23:00 +00:00
|
|
|
continue;
|
|
|
|
|
2013-03-01 15:27:04 +00:00
|
|
|
if (!(xml = virXMLParseStringCtxt(doc, _("(domain_snapshot)"), &ctxt)))
|
2012-07-23 06:23:00 +00:00
|
|
|
continue;
|
|
|
|
|
2013-03-01 15:27:04 +00:00
|
|
|
if (parent)
|
|
|
|
parent_snap = virXPathString("string(/domainsnapshot/parent/name)",
|
|
|
|
ctxt);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2013-03-01 15:27:04 +00:00
|
|
|
if (!(state = virXPathString("string(/domainsnapshot/state)", ctxt)))
|
2012-07-23 06:23:00 +00:00
|
|
|
continue;
|
2013-03-01 15:27:04 +00:00
|
|
|
|
2012-07-23 06:23:00 +00:00
|
|
|
if (virXPathLongLong("string(/domainsnapshot/creationTime)", ctxt,
|
|
|
|
&creation_longlong) < 0)
|
|
|
|
continue;
|
2020-01-09 14:07:15 +00:00
|
|
|
then = g_date_time_new_from_unix_local(creation_longlong);
|
|
|
|
thenstr = g_date_time_format(then, "%Y-%m-%d %H:%M:%S %z");
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2018-09-21 14:17:16 +00:00
|
|
|
if (parent) {
|
2020-01-09 14:07:15 +00:00
|
|
|
if (vshTableRowAppend(table, snap_name, thenstr, state,
|
2019-02-14 13:45:12 +00:00
|
|
|
NULLSTR_EMPTY(parent_snap),
|
2018-09-21 14:17:16 +00:00
|
|
|
NULL) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
} else {
|
2020-01-09 14:07:15 +00:00
|
|
|
if (vshTableRowAppend(table, snap_name, thenstr, state,
|
2018-09-21 14:17:16 +00:00
|
|
|
NULL) < 0)
|
|
|
|
goto cleanup;
|
|
|
|
}
|
2012-07-23 06:23:00 +00:00
|
|
|
}
|
|
|
|
|
2018-09-21 14:17:16 +00:00
|
|
|
if (table)
|
|
|
|
vshTablePrintToStdout(table, ctl);
|
|
|
|
|
2012-07-23 06:23:00 +00:00
|
|
|
ret = true;
|
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2012-07-23 06:23:00 +00:00
|
|
|
/* this frees up memory from the last iteration of the loop */
|
2015-06-15 16:53:58 +00:00
|
|
|
virshSnapshotListFree(snaplist);
|
2013-03-01 15:27:04 +00:00
|
|
|
VIR_FREE(parent_snap);
|
2012-07-23 06:23:00 +00:00
|
|
|
VIR_FREE(state);
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(start);
|
2012-07-23 06:23:00 +00:00
|
|
|
xmlXPathFreeContext(ctxt);
|
|
|
|
xmlFreeDoc(xml);
|
|
|
|
VIR_FREE(doc);
|
2017-04-11 10:16:52 +00:00
|
|
|
virshDomainFree(dom);
|
2018-09-21 14:17:16 +00:00
|
|
|
vshTableFree(table);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "snapshot-dumpxml" command
|
|
|
|
*/
|
|
|
|
static const vshCmdInfo info_snapshot_dumpxml[] = {
|
2013-02-07 15:25:10 +00:00
|
|
|
{.name = "help",
|
|
|
|
.data = N_("Dump XML for a domain snapshot")
|
|
|
|
},
|
|
|
|
{.name = "desc",
|
|
|
|
.data = N_("Snapshot Dump XML")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const vshCmdOptDef opts_snapshot_dumpxml[] = {
|
2020-09-11 07:13:03 +00:00
|
|
|
VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "snapshotname",
|
|
|
|
.type = VSH_OT_DATA,
|
|
|
|
.flags = VSH_OFLAG_REQ,
|
2018-01-12 14:28:30 +00:00
|
|
|
.help = N_("snapshot name"),
|
|
|
|
.completer = virshSnapshotNameCompleter,
|
2013-01-14 14:44:18 +00:00
|
|
|
},
|
|
|
|
{.name = "security-info",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("include security sensitive information in XML dump")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2013-01-21 16:54:17 +00:00
|
|
|
if (vshCommandOptStringReq(ctl, cmd, "snapshotname", &name) < 0)
|
|
|
|
return false;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
|
2013-01-21 16:54:17 +00:00
|
|
|
return false;
|
2012-07-23 06:23:00 +00:00
|
|
|
|
2013-01-21 16:54:17 +00:00
|
|
|
if (!(snapshot = virDomainSnapshotLookupByName(dom, name, 0)))
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
2013-01-21 16:54:17 +00:00
|
|
|
if (!(xml = virDomainSnapshotGetXMLDesc(snapshot, flags)))
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
vshPrint(ctl, "%s", xml);
|
|
|
|
ret = true;
|
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2012-07-23 06:23:00 +00:00
|
|
|
VIR_FREE(xml);
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(snapshot);
|
2017-04-11 10:16:52 +00:00
|
|
|
virshDomainFree(dom);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "snapshot-parent" command
|
|
|
|
*/
|
|
|
|
static const vshCmdInfo info_snapshot_parent[] = {
|
2013-02-07 15:25:10 +00:00
|
|
|
{.name = "help",
|
|
|
|
.data = N_("Get the name of the parent of a snapshot")
|
|
|
|
},
|
|
|
|
{.name = "desc",
|
|
|
|
.data = N_("Extract the snapshot's parent, if any")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const vshCmdOptDef opts_snapshot_parent[] = {
|
2020-09-11 07:13:03 +00:00
|
|
|
VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "snapshotname",
|
2014-12-11 02:46:15 +00:00
|
|
|
.type = VSH_OT_STRING,
|
2018-01-12 14:28:30 +00:00
|
|
|
.help = N_("find parent of snapshot name"),
|
|
|
|
.completer = virshSnapshotNameCompleter,
|
2013-01-14 14:44:18 +00:00
|
|
|
},
|
2016-01-09 13:36:28 +00:00
|
|
|
VIRSH_COMMON_OPT_CURRENT(N_("find parent of current snapshot")),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static bool
|
|
|
|
cmdSnapshotParent(vshControl *ctl, const vshCmd *cmd)
|
|
|
|
{
|
|
|
|
virDomainPtr dom = NULL;
|
|
|
|
bool ret = false;
|
|
|
|
const char *name = NULL;
|
|
|
|
virDomainSnapshotPtr snapshot = NULL;
|
|
|
|
char *parent = NULL;
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
dom = virshCommandOptDomain(ctl, cmd, NULL);
|
2012-07-23 06:23:00 +00:00
|
|
|
if (dom == NULL)
|
|
|
|
goto cleanup;
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (virshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
|
|
|
|
&snapshot, &name) < 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (virshGetSnapshotParent(ctl, snapshot, &parent) < 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
if (!parent) {
|
|
|
|
vshError(ctl, _("snapshot '%s' has no parent"), name);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
vshPrint(ctl, "%s", parent);
|
|
|
|
|
|
|
|
ret = true;
|
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2012-07-23 06:23:00 +00:00
|
|
|
VIR_FREE(parent);
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(snapshot);
|
2017-04-11 10:16:52 +00:00
|
|
|
virshDomainFree(dom);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "snapshot-revert" command
|
|
|
|
*/
|
|
|
|
static const vshCmdInfo info_snapshot_revert[] = {
|
2013-02-07 15:25:10 +00:00
|
|
|
{.name = "help",
|
|
|
|
.data = N_("Revert a domain to a snapshot")
|
|
|
|
},
|
|
|
|
{.name = "desc",
|
|
|
|
.data = N_("Revert domain to snapshot")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const vshCmdOptDef opts_snapshot_revert[] = {
|
2020-09-11 07:13:03 +00:00
|
|
|
VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "snapshotname",
|
2014-12-11 02:46:15 +00:00
|
|
|
.type = VSH_OT_STRING,
|
2018-01-12 14:28:30 +00:00
|
|
|
.help = N_("snapshot name"),
|
|
|
|
.completer = virshSnapshotNameCompleter,
|
2013-01-14 14:44:18 +00:00
|
|
|
},
|
2016-01-09 13:36:28 +00:00
|
|
|
VIRSH_COMMON_OPT_CURRENT(N_("revert to current snapshot")),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "running",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("after reverting, change state to running")
|
|
|
|
},
|
|
|
|
{.name = "paused",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("after reverting, change state to paused")
|
|
|
|
},
|
|
|
|
{.name = "force",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("try harder on risky reverts")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
dom = virshCommandOptDomain(ctl, cmd, NULL);
|
2012-07-23 06:23:00 +00:00
|
|
|
if (dom == NULL)
|
|
|
|
goto cleanup;
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (virshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
|
|
|
|
&snapshot, &name) < 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
result = virDomainRevertToSnapshot(snapshot, flags);
|
|
|
|
if (result < 0 && force &&
|
|
|
|
last_error->code == VIR_ERR_SNAPSHOT_REVERT_RISKY) {
|
|
|
|
flags |= VIR_DOMAIN_SNAPSHOT_REVERT_FORCE;
|
2012-07-25 11:41:49 +00:00
|
|
|
vshResetLibvirtError();
|
2012-07-23 06:23:00 +00:00
|
|
|
result = virDomainRevertToSnapshot(snapshot, flags);
|
|
|
|
}
|
|
|
|
if (result < 0)
|
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
ret = true;
|
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(snapshot);
|
2017-04-11 10:16:52 +00:00
|
|
|
virshDomainFree(dom);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* "snapshot-delete" command
|
|
|
|
*/
|
|
|
|
static const vshCmdInfo info_snapshot_delete[] = {
|
2013-02-07 15:25:10 +00:00
|
|
|
{.name = "help",
|
|
|
|
.data = N_("Delete a domain snapshot")
|
|
|
|
},
|
|
|
|
{.name = "desc",
|
|
|
|
.data = N_("Snapshot Delete")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const vshCmdOptDef opts_snapshot_delete[] = {
|
2020-09-11 07:13:03 +00:00
|
|
|
VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_SNAPSHOT),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "snapshotname",
|
2014-12-11 02:46:15 +00:00
|
|
|
.type = VSH_OT_STRING,
|
2018-01-12 14:28:30 +00:00
|
|
|
.help = N_("snapshot name"),
|
|
|
|
.completer = virshSnapshotNameCompleter,
|
2013-01-14 14:44:18 +00:00
|
|
|
},
|
2016-01-09 13:36:28 +00:00
|
|
|
VIRSH_COMMON_OPT_CURRENT(N_("delete current snapshot")),
|
2013-01-14 14:44:18 +00:00
|
|
|
{.name = "children",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("delete snapshot and all children")
|
|
|
|
},
|
|
|
|
{.name = "children-only",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("delete children but not snapshot")
|
|
|
|
},
|
|
|
|
{.name = "metadata",
|
|
|
|
.type = VSH_OT_BOOL,
|
|
|
|
.help = N_("delete only libvirt metadata, leaving snapshot contents behind")
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 06:23:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
dom = virshCommandOptDomain(ctl, cmd, NULL);
|
2012-07-23 06:23:00 +00:00
|
|
|
if (dom == NULL)
|
|
|
|
goto cleanup;
|
|
|
|
|
2015-06-15 16:53:58 +00:00
|
|
|
if (virshLookupSnapshot(ctl, cmd, "snapshotname", true, dom,
|
|
|
|
&snapshot, &name) < 0)
|
2012-07-23 06:23:00 +00:00
|
|
|
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)
|
2016-08-24 14:14:23 +00:00
|
|
|
vshPrintExtra(ctl, _("Domain snapshot %s children deleted\n"), name);
|
2012-07-23 06:23:00 +00:00
|
|
|
else
|
2016-08-24 14:14:23 +00:00
|
|
|
vshPrintExtra(ctl, _("Domain snapshot %s deleted\n"), name);
|
2012-07-23 06:23:00 +00:00
|
|
|
} else {
|
|
|
|
vshError(ctl, _("Failed to delete snapshot %s"), name);
|
|
|
|
goto cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = true;
|
|
|
|
|
2014-03-25 06:53:59 +00:00
|
|
|
cleanup:
|
2017-04-11 15:21:05 +00:00
|
|
|
virshDomainSnapshotFree(snapshot);
|
2017-04-11 10:16:52 +00:00
|
|
|
virshDomainFree(dom);
|
2012-07-23 06:23:00 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2012-07-23 07:19:04 +00:00
|
|
|
|
2012-08-20 23:29:03 +00:00
|
|
|
const vshCmdDef snapshotCmds[] = {
|
2013-02-07 15:25:10 +00:00
|
|
|
{.name = "snapshot-create",
|
|
|
|
.handler = cmdSnapshotCreate,
|
|
|
|
.opts = opts_snapshot_create,
|
|
|
|
.info = info_snapshot_create,
|
|
|
|
.flags = 0
|
|
|
|
},
|
|
|
|
{.name = "snapshot-create-as",
|
|
|
|
.handler = cmdSnapshotCreateAs,
|
|
|
|
.opts = opts_snapshot_create_as,
|
|
|
|
.info = info_snapshot_create_as,
|
|
|
|
.flags = 0
|
|
|
|
},
|
|
|
|
{.name = "snapshot-current",
|
|
|
|
.handler = cmdSnapshotCurrent,
|
|
|
|
.opts = opts_snapshot_current,
|
|
|
|
.info = info_snapshot_current,
|
|
|
|
.flags = 0
|
|
|
|
},
|
|
|
|
{.name = "snapshot-delete",
|
|
|
|
.handler = cmdSnapshotDelete,
|
|
|
|
.opts = opts_snapshot_delete,
|
|
|
|
.info = info_snapshot_delete,
|
|
|
|
.flags = 0
|
|
|
|
},
|
|
|
|
{.name = "snapshot-dumpxml",
|
|
|
|
.handler = cmdSnapshotDumpXML,
|
|
|
|
.opts = opts_snapshot_dumpxml,
|
|
|
|
.info = info_snapshot_dumpxml,
|
|
|
|
.flags = 0
|
|
|
|
},
|
|
|
|
{.name = "snapshot-edit",
|
|
|
|
.handler = cmdSnapshotEdit,
|
|
|
|
.opts = opts_snapshot_edit,
|
|
|
|
.info = info_snapshot_edit,
|
|
|
|
.flags = 0
|
|
|
|
},
|
|
|
|
{.name = "snapshot-info",
|
|
|
|
.handler = cmdSnapshotInfo,
|
|
|
|
.opts = opts_snapshot_info,
|
|
|
|
.info = info_snapshot_info,
|
|
|
|
.flags = 0
|
|
|
|
},
|
|
|
|
{.name = "snapshot-list",
|
|
|
|
.handler = cmdSnapshotList,
|
|
|
|
.opts = opts_snapshot_list,
|
|
|
|
.info = info_snapshot_list,
|
|
|
|
.flags = 0
|
|
|
|
},
|
|
|
|
{.name = "snapshot-parent",
|
|
|
|
.handler = cmdSnapshotParent,
|
|
|
|
.opts = opts_snapshot_parent,
|
|
|
|
.info = info_snapshot_parent,
|
|
|
|
.flags = 0
|
|
|
|
},
|
|
|
|
{.name = "snapshot-revert",
|
|
|
|
.handler = cmdDomainSnapshotRevert,
|
|
|
|
.opts = opts_snapshot_revert,
|
|
|
|
.info = info_snapshot_revert,
|
|
|
|
.flags = 0
|
|
|
|
},
|
|
|
|
{.name = NULL}
|
2012-07-23 07:19:04 +00:00
|
|
|
};
|