virsh: Fix '--name' and '--parent' used together in '(snapshot|checkpoint)-list'...
[libvirt.git] / tools / virsh-checkpoint.c
blobe3fd6b2df25df48ab02394e2255d1a209870fdba
1 /*
2 * virsh-checkpoint.c: Commands to manage domain checkpoints
4 * Copyright (C) 2005-2019 Red Hat, Inc.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see
18 * <http://www.gnu.org/licenses/>.
21 #include <config.h>
22 #include "virsh-checkpoint.h"
24 #include <assert.h>
26 #include <libxml/parser.h>
27 #include <libxml/tree.h>
28 #include <libxml/xpath.h>
29 #include <libxml/xmlsave.h>
31 #include "internal.h"
32 #include "virbuffer.h"
33 #include "viralloc.h"
34 #include "virfile.h"
35 #include "virsh-util.h"
36 #include "virxml.h"
37 #include "vsh-table.h"
39 /* Helper for checkpoint-create and checkpoint-create-as */
40 static bool
41 virshCheckpointCreate(vshControl *ctl,
42 virDomainPtr dom,
43 const char *buffer,
44 unsigned int flags,
45 const char *from)
47 g_autoptr(virshDomainCheckpoint) checkpoint = NULL;
48 const char *name = NULL;
50 checkpoint = virDomainCheckpointCreateXML(dom, buffer, flags);
52 if (checkpoint == NULL)
53 return false;
55 name = virDomainCheckpointGetName(checkpoint);
56 if (!name) {
57 vshError(ctl, "%s", _("Could not get checkpoint name"));
58 return false;
61 if (from)
62 vshPrintExtra(ctl, _("Domain checkpoint %1$s created from '%2$s'"),
63 name, from);
64 else
65 vshPrintExtra(ctl, _("Domain checkpoint %1$s created"), name);
67 return true;
72 * "checkpoint-create" command
74 static const vshCmdInfo info_checkpoint_create = {
75 .help = N_("Create a checkpoint from XML"),
76 .desc = N_("Create a checkpoint from XML for use in "
77 "future incremental backups"),
80 static const vshCmdOptDef opts_checkpoint_create[] = {
81 VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE),
82 {.name = "xmlfile",
83 .type = VSH_OT_STRING,
84 .positional = true,
85 .completer = virshCompletePathLocalExisting,
86 .help = N_("domain checkpoint XML")
88 {.name = "redefine",
89 .type = VSH_OT_BOOL,
90 .help = N_("redefine metadata for existing checkpoint")
92 {.name = "redefine-validate",
93 .type = VSH_OT_BOOL,
94 .help = N_("validate the redefined checkpoint")
96 {.name = "quiesce",
97 .type = VSH_OT_BOOL,
98 .help = N_("quiesce guest's file systems")
100 {.name = NULL}
103 static bool
104 cmdCheckpointCreate(vshControl *ctl,
105 const vshCmd *cmd)
107 g_autoptr(virshDomain) dom = NULL;
108 const char *from = NULL;
109 g_autofree char *buffer = NULL;
110 unsigned int flags = 0;
112 VSH_REQUIRE_OPTION("redefine-validate", "redefine");
114 if (vshCommandOptBool(cmd, "redefine"))
115 flags |= VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE;
116 if (vshCommandOptBool(cmd, "redefine-validate"))
117 flags |= VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE_VALIDATE;
118 if (vshCommandOptBool(cmd, "quiesce"))
119 flags |= VIR_DOMAIN_CHECKPOINT_CREATE_QUIESCE;
121 if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
122 return false;
124 if (vshCommandOptStringReq(ctl, cmd, "xmlfile", &from) < 0)
125 return false;
126 if (!from) {
127 buffer = g_strdup("<domaincheckpoint/>");
128 } else {
129 if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) {
130 vshSaveLibvirtError();
131 return false;
135 return virshCheckpointCreate(ctl, dom, buffer, flags, from);
140 * "checkpoint-create-as" command
142 static int
143 virshParseCheckpointDiskspec(vshControl *ctl,
144 virBuffer *buf,
145 const char *str)
147 int ret = -1;
148 const char *name = NULL;
149 const char *checkpoint = NULL;
150 const char *bitmap = NULL;
151 g_auto(GStrv) array = NULL;
152 int narray;
153 size_t i;
155 narray = vshStringToArray(str, &array);
156 if (narray <= 0)
157 goto cleanup;
159 name = array[0];
160 for (i = 1; i < narray; i++) {
161 if (!checkpoint && STRPREFIX(array[i], "checkpoint="))
162 checkpoint = array[i] + strlen("checkpoint=");
163 else if (!bitmap && STRPREFIX(array[i], "bitmap="))
164 bitmap = array[i] + strlen("bitmap=");
165 else
166 goto cleanup;
169 virBufferEscapeString(buf, "<disk name='%s'", name);
170 if (checkpoint)
171 virBufferAsprintf(buf, " checkpoint='%s'", checkpoint);
172 if (bitmap)
173 virBufferAsprintf(buf, " bitmap='%s'", bitmap);
174 virBufferAddLit(buf, "/>\n");
175 ret = 0;
176 cleanup:
177 if (ret < 0)
178 vshError(ctl, _("unable to parse diskspec: %1$s"), str);
179 return ret;
182 static const vshCmdInfo info_checkpoint_create_as = {
183 .help = N_("Create a checkpoint from a set of args"),
184 .desc = N_("Create a checkpoint from arguments for use in "
185 "future incremental backups"),
188 static const vshCmdOptDef opts_checkpoint_create_as[] = {
189 VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_ACTIVE),
190 {.name = "name",
191 .type = VSH_OT_STRING,
192 .unwanted_positional = true,
193 .completer = virshCompleteEmpty,
194 .help = N_("name of checkpoint")
196 {.name = "description",
197 .type = VSH_OT_STRING,
198 .unwanted_positional = true,
199 .completer = virshCompleteEmpty,
200 .help = N_("description of checkpoint")
202 {.name = "print-xml",
203 .type = VSH_OT_BOOL,
204 .help = N_("print XML document rather than create")
206 {.name = "quiesce",
207 .type = VSH_OT_BOOL,
208 .help = N_("quiesce guest's file systems")
210 {.name = "diskspec",
211 .type = VSH_OT_ARGV,
212 .unwanted_positional = true,
213 .help = N_("disk attributes: disk[,checkpoint=type][,bitmap=name]")
215 {.name = NULL}
219 static bool
220 cmdCheckpointCreateAs(vshControl *ctl,
221 const vshCmd *cmd)
223 g_autoptr(virshDomain) dom = NULL;
224 g_autofree char *buffer = NULL;
225 const char *name = NULL;
226 const char *desc = NULL;
227 g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
228 unsigned int flags = 0;
229 const vshCmdOpt *opt = NULL;
231 if (vshCommandOptBool(cmd, "quiesce"))
232 flags |= VIR_DOMAIN_CHECKPOINT_CREATE_QUIESCE;
234 if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
235 return false;
237 if (vshCommandOptStringReq(ctl, cmd, "name", &name) < 0 ||
238 vshCommandOptStringReq(ctl, cmd, "description", &desc) < 0)
239 return false;
241 virBufferAddLit(&buf, "<domaincheckpoint>\n");
242 virBufferAdjustIndent(&buf, 2);
243 virBufferEscapeString(&buf, "<name>%s</name>\n", name);
244 virBufferEscapeString(&buf, "<description>%s</description>\n", desc);
246 if (vshCommandOptBool(cmd, "diskspec")) {
247 virBufferAddLit(&buf, "<disks>\n");
248 virBufferAdjustIndent(&buf, 2);
249 while ((opt = vshCommandOptArgv(ctl, cmd, opt))) {
250 if (virshParseCheckpointDiskspec(ctl, &buf, opt->data) < 0)
251 return false;
253 virBufferAdjustIndent(&buf, -2);
254 virBufferAddLit(&buf, "</disks>\n");
256 virBufferAdjustIndent(&buf, -2);
257 virBufferAddLit(&buf, "</domaincheckpoint>\n");
259 buffer = virBufferContentAndReset(&buf);
261 if (vshCommandOptBool(cmd, "print-xml")) {
262 vshPrint(ctl, "%s\n", buffer);
263 return true;
266 return virshCheckpointCreate(ctl, dom, buffer, flags, NULL);
270 /* Helper for resolving --ARG name into a checkpoint
271 * belonging to DOM. On success, populate *CHK and *NAME, before
272 * returning 0. On failure, return -1 after issuing an error
273 * message. */
274 static int
275 virshLookupCheckpoint(vshControl *ctl,
276 const vshCmd *cmd,
277 const char *arg,
278 virDomainPtr dom,
279 virDomainCheckpointPtr *chk,
280 const char **name)
282 const char *chkname = NULL;
284 if (vshCommandOptStringReq(ctl, cmd, arg, &chkname) < 0)
285 return -1;
287 if (!(*chk = virDomainCheckpointLookupByName(dom, chkname, 0)))
288 return -1;
290 *name = virDomainCheckpointGetName(*chk);
291 return 0;
296 * "checkpoint-edit" command
298 static const vshCmdInfo info_checkpoint_edit = {
299 .help = N_("edit XML for a checkpoint"),
300 .desc = N_("Edit the domain checkpoint XML for a named checkpoint"),
303 static const vshCmdOptDef opts_checkpoint_edit[] = {
304 VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT),
305 {.name = "checkpointname",
306 .type = VSH_OT_STRING,
307 .positional = true,
308 .required = true,
309 .help = N_("checkpoint name"),
310 .completer = virshCheckpointNameCompleter,
312 {.name = NULL}
315 static bool
316 cmdCheckpointEdit(vshControl *ctl,
317 const vshCmd *cmd)
319 g_autoptr(virshDomain) dom = NULL;
320 g_autoptr(virshDomainCheckpoint) checkpoint = NULL;
321 g_autoptr(virshDomainCheckpoint) edited = NULL;
322 const char *name = NULL;
323 const char *edited_name;
324 bool ret = false;
325 unsigned int getxml_flags = VIR_DOMAIN_CHECKPOINT_XML_SECURE;
326 unsigned int define_flags = VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE;
328 if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
329 return false;
331 if (virshLookupCheckpoint(ctl, cmd, "checkpointname", dom,
332 &checkpoint, &name) < 0)
333 goto cleanup;
335 #define EDIT_GET_XML \
336 virDomainCheckpointGetXMLDesc(checkpoint, getxml_flags)
337 #define EDIT_NOT_CHANGED \
338 do { \
339 vshPrintExtra(ctl, \
340 _("Checkpoint %1$s XML configuration not changed.\n"), \
341 name); \
342 ret = true; \
343 goto edit_cleanup; \
344 } while (0)
345 #define EDIT_DEFINE \
346 edited = virDomainCheckpointCreateXML(dom, doc_edited, define_flags)
347 #include "virsh-edit.c"
349 edited_name = virDomainCheckpointGetName(edited);
350 if (STREQ(name, edited_name)) {
351 vshPrintExtra(ctl, _("Checkpoint %1$s edited.\n"), name);
352 } else {
353 unsigned int delete_flags = VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY;
355 if (virDomainCheckpointDelete(edited, delete_flags) < 0) {
356 vshReportError(ctl);
357 vshError(ctl, _("Failed to clean up %1$s"), edited_name);
358 goto cleanup;
360 vshError(ctl, _("Cannot rename checkpoint %1$s to %2$s"),
361 name, edited_name);
362 goto cleanup;
365 ret = true;
367 cleanup:
368 if (!ret && name)
369 vshError(ctl, _("Failed to update %1$s"), name);
370 return ret;
374 /* Helper function to get the name of a checkpoint's parent. Caller
375 * must free the result. Returns 0 on success (including when it was
376 * proven no parent exists), and -1 on failure with error reported
377 * (such as no checkpoint support or domain deleted in meantime). */
378 static int
379 virshGetCheckpointParent(vshControl *ctl,
380 virDomainCheckpointPtr checkpoint,
381 char **parent_name)
383 g_autoptr(virshDomainCheckpoint) parent = NULL;
384 int ret = -1;
386 *parent_name = NULL;
388 parent = virDomainCheckpointGetParent(checkpoint, 0);
389 if (parent) {
390 /* API works, and virDomainCheckpointGetName will succeed */
391 *parent_name = g_strdup(virDomainCheckpointGetName(parent));
392 ret = 0;
393 } else if (last_error->code == VIR_ERR_NO_DOMAIN_CHECKPOINT) {
394 /* API works, and we found a root with no parent */
395 ret = 0;
398 if (ret < 0) {
399 vshReportError(ctl);
400 vshError(ctl, "%s", _("unable to determine if checkpoint has parent"));
401 } else {
402 vshResetLibvirtError();
404 return ret;
409 * "checkpoint-info" command
411 static const vshCmdInfo info_checkpoint_info = {
412 .help = N_("checkpoint information"),
413 .desc = N_("Returns basic information about a checkpoint."),
416 static const vshCmdOptDef opts_checkpoint_info[] = {
417 VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT),
418 {.name = "checkpointname",
419 .type = VSH_OT_STRING,
420 .positional = true,
421 .required = true,
422 .help = N_("checkpoint name"),
423 .completer = virshCheckpointNameCompleter,
425 {.name = NULL}
429 static bool
430 cmdCheckpointInfo(vshControl *ctl,
431 const vshCmd *cmd)
433 g_autoptr(virshDomain) dom = NULL;
434 g_autoptr(virshDomainCheckpoint) checkpoint = NULL;
435 const char *name;
436 g_autofree char *parent = NULL;
437 int count;
438 unsigned int flags;
440 dom = virshCommandOptDomain(ctl, cmd, NULL);
441 if (dom == NULL)
442 return false;
444 if (virshLookupCheckpoint(ctl, cmd, "checkpointname", dom,
445 &checkpoint, &name) < 0)
446 return false;
448 vshPrint(ctl, "%-15s %s\n", _("Name:"), name);
449 vshPrint(ctl, "%-15s %s\n", _("Domain:"), virDomainGetName(dom));
451 if (virshGetCheckpointParent(ctl, checkpoint, &parent) < 0) {
452 vshError(ctl, "%s",
453 _("unexpected problem querying checkpoint state"));
454 return false;
456 vshPrint(ctl, "%-15s %s\n", _("Parent:"), parent ? parent : "-");
458 /* Children, Descendants. */
459 flags = 0;
460 count = virDomainCheckpointListAllChildren(checkpoint, NULL, flags);
461 if (count < 0) {
462 if (last_error->code == VIR_ERR_NO_SUPPORT) {
463 vshResetLibvirtError();
464 return true;
466 return false;
468 vshPrint(ctl, "%-15s %d\n", _("Children:"), count);
469 flags = VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS;
470 count = virDomainCheckpointListAllChildren(checkpoint, NULL, flags);
471 if (count < 0)
472 return false;
473 vshPrint(ctl, "%-15s %d\n", _("Descendants:"), count);
475 return true;
479 /* Helpers for collecting a list of checkpoints. */
480 struct virshChk {
481 virDomainCheckpointPtr chk;
482 char *parent;
484 struct virshCheckpointList {
485 struct virshChk *chks;
486 int nchks;
489 static void
490 virshCheckpointListFree(struct virshCheckpointList *checkpointlist)
492 size_t i;
494 if (!checkpointlist)
495 return;
496 if (checkpointlist->chks) {
497 for (i = 0; i < checkpointlist->nchks; i++) {
498 virshDomainCheckpointFree(checkpointlist->chks[i].chk);
499 g_free(checkpointlist->chks[i].parent);
501 g_free(checkpointlist->chks);
503 g_free(checkpointlist);
507 static int
508 virshChkSorter(const void *a,
509 const void *b,
510 void *opaque G_GNUC_UNUSED)
512 const struct virshChk *sa = a;
513 const struct virshChk *sb = b;
515 if (sa->chk && !sb->chk)
516 return -1;
517 if (!sa->chk)
518 return sb->chk != NULL;
520 return vshStrcasecmp(virDomainCheckpointGetName(sa->chk),
521 virDomainCheckpointGetName(sb->chk));
525 /* Compute a list of checkpoints from DOM. If FROM is provided, the
526 * list is limited to descendants of the given checkpoint. If FLAGS is
527 * given, the list is filtered. If TREE is specified, then all but
528 * FROM or the roots will also have parent information. */
529 static struct virshCheckpointList *
530 virshCheckpointListCollect(vshControl *ctl,
531 virDomainPtr dom,
532 virDomainCheckpointPtr from,
533 unsigned int orig_flags,
534 bool tree)
536 size_t i;
537 int count = -1;
538 virDomainCheckpointPtr *chks;
539 struct virshCheckpointList *checkpointlist = NULL;
540 struct virshCheckpointList *ret = NULL;
541 unsigned int flags = orig_flags;
543 checkpointlist = g_new0(struct virshCheckpointList, 1);
545 if (from)
546 count = virDomainCheckpointListAllChildren(from, &chks, flags);
547 else
548 count = virDomainListAllCheckpoints(dom, &chks, flags);
549 if (count < 0) {
550 vshError(ctl, "%s",
551 _("unexpected problem querying checkpoints"));
552 goto cleanup;
555 /* When mixing --from and --tree, we also want a copy of from
556 * in the list, but with no parent for that one entry. */
557 if (from && tree)
558 checkpointlist->chks = g_new0(struct virshChk, count + 1);
559 else
560 checkpointlist->chks = g_new0(struct virshChk, count);
561 checkpointlist->nchks = count;
562 for (i = 0; i < count; i++)
563 checkpointlist->chks[i].chk = chks[i];
564 VIR_FREE(chks);
565 if (tree) {
566 for (i = 0; i < count; i++) {
567 if (virshGetCheckpointParent(ctl, checkpointlist->chks[i].chk,
568 &checkpointlist->chks[i].parent) < 0)
569 goto cleanup;
571 if (from) {
572 checkpointlist->chks[checkpointlist->nchks++].chk = from;
573 virDomainCheckpointRef(from);
577 if (!(orig_flags & VIR_DOMAIN_CHECKPOINT_LIST_TOPOLOGICAL) &&
578 checkpointlist->chks) {
579 g_qsort_with_data(checkpointlist->chks, checkpointlist->nchks,
580 sizeof(*checkpointlist->chks), virshChkSorter, NULL);
583 ret = g_steal_pointer(&checkpointlist);
585 cleanup:
586 virshCheckpointListFree(checkpointlist);
587 return ret;
591 static const char *
592 virshCheckpointListLookup(int id,
593 bool parent,
594 void *opaque)
596 struct virshCheckpointList *checkpointlist = opaque;
597 if (parent)
598 return checkpointlist->chks[id].parent;
599 return virDomainCheckpointGetName(checkpointlist->chks[id].chk);
604 * "checkpoint-list" command
606 static const vshCmdInfo info_checkpoint_list = {
607 .help = N_("List checkpoints for a domain"),
608 .desc = N_("Checkpoint List"),
611 static const vshCmdOptDef opts_checkpoint_list[] = {
612 VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT),
613 {.name = "parent",
614 .type = VSH_OT_BOOL,
615 .help = N_("add a column showing parent checkpoint")
617 {.name = "roots",
618 .type = VSH_OT_BOOL,
619 .help = N_("list only checkpoints without parents")
621 {.name = "leaves",
622 .type = VSH_OT_BOOL,
623 .help = N_("list only checkpoints without children")
625 {.name = "no-leaves",
626 .type = VSH_OT_BOOL,
627 .help = N_("list only checkpoints that are not leaves (with children)")
629 {.name = "tree",
630 .type = VSH_OT_BOOL,
631 .help = N_("list checkpoints in a tree")
633 {.name = "from",
634 .type = VSH_OT_STRING,
635 .unwanted_positional = true,
636 .help = N_("limit list to children of given checkpoint"),
637 .completer = virshCheckpointNameCompleter,
639 {.name = "descendants",
640 .type = VSH_OT_BOOL,
641 .help = N_("with --from, list all descendants")
643 {.name = "name",
644 .type = VSH_OT_BOOL,
645 .help = N_("list checkpoint names only")
647 {.name = "topological",
648 .type = VSH_OT_BOOL,
649 .help = N_("sort list topologically rather than by name"),
651 {.name = NULL}
654 static bool
655 cmdCheckpointList(vshControl *ctl,
656 const vshCmd *cmd)
658 g_autoptr(virshDomain) dom = NULL;
659 bool ret = false;
660 unsigned int flags = 0;
661 size_t i;
662 virDomainCheckpointPtr checkpoint = NULL;
663 long long creation_longlong;
664 g_autoptr(GDateTime) then = NULL;
665 bool tree = vshCommandOptBool(cmd, "tree");
666 bool name = vshCommandOptBool(cmd, "name");
667 bool from = vshCommandOptBool(cmd, "from");
668 bool parent = vshCommandOptBool(cmd, "parent");
669 bool roots = vshCommandOptBool(cmd, "roots");
670 const char *from_chk = NULL;
671 g_autoptr(virshDomainCheckpoint) start = NULL;
672 struct virshCheckpointList *checkpointlist = NULL;
673 g_autoptr(vshTable) table = NULL;
675 VSH_EXCLUSIVE_OPTIONS_VAR(tree, name);
676 VSH_EXCLUSIVE_OPTIONS_VAR(parent, roots);
677 VSH_EXCLUSIVE_OPTIONS_VAR(parent, tree);
678 VSH_EXCLUSIVE_OPTIONS_VAR(roots, tree);
679 VSH_EXCLUSIVE_OPTIONS_VAR(roots, from);
681 #define FILTER(option, flag) \
682 do { \
683 if (vshCommandOptBool(cmd, option)) { \
684 if (tree) { \
685 vshError(ctl, \
686 _("--%1$s and --tree are mutually exclusive"), \
687 option); \
688 return false; \
690 flags |= VIR_DOMAIN_CHECKPOINT_LIST_ ## flag; \
692 } while (0)
694 FILTER("leaves", LEAVES);
695 FILTER("no-leaves", NO_LEAVES);
696 #undef FILTER
698 if (vshCommandOptBool(cmd, "topological"))
699 flags |= VIR_DOMAIN_CHECKPOINT_LIST_TOPOLOGICAL;
701 if (roots)
702 flags |= VIR_DOMAIN_CHECKPOINT_LIST_ROOTS;
704 if (vshCommandOptBool(cmd, "descendants")) {
705 if (!from) {
706 vshError(ctl, "%s",
707 _("--descendants requires --from"));
708 return false;
710 flags |= VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS;
713 if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
714 return false;
716 if (from &&
717 virshLookupCheckpoint(ctl, cmd, "from", dom, &start, &from_chk) < 0)
718 goto cleanup;
720 if (!(checkpointlist = virshCheckpointListCollect(ctl, dom, start, flags,
721 tree)))
722 goto cleanup;
724 if (!tree && !name) {
725 if (parent)
726 table = vshTableNew(_("Name"), _("Creation Time"), _("Parent"),
727 NULL);
728 else
729 table = vshTableNew(_("Name"), _("Creation Time"), NULL);
730 if (!table)
731 goto cleanup;
734 if (tree) {
735 for (i = 0; i < checkpointlist->nchks; i++) {
736 if (!checkpointlist->chks[i].parent &&
737 vshTreePrint(ctl, virshCheckpointListLookup, checkpointlist,
738 checkpointlist->nchks, i) < 0)
739 goto cleanup;
741 ret = true;
742 goto cleanup;
745 for (i = 0; i < checkpointlist->nchks; i++) {
746 g_autofree gchar *thenstr = NULL;
747 g_autoptr(xmlDoc) xml = NULL;
748 g_autoptr(xmlXPathContext) ctxt = NULL;
749 g_autofree char *parent_chk = NULL;
750 g_autofree char *doc = NULL;
751 const char *chk_name;
753 checkpoint = checkpointlist->chks[i].chk;
754 chk_name = virDomainCheckpointGetName(checkpoint);
755 assert(chk_name);
757 if (!(doc = virDomainCheckpointGetXMLDesc(checkpoint, 0)))
758 continue;
760 if (!(xml = virXMLParseStringCtxt(doc, _("(domain_checkpoint)"), &ctxt)))
761 continue;
763 if (parent)
764 parent_chk = virXPathString("string(/domaincheckpoint/parent/name)",
765 ctxt);
767 if (name) {
768 vshPrint(ctl, "%s", chk_name);
770 if (parent_chk)
771 vshPrint(ctl, "\t%s", parent_chk);
773 vshPrint(ctl, "\n");
775 /* just print the checkpoint name */
776 continue;
779 if (virXPathLongLong("string(/domaincheckpoint/creationTime)", ctxt,
780 &creation_longlong) < 0)
781 continue;
783 then = g_date_time_new_from_unix_local(creation_longlong);
784 thenstr = g_date_time_format(then, "%Y-%m-%d %H:%M:%S %z");
786 if (parent) {
787 if (vshTableRowAppend(table, chk_name, thenstr,
788 NULLSTR_EMPTY(parent_chk), NULL) < 0)
789 goto cleanup;
790 } else {
791 if (vshTableRowAppend(table, chk_name, thenstr, NULL) < 0)
792 goto cleanup;
796 if (table)
797 vshTablePrintToStdout(table, ctl);
799 ret = true;
801 cleanup:
802 virshCheckpointListFree(checkpointlist);
803 return ret;
808 * "checkpoint-dumpxml" command
810 static const vshCmdInfo info_checkpoint_dumpxml = {
811 .help = N_("Dump XML for a domain checkpoint"),
812 .desc = N_("Checkpoint Dump XML"),
815 static const vshCmdOptDef opts_checkpoint_dumpxml[] = {
816 VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT),
817 {.name = "checkpointname",
818 .type = VSH_OT_STRING,
819 .positional = true,
820 .required = true,
821 .help = N_("checkpoint name"),
822 .completer = virshCheckpointNameCompleter,
824 {.name = "security-info",
825 .type = VSH_OT_BOOL,
826 .help = N_("include security sensitive information in XML dump")
828 {.name = "no-domain",
829 .type = VSH_OT_BOOL,
830 .help = N_("exclude <domain> from XML")
832 {.name = "size",
833 .type = VSH_OT_BOOL,
834 .help = N_("include backup size estimate in XML dump")
836 {.name = "xpath",
837 .type = VSH_OT_STRING,
838 .completer = virshCompleteEmpty,
839 .help = N_("xpath expression to filter the XML document")
841 {.name = "wrap",
842 .type = VSH_OT_BOOL,
843 .help = N_("wrap xpath results in an common root element"),
845 {.name = NULL}
848 static bool
849 cmdCheckpointDumpXML(vshControl *ctl,
850 const vshCmd *cmd)
852 g_autoptr(virshDomain) dom = NULL;
853 const char *name = NULL;
854 g_autoptr(virshDomainCheckpoint) checkpoint = NULL;
855 g_autofree char *xml = NULL;
856 unsigned int flags = 0;
857 bool wrap = vshCommandOptBool(cmd, "wrap");
858 const char *xpath = NULL;
860 if (vshCommandOptBool(cmd, "security-info"))
861 flags |= VIR_DOMAIN_CHECKPOINT_XML_SECURE;
862 if (vshCommandOptBool(cmd, "no-domain"))
863 flags |= VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN;
864 if (vshCommandOptBool(cmd, "size"))
865 flags |= VIR_DOMAIN_CHECKPOINT_XML_SIZE;
867 if (!(dom = virshCommandOptDomain(ctl, cmd, NULL)))
868 return false;
870 if (vshCommandOptStringQuiet(ctl, cmd, "xpath", &xpath) < 0)
871 return false;
873 if (virshLookupCheckpoint(ctl, cmd, "checkpointname", dom,
874 &checkpoint, &name) < 0)
875 return false;
877 if (!(xml = virDomainCheckpointGetXMLDesc(checkpoint, flags)))
878 return false;
880 return virshDumpXML(ctl, xml, "domain-checkpoint", xpath, wrap);
885 * "checkpoint-parent" command
887 static const vshCmdInfo info_checkpoint_parent = {
888 .help = N_("Get the name of the parent of a checkpoint"),
889 .desc = N_("Extract the checkpoint's parent, if any"),
892 static const vshCmdOptDef opts_checkpoint_parent[] = {
893 VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT),
894 {.name = "checkpointname",
895 .type = VSH_OT_STRING,
896 .positional = true,
897 .required = true,
898 .help = N_("find parent of checkpoint name"),
899 .completer = virshCheckpointNameCompleter,
901 {.name = NULL}
904 static bool
905 cmdCheckpointParent(vshControl *ctl,
906 const vshCmd *cmd)
908 g_autoptr(virshDomain) dom = NULL;
909 const char *name = NULL;
910 g_autoptr(virshDomainCheckpoint) checkpoint = NULL;
911 g_autofree char *parent = NULL;
913 dom = virshCommandOptDomain(ctl, cmd, NULL);
914 if (dom == NULL)
915 return false;
917 if (virshLookupCheckpoint(ctl, cmd, "checkpointname", dom,
918 &checkpoint, &name) < 0)
919 return false;
921 if (virshGetCheckpointParent(ctl, checkpoint, &parent) < 0)
922 return false;
923 if (!parent) {
924 vshError(ctl, _("checkpoint '%1$s' has no parent"), name);
925 return false;
928 vshPrint(ctl, "%s", parent);
930 return true;
935 * "checkpoint-delete" command
937 static const vshCmdInfo info_checkpoint_delete = {
938 .help = N_("Delete a domain checkpoint"),
939 .desc = N_("Checkpoint Delete"),
942 static const vshCmdOptDef opts_checkpoint_delete[] = {
943 VIRSH_COMMON_OPT_DOMAIN_FULL(VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT |
944 VIR_CONNECT_LIST_DOMAINS_ACTIVE),
945 {.name = "checkpointname",
946 .type = VSH_OT_STRING,
947 .positional = true,
948 .required = true,
949 .help = N_("checkpoint name"),
950 .completer = virshCheckpointNameCompleter,
952 {.name = "children",
953 .type = VSH_OT_BOOL,
954 .help = N_("delete checkpoint and all children")
956 {.name = "children-only",
957 .type = VSH_OT_BOOL,
958 .help = N_("delete children but not checkpoint")
960 {.name = "metadata",
961 .type = VSH_OT_BOOL,
962 .help = N_("delete only libvirt metadata, leaving checkpoint contents behind")
964 {.name = NULL}
967 static bool
968 cmdCheckpointDelete(vshControl *ctl,
969 const vshCmd *cmd)
971 g_autoptr(virshDomain) dom = NULL;
972 const char *name = NULL;
973 g_autoptr(virshDomainCheckpoint) checkpoint = NULL;
974 unsigned int flags = 0;
976 dom = virshCommandOptDomain(ctl, cmd, NULL);
977 if (dom == NULL)
978 return false;
980 if (virshLookupCheckpoint(ctl, cmd, "checkpointname", dom,
981 &checkpoint, &name) < 0)
982 return false;
984 if (vshCommandOptBool(cmd, "children"))
985 flags |= VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN;
986 if (vshCommandOptBool(cmd, "children-only"))
987 flags |= VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY;
988 if (vshCommandOptBool(cmd, "metadata"))
989 flags |= VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY;
991 if (virDomainCheckpointDelete(checkpoint, flags) == 0) {
992 if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY)
993 vshPrintExtra(ctl, _("Domain checkpoint %1$s children deleted\n"), name);
994 else
995 vshPrintExtra(ctl, _("Domain checkpoint %1$s deleted\n"), name);
996 } else {
997 vshError(ctl, _("Failed to delete checkpoint %1$s"), name);
998 return false;
1001 return true;
1005 const vshCmdDef checkpointCmds[] = {
1006 {.name = "checkpoint-create",
1007 .handler = cmdCheckpointCreate,
1008 .opts = opts_checkpoint_create,
1009 .info = &info_checkpoint_create,
1010 .flags = 0
1012 {.name = "checkpoint-create-as",
1013 .handler = cmdCheckpointCreateAs,
1014 .opts = opts_checkpoint_create_as,
1015 .info = &info_checkpoint_create_as,
1016 .flags = 0
1018 {.name = "checkpoint-delete",
1019 .handler = cmdCheckpointDelete,
1020 .opts = opts_checkpoint_delete,
1021 .info = &info_checkpoint_delete,
1022 .flags = 0
1024 {.name = "checkpoint-dumpxml",
1025 .handler = cmdCheckpointDumpXML,
1026 .opts = opts_checkpoint_dumpxml,
1027 .info = &info_checkpoint_dumpxml,
1028 .flags = 0
1030 {.name = "checkpoint-edit",
1031 .handler = cmdCheckpointEdit,
1032 .opts = opts_checkpoint_edit,
1033 .info = &info_checkpoint_edit,
1034 .flags = 0
1036 {.name = "checkpoint-info",
1037 .handler = cmdCheckpointInfo,
1038 .opts = opts_checkpoint_info,
1039 .info = &info_checkpoint_info,
1040 .flags = 0
1042 {.name = "checkpoint-list",
1043 .handler = cmdCheckpointList,
1044 .opts = opts_checkpoint_list,
1045 .info = &info_checkpoint_list,
1046 .flags = 0
1048 {.name = "checkpoint-parent",
1049 .handler = cmdCheckpointParent,
1050 .opts = opts_checkpoint_parent,
1051 .info = &info_checkpoint_parent,
1052 .flags = 0
1054 {.name = NULL}