2015-06-15 19:53:58 +03:00
/*
* vsh . c : common data to be used by clients to exercise the libvirt API
*
2019-03-22 22:02:39 +03:00
* Copyright ( C ) 2005 - 2019 Red Hat , Inc .
2015-06-15 19:53:58 +03:00
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library . If not , see
* < http : //www.gnu.org/licenses/>.
*/
# include <config.h>
# include "vsh.h"
# include <assert.h>
# include <stdarg.h>
# include <unistd.h>
# include <fcntl.h>
# include <sys/stat.h>
# include <signal.h>
# if WITH_READLINE
2020-09-02 17:37:04 +03:00
/* In order to have proper rl_message declaration with older
* versions of readline , we have to declare this . See 9 ea3424a178
* for more info . */
# define HAVE_STDARG_H
2015-06-15 19:53:58 +03:00
# include <readline / readline.h>
# include <readline / history.h>
# endif
# include "internal.h"
# include "virbuffer.h"
# include "viralloc.h"
# include "virfile.h"
# include "virthread.h"
# include "vircommand.h"
# include "virstring.h"
2020-02-17 00:59:28 +03:00
# include "virutil.h"
2015-06-15 19:53:58 +03:00
2016-09-06 15:04:37 +03:00
# ifdef WITH_READLINE
/* For autocompletion */
2017-11-07 12:41:00 +03:00
vshControl * autoCompleteOpaque ;
2016-09-06 15:04:37 +03:00
# endif
2015-06-15 19:53:58 +03:00
/* NOTE: It would be much nicer to have these two as part of vshControl
* structure , unfortunately readline doesn ' t support passing opaque data
* and only relies on static data accessible from the user - side callback
*/
const vshCmdGrp * cmdGroups ;
2016-02-12 16:00:28 +03:00
2015-06-15 19:53:58 +03:00
double
vshPrettyCapacity ( unsigned long long val , const char * * unit )
{
double limit = 1024 ;
if ( val < limit ) {
* unit = " B " ;
return val ;
}
limit * = 1024 ;
if ( val < limit ) {
* unit = " KiB " ;
return val / ( limit / 1024 ) ;
}
limit * = 1024 ;
if ( val < limit ) {
* unit = " MiB " ;
return val / ( limit / 1024 ) ;
}
limit * = 1024 ;
if ( val < limit ) {
* unit = " GiB " ;
return val / ( limit / 1024 ) ;
}
limit * = 1024 ;
if ( val < limit ) {
* unit = " TiB " ;
return val / ( limit / 1024 ) ;
}
limit * = 1024 ;
if ( val < limit ) {
* unit = " PiB " ;
return val / ( limit / 1024 ) ;
}
limit * = 1024 ;
* unit = " EiB " ;
return val / ( limit / 1024 ) ;
}
int
vshNameSorter ( const void * a , const void * b )
{
const char * * sa = ( const char * * ) a ;
const char * * sb = ( const char * * ) b ;
return vshStrcasecmp ( * sa , * sb ) ;
}
/*
* Convert the strings separated by ' , ' into array . The returned
* array is a NULL terminated string list . The caller has to free
2020-08-03 01:01:11 +03:00
* the array using g_strfreev or a similar method .
2015-06-15 19:53:58 +03:00
*
* Returns the length of the filled array on success , or - 1
* on error .
*/
int
vshStringToArray ( const char * str ,
char * * * array )
{
2021-08-11 16:04:14 +03:00
g_auto ( GStrv ) tmp = NULL ;
GStrv n ;
size_t ntoks = 0 ;
bool concat = false ;
tmp = g_strsplit ( str , " , " , 0 ) ;
* array = g_new0 ( char * , g_strv_length ( tmp ) + 1 ) ;
( * array ) [ ntoks + + ] = g_strdup ( tmp [ 0 ] ) ;
/* undo splitting of comma escape (',,') by concatenating back on empty strings */
for ( n = tmp + 1 ; n [ 0 ] ; n + + ) {
if ( concat ) {
g_autofree char * old = ( * array ) [ ntoks - 1 ] ;
2015-06-15 19:53:58 +03:00
2021-08-11 16:04:14 +03:00
( * array ) [ ntoks - 1 ] = g_strconcat ( old , " , " , n [ 0 ] , NULL ) ;
concat = false ;
2015-06-15 19:53:58 +03:00
continue ;
}
2021-08-11 16:04:14 +03:00
if ( strlen ( n [ 0 ] ) = = 0 ) {
concat = true ;
} else {
( * array ) [ ntoks + + ] = g_strdup ( n [ 0 ] ) ;
}
2015-06-15 19:53:58 +03:00
}
2021-08-11 16:04:14 +03:00
/* corner case of ending with a single comma */
if ( concat )
( * array ) [ ntoks + + ] = g_strdup ( " " ) ;
return ntoks ;
2015-06-15 19:53:58 +03:00
}
virErrorPtr last_error ;
/*
* Quieten libvirt until we ' re done with the command .
*/
void
2019-10-14 15:44:29 +03:00
vshErrorHandler ( void * opaque G_GNUC_UNUSED ,
virErrorPtr error G_GNUC_UNUSED )
2015-06-15 19:53:58 +03:00
{
virFreeError ( last_error ) ;
last_error = virSaveLastError ( ) ;
}
/* Store a libvirt error that is from a helper API that doesn't raise errors
* so it doesn ' t get overwritten */
void
vshSaveLibvirtError ( void )
{
virFreeError ( last_error ) ;
last_error = virSaveLastError ( ) ;
}
2017-04-11 18:23:23 +03:00
/* Store libvirt error from helper API but don't overwrite existing errors */
void
vshSaveLibvirtHelperError ( void )
{
if ( last_error )
return ;
2018-05-05 15:04:21 +03:00
if ( virGetLastErrorCode ( ) = = VIR_ERR_OK )
2017-04-11 18:23:23 +03:00
return ;
vshSaveLibvirtError ( ) ;
}
2015-06-15 19:53:58 +03:00
/*
* Reset libvirt error on graceful fallback paths
*/
void
vshResetLibvirtError ( void )
{
2022-01-28 20:42:45 +03:00
g_clear_pointer ( & last_error , virFreeError ) ;
2018-10-11 19:29:58 +03:00
virResetLastError ( ) ;
2015-06-15 19:53:58 +03:00
}
/*
* Report an error when a command finishes . This is better than before
* ( when correct operation would report errors ) , but it has some
* problems : we lose the smarter formatting of virDefaultErrorFunc ( ) ,
* and it can become harder to debug problems , if errors get reported
* twice during one command . This case shouldn ' t really happen anyway ,
* and it ' s IMHO a bug that libvirt does that sometimes .
*/
void
vshReportError ( vshControl * ctl )
{
if ( last_error = = NULL ) {
/* Calling directly into libvirt util functions won't trigger the
* error callback ( which sets last_error ) , so check it ourselves .
*
* If the returned error has CODE_OK , this most likely means that
* no error was ever raised , so just ignore */
last_error = virSaveLastError ( ) ;
if ( ! last_error | | last_error - > code = = VIR_ERR_OK )
goto out ;
}
if ( last_error - > code = = VIR_ERR_OK ) {
vshError ( ctl , " %s " , _ ( " unknown error " ) ) ;
goto out ;
}
vshError ( ctl , " %s " , last_error - > message ) ;
out :
vshResetLibvirtError ( ) ;
}
/*
* Detection of disconnections and automatic reconnection support
*/
static int disconnected ; /* we may have been disconnected */
2024-04-15 15:09:04 +03:00
/* vshCmddefSearch:
* @ cmdname : name of command to find
*
* Looks for @ cmdname in the global list of command definitions @ cmdGroups and
* returns pointer to the definition struct if the command exists .
*/
static const vshCmdDef *
vshCmddefSearch ( const char * cmdname )
{
const vshCmdGrp * g ;
const vshCmdDef * c ;
for ( g = cmdGroups ; g - > name ; g + + ) {
for ( c = g - > commands ; c - > name ; c + + ) {
if ( STREQ ( c - > name , cmdname ) )
return c ;
}
}
return NULL ;
}
2024-03-01 18:39:39 +03:00
/* Check if the internal command definitions are correct.
* None of the errors are to be marked as translatable . */
2015-06-15 19:53:58 +03:00
static int
2019-03-12 05:17:33 +03:00
vshCmddefCheckInternals ( vshControl * ctl ,
2021-03-04 17:47:28 +03:00
const vshCmdDef * cmd ,
2024-03-06 00:37:43 +03:00
bool missingCompleters )
2015-06-15 19:53:58 +03:00
{
size_t i ;
2020-11-12 15:30:29 +03:00
bool seenOptionalOption = false ;
2024-03-14 18:21:58 +03:00
const char * seenOptionalPositionalOption = NULL ;
2021-03-04 17:47:28 +03:00
g_auto ( virBuffer ) complbuf = VIR_BUFFER_INITIALIZER ;
2016-09-19 14:10:38 +03:00
2016-09-07 18:30:48 +03:00
/* in order to perform the validation resolve the alias first */
2024-03-02 01:14:51 +03:00
if ( cmd - > alias ) {
2021-09-16 16:44:25 +03:00
const vshCmdDef * alias ;
if ( ! ( alias = vshCmddefSearch ( cmd - > alias ) ) ) {
2024-03-01 18:39:39 +03:00
vshError ( ctl , " command alias '%s' is pointing to a non-existent command '%s' " ,
2021-09-16 16:44:25 +03:00
cmd - > name , cmd - > alias ) ;
return - 1 ;
}
2024-03-02 01:14:51 +03:00
if ( alias - > alias ) {
2024-03-01 18:39:39 +03:00
vshError ( ctl , " command alias '%s' is pointing to another command alias '%s' " ,
2021-09-16 16:44:25 +03:00
cmd - > name , cmd - > alias ) ;
return - 1 ;
}
2021-09-17 10:52:43 +03:00
if ( cmd - > handler ) {
2024-03-01 18:39:39 +03:00
vshError ( ctl , " command '%s' has handler set " , cmd - > name ) ;
2021-09-17 10:52:43 +03:00
return - 1 ;
}
if ( cmd - > opts ) {
2024-03-01 18:39:39 +03:00
vshError ( ctl , " command '%s' has options set " , cmd - > name ) ;
2021-09-17 10:52:43 +03:00
return - 1 ;
}
if ( cmd - > info ) {
2024-03-01 18:39:39 +03:00
vshError ( ctl , " command '%s' has info set " , cmd - > name ) ;
2021-09-17 10:52:43 +03:00
return - 1 ;
}
2024-03-02 01:14:51 +03:00
if ( cmd - > flags ! = 0 ) {
2024-03-01 18:39:39 +03:00
vshError ( ctl , " command '%s' has multiple flags set " , cmd - > name ) ;
2021-09-17 10:52:43 +03:00
return - 1 ;
}
2021-09-16 16:44:25 +03:00
/* we don't need to continue as the real command will be checked separately */
return 0 ;
2016-09-07 18:30:48 +03:00
}
2016-09-19 14:10:38 +03:00
/* Each command has to provide a non-empty help string. */
2024-03-04 15:21:50 +03:00
if ( ! cmd - > info | | ! cmd - > info - > help | | ! * cmd - > info - > help ) {
2024-03-01 18:39:39 +03:00
vshError ( ctl , " command '%s' lacks help " , cmd - > name ) ;
2016-09-19 14:10:38 +03:00
return - 1 ;
2019-03-12 05:17:33 +03:00
}
2015-06-15 19:53:58 +03:00
if ( ! cmd - > opts )
return 0 ;
for ( i = 0 ; cmd - > opts [ i ] . name ; i + + ) {
const vshCmdOptDef * opt = & cmd - > opts [ i ] ;
2024-03-01 14:35:46 +03:00
if ( missingCompleters & & ! opt - > completer ) {
switch ( opt - > type ) {
case VSH_OT_STRING :
case VSH_OT_ARGV :
virBufferStrcat ( & complbuf , opt - > name , " , " , NULL ) ;
break ;
case VSH_OT_BOOL :
/* only name is completed */
case VSH_OT_INT :
/* no point in completing numbers */
case VSH_OT_ALIAS :
/* alias is handled in the referenced command */
case VSH_OT_NONE :
break ;
}
}
2021-03-04 17:47:28 +03:00
2024-03-14 18:21:58 +03:00
/* allow at most one optional positional option */
if ( opt - > positional & & ! opt - > required ) {
if ( seenOptionalPositionalOption ) {
vshError ( ctl , " multiple optional positional arguments (%s, %s) of command '%s' are not allowed " ,
seenOptionalPositionalOption , opt - > name , cmd - > name ) ;
return - 1 ;
}
seenOptionalPositionalOption = opt - > name ;
}
/* all optional positional arguments must be defined after the required ones */
if ( seenOptionalPositionalOption & & opt - > positional & & opt - > required ) {
vshError ( ctl , " required positional argument '%s' declared after an optional positional argument '%s' of command '%s' " ,
opt - > name , seenOptionalPositionalOption , cmd - > name ) ;
2024-03-05 18:17:21 +03:00
return - 1 ;
}
2024-03-14 12:31:17 +03:00
/* Mandate no completer flags if no completer is specified */
if ( opt - > completer_flags ! = 0 & & ! opt - > completer ) {
vshError ( ctl , " completer_flags of argument '%s' of command '%s' must be 0 if no completer is used " ,
opt - > name , cmd - > name ) ;
return - 1 ;
}
2024-03-14 00:31:20 +03:00
if ( opt - > unwanted_positional & & opt - > positional ) {
vshError ( ctl , " unwanted_positional flag of argument '%s' of command '%s' must not be used together with positional " ,
opt - > name , cmd - > name ) ;
return - 1 ;
}
2017-11-16 15:38:27 +03:00
switch ( opt - > type ) {
2024-03-04 17:40:40 +03:00
case VSH_OT_NONE :
vshError ( ctl , " invalid type 'NONE' of option '%s' of command '%s' " ,
opt - > name , cmd - > name ) ;
return - 1 ;
2017-11-16 15:38:27 +03:00
case VSH_OT_BOOL :
2024-03-14 12:31:17 +03:00
if ( opt - > completer ) {
2024-03-01 18:39:39 +03:00
vshError ( ctl , " bool parameter '%s' of command '%s' has completer set " ,
2021-09-17 10:58:03 +03:00
opt - > name , cmd - > name ) ;
return - 1 ;
}
2024-03-14 00:31:20 +03:00
if ( opt - > positional | | opt - > unwanted_positional ) {
2024-03-14 12:31:17 +03:00
vshError ( ctl , " boolean parameter '%s' of command '%s' must not be positional " ,
opt - > name , cmd - > name ) ;
return - 1 ;
}
2024-03-05 19:21:05 +03:00
if ( opt - > required ) {
vshError ( ctl , " parameter '%s' of command '%s' misused 'required' flag " ,
2020-11-12 14:04:11 +03:00
opt - > name , cmd - > name ) ;
2024-03-05 18:07:36 +03:00
return - 1 ; /* bool can't be mandatory */
2019-03-12 05:17:33 +03:00
}
2020-11-12 15:30:29 +03:00
2017-11-16 15:38:27 +03:00
break ;
case VSH_OT_ALIAS : {
2015-06-15 19:53:58 +03:00
size_t j ;
2021-08-10 19:56:18 +03:00
g_autofree char * name = NULL ;
2015-06-15 19:53:58 +03:00
char * p ;
2024-03-14 12:31:17 +03:00
if ( opt - > required | |
opt - > positional | |
2024-03-14 00:31:20 +03:00
opt - > unwanted_positional | |
2024-03-14 12:31:17 +03:00
opt - > completer | |
! opt - > help ) {
2024-03-01 18:39:39 +03:00
vshError ( ctl , " parameter '%s' of command '%s' has incorrect alias option " ,
2020-11-12 14:04:11 +03:00
opt - > name , cmd - > name ) ;
2024-03-01 18:39:39 +03:00
return - 1 ;
2019-03-12 05:17:33 +03:00
}
2021-08-10 19:56:18 +03:00
if ( ( p = strchr ( opt - > help , ' = ' ) ) )
name = g_strndup ( opt - > help , p - opt - > help ) ;
else
name = g_strdup ( opt - > help ) ;
2015-06-15 19:53:58 +03:00
for ( j = i + 1 ; cmd - > opts [ j ] . name ; j + + ) {
if ( STREQ ( name , cmd - > opts [ j ] . name ) & &
cmd - > opts [ j ] . type ! = VSH_OT_ALIAS )
break ;
}
2021-08-10 19:56:18 +03:00
if ( p ) {
2015-06-15 19:53:58 +03:00
/* If alias comes with value, replacement must not be bool */
2019-03-12 05:17:33 +03:00
if ( cmd - > opts [ j ] . type = = VSH_OT_BOOL ) {
2024-03-01 18:39:39 +03:00
vshError ( ctl , " alias '%s' of command '%s' has mismatched alias type " ,
2020-11-12 14:04:11 +03:00
opt - > name , cmd - > name ) ;
2015-06-15 19:53:58 +03:00
return - 1 ;
2019-03-12 05:17:33 +03:00
}
2015-06-15 19:53:58 +03:00
}
2019-03-12 05:17:33 +03:00
if ( ! cmd - > opts [ j ] . name ) {
2024-03-01 18:39:39 +03:00
vshError ( ctl , " alias '%s' of command '%s' has missing alias option " ,
2020-11-12 14:04:11 +03:00
opt - > name , cmd - > name ) ;
2024-03-01 18:39:39 +03:00
return - 1 ;
2019-03-12 05:17:33 +03:00
}
2015-06-15 19:53:58 +03:00
}
2017-11-16 15:38:27 +03:00
break ;
2024-03-05 17:07:47 +03:00
2017-11-16 15:38:27 +03:00
case VSH_OT_ARGV :
2019-03-12 05:17:33 +03:00
if ( cmd - > opts [ i + 1 ] . name ) {
2024-03-01 18:39:39 +03:00
vshError ( ctl , " parameter '%s' of command '%s' must be listed last " ,
2020-11-12 14:04:11 +03:00
opt - > name , cmd - > name ) ;
2024-03-01 18:39:39 +03:00
return - 1 ;
2019-03-12 05:17:33 +03:00
}
2017-11-16 15:38:27 +03:00
break ;
2024-03-05 18:07:36 +03:00
case VSH_OT_INT :
case VSH_OT_STRING :
if ( opt - > positional & & seenOptionalOption ) {
2024-03-01 18:39:39 +03:00
vshError ( ctl , " parameter '%s' of command '%s' must be listed before optional parameters " ,
2020-11-12 15:30:29 +03:00
opt - > name , cmd - > name ) ;
2024-03-01 18:39:39 +03:00
return - 1 ;
2020-11-12 15:30:29 +03:00
}
2024-03-05 18:07:36 +03:00
seenOptionalOption = ! opt - > required ;
2017-11-16 15:38:27 +03:00
break ;
}
2016-07-08 15:56:02 +03:00
}
2021-03-04 17:47:28 +03:00
virBufferTrim ( & complbuf , " , " ) ;
if ( missingCompleters & & virBufferUse ( & complbuf ) > 0 )
vshPrintExtra ( ctl , " %s: %s \n " , cmd - > name , virBufferCurrentContent ( & complbuf ) ) ;
2016-07-08 15:56:02 +03:00
return 0 ;
}
2024-04-15 17:55:18 +03:00
static vshCmdOpt *
vshCmdGetOption ( vshControl * ctl ,
vshCmd * cmd ,
const char * name ,
char * * optstr ,
bool report )
2015-06-15 19:53:58 +03:00
{
2021-08-10 18:09:07 +03:00
g_autofree char * alias = NULL ;
2024-04-15 17:55:18 +03:00
vshCmdOpt * n ;
2015-06-15 19:53:58 +03:00
2024-04-15 17:55:18 +03:00
for ( n = cmd - > opts ; n & & n - > def ; n + + ) {
if ( STRNEQ ( n - > def - > name , name ) )
2024-03-05 14:35:51 +03:00
continue ;
2024-04-15 17:55:18 +03:00
if ( n - > def - > type = = VSH_OT_ALIAS ) {
2024-03-05 14:35:51 +03:00
char * value ;
/* Two types of replacements:
opt - > help = " string " : straight replacement of name
opt - > help = " string=value " : treat boolean flag as
alias of option and its default value */
2024-04-15 17:55:18 +03:00
alias = g_strdup ( n - > def - > help ) ;
2024-03-05 14:35:51 +03:00
name = alias ;
if ( ( value = strchr ( name , ' = ' ) ) ) {
* value = ' \0 ' ;
if ( * optstr ) {
if ( report )
vshError ( ctl , _ ( " invalid '=' after option --%1$s " ) ,
2024-04-15 17:55:18 +03:00
n - > def - > name ) ;
2024-03-05 14:35:51 +03:00
return NULL ;
2015-06-15 19:53:58 +03:00
}
2024-03-05 14:35:51 +03:00
* optstr = g_strdup ( value + 1 ) ;
2015-06-15 19:53:58 +03:00
}
2024-03-05 14:35:51 +03:00
continue ;
}
2024-04-15 17:55:18 +03:00
if ( n - > present & & n - > def - > type ! = VSH_OT_ARGV ) {
2024-03-05 14:35:51 +03:00
if ( report )
vshError ( ctl , _ ( " option --%1$s already seen " ) , name ) ;
2024-04-15 17:55:18 +03:00
2024-03-05 14:35:51 +03:00
return NULL ;
2015-06-15 19:53:58 +03:00
}
2024-03-05 14:35:51 +03:00
2024-04-15 17:55:18 +03:00
return n ;
2015-06-15 19:53:58 +03:00
}
2024-03-05 14:35:51 +03:00
/* The 'help' command ignores extra options */
2024-04-15 17:55:18 +03:00
if ( STRNEQ ( cmd - > def - > name , " help " ) & & report ) {
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " command '%1$s' doesn't support option --%2$s " ) ,
2024-04-15 17:55:18 +03:00
cmd - > def - > name , name ) ;
2015-06-15 19:53:58 +03:00
}
2021-08-12 10:59:20 +03:00
return NULL ;
2015-06-15 19:53:58 +03:00
}
2024-04-15 17:55:18 +03:00
static void
2024-04-17 15:06:38 +03:00
vshCmdOptAssign ( vshControl * ctl ,
vshCmd * cmd ,
2024-04-15 17:55:18 +03:00
vshCmdOpt * opt ,
2024-04-17 15:06:38 +03:00
const char * val ,
bool report )
2015-06-15 19:53:58 +03:00
{
2024-04-15 17:55:18 +03:00
cmd - > lastopt = opt ;
2015-06-15 19:53:58 +03:00
2024-04-15 17:55:18 +03:00
opt - > present = true ;
2015-06-15 19:53:58 +03:00
2024-04-15 17:55:18 +03:00
switch ( opt - > def - > type ) {
case VSH_OT_BOOL :
/* nothing to do */
2024-04-17 15:06:38 +03:00
if ( report ) {
vshDebug ( ctl , VSH_ERR_INFO , " %s: %s(bool) \n " ,
cmd - > def - > name , opt - > def - > name ) ;
}
2024-04-15 17:55:18 +03:00
break ;
case VSH_OT_STRING :
case VSH_OT_INT :
2024-04-17 15:06:38 +03:00
if ( report ) {
vshDebug ( ctl , VSH_ERR_INFO , " %s: %s(optdata): %s \n " ,
cmd - > def - > name , opt - > def - > name , NULLSTR ( val ) ) ;
}
2024-04-15 17:55:18 +03:00
opt - > data = g_strdup ( val ) ;
break ;
case VSH_OT_ARGV :
2024-04-17 15:06:38 +03:00
if ( report ) {
vshDebug ( ctl , VSH_ERR_INFO , " %s: %s(argv: %zu): %s \n " ,
cmd - > def - > name , opt - > def - > name , opt - > nargv , NULLSTR ( val ) ) ;
}
2024-04-15 17:55:18 +03:00
VIR_EXPAND_N ( opt - > argv , opt - > nargv , 2 ) ;
/* VIR_EXPAND_N updates count */
opt - > nargv - - ;
opt - > argv [ opt - > nargv - 1 ] = g_strdup ( val ) ;
/* for completers to work properly we need to also remember the last
* field in ' data ' */
g_clear_pointer ( & opt - > data , g_free ) ;
opt - > data = g_strdup ( val ) ;
break ;
case VSH_OT_NONE :
case VSH_OT_ALIAS :
/* impossible code path */
break ;
}
2015-06-15 19:53:58 +03:00
}
2024-04-15 17:55:18 +03:00
/**
* vshCmdGetNextPositionalOpt :
* @ cmd : command structure
*
* Get next unpopulated positional argument definition .
*/
static vshCmdOpt *
vshCmdGetNextPositionalOpt ( const vshCmd * cmd )
{
vshCmdOpt * n ;
for ( n = cmd - > opts ; n & & n - > def ; n + + ) {
/* Consider only "positional" options. Tests ensure that boolean options
* don ' t set these . */
if ( ! ( n - > def - > positional | | n - > def - > unwanted_positional ) )
continue ;
/* 'VSH_OT_ARGV' positionals must allow multiple arguments */
if ( n - > present & &
n - > def - > type ! = VSH_OT_ARGV )
continue ;
return n ;
}
return NULL ;
}
2015-06-15 19:53:58 +03:00
/*
* Checks for required options
*/
static int
2024-04-15 17:55:18 +03:00
vshCommandCheckOpts ( vshControl * ctl ,
const vshCmd * cmd )
2015-06-15 19:53:58 +03:00
{
2024-04-15 17:55:18 +03:00
vshCmdOpt * n ;
2015-06-15 19:53:58 +03:00
2024-04-15 17:55:18 +03:00
for ( n = cmd - > opts ; n & & n - > def ; n + + ) {
if ( ! n - > present & & n - > def - > required ) {
if ( n - > def - > positional ) {
vshError ( ctl ,
_ ( " command '%1$s' requires <%2$s> option " ) ,
cmd - > def - > name , n - > def - > name ) ;
} else {
vshError ( ctl ,
_ ( " command '%1$s' requires --%2$s option " ) ,
cmd - > def - > name , n - > def - > name ) ;
}
2015-06-15 19:53:58 +03:00
2024-04-15 17:55:18 +03:00
return - 1 ;
2015-06-15 19:53:58 +03:00
}
}
2024-04-15 17:55:18 +03:00
return 0 ;
2015-06-15 19:53:58 +03:00
}
2024-04-15 17:55:18 +03:00
2024-04-15 15:09:04 +03:00
static const vshCmdGrp *
2015-06-15 19:53:58 +03:00
vshCmdGrpSearch ( const char * grpname )
{
const vshCmdGrp * g ;
for ( g = cmdGroups ; g - > name ; g + + ) {
if ( STREQ ( g - > name , grpname ) | | STREQ ( g - > keyword , grpname ) )
return g ;
}
return NULL ;
}
2024-04-15 15:09:04 +03:00
static bool
2018-01-25 18:08:46 +03:00
vshCmdGrpHelp ( vshControl * ctl , const vshCmdGrp * grp )
2015-06-15 19:53:58 +03:00
{
const vshCmdDef * cmd = NULL ;
2023-03-09 17:54:58 +03:00
vshPrint ( ctl , _ ( " %1$s (help keyword '%2$s'): \n " ) , grp - > name ,
2018-01-25 18:08:46 +03:00
grp - > keyword ) ;
2015-06-15 19:53:58 +03:00
2018-01-25 18:08:46 +03:00
for ( cmd = grp - > commands ; cmd - > name ; cmd + + ) {
2024-03-02 01:14:51 +03:00
if ( cmd - > alias | |
2021-09-16 16:57:28 +03:00
cmd - > flags & VSH_CMD_FLAG_HIDDEN )
2018-01-25 18:08:46 +03:00
continue ;
2024-03-04 15:21:50 +03:00
vshPrint ( ctl , " %-30s %s \n " , cmd - > name , _ ( cmd - > info - > help ) ) ;
2015-06-15 19:53:58 +03:00
}
return true ;
}
2020-11-12 15:38:23 +03:00
static bool
2020-11-12 15:42:45 +03:00
vshCmddefHelp ( const vshCmdDef * def )
2015-06-15 19:53:58 +03:00
{
2016-09-15 18:52:11 +03:00
fputs ( _ ( " NAME \n " ) , stdout ) ;
2024-03-04 15:21:50 +03:00
fprintf ( stdout , " %s - %s \n " , def - > name , _ ( def - > info - > help ) ) ;
2016-09-15 18:52:11 +03:00
fputs ( _ ( " \n SYNOPSIS \n " ) , stdout ) ;
fprintf ( stdout , " %s " , def - > name ) ;
if ( def - > opts ) {
const vshCmdOptDef * opt ;
for ( opt = def - > opts ; opt - > name ; opt + + ) {
2024-03-01 16:51:46 +03:00
2016-09-15 18:52:11 +03:00
switch ( opt - > type ) {
case VSH_OT_BOOL :
2024-03-01 16:51:46 +03:00
fprintf ( stdout , " [--%s] " , opt - > name ) ;
2016-09-15 18:52:11 +03:00
break ;
2024-03-01 16:51:46 +03:00
2024-03-05 18:53:55 +03:00
case VSH_OT_STRING :
2016-09-15 18:52:11 +03:00
case VSH_OT_INT :
2024-03-05 18:53:55 +03:00
if ( opt - > required ) {
fprintf ( stdout , " " ) ;
2024-03-01 16:51:46 +03:00
} else {
2024-03-05 18:53:55 +03:00
fprintf ( stdout , " [ " ) ;
2024-03-01 16:51:46 +03:00
}
2024-03-05 18:53:55 +03:00
if ( opt - > positional ) {
fprintf ( stdout , " <%s> " , opt - > name ) ;
2024-03-01 16:51:46 +03:00
} else {
2024-03-05 18:53:55 +03:00
if ( opt - > type = = VSH_OT_INT ) {
fprintf ( stdout , _ ( " --%1$s <number> " ) , opt - > name ) ;
} else {
fprintf ( stdout , _ ( " --%1$s <string> " ) , opt - > name ) ;
}
2024-03-01 16:51:46 +03:00
}
2024-03-05 18:53:55 +03:00
if ( ! opt - > required )
fprintf ( stdout , " ] " ) ;
2016-09-15 18:52:11 +03:00
break ;
2024-03-01 16:51:46 +03:00
2016-09-15 18:52:11 +03:00
case VSH_OT_ARGV :
2024-03-05 18:32:40 +03:00
if ( opt - > positional ) {
if ( opt - > required ) {
fprintf ( stdout , " <%s>... " , opt - > name ) ;
2024-03-01 16:51:46 +03:00
} else {
2024-03-05 18:32:40 +03:00
fprintf ( stdout , " [<%s>]... " , opt - > name ) ;
2024-03-01 16:51:46 +03:00
}
2016-09-15 18:52:11 +03:00
} else {
2024-03-05 18:32:40 +03:00
if ( opt - > required ) {
2024-03-13 18:58:52 +03:00
fprintf ( stdout , _ ( " --%1$s <string>... " ) , opt - > name ) ;
2024-03-01 16:51:46 +03:00
} else {
2024-03-13 18:58:52 +03:00
fprintf ( stdout , _ ( " [--%1$s <string>]... " ) , opt - > name ) ;
2024-03-01 16:51:46 +03:00
}
2015-06-15 19:53:58 +03:00
}
2016-09-15 18:52:11 +03:00
break ;
2024-03-01 16:51:46 +03:00
2016-09-15 18:52:11 +03:00
case VSH_OT_ALIAS :
2024-03-04 17:40:40 +03:00
case VSH_OT_NONE :
2016-09-15 18:52:11 +03:00
/* aliases are intentionally undocumented */
continue ;
2015-06-15 19:53:58 +03:00
}
}
2016-09-15 18:52:11 +03:00
}
fputc ( ' \n ' , stdout ) ;
2015-06-15 19:53:58 +03:00
2024-03-04 15:21:50 +03:00
if ( def - > info - > desc & & * def - > info - > desc ) {
2016-09-15 18:52:11 +03:00
/* Print the description only if it's not empty. */
fputs ( _ ( " \n DESCRIPTION \n " ) , stdout ) ;
2024-03-04 15:21:50 +03:00
fprintf ( stdout , " %s \n " , _ ( def - > info - > desc ) ) ;
2016-09-15 18:52:11 +03:00
}
2015-06-15 19:53:58 +03:00
2016-09-15 18:52:11 +03:00
if ( def - > opts & & def - > opts - > name ) {
const vshCmdOptDef * opt ;
fputs ( _ ( " \n OPTIONS \n " ) , stdout ) ;
for ( opt = def - > opts ; opt - > name ; opt + + ) {
2024-03-01 16:51:46 +03:00
g_autofree char * optstr = NULL ;
2016-09-15 18:52:11 +03:00
switch ( opt - > type ) {
case VSH_OT_BOOL :
2024-03-01 16:51:46 +03:00
optstr = g_strdup_printf ( " --%s " , opt - > name ) ;
2016-09-15 18:52:11 +03:00
break ;
2024-03-01 16:51:46 +03:00
2016-09-15 18:52:11 +03:00
case VSH_OT_INT :
2024-03-05 18:53:55 +03:00
if ( opt - > positional ) {
2024-03-01 16:51:46 +03:00
optstr = g_strdup_printf ( _ ( " [--%1$s] <number> " ) , opt - > name ) ;
} else {
optstr = g_strdup_printf ( _ ( " --%1$s <number> " ) , opt - > name ) ;
}
2016-09-15 18:52:11 +03:00
break ;
2024-03-01 16:51:46 +03:00
2016-09-15 18:52:11 +03:00
case VSH_OT_STRING :
2024-03-05 18:53:55 +03:00
if ( opt - > positional ) {
optstr = g_strdup_printf ( _ ( " [--%1$s] <string> " ) , opt - > name ) ;
} else {
optstr = g_strdup_printf ( _ ( " --%1$s <string> " ) , opt - > name ) ;
}
2016-09-15 18:52:11 +03:00
break ;
2024-03-01 16:51:46 +03:00
2016-09-15 18:52:11 +03:00
case VSH_OT_ARGV :
2024-03-05 18:32:40 +03:00
if ( opt - > positional ) {
2024-03-13 18:58:52 +03:00
optstr = g_strdup_printf ( _ ( " [--%1$s] <string>... " ) , opt - > name ) ;
2024-03-05 18:32:40 +03:00
} else {
2024-03-13 18:58:52 +03:00
optstr = g_strdup_printf ( _ ( " --%1$s <string>... " ) , opt - > name ) ;
2024-03-01 16:51:46 +03:00
}
2016-09-15 18:52:11 +03:00
break ;
2024-03-01 16:51:46 +03:00
2016-09-15 18:52:11 +03:00
case VSH_OT_ALIAS :
2024-03-04 17:40:40 +03:00
case VSH_OT_NONE :
2016-09-15 18:52:11 +03:00
continue ;
2015-06-15 19:53:58 +03:00
}
2016-09-15 18:52:11 +03:00
2024-03-01 16:51:46 +03:00
fprintf ( stdout , " %-15s %s \n " , optstr , _ ( opt - > help ) ) ;
2015-06-15 19:53:58 +03:00
}
}
2016-09-15 18:52:11 +03:00
2015-06-15 19:53:58 +03:00
return true ;
}
/* ---------------
* Utils for work with runtime commands data
* - - - - - - - - - - - - - - -
*/
static void
vshCommandFree ( vshCmd * cmd )
{
vshCmd * c = cmd ;
while ( c ) {
vshCmd * tmp = c ;
2024-04-15 17:55:18 +03:00
vshCmdOpt * n ;
2015-06-15 19:53:58 +03:00
c = c - > next ;
2024-04-15 17:55:18 +03:00
for ( n = tmp - > opts ; n & & n - > def ; n + + ) {
g_free ( n - > data ) ;
g_strfreev ( n - > argv ) ;
g_free ( n - > argvstr ) ;
}
g_free ( tmp - > opts ) ;
2021-02-03 22:32:55 +03:00
g_free ( tmp ) ;
2015-06-15 19:53:58 +03:00
}
}
2021-01-26 12:23:32 +03:00
G_DEFINE_AUTOPTR_CLEANUP_FUNC ( vshCmd , vshCommandFree ) ;
2015-06-15 19:53:58 +03:00
/**
* vshCommandOpt :
* @ cmd : parsed command line to search
* @ name : option name to search for
* @ opt : result of the search
* @ needData : true if option must be non - boolean
*
* Look up an option passed to CMD by NAME . Returns 1 with * OPT set
* to the option if found , 0 with * OPT set to NULL if the name is
* valid and the option is not required , - 1 with * OPT set to NULL if
* the option is required but not present , and assert if NAME is not
2017-11-09 20:06:11 +03:00
* valid ( which indicates a programming error ) unless cmd - > skipChecks
* is set . No error messages are issued if a value is returned .
2015-06-15 19:53:58 +03:00
*/
static int
2024-04-15 17:55:18 +03:00
vshCommandOpt ( const vshCmd * cmd ,
const char * name ,
vshCmdOpt * * opt ,
2015-06-15 19:53:58 +03:00
bool needData )
{
2024-04-15 17:55:18 +03:00
vshCmdOpt * n ;
2015-06-15 19:53:58 +03:00
* opt = NULL ;
2017-11-09 20:06:11 +03:00
2024-04-15 17:55:18 +03:00
for ( n = cmd - > opts ; n & & n - > def ; n + + ) {
if ( STRNEQ ( name , n - > def - > name ) )
continue ;
2017-11-09 20:06:11 +03:00
2024-04-15 17:55:18 +03:00
if ( ! cmd - > skipChecks )
assert ( ! needData | | n - > def - > type ! = VSH_OT_BOOL ) ;
2015-06-15 19:53:58 +03:00
2024-04-15 17:55:18 +03:00
if ( n - > present ) {
* opt = n ;
return 1 ;
} else {
return 0 ;
2015-06-15 19:53:58 +03:00
}
}
2024-04-15 17:55:18 +03:00
if ( ! cmd - > skipChecks )
assert ( false ) ;
return - 1 ;
2015-06-15 19:53:58 +03:00
}
2024-04-15 17:55:18 +03:00
2015-06-15 19:53:58 +03:00
/**
* vshCommandOptInt :
* @ ctl virtshell control structure
* @ cmd command reference
* @ name option name
* @ value result
*
* Convert option to int .
* On error , a message is displayed .
*
* Return value :
* > 0 if option found and valid ( @ value updated )
* 0 if option not found and not required ( @ value untouched )
* < 0 in all other cases ( @ value untouched )
*/
int
vshCommandOptInt ( vshControl * ctl , const vshCmd * cmd ,
const char * name , int * value )
{
vshCmdOpt * arg ;
int ret ;
if ( ( ret = vshCommandOpt ( cmd , name , & arg , true ) ) < = 0 )
return ret ;
if ( ( ret = virStrToLong_i ( arg - > data , NULL , 10 , value ) ) < 0 )
vshError ( ctl ,
2023-03-09 17:54:58 +03:00
_ ( " Numeric value '%1$s' for <%2$s> option is malformed or out of range " ) ,
2015-06-15 19:53:58 +03:00
arg - > data , name ) ;
else
ret = 1 ;
return ret ;
}
static int
vshCommandOptUIntInternal ( vshControl * ctl ,
const vshCmd * cmd ,
const char * name ,
unsigned int * value ,
bool wrap )
{
vshCmdOpt * arg ;
int ret ;
if ( ( ret = vshCommandOpt ( cmd , name , & arg , true ) ) < = 0 )
return ret ;
if ( wrap )
ret = virStrToLong_ui ( arg - > data , NULL , 10 , value ) ;
else
ret = virStrToLong_uip ( arg - > data , NULL , 10 , value ) ;
if ( ret < 0 )
vshError ( ctl ,
2023-03-09 17:54:58 +03:00
_ ( " Numeric value '%1$s' for <%2$s> option is malformed or out of range " ) ,
2015-06-15 19:53:58 +03:00
arg - > data , name ) ;
else
ret = 1 ;
return ret ;
}
/**
* vshCommandOptUInt :
* @ ctl virtshell control structure
* @ cmd command reference
* @ name option name
* @ value result
*
* Convert option to unsigned int , reject negative numbers
* See vshCommandOptInt ( )
*/
int
vshCommandOptUInt ( vshControl * ctl , const vshCmd * cmd ,
const char * name , unsigned int * value )
{
return vshCommandOptUIntInternal ( ctl , cmd , name , value , false ) ;
}
/**
* vshCommandOptUIntWrap :
* @ ctl virtshell control structure
* @ cmd command reference
* @ name option name
* @ value result
*
* Convert option to unsigned int , wraps negative numbers to positive
* See vshCommandOptInt ( )
*/
int
vshCommandOptUIntWrap ( vshControl * ctl , const vshCmd * cmd ,
const char * name , unsigned int * value )
{
return vshCommandOptUIntInternal ( ctl , cmd , name , value , true ) ;
}
static int
vshCommandOptULInternal ( vshControl * ctl ,
const vshCmd * cmd ,
const char * name ,
unsigned long * value ,
bool wrap )
{
vshCmdOpt * arg ;
int ret ;
if ( ( ret = vshCommandOpt ( cmd , name , & arg , true ) ) < = 0 )
return ret ;
if ( wrap )
ret = virStrToLong_ul ( arg - > data , NULL , 10 , value ) ;
else
ret = virStrToLong_ulp ( arg - > data , NULL , 10 , value ) ;
if ( ret < 0 )
vshError ( ctl ,
2023-03-09 17:54:58 +03:00
_ ( " Numeric value '%1$s' for <%2$s> option is malformed or out of range " ) ,
2015-06-15 19:53:58 +03:00
arg - > data , name ) ;
else
ret = 1 ;
return ret ;
}
/*
* vshCommandOptUL :
* @ ctl virtshell control structure
* @ cmd command reference
* @ name option name
* @ value result
*
* Convert option to unsigned long
* See vshCommandOptInt ( )
*/
int
vshCommandOptUL ( vshControl * ctl , const vshCmd * cmd ,
const char * name , unsigned long * value )
{
return vshCommandOptULInternal ( ctl , cmd , name , value , false ) ;
}
/**
* vshCommandOptULWrap :
* @ ctl virtshell control structure
* @ cmd command reference
* @ name option name
* @ value result
*
* Convert option to unsigned long , wraps negative numbers to positive
* See vshCommandOptInt ( )
*/
int
vshCommandOptULWrap ( vshControl * ctl , const vshCmd * cmd ,
const char * name , unsigned long * value )
{
return vshCommandOptULInternal ( ctl , cmd , name , value , true ) ;
}
/**
2015-12-03 15:47:56 +03:00
* vshCommandOptStringQuiet :
2015-06-15 19:53:58 +03:00
* @ ctl virtshell control structure
* @ cmd command reference
* @ name option name
* @ value result
*
2015-12-03 15:47:56 +03:00
* Returns option as STRING . On error - 1 is returned but no error is set .
2015-06-15 19:53:58 +03:00
* Return value :
* > 0 if option found and valid ( @ value updated )
* 0 if option not found and not required ( @ value untouched )
* < 0 in all other cases ( @ value untouched )
*/
int
2019-10-14 15:44:29 +03:00
vshCommandOptStringQuiet ( vshControl * ctl G_GNUC_UNUSED , const vshCmd * cmd ,
2015-12-03 15:47:56 +03:00
const char * name , const char * * value )
2015-06-15 19:53:58 +03:00
{
vshCmdOpt * arg ;
int ret ;
if ( ( ret = vshCommandOpt ( cmd , name , & arg , true ) ) < = 0 )
return ret ;
2024-03-11 15:17:50 +03:00
if ( ! arg - > def - > allowEmpty & & * arg - > data = = ' \0 ' )
2015-06-15 19:53:58 +03:00
return - 1 ;
* value = arg - > data ;
return 1 ;
}
/**
2024-03-11 17:55:11 +03:00
* vshCommandOptString :
2015-06-15 19:53:58 +03:00
* @ ctl virtshell control structure
* @ cmd command structure
* @ name option name
* @ value result ( updated to NULL or the option argument )
*
* Gets a option argument as string .
*
* Returns 0 on success or when the option is not present and not
* required , * value is set to the option argument . On error - 1 is
* returned and error message printed .
*/
int
2024-03-11 17:55:11 +03:00
vshCommandOptString ( vshControl * ctl ,
const vshCmd * cmd ,
const char * name ,
const char * * value )
2015-06-15 19:53:58 +03:00
{
vshCmdOpt * arg ;
int ret ;
const char * error = NULL ;
/* clear out the value */
* value = NULL ;
ret = vshCommandOpt ( cmd , name , & arg , true ) ;
/* option is not required and not present */
if ( ret = = 0 )
return 0 ;
/* this should not be propagated here, just to be sure */
if ( ret = = - 1 )
error = N_ ( " Mandatory option not present " ) ;
2024-03-11 15:17:50 +03:00
else if ( arg & & * arg - > data = = ' \0 ' & & ! arg - > def - > allowEmpty )
2015-06-15 19:53:58 +03:00
error = N_ ( " Option argument is empty " ) ;
if ( error ) {
2018-02-28 12:09:56 +03:00
if ( ! cmd - > skipChecks )
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " Failed to get option '%1$s': %2$s " ) , name , _ ( error ) ) ;
2015-06-15 19:53:58 +03:00
return - 1 ;
}
* value = arg - > data ;
return 0 ;
}
/**
* vshCommandOptLongLong :
* @ ctl virtshell control structure
* @ cmd command reference
* @ name option name
* @ value result
*
* Returns option as long long
* See vshCommandOptInt ( )
*/
int
vshCommandOptLongLong ( vshControl * ctl , const vshCmd * cmd ,
const char * name , long long * value )
{
vshCmdOpt * arg ;
int ret ;
if ( ( ret = vshCommandOpt ( cmd , name , & arg , true ) ) < = 0 )
return ret ;
if ( ( ret = virStrToLong_ll ( arg - > data , NULL , 10 , value ) ) < 0 )
vshError ( ctl ,
2023-03-09 17:54:58 +03:00
_ ( " Numeric value '%1$s' for <%2$s> option is malformed or out of range " ) ,
2015-06-15 19:53:58 +03:00
arg - > data , name ) ;
else
ret = 1 ;
return ret ;
}
static int
vshCommandOptULongLongInternal ( vshControl * ctl ,
const vshCmd * cmd ,
const char * name ,
unsigned long long * value ,
bool wrap )
{
vshCmdOpt * arg ;
int ret ;
if ( ( ret = vshCommandOpt ( cmd , name , & arg , true ) ) < = 0 )
return ret ;
if ( wrap )
ret = virStrToLong_ull ( arg - > data , NULL , 10 , value ) ;
else
ret = virStrToLong_ullp ( arg - > data , NULL , 10 , value ) ;
if ( ret < 0 )
vshError ( ctl ,
2023-03-09 17:54:58 +03:00
_ ( " Numeric value '%1$s' for <%2$s> option is malformed or out of range " ) ,
2015-06-15 19:53:58 +03:00
arg - > data , name ) ;
else
ret = 1 ;
return ret ;
}
/**
* vshCommandOptULongLong :
* @ ctl virtshell control structure
* @ cmd command reference
* @ name option name
* @ value result
*
* Returns option as long long , rejects negative numbers
* See vshCommandOptInt ( )
*/
int
vshCommandOptULongLong ( vshControl * ctl , const vshCmd * cmd ,
const char * name , unsigned long long * value )
{
return vshCommandOptULongLongInternal ( ctl , cmd , name , value , false ) ;
}
/**
* vshCommandOptULongLongWrap :
* @ ctl virtshell control structure
* @ cmd command reference
* @ name option name
* @ value result
*
* Returns option as long long , wraps negative numbers to positive
* See vshCommandOptInt ( )
*/
int
vshCommandOptULongLongWrap ( vshControl * ctl , const vshCmd * cmd ,
const char * name , unsigned long long * value )
{
return vshCommandOptULongLongInternal ( ctl , cmd , name , value , true ) ;
}
/**
* vshCommandOptScaledInt :
* @ ctl virtshell control structure
* @ cmd command reference
* @ name option name
* @ value result
* @ scale default of 1 or 1024 , if no suffix is present
* @ max maximum value permitted
*
* Returns option as long long , scaled according to suffix
* See vshCommandOptInt ( )
*/
int
vshCommandOptScaledInt ( vshControl * ctl , const vshCmd * cmd ,
const char * name , unsigned long long * value ,
int scale , unsigned long long max )
{
vshCmdOpt * arg ;
char * end ;
int ret ;
if ( ( ret = vshCommandOpt ( cmd , name , & arg , true ) ) < = 0 )
return ret ;
2016-03-17 17:45:09 +03:00
2015-06-15 19:53:58 +03:00
if ( virStrToLong_ullp ( arg - > data , & end , 10 , value ) < 0 | |
2016-03-17 17:45:09 +03:00
virScaleInteger ( value , end , scale , max ) < 0 ) {
2015-06-15 19:53:58 +03:00
vshError ( ctl ,
2023-03-09 17:54:58 +03:00
_ ( " Scaled numeric value '%1$s' for <%2$s> option is malformed or out of range " ) ,
arg - > data , name ) ;
2016-03-17 17:45:09 +03:00
return - 1 ;
2015-06-15 19:53:58 +03:00
}
2016-03-17 17:45:09 +03:00
return 1 ;
2015-06-15 19:53:58 +03:00
}
/**
* vshCommandOptBool :
* @ cmd command reference
* @ name option name
*
* Returns true / false if the option exists . Note that this does NOT
* validate whether the option is actually boolean , or even whether
* name is legal ; so that this can be used to probe whether a data
* option is present without actually using that data .
*/
bool
vshCommandOptBool ( const vshCmd * cmd , const char * name )
{
vshCmdOpt * dummy ;
return vshCommandOpt ( cmd , name , & dummy , false ) = = 1 ;
}
2024-03-06 19:26:56 +03:00
2015-06-15 19:53:58 +03:00
/**
* vshCommandOptArgv :
2024-03-06 19:26:56 +03:00
* @ cmd : command reference
* @ name : name of argument
2015-06-15 19:53:58 +03:00
*
2024-03-06 19:26:56 +03:00
* Returns a NULL terminated list of strings of values passed as argument of
* ARGV argument named @ name . The returned string list is owned by @ cmd and
* caller must not free or modify it .
*/
const char * *
vshCommandOptArgv ( const vshCmd * cmd ,
const char * name )
{
2024-04-15 17:55:18 +03:00
vshCmdOpt * opt ;
2024-03-06 19:26:56 +03:00
2024-04-15 17:55:18 +03:00
if ( vshCommandOpt ( cmd , name , & opt , true ) ! = 1 )
2024-03-06 19:26:56 +03:00
return NULL ;
2024-04-15 17:55:18 +03:00
return ( const char * * ) opt - > argv ;
2024-03-06 19:26:56 +03:00
}
/**
* vshCommandOptArgvString :
* @ cmd : command reference
* @ name : name of argument
2015-06-15 19:53:58 +03:00
*
2024-03-06 19:26:56 +03:00
* Returns a string containing all values passed as ARGV argument @ name
* delimited / concatenated by adding spaces .
2015-06-15 19:53:58 +03:00
*/
2024-03-06 19:26:56 +03:00
const char *
vshCommandOptArgvString ( const vshCmd * cmd ,
const char * name )
2015-06-15 19:53:58 +03:00
{
2024-04-15 17:55:18 +03:00
vshCmdOpt * opt ;
2015-06-15 19:53:58 +03:00
2024-04-15 17:55:18 +03:00
if ( vshCommandOpt ( cmd , name , & opt , true ) ! = 1 )
2024-03-06 19:26:56 +03:00
return NULL ;
2024-04-15 17:55:18 +03:00
if ( ! opt - > argvstr )
opt - > argvstr = g_strjoinv ( " " , opt - > argv ) ;
2024-03-06 19:26:56 +03:00
return opt - > argvstr ;
2015-06-15 19:53:58 +03:00
}
2016-03-29 16:47:40 +03:00
/**
* vshBlockJobOptionBandwidth :
* @ ctl : virsh control data
* @ cmd : virsh command description
* @ bytes : return bandwidth in bytes / s instead of MiB / s
* @ bandwidth : return value
*
* Extracts the value of - - bandwidth either as a wrap - able number without scale
* or as a scaled integer . The returned value is checked to fit into a unsigned
* long data type . This is a legacy compatibility function and it should not
* be used for things other the block job APIs .
*
* Returns 0 on success , - 1 on error .
*/
int
vshBlockJobOptionBandwidth ( vshControl * ctl ,
const vshCmd * cmd ,
bool bytes ,
unsigned long * bandwidth )
{
vshCmdOpt * arg ;
char * end ;
unsigned long long bw ;
int ret ;
if ( ( ret = vshCommandOpt ( cmd , " bandwidth " , & arg , true ) ) < = 0 )
return ret ;
/* due to historical reasons we declare to parse negative numbers and wrap
* them to the unsigned data type . */
if ( virStrToLong_ul ( arg - > data , NULL , 10 , bandwidth ) < 0 ) {
/* try to parse the number as scaled size in this case we don't accept
* wrapping since it would be ridiculous . In case of a 32 bit host ,
* limit the value to ULONG_MAX */
if ( virStrToLong_ullp ( arg - > data , & end , 10 , & bw ) < 0 | |
virScaleInteger ( & bw , end , 1 , ULONG_MAX ) < 0 ) {
vshError ( ctl ,
2023-03-09 17:54:58 +03:00
_ ( " Scaled numeric value '%1$s' for <--bandwidth> option is malformed or out of range " ) ,
arg - > data ) ;
2016-03-29 16:47:40 +03:00
return - 1 ;
}
if ( ! bytes )
bw > > = 20 ;
* bandwidth = bw ;
}
return 0 ;
}
2015-06-15 19:53:58 +03:00
/*
* Executes command ( s ) and returns return code from last command
*/
bool
vshCommandRun ( vshControl * ctl , const vshCmd * cmd )
{
const vshClientHooks * hooks = ctl - > hooks ;
bool ret = true ;
while ( cmd ) {
2019-12-20 19:19:30 +03:00
gint64 before , after ;
2015-06-15 19:53:58 +03:00
bool enable_timing = ctl - > timing ;
2019-12-20 19:19:30 +03:00
before = g_get_real_time ( ) ;
2015-06-15 19:53:58 +03:00
if ( ( cmd - > def - > flags & VSH_CMD_FLAG_NOCONNECT ) | |
( hooks & & hooks - > connHandler & & hooks - > connHandler ( ctl ) ) ) {
ret = cmd - > def - > handler ( ctl , cmd ) ;
} else {
/* connection is not usable, return error */
ret = false ;
}
2019-12-20 19:19:30 +03:00
after = g_get_real_time ( ) ;
2015-06-15 19:53:58 +03:00
/* try to automatically catch disconnections */
if ( ! ret & &
( ( last_error ! = NULL ) & &
( ( ( last_error - > code = = VIR_ERR_SYSTEM_ERROR ) & &
( last_error - > domain = = VIR_FROM_REMOTE ) ) | |
( last_error - > code = = VIR_ERR_RPC ) | |
( last_error - > code = = VIR_ERR_NO_CONNECT ) | |
( last_error - > code = = VIR_ERR_INVALID_CONN ) ) ) )
disconnected + + ;
if ( ! ret )
vshReportError ( ctl ) ;
if ( STREQ ( cmd - > def - > name , " quit " ) | |
STREQ ( cmd - > def - > name , " exit " ) ) /* hack ... */
return ret ;
if ( enable_timing ) {
2019-12-20 19:19:30 +03:00
double diff_ms = ( after - before ) / 1000.0 ;
2015-06-15 19:53:58 +03:00
2023-03-09 17:54:58 +03:00
vshPrint ( ctl , _ ( " \n (Time: %1$.3f ms) \n \n " ) , diff_ms ) ;
2015-06-15 19:53:58 +03:00
} else {
vshPrintExtra ( ctl , " \n " ) ;
}
cmd = cmd - > next ;
}
return ret ;
}
/* ---------------
* Command parsing
* - - - - - - - - - - - - - - -
*/
typedef enum {
VSH_TK_ERROR , /* Failed to parse a token */
VSH_TK_ARG , /* Arbitrary argument, might be option or empty */
VSH_TK_SUBCMD_END , /* Separation between commands */
VSH_TK_END /* No more commands */
} vshCommandToken ;
typedef struct _vshCommandParser vshCommandParser ;
struct _vshCommandParser {
vshCommandToken ( * getNextArg ) ( vshControl * , vshCommandParser * ,
2016-07-08 15:56:04 +03:00
char * * , bool ) ;
2015-06-15 19:53:58 +03:00
/* vshCommandStringGetArg() */
char * pos ;
2021-01-26 11:51:27 +03:00
size_t point ;
2015-06-15 19:53:58 +03:00
/* vshCommandArgvGetArg() */
char * * arg_pos ;
char * * arg_end ;
} ;
2024-04-15 17:55:18 +03:00
static vshCmd *
vshCmdNewHelp ( const char * name )
{
vshCmd * c = g_new0 ( vshCmd , 1 ) ;
c - > def = vshCmddefSearch ( " help " ) ;
c - > opts = g_new0 ( vshCmdOpt , 2 ) ;
c - > opts - > def = c - > def - > opts ;
c - > opts - > data = g_strdup ( name ) ;
c - > opts - > present = true ;
return c ;
}
static vshCmd *
vshCmdNew ( vshControl * ctl ,
const char * cmdname ,
bool report )
{
g_autoptr ( vshCmd ) c = g_new0 ( vshCmd , 1 ) ;
const vshCmdOptDef * optdef ;
vshCmdOpt * opt ;
size_t nopts = 0 ;
if ( ! ( c - > def = vshCmddefSearch ( cmdname ) ) ) {
if ( report )
vshError ( ctl , _ ( " unknown command: '%1$s' " ) , cmdname ) ;
return NULL ;
}
/* resolve command alias */
if ( c - > def - > alias ) {
if ( ! ( c - > def = vshCmddefSearch ( c - > def - > alias ) ) ) {
/* dead code: self-test ensures that the alias exists thus no error reported here */
return NULL ;
}
}
/* Find number of arguments */
for ( optdef = c - > def - > opts ; optdef & & optdef - > name ; optdef + + )
nopts + + ;
c - > opts = g_new0 ( vshCmdOpt , nopts + 1 ) ;
opt = c - > opts ;
/* populate links to definitions */
for ( optdef = c - > def - > opts ; optdef & & optdef - > name ; optdef + + ) {
opt - > def = optdef ;
opt + + ;
}
return g_steal_pointer ( & c ) ;
}
2024-03-07 19:06:18 +03:00
static int
vshCmdOptAssignPositional ( vshControl * ctl ,
vshCmd * cmd ,
const char * val ,
bool report )
{
vshCmdOpt * opt ;
if ( ! ( opt = vshCmdGetNextPositionalOpt ( cmd ) ) ) {
/* ignore spurious arguments for 'help' command */
if ( STREQ ( cmd - > def - > name , " help " ) )
return 0 ;
if ( report )
vshError ( ctl , _ ( " unexpected data '%1$s' " ) , val ) ;
return - 1 ;
}
vshCmdOptAssign ( ctl , cmd , opt , val , report ) ;
return 0 ;
}
typedef enum {
VSH_CMD_PARSER_STATE_START ,
VSH_CMD_PARSER_STATE_COMMENT ,
VSH_CMD_PARSER_STATE_COMMAND ,
VSH_CMD_PARSER_STATE_ASSIGN_OPT ,
VSH_CMD_PARSER_STATE_POSITIONAL_ONLY ,
} vshCommandParserState ;
2015-06-15 19:53:58 +03:00
static bool
2024-03-07 19:06:18 +03:00
vshCommandParse ( vshControl * ctl ,
vshCommandParser * parser ,
vshCmd * * partial )
2015-06-15 19:53:58 +03:00
{
2024-03-07 19:06:18 +03:00
g_autoptr ( vshCmd ) cmds = NULL ; /* linked list of all parsed commands in this session */
vshCmd * cmds_last = NULL ;
g_autoptr ( vshCmd ) cmd = NULL ; /* currently parsed command */
vshCommandParserState state = VSH_CMD_PARSER_STATE_START ;
vshCmdOpt * opt = NULL ;
g_autofree char * optionvalue = NULL ;
bool report = ! partial ;
bool ret = false ;
2015-06-15 19:53:58 +03:00
2024-03-07 19:06:18 +03:00
if ( partial ) {
g_clear_pointer ( partial , vshCommandFree ) ;
} else {
2022-01-28 20:42:45 +03:00
g_clear_pointer ( & ctl - > cmd , vshCommandFree ) ;
2017-11-06 17:46:50 +03:00
}
2015-06-15 19:53:58 +03:00
while ( 1 ) {
2024-03-07 19:06:18 +03:00
/* previous iteration might have already gotten a value. Store it as the
* token in this iteration */
g_autofree char * tkdata = g_steal_pointer ( & optionvalue ) ;
2015-06-15 19:53:58 +03:00
2024-03-07 19:06:18 +03:00
/* If we have a value already or the option to fill is a boolean we
* don ' t want to fetch a new token */
if ( ! ( tkdata | |
( opt & & opt - > def - > type = = VSH_OT_BOOL ) ) ) {
vshCommandToken tk ;
2015-06-15 19:53:58 +03:00
2024-03-07 19:06:18 +03:00
tk = parser - > getNextArg ( ctl , parser , & tkdata , report ) ;
2015-06-15 19:53:58 +03:00
2024-03-07 19:06:18 +03:00
switch ( tk ) {
case VSH_TK_ARG :
/* will be handled below */
2015-06-15 19:53:58 +03:00
break ;
2024-03-07 19:06:18 +03:00
case VSH_TK_ERROR :
goto out ;
case VSH_TK_END :
case VSH_TK_SUBCMD_END :
/* The last argument name expects a value, but it's missing */
if ( opt ) {
if ( partial ) {
/* for completion to work we need to also store the
* last token into the last ' opt ' */
vshCmdOptAssign ( ctl , cmd , opt , tkdata , report ) ;
} else {
if ( opt - > def - > type = = VSH_OT_INT )
vshError ( ctl , _ ( " expected syntax: --%1$s <number> " ) ,
opt - > def - > name ) ;
else
vshError ( ctl , _ ( " expected syntax: --%1$s <string> " ) ,
opt - > def - > name ) ;
goto out ;
}
2015-06-15 19:53:58 +03:00
}
2016-09-07 18:30:48 +03:00
2024-03-07 19:06:18 +03:00
/* command parsed -- allocate new struct for the command */
if ( cmd ) {
/* if we encountered --help, replace parsed command with 'help <cmdname>' */
if ( cmd - > helpOptionSeen ) {
vshCmd * helpcmd = vshCmdNewHelp ( cmd - > def - > name ) ;
2024-04-15 17:55:18 +03:00
2024-03-07 19:06:18 +03:00
vshCommandFree ( cmd ) ;
cmd = helpcmd ;
2015-06-15 19:53:58 +03:00
}
2024-03-07 19:06:18 +03:00
if ( ! partial & &
vshCommandCheckOpts ( ctl , cmd ) < 0 )
goto out ;
if ( ! cmds )
cmds = cmd ;
if ( cmds_last )
cmds_last - > next = cmd ;
cmds_last = g_steal_pointer ( & cmd ) ;
2015-06-15 19:53:58 +03:00
}
2024-03-07 19:06:18 +03:00
/* everything parsed */
if ( tk = = VSH_TK_END ) {
ret = true ;
goto out ;
2015-06-15 19:53:58 +03:00
}
2024-03-07 19:06:18 +03:00
/* after processing the command we need to start over again to
* fetch another token */
state = VSH_CMD_PARSER_STATE_START ;
continue ;
2015-06-15 19:53:58 +03:00
}
}
2024-03-07 19:06:18 +03:00
/* at this point we know that @tkdata is an argument */
switch ( state ) {
case VSH_CMD_PARSER_STATE_START :
if ( * tkdata = = ' # ' ) {
state = VSH_CMD_PARSER_STATE_COMMENT ;
} else {
state = VSH_CMD_PARSER_STATE_COMMAND ;
2015-06-15 19:53:58 +03:00
2024-03-07 19:06:18 +03:00
if ( ! ( cmd = vshCmdNew ( ctl , tkdata , ! partial ) ) )
goto out ;
2015-06-15 19:53:58 +03:00
}
2024-03-07 19:06:18 +03:00
break ;
case VSH_CMD_PARSER_STATE_COMMENT :
/* continue eating tokens until end of line or end of input */
state = VSH_CMD_PARSER_STATE_COMMENT ;
break ;
case VSH_CMD_PARSER_STATE_COMMAND : {
/* parsing individual options for the command. There are following options:
* - - option
* - - option value
* - - option = value
* - - aliasoptionwithvalue ( value is part of the alias definition )
* value
* - - ( terminate accepting ' - - option ' , fill only positional args )
*/
const char * optionname = tkdata + 2 ;
char * sep ;
if ( ! STRPREFIX ( tkdata , " -- " ) ) {
if ( vshCmdOptAssignPositional ( ctl , cmd , tkdata , report ) < 0 )
goto out ;
break ;
2015-06-15 19:53:58 +03:00
}
2024-03-07 19:06:18 +03:00
if ( STREQ ( tkdata , " -- " ) ) {
state = VSH_CMD_PARSER_STATE_POSITIONAL_ONLY ;
break ;
}
if ( ( sep = strchr ( optionname , ' = ' ) ) ) {
* ( sep + + ) = ' \0 ' ;
/* 'optionvalue' has lifetime until next iteration */
optionvalue = g_strdup ( sep ) ;
}
/* lookup the option. Note that vshCmdGetOption also resolves aliases
* and thus the value possibly contained in the alias */
2024-05-15 09:42:57 +03:00
if ( STREQ ( optionname , " help " ) ) {
cmd - > helpOptionSeen = true ;
g_clear_pointer ( & optionvalue , g_free ) ;
} else if ( ! ( opt = vshCmdGetOption ( ctl , cmd , optionname , & optionvalue , report ) ) ) {
2024-03-07 19:06:18 +03:00
if ( STRNEQ ( cmd - > def - > name , " help " ) )
goto out ;
/* ignore spurious arguments for 'help' command */
g_clear_pointer ( & optionvalue , g_free ) ;
state = VSH_CMD_PARSER_STATE_COMMAND ;
2017-11-06 17:46:50 +03:00
} else {
2024-03-07 19:06:18 +03:00
state = VSH_CMD_PARSER_STATE_ASSIGN_OPT ;
2017-11-06 17:46:50 +03:00
}
2015-06-15 19:53:58 +03:00
}
2024-03-07 19:06:18 +03:00
break ;
case VSH_CMD_PARSER_STATE_ASSIGN_OPT :
/* Parameter for a boolean was passed via --boolopt=val */
if ( tkdata & & opt - > def - > type = = VSH_OT_BOOL ) {
if ( report )
vshError ( ctl , _ ( " invalid '=' after option --%1$s " ) ,
opt - > def - > name ) ;
goto out ;
}
2015-06-15 19:53:58 +03:00
2024-03-07 19:06:18 +03:00
vshCmdOptAssign ( ctl , cmd , opt , tkdata , report ) ;
opt = NULL ;
state = VSH_CMD_PARSER_STATE_COMMAND ;
2015-06-15 19:53:58 +03:00
break ;
2024-03-07 19:06:18 +03:00
case VSH_CMD_PARSER_STATE_POSITIONAL_ONLY :
state = VSH_CMD_PARSER_STATE_POSITIONAL_ONLY ;
if ( vshCmdOptAssignPositional ( ctl , cmd , tkdata , report ) < 0 )
goto out ;
break ;
}
2015-06-15 19:53:58 +03:00
}
2024-03-07 19:06:18 +03:00
out :
2015-06-15 19:53:58 +03:00
2017-11-06 17:46:50 +03:00
if ( partial ) {
2024-03-07 19:06:18 +03:00
/* When parsing a command for command completion, the last processed
* command or the one being currently parsed */
if ( cmd ) {
* partial = g_steal_pointer ( & cmd ) ;
} else if ( cmds = = cmds_last ) {
* partial = g_steal_pointer ( & cmds ) ;
} else {
/* break the last command out of the linked list and let the rest be freed */
vshCmd * nc ;
for ( nc = cmds ; nc ; nc = nc - > next ) {
if ( nc - > next = = cmds_last ) {
nc - > next = NULL ;
break ;
}
}
* partial = cmds_last ;
}
2017-11-06 17:46:50 +03:00
} else {
2024-03-07 19:06:18 +03:00
/* for normal command parsing use the whole parsed command list, but
* only on success */
if ( ret = = true ) {
ctl - > cmd = g_steal_pointer ( & cmds ) ;
}
2017-11-06 17:46:50 +03:00
}
2024-03-07 19:06:18 +03:00
return ret ;
2015-06-15 19:53:58 +03:00
}
2024-03-07 19:06:18 +03:00
2015-06-15 19:53:58 +03:00
/* --------------------
* Command argv parsing
* - - - - - - - - - - - - - - - - - - - -
*/
static vshCommandToken ATTRIBUTE_NONNULL ( 2 ) ATTRIBUTE_NONNULL ( 3 )
2019-10-19 01:12:53 +03:00
vshCommandArgvGetArg ( vshControl * ctl G_GNUC_UNUSED ,
2019-10-19 01:12:53 +03:00
vshCommandParser * parser ,
char * * res ,
2019-10-14 15:44:29 +03:00
bool report G_GNUC_UNUSED )
2015-06-15 19:53:58 +03:00
{
if ( parser - > arg_pos = = parser - > arg_end ) {
* res = NULL ;
return VSH_TK_END ;
}
2019-10-19 01:12:53 +03:00
* res = g_strdup ( * parser - > arg_pos ) ;
2015-06-15 19:53:58 +03:00
parser - > arg_pos + + ;
return VSH_TK_ARG ;
}
bool
vshCommandArgvParse ( vshControl * ctl , int nargs , char * * argv )
{
2021-01-26 11:51:27 +03:00
vshCommandParser parser = { 0 } ;
2015-06-15 19:53:58 +03:00
if ( nargs < = 0 )
return false ;
parser . arg_pos = argv ;
parser . arg_end = argv + nargs ;
parser . getNextArg = vshCommandArgvGetArg ;
2017-11-06 17:46:50 +03:00
return vshCommandParse ( ctl , & parser , NULL ) ;
2015-06-15 19:53:58 +03:00
}
/* ----------------------
* Command string parsing
* - - - - - - - - - - - - - - - - - - - - - -
*/
static vshCommandToken ATTRIBUTE_NONNULL ( 2 ) ATTRIBUTE_NONNULL ( 3 )
2016-07-08 15:56:04 +03:00
vshCommandStringGetArg ( vshControl * ctl , vshCommandParser * parser , char * * res ,
bool report )
2015-06-15 19:53:58 +03:00
{
bool single_quote = false ;
bool double_quote = false ;
char * p = parser - > pos ;
2019-10-18 18:24:02 +03:00
char * q = g_strdup ( p ) ;
2015-06-15 19:53:58 +03:00
* res = q ;
2019-02-26 21:49:25 +03:00
while ( * p = = ' ' | | * p = = ' \t ' | | ( * p = = ' \\ ' & & p [ 1 ] = = ' \n ' ) )
p + = 1 + ( * p = = ' \\ ' ) ;
2015-06-15 19:53:58 +03:00
if ( * p = = ' \0 ' )
return VSH_TK_END ;
2019-02-21 21:36:32 +03:00
if ( * p = = ' ; ' | | * p = = ' \n ' ) {
2015-06-15 19:53:58 +03:00
parser - > pos = + + p ; /* = \0 or begin of next command */
return VSH_TK_SUBCMD_END ;
}
2019-03-22 22:02:39 +03:00
if ( * p = = ' # ' ) { /* Argument starting with # is comment to end of line */
while ( * p & & * p ! = ' \n ' )
p + + ;
parser - > pos = p + ! ! * p ;
return VSH_TK_SUBCMD_END ;
}
2015-06-15 19:53:58 +03:00
while ( * p ) {
/* end of token is blank space or ';' */
if ( ! double_quote & & ! single_quote & &
2019-02-21 21:36:32 +03:00
( * p = = ' ' | | * p = = ' \t ' | | * p = = ' ; ' | | * p = = ' \n ' ) )
2015-06-15 19:53:58 +03:00
break ;
if ( ! double_quote & & * p = = ' \' ' ) { /* single quote */
single_quote = ! single_quote ;
p + + ;
continue ;
} else if ( ! single_quote & & * p = = ' \\ ' ) { /* escape */
/*
2019-02-26 21:49:25 +03:00
* The same as in shell , a \ in " " is an escaper ,
2015-06-15 19:53:58 +03:00
* but a \ in ' ' is not an escaper .
*/
p + + ;
if ( * p = = ' \0 ' ) {
2016-07-08 15:56:04 +03:00
if ( report )
vshError ( ctl , " %s " , _ ( " dangling \\ " ) ) ;
2015-06-15 19:53:58 +03:00
return VSH_TK_ERROR ;
2019-02-26 21:49:25 +03:00
} else if ( * p = = ' \n ' ) {
/* Elide backslash-newline entirely */
p + + ;
continue ;
2015-06-15 19:53:58 +03:00
}
} else if ( ! single_quote & & * p = = ' " ' ) { /* double quote */
double_quote = ! double_quote ;
p + + ;
continue ;
}
* q + + = * p + + ;
}
2021-01-07 20:09:11 +03:00
2015-06-15 19:53:58 +03:00
if ( double_quote ) {
2021-01-07 20:09:11 +03:00
/* We have seen a double quote, but not it's companion
* ending . It ' s valid though , in case when we ' re called
* from completer ( report = false ) , but it ' s not valid
* when parsing real command ( report = true ) . */
if ( report ) {
2016-07-08 15:56:04 +03:00
vshError ( ctl , " %s " , _ ( " missing \" " ) ) ;
2021-01-07 20:09:11 +03:00
return VSH_TK_ERROR ;
}
2015-06-15 19:53:58 +03:00
}
* q = ' \0 ' ;
parser - > pos = p ;
return VSH_TK_ARG ;
}
2021-01-26 11:51:27 +03:00
/**
* vshCommandStringParse :
* @ ctl virsh control structure
* @ cmdstr : string to parse
* @ partial : store partially parsed command here
*
* Parse given string @ cmdstr as a command and store it under
* @ ctl - > cmd . For readline completion , if @ partial is not NULL on
* the input then errors in parsing are ignored ( because user is
* still in progress of writing the command string ) and partially
* parsed command is stored at * @ partial ( caller has to free it
2024-04-15 12:45:14 +03:00
* afterwards ) .
2021-01-26 11:51:27 +03:00
*/
2015-06-15 19:53:58 +03:00
bool
2021-01-26 11:51:27 +03:00
vshCommandStringParse ( vshControl * ctl ,
char * cmdstr ,
2024-04-15 12:45:14 +03:00
vshCmd * * partial )
2015-06-15 19:53:58 +03:00
{
2021-01-26 11:51:27 +03:00
vshCommandParser parser = { 0 } ;
2015-06-15 19:53:58 +03:00
if ( cmdstr = = NULL | | * cmdstr = = ' \0 ' )
return false ;
parser . pos = cmdstr ;
parser . getNextArg = vshCommandStringGetArg ;
2017-11-06 17:46:50 +03:00
return vshCommandParse ( ctl , & parser , partial ) ;
2015-06-15 19:53:58 +03:00
}
/**
* virshCommandOptTimeoutToMs :
* @ ctl virsh control structure
* @ cmd command reference
* @ timeout result
*
* Parse an optional - - timeout parameter in seconds , but store the
* value of the timeout in milliseconds .
* See vshCommandOptInt ( )
*/
int
vshCommandOptTimeoutToMs ( vshControl * ctl , const vshCmd * cmd , int * timeout )
{
int ret ;
unsigned int utimeout ;
if ( ( ret = vshCommandOptUInt ( ctl , cmd , " timeout " , & utimeout ) ) < = 0 )
return ret ;
/* Ensure that the timeout is not zero and that we can convert
* it from seconds to milliseconds without overflowing . */
if ( utimeout = = 0 | | utimeout > INT_MAX / 1000 ) {
vshError ( ctl ,
2023-03-09 17:54:58 +03:00
_ ( " Numeric value '%1$u' for <%2$s> option is malformed or out of range " ) ,
2015-06-15 19:53:58 +03:00
utimeout ,
" timeout " ) ;
ret = - 1 ;
} else {
* timeout = ( ( int ) utimeout ) * 1000 ;
}
return ret ;
}
/* ---------------
* Misc utils
* - - - - - - - - - - - - - - -
*/
2021-09-24 12:15:10 +03:00
/* Return a non-NULL string representation of a typed parameter; exit on
* unknown type . */
2015-06-15 19:53:58 +03:00
char *
vshGetTypedParamValue ( vshControl * ctl , virTypedParameterPtr item )
{
switch ( item - > type ) {
case VIR_TYPED_PARAM_INT :
2021-09-24 12:15:10 +03:00
return g_strdup_printf ( " %d " , item - > value . i ) ;
2015-06-15 19:53:58 +03:00
break ;
case VIR_TYPED_PARAM_UINT :
2021-09-24 12:15:10 +03:00
return g_strdup_printf ( " %u " , item - > value . ui ) ;
2015-06-15 19:53:58 +03:00
break ;
case VIR_TYPED_PARAM_LLONG :
2021-09-24 12:15:10 +03:00
return g_strdup_printf ( " %lld " , item - > value . l ) ;
2015-06-15 19:53:58 +03:00
break ;
case VIR_TYPED_PARAM_ULLONG :
2021-09-24 12:15:10 +03:00
return g_strdup_printf ( " %llu " , item - > value . ul ) ;
2015-06-15 19:53:58 +03:00
break ;
case VIR_TYPED_PARAM_DOUBLE :
2021-09-24 12:15:10 +03:00
return g_strdup_printf ( " %f " , item - > value . d ) ;
2015-06-15 19:53:58 +03:00
break ;
case VIR_TYPED_PARAM_BOOLEAN :
2021-09-24 12:15:10 +03:00
return g_strdup ( item - > value . b ? _ ( " yes " ) : _ ( " no " ) ) ;
2015-06-15 19:53:58 +03:00
break ;
case VIR_TYPED_PARAM_STRING :
2021-09-24 12:15:10 +03:00
return g_strdup ( item - > value . s ) ;
2015-06-15 19:53:58 +03:00
break ;
default :
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " unimplemented parameter type %1$d " ) , item - > type ) ;
2015-06-15 19:53:58 +03:00
exit ( EXIT_FAILURE ) ;
}
}
void
vshDebug ( vshControl * ctl , int level , const char * format , . . . )
{
va_list ap ;
2021-08-10 18:09:07 +03:00
g_autofree char * str = NULL ;
2015-06-15 19:53:58 +03:00
/* Aligning log levels to that of libvirt.
* Traces with levels > = user - specified - level
* gets logged into file
*/
if ( level < ctl - > debug )
return ;
va_start ( ap , format ) ;
vshOutputLogFile ( ctl , level , format , ap ) ;
va_end ( ap ) ;
va_start ( ap , format ) ;
2019-10-22 15:11:15 +03:00
str = g_strdup_vprintf ( format , ap ) ;
2015-06-15 19:53:58 +03:00
va_end ( ap ) ;
fputs ( str , stdout ) ;
2022-03-09 16:01:49 +03:00
fflush ( stdout ) ;
2015-06-15 19:53:58 +03:00
}
2023-04-13 10:19:27 +03:00
2015-06-15 19:53:58 +03:00
void
2023-04-13 10:19:27 +03:00
vshPrintVa ( vshControl * ctl G_GNUC_UNUSED ,
const char * format ,
va_list ap )
2015-06-15 19:53:58 +03:00
{
2021-08-10 18:09:07 +03:00
g_autofree char * str = NULL ;
2015-06-15 19:53:58 +03:00
2023-04-13 10:19:27 +03:00
str = g_strdup_vprintf ( format , ap ) ;
fputs ( str , stdout ) ;
fflush ( stdout ) ;
}
void
vshPrintExtra ( vshControl * ctl ,
const char * format ,
. . . )
{
va_list ap ;
2015-06-15 19:53:58 +03:00
if ( ctl & & ctl - > quiet )
return ;
va_start ( ap , format ) ;
2023-04-13 10:19:27 +03:00
vshPrintVa ( ctl , format , ap ) ;
2015-06-15 19:53:58 +03:00
va_end ( ap ) ;
}
2016-02-12 16:09:02 +03:00
void
2023-04-13 10:19:27 +03:00
vshPrint ( vshControl * ctl ,
const char * format ,
. . . )
2016-02-12 16:09:02 +03:00
{
va_list ap ;
va_start ( ap , format ) ;
2023-04-13 10:19:27 +03:00
vshPrintVa ( ctl , format , ap ) ;
2016-02-12 16:09:02 +03:00
va_end ( ap ) ;
}
2015-06-15 19:53:58 +03:00
bool
2019-10-14 15:44:29 +03:00
vshTTYIsInterruptCharacter ( vshControl * ctl G_GNUC_UNUSED ,
const char chr G_GNUC_UNUSED )
2015-06-15 19:53:58 +03:00
{
# ifndef WIN32
if ( ctl - > istty & &
ctl - > termattr . c_cc [ VINTR ] = = chr )
return true ;
# endif
return false ;
}
bool
vshTTYAvailable ( vshControl * ctl )
{
return ctl - > istty ;
}
int
2019-10-14 15:44:29 +03:00
vshTTYDisableInterrupt ( vshControl * ctl G_GNUC_UNUSED )
2015-06-15 19:53:58 +03:00
{
# ifndef WIN32
struct termios termset = ctl - > termattr ;
if ( ! ctl - > istty )
return - 1 ;
/* check if we need to set the terminal */
if ( termset . c_cc [ VINTR ] = = _POSIX_VDISABLE )
return 0 ;
termset . c_cc [ VINTR ] = _POSIX_VDISABLE ;
termset . c_lflag & = ~ ICANON ;
if ( tcsetattr ( STDIN_FILENO , TCSANOW , & termset ) < 0 )
return - 1 ;
# endif
return 0 ;
}
int
2019-10-14 15:44:29 +03:00
vshTTYRestore ( vshControl * ctl G_GNUC_UNUSED )
2015-06-15 19:53:58 +03:00
{
# ifndef WIN32
if ( ! ctl - > istty )
return 0 ;
if ( tcsetattr ( STDIN_FILENO , TCSAFLUSH , & ctl - > termattr ) < 0 )
return - 1 ;
# endif
return 0 ;
}
int
2019-10-14 15:44:29 +03:00
vshTTYMakeRaw ( vshControl * ctl G_GNUC_UNUSED ,
bool report_errors G_GNUC_UNUSED )
2015-06-15 19:53:58 +03:00
{
# ifndef WIN32
struct termios rawattr = ctl - > termattr ;
2020-02-26 20:57:34 +03:00
2015-06-15 19:53:58 +03:00
if ( ! ctl - > istty ) {
if ( report_errors ) {
vshError ( ctl , " %s " ,
_ ( " unable to make terminal raw: console isn't a tty " ) ) ;
}
return - 1 ;
}
cfmakeraw ( & rawattr ) ;
if ( tcsetattr ( STDIN_FILENO , TCSAFLUSH , & rawattr ) < 0 ) {
if ( report_errors )
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " unable to set tty attributes: %1$s " ) ,
2020-02-26 20:57:34 +03:00
g_strerror ( errno ) ) ;
2015-06-15 19:53:58 +03:00
return - 1 ;
}
# endif
return 0 ;
}
void
vshError ( vshControl * ctl , const char * format , . . . )
{
va_list ap ;
2021-08-10 18:09:07 +03:00
g_autofree char * str = NULL ;
2015-06-15 19:53:58 +03:00
if ( ctl ! = NULL ) {
va_start ( ap , format ) ;
vshOutputLogFile ( ctl , VSH_ERR_ERROR , format , ap ) ;
va_end ( ap ) ;
}
/* Most output is to stdout, but if someone ran virsh 2>&1, then
* printing to stderr will not interleave correctly with stdout
* unless we flush between every transition between streams . */
fflush ( stdout ) ;
fputs ( _ ( " error: " ) , stderr ) ;
va_start ( ap , format ) ;
2019-10-22 15:11:15 +03:00
str = g_strdup_vprintf ( format , ap ) ;
2015-06-15 19:53:58 +03:00
va_end ( ap ) ;
fprintf ( stderr , " %s \n " , NULLSTR ( str ) ) ;
fflush ( stderr ) ;
}
void
vshEventLoop ( void * opaque )
{
vshControl * ctl = opaque ;
while ( 1 ) {
2022-02-08 16:59:30 +03:00
bool quit = false ;
VIR_WITH_MUTEX_LOCK_GUARD ( & ctl - > lock ) {
quit = ctl - > quit ;
}
2015-06-15 19:53:58 +03:00
if ( quit )
break ;
if ( virEventRunDefaultImpl ( ) < 0 )
vshReportError ( ctl ) ;
}
}
/*
* Helpers for waiting for a libvirt event .
*/
/* We want to use SIGINT to cancel a wait; but as signal handlers
* don ' t have an opaque argument , we have to use static storage . */
2020-01-17 14:16:48 +03:00
# ifndef WIN32
2015-06-15 19:53:58 +03:00
static int vshEventFd = - 1 ;
static struct sigaction vshEventOldAction ;
/* Signal handler installed in vshEventStart, removed in vshEventCleanup. */
static void
2019-10-14 15:44:29 +03:00
vshEventInt ( int sig G_GNUC_UNUSED ,
siginfo_t * siginfo G_GNUC_UNUSED ,
void * context G_GNUC_UNUSED )
2015-06-15 19:53:58 +03:00
{
char reason = VSH_EVENT_INTERRUPT ;
if ( vshEventFd > = 0 )
ignore_value ( safewrite ( vshEventFd , & reason , 1 ) ) ;
}
2020-01-17 14:16:48 +03:00
# endif /* !WIN32 */
2015-06-15 19:53:58 +03:00
/* Event loop handler used to limit length of waiting for any other event. */
void
2019-10-14 15:44:29 +03:00
vshEventTimeout ( int timer G_GNUC_UNUSED ,
2015-06-15 19:53:58 +03:00
void * opaque )
{
vshControl * ctl = opaque ;
char reason = VSH_EVENT_TIMEOUT ;
if ( ctl - > eventPipe [ 1 ] > = 0 )
ignore_value ( safewrite ( ctl - > eventPipe [ 1 ] , & reason , 1 ) ) ;
}
/**
* vshEventStart :
* @ ctl vsh command struct
* @ timeout_ms max wait time in milliseconds , or 0 for indefinite
*
* Set up a wait for a libvirt event . The wait can be canceled by
* SIGINT or by calling vshEventDone ( ) in your event handler . If
* @ timeout_ms is positive , the wait will also end if the timeout
* expires . Call vshEventWait ( ) to block the main thread ( the event
* handler runs in the event loop thread ) . When done ( including if
* there was an error registering for an event ) , use vshEventCleanup ( )
* to quit waiting . Returns 0 on success , - 1 on failure . */
int
vshEventStart ( vshControl * ctl , int timeout_ms )
{
2020-01-17 14:16:48 +03:00
# ifndef WIN32
2015-06-15 19:53:58 +03:00
struct sigaction action ;
2020-01-17 14:16:48 +03:00
assert ( vshEventFd = = - 1 ) ;
# endif /* !WIN32 */
2015-06-15 19:53:58 +03:00
assert ( ctl - > eventPipe [ 0 ] = = - 1 & & ctl - > eventPipe [ 1 ] = = - 1 & &
2020-01-17 14:16:48 +03:00
ctl - > eventTimerId > = 0 ) ;
2020-01-24 18:22:12 +03:00
if ( virPipe ( ctl - > eventPipe ) < 0 ) {
vshSaveLibvirtError ( ) ;
vshReportError ( ctl ) ;
2015-06-15 19:53:58 +03:00
return - 1 ;
}
2020-01-17 14:16:48 +03:00
# ifndef WIN32
2015-06-15 19:53:58 +03:00
vshEventFd = ctl - > eventPipe [ 1 ] ;
action . sa_sigaction = vshEventInt ;
action . sa_flags = SA_SIGINFO ;
sigemptyset ( & action . sa_mask ) ;
sigaction ( SIGINT , & action , & vshEventOldAction ) ;
2020-01-17 14:16:48 +03:00
# endif /* !WIN32 */
2015-06-15 19:53:58 +03:00
if ( timeout_ms )
virEventUpdateTimeout ( ctl - > eventTimerId , timeout_ms ) ;
return 0 ;
}
/**
* vshEventDone :
* @ ctl vsh command struct
*
* Call this from an event callback to let the main thread quit
* blocking on further events .
*/
void
vshEventDone ( vshControl * ctl )
{
char reason = VSH_EVENT_DONE ;
if ( ctl - > eventPipe [ 1 ] > = 0 )
ignore_value ( safewrite ( ctl - > eventPipe [ 1 ] , & reason , 1 ) ) ;
}
/**
* vshEventWait :
* @ ctl vsh command struct
*
* Call this in the main thread after calling vshEventStart ( ) then
* registering for one or more events . This call will block until
* SIGINT , the timeout registered at the start , or until one of your
* event handlers calls vshEventDone ( ) . Returns an enum VSH_EVENT_ *
* stating how the wait concluded , or - 1 on error .
*/
int
vshEventWait ( vshControl * ctl )
{
char buf ;
int rv ;
assert ( ctl - > eventPipe [ 0 ] > = 0 ) ;
while ( ( rv = read ( ctl - > eventPipe [ 0 ] , & buf , 1 ) ) < 0 & & errno = = EINTR ) ;
if ( rv ! = 1 ) {
if ( ! rv )
errno = EPIPE ;
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " failed to determine loop exit status: %1$s " ) ,
2020-02-26 20:57:34 +03:00
g_strerror ( errno ) ) ;
2015-06-15 19:53:58 +03:00
return - 1 ;
}
return buf ;
}
/**
* vshEventCleanup :
* @ ctl vsh control struct
*
* Call at the end of any function that has used vshEventStart ( ) , to
* tear down any remaining SIGINT or timeout handlers .
*/
void
vshEventCleanup ( vshControl * ctl )
{
2020-01-17 14:16:48 +03:00
# ifndef WIN32
2015-06-15 19:53:58 +03:00
if ( vshEventFd > = 0 ) {
sigaction ( SIGINT , & vshEventOldAction , NULL ) ;
vshEventFd = - 1 ;
}
2020-01-17 14:16:48 +03:00
# endif /* !WIN32 */
2015-06-15 19:53:58 +03:00
VIR_FORCE_CLOSE ( ctl - > eventPipe [ 0 ] ) ;
VIR_FORCE_CLOSE ( ctl - > eventPipe [ 1 ] ) ;
virEventUpdateTimeout ( ctl - > eventTimerId , - 1 ) ;
}
2020-01-24 18:22:41 +03:00
# ifdef O_SYNC
# define LOGFILE_FLAGS (O_WRONLY | O_APPEND | O_CREAT | O_SYNC)
# else
# define LOGFILE_FLAGS (O_WRONLY | O_APPEND | O_CREAT)
# endif
2015-06-15 19:53:58 +03:00
/**
* vshOpenLogFile :
*
* Open log file .
*/
void
vshOpenLogFile ( vshControl * ctl )
{
if ( ctl - > logfile = = NULL )
return ;
if ( ( ctl - > log_fd = open ( ctl - > logfile , LOGFILE_FLAGS , FILE_MODE ) ) < 0 ) {
vshError ( ctl , " %s " ,
_ ( " failed to open the log file. check the log file path " ) ) ;
exit ( EXIT_FAILURE ) ;
}
}
/**
* vshOutputLogFile :
*
* Outputting an error to log file .
*/
void
vshOutputLogFile ( vshControl * ctl , int log_level , const char * msg_format ,
va_list ap )
{
2020-07-03 02:40:16 +03:00
g_auto ( virBuffer ) buf = VIR_BUFFER_INITIALIZER ;
2021-08-10 18:09:07 +03:00
g_autofree char * str = NULL ;
2015-06-15 19:53:58 +03:00
size_t len ;
const char * lvl = " " ;
2020-01-09 17:07:15 +03:00
g_autoptr ( GDateTime ) now = g_date_time_new_now_local ( ) ;
g_autofree gchar * nowstr = NULL ;
2015-06-15 19:53:58 +03:00
if ( ctl - > log_fd = = - 1 )
return ;
/**
* create log format
*
* [ YYYY . MM . DD HH : MM : SS SIGNATURE PID ] LOG_LEVEL message
*/
2020-01-09 17:07:15 +03:00
nowstr = g_date_time_format ( now , " %Y.%m.%d %H:%M:%S " ) ;
virBufferAsprintf ( & buf , " [%s %s %d] " ,
nowstr ,
2015-06-15 19:53:58 +03:00
ctl - > progname ,
( int ) getpid ( ) ) ;
switch ( log_level ) {
case VSH_ERR_DEBUG :
lvl = LVL_DEBUG ;
break ;
case VSH_ERR_INFO :
lvl = LVL_INFO ;
break ;
case VSH_ERR_NOTICE :
lvl = LVL_INFO ;
break ;
case VSH_ERR_WARNING :
lvl = LVL_WARNING ;
break ;
case VSH_ERR_ERROR :
lvl = LVL_ERROR ;
break ;
default :
lvl = LVL_DEBUG ;
break ;
}
virBufferAsprintf ( & buf , " %s " , lvl ) ;
virBufferVasprintf ( & buf , msg_format , ap ) ;
2020-02-02 22:26:38 +03:00
virBufferTrim ( & buf , " \n " ) ;
2015-06-15 19:53:58 +03:00
virBufferAddChar ( & buf , ' \n ' ) ;
str = virBufferContentAndReset ( & buf ) ;
len = strlen ( str ) ;
/* write log */
if ( safewrite ( ctl - > log_fd , str , len ) < 0 )
goto error ;
return ;
error :
vshCloseLogFile ( ctl ) ;
vshError ( ctl , " %s " , _ ( " failed to write the log file " ) ) ;
}
/**
* vshCloseLogFile :
*
* Close log file .
*/
void
vshCloseLogFile ( vshControl * ctl )
{
/* log file close */
if ( VIR_CLOSE ( ctl - > log_fd ) < 0 ) {
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " %1$s: failed to write log file: %2$s " ) ,
2015-06-15 19:53:58 +03:00
ctl - > logfile ? ctl - > logfile : " ? " ,
2020-02-26 20:57:34 +03:00
g_strerror ( errno ) ) ;
2015-06-15 19:53:58 +03:00
}
2021-08-10 18:09:30 +03:00
g_clear_pointer ( & ctl - > logfile , g_free ) ;
2015-06-15 19:53:58 +03:00
}
# ifndef WIN32
2024-06-05 16:50:16 +03:00
static void G_GNUC_NULL_TERMINATED
2015-06-15 19:53:58 +03:00
vshPrintRaw ( vshControl * ctl , . . . )
{
va_list ap ;
char * key ;
va_start ( ap , ctl ) ;
while ( ( key = va_arg ( ap , char * ) ) ! = NULL )
vshPrint ( ctl , " %s \r \n " , key ) ;
va_end ( ap ) ;
}
/**
* vshAskReedit :
* @ msg : Question to ask user
*
* Ask user if he wants to return to previously
* edited file .
*
* Returns ' y ' if he wants to
* ' n ' if he doesn ' t want to
* ' i ' if he wants to try defining it again while ignoring validation
* ' f ' if he forcibly wants to
* - 1 on error
* 0 otherwise
*/
int
vshAskReedit ( vshControl * ctl , const char * msg , bool relax_avail )
{
int c = - 1 ;
if ( ! isatty ( STDIN_FILENO ) )
return - 1 ;
vshReportError ( ctl ) ;
if ( vshTTYMakeRaw ( ctl , false ) < 0 )
return - 1 ;
while ( true ) {
vshPrint ( ctl , " \r %s %s %s: " , msg , _ ( " Try again? " ) ,
relax_avail ? " [y,n,i,f,?] " : " [y,n,f,?] " ) ;
2019-11-18 17:15:31 +03:00
c = g_ascii_tolower ( getchar ( ) ) ;
2015-06-15 19:53:58 +03:00
if ( c = = ' ? ' ) {
vshPrintRaw ( ctl ,
" " ,
_ ( " y - yes, start editor again " ) ,
_ ( " n - no, throw away my changes " ) ,
NULL ) ;
if ( relax_avail ) {
vshPrintRaw ( ctl ,
2023-08-24 18:04:34 +03:00
_ ( " i - turn off validation and try to redefine again " ) ,
2015-06-15 19:53:58 +03:00
NULL ) ;
}
vshPrintRaw ( ctl ,
_ ( " f - force, try to redefine again " ) ,
_ ( " ? - print this help " ) ,
NULL ) ;
continue ;
} else if ( c = = ' y ' | | c = = ' n ' | | c = = ' f ' | |
( relax_avail & & c = = ' i ' ) ) {
break ;
}
}
vshTTYRestore ( ctl ) ;
vshPrint ( ctl , " \r \n " ) ;
return c ;
}
# else /* WIN32 */
int
vshAskReedit ( vshControl * ctl ,
2019-10-14 15:44:29 +03:00
const char * msg G_GNUC_UNUSED ,
bool relax_avail G_GNUC_UNUSED )
2015-06-15 19:53:58 +03:00
{
2023-08-24 18:04:34 +03:00
vshDebug ( ctl , VSH_ERR_WARNING , " %s " , _ ( " This function is not supported on WIN32 platform " ) ) ;
2015-06-15 19:53:58 +03:00
return 0 ;
}
# endif /* WIN32 */
2022-03-01 19:00:56 +03:00
void
vshEditUnlinkTempfile ( char * file )
{
if ( ! file )
return ;
ignore_value ( unlink ( file ) ) ;
g_free ( file ) ;
}
2015-06-15 19:53:58 +03:00
/* Common code for the edit / net-edit / pool-edit functions which follow. */
char *
vshEditWriteToTempFile ( vshControl * ctl , const char * doc )
{
2022-03-01 19:00:56 +03:00
g_autofree char * filename = NULL ;
g_autoptr ( vshTempFile ) ret = NULL ;
2015-06-15 19:53:58 +03:00
const char * tmpdir ;
2021-09-26 13:54:10 +03:00
VIR_AUTOCLOSE fd = - 1 ;
2015-06-15 19:53:58 +03:00
2019-08-01 15:35:56 +03:00
tmpdir = getenv ( " TMPDIR " ) ;
2022-03-01 19:00:56 +03:00
if ( ! tmpdir )
tmpdir = " /tmp " ;
filename = g_strdup_printf ( " %s/virshXXXXXX.xml " , tmpdir ) ;
fd = g_mkstemp_full ( filename , O_RDWR | O_CLOEXEC , S_IRUSR | S_IWUSR ) ;
2015-06-15 19:53:58 +03:00
if ( fd = = - 1 ) {
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " g_mkstemp_full: failed to create temporary file: %1$s " ) ,
2020-02-26 20:57:34 +03:00
g_strerror ( errno ) ) ;
2015-06-15 19:53:58 +03:00
return NULL ;
}
2022-03-01 19:00:56 +03:00
ret = g_steal_pointer ( & filename ) ;
2015-06-15 19:53:58 +03:00
if ( safewrite ( fd , doc , strlen ( doc ) ) = = - 1 ) {
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " write: %1$s: failed to write to temporary file: %2$s " ) ,
2020-02-26 20:57:34 +03:00
ret , g_strerror ( errno ) ) ;
2015-06-15 19:53:58 +03:00
return NULL ;
}
if ( VIR_CLOSE ( fd ) < 0 ) {
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " close: %1$s: failed to write or close temporary file: %2$s " ) ,
2020-02-26 20:57:34 +03:00
ret , g_strerror ( errno ) ) ;
2015-06-15 19:53:58 +03:00
return NULL ;
}
/* Temporary filename: caller frees. */
2021-08-12 12:45:03 +03:00
return g_steal_pointer ( & ret ) ;
2015-06-15 19:53:58 +03:00
}
/* Characters permitted in $EDITOR environment variable and temp filename. */
# define ACCEPTED_CHARS \
" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/_.:@ "
2020-07-24 17:41:06 +03:00
/* Hard-code default editor used as a fallback if not configured by
* VISUAL or EDITOR environment variables . */
# define DEFAULT_EDITOR "vi"
2015-06-15 19:53:58 +03:00
int
vshEditFile ( vshControl * ctl , const char * filename )
{
const char * editor ;
2021-08-10 18:09:07 +03:00
g_autoptr ( virCommand ) cmd = NULL ;
2015-06-15 19:53:58 +03:00
int outfd = STDOUT_FILENO ;
int errfd = STDERR_FILENO ;
2019-08-01 15:35:56 +03:00
editor = getenv ( " VISUAL " ) ;
2015-06-15 19:53:58 +03:00
if ( ! editor )
2019-08-01 15:35:56 +03:00
editor = getenv ( " EDITOR " ) ;
2015-06-15 19:53:58 +03:00
if ( ! editor )
editor = DEFAULT_EDITOR ;
/* Check that filename doesn't contain shell meta-characters, and
* if it does , refuse to run . Follow the Unix conventions for
* EDITOR : the user can intentionally specify command options , so
* we don ' t protect any shell metacharacters there . Lots more
* than virsh will misbehave if EDITOR has bogus contents ( which
* is why sudo scrubs it by default ) . Conversely , if the editor
* is safe , we can run it directly rather than wasting a shell .
*/
if ( strspn ( editor , ACCEPTED_CHARS ) ! = strlen ( editor ) ) {
if ( strspn ( filename , ACCEPTED_CHARS ) ! = strlen ( filename ) ) {
vshError ( ctl ,
2023-03-09 17:54:58 +03:00
_ ( " %1$s: temporary filename contains shell meta or other unacceptable characters (is $TMPDIR wrong?) " ) ,
2015-06-15 19:53:58 +03:00
filename ) ;
return - 1 ;
}
cmd = virCommandNewArgList ( " sh " , " -c " , NULL ) ;
virCommandAddArgFormat ( cmd , " %s %s " , editor , filename ) ;
} else {
cmd = virCommandNewArgList ( editor , filename , NULL ) ;
}
virCommandSetInputFD ( cmd , STDIN_FILENO ) ;
virCommandSetOutputFD ( cmd , & outfd ) ;
virCommandSetErrorFD ( cmd , & errfd ) ;
if ( virCommandRunAsync ( cmd , NULL ) < 0 | |
virCommandWait ( cmd , NULL ) < 0 ) {
vshReportError ( ctl ) ;
2021-08-10 18:12:02 +03:00
return - 1 ;
2015-06-15 19:53:58 +03:00
}
2021-08-10 18:12:02 +03:00
return 0 ;
2015-06-15 19:53:58 +03:00
}
char *
vshEditReadBackFile ( vshControl * ctl , const char * filename )
{
char * ret ;
if ( virFileReadAll ( filename , VSH_MAX_XML_FILE , & ret ) = = - 1 ) {
vshError ( ctl ,
2023-03-09 17:54:58 +03:00
_ ( " %1$s: failed to read temporary file: %2$s " ) ,
2020-02-26 20:57:34 +03:00
filename , g_strerror ( errno ) ) ;
2015-06-15 19:53:58 +03:00
return NULL ;
}
return ret ;
}
2024-02-28 18:31:35 +03:00
int
vshEditString ( vshControl * ctl ,
char * * output ,
const char * string )
{
g_autoptr ( vshTempFile ) tmp = NULL ;
char * tmpstr ;
/* Create and open the temporary file. */
if ( ! ( tmp = vshEditWriteToTempFile ( ctl , string ) ) )
return - 1 ;
/* Start the editor. */
if ( vshEditFile ( ctl , tmp ) = = - 1 )
return - 1 ;
/* Read back the edited file. */
if ( ! ( * output = vshEditReadBackFile ( ctl , tmp ) ) )
return - 1 ;
/* strip a possible newline at the end of file; some
* editors enforce a newline , this makes editing
* more convenient */
if ( ( tmpstr = strrchr ( * output , ' \n ' ) ) & &
* ( tmpstr + 1 ) = = ' \0 ' )
* tmpstr = ' \0 ' ;
return 0 ;
}
2015-06-15 19:53:58 +03:00
/* Tree listing helpers. */
static int
vshTreePrintInternal ( vshControl * ctl ,
vshTreeLookup lookup ,
void * opaque ,
int num_devices ,
int devid ,
int lastdev ,
bool root ,
2021-03-11 10:16:13 +03:00
virBuffer * indent )
2015-06-15 19:53:58 +03:00
{
size_t i ;
int nextlastdev = - 1 ;
const char * dev = ( lookup ) ( devid , false , opaque ) ;
/* Print this device, with indent if not at root */
vshPrint ( ctl , " %s%s%s \n " , virBufferCurrentContent ( indent ) ,
root ? " " : " +- " , dev ) ;
/* Update indent to show '|' or ' ' for child devices */
if ( ! root ) {
virBufferAddChar ( indent , devid = = lastdev ? ' ' : ' | ' ) ;
virBufferAddChar ( indent , ' ' ) ;
}
/* Determine the index of the last child device */
for ( i = 0 ; i < num_devices ; i + + ) {
const char * parent = ( lookup ) ( i , true , opaque ) ;
if ( parent & & STREQ ( parent , dev ) )
nextlastdev = i ;
}
/* If there is a child device, then print another blank line */
if ( nextlastdev ! = - 1 )
vshPrint ( ctl , " %s | \n " , virBufferCurrentContent ( indent ) ) ;
/* Finally print all children */
virBufferAddLit ( indent , " " ) ;
for ( i = 0 ; i < num_devices ; i + + ) {
const char * parent = ( lookup ) ( i , true , opaque ) ;
if ( parent & & STREQ ( parent , dev ) & &
vshTreePrintInternal ( ctl , lookup , opaque ,
num_devices , i , nextlastdev ,
false , indent ) < 0 )
2019-10-21 21:19:09 +03:00
return - 1 ;
2015-06-15 19:53:58 +03:00
}
2020-02-02 22:26:38 +03:00
virBufferTrim ( indent , " " ) ;
2015-06-15 19:53:58 +03:00
/* If there was no child device, and we're the last in
* a list of devices , then print another blank line */
if ( nextlastdev = = - 1 & & devid = = lastdev )
vshPrint ( ctl , " %s \n " , virBufferCurrentContent ( indent ) ) ;
if ( ! root )
2020-02-02 22:17:20 +03:00
virBufferTrimLen ( indent , 2 ) ;
2019-10-21 21:19:09 +03:00
return 0 ;
2015-06-15 19:53:58 +03:00
}
int
vshTreePrint ( vshControl * ctl , vshTreeLookup lookup , void * opaque ,
int num_devices , int devid )
{
int ret ;
2020-07-03 02:40:16 +03:00
g_auto ( virBuffer ) indent = VIR_BUFFER_INITIALIZER ;
2015-06-15 19:53:58 +03:00
ret = vshTreePrintInternal ( ctl , lookup , opaque , num_devices ,
devid , devid , true , & indent ) ;
if ( ret < 0 )
vshError ( ctl , " %s " , _ ( " Failed to complete tree listing " ) ) ;
return ret ;
}
2017-11-21 19:23:11 +03:00
/**
* vshReadlineCommandGenerator :
*
2021-09-17 11:58:28 +03:00
* Generator function for command completion . Used also for completing the
* ' - - command ' option of the ' help ' command .
2017-11-21 19:23:11 +03:00
*
2021-02-03 09:08:19 +03:00
* Returns a string list of all commands , or NULL on failure .
2015-06-15 19:53:58 +03:00
*/
2017-11-21 19:23:11 +03:00
static char * *
2021-02-04 15:46:12 +03:00
vshReadlineCommandGenerator ( void )
2015-06-15 19:53:58 +03:00
{
2021-01-26 19:22:00 +03:00
size_t grp_list_index = 0 ;
2015-06-15 19:53:58 +03:00
const vshCmdGrp * grp ;
2017-11-21 19:23:11 +03:00
size_t ret_size = 0 ;
2021-02-02 13:09:01 +03:00
g_auto ( GStrv ) ret = NULL ;
2015-06-15 19:53:58 +03:00
grp = cmdGroups ;
2021-01-26 19:22:00 +03:00
for ( grp_list_index = 0 ; grp [ grp_list_index ] . name ; grp_list_index + + ) {
const vshCmdDef * cmds = grp [ grp_list_index ] . commands ;
size_t cmd_list_index ;
2015-06-15 19:53:58 +03:00
2021-01-26 19:22:00 +03:00
for ( cmd_list_index = 0 ; cmds [ cmd_list_index ] . name ; cmd_list_index + + ) {
const char * name = cmds [ cmd_list_index ] . name ;
2024-03-02 01:14:51 +03:00
if ( cmds [ cmd_list_index ] . alias | |
2021-09-16 16:57:28 +03:00
cmds [ cmd_list_index ] . flags & VSH_CMD_FLAG_HIDDEN )
2021-01-26 19:22:00 +03:00
continue ;
2015-06-15 19:53:58 +03:00
2021-03-20 02:37:05 +03:00
VIR_REALLOC_N ( ret , ret_size + 2 ) ;
2021-02-02 13:09:01 +03:00
2021-01-26 19:22:00 +03:00
ret [ ret_size ] = g_strdup ( name ) ;
ret_size + + ;
/* Terminate the string list properly. */
ret [ ret_size ] = NULL ;
2015-06-15 19:53:58 +03:00
}
}
2021-02-02 13:09:01 +03:00
return g_steal_pointer ( & ret ) ;
2015-06-15 19:53:58 +03:00
}
2021-09-17 11:58:28 +03:00
# if WITH_READLINE
/* -----------------
* Readline stuff
* - - - - - - - - - - - - - - - - -
*/
2017-11-21 19:23:11 +03:00
static char * *
2024-04-15 17:55:18 +03:00
vshReadlineOptionsGenerator ( vshCmd * cmd )
2015-06-15 19:53:58 +03:00
{
2017-11-21 19:23:11 +03:00
size_t ret_size = 0 ;
2021-02-02 13:09:01 +03:00
g_auto ( GStrv ) ret = NULL ;
2024-04-15 17:55:18 +03:00
vshCmdOpt * n ;
2015-06-15 19:53:58 +03:00
2024-04-15 17:55:18 +03:00
for ( n = cmd - > opts ; n & & n - > def ; n + + ) {
2021-02-02 16:55:28 +03:00
/* Skip aliases, we do not report them in help output either. */
2024-04-15 17:55:18 +03:00
if ( n - > def - > type = = VSH_OT_ALIAS )
2021-02-02 16:55:28 +03:00
continue ;
2015-06-15 19:53:58 +03:00
2024-04-15 17:55:18 +03:00
/* skip already populated single-instance arguments */
if ( n - > present & & n - > def - > type ! = VSH_OT_ARGV )
2018-01-12 19:05:33 +03:00
continue ;
2021-03-20 02:37:05 +03:00
VIR_REALLOC_N ( ret , ret_size + 2 ) ;
2017-11-21 19:23:11 +03:00
2024-04-15 17:55:18 +03:00
ret [ ret_size ] = g_strdup_printf ( " --%s " , n - > def - > name ) ;
2017-11-21 19:23:11 +03:00
ret_size + + ;
/* Terminate the string list properly. */
ret [ ret_size ] = NULL ;
2015-06-15 19:53:58 +03:00
}
2021-02-02 13:09:01 +03:00
return g_steal_pointer ( & ret ) ;
2015-06-15 19:53:58 +03:00
}
2017-11-21 19:45:50 +03:00
2017-12-28 14:26:41 +03:00
static int
vshCompleterFilter ( char * * * list ,
const char * text )
{
char * * newList = NULL ;
size_t newList_len = 0 ;
size_t list_len ;
size_t i ;
if ( ! list | | ! * list )
2021-02-02 12:39:19 +03:00
return 0 ;
2017-12-28 14:26:41 +03:00
2021-02-05 20:03:26 +03:00
list_len = g_strv_length ( * list ) ;
2020-09-11 14:42:16 +03:00
newList = g_new0 ( char * , list_len + 1 ) ;
2017-12-28 14:26:41 +03:00
for ( i = 0 ; i < list_len ; i + + ) {
2024-05-27 12:06:41 +03:00
if ( text & & ! STRPREFIX ( ( * list ) [ i ] , text ) ) {
2020-09-11 14:42:16 +03:00
g_clear_pointer ( & ( * list ) [ i ] , g_free ) ;
2017-12-28 14:26:41 +03:00
continue ;
}
2019-10-16 14:43:36 +03:00
newList [ newList_len ] = g_steal_pointer ( & ( * list ) [ i ] ) ;
2017-12-28 14:26:41 +03:00
newList_len + + ;
}
2020-09-11 14:42:16 +03:00
newList = g_renew ( char * , newList , newList_len + 1 ) ;
g_free ( * list ) ;
2017-12-28 14:26:41 +03:00
* list = newList ;
return 0 ;
}
2016-07-08 15:56:05 +03:00
static char *
vshReadlineParse ( const char * text , int state )
{
2017-11-21 19:23:11 +03:00
static char * * list ;
static size_t list_index ;
char * ret = NULL ;
2016-07-08 15:56:05 +03:00
2021-01-26 11:18:21 +03:00
/* Readline calls this function until NULL is returned. On
* the very first call @ state is zero which means we should
* initialize those static variables above . On subsequent
* calls @ state is non zero . */
2016-07-08 15:56:05 +03:00
if ( ! state ) {
2021-01-26 12:23:32 +03:00
g_autoptr ( vshCmd ) partial = NULL ;
2021-01-26 11:18:21 +03:00
const vshCmdDef * cmd = NULL ;
2021-01-26 11:23:10 +03:00
g_autofree char * line = g_strdup ( rl_line_buffer ) ;
2016-07-08 15:56:05 +03:00
2022-01-28 20:42:45 +03:00
g_clear_pointer ( & list , g_strfreev ) ;
2017-11-21 19:23:11 +03:00
list_index = 0 ;
2016-09-05 12:45:09 +03:00
2021-01-26 11:23:10 +03:00
* ( line + rl_point ) = ' \0 ' ;
2016-07-08 15:56:05 +03:00
2024-04-15 12:45:14 +03:00
vshCommandStringParse ( NULL , line , & partial ) ;
2016-07-08 15:56:05 +03:00
2017-11-21 19:45:50 +03:00
if ( partial ) {
2017-11-21 17:07:34 +03:00
cmd = partial - > def ;
2017-11-21 19:45:50 +03:00
partial - > skipChecks = true ;
}
2016-07-08 15:56:05 +03:00
2017-11-21 17:07:34 +03:00
if ( cmd & & STREQ ( cmd - > name , text ) ) {
/* Corner case - some commands share prefix (e.g.
* dump and dumpxml ) . If user typed ' dump < TAB > < TAB > ' ,
* then @ text = " dump " and we want to offer command
* completion . If they typed ' dump < TAB > < TAB > ' then
* @ text = " " ( the space after the command ) and we
* want to offer options completion for dump command .
*/
cmd = NULL ;
2016-09-05 12:45:09 +03:00
}
2017-11-21 19:45:50 +03:00
2017-11-21 19:23:11 +03:00
if ( ! cmd ) {
2021-02-04 15:46:12 +03:00
list = vshReadlineCommandGenerator ( ) ;
2024-03-12 10:29:00 +03:00
} else {
bool complete_argument = false ;
/* attempt completion only when:
- there is an argument
- it has the ' data ' field filled
- it has a completer ( rules out booleans )
*/
if ( partial - > lastopt & & partial - > lastopt - > data & & partial - > lastopt - > def - > completer ) {
/* Furthermore we want to do the completion only at the point of
* user ' s cursor . This is the case if :
* - value in ' data ' is equal to ' text ' ( last component of the completed command )
* - value in ' data ' is a space when ' text ' is empty ( quirk )
*/
if ( STREQ_NULLABLE ( partial - > lastopt - > data , text ) )
complete_argument = true ;
if ( STREQ_NULLABLE ( partial - > lastopt - > data , " " ) & & * text = = ' \0 ' )
complete_argument = true ;
}
if ( complete_argument ) {
list = partial - > lastopt - > def - > completer ( autoCompleteOpaque ,
partial ,
partial - > lastopt - > def - > completer_flags ) ;
} else {
2024-04-15 17:55:18 +03:00
list = vshReadlineOptionsGenerator ( partial ) ;
2024-03-12 10:29:00 +03:00
}
2021-02-02 13:52:27 +03:00
}
/* Escape completions, if needed (i.e. argument
* we are completing wasn ' t started with a quote
* character ) . This also enables filtering done
* below to work properly . */
if ( list & &
! rl_completion_quote_character ) {
size_t i ;
for ( i = 0 ; list [ i ] ; i + + ) {
g_auto ( virBuffer ) buf = VIR_BUFFER_INITIALIZER ;
2021-01-26 11:23:24 +03:00
2021-02-02 13:52:27 +03:00
virBufferEscape ( & buf , ' \\ ' , " " , " %s " , list [ i ] ) ;
VIR_FREE ( list [ i ] ) ;
list [ i ] = virBufferContentAndReset ( & buf ) ;
2017-11-21 19:45:50 +03:00
}
2017-11-21 19:23:11 +03:00
}
2021-02-03 09:08:19 +03:00
/* For string list returned by completers we have to do
* filtering based on @ text because completers returns all
* possible strings . */
if ( vshCompleterFilter ( & list , text ) < 0 )
goto cleanup ;
2017-11-21 19:23:11 +03:00
}
if ( list ) {
2019-10-18 18:24:02 +03:00
ret = g_strdup ( list [ list_index ] ) ;
2017-11-21 19:23:11 +03:00
list_index + + ;
2017-11-21 17:07:34 +03:00
}
2017-11-21 19:45:50 +03:00
cleanup :
2017-11-21 19:23:11 +03:00
if ( ! ret ) {
2022-01-28 20:42:45 +03:00
g_clear_pointer ( & list , g_strfreev ) ;
2017-11-21 19:23:11 +03:00
list_index = 0 ;
2016-07-08 15:56:05 +03:00
}
2017-11-21 19:23:11 +03:00
return ret ;
2016-07-08 15:56:05 +03:00
}
2015-06-15 19:53:58 +03:00
static char * *
2016-12-09 12:52:29 +03:00
vshReadlineCompletion ( const char * text ,
2019-10-14 15:44:29 +03:00
int start G_GNUC_UNUSED ,
int end G_GNUC_UNUSED )
2015-06-15 19:53:58 +03:00
{
2019-10-17 11:10:10 +03:00
return rl_completion_matches ( text , vshReadlineParse ) ;
2015-06-15 19:53:58 +03:00
}
2017-11-13 15:34:54 +03:00
static int
vshReadlineCharIsQuoted ( char * line , int idx )
{
return idx > 0 & &
line [ idx - 1 ] = = ' \\ ' & &
! vshReadlineCharIsQuoted ( line , idx - 1 ) ;
}
2015-06-15 19:53:58 +03:00
# define HISTSIZE_MAX 500000
static int
vshReadlineInit ( vshControl * ctl )
{
2021-08-10 18:09:07 +03:00
g_autofree char * userdir = NULL ;
2015-06-15 19:53:58 +03:00
int max_history = 500 ;
2021-08-10 18:09:07 +03:00
g_autofree char * histsize_env = NULL ;
2015-06-15 19:53:58 +03:00
const char * histsize_str = NULL ;
2021-01-07 19:58:41 +03:00
const char * break_characters = " \t \n `@$><=;|&{( " ;
2017-11-13 15:34:54 +03:00
const char * quote_characters = " \" ' " ;
2015-06-15 19:53:58 +03:00
2024-04-25 11:15:53 +03:00
/* initialize readline stuff only once */
2024-05-04 06:12:54 +03:00
if ( autoCompleteOpaque )
2024-04-25 11:15:53 +03:00
return 0 ;
2016-09-06 15:04:37 +03:00
/* Opaque data for autocomplete callbacks. */
autoCompleteOpaque = ctl ;
2015-06-15 19:53:58 +03:00
rl_readline_name = ctl - > name ;
/* Tell the completer that we want a crack first. */
rl_attempted_completion_function = vshReadlineCompletion ;
2017-11-23 20:00:33 +03:00
rl_basic_word_break_characters = break_characters ;
2016-07-08 15:56:05 +03:00
2017-11-13 15:34:54 +03:00
rl_completer_quote_characters = quote_characters ;
rl_char_is_quoted_p = vshReadlineCharIsQuoted ;
2024-05-04 06:12:54 +03:00
/* Stuff below is needed only for interactive mode. */
if ( ! ctl - > imode ) {
return 0 ;
}
2019-10-22 16:26:14 +03:00
histsize_env = g_strdup_printf ( " %s_HISTSIZE " , ctl - > env_prefix ) ;
2015-06-15 19:53:58 +03:00
/* Limit the total size of the history buffer */
2019-08-01 15:35:56 +03:00
if ( ( histsize_str = getenv ( histsize_env ) ) ) {
2015-06-15 19:53:58 +03:00
if ( virStrToLong_i ( histsize_str , NULL , 10 , & max_history ) < 0 ) {
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " Bad $%1$s value. " ) , histsize_env ) ;
2021-08-10 18:12:02 +03:00
return - 1 ;
2015-06-15 19:53:58 +03:00
} else if ( max_history > HISTSIZE_MAX | | max_history < 0 ) {
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " $%1$s value should be between 0 and %2$d " ) ,
2015-06-15 19:53:58 +03:00
histsize_env , HISTSIZE_MAX ) ;
2021-08-10 18:12:02 +03:00
return - 1 ;
2015-06-15 19:53:58 +03:00
}
}
stifle_history ( max_history ) ;
/* Prepare to read/write history from/to the
* $ XDG_CACHE_HOME / virtshell / history file
*/
userdir = virGetUserCacheDirectory ( ) ;
2019-10-22 16:26:14 +03:00
ctl - > historydir = g_strdup_printf ( " %s/%s " , userdir , ctl - > name ) ;
2015-06-15 19:53:58 +03:00
2019-10-22 16:26:14 +03:00
ctl - > historyfile = g_strdup_printf ( " %s/history " , ctl - > historydir ) ;
2015-06-15 19:53:58 +03:00
read_history ( ctl - > historyfile ) ;
2021-08-10 18:12:02 +03:00
return 0 ;
2015-06-15 19:53:58 +03:00
}
static void
vshReadlineDeinit ( vshControl * ctl )
{
if ( ctl - > historyfile ! = NULL ) {
2021-02-26 11:17:30 +03:00
if ( g_mkdir_with_parents ( ctl - > historydir , 0755 ) < 0 & &
2015-06-15 19:53:58 +03:00
errno ! = EEXIST ) {
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " Failed to create '%1$s': %2$s " ) ,
2020-02-26 20:57:34 +03:00
ctl - > historydir , g_strerror ( errno ) ) ;
2015-06-15 19:53:58 +03:00
} else {
write_history ( ctl - > historyfile ) ;
}
}
2021-08-10 18:09:30 +03:00
g_clear_pointer ( & ctl - > historydir , g_free ) ;
g_clear_pointer ( & ctl - > historyfile , g_free ) ;
2024-08-08 15:21:20 +03:00
/* Allow vshReadlineInit() to be called again. */
autoCompleteOpaque = NULL ;
2015-06-15 19:53:58 +03:00
}
char *
2019-10-14 15:44:29 +03:00
vshReadline ( vshControl * ctl G_GNUC_UNUSED , const char * prompt )
2015-06-15 19:53:58 +03:00
{
return readline ( prompt ) ;
}
2020-09-03 11:02:52 +03:00
void
vshReadlineHistoryAdd ( const char * cmd )
{
return add_history ( cmd ) ;
}
2015-06-15 19:53:58 +03:00
# else /* !WITH_READLINE */
static int
2019-10-14 15:44:29 +03:00
vshReadlineInit ( vshControl * ctl G_GNUC_UNUSED )
2015-06-15 19:53:58 +03:00
{
/* empty */
return 0 ;
}
static void
2019-10-14 15:44:29 +03:00
vshReadlineDeinit ( vshControl * ctl G_GNUC_UNUSED )
2015-06-15 19:53:58 +03:00
{
/* empty */
}
2015-08-15 10:59:41 +03:00
char *
2019-10-21 12:11:04 +03:00
vshReadline ( vshControl * ctl G_GNUC_UNUSED ,
const char * prompt )
2015-06-15 19:53:58 +03:00
{
char line [ 1024 ] ;
char * r ;
int len ;
fputs ( prompt , stdout ) ;
2022-03-09 16:01:49 +03:00
fflush ( stdout ) ;
2015-06-15 19:53:58 +03:00
r = fgets ( line , sizeof ( line ) , stdin ) ;
if ( r = = NULL ) return NULL ; /* EOF */
/* Chomp trailing \n */
len = strlen ( r ) ;
if ( len > 0 & & r [ len - 1 ] = = ' \n ' )
r [ len - 1 ] = ' \0 ' ;
2019-10-18 18:24:02 +03:00
return g_strdup ( r ) ;
2015-06-15 19:53:58 +03:00
}
2020-09-03 11:02:52 +03:00
void
2020-09-04 12:17:30 +03:00
vshReadlineHistoryAdd ( const char * cmd G_GNUC_UNUSED )
2020-09-03 11:02:52 +03:00
{
/* empty */
}
2015-06-15 19:53:58 +03:00
# endif /* !WITH_READLINE */
/*
* Initialize debug settings .
*/
2016-07-28 13:33:21 +03:00
static int
2015-06-15 19:53:58 +03:00
vshInitDebug ( vshControl * ctl )
{
const char * debugEnv ;
if ( ctl - > debug = = VSH_DEBUG_DEFAULT ) {
2021-08-10 18:09:07 +03:00
g_autofree char * env = g_strdup_printf ( " %s_DEBUG " , ctl - > env_prefix ) ;
2016-07-28 13:54:16 +03:00
2015-06-15 19:53:58 +03:00
/* log level not set from commandline, check env variable */
2019-08-01 15:35:56 +03:00
debugEnv = getenv ( env ) ;
2015-06-15 19:53:58 +03:00
if ( debugEnv ) {
int debug ;
if ( virStrToLong_i ( debugEnv , NULL , 10 , & debug ) < 0 | |
debug < VSH_ERR_DEBUG | | debug > VSH_ERR_ERROR ) {
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " %1$s_DEBUG not set with a valid numeric value " ) ,
2016-11-11 15:39:45 +03:00
ctl - > env_prefix ) ;
2015-06-15 19:53:58 +03:00
} else {
ctl - > debug = debug ;
}
}
}
if ( ctl - > logfile = = NULL ) {
2021-08-10 18:09:07 +03:00
g_autofree char * env = g_strdup_printf ( " %s_LOG_FILE " , ctl - > env_prefix ) ;
2016-07-28 13:54:16 +03:00
2015-06-15 19:53:58 +03:00
/* log file not set from cmdline */
2019-08-01 15:35:56 +03:00
debugEnv = getenv ( env ) ;
2015-06-15 19:53:58 +03:00
if ( debugEnv & & * debugEnv ) {
2019-10-18 18:24:02 +03:00
ctl - > logfile = g_strdup ( debugEnv ) ;
2015-06-15 19:53:58 +03:00
vshOpenLogFile ( ctl ) ;
}
}
2016-07-28 13:33:21 +03:00
return 0 ;
2015-06-15 19:53:58 +03:00
}
/*
* Initialize global data
*/
2015-09-03 17:52:44 +03:00
bool
2024-03-04 17:51:28 +03:00
vshInit ( vshControl * ctl , const vshCmdGrp * groups )
2015-06-15 19:53:58 +03:00
{
if ( ! ctl - > hooks ) {
vshError ( ctl , " %s " , _ ( " client hooks cannot be NULL " ) ) ;
2015-09-03 17:52:44 +03:00
return false ;
2015-06-15 19:53:58 +03:00
}
2024-03-04 17:51:28 +03:00
if ( ! groups ) {
vshError ( ctl , " %s " , _ ( " command groups must be non-NULL " ) ) ;
2015-09-03 17:52:44 +03:00
return false ;
2015-06-15 19:53:58 +03:00
}
cmdGroups = groups ;
2016-07-28 13:33:21 +03:00
if ( vshInitDebug ( ctl ) < 0 | |
2024-05-04 06:12:54 +03:00
vshReadlineInit ( ctl ) < 0 )
2015-09-03 17:52:44 +03:00
return false ;
2015-06-15 19:53:58 +03:00
2015-09-03 17:52:44 +03:00
return true ;
2015-06-15 19:53:58 +03:00
}
2015-09-03 17:59:01 +03:00
bool
vshInitReload ( vshControl * ctl )
{
2024-03-04 17:51:28 +03:00
if ( ! cmdGroups ) {
vshError ( ctl , " %s " , _ ( " command groups is NULL run vshInit before reloading " ) ) ;
2015-09-03 17:59:01 +03:00
return false ;
}
2016-07-28 13:33:21 +03:00
if ( vshInitDebug ( ctl ) < 0 )
return false ;
2015-09-03 17:59:01 +03:00
if ( ctl - > imode )
vshReadlineDeinit ( ctl ) ;
2024-05-04 06:12:54 +03:00
if ( vshReadlineInit ( ctl ) < 0 )
2015-09-03 17:59:01 +03:00
return false ;
return true ;
}
2015-06-15 19:53:58 +03:00
void
vshDeinit ( vshControl * ctl )
{
2016-09-28 22:01:55 +03:00
/* NB: Don't make calling of vshReadlineDeinit conditional on active
* interactive mode . */
vshReadlineDeinit ( ctl ) ;
2015-06-15 19:53:58 +03:00
vshCloseLogFile ( ctl ) ;
}
/* -----------------------------------------------
* Generic commands available to use by any client
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
2021-09-16 17:07:19 +03:00
static char * *
vshCompleteHelpCommand ( vshControl * ctl G_GNUC_UNUSED ,
const vshCmd * cmd G_GNUC_UNUSED ,
unsigned int completerflags G_GNUC_UNUSED )
{
return vshReadlineCommandGenerator ( ) ;
}
2015-06-15 19:53:58 +03:00
const vshCmdOptDef opts_help [ ] = {
{ . name = " command " ,
. type = VSH_OT_STRING ,
2024-03-14 18:58:40 +03:00
. positional = true ,
2021-09-16 17:07:19 +03:00
. completer = vshCompleteHelpCommand ,
2015-06-15 19:53:58 +03:00
. help = N_ ( " Prints global help, command specific help, or help for a group of related commands " )
} ,
{ . name = NULL }
} ;
2024-03-04 15:21:50 +03:00
const vshCmdInfo info_help = {
. help = N_ ( " print help " ) ,
. desc = N_ ( " Prints global help, command specific help, or help for a \n "
" group of related commands " ) ,
2015-06-15 19:53:58 +03:00
} ;
bool
cmdHelp ( vshControl * ctl , const vshCmd * cmd )
2016-06-17 20:27:08 +03:00
{
2018-01-25 18:08:46 +03:00
const vshCmdDef * def = NULL ;
const vshCmdGrp * grp = NULL ;
2015-06-15 19:53:58 +03:00
const char * name = NULL ;
2015-12-03 15:47:56 +03:00
if ( vshCommandOptStringQuiet ( ctl , cmd , " command " , & name ) < = 0 ) {
2015-06-15 19:53:58 +03:00
vshPrint ( ctl , " %s " , _ ( " Grouped commands: \n \n " ) ) ;
for ( grp = cmdGroups ; grp - > name ; grp + + ) {
2023-03-09 17:54:58 +03:00
vshPrint ( ctl , _ ( " %1$s (help keyword '%2$s'): \n " ) , grp - > name ,
2015-06-15 19:53:58 +03:00
grp - > keyword ) ;
for ( def = grp - > commands ; def - > name ; def + + ) {
2024-03-02 01:14:51 +03:00
if ( def - > alias | |
2021-09-16 16:57:28 +03:00
def - > flags & VSH_CMD_FLAG_HIDDEN )
2015-06-15 19:53:58 +03:00
continue ;
2024-03-04 15:21:50 +03:00
vshPrint ( ctl , " %-30s %s \n " , def - > name , _ ( def - > info - > help ) ) ;
2015-06-15 19:53:58 +03:00
}
vshPrint ( ctl , " \n " ) ;
}
return true ;
}
2018-01-25 18:08:46 +03:00
if ( ( def = vshCmddefSearch ( name ) ) ) {
2024-03-02 01:14:51 +03:00
if ( def - > alias )
2018-01-25 18:13:37 +03:00
def = vshCmddefSearch ( def - > alias ) ;
2024-03-04 17:51:28 +03:00
}
if ( def ) {
2020-11-12 15:42:45 +03:00
return vshCmddefHelp ( def ) ;
2018-01-25 18:08:46 +03:00
} else if ( ( grp = vshCmdGrpSearch ( name ) ) ) {
return vshCmdGrpHelp ( ctl , grp ) ;
2015-06-15 19:53:58 +03:00
} else {
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " command or command group '%1$s' doesn't exist " ) , name ) ;
2015-06-15 19:53:58 +03:00
return false ;
}
}
const vshCmdOptDef opts_cd [ ] = {
{ . name = " dir " ,
. type = VSH_OT_STRING ,
2024-03-14 18:58:40 +03:00
. positional = true ,
2015-06-15 19:53:58 +03:00
. help = N_ ( " directory to switch to (default: home or else root) " )
} ,
{ . name = NULL }
} ;
2024-03-04 15:21:50 +03:00
const vshCmdInfo info_cd = {
. help = N_ ( " change the current directory " ) ,
. desc = N_ ( " Change the current directory. " ) ,
2015-06-15 19:53:58 +03:00
} ;
bool
cmdCd ( vshControl * ctl , const vshCmd * cmd )
{
const char * dir = NULL ;
2019-12-19 11:09:02 +03:00
g_autofree char * dir_malloced = NULL ;
2015-06-15 19:53:58 +03:00
2015-12-03 15:47:56 +03:00
if ( vshCommandOptStringQuiet ( ctl , cmd , " dir " , & dir ) < = 0 )
2015-06-15 19:53:58 +03:00
dir = dir_malloced = virGetUserDirectory ( ) ;
if ( ! dir )
dir = " / " ;
if ( chdir ( dir ) = = - 1 ) {
2023-03-09 17:54:58 +03:00
vshError ( ctl , _ ( " cd: %1$s: %2$s " ) ,
2020-02-26 20:57:34 +03:00
g_strerror ( errno ) , dir ) ;
2019-12-19 11:09:02 +03:00
return false ;
2015-06-15 19:53:58 +03:00
}
2019-12-19 11:09:02 +03:00
return true ;
2015-06-15 19:53:58 +03:00
}
const vshCmdOptDef opts_echo [ ] = {
{ . name = " shell " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " escape for shell use " )
} ,
{ . name = " xml " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " escape for XML use " )
} ,
2021-08-11 16:22:59 +03:00
{ . name = " split " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " split each argument on ','; ',,' is an escape sequence " )
} ,
2019-03-24 03:02:13 +03:00
{ . name = " err " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " output to stderr " ) ,
} ,
2015-06-15 19:53:58 +03:00
{ . name = " str " ,
. type = VSH_OT_ALIAS ,
. help = " string "
} ,
{ . name = " hi " ,
. type = VSH_OT_ALIAS ,
. help = " string=hello "
} ,
2024-04-03 08:07:57 +03:00
{ . name = " prefix " ,
. type = VSH_OT_STRING ,
. help = N_ ( " prefix the message " )
} ,
2015-06-15 19:53:58 +03:00
{ . name = " string " ,
. type = VSH_OT_ARGV ,
2024-03-05 17:07:47 +03:00
. positional = true ,
2015-06-15 19:53:58 +03:00
. help = N_ ( " arguments to echo " )
} ,
{ . name = NULL }
} ;
2024-03-04 15:21:50 +03:00
const vshCmdInfo info_echo = {
. help = N_ ( " echo arguments. Used for internal testing. " ) ,
. desc = N_ ( " Echo back arguments, possibly with quoting. Used for internal testing. " ) ,
2015-06-15 19:53:58 +03:00
} ;
/* Exists mainly for debugging virsh, but also handy for adding back
* quotes for later evaluation .
*/
bool
cmdEcho ( vshControl * ctl , const vshCmd * cmd )
{
2021-08-11 11:36:04 +03:00
bool shell = vshCommandOptBool ( cmd , " shell " ) ;
bool xml = vshCommandOptBool ( cmd , " xml " ) ;
bool err = vshCommandOptBool ( cmd , " err " ) ;
2021-08-11 16:22:59 +03:00
bool split = vshCommandOptBool ( cmd , " split " ) ;
2024-04-03 08:07:57 +03:00
const char * prefix ;
2021-08-10 18:09:07 +03:00
g_autofree char * arg = NULL ;
2020-07-03 02:40:16 +03:00
g_auto ( virBuffer ) buf = VIR_BUFFER_INITIALIZER ;
2024-03-06 19:26:56 +03:00
const char * * o ;
2015-06-15 19:53:58 +03:00
2021-08-11 11:36:04 +03:00
VSH_EXCLUSIVE_OPTIONS_VAR ( shell , xml ) ;
2021-08-11 16:22:59 +03:00
VSH_EXCLUSIVE_OPTIONS_VAR ( shell , split ) ;
VSH_EXCLUSIVE_OPTIONS_VAR ( xml , split ) ;
2015-06-15 19:53:58 +03:00
2024-04-03 08:07:57 +03:00
ignore_value ( vshCommandOptString ( ctl , cmd , " prefix " , & prefix ) ) ;
if ( prefix )
virBufferAsprintf ( & buf , " %s " , prefix ) ;
2024-03-06 19:26:56 +03:00
for ( o = vshCommandOptArgv ( cmd , " string " ) ; o & & * o ; o + + ) {
const char * curr = * o ;
2015-06-15 19:53:58 +03:00
if ( xml ) {
2021-08-11 12:10:12 +03:00
virBufferEscapeString ( & buf , " %s " , curr ) ;
} else if ( shell ) {
virBufferEscapeShell ( & buf , curr ) ;
2021-08-11 16:22:59 +03:00
} else if ( split ) {
g_auto ( GStrv ) spl = NULL ;
GStrv n ;
vshStringToArray ( curr , & spl ) ;
for ( n = spl ; * n ; n + + )
virBufferAsprintf ( & buf , " %s \n " , * n ) ;
2015-06-15 19:53:58 +03:00
} else {
2021-08-11 12:10:12 +03:00
virBufferAdd ( & buf , curr , - 1 ) ;
2015-06-15 19:53:58 +03:00
}
2021-08-11 12:10:12 +03:00
virBufferAddChar ( & buf , ' ' ) ;
2015-06-15 19:53:58 +03:00
}
2021-08-11 12:10:12 +03:00
virBufferTrim ( & buf , " " ) ;
2015-06-15 19:53:58 +03:00
arg = virBufferContentAndReset ( & buf ) ;
2019-03-24 03:02:13 +03:00
if ( arg ) {
if ( err )
vshError ( ctl , " %s " , arg ) ;
else
vshPrint ( ctl , " %s " , arg ) ;
}
2015-06-15 19:53:58 +03:00
return true ;
}
2024-03-04 15:21:50 +03:00
const vshCmdInfo info_pwd = {
. help = N_ ( " print the current directory " ) ,
. desc = N_ ( " Print the current directory. " ) ,
2015-06-15 19:53:58 +03:00
} ;
bool
2019-10-14 15:44:29 +03:00
cmdPwd ( vshControl * ctl , const vshCmd * cmd G_GNUC_UNUSED )
2015-06-15 19:53:58 +03:00
{
2019-12-20 19:51:16 +03:00
g_autofree char * cwd = g_get_current_dir ( ) ;
2015-06-15 19:53:58 +03:00
2023-03-09 17:54:58 +03:00
vshPrint ( ctl , _ ( " %1$s \n " ) , cwd ) ;
2015-06-15 19:53:58 +03:00
2019-12-20 19:51:16 +03:00
return true ;
2015-06-15 19:53:58 +03:00
}
2024-03-04 15:21:50 +03:00
const vshCmdInfo info_quit = {
. help = N_ ( " quit this interactive terminal " ) ,
. desc = " " ,
2015-06-15 19:53:58 +03:00
} ;
bool
2019-10-14 15:44:29 +03:00
cmdQuit ( vshControl * ctl , const vshCmd * cmd G_GNUC_UNUSED )
2015-06-15 19:53:58 +03:00
{
ctl - > imode = false ;
return true ;
}
2016-09-14 10:50:22 +03:00
/* -----------------
* Command self - test
* - - - - - - - - - - - - - - - - - */
2021-03-04 17:47:28 +03:00
const vshCmdOptDef opts_selftest [ ] = {
{ . name = " completers-missing " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " output the list of options which are missing completers " )
} ,
2024-03-02 01:01:54 +03:00
{ . name = " dump-help " ,
. type = VSH_OT_BOOL ,
. help = N_ ( " output help for each command " )
} ,
2021-03-04 17:47:28 +03:00
{ . name = NULL }
} ;
2024-03-04 15:21:50 +03:00
const vshCmdInfo info_selftest = {
. help = N_ ( " internal command for testing virt shells " ) ,
. desc = N_ ( " internal use only " ) ,
2016-09-14 10:50:22 +03:00
} ;
bool
2021-03-04 17:47:28 +03:00
cmdSelfTest ( vshControl * ctl , const vshCmd * cmd )
2016-09-14 10:50:22 +03:00
{
const vshCmdGrp * grp ;
const vshCmdDef * def ;
2021-03-04 17:47:28 +03:00
bool completers = vshCommandOptBool ( cmd , " completers-missing " ) ;
2024-03-02 01:01:54 +03:00
bool dumphelp = vshCommandOptBool ( cmd , " dump-help " ) ;
2016-09-14 10:50:22 +03:00
for ( grp = cmdGroups ; grp - > name ; grp + + ) {
for ( def = grp - > commands ; def - > name ; def + + ) {
2024-03-02 01:01:54 +03:00
if ( dumphelp & & ! def - > alias )
vshCmddefHelp ( def ) ;
2024-03-06 00:37:43 +03:00
if ( vshCmddefCheckInternals ( ctl , def , completers ) < 0 )
2016-09-14 10:50:22 +03:00
return false ;
}
}
return true ;
}
2017-11-01 17:34:14 +03:00
/* ----------------------
* Autocompletion command
* - - - - - - - - - - - - - - - - - - - - - - */
const vshCmdOptDef opts_complete [ ] = {
{ . name = " string " ,
. type = VSH_OT_ARGV ,
2024-03-05 17:07:47 +03:00
. positional = true ,
2024-03-11 15:17:50 +03:00
. allowEmpty = true ,
2017-11-01 17:34:14 +03:00
. help = N_ ( " partial string to autocomplete " )
} ,
{ . name = NULL }
} ;
2024-03-04 15:21:50 +03:00
const vshCmdInfo info_complete = {
. help = N_ ( " internal command for autocompletion " ) ,
. desc = N_ ( " internal use only " ) ,
2017-11-01 17:34:14 +03:00
} ;
2018-01-14 13:43:51 +03:00
# ifdef WITH_READLINE
2024-03-26 18:28:33 +03:00
2024-05-23 16:49:05 +03:00
static virOnceControl vshCmdCompleteCloseStdinStderrOnce = VIR_ONCE_CONTROL_INITIALIZER ;
2024-03-26 18:28:33 +03:00
static void
2024-05-23 16:49:05 +03:00
vshCmdCompleteCloseStdinStderr ( void )
2024-03-26 18:28:33 +03:00
{
/* In non-interactive mode which is how the 'complete' command is intended
* to be used we need to ensure that any authentication callback will not
2024-05-23 16:49:05 +03:00
* attempt to read any input which would break the completion . Similarly ,
* printing anything onto stderr should be avoided . */
2024-03-26 18:28:33 +03:00
int stdin_fileno = STDIN_FILENO ;
2024-05-23 16:49:05 +03:00
int stderr_fileno = STDERR_FILENO ;
2024-03-26 18:28:33 +03:00
VIR_FORCE_CLOSE ( stdin_fileno ) ;
2024-05-23 16:49:05 +03:00
VIR_FORCE_CLOSE ( stderr_fileno ) ;
2024-03-26 18:28:33 +03:00
}
2017-11-01 17:34:14 +03:00
bool
cmdComplete ( vshControl * ctl , const vshCmd * cmd )
{
const vshClientHooks * hooks = ctl - > hooks ;
2024-03-06 19:26:56 +03:00
const char * lastArg = NULL ;
const char * * args = NULL ;
2024-05-27 11:39:21 +03:00
char * old_rl_line_buffer = NULL ;
2021-11-01 12:34:10 +03:00
g_auto ( GStrv ) matches = NULL ;
char * * iter ;
2017-11-01 17:34:14 +03:00
2024-03-06 19:26:56 +03:00
/* The completer needs also the last component */
for ( args = vshCommandOptArgv ( cmd , " string " ) ; args & & * args ; args + + )
lastArg = * args ;
2017-11-01 17:34:14 +03:00
/* This command is flagged VSH_CMD_FLAG_NOCONNECT because we
* need to prevent auth hooks reading any input . Therefore , we
* have to close stdin and then connect ourselves . */
2024-03-26 18:28:33 +03:00
if ( ! ctl - > imode ) {
2024-05-23 16:49:05 +03:00
if ( virOnce ( & vshCmdCompleteCloseStdinStderrOnce , vshCmdCompleteCloseStdinStderr ) < 0 )
2024-03-26 18:28:33 +03:00
return false ;
}
2017-11-01 17:34:14 +03:00
if ( ! ( hooks & & hooks - > connHandler & & hooks - > connHandler ( ctl ) ) )
2022-01-14 16:05:52 +03:00
return false ;
2017-11-01 17:34:14 +03:00
vshReadlineInit ( ctl ) ;
2024-05-27 11:39:21 +03:00
old_rl_line_buffer = g_steal_pointer ( & rl_line_buffer ) ;
2024-03-06 19:26:56 +03:00
if ( ! ( rl_line_buffer = g_strdup ( vshCommandOptArgvString ( cmd , " string " ) ) ) )
2019-10-20 14:49:46 +03:00
rl_line_buffer = g_strdup ( " " ) ;
2017-11-01 17:34:14 +03:00
/* rl_point is current cursor position in rl_line_buffer.
* In our case it ' s at the end of the whole line . */
rl_point = strlen ( rl_line_buffer ) ;
2024-03-06 19:26:56 +03:00
matches = vshReadlineCompletion ( lastArg , 0 , 0 ) ;
2024-04-25 11:05:42 +03:00
g_clear_pointer ( & rl_line_buffer , g_free ) ;
2024-05-27 11:39:21 +03:00
rl_line_buffer = g_steal_pointer ( & old_rl_line_buffer ) ;
2024-04-25 11:05:42 +03:00
if ( ! matches )
2022-01-14 16:05:52 +03:00
return false ;
2017-11-01 17:34:14 +03:00
2018-05-11 12:13:10 +03:00
for ( iter = matches ; * iter ; iter + + ) {
if ( iter = = matches & & matches [ 1 ] )
continue ;
2017-11-01 17:34:14 +03:00
printf ( " %s \n " , * iter ) ;
2018-05-11 12:13:10 +03:00
}
2017-11-01 17:34:14 +03:00
2022-01-14 16:05:52 +03:00
return true ;
2017-11-01 17:34:14 +03:00
}
2018-01-14 13:43:51 +03:00
# else /* !WITH_READLINE */
bool
2019-10-14 15:44:29 +03:00
cmdComplete ( vshControl * ctl G_GNUC_UNUSED ,
const vshCmd * cmd G_GNUC_UNUSED )
2018-01-14 13:43:51 +03:00
{
return false ;
}
# endif /* !WITH_READLINE */