From nobody Mon Sep 16 19:40:46 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) client-ip=209.132.183.28; envelope-from=libvir-list-bounces@redhat.com; helo=mx1.redhat.com; Authentication-Results: mx.zohomail.com; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1540495777681735.9477547179202; Thu, 25 Oct 2018 12:29:37 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 461AC81256; Thu, 25 Oct 2018 19:29:35 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 050D660F80; Thu, 25 Oct 2018 19:29:35 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id AF7DD4CA95; Thu, 25 Oct 2018 19:29:34 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id w9PJL7d4027743 for ; Thu, 25 Oct 2018 15:21:07 -0400 Received: by smtp.corp.redhat.com (Postfix) id AB81A60F97; Thu, 25 Oct 2018 19:21:07 +0000 (UTC) Received: from red.redhat.com (ovpn-122-116.rdu2.redhat.com [10.10.122.116]) by smtp.corp.redhat.com (Postfix) with ESMTP id 23B3E62527; Thu, 25 Oct 2018 19:21:02 +0000 (UTC) From: Eric Blake To: libvir-list@redhat.com Date: Thu, 25 Oct 2018 20:20:14 +0100 Message-Id: <20181025192021.350438-14-eblake@redhat.com> In-Reply-To: <20181025192021.350438-1-eblake@redhat.com> References: <20181025192021.350438-1-eblake@redhat.com> X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: libvir-list@redhat.com Cc: amureini@redhat.com, derez@redhat.com, vsementsov@virtuozzo.com, ydary@redhat.com, nsoffer@redhat.com, jsnow@redhat.com Subject: [libvirt] [PATCH v3 13/20] backup: Implement virsh support for checkpoints X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.25]); Thu, 25 Oct 2018 19:29:36 +0000 (UTC) Content-Type: text/plain; charset="utf-8" Introduce a bunch of new virsh commands for managing checkpoints in isolation. More commands are needed for performing incremental backups, but these commands were easy to implement by modeling heavily after virsh-snapshot.c (no need for checkpoint-revert, and checkpoint-list was a lot easier since we don't have to cater to older libvirt API). Signed-off-by: Eric Blake --- tools/virsh-checkpoint.h | 29 + tools/virsh-completer.h | 4 + tools/virsh-util.h | 3 + tools/virsh.h | 1 + po/POTFILES | 1 + tools/Makefile.am | 3 +- tools/virsh-checkpoint.c | 1329 ++++++++++++++++++++++++++++++++++++++ tools/virsh-completer.c | 52 +- tools/virsh-util.c | 11 + tools/virsh.c | 2 + 10 files changed, 1433 insertions(+), 2 deletions(-) create mode 100644 tools/virsh-checkpoint.h create mode 100644 tools/virsh-checkpoint.c diff --git a/tools/virsh-checkpoint.h b/tools/virsh-checkpoint.h new file mode 100644 index 0000000000..a3874f8d60 --- /dev/null +++ b/tools/virsh-checkpoint.h @@ -0,0 +1,29 @@ +/* + * virsh-checkpoint.h: Commands to manage domain checkpoints + * + * Copyright (C) 2005-2018 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + */ + +#ifndef VIRSH_CHECKPOINT_H +# define VIRSH_CHECKPOINT_H + +# include "virsh.h" + +extern const vshCmdDef checkpointCmds[]; + +#endif /* VIRSH_CHECKPOINT_H */ diff --git a/tools/virsh-completer.h b/tools/virsh-completer.h index b4fd2a86c6..d70c32cd52 100644 --- a/tools/virsh-completer.h +++ b/tools/virsh-completer.h @@ -74,6 +74,10 @@ char ** virshSecretUUIDCompleter(vshControl *ctl, const vshCmd *cmd, unsigned int flags); +char ** virshCheckpointNameCompleter(vshControl *ctl, + const vshCmd *cmd, + unsigned int flags); + char ** virshSnapshotNameCompleter(vshControl *ctl, const vshCmd *cmd, unsigned int flags); diff --git a/tools/virsh-util.h b/tools/virsh-util.h index 9a0af3513d..11712560c3 100644 --- a/tools/virsh-util.h +++ b/tools/virsh-util.h @@ -43,6 +43,9 @@ virshCommandOptDomain(vshControl *ctl, void virshDomainFree(virDomainPtr dom); +void +virshDomainCheckpointFree(virDomainCheckpointPtr chk); + void virshDomainSnapshotFree(virDomainSnapshotPtr snap); diff --git a/tools/virsh.h b/tools/virsh.h index 6ff5c74b5e..fe8db253d8 100644 --- a/tools/virsh.h +++ b/tools/virsh.h @@ -45,6 +45,7 @@ /* * Command group types */ +# define VIRSH_CMD_GRP_CHECKPOINT "Checkpoint" # define VIRSH_CMD_GRP_DOM_MANAGEMENT "Domain Management" # define VIRSH_CMD_GRP_DOM_MONITORING "Domain Monitoring" # define VIRSH_CMD_GRP_STORAGE_POOL "Storage Pool" diff --git a/po/POTFILES b/po/POTFILES index 0a2845c4c6..c99f7819c4 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -302,6 +302,7 @@ src/xenconfig/xen_xl.c src/xenconfig/xen_xm.c tests/virpolkittest.c tools/libvirt-guests.sh.in +tools/virsh-checkpoint.c tools/virsh-console.c tools/virsh-domain-monitor.c tools/virsh-domain.c diff --git a/tools/Makefile.am b/tools/Makefile.am index f069167acc..c4354d0a9d 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,4 +1,4 @@ -## Copyright (C) 2005-2016 Red Hat, Inc. +## Copyright (C) 2005-2018 Red Hat, Inc. ## Copyright (C) 2013 Yuto KAWAMURA(kawamuray) ## ## This library is free software; you can redistribute it and/or @@ -219,6 +219,7 @@ virt_login_shell_CFLAGS =3D \ virsh_SOURCES =3D \ virsh.c virsh.h \ + virsh-checkpoint.c virsh-checkpoint.h \ virsh-completer.c virsh-completer.h \ virsh-console.c virsh-console.h \ virsh-domain.c virsh-domain.h \ diff --git a/tools/virsh-checkpoint.c b/tools/virsh-checkpoint.c new file mode 100644 index 0000000000..cd08569813 --- /dev/null +++ b/tools/virsh-checkpoint.c @@ -0,0 +1,1329 @@ +/* + * virsh-checkpoint.c: Commands to manage domain checkpoints + * + * Copyright (C) 2005-2018 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * . + * + * Daniel Veillard + * Karel Zak + * Daniel P. Berrange + * + */ + +#include +#include "virsh-checkpoint.h" + +#include + +#include +#include +#include +#include + +#include "internal.h" +#include "virbuffer.h" +#include "viralloc.h" +#include "virfile.h" +#include "virsh-util.h" +#include "virstring.h" +#include "virxml.h" +#include "conf/checkpoint_conf.h" + +/* Helper for checkpoint-create and checkpoint-create-as */ +static bool +virshCheckpointCreate(vshControl *ctl, virDomainPtr dom, const char *buffe= r, + unsigned int flags, const char *from) +{ + bool ret =3D false; + virDomainCheckpointPtr checkpoint; + const char *name =3D NULL; + + checkpoint =3D virDomainCheckpointCreateXML(dom, buffer, flags); + + if (checkpoint =3D=3D NULL) + goto cleanup; + + name =3D virDomainCheckpointGetName(checkpoint); + if (!name) { + vshError(ctl, "%s", _("Could not get snapshot name")); + goto cleanup; + } + + if (from) + vshPrintExtra(ctl, _("Domain checkpoint %s created from '%s'"), + name, from); + else + vshPrintExtra(ctl, _("Domain checkpoint %s created"), name); + + ret =3D true; + + cleanup: + virshDomainCheckpointFree(checkpoint); + return ret; +} + +/* + * "checkpoint-create" command + */ +static const vshCmdInfo info_checkpoint_create[] =3D { + {.name =3D "help", + .data =3D N_("Create a checkpoint from XML") + }, + {.name =3D "desc", + .data =3D N_("Create a checkpoint from XML for use in " + "future incremental backups") + }, + {.name =3D NULL} +}; + +static const vshCmdOptDef opts_checkpoint_create[] =3D { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name =3D "xmlfile", + .type =3D VSH_OT_STRING, + .help =3D N_("domain checkpoint XML") + }, + {.name =3D "redefine", + .type =3D VSH_OT_BOOL, + .help =3D N_("redefine metadata for existing checkpoint") + }, + VIRSH_COMMON_OPT_CURRENT(N_("with redefine, set current checkpoint")), + {.name =3D "no-metadata", + .type =3D VSH_OT_BOOL, + .help =3D N_("create checkpoint but create no metadata") + }, + /* TODO - worth adding this flag? + {.name =3D "quiesce", + .type =3D VSH_OT_BOOL, + .help =3D N_("quiesce guest's file systems") + }, + */ + {.name =3D NULL} +}; + +static bool +cmdCheckpointCreate(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom =3D NULL; + bool ret =3D false; + const char *from =3D NULL; + char *buffer =3D NULL; + unsigned int flags =3D 0; + + if (vshCommandOptBool(cmd, "redefine")) + flags |=3D VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE; + if (vshCommandOptBool(cmd, "current")) + flags |=3D VIR_DOMAIN_CHECKPOINT_CREATE_CURRENT; + if (vshCommandOptBool(cmd, "no-metadata")) + flags |=3D VIR_DOMAIN_CHECKPOINT_CREATE_NO_METADATA; + /* TODO + if (vshCommandOptBool(cmd, "quiesce")) + flags |=3D VIR_DOMAIN_SNAPSHOT_CREATE_QUIESCE; + */ + + if (!(dom =3D virshCommandOptDomain(ctl, cmd, NULL))) + goto cleanup; + + if (vshCommandOptStringReq(ctl, cmd, "xmlfile", &from) < 0) + goto cleanup; + if (!from) { + buffer =3D vshStrdup(ctl, ""); + } else { + if (virFileReadAll(from, VSH_MAX_XML_FILE, &buffer) < 0) { + vshSaveLibvirtError(); + goto cleanup; + } + } + + ret =3D virshCheckpointCreate(ctl, dom, buffer, flags, from); + + cleanup: + VIR_FREE(buffer); + virshDomainFree(dom); + + return ret; +} + +/* + * "checkpoint-create-as" command + */ +static int +virshParseCheckpointDiskspec(vshControl *ctl, virBufferPtr buf, const char= *str) +{ + int ret =3D -1; + const char *name =3D NULL; + const char *checkpoint =3D NULL; + const char *bitmap =3D NULL; + char **array =3D NULL; + int narray; + size_t i; + + narray =3D vshStringToArray(str, &array); + if (narray <=3D 0) + goto cleanup; + + name =3D array[0]; + for (i =3D 1; i < narray; i++) { + if (!checkpoint && STRPREFIX(array[i], "checkpoint=3D")) + checkpoint =3D array[i] + strlen("checkpoint=3D"); + else if (!bitmap && STRPREFIX(array[i], "bitmap=3D")) + bitmap =3D array[i] + strlen("bitmap=3D"); + else + goto cleanup; + } + + virBufferEscapeString(buf, "\n"); + ret =3D 0; + cleanup: + if (ret < 0) + vshError(ctl, _("unable to parse diskspec: %s"), str); + virStringListFree(array); + return ret; +} + +static const vshCmdInfo info_checkpoint_create_as[] =3D { + {.name =3D "help", + .data =3D N_("Create a checkpoint from a set of args") + }, + {.name =3D "desc", + .data =3D N_("Create a checkpoint from arguments for use in " + "future incremental backups") + }, + {.name =3D NULL} +}; + +static const vshCmdOptDef opts_checkpoint_create_as[] =3D { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name =3D "name", + .type =3D VSH_OT_STRING, + .help =3D N_("name of checkpoint") + }, + {.name =3D "description", + .type =3D VSH_OT_STRING, + .help =3D N_("description of checkpoint") + }, + {.name =3D "print-xml", + .type =3D VSH_OT_BOOL, + .help =3D N_("print XML document rather than create") + }, + {.name =3D "no-metadata", + .type =3D VSH_OT_BOOL, + .help =3D N_("take checkpoint but create no metadata") + }, + /* TODO + {.name =3D "quiesce", + .type =3D VSH_OT_BOOL, + .help =3D N_("quiesce guest's file systems") + }, + */ + {.name =3D "diskspec", + .type =3D VSH_OT_ARGV, + .help =3D N_("disk attributes: disk[,checkpoint=3Dtype][,bitmap=3Dnam= e]") + }, + {.name =3D NULL} +}; + +static bool +cmdCheckpointCreateAs(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom =3D NULL; + bool ret =3D false; + char *buffer =3D NULL; + const char *name =3D NULL; + const char *desc =3D NULL; + virBuffer buf =3D VIR_BUFFER_INITIALIZER; + unsigned int flags =3D 0; + const vshCmdOpt *opt =3D NULL; + + if (vshCommandOptBool(cmd, "no-metadata")) { + if (vshCommandOptBool(cmd, "print-xml")) { + vshError(ctl, "%s", + _("--print-xml is incompatible with --no-metadata")); + return false; + } + flags |=3D VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA; + } + /* TODO + if (vshCommandOptBool(cmd, "quiesce")) + flags |=3D VIR_DOMAIN_CHECKPOINT_CREATE_QUIESCE; + */ + + if (!(dom =3D virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (vshCommandOptStringReq(ctl, cmd, "name", &name) < 0 || + vshCommandOptStringReq(ctl, cmd, "description", &desc) < 0) + goto cleanup; + + virBufferAddLit(&buf, "\n"); + virBufferAdjustIndent(&buf, 2); + virBufferEscapeString(&buf, "%s\n", name); + virBufferEscapeString(&buf, "%s\n", desc); + + if (vshCommandOptBool(cmd, "diskspec")) { + virBufferAddLit(&buf, "\n"); + virBufferAdjustIndent(&buf, 2); + while ((opt =3D vshCommandOptArgv(ctl, cmd, opt))) { + if (virshParseCheckpointDiskspec(ctl, &buf, opt->data) < 0) + goto cleanup; + } + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "\n"); + } + virBufferAdjustIndent(&buf, -2); + virBufferAddLit(&buf, "\n"); + + if (virBufferError(&buf)) { + vshError(ctl, "%s", _("Out of memory")); + goto cleanup; + } + + buffer =3D virBufferContentAndReset(&buf); + + if (vshCommandOptBool(cmd, "print-xml")) { + vshPrint(ctl, "%s\n", buffer); + ret =3D true; + goto cleanup; + } + + ret =3D virshCheckpointCreate(ctl, dom, buffer, flags, NULL); + + cleanup: + virBufferFreeAndReset(&buf); + VIR_FREE(buffer); + virshDomainFree(dom); + + return ret; +} + +/* Helper for resolving {--current | --ARG name} into a checkpoint + * belonging to DOM. If EXCLUSIVE, fail if both --current and arg are + * present. On success, populate *CHK and *NAME, before returning 0. + * On failure, return -1 after issuing an error message. */ +static int +virshLookupCheckpoint(vshControl *ctl, const vshCmd *cmd, + const char *arg, bool exclusive, virDomainPtr dom, + virDomainCheckpointPtr *chk, const char **name) +{ + bool current =3D vshCommandOptBool(cmd, "current"); + const char *chkname =3D NULL; + + if (vshCommandOptStringReq(ctl, cmd, arg, &chkname) < 0) + return -1; + + if (exclusive && current && chkname) { + vshError(ctl, _("--%s and --current are mutually exclusive"), arg); + return -1; + } + + if (chkname) { + *chk =3D virDomainCheckpointLookupByName(dom, chkname, 0); + } else if (current) { + *chk =3D virDomainCheckpointCurrent(dom, 0); + } else { + vshError(ctl, _("--%s or --current is required"), arg); + return -1; + } + if (!*chk) { + vshReportError(ctl); + return -1; + } + + *name =3D virDomainCheckpointGetName(*chk); + return 0; +} + +/* + * "checkpoint-edit" command + */ +static const vshCmdInfo info_checkpoint_edit[] =3D { + {.name =3D "help", + .data =3D N_("edit XML for a checkpoint") + }, + {.name =3D "desc", + .data =3D N_("Edit the domain checkpoint XML for a named checkpoint") + }, + {.name =3D NULL} +}; + +static const vshCmdOptDef opts_checkpoint_edit[] =3D { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name =3D "checkpointname", + .type =3D VSH_OT_STRING, + .help =3D N_("checkpoint name"), + .completer =3D virshCheckpointNameCompleter, + }, + VIRSH_COMMON_OPT_CURRENT(N_("also set edited checkpoint as current")), + {.name =3D "rename", + .type =3D VSH_OT_BOOL, + .help =3D N_("allow renaming an existing checkpoint") + }, + {.name =3D "clone", + .type =3D VSH_OT_BOOL, + .help =3D N_("allow cloning to new name") + }, + {.name =3D NULL} +}; + +static bool +cmdCheckpointEdit(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom =3D NULL; + virDomainCheckpointPtr checkpoint =3D NULL; + virDomainCheckpointPtr edited =3D NULL; + const char *name =3D NULL; + const char *edited_name; + bool ret =3D false; + unsigned int getxml_flags =3D VIR_DOMAIN_CHECKPOINT_XML_SECURE; + unsigned int define_flags =3D VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE; + bool rename_okay =3D vshCommandOptBool(cmd, "rename"); + bool clone_okay =3D vshCommandOptBool(cmd, "clone"); + + VSH_EXCLUSIVE_OPTIONS_EXPR("rename", rename_okay, "clone", clone_okay) + + if (vshCommandOptBool(cmd, "current") && + vshCommandOptBool(cmd, "checkpointname")) + define_flags |=3D VIR_DOMAIN_CHECKPOINT_CREATE_CURRENT; + + if (!(dom =3D virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (virshLookupCheckpoint(ctl, cmd, "checkpointname", false, dom, + &checkpoint, &name) < 0) + goto cleanup; + +#define EDIT_GET_XML \ + virDomainCheckpointGetXMLDesc(checkpoint, getxml_flags) +#define EDIT_NOT_CHANGED \ + do { \ + /* Depending on flags, we re-edit even if XML is unchanged. */ \ + if (!(define_flags & VIR_DOMAIN_CHECKPOINT_CREATE_CURRENT)) { \ + vshPrintExtra(ctl, \ + _("Checkpoint %s XML configuration not changed.\= n"), \ + name); \ + ret =3D true; \ + goto edit_cleanup; \ + } \ + } while (0) +#define EDIT_DEFINE \ + edited =3D virDomainCheckpointCreateXML(dom, doc_edited, define_flags) +#include "virsh-edit.c" + + edited_name =3D virDomainCheckpointGetName(edited); + if (STREQ(name, edited_name)) { + vshPrintExtra(ctl, _("Checkpoint %s edited.\n"), name); + } else if (clone_okay) { + vshPrintExtra(ctl, _("Checkpoint %s cloned to %s.\n"), name, + edited_name); + } else { + unsigned int delete_flags; + + delete_flags =3D VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY; + if (virDomainCheckpointDelete(rename_okay ? checkpoint : edited, + delete_flags) < 0) { + vshReportError(ctl); + vshError(ctl, _("Failed to clean up %s"), + rename_okay ? name : edited_name); + goto cleanup; + } + if (!rename_okay) { + vshError(ctl, _("Must use --rename or --clone to change %s to = %s"), + name, edited_name); + goto cleanup; + } + } + + ret =3D true; + + cleanup: + if (!ret && name) + vshError(ctl, _("Failed to update %s"), name); + virshDomainCheckpointFree(edited); + virshDomainCheckpointFree(checkpoint); + virshDomainFree(dom); + return ret; +} + +/* + * "checkpoint-current" command + */ +static const vshCmdInfo info_checkpoint_current[] =3D { + {.name =3D "help", + .data =3D N_("Get or set the current checkpoint") + }, + {.name =3D "desc", + .data =3D N_("Get or set the current checkpoint") + }, + {.name =3D NULL} +}; + +static const vshCmdOptDef opts_checkpoint_current[] =3D { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name =3D "name", + .type =3D VSH_OT_BOOL, + .help =3D N_("list the name, rather than the full xml") + }, + {.name =3D "security-info", + .type =3D VSH_OT_BOOL, + .help =3D N_("include security sensitive information in XML dump") + }, + {.name =3D "no-domain", + .type =3D VSH_OT_BOOL, + .help =3D N_("exclude from XML") + }, + {.name =3D "size", + .type =3D VSH_OT_BOOL, + .help =3D N_("include backup size estimate in XML dump") + }, + {.name =3D "checkpointname", + .type =3D VSH_OT_STRING, + .help =3D N_("name of existing checkpoint to make current"), + .completer =3D virshCheckpointNameCompleter, + }, + {.name =3D NULL} +}; + +static bool +cmdCheckpointCurrent(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom =3D NULL; + bool ret =3D false; + int current; + virDomainCheckpointPtr checkpoint =3D NULL; + char *xml =3D NULL; + const char *checkpointname =3D NULL; + unsigned int flags =3D 0; + const char *domname; + + if (vshCommandOptBool(cmd, "security-info")) + flags |=3D VIR_DOMAIN_CHECKPOINT_XML_SECURE; + if (vshCommandOptBool(cmd, "no-domain")) + flags |=3D VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN; + if (vshCommandOptBool(cmd, "size")) + flags |=3D VIR_DOMAIN_CHECKPOINT_XML_SIZE; + + VSH_EXCLUSIVE_OPTIONS("name", "checkpointname"); + + if (!(dom =3D virshCommandOptDomain(ctl, cmd, &domname))) + return false; + + if (vshCommandOptStringReq(ctl, cmd, "checkpointname", &checkpointname= ) < 0) + goto cleanup; + + if (checkpointname) { + virDomainCheckpointPtr checkpoint2 =3D NULL; + flags =3D (VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE | + VIR_DOMAIN_CHECKPOINT_CREATE_CURRENT); + + if (!(checkpoint =3D virDomainCheckpointLookupByName(dom, + checkpointname,= 0))) + goto cleanup; + + xml =3D virDomainCheckpointGetXMLDesc(checkpoint, + VIR_DOMAIN_CHECKPOINT_XML_SECU= RE); + if (!xml) + goto cleanup; + + if (!(checkpoint2 =3D virDomainCheckpointCreateXML(dom, xml, flags= ))) + goto cleanup; + + virshDomainCheckpointFree(checkpoint2); + vshPrintExtra(ctl, _("Checkpoint %s set as current"), checkpointna= me); + ret =3D true; + goto cleanup; + } + + if ((current =3D virDomainHasCurrentCheckpoint(dom, 0)) < 0) + goto cleanup; + + if (!current) { + vshError(ctl, _("domain '%s' has no current checkpoint"), domname); + goto cleanup; + } else { + if (!(checkpoint =3D virDomainCheckpointCurrent(dom, 0))) + goto cleanup; + + if (vshCommandOptBool(cmd, "name")) { + const char *name; + if (!(name =3D virDomainCheckpointGetName(checkpoint))) + goto cleanup; + + vshPrint(ctl, "%s", name); + } else { + if (!(xml =3D virDomainCheckpointGetXMLDesc(checkpoint, flags)= )) + goto cleanup; + + vshPrint(ctl, "%s", xml); + } + } + + ret =3D true; + + cleanup: + if (!ret) + vshReportError(ctl); + VIR_FREE(xml); + virshDomainCheckpointFree(checkpoint); + virshDomainFree(dom); + + return ret; +} + +/* Helper function to get the name of a checkpoint's parent. Caller + * must free the result. Returns 0 on success (including when it was + * proven no parent exists), and -1 on failure with error reported + * (such as no checkpoint support or domain deleted in meantime). */ +static int +virshGetCheckpointParent(vshControl *ctl, virDomainCheckpointPtr checkpoin= t, + char **parent_name) +{ + virDomainCheckpointPtr parent =3D NULL; + int ret =3D -1; + + *parent_name =3D NULL; + + parent =3D virDomainCheckpointGetParent(checkpoint, 0); + if (parent) { + /* API works, and virDomainCheckpointGetName will succeed */ + *parent_name =3D vshStrdup(ctl, virDomainCheckpointGetName(parent)= ); + ret =3D 0; + } else if (last_error->code =3D=3D VIR_ERR_NO_DOMAIN_CHECKPOINT) { + /* API works, and we found a root with no parent */ + ret =3D 0; + } + + if (ret < 0) { + vshReportError(ctl); + vshError(ctl, "%s", _("unable to determine if checkpoint has paren= t")); + } else { + vshResetLibvirtError(); + } + virshDomainCheckpointFree(parent); + return ret; +} + +/* + * "checkpoint-info" command + */ +static const vshCmdInfo info_checkpoint_info[] =3D { + {.name =3D "help", + .data =3D N_("checkpoint information") + }, + {.name =3D "desc", + .data =3D N_("Returns basic information about a checkpoint.") + }, + {.name =3D NULL} +}; + +static const vshCmdOptDef opts_checkpoint_info[] =3D { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name =3D "checkpointname", + .type =3D VSH_OT_STRING, + .help =3D N_("checkpoint name"), + .completer =3D virshCheckpointNameCompleter, + }, + VIRSH_COMMON_OPT_CURRENT(N_("info on current checkpoint")), + {.name =3D NULL} +}; + +static bool +cmdCheckpointInfo(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom; + virDomainCheckpointPtr checkpoint =3D NULL; + const char *name; + char *parent =3D NULL; + bool ret =3D false; + int count; + unsigned int flags; + int current; + int metadata; + + dom =3D virshCommandOptDomain(ctl, cmd, NULL); + if (dom =3D=3D NULL) + return false; + + if (virshLookupCheckpoint(ctl, cmd, "checkpointname", true, dom, + &checkpoint, &name) < 0) + goto cleanup; + + vshPrint(ctl, "%-15s %s\n", _("Name:"), name); + vshPrint(ctl, "%-15s %s\n", _("Domain:"), virDomainGetName(dom)); + + /* Determine if checkpoint is current. */ + current =3D virDomainCheckpointIsCurrent(checkpoint, 0); + if (current < 0) { + vshError(ctl, "%s", + _("unexpected problem querying checkpoint state")); + goto cleanup; + } + vshPrint(ctl, "%-15s %s\n", _("Current:"), + current > 0 ? _("yes") : _("no")); + + if (virshGetCheckpointParent(ctl, checkpoint, &parent) < 0) { + vshError(ctl, "%s", + _("unexpected problem querying checkpoint state")); + goto cleanup; + } + vshPrint(ctl, "%-15s %s\n", _("Parent:"), parent ? parent : "-"); + + /* Children, Descendants. */ + flags =3D 0; + count =3D virDomainCheckpointListChildren(checkpoint, NULL, flags); + if (count < 0) { + if (last_error->code =3D=3D VIR_ERR_NO_SUPPORT) { + vshResetLibvirtError(); + ret =3D true; + } + goto cleanup; + } + vshPrint(ctl, "%-15s %d\n", _("Children:"), count); + flags =3D VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS; + count =3D virDomainCheckpointListChildren(checkpoint, NULL, flags); + if (count < 0) + goto cleanup; + vshPrint(ctl, "%-15s %d\n", _("Descendants:"), count); + + /* Metadata. */ + metadata =3D virDomainCheckpointHasMetadata(checkpoint, 0); + if (metadata >=3D 0) + vshPrint(ctl, "%-15s %s\n", _("Metadata:"), + metadata ? _("yes") : _("no")); + + ret =3D true; + + cleanup: + VIR_FREE(parent); + virshDomainCheckpointFree(checkpoint); + virshDomainFree(dom); + return ret; +} + +/* Helpers for collecting a list of checkpoints. */ +struct virshChk { + virDomainCheckpointPtr chk; + char *parent; +}; +struct virshCheckpointList { + struct virshChk *chks; + int nchks; +}; +typedef struct virshCheckpointList *virshCheckpointListPtr; + +static void +virshCheckpointListFree(virshCheckpointListPtr chklist) +{ + size_t i; + + if (!chklist) + return; + if (chklist->chks) { + for (i =3D 0; i < chklist->nchks; i++) { + virshDomainCheckpointFree(chklist->chks[i].chk); + VIR_FREE(chklist->chks[i].parent); + } + VIR_FREE(chklist->chks); + } + VIR_FREE(chklist); +} + +static int +virshChkSorter(const void *a, const void *b) +{ + const struct virshChk *sa =3D a; + const struct virshChk *sb =3D b; + + if (sa->chk && !sb->chk) + return -1; + if (!sa->chk) + return sb->chk !=3D NULL; + + return vshStrcasecmp(virDomainCheckpointGetName(sa->chk), + virDomainCheckpointGetName(sb->chk)); +} + +/* Compute a list of checkpoints from DOM. If FROM is provided, the + * list is limited to descendants of the given checkpoint. If FLAGS is + * given, the list is filtered. If TREE is specified, then all but + * FROM or the roots will also have parent information. */ +static virshCheckpointListPtr +virshCheckpointListCollect(vshControl *ctl, virDomainPtr dom, + virDomainCheckpointPtr from, + unsigned int orig_flags, bool tree) +{ + size_t i; + char **names =3D NULL; + int count =3D -1; + virDomainCheckpointPtr *chks; + virshCheckpointListPtr chklist =3D vshMalloc(ctl, sizeof(*chklist)); + virshCheckpointListPtr ret =3D NULL; + unsigned int flags =3D orig_flags; + + if (from) + count =3D virDomainCheckpointListChildren(from, &chks, flags); + else + count =3D virDomainListCheckpoints(dom, &chks, flags); + if (count < 0) { + vshError(ctl, "%s", + _("unexpected problem querying checkpoints")); + goto cleanup; + } + + /* When mixing --from and --tree, we also want a copy of from + * in the list, but with no parent for that one entry. */ + chklist->chks =3D vshCalloc(ctl, count + (tree && from), + sizeof(*chklist->chks)); + chklist->nchks =3D count; + for (i =3D 0; i < count; i++) + chklist->chks[i].chk =3D chks[i]; + VIR_FREE(chks); + if (tree) { + for (i =3D 0; i < count; i++) { + if (virshGetCheckpointParent(ctl, chklist->chks[i].chk, + &chklist->chks[i].parent) < 0) + goto cleanup; + } + if (from) { + chklist->chks[chklist->nchks++].chk =3D from; + virDomainCheckpointRef(from); + } + } + + qsort(chklist->chks, chklist->nchks, sizeof(*chklist->chks), + virshChkSorter); + + ret =3D chklist; + chklist =3D NULL; + + cleanup: + virshCheckpointListFree(chklist); + if (names && count > 0) + for (i =3D 0; i < count; i++) + VIR_FREE(names[i]); + VIR_FREE(names); + return ret; +} + +static const char * +virshCheckpointListLookup(int id, bool parent, void *opaque) +{ + virshCheckpointListPtr chklist =3D opaque; + if (parent) + return chklist->chks[id].parent; + return virDomainCheckpointGetName(chklist->chks[id].chk); +} + +/* + * "checkpoint-list" command + */ +static const vshCmdInfo info_checkpoint_list[] =3D { + {.name =3D "help", + .data =3D N_("List checkpoints for a domain") + }, + {.name =3D "desc", + .data =3D N_("Checkpoint List") + }, + {.name =3D NULL} +}; + +static const vshCmdOptDef opts_checkpoint_list[] =3D { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name =3D "parent", + .type =3D VSH_OT_BOOL, + .help =3D N_("add a column showing parent checkpoint") + }, + {.name =3D "roots", + .type =3D VSH_OT_BOOL, + .help =3D N_("list only checkpoints without parents") + }, + {.name =3D "leaves", + .type =3D VSH_OT_BOOL, + .help =3D N_("list only checkpoints without children") + }, + {.name =3D "no-leaves", + .type =3D VSH_OT_BOOL, + .help =3D N_("list only checkpoints that are not leaves (with childre= n)") + }, + {.name =3D "metadata", + .type =3D VSH_OT_BOOL, + .help =3D N_("list only checkpoints that have metadata that would pre= vent undefine") + }, + {.name =3D "no-metadata", + .type =3D VSH_OT_BOOL, + .help =3D N_("list only checkpoints that have no metadata managed by = libvirt") + }, + {.name =3D "tree", + .type =3D VSH_OT_BOOL, + .help =3D N_("list checkpoints in a tree") + }, + {.name =3D "from", + .type =3D VSH_OT_STRING, + .help =3D N_("limit list to children of given checkpoint"), + .completer =3D virshCheckpointNameCompleter, + }, + VIRSH_COMMON_OPT_CURRENT(N_("limit list to children of current checkpo= int")), + {.name =3D "descendants", + .type =3D VSH_OT_BOOL, + .help =3D N_("with --from, list all descendants") + }, + {.name =3D "name", + .type =3D VSH_OT_BOOL, + .help =3D N_("list checkpoint names only") + }, + + {.name =3D NULL} +}; + +static bool +cmdCheckpointList(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom =3D NULL; + bool ret =3D false; + unsigned int flags =3D 0; + size_t i; + xmlDocPtr xml =3D NULL; + xmlXPathContextPtr ctxt =3D NULL; + char *doc =3D NULL; + virDomainCheckpointPtr checkpoint =3D NULL; + long long creation_longlong; + time_t creation_time_t; + char timestr[100]; + struct tm time_info; + bool tree =3D vshCommandOptBool(cmd, "tree"); + bool name =3D vshCommandOptBool(cmd, "name"); + bool from =3D vshCommandOptBool(cmd, "from"); + bool parent =3D vshCommandOptBool(cmd, "parent"); + bool roots =3D vshCommandOptBool(cmd, "roots"); + bool current =3D vshCommandOptBool(cmd, "current"); + const char *from_chk =3D NULL; + char *parent_chk =3D NULL; + virDomainCheckpointPtr start =3D NULL; + virshCheckpointListPtr chklist =3D NULL; + + VSH_EXCLUSIVE_OPTIONS_VAR(tree, name); + VSH_EXCLUSIVE_OPTIONS_VAR(parent, roots); + VSH_EXCLUSIVE_OPTIONS_VAR(parent, tree); + VSH_EXCLUSIVE_OPTIONS_VAR(roots, tree); + VSH_EXCLUSIVE_OPTIONS_VAR(roots, from); + VSH_EXCLUSIVE_OPTIONS_VAR(roots, current); + +#define FILTER(option, flag) \ + do { \ + if (vshCommandOptBool(cmd, option)) { \ + if (tree) { \ + vshError(ctl, \ + _("--%s and --tree are mutually exclusive"), \ + option); \ + return false; \ + } \ + flags |=3D VIR_DOMAIN_CHECKPOINT_LIST_ ## flag; \ + } \ + } while (0) + + FILTER("leaves", LEAVES); + FILTER("no-leaves", NO_LEAVES); +#undef FILTER + + if (roots) + flags |=3D VIR_DOMAIN_CHECKPOINT_LIST_ROOTS; + + if (vshCommandOptBool(cmd, "metadata")) + flags |=3D VIR_DOMAIN_CHECKPOINT_LIST_METADATA; + + if (vshCommandOptBool(cmd, "no-metadata")) + flags |=3D VIR_DOMAIN_CHECKPOINT_LIST_NO_METADATA; + + if (vshCommandOptBool(cmd, "descendants")) { + if (!from && !current) { + vshError(ctl, "%s", + _("--descendants requires either --from or --current"= )); + return false; + } + flags |=3D VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS; + } + + if (!(dom =3D virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if ((from || current) && + virshLookupCheckpoint(ctl, cmd, "from", true, dom, &start, &from_c= hk) < 0) + goto cleanup; + + if (!(chklist =3D virshCheckpointListCollect(ctl, dom, start, flags, t= ree))) + goto cleanup; + + if (!tree && !name) { + if (parent) + vshPrintExtra(ctl, " %-20s %-25s %s", + _("Name"), _("Creation Time"), _("Parent")); + else + vshPrintExtra(ctl, " %-20s %-25s", + _("Name"), _("Creation Time")); + vshPrintExtra(ctl, "\n" + "------------------------------" + "--------------\n"); + } + + if (tree) { + for (i =3D 0; i < chklist->nchks; i++) { + if (!chklist->chks[i].parent && + vshTreePrint(ctl, virshCheckpointListLookup, chklist, + chklist->nchks, i) < 0) + goto cleanup; + } + ret =3D true; + goto cleanup; + } + + for (i =3D 0; i < chklist->nchks; i++) { + const char *chk_name; + + /* free up memory from previous iterations of the loop */ + VIR_FREE(parent_chk); + xmlXPathFreeContext(ctxt); + xmlFreeDoc(xml); + VIR_FREE(doc); + + checkpoint =3D chklist->chks[i].chk; + chk_name =3D virDomainCheckpointGetName(checkpoint); + assert(chk_name); + + if (name) { + /* just print the checkpoint name */ + vshPrint(ctl, "%s\n", chk_name); + continue; + } + + if (!(doc =3D virDomainCheckpointGetXMLDesc(checkpoint, 0))) + continue; + + if (!(xml =3D virXMLParseStringCtxt(doc, _("(domain_checkpoint)"),= &ctxt))) + continue; + + if (parent) + parent_chk =3D virXPathString("string(/domaincheckpoint/parent= /name)", + ctxt); + + if (virXPathLongLong("string(/domaincheckpoint/creationTime)", ctx= t, + &creation_longlong) < 0) + continue; + creation_time_t =3D creation_longlong; + if (creation_time_t !=3D creation_longlong) { + vshError(ctl, "%s", _("time_t overflow")); + continue; + } + localtime_r(&creation_time_t, &time_info); + strftime(timestr, sizeof(timestr), "%Y-%m-%d %H:%M:%S %z", + &time_info); + + if (parent) + vshPrint(ctl, " %-20s %-25s %s\n", + chk_name, timestr, parent_chk ?: "-"); + else + vshPrint(ctl, " %-20s %-25s\n", chk_name, timestr); + } + + ret =3D true; + + cleanup: + /* this frees up memory from the last iteration of the loop */ + virshCheckpointListFree(chklist); + VIR_FREE(parent_chk); + virshDomainCheckpointFree(start); + xmlXPathFreeContext(ctxt); + xmlFreeDoc(xml); + VIR_FREE(doc); + virshDomainFree(dom); + + return ret; +} + +/* + * "checkpoint-dumpxml" command + */ +static const vshCmdInfo info_checkpoint_dumpxml[] =3D { + {.name =3D "help", + .data =3D N_("Dump XML for a domain checkpoint") + }, + {.name =3D "desc", + .data =3D N_("Checkpoint Dump XML") + }, + {.name =3D NULL} +}; + +static const vshCmdOptDef opts_checkpoint_dumpxml[] =3D { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name =3D "checkpointname", + .type =3D VSH_OT_DATA, + .flags =3D VSH_OFLAG_REQ, + .help =3D N_("checkpoint name"), + .completer =3D virshCheckpointNameCompleter, + }, + {.name =3D "security-info", + .type =3D VSH_OT_BOOL, + .help =3D N_("include security sensitive information in XML dump") + }, + {.name =3D "no-domain", + .type =3D VSH_OT_BOOL, + .help =3D N_("exclude from XML") + }, + {.name =3D "size", + .type =3D VSH_OT_BOOL, + .help =3D N_("include backup size estimate in XML dump") + }, + {.name =3D NULL} +}; + +static bool +cmdCheckpointDumpXML(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom =3D NULL; + bool ret =3D false; + const char *name =3D NULL; + virDomainCheckpointPtr checkpoint =3D NULL; + char *xml =3D NULL; + unsigned int flags =3D 0; + + if (vshCommandOptBool(cmd, "security-info")) + flags |=3D VIR_DOMAIN_CHECKPOINT_XML_SECURE; + if (vshCommandOptBool(cmd, "no-domain")) + flags |=3D VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN; + if (vshCommandOptBool(cmd, "size")) + flags |=3D VIR_DOMAIN_CHECKPOINT_XML_SIZE; + + if (vshCommandOptStringReq(ctl, cmd, "checkpointname", &name) < 0) + return false; + + if (!(dom =3D virshCommandOptDomain(ctl, cmd, NULL))) + return false; + + if (!(checkpoint =3D virDomainCheckpointLookupByName(dom, name, 0))) + goto cleanup; + + if (!(xml =3D virDomainCheckpointGetXMLDesc(checkpoint, flags))) + goto cleanup; + + vshPrint(ctl, "%s", xml); + ret =3D true; + + cleanup: + VIR_FREE(xml); + virshDomainCheckpointFree(checkpoint); + virshDomainFree(dom); + + return ret; +} + +/* + * "checkpoint-parent" command + */ +static const vshCmdInfo info_checkpoint_parent[] =3D { + {.name =3D "help", + .data =3D N_("Get the name of the parent of a checkpoint") + }, + {.name =3D "desc", + .data =3D N_("Extract the checkpoint's parent, if any") + }, + {.name =3D NULL} +}; + +static const vshCmdOptDef opts_checkpoint_parent[] =3D { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name =3D "checkpointname", + .type =3D VSH_OT_STRING, + .help =3D N_("find parent of checkpoint name"), + .completer =3D virshCheckpointNameCompleter, + }, + VIRSH_COMMON_OPT_CURRENT(N_("find parent of current checkpoint")), + {.name =3D NULL} +}; + +static bool +cmdCheckpointParent(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom =3D NULL; + bool ret =3D false; + const char *name =3D NULL; + virDomainCheckpointPtr checkpoint =3D NULL; + char *parent =3D NULL; + + dom =3D virshCommandOptDomain(ctl, cmd, NULL); + if (dom =3D=3D NULL) + goto cleanup; + + if (virshLookupCheckpoint(ctl, cmd, "checkpointname", true, dom, + &checkpoint, &name) < 0) + goto cleanup; + + if (virshGetCheckpointParent(ctl, checkpoint, &parent) < 0) + goto cleanup; + if (!parent) { + vshError(ctl, _("checkpoint '%s' has no parent"), name); + goto cleanup; + } + + vshPrint(ctl, "%s", parent); + + ret =3D true; + + cleanup: + VIR_FREE(parent); + virshDomainCheckpointFree(checkpoint); + virshDomainFree(dom); + + return ret; +} + +/* + * "checkpoint-delete" command + */ +static const vshCmdInfo info_checkpoint_delete[] =3D { + {.name =3D "help", + .data =3D N_("Delete a domain checkpoint") + }, + {.name =3D "desc", + .data =3D N_("Checkpoint Delete") + }, + {.name =3D NULL} +}; + +static const vshCmdOptDef opts_checkpoint_delete[] =3D { + VIRSH_COMMON_OPT_DOMAIN_FULL(0), + {.name =3D "checkpointname", + .type =3D VSH_OT_STRING, + .help =3D N_("checkpoint name"), + .completer =3D virshCheckpointNameCompleter, + }, + VIRSH_COMMON_OPT_CURRENT(N_("delete current checkpoint")), + {.name =3D "children", + .type =3D VSH_OT_BOOL, + .help =3D N_("delete checkpoint and all children") + }, + {.name =3D "children-only", + .type =3D VSH_OT_BOOL, + .help =3D N_("delete children but not checkpoint") + }, + {.name =3D "metadata", + .type =3D VSH_OT_BOOL, + .help =3D N_("delete only libvirt metadata, leaving checkpoint conten= ts behind") + }, + {.name =3D NULL} +}; + +static bool +cmdCheckpointDelete(vshControl *ctl, const vshCmd *cmd) +{ + virDomainPtr dom =3D NULL; + bool ret =3D false; + const char *name =3D NULL; + virDomainCheckpointPtr checkpoint =3D NULL; + unsigned int flags =3D 0; + + dom =3D virshCommandOptDomain(ctl, cmd, NULL); + if (dom =3D=3D NULL) + goto cleanup; + + if (virshLookupCheckpoint(ctl, cmd, "checkpointname", true, dom, + &checkpoint, &name) < 0) + goto cleanup; + + if (vshCommandOptBool(cmd, "children")) + flags |=3D VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN; + if (vshCommandOptBool(cmd, "children-only")) + flags |=3D VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY; + if (vshCommandOptBool(cmd, "metadata")) + flags |=3D VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY; + + if (virDomainCheckpointDelete(checkpoint, flags) =3D=3D 0) { + if (flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY) + vshPrintExtra(ctl, _("Domain checkpoint %s children deleted\n"= ), name); + else + vshPrintExtra(ctl, _("Domain checkpoint %s deleted\n"), name); + } else { + vshError(ctl, _("Failed to delete checkpoint %s"), name); + goto cleanup; + } + + ret =3D true; + + cleanup: + virshDomainCheckpointFree(checkpoint); + virshDomainFree(dom); + + return ret; +} + +const vshCmdDef checkpointCmds[] =3D { + {.name =3D "checkpoint-create", + .handler =3D cmdCheckpointCreate, + .opts =3D opts_checkpoint_create, + .info =3D info_checkpoint_create, + .flags =3D 0 + }, + {.name =3D "checkpoint-create-as", + .handler =3D cmdCheckpointCreateAs, + .opts =3D opts_checkpoint_create_as, + .info =3D info_checkpoint_create_as, + .flags =3D 0 + }, + {.name =3D "checkpoint-current", + .handler =3D cmdCheckpointCurrent, + .opts =3D opts_checkpoint_current, + .info =3D info_checkpoint_current, + .flags =3D 0 + }, + {.name =3D "checkpoint-delete", + .handler =3D cmdCheckpointDelete, + .opts =3D opts_checkpoint_delete, + .info =3D info_checkpoint_delete, + .flags =3D 0 + }, + {.name =3D "checkpoint-dumpxml", + .handler =3D cmdCheckpointDumpXML, + .opts =3D opts_checkpoint_dumpxml, + .info =3D info_checkpoint_dumpxml, + .flags =3D 0 + }, + {.name =3D "checkpoint-edit", + .handler =3D cmdCheckpointEdit, + .opts =3D opts_checkpoint_edit, + .info =3D info_checkpoint_edit, + .flags =3D 0 + }, + {.name =3D "checkpoint-info", + .handler =3D cmdCheckpointInfo, + .opts =3D opts_checkpoint_info, + .info =3D info_checkpoint_info, + .flags =3D 0 + }, + {.name =3D "checkpoint-list", + .handler =3D cmdCheckpointList, + .opts =3D opts_checkpoint_list, + .info =3D info_checkpoint_list, + .flags =3D 0 + }, + {.name =3D "checkpoint-parent", + .handler =3D cmdCheckpointParent, + .opts =3D opts_checkpoint_parent, + .info =3D info_checkpoint_parent, + .flags =3D 0 + }, + {.name =3D NULL} +}; diff --git a/tools/virsh-completer.c b/tools/virsh-completer.c index 44ddada538..dfe0afe933 100644 --- a/tools/virsh-completer.c +++ b/tools/virsh-completer.c @@ -1,7 +1,7 @@ /* * virsh-completer.c: virsh completer callbacks * - * Copyright (C) 2017 Red Hat, Inc. + * Copyright (C) 2017-2018 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -565,6 +565,56 @@ virshSecretUUIDCompleter(vshControl *ctl, } +char ** +virshCheckpointNameCompleter(vshControl *ctl, + const vshCmd *cmd, + unsigned int flags) +{ + virshControlPtr priv =3D ctl->privData; + virDomainPtr dom =3D NULL; + virDomainCheckpointPtr *checkpoints =3D NULL; + int ncheckpoints =3D 0; + size_t i =3D 0; + char **ret =3D NULL; + + virCheckFlags(0, NULL); + + if (!priv->conn || virConnectIsAlive(priv->conn) <=3D 0) + return NULL; + + if (!(dom =3D virshCommandOptDomain(ctl, cmd, NULL))) + return NULL; + + if ((ncheckpoints =3D virDomainListCheckpoints(dom, &checkpoints, flag= s)) < 0) + goto error; + + if (VIR_ALLOC_N(ret, ncheckpoints + 1) < 0) + goto error; + + for (i =3D 0; i < ncheckpoints; i++) { + const char *name =3D virDomainCheckpointGetName(checkpoints[i]); + + if (VIR_STRDUP(ret[i], name) < 0) + goto error; + + virshDomainCheckpointFree(checkpoints[i]); + } + VIR_FREE(checkpoints); + virshDomainFree(dom); + + return ret; + + error: + for (; i < ncheckpoints; i++) + virshDomainCheckpointFree(checkpoints[i]); + VIR_FREE(checkpoints); + for (i =3D 0; i < ncheckpoints; i++) + VIR_FREE(ret[i]); + VIR_FREE(ret); + virshDomainFree(dom); + return NULL; +} + char ** virshSnapshotNameCompleter(vshControl *ctl, const vshCmd *cmd, diff --git a/tools/virsh-util.c b/tools/virsh-util.c index aa88397d61..933d1c825d 100644 --- a/tools/virsh-util.c +++ b/tools/virsh-util.c @@ -228,6 +228,17 @@ virshDomainFree(virDomainPtr dom) } +void +virshDomainCheckpointFree(virDomainCheckpointPtr chk) +{ + if (!chk) + return; + + vshSaveLibvirtHelperError(); + virDomainCheckpointFree(chk); /* sc_prohibit_obj_free_apis_in_virsh */ +} + + void virshDomainSnapshotFree(virDomainSnapshotPtr snap) { diff --git a/tools/virsh.c b/tools/virsh.c index eb4f9caa7f..9501223243 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -54,6 +54,7 @@ #include "virstring.h" #include "virgettext.h" +#include "virsh-checkpoint.h" #include "virsh-console.h" #include "virsh-domain.h" #include "virsh-domain-monitor.h" @@ -839,6 +840,7 @@ static const vshCmdGrp cmdGroups[] =3D { {VIRSH_CMD_GRP_DOM_MANAGEMENT, "domain", domManagementCmds}, {VIRSH_CMD_GRP_DOM_MONITORING, "monitor", domMonitoringCmds}, {VIRSH_CMD_GRP_HOST_AND_HV, "host", hostAndHypervisorCmds}, + {VIRSH_CMD_GRP_CHECKPOINT, "checkpoint", checkpointCmds}, {VIRSH_CMD_GRP_IFACE, "interface", ifaceCmds}, {VIRSH_CMD_GRP_NWFILTER, "filter", nwfilterCmds}, {VIRSH_CMD_GRP_NETWORK, "network", networkCmds}, --=20 2.17.2 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list