while (!s->quit) {
assert(s->reply.handle == 0);
ret = nbd_receive_reply(s->ioc, &s->reply, &local_err);
- if (ret < 0) {
+ if (local_err) {
error_report_err(local_err);
}
if (ret <= 0) {
return 0;
}
+/* nbd_parse_blockstatus_payload
+ * support only one extent in reply and only for
+ * base:allocation context
+ */
+static int nbd_parse_blockstatus_payload(NBDClientSession *client,
+ NBDStructuredReplyChunk *chunk,
+ uint8_t *payload, uint64_t orig_length,
+ NBDExtent *extent, Error **errp)
+{
+ uint32_t context_id;
+
+ if (chunk->length != sizeof(context_id) + sizeof(*extent)) {
+ error_setg(errp, "Protocol error: invalid payload for "
+ "NBD_REPLY_TYPE_BLOCK_STATUS");
+ return -EINVAL;
+ }
+
+ context_id = payload_advance32(&payload);
+ if (client->info.meta_base_allocation_id != context_id) {
+ error_setg(errp, "Protocol error: unexpected context id %d for "
+ "NBD_REPLY_TYPE_BLOCK_STATUS, when negotiated context "
+ "id is %d", context_id,
+ client->info.meta_base_allocation_id);
+ return -EINVAL;
+ }
+
+ extent->length = payload_advance32(&payload);
+ extent->flags = payload_advance32(&payload);
+
+ if (extent->length == 0 ||
+ (client->info.min_block && !QEMU_IS_ALIGNED(extent->length,
+ client->info.min_block))) {
+ error_setg(errp, "Protocol error: server sent status chunk with "
+ "invalid length");
+ return -EINVAL;
+ }
+
+ /* The server is allowed to send us extra information on the final
+ * extent; just clamp it to the length we requested. */
+ if (extent->length > orig_length) {
+ extent->length = orig_length;
+ }
+
+ return 0;
+}
+
/* nbd_parse_error_payload
* on success @errp contains message describing nbd error reply
*/
typedef struct NBDReplyChunkIter {
int ret;
+ bool fatal;
Error *err;
bool done, only_structured;
} NBDReplyChunkIter;
{
assert(ret < 0);
- if (fatal || iter->ret == 0) {
+ if ((fatal && !iter->fatal) || iter->ret == 0) {
if (iter->ret != 0) {
error_free(iter->err);
iter->err = NULL;
}
+ iter->fatal = fatal;
iter->ret = ret;
error_propagate(&iter->err, *local_err);
} else {
return iter.ret;
}
+static int nbd_co_receive_blockstatus_reply(NBDClientSession *s,
+ uint64_t handle, uint64_t length,
+ NBDExtent *extent, Error **errp)
+{
+ NBDReplyChunkIter iter;
+ NBDReply reply;
+ void *payload = NULL;
+ Error *local_err = NULL;
+ bool received = false;
+
+ assert(!extent->length);
+ NBD_FOREACH_REPLY_CHUNK(s, iter, handle, s->info.structured_reply,
+ NULL, &reply, &payload)
+ {
+ int ret;
+ NBDStructuredReplyChunk *chunk = &reply.structured;
+
+ assert(nbd_reply_is_structured(&reply));
+
+ switch (chunk->type) {
+ case NBD_REPLY_TYPE_BLOCK_STATUS:
+ if (received) {
+ s->quit = true;
+ error_setg(&local_err, "Several BLOCK_STATUS chunks in reply");
+ nbd_iter_error(&iter, true, -EINVAL, &local_err);
+ }
+ received = true;
+
+ ret = nbd_parse_blockstatus_payload(s, &reply.structured,
+ payload, length, extent,
+ &local_err);
+ if (ret < 0) {
+ s->quit = true;
+ nbd_iter_error(&iter, true, ret, &local_err);
+ }
+ break;
+ default:
+ if (!nbd_reply_type_is_error(chunk->type)) {
+ s->quit = true;
+ error_setg(&local_err,
+ "Unexpected reply type: %d (%s) "
+ "for CMD_BLOCK_STATUS",
+ chunk->type, nbd_reply_type_lookup(chunk->type));
+ nbd_iter_error(&iter, true, -EINVAL, &local_err);
+ }
+ }
+
+ g_free(payload);
+ payload = NULL;
+ }
+
+ if (!extent->length && !iter.err) {
+ error_setg(&iter.err,
+ "Server did not reply with any status extents");
+ if (!iter.ret) {
+ iter.ret = -EIO;
+ }
+ }
+ error_propagate(errp, iter.err);
+ return iter.ret;
+}
+
static int nbd_co_request(BlockDriverState *bs, NBDRequest *request,
QEMUIOVector *write_qiov)
{
ret = nbd_co_receive_cmdread_reply(client, request.handle, offset, qiov,
&local_err);
- if (ret < 0) {
+ if (local_err) {
error_report_err(local_err);
}
return ret;
return nbd_co_request(bs, &request, NULL);
}
+int coroutine_fn nbd_client_co_block_status(BlockDriverState *bs,
+ bool want_zero,
+ int64_t offset, int64_t bytes,
+ int64_t *pnum, int64_t *map,
+ BlockDriverState **file)
+{
+ int64_t ret;
+ NBDExtent extent = { 0 };
+ NBDClientSession *client = nbd_get_client_session(bs);
+ Error *local_err = NULL;
+
+ NBDRequest request = {
+ .type = NBD_CMD_BLOCK_STATUS,
+ .from = offset,
+ .len = MIN(MIN_NON_ZERO(QEMU_ALIGN_DOWN(INT_MAX,
+ bs->bl.request_alignment),
+ client->info.max_block), bytes),
+ .flags = NBD_CMD_FLAG_REQ_ONE,
+ };
+
+ if (!client->info.base_allocation) {
+ *pnum = bytes;
+ return BDRV_BLOCK_DATA;
+ }
+
+ ret = nbd_co_send_request(bs, &request, NULL);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = nbd_co_receive_blockstatus_reply(client, request.handle, bytes,
+ &extent, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ }
+ if (ret < 0) {
+ return ret;
+ }
+
+ assert(extent.length);
+ *pnum = extent.length;
+ return (extent.flags & NBD_STATE_HOLE ? 0 : BDRV_BLOCK_DATA) |
+ (extent.flags & NBD_STATE_ZERO ? BDRV_BLOCK_ZERO : 0);
+}
+
void nbd_client_detach_aio_context(BlockDriverState *bs)
{
NBDClientSession *client = nbd_get_client_session(bs);
const char *export,
QCryptoTLSCreds *tlscreds,
const char *hostname,
+ const char *x_dirty_bitmap,
Error **errp)
{
NBDClientSession *client = nbd_get_client_session(bs);
client->info.request_sizes = true;
client->info.structured_reply = true;
+ client->info.base_allocation = true;
+ client->info.x_dirty_bitmap = g_strdup(x_dirty_bitmap);
ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), export,
tlscreds, hostname,
&client->ioc, &client->info, errp);
+ g_free(client->info.x_dirty_bitmap);
if (ret < 0) {
logout("Failed to negotiate with the NBD server\n");
return ret;
}
- if (client->info.flags & NBD_FLAG_READ_ONLY &&
- !bdrv_is_read_only(bs)) {
- error_setg(errp,
- "request for write access conflicts with read-only export");
- return -EACCES;
+ if (x_dirty_bitmap && !client->info.base_allocation) {
+ error_setg(errp, "requested x-dirty-bitmap %s not found",
+ x_dirty_bitmap);
+ ret = -EINVAL;
+ goto fail;
+ }
+ if (client->info.flags & NBD_FLAG_READ_ONLY) {
+ ret = bdrv_apply_auto_read_only(bs, "NBD export is read-only", errp);
+ if (ret < 0) {
+ goto fail;
+ }
}
if (client->info.flags & NBD_FLAG_SEND_FUA) {
bs->supported_write_flags = BDRV_REQ_FUA;
if (client->info.flags & NBD_FLAG_SEND_WRITE_ZEROES) {
bs->supported_zero_flags |= BDRV_REQ_MAY_UNMAP;
}
- if (client->info.min_block > bs->bl.request_alignment) {
- bs->bl.request_alignment = client->info.min_block;
- }
qemu_co_mutex_init(&client->send_mutex);
qemu_co_queue_init(&client->free_sema);
logout("Established connection with NBD server\n");
return 0;
+
+ fail:
+ /*
+ * We have connected, but must fail for other reasons. The
+ * connection is still blocking; send NBD_CMD_DISC as a courtesy
+ * to the server.
+ */
+ {
+ NBDRequest request = { .type = NBD_CMD_DISC };
+
+ nbd_send_request(client->ioc ?: QIO_CHANNEL(sioc), &request);
+ return ret;
+ }
}