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:
Peter Maydell 2017-03-07 17:06:48 +00:00
commit 87467097f8
23 changed files with 1747 additions and 83 deletions

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}
/*

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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"

View File

@ -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);

2
tests/.gitignore vendored
View File

@ -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

View File

@ -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)$@")

View File

@ -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);
}

View File

@ -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. */

624
tests/test-keyval.c Normal file
View File

@ -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;
}

85
tests/test-qapi-util.c Normal file
View File

@ -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;
}

View File

@ -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);

View File

@ -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",

View File

@ -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);

View File

@ -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

394
util/keyval.c Normal file
View File

@ -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
View File

@ -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);