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

[Nbd] [PATCH] define error values as part of the protocol



Right now, NBD includes potentially platform-specific error values in
the protocol.

Luckily, most common error values are more or less universal: of all
errno values <= 34 (up to ERANGE), only 11 seems to differ across the
common platforms: it is EAGAIN on Windows and Linux, but it is EDEADLK
on Darwin and the *BSDs.

This patch defines a limited set of errno values that are valid for the
NBD protocol, and specifies recommendations for what error to return
in specific corner cases.  The set of errno values is roughly based on
the errors listed in the read(2) and write(2) man pages, with some
exception:

- ENOMEM is added for servers that implement copy-on-write or other
  formats that require dynamic allocation.

- EDQUOT is not part of the universal set of errors; it can be changed
  to ENOSPC on the wire format.

- EFBIG is part of the universal set of errors, but it is also changed
  to ENOSPC because it is pretty similar to ENOSPC or EDQUOT.

The server is changed to match the recommendations.

Signed-off-by: Paolo Bonzini <pbonzini@...696...>
---
 doc/proto.txt | 26 +++++++++++++++++++++++++-
 nbd-server.c  | 34 +++++++++++++++++++++++++++++++---
 2 files changed, 56 insertions(+), 4 deletions(-)

diff --git a/doc/proto.txt b/doc/proto.txt
index 1eb37de..0ca46f2 100644
--- a/doc/proto.txt
+++ b/doc/proto.txt
@@ -43,7 +43,7 @@ now, the only flag is NBD_CMD_FLAG_FUA (bit 16), "Force unit access".
 
 The reply contains three fields: a 32 bit magic number ('magic'), a 32
 bit error code ('error'; 0, unless an error occurred in which case it is
-the errno of the error on the server side), and the same 64 bit handle
+one of the error values documented below), and the same 64 bit handle
 that the corresponding request had in its 'handle' field. In case the
 reply is sent in response to a read request and the error field is 0
 (zero), the reply header is immediately followed by request.len bytes of
@@ -65,6 +65,30 @@ if present, else by fdatasync() of that file (note not all files
 in a multifile environment). NBD_CMD_FLAG_FUA will not be set
 unless NBD_FLAG_SEND_FUA is set.
 
+Error values
+------------
+
+The following error values are defined:
+
+      Integer value    Short name     Description
+      -------------------------------------------------------------
+         1             EPERM          Operation not permitted
+         5             EIO            Input/output error
+        12             ENOMEM         Cannot allocate memory
+        22             EINVAL         Invalid argument
+        28             ENOSPC         No space left on device
+
+The server should return ENOSPC if it receives a write request including
+one or more sectors beyond the size of the device.  It should return
+EINVAL if it receives a read or trim request including one or more
+sectors beyond the size of the device.  It also should map the EDQUOT
+and EFBIG errors to ENOSPC.  Finally, it should return EPERM if it
+receives a write or trim request on a read-only export.  Which error to
+return in any other case is not specified by the NBD protocol.
+
+Negotiation
+===========
+
 There are two versions of the negotiation: the 'old' style (nbd <=
 2.9.16) and the 'new' style (nbd >= 2.9.17, though due to a bug it does
 not work with anything below 2.9.18). What follows is a description of
diff --git a/nbd-server.c b/nbd-server.c
index 3075a0e..d5745f9 100644
--- a/nbd-server.c
+++ b/nbd-server.c
@@ -1554,12 +1554,33 @@ void send_export_info(CLIENT* client) {
 	}
 }
 
+static int nbd_errno(int errcode) {
+	switch (errcode) {
+	case EPERM:
+		return htonl(1);
+	case EIO:
+		return htonl(5);
+	case ENOMEM:
+		return htonl(12);
+	case EINVAL:
+		return htonl(22);
+	case EFBIG:
+	case ENOSPC:
+#ifdef EDQUOT
+	case EDQUOT:
+#endif
+		return htonl(28); // ENOSPC
+	default:
+		return htonl(22); // EINVAL
+	}
+}
+
 /** sending macro. */
 #define SEND(net,reply) { writeit( net, &reply, sizeof( reply )); \
 	if (client->transactionlogfd != -1) \
 		writeit(client->transactionlogfd, &reply, sizeof(reply)); }
 /** error macro. */
-#define ERROR(client,reply,errcode) { reply.error = htonl(errcode); SEND(client->net,reply); reply.error = 0; }
+#define ERROR(client,reply,errcode) { reply.error = nbd_errno(errcode); SEND(client->net,reply); reply.error = 0; }
 /**
  * Serve a file to a single client.
  *
@@ -1609,7 +1630,8 @@ int mainloop(CLIENT *client) {
 
 		memcpy(reply.handle, request.handle, sizeof(reply.handle));
 
-		if ((command==NBD_CMD_WRITE) || (command==NBD_CMD_READ)) {
+		if ((command==NBD_CMD_WRITE) || (command==NBD_CMD_READ) ||
+		    (command==NBD_CMD_TRIM)) {
 			if (request.from + len < request.from) { // 64 bit overflow!!
 				DEBUG("[Number too large!]");
 				ERROR(client, reply, EINVAL);
@@ -1618,7 +1640,7 @@ int mainloop(CLIENT *client) {
 
 			if (((off_t)request.from + len) > client->exportsize) {
 				DEBUG("[RANGE!]");
-				ERROR(client, reply, EINVAL);
+				ERROR(client, reply, (command==NBD_CMD_WRITE) ? ENOSPC : EINVAL);
 				continue;
 			}
 
@@ -1711,6 +1733,12 @@ int mainloop(CLIENT *client) {
 		case NBD_CMD_TRIM:
 			/* The kernel module sets discard_zeroes_data == 0,
 			 * so it is okay to do nothing.  */
+			if ((client->server->flags & F_READONLY) ||
+			    (client->server->flags & F_AUTOREADONLY)) {
+				DEBUG("[TRIM to READONLY!]");
+				ERROR(client, reply, EPERM);
+				continue;
+			}
 			if (exptrim(&request, client)) {
 				DEBUG("Trim failed: %m");
 				ERROR(client, reply, errno);
-- 
2.3.5




Reply to: