diff mbox

[v4,10/14] nbd: Less allocation during NBD_OPT_LIST

Message ID 1466892954-8684-11-git-send-email-eblake@redhat.com
State New
Headers show

Commit Message

Eric Blake June 25, 2016, 10:15 p.m. UTC
Since we know that the maximum name we are willing to accept
is small enough to stack-allocate, rework the iteration over
NBD_OPT_LIST responses to reuse a stack buffer rather than
allocating every time.  Furthermore, we don't even have to
allocate if we know the server's length doesn't match what
we are searching for.

Signed-off-by: Eric Blake <eblake@redhat.com>

---
v4: rebase
v3: tweak commit message
---
 nbd/client.c | 144 +++++++++++++++++++++++++++++------------------------------
 1 file changed, 72 insertions(+), 72 deletions(-)

Comments

Paolo Bonzini June 27, 2016, 12:16 p.m. UTC | #1
On 26/06/2016 00:15, Eric Blake wrote:
> +/* Return -1 if unrecoverable error occurs
> , 0 if NBD_OPT_LIST is unsupported,

These two should return errp != NULL and negative errno.

> 1 if iteration is done
> 2 to keep looking,
> and 3 if * this entry matches @want. */

These three should return errp == NULL.  Please change the function so
that the return value signifies "need another call", and a bool*
argument is set to true if the name matches.

Paolo

> +static int nbd_receive_list(QIOChannel *ioc, const char *want, Error **errp)
Eric Blake July 18, 2016, 11:31 p.m. UTC | #2
On 06/27/2016 06:16 AM, Paolo Bonzini wrote:
> 
> 
> On 26/06/2016 00:15, Eric Blake wrote:
>> +/* Return -1 if unrecoverable error occurs
>> , 0 if NBD_OPT_LIST is unsupported,
> 
> These two should return errp != NULL and negative errno.

Not quite.  It returns 0 if nbd_handle_reply_err() detected an
unsupported command, in which case errp is unset.  And the caller
doesn't really care what value is sent (the return value is not -errno,
merely negative).

> 
>> 1 if iteration is done
>> 2 to keep looking,
>> and 3 if * this entry matches @want. */
> 
> These three should return errp == NULL.  Please change the function so
> that the return value signifies "need another call", and a bool*
> argument is set to true if the name matches.

By that argument, you want:

old code					 new code
-1, errp set - done iterating			 -1, *match unspecified
0, unsupported - done iterating, assume match	 0, *match set
1, done iterating, match determined earlier	 0, *match unchanged
2, still iterating, this round didn't match	 1, *match unchanged
3, still iterating, this round matched		 1, *match set

where the caller starts with *match clear, iterates as long as the
result is positive, then when the result is -1 returns an error, or when
the result is 0 it returns the current setting in *match.

I can try that alternative flow, and will post an update shortly.  I
still think this is worth having in 2.7 if it is not too late (as it was
originally posted before soft freeze), but if it misses hard freeze
tomorrow, then it is 2.8 material.
diff mbox

Patch

diff --git a/nbd/client.c b/nbd/client.c
index 004dec6..0d0ce05 100644
--- a/nbd/client.c
+++ b/nbd/client.c
@@ -249,14 +249,17 @@  static int nbd_handle_reply_err(QIOChannel *ioc, nbd_opt_reply *reply,
     return result;
 }

-static int nbd_receive_list(QIOChannel *ioc, char **name, Error **errp)
+/* Return -1 if unrecoverable error occurs, 0 if NBD_OPT_LIST is
+ * unsupported, 1 if iteration is done, 2 to keep looking, and 3 if
+ * this entry matches @want. */
+static int nbd_receive_list(QIOChannel *ioc, const char *want, Error **errp)
 {
     nbd_opt_reply reply;
     uint32_t len;
     uint32_t namelen;
+    char name[NBD_MAX_NAME_SIZE + 1];
     int error;

-    *name = NULL;
     if (nbd_receive_option_reply(ioc, NBD_OPT_LIST, &reply, errp) < 0) {
         return -1;
     }
@@ -272,105 +275,102 @@  static int nbd_receive_list(QIOChannel *ioc, char **name, Error **errp)
             nbd_send_opt_abort(ioc);
             return -1;
         }
-    } else if (reply.type == NBD_REP_SERVER) {
-        if (len < sizeof(namelen) || len > NBD_MAX_BUFFER_SIZE) {
-            error_setg(errp, "incorrect option length %"PRIu32, len);
-            nbd_send_opt_abort(ioc);
-            return -1;
-        }
-        if (read_sync(ioc, &namelen, sizeof(namelen)) != sizeof(namelen)) {
-            error_setg(errp, "failed to read option name length");
-            nbd_send_opt_abort(ioc);
-            return -1;
-        }
-        namelen = be32_to_cpu(namelen);
-        len -= sizeof(namelen);
-        if (len < namelen) {
-            error_setg(errp, "incorrect option name length");
-            nbd_send_opt_abort(ioc);
-            return -1;
-        }
-        if (namelen > NBD_MAX_NAME_SIZE) {
-            error_setg(errp, "export name length too long %" PRIu32, namelen);
-            nbd_send_opt_abort(ioc);
-            return -1;
-        }
-
-        *name = g_new0(char, namelen + 1);
-        if (read_sync(ioc, *name, namelen) != namelen) {
-            error_setg(errp, "failed to read export name");
-            g_free(*name);
-            *name = NULL;
-            nbd_send_opt_abort(ioc);
-            return -1;
-        }
-        (*name)[namelen] = '\0';
-        len -= namelen;
-        if (drop_sync(ioc, len) != len) {
-            error_setg(errp, "failed to read export description");
-            g_free(*name);
-            *name = NULL;
-            nbd_send_opt_abort(ioc);
-            return -1;
-        }
-    } else {
+        return 1;
+    } else if (reply.type != NBD_REP_SERVER) {
         error_setg(errp, "Unexpected reply type %" PRIx32 " expected %x",
                    reply.type, NBD_REP_SERVER);
         nbd_send_opt_abort(ioc);
         return -1;
     }
-    return 1;
+
+    if (len < sizeof(namelen) || len > NBD_MAX_BUFFER_SIZE) {
+        error_setg(errp, "incorrect option length %"PRIu32, len);
+        nbd_send_opt_abort(ioc);
+        return -1;
+    }
+    if (read_sync(ioc, &namelen, sizeof(namelen)) != sizeof(namelen)) {
+        error_setg(errp, "failed to read option name length");
+        nbd_send_opt_abort(ioc);
+        return -1;
+    }
+    namelen = be32_to_cpu(namelen);
+    len -= sizeof(namelen);
+    if (len < namelen) {
+        error_setg(errp, "incorrect option name length");
+        nbd_send_opt_abort(ioc);
+        return -1;
+    }
+    if (namelen != strlen(want)) {
+        if (drop_sync(ioc, len) != len) {
+            error_setg(errp, "failed to skip export name with wrong length");
+            nbd_send_opt_abort(ioc);
+            return -1;
+        }
+        return 2;
+    }
+
+    assert(namelen < sizeof(name));
+    if (read_sync(ioc, name, namelen) != namelen) {
+        error_setg(errp, "failed to read export name");
+        nbd_send_opt_abort(ioc);
+        return -1;
+    }
+    name[namelen] = '\0';
+    len -= namelen;
+    if (drop_sync(ioc, len) != len) {
+        error_setg(errp, "failed to read export description");
+        nbd_send_opt_abort(ioc);
+        return -1;
+    }
+    return strcmp(name, want) == 0 ? 3 : 2;
 }


+/* Return -1 on failure, 0 if wantname is an available export. */
 static int nbd_receive_query_exports(QIOChannel *ioc,
                                      const char *wantname,
                                      Error **errp)
 {
     bool foundExport = false;

-    TRACE("Querying export list");
+    TRACE("Querying export list for '%s'", wantname);
     if (nbd_send_option_request(ioc, NBD_OPT_LIST, 0, NULL, errp) < 0) {
         return -1;
     }

     TRACE("Reading available export names");
     while (1) {
-        char *name = NULL;
-        int ret = nbd_receive_list(ioc, &name, errp);
+        int ret = nbd_receive_list(ioc, wantname, errp);

-        if (ret < 0) {
-            g_free(name);
-            name = NULL;
+        switch (ret) {
+        default:
+            /* Server gave unexpected reply */
+            assert(ret < 0);
             return -1;
-        }
-        if (ret == 0) {
+        case 0:
             /* Server doesn't support export listing, so
              * we will just assume an export with our
              * wanted name exists */
-            foundExport = true;
-            break;
-        }
-        if (name == NULL) {
-            TRACE("End of export name list");
+            return 0;
+        case 1:
+            /* Done iterating. */
+            if (!foundExport) {
+                error_setg(errp, "No export with name '%s' available",
+                           wantname);
+                nbd_send_opt_abort(ioc);
+                return -1;
+            }
+            return 0;
+        case 2:
+            /* Wasn't this one, keep going. */
             break;
-        }
-        if (g_str_equal(name, wantname)) {
+        case 3:
+            /* Found a match, but must finish parsing reply. */
+            TRACE("Found desired export name '%s'", wantname);
             foundExport = true;
-            TRACE("Found desired export name '%s'", name);
-        } else {
-            TRACE("Ignored export name '%s'", name);
+            break;
         }
-        g_free(name);
-    }
-
-    if (!foundExport) {
-        error_setg(errp, "No export with name '%s' available", wantname);
-        nbd_send_opt_abort(ioc);
-        return -1;
     }
-
-    return 0;
 }

 static QIOChannel *nbd_receive_starttls(QIOChannel *ioc,