qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Libguestfs] [PATCH v3 14/14] nbd/server: Add FLAG_PAYLOAD support t


From: Eric Blake
Subject: Re: [Libguestfs] [PATCH v3 14/14] nbd/server: Add FLAG_PAYLOAD support to CMD_BLOCK_STATUS
Date: Fri, 2 Jun 2023 08:14:13 -0500
User-agent: NeoMutt/20230517

On Fri, Jun 02, 2023 at 12:13:25PM +0300, Vladimir Sementsov-Ogievskiy wrote:
> On 15.05.23 22:53, Eric Blake wrote:
> > Allow a client to request a subset of negotiated meta contexts.  For
> > example, a client may ask to use a single connection to learn about
> > both block status and dirty bitmaps, but where the dirty bitmap
> > queries only need to be performed on a subset of the disk; forcing the
> > server to compute that information on block status queries in the rest
> > of the disk is wasted effort (both at the server, and on the amount of
> > traffic sent over the wire to be parsed and ignored by the client).
> > 
...
> > 
> > +/*
> > + * nbd_co_block_status_payload_read
> > + * Called when a client wants a subset of negotiated contexts via a
> > + * BLOCK_STATUS payload.  Check the payload for valid length and
> > + * contents.  On success, return 0 with request updated to effective
> > + * length.  If request was invalid but payload consumed, return 0 with
> > + * request->len and request->contexts.count set to 0 (which will
> > + * trigger an appropriate NBD_EINVAL response later on).
> 
> Hmm. So, it leads to
> 
>     case NBD_CMD_BLOCK_STATUS:
>         if (!request->len) {
>             return nbd_send_generic_reply(client, request, -EINVAL,
>                                           "need non-zero length", errp);
>         }
> 
> EINVAL is OK, but "need non-zero length" message is not appropriate.. I think 
> we need separate reply for the case of invalid payload.

Or maybe just reword the error to "unexpected length", which covers a
broader swath of errors (none of which are likely from compliant
clients).

> 
> > On I/O
> > + * error, return -EIO.
> > + */
> > +static int
> > +nbd_co_block_status_payload_read(NBDClient *client, NBDRequest *request,
> > +                                 Error **errp)
> > +{
> > +    int payload_len = request->len;
> > +    g_autofree char *buf = NULL;
> > +    g_autofree bool *bitmaps = NULL;
> > +    size_t count, i;
> > +    uint32_t id;
> > +
> > +    assert(request->len <= NBD_MAX_BUFFER_SIZE);
> > +    if (payload_len % sizeof(uint32_t) ||
> > +        payload_len < sizeof(NBDBlockStatusPayload) ||
> > +        payload_len > (sizeof(NBDBlockStatusPayload) +
> > +                       sizeof(id) * client->contexts.count)) {
> > +        goto skip;
> > +    }
> > +
> > +    buf = g_malloc(payload_len);
> > +    if (nbd_read(client->ioc, buf, payload_len,
> > +                 "CMD_BLOCK_STATUS data", errp) < 0) {
> > +        return -EIO;
> > +    }
> > +    trace_nbd_co_receive_request_payload_received(request->handle,
> > +                                                  payload_len);
> > +    memset(&request->contexts, 0, sizeof(request->contexts));
> > +    request->contexts.nr_bitmaps = client->context_exp->nr_export_bitmaps;
> > +    bitmaps = g_new0(bool, request->contexts.nr_bitmaps);
> > +    count = (payload_len - sizeof(NBDBlockStatusPayload)) / sizeof(id);
> 
> In doc we have MUST: "The payload form MUST occupy 8 + n*4 bytes", do we 
> really want to forgive and ignore unaligned tail? May be better to "goto 
> skip" in this case, to avoid ambiguity.

That's what happened above, when checking that payload_len %
sizeof(uint32_t) was 0.  Or am I misunderstanding your question about
another condition where goto skip would be appropriate?

> 
> > +    payload_len = 0;
> > +
> > +    for (i = 0; i < count; i++) {
> > +
> > +        id = ldl_be_p(buf + sizeof(NBDBlockStatusPayload) + sizeof(id) * 
> > i);
> > +        if (id == NBD_META_ID_BASE_ALLOCATION) {
> > +            if (request->contexts.base_allocation) {
> > +                goto skip;
> > +            }
> > +            request->contexts.base_allocation = true;
> > +        } else if (id == NBD_META_ID_ALLOCATION_DEPTH) {
> > +            if (request->contexts.allocation_depth) {
> > +                goto skip;
> > +            }
> > +            request->contexts.allocation_depth = true;
> > +        } else {
> > +            if (id - NBD_META_ID_DIRTY_BITMAP >
> > +                request->contexts.nr_bitmaps ||
> > +                bitmaps[id - NBD_META_ID_DIRTY_BITMAP]) {
> > +                goto skip;
> > +            }
> > +            bitmaps[id - NBD_META_ID_DIRTY_BITMAP] = true;
> > +        }
> > +    }
> > +
> > +    request->len = ldq_be_p(buf);
> > +    request->contexts.count = count;
> > +    request->contexts.bitmaps = bitmaps;
> > +    bitmaps = NULL;
> 
> better I think:
> 
> request->context.bitmaps = g_steal_pointer(bitmaps);
> 
>  - as a pair to g_autofree.

Yes, that is shorter.

> 
> > +    return 0;
> > +
> > + skip:
> > +    trace_nbd_co_receive_block_status_payload_compliance(request->from,
> > +                                                         request->len);
> > +    request->len = request->contexts.count = 0;
> > +    return nbd_drop(client->ioc, payload_len, errp);
> > +}
> > +
> >   /* nbd_co_receive_request
> >    * Collect a client request. Return 0 if request looks valid, -EIO to drop
> >    * connection right away, -EAGAIN to indicate we were interrupted and the
> > @@ -2461,7 +2547,14 @@ static int coroutine_fn 
> > nbd_co_receive_request(NBDRequestData *req, NBDRequest *
> > 
> >           if (request->type == NBD_CMD_WRITE || extended_with_payload) {
> >               payload_len = request->len;
> > -            if (request->type != NBD_CMD_WRITE) {
> > +            if (request->type == NBD_CMD_BLOCK_STATUS) {
> > +                payload_len = nbd_co_block_status_payload_read(client,
> > +                                                               request,
> > +                                                               errp);
> > +                if (payload_len < 0) {
> > +                    return -EIO;
> > +                }
> 
> Seems we can handle all payload in one "switch" block, instead of handling 
> BLOCK_STATUS here and postponing WRITE payload (and dropping) up to the end 
> of the block with help of payload_len variable.

I can experiment with other ways of respresenting the logic; there's
enough complexity that I'm trying to balance between repeating
conditionals vs. avoiding added nesting.

> 
> > +            } else if (request->type != NBD_CMD_WRITE) {
> >                   /*
> >                    * For now, we don't support payloads on other
> >                    * commands; but we can keep the connection alive.
> > @@ -2540,6 +2633,9 @@ static int coroutine_fn 
> > nbd_co_receive_request(NBDRequestData *req, NBDRequest *
> >           valid_flags |= NBD_CMD_FLAG_NO_HOLE | NBD_CMD_FLAG_FAST_ZERO;
> >       } else if (request->type == NBD_CMD_BLOCK_STATUS) {
> >           valid_flags |= NBD_CMD_FLAG_REQ_ONE;
> > +        if (client->extended_headers && client->contexts.count) {
> > +            valid_flags |= NBD_CMD_FLAG_PAYLOAD_LEN;
> > +        }
> >       }
> >       if (request->flags & ~valid_flags) {
> >           error_setg(errp, "unsupported flags for command %s (got 0x%x)",

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org




reply via email to

[Prev in Thread] Current Thread [Next in Thread]