From patchwork Thu Mar 10 18:59:49 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= X-Patchwork-Id: 8559431 Return-Path: X-Original-To: patchwork-qemu-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id BDC3DC0553 for ; Thu, 10 Mar 2016 19:00:55 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 82F072034C for ; Thu, 10 Mar 2016 19:00:53 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 376972034B for ; Thu, 10 Mar 2016 19:00:47 +0000 (UTC) Received: from localhost ([::1]:50681 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ae5pG-00048J-Mr for patchwork-qemu-devel@patchwork.kernel.org; Thu, 10 Mar 2016 14:00:46 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:46317) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ae5os-000424-LR for qemu-devel@nongnu.org; Thu, 10 Mar 2016 14:00:24 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1ae5on-0003RG-K6 for qemu-devel@nongnu.org; Thu, 10 Mar 2016 14:00:22 -0500 Received: from mx1.redhat.com ([209.132.183.28]:58493) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ae5oe-0003Oo-BC; Thu, 10 Mar 2016 14:00:08 -0500 Received: from int-mx11.intmail.prod.int.phx2.redhat.com (int-mx11.intmail.prod.int.phx2.redhat.com [10.5.11.24]) by mx1.redhat.com (Postfix) with ESMTPS id DF236C09FA83; Thu, 10 Mar 2016 19:00:07 +0000 (UTC) Received: from t530wlan.home.berrange.com.com (vpn1-4-172.ams2.redhat.com [10.36.4.172]) by int-mx11.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u2AJ01Mr002433; Thu, 10 Mar 2016 14:00:06 -0500 From: "Daniel P. Berrange" To: qemu-devel@nongnu.org Date: Thu, 10 Mar 2016 18:59:49 +0000 Message-Id: <1457636396-24983-3-git-send-email-berrange@redhat.com> In-Reply-To: <1457636396-24983-1-git-send-email-berrange@redhat.com> References: <1457635927-23045-1-git-send-email-berrange@redhat.com> <1457636396-24983-1-git-send-email-berrange@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.24 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 209.132.183.28 Cc: qemu-block@nongnu.org, Markus Armbruster , Max Reitz , Paolo Bonzini , =?UTF-8?q?Andreas=20F=C3=A4rber?= Subject: [Qemu-devel] [PATCH v3 03/10] qom: support arbitrary non-scalar properties with -object X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP The current -object command line syntax only allows for creation of objects with scalar properties, or a list with a fixed scalar element type. Objects which have properties that are represented as structs in the QAPI schema cannot be created using -object. This is a design limitation of the way the OptsVisitor is written. It simply iterates over the QemuOpts values as a flat list. The support for lists is enabled by allowing the same key to be repeated in the opts string. It is not practical to extend the OptsVisitor to support more complex data structures while also maintaining the existing list handling behaviour that is relied upon by other areas of QEMU. Fortunately there is no existing object that implements the UserCreatable interface that relies on the list handling behaviour, so it is possible to swap out the OptsVisitor for a different visitor implementation, so -object supports non-scalar properties, thus leaving other users of OptsVisitor unaffected. The previously added qdict_crumple() method is able to take a qdict containing a flat set of properties and turn that into a arbitrarily nested set of dicts and lists. By combining qemu_opts_to_qdict and qdict_crumple() together, we can turn the opt string into a data structure that is practically identical to that passed over QMP when defining an object. The only difference is that all the scalar values are represented as strings, rather than strings, ints and bools. This is sufficient to let us replace the OptsVisitor with the QMPInputVisitor for use with -object. Thus -object can now support non-scalar properties, for example the QMP object { "execute": "object-add", "arguments": { "qom-type": "demo", "id": "demo0", "parameters": { "foo": [ { "bar": "one", "wizz": "1" }, { "bar": "two", "wizz": "2" } ] } } } Would be creatable via the CLI now using $QEMU \ -object demo,id=demo0,\ foo.0.bar=one,foo.0.wizz=1,\ foo.1.bar=two,foo.1.wizz=2 This is also wired up to work for the 'object_add' command in the HMP monitor with the same syntax. (hmp) object_add demo,id=demo0,\ foo.0.bar=one,foo.0.wizz=1,\ foo.1.bar=two,foo.1.wizz=2 Signed-off-by: Daniel P. Berrange --- hmp.c | 18 +-- qom/object_interfaces.c | 20 ++- tests/check-qom-proplist.c | 295 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 313 insertions(+), 20 deletions(-) diff --git a/hmp.c b/hmp.c index 5b6084a..7a98726 100644 --- a/hmp.c +++ b/hmp.c @@ -25,7 +25,7 @@ #include "qemu/sockets.h" #include "monitor/monitor.h" #include "monitor/qdev.h" -#include "qapi/opts-visitor.h" +#include "qapi/qmp-input-visitor.h" #include "qapi/qmp/qerror.h" #include "qapi/string-output-visitor.h" #include "qapi/util.h" @@ -1673,20 +1673,12 @@ void hmp_netdev_del(Monitor *mon, const QDict *qdict) void hmp_object_add(Monitor *mon, const QDict *qdict) { Error *err = NULL; - QemuOpts *opts; - OptsVisitor *ov; + QmpInputVisitor *qiv; Object *obj = NULL; - opts = qemu_opts_from_qdict(qemu_find_opts("object"), qdict, &err); - if (err) { - hmp_handle_error(mon, &err); - return; - } - - ov = opts_visitor_new(opts); - obj = user_creatable_add(qdict, opts_get_visitor(ov), &err); - opts_visitor_cleanup(ov); - qemu_opts_del(opts); + qiv = qmp_input_visitor_new_full((QObject *)qdict, true, true); + obj = user_creatable_add(qdict, qmp_input_get_visitor(qiv), &err); + qmp_input_visitor_cleanup(qiv); if (err) { hmp_handle_error(mon, &err); diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c index c2f6e29..9c41730 100644 --- a/qom/object_interfaces.c +++ b/qom/object_interfaces.c @@ -1,9 +1,9 @@ #include "qemu/osdep.h" #include "qom/object_interfaces.h" #include "qemu/module.h" +#include "qemu/option.h" #include "qapi-visit.h" -#include "qapi/qmp-output-visitor.h" -#include "qapi/opts-visitor.h" +#include "qapi/qmp-input-visitor.h" void user_creatable_complete(Object *obj, Error **errp) { @@ -120,6 +120,7 @@ Object *user_creatable_add_type(const char *type, const char *id, obj = object_new(type); if (qdict) { for (e = qdict_first(qdict); e; e = qdict_next(qdict, e)) { + object_property_set(obj, v, e->key, &local_err); if (local_err) { goto out; @@ -151,15 +152,22 @@ out: Object *user_creatable_add_opts(QemuOpts *opts, Error **errp) { - OptsVisitor *ov; + QmpInputVisitor *qiv; QDict *pdict; + QObject *pobj; Object *obj = NULL; - ov = opts_visitor_new(opts); pdict = qemu_opts_to_qdict(opts, NULL); + pobj = qdict_crumple(pdict, true, errp); + if (!pobj) { + goto cleanup; + } + qiv = qmp_input_visitor_new_full(pobj, true, true); - obj = user_creatable_add(pdict, opts_get_visitor(ov), errp); - opts_visitor_cleanup(ov); + obj = user_creatable_add((QDict *)pobj, qmp_input_get_visitor(qiv), errp); + qmp_input_visitor_cleanup(qiv); + qobject_decref(pobj); + cleanup: QDECREF(pdict); return obj; } diff --git a/tests/check-qom-proplist.c b/tests/check-qom-proplist.c index a2bb556..b19bfb0 100644 --- a/tests/check-qom-proplist.c +++ b/tests/check-qom-proplist.c @@ -23,6 +23,14 @@ #include "qom/object.h" #include "qemu/module.h" +#include "qapi/visitor.h" +#include "qom/object_interfaces.h" +#include "qemu/option.h" +#include "qemu/config-file.h" +#include "qapi/qmp/qjson.h" +#include "qapi/qmp-input-visitor.h" +#include "qapi-visit.h" +#include "qapi/dealloc-visitor.h" #define TYPE_DUMMY "qemu-dummy" @@ -30,6 +38,11 @@ typedef struct DummyObject DummyObject; typedef struct DummyObjectClass DummyObjectClass; +typedef struct DummyPerson DummyPerson; +typedef struct DummyAddr DummyAddr; +typedef struct DummyAddrList DummyAddrList; +typedef struct DummySizeList DummySizeList; + #define DUMMY_OBJECT(obj) \ OBJECT_CHECK(DummyObject, (obj), TYPE_DUMMY) @@ -50,12 +63,35 @@ static const char *const dummy_animal_map[DUMMY_LAST + 1] = { [DUMMY_LAST] = NULL, }; + +struct DummyAddr { + char *ip; + int64_t prefix; + bool ipv6only; +}; + +struct DummyAddrList { + DummyAddrList *next; + struct DummyAddr *value; +}; + +struct DummyPerson { + char *name; + int64_t age; +}; + struct DummyObject { Object parent_obj; bool bv; DummyAnimal av; char *sv; + + intList *sizes; + + DummyPerson *person; + + DummyAddrList *addrs; }; struct DummyObjectClass { @@ -117,6 +153,135 @@ static char *dummy_get_sv(Object *obj, return g_strdup(dobj->sv); } +static void visit_type_DummyPerson_fields(Visitor *v, DummyPerson **obj, + Error **errp) +{ + Error *err = NULL; + + visit_type_str(v, "name", &(*obj)->name, &err); + if (err) { + goto out; + } + visit_type_int(v, "age", &(*obj)->age, &err); + if (err) { + goto out; + } + +out: + error_propagate(errp, err); +} + +static void visit_type_DummyPerson(Visitor *v, const char *name, + DummyPerson **obj, Error **errp) +{ + Error *err = NULL; + + visit_start_struct(v, name, (void **)obj, sizeof(DummyPerson), &err); + if (err) { + goto out; + } + if (!*obj) { + goto out_obj; + } + visit_type_DummyPerson_fields(v, obj, &err); + error_propagate(errp, err); + err = NULL; +out_obj: + visit_end_struct(v, &err); +out: + error_propagate(errp, err); +} + +static void visit_type_DummyAddr_members(Visitor *v, DummyAddr **obj, + Error **errp) +{ + Error *err = NULL; + + visit_type_str(v, "ip", &(*obj)->ip, &err); + if (err) { + goto out; + } + visit_type_int(v, "prefix", &(*obj)->prefix, &err); + if (err) { + goto out; + } + visit_type_bool(v, "ipv6only", &(*obj)->ipv6only, &err); + if (err) { + goto out; + } + +out: + error_propagate(errp, err); +} + +static void visit_type_DummyAddr(Visitor *v, const char *name, + DummyAddr **obj, Error **errp) +{ + Error *err = NULL; + + visit_start_struct(v, name, (void **)obj, sizeof(DummyAddr), &err); + if (err) { + goto out; + } + if (!*obj) { + goto out_obj; + } + visit_type_DummyAddr_members(v, obj, &err); + error_propagate(errp, err); + err = NULL; +out_obj: + visit_end_struct(v, &err); +out: + error_propagate(errp, err); +} + +static void visit_type_DummyAddrList(Visitor *v, const char *name, + DummyAddrList **obj, Error **errp) +{ + Error *err = NULL; + GenericList *i, **prev; + + visit_start_list(v, name, &err); + if (err) { + goto out; + } + + for (prev = (GenericList **)obj; + !err && (i = visit_next_list(v, prev, sizeof(**obj))) != NULL; + prev = &i) { + DummyAddrList *native_i = (DummyAddrList *)i; + visit_type_DummyAddr(v, NULL, &native_i->value, &err); + } + + visit_end_list(v); +out: + error_propagate(errp, err); +} + + +static void dummy_set_sizes(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + DummyObject *dobj = DUMMY_OBJECT(obj); + + visit_type_intList(v, name, &dobj->sizes, errp); +} + +static void dummy_set_person(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + DummyObject *dobj = DUMMY_OBJECT(obj); + + visit_type_DummyPerson(v, name, &dobj->person, errp); +} + +static void dummy_set_addrs(Object *obj, Visitor *v, const char *name, + void *opaque, Error **errp) +{ + DummyObject *dobj = DUMMY_OBJECT(obj); + + visit_type_DummyAddrList(v, name, &dobj->addrs, errp); +} static void dummy_init(Object *obj) { @@ -126,9 +291,16 @@ static void dummy_init(Object *obj) NULL); } +static void +dummy_complete(UserCreatable *uc, Error **errp) +{ +} static void dummy_class_init(ObjectClass *cls, void *data) { + UserCreatableClass *ucc = USER_CREATABLE_CLASS(cls); + ucc->complete = dummy_complete; + object_class_property_add_bool(cls, "bv", dummy_get_bv, dummy_set_bv, @@ -143,12 +315,36 @@ static void dummy_class_init(ObjectClass *cls, void *data) dummy_get_av, dummy_set_av, NULL); + object_class_property_add(cls, "sizes", + "int[]", + NULL, + dummy_set_sizes, + NULL, NULL, NULL); + object_class_property_add(cls, "person", + "DummyPerson", + NULL, + dummy_set_person, + NULL, NULL, NULL); + object_class_property_add(cls, "addrs", + "DummyAddrList", + NULL, + dummy_set_addrs, + NULL, NULL, NULL); } static void dummy_finalize(Object *obj) { DummyObject *dobj = DUMMY_OBJECT(obj); + QapiDeallocVisitor *qdv; + Visitor *v; + + qdv = qapi_dealloc_visitor_new(); + v = qapi_dealloc_get_visitor(qdv); + visit_type_intList(v, NULL, &dobj->sizes, NULL); + visit_type_DummyAddrList(v, NULL, &dobj->addrs, NULL); + visit_type_DummyPerson(v, NULL, &dobj->person, NULL); + qapi_dealloc_visitor_cleanup(qdv); g_free(dobj->sv); } @@ -162,6 +358,10 @@ static const TypeInfo dummy_info = { .instance_finalize = dummy_finalize, .class_size = sizeof(DummyObjectClass), .class_init = dummy_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_USER_CREATABLE }, + { } + } }; @@ -457,7 +657,9 @@ static void test_dummy_iterator(void) ObjectProperty *prop; ObjectPropertyIterator iter; - bool seenbv = false, seensv = false, seenav = false, seentype; + bool seenbv = false, seensv = false, seenav = false, + seentype = false, seenaddrs = false, seenperson = false, + seensizes = false; object_property_iter_init(&iter, OBJECT(dobj)); while ((prop = object_property_iter_next(&iter))) { @@ -470,6 +672,12 @@ static void test_dummy_iterator(void) } else if (g_str_equal(prop->name, "type")) { /* This prop comes from the base Object class */ seentype = true; + } else if (g_str_equal(prop->name, "addrs")) { + seenaddrs = true; + } else if (g_str_equal(prop->name, "person")) { + seenperson = true; + } else if (g_str_equal(prop->name, "sizes")) { + seensizes = true; } else { g_printerr("Found prop '%s'\n", prop->name); g_assert_not_reached(); @@ -479,6 +687,9 @@ static void test_dummy_iterator(void) g_assert(seenav); g_assert(seensv); g_assert(seentype); + g_assert(seenaddrs); + g_assert(seenperson); + g_assert(seensizes); object_unparent(OBJECT(dobj)); } @@ -497,11 +708,91 @@ static void test_dummy_delchild(void) object_unparent(OBJECT(dev)); } + +static QemuOptsList dummy_opts = { + .name = "object", + .implied_opt_name = "qom-type", + .head = QTAILQ_HEAD_INITIALIZER(dummy_opts.head), + .desc = { + { } + }, +}; + +static void test_dummy_create_complex(DummyObject *dummy) +{ + g_assert(dummy->person != NULL); + g_assert_cmpstr(dummy->person->name, ==, "fred"); + g_assert_cmpint(dummy->person->age, ==, 52); + + g_assert(dummy->sizes != NULL); + g_assert_cmpint(dummy->sizes->value, ==, 12); + g_assert_cmpint(dummy->sizes->next->value, ==, 65); + g_assert_cmpint(dummy->sizes->next->next->value, ==, 8139); + + g_assert(dummy->addrs != NULL); + g_assert_cmpstr(dummy->addrs->value->ip, ==, "127.0.0.1"); + g_assert_cmpint(dummy->addrs->value->prefix, ==, 24); + g_assert(dummy->addrs->value->ipv6only); + g_assert_cmpstr(dummy->addrs->next->value->ip, ==, "0.0.0.0"); + g_assert_cmpint(dummy->addrs->next->value->prefix, ==, 16); + g_assert(!dummy->addrs->next->value->ipv6only); +} + + +static void test_dummy_createopts(void) +{ + const char *optstr = "qemu-dummy,id=dummy0,bv=yes,av=alligator,sv=hiss," + "person.name=fred,person.age=52,sizes.0=12,sizes.1=65,sizes.2=8139," + "addrs.0.ip=127.0.0.1,addrs.0.prefix=24,addrs.0.ipv6only=yes," + "addrs.1.ip=0.0.0.0,addrs.1.prefix=16,addrs.1.ipv6only=no"; + QemuOpts *opts; + DummyObject *dummy; + + opts = qemu_opts_parse_noisily(&dummy_opts, + optstr, true); + g_assert(opts != NULL); + + dummy = DUMMY_OBJECT(user_creatable_add_opts(opts, &error_abort)); + + test_dummy_create_complex(dummy); + + object_unparent(OBJECT(dummy)); + object_unref(OBJECT(dummy)); +} + + +static void test_dummy_createqmp(void) +{ + const char *jsonstr = + "{ 'qom-type': 'qemu-dummy', 'id': 'dummy0', " + " 'bv': true, 'av': 'alligator', 'sv': 'hiss', " + " 'person': { 'name': 'fred', 'age': 52 }, " + " 'sizes': [12, 65, 8139], " + " 'addrs': [ { 'ip': '127.0.0.1', 'prefix': 24, 'ipv6only': true }, " + " { 'ip': '0.0.0.0', 'prefix': 16, 'ipv6only': false } ] }"; + QObject *obj = qobject_from_json(jsonstr); + QmpInputVisitor *qiv = qmp_input_visitor_new_strict(obj); + DummyObject *dummy; + g_assert(obj); + dummy = DUMMY_OBJECT(user_creatable_add(qobject_to_qdict(obj), + qmp_input_get_visitor(qiv), + &error_abort)); + + test_dummy_create_complex(dummy); + qmp_input_visitor_cleanup(qiv); + object_unparent(OBJECT(dummy)); + object_unref(OBJECT(dummy)); + qobject_decref(obj); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); module_call_init(MODULE_INIT_QOM); + + qemu_add_opts(&dummy_opts); + type_register_static(&dummy_info); type_register_static(&dummy_dev_info); type_register_static(&dummy_bus_info); @@ -513,6 +804,8 @@ int main(int argc, char **argv) g_test_add_func("/qom/proplist/getenum", test_dummy_getenum); g_test_add_func("/qom/proplist/iterator", test_dummy_iterator); g_test_add_func("/qom/proplist/delchild", test_dummy_delchild); + g_test_add_func("/qom/proplist/createopts", test_dummy_createopts); + g_test_add_func("/qom/proplist/createqmp", test_dummy_createqmp); return g_test_run(); }