[Nbd] [PATCHv2] Add support for NBD_CMD_WRITE_ZEROES
This is a very basic implementation which could do with optimisation.
Signed-off-by: Alex Bligh <alex@...872...>
---
nbd-server.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
nbd.h | 5 +++-
2 files changed, 87 insertions(+), 6 deletions(-)
This is marked PATCH/RFC as it's only compile tested as I don't yet have
a client that interoperates with it.
Changes from v1:
* Add support for NBD_CMD_FLAG_NO_HOLE
diff --git a/nbd-server.c b/nbd-server.c
index 4edb883..1b3bb48 100644
--- a/nbd-server.c
+++ b/nbd-server.c
@@ -261,6 +261,8 @@ static inline const char * getcommandname(uint64_t command) {
return "NBD_CMD_FLUSH";
case NBD_CMD_TRIM:
return "NBD_CMD_TRIM";
+ case NBD_CMD_WRITE_ZEROES:
+ return "NBD_CMD_WRITE_ZEROES";
default:
return "UNKNOWN";
}
@@ -1183,6 +1185,39 @@ int expwrite(off_t a, char *buf, size_t len, CLIENT *client, int fua) {
return 0;
}
+
+/**
+ * Write an amount of zeroes at a given offset to the right file.
+ * This routine could be optimised by not calling expwrite. However,
+ * this is by far the simplest way to do it.
+ *
+ * @param req the request
+ * @param client The client we're going to write for.
+ * @return 0 on success, nonzero on failure
+ **/
+int expwrite_zeroes(struct nbd_request* req, CLIENT* client) {
+ off_t a = req->from;
+ size_t len = req->len;
+ int fua = !!(req->type & NBD_CMD_FLAG_FUA);
+ size_t maxsize = 64LL*1024LL*1024LL;
+ /* use calloc() as sadly MAP_ANON is apparently not POSIX standard */
+ char *buf = calloc (1, maxsize);
+ int ret;
+ while (len > 0) {
+ size_t l = len;
+ if (l > maxsize)
+ l = maxsize;
+ ret = expwrite(a, buf, l, client, fua);
+ if (ret) {
+ free(buf);
+ return ret;
+ }
+ len -= l;
+ }
+ free(buf);
+ return 0;
+}
+
/**
* Flush data to a client
*
@@ -1382,7 +1417,7 @@ CLIENT* negotiate(int net, GArray* servers) {
void send_export_info(CLIENT* client) {
uint64_t size_host = htonll((u64)(client->exportsize));
- uint16_t flags = NBD_FLAG_HAS_FLAGS;
+ uint16_t flags = NBD_FLAG_HAS_FLAGS | NBD_FLAG_SEND_WRITE_ZEROES;
if (write(client->net, &size_host, 8) < 0)
err("Negotiation failed/9: %m");
@@ -1481,7 +1516,7 @@ static void handle_write(CLIENT* client, struct nbd_request* req, void* data) {
DEBUG("[WRITE to READONLY!]");
rep.error = nbd_errno(EPERM);
} else {
- if(expwrite(req->from, data, req->len, client, (req->type &~NBD_CMD_MASK_COMMAND))) {
+ if(expwrite(req->from, data, req->len, client, !!(req->type & NBD_CMD_FLAG_FUA))) {
DEBUG("Write failed: %m");
rep.error = nbd_errno(errno);
}
@@ -1517,13 +1552,38 @@ static void handle_trim(CLIENT* client, struct nbd_request* req) {
pthread_mutex_unlock(&(client->lock));
}
+static void handle_write_zeroes(CLIENT* client, struct nbd_request* req) {
+ struct nbd_reply rep;
+ DEBUG("handling write_zeroes request\n");
+ setup_reply(&rep, req);
+ if(expwrite_zeroes(req, client)) {
+ DEBUG("Write_zeroes failed: %m");
+ rep.error = nbd_errno(errno);
+ } else {
+ // if the device supports trim, and we are not told we cannot
+ // make a hole, then make a hole. Note that this hole-making
+ // may fail silently, and may not be complete (depending on
+ // alignment etc.) which is why we need to do the zero write
+ // first (sadly).
+ if ((client->server->flags & F_TRIM) && !(req->type & NBD_CMD_FLAG_NO_HOLE)) {
+ if (exptrim(req, client)) {
+ DEBUG("Trim after write_zeroes failed: %m");
+ rep.error = nbd_errno(errno);
+ }
+ }
+ }
+ pthread_mutex_lock(&(client->lock));
+ writeit(client->net, &rep, sizeof rep);
+ pthread_mutex_unlock(&(client->lock));
+}
+
static void handle_request(gpointer data, gpointer user_data) {
struct work_package* package = (struct work_package*) data;
uint32_t type = package->req->type & NBD_CMD_MASK_COMMAND;
uint32_t flags = package->req->type & ~NBD_CMD_MASK_COMMAND;
struct nbd_reply rep;
- if(flags & ~NBD_CMD_FLAG_FUA) {
+ if(flags & ~(NBD_CMD_FLAG_FUA | NBD_CMD_FLAG_NO_HOLE)) {
msg(LOG_ERR, "E: received invalid flag %d on command %d, ignoring", flags, type);
goto error;
}
@@ -1541,6 +1601,9 @@ static void handle_request(gpointer data, gpointer user_data) {
case NBD_CMD_TRIM:
handle_trim(package->client, package->req);
break;
+ case NBD_CMD_WRITE_ZEROES:
+ handle_write_zeroes(package->client, package->req);
+ break;
default:
msg(LOG_ERR, "E: received unknown command %d of type, ignoring", package->req->type);
goto error;
@@ -1646,7 +1709,7 @@ int mainloop(CLIENT *client) {
memcpy(reply.handle, request.handle, sizeof(reply.handle));
if ((command==NBD_CMD_WRITE) || (command==NBD_CMD_READ) ||
- (command==NBD_CMD_TRIM)) {
+ (command==NBD_CMD_TRIM) || (command==NBD_CMD_WRITE_ZEROES)) {
if (request.from + len < request.from) { // 64 bit overflow!!
DEBUG("[Number too large!]");
ERROR(client, reply, EINVAL);
@@ -1655,7 +1718,7 @@ int mainloop(CLIENT *client) {
if (((off_t)request.from + len) > client->exportsize) {
DEBUG("[RANGE!]");
- ERROR(client, reply, (command==NBD_CMD_WRITE) ? ENOSPC : EINVAL);
+ ERROR(client, reply, (command==NBD_CMD_WRITE || command==NBD_CMD_WRITE_ZEROES) ? ENOSPC : EINVAL);
continue;
}
@@ -1762,6 +1825,21 @@ int mainloop(CLIENT *client) {
SEND(client->net, reply);
continue;
+ case NBD_CMD_WRITE_ZEROES:
+ if ((client->server->flags & F_READONLY) ||
+ (client->server->flags & F_AUTOREADONLY)) {
+ DEBUG("[WRITE_ZEROES to READONLY!]");
+ ERROR(client, reply, EPERM);
+ continue;
+ }
+ if (expwrite_zeroes(&request, client)) {
+ DEBUG("Write zeroes failed: %m");
+ ERROR(client, reply, errno);
+ continue;
+ }
+ SEND(client->net, reply);
+ continue;
+
default:
DEBUG ("Ignoring unknown command\n");
continue;
diff --git a/nbd.h b/nbd.h
index 732c605..a5a1946 100644
--- a/nbd.h
+++ b/nbd.h
@@ -34,12 +34,14 @@ enum {
NBD_CMD_WRITE = 1,
NBD_CMD_DISC = 2,
NBD_CMD_FLUSH = 3,
- NBD_CMD_TRIM = 4
+ NBD_CMD_TRIM = 4,
+ NBD_CMD_WRITE_ZEROES = 6
};
#define NBD_CMD_MASK_COMMAND 0x0000ffff
#define NBD_CMD_SHIFT (16)
#define NBD_CMD_FLAG_FUA ((1 << 0) << NBD_CMD_SHIFT)
+#define NBD_CMD_FLAG_NO_HOLE ((1 << 1) << NBD_CMD_SHIFT)
/* values for flags field */
#define NBD_FLAG_HAS_FLAGS (1 << 0) /* Flags are there */
@@ -48,6 +50,7 @@ enum {
#define NBD_FLAG_SEND_FUA (1 << 3) /* Send FUA (Force Unit Access) */
#define NBD_FLAG_ROTATIONAL (1 << 4) /* Use elevator algorithm - rotational media */
#define NBD_FLAG_SEND_TRIM (1 << 5) /* Send TRIM (discard) */
+#define NBD_FLAG_SEND_WRITE_ZEROES (1 << 6) /* Send NBD_CMD_WRITE_ZEROES */
#define nbd_cmd(req) ((req)->cmd[0])
--
1.9.1
Reply to: