1 /*
2  * testutilsqemuschema.c: helper functions for QEMU QAPI schema testing
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library.  If not, see
16  * <http://www.gnu.org/licenses/>.
17  */
18 #include <config.h>
19 #include "testutils.h"
20 #include "testutilsqemu.h"
21 #include "testutilsqemuschema.h"
22 #include "qemu/qemu_qapi.h"
23 
24 struct testQEMUSchemaValidateCtxt {
25     GHashTable *schema;
26     virBuffer *debug;
27     bool allowDeprecated;
28     bool allowIncomplete; /* allow members not (yet) covered by the schema */
29 };
30 
31 
32 static int
testQEMUSchemaValidateDeprecated(virJSONValue * root,const char * name,struct testQEMUSchemaValidateCtxt * ctxt)33 testQEMUSchemaValidateDeprecated(virJSONValue *root,
34                                  const char *name,
35                                  struct testQEMUSchemaValidateCtxt *ctxt)
36 {
37     virJSONValue *features = virJSONValueObjectGetArray(root, "features");
38     size_t nfeatures;
39     size_t i;
40 
41     if (!features)
42         return 0;
43 
44     nfeatures = virJSONValueArraySize(features);
45 
46     for (i = 0; i < nfeatures; i++) {
47         virJSONValue *cur = virJSONValueArrayGet(features, i);
48         const char *curstr;
49 
50         if (!cur ||
51             !(curstr = virJSONValueGetString(cur))) {
52             virBufferAsprintf(ctxt->debug, "ERROR: features of '%s' are malformed", name);
53             return -2;
54         }
55 
56         if (STREQ(curstr, "deprecated")) {
57             if (ctxt->allowDeprecated) {
58                 virBufferAsprintf(ctxt->debug, "WARNING: '%s' is deprecated", name);
59                 if (virTestGetVerbose())
60                     g_fprintf(stderr, "\nWARNING: '%s' is deprecated\n", name);
61                 return 0;
62             } else {
63                 virBufferAsprintf(ctxt->debug, "ERROR: '%s' is deprecated", name);
64                 return -1;
65             }
66         }
67     }
68 
69     return 0;
70 }
71 
72 
73 static int
74 testQEMUSchemaValidateRecurse(virJSONValue *obj,
75                               virJSONValue *root,
76                               struct testQEMUSchemaValidateCtxt *ctxt);
77 
78 static int
testQEMUSchemaValidateBuiltin(virJSONValue * obj,virJSONValue * root,struct testQEMUSchemaValidateCtxt * ctxt)79 testQEMUSchemaValidateBuiltin(virJSONValue *obj,
80                               virJSONValue *root,
81                               struct testQEMUSchemaValidateCtxt *ctxt)
82 {
83     const char *t = virJSONValueObjectGetString(root, "json-type");
84     const char *s = NULL;
85     bool b = false;
86     int ret = -1;
87 
88     if (STREQ_NULLABLE(t, "value")) {
89         s = "{any}";
90         ret = 0;
91         goto cleanup;
92     }
93 
94     switch (virJSONValueGetType(obj)) {
95     case VIR_JSON_TYPE_STRING:
96         if (STRNEQ_NULLABLE(t, "string"))
97             goto cleanup;
98         s = virJSONValueGetString(obj);
99         break;
100 
101     case VIR_JSON_TYPE_NUMBER:
102         if (STRNEQ_NULLABLE(t, "int") &&
103             STRNEQ_NULLABLE(t, "number"))
104             goto cleanup;
105         s = "{number}";
106         break;
107 
108     case VIR_JSON_TYPE_BOOLEAN:
109         if (STRNEQ_NULLABLE(t, "boolean"))
110             goto cleanup;
111         virJSONValueGetBoolean(obj, &b);
112         if (b)
113             s = "true";
114         else
115             s = "false";
116         break;
117 
118     case VIR_JSON_TYPE_NULL:
119         if (STRNEQ_NULLABLE(t, "null"))
120             goto cleanup;
121         break;
122 
123     case VIR_JSON_TYPE_OBJECT:
124     case VIR_JSON_TYPE_ARRAY:
125         goto cleanup;
126     }
127 
128     ret = 0;
129 
130  cleanup:
131     if (ret == 0)
132         virBufferAsprintf(ctxt->debug, "'%s': OK", s);
133     else
134         virBufferAsprintf(ctxt->debug, "ERROR: expected type '%s', actual type %d",
135                           t, virJSONValueGetType(obj));
136     return ret;
137 }
138 
139 struct testQEMUSchemaValidateObjectMemberData {
140     virJSONValue *rootmembers;
141     struct testQEMUSchemaValidateCtxt *ctxt;
142     bool missingMandatory;
143 };
144 
145 
146 static virJSONValue *
testQEMUSchemaStealObjectMemberByName(const char * name,virJSONValue * members)147 testQEMUSchemaStealObjectMemberByName(const char *name,
148                                       virJSONValue *members)
149 {
150     virJSONValue *member;
151     virJSONValue *ret = NULL;
152     size_t i;
153 
154     for (i = 0; i < virJSONValueArraySize(members); i++) {
155         member = virJSONValueArrayGet(members, i);
156 
157         if (STREQ_NULLABLE(name, virJSONValueObjectGetString(member, "name"))) {
158             ret = virJSONValueArraySteal(members, i);
159             break;
160         }
161     }
162 
163     return ret;
164 }
165 
166 
167 static int
testQEMUSchemaValidateObjectMember(const char * key,virJSONValue * value,void * opaque)168 testQEMUSchemaValidateObjectMember(const char *key,
169                                    virJSONValue *value,
170                                    void *opaque)
171 {
172     struct testQEMUSchemaValidateObjectMemberData *data = opaque;
173     g_autoptr(virJSONValue) keymember = NULL;
174     const char *keytype;
175     virJSONValue *keyschema = NULL;
176     int rc;
177 
178     virBufferStrcat(data->ctxt->debug, key, ": ", NULL);
179 
180     /* lookup 'member' entry for key */
181     if (!(keymember = testQEMUSchemaStealObjectMemberByName(key, data->rootmembers))) {
182         if (data->ctxt->allowIncomplete) {
183             virBufferAddLit(data->ctxt->debug, " schema missing - OK(waived)\n");
184             return 0;
185         }
186         virBufferAddLit(data->ctxt->debug, "ERROR: attribute not in schema\n");
187         return -1;
188     }
189 
190     /* lookup schema entry for keytype */
191     if (!(keytype = virJSONValueObjectGetString(keymember, "type")) ||
192         !(keyschema = virHashLookup(data->ctxt->schema, keytype))) {
193         virBufferAsprintf(data->ctxt->debug, "ERROR: can't find schema for type '%s'\n",
194                           NULLSTR(keytype));
195         return -2;
196     }
197 
198     /* recurse */
199     rc = testQEMUSchemaValidateRecurse(value, keyschema, data->ctxt);
200 
201     virBufferAddLit(data->ctxt->debug, "\n");
202     return rc;
203 }
204 
205 
206 static int
testQEMUSchemaValidateObjectMergeVariantMember(size_t pos G_GNUC_UNUSED,virJSONValue * item,void * opaque)207 testQEMUSchemaValidateObjectMergeVariantMember(size_t pos G_GNUC_UNUSED,
208                                                virJSONValue *item,
209                                                void *opaque)
210 {
211     virJSONValue *array = opaque;
212     g_autoptr(virJSONValue) copy = NULL;
213 
214     if (!(copy = virJSONValueCopy(item)))
215         return -1;
216 
217     if (virJSONValueArrayAppend(array, &copy) < 0)
218         return -1;
219 
220     return 1;
221 }
222 
223 
224 /**
225  * testQEMUSchemaValidateObjectMergeVariant:
226  *
227  * Merges schema of variant @variantname in @root into @root and removes the
228  * 'variants' array from @root.
229  */
230 static int
testQEMUSchemaValidateObjectMergeVariant(virJSONValue * root,const char * variantfield,const char * variantname,struct testQEMUSchemaValidateCtxt * ctxt)231 testQEMUSchemaValidateObjectMergeVariant(virJSONValue *root,
232                                          const char *variantfield,
233                                          const char *variantname,
234                                          struct testQEMUSchemaValidateCtxt *ctxt)
235 {
236     size_t i;
237     g_autoptr(virJSONValue) variants = NULL;
238     virJSONValue *variant;
239     virJSONValue *variantschema;
240     virJSONValue *variantschemamembers;
241     virJSONValue *rootmembers;
242     const char *varianttype = NULL;
243 
244     if (!(variants = virJSONValueObjectStealArray(root, "variants"))) {
245         virBufferAddLit(ctxt->debug, "ERROR: missing 'variants' in schema\n");
246         return -2;
247     }
248 
249     for (i = 0; i < virJSONValueArraySize(variants); i++) {
250         variant = virJSONValueArrayGet(variants, i);
251 
252         if (STREQ_NULLABLE(variantname,
253                            virJSONValueObjectGetString(variant, "case"))) {
254             varianttype = virJSONValueObjectGetString(variant, "type");
255             break;
256         }
257     }
258 
259     if (!varianttype) {
260         virBufferAsprintf(ctxt->debug, "ERROR: variant '%s' for discriminator '%s' not found\n",
261                           variantname, variantfield);
262         return -1;
263     }
264 
265     if (!(variantschema = virHashLookup(ctxt->schema, varianttype)) ||
266         !(variantschemamembers = virJSONValueObjectGetArray(variantschema, "members"))) {
267         virBufferAsprintf(ctxt->debug,
268                           "ERROR: missing schema or schema members for variant '%s'(%s)\n",
269                           variantname, varianttype);
270         return -2;
271     }
272 
273     rootmembers = virJSONValueObjectGetArray(root, "members");
274 
275     if (virJSONValueArrayForeachSteal(variantschemamembers,
276                                       testQEMUSchemaValidateObjectMergeVariantMember,
277                                       rootmembers) < 0) {
278         return -2;
279     }
280 
281     return 0;
282 }
283 
284 
285 static int
testQEMUSchemaValidateObjectMandatoryMember(size_t pos G_GNUC_UNUSED,virJSONValue * item,void * opaque G_GNUC_UNUSED)286 testQEMUSchemaValidateObjectMandatoryMember(size_t pos G_GNUC_UNUSED,
287                                             virJSONValue *item,
288                                             void *opaque G_GNUC_UNUSED)
289 {
290     struct testQEMUSchemaValidateObjectMemberData *data = opaque;
291 
292     if (virJSONValueObjectHasKey(item, "default") != 1) {
293         virBufferAsprintf(data->ctxt->debug, "ERROR: missing mandatory attribute '%s'\n",
294                           NULLSTR(virJSONValueObjectGetString(item, "name")));
295         data->missingMandatory = true;
296     }
297 
298     return 1;
299 }
300 
301 
302 static int
testQEMUSchemaValidateObject(virJSONValue * obj,virJSONValue * root,struct testQEMUSchemaValidateCtxt * ctxt)303 testQEMUSchemaValidateObject(virJSONValue *obj,
304                              virJSONValue *root,
305                              struct testQEMUSchemaValidateCtxt *ctxt)
306 {
307     struct testQEMUSchemaValidateObjectMemberData data = { NULL, ctxt, false };
308     g_autoptr(virJSONValue) localroot = NULL;
309     const char *variantfield;
310     const char *variantname;
311 
312     if (virJSONValueGetType(obj) != VIR_JSON_TYPE_OBJECT) {
313         virBufferAddLit(ctxt->debug, "ERROR: not an object");
314         return -1;
315     }
316 
317     virBufferAddLit(ctxt->debug, "{\n");
318     virBufferAdjustIndent(ctxt->debug, 3);
319 
320     /* copy schema */
321     if (!(localroot = virJSONValueCopy(root)))
322         return -2;
323 
324     /* remove variant */
325     if ((variantfield = virJSONValueObjectGetString(localroot, "tag"))) {
326         if (!(variantname = virJSONValueObjectGetString(obj, variantfield))) {
327             virBufferAsprintf(ctxt->debug, "ERROR: missing variant discriminator attribute '%s'\n",
328                               variantfield);
329             return -1;
330         }
331 
332         if (testQEMUSchemaValidateObjectMergeVariant(localroot, variantfield,
333                                                      variantname, ctxt) < 0)
334             return -1;
335     }
336 
337 
338     /* validate members */
339     data.rootmembers = virJSONValueObjectGetArray(localroot, "members");
340     if (virJSONValueObjectForeachKeyValue(obj,
341                                           testQEMUSchemaValidateObjectMember,
342                                           &data) < 0)
343         return -1;
344 
345     /* check missing mandatory values */
346     if (virJSONValueArrayForeachSteal(data.rootmembers,
347                                       testQEMUSchemaValidateObjectMandatoryMember,
348                                       &data) < 0) {
349         return -2;
350     }
351 
352     if (data.missingMandatory)
353         return -1;
354 
355     virBufferAdjustIndent(ctxt->debug, -3);
356     virBufferAddLit(ctxt->debug, "} OK");
357     return 0;
358 }
359 
360 
361 static int
testQEMUSchemaValidateEnum(virJSONValue * obj,virJSONValue * root,struct testQEMUSchemaValidateCtxt * ctxt)362 testQEMUSchemaValidateEnum(virJSONValue *obj,
363                            virJSONValue *root,
364                            struct testQEMUSchemaValidateCtxt *ctxt)
365 {
366     const char *objstr;
367     virJSONValue *values = NULL;
368     virJSONValue *members = NULL;
369     size_t i;
370 
371     if (virJSONValueGetType(obj) != VIR_JSON_TYPE_STRING) {
372         virBufferAddLit(ctxt->debug, "ERROR: not a string");
373         return -1;
374     }
375 
376     objstr = virJSONValueGetString(obj);
377 
378     /* qemu-6.2 added a "members" array superseding "values" */
379     if ((members = virJSONValueObjectGetArray(root, "members"))) {
380         for (i = 0; i < virJSONValueArraySize(members); i++) {
381             virJSONValue *member = virJSONValueArrayGet(members, i);
382 
383             if (STREQ_NULLABLE(objstr, virJSONValueObjectGetString(member, "name"))) {
384                 int rc;
385 
386                 /* the new 'members' array allows us to check deprecations */
387                 if ((rc = testQEMUSchemaValidateDeprecated(member, objstr, ctxt)) < 0)
388                     return rc;
389 
390                 virBufferAsprintf(ctxt->debug, "'%s' OK", NULLSTR(objstr));
391                 return 0;
392             }
393         }
394 
395         virBufferAsprintf(ctxt->debug, "ERROR: enum value '%s' is not in schema",
396                           NULLSTR(objstr));
397         return -1;
398     }
399 
400     if ((values = virJSONValueObjectGetArray(root, "values"))) {
401         for (i = 0; i < virJSONValueArraySize(values); i++) {
402             virJSONValue *value = virJSONValueArrayGet(values, i);
403 
404             if (STREQ_NULLABLE(objstr, virJSONValueGetString(value))) {
405                 virBufferAsprintf(ctxt->debug, "'%s' OK", NULLSTR(objstr));
406                 return 0;
407             }
408         }
409 
410         virBufferAsprintf(ctxt->debug, "ERROR: enum value '%s' is not in schema",
411                           NULLSTR(objstr));
412         return -1;
413     }
414 
415     virBufferAsprintf(ctxt->debug, "ERROR: missing enum values in schema '%s'",
416                       NULLSTR(virJSONValueObjectGetString(root, "name")));
417     return -2;
418 }
419 
420 
421 static int
testQEMUSchemaValidateArray(virJSONValue * objs,virJSONValue * root,struct testQEMUSchemaValidateCtxt * ctxt)422 testQEMUSchemaValidateArray(virJSONValue *objs,
423                             virJSONValue *root,
424                             struct testQEMUSchemaValidateCtxt *ctxt)
425 {
426     const char *elemtypename = virJSONValueObjectGetString(root, "element-type");
427     virJSONValue *elementschema;
428     virJSONValue *obj;
429     size_t i;
430 
431     if (virJSONValueGetType(objs) != VIR_JSON_TYPE_ARRAY) {
432         virBufferAddLit(ctxt->debug, "ERROR: not an array\n");
433         return -1;
434     }
435 
436     if (!elemtypename ||
437         !(elementschema = virHashLookup(ctxt->schema, elemtypename))) {
438         virBufferAsprintf(ctxt->debug, "ERROR: missing schema for array element type '%s'",
439                          NULLSTR(elemtypename));
440         return -2;
441     }
442 
443     virBufferAddLit(ctxt->debug, "[\n");
444     virBufferAdjustIndent(ctxt->debug, 3);
445 
446     for (i = 0; i < virJSONValueArraySize(objs); i++) {
447         obj = virJSONValueArrayGet(objs, i);
448 
449         if (testQEMUSchemaValidateRecurse(obj, elementschema, ctxt) < 0)
450             return -1;
451         virBufferAddLit(ctxt->debug, ",\n");
452     }
453     virBufferAddLit(ctxt->debug, "] OK");
454     virBufferAdjustIndent(ctxt->debug, -3);
455 
456     return 0;
457 }
458 
459 static int
testQEMUSchemaValidateAlternate(virJSONValue * obj,virJSONValue * root,struct testQEMUSchemaValidateCtxt * ctxt)460 testQEMUSchemaValidateAlternate(virJSONValue *obj,
461                                 virJSONValue *root,
462                                 struct testQEMUSchemaValidateCtxt *ctxt)
463 {
464     virJSONValue *members;
465     virJSONValue *member;
466     size_t i;
467     size_t n;
468     const char *membertype;
469     virJSONValue *memberschema;
470     int indent;
471     int rc;
472 
473     if (!(members = virJSONValueObjectGetArray(root, "members"))) {
474         virBufferAddLit(ctxt->debug, "ERROR: missing 'members' for alternate schema");
475         return -2;
476     }
477 
478     virBufferAddLit(ctxt->debug, "(\n");
479     virBufferAdjustIndent(ctxt->debug, 3);
480     indent = virBufferGetIndent(ctxt->debug);
481 
482     n = virJSONValueArraySize(members);
483     for (i = 0; i < n; i++) {
484         membertype = NULL;
485 
486         /* P != NP */
487         virBufferAsprintf(ctxt->debug, "(alternate %zu/%zu)\n", i + 1, n);
488         virBufferAdjustIndent(ctxt->debug, 3);
489 
490         if (!(member = virJSONValueArrayGet(members, i)) ||
491             !(membertype = virJSONValueObjectGetString(member, "type")) ||
492             !(memberschema = virHashLookup(ctxt->schema, membertype))) {
493             virBufferAsprintf(ctxt->debug, "ERROR: missing schema for alternate type '%s'",
494                               NULLSTR(membertype));
495             return -2;
496         }
497 
498         rc = testQEMUSchemaValidateRecurse(obj, memberschema, ctxt);
499 
500         virBufferAddLit(ctxt->debug, "\n");
501         virBufferSetIndent(ctxt->debug, indent);
502         virBufferAsprintf(ctxt->debug, "(/alternate %zu/%zu)\n", i + 1, n);
503 
504         if (rc == 0) {
505             virBufferAdjustIndent(ctxt->debug, -3);
506             virBufferAddLit(ctxt->debug, ") OK");
507             return 0;
508         }
509     }
510 
511     virBufferAddLit(ctxt->debug, "ERROR: no alternate type was matched");
512     return -1;
513 }
514 
515 
516 static int
testQEMUSchemaValidateRecurse(virJSONValue * obj,virJSONValue * root,struct testQEMUSchemaValidateCtxt * ctxt)517 testQEMUSchemaValidateRecurse(virJSONValue *obj,
518                               virJSONValue *root,
519                               struct testQEMUSchemaValidateCtxt *ctxt)
520 {
521     const char *n = virJSONValueObjectGetString(root, "name");
522     const char *t = virJSONValueObjectGetString(root, "meta-type");
523     int rc;
524 
525     if ((rc = testQEMUSchemaValidateDeprecated(root, n, ctxt)) < 0)
526         return rc;
527 
528     if (STREQ_NULLABLE(t, "builtin")) {
529         return testQEMUSchemaValidateBuiltin(obj, root, ctxt);
530     } else if (STREQ_NULLABLE(t, "object")) {
531         return testQEMUSchemaValidateObject(obj, root, ctxt);
532     } else if (STREQ_NULLABLE(t, "enum")) {
533         return testQEMUSchemaValidateEnum(obj, root, ctxt);
534     } else if (STREQ_NULLABLE(t, "array")) {
535         return testQEMUSchemaValidateArray(obj, root, ctxt);
536     } else if (STREQ_NULLABLE(t, "alternate")) {
537         return testQEMUSchemaValidateAlternate(obj, root, ctxt);
538     }
539 
540     virBufferAsprintf(ctxt->debug,
541                       "qapi schema meta-type '%s' of type '%s' not handled\n",
542                       NULLSTR(t), NULLSTR(n));
543     return -2;
544 }
545 
546 
547 /**
548  * testQEMUSchemaValidate:
549  * @obj: object to validate
550  * @root: schema entry to start from
551  * @schema: hash table containing schema entries
552  * @debug: a virBuffer which will be filled with debug information if provided
553  *
554  * Validates whether @obj conforms to the QAPI schema passed in via @schema,
555  * starting from the node @root. Returns 0, if @obj matches @schema, -1 if it
556  * does not and -2 if there is a problem with the schema or with internals.
557  *
558  * @debug is filled with information regarding the validation process
559  */
560 int
testQEMUSchemaValidate(virJSONValue * obj,virJSONValue * root,GHashTable * schema,bool allowDeprecated,virBuffer * debug)561 testQEMUSchemaValidate(virJSONValue *obj,
562                        virJSONValue *root,
563                        GHashTable *schema,
564                        bool allowDeprecated,
565                        virBuffer *debug)
566 {
567     struct testQEMUSchemaValidateCtxt ctxt = { .schema = schema,
568                                                .debug = debug,
569                                                .allowDeprecated = allowDeprecated };
570 
571     return testQEMUSchemaValidateRecurse(obj, root, &ctxt);
572 }
573 
574 
575 /**
576  * testQEMUSchemaValidateCommand:
577  * @command: command to validate
578  * @arguments: arguments of @command to validate
579  * @schema: hash table containing schema entries
580  * @allowDeprecated: don't fails schema validation if @command or one of @arguments
581  *                   is deprecated
582  * @allowRemoved: skip validation fully if @command was not found
583  * @allowIncomplete: don't fail validation if members not covered by schema are present
584  *                   (for waiving commands with incomplete schema)
585  * @debug: a virBuffer which will be filled with debug information if provided
586  *
587  * Validates whether @command and its @arguments conform to the QAPI schema
588  * passed in via @schema. Returns 0, if the command and args match @schema,
589  * -1 if it does not and -2 if there is a problem with the schema or with
590  *  internals.
591  *
592  * @allowRemoved should generally be used only if it's certain that there's a
593  * replacement of @command in place.
594  *
595  * @debug is filled with information regarding the validation process
596  */
597 int
testQEMUSchemaValidateCommand(const char * command,virJSONValue * arguments,GHashTable * schema,bool allowDeprecated,bool allowRemoved,bool allowIncomplete,virBuffer * debug)598 testQEMUSchemaValidateCommand(const char *command,
599                               virJSONValue *arguments,
600                               GHashTable *schema,
601                               bool allowDeprecated,
602                               bool allowRemoved,
603                               bool allowIncomplete,
604                               virBuffer *debug)
605 {
606     struct testQEMUSchemaValidateCtxt ctxt = { .schema = schema,
607                                                .debug = debug,
608                                                .allowDeprecated = allowDeprecated,
609                                                .allowIncomplete = allowIncomplete };
610     g_autofree char *schemapatharguments = g_strdup_printf("%s/arg-type", command);
611     g_autoptr(virJSONValue) emptyargs = NULL;
612     virJSONValue *schemarootcommand;
613     virJSONValue *schemarootarguments;
614     int rc;
615 
616     if (virQEMUQAPISchemaPathGet(command, schema, &schemarootcommand) < 0 ||
617         !schemarootcommand) {
618         if (allowRemoved)
619             return 0;
620 
621         virBufferAsprintf(debug, "ERROR: command '%s' not found in the schema", command);
622         return -1;
623     }
624 
625     if ((rc = testQEMUSchemaValidateDeprecated(schemarootcommand, command, &ctxt)) < 0)
626         return rc;
627 
628     if (!arguments)
629         arguments = emptyargs = virJSONValueNewObject();
630 
631     if (virQEMUQAPISchemaPathGet(schemapatharguments, schema, &schemarootarguments) < 0 ||
632         !schemarootarguments) {
633         virBufferAsprintf(debug, "ERROR: failed to look up 'arg-type' of  '%s'", command);
634         return -1;
635     }
636 
637     return testQEMUSchemaValidateRecurse(arguments, schemarootarguments, &ctxt);
638 }
639 
640 
641 /**
642  * testQEMUSchemaEntryMatchTemplate:
643  *
644  * @schemaentry: a JSON object representing a 'object' node in the QAPI schema
645  * ...: a NULL terminated list of strings representing the template of properties
646  *      which the QMP object needs to have.
647  *
648  *      The strings have following format:
649  *
650  *      "type:name"
651  *      "?type:name"
652  *
653  *      "type" corresponds to the 'type' property of the member to check (str, bool, any ...)
654  *      "name" corresponds to the name of the member to check
655  *
656  *      If the query string starts with an '?' and member 'name' may be missing.
657  *
658  * This function matches that @schemaentry has all expected members and the
659  * members have expected types. @schemaentry also must not have any unknown
660  * members.
661  */
662 int
testQEMUSchemaEntryMatchTemplate(virJSONValue * schemaentry,...)663 testQEMUSchemaEntryMatchTemplate(virJSONValue *schemaentry,
664                                  ...)
665 {
666     g_autoptr(virJSONValue) members = NULL;
667     va_list ap;
668     const char *next;
669     int ret = -1;
670 
671     if (STRNEQ_NULLABLE(virJSONValueObjectGetString(schemaentry, "meta-type"), "object")) {
672         VIR_TEST_VERBOSE("schemaentry is not an object");
673         return -1;
674     }
675 
676     if (!(members = virJSONValueCopy(virJSONValueObjectGetArray(schemaentry, "members")))) {
677         VIR_TEST_VERBOSE("failed to copy 'members'");
678         return -1;
679     }
680 
681     va_start(ap, schemaentry);
682 
683     /* pass 1 */
684 
685     while ((next = va_arg(ap, const char *))) {
686         char modifier = *next;
687         g_autofree char *type = NULL;
688         char *name;
689         size_t i;
690         bool found = false;
691         bool optional = false;
692 
693         if (!g_ascii_isalpha(modifier))
694             next++;
695 
696         if (modifier == '?')
697             optional = true;
698 
699         type = g_strdup(next);
700 
701         if ((name = strchr(type, ':'))) {
702             *(name++) = '\0';
703         } else {
704             VIR_TEST_VERBOSE("malformed template string '%s'", next);
705             goto cleanup;
706         }
707 
708         for (i = 0; i < virJSONValueArraySize(members); i++) {
709             virJSONValue *member = virJSONValueArrayGet(members, i);
710             const char *membername = virJSONValueObjectGetString(member, "name");
711             const char *membertype = virJSONValueObjectGetString(member, "type");
712 
713             if (STRNEQ_NULLABLE(name, membername))
714                 continue;
715 
716             if (STRNEQ_NULLABLE(membertype, type)) {
717                 VIR_TEST_VERBOSE("member '%s' is of unexpected type '%s' (expected '%s')",
718                                  NULLSTR(membername), NULLSTR(membertype), type);
719                 goto cleanup;
720             }
721 
722             found = true;
723             break;
724         }
725 
726         if (found) {
727             virJSONValueFree(virJSONValueArraySteal(members, i));
728         } else {
729             if (!optional) {
730                 VIR_TEST_VERBOSE("mandatory member '%s' not found", name);
731                 goto cleanup;
732             }
733         }
734     }
735 
736     /* pass 2 - check any unexpected members */
737     if (virJSONValueArraySize(members) > 0) {
738         size_t i;
739 
740         for (i = 0; i < virJSONValueArraySize(members); i++) {
741             VIR_TEST_VERBOSE("unexpected member '%s'",
742                              NULLSTR(virJSONValueObjectGetString(virJSONValueArrayGet(members, i), "name")));
743         }
744 
745         goto cleanup;
746     }
747 
748     ret = 0;
749 
750  cleanup:
751     va_end(ap);
752     return ret;
753 }
754 
755 
756 static virJSONValue *
testQEMUSchemaLoadReplies(const char * filename)757 testQEMUSchemaLoadReplies(const char *filename)
758 {
759     g_autofree char *caps = NULL;
760     char *schemaReply;
761     char *end;
762     g_autoptr(virJSONValue) reply = NULL;
763     virJSONValue *schema = NULL;
764 
765     if (virTestLoadFile(filename, &caps) < 0)
766         return NULL;
767 
768     if (!(schemaReply = strstr(caps, "\"execute\": \"query-qmp-schema\"")) ||
769         !(schemaReply = strstr(schemaReply, "\n\n")) ||
770         !(end = strstr(schemaReply + 2, "\n\n"))) {
771         VIR_TEST_VERBOSE("failed to find reply to 'query-qmp-schema' in '%s'",
772                          filename);
773         return NULL;
774     }
775 
776     schemaReply += 2;
777     *end = '\0';
778 
779     if (!(reply = virJSONValueFromString(schemaReply))) {
780         VIR_TEST_VERBOSE("failed to parse 'query-qmp-schema' reply from '%s'",
781                          filename);
782         return NULL;
783     }
784 
785     if (!(schema = virJSONValueObjectStealArray(reply, "return"))) {
786         VIR_TEST_VERBOSE("missing qapi schema data in reply in '%s'",
787                          filename);
788         return NULL;
789     }
790 
791     return schema;
792 }
793 
794 
795 /**
796  * testQEMUSchemaGetLatest:
797  *
798  * Returns the schema data as the qemu monitor would reply from the latest
799  * replies file used for qemucapabilitiestest for the x86_64 architecture.
800  */
801 virJSONValue *
testQEMUSchemaGetLatest(const char * arch)802 testQEMUSchemaGetLatest(const char *arch)
803 {
804     g_autofree char *capsLatestFile = NULL;
805 
806     if (!(capsLatestFile = testQemuGetLatestCapsForArch(arch, "replies"))) {
807         VIR_TEST_VERBOSE("failed to find latest caps replies");
808         return NULL;
809     }
810 
811     VIR_TEST_DEBUG("replies file: '%s'", capsLatestFile);
812 
813     return testQEMUSchemaLoadReplies(capsLatestFile);
814 }
815 
816 
817 GHashTable *
testQEMUSchemaLoadLatest(const char * arch)818 testQEMUSchemaLoadLatest(const char *arch)
819 {
820     virJSONValue *schema;
821 
822     if (!(schema = testQEMUSchemaGetLatest(arch)))
823         return NULL;
824 
825     return virQEMUQAPISchemaConvert(schema);
826 }
827 
828 
829 GHashTable *
testQEMUSchemaLoad(const char * filename)830 testQEMUSchemaLoad(const char *filename)
831 {
832     virJSONValue *schema;
833 
834     if (!(schema = testQEMUSchemaLoadReplies(filename)))
835         return NULL;
836 
837     return virQEMUQAPISchemaConvert(schema);
838 }
839