nbd: BLOCK_STATUS for standard get_block_status function: client part
Minimal realization: only one extent in server answer is supported. Flag NBD_CMD_FLAG_REQ_ONE is used to force this behavior. Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@virtuozzo.com> Message-Id: <20180312152126.286890-6-vsementsov@virtuozzo.com> Reviewed-by: Eric Blake <eblake@redhat.com> [eblake: grammar tweaks, fix min_block check and 32-bit cap, use -1 instead of errno on failure in nbd_negotiate_simple_meta_context, ensure that block status makes progress on success] Signed-off-by: Eric Blake <eblake@redhat.com>
This commit is contained in:
parent
1e98efc029
commit
78a33ab587
@ -228,6 +228,48 @@ static int nbd_parse_offset_hole_payload(NBDStructuredReplyChunk *chunk,
|
|||||||
return 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)) ||
|
||||||
|
extent->length > orig_length)
|
||||||
|
{
|
||||||
|
error_setg(errp, "Protocol error: server sent status chunk with "
|
||||||
|
"invalid length");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* nbd_parse_error_payload
|
/* nbd_parse_error_payload
|
||||||
* on success @errp contains message describing nbd error reply
|
* on success @errp contains message describing nbd error reply
|
||||||
*/
|
*/
|
||||||
@ -642,6 +684,68 @@ static int nbd_co_receive_cmdread_reply(NBDClientSession *s, uint64_t handle,
|
|||||||
return iter.ret;
|
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,
|
static int nbd_co_request(BlockDriverState *bs, NBDRequest *request,
|
||||||
QEMUIOVector *write_qiov)
|
QEMUIOVector *write_qiov)
|
||||||
{
|
{
|
||||||
@ -784,6 +888,51 @@ int nbd_client_co_pdiscard(BlockDriverState *bs, int64_t offset, int bytes)
|
|||||||
return nbd_co_request(bs, &request, NULL);
|
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)
|
void nbd_client_detach_aio_context(BlockDriverState *bs)
|
||||||
{
|
{
|
||||||
NBDClientSession *client = nbd_get_client_session(bs);
|
NBDClientSession *client = nbd_get_client_session(bs);
|
||||||
@ -828,6 +977,7 @@ int nbd_client_init(BlockDriverState *bs,
|
|||||||
|
|
||||||
client->info.request_sizes = true;
|
client->info.request_sizes = true;
|
||||||
client->info.structured_reply = true;
|
client->info.structured_reply = true;
|
||||||
|
client->info.base_allocation = true;
|
||||||
ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), export,
|
ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), export,
|
||||||
tlscreds, hostname,
|
tlscreds, hostname,
|
||||||
&client->ioc, &client->info, errp);
|
&client->ioc, &client->info, errp);
|
||||||
|
@ -61,4 +61,10 @@ void nbd_client_detach_aio_context(BlockDriverState *bs);
|
|||||||
void nbd_client_attach_aio_context(BlockDriverState *bs,
|
void nbd_client_attach_aio_context(BlockDriverState *bs,
|
||||||
AioContext *new_context);
|
AioContext *new_context);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
#endif /* NBD_CLIENT_H */
|
#endif /* NBD_CLIENT_H */
|
||||||
|
@ -585,6 +585,7 @@ static BlockDriver bdrv_nbd = {
|
|||||||
.bdrv_detach_aio_context = nbd_detach_aio_context,
|
.bdrv_detach_aio_context = nbd_detach_aio_context,
|
||||||
.bdrv_attach_aio_context = nbd_attach_aio_context,
|
.bdrv_attach_aio_context = nbd_attach_aio_context,
|
||||||
.bdrv_refresh_filename = nbd_refresh_filename,
|
.bdrv_refresh_filename = nbd_refresh_filename,
|
||||||
|
.bdrv_co_block_status = nbd_client_co_block_status,
|
||||||
};
|
};
|
||||||
|
|
||||||
static BlockDriver bdrv_nbd_tcp = {
|
static BlockDriver bdrv_nbd_tcp = {
|
||||||
@ -604,6 +605,7 @@ static BlockDriver bdrv_nbd_tcp = {
|
|||||||
.bdrv_detach_aio_context = nbd_detach_aio_context,
|
.bdrv_detach_aio_context = nbd_detach_aio_context,
|
||||||
.bdrv_attach_aio_context = nbd_attach_aio_context,
|
.bdrv_attach_aio_context = nbd_attach_aio_context,
|
||||||
.bdrv_refresh_filename = nbd_refresh_filename,
|
.bdrv_refresh_filename = nbd_refresh_filename,
|
||||||
|
.bdrv_co_block_status = nbd_client_co_block_status,
|
||||||
};
|
};
|
||||||
|
|
||||||
static BlockDriver bdrv_nbd_unix = {
|
static BlockDriver bdrv_nbd_unix = {
|
||||||
@ -623,6 +625,7 @@ static BlockDriver bdrv_nbd_unix = {
|
|||||||
.bdrv_detach_aio_context = nbd_detach_aio_context,
|
.bdrv_detach_aio_context = nbd_detach_aio_context,
|
||||||
.bdrv_attach_aio_context = nbd_attach_aio_context,
|
.bdrv_attach_aio_context = nbd_attach_aio_context,
|
||||||
.bdrv_refresh_filename = nbd_refresh_filename,
|
.bdrv_refresh_filename = nbd_refresh_filename,
|
||||||
|
.bdrv_co_block_status = nbd_client_co_block_status,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void bdrv_nbd_init(void)
|
static void bdrv_nbd_init(void)
|
||||||
|
@ -260,6 +260,7 @@ struct NBDExportInfo {
|
|||||||
/* In-out fields, set by client before nbd_receive_negotiate() and
|
/* In-out fields, set by client before nbd_receive_negotiate() and
|
||||||
* updated by server results during nbd_receive_negotiate() */
|
* updated by server results during nbd_receive_negotiate() */
|
||||||
bool structured_reply;
|
bool structured_reply;
|
||||||
|
bool base_allocation; /* base:allocation context for NBD_CMD_BLOCK_STATUS */
|
||||||
|
|
||||||
/* Set by server results during nbd_receive_negotiate() */
|
/* Set by server results during nbd_receive_negotiate() */
|
||||||
uint64_t size;
|
uint64_t size;
|
||||||
@ -267,6 +268,8 @@ struct NBDExportInfo {
|
|||||||
uint32_t min_block;
|
uint32_t min_block;
|
||||||
uint32_t opt_block;
|
uint32_t opt_block;
|
||||||
uint32_t max_block;
|
uint32_t max_block;
|
||||||
|
|
||||||
|
uint32_t meta_base_allocation_id;
|
||||||
};
|
};
|
||||||
typedef struct NBDExportInfo NBDExportInfo;
|
typedef struct NBDExportInfo NBDExportInfo;
|
||||||
|
|
||||||
|
117
nbd/client.c
117
nbd/client.c
@ -595,6 +595,111 @@ static QIOChannel *nbd_receive_starttls(QIOChannel *ioc,
|
|||||||
return QIO_CHANNEL(tioc);
|
return QIO_CHANNEL(tioc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* nbd_negotiate_simple_meta_context:
|
||||||
|
* Set one meta context. Simple means that reply must contain zero (not
|
||||||
|
* negotiated) or one (negotiated) contexts. More contexts would be considered
|
||||||
|
* as a protocol error. It's also implied that meta-data query equals queried
|
||||||
|
* context name, so, if server replies with something different then @context,
|
||||||
|
* it considered as error too.
|
||||||
|
* return 1 for successful negotiation, context_id is set
|
||||||
|
* 0 if operation is unsupported,
|
||||||
|
* -1 with errp set for any other error
|
||||||
|
*/
|
||||||
|
static int nbd_negotiate_simple_meta_context(QIOChannel *ioc,
|
||||||
|
const char *export,
|
||||||
|
const char *context,
|
||||||
|
uint32_t *context_id,
|
||||||
|
Error **errp)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
NBDOptionReply reply;
|
||||||
|
uint32_t received_id;
|
||||||
|
bool received;
|
||||||
|
uint32_t export_len = strlen(export);
|
||||||
|
uint32_t context_len = strlen(context);
|
||||||
|
uint32_t data_len = sizeof(export_len) + export_len +
|
||||||
|
sizeof(uint32_t) + /* number of queries */
|
||||||
|
sizeof(context_len) + context_len;
|
||||||
|
char *data = g_malloc(data_len);
|
||||||
|
char *p = data;
|
||||||
|
|
||||||
|
stl_be_p(p, export_len);
|
||||||
|
memcpy(p += sizeof(export_len), export, export_len);
|
||||||
|
stl_be_p(p += export_len, 1);
|
||||||
|
stl_be_p(p += sizeof(uint32_t), context_len);
|
||||||
|
memcpy(p += sizeof(context_len), context, context_len);
|
||||||
|
|
||||||
|
ret = nbd_send_option_request(ioc, NBD_OPT_SET_META_CONTEXT, data_len, data,
|
||||||
|
errp);
|
||||||
|
g_free(data);
|
||||||
|
if (ret < 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nbd_receive_option_reply(ioc, NBD_OPT_SET_META_CONTEXT, &reply,
|
||||||
|
errp) < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = nbd_handle_reply_err(ioc, &reply, errp);
|
||||||
|
if (ret <= 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reply.type == NBD_REP_META_CONTEXT) {
|
||||||
|
char *name;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
if (nbd_read(ioc, &received_id, sizeof(received_id), errp) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
be32_to_cpus(&received_id);
|
||||||
|
|
||||||
|
len = reply.length - sizeof(received_id);
|
||||||
|
name = g_malloc(len + 1);
|
||||||
|
if (nbd_read(ioc, name, len, errp) < 0) {
|
||||||
|
g_free(name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
name[len] = '\0';
|
||||||
|
if (strcmp(context, name)) {
|
||||||
|
error_setg(errp, "Failed to negotiate meta context '%s', server "
|
||||||
|
"answered with different context '%s'", context,
|
||||||
|
name);
|
||||||
|
g_free(name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
g_free(name);
|
||||||
|
|
||||||
|
received = true;
|
||||||
|
|
||||||
|
/* receive NBD_REP_ACK */
|
||||||
|
if (nbd_receive_option_reply(ioc, NBD_OPT_SET_META_CONTEXT, &reply,
|
||||||
|
errp) < 0)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = nbd_handle_reply_err(ioc, &reply, errp);
|
||||||
|
if (ret <= 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reply.type != NBD_REP_ACK) {
|
||||||
|
error_setg(errp, "Unexpected reply type %" PRIx32 " expected %x",
|
||||||
|
reply.type, NBD_REP_ACK);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (received) {
|
||||||
|
*context_id = received_id;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
|
int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
|
||||||
QCryptoTLSCreds *tlscreds, const char *hostname,
|
QCryptoTLSCreds *tlscreds, const char *hostname,
|
||||||
@ -606,10 +711,12 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
|
|||||||
int rc;
|
int rc;
|
||||||
bool zeroes = true;
|
bool zeroes = true;
|
||||||
bool structured_reply = info->structured_reply;
|
bool structured_reply = info->structured_reply;
|
||||||
|
bool base_allocation = info->base_allocation;
|
||||||
|
|
||||||
trace_nbd_receive_negotiate(tlscreds, hostname ? hostname : "<null>");
|
trace_nbd_receive_negotiate(tlscreds, hostname ? hostname : "<null>");
|
||||||
|
|
||||||
info->structured_reply = false;
|
info->structured_reply = false;
|
||||||
|
info->base_allocation = false;
|
||||||
rc = -EINVAL;
|
rc = -EINVAL;
|
||||||
|
|
||||||
if (outioc) {
|
if (outioc) {
|
||||||
@ -700,6 +807,16 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name,
|
|||||||
info->structured_reply = result == 1;
|
info->structured_reply = result == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (info->structured_reply && base_allocation) {
|
||||||
|
result = nbd_negotiate_simple_meta_context(
|
||||||
|
ioc, name, "base:allocation",
|
||||||
|
&info->meta_base_allocation_id, errp);
|
||||||
|
if (result < 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
info->base_allocation = result == 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Try NBD_OPT_GO first - if it works, we are done (it
|
/* Try NBD_OPT_GO first - if it works, we are done (it
|
||||||
* also gives us a good message if the server requires
|
* also gives us a good message if the server requires
|
||||||
* TLS). If it is not available, fall back to
|
* TLS). If it is not available, fall back to
|
||||||
|
Loading…
Reference in New Issue
Block a user