diff options
author | 2023-10-10 11:40:56 +0000 | |
---|---|---|
committer | 2023-10-10 11:40:56 +0000 | |
commit | e02cda008591317b1625707ff8e115a4841aa889 (patch) | |
tree | aee302e3cf8b59ec2d32ec481be3d1afddfc8968 /qobject | |
parent | cc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (diff) |
Introduce Virtio-loopback epsilon release:
Epsilon release introduces a new compatibility layer which make virtio-loopback
design to work with QEMU and rust-vmm vhost-user backend without require any
changes.
Signed-off-by: Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
Change-Id: I52e57563e08a7d0bdc002f8e928ee61ba0c53dd9
Diffstat (limited to 'qobject')
-rw-r--r-- | qobject/block-qdict.c | 738 | ||||
-rw-r--r-- | qobject/json-lexer.c | 365 | ||||
-rw-r--r-- | qobject/json-parser-int.h | 54 | ||||
-rw-r--r-- | qobject/json-parser.c | 590 | ||||
-rw-r--r-- | qobject/json-streamer.c | 134 | ||||
-rw-r--r-- | qobject/json-writer.c | 247 | ||||
-rw-r--r-- | qobject/meson.build | 4 | ||||
-rw-r--r-- | qobject/qbool.c | 58 | ||||
-rw-r--r-- | qobject/qdict.c | 444 | ||||
-rw-r--r-- | qobject/qjson.c | 232 | ||||
-rw-r--r-- | qobject/qlist.c | 184 | ||||
-rw-r--r-- | qobject/qlit.c | 125 | ||||
-rw-r--r-- | qobject/qnull.c | 31 | ||||
-rw-r--r-- | qobject/qnum.c | 241 | ||||
-rw-r--r-- | qobject/qobject-internal.h | 39 | ||||
-rw-r--r-- | qobject/qobject.c | 72 | ||||
-rw-r--r-- | qobject/qstring.c | 102 |
17 files changed, 3660 insertions, 0 deletions
diff --git a/qobject/block-qdict.c b/qobject/block-qdict.c new file mode 100644 index 000000000..1487cc5dd --- /dev/null +++ b/qobject/block-qdict.c @@ -0,0 +1,738 @@ +/* + * Special QDict functions used by the block layer + * + * Copyright (c) 2013-2018 Red Hat, Inc. + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "block/qdict.h" +#include "qapi/qmp/qbool.h" +#include "qapi/qmp/qlist.h" +#include "qapi/qmp/qnum.h" +#include "qapi/qmp/qstring.h" +#include "qapi/qobject-input-visitor.h" +#include "qemu/cutils.h" +#include "qapi/error.h" + +/** + * qdict_copy_default(): If no entry mapped by 'key' exists in 'dst' yet, the + * value of 'key' in 'src' is copied there (and the refcount increased + * accordingly). + */ +void qdict_copy_default(QDict *dst, QDict *src, const char *key) +{ + QObject *val; + + if (qdict_haskey(dst, key)) { + return; + } + + val = qdict_get(src, key); + if (val) { + qdict_put_obj(dst, key, qobject_ref(val)); + } +} + +/** + * qdict_set_default_str(): If no entry mapped by 'key' exists in 'dst' yet, a + * new QString initialised by 'val' is put there. + */ +void qdict_set_default_str(QDict *dst, const char *key, const char *val) +{ + if (qdict_haskey(dst, key)) { + return; + } + + qdict_put_str(dst, key, val); +} + +static void qdict_flatten_qdict(QDict *qdict, QDict *target, + const char *prefix); + +static void qdict_flatten_qlist(QList *qlist, QDict *target, const char *prefix) +{ + QObject *value; + const QListEntry *entry; + QDict *dict_val; + QList *list_val; + char *new_key; + int i; + + /* This function is never called with prefix == NULL, i.e., it is always + * called from within qdict_flatten_q(list|dict)(). Therefore, it does not + * need to remove list entries during the iteration (the whole list will be + * deleted eventually anyway from qdict_flatten_qdict()). */ + assert(prefix); + + entry = qlist_first(qlist); + + for (i = 0; entry; entry = qlist_next(entry), i++) { + value = qlist_entry_obj(entry); + dict_val = qobject_to(QDict, value); + list_val = qobject_to(QList, value); + new_key = g_strdup_printf("%s.%i", prefix, i); + + /* + * Flatten non-empty QDict and QList recursively into @target, + * copy other objects to @target + */ + if (dict_val && qdict_size(dict_val)) { + qdict_flatten_qdict(dict_val, target, new_key); + } else if (list_val && !qlist_empty(list_val)) { + qdict_flatten_qlist(list_val, target, new_key); + } else { + qdict_put_obj(target, new_key, qobject_ref(value)); + } + + g_free(new_key); + } +} + +static void qdict_flatten_qdict(QDict *qdict, QDict *target, const char *prefix) +{ + QObject *value; + const QDictEntry *entry, *next; + QDict *dict_val; + QList *list_val; + char *key, *new_key; + + entry = qdict_first(qdict); + + while (entry != NULL) { + next = qdict_next(qdict, entry); + value = qdict_entry_value(entry); + dict_val = qobject_to(QDict, value); + list_val = qobject_to(QList, value); + + if (prefix) { + key = new_key = g_strdup_printf("%s.%s", prefix, entry->key); + } else { + key = entry->key; + new_key = NULL; + } + + /* + * Flatten non-empty QDict and QList recursively into @target, + * copy other objects to @target. + * On the root level (if @qdict == @target), remove flattened + * nested QDicts and QLists from @qdict. + * + * (Note that we do not need to remove entries from nested + * dicts or lists. Their reference count is decremented on + * the root level, so there are no leaks. In fact, if they + * have a reference count greater than one, we are probably + * well advised not to modify them altogether.) + */ + if (dict_val && qdict_size(dict_val)) { + qdict_flatten_qdict(dict_val, target, key); + if (target == qdict) { + qdict_del(qdict, entry->key); + } + } else if (list_val && !qlist_empty(list_val)) { + qdict_flatten_qlist(list_val, target, key); + if (target == qdict) { + qdict_del(qdict, entry->key); + } + } else if (target != qdict) { + qdict_put_obj(target, key, qobject_ref(value)); + } + + g_free(new_key); + entry = next; + } +} + +/** + * qdict_flatten(): For each nested non-empty QDict with key x, all + * fields with key y are moved to this QDict and their key is renamed + * to "x.y". For each nested non-empty QList with key x, the field at + * index y is moved to this QDict with the key "x.y" (i.e., the + * reverse of what qdict_array_split() does). + * This operation is applied recursively for nested QDicts and QLists. + */ +void qdict_flatten(QDict *qdict) +{ + qdict_flatten_qdict(qdict, qdict, NULL); +} + +/* extract all the src QDict entries starting by start into dst. + * If dst is NULL then the entries are simply removed from src. */ +void qdict_extract_subqdict(QDict *src, QDict **dst, const char *start) + +{ + const QDictEntry *entry, *next; + const char *p; + + if (dst) { + *dst = qdict_new(); + } + entry = qdict_first(src); + + while (entry != NULL) { + next = qdict_next(src, entry); + if (strstart(entry->key, start, &p)) { + if (dst) { + qdict_put_obj(*dst, p, qobject_ref(entry->value)); + } + qdict_del(src, entry->key); + } + entry = next; + } +} + +static int qdict_count_prefixed_entries(const QDict *src, const char *start) +{ + const QDictEntry *entry; + int count = 0; + + for (entry = qdict_first(src); entry; entry = qdict_next(src, entry)) { + if (strstart(entry->key, start, NULL)) { + if (count == INT_MAX) { + return -ERANGE; + } + count++; + } + } + + return count; +} + +/** + * qdict_array_split(): This function moves array-like elements of a QDict into + * a new QList. Every entry in the original QDict with a key "%u" or one + * prefixed "%u.", where %u designates an unsigned integer starting at 0 and + * incrementally counting up, will be moved to a new QDict at index %u in the + * output QList with the key prefix removed, if that prefix is "%u.". If the + * whole key is just "%u", the whole QObject will be moved unchanged without + * creating a new QDict. The function terminates when there is no entry in the + * QDict with a prefix directly (incrementally) following the last one; it also + * returns if there are both entries with "%u" and "%u." for the same index %u. + * Example: {"0.a": 42, "0.b": 23, "1.x": 0, "4.y": 1, "o.o": 7, "2": 66} + * (or {"1.x": 0, "4.y": 1, "0.a": 42, "o.o": 7, "0.b": 23, "2": 66}) + * => [{"a": 42, "b": 23}, {"x": 0}, 66] + * and {"4.y": 1, "o.o": 7} (remainder of the old QDict) + */ +void qdict_array_split(QDict *src, QList **dst) +{ + unsigned i; + + *dst = qlist_new(); + + for (i = 0; i < UINT_MAX; i++) { + QObject *subqobj; + bool is_subqdict; + QDict *subqdict; + char indexstr[32], prefix[32]; + size_t snprintf_ret; + + snprintf_ret = snprintf(indexstr, 32, "%u", i); + assert(snprintf_ret < 32); + + subqobj = qdict_get(src, indexstr); + + snprintf_ret = snprintf(prefix, 32, "%u.", i); + assert(snprintf_ret < 32); + + /* Overflow is the same as positive non-zero results */ + is_subqdict = qdict_count_prefixed_entries(src, prefix); + + /* + * There may be either a single subordinate object (named + * "%u") or multiple objects (each with a key prefixed "%u."), + * but not both. + */ + if (!subqobj == !is_subqdict) { + break; + } + + if (is_subqdict) { + qdict_extract_subqdict(src, &subqdict, prefix); + assert(qdict_size(subqdict) > 0); + } else { + qobject_ref(subqobj); + qdict_del(src, indexstr); + } + + qlist_append_obj(*dst, subqobj ?: QOBJECT(subqdict)); + } +} + +/** + * qdict_split_flat_key: + * @key: the key string to split + * @prefix: non-NULL pointer to hold extracted prefix + * @suffix: non-NULL pointer to remaining suffix + * + * Given a flattened key such as 'foo.0.bar', split it into two parts + * at the first '.' separator. Allows double dot ('..') to escape the + * normal separator. + * + * e.g. + * 'foo.0.bar' -> prefix='foo' and suffix='0.bar' + * 'foo..0.bar' -> prefix='foo.0' and suffix='bar' + * + * The '..' sequence will be unescaped in the returned 'prefix' + * string. The 'suffix' string will be left in escaped format, so it + * can be fed back into the qdict_split_flat_key() key as the input + * later. + * + * The caller is responsible for freeing the string returned in @prefix + * using g_free(). + */ +static void qdict_split_flat_key(const char *key, char **prefix, + const char **suffix) +{ + const char *separator; + size_t i, j; + + /* Find first '.' separator, but if there is a pair '..' + * that acts as an escape, so skip over '..' */ + separator = NULL; + do { + if (separator) { + separator += 2; + } else { + separator = key; + } + separator = strchr(separator, '.'); + } while (separator && separator[1] == '.'); + + if (separator) { + *prefix = g_strndup(key, separator - key); + *suffix = separator + 1; + } else { + *prefix = g_strdup(key); + *suffix = NULL; + } + + /* Unescape the '..' sequence into '.' */ + for (i = 0, j = 0; (*prefix)[i] != '\0'; i++, j++) { + if ((*prefix)[i] == '.') { + assert((*prefix)[i + 1] == '.'); + i++; + } + (*prefix)[j] = (*prefix)[i]; + } + (*prefix)[j] = '\0'; +} + +/** + * qdict_is_list: + * @maybe_list: dict to check if keys represent list elements. + * + * Determine whether all keys in @maybe_list are valid list elements. + * If @maybe_list is non-zero in length and all the keys look like + * valid list indexes, this will return 1. If @maybe_list is zero + * length or all keys are non-numeric then it will return 0 to indicate + * it is a normal qdict. If there is a mix of numeric and non-numeric + * keys, or the list indexes are non-contiguous, an error is reported. + * + * Returns: 1 if a valid list, 0 if a dict, -1 on error + */ +static int qdict_is_list(QDict *maybe_list, Error **errp) +{ + const QDictEntry *ent; + ssize_t len = 0; + ssize_t max = -1; + int is_list = -1; + int64_t val; + + for (ent = qdict_first(maybe_list); ent != NULL; + ent = qdict_next(maybe_list, ent)) { + int is_index = !qemu_strtoi64(ent->key, NULL, 10, &val); + + if (is_list == -1) { + is_list = is_index; + } + + if (is_index != is_list) { + error_setg(errp, "Cannot mix list and non-list keys"); + return -1; + } + + if (is_index) { + len++; + if (val > max) { + max = val; + } + } + } + + if (is_list == -1) { + assert(!qdict_size(maybe_list)); + is_list = 0; + } + + /* NB this isn't a perfect check - e.g. it won't catch + * a list containing '1', '+1', '01', '3', but that + * does not matter - we've still proved that the + * input is a list. It is up the caller to do a + * stricter check if desired */ + if (len != (max + 1)) { + error_setg(errp, "List indices are not contiguous, " + "saw %zd elements but %zd largest index", + len, max); + return -1; + } + + return is_list; +} + +/** + * qdict_crumple: + * @src: the original flat dictionary (only scalar values) to crumple + * + * Takes a flat dictionary whose keys use '.' separator to indicate + * nesting, and values are scalars, empty dictionaries or empty lists, + * and crumples it into a nested structure. + * + * To include a literal '.' in a key name, it must be escaped as '..' + * + * For example, an input of: + * + * { 'foo.0.bar': 'one', 'foo.0.wizz': '1', + * 'foo.1.bar': 'two', 'foo.1.wizz': '2' } + * + * will result in an output of: + * + * { + * 'foo': [ + * { 'bar': 'one', 'wizz': '1' }, + * { 'bar': 'two', 'wizz': '2' } + * ], + * } + * + * The following scenarios in the input dict will result in an + * error being returned: + * + * - Any values in @src are non-scalar types + * - If keys in @src imply that a particular level is both a + * list and a dict. e.g., "foo.0.bar" and "foo.eek.bar". + * - If keys in @src imply that a particular level is a list, + * but the indices are non-contiguous. e.g. "foo.0.bar" and + * "foo.2.bar" without any "foo.1.bar" present. + * - If keys in @src represent list indexes, but are not in + * the "%zu" format. e.g. "foo.+0.bar" + * + * Returns: either a QDict or QList for the nested data structure, or NULL + * on error + */ +QObject *qdict_crumple(const QDict *src, Error **errp) +{ + const QDictEntry *ent; + QDict *two_level, *multi_level = NULL, *child_dict; + QDict *dict_val; + QList *list_val; + QObject *dst = NULL, *child; + size_t i; + char *prefix = NULL; + const char *suffix = NULL; + int is_list; + + two_level = qdict_new(); + + /* Step 1: split our totally flat dict into a two level dict */ + for (ent = qdict_first(src); ent != NULL; ent = qdict_next(src, ent)) { + dict_val = qobject_to(QDict, ent->value); + list_val = qobject_to(QList, ent->value); + if ((dict_val && qdict_size(dict_val)) + || (list_val && !qlist_empty(list_val))) { + error_setg(errp, "Value %s is not flat", ent->key); + goto error; + } + + qdict_split_flat_key(ent->key, &prefix, &suffix); + child = qdict_get(two_level, prefix); + child_dict = qobject_to(QDict, child); + + if (child) { + /* + * If @child_dict, then all previous keys with this prefix + * had a suffix. If @suffix, this one has one as well, + * and we're good, else there's a clash. + */ + if (!child_dict || !suffix) { + error_setg(errp, "Cannot mix scalar and non-scalar keys"); + goto error; + } + } + + if (suffix) { + if (!child_dict) { + child_dict = qdict_new(); + qdict_put(two_level, prefix, child_dict); + } + qdict_put_obj(child_dict, suffix, qobject_ref(ent->value)); + } else { + qdict_put_obj(two_level, prefix, qobject_ref(ent->value)); + } + + g_free(prefix); + prefix = NULL; + } + + /* Step 2: optionally process the two level dict recursively + * into a multi-level dict */ + multi_level = qdict_new(); + for (ent = qdict_first(two_level); ent != NULL; + ent = qdict_next(two_level, ent)) { + dict_val = qobject_to(QDict, ent->value); + if (dict_val && qdict_size(dict_val)) { + child = qdict_crumple(dict_val, errp); + if (!child) { + goto error; + } + + qdict_put_obj(multi_level, ent->key, child); + } else { + qdict_put_obj(multi_level, ent->key, qobject_ref(ent->value)); + } + } + qobject_unref(two_level); + two_level = NULL; + + /* Step 3: detect if we need to turn our dict into list */ + is_list = qdict_is_list(multi_level, errp); + if (is_list < 0) { + goto error; + } + + if (is_list) { + dst = QOBJECT(qlist_new()); + + for (i = 0; i < qdict_size(multi_level); i++) { + char *key = g_strdup_printf("%zu", i); + + child = qdict_get(multi_level, key); + g_free(key); + + if (!child) { + error_setg(errp, "Missing list index %zu", i); + goto error; + } + + qlist_append_obj(qobject_to(QList, dst), qobject_ref(child)); + } + qobject_unref(multi_level); + multi_level = NULL; + } else { + dst = QOBJECT(multi_level); + } + + return dst; + + error: + g_free(prefix); + qobject_unref(multi_level); + qobject_unref(two_level); + qobject_unref(dst); + return NULL; +} + +/** + * qdict_crumple_for_keyval_qiv: + * @src: the flat dictionary (only scalar values) to crumple + * @errp: location to store error + * + * Like qdict_crumple(), but additionally transforms scalar values so + * the result can be passed to qobject_input_visitor_new_keyval(). + * + * The block subsystem uses this function to prepare its flat QDict + * with possibly confused scalar types for a visit. It should not be + * used for anything else, and it should go away once the block + * subsystem has been cleaned up. + */ +static QObject *qdict_crumple_for_keyval_qiv(QDict *src, Error **errp) +{ + QDict *tmp = NULL; + char *buf; + const char *s; + const QDictEntry *ent; + QObject *dst; + + for (ent = qdict_first(src); ent; ent = qdict_next(src, ent)) { + buf = NULL; + switch (qobject_type(ent->value)) { + case QTYPE_QNULL: + case QTYPE_QSTRING: + continue; + case QTYPE_QNUM: + s = buf = qnum_to_string(qobject_to(QNum, ent->value)); + break; + case QTYPE_QDICT: + case QTYPE_QLIST: + /* @src isn't flat; qdict_crumple() will fail */ + continue; + case QTYPE_QBOOL: + s = qbool_get_bool(qobject_to(QBool, ent->value)) + ? "on" : "off"; + break; + default: + abort(); + } + + if (!tmp) { + tmp = qdict_clone_shallow(src); + } + qdict_put_str(tmp, ent->key, s); + g_free(buf); + } + + dst = qdict_crumple(tmp ?: src, errp); + qobject_unref(tmp); + return dst; +} + +/** + * qdict_array_entries(): Returns the number of direct array entries if the + * sub-QDict of src specified by the prefix in subqdict (or src itself for + * prefix == "") is valid as an array, i.e. the length of the created list if + * the sub-QDict would become empty after calling qdict_array_split() on it. If + * the array is not valid, -EINVAL is returned. + */ +int qdict_array_entries(QDict *src, const char *subqdict) +{ + const QDictEntry *entry; + unsigned i; + unsigned entries = 0; + size_t subqdict_len = strlen(subqdict); + + assert(!subqdict_len || subqdict[subqdict_len - 1] == '.'); + + /* qdict_array_split() loops until UINT_MAX, but as we want to return + * negative errors, we only have a signed return value here. Any additional + * entries will lead to -EINVAL. */ + for (i = 0; i < INT_MAX; i++) { + QObject *subqobj; + int subqdict_entries; + char *prefix = g_strdup_printf("%s%u.", subqdict, i); + + subqdict_entries = qdict_count_prefixed_entries(src, prefix); + + /* Remove ending "." */ + prefix[strlen(prefix) - 1] = 0; + subqobj = qdict_get(src, prefix); + + g_free(prefix); + + if (subqdict_entries < 0) { + return subqdict_entries; + } + + /* There may be either a single subordinate object (named "%u") or + * multiple objects (each with a key prefixed "%u."), but not both. */ + if (subqobj && subqdict_entries) { + return -EINVAL; + } else if (!subqobj && !subqdict_entries) { + break; + } + + entries += subqdict_entries ? subqdict_entries : 1; + } + + /* Consider everything handled that isn't part of the given sub-QDict */ + for (entry = qdict_first(src); entry; entry = qdict_next(src, entry)) { + if (!strstart(qdict_entry_key(entry), subqdict, NULL)) { + entries++; + } + } + + /* Anything left in the sub-QDict that wasn't handled? */ + if (qdict_size(src) != entries) { + return -EINVAL; + } + + return i; +} + +/** + * qdict_join(): Absorb the src QDict into the dest QDict, that is, move all + * elements from src to dest. + * + * If an element from src has a key already present in dest, it will not be + * moved unless overwrite is true. + * + * If overwrite is true, the conflicting values in dest will be discarded and + * replaced by the corresponding values from src. + * + * Therefore, with overwrite being true, the src QDict will always be empty when + * this function returns. If overwrite is false, the src QDict will be empty + * iff there were no conflicts. + */ +void qdict_join(QDict *dest, QDict *src, bool overwrite) +{ + const QDictEntry *entry, *next; + + entry = qdict_first(src); + while (entry) { + next = qdict_next(src, entry); + + if (overwrite || !qdict_haskey(dest, entry->key)) { + qdict_put_obj(dest, entry->key, qobject_ref(entry->value)); + qdict_del(src, entry->key); + } + + entry = next; + } +} + +/** + * qdict_rename_keys(): Rename keys in qdict according to the replacements + * specified in the array renames. The array must be terminated by an entry + * with from = NULL. + * + * The renames are performed individually in the order of the array, so entries + * may be renamed multiple times and may or may not conflict depending on the + * order of the renames array. + * + * Returns true for success, false in error cases. + */ +bool qdict_rename_keys(QDict *qdict, const QDictRenames *renames, Error **errp) +{ + QObject *qobj; + + while (renames->from) { + if (qdict_haskey(qdict, renames->from)) { + if (qdict_haskey(qdict, renames->to)) { + error_setg(errp, "'%s' and its alias '%s' can't be used at the " + "same time", renames->to, renames->from); + return false; + } + + qobj = qdict_get(qdict, renames->from); + qdict_put_obj(qdict, renames->to, qobject_ref(qobj)); + qdict_del(qdict, renames->from); + } + + renames++; + } + return true; +} + +/* + * Create a QObject input visitor for flat @qdict with possibly + * confused scalar types. + * + * The block subsystem uses this function to visit its flat QDict with + * possibly confused scalar types. It should not be used for anything + * else, and it should go away once the block subsystem has been + * cleaned up. + */ +Visitor *qobject_input_visitor_new_flat_confused(QDict *qdict, + Error **errp) +{ + QObject *crumpled; + Visitor *v; + + crumpled = qdict_crumple_for_keyval_qiv(qdict, errp); + if (!crumpled) { + return NULL; + } + + v = qobject_input_visitor_new_keyval(crumpled); + qobject_unref(crumpled); + return v; +} diff --git a/qobject/json-lexer.c b/qobject/json-lexer.c new file mode 100644 index 000000000..632320d72 --- /dev/null +++ b/qobject/json-lexer.c @@ -0,0 +1,365 @@ +/* + * JSON lexer + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include "json-parser-int.h" + +#define MAX_TOKEN_SIZE (64ULL << 20) + +/* + * From RFC 8259 "The JavaScript Object Notation (JSON) Data + * Interchange Format", with [comments in brackets]: + * + * The set of tokens includes six structural characters, strings, + * numbers, and three literal names. + * + * These are the six structural characters: + * + * begin-array = ws %x5B ws ; [ left square bracket + * begin-object = ws %x7B ws ; { left curly bracket + * end-array = ws %x5D ws ; ] right square bracket + * end-object = ws %x7D ws ; } right curly bracket + * name-separator = ws %x3A ws ; : colon + * value-separator = ws %x2C ws ; , comma + * + * Insignificant whitespace is allowed before or after any of the six + * structural characters. + * [This lexer accepts it before or after any token, which is actually + * the same, as the grammar always has structural characters between + * other tokens.] + * + * ws = *( + * %x20 / ; Space + * %x09 / ; Horizontal tab + * %x0A / ; Line feed or New line + * %x0D ) ; Carriage return + * + * [...] three literal names: + * false null true + * [This lexer accepts [a-z]+, and leaves rejecting unknown literal + * names to the parser.] + * + * [Numbers:] + * + * number = [ minus ] int [ frac ] [ exp ] + * decimal-point = %x2E ; . + * digit1-9 = %x31-39 ; 1-9 + * e = %x65 / %x45 ; e E + * exp = e [ minus / plus ] 1*DIGIT + * frac = decimal-point 1*DIGIT + * int = zero / ( digit1-9 *DIGIT ) + * minus = %x2D ; - + * plus = %x2B ; + + * zero = %x30 ; 0 + * + * [Strings:] + * string = quotation-mark *char quotation-mark + * + * char = unescaped / + * escape ( + * %x22 / ; " quotation mark U+0022 + * %x5C / ; \ reverse solidus U+005C + * %x2F / ; / solidus U+002F + * %x62 / ; b backspace U+0008 + * %x66 / ; f form feed U+000C + * %x6E / ; n line feed U+000A + * %x72 / ; r carriage return U+000D + * %x74 / ; t tab U+0009 + * %x75 4HEXDIG ) ; uXXXX U+XXXX + * escape = %x5C ; \ + * quotation-mark = %x22 ; " + * unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + * [This lexer accepts any non-control character after escape, and + * leaves rejecting invalid ones to the parser.] + * + * + * Extensions over RFC 8259: + * - Extra escape sequence in strings: + * 0x27 (apostrophe) is recognized after escape, too + * - Single-quoted strings: + * Like double-quoted strings, except they're delimited by %x27 + * (apostrophe) instead of %x22 (quotation mark), and can't contain + * unescaped apostrophe, but can contain unescaped quotation mark. + * - Interpolation, if enabled: + * The lexer accepts %[A-Za-z0-9]*, and leaves rejecting invalid + * ones to the parser. + * + * Note: + * - Input must be encoded in modified UTF-8. + * - Decoding and validating is left to the parser. + */ + +enum json_lexer_state { + IN_RECOVERY = 1, + IN_DQ_STRING_ESCAPE, + IN_DQ_STRING, + IN_SQ_STRING_ESCAPE, + IN_SQ_STRING, + IN_ZERO, + IN_EXP_DIGITS, + IN_EXP_SIGN, + IN_EXP_E, + IN_MANTISSA, + IN_MANTISSA_DIGITS, + IN_DIGITS, + IN_SIGN, + IN_KEYWORD, + IN_INTERP, + IN_START, + IN_START_INTERP, /* must be IN_START + 1 */ +}; + +QEMU_BUILD_BUG_ON(JSON_ERROR != 0); +QEMU_BUILD_BUG_ON(IN_RECOVERY != JSON_ERROR + 1); +QEMU_BUILD_BUG_ON((int)JSON_MIN <= (int)IN_START_INTERP); +QEMU_BUILD_BUG_ON(JSON_MAX >= 0x80); +QEMU_BUILD_BUG_ON(IN_START_INTERP != IN_START + 1); + +#define LOOKAHEAD 0x80 +#define TERMINAL(state) [0 ... 0xFF] = ((state) | LOOKAHEAD) + +static const uint8_t json_lexer[][256] = { + /* Relies on default initialization to IN_ERROR! */ + + /* error recovery */ + [IN_RECOVERY] = { + /* + * Skip characters until a structural character, an ASCII + * control character other than '\t', or impossible UTF-8 + * bytes '\xFE', '\xFF'. Structural characters and line + * endings are promising resynchronization points. Clients + * may use the others to force the JSON parser into known-good + * state; see docs/interop/qmp-spec.txt. + */ + [0 ... 0x1F] = IN_START | LOOKAHEAD, + [0x20 ... 0xFD] = IN_RECOVERY, + [0xFE ... 0xFF] = IN_START | LOOKAHEAD, + ['\t'] = IN_RECOVERY, + ['['] = IN_START | LOOKAHEAD, + [']'] = IN_START | LOOKAHEAD, + ['{'] = IN_START | LOOKAHEAD, + ['}'] = IN_START | LOOKAHEAD, + [':'] = IN_START | LOOKAHEAD, + [','] = IN_START | LOOKAHEAD, + }, + + /* double quote string */ + [IN_DQ_STRING_ESCAPE] = { + [0x20 ... 0xFD] = IN_DQ_STRING, + }, + [IN_DQ_STRING] = { + [0x20 ... 0xFD] = IN_DQ_STRING, + ['\\'] = IN_DQ_STRING_ESCAPE, + ['"'] = JSON_STRING, + }, + + /* single quote string */ + [IN_SQ_STRING_ESCAPE] = { + [0x20 ... 0xFD] = IN_SQ_STRING, + }, + [IN_SQ_STRING] = { + [0x20 ... 0xFD] = IN_SQ_STRING, + ['\\'] = IN_SQ_STRING_ESCAPE, + ['\''] = JSON_STRING, + }, + + /* Zero */ + [IN_ZERO] = { + TERMINAL(JSON_INTEGER), + ['0' ... '9'] = JSON_ERROR, + ['.'] = IN_MANTISSA, + }, + + /* Float */ + [IN_EXP_DIGITS] = { + TERMINAL(JSON_FLOAT), + ['0' ... '9'] = IN_EXP_DIGITS, + }, + + [IN_EXP_SIGN] = { + ['0' ... '9'] = IN_EXP_DIGITS, + }, + + [IN_EXP_E] = { + ['-'] = IN_EXP_SIGN, + ['+'] = IN_EXP_SIGN, + ['0' ... '9'] = IN_EXP_DIGITS, + }, + + [IN_MANTISSA_DIGITS] = { + TERMINAL(JSON_FLOAT), + ['0' ... '9'] = IN_MANTISSA_DIGITS, + ['e'] = IN_EXP_E, + ['E'] = IN_EXP_E, + }, + + [IN_MANTISSA] = { + ['0' ... '9'] = IN_MANTISSA_DIGITS, + }, + + /* Number */ + [IN_DIGITS] = { + TERMINAL(JSON_INTEGER), + ['0' ... '9'] = IN_DIGITS, + ['e'] = IN_EXP_E, + ['E'] = IN_EXP_E, + ['.'] = IN_MANTISSA, + }, + + [IN_SIGN] = { + ['0'] = IN_ZERO, + ['1' ... '9'] = IN_DIGITS, + }, + + /* keywords */ + [IN_KEYWORD] = { + TERMINAL(JSON_KEYWORD), + ['a' ... 'z'] = IN_KEYWORD, + }, + + /* interpolation */ + [IN_INTERP] = { + TERMINAL(JSON_INTERP), + ['A' ... 'Z'] = IN_INTERP, + ['a' ... 'z'] = IN_INTERP, + ['0' ... '9'] = IN_INTERP, + }, + + /* + * Two start states: + * - IN_START recognizes JSON tokens with our string extensions + * - IN_START_INTERP additionally recognizes interpolation. + */ + [IN_START ... IN_START_INTERP] = { + ['"'] = IN_DQ_STRING, + ['\''] = IN_SQ_STRING, + ['0'] = IN_ZERO, + ['1' ... '9'] = IN_DIGITS, + ['-'] = IN_SIGN, + ['{'] = JSON_LCURLY, + ['}'] = JSON_RCURLY, + ['['] = JSON_LSQUARE, + [']'] = JSON_RSQUARE, + [','] = JSON_COMMA, + [':'] = JSON_COLON, + ['a' ... 'z'] = IN_KEYWORD, + [' '] = IN_START, + ['\t'] = IN_START, + ['\r'] = IN_START, + ['\n'] = IN_START, + }, + [IN_START_INTERP]['%'] = IN_INTERP, +}; + +static inline uint8_t next_state(JSONLexer *lexer, char ch, bool flush, + bool *char_consumed) +{ + uint8_t next; + + assert(lexer->state < ARRAY_SIZE(json_lexer)); + next = json_lexer[lexer->state][(uint8_t)ch]; + *char_consumed = !flush && !(next & LOOKAHEAD); + return next & ~LOOKAHEAD; +} + +void json_lexer_init(JSONLexer *lexer, bool enable_interpolation) +{ + lexer->start_state = lexer->state = enable_interpolation + ? IN_START_INTERP : IN_START; + lexer->token = g_string_sized_new(3); + lexer->x = lexer->y = 0; +} + +static void json_lexer_feed_char(JSONLexer *lexer, char ch, bool flush) +{ + int new_state; + bool char_consumed = false; + + lexer->x++; + if (ch == '\n') { + lexer->x = 0; + lexer->y++; + } + + while (flush ? lexer->state != lexer->start_state : !char_consumed) { + new_state = next_state(lexer, ch, flush, &char_consumed); + if (char_consumed) { + assert(!flush); + g_string_append_c(lexer->token, ch); + } + + switch (new_state) { + case JSON_LCURLY: + case JSON_RCURLY: + case JSON_LSQUARE: + case JSON_RSQUARE: + case JSON_COLON: + case JSON_COMMA: + case JSON_INTERP: + case JSON_INTEGER: + case JSON_FLOAT: + case JSON_KEYWORD: + case JSON_STRING: + json_message_process_token(lexer, lexer->token, new_state, + lexer->x, lexer->y); + /* fall through */ + case IN_START: + g_string_truncate(lexer->token, 0); + new_state = lexer->start_state; + break; + case JSON_ERROR: + json_message_process_token(lexer, lexer->token, JSON_ERROR, + lexer->x, lexer->y); + new_state = IN_RECOVERY; + /* fall through */ + case IN_RECOVERY: + g_string_truncate(lexer->token, 0); + break; + default: + break; + } + lexer->state = new_state; + } + + /* Do not let a single token grow to an arbitrarily large size, + * this is a security consideration. + */ + if (lexer->token->len > MAX_TOKEN_SIZE) { + json_message_process_token(lexer, lexer->token, lexer->state, + lexer->x, lexer->y); + g_string_truncate(lexer->token, 0); + lexer->state = lexer->start_state; + } +} + +void json_lexer_feed(JSONLexer *lexer, const char *buffer, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) { + json_lexer_feed_char(lexer, buffer[i], false); + } +} + +void json_lexer_flush(JSONLexer *lexer) +{ + json_lexer_feed_char(lexer, 0, true); + assert(lexer->state == lexer->start_state); + json_message_process_token(lexer, lexer->token, JSON_END_OF_INPUT, + lexer->x, lexer->y); +} + +void json_lexer_destroy(JSONLexer *lexer) +{ + g_string_free(lexer->token, true); +} diff --git a/qobject/json-parser-int.h b/qobject/json-parser-int.h new file mode 100644 index 000000000..16a25d00b --- /dev/null +++ b/qobject/json-parser-int.h @@ -0,0 +1,54 @@ +/* + * JSON Parser + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef JSON_PARSER_INT_H +#define JSON_PARSER_INT_H + +#include "qapi/qmp/json-parser.h" + +typedef enum json_token_type { + JSON_ERROR = 0, /* must be zero, see json_lexer[] */ + /* Gap for lexer states */ + JSON_LCURLY = 100, + JSON_MIN = JSON_LCURLY, + JSON_RCURLY, + JSON_LSQUARE, + JSON_RSQUARE, + JSON_COLON, + JSON_COMMA, + JSON_INTEGER, + JSON_FLOAT, + JSON_KEYWORD, + JSON_STRING, + JSON_INTERP, + JSON_END_OF_INPUT, + JSON_MAX = JSON_END_OF_INPUT +} JSONTokenType; + +typedef struct JSONToken JSONToken; + +/* json-lexer.c */ +void json_lexer_init(JSONLexer *lexer, bool enable_interpolation); +void json_lexer_feed(JSONLexer *lexer, const char *buffer, size_t size); +void json_lexer_flush(JSONLexer *lexer); +void json_lexer_destroy(JSONLexer *lexer); + +/* json-streamer.c */ +void json_message_process_token(JSONLexer *lexer, GString *input, + JSONTokenType type, int x, int y); + +/* json-parser.c */ +JSONToken *json_token(JSONTokenType type, int x, int y, GString *tokstr); +QObject *json_parser_parse(GQueue *tokens, va_list *ap, Error **errp); + +#endif diff --git a/qobject/json-parser.c b/qobject/json-parser.c new file mode 100644 index 000000000..008b326fb --- /dev/null +++ b/qobject/json-parser.c @@ -0,0 +1,590 @@ +/* + * JSON Parser + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include "qemu/ctype.h" +#include "qemu/cutils.h" +#include "qemu/unicode.h" +#include "qapi/error.h" +#include "qapi/qmp/qbool.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qlist.h" +#include "qapi/qmp/qnull.h" +#include "qapi/qmp/qnum.h" +#include "qapi/qmp/qstring.h" +#include "json-parser-int.h" + +struct JSONToken { + JSONTokenType type; + int x; + int y; + char str[]; +}; + +typedef struct JSONParserContext { + Error *err; + JSONToken *current; + GQueue *buf; + va_list *ap; +} JSONParserContext; + +#define BUG_ON(cond) assert(!(cond)) + +/** + * TODO + * + * 0) make errors meaningful again + * 1) add geometry information to tokens + * 3) should we return a parsed size? + * 4) deal with premature EOI + */ + +static QObject *parse_value(JSONParserContext *ctxt); + +/** + * Error handler + */ +static void GCC_FMT_ATTR(3, 4) parse_error(JSONParserContext *ctxt, + JSONToken *token, const char *msg, ...) +{ + va_list ap; + char message[1024]; + + if (ctxt->err) { + return; + } + va_start(ap, msg); + vsnprintf(message, sizeof(message), msg, ap); + va_end(ap); + error_setg(&ctxt->err, "JSON parse error, %s", message); +} + +static int cvt4hex(const char *s) +{ + int cp, i; + + cp = 0; + for (i = 0; i < 4; i++) { + if (!qemu_isxdigit(s[i])) { + return -1; + } + cp <<= 4; + if (s[i] >= '0' && s[i] <= '9') { + cp |= s[i] - '0'; + } else if (s[i] >= 'a' && s[i] <= 'f') { + cp |= 10 + s[i] - 'a'; + } else if (s[i] >= 'A' && s[i] <= 'F') { + cp |= 10 + s[i] - 'A'; + } else { + return -1; + } + } + return cp; +} + +/** + * parse_string(): Parse a JSON string + * + * From RFC 8259 "The JavaScript Object Notation (JSON) Data + * Interchange Format": + * + * char = unescaped / + * escape ( + * %x22 / ; " quotation mark U+0022 + * %x5C / ; \ reverse solidus U+005C + * %x2F / ; / solidus U+002F + * %x62 / ; b backspace U+0008 + * %x66 / ; f form feed U+000C + * %x6E / ; n line feed U+000A + * %x72 / ; r carriage return U+000D + * %x74 / ; t tab U+0009 + * %x75 4HEXDIG ) ; uXXXX U+XXXX + * escape = %x5C ; \ + * quotation-mark = %x22 ; " + * unescaped = %x20-21 / %x23-5B / %x5D-10FFFF + * + * Extensions over RFC 8259: + * - Extra escape sequence in strings: + * 0x27 (apostrophe) is recognized after escape, too + * - Single-quoted strings: + * Like double-quoted strings, except they're delimited by %x27 + * (apostrophe) instead of %x22 (quotation mark), and can't contain + * unescaped apostrophe, but can contain unescaped quotation mark. + * + * Note: + * - Encoding is modified UTF-8. + * - Invalid Unicode characters are rejected. + * - Control characters \x00..\x1F are rejected by the lexer. + */ +static QString *parse_string(JSONParserContext *ctxt, JSONToken *token) +{ + const char *ptr = token->str; + GString *str; + char quote; + const char *beg; + int cp, trailing; + char *end; + ssize_t len; + char utf8_buf[5]; + + assert(*ptr == '"' || *ptr == '\''); + quote = *ptr++; + str = g_string_new(NULL); + + while (*ptr != quote) { + assert(*ptr); + switch (*ptr) { + case '\\': + beg = ptr++; + switch (*ptr++) { + case '"': + g_string_append_c(str, '"'); + break; + case '\'': + g_string_append_c(str, '\''); + break; + case '\\': + g_string_append_c(str, '\\'); + break; + case '/': + g_string_append_c(str, '/'); + break; + case 'b': + g_string_append_c(str, '\b'); + break; + case 'f': + g_string_append_c(str, '\f'); + break; + case 'n': + g_string_append_c(str, '\n'); + break; + case 'r': + g_string_append_c(str, '\r'); + break; + case 't': + g_string_append_c(str, '\t'); + break; + case 'u': + cp = cvt4hex(ptr); + ptr += 4; + + /* handle surrogate pairs */ + if (cp >= 0xD800 && cp <= 0xDBFF + && ptr[0] == '\\' && ptr[1] == 'u') { + /* leading surrogate followed by \u */ + cp = 0x10000 + ((cp & 0x3FF) << 10); + trailing = cvt4hex(ptr + 2); + if (trailing >= 0xDC00 && trailing <= 0xDFFF) { + /* followed by trailing surrogate */ + cp |= trailing & 0x3FF; + ptr += 6; + } else { + cp = -1; /* invalid */ + } + } + + if (mod_utf8_encode(utf8_buf, sizeof(utf8_buf), cp) < 0) { + parse_error(ctxt, token, + "%.*s is not a valid Unicode character", + (int)(ptr - beg), beg); + goto out; + } + g_string_append(str, utf8_buf); + break; + default: + parse_error(ctxt, token, "invalid escape sequence in string"); + goto out; + } + break; + case '%': + if (ctxt->ap) { + if (ptr[1] != '%') { + parse_error(ctxt, token, "can't interpolate into string"); + goto out; + } + ptr++; + } + /* fall through */ + default: + cp = mod_utf8_codepoint(ptr, 6, &end); + if (cp < 0) { + parse_error(ctxt, token, "invalid UTF-8 sequence in string"); + goto out; + } + ptr = end; + len = mod_utf8_encode(utf8_buf, sizeof(utf8_buf), cp); + assert(len >= 0); + g_string_append(str, utf8_buf); + } + } + + return qstring_from_gstring(str); + +out: + g_string_free(str, true); + return NULL; +} + +/* Note: the token object returned by parser_context_peek_token or + * parser_context_pop_token is deleted as soon as parser_context_pop_token + * is called again. + */ +static JSONToken *parser_context_pop_token(JSONParserContext *ctxt) +{ + g_free(ctxt->current); + ctxt->current = g_queue_pop_head(ctxt->buf); + return ctxt->current; +} + +static JSONToken *parser_context_peek_token(JSONParserContext *ctxt) +{ + return g_queue_peek_head(ctxt->buf); +} + +/** + * Parsing rules + */ +static int parse_pair(JSONParserContext *ctxt, QDict *dict) +{ + QObject *key_obj = NULL; + QString *key; + QObject *value; + JSONToken *peek, *token; + + peek = parser_context_peek_token(ctxt); + if (peek == NULL) { + parse_error(ctxt, NULL, "premature EOI"); + goto out; + } + + key_obj = parse_value(ctxt); + key = qobject_to(QString, key_obj); + if (!key) { + parse_error(ctxt, peek, "key is not a string in object"); + goto out; + } + + token = parser_context_pop_token(ctxt); + if (token == NULL) { + parse_error(ctxt, NULL, "premature EOI"); + goto out; + } + + if (token->type != JSON_COLON) { + parse_error(ctxt, token, "missing : in object pair"); + goto out; + } + + value = parse_value(ctxt); + if (value == NULL) { + parse_error(ctxt, token, "Missing value in dict"); + goto out; + } + + if (qdict_haskey(dict, qstring_get_str(key))) { + parse_error(ctxt, token, "duplicate key"); + goto out; + } + + qdict_put_obj(dict, qstring_get_str(key), value); + + qobject_unref(key_obj); + return 0; + +out: + qobject_unref(key_obj); + return -1; +} + +static QObject *parse_object(JSONParserContext *ctxt) +{ + QDict *dict = NULL; + JSONToken *token, *peek; + + token = parser_context_pop_token(ctxt); + assert(token && token->type == JSON_LCURLY); + + dict = qdict_new(); + + peek = parser_context_peek_token(ctxt); + if (peek == NULL) { + parse_error(ctxt, NULL, "premature EOI"); + goto out; + } + + if (peek->type != JSON_RCURLY) { + if (parse_pair(ctxt, dict) == -1) { + goto out; + } + + token = parser_context_pop_token(ctxt); + if (token == NULL) { + parse_error(ctxt, NULL, "premature EOI"); + goto out; + } + + while (token->type != JSON_RCURLY) { + if (token->type != JSON_COMMA) { + parse_error(ctxt, token, "expected separator in dict"); + goto out; + } + + if (parse_pair(ctxt, dict) == -1) { + goto out; + } + + token = parser_context_pop_token(ctxt); + if (token == NULL) { + parse_error(ctxt, NULL, "premature EOI"); + goto out; + } + } + } else { + (void)parser_context_pop_token(ctxt); + } + + return QOBJECT(dict); + +out: + qobject_unref(dict); + return NULL; +} + +static QObject *parse_array(JSONParserContext *ctxt) +{ + QList *list = NULL; + JSONToken *token, *peek; + + token = parser_context_pop_token(ctxt); + assert(token && token->type == JSON_LSQUARE); + + list = qlist_new(); + + peek = parser_context_peek_token(ctxt); + if (peek == NULL) { + parse_error(ctxt, NULL, "premature EOI"); + goto out; + } + + if (peek->type != JSON_RSQUARE) { + QObject *obj; + + obj = parse_value(ctxt); + if (obj == NULL) { + parse_error(ctxt, token, "expecting value"); + goto out; + } + + qlist_append_obj(list, obj); + + token = parser_context_pop_token(ctxt); + if (token == NULL) { + parse_error(ctxt, NULL, "premature EOI"); + goto out; + } + + while (token->type != JSON_RSQUARE) { + if (token->type != JSON_COMMA) { + parse_error(ctxt, token, "expected separator in list"); + goto out; + } + + obj = parse_value(ctxt); + if (obj == NULL) { + parse_error(ctxt, token, "expecting value"); + goto out; + } + + qlist_append_obj(list, obj); + + token = parser_context_pop_token(ctxt); + if (token == NULL) { + parse_error(ctxt, NULL, "premature EOI"); + goto out; + } + } + } else { + (void)parser_context_pop_token(ctxt); + } + + return QOBJECT(list); + +out: + qobject_unref(list); + return NULL; +} + +static QObject *parse_keyword(JSONParserContext *ctxt) +{ + JSONToken *token; + + token = parser_context_pop_token(ctxt); + assert(token && token->type == JSON_KEYWORD); + + if (!strcmp(token->str, "true")) { + return QOBJECT(qbool_from_bool(true)); + } else if (!strcmp(token->str, "false")) { + return QOBJECT(qbool_from_bool(false)); + } else if (!strcmp(token->str, "null")) { + return QOBJECT(qnull()); + } + parse_error(ctxt, token, "invalid keyword '%s'", token->str); + return NULL; +} + +static QObject *parse_interpolation(JSONParserContext *ctxt) +{ + JSONToken *token; + + token = parser_context_pop_token(ctxt); + assert(token && token->type == JSON_INTERP); + + if (!strcmp(token->str, "%p")) { + return va_arg(*ctxt->ap, QObject *); + } else if (!strcmp(token->str, "%i")) { + return QOBJECT(qbool_from_bool(va_arg(*ctxt->ap, int))); + } else if (!strcmp(token->str, "%d")) { + return QOBJECT(qnum_from_int(va_arg(*ctxt->ap, int))); + } else if (!strcmp(token->str, "%ld")) { + return QOBJECT(qnum_from_int(va_arg(*ctxt->ap, long))); + } else if (!strcmp(token->str, "%lld")) { + return QOBJECT(qnum_from_int(va_arg(*ctxt->ap, long long))); + } else if (!strcmp(token->str, "%" PRId64)) { + return QOBJECT(qnum_from_int(va_arg(*ctxt->ap, int64_t))); + } else if (!strcmp(token->str, "%u")) { + return QOBJECT(qnum_from_uint(va_arg(*ctxt->ap, unsigned int))); + } else if (!strcmp(token->str, "%lu")) { + return QOBJECT(qnum_from_uint(va_arg(*ctxt->ap, unsigned long))); + } else if (!strcmp(token->str, "%llu")) { + return QOBJECT(qnum_from_uint(va_arg(*ctxt->ap, unsigned long long))); + } else if (!strcmp(token->str, "%" PRIu64)) { + return QOBJECT(qnum_from_uint(va_arg(*ctxt->ap, uint64_t))); + } else if (!strcmp(token->str, "%s")) { + return QOBJECT(qstring_from_str(va_arg(*ctxt->ap, const char *))); + } else if (!strcmp(token->str, "%f")) { + return QOBJECT(qnum_from_double(va_arg(*ctxt->ap, double))); + } + parse_error(ctxt, token, "invalid interpolation '%s'", token->str); + return NULL; +} + +static QObject *parse_literal(JSONParserContext *ctxt) +{ + JSONToken *token; + + token = parser_context_pop_token(ctxt); + assert(token); + + switch (token->type) { + case JSON_STRING: + return QOBJECT(parse_string(ctxt, token)); + case JSON_INTEGER: { + /* + * Represent JSON_INTEGER as QNUM_I64 if possible, else as + * QNUM_U64, else as QNUM_DOUBLE. Note that qemu_strtoi64() + * and qemu_strtou64() fail with ERANGE when it's not + * possible. + * + * qnum_get_int() will then work for any signed 64-bit + * JSON_INTEGER, qnum_get_uint() for any unsigned 64-bit + * integer, and qnum_get_double() both for any JSON_INTEGER + * and any JSON_FLOAT (with precision loss for integers beyond + * 53 bits) + */ + int ret; + int64_t value; + uint64_t uvalue; + + ret = qemu_strtoi64(token->str, NULL, 10, &value); + if (!ret) { + return QOBJECT(qnum_from_int(value)); + } + assert(ret == -ERANGE); + + if (token->str[0] != '-') { + ret = qemu_strtou64(token->str, NULL, 10, &uvalue); + if (!ret) { + return QOBJECT(qnum_from_uint(uvalue)); + } + assert(ret == -ERANGE); + } + } + /* fall through to JSON_FLOAT */ + case JSON_FLOAT: + /* FIXME dependent on locale; a pervasive issue in QEMU */ + /* FIXME our lexer matches RFC 8259 in forbidding Inf or NaN, + * but those might be useful extensions beyond JSON */ + return QOBJECT(qnum_from_double(strtod(token->str, NULL))); + default: + abort(); + } +} + +static QObject *parse_value(JSONParserContext *ctxt) +{ + JSONToken *token; + + token = parser_context_peek_token(ctxt); + if (token == NULL) { + parse_error(ctxt, NULL, "premature EOI"); + return NULL; + } + + switch (token->type) { + case JSON_LCURLY: + return parse_object(ctxt); + case JSON_LSQUARE: + return parse_array(ctxt); + case JSON_INTERP: + return parse_interpolation(ctxt); + case JSON_INTEGER: + case JSON_FLOAT: + case JSON_STRING: + return parse_literal(ctxt); + case JSON_KEYWORD: + return parse_keyword(ctxt); + default: + parse_error(ctxt, token, "expecting value"); + return NULL; + } +} + +JSONToken *json_token(JSONTokenType type, int x, int y, GString *tokstr) +{ + JSONToken *token = g_malloc(sizeof(JSONToken) + tokstr->len + 1); + + token->type = type; + memcpy(token->str, tokstr->str, tokstr->len); + token->str[tokstr->len] = 0; + token->x = x; + token->y = y; + return token; +} + +QObject *json_parser_parse(GQueue *tokens, va_list *ap, Error **errp) +{ + JSONParserContext ctxt = { .buf = tokens, .ap = ap }; + QObject *result; + + result = parse_value(&ctxt); + assert(ctxt.err || g_queue_is_empty(ctxt.buf)); + + error_propagate(errp, ctxt.err); + + while (!g_queue_is_empty(ctxt.buf)) { + parser_context_pop_token(&ctxt); + } + g_free(ctxt.current); + + return result; +} diff --git a/qobject/json-streamer.c b/qobject/json-streamer.c new file mode 100644 index 000000000..b93d97b99 --- /dev/null +++ b/qobject/json-streamer.c @@ -0,0 +1,134 @@ +/* + * JSON streaming support + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "json-parser-int.h" + +#define MAX_TOKEN_SIZE (64ULL << 20) +#define MAX_TOKEN_COUNT (2ULL << 20) +#define MAX_NESTING (1 << 10) + +static void json_message_free_tokens(JSONMessageParser *parser) +{ + JSONToken *token; + + while ((token = g_queue_pop_head(&parser->tokens))) { + g_free(token); + } +} + +void json_message_process_token(JSONLexer *lexer, GString *input, + JSONTokenType type, int x, int y) +{ + JSONMessageParser *parser = container_of(lexer, JSONMessageParser, lexer); + QObject *json = NULL; + Error *err = NULL; + JSONToken *token; + + switch (type) { + case JSON_LCURLY: + parser->brace_count++; + break; + case JSON_RCURLY: + parser->brace_count--; + break; + case JSON_LSQUARE: + parser->bracket_count++; + break; + case JSON_RSQUARE: + parser->bracket_count--; + break; + case JSON_ERROR: + error_setg(&err, "JSON parse error, stray '%s'", input->str); + goto out_emit; + case JSON_END_OF_INPUT: + if (g_queue_is_empty(&parser->tokens)) { + return; + } + json = json_parser_parse(&parser->tokens, parser->ap, &err); + goto out_emit; + default: + break; + } + + /* + * Security consideration, we limit total memory allocated per object + * and the maximum recursion depth that a message can force. + */ + if (parser->token_size + input->len + 1 > MAX_TOKEN_SIZE) { + error_setg(&err, "JSON token size limit exceeded"); + goto out_emit; + } + if (g_queue_get_length(&parser->tokens) + 1 > MAX_TOKEN_COUNT) { + error_setg(&err, "JSON token count limit exceeded"); + goto out_emit; + } + if (parser->bracket_count + parser->brace_count > MAX_NESTING) { + error_setg(&err, "JSON nesting depth limit exceeded"); + goto out_emit; + } + + token = json_token(type, x, y, input); + parser->token_size += input->len; + + g_queue_push_tail(&parser->tokens, token); + + if ((parser->brace_count > 0 || parser->bracket_count > 0) + && parser->brace_count >= 0 && parser->bracket_count >= 0) { + return; + } + + json = json_parser_parse(&parser->tokens, parser->ap, &err); + +out_emit: + parser->brace_count = 0; + parser->bracket_count = 0; + json_message_free_tokens(parser); + parser->token_size = 0; + parser->emit(parser->opaque, json, err); +} + +void json_message_parser_init(JSONMessageParser *parser, + void (*emit)(void *opaque, QObject *json, + Error *err), + void *opaque, va_list *ap) +{ + parser->emit = emit; + parser->opaque = opaque; + parser->ap = ap; + parser->brace_count = 0; + parser->bracket_count = 0; + g_queue_init(&parser->tokens); + parser->token_size = 0; + + json_lexer_init(&parser->lexer, !!ap); +} + +void json_message_parser_feed(JSONMessageParser *parser, + const char *buffer, size_t size) +{ + json_lexer_feed(&parser->lexer, buffer, size); +} + +void json_message_parser_flush(JSONMessageParser *parser) +{ + json_lexer_flush(&parser->lexer); + assert(g_queue_is_empty(&parser->tokens)); +} + +void json_message_parser_destroy(JSONMessageParser *parser) +{ + json_lexer_destroy(&parser->lexer); + json_message_free_tokens(parser); +} diff --git a/qobject/json-writer.c b/qobject/json-writer.c new file mode 100644 index 000000000..309a31d57 --- /dev/null +++ b/qobject/json-writer.c @@ -0,0 +1,247 @@ +/* + * JSON Writer + * + * Copyright IBM, Corp. 2009 + * Copyright (c) 2010-2020 Red Hat Inc. + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * Markus Armbruster <armbru@redhat.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include "qapi/qmp/json-writer.h" +#include "qemu/unicode.h" + +struct JSONWriter { + bool pretty; + bool need_comma; + GString *contents; + GByteArray *container_is_array; +}; + +JSONWriter *json_writer_new(bool pretty) +{ + JSONWriter *writer = g_new(JSONWriter, 1); + + writer->pretty = pretty; + writer->need_comma = false; + writer->contents = g_string_new(NULL); + writer->container_is_array = g_byte_array_new(); + return writer; +} + +const char *json_writer_get(JSONWriter *writer) +{ + g_assert(!writer->container_is_array->len); + return writer->contents->str; +} + +GString *json_writer_get_and_free(JSONWriter *writer) +{ + GString *contents = writer->contents; + + writer->contents = NULL; + g_byte_array_free(writer->container_is_array, true); + g_free(writer); + return contents; +} + +void json_writer_free(JSONWriter *writer) +{ + if (writer) { + g_string_free(json_writer_get_and_free(writer), true); + } +} + +static void enter_container(JSONWriter *writer, bool is_array) +{ + unsigned depth = writer->container_is_array->len; + + g_byte_array_set_size(writer->container_is_array, depth + 1); + writer->container_is_array->data[depth] = is_array; + writer->need_comma = false; +} + +static void leave_container(JSONWriter *writer, bool is_array) +{ + unsigned depth = writer->container_is_array->len; + + assert(depth); + assert(writer->container_is_array->data[depth - 1] == is_array); + g_byte_array_set_size(writer->container_is_array, depth - 1); + writer->need_comma = true; +} + +static bool in_object(JSONWriter *writer) +{ + unsigned depth = writer->container_is_array->len; + + return depth && !writer->container_is_array->data[depth - 1]; +} + +static void pretty_newline(JSONWriter *writer) +{ + if (writer->pretty) { + g_string_append_printf(writer->contents, "\n%*s", + writer->container_is_array->len * 4, ""); + } +} + +static void pretty_newline_or_space(JSONWriter *writer) +{ + if (writer->pretty) { + g_string_append_printf(writer->contents, "\n%*s", + writer->container_is_array->len * 4, ""); + } else { + g_string_append_c(writer->contents, ' '); + } +} + +static void quoted_str(JSONWriter *writer, const char *str) +{ + const char *ptr; + char *end; + int cp; + + g_string_append_c(writer->contents, '"'); + + for (ptr = str; *ptr; ptr = end) { + cp = mod_utf8_codepoint(ptr, 6, &end); + switch (cp) { + case '\"': + g_string_append(writer->contents, "\\\""); + break; + case '\\': + g_string_append(writer->contents, "\\\\"); + break; + case '\b': + g_string_append(writer->contents, "\\b"); + break; + case '\f': + g_string_append(writer->contents, "\\f"); + break; + case '\n': + g_string_append(writer->contents, "\\n"); + break; + case '\r': + g_string_append(writer->contents, "\\r"); + break; + case '\t': + g_string_append(writer->contents, "\\t"); + break; + default: + if (cp < 0) { + cp = 0xFFFD; /* replacement character */ + } + if (cp > 0xFFFF) { + /* beyond BMP; need a surrogate pair */ + g_string_append_printf(writer->contents, "\\u%04X\\u%04X", + 0xD800 + ((cp - 0x10000) >> 10), + 0xDC00 + ((cp - 0x10000) & 0x3FF)); + } else if (cp < 0x20 || cp >= 0x7F) { + g_string_append_printf(writer->contents, "\\u%04X", cp); + } else { + g_string_append_c(writer->contents, cp); + } + } + }; + + g_string_append_c(writer->contents, '"'); +} + +static void maybe_comma_name(JSONWriter *writer, const char *name) +{ + if (writer->need_comma) { + g_string_append_c(writer->contents, ','); + pretty_newline_or_space(writer); + } else { + if (writer->contents->len) { + pretty_newline(writer); + } + writer->need_comma = true; + } + + if (in_object(writer)) { + quoted_str(writer, name); + g_string_append(writer->contents, ": "); + } +} + +void json_writer_start_object(JSONWriter *writer, const char *name) +{ + maybe_comma_name(writer, name); + g_string_append_c(writer->contents, '{'); + enter_container(writer, false); +} + +void json_writer_end_object(JSONWriter *writer) +{ + leave_container(writer, false); + pretty_newline(writer); + g_string_append_c(writer->contents, '}'); +} + +void json_writer_start_array(JSONWriter *writer, const char *name) +{ + maybe_comma_name(writer, name); + g_string_append_c(writer->contents, '['); + enter_container(writer, true); +} + +void json_writer_end_array(JSONWriter *writer) +{ + leave_container(writer, true); + pretty_newline(writer); + g_string_append_c(writer->contents, ']'); +} + +void json_writer_bool(JSONWriter *writer, const char *name, bool val) +{ + maybe_comma_name(writer, name); + g_string_append(writer->contents, val ? "true" : "false"); +} + +void json_writer_null(JSONWriter *writer, const char *name) +{ + maybe_comma_name(writer, name); + g_string_append(writer->contents, "null"); +} + +void json_writer_int64(JSONWriter *writer, const char *name, int64_t val) +{ + maybe_comma_name(writer, name); + g_string_append_printf(writer->contents, "%" PRId64, val); +} + +void json_writer_uint64(JSONWriter *writer, const char *name, uint64_t val) +{ + maybe_comma_name(writer, name); + g_string_append_printf(writer->contents, "%" PRIu64, val); +} + +void json_writer_double(JSONWriter *writer, const char *name, double val) +{ + maybe_comma_name(writer, name); + + /* + * FIXME: g_string_append_printf() is locale dependent; but JSON + * requires numbers to be formatted as if in the C locale. + * Dependence on C locale is a pervasive issue in QEMU. + */ + /* + * FIXME: This risks printing Inf or NaN, which are not valid + * JSON values. + */ + g_string_append_printf(writer->contents, "%.17g", val); +} + +void json_writer_str(JSONWriter *writer, const char *name, const char *str) +{ + maybe_comma_name(writer, name); + quoted_str(writer, str); +} diff --git a/qobject/meson.build b/qobject/meson.build new file mode 100644 index 000000000..4683a852a --- /dev/null +++ b/qobject/meson.build @@ -0,0 +1,4 @@ +util_ss.add(files('qnull.c', 'qnum.c', 'qstring.c', 'qdict.c', + 'qlist.c', 'qbool.c', 'qlit.c', 'qjson.c', 'qobject.c', + 'json-writer.c', 'json-lexer.c', 'json-streamer.c', 'json-parser.c', + 'block-qdict.c')) diff --git a/qobject/qbool.c b/qobject/qbool.c new file mode 100644 index 000000000..16a600abb --- /dev/null +++ b/qobject/qbool.c @@ -0,0 +1,58 @@ +/* + * QBool Module + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include "qapi/qmp/qbool.h" +#include "qobject-internal.h" + +/** + * qbool_from_bool(): Create a new QBool from a bool + * + * Return strong reference. + */ +QBool *qbool_from_bool(bool value) +{ + QBool *qb; + + qb = g_malloc(sizeof(*qb)); + qobject_init(QOBJECT(qb), QTYPE_QBOOL); + qb->value = value; + + return qb; +} + +/** + * qbool_get_bool(): Get the stored bool + */ +bool qbool_get_bool(const QBool *qb) +{ + return qb->value; +} + +/** + * qbool_is_equal(): Test whether the two QBools are equal + */ +bool qbool_is_equal(const QObject *x, const QObject *y) +{ + return qobject_to(QBool, x)->value == qobject_to(QBool, y)->value; +} + +/** + * qbool_destroy_obj(): Free all memory allocated by a + * QBool object + */ +void qbool_destroy_obj(QObject *obj) +{ + assert(obj != NULL); + g_free(qobject_to(QBool, obj)); +} diff --git a/qobject/qdict.c b/qobject/qdict.c new file mode 100644 index 000000000..0216ca7ac --- /dev/null +++ b/qobject/qdict.c @@ -0,0 +1,444 @@ +/* + * QDict Module + * + * Copyright (C) 2009 Red Hat Inc. + * + * Authors: + * Luiz Capitulino <lcapitulino@redhat.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/qmp/qnum.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qbool.h" +#include "qapi/qmp/qnull.h" +#include "qapi/qmp/qstring.h" +#include "qobject-internal.h" + +/** + * qdict_new(): Create a new QDict + * + * Return strong reference. + */ +QDict *qdict_new(void) +{ + QDict *qdict; + + qdict = g_malloc0(sizeof(*qdict)); + qobject_init(QOBJECT(qdict), QTYPE_QDICT); + + return qdict; +} + +/** + * tdb_hash(): based on the hash algorithm from gdbm, via tdb + * (from module-init-tools) + */ +static unsigned int tdb_hash(const char *name) +{ + unsigned value; /* Used to compute the hash value. */ + unsigned i; /* Used to cycle through random values. */ + + /* Set the initial value from the key size. */ + for (value = 0x238F13AF * strlen(name), i = 0; name[i]; i++) { + value = (value + (((const unsigned char *)name)[i] << (i * 5 % 24))); + } + + return (1103515243 * value + 12345); +} + +/** + * alloc_entry(): allocate a new QDictEntry + */ +static QDictEntry *alloc_entry(const char *key, QObject *value) +{ + QDictEntry *entry; + + entry = g_malloc0(sizeof(*entry)); + entry->key = g_strdup(key); + entry->value = value; + + return entry; +} + +/** + * qdict_entry_value(): Return qdict entry value + * + * Return weak reference. + */ +QObject *qdict_entry_value(const QDictEntry *entry) +{ + return entry->value; +} + +/** + * qdict_entry_key(): Return qdict entry key + * + * Return a *pointer* to the string, it has to be duplicated before being + * stored. + */ +const char *qdict_entry_key(const QDictEntry *entry) +{ + return entry->key; +} + +/** + * qdict_find(): List lookup function + */ +static QDictEntry *qdict_find(const QDict *qdict, + const char *key, unsigned int bucket) +{ + QDictEntry *entry; + + QLIST_FOREACH(entry, &qdict->table[bucket], next) + if (!strcmp(entry->key, key)) { + return entry; + } + + return NULL; +} + +/** + * qdict_put_obj(): Put a new QObject into the dictionary + * + * Insert the pair 'key:value' into 'qdict', if 'key' already exists + * its 'value' will be replaced. + * + * This is done by freeing the reference to the stored QObject and + * storing the new one in the same entry. + * + * NOTE: ownership of 'value' is transferred to the QDict + */ +void qdict_put_obj(QDict *qdict, const char *key, QObject *value) +{ + unsigned int bucket; + QDictEntry *entry; + + bucket = tdb_hash(key) % QDICT_BUCKET_MAX; + entry = qdict_find(qdict, key, bucket); + if (entry) { + /* replace key's value */ + qobject_unref(entry->value); + entry->value = value; + } else { + /* allocate a new entry */ + entry = alloc_entry(key, value); + QLIST_INSERT_HEAD(&qdict->table[bucket], entry, next); + qdict->size++; + } +} + +void qdict_put_int(QDict *qdict, const char *key, int64_t value) +{ + qdict_put(qdict, key, qnum_from_int(value)); +} + +void qdict_put_bool(QDict *qdict, const char *key, bool value) +{ + qdict_put(qdict, key, qbool_from_bool(value)); +} + +void qdict_put_str(QDict *qdict, const char *key, const char *value) +{ + qdict_put(qdict, key, qstring_from_str(value)); +} + +void qdict_put_null(QDict *qdict, const char *key) +{ + qdict_put(qdict, key, qnull()); +} + +/** + * qdict_get(): Lookup for a given 'key' + * + * Return a weak reference to the QObject associated with 'key' if + * 'key' is present in the dictionary, NULL otherwise. + */ +QObject *qdict_get(const QDict *qdict, const char *key) +{ + QDictEntry *entry; + + entry = qdict_find(qdict, key, tdb_hash(key) % QDICT_BUCKET_MAX); + return (entry == NULL ? NULL : entry->value); +} + +/** + * qdict_haskey(): Check if 'key' exists + * + * Return 1 if 'key' exists in the dict, 0 otherwise + */ +int qdict_haskey(const QDict *qdict, const char *key) +{ + unsigned int bucket = tdb_hash(key) % QDICT_BUCKET_MAX; + return (qdict_find(qdict, key, bucket) == NULL ? 0 : 1); +} + +/** + * qdict_size(): Return the size of the dictionary + */ +size_t qdict_size(const QDict *qdict) +{ + return qdict->size; +} + +/** + * qdict_get_double(): Get an number mapped by 'key' + * + * This function assumes that 'key' exists and it stores a QNum. + * + * Return number mapped by 'key'. + */ +double qdict_get_double(const QDict *qdict, const char *key) +{ + return qnum_get_double(qobject_to(QNum, qdict_get(qdict, key))); +} + +/** + * qdict_get_int(): Get an integer mapped by 'key' + * + * This function assumes that 'key' exists and it stores a + * QNum representable as int. + * + * Return integer mapped by 'key'. + */ +int64_t qdict_get_int(const QDict *qdict, const char *key) +{ + return qnum_get_int(qobject_to(QNum, qdict_get(qdict, key))); +} + +/** + * qdict_get_bool(): Get a bool mapped by 'key' + * + * This function assumes that 'key' exists and it stores a + * QBool object. + * + * Return bool mapped by 'key'. + */ +bool qdict_get_bool(const QDict *qdict, const char *key) +{ + return qbool_get_bool(qobject_to(QBool, qdict_get(qdict, key))); +} + +/** + * qdict_get_qlist(): If @qdict maps @key to a QList, return it, else NULL. + */ +QList *qdict_get_qlist(const QDict *qdict, const char *key) +{ + return qobject_to(QList, qdict_get(qdict, key)); +} + +/** + * qdict_get_qdict(): If @qdict maps @key to a QDict, return it, else NULL. + */ +QDict *qdict_get_qdict(const QDict *qdict, const char *key) +{ + return qobject_to(QDict, qdict_get(qdict, key)); +} + +/** + * qdict_get_str(): Get a pointer to the stored string mapped + * by 'key' + * + * This function assumes that 'key' exists and it stores a + * QString object. + * + * Return pointer to the string mapped by 'key'. + */ +const char *qdict_get_str(const QDict *qdict, const char *key) +{ + return qstring_get_str(qobject_to(QString, qdict_get(qdict, key))); +} + +/** + * qdict_get_try_int(): Try to get integer mapped by 'key' + * + * Return integer mapped by 'key', if it is not present in the + * dictionary or if the stored object is not a QNum representing an + * integer, 'def_value' will be returned. + */ +int64_t qdict_get_try_int(const QDict *qdict, const char *key, + int64_t def_value) +{ + QNum *qnum = qobject_to(QNum, qdict_get(qdict, key)); + int64_t val; + + if (!qnum || !qnum_get_try_int(qnum, &val)) { + return def_value; + } + + return val; +} + +/** + * qdict_get_try_bool(): Try to get a bool mapped by 'key' + * + * Return bool mapped by 'key', if it is not present in the + * dictionary or if the stored object is not of QBool type + * 'def_value' will be returned. + */ +bool qdict_get_try_bool(const QDict *qdict, const char *key, bool def_value) +{ + QBool *qbool = qobject_to(QBool, qdict_get(qdict, key)); + + return qbool ? qbool_get_bool(qbool) : def_value; +} + +/** + * qdict_get_try_str(): Try to get a pointer to the stored string + * mapped by 'key' + * + * Return a pointer to the string mapped by 'key', if it is not present + * in the dictionary or if the stored object is not of QString type + * NULL will be returned. + */ +const char *qdict_get_try_str(const QDict *qdict, const char *key) +{ + QString *qstr = qobject_to(QString, qdict_get(qdict, key)); + + return qstr ? qstring_get_str(qstr) : NULL; +} + +static QDictEntry *qdict_next_entry(const QDict *qdict, int first_bucket) +{ + int i; + + for (i = first_bucket; i < QDICT_BUCKET_MAX; i++) { + if (!QLIST_EMPTY(&qdict->table[i])) { + return QLIST_FIRST(&qdict->table[i]); + } + } + + return NULL; +} + +/** + * qdict_first(): Return first qdict entry for iteration. + */ +const QDictEntry *qdict_first(const QDict *qdict) +{ + return qdict_next_entry(qdict, 0); +} + +/** + * qdict_next(): Return next qdict entry in an iteration. + */ +const QDictEntry *qdict_next(const QDict *qdict, const QDictEntry *entry) +{ + QDictEntry *ret; + + ret = QLIST_NEXT(entry, next); + if (!ret) { + unsigned int bucket = tdb_hash(entry->key) % QDICT_BUCKET_MAX; + ret = qdict_next_entry(qdict, bucket + 1); + } + + return ret; +} + +/** + * qdict_clone_shallow(): Clones a given QDict. Its entries are not copied, but + * another reference is added. + */ +QDict *qdict_clone_shallow(const QDict *src) +{ + QDict *dest; + QDictEntry *entry; + int i; + + dest = qdict_new(); + + for (i = 0; i < QDICT_BUCKET_MAX; i++) { + QLIST_FOREACH(entry, &src->table[i], next) { + qdict_put_obj(dest, entry->key, qobject_ref(entry->value)); + } + } + + return dest; +} + +/** + * qentry_destroy(): Free all the memory allocated by a QDictEntry + */ +static void qentry_destroy(QDictEntry *e) +{ + assert(e != NULL); + assert(e->key != NULL); + assert(e->value != NULL); + + qobject_unref(e->value); + g_free(e->key); + g_free(e); +} + +/** + * qdict_del(): Delete a 'key:value' pair from the dictionary + * + * This will destroy all data allocated by this entry. + */ +void qdict_del(QDict *qdict, const char *key) +{ + QDictEntry *entry; + + entry = qdict_find(qdict, key, tdb_hash(key) % QDICT_BUCKET_MAX); + if (entry) { + QLIST_REMOVE(entry, next); + qentry_destroy(entry); + qdict->size--; + } +} + +/** + * qdict_is_equal(): Test whether the two QDicts are equal + * + * Here, equality means whether they contain the same keys and whether + * the respective values are in turn equal (i.e. invoking + * qobject_is_equal() on them yields true). + */ +bool qdict_is_equal(const QObject *x, const QObject *y) +{ + const QDict *dict_x = qobject_to(QDict, x); + const QDict *dict_y = qobject_to(QDict, y); + const QDictEntry *e; + + if (qdict_size(dict_x) != qdict_size(dict_y)) { + return false; + } + + for (e = qdict_first(dict_x); e; e = qdict_next(dict_x, e)) { + const QObject *obj_x = qdict_entry_value(e); + const QObject *obj_y = qdict_get(dict_y, qdict_entry_key(e)); + + if (!qobject_is_equal(obj_x, obj_y)) { + return false; + } + } + + return true; +} + +/** + * qdict_destroy_obj(): Free all the memory allocated by a QDict + */ +void qdict_destroy_obj(QObject *obj) +{ + int i; + QDict *qdict; + + assert(obj != NULL); + qdict = qobject_to(QDict, obj); + + for (i = 0; i < QDICT_BUCKET_MAX; i++) { + QDictEntry *entry = QLIST_FIRST(&qdict->table[i]); + while (entry) { + QDictEntry *tmp = QLIST_NEXT(entry, next); + QLIST_REMOVE(entry, next); + qentry_destroy(entry); + entry = tmp; + } + } + + g_free(qdict); +} diff --git a/qobject/qjson.c b/qobject/qjson.c new file mode 100644 index 000000000..167fcb429 --- /dev/null +++ b/qobject/qjson.c @@ -0,0 +1,232 @@ +/* + * QObject JSON integration + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qapi/qmp/json-parser.h" +#include "qapi/qmp/json-writer.h" +#include "qapi/qmp/qjson.h" +#include "qapi/qmp/qbool.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qlist.h" +#include "qapi/qmp/qnum.h" +#include "qapi/qmp/qstring.h" + +typedef struct JSONParsingState { + JSONMessageParser parser; + QObject *result; + Error *err; +} JSONParsingState; + +static void consume_json(void *opaque, QObject *json, Error *err) +{ + JSONParsingState *s = opaque; + + assert(!json != !err); + assert(!s->result || !s->err); + + if (s->result) { + qobject_unref(s->result); + s->result = NULL; + error_setg(&s->err, "Expecting at most one JSON value"); + } + if (s->err) { + qobject_unref(json); + error_free(err); + return; + } + s->result = json; + s->err = err; +} + +/* + * Parse @string as JSON value. + * If @ap is non-null, interpolate %-escapes. + * Takes ownership of %p arguments. + * On success, return the JSON value. + * On failure, store an error through @errp and return NULL. + * Ownership of %p arguments becomes indeterminate then. To avoid + * leaks, callers passing %p must terminate on error, e.g. by passing + * &error_abort. + */ +static QObject *qobject_from_jsonv(const char *string, va_list *ap, + Error **errp) +{ + JSONParsingState state = {}; + + json_message_parser_init(&state.parser, consume_json, &state, ap); + json_message_parser_feed(&state.parser, string, strlen(string)); + json_message_parser_flush(&state.parser); + json_message_parser_destroy(&state.parser); + + if (!state.result && !state.err) { + error_setg(&state.err, "Expecting a JSON value"); + } + + error_propagate(errp, state.err); + return state.result; +} + +QObject *qobject_from_json(const char *string, Error **errp) +{ + return qobject_from_jsonv(string, NULL, errp); +} + +/* + * Parse @string as JSON value with %-escapes interpolated. + * Abort on error. Do not use with untrusted @string. + * Return the resulting QObject. It is never null. + */ +QObject *qobject_from_vjsonf_nofail(const char *string, va_list ap) +{ + va_list ap_copy; + QObject *obj; + + /* va_copy() is needed when va_list is an array type */ + va_copy(ap_copy, ap); + obj = qobject_from_jsonv(string, &ap_copy, &error_abort); + va_end(ap_copy); + + assert(obj); + return obj; +} + +/* + * Parse @string as JSON value with %-escapes interpolated. + * Abort on error. Do not use with untrusted @string. + * Return the resulting QObject. It is never null. + */ +QObject *qobject_from_jsonf_nofail(const char *string, ...) +{ + QObject *obj; + va_list ap; + + va_start(ap, string); + obj = qobject_from_vjsonf_nofail(string, ap); + va_end(ap); + + return obj; +} + +/* + * Parse @string as JSON object with %-escapes interpolated. + * Abort on error. Do not use with untrusted @string. + * Return the resulting QDict. It is never null. + */ +QDict *qdict_from_vjsonf_nofail(const char *string, va_list ap) +{ + QDict *qdict; + + qdict = qobject_to(QDict, qobject_from_vjsonf_nofail(string, ap)); + assert(qdict); + return qdict; +} + +/* + * Parse @string as JSON object with %-escapes interpolated. + * Abort on error. Do not use with untrusted @string. + * Return the resulting QDict. It is never null. + */ +QDict *qdict_from_jsonf_nofail(const char *string, ...) +{ + QDict *qdict; + va_list ap; + + va_start(ap, string); + qdict = qdict_from_vjsonf_nofail(string, ap); + va_end(ap); + return qdict; +} + +static void to_json(JSONWriter *writer, const char *name, + const QObject *obj) +{ + switch (qobject_type(obj)) { + case QTYPE_QNULL: + json_writer_null(writer, name); + break; + case QTYPE_QNUM: { + QNum *val = qobject_to(QNum, obj); + + switch (val->kind) { + case QNUM_I64: + json_writer_int64(writer, name, val->u.i64); + break; + case QNUM_U64: + json_writer_uint64(writer, name, val->u.u64); + break; + case QNUM_DOUBLE: + json_writer_double(writer, name, val->u.dbl); + break; + default: + abort(); + } + break; + } + case QTYPE_QSTRING: { + QString *val = qobject_to(QString, obj); + + json_writer_str(writer, name, qstring_get_str(val)); + break; + } + case QTYPE_QDICT: { + QDict *val = qobject_to(QDict, obj); + const QDictEntry *entry; + + json_writer_start_object(writer, name); + + for (entry = qdict_first(val); + entry; + entry = qdict_next(val, entry)) { + to_json(writer, qdict_entry_key(entry), qdict_entry_value(entry)); + } + + json_writer_end_object(writer); + break; + } + case QTYPE_QLIST: { + QList *val = qobject_to(QList, obj); + QListEntry *entry; + + json_writer_start_array(writer, name); + + QLIST_FOREACH_ENTRY(val, entry) { + to_json(writer, NULL, qlist_entry_obj(entry)); + } + + json_writer_end_array(writer); + break; + } + case QTYPE_QBOOL: { + QBool *val = qobject_to(QBool, obj); + + json_writer_bool(writer, name, qbool_get_bool(val)); + break; + } + default: + abort(); + } +} + +GString *qobject_to_json_pretty(const QObject *obj, bool pretty) +{ + JSONWriter *writer = json_writer_new(pretty); + + to_json(writer, NULL, obj); + return json_writer_get_and_free(writer); +} + +GString *qobject_to_json(const QObject *obj) +{ + return qobject_to_json_pretty(obj, false); +} diff --git a/qobject/qlist.c b/qobject/qlist.c new file mode 100644 index 000000000..60562a1f5 --- /dev/null +++ b/qobject/qlist.c @@ -0,0 +1,184 @@ +/* + * QList Module + * + * Copyright (C) 2009 Red Hat Inc. + * + * Authors: + * Luiz Capitulino <lcapitulino@redhat.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/qmp/qbool.h" +#include "qapi/qmp/qlist.h" +#include "qapi/qmp/qnull.h" +#include "qapi/qmp/qnum.h" +#include "qapi/qmp/qstring.h" +#include "qemu/queue.h" +#include "qobject-internal.h" + +/** + * qlist_new(): Create a new QList + * + * Return strong reference. + */ +QList *qlist_new(void) +{ + QList *qlist; + + qlist = g_malloc(sizeof(*qlist)); + qobject_init(QOBJECT(qlist), QTYPE_QLIST); + QTAILQ_INIT(&qlist->head); + + return qlist; +} + +QList *qlist_copy(QList *src) +{ + QList *dst = qlist_new(); + QListEntry *entry; + QObject *elt; + + QLIST_FOREACH_ENTRY(src, entry) { + elt = qlist_entry_obj(entry); + qobject_ref(elt); + qlist_append_obj(dst, elt); + } + return dst; +} + +/** + * qlist_append_obj(): Append an QObject into QList + * + * NOTE: ownership of 'value' is transferred to the QList + */ +void qlist_append_obj(QList *qlist, QObject *value) +{ + QListEntry *entry; + + entry = g_malloc(sizeof(*entry)); + entry->value = value; + + QTAILQ_INSERT_TAIL(&qlist->head, entry, next); +} + +void qlist_append_int(QList *qlist, int64_t value) +{ + qlist_append(qlist, qnum_from_int(value)); +} + +void qlist_append_bool(QList *qlist, bool value) +{ + qlist_append(qlist, qbool_from_bool(value)); +} + +void qlist_append_str(QList *qlist, const char *value) +{ + qlist_append(qlist, qstring_from_str(value)); +} + +void qlist_append_null(QList *qlist) +{ + qlist_append(qlist, qnull()); +} + +QObject *qlist_pop(QList *qlist) +{ + QListEntry *entry; + QObject *ret; + + if (qlist == NULL || QTAILQ_EMPTY(&qlist->head)) { + return NULL; + } + + entry = QTAILQ_FIRST(&qlist->head); + QTAILQ_REMOVE(&qlist->head, entry, next); + + ret = entry->value; + g_free(entry); + + return ret; +} + +QObject *qlist_peek(QList *qlist) +{ + QListEntry *entry; + + if (qlist == NULL || QTAILQ_EMPTY(&qlist->head)) { + return NULL; + } + + entry = QTAILQ_FIRST(&qlist->head); + + return entry->value; +} + +int qlist_empty(const QList *qlist) +{ + return QTAILQ_EMPTY(&qlist->head); +} + +size_t qlist_size(const QList *qlist) +{ + size_t count = 0; + QListEntry *entry; + + QLIST_FOREACH_ENTRY(qlist, entry) { + count++; + } + return count; +} + +/** + * qlist_is_equal(): Test whether the two QLists are equal + * + * In order to be considered equal, the respective two objects at each + * index of the two lists have to compare equal (regarding + * qobject_is_equal()), and both lists have to have the same number of + * elements. + * That means both lists have to contain equal objects in equal order. + */ +bool qlist_is_equal(const QObject *x, const QObject *y) +{ + const QList *list_x = qobject_to(QList, x); + const QList *list_y = qobject_to(QList, y); + const QListEntry *entry_x, *entry_y; + + entry_x = qlist_first(list_x); + entry_y = qlist_first(list_y); + + while (entry_x && entry_y) { + if (!qobject_is_equal(qlist_entry_obj(entry_x), + qlist_entry_obj(entry_y))) + { + return false; + } + + entry_x = qlist_next(entry_x); + entry_y = qlist_next(entry_y); + } + + return !entry_x && !entry_y; +} + +/** + * qlist_destroy_obj(): Free all the memory allocated by a QList + */ +void qlist_destroy_obj(QObject *obj) +{ + QList *qlist; + QListEntry *entry, *next_entry; + + assert(obj != NULL); + qlist = qobject_to(QList, obj); + + QTAILQ_FOREACH_SAFE(entry, &qlist->head, next, next_entry) { + QTAILQ_REMOVE(&qlist->head, entry, next); + qobject_unref(entry->value); + g_free(entry); + } + + g_free(qlist); +} diff --git a/qobject/qlit.c b/qobject/qlit.c new file mode 100644 index 000000000..be8332136 --- /dev/null +++ b/qobject/qlit.c @@ -0,0 +1,125 @@ +/* + * QLit literal qobject + * + * Copyright IBM, Corp. 2009 + * Copyright (c) 2013, 2015, 2017 Red Hat Inc. + * + * Authors: + * Anthony Liguori <aliguori@us.ibm.com> + * Markus Armbruster <armbru@redhat.com> + * Marc-André Lureau <marcandre.lureau@redhat.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ + +#include "qemu/osdep.h" + +#include "qapi/qmp/qlit.h" +#include "qapi/qmp/qbool.h" +#include "qapi/qmp/qlist.h" +#include "qapi/qmp/qnum.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qstring.h" +#include "qapi/qmp/qnull.h" + +static bool qlit_equal_qdict(const QLitObject *lhs, const QDict *qdict) +{ + int i; + + for (i = 0; lhs->value.qdict[i].key; i++) { + QObject *obj = qdict_get(qdict, lhs->value.qdict[i].key); + + if (!qlit_equal_qobject(&lhs->value.qdict[i].value, obj)) { + return false; + } + } + + /* Note: the literal qdict must not contain duplicates, this is + * considered a programming error and it isn't checked here. */ + if (qdict_size(qdict) != i) { + return false; + } + + return true; +} + +static bool qlit_equal_qlist(const QLitObject *lhs, const QList *qlist) +{ + QListEntry *e; + int i = 0; + + QLIST_FOREACH_ENTRY(qlist, e) { + QObject *obj = qlist_entry_obj(e); + + if (!qlit_equal_qobject(&lhs->value.qlist[i], obj)) { + return false; + } + i++; + } + + return !e && lhs->value.qlist[i].type == QTYPE_NONE; +} + +bool qlit_equal_qobject(const QLitObject *lhs, const QObject *rhs) +{ + if (!rhs || lhs->type != qobject_type(rhs)) { + return false; + } + + switch (lhs->type) { + case QTYPE_QBOOL: + return lhs->value.qbool == qbool_get_bool(qobject_to(QBool, rhs)); + case QTYPE_QNUM: + return lhs->value.qnum == qnum_get_int(qobject_to(QNum, rhs)); + case QTYPE_QSTRING: + return (strcmp(lhs->value.qstr, + qstring_get_str(qobject_to(QString, rhs))) == 0); + case QTYPE_QDICT: + return qlit_equal_qdict(lhs, qobject_to(QDict, rhs)); + case QTYPE_QLIST: + return qlit_equal_qlist(lhs, qobject_to(QList, rhs)); + case QTYPE_QNULL: + return true; + default: + break; + } + + return false; +} + +QObject *qobject_from_qlit(const QLitObject *qlit) +{ + switch (qlit->type) { + case QTYPE_QNULL: + return QOBJECT(qnull()); + case QTYPE_QNUM: + return QOBJECT(qnum_from_int(qlit->value.qnum)); + case QTYPE_QSTRING: + return QOBJECT(qstring_from_str(qlit->value.qstr)); + case QTYPE_QDICT: { + QDict *qdict = qdict_new(); + QLitDictEntry *e; + + for (e = qlit->value.qdict; e->key; e++) { + qdict_put_obj(qdict, e->key, qobject_from_qlit(&e->value)); + } + return QOBJECT(qdict); + } + case QTYPE_QLIST: { + QList *qlist = qlist_new(); + QLitObject *e; + + for (e = qlit->value.qlist; e->type != QTYPE_NONE; e++) { + qlist_append_obj(qlist, qobject_from_qlit(e)); + } + return QOBJECT(qlist); + } + case QTYPE_QBOOL: + return QOBJECT(qbool_from_bool(qlit->value.qbool)); + default: + assert(0); + } + + return NULL; +} diff --git a/qobject/qnull.c b/qobject/qnull.c new file mode 100644 index 000000000..b26b36821 --- /dev/null +++ b/qobject/qnull.c @@ -0,0 +1,31 @@ +/* + * QNull + * + * Copyright (C) 2015 Red Hat, Inc. + * + * Authors: + * Markus Armbruster <armbru@redhat.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 + * or later. See the COPYING.LIB file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/qmp/qnull.h" +#include "qobject-internal.h" + +QNull qnull_ = { + .base = { + .type = QTYPE_QNULL, + .refcnt = 1, + }, +}; + +/** + * qnull_is_equal(): Always return true because any two QNull objects + * are equal. + */ +bool qnull_is_equal(const QObject *x, const QObject *y) +{ + return true; +} diff --git a/qobject/qnum.c b/qobject/qnum.c new file mode 100644 index 000000000..5dd66938d --- /dev/null +++ b/qobject/qnum.c @@ -0,0 +1,241 @@ +/* + * QNum Module + * + * Copyright (C) 2009 Red Hat Inc. + * + * Authors: + * Luiz Capitulino <lcapitulino@redhat.com> + * Anthony Liguori <aliguori@us.ibm.com> + * Marc-André Lureau <marcandre.lureau@redhat.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/qmp/qnum.h" +#include "qobject-internal.h" + +/** + * qnum_from_int(): Create a new QNum from an int64_t + * + * Return strong reference. + */ +QNum *qnum_from_int(int64_t value) +{ + QNum *qn = g_new(QNum, 1); + + qobject_init(QOBJECT(qn), QTYPE_QNUM); + qn->kind = QNUM_I64; + qn->u.i64 = value; + + return qn; +} + +/** + * qnum_from_uint(): Create a new QNum from an uint64_t + * + * Return strong reference. + */ +QNum *qnum_from_uint(uint64_t value) +{ + QNum *qn = g_new(QNum, 1); + + qobject_init(QOBJECT(qn), QTYPE_QNUM); + qn->kind = QNUM_U64; + qn->u.u64 = value; + + return qn; +} + +/** + * qnum_from_double(): Create a new QNum from a double + * + * Return strong reference. + */ +QNum *qnum_from_double(double value) +{ + QNum *qn = g_new(QNum, 1); + + qobject_init(QOBJECT(qn), QTYPE_QNUM); + qn->kind = QNUM_DOUBLE; + qn->u.dbl = value; + + return qn; +} + +/** + * qnum_get_try_int(): Get an integer representation of the number + * + * Return true on success. + */ +bool qnum_get_try_int(const QNum *qn, int64_t *val) +{ + switch (qn->kind) { + case QNUM_I64: + *val = qn->u.i64; + return true; + case QNUM_U64: + if (qn->u.u64 > INT64_MAX) { + return false; + } + *val = qn->u.u64; + return true; + case QNUM_DOUBLE: + return false; + } + + assert(0); + return false; +} + +/** + * qnum_get_int(): Get an integer representation of the number + * + * assert() on failure. + */ +int64_t qnum_get_int(const QNum *qn) +{ + int64_t val; + bool success = qnum_get_try_int(qn, &val); + assert(success); + return val; +} + +/** + * qnum_get_uint(): Get an unsigned integer from the number + * + * Return true on success. + */ +bool qnum_get_try_uint(const QNum *qn, uint64_t *val) +{ + switch (qn->kind) { + case QNUM_I64: + if (qn->u.i64 < 0) { + return false; + } + *val = qn->u.i64; + return true; + case QNUM_U64: + *val = qn->u.u64; + return true; + case QNUM_DOUBLE: + return false; + } + + assert(0); + return false; +} + +/** + * qnum_get_uint(): Get an unsigned integer from the number + * + * assert() on failure. + */ +uint64_t qnum_get_uint(const QNum *qn) +{ + uint64_t val; + bool success = qnum_get_try_uint(qn, &val); + assert(success); + return val; +} + +/** + * qnum_get_double(): Get a float representation of the number + * + * qnum_get_double() loses precision for integers beyond 53 bits. + */ +double qnum_get_double(QNum *qn) +{ + switch (qn->kind) { + case QNUM_I64: + return qn->u.i64; + case QNUM_U64: + return qn->u.u64; + case QNUM_DOUBLE: + return qn->u.dbl; + } + + assert(0); + return 0.0; +} + +char *qnum_to_string(QNum *qn) +{ + switch (qn->kind) { + case QNUM_I64: + return g_strdup_printf("%" PRId64, qn->u.i64); + case QNUM_U64: + return g_strdup_printf("%" PRIu64, qn->u.u64); + case QNUM_DOUBLE: + /* 17 digits suffice for IEEE double */ + return g_strdup_printf("%.17g", qn->u.dbl); + } + + assert(0); + return NULL; +} + +/** + * qnum_is_equal(): Test whether the two QNums are equal + * + * Negative integers are never considered equal to unsigned integers, + * but positive integers in the range [0, INT64_MAX] are considered + * equal independently of whether the QNum's kind is i64 or u64. + * + * Doubles are never considered equal to integers. + */ +bool qnum_is_equal(const QObject *x, const QObject *y) +{ + QNum *num_x = qobject_to(QNum, x); + QNum *num_y = qobject_to(QNum, y); + + switch (num_x->kind) { + case QNUM_I64: + switch (num_y->kind) { + case QNUM_I64: + /* Comparison in native int64_t type */ + return num_x->u.i64 == num_y->u.i64; + case QNUM_U64: + /* Implicit conversion of x to uin64_t, so we have to + * check its sign before */ + return num_x->u.i64 >= 0 && num_x->u.i64 == num_y->u.u64; + case QNUM_DOUBLE: + return false; + } + abort(); + case QNUM_U64: + switch (num_y->kind) { + case QNUM_I64: + return qnum_is_equal(y, x); + case QNUM_U64: + /* Comparison in native uint64_t type */ + return num_x->u.u64 == num_y->u.u64; + case QNUM_DOUBLE: + return false; + } + abort(); + case QNUM_DOUBLE: + switch (num_y->kind) { + case QNUM_I64: + case QNUM_U64: + return false; + case QNUM_DOUBLE: + /* Comparison in native double type */ + return num_x->u.dbl == num_y->u.dbl; + } + abort(); + } + + abort(); +} + +/** + * qnum_destroy_obj(): Free all memory allocated by a + * QNum object + */ +void qnum_destroy_obj(QObject *obj) +{ + assert(obj != NULL); + g_free(qobject_to(QNum, obj)); +} diff --git a/qobject/qobject-internal.h b/qobject/qobject-internal.h new file mode 100644 index 000000000..b310c8e1b --- /dev/null +++ b/qobject/qobject-internal.h @@ -0,0 +1,39 @@ +/* + * QObject internals + * + * Copyright (C) 2015 Red Hat, Inc. + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 + * or later. See the COPYING.LIB file in the top-level directory. + */ + +#ifndef QOBJECT_INTERNAL_H +#define QOBJECT_INTERNAL_H + +#include "qapi/qmp/qobject.h" + +static inline void qobject_init(QObject *obj, QType type) +{ + assert(QTYPE_NONE < type && type < QTYPE__MAX); + obj->base.refcnt = 1; + obj->base.type = type; +} + +void qbool_destroy_obj(QObject *obj); +bool qbool_is_equal(const QObject *x, const QObject *y); + +void qdict_destroy_obj(QObject *obj); +bool qdict_is_equal(const QObject *x, const QObject *y); + +void qlist_destroy_obj(QObject *obj); +bool qlist_is_equal(const QObject *x, const QObject *y); + +bool qnull_is_equal(const QObject *x, const QObject *y); + +void qnum_destroy_obj(QObject *obj); +bool qnum_is_equal(const QObject *x, const QObject *y); + +void qstring_destroy_obj(QObject *obj); +bool qstring_is_equal(const QObject *x, const QObject *y); + +#endif diff --git a/qobject/qobject.c b/qobject/qobject.c new file mode 100644 index 000000000..d7077b8f2 --- /dev/null +++ b/qobject/qobject.c @@ -0,0 +1,72 @@ +/* + * QObject + * + * Copyright (C) 2015 Red Hat, Inc. + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 + * or later. See the COPYING.LIB file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/qmp/qbool.h" +#include "qapi/qmp/qnull.h" +#include "qapi/qmp/qnum.h" +#include "qapi/qmp/qdict.h" +#include "qapi/qmp/qlist.h" +#include "qapi/qmp/qstring.h" +#include "qobject-internal.h" + +QEMU_BUILD_BUG_MSG( + offsetof(QNull, base) != 0 || + offsetof(QNum, base) != 0 || + offsetof(QString, base) != 0 || + offsetof(QDict, base) != 0 || + offsetof(QList, base) != 0 || + offsetof(QBool, base) != 0, + "base qobject must be at offset 0"); + +static void (*qdestroy[QTYPE__MAX])(QObject *) = { + [QTYPE_NONE] = NULL, /* No such object exists */ + [QTYPE_QNULL] = NULL, /* qnull_ is indestructible */ + [QTYPE_QNUM] = qnum_destroy_obj, + [QTYPE_QSTRING] = qstring_destroy_obj, + [QTYPE_QDICT] = qdict_destroy_obj, + [QTYPE_QLIST] = qlist_destroy_obj, + [QTYPE_QBOOL] = qbool_destroy_obj, +}; + +void qobject_destroy(QObject *obj) +{ + assert(!obj->base.refcnt); + assert(QTYPE_QNULL < obj->base.type && obj->base.type < QTYPE__MAX); + qdestroy[obj->base.type](obj); +} + + +static bool (*qis_equal[QTYPE__MAX])(const QObject *, const QObject *) = { + [QTYPE_NONE] = NULL, /* No such object exists */ + [QTYPE_QNULL] = qnull_is_equal, + [QTYPE_QNUM] = qnum_is_equal, + [QTYPE_QSTRING] = qstring_is_equal, + [QTYPE_QDICT] = qdict_is_equal, + [QTYPE_QLIST] = qlist_is_equal, + [QTYPE_QBOOL] = qbool_is_equal, +}; + +bool qobject_is_equal(const QObject *x, const QObject *y) +{ + /* We cannot test x == y because an object does not need to be + * equal to itself (e.g. NaN floats are not). */ + + if (!x && !y) { + return true; + } + + if (!x || !y || x->base.type != y->base.type) { + return false; + } + + assert(QTYPE_NONE < x->base.type && x->base.type < QTYPE__MAX); + + return qis_equal[x->base.type](x, y); +} diff --git a/qobject/qstring.c b/qobject/qstring.c new file mode 100644 index 000000000..b4613899b --- /dev/null +++ b/qobject/qstring.c @@ -0,0 +1,102 @@ +/* + * QString Module + * + * Copyright (C) 2009 Red Hat Inc. + * + * Authors: + * Luiz Capitulino <lcapitulino@redhat.com> + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/qmp/qstring.h" +#include "qobject-internal.h" + +/** + * qstring_new(): Create a new empty QString + * + * Return strong reference. + */ +QString *qstring_new(void) +{ + return qstring_from_str(""); +} + +/** + * qstring_from_substr(): Create a new QString from a C string substring + * + * Return string reference + */ +QString *qstring_from_substr(const char *str, size_t start, size_t end) +{ + QString *qstring; + + assert(start <= end); + qstring = g_malloc(sizeof(*qstring)); + qobject_init(QOBJECT(qstring), QTYPE_QSTRING); + qstring->string = g_strndup(str + start, end - start); + return qstring; +} + +/** + * qstring_from_str(): Create a new QString from a regular C string + * + * Return strong reference. + */ +QString *qstring_from_str(const char *str) +{ + return qstring_from_substr(str, 0, strlen(str)); +} + +/** + * qstring_from_gstring(): Convert a GString to a QString + * + * Return strong reference. + */ + +QString *qstring_from_gstring(GString *gstr) +{ + QString *qstring; + + qstring = g_malloc(sizeof(*qstring)); + qobject_init(QOBJECT(qstring), QTYPE_QSTRING); + qstring->string = g_string_free(gstr, false); + return qstring; +} + + +/** + * qstring_get_str(): Return a pointer to the stored string + * + * NOTE: Should be used with caution, if the object is deallocated + * this pointer becomes invalid. + */ +const char *qstring_get_str(const QString *qstring) +{ + return qstring->string; +} + +/** + * qstring_is_equal(): Test whether the two QStrings are equal + */ +bool qstring_is_equal(const QObject *x, const QObject *y) +{ + return !strcmp(qobject_to(QString, x)->string, + qobject_to(QString, y)->string); +} + +/** + * qstring_destroy_obj(): Free all memory allocated by a QString + * object + */ +void qstring_destroy_obj(QObject *obj) +{ + QString *qs; + + assert(obj != NULL); + qs = qobject_to(QString, obj); + g_free((char *)qs->string); + g_free(qs); +} |