[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

[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: