X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=util%2Fkeyval.c;h=93d5db6b590427e412dfb172f1c406d6dd8958c1;hb=24ec2863b147aadd8cbd63f87ad0467210164304;hp=cb484ef6c6accc0a505c9999f3cc4cebb7eda100;hpb=f740048323398ebde9575a5730bf6d9f2a237f08;p=mirror_qemu.git diff --git a/util/keyval.c b/util/keyval.c index cb484ef6c6..93d5db6b59 100644 --- a/util/keyval.c +++ b/util/keyval.c @@ -21,29 +21,55 @@ * * Semantics defined by reduction to JSON: * - * key-vals defines a tree of objects rooted at R - * where for each key-val = key-fragment . ... = val in key-vals - * R op key-fragment op ... = val' - * where (left-associative) op is member reference L.key-fragment - * val' is val with ',,' replaced by ',' - * and only R may be empty. + * key-vals specifies a JSON object, i.e. a tree whose root is an + * object, inner nodes other than the root are objects or arrays, + * and leaves are strings. * - * Duplicate keys are permitted; all but the last one are ignored. + * Each key-val = key-fragment '.' ... '=' val specifies a path from + * root to a leaf (left of '='), and the leaf's value (right of + * '='). * - * The equations must have a solution. Counter-example: a.b=1,a=2 - * doesn't have one, because R.a must be an object to satisfy a.b=1 - * and a string to satisfy a=2. + * A path from the root is defined recursively: + * L '.' key-fragment is a child of the node denoted by path L + * key-fragment is a child of the tree root + * If key-fragment is numeric, the parent is an array and the child + * is its key-fragment-th member, counting from zero. + * Else, the parent is an object, and the child is its member named + * key-fragment. * - * Key-fragments must be valid QAPI names. + * This constrains inner nodes to be either array or object. The + * constraints must be satisfiable. Counter-example: a.b=1,a=2 is + * not, because root.a must be an object to satisfy a.b=1 and a + * string to satisfy a=2. + * + * Array subscripts can occur in any order, but the set of + * subscripts must not have gaps. For instance, a.1=v is not okay, + * because root.a[0] is missing. + * + * If multiple key-val denote the same leaf, the last one determines + * the value. + * + * Key-fragments must be valid QAPI names or consist only of decimal + * digits. * * The length of any key-fragment must be between 1 and 127. * - * Design flaw: there is no way to denote an empty non-root object. - * While interpreting "key absent" as empty object seems natural + * Design flaw: there is no way to denote an empty array or non-root + * object. While interpreting "key absent" as empty seems natural * (removing a key-val from the input string removes the member when * there are more, so why not when it's the last), it doesn't work: - * "key absent" already means "optional object absent", which isn't - * the same as "empty object present". + * "key absent" already means "optional object/array absent", which + * isn't the same as "empty object/array present". + * + * Design flaw: scalar values can only be strings; there is no way to + * denote numbers, true, false or null. The special QObject input + * visitor returned by qobject_input_visitor_new_keyval() mostly hides + * this by automatically converting strings to the type the visitor + * expects. Breaks down for alternate types and type 'any', where the + * visitor's expectation isn't clear. Code visiting such types needs + * to do the conversion itself, but only when using this keyval + * visitor. Awkward. Alternate types without a string member don't + * work at all. * * Additional syntax for use with an implied key: * @@ -51,16 +77,42 @@ * val-no-key = / [^=,]* / * * where no-key is syntactic sugar for implied-key=val-no-key. - * - * TODO support lists */ #include "qemu/osdep.h" #include "qapi/error.h" #include "qapi/qmp/qstring.h" #include "qapi/util.h" +#include "qemu/cutils.h" #include "qemu/option.h" +/* + * Convert @key to a list index. + * Convert all leading decimal digits to a (non-negative) number, + * capped at INT_MAX. + * If @end is non-null, assign a pointer to the first character after + * the number to *@end. + * Else, fail if any characters follow. + * On success, return the converted number. + * On failure, return a negative value. + * Note: since only digits are converted, no two keys can map to the + * same number, except by overflow to INT_MAX. + */ +static int key_to_index(const char *key, const char **end) +{ + int ret; + unsigned long index; + + if (*key < '0' || *key > '9') { + return -EINVAL; + } + ret = qemu_strtoul(key, end, 10, &index); + if (ret) { + return ret == -ERANGE ? INT_MAX : ret; + } + return index <= INT_MAX ? index : INT_MAX; +} + /* * Ensure @cur maps @key_in_cur the right way. * If @value is null, it needs to map to a QDict, else to this @@ -116,7 +168,7 @@ static const char *keyval_parse_one(QDict *qdict, const char *params, const char *implied_key, Error **errp) { - const char *key, *key_end, *s; + const char *key, *key_end, *s, *end; size_t len; char key_in_cur[128]; QDict *cur; @@ -140,8 +192,13 @@ static const char *keyval_parse_one(QDict *qdict, const char *params, cur = qdict; s = key; for (;;) { - ret = parse_qapi_name(s, false); - len = ret < 0 ? 0 : ret; + /* Want a key index (unless it's first) or a QAPI name */ + if (s != key && key_to_index(s, &end) >= 0) { + len = end - s; + } else { + ret = parse_qapi_name(s, false); + len = ret < 0 ? 0 : ret; + } assert(s + len <= key_end); if (!len || (s + len < key_end && s[len] != '.')) { assert(key != implied_key); @@ -208,6 +265,126 @@ static const char *keyval_parse_one(QDict *qdict, const char *params, return s; } +static char *reassemble_key(GSList *key) +{ + GString *s = g_string_new(""); + GSList *p; + + for (p = key; p; p = p->next) { + g_string_prepend_c(s, '.'); + g_string_prepend(s, (char *)p->data); + } + + return g_string_free(s, FALSE); +} + +/* + * Listify @cur recursively. + * Replace QDicts whose keys are all valid list indexes by QLists. + * @key_of_cur is the list of key fragments leading up to @cur. + * On success, return either @cur or its replacement. + * On failure, store an error through @errp and return NULL. + */ +static QObject *keyval_listify(QDict *cur, GSList *key_of_cur, Error **errp) +{ + GSList key_node; + bool has_index, has_member; + const QDictEntry *ent; + QDict *qdict; + QObject *val; + char *key; + size_t nelt; + QObject **elt; + int index, max_index, i; + QList *list; + + key_node.next = key_of_cur; + + /* + * Recursively listify @cur's members, and figure out whether @cur + * itself is to be listified. + */ + has_index = false; + has_member = false; + for (ent = qdict_first(cur); ent; ent = qdict_next(cur, ent)) { + if (key_to_index(ent->key, NULL) >= 0) { + has_index = true; + } else { + has_member = true; + } + + qdict = qobject_to_qdict(ent->value); + if (!qdict) { + continue; + } + + key_node.data = ent->key; + val = keyval_listify(qdict, &key_node, errp); + if (!val) { + return NULL; + } + if (val != ent->value) { + qdict_put_obj(cur, ent->key, val); + } + } + + if (has_index && has_member) { + key = reassemble_key(key_of_cur); + error_setg(errp, "Parameters '%s*' used inconsistently", key); + g_free(key); + return NULL; + } + if (!has_index) { + return QOBJECT(cur); + } + + /* Copy @cur's values to @elt[] */ + nelt = qdict_size(cur) + 1; /* one extra, for use as sentinel */ + elt = g_new0(QObject *, nelt); + max_index = -1; + for (ent = qdict_first(cur); ent; ent = qdict_next(cur, ent)) { + index = key_to_index(ent->key, NULL); + assert(index >= 0); + if (index > max_index) { + max_index = index; + } + /* + * We iterate @nelt times. If we get one exceeding @nelt + * here, we will put less than @nelt values into @elt[], + * triggering the error in the next loop. + */ + if ((size_t)index >= nelt - 1) { + continue; + } + /* Even though dict keys are distinct, indexes need not be */ + elt[index] = ent->value; + } + + /* + * Make a list from @elt[], reporting the first missing element, + * if any. + * If we dropped an index >= nelt in the previous loop, this loop + * will run into the sentinel and report index @nelt missing. + */ + list = qlist_new(); + assert(!elt[nelt-1]); /* need the sentinel to be null */ + for (i = 0; i < MIN(nelt, max_index + 1); i++) { + if (!elt[i]) { + key = reassemble_key(key_of_cur); + error_setg(errp, "Parameter '%s%d' missing", key, i); + g_free(key); + g_free(elt); + QDECREF(list); + return NULL; + } + qobject_incref(elt[i]); + qlist_append_obj(list, elt[i]); + } + + g_free(elt); + return QOBJECT(list); +} + /* * Parse @params in QEMU's traditional KEY=VALUE,... syntax. * If @implied_key, the first KEY= can be omitted. @implied_key is @@ -219,6 +396,7 @@ QDict *keyval_parse(const char *params, const char *implied_key, Error **errp) { QDict *qdict = qdict_new(); + QObject *listified; const char *s; s = params; @@ -231,5 +409,11 @@ QDict *keyval_parse(const char *params, const char *implied_key, implied_key = NULL; } + listified = keyval_listify(qdict, NULL, errp); + if (!listified) { + QDECREF(qdict); + return NULL; + } + assert(listified == QOBJECT(qdict)); return qdict; }