qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Markus Armbruster <armbru@redhat.com>
To: Eric Blake <eblake@redhat.com>
Cc: qemu-devel@nongnu.org, Michael Roth <mdroth@linux.vnet.ibm.com>
Subject: Re: [Qemu-devel] [PATCH v8 10/17] qapi: Simplify visiting of alternate types
Date: Wed, 04 Nov 2015 08:30:32 +0100	[thread overview]
Message-ID: <87d1vqw7af.fsf@blackfin.pond.sub.org> (raw)
In-Reply-To: <56390423.90606@redhat.com> (Eric Blake's message of "Tue, 3 Nov 2015 11:59:47 -0700")

Eric Blake <eblake@redhat.com> writes:

> On 11/03/2015 11:30 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> Previously, working with alternates required two enums, and
>>> some indirection: for type Foo, we created Foo_qtypes[] which
>>> maps each qtype to a member of FooKind_lookup[], then use
>> 
>> member of FooKind, actually.
>
> Or entry in the FooKind_lookup[] array.

The generated

    const int BlockdevRef_qtypes[QTYPE_MAX] = {
        [QTYPE_QDICT] = BLOCKDEV_REF_KIND_DEFINITION,
        [QTYPE_QSTRING] = BLOCKDEV_REF_KIND_REFERENCE,
    };

maps from qtype_code to BlockdevRefKind, except it uses int instead of
BlockdevRefKind, so it can be passed to visit_get_next_type().

>>> FooKind_lookup[] like we do for other union types.
>> 
>> You probably mean FooKind here as well.
>
> I'll play with the wording.
>
>> 
>>> This has a subtle bug: since the values of FooKind_lookup
>>> start at zero, all entries of Foo_qtypes that were not
>>> explicitly initialized map to the same branch of the union as
>>> the first member of the alternate, rather than triggering a
>>> failure in visit_get_next_type().  Fortunately, the bug
>>> seldom bites; the very next thing the input visitor does is
>>> try to parse the incoming JSON with the wrong parser, which
>>> fails; the output visitor is not used with a C struct in that
>>> state, and the dealloc visitor has nothing to clean up (so
>>> there is no leak).
>> 
>> Yes, I remember us discussing this bug.
>> 
>> While reading code to double-check your description, I stumbled over
>> this beauty in generated qapi-visit.c:
>> 
>>     visit_get_next_type(v, (int*) &(*obj)->type, BlockdevRef_qtypes,
>> name, &err);
>> 
>> This casts enum BlockdevRefKind * to int *, which assumes the compiler
>> represents the enum BlockdevRefKind as int or unsigned.  It is free to
>> use any integer type, though.  Common mistake of programmers with
>> insufficiently developed wariness of C's subtleties.
>> 
>> visit_get_next_type() passes the fishy int * on to v->get_next_type().
>> Only implementation is qmp_input_get_next_type(), which uses it so:
>> 
>>     *kind = qobjects[qobject_type(qobj)];
>> 
>> Latent death trap.
>> 
>> Does your patch clean this up?
>
> Yes, and I need to also document that this is an additional bug fix.
>
>> 
>>> However, it IS observable in one case: the behavior of an
>>> alternate that contains a 'number' member but no 'int' member
>>> differs according to whether the 'number' was first in the
>>> qapi definition, and when the input being parsed is an integer;
>>> this is because the 'number' parser accepts QTYPE_QINT in
>>> addition to the expected QTYPE_QFLOAT.  A later patch will worry
>>> about fixing alternates to parse all inputs that a non-alternate
>>> 'number' would accept, for now it is still marked FIXME.
>
> See [1] below.
>
>>>
>>> This patch fixes the validation bug by deleting the indirection,
>>> and modifying get_next_type() to directly return a qtype code.
>> 
>> get_next_type() doesn't return anything.  Do you mean "store a qtype
>> code"?
>
> Yes.
>
>> 
>>> There is no longer a need to generate an implicit FooKind array
>> 
>> FooKind is an enum, not an array.
>
> ...to generate an implicit FooKind enum, nor FooKind_lookup[] array.

Yes.

>>> associated with the alternate type (since the QMP wire format
>>> never uses the stringized counterparts of the C union member
>>> names); that also means we no longer have a collision with an
>>> alternate branch named 'max'.  Next, the generated visitor is
>>> fixed to properly detect unexpected qtypes in the switch
>>> statement.  This is done via the use of a new
>>> QAPISchemaAlternateTypeTag subclass and the use of a new
>>> member.c_type() method when producing qapi-types.  The new
>>> subtype also allows us to clean up a TODO left in the previous
>>> commit.
>>>
>>> Callers now have to know the QTYPE_* mapping when looking at the
>>> discriminator; but so far, only the testsuite was even using the
>>> C struct of an alternate types.  If that gets too confusing, we
>>> could reintroduce FooKind, but initialize it differently than
>>> most generated arrays, as in:
>>>   typedef enum FooKind {
>>>       FOO_KIND_A = QTYPE_QDICT,
>>>       FOO_KIND_B = QTYPE_QINT,
>>>   } FooKind;
>>> to create nicer aliases for knowing when to use foo->a or foo->b
>>> when inspecting foo->type.  But without a current client, I
>>> didn't see the point of doing it now.
>
> You have a point below that we either need to reserve MAX and require no
> case-insensitive clashes, or that we will never want to add it.  I'm
> leaning towards never going back, because the new way feels so much nicer.

Perhaps un-reserving MAX and making the collision checking more lenient
should be a separate follow-up patch we could easily revert.  Probably
useful only if this patch doesn't make 2.5.

>>>
>>> There is a user-visible side effect to this change, but I
>>> consider it to be an improvement. Previously,
>>> the invalid QMP command:
>>>   {"execute":"blockdev-add", "arguments":{"options":
>>>     {"driver":"raw", "id":"a", "file":true}}}
>>> failed with:
>>>   {"error": {"class": "GenericError",
>>>     "desc": "Invalid parameter type for 'file', expected: QDict"}}
>>> Now it fails with:
>>>   {"error": {"class": "GenericError",
>>>     "desc": "Invalid parameter type for 'file', expected: BlockdevRef"}}
>> 
>> I wonder how that happens.  Perhaps it's obvious in the patch.
>
> I think you found it below.
>
>> 
>> QMP introspection isn't affected, because we carefully minimized the
>> information to expose there.
>
> Ooh, nice tidbit to add.
>
>
>>> +/**
>>> + * Determine the qtype of the item @name in the current object visit.
>>> + * For input visitors, set *@type to the correct qtype of a qapi
>>> + * alternate type; for other visitors, leave *@type unchanged.
>>> + */
>>> +void visit_get_next_type(Visitor *v, qtype_code *type,
>>>                           const char *name, Error **errp);
>> 
>> Naive question: what makes a visitor an input visitor?
>
> I've got a later patch in my queue that adds a lot more documentation:
> http://repo.or.cz/qemu/ericb.git/commitdiff/f7674a87e72
>
> +/* This file describes the client view for visiting a map between
> + * generated QAPI C structs and another representation (command line
> + * options, strings, or QObjects).  An input visitor converts from
> + * some other form into QAPI representation; an output visitor
> + * converts from QAPI back into another form.  In the descriptions
> + * below, an object or dictionary refers to a JSON '{}', and an array
> + * or list refers to a JSON '[]'.  These functions seldom need to be
> + * called directly, but are instead used by code generated by
> + * scripts/qapi-visit.py.  For the visitor callback contracts, see
> + * visitor-impl.h. */

That's a useful step towards a visitors contract.

>>> +++ b/scripts/qapi-visit.py
>>> @@ -189,7 +189,7 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
>>>      if (err) {
>>>          goto out;
>>>      }
>>> -    visit_get_next_type(v, (int*) &(*obj)->type, %(c_name)s_qtypes, name, &err);
>>> +    visit_get_next_type(v, &(*obj)->type, name, &err);
>>>      if (err) {
>>>          goto out_obj;
>>>      }
>> 
>> Yes, your patch disarms the latent death trap: no more pointer casting.
>
> Indeed, I noticed the cleanup as well (I'm quite familiar with the
> unsafe nature of casting enum* because you cannot guarantee its size),
> but failed to call out the trap in my commit message.
>
>> 
>>> @@ -203,14 +203,14 @@ void visit_type_%(c_name)s(Visitor *v, %(c_name)s **obj, const char *name, Error
>>>          visit_type_%(c_type)s(v, &(*obj)->u.%(c_name)s, name, &err);
>>>          break;
>>>  ''',
>>> -                     case=c_enum_const(variants.tag_member.type.name,
>>> -                                       var.name),
>>> +                     case=var.type.alternate_qtype(),
>>>                       c_type=var.type.c_name(),
>>>                       c_name=c_name(var.name))
>>>
>>>      ret += mcgen('''
>>>      default:
>>> -        abort();
>>> +        error_setg(&err, QERR_INVALID_PARAMETER_TYPE, name ? name : "null",
>>> +                   "%(name)s");
>> 
>> Okay, this is where the new error message comes from.
>> 
>> Before, default is unreachable, because (*obj)->type got erroneously set
>> the enum's first member when none of the alternate's variants matches
>> the qtype.
>> 
>> After, (*obj)->type *is* the qtype, and we do reach default when no
>> variant matches.
>> 
>> How can name be null?
>
> When you have the qapi representation ['MyAlternate'], you will have
> qapi_visit_type_MyAlternateList() which passes NULL for the name of each
> list member (because names are only present for objects, not lists).

Examining the occurences of visit_type_BlockdevRef* in generated
qapi-visit.c shows that parameter name is commonly the member name.  It
appears to be used only for error messages.

Works fine when the object containing the member is obvious.  With
multiple objects, the recipient of the error message has to guess the
object containing the member.  If the member name isn't unique, the
error message is ambiguous.  Crap, but not crap we can afford to improve
right now.

Array elements are anonymous, and I guess that's what led to NULL.
Stupid choice.  Make it "array element" or something.  Code becomes
slightly simpler, error message becomes slightly less cruel.  This could
be simple enough to improve now.

>> I really need to finish the QERR_ killing job.
>
> Agreed. But shouldn't stall this patch, though.

Certainly not.

>>>      # Check every branch
>>>      for (key, value) in members.items():
>>>          check_name(expr_info, "Member of alternate '%s'" % name, key)
>>>
>>> -        # Check for conflicts in the generated enum
>>> -        c_key = camel_to_upper(key)
>>> +        # Check for conflicts in the branch names
>>> +        c_key = c_name(key)
>> 
>> Why c_name()?
>
> So that 'a-b' and 'a_b' are properly flagged as conflicting (they map to
> the same c_name).
>
>>>  class QAPISchemaObjectTypeVariants(object):
>>>      def __init__(self, tag_name, tag_member, variants):
>>>          # Flat unions pass tag_name but not tag_member.
>>>          # Simple unions and alternates pass tag_member but not tag_name.
>>>          # After check(), tag_member is always set, and tag_name remains
>>> -        # a reliable witness of being used by a flat union.
>>> +        # a reliable witness of being used by a flat union, and
>>> +        # tag_member.type being None is a reliable witness of an alternate.
>> 
>> A member without a type?  Ugh!  I wouldn't dare breaking invariants like
>> that.
>> 
>> Of course, an alternate's tag member still has a type: qtype_code.  It's
>> just not declared in the schema.  Should it be a built-in type then?
>
> It's not a builtin that can ever be referenced in the .json files.  But
> I could probably come up with something, if it would make you feel better.
>
>
>>>          for v in self.variants:
>>>              # Reset seen array for each variant, since QMP names from one
>>>              # branch do not affect another branch, nor add to all_members
>>> -            v.check(schema, self.tag_member.type, dict(seen), cases, union)
>>> +            v.check(schema, self.tag_member.type, dict(seen), cases)
>> 
>> I expect some rebase churn around here, so I'm not reviewing closely.
>> 
>
> Indeed. All the more reason for me to post a v9 spin (and maybe defer
> the question of a non-None type for tag_member until after that post).
>
>
>> My patches move the member name collision checking to
>> QAPISchemaObjectType.check().
>> 
>> I suspect alternate branch name collision checking should similarly move
>> to QAPISchemaAlternateType.check().
>> 
>
> Yep, already that way in my pending v9 series after incorporating your
> patches.
>
>>> +class QAPISchemaAlternateTypeTag(QAPISchemaObjectTypeMember):
>>> +    def __init__(self):
>>> +        QAPISchemaObjectTypeMember.__init__(self, 'type', '', False)
>>> +
>>> +    def check(self, schema, seen):
>>> +        assert len(seen) == 0
>>> +        seen[self.name] = self
>>> +
>>> +    def c_type(self):
>>> +        return 'qtype_code'
>>> +
>>> +
>> 
>> This is a hack to work around the lack of a qtype_code type.  I suspect
>> creating such a type would be simpler in the end.  Safer, too, because
>> it would avoid having members without a type, which scares me.
>
> I can play with dropping c_type() here in favor of adding a qtype_code
> special class, but I may still need to keep this
> QAPISchemaAlternateTypeTag subclass.

Let's give it a try and see.

>>> +++ b/tests/qapi-schema/qapi-schema-test.json
>>> @@ -131,7 +131,7 @@
>>>    'data': { 'value1': 'UserDefZero', 'has_a': 'UserDefZero',
>>>              'u': 'UserDefZero', 'type': 'UserDefZero' } }
>>>  { 'alternate': 'AltName', 'data': { 'type': 'int', 'u': 'bool',
>>> -                                    'myKind': 'has_a' } }
>>> +                                    'myKind': 'has_a', 'max': 'str' } }
>> 
>> Here, you add the positive test that alternate name 'max' works.
>> 
>> One, not mentioned in the commit message.
>
> D'oh.
>
>> 
>> Two, the commit message says we may reintroduce FooKind if working with
>> qtype_code turns out to be too confusing.  If we ever do that, alternate
>> name 'max' breaks, doesn't it?  Shouldn't we keep it reserved then, just
>> in case?
>> 
>
> See my comment above; at this point, I doubt we'll ever want to go back,
> so maybe I just need to be more definitive in stating that.
>
>>> +++ b/tests/test-qmp-input-visitor.c
>
>>> @@ -386,11 +386,10 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
>>>      qapi_free_AltStrBool(asb);
>>>      visitor_input_teardown(data, NULL);
>>>
>>> -    /* FIXME: Order of alternate should not affect semantics; asn should
>>> -     * parse the same as ans */
>>> +    /* FIXME: integer should parse as number */
>>>      v = visitor_input_test_init(data, "42");
>>>      visit_type_AltStrNum(v, &asn, NULL, &err);
>>> -    /* FIXME g_assert_cmpint(asn->type, == ALT_STR_NUM_KIND_N); */
>>> +    /* FIXME g_assert_cmpint(asn->type, ==, QTYPE_QFLOAT); */
>>>      /* FIXME g_assert_cmpfloat(asn->u.n, ==, 42); */
>>>      g_assert(err);
>>>      error_free(err);
>>> @@ -398,30 +397,34 @@ static void test_visitor_in_alternate_number(TestInputVisitorData *data,
>>>      qapi_free_AltStrNum(asn);
>>>      visitor_input_teardown(data, NULL);
>>>
>>> +    /* FIXME: integer should parse as number */
>>>      v = visitor_input_test_init(data, "42");
>>> -    visit_type_AltNumStr(v, &ans, NULL, &error_abort);
>>> -    g_assert_cmpint(ans->type, ==, ALT_NUM_STR_KIND_N);
>>> -    g_assert_cmpfloat(ans->u.n, ==, 42);
>>> +    visit_type_AltNumStr(v, &ans, NULL, &err);
>>> +    /* FIXME g_assert_cmpint(ans->type, ==, QTYPE_QFLOAT); */
>>> +    /* FIXME g_assert_cmpfloat(ans->u.n, ==, 42); */
>>> +    g_assert(err);
>>> +    error_free(err);
>>> +    err = NULL;
>> 
>> What's happening here?  Whatever it is, the commit message didn't
>> prepare me for it...
>
> See [1] above.  'asn' is now parsing the same as 'ans' (we are no longer
> sensitive to whether 'number' was the first member of the alternate),
> but it isn't until patch 11/17 that we fix things that 'ans' and 'asn'
> both properly parse '1' as a number instead of rejecting it as an integer.

Recommend to stick a brief note into the commit message that this
temporarily breaks test so-and-so, marked FIXME.

>>> +++ b/tests/test-qmp-output-visitor.c
>>> @@ -449,20 +449,31 @@ static void test_visitor_out_alternate(TestOutputVisitorData *data,
>>>                                         const void *unused)
>>>  {
>>>      QObject *arg;
>>> -    Error *err = NULL;
>>> +    UserDefAlternate *tmp;
>>>
>>> -    UserDefAlternate *tmp = g_malloc0(sizeof(UserDefAlternate));
>>> -    tmp->type = USER_DEF_ALTERNATE_KIND_I;
>>> +    tmp = g_new0(UserDefAlternate, 1);
>>> +    tmp->type = QTYPE_QINT;
>>>      tmp->u.i = 42;
>> 
>> Coding style touched up.  Okay.
>> 
>>>
>>> -    visit_type_UserDefAlternate(data->ov, &tmp, NULL, &err);
>>> -    g_assert(err == NULL);
>>> +    visit_type_UserDefAlternate(data->ov, &tmp, NULL, &error_abort);
>> 
>> We need to make up our mind whether to use g_assert(err == NULL) or
>> &error_abort in tests.  Wholesale conversion could be in order.  I like
>> &error_abort, because it's more concise.
>
> Wholesale conversion dead-ahead, in 14/17 of this subset.
>
>> 
>>>      arg = qmp_output_get_qobject(data->qov);
>>>
>>>      g_assert(qobject_type(arg) == QTYPE_QINT);
>>>      g_assert_cmpint(qint_get_int(qobject_to_qint(arg)), ==, 42);
>>>
>>>      qapi_free_UserDefAlternate(tmp);
>>> +
>>> +    tmp = g_malloc0(sizeof(UserDefAlternate));
>> 
>> g_new0(UserDefAlternate, 1), please.
>> 
>>> +    tmp->type = QTYPE_QSTRING;
>>> +    tmp->u.s = g_strdup("hello");
>>> +
>>> +    visit_type_UserDefAlternate(data->ov, &tmp, NULL, &error_abort);
>>> +    arg = qmp_output_get_qobject(data->qov);
>>> +
>>> +    g_assert(qobject_type(arg) == QTYPE_QSTRING);
>>> +    g_assert_cmpstr(qstring_get_str(qobject_to_qstring(arg)), ==, "hello");
>>> +
>>> +    qapi_free_UserDefAlternate(tmp);
>> 
>> New test, not mentioned in commit message.  Separate patch, perhaps,
>> along with the nearby coding style touch ups?
>
> Yes, I will split this portion of the test changes out to a separate commit.

  reply	other threads:[~2015-11-04  7:30 UTC|newest]

Thread overview: 56+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-10-28 17:14 [Qemu-devel] [PATCH v8 00/17] alternate layout (post-introspection cleanups, subset C) Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 01/17] qapi: Use generated TestStruct machinery in tests Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 02/17] qapi: Strengthen test of TestStructList Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 03/17] qapi: Provide nicer array names in introspection Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 04/17] qapi-introspect: Guarantee particular sorting Eric Blake
2015-10-30 11:20   ` Markus Armbruster
2015-10-30 15:41     ` Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 05/17] qapi: Track simple union tag in object.local_members Eric Blake
2015-10-30 12:54   ` Markus Armbruster
2015-10-30 16:32     ` Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 06/17] qapi-types: Consolidate gen_struct() and gen_union() Eric Blake
2015-10-30 13:01   ` Markus Armbruster
2015-10-30 16:36     ` Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 07/17] qapi: Rework collision assertions Eric Blake
2015-11-02 15:37   ` Markus Armbruster
2015-11-02 17:20     ` Eric Blake
2015-11-02 21:24     ` Eric Blake
2015-11-03  7:56       ` Markus Armbruster
2015-11-03 13:30         ` Eric Blake
2015-11-02 22:41     ` [Qemu-devel] [PATCH v8.5 0/4] rework of 7/17 Eric Blake
2015-11-02 22:41     ` [Qemu-devel] [PATCH v8.5 1/4] qapi: Drop all_members parameter from check() Eric Blake
2015-11-03 11:06       ` Markus Armbruster
2015-11-03 13:26         ` Eric Blake
2015-11-03 14:02           ` Markus Armbruster
2015-11-03 14:12             ` Eric Blake
2015-11-03 16:19               ` Markus Armbruster
2015-11-03 17:34                 ` Eric Blake
2015-11-03 14:04         ` [Qemu-devel] [PATCH 1/7] qapi: Drop obsolete tag value collision assertions Markus Armbruster
2015-11-03 14:04           ` [Qemu-devel] [PATCH 2/7] qapi: Simplify QAPISchemaObjectTypeMember.check() Markus Armbruster
2015-11-03 14:04           ` [Qemu-devel] [PATCH 3/7] qapi: Clean up after previous commit Markus Armbruster
2015-11-03 14:04           ` [Qemu-devel] [PATCH 4/7] qapi: Fix up commit 7618b91's clash sanity checking change Markus Armbruster
2015-11-03 14:04           ` [Qemu-devel] [PATCH 5/7] qapi: Eliminate QAPISchemaObjectType.check() variable members Markus Armbruster
2015-11-03 14:04           ` [Qemu-devel] [PATCH 6/7] qapi: Factor out QAPISchemaObjectTypeMember.check_clash() Markus Armbruster
2015-11-03 14:04           ` [Qemu-devel] [PATCH 7/7] qapi: QAPISchemaObjectTypeVariants.check() Markus Armbruster
2015-11-02 22:41     ` [Qemu-devel] [PATCH v8.5 2/4] qapi: Check for QMP collisions of flat union branches Eric Blake
2015-11-02 22:41     ` [Qemu-devel] [PATCH v8.5 3/4] qapi: Fix check for variant tag values collision Eric Blake
2015-11-02 22:41     ` [Qemu-devel] [PATCH v8.5 4/4] qapi: Consolidate collision detection code Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 08/17] qapi: Remove outdated tests related to QMP/branch collisions Eric Blake
2015-11-03 16:32   ` Markus Armbruster
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 09/17] qapi: Add positive tests to qapi-schema-test Eric Blake
2015-11-03 16:43   ` Markus Armbruster
2015-11-03 16:56     ` Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 10/17] qapi: Simplify visiting of alternate types Eric Blake
2015-11-03 18:30   ` Markus Armbruster
2015-11-03 18:59     ` Eric Blake
2015-11-04  7:30       ` Markus Armbruster [this message]
2015-11-04 16:03       ` Markus Armbruster
2015-11-04 20:52         ` Eric Blake
2015-11-05  7:17           ` Markus Armbruster
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 11/17] qapi: Fix alternates that accept 'number' but not 'int' Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 12/17] qapi: Remove dead visitor code Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 13/17] qapi: Plug leaks in test-qmp-* Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 14/17] qapi: Simplify error testing " Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 15/17] qapi: Test failure in middle of array parse Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 16/17] qapi: More tests of input arrays Eric Blake
2015-10-28 17:14 ` [Qemu-devel] [PATCH v8 17/17] qapi: Simplify visits of optional fields Eric Blake

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87d1vqw7af.fsf@blackfin.pond.sub.org \
    --to=armbru@redhat.com \
    --cc=eblake@redhat.com \
    --cc=mdroth@linux.vnet.ibm.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).