*/
#include "block/nbd.h"
-#include "block/block.h"
+#include "sysemu/block-backend.h"
#include "block/coroutine.h"
__FILE__, __FUNCTION__, __LINE__, ## __VA_ARGS__); \
} while(0)
-/* This is all part of the "official" NBD API */
+/* This is all part of the "official" NBD API.
+ *
+ * The most up-to-date documentation is available at:
+ * https://github.com/yoe/nbd/blob/master/doc/proto.txt
+ */
#define NBD_REQUEST_SIZE (4 + 4 + 8 + 8 + 4)
#define NBD_REPLY_SIZE (4 + 4 + 8)
#define NBD_REPLY_MAGIC 0x67446698
#define NBD_OPTS_MAGIC 0x49484156454F5054LL
#define NBD_CLIENT_MAGIC 0x0000420281861253LL
+#define NBD_REP_MAGIC 0x3e889045565a9LL
#define NBD_SET_SOCK _IO(0xab, 0)
#define NBD_SET_BLKSIZE _IO(0xab, 1)
#define NBD_SET_TIMEOUT _IO(0xab, 9)
#define NBD_SET_FLAGS _IO(0xab, 10)
-#define NBD_OPT_EXPORT_NAME (1 << 0)
+#define NBD_OPT_EXPORT_NAME (1)
+#define NBD_OPT_ABORT (2)
+#define NBD_OPT_LIST (3)
/* Definitions for opaque data types */
int refcount;
void (*close)(NBDExport *exp);
- BlockDriverState *bs;
+ BlockBackend *blk;
char *name;
off_t dev_offset;
off_t size;
uint32_t nbdflags;
QTAILQ_HEAD(, NBDClient) clients;
QTAILQ_ENTRY(NBDExport) next;
+
+ AioContext *ctx;
};
static QTAILQ_HEAD(, NBDExport) exports = QTAILQ_HEAD_INITIALIZER(exports);
CoMutex send_lock;
Coroutine *send_coroutine;
+ bool can_read;
+
QTAILQ_ENTRY(NBDClient) next;
int nb_requests;
bool closing;
/* That's all folks */
+static void nbd_set_handlers(NBDClient *client);
+static void nbd_unset_handlers(NBDClient *client);
+static void nbd_update_can_read(NBDClient *client);
+
ssize_t nbd_wr_sync(int fd, void *buffer, size_t size, bool do_read)
{
size_t offset = 0;
err = socket_error();
/* recoverable error */
- if (err == EINTR || (offset > 0 && err == EAGAIN)) {
+ if (err == EINTR || (offset > 0 && (err == EAGAIN || err == EWOULDBLOCK))) {
continue;
}
*/
-static int nbd_receive_options(NBDClient *client)
+static int nbd_send_rep(int csock, uint32_t type, uint32_t opt)
{
- int csock = client->sock;
- char name[256];
- uint32_t tmp, length;
uint64_t magic;
- int rc;
+ uint32_t len;
- /* Client sends:
- [ 0 .. 3] reserved (0)
- [ 4 .. 11] NBD_OPTS_MAGIC
- [12 .. 15] NBD_OPT_EXPORT_NAME
- [16 .. 19] length
- [20 .. xx] export name (length bytes)
- */
-
- rc = -EINVAL;
- if (read_sync(csock, &tmp, sizeof(tmp)) != sizeof(tmp)) {
- LOG("read failed");
- goto fail;
+ magic = cpu_to_be64(NBD_REP_MAGIC);
+ if (write_sync(csock, &magic, sizeof(magic)) != sizeof(magic)) {
+ LOG("write failed (rep magic)");
+ return -EINVAL;
}
- TRACE("Checking reserved");
- if (tmp != 0) {
- LOG("Bad reserved received");
- goto fail;
+ opt = cpu_to_be32(opt);
+ if (write_sync(csock, &opt, sizeof(opt)) != sizeof(opt)) {
+ LOG("write failed (rep opt)");
+ return -EINVAL;
}
-
- if (read_sync(csock, &magic, sizeof(magic)) != sizeof(magic)) {
- LOG("read failed");
- goto fail;
+ type = cpu_to_be32(type);
+ if (write_sync(csock, &type, sizeof(type)) != sizeof(type)) {
+ LOG("write failed (rep type)");
+ return -EINVAL;
}
- TRACE("Checking reserved");
- if (magic != be64_to_cpu(NBD_OPTS_MAGIC)) {
- LOG("Bad magic received");
- goto fail;
+ len = cpu_to_be32(0);
+ if (write_sync(csock, &len, sizeof(len)) != sizeof(len)) {
+ LOG("write failed (rep data length)");
+ return -EINVAL;
}
+ return 0;
+}
- if (read_sync(csock, &tmp, sizeof(tmp)) != sizeof(tmp)) {
- LOG("read failed");
- goto fail;
+static int nbd_send_rep_list(int csock, NBDExport *exp)
+{
+ uint64_t magic, name_len;
+ uint32_t opt, type, len;
+
+ name_len = strlen(exp->name);
+ magic = cpu_to_be64(NBD_REP_MAGIC);
+ if (write_sync(csock, &magic, sizeof(magic)) != sizeof(magic)) {
+ LOG("write failed (magic)");
+ return -EINVAL;
+ }
+ opt = cpu_to_be32(NBD_OPT_LIST);
+ if (write_sync(csock, &opt, sizeof(opt)) != sizeof(opt)) {
+ LOG("write failed (opt)");
+ return -EINVAL;
}
- TRACE("Checking option");
- if (tmp != be32_to_cpu(NBD_OPT_EXPORT_NAME)) {
- LOG("Bad option received");
- goto fail;
+ type = cpu_to_be32(NBD_REP_SERVER);
+ if (write_sync(csock, &type, sizeof(type)) != sizeof(type)) {
+ LOG("write failed (reply type)");
+ return -EINVAL;
}
+ len = cpu_to_be32(name_len + sizeof(len));
+ if (write_sync(csock, &len, sizeof(len)) != sizeof(len)) {
+ LOG("write failed (length)");
+ return -EINVAL;
+ }
+ len = cpu_to_be32(name_len);
+ if (write_sync(csock, &len, sizeof(len)) != sizeof(len)) {
+ LOG("write failed (length)");
+ return -EINVAL;
+ }
+ if (write_sync(csock, exp->name, name_len) != name_len) {
+ LOG("write failed (buffer)");
+ return -EINVAL;
+ }
+ return 0;
+}
- if (read_sync(csock, &length, sizeof(length)) != sizeof(length)) {
- LOG("read failed");
- goto fail;
+static int nbd_handle_list(NBDClient *client, uint32_t length)
+{
+ int csock;
+ NBDExport *exp;
+
+ csock = client->sock;
+ if (length) {
+ return nbd_send_rep(csock, NBD_REP_ERR_INVALID, NBD_OPT_LIST);
}
+
+ /* For each export, send a NBD_REP_SERVER reply. */
+ QTAILQ_FOREACH(exp, &exports, next) {
+ if (nbd_send_rep_list(csock, exp)) {
+ return -EINVAL;
+ }
+ }
+ /* Finish with a NBD_REP_ACK. */
+ return nbd_send_rep(csock, NBD_REP_ACK, NBD_OPT_LIST);
+}
+
+static int nbd_handle_export_name(NBDClient *client, uint32_t length)
+{
+ int rc = -EINVAL, csock = client->sock;
+ char name[256];
+
+ /* Client sends:
+ [20 .. xx] export name (length bytes)
+ */
TRACE("Checking length");
- length = be32_to_cpu(length);
if (length > 255) {
LOG("Bad length received");
goto fail;
QTAILQ_INSERT_TAIL(&client->exp->clients, client, next);
nbd_export_get(client->exp);
-
- TRACE("Option negotiation succeeded.");
rc = 0;
fail:
return rc;
}
+static int nbd_receive_options(NBDClient *client)
+{
+ while (1) {
+ int csock = client->sock, ret;
+ uint32_t tmp, length;
+ uint64_t magic;
+
+ /* Client sends:
+ [ 0 .. 3] client flags
+ [ 4 .. 11] NBD_OPTS_MAGIC
+ [12 .. 15] NBD option
+ [16 .. 19] length
+ ... Rest of request
+ */
+
+ if (read_sync(csock, &tmp, sizeof(tmp)) != sizeof(tmp)) {
+ LOG("read failed");
+ return -EINVAL;
+ }
+ TRACE("Checking client flags");
+ tmp = be32_to_cpu(tmp);
+ if (tmp != 0 && tmp != NBD_FLAG_C_FIXED_NEWSTYLE) {
+ LOG("Bad client flags received");
+ return -EINVAL;
+ }
+
+ if (read_sync(csock, &magic, sizeof(magic)) != sizeof(magic)) {
+ LOG("read failed");
+ return -EINVAL;
+ }
+ TRACE("Checking opts magic");
+ if (magic != be64_to_cpu(NBD_OPTS_MAGIC)) {
+ LOG("Bad magic received");
+ return -EINVAL;
+ }
+
+ if (read_sync(csock, &tmp, sizeof(tmp)) != sizeof(tmp)) {
+ LOG("read failed");
+ return -EINVAL;
+ }
+
+ if (read_sync(csock, &length, sizeof(length)) != sizeof(length)) {
+ LOG("read failed");
+ return -EINVAL;
+ }
+ length = be32_to_cpu(length);
+
+ TRACE("Checking option");
+ switch (be32_to_cpu(tmp)) {
+ case NBD_OPT_LIST:
+ ret = nbd_handle_list(client, length);
+ if (ret < 0) {
+ return ret;
+ }
+ break;
+
+ case NBD_OPT_ABORT:
+ return -EINVAL;
+
+ case NBD_OPT_EXPORT_NAME:
+ return nbd_handle_export_name(client, length);
+
+ default:
+ tmp = be32_to_cpu(tmp);
+ LOG("Unsupported option 0x%x", tmp);
+ nbd_send_rep(client->sock, NBD_REP_ERR_UNSUP, tmp);
+ return -EINVAL;
+ }
+ }
+}
+
static int nbd_send_negotiate(NBDClient *client)
{
int csock = client->sock;
cpu_to_be16w((uint16_t*)(buf + 26), client->exp->nbdflags | myflags);
} else {
cpu_to_be64w((uint64_t*)(buf + 8), NBD_OPTS_MAGIC);
+ cpu_to_be16w((uint16_t *)(buf + 16), NBD_FLAG_FIXED_NEWSTYLE);
}
if (client->exp) {
goto fail;
}
rc = nbd_receive_options(client);
- if (rc < 0) {
+ if (rc != 0) {
LOG("option negotiation failed");
goto fail;
}
}
int nbd_receive_negotiate(int csock, const char *name, uint32_t *flags,
- off_t *size, size_t *blocksize)
+ off_t *size, size_t *blocksize, Error **errp)
{
char buf[256];
uint64_t magic, s;
rc = -EINVAL;
if (read_sync(csock, buf, 8) != 8) {
- LOG("read failed");
+ error_setg(errp, "Failed to read data");
goto fail;
}
buf[8] = '\0';
if (strlen(buf) == 0) {
- LOG("server connection closed");
+ error_setg(errp, "Server connection closed unexpectedly");
goto fail;
}
qemu_isprint(buf[7]) ? buf[7] : '.');
if (memcmp(buf, "NBDMAGIC", 8) != 0) {
- LOG("Invalid magic received");
+ error_setg(errp, "Invalid magic received");
goto fail;
}
if (read_sync(csock, &magic, sizeof(magic)) != sizeof(magic)) {
- LOG("read failed");
+ error_setg(errp, "Failed to read magic");
goto fail;
}
magic = be64_to_cpu(magic);
TRACE("Checking magic (opts_magic)");
if (magic != NBD_OPTS_MAGIC) {
- LOG("Bad magic received");
+ if (magic == NBD_CLIENT_MAGIC) {
+ error_setg(errp, "Server does not support export names");
+ } else {
+ error_setg(errp, "Bad magic received");
+ }
goto fail;
}
if (read_sync(csock, &tmp, sizeof(tmp)) != sizeof(tmp)) {
- LOG("flags read failed");
+ error_setg(errp, "Failed to read server flags");
goto fail;
}
*flags = be16_to_cpu(tmp) << 16;
/* reserved for future use */
if (write_sync(csock, &reserved, sizeof(reserved)) !=
sizeof(reserved)) {
- LOG("write failed (reserved)");
+ error_setg(errp, "Failed to read reserved field");
goto fail;
}
/* write the export name */
magic = cpu_to_be64(magic);
if (write_sync(csock, &magic, sizeof(magic)) != sizeof(magic)) {
- LOG("write failed (magic)");
+ error_setg(errp, "Failed to send export name magic");
goto fail;
}
opt = cpu_to_be32(NBD_OPT_EXPORT_NAME);
if (write_sync(csock, &opt, sizeof(opt)) != sizeof(opt)) {
- LOG("write failed (opt)");
+ error_setg(errp, "Failed to send export name option number");
goto fail;
}
namesize = cpu_to_be32(strlen(name));
if (write_sync(csock, &namesize, sizeof(namesize)) !=
sizeof(namesize)) {
- LOG("write failed (namesize)");
+ error_setg(errp, "Failed to send export name length");
goto fail;
}
if (write_sync(csock, (char*)name, strlen(name)) != strlen(name)) {
- LOG("write failed (name)");
+ error_setg(errp, "Failed to send export name");
goto fail;
}
} else {
TRACE("Checking magic (cli_magic)");
if (magic != NBD_CLIENT_MAGIC) {
- LOG("Bad magic received");
+ if (magic == NBD_OPTS_MAGIC) {
+ error_setg(errp, "Server requires an export name");
+ } else {
+ error_setg(errp, "Bad magic received");
+ }
goto fail;
}
}
if (read_sync(csock, &s, sizeof(s)) != sizeof(s)) {
- LOG("read failed");
+ error_setg(errp, "Failed to read export length");
goto fail;
}
*size = be64_to_cpu(s);
if (!name) {
if (read_sync(csock, flags, sizeof(*flags)) != sizeof(*flags)) {
- LOG("read failed (flags)");
+ error_setg(errp, "Failed to read export flags");
goto fail;
}
*flags = be32_to_cpup(flags);
} else {
if (read_sync(csock, &tmp, sizeof(tmp)) != sizeof(tmp)) {
- LOG("read failed (tmp)");
+ error_setg(errp, "Failed to read export flags");
goto fail;
}
*flags |= be32_to_cpu(tmp);
}
if (read_sync(csock, &buf, 124) != 124) {
- LOG("read failed (buf)");
+ error_setg(errp, "Failed to read reserved block");
goto fail;
}
rc = 0;
{
if (--client->refcount == 0) {
/* The last reference should be dropped by client->close,
- * which is called by nbd_client_close.
+ * which is called by client_close.
*/
assert(client->closing);
- qemu_set_fd_handler2(client->sock, NULL, NULL, NULL, NULL);
+ nbd_unset_handlers(client);
close(client->sock);
client->sock = -1;
if (client->exp) {
}
}
-void nbd_client_close(NBDClient *client)
+static void client_close(NBDClient *client)
{
if (client->closing) {
return;
assert(client->nb_requests <= MAX_NBD_REQUESTS - 1);
client->nb_requests++;
+ nbd_update_can_read(client);
req = g_slice_new0(NBDRequest);
nbd_client_get(client);
}
g_slice_free(NBDRequest, req);
- if (client->nb_requests-- == MAX_NBD_REQUESTS) {
- qemu_notify_event();
- }
+ client->nb_requests--;
+ nbd_update_can_read(client);
nbd_client_put(client);
}
-NBDExport *nbd_export_new(BlockDriverState *bs, off_t dev_offset,
- off_t size, uint32_t nbdflags,
- void (*close)(NBDExport *))
+static void blk_aio_attached(AioContext *ctx, void *opaque)
+{
+ NBDExport *exp = opaque;
+ NBDClient *client;
+
+ TRACE("Export %s: Attaching clients to AIO context %p\n", exp->name, ctx);
+
+ exp->ctx = ctx;
+
+ QTAILQ_FOREACH(client, &exp->clients, next) {
+ nbd_set_handlers(client);
+ }
+}
+
+static void blk_aio_detach(void *opaque)
+{
+ NBDExport *exp = opaque;
+ NBDClient *client;
+
+ TRACE("Export %s: Detaching clients from AIO context %p\n", exp->name, exp->ctx);
+
+ QTAILQ_FOREACH(client, &exp->clients, next) {
+ nbd_unset_handlers(client);
+ }
+
+ exp->ctx = NULL;
+}
+
+NBDExport *nbd_export_new(BlockBackend *blk, off_t dev_offset, off_t size,
+ uint32_t nbdflags, void (*close)(NBDExport *),
+ Error **errp)
{
NBDExport *exp = g_malloc0(sizeof(NBDExport));
exp->refcount = 1;
QTAILQ_INIT(&exp->clients);
- exp->bs = bs;
+ exp->blk = blk;
exp->dev_offset = dev_offset;
exp->nbdflags = nbdflags;
- exp->size = size == -1 ? bdrv_getlength(bs) : size;
+ exp->size = size < 0 ? blk_getlength(blk) : size;
+ if (exp->size < 0) {
+ error_setg_errno(errp, -exp->size,
+ "Failed to determine the NBD export's length");
+ goto fail;
+ }
+ exp->size -= exp->size % BDRV_SECTOR_SIZE;
+
exp->close = close;
- bdrv_ref(bs);
+ exp->ctx = blk_get_aio_context(blk);
+ blk_ref(blk);
+ blk_add_aio_context_notifier(blk, blk_aio_attached, blk_aio_detach, exp);
+ /*
+ * NBD exports are used for non-shared storage migration. Make sure
+ * that BDRV_O_INCOMING is cleared and the image is ready for write
+ * access since the export could be available before migration handover.
+ */
+ blk_invalidate_cache(blk, NULL);
return exp;
+
+fail:
+ g_free(exp);
+ return NULL;
}
NBDExport *nbd_export_find(const char *name)
nbd_export_get(exp);
QTAILQ_FOREACH_SAFE(client, &exp->clients, next, next) {
- nbd_client_close(client);
+ client_close(client);
}
nbd_export_set_name(exp, NULL);
nbd_export_put(exp);
- if (exp->bs) {
- bdrv_unref(exp->bs);
- exp->bs = NULL;
+ if (exp->blk) {
+ blk_remove_aio_context_notifier(exp->blk, blk_aio_attached,
+ blk_aio_detach, exp);
+ blk_unref(exp->blk);
+ exp->blk = NULL;
}
}
}
}
-BlockDriverState *nbd_export_get_blockdev(NBDExport *exp)
+BlockBackend *nbd_export_get_blockdev(NBDExport *exp)
{
- return exp->bs;
+ return exp->blk;
}
void nbd_export_close_all(void)
}
}
-static int nbd_can_read(void *opaque);
-static void nbd_read(void *opaque);
-static void nbd_restart_write(void *opaque);
-
static ssize_t nbd_co_send_reply(NBDRequest *req, struct nbd_reply *reply,
int len)
{
ssize_t rc, ret;
qemu_co_mutex_lock(&client->send_lock);
- qemu_set_fd_handler2(csock, nbd_can_read, nbd_read,
- nbd_restart_write, client);
client->send_coroutine = qemu_coroutine_self();
+ nbd_set_handlers(client);
if (!len) {
rc = nbd_send_reply(csock, reply);
}
client->send_coroutine = NULL;
- qemu_set_fd_handler2(csock, nbd_can_read, nbd_read, NULL, client);
+ nbd_set_handlers(client);
qemu_co_mutex_unlock(&client->send_lock);
return rc;
}
ssize_t rc;
client->recv_coroutine = qemu_coroutine_self();
+ nbd_update_can_read(client);
+
rc = nbd_receive_request(csock, request);
if (rc < 0) {
if (rc != -EAGAIN) {
command = request->type & NBD_CMD_MASK_COMMAND;
if (command == NBD_CMD_READ || command == NBD_CMD_WRITE) {
- req->data = qemu_blockalign(client->exp->bs, request->len);
+ req->data = blk_blockalign(client->exp->blk, request->len);
}
if (command == NBD_CMD_WRITE) {
TRACE("Reading %u byte(s)", request->len);
out:
client->recv_coroutine = NULL;
+ nbd_update_can_read(client);
+
return rc;
}
struct nbd_request request;
struct nbd_reply reply;
ssize_t ret;
+ uint32_t command;
TRACE("Reading request.");
if (client->closing) {
reply.error = -ret;
goto error_reply;
}
-
- if ((request.from + request.len) > exp->size) {
+ command = request.type & NBD_CMD_MASK_COMMAND;
+ if (command != NBD_CMD_DISC && (request.from + request.len) > exp->size) {
LOG("From: %" PRIu64 ", Len: %u, Size: %" PRIu64
", Offset: %" PRIu64 "\n",
request.from, request.len,
goto invalid_request;
}
- switch (request.type & NBD_CMD_MASK_COMMAND) {
+ switch (command) {
case NBD_CMD_READ:
TRACE("Request type is READ");
if (request.type & NBD_CMD_FLAG_FUA) {
- ret = bdrv_co_flush(exp->bs);
+ ret = blk_co_flush(exp->blk);
if (ret < 0) {
LOG("flush failed");
reply.error = -ret;
}
}
- ret = bdrv_read(exp->bs, (request.from + exp->dev_offset) / 512,
- req->data, request.len / 512);
+ ret = blk_read(exp->blk,
+ (request.from + exp->dev_offset) / BDRV_SECTOR_SIZE,
+ req->data, request.len / BDRV_SECTOR_SIZE);
if (ret < 0) {
LOG("reading from file failed");
reply.error = -ret;
TRACE("Writing to device");
- ret = bdrv_write(exp->bs, (request.from + exp->dev_offset) / 512,
- req->data, request.len / 512);
+ ret = blk_write(exp->blk,
+ (request.from + exp->dev_offset) / BDRV_SECTOR_SIZE,
+ req->data, request.len / BDRV_SECTOR_SIZE);
if (ret < 0) {
LOG("writing to file failed");
reply.error = -ret;
}
if (request.type & NBD_CMD_FLAG_FUA) {
- ret = bdrv_co_flush(exp->bs);
+ ret = blk_co_flush(exp->blk);
if (ret < 0) {
LOG("flush failed");
reply.error = -ret;
case NBD_CMD_FLUSH:
TRACE("Request type is FLUSH");
- ret = bdrv_co_flush(exp->bs);
+ ret = blk_co_flush(exp->blk);
if (ret < 0) {
LOG("flush failed");
reply.error = -ret;
break;
case NBD_CMD_TRIM:
TRACE("Request type is TRIM");
- ret = bdrv_co_discard(exp->bs, (request.from + exp->dev_offset) / 512,
- request.len / 512);
+ ret = blk_co_discard(exp->blk, (request.from + exp->dev_offset)
+ / BDRV_SECTOR_SIZE,
+ request.len / BDRV_SECTOR_SIZE);
if (ret < 0) {
LOG("discard failed");
reply.error = -ret;
default:
LOG("invalid request type (%u) received", request.type);
invalid_request:
- reply.error = -EINVAL;
+ reply.error = EINVAL;
error_reply:
if (nbd_co_send_reply(req, &reply, 0) < 0) {
goto out;
out:
nbd_request_put(req);
- nbd_client_close(client);
-}
-
-static int nbd_can_read(void *opaque)
-{
- NBDClient *client = opaque;
-
- return client->recv_coroutine || client->nb_requests < MAX_NBD_REQUESTS;
+ client_close(client);
}
static void nbd_read(void *opaque)
qemu_coroutine_enter(client->send_coroutine, NULL);
}
+static void nbd_set_handlers(NBDClient *client)
+{
+ if (client->exp && client->exp->ctx) {
+ aio_set_fd_handler(client->exp->ctx, client->sock,
+ client->can_read ? nbd_read : NULL,
+ client->send_coroutine ? nbd_restart_write : NULL,
+ client);
+ }
+}
+
+static void nbd_unset_handlers(NBDClient *client)
+{
+ if (client->exp && client->exp->ctx) {
+ aio_set_fd_handler(client->exp->ctx, client->sock, NULL, NULL, NULL);
+ }
+}
+
+static void nbd_update_can_read(NBDClient *client)
+{
+ bool can_read = client->recv_coroutine ||
+ client->nb_requests < MAX_NBD_REQUESTS;
+
+ if (can_read != client->can_read) {
+ client->can_read = can_read;
+ nbd_set_handlers(client);
+
+ /* There is no need to invoke aio_notify(), since aio_set_fd_handler()
+ * in nbd_set_handlers() will have taken care of that */
+ }
+}
+
NBDClient *nbd_client_new(NBDExport *exp, int csock,
void (*close)(NBDClient *))
{
client->refcount = 1;
client->exp = exp;
client->sock = csock;
- if (nbd_send_negotiate(client) < 0) {
+ client->can_read = true;
+ if (nbd_send_negotiate(client)) {
g_free(client);
return NULL;
}
client->close = close;
qemu_co_mutex_init(&client->send_lock);
- qemu_set_fd_handler2(csock, nbd_can_read, nbd_read, NULL, client);
+ nbd_set_handlers(client);
if (exp) {
QTAILQ_INSERT_TAIL(&exp->clients, client, next);