2019-03-14 00:04:51 +03:00
/*
* virsh - checkpoint . c : Commands to manage domain checkpoints
*
* Copyright ( C ) 2005 - 2019 Red Hat , Inc .
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library . If not , see
* < http : //www.gnu.org/licenses/>.
*/
# include <config.h>
# include "virsh-checkpoint.h"
# include <assert.h>
# include <libxml/parser.h>
# include <libxml/tree.h>
# include <libxml/xpath.h>
# include <libxml/xmlsave.h>
# include "internal.h"
# include "virbuffer.h"
# include "viralloc.h"
# include "virfile.h"
# include "virsh-util.h"
# include "virxml.h"
# include "vsh-table.h"
/* Helper for checkpoint-create and checkpoint-create-as */
static bool
virshCheckpointCreate ( vshControl * ctl ,
virDomainPtr dom ,
const char * buffer ,
unsigned int flags ,
const char * from )
{
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomainCheckpoint ) checkpoint = NULL ;
2019-03-14 00:04:51 +03:00
const char * name = NULL ;
checkpoint = virDomainCheckpointCreateXML ( dom , buffer , flags ) ;
if ( checkpoint = = NULL )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
name = virDomainCheckpointGetName ( checkpoint ) ;
if ( ! name ) {
vshError ( ctl , " %s " , _ ( " Could not get checkpoint name " ) ) ;
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
}
if ( from )
2023-03-09 17:54:42 +03:00
vshPrintExtra ( ctl , _ ( " Domain checkpoint %1$s created from '%2$s' " ) ,
2019-03-14 00:04:51 +03:00
name , from ) ;
else
2023-03-09 17:54:42 +03:00
vshPrintExtra ( ctl , _ ( " Domain checkpoint %1$s created " ) , name ) ;
2019-03-14 00:04:51 +03:00
2021-08-10 18:47:59 +03:00
return true ;
2019-03-14 00:04:51 +03:00
}
/*
* " checkpoint-create " command
*/
static const vshCmdInfo info_checkpoint_create [ ] = {
{ . name = " help " ,
. data = N_ ( " Create a checkpoint from XML " )
} ,
{ . name = " desc " ,
. data = N_ ( " Create a checkpoint from XML for use in "
" future incremental backups " )
} ,
{ . name = NULL }
} ;
static const vshCmdOptDef opts_checkpoint_create [ ] = {
2020-09-11 10:13:18 +03:00
VIRSH_COMMON_OPT_DOMAIN_FULL ( VIR_CONNECT_LIST_DOMAINS_ACTIVE ) ,
2019-03-14 00:04:51 +03:00
{ . name = " xmlfile " ,
. type = VSH_OT_STRING ,
2021-09-15 18:26:35 +03:00
. completer = virshCompletePathLocalExisting ,
2019-03-14 00:04:51 +03:00
. help = N_ ( " domain checkpoint XML " )
} ,
{ . name = " redefine " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " redefine metadata for existing checkpoint " )
} ,
2020-11-03 14:49:11 +03:00
{ . name = " redefine-validate " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " validate the redefined checkpoint " )
} ,
2019-03-14 00:04:51 +03:00
{ . name = " quiesce " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " quiesce guest's file systems " )
} ,
{ . name = NULL }
} ;
static bool
cmdCheckpointCreate ( vshControl * ctl ,
const vshCmd * cmd )
{
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomain ) dom = NULL ;
2019-03-14 00:04:51 +03:00
const char * from = NULL ;
2021-08-10 18:07:38 +03:00
g_autofree char * buffer = NULL ;
2019-03-14 00:04:51 +03:00
unsigned int flags = 0 ;
2020-11-03 14:49:11 +03:00
VSH_REQUIRE_OPTION ( " redefine-validate " , " redefine " ) ;
2019-03-14 00:04:51 +03:00
if ( vshCommandOptBool ( cmd , " redefine " ) )
flags | = VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE ;
2020-11-03 14:49:11 +03:00
if ( vshCommandOptBool ( cmd , " redefine-validate " ) )
2020-11-09 19:14:53 +03:00
flags | = VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE_VALIDATE ;
2019-03-14 00:04:51 +03:00
if ( vshCommandOptBool ( cmd , " quiesce " ) )
flags | = VIR_DOMAIN_CHECKPOINT_CREATE_QUIESCE ;
if ( ! ( dom = virshCommandOptDomain ( ctl , cmd , NULL ) ) )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
if ( vshCommandOptStringReq ( ctl , cmd , " xmlfile " , & from ) < 0 )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
if ( ! from ) {
2019-10-18 18:24:02 +03:00
buffer = g_strdup ( " <domaincheckpoint/> " ) ;
2019-03-14 00:04:51 +03:00
} else {
if ( virFileReadAll ( from , VSH_MAX_XML_FILE , & buffer ) < 0 ) {
vshSaveLibvirtError ( ) ;
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
}
}
2021-08-10 18:47:59 +03:00
return virshCheckpointCreate ( ctl , dom , buffer , flags , from ) ;
2019-03-14 00:04:51 +03:00
}
/*
* " checkpoint-create-as " command
*/
static int
virshParseCheckpointDiskspec ( vshControl * ctl ,
2021-03-11 10:16:13 +03:00
virBuffer * buf ,
2019-03-14 00:04:51 +03:00
const char * str )
{
int ret = - 1 ;
const char * name = NULL ;
const char * checkpoint = NULL ;
const char * bitmap = NULL ;
2021-08-10 18:07:38 +03:00
g_auto ( GStrv ) array = NULL ;
2019-03-14 00:04:51 +03:00
int narray ;
size_t i ;
narray = vshStringToArray ( str , & array ) ;
if ( narray < = 0 )
goto cleanup ;
name = array [ 0 ] ;
for ( i = 1 ; i < narray ; i + + ) {
if ( ! checkpoint & & STRPREFIX ( array [ i ] , " checkpoint= " ) )
checkpoint = array [ i ] + strlen ( " checkpoint= " ) ;
else if ( ! bitmap & & STRPREFIX ( array [ i ] , " bitmap= " ) )
bitmap = array [ i ] + strlen ( " bitmap= " ) ;
else
goto cleanup ;
}
virBufferEscapeString ( buf , " <disk name='%s' " , name ) ;
if ( checkpoint )
virBufferAsprintf ( buf , " checkpoint='%s' " , checkpoint ) ;
if ( bitmap )
virBufferAsprintf ( buf , " bitmap='%s' " , bitmap ) ;
virBufferAddLit ( buf , " /> \n " ) ;
ret = 0 ;
cleanup :
if ( ret < 0 )
2023-03-09 17:54:42 +03:00
vshError ( ctl , _ ( " unable to parse diskspec: %1$s " ) , str ) ;
2019-03-14 00:04:51 +03:00
return ret ;
}
static const vshCmdInfo info_checkpoint_create_as [ ] = {
{ . name = " help " ,
. data = N_ ( " Create a checkpoint from a set of args " )
} ,
{ . name = " desc " ,
. data = N_ ( " Create a checkpoint from arguments for use in "
" future incremental backups " )
} ,
{ . name = NULL }
} ;
static const vshCmdOptDef opts_checkpoint_create_as [ ] = {
2020-09-11 10:13:18 +03:00
VIRSH_COMMON_OPT_DOMAIN_FULL ( VIR_CONNECT_LIST_DOMAINS_ACTIVE ) ,
2019-03-14 00:04:51 +03:00
{ . name = " name " ,
. type = VSH_OT_STRING ,
2021-09-15 18:42:08 +03:00
. completer = virshCompleteEmpty ,
2019-03-14 00:04:51 +03:00
. help = N_ ( " name of checkpoint " )
} ,
{ . name = " description " ,
. type = VSH_OT_STRING ,
2021-09-15 18:42:08 +03:00
. completer = virshCompleteEmpty ,
2019-03-14 00:04:51 +03:00
. help = N_ ( " description of checkpoint " )
} ,
{ . name = " print-xml " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " print XML document rather than create " )
} ,
{ . name = " quiesce " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " quiesce guest's file systems " )
} ,
{ . name = " diskspec " ,
. type = VSH_OT_ARGV ,
. help = N_ ( " disk attributes: disk[,checkpoint=type][,bitmap=name] " )
} ,
{ . name = NULL }
} ;
static bool
cmdCheckpointCreateAs ( vshControl * ctl ,
const vshCmd * cmd )
{
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomain ) dom = NULL ;
g_autofree char * buffer = NULL ;
2019-03-14 00:04:51 +03:00
const char * name = NULL ;
const char * desc = NULL ;
2020-07-03 02:40:16 +03:00
g_auto ( virBuffer ) buf = VIR_BUFFER_INITIALIZER ;
2019-03-14 00:04:51 +03:00
unsigned int flags = 0 ;
const vshCmdOpt * opt = NULL ;
if ( vshCommandOptBool ( cmd , " quiesce " ) )
flags | = VIR_DOMAIN_CHECKPOINT_CREATE_QUIESCE ;
if ( ! ( dom = virshCommandOptDomain ( ctl , cmd , NULL ) ) )
return false ;
if ( vshCommandOptStringReq ( ctl , cmd , " name " , & name ) < 0 | |
vshCommandOptStringReq ( ctl , cmd , " description " , & desc ) < 0 )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
virBufferAddLit ( & buf , " <domaincheckpoint> \n " ) ;
virBufferAdjustIndent ( & buf , 2 ) ;
virBufferEscapeString ( & buf , " <name>%s</name> \n " , name ) ;
virBufferEscapeString ( & buf , " <description>%s</description> \n " , desc ) ;
if ( vshCommandOptBool ( cmd , " diskspec " ) ) {
virBufferAddLit ( & buf , " <disks> \n " ) ;
virBufferAdjustIndent ( & buf , 2 ) ;
while ( ( opt = vshCommandOptArgv ( ctl , cmd , opt ) ) ) {
if ( virshParseCheckpointDiskspec ( ctl , & buf , opt - > data ) < 0 )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
}
virBufferAdjustIndent ( & buf , - 2 ) ;
virBufferAddLit ( & buf , " </disks> \n " ) ;
}
virBufferAdjustIndent ( & buf , - 2 ) ;
virBufferAddLit ( & buf , " </domaincheckpoint> \n " ) ;
buffer = virBufferContentAndReset ( & buf ) ;
if ( vshCommandOptBool ( cmd , " print-xml " ) ) {
vshPrint ( ctl , " %s \n " , buffer ) ;
2021-08-10 18:47:59 +03:00
return true ;
2019-03-14 00:04:51 +03:00
}
2021-08-10 18:47:59 +03:00
return virshCheckpointCreate ( ctl , dom , buffer , flags , NULL ) ;
2019-03-14 00:04:51 +03:00
}
/* Helper for resolving --ARG name into a checkpoint
* belonging to DOM . On success , populate * CHK and * NAME , before
* returning 0. On failure , return - 1 after issuing an error
* message . */
static int
virshLookupCheckpoint ( vshControl * ctl ,
const vshCmd * cmd ,
const char * arg ,
virDomainPtr dom ,
virDomainCheckpointPtr * chk ,
const char * * name )
{
const char * chkname = NULL ;
if ( vshCommandOptStringReq ( ctl , cmd , arg , & chkname ) < 0 )
return - 1 ;
if ( chkname ) {
* chk = virDomainCheckpointLookupByName ( dom , chkname , 0 ) ;
} else {
2023-03-09 17:54:42 +03:00
vshError ( ctl , _ ( " --%1$s is required " ) , arg ) ;
2019-03-14 00:04:51 +03:00
return - 1 ;
}
if ( ! * chk ) {
vshReportError ( ctl ) ;
return - 1 ;
}
* name = virDomainCheckpointGetName ( * chk ) ;
return 0 ;
}
/*
* " checkpoint-edit " command
*/
static const vshCmdInfo info_checkpoint_edit [ ] = {
{ . name = " help " ,
. data = N_ ( " edit XML for a checkpoint " )
} ,
{ . name = " desc " ,
. data = N_ ( " Edit the domain checkpoint XML for a named checkpoint " )
} ,
{ . name = NULL }
} ;
static const vshCmdOptDef opts_checkpoint_edit [ ] = {
2020-09-11 10:13:05 +03:00
VIRSH_COMMON_OPT_DOMAIN_FULL ( VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT ) ,
2019-03-14 00:04:51 +03:00
{ . name = " checkpointname " ,
. type = VSH_OT_STRING ,
. help = N_ ( " checkpoint name " ) ,
. completer = virshCheckpointNameCompleter ,
} ,
{ . name = NULL }
} ;
static bool
cmdCheckpointEdit ( vshControl * ctl ,
const vshCmd * cmd )
{
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomain ) dom = NULL ;
g_autoptr ( virshDomainCheckpoint ) checkpoint = NULL ;
g_autoptr ( virshDomainCheckpoint ) edited = NULL ;
2019-03-14 00:04:51 +03:00
const char * name = NULL ;
const char * edited_name ;
bool ret = false ;
unsigned int getxml_flags = VIR_DOMAIN_CHECKPOINT_XML_SECURE ;
unsigned int define_flags = VIR_DOMAIN_CHECKPOINT_CREATE_REDEFINE ;
if ( ! ( dom = virshCommandOptDomain ( ctl , cmd , NULL ) ) )
return false ;
if ( virshLookupCheckpoint ( ctl , cmd , " checkpointname " , dom ,
& checkpoint , & name ) < 0 )
goto cleanup ;
# define EDIT_GET_XML \
virDomainCheckpointGetXMLDesc ( checkpoint , getxml_flags )
# define EDIT_NOT_CHANGED \
do { \
vshPrintExtra ( ctl , \
2023-03-09 17:54:42 +03:00
_ ( " Checkpoint %1$s XML configuration not changed. \n " ) , \
2019-03-14 00:04:51 +03:00
name ) ; \
ret = true ; \
goto edit_cleanup ; \
} while ( 0 )
# define EDIT_DEFINE \
edited = virDomainCheckpointCreateXML ( dom , doc_edited , define_flags )
# include "virsh-edit.c"
edited_name = virDomainCheckpointGetName ( edited ) ;
if ( STREQ ( name , edited_name ) ) {
2023-03-09 17:54:42 +03:00
vshPrintExtra ( ctl , _ ( " Checkpoint %1$s edited. \n " ) , name ) ;
2019-03-14 00:04:51 +03:00
} else {
unsigned int delete_flags = VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY ;
if ( virDomainCheckpointDelete ( edited , delete_flags ) < 0 ) {
vshReportError ( ctl ) ;
2023-03-09 17:54:42 +03:00
vshError ( ctl , _ ( " Failed to clean up %1$s " ) , edited_name ) ;
2019-03-14 00:04:51 +03:00
goto cleanup ;
}
2023-03-09 17:54:42 +03:00
vshError ( ctl , _ ( " Cannot rename checkpoint %1$s to %2$s " ) ,
2019-03-14 00:04:51 +03:00
name , edited_name ) ;
goto cleanup ;
}
ret = true ;
cleanup :
if ( ! ret & & name )
2023-03-09 17:54:42 +03:00
vshError ( ctl , _ ( " Failed to update %1$s " ) , name ) ;
2019-03-14 00:04:51 +03:00
return ret ;
}
/* Helper function to get the name of a checkpoint's parent. Caller
* must free the result . Returns 0 on success ( including when it was
* proven no parent exists ) , and - 1 on failure with error reported
* ( such as no checkpoint support or domain deleted in meantime ) . */
static int
virshGetCheckpointParent ( vshControl * ctl ,
virDomainCheckpointPtr checkpoint ,
char * * parent_name )
{
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomainCheckpoint ) parent = NULL ;
2019-03-14 00:04:51 +03:00
int ret = - 1 ;
* parent_name = NULL ;
parent = virDomainCheckpointGetParent ( checkpoint , 0 ) ;
if ( parent ) {
/* API works, and virDomainCheckpointGetName will succeed */
2019-10-18 18:24:02 +03:00
* parent_name = g_strdup ( virDomainCheckpointGetName ( parent ) ) ;
2019-03-14 00:04:51 +03:00
ret = 0 ;
} else if ( last_error - > code = = VIR_ERR_NO_DOMAIN_CHECKPOINT ) {
/* API works, and we found a root with no parent */
ret = 0 ;
}
if ( ret < 0 ) {
vshReportError ( ctl ) ;
vshError ( ctl , " %s " , _ ( " unable to determine if checkpoint has parent " ) ) ;
} else {
vshResetLibvirtError ( ) ;
}
return ret ;
}
/*
* " checkpoint-info " command
*/
static const vshCmdInfo info_checkpoint_info [ ] = {
{ . name = " help " ,
. data = N_ ( " checkpoint information " )
} ,
{ . name = " desc " ,
. data = N_ ( " Returns basic information about a checkpoint. " )
} ,
{ . name = NULL }
} ;
static const vshCmdOptDef opts_checkpoint_info [ ] = {
2020-09-11 10:13:05 +03:00
VIRSH_COMMON_OPT_DOMAIN_FULL ( VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT ) ,
2019-03-14 00:04:51 +03:00
{ . name = " checkpointname " ,
. type = VSH_OT_STRING ,
. help = N_ ( " checkpoint name " ) ,
. completer = virshCheckpointNameCompleter ,
} ,
{ . name = NULL }
} ;
static bool
cmdCheckpointInfo ( vshControl * ctl ,
const vshCmd * cmd )
{
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomain ) dom = NULL ;
g_autoptr ( virshDomainCheckpoint ) checkpoint = NULL ;
2019-03-14 00:04:51 +03:00
const char * name ;
2021-08-10 18:07:38 +03:00
g_autofree char * parent = NULL ;
2019-03-14 00:04:51 +03:00
int count ;
unsigned int flags ;
dom = virshCommandOptDomain ( ctl , cmd , NULL ) ;
if ( dom = = NULL )
return false ;
if ( virshLookupCheckpoint ( ctl , cmd , " checkpointname " , dom ,
& checkpoint , & name ) < 0 )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
vshPrint ( ctl , " %-15s %s \n " , _ ( " Name: " ) , name ) ;
vshPrint ( ctl , " %-15s %s \n " , _ ( " Domain: " ) , virDomainGetName ( dom ) ) ;
if ( virshGetCheckpointParent ( ctl , checkpoint , & parent ) < 0 ) {
vshError ( ctl , " %s " ,
_ ( " unexpected problem querying checkpoint state " ) ) ;
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
}
vshPrint ( ctl , " %-15s %s \n " , _ ( " Parent: " ) , parent ? parent : " - " ) ;
/* Children, Descendants. */
flags = 0 ;
count = virDomainCheckpointListAllChildren ( checkpoint , NULL , flags ) ;
if ( count < 0 ) {
if ( last_error - > code = = VIR_ERR_NO_SUPPORT ) {
vshResetLibvirtError ( ) ;
2021-08-10 18:47:59 +03:00
return true ;
2019-03-14 00:04:51 +03:00
}
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
}
vshPrint ( ctl , " %-15s %d \n " , _ ( " Children: " ) , count ) ;
flags = VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS ;
count = virDomainCheckpointListAllChildren ( checkpoint , NULL , flags ) ;
if ( count < 0 )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
vshPrint ( ctl , " %-15s %d \n " , _ ( " Descendants: " ) , count ) ;
2021-08-10 18:47:59 +03:00
return true ;
2019-03-14 00:04:51 +03:00
}
/* Helpers for collecting a list of checkpoints. */
struct virshChk {
virDomainCheckpointPtr chk ;
char * parent ;
} ;
struct virshCheckpointList {
struct virshChk * chks ;
int nchks ;
} ;
static void
2021-03-11 10:16:13 +03:00
virshCheckpointListFree ( struct virshCheckpointList * checkpointlist )
2019-03-14 00:04:51 +03:00
{
size_t i ;
if ( ! checkpointlist )
return ;
if ( checkpointlist - > chks ) {
for ( i = 0 ; i < checkpointlist - > nchks ; i + + ) {
virshDomainCheckpointFree ( checkpointlist - > chks [ i ] . chk ) ;
2021-02-03 22:32:55 +03:00
g_free ( checkpointlist - > chks [ i ] . parent ) ;
2019-03-14 00:04:51 +03:00
}
2021-02-03 22:32:55 +03:00
g_free ( checkpointlist - > chks ) ;
2019-03-14 00:04:51 +03:00
}
2021-02-03 22:32:55 +03:00
g_free ( checkpointlist ) ;
2019-03-14 00:04:51 +03:00
}
static int
virshChkSorter ( const void * a ,
const void * b )
{
const struct virshChk * sa = a ;
const struct virshChk * sb = b ;
if ( sa - > chk & & ! sb - > chk )
return - 1 ;
if ( ! sa - > chk )
return sb - > chk ! = NULL ;
return vshStrcasecmp ( virDomainCheckpointGetName ( sa - > chk ) ,
virDomainCheckpointGetName ( sb - > chk ) ) ;
}
/* Compute a list of checkpoints from DOM. If FROM is provided, the
* list is limited to descendants of the given checkpoint . If FLAGS is
* given , the list is filtered . If TREE is specified , then all but
* FROM or the roots will also have parent information . */
2021-03-11 10:16:13 +03:00
static struct virshCheckpointList *
2019-03-14 00:04:51 +03:00
virshCheckpointListCollect ( vshControl * ctl ,
virDomainPtr dom ,
virDomainCheckpointPtr from ,
unsigned int orig_flags ,
bool tree )
{
size_t i ;
int count = - 1 ;
virDomainCheckpointPtr * chks ;
2021-03-11 10:16:13 +03:00
struct virshCheckpointList * checkpointlist = NULL ;
struct virshCheckpointList * ret = NULL ;
2019-03-14 00:04:51 +03:00
unsigned int flags = orig_flags ;
2020-10-05 19:50:09 +03:00
checkpointlist = g_new0 ( struct virshCheckpointList , 1 ) ;
2019-03-14 00:04:51 +03:00
if ( from )
count = virDomainCheckpointListAllChildren ( from , & chks , flags ) ;
else
count = virDomainListAllCheckpoints ( dom , & chks , flags ) ;
if ( count < 0 ) {
vshError ( ctl , " %s " ,
_ ( " unexpected problem querying checkpoints " ) ) ;
goto cleanup ;
}
/* When mixing --from and --tree, we also want a copy of from
* in the list , but with no parent for that one entry . */
2020-10-05 19:48:21 +03:00
if ( from & & tree )
checkpointlist - > chks = g_new0 ( struct virshChk , count + 1 ) ;
else
checkpointlist - > chks = g_new0 ( struct virshChk , count ) ;
2019-03-14 00:04:51 +03:00
checkpointlist - > nchks = count ;
for ( i = 0 ; i < count ; i + + )
checkpointlist - > chks [ i ] . chk = chks [ i ] ;
VIR_FREE ( chks ) ;
if ( tree ) {
for ( i = 0 ; i < count ; i + + ) {
if ( virshGetCheckpointParent ( ctl , checkpointlist - > chks [ i ] . chk ,
& checkpointlist - > chks [ i ] . parent ) < 0 )
goto cleanup ;
}
if ( from ) {
checkpointlist - > chks [ checkpointlist - > nchks + + ] . chk = from ;
virDomainCheckpointRef ( from ) ;
}
}
2021-04-19 14:54:15 +03:00
if ( ! ( orig_flags & VIR_DOMAIN_CHECKPOINT_LIST_TOPOLOGICAL ) & &
checkpointlist - > chks )
2019-03-14 00:04:51 +03:00
qsort ( checkpointlist - > chks , checkpointlist - > nchks ,
sizeof ( * checkpointlist - > chks ) , virshChkSorter ) ;
2021-02-23 16:58:29 +03:00
ret = g_steal_pointer ( & checkpointlist ) ;
2019-03-14 00:04:51 +03:00
cleanup :
virshCheckpointListFree ( checkpointlist ) ;
return ret ;
}
static const char *
virshCheckpointListLookup ( int id ,
bool parent ,
void * opaque )
{
2021-03-11 10:16:13 +03:00
struct virshCheckpointList * checkpointlist = opaque ;
2019-03-14 00:04:51 +03:00
if ( parent )
return checkpointlist - > chks [ id ] . parent ;
return virDomainCheckpointGetName ( checkpointlist - > chks [ id ] . chk ) ;
}
/*
* " checkpoint-list " command
*/
static const vshCmdInfo info_checkpoint_list [ ] = {
{ . name = " help " ,
. data = N_ ( " List checkpoints for a domain " )
} ,
{ . name = " desc " ,
. data = N_ ( " Checkpoint List " )
} ,
{ . name = NULL }
} ;
static const vshCmdOptDef opts_checkpoint_list [ ] = {
2020-09-11 10:13:05 +03:00
VIRSH_COMMON_OPT_DOMAIN_FULL ( VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT ) ,
2019-03-14 00:04:51 +03:00
{ . name = " parent " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " add a column showing parent checkpoint " )
} ,
{ . name = " roots " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " list only checkpoints without parents " )
} ,
{ . name = " leaves " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " list only checkpoints without children " )
} ,
{ . name = " no-leaves " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " list only checkpoints that are not leaves (with children) " )
} ,
{ . name = " tree " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " list checkpoints in a tree " )
} ,
{ . name = " from " ,
. type = VSH_OT_STRING ,
. help = N_ ( " limit list to children of given checkpoint " ) ,
. completer = virshCheckpointNameCompleter ,
} ,
{ . name = " descendants " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " with --from, list all descendants " )
} ,
{ . name = " name " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " list checkpoint names only " )
} ,
{ . name = " topological " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " sort list topologically rather than by name " ) ,
} ,
{ . name = NULL }
} ;
static bool
cmdCheckpointList ( vshControl * ctl ,
const vshCmd * cmd )
{
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomain ) dom = NULL ;
2019-03-14 00:04:51 +03:00
bool ret = false ;
unsigned int flags = 0 ;
size_t i ;
virDomainCheckpointPtr checkpoint = NULL ;
long long creation_longlong ;
2020-01-09 17:07:15 +03:00
g_autoptr ( GDateTime ) then = NULL ;
2019-03-14 00:04:51 +03:00
bool tree = vshCommandOptBool ( cmd , " tree " ) ;
bool name = vshCommandOptBool ( cmd , " name " ) ;
bool from = vshCommandOptBool ( cmd , " from " ) ;
bool parent = vshCommandOptBool ( cmd , " parent " ) ;
bool roots = vshCommandOptBool ( cmd , " roots " ) ;
const char * from_chk = NULL ;
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomainCheckpoint ) start = NULL ;
2021-03-11 10:16:13 +03:00
struct virshCheckpointList * checkpointlist = NULL ;
2021-08-10 18:07:38 +03:00
g_autoptr ( vshTable ) table = NULL ;
2019-03-14 00:04:51 +03: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 ) ;
# define FILTER(option, flag) \
do { \
if ( vshCommandOptBool ( cmd , option ) ) { \
if ( tree ) { \
vshError ( ctl , \
2023-03-09 17:54:42 +03:00
_ ( " --%1$s and --tree are mutually exclusive " ) , \
2019-03-14 00:04:51 +03:00
option ) ; \
return false ; \
} \
flags | = VIR_DOMAIN_CHECKPOINT_LIST_ # # flag ; \
} \
} while ( 0 )
FILTER ( " leaves " , LEAVES ) ;
FILTER ( " no-leaves " , NO_LEAVES ) ;
# undef FILTER
if ( vshCommandOptBool ( cmd , " topological " ) )
flags | = VIR_DOMAIN_CHECKPOINT_LIST_TOPOLOGICAL ;
if ( roots )
flags | = VIR_DOMAIN_CHECKPOINT_LIST_ROOTS ;
if ( vshCommandOptBool ( cmd , " descendants " ) ) {
if ( ! from ) {
vshError ( ctl , " %s " ,
_ ( " --descendants requires --from " ) ) ;
return false ;
}
flags | = VIR_DOMAIN_CHECKPOINT_LIST_DESCENDANTS ;
}
if ( ! ( dom = virshCommandOptDomain ( ctl , cmd , NULL ) ) )
return false ;
if ( from & &
virshLookupCheckpoint ( ctl , cmd , " from " , dom , & start , & from_chk ) < 0 )
goto cleanup ;
if ( ! ( checkpointlist = virshCheckpointListCollect ( ctl , dom , start , flags ,
tree ) ) )
goto cleanup ;
if ( ! tree & & ! name ) {
if ( parent )
table = vshTableNew ( _ ( " Name " ) , _ ( " Creation Time " ) , _ ( " Parent " ) ,
NULL ) ;
else
table = vshTableNew ( _ ( " Name " ) , _ ( " Creation Time " ) , NULL ) ;
if ( ! table )
goto cleanup ;
}
if ( tree ) {
for ( i = 0 ; i < checkpointlist - > nchks ; i + + ) {
if ( ! checkpointlist - > chks [ i ] . parent & &
vshTreePrint ( ctl , virshCheckpointListLookup , checkpointlist ,
checkpointlist - > nchks , i ) < 0 )
goto cleanup ;
}
ret = true ;
goto cleanup ;
}
for ( i = 0 ; i < checkpointlist - > nchks ; i + + ) {
2021-04-19 14:54:13 +03:00
g_autofree gchar * thenstr = NULL ;
2021-08-10 20:26:45 +03:00
g_autoptr ( xmlDoc ) xml = NULL ;
g_autoptr ( xmlXPathContext ) ctxt = NULL ;
g_autofree char * parent_chk = NULL ;
g_autofree char * doc = NULL ;
2019-03-14 00:04:51 +03:00
const char * chk_name ;
checkpoint = checkpointlist - > chks [ i ] . chk ;
chk_name = virDomainCheckpointGetName ( checkpoint ) ;
assert ( chk_name ) ;
if ( name ) {
/* just print the checkpoint name */
vshPrint ( ctl , " %s \n " , chk_name ) ;
continue ;
}
if ( ! ( doc = virDomainCheckpointGetXMLDesc ( checkpoint , 0 ) ) )
continue ;
if ( ! ( xml = virXMLParseStringCtxt ( doc , _ ( " (domain_checkpoint) " ) , & ctxt ) ) )
continue ;
if ( parent )
parent_chk = virXPathString ( " string(/domaincheckpoint/parent/name) " ,
ctxt ) ;
if ( virXPathLongLong ( " string(/domaincheckpoint/creationTime) " , ctxt ,
& creation_longlong ) < 0 )
continue ;
2020-01-09 17:07:15 +03:00
then = g_date_time_new_from_unix_local ( creation_longlong ) ;
thenstr = g_date_time_format ( then , " %Y-%m-%d %H:%M:%S %z " ) ;
2019-03-14 00:04:51 +03:00
if ( parent ) {
2020-01-09 17:07:15 +03:00
if ( vshTableRowAppend ( table , chk_name , thenstr ,
2019-03-14 00:04:51 +03:00
NULLSTR_EMPTY ( parent_chk ) , NULL ) < 0 )
goto cleanup ;
} else {
2020-01-09 17:07:15 +03:00
if ( vshTableRowAppend ( table , chk_name , thenstr , NULL ) < 0 )
2019-03-14 00:04:51 +03:00
goto cleanup ;
}
}
if ( table )
vshTablePrintToStdout ( table , ctl ) ;
ret = true ;
cleanup :
virshCheckpointListFree ( checkpointlist ) ;
return ret ;
}
/*
* " checkpoint-dumpxml " command
*/
static const vshCmdInfo info_checkpoint_dumpxml [ ] = {
{ . name = " help " ,
. data = N_ ( " Dump XML for a domain checkpoint " )
} ,
{ . name = " desc " ,
. data = N_ ( " Checkpoint Dump XML " )
} ,
{ . name = NULL }
} ;
static const vshCmdOptDef opts_checkpoint_dumpxml [ ] = {
2020-09-11 10:13:05 +03:00
VIRSH_COMMON_OPT_DOMAIN_FULL ( VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT ) ,
2019-03-14 00:04:51 +03:00
{ . name = " checkpointname " ,
. type = VSH_OT_STRING ,
. help = N_ ( " checkpoint name " ) ,
. completer = virshCheckpointNameCompleter ,
} ,
{ . name = " security-info " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " include security sensitive information in XML dump " )
} ,
{ . name = " no-domain " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " exclude <domain> from XML " )
} ,
{ . name = " size " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " include backup size estimate in XML dump " )
} ,
2022-06-16 18:29:54 +03:00
{ . name = " xpath " ,
. type = VSH_OT_STRING ,
virsh: Require --xpath for *dumpxml
Historically, the dumpxml command reject any unknown arguments,
for instance:
virsh dumpxml fedora xxx
However, after v8.5.0-rc1~31 the second argument ('xxx') is
treated as an XPath, but it's not that clearly visible.
Therefore, require the --xpath switch, like this:
virsh dumpxml fedora --xpath xxx
Yes, this breaks already released virsh, but I think we can argue
that the pool of users of this particular function is very small.
We also document the argument being mandatory:
dumpxml [--inactive] [--security-info] [--update-cpu] [--migratable]
[--xpath EXPRESSION] [--wrap] domain
The sooner we do this change, the better.
The same applies for other *dumpxml functions (net-dumpxml,
pool-dumpxml, vol-dumpxl to name a few).
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=2103524
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
2022-07-08 13:45:42 +03:00
. flags = VSH_OFLAG_REQ_OPT ,
2022-06-16 18:29:54 +03:00
. completer = virshCompleteEmpty ,
. help = N_ ( " xpath expression to filter the XML document " )
} ,
{ . name = " wrap " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " wrap xpath results in an common root element " ) ,
} ,
2019-03-14 00:04:51 +03:00
{ . name = NULL }
} ;
static bool
cmdCheckpointDumpXML ( vshControl * ctl ,
const vshCmd * cmd )
{
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomain ) dom = NULL ;
2019-03-14 00:04:51 +03:00
const char * name = NULL ;
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomainCheckpoint ) checkpoint = NULL ;
g_autofree char * xml = NULL ;
2019-03-14 00:04:51 +03:00
unsigned int flags = 0 ;
2022-06-16 18:29:54 +03:00
bool wrap = vshCommandOptBool ( cmd , " wrap " ) ;
const char * xpath = NULL ;
2019-03-14 00:04:51 +03:00
if ( vshCommandOptBool ( cmd , " security-info " ) )
flags | = VIR_DOMAIN_CHECKPOINT_XML_SECURE ;
if ( vshCommandOptBool ( cmd , " no-domain " ) )
flags | = VIR_DOMAIN_CHECKPOINT_XML_NO_DOMAIN ;
if ( vshCommandOptBool ( cmd , " size " ) )
flags | = VIR_DOMAIN_CHECKPOINT_XML_SIZE ;
if ( ! ( dom = virshCommandOptDomain ( ctl , cmd , NULL ) ) )
return false ;
2022-06-16 18:29:54 +03:00
if ( vshCommandOptStringQuiet ( ctl , cmd , " xpath " , & xpath ) < 0 )
return false ;
2019-03-14 00:04:51 +03:00
if ( virshLookupCheckpoint ( ctl , cmd , " checkpointname " , dom ,
& checkpoint , & name ) < 0 )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
if ( ! ( xml = virDomainCheckpointGetXMLDesc ( checkpoint , flags ) ) )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
2022-06-16 18:29:54 +03:00
return virshDumpXML ( ctl , xml , " domain-checkpoint " , xpath , wrap ) ;
2019-03-14 00:04:51 +03:00
}
/*
* " checkpoint-parent " command
*/
static const vshCmdInfo info_checkpoint_parent [ ] = {
{ . name = " help " ,
. data = N_ ( " Get the name of the parent of a checkpoint " )
} ,
{ . name = " desc " ,
. data = N_ ( " Extract the checkpoint's parent, if any " )
} ,
{ . name = NULL }
} ;
static const vshCmdOptDef opts_checkpoint_parent [ ] = {
2020-09-11 10:13:05 +03:00
VIRSH_COMMON_OPT_DOMAIN_FULL ( VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT ) ,
2019-03-14 00:04:51 +03:00
{ . name = " checkpointname " ,
. type = VSH_OT_STRING ,
. help = N_ ( " find parent of checkpoint name " ) ,
. completer = virshCheckpointNameCompleter ,
} ,
{ . name = NULL }
} ;
static bool
cmdCheckpointParent ( vshControl * ctl ,
const vshCmd * cmd )
{
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomain ) dom = NULL ;
2019-03-14 00:04:51 +03:00
const char * name = NULL ;
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomainCheckpoint ) checkpoint = NULL ;
g_autofree char * parent = NULL ;
2019-03-14 00:04:51 +03:00
dom = virshCommandOptDomain ( ctl , cmd , NULL ) ;
if ( dom = = NULL )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
if ( virshLookupCheckpoint ( ctl , cmd , " checkpointname " , dom ,
& checkpoint , & name ) < 0 )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
if ( virshGetCheckpointParent ( ctl , checkpoint , & parent ) < 0 )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
if ( ! parent ) {
2023-03-09 17:54:42 +03:00
vshError ( ctl , _ ( " checkpoint '%1$s' has no parent " ) , name ) ;
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
}
vshPrint ( ctl , " %s " , parent ) ;
2021-08-10 18:47:59 +03:00
return true ;
2019-03-14 00:04:51 +03:00
}
/*
* " checkpoint-delete " command
*/
static const vshCmdInfo info_checkpoint_delete [ ] = {
{ . name = " help " ,
. data = N_ ( " Delete a domain checkpoint " )
} ,
{ . name = " desc " ,
. data = N_ ( " Checkpoint Delete " )
} ,
{ . name = NULL }
} ;
static const vshCmdOptDef opts_checkpoint_delete [ ] = {
2020-09-11 10:13:18 +03:00
VIRSH_COMMON_OPT_DOMAIN_FULL ( VIR_CONNECT_LIST_DOMAINS_HAS_CHECKPOINT |
VIR_CONNECT_LIST_DOMAINS_ACTIVE ) ,
2019-03-14 00:04:51 +03:00
{ . name = " checkpointname " ,
. type = VSH_OT_STRING ,
. help = N_ ( " checkpoint name " ) ,
. completer = virshCheckpointNameCompleter ,
} ,
{ . name = " children " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " delete checkpoint and all children " )
} ,
{ . name = " children-only " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " delete children but not checkpoint " )
} ,
{ . name = " metadata " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " delete only libvirt metadata, leaving checkpoint contents behind " )
} ,
{ . name = NULL }
} ;
static bool
cmdCheckpointDelete ( vshControl * ctl ,
const vshCmd * cmd )
{
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomain ) dom = NULL ;
2019-03-14 00:04:51 +03:00
const char * name = NULL ;
2021-08-10 18:07:38 +03:00
g_autoptr ( virshDomainCheckpoint ) checkpoint = NULL ;
2019-03-14 00:04:51 +03:00
unsigned int flags = 0 ;
dom = virshCommandOptDomain ( ctl , cmd , NULL ) ;
if ( dom = = NULL )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
if ( virshLookupCheckpoint ( ctl , cmd , " checkpointname " , dom ,
& checkpoint , & name ) < 0 )
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
if ( vshCommandOptBool ( cmd , " children " ) )
flags | = VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN ;
if ( vshCommandOptBool ( cmd , " children-only " ) )
flags | = VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY ;
if ( vshCommandOptBool ( cmd , " metadata " ) )
flags | = VIR_DOMAIN_CHECKPOINT_DELETE_METADATA_ONLY ;
if ( virDomainCheckpointDelete ( checkpoint , flags ) = = 0 ) {
if ( flags & VIR_DOMAIN_CHECKPOINT_DELETE_CHILDREN_ONLY )
2023-03-09 17:54:42 +03:00
vshPrintExtra ( ctl , _ ( " Domain checkpoint %1$s children deleted \n " ) , name ) ;
2019-03-14 00:04:51 +03:00
else
2023-03-09 17:54:42 +03:00
vshPrintExtra ( ctl , _ ( " Domain checkpoint %1$s deleted \n " ) , name ) ;
2019-03-14 00:04:51 +03:00
} else {
2023-03-09 17:54:42 +03:00
vshError ( ctl , _ ( " Failed to delete checkpoint %1$s " ) , name ) ;
2021-08-10 18:47:59 +03:00
return false ;
2019-03-14 00:04:51 +03:00
}
2021-08-10 18:47:59 +03:00
return true ;
2019-03-14 00:04:51 +03:00
}
const vshCmdDef checkpointCmds [ ] = {
{ . name = " checkpoint-create " ,
. handler = cmdCheckpointCreate ,
. opts = opts_checkpoint_create ,
. info = info_checkpoint_create ,
. flags = 0
} ,
{ . name = " checkpoint-create-as " ,
. handler = cmdCheckpointCreateAs ,
. opts = opts_checkpoint_create_as ,
. info = info_checkpoint_create_as ,
. flags = 0
} ,
{ . name = " checkpoint-delete " ,
. handler = cmdCheckpointDelete ,
. opts = opts_checkpoint_delete ,
. info = info_checkpoint_delete ,
. flags = 0
} ,
{ . name = " checkpoint-dumpxml " ,
. handler = cmdCheckpointDumpXML ,
. opts = opts_checkpoint_dumpxml ,
. info = info_checkpoint_dumpxml ,
. flags = 0
} ,
{ . name = " checkpoint-edit " ,
. handler = cmdCheckpointEdit ,
. opts = opts_checkpoint_edit ,
. info = info_checkpoint_edit ,
. flags = 0
} ,
{ . name = " checkpoint-info " ,
. handler = cmdCheckpointInfo ,
. opts = opts_checkpoint_info ,
. info = info_checkpoint_info ,
. flags = 0
} ,
{ . name = " checkpoint-list " ,
. handler = cmdCheckpointList ,
. opts = opts_checkpoint_list ,
. info = info_checkpoint_list ,
. flags = 0
} ,
{ . name = " checkpoint-parent " ,
. handler = cmdCheckpointParent ,
. opts = opts_checkpoint_parent ,
. info = info_checkpoint_parent ,
. flags = 0
} ,
{ . name = NULL }
} ;