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