2018-03-22 19:07:43 +03:00
/*
* testutilsqemuschema . c : helper functions for QEMU QAPI schema testing
*
* 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 "testutils.h"
2018-07-09 16:44:52 +03:00
# include "testutilsqemu.h"
2018-03-22 19:07:43 +03:00
# include "testutilsqemuschema.h"
# include "qemu/qemu_qapi.h"
2020-04-29 18:31:20 +03:00
struct testQEMUSchemaValidateCtxt {
2020-10-22 20:04:18 +03:00
GHashTable * schema ;
2021-03-11 10:16:13 +03:00
virBuffer * debug ;
2020-04-29 18:52:43 +03:00
bool allowDeprecated ;
2021-10-15 13:06:14 +03:00
bool allowIncomplete ; /* allow members not (yet) covered by the schema */
2020-04-29 18:31:20 +03:00
} ;
2021-09-17 17:37:10 +03:00
static int
testQEMUSchemaValidateDeprecated ( virJSONValue * root ,
const char * name ,
struct testQEMUSchemaValidateCtxt * ctxt )
{
virJSONValue * features = virJSONValueObjectGetArray ( root , " features " ) ;
size_t nfeatures ;
size_t i ;
if ( ! features )
return 0 ;
nfeatures = virJSONValueArraySize ( features ) ;
for ( i = 0 ; i < nfeatures ; i + + ) {
virJSONValue * cur = virJSONValueArrayGet ( features , i ) ;
const char * curstr ;
if ( ! cur | |
! ( curstr = virJSONValueGetString ( cur ) ) ) {
virBufferAsprintf ( ctxt - > debug , " ERROR: features of '%s' are malformed " , name ) ;
return - 2 ;
}
if ( STREQ ( curstr , " deprecated " ) ) {
if ( ctxt - > allowDeprecated ) {
virBufferAsprintf ( ctxt - > debug , " WARNING: '%s' is deprecated " , name ) ;
if ( virTestGetVerbose ( ) )
g_fprintf ( stderr , " \n WARNING: '%s' is deprecated \n " , name ) ;
return 0 ;
} else {
virBufferAsprintf ( ctxt - > debug , " ERROR: '%s' is deprecated " , name ) ;
return - 1 ;
}
}
}
return 0 ;
}
2018-03-22 19:07:43 +03:00
static int
2021-03-11 10:16:13 +03:00
testQEMUSchemaValidateRecurse ( virJSONValue * obj ,
virJSONValue * root ,
2020-04-29 18:31:20 +03:00
struct testQEMUSchemaValidateCtxt * ctxt ) ;
2018-03-22 19:07:43 +03:00
static int
2021-03-11 10:16:13 +03:00
testQEMUSchemaValidateBuiltin ( virJSONValue * obj ,
virJSONValue * root ,
2020-04-29 18:31:20 +03:00
struct testQEMUSchemaValidateCtxt * ctxt )
2018-03-22 19:07:43 +03:00
{
const char * t = virJSONValueObjectGetString ( root , " json-type " ) ;
const char * s = NULL ;
bool b = false ;
int ret = - 1 ;
if ( STREQ_NULLABLE ( t , " value " ) ) {
s = " {any} " ;
ret = 0 ;
goto cleanup ;
}
switch ( virJSONValueGetType ( obj ) ) {
case VIR_JSON_TYPE_STRING :
if ( STRNEQ_NULLABLE ( t , " string " ) )
goto cleanup ;
s = virJSONValueGetString ( obj ) ;
break ;
case VIR_JSON_TYPE_NUMBER :
if ( STRNEQ_NULLABLE ( t , " int " ) & &
STRNEQ_NULLABLE ( t , " number " ) )
goto cleanup ;
s = " {number} " ;
break ;
case VIR_JSON_TYPE_BOOLEAN :
if ( STRNEQ_NULLABLE ( t , " boolean " ) )
goto cleanup ;
virJSONValueGetBoolean ( obj , & b ) ;
if ( b )
s = " true " ;
else
s = " false " ;
break ;
case VIR_JSON_TYPE_NULL :
if ( STRNEQ_NULLABLE ( t , " null " ) )
goto cleanup ;
break ;
case VIR_JSON_TYPE_OBJECT :
case VIR_JSON_TYPE_ARRAY :
goto cleanup ;
}
ret = 0 ;
cleanup :
if ( ret = = 0 )
2020-04-29 18:31:20 +03:00
virBufferAsprintf ( ctxt - > debug , " '%s': OK " , s ) ;
2018-03-22 19:07:43 +03:00
else
2020-04-29 18:31:20 +03:00
virBufferAsprintf ( ctxt - > debug , " ERROR: expected type '%s', actual type %d " ,
2018-03-22 19:07:43 +03:00
t , virJSONValueGetType ( obj ) ) ;
return ret ;
}
struct testQEMUSchemaValidateObjectMemberData {
2021-03-11 10:16:13 +03:00
virJSONValue * rootmembers ;
2020-04-29 18:31:20 +03:00
struct testQEMUSchemaValidateCtxt * ctxt ;
2018-03-22 19:07:43 +03:00
bool missingMandatory ;
} ;
2021-03-11 10:16:13 +03:00
static virJSONValue *
2018-03-22 19:07:43 +03:00
testQEMUSchemaStealObjectMemberByName ( const char * name ,
2021-03-11 10:16:13 +03:00
virJSONValue * members )
2018-03-22 19:07:43 +03:00
{
2021-03-11 10:16:13 +03:00
virJSONValue * member ;
virJSONValue * ret = NULL ;
2018-03-22 19:07:43 +03:00
size_t i ;
2018-04-20 00:29:02 +03:00
for ( i = 0 ; i < virJSONValueArraySize ( members ) ; i + + ) {
2018-03-22 19:07:43 +03:00
member = virJSONValueArrayGet ( members , i ) ;
if ( STREQ_NULLABLE ( name , virJSONValueObjectGetString ( member , " name " ) ) ) {
ret = virJSONValueArraySteal ( members , i ) ;
break ;
}
}
return ret ;
}
static int
testQEMUSchemaValidateObjectMember ( const char * key ,
2021-03-11 10:16:13 +03:00
virJSONValue * value ,
2018-03-22 19:07:43 +03:00
void * opaque )
{
struct testQEMUSchemaValidateObjectMemberData * data = opaque ;
2020-04-29 18:20:07 +03:00
g_autoptr ( virJSONValue ) keymember = NULL ;
2018-03-22 19:07:43 +03:00
const char * keytype ;
2021-03-11 10:16:13 +03:00
virJSONValue * keyschema = NULL ;
2020-04-29 18:20:07 +03:00
int rc ;
2018-03-22 19:07:43 +03:00
2020-04-29 18:31:20 +03:00
virBufferStrcat ( data - > ctxt - > debug , key , " : " , NULL ) ;
2018-03-22 19:07:43 +03:00
/* lookup 'member' entry for key */
if ( ! ( keymember = testQEMUSchemaStealObjectMemberByName ( key , data - > rootmembers ) ) ) {
2021-10-15 13:06:14 +03:00
if ( data - > ctxt - > allowIncomplete ) {
virBufferAddLit ( data - > ctxt - > debug , " schema missing - OK(waived) \n " ) ;
return 0 ;
}
2020-04-29 18:31:20 +03:00
virBufferAddLit ( data - > ctxt - > debug , " ERROR: attribute not in schema \n " ) ;
2020-04-29 18:20:07 +03:00
return - 1 ;
2018-03-22 19:07:43 +03:00
}
/* lookup schema entry for keytype */
if ( ! ( keytype = virJSONValueObjectGetString ( keymember , " type " ) ) | |
2020-04-29 18:31:20 +03:00
! ( keyschema = virHashLookup ( data - > ctxt - > schema , keytype ) ) ) {
virBufferAsprintf ( data - > ctxt - > debug , " ERROR: can't find schema for type '%s' \n " ,
2018-03-22 19:07:43 +03:00
NULLSTR ( keytype ) ) ;
2020-04-29 18:20:07 +03:00
return - 2 ;
2018-03-22 19:07:43 +03:00
}
/* recurse */
2020-04-29 18:31:20 +03:00
rc = testQEMUSchemaValidateRecurse ( value , keyschema , data - > ctxt ) ;
2018-03-22 19:07:43 +03:00
2020-04-29 18:31:20 +03:00
virBufferAddLit ( data - > ctxt - > debug , " \n " ) ;
2020-04-29 18:20:07 +03:00
return rc ;
2018-03-22 19:07:43 +03:00
}
static int
2019-10-14 15:45:03 +03:00
testQEMUSchemaValidateObjectMergeVariantMember ( size_t pos G_GNUC_UNUSED ,
2021-03-11 10:16:13 +03:00
virJSONValue * item ,
2018-03-22 19:07:43 +03:00
void * opaque )
{
2021-03-11 10:16:13 +03:00
virJSONValue * array = opaque ;
2021-02-12 13:36:30 +03:00
g_autoptr ( virJSONValue ) copy = NULL ;
2018-03-22 19:07:43 +03:00
if ( ! ( copy = virJSONValueCopy ( item ) ) )
return - 1 ;
2021-02-11 19:57:45 +03:00
if ( virJSONValueArrayAppend ( array , & copy ) < 0 )
2018-03-22 19:07:43 +03:00
return - 1 ;
return 1 ;
}
/**
* testQEMUSchemaValidateObjectMergeVariant :
*
* Merges schema of variant @ variantname in @ root into @ root and removes the
* ' variants ' array from @ root .
*/
static int
2021-03-11 10:16:13 +03:00
testQEMUSchemaValidateObjectMergeVariant ( virJSONValue * root ,
2018-03-22 19:07:43 +03:00
const char * variantfield ,
const char * variantname ,
2020-04-29 18:31:20 +03:00
struct testQEMUSchemaValidateCtxt * ctxt )
2018-03-22 19:07:43 +03:00
{
size_t i ;
2020-04-29 18:20:07 +03:00
g_autoptr ( virJSONValue ) variants = NULL ;
2021-03-11 10:16:13 +03:00
virJSONValue * variant ;
virJSONValue * variantschema ;
virJSONValue * variantschemamembers ;
virJSONValue * rootmembers ;
2018-03-22 19:07:43 +03:00
const char * varianttype = NULL ;
if ( ! ( variants = virJSONValueObjectStealArray ( root , " variants " ) ) ) {
2020-04-29 18:31:20 +03:00
virBufferAddLit ( ctxt - > debug , " ERROR: missing 'variants' in schema \n " ) ;
2018-03-22 19:07:43 +03:00
return - 2 ;
}
2018-04-20 00:29:02 +03:00
for ( i = 0 ; i < virJSONValueArraySize ( variants ) ; i + + ) {
2018-03-22 19:07:43 +03:00
variant = virJSONValueArrayGet ( variants , i ) ;
if ( STREQ_NULLABLE ( variantname ,
virJSONValueObjectGetString ( variant , " case " ) ) ) {
varianttype = virJSONValueObjectGetString ( variant , " type " ) ;
break ;
}
}
if ( ! varianttype ) {
2020-04-29 18:31:20 +03:00
virBufferAsprintf ( ctxt - > debug , " ERROR: variant '%s' for discriminator '%s' not found \n " ,
2018-03-22 19:07:43 +03:00
variantname , variantfield ) ;
2020-04-29 18:20:07 +03:00
return - 1 ;
2018-03-22 19:07:43 +03:00
}
2020-04-29 18:31:20 +03:00
if ( ! ( variantschema = virHashLookup ( ctxt - > schema , varianttype ) ) | |
2018-03-22 19:07:43 +03:00
! ( variantschemamembers = virJSONValueObjectGetArray ( variantschema , " members " ) ) ) {
2020-04-29 18:31:20 +03:00
virBufferAsprintf ( ctxt - > debug ,
2018-03-22 19:07:43 +03:00
" ERROR: missing schema or schema members for variant '%s'(%s) \n " ,
variantname , varianttype ) ;
2020-04-29 18:20:07 +03:00
return - 2 ;
2018-03-22 19:07:43 +03:00
}
rootmembers = virJSONValueObjectGetArray ( root , " members " ) ;
if ( virJSONValueArrayForeachSteal ( variantschemamembers ,
testQEMUSchemaValidateObjectMergeVariantMember ,
rootmembers ) < 0 ) {
2020-04-29 18:20:07 +03:00
return - 2 ;
2018-03-22 19:07:43 +03:00
}
2020-04-29 18:20:07 +03:00
return 0 ;
2018-03-22 19:07:43 +03:00
}
static int
2019-10-14 15:45:03 +03:00
testQEMUSchemaValidateObjectMandatoryMember ( size_t pos G_GNUC_UNUSED ,
2021-03-11 10:16:13 +03:00
virJSONValue * item ,
2019-10-14 15:45:03 +03:00
void * opaque G_GNUC_UNUSED )
2018-03-22 19:07:43 +03:00
{
struct testQEMUSchemaValidateObjectMemberData * data = opaque ;
if ( virJSONValueObjectHasKey ( item , " default " ) ! = 1 ) {
2020-04-29 18:31:20 +03:00
virBufferAsprintf ( data - > ctxt - > debug , " ERROR: missing mandatory attribute '%s' \n " ,
2018-03-22 19:07:43 +03:00
NULLSTR ( virJSONValueObjectGetString ( item , " name " ) ) ) ;
data - > missingMandatory = true ;
}
return 1 ;
}
static int
2021-03-11 10:16:13 +03:00
testQEMUSchemaValidateObject ( virJSONValue * obj ,
virJSONValue * root ,
2020-04-29 18:31:20 +03:00
struct testQEMUSchemaValidateCtxt * ctxt )
2018-03-22 19:07:43 +03:00
{
2020-04-29 18:31:20 +03:00
struct testQEMUSchemaValidateObjectMemberData data = { NULL , ctxt , false } ;
2020-04-29 18:20:07 +03:00
g_autoptr ( virJSONValue ) localroot = NULL ;
2018-03-22 19:07:43 +03:00
const char * variantfield ;
const char * variantname ;
if ( virJSONValueGetType ( obj ) ! = VIR_JSON_TYPE_OBJECT ) {
2020-04-29 18:31:20 +03:00
virBufferAddLit ( ctxt - > debug , " ERROR: not an object " ) ;
2018-03-22 19:07:43 +03:00
return - 1 ;
}
2020-04-29 18:31:20 +03:00
virBufferAddLit ( ctxt - > debug , " { \n " ) ;
virBufferAdjustIndent ( ctxt - > debug , 3 ) ;
2018-03-22 19:07:43 +03:00
/* copy schema */
2020-04-29 18:20:07 +03:00
if ( ! ( localroot = virJSONValueCopy ( root ) ) )
return - 2 ;
2018-03-22 19:07:43 +03:00
/* remove variant */
if ( ( variantfield = virJSONValueObjectGetString ( localroot , " tag " ) ) ) {
if ( ! ( variantname = virJSONValueObjectGetString ( obj , variantfield ) ) ) {
2020-04-29 18:31:20 +03:00
virBufferAsprintf ( ctxt - > debug , " ERROR: missing variant discriminator attribute '%s' \n " ,
2018-03-22 19:07:43 +03:00
variantfield ) ;
2020-04-29 18:20:07 +03:00
return - 1 ;
2018-03-22 19:07:43 +03:00
}
if ( testQEMUSchemaValidateObjectMergeVariant ( localroot , variantfield ,
2020-04-29 18:31:20 +03:00
variantname , ctxt ) < 0 )
2020-04-29 18:20:07 +03:00
return - 1 ;
2018-03-22 19:07:43 +03:00
}
/* validate members */
data . rootmembers = virJSONValueObjectGetArray ( localroot , " members " ) ;
if ( virJSONValueObjectForeachKeyValue ( obj ,
testQEMUSchemaValidateObjectMember ,
& data ) < 0 )
2020-04-29 18:20:07 +03:00
return - 1 ;
2018-03-22 19:07:43 +03:00
/* check missing mandatory values */
if ( virJSONValueArrayForeachSteal ( data . rootmembers ,
testQEMUSchemaValidateObjectMandatoryMember ,
& data ) < 0 ) {
2020-04-29 18:20:07 +03:00
return - 2 ;
2018-03-22 19:07:43 +03:00
}
if ( data . missingMandatory )
2020-04-29 18:20:07 +03:00
return - 1 ;
2018-03-22 19:07:43 +03:00
2020-04-29 18:31:20 +03:00
virBufferAdjustIndent ( ctxt - > debug , - 3 ) ;
virBufferAddLit ( ctxt - > debug , " } OK " ) ;
2020-04-29 18:20:07 +03:00
return 0 ;
2018-03-22 19:07:43 +03:00
}
static int
2021-03-11 10:16:13 +03:00
testQEMUSchemaValidateEnum ( virJSONValue * obj ,
virJSONValue * root ,
2020-04-29 18:31:20 +03:00
struct testQEMUSchemaValidateCtxt * ctxt )
2018-03-22 19:07:43 +03:00
{
const char * objstr ;
2021-03-11 10:16:13 +03:00
virJSONValue * values = NULL ;
2021-09-17 17:26:09 +03:00
virJSONValue * members = NULL ;
2018-03-22 19:07:43 +03:00
size_t i ;
if ( virJSONValueGetType ( obj ) ! = VIR_JSON_TYPE_STRING ) {
2020-04-29 18:31:20 +03:00
virBufferAddLit ( ctxt - > debug , " ERROR: not a string " ) ;
2018-03-22 19:07:43 +03:00
return - 1 ;
}
objstr = virJSONValueGetString ( obj ) ;
2021-09-17 17:26:09 +03:00
/* qemu-6.2 added a "members" array superseding "values" */
if ( ( members = virJSONValueObjectGetArray ( root , " members " ) ) ) {
for ( i = 0 ; i < virJSONValueArraySize ( members ) ; i + + ) {
virJSONValue * member = virJSONValueArrayGet ( members , i ) ;
if ( STREQ_NULLABLE ( objstr , virJSONValueObjectGetString ( member , " name " ) ) ) {
2021-09-17 17:38:11 +03:00
int rc ;
/* the new 'members' array allows us to check deprecations */
if ( ( rc = testQEMUSchemaValidateDeprecated ( member , objstr , ctxt ) ) < 0 )
return rc ;
2021-09-17 17:26:09 +03:00
virBufferAsprintf ( ctxt - > debug , " '%s' OK " , NULLSTR ( objstr ) ) ;
return 0 ;
}
}
virBufferAsprintf ( ctxt - > debug , " ERROR: enum value '%s' is not in schema " ,
NULLSTR ( objstr ) ) ;
return - 1 ;
}
2021-09-17 17:21:43 +03:00
if ( ( values = virJSONValueObjectGetArray ( root , " values " ) ) ) {
for ( i = 0 ; i < virJSONValueArraySize ( values ) ; i + + ) {
virJSONValue * value = virJSONValueArrayGet ( values , i ) ;
2018-03-22 19:07:43 +03:00
2021-09-17 17:21:43 +03:00
if ( STREQ_NULLABLE ( objstr , virJSONValueGetString ( value ) ) ) {
virBufferAsprintf ( ctxt - > debug , " '%s' OK " , NULLSTR ( objstr ) ) ;
return 0 ;
}
2018-03-22 19:07:43 +03:00
}
2021-09-17 17:21:43 +03:00
virBufferAsprintf ( ctxt - > debug , " ERROR: enum value '%s' is not in schema " ,
NULLSTR ( objstr ) ) ;
return - 1 ;
2018-03-22 19:07:43 +03:00
}
2021-09-17 17:21:43 +03:00
virBufferAsprintf ( ctxt - > debug , " ERROR: missing enum values in schema '%s' " ,
NULLSTR ( virJSONValueObjectGetString ( root , " name " ) ) ) ;
return - 2 ;
2018-03-22 19:07:43 +03:00
}
static int
2021-03-11 10:16:13 +03:00
testQEMUSchemaValidateArray ( virJSONValue * objs ,
virJSONValue * root ,
2020-04-29 18:31:20 +03:00
struct testQEMUSchemaValidateCtxt * ctxt )
2018-03-22 19:07:43 +03:00
{
const char * elemtypename = virJSONValueObjectGetString ( root , " element-type " ) ;
2021-03-11 10:16:13 +03:00
virJSONValue * elementschema ;
virJSONValue * obj ;
2018-03-22 19:07:43 +03:00
size_t i ;
if ( virJSONValueGetType ( objs ) ! = VIR_JSON_TYPE_ARRAY ) {
2020-04-29 18:31:20 +03:00
virBufferAddLit ( ctxt - > debug , " ERROR: not an array \n " ) ;
2018-03-22 19:07:43 +03:00
return - 1 ;
}
if ( ! elemtypename | |
2020-04-29 18:31:20 +03:00
! ( elementschema = virHashLookup ( ctxt - > schema , elemtypename ) ) ) {
virBufferAsprintf ( ctxt - > debug , " ERROR: missing schema for array element type '%s' " ,
2018-03-22 19:07:43 +03:00
NULLSTR ( elemtypename ) ) ;
return - 2 ;
}
2020-04-29 18:31:20 +03:00
virBufferAddLit ( ctxt - > debug , " [ \n " ) ;
virBufferAdjustIndent ( ctxt - > debug , 3 ) ;
2018-03-22 19:07:43 +03:00
2018-04-20 00:29:02 +03:00
for ( i = 0 ; i < virJSONValueArraySize ( objs ) ; i + + ) {
2018-03-22 19:07:43 +03:00
obj = virJSONValueArrayGet ( objs , i ) ;
2020-04-29 18:31:20 +03:00
if ( testQEMUSchemaValidateRecurse ( obj , elementschema , ctxt ) < 0 )
2018-03-22 19:07:43 +03:00
return - 1 ;
2020-04-29 18:31:20 +03:00
virBufferAddLit ( ctxt - > debug , " , \n " ) ;
2018-03-22 19:07:43 +03:00
}
2020-04-29 18:31:20 +03:00
virBufferAddLit ( ctxt - > debug , " ] OK " ) ;
virBufferAdjustIndent ( ctxt - > debug , - 3 ) ;
2018-03-22 19:07:43 +03:00
return 0 ;
}
static int
2021-03-11 10:16:13 +03:00
testQEMUSchemaValidateAlternate ( virJSONValue * obj ,
virJSONValue * root ,
2020-04-29 18:31:20 +03:00
struct testQEMUSchemaValidateCtxt * ctxt )
2018-03-22 19:07:43 +03:00
{
2021-03-11 10:16:13 +03:00
virJSONValue * members ;
virJSONValue * member ;
2018-03-22 19:07:43 +03:00
size_t i ;
2018-04-20 00:29:02 +03:00
size_t n ;
2018-03-22 19:07:43 +03:00
const char * membertype ;
2021-03-11 10:16:13 +03:00
virJSONValue * memberschema ;
2018-03-22 19:07:43 +03:00
int indent ;
int rc ;
if ( ! ( members = virJSONValueObjectGetArray ( root , " members " ) ) ) {
2020-04-29 18:31:20 +03:00
virBufferAddLit ( ctxt - > debug , " ERROR: missing 'members' for alternate schema " ) ;
2018-03-22 19:07:43 +03:00
return - 2 ;
}
2020-04-29 18:31:20 +03:00
virBufferAddLit ( ctxt - > debug , " ( \n " ) ;
virBufferAdjustIndent ( ctxt - > debug , 3 ) ;
indent = virBufferGetIndent ( ctxt - > debug ) ;
2018-03-22 19:07:43 +03:00
n = virJSONValueArraySize ( members ) ;
for ( i = 0 ; i < n ; i + + ) {
membertype = NULL ;
/* P != NP */
2020-04-29 18:31:20 +03:00
virBufferAsprintf ( ctxt - > debug , " (alternate %zu/%zu) \n " , i + 1 , n ) ;
virBufferAdjustIndent ( ctxt - > debug , 3 ) ;
2018-03-22 19:07:43 +03:00
if ( ! ( member = virJSONValueArrayGet ( members , i ) ) | |
! ( membertype = virJSONValueObjectGetString ( member , " type " ) ) | |
2020-04-29 18:31:20 +03:00
! ( memberschema = virHashLookup ( ctxt - > schema , membertype ) ) ) {
virBufferAsprintf ( ctxt - > debug , " ERROR: missing schema for alternate type '%s' " ,
2018-03-22 19:07:43 +03:00
NULLSTR ( membertype ) ) ;
return - 2 ;
}
2020-04-29 18:31:20 +03:00
rc = testQEMUSchemaValidateRecurse ( obj , memberschema , ctxt ) ;
2018-03-22 19:07:43 +03:00
2020-04-29 18:31:20 +03:00
virBufferAddLit ( ctxt - > debug , " \n " ) ;
virBufferSetIndent ( ctxt - > debug , indent ) ;
virBufferAsprintf ( ctxt - > debug , " (/alternate %zu/%zu) \n " , i + 1 , n ) ;
2018-03-22 19:07:43 +03:00
if ( rc = = 0 ) {
2020-04-29 18:31:20 +03:00
virBufferAdjustIndent ( ctxt - > debug , - 3 ) ;
virBufferAddLit ( ctxt - > debug , " ) OK " ) ;
2018-03-22 19:07:43 +03:00
return 0 ;
}
}
2020-04-29 18:31:20 +03:00
virBufferAddLit ( ctxt - > debug , " ERROR: no alternate type was matched " ) ;
2018-03-22 19:07:43 +03:00
return - 1 ;
}
static int
2021-03-11 10:16:13 +03:00
testQEMUSchemaValidateRecurse ( virJSONValue * obj ,
virJSONValue * root ,
2020-04-29 18:31:20 +03:00
struct testQEMUSchemaValidateCtxt * ctxt )
2018-03-22 19:07:43 +03:00
{
const char * n = virJSONValueObjectGetString ( root , " name " ) ;
const char * t = virJSONValueObjectGetString ( root , " meta-type " ) ;
2020-03-04 11:02:54 +03:00
int rc ;
if ( ( rc = testQEMUSchemaValidateDeprecated ( root , n , ctxt ) ) < 0 )
return rc ;
2018-03-22 19:07:43 +03:00
if ( STREQ_NULLABLE ( t , " builtin " ) ) {
2020-04-29 18:31:20 +03:00
return testQEMUSchemaValidateBuiltin ( obj , root , ctxt ) ;
2018-03-22 19:07:43 +03:00
} else if ( STREQ_NULLABLE ( t , " object " ) ) {
2020-04-29 18:31:20 +03:00
return testQEMUSchemaValidateObject ( obj , root , ctxt ) ;
2018-03-22 19:07:43 +03:00
} else if ( STREQ_NULLABLE ( t , " enum " ) ) {
2020-04-29 18:31:20 +03:00
return testQEMUSchemaValidateEnum ( obj , root , ctxt ) ;
2018-03-22 19:07:43 +03:00
} else if ( STREQ_NULLABLE ( t , " array " ) ) {
2020-04-29 18:31:20 +03:00
return testQEMUSchemaValidateArray ( obj , root , ctxt ) ;
2018-03-22 19:07:43 +03:00
} else if ( STREQ_NULLABLE ( t , " alternate " ) ) {
2020-04-29 18:31:20 +03:00
return testQEMUSchemaValidateAlternate ( obj , root , ctxt ) ;
2018-03-22 19:07:43 +03:00
}
2020-04-29 18:31:20 +03:00
virBufferAsprintf ( ctxt - > debug ,
2018-03-22 19:07:43 +03:00
" qapi schema meta-type '%s' of type '%s' not handled \n " ,
NULLSTR ( t ) , NULLSTR ( n ) ) ;
return - 2 ;
}
/**
* testQEMUSchemaValidate :
* @ obj : object to validate
* @ root : schema entry to start from
* @ schema : hash table containing schema entries
* @ debug : a virBuffer which will be filled with debug information if provided
*
* Validates whether @ obj conforms to the QAPI schema passed in via @ schema ,
* starting from the node @ root . Returns 0 , if @ obj matches @ schema , - 1 if it
* does not and - 2 if there is a problem with the schema or with internals .
*
* @ debug is filled with information regarding the validation process
*/
int
2021-03-11 10:16:13 +03:00
testQEMUSchemaValidate ( virJSONValue * obj ,
virJSONValue * root ,
2020-10-22 20:04:18 +03:00
GHashTable * schema ,
2020-04-29 18:52:43 +03:00
bool allowDeprecated ,
2021-03-11 10:16:13 +03:00
virBuffer * debug )
2018-03-22 19:07:43 +03:00
{
2020-04-29 18:31:20 +03:00
struct testQEMUSchemaValidateCtxt ctxt = { . schema = schema ,
2020-04-29 18:52:43 +03:00
. debug = debug ,
. allowDeprecated = allowDeprecated } ;
2020-04-29 18:31:20 +03:00
return testQEMUSchemaValidateRecurse ( obj , root , & ctxt ) ;
2018-03-22 19:07:43 +03:00
}
2020-03-04 12:22:19 +03:00
/**
* testQEMUSchemaValidateCommand :
* @ command : command to validate
* @ arguments : arguments of @ command to validate
* @ schema : hash table containing schema entries
2020-04-29 18:52:43 +03:00
* @ allowDeprecated : don ' t fails schema validation if @ command or one of @ arguments
* is deprecated
* @ allowRemoved : skip validation fully if @ command was not found
2021-10-15 13:06:14 +03:00
* @ allowIncomplete : don ' t fail validation if members not covered by schema are present
* ( for waiving commands with incomplete schema )
2020-03-04 12:22:19 +03:00
* @ debug : a virBuffer which will be filled with debug information if provided
*
* Validates whether @ command and its @ arguments conform to the QAPI schema
* passed in via @ schema . Returns 0 , if the command and args match @ schema ,
* - 1 if it does not and - 2 if there is a problem with the schema or with
* internals .
*
2020-04-29 18:52:43 +03:00
* @ allowRemoved should generally be used only if it ' s certain that there ' s a
* replacement of @ command in place .
*
2020-03-04 12:22:19 +03:00
* @ debug is filled with information regarding the validation process
*/
int
testQEMUSchemaValidateCommand ( const char * command ,
2021-03-11 10:16:13 +03:00
virJSONValue * arguments ,
2020-10-22 20:04:18 +03:00
GHashTable * schema ,
2020-04-29 18:52:43 +03:00
bool allowDeprecated ,
2020-03-04 11:02:54 +03:00
bool allowRemoved ,
2021-10-15 13:06:14 +03:00
bool allowIncomplete ,
2021-03-11 10:16:13 +03:00
virBuffer * debug )
2020-03-04 12:22:19 +03:00
{
2020-04-29 18:31:20 +03:00
struct testQEMUSchemaValidateCtxt ctxt = { . schema = schema ,
2020-04-29 18:52:43 +03:00
. debug = debug ,
2021-10-15 13:06:14 +03:00
. allowDeprecated = allowDeprecated ,
. allowIncomplete = allowIncomplete } ;
2020-03-04 12:22:19 +03:00
g_autofree char * schemapatharguments = g_strdup_printf ( " %s/arg-type " , command ) ;
g_autoptr ( virJSONValue ) emptyargs = NULL ;
2021-03-11 10:16:13 +03:00
virJSONValue * schemarootcommand ;
virJSONValue * schemarootarguments ;
2020-03-04 11:02:54 +03:00
int rc ;
2020-03-04 12:22:19 +03:00
if ( virQEMUQAPISchemaPathGet ( command , schema , & schemarootcommand ) < 0 | |
! schemarootcommand ) {
2020-03-04 11:02:54 +03:00
if ( allowRemoved )
return 0 ;
2020-03-04 12:22:19 +03:00
virBufferAsprintf ( debug , " ERROR: command '%s' not found in the schema " , command ) ;
return - 1 ;
}
2020-03-04 11:02:54 +03:00
if ( ( rc = testQEMUSchemaValidateDeprecated ( schemarootcommand , command , & ctxt ) ) < 0 )
return rc ;
2020-03-04 12:22:19 +03:00
if ( ! arguments )
arguments = emptyargs = virJSONValueNewObject ( ) ;
if ( virQEMUQAPISchemaPathGet ( schemapatharguments , schema , & schemarootarguments ) < 0 | |
! schemarootarguments ) {
virBufferAsprintf ( debug , " ERROR: failed to look up 'arg-type' of '%s' " , command ) ;
return - 1 ;
}
2020-04-29 18:31:20 +03:00
return testQEMUSchemaValidateRecurse ( arguments , schemarootarguments , & ctxt ) ;
2020-03-04 12:22:19 +03:00
}
2020-08-06 20:43:51 +03:00
/**
* testQEMUSchemaEntryMatchTemplate :
*
* @ schemaentry : a JSON object representing a ' object ' node in the QAPI schema
* . . . : a NULL terminated list of strings representing the template of properties
* which the QMP object needs to have .
*
* The strings have following format :
*
* " type:name "
* " ?type:name "
*
* " type " corresponds to the ' type ' property of the member to check ( str , bool , any . . . )
* " name " corresponds to the name of the member to check
*
* If the query string starts with an ' ? ' and member ' name ' may be missing .
*
* This function matches that @ schemaentry has all expected members and the
* members have expected types . @ schemaentry also must not have any unknown
* members .
*/
int
2021-03-11 10:16:13 +03:00
testQEMUSchemaEntryMatchTemplate ( virJSONValue * schemaentry ,
2020-08-06 20:43:51 +03:00
. . . )
{
g_autoptr ( virJSONValue ) members = NULL ;
va_list ap ;
const char * next ;
int ret = - 1 ;
if ( STRNEQ_NULLABLE ( virJSONValueObjectGetString ( schemaentry , " meta-type " ) , " object " ) ) {
VIR_TEST_VERBOSE ( " schemaentry is not an object " ) ;
return - 1 ;
}
if ( ! ( members = virJSONValueCopy ( virJSONValueObjectGetArray ( schemaentry , " members " ) ) ) ) {
VIR_TEST_VERBOSE ( " failed to copy 'members' " ) ;
return - 1 ;
}
va_start ( ap , schemaentry ) ;
/* pass 1 */
while ( ( next = va_arg ( ap , const char * ) ) ) {
char modifier = * next ;
g_autofree char * type = NULL ;
char * name ;
size_t i ;
bool found = false ;
bool optional = false ;
if ( ! g_ascii_isalpha ( modifier ) )
next + + ;
if ( modifier = = ' ? ' )
optional = true ;
type = g_strdup ( next ) ;
if ( ( name = strchr ( type , ' : ' ) ) ) {
* ( name + + ) = ' \0 ' ;
} else {
VIR_TEST_VERBOSE ( " malformed template string '%s' " , next ) ;
goto cleanup ;
}
for ( i = 0 ; i < virJSONValueArraySize ( members ) ; i + + ) {
2021-03-11 10:16:13 +03:00
virJSONValue * member = virJSONValueArrayGet ( members , i ) ;
2020-08-06 20:43:51 +03:00
const char * membername = virJSONValueObjectGetString ( member , " name " ) ;
const char * membertype = virJSONValueObjectGetString ( member , " type " ) ;
if ( STRNEQ_NULLABLE ( name , membername ) )
continue ;
if ( STRNEQ_NULLABLE ( membertype , type ) ) {
VIR_TEST_VERBOSE ( " member '%s' is of unexpected type '%s' (expected '%s') " ,
NULLSTR ( membername ) , NULLSTR ( membertype ) , type ) ;
goto cleanup ;
}
found = true ;
break ;
}
if ( found ) {
virJSONValueFree ( virJSONValueArraySteal ( members , i ) ) ;
} else {
if ( ! optional ) {
VIR_TEST_VERBOSE ( " mandatory member '%s' not found " , name ) ;
goto cleanup ;
}
}
}
/* pass 2 - check any unexpected members */
if ( virJSONValueArraySize ( members ) > 0 ) {
size_t i ;
for ( i = 0 ; i < virJSONValueArraySize ( members ) ; i + + ) {
VIR_TEST_VERBOSE ( " unexpected member '%s' " ,
NULLSTR ( virJSONValueObjectGetString ( virJSONValueArrayGet ( members , i ) , " name " ) ) ) ;
}
goto cleanup ;
}
ret = 0 ;
cleanup :
va_end ( ap ) ;
return ret ;
}
2021-03-11 10:16:13 +03:00
static virJSONValue *
2020-05-15 17:32:40 +03:00
testQEMUSchemaLoadReplies ( const char * filename )
2018-07-09 16:44:52 +03:00
{
2020-05-15 17:32:40 +03:00
g_autofree char * caps = NULL ;
2018-07-09 16:44:52 +03:00
char * schemaReply ;
char * end ;
2020-04-29 18:20:07 +03:00
g_autoptr ( virJSONValue ) reply = NULL ;
2021-03-11 10:16:13 +03:00
virJSONValue * schema = NULL ;
2018-07-09 16:44:52 +03:00
2020-05-15 17:32:40 +03:00
if ( virTestLoadFile ( filename , & caps ) < 0 )
2018-07-09 16:44:52 +03:00
return NULL ;
2020-05-15 17:32:40 +03:00
if ( ! ( schemaReply = strstr ( caps , " \" execute \" : \" query-qmp-schema \" " ) ) | |
2018-07-09 16:44:52 +03:00
! ( schemaReply = strstr ( schemaReply , " \n \n " ) ) | |
! ( end = strstr ( schemaReply + 2 , " \n \n " ) ) ) {
2019-05-03 11:45:58 +03:00
VIR_TEST_VERBOSE ( " failed to find reply to 'query-qmp-schema' in '%s' " ,
2020-05-15 17:32:40 +03:00
filename ) ;
2020-04-29 18:20:07 +03:00
return NULL ;
2018-07-09 16:44:52 +03:00
}
schemaReply + = 2 ;
* end = ' \0 ' ;
if ( ! ( reply = virJSONValueFromString ( schemaReply ) ) ) {
2019-05-03 11:45:58 +03:00
VIR_TEST_VERBOSE ( " failed to parse 'query-qmp-schema' reply from '%s' " ,
2020-05-15 17:32:40 +03:00
filename ) ;
2020-04-29 18:20:07 +03:00
return NULL ;
2018-07-09 16:44:52 +03:00
}
if ( ! ( schema = virJSONValueObjectStealArray ( reply , " return " ) ) ) {
2019-05-03 11:45:58 +03:00
VIR_TEST_VERBOSE ( " missing qapi schema data in reply in '%s' " ,
2020-05-15 17:32:40 +03:00
filename ) ;
2020-04-29 18:20:07 +03:00
return NULL ;
2018-07-09 16:44:52 +03:00
}
return schema ;
}
2020-05-15 17:32:40 +03:00
/**
* testQEMUSchemaGetLatest :
*
* Returns the schema data as the qemu monitor would reply from the latest
* replies file used for qemucapabilitiestest for the x86_64 architecture .
*/
2021-03-11 10:16:13 +03:00
virJSONValue *
2020-05-15 17:32:40 +03:00
testQEMUSchemaGetLatest ( const char * arch )
{
g_autofree char * capsLatestFile = NULL ;
if ( ! ( capsLatestFile = testQemuGetLatestCapsForArch ( arch , " replies " ) ) ) {
VIR_TEST_VERBOSE ( " failed to find latest caps replies " ) ;
return NULL ;
}
VIR_TEST_DEBUG ( " replies file: '%s' " , capsLatestFile ) ;
return testQEMUSchemaLoadReplies ( capsLatestFile ) ;
}
2020-10-22 20:04:18 +03:00
GHashTable *
2020-05-15 17:21:25 +03:00
testQEMUSchemaLoadLatest ( const char * arch )
2018-03-22 19:07:43 +03:00
{
2021-03-11 10:16:13 +03:00
virJSONValue * schema ;
2018-03-22 19:07:43 +03:00
2020-02-21 01:01:46 +03:00
if ( ! ( schema = testQEMUSchemaGetLatest ( arch ) ) )
2018-03-22 19:07:43 +03:00
return NULL ;
2018-07-09 16:44:52 +03:00
return virQEMUQAPISchemaConvert ( schema ) ;
2018-03-22 19:07:43 +03:00
}
2020-05-15 17:32:40 +03:00
2020-10-22 20:04:18 +03:00
GHashTable *
2020-05-15 17:32:40 +03:00
testQEMUSchemaLoad ( const char * filename )
{
2021-03-11 10:16:13 +03:00
virJSONValue * schema ;
2020-05-15 17:32:40 +03:00
if ( ! ( schema = testQEMUSchemaLoadReplies ( filename ) ) )
return NULL ;
return virQEMUQAPISchemaConvert ( schema ) ;
}