mirror of https://gitee.com/openkylin/qemu.git
block: Command line option -blockdev
-----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJYvszPAAoJEDhwtADrkYZTBesP/2A/9o/+1iziCOERjpH3r6Nb AUhr5V406FG1D+c7U7otTnPFQxOnyE6/7vPpofOh1sOvgxDsuJJfE0zkh2iOhIcW fwucbsUYG60wTyRqx+3AwAGY/iN+dRSoDlLgaVyy4KuZQbWtnvWn4USZjGULD9tD zpxuDFR0JsXTUGfYVDA/sqL1/k3AlaXpeI8n6+7YOggyvj/ja50CLPLA7EO3IX7B P4yRhNHN4E98lAzYwXE/Grl87UCJX9f7PZSSsEAU2+/wtazKi64MM0c+5zWcm8QJ qjGAfoZtP1+pa7CgZpYOMrfk02FDanD1LQ9nzKk67+5K90Sa5IU0PlXpzgD9gL3W yfiUbt6RyP/hNzNH9pylCB3nGl6YIgBc1TNduLLjlRJldWV1e/PSumH+fXNvu+lH xa0la20XqURqakO76cwwv/2ePmhUhumCuw2XV93D65c9cRRvgTOMNrO6OdEA1bXs rW5T1Rq9eReq4VXWtYPGV9kadzguQzijUzRG105GVRXZxlc4RfXfiPSDlgAUC16e daoyH1D2s2F/+B/VheFWhQXxl1NLLYMrmnMKVdF5zSkXHjvDHnMYbCYvrD/+bV6j sK6wmNhxvRbgXD06noWM0XE/GxHcUW37A/9G2PgO+JJQtXshGe5U9B2ov7zMdoyi exXek6jkUfBCt3L2lgWu =vCJo -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/armbru/tags/pull-block-2017-02-28-v4' into staging block: Command line option -blockdev # gpg: Signature made Tue 07 Mar 2017 15:07:59 GMT # gpg: using RSA key 0x3870B400EB918653 # gpg: Good signature from "Markus Armbruster <armbru@redhat.com>" # gpg: aka "Markus Armbruster <armbru@pond.sub.org>" # Primary key fingerprint: 354B C8B3 D7EB 2A6B 6867 4E5F 3870 B400 EB91 8653 * remotes/armbru/tags/pull-block-2017-02-28-v4: (24 commits) keyval: Support lists docs/qapi-code-gen.txt: Clarify naming rules qapi: Improve how keyval input visitor reports unexpected dicts block: Initial implementation of -blockdev qapi: New qobject_input_visitor_new_str() for convenience keyval: Restrict key components to valid QAPI names qapi: New parse_qapi_name() test-qapi-util: New, covering qapi/qapi-util.c monitor: Assert qmp_schema_json[] is sane test-visitor-serialization: Pass &error_abort to qobject_from_json() check-qjson: Test errors from qobject_from_json() block: More detailed syntax error reporting for JSON filenames qobject: Propagate parse errors through qobject_from_json() test-qobject-input-visitor: Abort earlier on bad test input qjson: Abort earlier on qobject_from_jsonf() misuse libqtest: Fix qmp() & friends to abort on JSON parse errors qobject: Propagate parse errors through qobject_from_jsonv() qapi: Factor out common qobject_input_get_keyval() qapi: Factor out common part of qobject input visitor creation test-keyval: Cover use with qobject input visitor ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
87467097f8
9
block.c
9
block.c
|
@ -1262,9 +1262,14 @@ static QDict *parse_json_filename(const char *filename, Error **errp)
|
|||
ret = strstart(filename, "json:", &filename);
|
||||
assert(ret);
|
||||
|
||||
options_obj = qobject_from_json(filename);
|
||||
options_obj = qobject_from_json(filename, errp);
|
||||
if (!options_obj) {
|
||||
error_setg(errp, "Could not parse the JSON options");
|
||||
/* Work around qobject_from_json() lossage TODO fix that */
|
||||
if (errp && !*errp) {
|
||||
error_setg(errp, "Could not parse the JSON options");
|
||||
return NULL;
|
||||
}
|
||||
error_prepend(errp, "Could not parse the JSON options: ");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -216,33 +216,38 @@ single-dimension array of that type; multi-dimension arrays are not
|
|||
directly supported (although an array of a complex struct that
|
||||
contains an array member is possible).
|
||||
|
||||
All names must begin with a letter, and contain only ASCII letters,
|
||||
digits, hyphen, and underscore. There are two exceptions: enum values
|
||||
may start with a digit, and names that are downstream extensions (see
|
||||
section Downstream extensions) start with underscore.
|
||||
|
||||
Names beginning with 'q_' are reserved for the generator, which uses
|
||||
them for munging QMP names that resemble C keywords or other
|
||||
problematic strings. For example, a member named "default" in qapi
|
||||
becomes "q_default" in the generated C code.
|
||||
|
||||
Types, commands, and events share a common namespace. Therefore,
|
||||
generally speaking, type definitions should always use CamelCase for
|
||||
user-defined type names, while built-in types are lowercase. Type
|
||||
definitions should not end in 'Kind', as this namespace is used for
|
||||
creating implicit C enums for visiting union types, or in 'List', as
|
||||
this namespace is used for creating array types. Command names,
|
||||
and member names within a type, should be all lower case with words
|
||||
separated by a hyphen. However, some existing older commands and
|
||||
complex types use underscore; when extending such expressions,
|
||||
consistency is preferred over blindly avoiding underscore. Event
|
||||
names should be ALL_CAPS with words separated by underscore. Member
|
||||
names cannot start with 'has-' or 'has_', as this is reserved for
|
||||
tracking optional members.
|
||||
user-defined type names, while built-in types are lowercase.
|
||||
|
||||
Type names ending with 'Kind' or 'List' are reserved for the
|
||||
generator, which uses them for implicit union enums and array types,
|
||||
respectively.
|
||||
|
||||
Command names, and member names within a type, should be all lower
|
||||
case with words separated by a hyphen. However, some existing older
|
||||
commands and complex types use underscore; when extending such
|
||||
expressions, consistency is preferred over blindly avoiding
|
||||
underscore.
|
||||
|
||||
Event names should be ALL_CAPS with words separated by underscore.
|
||||
|
||||
Member names starting with 'has-' or 'has_' are reserved for the
|
||||
generator, which uses them for tracking optional members.
|
||||
|
||||
Any name (command, event, type, member, or enum value) beginning with
|
||||
"x-" is marked experimental, and may be withdrawn or changed
|
||||
incompatibly in a future release. All names must begin with a letter,
|
||||
and contain only ASCII letters, digits, dash, and underscore. There
|
||||
are two exceptions: enum values may start with a digit, and any
|
||||
extensions added by downstream vendors should start with a prefix
|
||||
matching "__RFQDN_" (for the reverse-fully-qualified-domain-name of
|
||||
the vendor), even if the rest of the name uses dash (example:
|
||||
__com.redhat_drive-mirror). Names beginning with 'q_' are reserved
|
||||
for the generator: QMP names that resemble C keywords or other
|
||||
problematic strings will be munged in C to use this prefix. For
|
||||
example, a member named "default" in qapi becomes "q_default" in the
|
||||
generated C code.
|
||||
incompatibly in a future release.
|
||||
|
||||
In the rest of this document, usage lines are given for each
|
||||
expression type, with literal strings written in lower case and
|
||||
|
@ -643,6 +648,18 @@ any non-empty complex type (struct, union, or alternate), and a
|
|||
pointer to that QAPI type is passed as a single argument.
|
||||
|
||||
|
||||
=== Downstream extensions ===
|
||||
|
||||
QAPI schema names that are externally visible, say in the Client JSON
|
||||
Protocol, need to be managed with care. Names starting with a
|
||||
downstream prefix of the form __RFQDN_ are reserved for the downstream
|
||||
who controls the valid, reverse fully qualified domain name RFQDN.
|
||||
RFQDN may only contain ASCII letters, digits, hyphen and period.
|
||||
|
||||
Example: Red Hat, Inc. controls redhat.com, and may therefore add a
|
||||
downstream command __com.redhat_drive-mirror.
|
||||
|
||||
|
||||
== Client JSON Protocol introspection ==
|
||||
|
||||
Clients of a Client JSON Protocol commonly need to figure out what
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
#include "qapi/qmp/qobject.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
|
||||
QObject *qobject_from_json(const char *string);
|
||||
QObject *qobject_from_json(const char *string, Error **errp);
|
||||
QObject *qobject_from_jsonf(const char *string, ...) GCC_FMT_ATTR(1, 2);
|
||||
QObject *qobject_from_jsonv(const char *string, va_list *ap) GCC_FMT_ATTR(1, 0);
|
||||
QObject *qobject_from_jsonv(const char *string, va_list *ap, Error **errp)
|
||||
GCC_FMT_ATTR(1, 0);
|
||||
|
||||
QString *qobject_to_json(const QObject *obj);
|
||||
QString *qobject_to_json_pretty(const QObject *obj);
|
||||
|
|
|
@ -59,4 +59,25 @@ typedef struct QObjectInputVisitor QObjectInputVisitor;
|
|||
*/
|
||||
Visitor *qobject_input_visitor_new(QObject *obj);
|
||||
|
||||
/*
|
||||
* Create a QObject input visitor for @obj for use with keyval_parse()
|
||||
*
|
||||
* This is like qobject_input_visitor_new(), except scalars are all
|
||||
* QString, and error messages refer to parts of @obj in the syntax
|
||||
* keyval_parse() uses for KEYs.
|
||||
*/
|
||||
Visitor *qobject_input_visitor_new_keyval(QObject *obj);
|
||||
|
||||
/*
|
||||
* Create a QObject input visitor for parsing @str.
|
||||
*
|
||||
* If @str looks like JSON, parse it as JSON, else as KEY=VALUE,...
|
||||
* @implied_key applies to KEY=VALUE, and works as in keyval_parse().
|
||||
* On failure, store an error through @errp and return NULL.
|
||||
* On success, return a new QObject input visitor for the parse.
|
||||
*/
|
||||
Visitor *qobject_input_visitor_new_str(const char *str,
|
||||
const char *implied_key,
|
||||
Error **errp);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -14,4 +14,6 @@
|
|||
int qapi_enum_parse(const char * const lookup[], const char *buf,
|
||||
int max, int def, Error **errp);
|
||||
|
||||
int parse_qapi_name(const char *name, bool complete);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -141,4 +141,7 @@ void qemu_opts_print_help(QemuOptsList *list);
|
|||
void qemu_opts_free(QemuOptsList *list);
|
||||
QemuOptsList *qemu_opts_append(QemuOptsList *dst, QemuOptsList *list);
|
||||
|
||||
QDict *keyval_parse(const char *params, const char *implied_key,
|
||||
Error **errp);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -953,7 +953,7 @@ EventInfoList *qmp_query_events(Error **errp)
|
|||
static void qmp_query_qmp_schema(QDict *qdict, QObject **ret_data,
|
||||
Error **errp)
|
||||
{
|
||||
*ret_data = qobject_from_json(qmp_schema_json);
|
||||
*ret_data = qobject_from_json(qmp_schema_json, &error_abort);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -33,3 +33,50 @@ int qapi_enum_parse(const char * const lookup[], const char *buf,
|
|||
error_setg(errp, "invalid parameter value: %s", buf);
|
||||
return def;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a valid QAPI name from @str.
|
||||
* A valid name consists of letters, digits, hyphen and underscore.
|
||||
* It may be prefixed by __RFQDN_ (downstream extension), where RFQDN
|
||||
* may contain only letters, digits, hyphen and period.
|
||||
* The special exception for enumeration names is not implemented.
|
||||
* See docs/qapi-code-gen.txt for more on QAPI naming rules.
|
||||
* Keep this consistent with scripts/qapi.py!
|
||||
* If @complete, the parse fails unless it consumes @str completely.
|
||||
* Return its length on success, -1 on failure.
|
||||
*/
|
||||
int parse_qapi_name(const char *str, bool complete)
|
||||
{
|
||||
const char *p = str;
|
||||
|
||||
if (*p == '_') { /* Downstream __RFQDN_ */
|
||||
p++;
|
||||
if (*p != '_') {
|
||||
return -1;
|
||||
}
|
||||
while (*++p) {
|
||||
if (!qemu_isalnum(*p) && *p != '-' && *p != '.') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (*p != '_') {
|
||||
return -1;
|
||||
}
|
||||
p++;
|
||||
}
|
||||
|
||||
if (!qemu_isalpha(*p)) {
|
||||
return -1;
|
||||
}
|
||||
while (*++p) {
|
||||
if (!qemu_isalnum(*p) && *p != '-' && *p != '_') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (complete && *p) {
|
||||
return -1;
|
||||
}
|
||||
return p - str;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Input Visitor
|
||||
*
|
||||
* Copyright (C) 2012-2016 Red Hat, Inc.
|
||||
* Copyright (C) 2012-2017 Red Hat, Inc.
|
||||
* Copyright IBM, Corp. 2011
|
||||
*
|
||||
* Authors:
|
||||
|
@ -18,8 +18,11 @@
|
|||
#include "qapi/visitor-impl.h"
|
||||
#include "qemu/queue.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
#include "qapi/qmp/types.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qemu/option.h"
|
||||
|
||||
typedef struct StackObject {
|
||||
const char *name; /* Name of @obj in its parent, if any */
|
||||
|
@ -38,6 +41,7 @@ struct QObjectInputVisitor {
|
|||
|
||||
/* Root of visit at visitor creation. */
|
||||
QObject *root;
|
||||
bool keyval; /* Assume @root made with keyval_parse() */
|
||||
|
||||
/* Stack of objects being visited (all entries will be either
|
||||
* QDict or QList). */
|
||||
|
@ -70,7 +74,9 @@ static const char *full_name_nth(QObjectInputVisitor *qiv, const char *name,
|
|||
g_string_prepend(qiv->errname, name ?: "<anonymous>");
|
||||
g_string_prepend_c(qiv->errname, '.');
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "[%u]", so->index);
|
||||
snprintf(buf, sizeof(buf),
|
||||
qiv->keyval ? ".%u" : "[%u]",
|
||||
so->index);
|
||||
g_string_prepend(qiv->errname, buf);
|
||||
}
|
||||
name = so->name;
|
||||
|
@ -150,6 +156,37 @@ static QObject *qobject_input_get_object(QObjectInputVisitor *qiv,
|
|||
return obj;
|
||||
}
|
||||
|
||||
static const char *qobject_input_get_keyval(QObjectInputVisitor *qiv,
|
||||
const char *name,
|
||||
Error **errp)
|
||||
{
|
||||
QObject *qobj;
|
||||
QString *qstr;
|
||||
|
||||
qobj = qobject_input_get_object(qiv, name, true, errp);
|
||||
if (!qobj) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
qstr = qobject_to_qstring(qobj);
|
||||
if (!qstr) {
|
||||
switch (qobject_type(qobj)) {
|
||||
case QTYPE_QDICT:
|
||||
case QTYPE_QLIST:
|
||||
error_setg(errp, "Parameters '%s.*' are unexpected",
|
||||
full_name(qiv, name));
|
||||
return NULL;
|
||||
default:
|
||||
/* Non-string scalar (should this be an assertion?) */
|
||||
error_setg(errp, "Internal error: parameter %s invalid",
|
||||
full_name(qiv, name));
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return qstring_get_str(qstr);
|
||||
}
|
||||
|
||||
static void qdict_add_key(const char *key, QObject *obj, void *opaque)
|
||||
{
|
||||
GHashTable *h = opaque;
|
||||
|
@ -337,6 +374,24 @@ static void qobject_input_type_int64(Visitor *v, const char *name, int64_t *obj,
|
|||
*obj = qint_get_int(qint);
|
||||
}
|
||||
|
||||
|
||||
static void qobject_input_type_int64_keyval(Visitor *v, const char *name,
|
||||
int64_t *obj, Error **errp)
|
||||
{
|
||||
QObjectInputVisitor *qiv = to_qiv(v);
|
||||
const char *str = qobject_input_get_keyval(qiv, name, errp);
|
||||
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (qemu_strtoi64(str, NULL, 0, obj) < 0) {
|
||||
/* TODO report -ERANGE more nicely */
|
||||
error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
|
||||
full_name(qiv, name), "integer");
|
||||
}
|
||||
}
|
||||
|
||||
static void qobject_input_type_uint64(Visitor *v, const char *name,
|
||||
uint64_t *obj, Error **errp)
|
||||
{
|
||||
|
@ -358,6 +413,23 @@ static void qobject_input_type_uint64(Visitor *v, const char *name,
|
|||
*obj = qint_get_int(qint);
|
||||
}
|
||||
|
||||
static void qobject_input_type_uint64_keyval(Visitor *v, const char *name,
|
||||
uint64_t *obj, Error **errp)
|
||||
{
|
||||
QObjectInputVisitor *qiv = to_qiv(v);
|
||||
const char *str = qobject_input_get_keyval(qiv, name, errp);
|
||||
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (qemu_strtou64(str, NULL, 0, obj) < 0) {
|
||||
/* TODO report -ERANGE more nicely */
|
||||
error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
|
||||
full_name(qiv, name), "integer");
|
||||
}
|
||||
}
|
||||
|
||||
static void qobject_input_type_bool(Visitor *v, const char *name, bool *obj,
|
||||
Error **errp)
|
||||
{
|
||||
|
@ -378,6 +450,26 @@ static void qobject_input_type_bool(Visitor *v, const char *name, bool *obj,
|
|||
*obj = qbool_get_bool(qbool);
|
||||
}
|
||||
|
||||
static void qobject_input_type_bool_keyval(Visitor *v, const char *name,
|
||||
bool *obj, Error **errp)
|
||||
{
|
||||
QObjectInputVisitor *qiv = to_qiv(v);
|
||||
const char *str = qobject_input_get_keyval(qiv, name, errp);
|
||||
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strcmp(str, "on")) {
|
||||
*obj = true;
|
||||
} else if (!strcmp(str, "off")) {
|
||||
*obj = false;
|
||||
} else {
|
||||
error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
|
||||
full_name(qiv, name), "'on' or 'off'");
|
||||
}
|
||||
}
|
||||
|
||||
static void qobject_input_type_str(Visitor *v, const char *name, char **obj,
|
||||
Error **errp)
|
||||
{
|
||||
|
@ -399,6 +491,15 @@ static void qobject_input_type_str(Visitor *v, const char *name, char **obj,
|
|||
*obj = g_strdup(qstring_get_str(qstr));
|
||||
}
|
||||
|
||||
static void qobject_input_type_str_keyval(Visitor *v, const char *name,
|
||||
char **obj, Error **errp)
|
||||
{
|
||||
QObjectInputVisitor *qiv = to_qiv(v);
|
||||
const char *str = qobject_input_get_keyval(qiv, name, errp);
|
||||
|
||||
*obj = g_strdup(str);
|
||||
}
|
||||
|
||||
static void qobject_input_type_number(Visitor *v, const char *name, double *obj,
|
||||
Error **errp)
|
||||
{
|
||||
|
@ -426,6 +527,26 @@ static void qobject_input_type_number(Visitor *v, const char *name, double *obj,
|
|||
full_name(qiv, name), "number");
|
||||
}
|
||||
|
||||
static void qobject_input_type_number_keyval(Visitor *v, const char *name,
|
||||
double *obj, Error **errp)
|
||||
{
|
||||
QObjectInputVisitor *qiv = to_qiv(v);
|
||||
const char *str = qobject_input_get_keyval(qiv, name, errp);
|
||||
char *endp;
|
||||
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
*obj = strtod(str, &endp);
|
||||
if (errno || endp == str || *endp) {
|
||||
/* TODO report -ERANGE more nicely */
|
||||
error_setg(errp, QERR_INVALID_PARAMETER_TYPE,
|
||||
full_name(qiv, name), "number");
|
||||
}
|
||||
}
|
||||
|
||||
static void qobject_input_type_any(Visitor *v, const char *name, QObject **obj,
|
||||
Error **errp)
|
||||
{
|
||||
|
@ -456,6 +577,23 @@ static void qobject_input_type_null(Visitor *v, const char *name, Error **errp)
|
|||
}
|
||||
}
|
||||
|
||||
static void qobject_input_type_size_keyval(Visitor *v, const char *name,
|
||||
uint64_t *obj, Error **errp)
|
||||
{
|
||||
QObjectInputVisitor *qiv = to_qiv(v);
|
||||
const char *str = qobject_input_get_keyval(qiv, name, errp);
|
||||
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (qemu_strtosz(str, NULL, obj) < 0) {
|
||||
/* TODO report -ERANGE more nicely */
|
||||
error_setg(errp, QERR_INVALID_PARAMETER_VALUE,
|
||||
full_name(qiv, name), "size");
|
||||
}
|
||||
}
|
||||
|
||||
static void qobject_input_optional(Visitor *v, const char *name, bool *present)
|
||||
{
|
||||
QObjectInputVisitor *qiv = to_qiv(v);
|
||||
|
@ -487,12 +625,11 @@ static void qobject_input_free(Visitor *v)
|
|||
g_free(qiv);
|
||||
}
|
||||
|
||||
Visitor *qobject_input_visitor_new(QObject *obj)
|
||||
static QObjectInputVisitor *qobject_input_visitor_base_new(QObject *obj)
|
||||
{
|
||||
QObjectInputVisitor *v;
|
||||
QObjectInputVisitor *v = g_malloc0(sizeof(*v));
|
||||
|
||||
assert(obj);
|
||||
v = g_malloc0(sizeof(*v));
|
||||
|
||||
v->visitor.type = VISITOR_INPUT;
|
||||
v->visitor.start_struct = qobject_input_start_struct;
|
||||
|
@ -503,6 +640,19 @@ Visitor *qobject_input_visitor_new(QObject *obj)
|
|||
v->visitor.check_list = qobject_input_check_list;
|
||||
v->visitor.end_list = qobject_input_pop;
|
||||
v->visitor.start_alternate = qobject_input_start_alternate;
|
||||
v->visitor.optional = qobject_input_optional;
|
||||
v->visitor.free = qobject_input_free;
|
||||
|
||||
v->root = obj;
|
||||
qobject_incref(obj);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
Visitor *qobject_input_visitor_new(QObject *obj)
|
||||
{
|
||||
QObjectInputVisitor *v = qobject_input_visitor_base_new(obj);
|
||||
|
||||
v->visitor.type_int64 = qobject_input_type_int64;
|
||||
v->visitor.type_uint64 = qobject_input_type_uint64;
|
||||
v->visitor.type_bool = qobject_input_type_bool;
|
||||
|
@ -510,11 +660,57 @@ Visitor *qobject_input_visitor_new(QObject *obj)
|
|||
v->visitor.type_number = qobject_input_type_number;
|
||||
v->visitor.type_any = qobject_input_type_any;
|
||||
v->visitor.type_null = qobject_input_type_null;
|
||||
v->visitor.optional = qobject_input_optional;
|
||||
v->visitor.free = qobject_input_free;
|
||||
|
||||
v->root = obj;
|
||||
qobject_incref(obj);
|
||||
|
||||
return &v->visitor;
|
||||
}
|
||||
|
||||
Visitor *qobject_input_visitor_new_keyval(QObject *obj)
|
||||
{
|
||||
QObjectInputVisitor *v = qobject_input_visitor_base_new(obj);
|
||||
|
||||
v->visitor.type_int64 = qobject_input_type_int64_keyval;
|
||||
v->visitor.type_uint64 = qobject_input_type_uint64_keyval;
|
||||
v->visitor.type_bool = qobject_input_type_bool_keyval;
|
||||
v->visitor.type_str = qobject_input_type_str_keyval;
|
||||
v->visitor.type_number = qobject_input_type_number_keyval;
|
||||
v->visitor.type_any = qobject_input_type_any;
|
||||
v->visitor.type_null = qobject_input_type_null;
|
||||
v->visitor.type_size = qobject_input_type_size_keyval;
|
||||
v->keyval = true;
|
||||
|
||||
return &v->visitor;
|
||||
}
|
||||
|
||||
Visitor *qobject_input_visitor_new_str(const char *str,
|
||||
const char *implied_key,
|
||||
Error **errp)
|
||||
{
|
||||
bool is_json = str[0] == '{';
|
||||
QObject *obj;
|
||||
QDict *args;
|
||||
Visitor *v;
|
||||
|
||||
if (is_json) {
|
||||
obj = qobject_from_json(str, errp);
|
||||
if (!obj) {
|
||||
/* Work around qobject_from_json() lossage TODO fix that */
|
||||
if (errp && !*errp) {
|
||||
error_setg(errp, "JSON parse error");
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
args = qobject_to_qdict(obj);
|
||||
assert(args);
|
||||
v = qobject_input_visitor_new(QOBJECT(args));
|
||||
} else {
|
||||
args = keyval_parse(str, implied_key, errp);
|
||||
if (!args) {
|
||||
return NULL;
|
||||
}
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(args));
|
||||
}
|
||||
QDECREF(args);
|
||||
|
||||
return v;
|
||||
}
|
||||
|
|
|
@ -550,6 +550,13 @@ Use @var{file} as CD-ROM image (you cannot use @option{-hdc} and
|
|||
using @file{/dev/cdrom} as filename (@pxref{host_drives}).
|
||||
ETEXI
|
||||
|
||||
DEF("blockdev", HAS_ARG, QEMU_OPTION_blockdev,
|
||||
"-blockdev [driver=]driver[,node-name=N][,discard=ignore|unmap]\n"
|
||||
" [,cache.direct=on|off][,cache.no-flush=on|off]\n"
|
||||
" [,read-only=on|off][,detect-zeroes=on|off|unmap]\n"
|
||||
" [,driver specific parameters...]\n"
|
||||
" configure a block backend\n", QEMU_ARCH_ALL)
|
||||
|
||||
DEF("drive", HAS_ARG, QEMU_OPTION_drive,
|
||||
"-drive [file=file][,if=type][,bus=n][,unit=m][,media=d][,index=i]\n"
|
||||
" [,cyls=c,heads=h,secs=s[,trans=t]][,snapshot=on|off]\n"
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/json-lexer.h"
|
||||
#include "qapi/qmp/json-parser.h"
|
||||
#include "qapi/qmp/json-streamer.h"
|
||||
|
@ -24,15 +25,17 @@ typedef struct JSONParsingState
|
|||
JSONMessageParser parser;
|
||||
va_list *ap;
|
||||
QObject *result;
|
||||
Error *err;
|
||||
} JSONParsingState;
|
||||
|
||||
static void parse_json(JSONMessageParser *parser, GQueue *tokens)
|
||||
{
|
||||
JSONParsingState *s = container_of(parser, JSONParsingState, parser);
|
||||
s->result = json_parser_parse(tokens, s->ap);
|
||||
|
||||
s->result = json_parser_parse_err(tokens, s->ap, &s->err);
|
||||
}
|
||||
|
||||
QObject *qobject_from_jsonv(const char *string, va_list *ap)
|
||||
QObject *qobject_from_jsonv(const char *string, va_list *ap, Error **errp)
|
||||
{
|
||||
JSONParsingState state = {};
|
||||
|
||||
|
@ -43,12 +46,13 @@ QObject *qobject_from_jsonv(const char *string, va_list *ap)
|
|||
json_message_parser_flush(&state.parser);
|
||||
json_message_parser_destroy(&state.parser);
|
||||
|
||||
error_propagate(errp, state.err);
|
||||
return state.result;
|
||||
}
|
||||
|
||||
QObject *qobject_from_json(const char *string)
|
||||
QObject *qobject_from_json(const char *string, Error **errp)
|
||||
{
|
||||
return qobject_from_jsonv(string, NULL);
|
||||
return qobject_from_jsonv(string, NULL, errp);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -61,7 +65,7 @@ QObject *qobject_from_jsonf(const char *string, ...)
|
|||
va_list ap;
|
||||
|
||||
va_start(ap, string);
|
||||
obj = qobject_from_jsonv(string, &ap);
|
||||
obj = qobject_from_jsonv(string, &ap, &error_abort);
|
||||
va_end(ap);
|
||||
|
||||
assert(obj != NULL);
|
||||
|
|
|
@ -47,11 +47,13 @@ test-io-channel-file.txt
|
|||
test-io-channel-socket
|
||||
test-io-channel-tls
|
||||
test-io-task
|
||||
test-keyval
|
||||
test-logging
|
||||
test-mul64
|
||||
test-opts-visitor
|
||||
test-qapi-event.[ch]
|
||||
test-qapi-types.[ch]
|
||||
test-qapi-util
|
||||
test-qapi-visit.[ch]
|
||||
test-qdev-global-props
|
||||
test-qemu-opts
|
||||
|
|
|
@ -93,7 +93,9 @@ gcov-files-check-qom-interface-y = qom/object.c
|
|||
check-unit-y += tests/check-qom-proplist$(EXESUF)
|
||||
gcov-files-check-qom-proplist-y = qom/object.c
|
||||
check-unit-y += tests/test-qemu-opts$(EXESUF)
|
||||
gcov-files-test-qemu-opts-y = qom/test-qemu-opts.c
|
||||
gcov-files-test-qemu-opts-y = util/qemu-option.c
|
||||
check-unit-y += tests/test-keyval$(EXESUF)
|
||||
gcov-files-test-keyval-y = util/keyval.c
|
||||
check-unit-y += tests/test-write-threshold$(EXESUF)
|
||||
gcov-files-test-write-threshold-y = block/write-threshold.c
|
||||
check-unit-y += tests/test-crypto-hash$(EXESUF)
|
||||
|
@ -118,14 +120,16 @@ check-unit-y += tests/test-crypto-ivgen$(EXESUF)
|
|||
check-unit-y += tests/test-crypto-afsplit$(EXESUF)
|
||||
check-unit-y += tests/test-crypto-xts$(EXESUF)
|
||||
check-unit-y += tests/test-crypto-block$(EXESUF)
|
||||
gcov-files-test-logging-y = tests/test-logging.c
|
||||
check-unit-y += tests/test-logging$(EXESUF)
|
||||
gcov-files-test-logging-y = util/log.c
|
||||
check-unit-$(CONFIG_REPLICATION) += tests/test-replication$(EXESUF)
|
||||
check-unit-y += tests/test-bufferiszero$(EXESUF)
|
||||
gcov-files-check-bufferiszero-y = util/bufferiszero.c
|
||||
check-unit-y += tests/test-uuid$(EXESUF)
|
||||
check-unit-y += tests/ptimer-test$(EXESUF)
|
||||
gcov-files-ptimer-test-y = hw/core/ptimer.c
|
||||
check-unit-y += tests/test-qapi-util$(EXESUF)
|
||||
gcov-files-test-qapi-util-y = qapi/qapi-util.c
|
||||
|
||||
check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
|
||||
|
||||
|
@ -720,6 +724,7 @@ tests/vhost-user-test$(EXESUF): tests/vhost-user-test.o $(test-util-obj-y) \
|
|||
$(chardev-obj-y)
|
||||
tests/qemu-iotests/socket_scm_helper$(EXESUF): tests/qemu-iotests/socket_scm_helper.o
|
||||
tests/test-qemu-opts$(EXESUF): tests/test-qemu-opts.o $(test-util-obj-y)
|
||||
tests/test-keyval$(EXESUF): tests/test-keyval.o $(test-util-obj-y)
|
||||
tests/test-write-threshold$(EXESUF): tests/test-write-threshold.o $(test-block-obj-y)
|
||||
tests/test-netfilter$(EXESUF): tests/test-netfilter.o $(qtest-obj-y)
|
||||
tests/test-filter-mirror$(EXESUF): tests/test-filter-mirror.o $(qtest-obj-y)
|
||||
|
@ -729,6 +734,7 @@ tests/ivshmem-test$(EXESUF): tests/ivshmem-test.o contrib/ivshmem-server/ivshmem
|
|||
tests/vhost-user-bridge$(EXESUF): tests/vhost-user-bridge.o contrib/libvhost-user/libvhost-user.o $(test-util-obj-y)
|
||||
tests/test-uuid$(EXESUF): tests/test-uuid.o $(test-util-obj-y)
|
||||
tests/test-arm-mptimer$(EXESUF): tests/test-arm-mptimer.o
|
||||
tests/test-qapi-util$(EXESUF): tests/test-qapi-util.o $(test-util-obj-y)
|
||||
|
||||
tests/migration/stress$(EXESUF): tests/migration/stress.o
|
||||
$(call quiet-command, $(LINKPROG) -static -O3 $(PTHREAD_LIB) -o $@ $< ,"LINK","$(TARGET_DIR)$@")
|
||||
|
|
|
@ -10,8 +10,10 @@
|
|||
* See the COPYING.LIB file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/types.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
#include "qemu-common.h"
|
||||
|
@ -53,7 +55,7 @@ static void escaped_string(void)
|
|||
QObject *obj;
|
||||
QString *str;
|
||||
|
||||
obj = qobject_from_json(test_cases[i].encoded);
|
||||
obj = qobject_from_json(test_cases[i].encoded, &error_abort);
|
||||
str = qobject_to_qstring(obj);
|
||||
g_assert(str);
|
||||
g_assert_cmpstr(qstring_get_str(str), ==, test_cases[i].decoded);
|
||||
|
@ -85,7 +87,7 @@ static void simple_string(void)
|
|||
QObject *obj;
|
||||
QString *str;
|
||||
|
||||
obj = qobject_from_json(test_cases[i].encoded);
|
||||
obj = qobject_from_json(test_cases[i].encoded, &error_abort);
|
||||
str = qobject_to_qstring(obj);
|
||||
g_assert(str);
|
||||
g_assert(strcmp(qstring_get_str(str), test_cases[i].decoded) == 0);
|
||||
|
@ -116,7 +118,7 @@ static void single_quote_string(void)
|
|||
QObject *obj;
|
||||
QString *str;
|
||||
|
||||
obj = qobject_from_json(test_cases[i].encoded);
|
||||
obj = qobject_from_json(test_cases[i].encoded, &error_abort);
|
||||
str = qobject_to_qstring(obj);
|
||||
g_assert(str);
|
||||
g_assert(strcmp(qstring_get_str(str), test_cases[i].decoded) == 0);
|
||||
|
@ -809,7 +811,7 @@ static void utf8_string(void)
|
|||
utf8_in = test_cases[i].utf8_in ?: test_cases[i].utf8_out;
|
||||
json_out = test_cases[i].json_out ?: test_cases[i].json_in;
|
||||
|
||||
obj = qobject_from_json(json_in);
|
||||
obj = qobject_from_json(json_in, utf8_out ? &error_abort : NULL);
|
||||
if (utf8_out) {
|
||||
str = qobject_to_qstring(obj);
|
||||
g_assert(str);
|
||||
|
@ -836,7 +838,7 @@ static void utf8_string(void)
|
|||
* FIXME Enable once these bugs have been fixed.
|
||||
*/
|
||||
if (0 && json_out != json_in) {
|
||||
obj = qobject_from_json(json_out);
|
||||
obj = qobject_from_json(json_out, &error_abort);
|
||||
str = qobject_to_qstring(obj);
|
||||
g_assert(str);
|
||||
g_assert_cmpstr(qstring_get_str(str), ==, utf8_out);
|
||||
|
@ -886,7 +888,8 @@ static void simple_number(void)
|
|||
for (i = 0; test_cases[i].encoded; i++) {
|
||||
QInt *qint;
|
||||
|
||||
qint = qobject_to_qint(qobject_from_json(test_cases[i].encoded));
|
||||
qint = qobject_to_qint(qobject_from_json(test_cases[i].encoded,
|
||||
&error_abort));
|
||||
g_assert(qint);
|
||||
g_assert(qint_get_int(qint) == test_cases[i].decoded);
|
||||
if (test_cases[i].skip == 0) {
|
||||
|
@ -920,7 +923,7 @@ static void float_number(void)
|
|||
QObject *obj;
|
||||
QFloat *qfloat;
|
||||
|
||||
obj = qobject_from_json(test_cases[i].encoded);
|
||||
obj = qobject_from_json(test_cases[i].encoded, &error_abort);
|
||||
qfloat = qobject_to_qfloat(obj);
|
||||
g_assert(qfloat);
|
||||
g_assert(qfloat_get_double(qfloat) == test_cases[i].decoded);
|
||||
|
@ -965,7 +968,7 @@ static void keyword_literal(void)
|
|||
QObject *null;
|
||||
QString *str;
|
||||
|
||||
obj = qobject_from_json("true");
|
||||
obj = qobject_from_json("true", &error_abort);
|
||||
qbool = qobject_to_qbool(obj);
|
||||
g_assert(qbool);
|
||||
g_assert(qbool_get_bool(qbool) == true);
|
||||
|
@ -976,7 +979,7 @@ static void keyword_literal(void)
|
|||
|
||||
QDECREF(qbool);
|
||||
|
||||
obj = qobject_from_json("false");
|
||||
obj = qobject_from_json("false", &error_abort);
|
||||
qbool = qobject_to_qbool(obj);
|
||||
g_assert(qbool);
|
||||
g_assert(qbool_get_bool(qbool) == false);
|
||||
|
@ -998,7 +1001,7 @@ static void keyword_literal(void)
|
|||
g_assert(qbool_get_bool(qbool) == true);
|
||||
QDECREF(qbool);
|
||||
|
||||
obj = qobject_from_json("null");
|
||||
obj = qobject_from_json("null", &error_abort);
|
||||
g_assert(obj != NULL);
|
||||
g_assert(qobject_type(obj) == QTYPE_QNULL);
|
||||
|
||||
|
@ -1134,13 +1137,13 @@ static void simple_dict(void)
|
|||
QObject *obj;
|
||||
QString *str;
|
||||
|
||||
obj = qobject_from_json(test_cases[i].encoded);
|
||||
obj = qobject_from_json(test_cases[i].encoded, &error_abort);
|
||||
g_assert(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1);
|
||||
|
||||
str = qobject_to_json(obj);
|
||||
qobject_decref(obj);
|
||||
|
||||
obj = qobject_from_json(qstring_get_str(str));
|
||||
obj = qobject_from_json(qstring_get_str(str), &error_abort);
|
||||
g_assert(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1);
|
||||
qobject_decref(obj);
|
||||
QDECREF(str);
|
||||
|
@ -1192,7 +1195,7 @@ static void large_dict(void)
|
|||
QObject *obj;
|
||||
|
||||
gen_test_json(gstr, 10, 100);
|
||||
obj = qobject_from_json(gstr->str);
|
||||
obj = qobject_from_json(gstr->str, &error_abort);
|
||||
g_assert(obj != NULL);
|
||||
|
||||
qobject_decref(obj);
|
||||
|
@ -1243,13 +1246,13 @@ static void simple_list(void)
|
|||
QObject *obj;
|
||||
QString *str;
|
||||
|
||||
obj = qobject_from_json(test_cases[i].encoded);
|
||||
obj = qobject_from_json(test_cases[i].encoded, &error_abort);
|
||||
g_assert(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1);
|
||||
|
||||
str = qobject_to_json(obj);
|
||||
qobject_decref(obj);
|
||||
|
||||
obj = qobject_from_json(qstring_get_str(str));
|
||||
obj = qobject_from_json(qstring_get_str(str), &error_abort);
|
||||
g_assert(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1);
|
||||
qobject_decref(obj);
|
||||
QDECREF(str);
|
||||
|
@ -1305,13 +1308,13 @@ static void simple_whitespace(void)
|
|||
QObject *obj;
|
||||
QString *str;
|
||||
|
||||
obj = qobject_from_json(test_cases[i].encoded);
|
||||
obj = qobject_from_json(test_cases[i].encoded, &error_abort);
|
||||
g_assert(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1);
|
||||
|
||||
str = qobject_to_json(obj);
|
||||
qobject_decref(obj);
|
||||
|
||||
obj = qobject_from_json(qstring_get_str(str));
|
||||
obj = qobject_from_json(qstring_get_str(str), &error_abort);
|
||||
g_assert(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1);
|
||||
|
||||
qobject_decref(obj);
|
||||
|
@ -1332,7 +1335,7 @@ static void simple_varargs(void)
|
|||
{}})),
|
||||
{}}));
|
||||
|
||||
embedded_obj = qobject_from_json("[32, 42]");
|
||||
embedded_obj = qobject_from_json("[32, 42]", &error_abort);
|
||||
g_assert(embedded_obj != NULL);
|
||||
|
||||
obj = qobject_from_jsonf("[%d, 2, %p]", 1, embedded_obj);
|
||||
|
@ -1344,68 +1347,87 @@ static void simple_varargs(void)
|
|||
static void empty_input(void)
|
||||
{
|
||||
const char *empty = "";
|
||||
|
||||
QObject *obj = qobject_from_json(empty);
|
||||
QObject *obj = qobject_from_json(empty, &error_abort);
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
static void unterminated_string(void)
|
||||
{
|
||||
QObject *obj = qobject_from_json("\"abc");
|
||||
Error *err = NULL;
|
||||
QObject *obj = qobject_from_json("\"abc", &err);
|
||||
g_assert(!err); /* BUG */
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
static void unterminated_sq_string(void)
|
||||
{
|
||||
QObject *obj = qobject_from_json("'abc");
|
||||
Error *err = NULL;
|
||||
QObject *obj = qobject_from_json("'abc", &err);
|
||||
g_assert(!err); /* BUG */
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
static void unterminated_escape(void)
|
||||
{
|
||||
QObject *obj = qobject_from_json("\"abc\\\"");
|
||||
Error *err = NULL;
|
||||
QObject *obj = qobject_from_json("\"abc\\\"", &err);
|
||||
g_assert(!err); /* BUG */
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
static void unterminated_array(void)
|
||||
{
|
||||
QObject *obj = qobject_from_json("[32");
|
||||
Error *err = NULL;
|
||||
QObject *obj = qobject_from_json("[32", &err);
|
||||
g_assert(!err); /* BUG */
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
static void unterminated_array_comma(void)
|
||||
{
|
||||
QObject *obj = qobject_from_json("[32,");
|
||||
Error *err = NULL;
|
||||
QObject *obj = qobject_from_json("[32,", &err);
|
||||
g_assert(!err); /* BUG */
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
static void invalid_array_comma(void)
|
||||
{
|
||||
QObject *obj = qobject_from_json("[32,}");
|
||||
Error *err = NULL;
|
||||
QObject *obj = qobject_from_json("[32,}", &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
static void unterminated_dict(void)
|
||||
{
|
||||
QObject *obj = qobject_from_json("{'abc':32");
|
||||
Error *err = NULL;
|
||||
QObject *obj = qobject_from_json("{'abc':32", &err);
|
||||
g_assert(!err); /* BUG */
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
static void unterminated_dict_comma(void)
|
||||
{
|
||||
QObject *obj = qobject_from_json("{'abc':32,");
|
||||
Error *err = NULL;
|
||||
QObject *obj = qobject_from_json("{'abc':32,", &err);
|
||||
g_assert(!err); /* BUG */
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
static void invalid_dict_comma(void)
|
||||
{
|
||||
QObject *obj = qobject_from_json("{'abc':32,}");
|
||||
Error *err = NULL;
|
||||
QObject *obj = qobject_from_json("{'abc':32,}", &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
static void unterminated_literal(void)
|
||||
{
|
||||
QObject *obj = qobject_from_json("nul");
|
||||
Error *err = NULL;
|
||||
QObject *obj = qobject_from_json("nul", &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
|
@ -1421,15 +1443,17 @@ static char *make_nest(char *buf, size_t cnt)
|
|||
|
||||
static void limits_nesting(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
enum { max_nesting = 1024 }; /* see qobject/json-streamer.c */
|
||||
char buf[2 * (max_nesting + 1) + 1];
|
||||
QObject *obj;
|
||||
|
||||
obj = qobject_from_json(make_nest(buf, max_nesting));
|
||||
obj = qobject_from_json(make_nest(buf, max_nesting), &error_abort);
|
||||
g_assert(obj != NULL);
|
||||
qobject_decref(obj);
|
||||
|
||||
obj = qobject_from_json(make_nest(buf, max_nesting + 1));
|
||||
obj = qobject_from_json(make_nest(buf, max_nesting + 1), &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(obj == NULL);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <sys/wait.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/json-parser.h"
|
||||
#include "qapi/qmp/json-streamer.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
|
@ -442,7 +443,7 @@ void qmp_fd_sendv(int fd, const char *fmt, va_list ap)
|
|||
* is an array type.
|
||||
*/
|
||||
va_copy(ap_copy, ap);
|
||||
qobj = qobject_from_jsonv(fmt, &ap_copy);
|
||||
qobj = qobject_from_jsonv(fmt, &ap_copy, &error_abort);
|
||||
va_end(ap_copy);
|
||||
|
||||
/* No need to send anything for an empty QObject. */
|
||||
|
|
|
@ -0,0 +1,624 @@
|
|||
/*
|
||||
* Unit tests for parsing of KEY=VALUE,... strings
|
||||
*
|
||||
* Copyright (C) 2017 Red Hat Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Markus Armbruster <armbru@redhat.com>,
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qemu/option.h"
|
||||
|
||||
static void test_keyval_parse(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
QDict *qdict, *sub_qdict;
|
||||
char long_key[129];
|
||||
char *params;
|
||||
|
||||
/* Nothing */
|
||||
qdict = keyval_parse("", NULL, &error_abort);
|
||||
g_assert_cmpuint(qdict_size(qdict), ==, 0);
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Empty key (qemu_opts_parse() accepts this) */
|
||||
qdict = keyval_parse("=val", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* Empty key fragment */
|
||||
qdict = keyval_parse(".", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
qdict = keyval_parse("key.", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* Invalid non-empty key (qemu_opts_parse() doesn't care) */
|
||||
qdict = keyval_parse("7up=val", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* Overlong key */
|
||||
memset(long_key, 'a', 127);
|
||||
long_key[127] = 'z';
|
||||
long_key[128] = 0;
|
||||
params = g_strdup_printf("k.%s=v", long_key);
|
||||
qdict = keyval_parse(params + 2, NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* Overlong key fragment */
|
||||
qdict = keyval_parse(params, NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
g_free(params);
|
||||
|
||||
/* Long key (qemu_opts_parse() accepts and truncates silently) */
|
||||
params = g_strdup_printf("k.%s=v", long_key + 1);
|
||||
qdict = keyval_parse(params + 2, NULL, &error_abort);
|
||||
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
||||
g_assert_cmpstr(qdict_get_try_str(qdict, long_key + 1), ==, "v");
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Long key fragment */
|
||||
qdict = keyval_parse(params, NULL, &error_abort);
|
||||
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
||||
sub_qdict = qdict_get_qdict(qdict, "k");
|
||||
g_assert(sub_qdict);
|
||||
g_assert_cmpuint(qdict_size(sub_qdict), ==, 1);
|
||||
g_assert_cmpstr(qdict_get_try_str(sub_qdict, long_key + 1), ==, "v");
|
||||
QDECREF(qdict);
|
||||
g_free(params);
|
||||
|
||||
/* Crap after valid key */
|
||||
qdict = keyval_parse("key[0]=val", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* Multiple keys, last one wins */
|
||||
qdict = keyval_parse("a=1,b=2,,x,a=3", NULL, &error_abort);
|
||||
g_assert_cmpuint(qdict_size(qdict), ==, 2);
|
||||
g_assert_cmpstr(qdict_get_try_str(qdict, "a"), ==, "3");
|
||||
g_assert_cmpstr(qdict_get_try_str(qdict, "b"), ==, "2,x");
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Even when it doesn't in qemu_opts_parse() */
|
||||
qdict = keyval_parse("id=foo,id=bar", NULL, &error_abort);
|
||||
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
||||
g_assert_cmpstr(qdict_get_try_str(qdict, "id"), ==, "bar");
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Dotted keys */
|
||||
qdict = keyval_parse("a.b.c=1,a.b.c=2,d=3", NULL, &error_abort);
|
||||
g_assert_cmpuint(qdict_size(qdict), ==, 2);
|
||||
sub_qdict = qdict_get_qdict(qdict, "a");
|
||||
g_assert(sub_qdict);
|
||||
g_assert_cmpuint(qdict_size(sub_qdict), ==, 1);
|
||||
sub_qdict = qdict_get_qdict(sub_qdict, "b");
|
||||
g_assert(sub_qdict);
|
||||
g_assert_cmpuint(qdict_size(sub_qdict), ==, 1);
|
||||
g_assert_cmpstr(qdict_get_try_str(sub_qdict, "c"), ==, "2");
|
||||
g_assert_cmpstr(qdict_get_try_str(qdict, "d"), ==, "3");
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Inconsistent dotted keys */
|
||||
qdict = keyval_parse("a.b=1,a=2", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
qdict = keyval_parse("a.b=1,a.b.c=2", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* Trailing comma is ignored */
|
||||
qdict = keyval_parse("x=y,", NULL, &error_abort);
|
||||
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
||||
g_assert_cmpstr(qdict_get_try_str(qdict, "x"), ==, "y");
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Except when it isn't */
|
||||
qdict = keyval_parse(",", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* Value containing ,id= not misinterpreted as qemu_opts_parse() does */
|
||||
qdict = keyval_parse("x=,,id=bar", NULL, &error_abort);
|
||||
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
||||
g_assert_cmpstr(qdict_get_try_str(qdict, "x"), ==, ",id=bar");
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Anti-social ID is left to caller (qemu_opts_parse() rejects it) */
|
||||
qdict = keyval_parse("id=666", NULL, &error_abort);
|
||||
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
||||
g_assert_cmpstr(qdict_get_try_str(qdict, "id"), ==, "666");
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Implied value not supported (unlike qemu_opts_parse()) */
|
||||
qdict = keyval_parse("an,noaus,noaus=", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* Implied value, key "no" (qemu_opts_parse(): negated empty key) */
|
||||
qdict = keyval_parse("no", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* Implied key */
|
||||
qdict = keyval_parse("an,aus=off,noaus=", "implied", &error_abort);
|
||||
g_assert_cmpuint(qdict_size(qdict), ==, 3);
|
||||
g_assert_cmpstr(qdict_get_try_str(qdict, "implied"), ==, "an");
|
||||
g_assert_cmpstr(qdict_get_try_str(qdict, "aus"), ==, "off");
|
||||
g_assert_cmpstr(qdict_get_try_str(qdict, "noaus"), ==, "");
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Implied dotted key */
|
||||
qdict = keyval_parse("val", "eins.zwei", &error_abort);
|
||||
g_assert_cmpuint(qdict_size(qdict), ==, 1);
|
||||
sub_qdict = qdict_get_qdict(qdict, "eins");
|
||||
g_assert(sub_qdict);
|
||||
g_assert_cmpuint(qdict_size(sub_qdict), ==, 1);
|
||||
g_assert_cmpstr(qdict_get_try_str(sub_qdict, "zwei"), ==, "val");
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Implied key with empty value (qemu_opts_parse() accepts this) */
|
||||
qdict = keyval_parse(",", "implied", &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* Likewise (qemu_opts_parse(): implied key with comma value) */
|
||||
qdict = keyval_parse(",,,a=1", "implied", &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* Empty key is not an implied key */
|
||||
qdict = keyval_parse("=val", "implied", &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
}
|
||||
|
||||
static void check_list012(QList *qlist)
|
||||
{
|
||||
static const char *expected[] = { "null", "eins", "zwei" };
|
||||
int i;
|
||||
QString *qstr;
|
||||
|
||||
g_assert(qlist);
|
||||
for (i = 0; i < ARRAY_SIZE(expected); i++) {
|
||||
qstr = qobject_to_qstring(qlist_pop(qlist));
|
||||
g_assert(qstr);
|
||||
g_assert_cmpstr(qstring_get_str(qstr), ==, expected[i]);
|
||||
QDECREF(qstr);
|
||||
}
|
||||
g_assert(qlist_empty(qlist));
|
||||
}
|
||||
|
||||
static void test_keyval_parse_list(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
QDict *qdict, *sub_qdict;
|
||||
|
||||
/* Root can't be a list */
|
||||
qdict = keyval_parse("0=1", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* List elements need not be in order */
|
||||
qdict = keyval_parse("list.0=null,list.2=zwei,list.1=eins",
|
||||
NULL, &error_abort);
|
||||
g_assert_cmpint(qdict_size(qdict), ==, 1);
|
||||
check_list012(qdict_get_qlist(qdict, "list"));
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Multiple indexes, last one wins */
|
||||
qdict = keyval_parse("list.1=goner,list.0=null,list.1=eins,list.2=zwei",
|
||||
NULL, &error_abort);
|
||||
g_assert_cmpint(qdict_size(qdict), ==, 1);
|
||||
check_list012(qdict_get_qlist(qdict, "list"));
|
||||
QDECREF(qdict);
|
||||
|
||||
/* List at deeper nesting */
|
||||
qdict = keyval_parse("a.list.1=eins,a.list.0=null,a.list.2=zwei",
|
||||
NULL, &error_abort);
|
||||
g_assert_cmpint(qdict_size(qdict), ==, 1);
|
||||
sub_qdict = qdict_get_qdict(qdict, "a");
|
||||
g_assert_cmpint(qdict_size(sub_qdict), ==, 1);
|
||||
check_list012(qdict_get_qlist(sub_qdict, "list"));
|
||||
QDECREF(qdict);
|
||||
|
||||
/* Inconsistent dotted keys: both list and dictionary */
|
||||
qdict = keyval_parse("a.b.c=1,a.b.0=2", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
qdict = keyval_parse("a.0.c=1,a.b.c=2", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
|
||||
/* Missing list indexes */
|
||||
qdict = keyval_parse("list.2=lonely", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
qdict = keyval_parse("list.0=null,list.2=eins,list.02=zwei", NULL, &err);
|
||||
error_free_or_abort(&err);
|
||||
g_assert(!qdict);
|
||||
}
|
||||
|
||||
static void test_keyval_visit_bool(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
Visitor *v;
|
||||
QDict *qdict;
|
||||
bool b;
|
||||
|
||||
qdict = keyval_parse("bool1=on,bool2=off", NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_bool(v, "bool1", &b, &error_abort);
|
||||
g_assert(b);
|
||||
visit_type_bool(v, "bool2", &b, &error_abort);
|
||||
g_assert(!b);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
qdict = keyval_parse("bool1=offer", NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_bool(v, "bool1", &b, &err);
|
||||
error_free_or_abort(&err);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
}
|
||||
|
||||
static void test_keyval_visit_number(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
Visitor *v;
|
||||
QDict *qdict;
|
||||
uint64_t u;
|
||||
|
||||
/* Lower limit zero */
|
||||
qdict = keyval_parse("number1=0", NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_uint64(v, "number1", &u, &error_abort);
|
||||
g_assert_cmpuint(u, ==, 0);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
/* Upper limit 2^64-1 */
|
||||
qdict = keyval_parse("number1=18446744073709551615,number2=-1",
|
||||
NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_uint64(v, "number1", &u, &error_abort);
|
||||
g_assert_cmphex(u, ==, UINT64_MAX);
|
||||
visit_type_uint64(v, "number2", &u, &error_abort);
|
||||
g_assert_cmphex(u, ==, UINT64_MAX);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
/* Above upper limit */
|
||||
qdict = keyval_parse("number1=18446744073709551616",
|
||||
NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_uint64(v, "number1", &u, &err);
|
||||
error_free_or_abort(&err);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
/* Below lower limit */
|
||||
qdict = keyval_parse("number1=-18446744073709551616",
|
||||
NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_uint64(v, "number1", &u, &err);
|
||||
error_free_or_abort(&err);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
/* Hex and octal */
|
||||
qdict = keyval_parse("number1=0x2a,number2=052",
|
||||
NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_uint64(v, "number1", &u, &error_abort);
|
||||
g_assert_cmpuint(u, ==, 42);
|
||||
visit_type_uint64(v, "number2", &u, &error_abort);
|
||||
g_assert_cmpuint(u, ==, 42);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
/* Trailing crap */
|
||||
qdict = keyval_parse("number1=3.14,number2=08",
|
||||
NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_uint64(v, "number1", &u, &err);
|
||||
error_free_or_abort(&err);
|
||||
visit_type_uint64(v, "number2", &u, &err);
|
||||
error_free_or_abort(&err);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
}
|
||||
|
||||
static void test_keyval_visit_size(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
Visitor *v;
|
||||
QDict *qdict;
|
||||
uint64_t sz;
|
||||
|
||||
/* Lower limit zero */
|
||||
qdict = keyval_parse("sz1=0", NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_size(v, "sz1", &sz, &error_abort);
|
||||
g_assert_cmpuint(sz, ==, 0);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
/* Note: precision is 53 bits since we're parsing with strtod() */
|
||||
|
||||
/* Around limit of precision: 2^53-1, 2^53, 2^53+1 */
|
||||
qdict = keyval_parse("sz1=9007199254740991,"
|
||||
"sz2=9007199254740992,"
|
||||
"sz3=9007199254740993",
|
||||
NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_size(v, "sz1", &sz, &error_abort);
|
||||
g_assert_cmphex(sz, ==, 0x1fffffffffffff);
|
||||
visit_type_size(v, "sz2", &sz, &error_abort);
|
||||
g_assert_cmphex(sz, ==, 0x20000000000000);
|
||||
visit_type_size(v, "sz3", &sz, &error_abort);
|
||||
g_assert_cmphex(sz, ==, 0x20000000000000);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
/* Close to signed upper limit 0x7ffffffffffffc00 (53 msbs set) */
|
||||
qdict = keyval_parse("sz1=9223372036854774784," /* 7ffffffffffffc00 */
|
||||
"sz2=9223372036854775295", /* 7ffffffffffffdff */
|
||||
NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_size(v, "sz1", &sz, &error_abort);
|
||||
g_assert_cmphex(sz, ==, 0x7ffffffffffffc00);
|
||||
visit_type_size(v, "sz2", &sz, &error_abort);
|
||||
g_assert_cmphex(sz, ==, 0x7ffffffffffffc00);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
/* Close to actual upper limit 0xfffffffffffff800 (53 msbs set) */
|
||||
qdict = keyval_parse("sz1=18446744073709549568," /* fffffffffffff800 */
|
||||
"sz2=18446744073709550591", /* fffffffffffffbff */
|
||||
NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_size(v, "sz1", &sz, &error_abort);
|
||||
g_assert_cmphex(sz, ==, 0xfffffffffffff800);
|
||||
visit_type_size(v, "sz2", &sz, &error_abort);
|
||||
g_assert_cmphex(sz, ==, 0xfffffffffffff800);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
/* Beyond limits */
|
||||
qdict = keyval_parse("sz1=-1,"
|
||||
"sz2=18446744073709550592", /* fffffffffffffc00 */
|
||||
NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_size(v, "sz1", &sz, &err);
|
||||
error_free_or_abort(&err);
|
||||
visit_type_size(v, "sz2", &sz, &err);
|
||||
error_free_or_abort(&err);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
/* Suffixes */
|
||||
qdict = keyval_parse("sz1=8b,sz2=1.5k,sz3=2M,sz4=0.1G,sz5=16777215T",
|
||||
NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_size(v, "sz1", &sz, &error_abort);
|
||||
g_assert_cmpuint(sz, ==, 8);
|
||||
visit_type_size(v, "sz2", &sz, &error_abort);
|
||||
g_assert_cmpuint(sz, ==, 1536);
|
||||
visit_type_size(v, "sz3", &sz, &error_abort);
|
||||
g_assert_cmphex(sz, ==, 2 * M_BYTE);
|
||||
visit_type_size(v, "sz4", &sz, &error_abort);
|
||||
g_assert_cmphex(sz, ==, G_BYTE / 10);
|
||||
visit_type_size(v, "sz5", &sz, &error_abort);
|
||||
g_assert_cmphex(sz, ==, 16777215 * T_BYTE);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
/* Beyond limit with suffix */
|
||||
qdict = keyval_parse("sz1=16777216T", NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_size(v, "sz1", &sz, &err);
|
||||
error_free_or_abort(&err);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
/* Trailing crap */
|
||||
qdict = keyval_parse("sz1=16E,sz2=16Gi", NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_size(v, "sz1", &sz, &err);
|
||||
error_free_or_abort(&err);
|
||||
visit_type_size(v, "sz2", &sz, &err);
|
||||
error_free_or_abort(&err);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
}
|
||||
|
||||
static void test_keyval_visit_dict(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
Visitor *v;
|
||||
QDict *qdict;
|
||||
int64_t i;
|
||||
|
||||
qdict = keyval_parse("a.b.c=1,a.b.c=2,d=3", NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_start_struct(v, "a", NULL, 0, &error_abort);
|
||||
visit_start_struct(v, "b", NULL, 0, &error_abort);
|
||||
visit_type_int(v, "c", &i, &error_abort);
|
||||
g_assert_cmpint(i, ==, 2);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_type_int(v, "d", &i, &error_abort);
|
||||
g_assert_cmpint(i, ==, 3);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
qdict = keyval_parse("a.b=", NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_start_struct(v, "a", NULL, 0, &error_abort);
|
||||
visit_type_int(v, "c", &i, &err); /* a.c missing */
|
||||
error_free_or_abort(&err);
|
||||
visit_check_struct(v, &err);
|
||||
error_free_or_abort(&err); /* a.b unexpected */
|
||||
visit_end_struct(v, NULL);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
}
|
||||
|
||||
static void test_keyval_visit_list(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
Visitor *v;
|
||||
QDict *qdict;
|
||||
char *s;
|
||||
|
||||
qdict = keyval_parse("a.0=,a.1=I,a.2.0=II", NULL, &error_abort);
|
||||
/* TODO empty list */
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_start_list(v, "a", NULL, 0, &error_abort);
|
||||
visit_type_str(v, NULL, &s, &error_abort);
|
||||
g_assert_cmpstr(s, ==, "");
|
||||
g_free(s);
|
||||
visit_type_str(v, NULL, &s, &error_abort);
|
||||
g_assert_cmpstr(s, ==, "I");
|
||||
g_free(s);
|
||||
visit_start_list(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_str(v, NULL, &s, &error_abort);
|
||||
g_assert_cmpstr(s, ==, "II");
|
||||
g_free(s);
|
||||
visit_check_list(v, &error_abort);
|
||||
visit_end_list(v, NULL);
|
||||
visit_check_list(v, &error_abort);
|
||||
visit_end_list(v, NULL);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
|
||||
qdict = keyval_parse("a.0=,b.0.0=head", NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_start_list(v, "a", NULL, 0, &error_abort);
|
||||
visit_check_list(v, &err); /* a[0] unexpected */
|
||||
error_free_or_abort(&err);
|
||||
visit_end_list(v, NULL);
|
||||
visit_start_list(v, "b", NULL, 0, &error_abort);
|
||||
visit_start_list(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_str(v, NULL, &s, &error_abort);
|
||||
g_assert_cmpstr(s, ==, "head");
|
||||
g_free(s);
|
||||
visit_type_str(v, NULL, &s, &err); /* b[0][1] missing */
|
||||
error_free_or_abort(&err);
|
||||
visit_end_list(v, NULL);
|
||||
visit_end_list(v, NULL);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
}
|
||||
|
||||
static void test_keyval_visit_optional(void)
|
||||
{
|
||||
Visitor *v;
|
||||
QDict *qdict;
|
||||
bool present;
|
||||
int64_t i;
|
||||
|
||||
qdict = keyval_parse("a.b=1", NULL, &error_abort);
|
||||
v = qobject_input_visitor_new_keyval(QOBJECT(qdict));
|
||||
QDECREF(qdict);
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_optional(v, "b", &present);
|
||||
g_assert(!present); /* b missing */
|
||||
visit_optional(v, "a", &present);
|
||||
g_assert(present); /* a present */
|
||||
visit_start_struct(v, "a", NULL, 0, &error_abort);
|
||||
visit_optional(v, "b", &present);
|
||||
g_assert(present); /* a.b present */
|
||||
visit_type_int(v, "b", &i, &error_abort);
|
||||
g_assert_cmpint(i, ==, 1);
|
||||
visit_optional(v, "a", &present);
|
||||
g_assert(!present); /* a.a missing */
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_check_struct(v, &error_abort);
|
||||
visit_end_struct(v, NULL);
|
||||
visit_free(v);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
g_test_add_func("/keyval/keyval_parse", test_keyval_parse);
|
||||
g_test_add_func("/keyval/keyval_parse/list", test_keyval_parse_list);
|
||||
g_test_add_func("/keyval/visit/bool", test_keyval_visit_bool);
|
||||
g_test_add_func("/keyval/visit/number", test_keyval_visit_number);
|
||||
g_test_add_func("/keyval/visit/size", test_keyval_visit_size);
|
||||
g_test_add_func("/keyval/visit/dict", test_keyval_visit_dict);
|
||||
g_test_add_func("/keyval/visit/list", test_keyval_visit_list);
|
||||
g_test_add_func("/keyval/visit/optional", test_keyval_visit_optional);
|
||||
g_test_run();
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Unit tests for QAPI utility functions
|
||||
*
|
||||
* Copyright (C) 2017 Red Hat Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Markus Armbruster <armbru@redhat.com>,
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/util.h"
|
||||
#include "test-qapi-types.h"
|
||||
|
||||
static void test_qapi_enum_parse(void)
|
||||
{
|
||||
Error *err = NULL;
|
||||
int ret;
|
||||
|
||||
ret = qapi_enum_parse(QType_lookup, NULL, QTYPE__MAX, QTYPE_NONE,
|
||||
&error_abort);
|
||||
g_assert_cmpint(ret, ==, QTYPE_NONE);
|
||||
|
||||
ret = qapi_enum_parse(QType_lookup, "junk", QTYPE__MAX, -1,
|
||||
NULL);
|
||||
g_assert_cmpint(ret, ==, -1);
|
||||
|
||||
ret = qapi_enum_parse(QType_lookup, "junk", QTYPE__MAX, -1,
|
||||
&err);
|
||||
error_free_or_abort(&err);
|
||||
|
||||
ret = qapi_enum_parse(QType_lookup, "none", QTYPE__MAX, -1,
|
||||
&error_abort);
|
||||
g_assert_cmpint(ret, ==, QTYPE_NONE);
|
||||
|
||||
ret = qapi_enum_parse(QType_lookup, QType_lookup[QTYPE__MAX - 1],
|
||||
QTYPE__MAX, QTYPE__MAX - 1,
|
||||
&error_abort);
|
||||
g_assert_cmpint(ret, ==, QTYPE__MAX - 1);
|
||||
}
|
||||
|
||||
static void test_parse_qapi_name(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Must start with a letter */
|
||||
ret = parse_qapi_name("a", true);
|
||||
g_assert(ret == 1);
|
||||
ret = parse_qapi_name("a$", false);
|
||||
g_assert(ret == 1);
|
||||
ret = parse_qapi_name("", false);
|
||||
g_assert(ret == -1);
|
||||
ret = parse_qapi_name("1", false);
|
||||
g_assert(ret == -1);
|
||||
|
||||
/* Only letters, digits, hyphen, underscore */
|
||||
ret = parse_qapi_name("A-Za-z0-9_", true);
|
||||
g_assert(ret == 10);
|
||||
ret = parse_qapi_name("A-Za-z0-9_$", false);
|
||||
g_assert(ret == 10);
|
||||
ret = parse_qapi_name("A-Za-z0-9_$", true);
|
||||
g_assert(ret == -1);
|
||||
|
||||
/* __RFQDN_ */
|
||||
ret = parse_qapi_name("__com.redhat_supports", true);
|
||||
g_assert(ret == 21);
|
||||
ret = parse_qapi_name("_com.example_", false);
|
||||
g_assert(ret == -1);
|
||||
ret = parse_qapi_name("__com.example", false);
|
||||
g_assert(ret == -1);
|
||||
ret = parse_qapi_name("__com.example_", false);
|
||||
g_assert(ret == -1);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
g_test_add_func("/qapi/util/qapi_enum_parse", test_qapi_enum_parse);
|
||||
g_test_add_func("/qapi/util/parse_qapi_name", test_parse_qapi_name);
|
||||
g_test_run();
|
||||
return 0;
|
||||
}
|
|
@ -532,6 +532,11 @@ static void test_opts_parse(void)
|
|||
g_assert_cmpstr(qemu_opt_get(opts, "aus"), ==, "off");
|
||||
g_assert_cmpstr(qemu_opt_get(opts, "noaus"), ==, "");
|
||||
|
||||
/* Implied value, negated empty key */
|
||||
opts = qemu_opts_parse(&opts_list_03, "no", false, &error_abort);
|
||||
g_assert_cmpuint(opts_count(opts), ==, 1);
|
||||
g_assert_cmpstr(qemu_opt_get(opts, ""), ==, "off");
|
||||
|
||||
/* Implied key */
|
||||
opts = qemu_opts_parse(&opts_list_03, "an,noaus,noaus=", true,
|
||||
&error_abort);
|
||||
|
|
|
@ -45,19 +45,38 @@ static void visitor_input_teardown(TestInputVisitorData *data,
|
|||
function so that the JSON string used by the tests are kept in the test
|
||||
functions (and not in main()). */
|
||||
static Visitor *visitor_input_test_init_internal(TestInputVisitorData *data,
|
||||
bool keyval,
|
||||
const char *json_string,
|
||||
va_list *ap)
|
||||
{
|
||||
visitor_input_teardown(data, NULL);
|
||||
|
||||
data->obj = qobject_from_jsonv(json_string, ap);
|
||||
data->obj = qobject_from_jsonv(json_string, ap, &error_abort);
|
||||
g_assert(data->obj);
|
||||
|
||||
data->qiv = qobject_input_visitor_new(data->obj);
|
||||
if (keyval) {
|
||||
data->qiv = qobject_input_visitor_new_keyval(data->obj);
|
||||
} else {
|
||||
data->qiv = qobject_input_visitor_new(data->obj);
|
||||
}
|
||||
g_assert(data->qiv);
|
||||
return data->qiv;
|
||||
}
|
||||
|
||||
static GCC_FMT_ATTR(3, 4)
|
||||
Visitor *visitor_input_test_init_full(TestInputVisitorData *data,
|
||||
bool keyval,
|
||||
const char *json_string, ...)
|
||||
{
|
||||
Visitor *v;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, json_string);
|
||||
v = visitor_input_test_init_internal(data, keyval, json_string, &ap);
|
||||
va_end(ap);
|
||||
return v;
|
||||
}
|
||||
|
||||
static GCC_FMT_ATTR(2, 3)
|
||||
Visitor *visitor_input_test_init(TestInputVisitorData *data,
|
||||
const char *json_string, ...)
|
||||
|
@ -66,7 +85,7 @@ Visitor *visitor_input_test_init(TestInputVisitorData *data,
|
|||
va_list ap;
|
||||
|
||||
va_start(ap, json_string);
|
||||
v = visitor_input_test_init_internal(data, json_string, &ap);
|
||||
v = visitor_input_test_init_internal(data, false, json_string, &ap);
|
||||
va_end(ap);
|
||||
return v;
|
||||
}
|
||||
|
@ -81,7 +100,7 @@ Visitor *visitor_input_test_init(TestInputVisitorData *data,
|
|||
static Visitor *visitor_input_test_init_raw(TestInputVisitorData *data,
|
||||
const char *json_string)
|
||||
{
|
||||
return visitor_input_test_init_internal(data, json_string, NULL);
|
||||
return visitor_input_test_init_internal(data, false, json_string, NULL);
|
||||
}
|
||||
|
||||
static void test_visitor_in_int(TestInputVisitorData *data,
|
||||
|
@ -114,6 +133,43 @@ static void test_visitor_in_int_overflow(TestInputVisitorData *data,
|
|||
error_free_or_abort(&err);
|
||||
}
|
||||
|
||||
static void test_visitor_in_int_keyval(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
int64_t res = 0, value = -42;
|
||||
Error *err = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init_full(data, true, "%" PRId64, value);
|
||||
visit_type_int(v, NULL, &res, &err);
|
||||
error_free_or_abort(&err);
|
||||
}
|
||||
|
||||
static void test_visitor_in_int_str_keyval(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
int64_t res = 0, value = -42;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init_full(data, true, "\"-42\"");
|
||||
|
||||
visit_type_int(v, NULL, &res, &error_abort);
|
||||
g_assert_cmpint(res, ==, value);
|
||||
}
|
||||
|
||||
static void test_visitor_in_int_str_fail(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
int64_t res = 0;
|
||||
Visitor *v;
|
||||
Error *err = NULL;
|
||||
|
||||
v = visitor_input_test_init(data, "\"-42\"");
|
||||
|
||||
visit_type_int(v, NULL, &res, &err);
|
||||
error_free_or_abort(&err);
|
||||
}
|
||||
|
||||
static void test_visitor_in_bool(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
|
@ -126,6 +182,44 @@ static void test_visitor_in_bool(TestInputVisitorData *data,
|
|||
g_assert_cmpint(res, ==, true);
|
||||
}
|
||||
|
||||
static void test_visitor_in_bool_keyval(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
bool res = false;
|
||||
Error *err = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init_full(data, true, "true");
|
||||
|
||||
visit_type_bool(v, NULL, &res, &err);
|
||||
error_free_or_abort(&err);
|
||||
}
|
||||
|
||||
static void test_visitor_in_bool_str_keyval(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
bool res = false;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init_full(data, true, "\"on\"");
|
||||
|
||||
visit_type_bool(v, NULL, &res, &error_abort);
|
||||
g_assert_cmpint(res, ==, true);
|
||||
}
|
||||
|
||||
static void test_visitor_in_bool_str_fail(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
bool res = false;
|
||||
Visitor *v;
|
||||
Error *err = NULL;
|
||||
|
||||
v = visitor_input_test_init(data, "\"true\"");
|
||||
|
||||
visit_type_bool(v, NULL, &res, &err);
|
||||
error_free_or_abort(&err);
|
||||
}
|
||||
|
||||
static void test_visitor_in_number(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
|
@ -138,6 +232,69 @@ static void test_visitor_in_number(TestInputVisitorData *data,
|
|||
g_assert_cmpfloat(res, ==, value);
|
||||
}
|
||||
|
||||
static void test_visitor_in_number_keyval(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
double res = 0, value = 3.14;
|
||||
Error *err = NULL;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init_full(data, true, "%f", value);
|
||||
|
||||
visit_type_number(v, NULL, &res, &err);
|
||||
error_free_or_abort(&err);
|
||||
}
|
||||
|
||||
static void test_visitor_in_number_str_keyval(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
double res = 0, value = 3.14;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init_full(data, true, "\"3.14\"");
|
||||
|
||||
visit_type_number(v, NULL, &res, &error_abort);
|
||||
g_assert_cmpfloat(res, ==, value);
|
||||
}
|
||||
|
||||
static void test_visitor_in_number_str_fail(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
double res = 0;
|
||||
Visitor *v;
|
||||
Error *err = NULL;
|
||||
|
||||
v = visitor_input_test_init(data, "\"3.14\"");
|
||||
|
||||
visit_type_number(v, NULL, &res, &err);
|
||||
error_free_or_abort(&err);
|
||||
}
|
||||
|
||||
static void test_visitor_in_size_str_keyval(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
uint64_t res, value = 500 * 1024 * 1024;
|
||||
Visitor *v;
|
||||
|
||||
v = visitor_input_test_init_full(data, true, "\"500M\"");
|
||||
|
||||
visit_type_size(v, NULL, &res, &error_abort);
|
||||
g_assert_cmpfloat(res, ==, value);
|
||||
}
|
||||
|
||||
static void test_visitor_in_size_str_fail(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
uint64_t res = 0;
|
||||
Visitor *v;
|
||||
Error *err = NULL;
|
||||
|
||||
v = visitor_input_test_init(data, "\"500M\"");
|
||||
|
||||
visit_type_size(v, NULL, &res, &err);
|
||||
error_free_or_abort(&err);
|
||||
}
|
||||
|
||||
static void test_visitor_in_string(TestInputVisitorData *data,
|
||||
const void *unused)
|
||||
{
|
||||
|
@ -294,7 +451,8 @@ static void test_visitor_in_null(TestInputVisitorData *data,
|
|||
* when input is not null.
|
||||
*/
|
||||
|
||||
v = visitor_input_test_init(data, "{ 'a': null, 'b': '', 'c': null }");
|
||||
v = visitor_input_test_init_full(data, false,
|
||||
"{ 'a': null, 'b': '' }");
|
||||
visit_start_struct(v, NULL, NULL, 0, &error_abort);
|
||||
visit_type_null(v, "a", &error_abort);
|
||||
visit_type_null(v, "b", &err);
|
||||
|
@ -1069,10 +1227,32 @@ int main(int argc, char **argv)
|
|||
NULL, test_visitor_in_int);
|
||||
input_visitor_test_add("/visitor/input/int_overflow",
|
||||
NULL, test_visitor_in_int_overflow);
|
||||
input_visitor_test_add("/visitor/input/int_keyval",
|
||||
NULL, test_visitor_in_int_keyval);
|
||||
input_visitor_test_add("/visitor/input/int_str_keyval",
|
||||
NULL, test_visitor_in_int_str_keyval);
|
||||
input_visitor_test_add("/visitor/input/int_str_fail",
|
||||
NULL, test_visitor_in_int_str_fail);
|
||||
input_visitor_test_add("/visitor/input/bool",
|
||||
NULL, test_visitor_in_bool);
|
||||
input_visitor_test_add("/visitor/input/bool_keyval",
|
||||
NULL, test_visitor_in_bool_keyval);
|
||||
input_visitor_test_add("/visitor/input/bool_str_keyval",
|
||||
NULL, test_visitor_in_bool_str_keyval);
|
||||
input_visitor_test_add("/visitor/input/bool_str_fail",
|
||||
NULL, test_visitor_in_bool_str_fail);
|
||||
input_visitor_test_add("/visitor/input/number",
|
||||
NULL, test_visitor_in_number);
|
||||
input_visitor_test_add("/visitor/input/number_keyval",
|
||||
NULL, test_visitor_in_number_keyval);
|
||||
input_visitor_test_add("/visitor/input/number_str_keyval",
|
||||
NULL, test_visitor_in_number_str_keyval);
|
||||
input_visitor_test_add("/visitor/input/number_str_fail",
|
||||
NULL, test_visitor_in_number_str_fail);
|
||||
input_visitor_test_add("/visitor/input/size_str_keyval",
|
||||
NULL, test_visitor_in_size_str_keyval);
|
||||
input_visitor_test_add("/visitor/input/size_str_fail",
|
||||
NULL, test_visitor_in_size_str_fail);
|
||||
input_visitor_test_add("/visitor/input/string",
|
||||
NULL, test_visitor_in_string);
|
||||
input_visitor_test_add("/visitor/input/enum",
|
||||
|
|
|
@ -1037,7 +1037,7 @@ static void qmp_deserialize(void **native_out, void *datap,
|
|||
visit_complete(d->qov, &d->obj);
|
||||
obj_orig = d->obj;
|
||||
output_json = qobject_to_json(obj_orig);
|
||||
obj = qobject_from_json(qstring_get_str(output_json));
|
||||
obj = qobject_from_json(qstring_get_str(output_json), &error_abort);
|
||||
|
||||
QDECREF(output_json);
|
||||
d->qiv = qobject_input_visitor_new(obj);
|
||||
|
|
|
@ -24,6 +24,7 @@ util-obj-y += error.o qemu-error.o
|
|||
util-obj-y += id.o
|
||||
util-obj-y += iov.o qemu-config.o qemu-sockets.o uri.o notify.o
|
||||
util-obj-y += qemu-option.o qemu-progress.o
|
||||
util-obj-y += keyval.o
|
||||
util-obj-y += hexdump.o
|
||||
util-obj-y += crc32c.o
|
||||
util-obj-y += uuid.o
|
||||
|
|
|
@ -0,0 +1,394 @@
|
|||
/*
|
||||
* Parsing KEY=VALUE,... strings
|
||||
*
|
||||
* Copyright (C) 2017 Red Hat Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Markus Armbruster <armbru@redhat.com>,
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
/*
|
||||
* KEY=VALUE,... syntax:
|
||||
*
|
||||
* key-vals = [ key-val { ',' key-val } [ ',' ] ]
|
||||
* key-val = key '=' val
|
||||
* key = key-fragment { '.' key-fragment }
|
||||
* key-fragment = / [^=,.]* /
|
||||
* val = { / [^,]* / | ',,' }
|
||||
*
|
||||
* Semantics defined by reduction to JSON:
|
||||
*
|
||||
* key-vals is a tree of objects and arrays rooted at object R
|
||||
* where for each key-val = key-fragment . ... = val in key-vals
|
||||
* R op key-fragment op ... = val'
|
||||
* where (left-associative) op is
|
||||
* array subscript L[key-fragment] for numeric key-fragment
|
||||
* member reference L.key-fragment otherwise
|
||||
* val' is val with ',,' replaced by ','
|
||||
* and only R may be empty.
|
||||
*
|
||||
* Duplicate keys are permitted; all but the last one are ignored.
|
||||
*
|
||||
* The equations must have a solution. Counter-example: a.b=1,a=2
|
||||
* doesn't have one, because R.a must be an object to satisfy a.b=1
|
||||
* and a string to satisfy a=2.
|
||||
*
|
||||
* Key-fragments must be valid QAPI names or consist only of digits.
|
||||
*
|
||||
* The length of any key-fragment must be between 1 and 127.
|
||||
*
|
||||
* Design flaw: there is no way to denote an empty array or non-root
|
||||
* object. While interpreting "key absent" as empty seems natural
|
||||
* (removing a key-val from the input string removes the member when
|
||||
* there are more, so why not when it's the last), it doesn't work:
|
||||
* "key absent" already means "optional object/array absent", which
|
||||
* isn't the same as "empty object/array present".
|
||||
*
|
||||
* Additional syntax for use with an implied key:
|
||||
*
|
||||
* key-vals-ik = val-no-key [ ',' key-vals ]
|
||||
* val-no-key = / [^=,]* /
|
||||
*
|
||||
* where no-key is syntactic sugar for implied-key=val-no-key.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/qstring.h"
|
||||
#include "qapi/util.h"
|
||||
#include "qemu/cutils.h"
|
||||
#include "qemu/option.h"
|
||||
|
||||
/*
|
||||
* Convert @key to a list index.
|
||||
* Convert all leading digits to a (non-negative) number, capped at
|
||||
* INT_MAX.
|
||||
* If @end is non-null, assign a pointer to the first character after
|
||||
* the number to *@end.
|
||||
* Else, fail if any characters follow.
|
||||
* On success, return the converted number.
|
||||
* On failure, return a negative value.
|
||||
* Note: since only digits are converted, no two keys can map to the
|
||||
* same number, except by overflow to INT_MAX.
|
||||
*/
|
||||
static int key_to_index(const char *key, const char **end)
|
||||
{
|
||||
int ret;
|
||||
unsigned long index;
|
||||
|
||||
if (*key < '0' || *key > '9') {
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = qemu_strtoul(key, end, 10, &index);
|
||||
if (ret) {
|
||||
return ret == -ERANGE ? INT_MAX : ret;
|
||||
}
|
||||
return index <= INT_MAX ? index : INT_MAX;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure @cur maps @key_in_cur the right way.
|
||||
* If @value is null, it needs to map to a QDict, else to this
|
||||
* QString.
|
||||
* If @cur doesn't have @key_in_cur, put an empty QDict or @value,
|
||||
* respectively.
|
||||
* Else, if it needs to map to a QDict, and already does, do nothing.
|
||||
* Else, if it needs to map to this QString, and already maps to a
|
||||
* QString, replace it by @value.
|
||||
* Else, fail because we have conflicting needs on how to map
|
||||
* @key_in_cur.
|
||||
* In any case, take over the reference to @value, i.e. if the caller
|
||||
* wants to hold on to a reference, it needs to QINCREF().
|
||||
* Use @key up to @key_cursor to identify the key in error messages.
|
||||
* On success, return the mapped value.
|
||||
* On failure, store an error through @errp and return NULL.
|
||||
*/
|
||||
static QObject *keyval_parse_put(QDict *cur,
|
||||
const char *key_in_cur, QString *value,
|
||||
const char *key, const char *key_cursor,
|
||||
Error **errp)
|
||||
{
|
||||
QObject *old, *new;
|
||||
|
||||
old = qdict_get(cur, key_in_cur);
|
||||
if (old) {
|
||||
if (qobject_type(old) != (value ? QTYPE_QSTRING : QTYPE_QDICT)) {
|
||||
error_setg(errp, "Parameters '%.*s.*' used inconsistently",
|
||||
(int)(key_cursor - key), key);
|
||||
QDECREF(value);
|
||||
return NULL;
|
||||
}
|
||||
if (!value) {
|
||||
return old; /* already QDict, do nothing */
|
||||
}
|
||||
new = QOBJECT(value); /* replacement */
|
||||
} else {
|
||||
new = value ? QOBJECT(value) : QOBJECT(qdict_new());
|
||||
}
|
||||
qdict_put_obj(cur, key_in_cur, new);
|
||||
return new;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse one KEY=VALUE from @params, store result in @qdict.
|
||||
* The first fragment of KEY applies to @qdict. Subsequent fragments
|
||||
* apply to nested QDicts, which are created on demand. @implied_key
|
||||
* is as in keyval_parse().
|
||||
* On success, return a pointer to the next KEY=VALUE, or else to '\0'.
|
||||
* On failure, return NULL.
|
||||
*/
|
||||
static const char *keyval_parse_one(QDict *qdict, const char *params,
|
||||
const char *implied_key,
|
||||
Error **errp)
|
||||
{
|
||||
const char *key, *key_end, *s, *end;
|
||||
size_t len;
|
||||
char key_in_cur[128];
|
||||
QDict *cur;
|
||||
int ret;
|
||||
QObject *next;
|
||||
QString *val;
|
||||
|
||||
key = params;
|
||||
len = strcspn(params, "=,");
|
||||
if (implied_key && len && key[len] != '=') {
|
||||
/* Desugar implied key */
|
||||
key = implied_key;
|
||||
len = strlen(implied_key);
|
||||
}
|
||||
key_end = key + len;
|
||||
|
||||
/*
|
||||
* Loop over key fragments: @s points to current fragment, it
|
||||
* applies to @cur. @key_in_cur[] holds the previous fragment.
|
||||
*/
|
||||
cur = qdict;
|
||||
s = key;
|
||||
for (;;) {
|
||||
/* Want a key index (unless it's first) or a QAPI name */
|
||||
if (s != key && key_to_index(s, &end) >= 0) {
|
||||
len = end - s;
|
||||
} else {
|
||||
ret = parse_qapi_name(s, false);
|
||||
len = ret < 0 ? 0 : ret;
|
||||
}
|
||||
assert(s + len <= key_end);
|
||||
if (!len || (s + len < key_end && s[len] != '.')) {
|
||||
assert(key != implied_key);
|
||||
error_setg(errp, "Invalid parameter '%.*s'",
|
||||
(int)(key_end - key), key);
|
||||
return NULL;
|
||||
}
|
||||
if (len >= sizeof(key_in_cur)) {
|
||||
assert(key != implied_key);
|
||||
error_setg(errp, "Parameter%s '%.*s' is too long",
|
||||
s != key || s + len != key_end ? " fragment" : "",
|
||||
(int)len, s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (s != key) {
|
||||
next = keyval_parse_put(cur, key_in_cur, NULL,
|
||||
key, s - 1, errp);
|
||||
if (!next) {
|
||||
return NULL;
|
||||
}
|
||||
cur = qobject_to_qdict(next);
|
||||
assert(cur);
|
||||
}
|
||||
|
||||
memcpy(key_in_cur, s, len);
|
||||
key_in_cur[len] = 0;
|
||||
s += len;
|
||||
|
||||
if (*s != '.') {
|
||||
break;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
|
||||
if (key == implied_key) {
|
||||
assert(!*s);
|
||||
s = params;
|
||||
} else {
|
||||
if (*s != '=') {
|
||||
error_setg(errp, "Expected '=' after parameter '%.*s'",
|
||||
(int)(s - key), key);
|
||||
return NULL;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
|
||||
val = qstring_new();
|
||||
for (;;) {
|
||||
if (!*s) {
|
||||
break;
|
||||
} else if (*s == ',') {
|
||||
s++;
|
||||
if (*s != ',') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
qstring_append_chr(val, *s++);
|
||||
}
|
||||
|
||||
if (!keyval_parse_put(cur, key_in_cur, val, key, key_end, errp)) {
|
||||
return NULL;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static char *reassemble_key(GSList *key)
|
||||
{
|
||||
GString *s = g_string_new("");
|
||||
GSList *p;
|
||||
|
||||
for (p = key; p; p = p->next) {
|
||||
g_string_prepend_c(s, '.');
|
||||
g_string_prepend(s, (char *)p->data);
|
||||
}
|
||||
|
||||
return g_string_free(s, FALSE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Listify @cur recursively.
|
||||
* Replace QDicts whose keys are all valid list indexes by QLists.
|
||||
* @key_of_cur is the list of key fragments leading up to @cur.
|
||||
* On success, return either @cur or its replacement.
|
||||
* On failure, store an error through @errp and return NULL.
|
||||
*/
|
||||
static QObject *keyval_listify(QDict *cur, GSList *key_of_cur, Error **errp)
|
||||
{
|
||||
GSList key_node;
|
||||
bool has_index, has_member;
|
||||
const QDictEntry *ent;
|
||||
QDict *qdict;
|
||||
QObject *val;
|
||||
char *key;
|
||||
size_t nelt;
|
||||
QObject **elt;
|
||||
int index, max_index, i;
|
||||
QList *list;
|
||||
|
||||
key_node.next = key_of_cur;
|
||||
|
||||
/*
|
||||
* Recursively listify @cur's members, and figure out whether @cur
|
||||
* itself is to be listified.
|
||||
*/
|
||||
has_index = false;
|
||||
has_member = false;
|
||||
for (ent = qdict_first(cur); ent; ent = qdict_next(cur, ent)) {
|
||||
if (key_to_index(ent->key, NULL) >= 0) {
|
||||
has_index = true;
|
||||
} else {
|
||||
has_member = true;
|
||||
}
|
||||
|
||||
qdict = qobject_to_qdict(ent->value);
|
||||
if (!qdict) {
|
||||
continue;
|
||||
}
|
||||
|
||||
key_node.data = ent->key;
|
||||
val = keyval_listify(qdict, &key_node, errp);
|
||||
if (!val) {
|
||||
return NULL;
|
||||
}
|
||||
if (val != ent->value) {
|
||||
qdict_put_obj(cur, ent->key, val);
|
||||
}
|
||||
}
|
||||
|
||||
if (has_index && has_member) {
|
||||
key = reassemble_key(key_of_cur);
|
||||
error_setg(errp, "Parameters '%s*' used inconsistently", key);
|
||||
g_free(key);
|
||||
return NULL;
|
||||
}
|
||||
if (!has_index) {
|
||||
return QOBJECT(cur);
|
||||
}
|
||||
|
||||
/* Copy @cur's values to @elt[] */
|
||||
nelt = qdict_size(cur) + 1; /* one extra, for use as sentinel */
|
||||
elt = g_new0(QObject *, nelt);
|
||||
max_index = -1;
|
||||
for (ent = qdict_first(cur); ent; ent = qdict_next(cur, ent)) {
|
||||
index = key_to_index(ent->key, NULL);
|
||||
assert(index >= 0);
|
||||
if (index > max_index) {
|
||||
max_index = index;
|
||||
}
|
||||
/*
|
||||
* We iterate @nelt times. If we get one exceeding @nelt
|
||||
* here, we will put less than @nelt values into @elt[],
|
||||
* triggering the error in the next loop.
|
||||
*/
|
||||
if ((size_t)index >= nelt - 1) {
|
||||
continue;
|
||||
}
|
||||
/* Even though dict keys are distinct, indexes need not be */
|
||||
elt[index] = ent->value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a list from @elt[], reporting any missing elements.
|
||||
* If we dropped an index >= nelt in the previous loop, this loop
|
||||
* will run into the sentinel and report index @nelt missing.
|
||||
*/
|
||||
list = qlist_new();
|
||||
assert(!elt[nelt-1]); /* need the sentinel to be null */
|
||||
for (i = 0; i < MIN(nelt, max_index + 1); i++) {
|
||||
if (!elt[i]) {
|
||||
key = reassemble_key(key_of_cur);
|
||||
error_setg(errp, "Parameter '%s%d' missing", key, i);
|
||||
g_free(key);
|
||||
g_free(elt);
|
||||
QDECREF(list);
|
||||
return NULL;
|
||||
}
|
||||
qobject_incref(elt[i]);
|
||||
qlist_append_obj(list, elt[i]);
|
||||
}
|
||||
|
||||
g_free(elt);
|
||||
return QOBJECT(list);
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse @params in QEMU's traditional KEY=VALUE,... syntax.
|
||||
* If @implied_key, the first KEY= can be omitted. @implied_key is
|
||||
* implied then, and VALUE can't be empty or contain ',' or '='.
|
||||
* On success, return a dictionary of the parsed keys and values.
|
||||
* On failure, store an error through @errp and return NULL.
|
||||
*/
|
||||
QDict *keyval_parse(const char *params, const char *implied_key,
|
||||
Error **errp)
|
||||
{
|
||||
QDict *qdict = qdict_new();
|
||||
QObject *listified;
|
||||
const char *s;
|
||||
|
||||
s = params;
|
||||
while (*s) {
|
||||
s = keyval_parse_one(qdict, s, implied_key, errp);
|
||||
if (!s) {
|
||||
QDECREF(qdict);
|
||||
return NULL;
|
||||
}
|
||||
implied_key = NULL;
|
||||
}
|
||||
|
||||
listified = keyval_listify(qdict, NULL, errp);
|
||||
if (!listified) {
|
||||
QDECREF(qdict);
|
||||
return NULL;
|
||||
}
|
||||
assert(listified == QOBJECT(qdict));
|
||||
return qdict;
|
||||
}
|
39
vl.c
39
vl.c
|
@ -95,6 +95,9 @@ int main(int argc, char **argv)
|
|||
#include "migration/colo.h"
|
||||
#include "sysemu/kvm.h"
|
||||
#include "sysemu/hax.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
#include "qapi/qobject-input-visitor.h"
|
||||
#include "qapi-visit.h"
|
||||
#include "qapi/qmp/qjson.h"
|
||||
#include "qemu/option.h"
|
||||
#include "qemu/config-file.h"
|
||||
|
@ -2976,6 +2979,13 @@ int main(int argc, char **argv, char **envp)
|
|||
Error *main_loop_err = NULL;
|
||||
Error *err = NULL;
|
||||
bool list_data_dirs = false;
|
||||
typedef struct BlockdevOptions_queue {
|
||||
BlockdevOptions *bdo;
|
||||
Location loc;
|
||||
QSIMPLEQ_ENTRY(BlockdevOptions_queue) entry;
|
||||
} BlockdevOptions_queue;
|
||||
QSIMPLEQ_HEAD(, BlockdevOptions_queue) bdo_queue
|
||||
= QSIMPLEQ_HEAD_INITIALIZER(bdo_queue);
|
||||
|
||||
module_call_init(MODULE_INIT_TRACE);
|
||||
|
||||
|
@ -3118,6 +3128,25 @@ int main(int argc, char **argv, char **envp)
|
|||
drive_add(IF_DEFAULT, popt->index - QEMU_OPTION_hda, optarg,
|
||||
HD_OPTS);
|
||||
break;
|
||||
case QEMU_OPTION_blockdev:
|
||||
{
|
||||
Visitor *v;
|
||||
BlockdevOptions_queue *bdo;
|
||||
|
||||
v = qobject_input_visitor_new_str(optarg, "driver", &err);
|
||||
if (!v) {
|
||||
error_report_err(err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
bdo = g_new(BlockdevOptions_queue, 1);
|
||||
visit_type_BlockdevOptions(v, NULL, &bdo->bdo,
|
||||
&error_fatal);
|
||||
visit_free(v);
|
||||
loc_save(&bdo->loc);
|
||||
QSIMPLEQ_INSERT_TAIL(&bdo_queue, bdo, entry);
|
||||
break;
|
||||
}
|
||||
case QEMU_OPTION_drive:
|
||||
if (drive_def(optarg) == NULL) {
|
||||
exit(1);
|
||||
|
@ -4451,6 +4480,16 @@ int main(int argc, char **argv, char **envp)
|
|||
}
|
||||
|
||||
/* open the virtual block devices */
|
||||
while (!QSIMPLEQ_EMPTY(&bdo_queue)) {
|
||||
BlockdevOptions_queue *bdo = QSIMPLEQ_FIRST(&bdo_queue);
|
||||
|
||||
QSIMPLEQ_REMOVE_HEAD(&bdo_queue, entry);
|
||||
loc_push_restore(&bdo->loc);
|
||||
qmp_blockdev_add(bdo->bdo, &error_fatal);
|
||||
loc_pop(&bdo->loc);
|
||||
qapi_free_BlockdevOptions(bdo->bdo);
|
||||
g_free(bdo);
|
||||
}
|
||||
if (snapshot || replay_mode != REPLAY_MODE_NONE) {
|
||||
qemu_opts_foreach(qemu_find_opts("drive"), drive_enable_snapshot,
|
||||
NULL, NULL);
|
||||
|
|
Loading…
Reference in New Issue