[Libguestfs] [PATCH libnbd] api: Get rid of nbd_connection.

Richard W.M. Jones rjones at redhat.com
Thu May 23 15:03:29 UTC 2019


There is now simple a handle (struct nbd_handle).  It manages a single
connection to the server.  If you want to do multi-conn you have to
manage it yourself.
---
 examples/threaded-reads-and-writes.c          |  47 ++-
 generator/generator                           | 375 ++++++------------
 generator/states-connect.c                    |  84 ++--
 generator/states-issue-command.c              |  64 +--
 generator/states-magic.c                      |  10 +-
 generator/states-newstyle-opt-export-name.c   |  34 +-
 generator/states-newstyle-opt-go.c            |  78 ++--
 .../states-newstyle-opt-set-meta-context.c    | 104 ++---
 generator/states-newstyle-opt-starttls.c      |  56 +--
 .../states-newstyle-opt-structured-reply.c    |  42 +-
 generator/states-newstyle.c                   |  22 +-
 generator/states-oldstyle.c                   |  22 +-
 generator/states-reply-simple.c               |  14 +-
 generator/states-reply-structured.c           | 114 +++---
 generator/states-reply.c                      |  26 +-
 generator/states.c                            |  54 +--
 lib/aio.c                                     |  52 +--
 lib/connect.c                                 | 152 +++----
 lib/crypto.c                                  |  60 +--
 lib/disconnect.c                              |  30 +-
 lib/flags.c                                   |  34 +-
 lib/handle.c                                  | 233 ++---------
 lib/internal.h                                |  45 +--
 lib/poll.c                                    |  63 ++-
 lib/rw.c                                      | 230 +++--------
 25 files changed, 770 insertions(+), 1275 deletions(-)

diff --git a/examples/threaded-reads-and-writes.c b/examples/threaded-reads-and-writes.c
index 880bfc4..a1c605a 100644
--- a/examples/threaded-reads-and-writes.c
+++ b/examples/threaded-reads-and-writes.c
@@ -55,9 +55,6 @@ static int64_t exportsize;
 /* Number of commands we issue (per thread). */
 #define NR_CYCLES 10000
 
-/* The single NBD handle.  This contains NR_MULTI_CONN connections. */
-static struct nbd_handle *nbd;
-
 struct thread_status {
   size_t i;                     /* Thread index, 0 .. NR_MULTI_CONN-1 */
   int status;                   /* Return status. */
@@ -70,6 +67,7 @@ static void *start_thread (void *arg);
 int
 main (int argc, char *argv[])
 {
+  struct nbd_handle *nbd;
   pthread_t threads[NR_MULTI_CONN];
   struct thread_status status[NR_MULTI_CONN];
   size_t i;
@@ -88,12 +86,8 @@ main (int argc, char *argv[])
     fprintf (stderr, "%s\n", nbd_get_error ());
     exit (EXIT_FAILURE);
   }
-  if (nbd_set_multi_conn (nbd, NR_MULTI_CONN) == -1) {
-    fprintf (stderr, "%s\n", nbd_get_error ());
-    exit (EXIT_FAILURE);
-  }
 
-  /* Connect all connections synchronously as this is simpler. */
+  /* Connect first to check if the server supports writes and multi-conn. */
   if (argc == 2) {
     if (nbd_connect_unix (nbd, argv[1]) == -1) {
       fprintf (stderr, "%s\n", nbd_get_error ());
@@ -124,6 +118,8 @@ main (int argc, char *argv[])
     exit (EXIT_FAILURE);
   }
 
+  nbd_close (nbd);
+
   /* Start the worker threads, one per connection. */
   for (i = 0; i < NR_MULTI_CONN; ++i) {
     status[i].i = i;
@@ -159,13 +155,6 @@ main (int argc, char *argv[])
       most_in_flight = status[i].most_in_flight;
   }
 
-  if (nbd_shutdown (nbd) == -1) {
-    fprintf (stderr, "%s\n", nbd_get_error ());
-    exit (EXIT_FAILURE);
-  }
-
-  nbd_close (nbd);
-
   /* Make sure the number of requests that were required matches what
    * we expect.
    */
@@ -181,6 +170,7 @@ main (int argc, char *argv[])
 static void *
 start_thread (void *arg)
 {
+  struct nbd_handle *nbd;
   struct pollfd fds[1];
   struct thread_status *status = arg;
   struct nbd_connection *conn;
@@ -192,8 +182,24 @@ start_thread (void *arg)
   int dir, r, cmd;
   bool want_to_send;
 
-  /* The single thread "owns" the connection. */
-  conn = nbd_get_connection (nbd, status->i);
+  nbd = nbd_create ();
+  if (nbd == NULL) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+
+  if (argc == 2) {
+    if (nbd_connect_unix (nbd, argv[1]) == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+  }
+  else {
+    if (nbd_connect_tcp (nbd, argv[1], argv[2]) == -1) {
+      fprintf (stderr, "%s\n", nbd_get_error ());
+      exit (EXIT_FAILURE);
+    }
+  }
 
   for (i = 0; i < sizeof buf; ++i)
     buf[i] = rand ();
@@ -277,6 +283,13 @@ start_thread (void *arg)
     }
   }
 
+  if (nbd_shutdown (nbd) == -1) {
+    fprintf (stderr, "%s\n", nbd_get_error ());
+    exit (EXIT_FAILURE);
+  }
+
+  nbd_close (nbd);
+
   printf ("thread %zu: finished OK\n", status->i);
 
   status->status = 0;
diff --git a/generator/generator b/generator/generator
index 19af09b..5a9bf5a 100755
--- a/generator/generator
+++ b/generator/generator
@@ -41,10 +41,7 @@ open Printf
  * of an external event.  That code is not in this file, it's
  * in [generator/states*.c].
  *
- * The state machine applies to connections, not handles.  A
- * handle can contain more than one connection if using multi-conn.
- *
- * Each connection starts in the top level START state.
+ * Each handle starts in the top level START state.
  *
  * When you enter a state, the associated C code for that state
  * runs.  If the C code calls SET_NEXT_STATE then the connection
@@ -151,7 +148,7 @@ let rec state_machine = [
   State {
     default_state with
     name = "START";
-    comment = "Connection after being initially created";
+    comment = "Handle after being initially created";
     external_events = [ CmdCreate, "";
                         CmdConnectSockAddr, "CONNECT.START";
                         CmdConnectTCP, "CONNECT_TCP.START";
@@ -804,6 +801,8 @@ and arg =
 | BytesIn of string * string (* byte array + size passed in to the function *)
 | BytesOut of string * string(* byte array + size specified by caller,
                               written by the function *)
+| BytesPersistIn of string * string (* same as above, but buffer persists *)
+| BytesPersistOut of string * string
 | Callback of string * arg list (* callback function returning void *)
 | Int of string            (* small int *)
 | Int64 of string          (* 64 bit signed int *)
@@ -827,7 +826,7 @@ and ret =
 let default_call = { args = []; ret = RErr; is_locked = true;
                      shortdesc = ""; longdesc = "" }
 
-(* Calls on [nbd_handle *nbd] *)
+(* Calls - first parameter [struct nbd_handle *nbd] is implicit. *)
 let handle_calls = [
   "set_debug", {
     default_call with
@@ -887,33 +886,6 @@ use the empty string.";
 Get the export name associated with the handle.";
   };
 
-  "set_multi_conn", {
-    default_call with
-    args = [ UInt "multi_conn" ]; ret = RErr;
-    shortdesc = "enable or disable multi-conn and set nr connections";
-    longdesc = "\
-NBD can make multiple connections, if the server supports it.
-The C<multi_conn> parameter controls whether this feature is
-enabled (if E<gt> 1) or disabled (if C<1>).  The parameter
-passed must not be C<0>.  Usually small powers of 2 (eg. 2, 4, 8)
-will provide increments in performance.  Some servers do not
-support this feature and libnbd will fail on read-write connections
-to these servers if this setting is E<gt> 1.
-
-At present, libnbd assumes that all connections will report the
-same capabilities; a server that behaves otherwise may trigger
-unexpected behavior.";
-  };
-
-  "get_multi_conn", {
-    default_call with
-    args = []; ret = RInt;
-    shortdesc = "get the multi-conn setting";
-    longdesc = "\
-Get the multi-conn setting for this handle.  This is always E<ge> 1,
-where 1 means multi-conn is not enabled.";
-  };
-
   "set_tls", {
     default_call with
     args = [Int "tls"]; ret = RErr;
@@ -1072,8 +1044,7 @@ C<\"qemu:dirty-bitmap:...\"> for qemu-nbd
     longdesc = "\
 Connect (synchronously) over the named Unix domain socket (C<sockpath>)
 to an NBD server running on the same machine.  This call returns
-when the connection has been made.  If multi-conn is enabled, this
-returns when all of the connections are connected.";
+when the connection has been made.";
   };
 
   "connect_tcp", {
@@ -1085,8 +1056,7 @@ Connect (synchronously) to the NBD server listening on
 C<hostname:port>.  The C<port> may be a port name such
 as C<\"nbd\">, or it may be a port number as a string
 such as C<\"10809\">.  This call returns when the connection
-has been made.  If multi-conn is enabled, this returns when
-all of the connections are connected.";
+has been made.";
   };
 
   "connect_command", {
@@ -1169,21 +1139,17 @@ connected to and completed the handshake with the server.";
     args = []; ret = RBool;
     shortdesc = "does the server support multi-conn?";
     longdesc = "\
-Returns true if the server supports multi-conn
-(see C<nbd_set_multi_conn>).  Returns false if
-the server does not.  Can return an error if we have not
-connected to and completed the handshake with the server.
+Returns true if the server supports multi-conn.  Returns
+false if the server does not.  Can return an error if we
+have not connected to and completed the handshake with
+the server.
 
-Note that you I<cannot> set multi-conn on the handle
-to E<gt> 1, connect, and then check this setting, because
-if the server does not support multi-conn the connection
-can fail.  Instead you should connect with multi-conn
-set to C<1> (the default), check this setting, then if
-multi-conn is found to be supported you can call
-C<nbd_set_multi_conn> with a larger value to increase
-the number of connection objects, then call one of the
-C<nbd_connect*> functions again on the handle to connect
-the remaining connections.";
+It is not safe to open multiple handles connecting to the
+same server if you will write to the server and the
+server does not advertize multi-conn support.  The safe
+way to check for this is to open one connection, check
+this flag is true, then open further connections as
+required.";
   };
 
   "can_cache", {
@@ -1231,10 +1197,7 @@ Issue a read command to the NBD server for the range starting
 at C<offset> and ending at C<offset> + C<count> - 1.  NBD
 can only read all or nothing using this call.  The call
 returns when the data has been read fully into C<buf> or there is an
-error.
-
-If multi-conn is enabled, the command is issued on the next
-ready connection, picked in a round-robin manner.";
+error.";
   };
 
   "pwrite", {
@@ -1252,10 +1215,7 @@ acknowledged by the server, or there is an error.
 The C<flags> parameter may be C<0> for no flags, or may contain
 C<LIBNBD_CMD_FLAG_FUA> meaning that the server should not
 return until the data has been committed to permanent storage
-(if that is supported - some servers cannot do this).
-
-If multi-conn is enabled, the command is issued on the next
-ready connection, picked in a round-robin manner.";
+(if that is supported - some servers cannot do this).";
   };
 
   "shutdown", {
@@ -1295,10 +1255,7 @@ or there is an error.
 The C<flags> parameter may be C<0> for no flags, or may contain
 C<LIBNBD_CMD_FLAG_FUA> meaning that the server should not
 return until the data has been committed to permanent storage
-(if that is supported - some servers cannot do this).
-
-If multi-conn is enabled, the command is issued on the next
-ready connection, picked in a round-robin manner.";
+(if that is supported - some servers cannot do this).";
   };
 
   "cache", {
@@ -1330,10 +1287,7 @@ or there is an error.
 The C<flags> parameter may be C<0> for no flags, or may contain
 C<LIBNBD_CMD_FLAG_FUA> meaning that the server should not
 return until the data has been committed to permanent storage
-(if that is supported - some servers cannot do this).
-
-If multi-conn is enabled, the command is issued on the next
-ready connection, picked in a round-robin manner.";
+(if that is supported - some servers cannot do this).";
   };
 
   "block_status", {
@@ -1402,10 +1356,7 @@ internally by synchronous API calls.  It is mainly useful as
 an example of how you might integrate libnbd with your own
 main loop, rather than being intended as something you would use.";
   };
-]
 
-(* Calls on [nbd_connection *conn] *)
-let connection_calls = [
   "aio_connect", {
     default_call with
     args = [ SockAddrAndLen ("addr", "addrlen") ]; ret = RErr;
@@ -1448,7 +1399,7 @@ on the connection.";
 
   "aio_pread", {
     default_call with
-    args = [ BytesOut ("buf", "count"); UInt64 "offset" ];
+    args = [ BytesPersistOut ("buf", "count"); UInt64 "offset" ];
     ret = RInt64;
     shortdesc = "read from the NBD server";
     longdesc = "\
@@ -1461,7 +1412,7 @@ C<buf> is valid until the command has completed.";
 
   "aio_pwrite", {
     default_call with
-    args = [ BytesIn ("buf", "count"); UInt64 "offset"; UInt32 "flags" ];
+    args = [ BytesPersistIn ("buf", "count"); UInt64 "offset"; UInt32 "flags" ];
     ret = RInt64;
     shortdesc = "write to the NBD server";
     longdesc = "\
@@ -1623,9 +1574,9 @@ connection is writable.";
     shortdesc = "check if the connection has just been created";
     longdesc = "\
 Return true if this connection has just been created.  This
-is the state before the connection object has started
-connecting to a server.  In this state the handle can start
-to be connected by calling functions such as C<nbd_aio_connect>.";
+is the state before the handle has started connecting to a
+server.  In this state the handle can start to be connected
+by calling functions such as C<nbd_aio_connect>.";
   };
 
   "aio_is_connecting", {
@@ -1635,7 +1586,7 @@ to be connected by calling functions such as C<nbd_aio_connect>.";
     longdesc = "\
 Return true if this connection is connecting to the server
 or in the process of handshaking and negotiating options
-which happens before the connection object becomes ready to
+which happens before the handle becomes ready to
 issue commands (see C<nbd_aio_is_ready>).";
   };
 
@@ -1670,9 +1621,8 @@ is processing them), but libnbd is not processing them.";
     shortdesc = "check if the connection is dead";
     longdesc = "\
 Return true if the connection has encountered a fatal
-error and is dead.  In this state the connection (or whole handle)
-may only be closed.  There is no way to recover a handle from
-the dead state.";
+error and is dead.  In this state the handle may only be closed.
+There is no way to recover a handle from the dead state.";
   };
 
   "aio_is_closed", {
@@ -1681,8 +1631,8 @@ the dead state.";
     shortdesc = "check if the connection is closed";
     longdesc = "\
 Return true if the connection has closed.  There is no way to
-reconnect a closed connection.  Instead you must recreate the
-connection object or close the whole handle.";
+reconnect a closed connection.  Instead you must close the
+whole handle.";
   };
 
   "aio_command_completed", {
@@ -2149,7 +2099,6 @@ let generate_lib_states_c () =
       pr "/* %s: %s */\n" display_name comment;
       pr "static int\n";
       pr "_enter_%s (struct nbd_handle *h,\n" state_enum;
-      pr "             struct nbd_connection *conn,\n";
       pr "             enum state *next_state,\n";
       pr "             bool *blocked)\n";
       pr "{\n";
@@ -2162,19 +2111,17 @@ let generate_lib_states_c () =
       pr "}\n";
       pr "\n";
       pr "static int\n";
-      pr "enter_%s (struct nbd_handle *h, struct nbd_connection *conn,\n"
-         state_enum;
-      pr "            bool *blocked)\n";
+      pr "enter_%s (struct nbd_handle *h, bool *blocked)\n" state_enum;
       pr "{\n";
       pr "  int r;\n";
       pr "  enum state next_state = %s;\n" state_enum;
       pr "\n";
-      pr "  r = _enter_%s (h, conn, &next_state, blocked);\n" state_enum;
-      pr "  if (conn->state != next_state) {\n";
-      pr "    debug (h, \"conn %%\" PRIi64 \": transition: %%s -> %%s\",\n";
-      pr "           conn->id, \"%s\",\n" display_name;
+      pr "  r = _enter_%s (h, &next_state, blocked);\n" state_enum;
+      pr "  if (h->state != next_state) {\n";
+      pr "    debug (h, \"transition: %%s -> %%s\",\n";
+      pr "           \"%s\",\n" display_name;
       pr "           nbd_internal_state_short_string (next_state));\n";
-      pr "    conn->state = next_state;\n";
+      pr "    h->state = next_state;\n";
       pr "  }\n";
       pr "  return r;\n";
       pr "}\n";
@@ -2183,14 +2130,13 @@ let generate_lib_states_c () =
 
   pr "/* Run the state machine based on an external event until it would block. */\n";
   pr "int\n";
-  pr "nbd_internal_run (struct nbd_handle *h, struct nbd_connection *conn,\n";
-  pr "                  enum external_event ev)\n";
+  pr "nbd_internal_run (struct nbd_handle *h, enum external_event ev)\n";
   pr "{\n";
   pr "  int r;\n";
   pr "  bool blocked;\n";
   pr "\n";
   pr "  /* Validate and handle the external event. */\n";
-  pr "  switch (conn->state)\n";
+  pr "  switch (h->state)\n";
   pr "  {\n";
   List.iter (
     fun ({ parsed = { display_name; state_enum; events } } as state) ->
@@ -2202,9 +2148,9 @@ let generate_lib_states_c () =
           fun (e, next_state) ->
             pr "    case %s:\n" (c_string_of_external_event e);
             if state != next_state then (
-              pr "      conn->state = %s;\n" next_state.parsed.state_enum;
-              pr "      debug (h, \"conn %%\" PRIi64 \": event %%s: %%s -> %%s\",\n";
-              pr "             conn->id, \"%s\", \"%s\", \"%s\");\n"
+              pr "      h->state = %s;\n" next_state.parsed.state_enum;
+              pr "      debug (h, \"event %%s: %%s -> %%s\",\n";
+              pr "             \"%s\", \"%s\", \"%s\");\n"
                  (string_of_external_event e)
                  display_name next_state.parsed.display_name;
             );
@@ -2218,7 +2164,7 @@ let generate_lib_states_c () =
   pr "  }\n";
   pr "\n";
   pr "  set_error (0, \"external event %%d is invalid in state %%s\",\n";
-  pr "             ev, nbd_internal_state_short_string (conn->state));\n";
+  pr "             ev, nbd_internal_state_short_string (h->state));\n";
   pr "  return -1;\n";
   pr "\n";
   pr " ok:\n";
@@ -2226,12 +2172,12 @@ let generate_lib_states_c () =
   pr "    blocked = true;\n";
   pr "\n";
   pr "    /* Run a single step. */\n";
-  pr "    switch (conn->state)\n";
+  pr "    switch (h->state)\n";
   pr "    {\n";
   List.iter (
     fun { parsed = { state_enum } } ->
       pr "    case %s:\n" state_enum;
-      pr "      r = enter_%s (h, conn, &blocked);\n" state_enum;
+      pr "      r = enter_%s (h, &blocked);\n" state_enum;
       pr "      break;\n"
   ) states;
   pr "    default:\n";
@@ -2246,11 +2192,11 @@ let generate_lib_states_c () =
 
   pr "/* Returns whether in the current state read or write would be valid. */\n";
   pr "int\n";
-  pr "nbd_unlocked_aio_get_direction (struct nbd_connection *conn)\n";
+  pr "nbd_unlocked_aio_get_direction (struct nbd_handle *h)\n";
   pr "{\n";
   pr "  int r = 0;\n";
   pr "\n";
-  pr "  switch (conn->state)\n";
+  pr "  switch (h->state)\n";
   pr "  {\n";
   List.iter (
     fun ({ parsed = { state_enum; events } }) ->
@@ -2292,9 +2238,9 @@ let generate_lib_states_c () =
   pr "\n";
 
   pr "const char *\n";
-  pr "nbd_unlocked_connection_state (struct nbd_connection *conn)\n";
+  pr "nbd_unlocked_connection_state (struct nbd_handle *h)\n";
   pr "{\n";
-  pr "  switch (conn->state)\n";
+  pr "  switch (h->state)\n";
   pr "  {\n";
   List.iter (
     fun ({ comment; parsed = { display_name; state_enum } }) ->
@@ -2372,12 +2318,9 @@ let generate_lib_libnbd_syms () =
   pr "  global:\n";
   pr "    nbd_create;\n";
   pr "    nbd_close;\n";
-  pr "    nbd_get_connection;\n";
   pr "    nbd_get_errno;\n";
   pr "    nbd_get_error;\n";
-  pr "    nbd_connection_close;\n";
   List.iter (fun (name, _) -> pr "    nbd_%s;\n" name) handle_calls;
-  List.iter (fun (name, _) -> pr "    nbd_%s;\n" name) connection_calls;
   pr "\n";
   pr "  # Everything else is hidden.\n";
   pr "  local: *;\n";
@@ -2388,6 +2331,8 @@ let rec name_of_arg = function
 | Bool n -> [n]
 | BytesIn (n, len) -> [n; len]
 | BytesOut (n, len) -> [n; len]
+| BytesPersistIn (n, len) -> [n; len]
+| BytesPersistOut (n, len) -> [n; len]
 | Callback (n, _) -> [n]
 | Int n -> [n]
 | Int64 n -> [n]
@@ -2400,17 +2345,12 @@ let rec name_of_arg = function
 | UInt32 n -> [n]
 | UInt64 n -> [n]
 
-let rec print_c_arg_list ?handle args =
+let rec print_c_arg_list ?(handle = false) args =
   pr "(";
   let comma = ref false in
-  (match handle with
-   | None -> ()
-   | Some (htype, hname) ->
-      comma := true;
-      pr "struct nbd_%s *" htype;
-      match hname with
-      | None -> ()
-      | Some hname -> pr "%s" hname
+  if handle then (
+    comma := true;
+    pr "struct nbd_handle *h";
   );
   List.iter (
     fun arg ->
@@ -2420,8 +2360,10 @@ let rec print_c_arg_list ?handle args =
       | ArrayAndLen (UInt32 n, len) -> pr "uint32_t *%s, size_t %s" n len
       | ArrayAndLen _ -> assert false
       | Bool n -> pr "bool %s" n
-      | BytesIn (n, len) -> pr "const void *%s, size_t %s" n len
-      | BytesOut (n, len) -> pr "void *%s, size_t %s" n len
+      | BytesIn (n, len)
+      | BytesPersistIn (n, len) -> pr "const void *%s, size_t %s" n len
+      | BytesOut (n, len)
+      | BytesPersistOut (n, len) -> pr "void *%s, size_t %s" n len
       | Callback (n, args) -> pr "void (*%s) " n; print_c_arg_list args
       | Int n -> pr "int %s" n
       | Int64 n -> pr "int64_t %s" n
@@ -2437,7 +2379,7 @@ let rec print_c_arg_list ?handle args =
   ) args;
   pr ")"
 
-let print_call name htype args ret =
+let print_call name args ret =
   (match ret with
    | RBool
    | RErr
@@ -2448,11 +2390,11 @@ let print_call name htype args ret =
    | RString -> pr "char *"
   );
   pr "nbd_%s " name;
-  print_c_arg_list ~handle:(htype, None) args
+  print_c_arg_list ~handle:true args
 
-let print_extern name htype args ret =
+let print_extern name args ret =
   pr "extern ";
-  print_call name htype args ret;
+  print_call name args ret;
   pr ";\n"
 
 let generate_include_libnbd_h () =
@@ -2466,7 +2408,6 @@ let generate_include_libnbd_h () =
   pr "#include <sys/socket.h>\n";
   pr "\n";
   pr "struct nbd_handle;\n";
-  pr "struct nbd_connection;\n";
   pr "\n";
   pr "#define LIBNBD_AIO_DIRECTION_READ  1\n";
   pr "#define LIBNBD_AIO_DIRECTION_WRITE 2\n";
@@ -2481,17 +2422,9 @@ let generate_include_libnbd_h () =
   pr "extern int nbd_get_errno (void);\n";
   pr "\n";
   List.iter (
-    fun (name, { args; ret }) -> print_extern name "handle" args ret
+    fun (name, { args; ret }) -> print_extern name args ret
   ) handle_calls;
   pr "\n";
-  pr "extern struct nbd_connection *nbd_get_connection (struct nbd_handle *h,\n";
-  pr "                                                  unsigned i);\n";
-  pr "extern int nbd_connection_close (struct nbd_connection *conn);\n";
-  pr "\n";
-  List.iter (
-    fun (name, { args; ret }) -> print_extern name "connection" args ret
-  ) connection_calls;
-  pr "\n";
   pr "#endif /* LIBNBD_H */\n"
 
 let generate_lib_unlocked_h () =
@@ -2502,21 +2435,16 @@ let generate_lib_unlocked_h () =
   pr "\n";
   List.iter (
     fun (name, { args; ret }) ->
-      print_extern ("unlocked_" ^ name) "handle" args ret
+      print_extern ("unlocked_" ^ name) args ret
   ) handle_calls;
   pr "\n";
-  List.iter (
-    fun (name, { args; ret }) ->
-      print_extern ("unlocked_" ^ name) "connection" args ret
-  ) connection_calls;
-  pr "\n";
   pr "#endif /* LIBNBD_UNLOCKED_H */\n"
 
 (* Generate wrappers around each API call which are a place to
  * grab the thread mutex (lock) and do logging.
  *)
 let generate_lib_api_c () =
-  let print_wrapper name htype hname lock args ret =
+  let print_wrapper name args ret =
     (match ret with
      | RBool
      | RErr
@@ -2527,7 +2455,7 @@ let generate_lib_api_c () =
      | RString -> pr "char *\n"
     );
     pr "nbd_%s " name;
-    print_c_arg_list ~handle:(htype, Some hname) args;
+    print_c_arg_list ~handle:true args;
     pr "\n";
     pr "{\n";
     (match ret with
@@ -2540,13 +2468,13 @@ let generate_lib_api_c () =
      | RString -> pr "  char *ret;\n"
     );
     pr "\n";
-    pr "  pthread_mutex_lock (&%s);\n" lock;
+    pr "  pthread_mutex_lock (&h->lock);\n";
     pr "  nbd_internal_reset_error (\"nbd_%s\");\n" name;
-    pr "  ret = nbd_unlocked_%s (%s" name hname;
+    pr "  ret = nbd_unlocked_%s (h" name;
     let argnames = List.flatten (List.map name_of_arg args) in
     List.iter (pr ", %s") argnames;
     pr ");\n";
-    pr "  pthread_mutex_unlock (&%s);\n" lock;
+    pr "  pthread_mutex_unlock (&h->lock);\n";
     pr "  return ret;\n";
     pr "}\n";
     pr "\n";
@@ -2566,20 +2494,15 @@ let generate_lib_api_c () =
   pr "\n";
   List.iter (
     fun (name, { args; ret }) ->
-      print_wrapper name "handle" "h" "h->lock" args ret
-  ) handle_calls;
-  pr "\n";
-  List.iter (
-    fun (name, { args; ret }) ->
-      print_wrapper name "connection" "conn" "conn->h->lock" args ret
-  ) connection_calls
+      print_wrapper name args ret
+  ) handle_calls
 
-let print_api htype (name, { args; ret; shortdesc; longdesc }) =
+let print_api (name, { args; ret; shortdesc; longdesc }) =
   pr "=head2 %s —\n" name;
   pr "%s\n" shortdesc;
   pr "\n";
   pr " ";
-  print_call name htype args ret;
+  print_call name args ret;
   pr "\n";
   pr "\n";
   pr "%s\n" longdesc;
@@ -2654,7 +2577,7 @@ with libnbd(3).
 This opaque structure describes an NBD client handle.  It may
 contain one or more connections to the NBD server.
 
- struct nbd_connection *conn;
+ struct nbd_handle *h;
 
 This opaque structure describes an NBD connection to a server
 (eg. over a Unix domain socket or TCP port).  There is usually
@@ -2686,7 +2609,7 @@ returned here.
 On error this returns C<NULL>.  See L<libnbd(3)/ERROR HANDLING>
 for how to get further details of the error.
 
- int nbd_connection_close (struct nbd_connection *conn);
+ int nbd_connection_close (struct nbd_handle *h);
 
 Close and reopen the connection.  This closes the connection
 object and frees all resources associated with it.  Then it
@@ -2726,21 +2649,10 @@ C<E<lt>errno.hE<gt>>.
 
 ";
 
-  pr "=head1 HANDLE CALLS\n";
-  pr "\n";
-  pr "These are calls where the first parameter is always\n";
-  pr "C<struct nbd_handle *>.\n";
-  pr "\n";
-
-  List.iter (print_api "handle") handle_calls;
-
-  pr "=head1 CONNECTION CALLS\n";
-  pr "\n";
-  pr "These are calls where the first parameter is always\n";
-  pr "C<struct nbd_connection *>.\n";
+  pr "=head1 API CALLS\n";
   pr "\n";
 
-  List.iter (print_api "connection") connection_calls;
+  List.iter print_api handle_calls;
 
   pr "\
 =head1 SEE ALSO
@@ -2785,21 +2697,13 @@ get_handle (PyObject *obj)
   return (struct nbd_handle *) PyCapsule_GetPointer(obj, \"nbd_handle\");
 }
 
-static inline struct nbd_connection *
-get_connection (PyObject *obj)
-{
-  assert (obj);
-  assert (obj != Py_None);
-  return (struct nbd_connection *) PyCapsule_GetPointer(obj, \"nbd_connection\");
-}
 ";
 
   List.iter (
     fun name ->
       pr "extern PyObject *nbd_internal_py_%s (PyObject *self, PyObject *args);\n"
          name;
-  ) ([ "create"; "close"; "get_connection"; "connection_close" ] @
-       List.map fst (handle_calls @ connection_calls));
+  ) ([ "create"; "close" ] @ List.map fst handle_calls);
 
   pr "\n";
   pr "#endif /* LIBNBD_METHODS_H */\n"
@@ -2825,8 +2729,7 @@ let generate_python_libnbdmod_c () =
     fun name ->
       pr "  { (char *) \"%s\", nbd_internal_py_%s, METH_VARARGS, NULL },\n"
          name name;
-  ) ([ "create"; "close"; "get_connection"; "connection_close" ] @
-       List.map fst (handle_calls @ connection_calls));
+  ) ([ "create"; "close" ] @ List.map fst handle_calls);
   pr "  { NULL, NULL, 0, NULL }\n";
   pr "};\n";
   pr "\n";
@@ -2858,7 +2761,7 @@ PyInit_libnbdmod (void)
 }
 "
 
-let print_python_binding name { args; ret } htype hname =
+let print_python_binding name { args; ret } =
   (* Functions with a callback parameter are special because we
    * have to generate a wrapper function which translates the
    * callback parameters back to Python.  This only supports one
@@ -2891,7 +2794,8 @@ let print_python_binding name { args; ret } htype hname =
          | String n
          | UInt64 n -> ()
          (* The following not yet implemented for callbacks XXX *)
-         | ArrayAndLen _ | Bool _ | BytesIn _ | BytesOut _ | Callback _
+         | ArrayAndLen _ | Bool _ | BytesIn _ | BytesOut _
+         | BytesPersistIn _ | BytesPersistOut _ | Callback _
          | Int _ | Int64 _ | Path _ | SockAddrAndLen _ | StringList _
          | UInt _ | UInt32 _ -> pr "  abort ();\n"
        ) args;
@@ -2905,7 +2809,8 @@ let print_python_binding name { args; ret } htype hname =
          | String n -> pr " \"s\""
          | UInt64 n -> pr " \"K\""
          (* The following not yet implemented for callbacks XXX *)
-         | ArrayAndLen _ | Bool _ | BytesIn _ | BytesOut _ | Callback _
+         | ArrayAndLen _ | Bool _ | BytesIn _ | BytesOut _
+         | BytesPersistIn _ | BytesPersistOut _ | Callback _
          | Int _ | Int64 _ | Path _ | SockAddrAndLen _ | StringList _
          | UInt _ | UInt32 _ -> pr "  abort ();\n"
        ) args;
@@ -2917,7 +2822,8 @@ let print_python_binding name { args; ret } htype hname =
          | String n
          | UInt64 n -> pr ", %s" n
          (* The following not yet implemented for callbacks XXX *)
-         | ArrayAndLen _ | Bool _ | BytesIn _ | BytesOut _ | Callback _
+         | ArrayAndLen _ | Bool _ | BytesIn _ | BytesOut _
+         | BytesPersistIn _ | BytesPersistOut _ | Callback _
          | Int _ | Int64 _ | Path _ | SockAddrAndLen _ | StringList _
          | UInt _ | UInt32 _ -> pr "  abort ();\n"
        ) args;
@@ -2947,7 +2853,8 @@ let print_python_binding name { args; ret } htype hname =
          | UInt64 _
          | Opaque _ -> ()
          (* The following not yet implemented for callbacks XXX *)
-         | ArrayAndLen _ | Bool _ | BytesIn _ | BytesOut _ | Callback _
+         | ArrayAndLen _ | Bool _ | BytesIn _ | BytesOut _
+         | BytesPersistIn _ | BytesPersistOut _ | Callback _
          | Int _ | Int64 _ | Path _ | SockAddrAndLen _ | StringList _
          | UInt _ | UInt32 _ -> ()
        ) args;
@@ -2960,8 +2867,8 @@ let print_python_binding name { args; ret } htype hname =
   pr "PyObject *\n";
   pr "nbd_internal_py_%s (PyObject *self, PyObject *args)\n" name;
   pr "{\n";
-  pr "  PyObject *py_%s;\n" hname;
-  pr "  struct nbd_%s *%s;\n" htype hname;
+  pr "  PyObject *py_h;\n";
+  pr "  struct nbd_handle *h;\n";
   (match ret with
    | RBool
    | RErr
@@ -2984,6 +2891,7 @@ let print_python_binding name { args; ret } htype hname =
     | BytesOut (n, count) ->
        pr "  char *%s;\n" n;
        pr "  Py_ssize_t %s;\n" count
+    | BytesPersistIn _ | BytesPersistOut _ -> ()
     | Callback (n, _) ->
        pr "  struct %s_%s_data callback_data;\n" name n
     | Int n -> pr "  int %s;\n" n
@@ -3020,6 +2928,7 @@ let print_python_binding name { args; ret } htype hname =
     | Bool n -> pr " \"b\""
     | BytesIn (n, _) -> pr " \"y*\""
     | BytesOut (_, count) -> pr " \"n\""
+    | BytesPersistIn _ | BytesPersistOut _ -> ()
     | Callback (n, _) -> pr " \"O\""
     | Int n -> pr " \"i\""
     | Int64 n -> pr " \"L\""
@@ -3034,7 +2943,7 @@ let print_python_binding name { args; ret } htype hname =
   ) args;
   pr "\n";
   pr "                         \":nbd_%s\",\n" name;
-  pr "                         &py_%s" hname;
+  pr "                         &py_h";
   List.iter (
     function
     | ArrayAndLen (UInt32 n, _) -> pr ", &py_%s" n
@@ -3042,6 +2951,7 @@ let print_python_binding name { args; ret } htype hname =
     | Bool n -> pr ", &%s" n
     | BytesIn (n, _) -> pr ", &%s" n
     | BytesOut (_, count) -> pr ", &%s" count
+    | BytesPersistIn _ | BytesPersistOut _ -> ()
     | Callback _ -> pr ", &callback_data.fn"
     | Int n -> pr ", &%s" n
     | Int64 n -> pr ", &%s" n
@@ -3057,7 +2967,7 @@ let print_python_binding name { args; ret } htype hname =
   pr "))\n";
   pr "    return NULL;\n";
 
-  pr "  %s = get_%s (py_%s);\n" hname htype hname;
+  pr "  h = get_handle (py_h);\n";
   List.iter (
     function
     | ArrayAndLen (UInt32 n, len) ->
@@ -3079,20 +2989,17 @@ let print_python_binding name { args; ret } htype hname =
        pr "    %s[_i] = PyLong_AsUnsignedLong (PyList_GetItem (%s, _i));\n" n n
     | ArrayAndLen _ -> assert false
     | Bool _ -> ()
-    | BytesIn _ ->
-       if htype = "connection" then
-         (* The AIO pread/pwrite calls require a buffer which
-          * persists for as long as the command is running, which
-          * is at least after this function returns.  So we need
-          * a way to allocate one of those and pass it to the
-          * function from Python and free it later.
-          *)
-         pr "  abort (); /* XXX BytesIn not implemented for AIO */\n"
+    | BytesIn _ -> ()
     | BytesOut (n, count) ->
-       if htype = "connection" then
-         pr "  abort (); /* XXX BytesOut not implemented for AIO */\n"
-       else
-         pr "  %s = malloc (%s);\n" n count
+       pr "  %s = malloc (%s);\n" n count
+    | BytesPersistIn _ | BytesPersistOut _ ->
+       (* The AIO pread/pwrite calls require a buffer which
+        * persists for as long as the command is running, which
+        * is at least after this function returns.  So we need
+        * a way to allocate one of those and pass it to the
+        * function from Python and free it later.
+        *)
+       pr "  abort (); /* XXX BytesPersistIn/Out not implemented */\n"
     | Callback (n, _) ->
        pr "  if (!PyCallable_Check (callback_data.fn)) {\n";
        pr "    PyErr_SetString (PyExc_TypeError,\n";
@@ -3117,7 +3024,7 @@ let print_python_binding name { args; ret } htype hname =
   ) args;
 
   (* Call the underlying C function. *)
-  pr "  ret = nbd_%s (%s" name hname;
+  pr "  ret = nbd_%s (h" name;
   List.iter (
     function
     | ArrayAndLen (UInt32 n, len) -> pr ", %s, %s" n len
@@ -3125,6 +3032,7 @@ let print_python_binding name { args; ret } htype hname =
     | Bool n -> pr ", %s" n
     | BytesIn (n, _) -> pr ", %s.buf, %s.len" n n
     | BytesOut (n, count) -> pr ", %s, %s" n count
+    | BytesPersistIn _ | BytesPersistOut _ -> ()
     | Callback (n, _) -> pr ", %s_%s_wrapper" name n
     | Int n -> pr ", %s" n
     | Int64 n -> pr ", %s_i64" n
@@ -3157,6 +3065,7 @@ let print_python_binding name { args; ret } htype hname =
     | ArrayAndLen _
     | Bool _
     | BytesIn _
+    | BytesPersistIn _ | BytesPersistOut _
     | Callback _
     | Int _
     | Int64 _
@@ -3198,6 +3107,7 @@ let print_python_binding name { args; ret } htype hname =
     | Bool _ -> ()
     | BytesIn (n, _) -> pr "  PyBuffer_Release (&%s);\n" n
     | BytesOut _ -> ()
+    | BytesPersistIn _ | BytesPersistOut _ -> ()
     | Callback _ -> ()
     | Int _ -> ()
     | Int64 _ -> ()
@@ -3229,12 +3139,8 @@ let generate_python_methods_c () =
   pr "\n";
   List.iter (
     fun (name, fn) ->
-      print_python_binding name fn "handle" "h"
-  ) handle_calls;
-  List.iter (
-    fun (name, fn) ->
-      print_python_binding name fn "connection" "conn"
-  ) connection_calls
+      print_python_binding name fn
+  ) handle_calls
 
 let generate_python_nbd_py () =
   generate_header HashStyle;
@@ -3264,13 +3170,9 @@ class NBD (object):
         self._o = libnbdmod.create ()
 
     def __del__ (self):
-        \"\"\"Close the NBD handle and all connections\"\"\"
+        \"\"\"Close the NBD handle and underlying connection\"\"\"
         libnbdmod.close (self._o)
 
-    def get_connection (self, i):
-        \"\"\"Get the i'th connection associated with the handle\"\"\"
-        return NBDConnection (self, i)
-
 ";
 
   List.iter (
@@ -3280,8 +3182,8 @@ class NBD (object):
                      | ArrayAndLen (UInt32 n, _) -> n
                      | ArrayAndLen _ -> assert false
                      | Bool n -> n
-                     | BytesIn (n, _) -> n
-                     | BytesOut (_, count) -> count
+                     | BytesIn (n, _) | BytesPersistIn (n, _) -> n
+                     | BytesOut (_, count) | BytesPersistOut (_, count) -> count
                      | Callback (n, _) -> n
                      | Int n -> n
                      | Int64 n -> n
@@ -3302,53 +3204,6 @@ class NBD (object):
       pr "\n"
   ) handle_calls;
 
-  pr "\
-class NBDConnection (object):
-    def __init__ (self, h, i):
-        \"\"\"Get the i'th connection associated with the handle\"\"\"
-        self._o = libnbdmod.get_connection (h, i)
-
-    # Note there is intentionally no __del__ method.
-    def close (self):
-        \"\"\"Close this connection.  The i'th connection slot in
-        the handle is reopened with a freshly created connection.
-
-        The underlying C object is freed when you call this, so
-        you must not call any other methods on the connection
-        afterwards.
-        \"\"\"
-        libnbdmod.connection_close (self._o)
-";
-
-  List.iter (
-    fun (name, { args; shortdesc }) ->
-      let args = List.map (
-                     function
-                     | ArrayAndLen (UInt32 n, _) -> n
-                     | ArrayAndLen _ -> assert false
-                     | Bool n -> n
-                     | BytesIn (n, _) -> n
-                     | BytesOut (_, count) -> count
-                     | Callback (n, _) -> n
-                     | Int n -> n
-                     | Int64 n -> n
-                     | Opaque n -> n
-                     | Path n -> n
-                     | SockAddrAndLen (n, _) -> n
-                     | String n -> n
-                     | StringList n -> n
-                     | UInt n -> n
-                     | UInt32 n -> n
-                     | UInt64 n -> n
-                   ) args in
-      let args = List.map ((^) ", ") args in
-      let args = String.concat "" args in
-      pr "    def %s (self%s):\n" name args;
-      pr "        \"\"\"%s\"\"\"\n" shortdesc;
-      pr "        return libnbdmod.%s (self._o%s)\n" name args;
-      pr "\n"
-  ) connection_calls;
-
   (* For nbdsh. *)
   pr "\
 if __name__ == \"__main__\":
diff --git a/generator/states-connect.c b/generator/states-connect.c
index 8730798..ba8b240 100644
--- a/generator/states-connect.c
+++ b/generator/states-connect.c
@@ -39,21 +39,21 @@
  CONNECT.START:
   int fd;
 
-  assert (!conn->sock);
+  assert (!h->sock);
   fd = socket (AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0);
   if (fd == -1) {
     SET_NEXT_STATE (%.DEAD);
     set_error (errno, "socket");
     return -1;
   }
-  conn->sock = nbd_internal_socket_create (fd);
-  if (!conn->sock) {
+  h->sock = nbd_internal_socket_create (fd);
+  if (!h->sock) {
     SET_NEXT_STATE (%.DEAD);
     return -1;
   }
 
-  if (connect (fd, (struct sockaddr *) &conn->connaddr,
-               conn->connaddrlen) == -1) {
+  if (connect (fd, (struct sockaddr *) &h->connaddr,
+               h->connaddrlen) == -1) {
     if (errno != EINPROGRESS) {
       SET_NEXT_STATE (%.DEAD);
       set_error (errno, "connect");
@@ -66,7 +66,7 @@
   int status;
   socklen_t len = sizeof status;
 
-  if (getsockopt (conn->sock->ops->get_fd (conn->sock),
+  if (getsockopt (h->sock->ops->get_fd (h->sock),
                   SOL_SOCKET, SO_ERROR, &status, &len) == -1) {
     SET_NEXT_STATE (%.DEAD);
     set_error (errno, "getsockopt: SO_ERROR");
@@ -86,64 +86,64 @@
  CONNECT_TCP.START:
   int r;
 
-  assert (conn->hostname != NULL);
-  assert (conn->port != NULL);
+  assert (h->hostname != NULL);
+  assert (h->port != NULL);
 
-  if (conn->result) {
-    freeaddrinfo (conn->result);
-    conn->result = NULL;
+  if (h->result) {
+    freeaddrinfo (h->result);
+    h->result = NULL;
   }
 
-  memset (&conn->hints, 0, sizeof conn->hints);
-  conn->hints.ai_family = AF_UNSPEC;
-  conn->hints.ai_socktype = SOCK_STREAM;
-  conn->hints.ai_flags = 0;
-  conn->hints.ai_protocol = 0;
+  memset (&h->hints, 0, sizeof h->hints);
+  h->hints.ai_family = AF_UNSPEC;
+  h->hints.ai_socktype = SOCK_STREAM;
+  h->hints.ai_flags = 0;
+  h->hints.ai_protocol = 0;
 
   /* XXX Unfortunately getaddrinfo blocks.  getaddrinfo_a isn't
    * portable and in any case isn't an alternative because it can't be
    * integrated into a main loop.
    */
-  r = getaddrinfo (conn->hostname, conn->port, &conn->hints, &conn->result);
+  r = getaddrinfo (h->hostname, h->port, &h->hints, &h->result);
   if (r != 0) {
     SET_NEXT_STATE (%^START);
     set_error (0, "getaddrinfo: %s:%s: %s",
-               conn->hostname, conn->port, gai_strerror (r));
+               h->hostname, h->port, gai_strerror (r));
     return -1;
   }
 
-  conn->rp = conn->result;
+  h->rp = h->result;
   SET_NEXT_STATE (%CONNECT);
   return 0;
 
  CONNECT_TCP.CONNECT:
   int fd;
 
-  assert (!conn->sock);
+  assert (!h->sock);
 
-  if (conn->rp == NULL) {
+  if (h->rp == NULL) {
     /* We tried all the results from getaddrinfo without success.
      * Save errno from most recent connect(2) call. XXX
      */
     SET_NEXT_STATE (%^START);
     set_error (0, "connect: %s:%s: could not connect to remote host",
-               conn->hostname, conn->port);
+               h->hostname, h->port);
     return -1;
   }
 
-  fd = socket (conn->rp->ai_family,
-               conn->rp->ai_socktype|SOCK_NONBLOCK|SOCK_CLOEXEC,
-               conn->rp->ai_protocol);
+  fd = socket (h->rp->ai_family,
+               h->rp->ai_socktype|SOCK_NONBLOCK|SOCK_CLOEXEC,
+               h->rp->ai_protocol);
   if (fd == -1) {
     SET_NEXT_STATE (%NEXT_ADDRESS);
     return 0;
   }
-  conn->sock = nbd_internal_socket_create (fd);
-  if (!conn->sock) {
+  h->sock = nbd_internal_socket_create (fd);
+  if (!h->sock) {
     SET_NEXT_STATE (%.DEAD);
     return -1;
   }
-  if (connect (fd, conn->rp->ai_addr, conn->rp->ai_addrlen) == -1) {
+  if (connect (fd, h->rp->ai_addr, h->rp->ai_addrlen) == -1) {
     if (errno != EINPROGRESS) {
       SET_NEXT_STATE (%NEXT_ADDRESS);
       return 0;
@@ -157,7 +157,7 @@
   int status;
   socklen_t len = sizeof status;
 
-  if (getsockopt (conn->sock->ops->get_fd (conn->sock),
+  if (getsockopt (h->sock->ops->get_fd (h->sock),
                   SOL_SOCKET, SO_ERROR, &status, &len) == -1) {
     SET_NEXT_STATE (%.DEAD);
     set_error (errno, "getsockopt: SO_ERROR");
@@ -171,12 +171,12 @@
   return 0;
 
  CONNECT_TCP.NEXT_ADDRESS:
-  if (conn->sock) {
-    conn->sock->ops->close (conn->sock);
-    conn->sock = NULL;
+  if (h->sock) {
+    h->sock->ops->close (h->sock);
+    h->sock = NULL;
   }
-  if (conn->rp)
-    conn->rp = conn->rp->ai_next;
+  if (h->rp)
+    h->rp = h->rp->ai_next;
   SET_NEXT_STATE (%CONNECT);
   return 0;
 
@@ -184,9 +184,9 @@
   int sv[2];
   pid_t pid;
 
-  assert (!conn->sock);
-  assert (conn->argv);
-  assert (conn->argv[0]);
+  assert (!h->sock);
+  assert (h->argv);
+  assert (h->argv[0]);
   if (socketpair (AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0,
                   sv) == -1) {
     SET_NEXT_STATE (%.DEAD);
@@ -213,15 +213,15 @@
     /* Restore SIGPIPE back to SIG_DFL. */
     signal (SIGPIPE, SIG_DFL);
 
-    execvp (conn->argv[0], conn->argv);
-    perror (conn->argv[0]);
+    execvp (h->argv[0], h->argv);
+    perror (h->argv[0]);
     _exit (EXIT_FAILURE);
   }
 
   /* Parent. */
-  conn->pid = pid;
-  conn->sock = nbd_internal_socket_create (sv[0]);
-  if (!conn->sock) {
+  h->pid = pid;
+  h->sock = nbd_internal_socket_create (sv[0]);
+  if (!h->sock) {
     SET_NEXT_STATE (%.DEAD);
     return -1;
   }
diff --git a/generator/states-issue-command.c b/generator/states-issue-command.c
index 9e67219..627a54f 100644
--- a/generator/states-issue-command.c
+++ b/generator/states-issue-command.c
@@ -22,52 +22,52 @@
  ISSUE_COMMAND.START:
   struct command_in_flight *cmd;
 
-  assert (conn->cmds_to_issue != NULL);
-  cmd = conn->cmds_to_issue;
+  assert (h->cmds_to_issue != NULL);
+  cmd = h->cmds_to_issue;
 
   /* Were we interrupted by reading a reply to an earlier command? */
-  if (conn->wlen) {
-    if (conn->in_write_payload)
+  if (h->wlen) {
+    if (h->in_write_payload)
       SET_NEXT_STATE(%SEND_WRITE_PAYLOAD);
     else
       SET_NEXT_STATE(%SEND_REQUEST);
     return 0;
   }
 
-  conn->request.magic = htobe32 (NBD_REQUEST_MAGIC);
-  conn->request.flags = htobe16 (cmd->flags);
-  conn->request.type = htobe16 (cmd->type);
-  conn->request.handle = htobe64 (cmd->handle);
-  conn->request.offset = htobe64 (cmd->offset);
-  conn->request.count = htobe32 ((uint32_t) cmd->count);
-  conn->wbuf = &conn->request;
-  conn->wlen = sizeof (conn->request);
+  h->request.magic = htobe32 (NBD_REQUEST_MAGIC);
+  h->request.flags = htobe16 (cmd->flags);
+  h->request.type = htobe16 (cmd->type);
+  h->request.handle = htobe64 (cmd->handle);
+  h->request.offset = htobe64 (cmd->offset);
+  h->request.count = htobe32 ((uint32_t) cmd->count);
+  h->wbuf = &h->request;
+  h->wlen = sizeof (h->request);
   SET_NEXT_STATE (%SEND_REQUEST);
   return 0;
 
  ISSUE_COMMAND.SEND_REQUEST:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:  SET_NEXT_STATE (%PREPARE_WRITE_PAYLOAD);
   }
   return 0;
 
  ISSUE_COMMAND.PAUSE_SEND_REQUEST:
-  assert (conn->wlen);
-  assert (conn->cmds_to_issue != NULL);
-  conn->in_write_payload = false;
+  assert (h->wlen);
+  assert (h->cmds_to_issue != NULL);
+  h->in_write_payload = false;
   SET_NEXT_STATE (%^REPLY.START);
   return 0;
 
  ISSUE_COMMAND.PREPARE_WRITE_PAYLOAD:
   struct command_in_flight *cmd;
 
-  assert (conn->cmds_to_issue != NULL);
-  cmd = conn->cmds_to_issue;
-  assert (cmd->handle == be64toh (conn->request.handle));
+  assert (h->cmds_to_issue != NULL);
+  cmd = h->cmds_to_issue;
+  assert (cmd->handle == be64toh (h->request.handle));
   if (cmd->type == NBD_CMD_WRITE) {
-    conn->wbuf = cmd->data;
-    conn->wlen = cmd->count;
+    h->wbuf = cmd->data;
+    h->wlen = cmd->count;
     SET_NEXT_STATE (%SEND_WRITE_PAYLOAD);
   }
   else
@@ -75,29 +75,29 @@
   return 0;
 
  ISSUE_COMMAND.SEND_WRITE_PAYLOAD:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:  SET_NEXT_STATE (%FINISH);
   }
   return 0;
 
  ISSUE_COMMAND.PAUSE_WRITE_PAYLOAD:
-  assert (conn->wlen);
-  assert (conn->cmds_to_issue != NULL);
-  conn->in_write_payload = true;
+  assert (h->wlen);
+  assert (h->cmds_to_issue != NULL);
+  h->in_write_payload = true;
   SET_NEXT_STATE (%^REPLY.START);
   return 0;
 
  ISSUE_COMMAND.FINISH:
   struct command_in_flight *cmd;
 
-  assert (!conn->wlen);
-  assert (conn->cmds_to_issue != NULL);
-  cmd = conn->cmds_to_issue;
-  assert (cmd->handle == be64toh (conn->request.handle));
-  conn->cmds_to_issue = cmd->next;
-  cmd->next = conn->cmds_in_flight;
-  conn->cmds_in_flight = cmd;
+  assert (!h->wlen);
+  assert (h->cmds_to_issue != NULL);
+  cmd = h->cmds_to_issue;
+  assert (cmd->handle == be64toh (h->request.handle));
+  h->cmds_to_issue = cmd->next;
+  cmd->next = h->cmds_in_flight;
+  h->cmds_in_flight = cmd;
   SET_NEXT_STATE (%.READY);
   return 0;
 
diff --git a/generator/states-magic.c b/generator/states-magic.c
index f492f3c..93c92fc 100644
--- a/generator/states-magic.c
+++ b/generator/states-magic.c
@@ -20,13 +20,13 @@
 
 /* STATE MACHINE */ {
  MAGIC.START:
-  conn->rbuf = &conn->sbuf;
-  conn->rlen = 16;
+  h->rbuf = &h->sbuf;
+  h->rlen = 16;
   SET_NEXT_STATE (%RECV_MAGIC);
   return 0;
 
  MAGIC.RECV_MAGIC:
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:  SET_NEXT_STATE (%CHECK_MAGIC);
   }
@@ -35,13 +35,13 @@
  MAGIC.CHECK_MAGIC:
   uint64_t version;
 
-  if (strncmp (conn->sbuf.new_handshake.nbdmagic, "NBDMAGIC", 8) != 0) {
+  if (strncmp (h->sbuf.new_handshake.nbdmagic, "NBDMAGIC", 8) != 0) {
     SET_NEXT_STATE (%.DEAD);
     set_error (0, "handshake: server did not send expected NBD magic");
     return -1;
   }
 
-  version = be64toh (conn->sbuf.new_handshake.version);
+  version = be64toh (h->sbuf.new_handshake.version);
   if (version == NBD_NEW_VERSION)
     SET_NEXT_STATE (%.NEWSTYLE.START);
   else if (version == NBD_OLD_VERSION)
diff --git a/generator/states-newstyle-opt-export-name.c b/generator/states-newstyle-opt-export-name.c
index 07b6c9e..774c93c 100644
--- a/generator/states-newstyle-opt-export-name.c
+++ b/generator/states-newstyle-opt-export-name.c
@@ -20,38 +20,38 @@
 
 /* STATE MACHINE */ {
  NEWSTYLE.OPT_EXPORT_NAME.START:
-  conn->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
-  conn->sbuf.option.option = htobe32 (NBD_OPT_EXPORT_NAME);
-  conn->sbuf.option.optlen = strlen (h->export_name);
-  conn->wbuf = &conn->sbuf;
-  conn->wlen = sizeof conn->sbuf.option;
+  h->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
+  h->sbuf.option.option = htobe32 (NBD_OPT_EXPORT_NAME);
+  h->sbuf.option.optlen = strlen (h->export_name);
+  h->wbuf = &h->sbuf;
+  h->wlen = sizeof h->sbuf.option;
   SET_NEXT_STATE (%SEND);
   return 0;
 
  NEWSTYLE.OPT_EXPORT_NAME.SEND:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->wbuf = h->export_name;
-    conn->wlen = strlen (h->export_name);
+    h->wbuf = h->export_name;
+    h->wlen = strlen (h->export_name);
     SET_NEXT_STATE (%SEND_EXPORT);
   }
   return 0;
 
  NEWSTYLE.OPT_EXPORT_NAME.SEND_EXPORT:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->rbuf = &conn->sbuf;
-    conn->rlen = sizeof conn->sbuf.export_name_reply;
-    if ((conn->gflags & NBD_FLAG_NO_ZEROES) != 0)
-      conn->rlen -= sizeof conn->sbuf.export_name_reply.zeroes;
+    h->rbuf = &h->sbuf;
+    h->rlen = sizeof h->sbuf.export_name_reply;
+    if ((h->gflags & NBD_FLAG_NO_ZEROES) != 0)
+      h->rlen -= sizeof h->sbuf.export_name_reply.zeroes;
     SET_NEXT_STATE (%RECV_REPLY);
   }
   return 0;
 
  NEWSTYLE.OPT_EXPORT_NAME.RECV_REPLY:
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:  SET_NEXT_STATE (%CHECK_REPLY);
   }
@@ -61,9 +61,9 @@
   uint64_t exportsize;
   uint16_t eflags;
 
-  exportsize = be64toh (conn->sbuf.export_name_reply.exportsize);
-  eflags = be16toh (conn->sbuf.export_name_reply.eflags);
-  if (nbd_internal_set_size_and_flags (conn->h, exportsize, eflags) == -1) {
+  exportsize = be64toh (h->sbuf.export_name_reply.exportsize);
+  eflags = be16toh (h->sbuf.export_name_reply.eflags);
+  if (nbd_internal_set_size_and_flags (h, exportsize, eflags) == -1) {
     SET_NEXT_STATE (%.DEAD);
     return -1;
   }
diff --git a/generator/states-newstyle-opt-go.c b/generator/states-newstyle-opt-go.c
index 97f25f8..eea70cb 100644
--- a/generator/states-newstyle-opt-go.c
+++ b/generator/states-newstyle-opt-go.c
@@ -20,81 +20,81 @@
 
 /* STATE MACHINE */ {
  NEWSTYLE.OPT_GO.START:
-  conn->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
-  conn->sbuf.option.option = htobe32 (NBD_OPT_GO);
-  conn->sbuf.option.optlen =
+  h->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
+  h->sbuf.option.option = htobe32 (NBD_OPT_GO);
+  h->sbuf.option.optlen =
     htobe32 (/* exportnamelen */ 4 + strlen (h->export_name) + /* nrinfos */ 2);
-  conn->wbuf = &conn->sbuf;
-  conn->wlen = sizeof conn->sbuf.option;
+  h->wbuf = &h->sbuf;
+  h->wlen = sizeof h->sbuf.option;
   SET_NEXT_STATE (%SEND);
   return 0;
 
  NEWSTYLE.OPT_GO.SEND:
   const uint32_t exportnamelen = strlen (h->export_name);
 
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->sbuf.len = htobe32 (exportnamelen);
-    conn->wbuf = &conn->sbuf;
-    conn->wlen = 4;
+    h->sbuf.len = htobe32 (exportnamelen);
+    h->wbuf = &h->sbuf;
+    h->wlen = 4;
     SET_NEXT_STATE (%SEND_EXPORTNAMELEN);
   }
   return 0;
 
  NEWSTYLE.OPT_GO.SEND_EXPORTNAMELEN:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->wbuf = h->export_name;
-    conn->wlen = strlen (h->export_name);
+    h->wbuf = h->export_name;
+    h->wlen = strlen (h->export_name);
     SET_NEXT_STATE (%SEND_EXPORT);
   }
   return 0;
 
  NEWSTYLE.OPT_GO.SEND_EXPORT:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->sbuf.nrinfos = 0;
-    conn->wbuf = &conn->sbuf;
-    conn->wlen = 2;
+    h->sbuf.nrinfos = 0;
+    h->wbuf = &h->sbuf;
+    h->wlen = 2;
     SET_NEXT_STATE (%SEND_NRINFOS);
   }
   return 0;
 
  NEWSTYLE.OPT_GO.SEND_NRINFOS:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->rbuf = &conn->sbuf;
-    conn->rlen = sizeof conn->sbuf.or.option_reply;
+    h->rbuf = &h->sbuf;
+    h->rlen = sizeof h->sbuf.or.option_reply;
     SET_NEXT_STATE (%RECV_REPLY);
   }
   return 0;
 
  NEWSTYLE.OPT_GO.RECV_REPLY:
   uint32_t len;
-  const size_t maxpayload = sizeof conn->sbuf.or.payload;
+  const size_t maxpayload = sizeof h->sbuf.or.payload;
 
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
     /* Read the following payload if it is short enough to fit in the
      * static buffer.  If it's too long, skip it.
      */
-    len = be32toh (conn->sbuf.or.option_reply.replylen);
+    len = be32toh (h->sbuf.or.option_reply.replylen);
     if (len <= maxpayload)
-      conn->rbuf = &conn->sbuf.or.payload;
+      h->rbuf = &h->sbuf.or.payload;
     else
-      conn->rbuf = NULL;
-    conn->rlen = len;
+      h->rbuf = NULL;
+    h->rlen = len;
     SET_NEXT_STATE (%RECV_REPLY_PAYLOAD);
   }
   return 0;
 
  NEWSTYLE.OPT_GO.RECV_REPLY_PAYLOAD:
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:  SET_NEXT_STATE (%CHECK_REPLY);
   }
@@ -105,14 +105,14 @@
   uint32_t option;
   uint32_t reply;
   uint32_t len;
-  const size_t maxpayload = sizeof conn->sbuf.or.payload;
+  const size_t maxpayload = sizeof h->sbuf.or.payload;
   uint64_t exportsize;
   uint16_t eflags;
 
-  magic = be64toh (conn->sbuf.or.option_reply.magic);
-  option = be32toh (conn->sbuf.or.option_reply.option);
-  reply = be32toh (conn->sbuf.or.option_reply.reply);
-  len = be32toh (conn->sbuf.or.option_reply.replylen);
+  magic = be64toh (h->sbuf.or.option_reply.magic);
+  option = be32toh (h->sbuf.or.option_reply.option);
+  reply = be32toh (h->sbuf.or.option_reply.reply);
+  len = be32toh (h->sbuf.or.option_reply.replylen);
   if (magic != NBD_REP_MAGIC || option != NBD_OPT_GO) {
     SET_NEXT_STATE (%.DEAD);
     set_error (0, "handshake: invalid option reply magic or option");
@@ -129,11 +129,11 @@
     return 0;
   case NBD_REP_INFO:
     if (len <= maxpayload /* see RECV_NEWSTYLE_OPT_GO_REPLY */) {
-      if (len >= sizeof conn->sbuf.or.payload.export) {
-        if (be16toh (conn->sbuf.or.payload.export.info) == NBD_INFO_EXPORT) {
-          exportsize = be64toh (conn->sbuf.or.payload.export.exportsize);
-          eflags = be16toh (conn->sbuf.or.payload.export.eflags);
-          if (nbd_internal_set_size_and_flags (conn->h,
+      if (len >= sizeof h->sbuf.or.payload.export) {
+        if (be16toh (h->sbuf.or.payload.export.info) == NBD_INFO_EXPORT) {
+          exportsize = be64toh (h->sbuf.or.payload.export.exportsize);
+          eflags = be16toh (h->sbuf.or.payload.export.eflags);
+          if (nbd_internal_set_size_and_flags (h,
                                                exportsize, eflags) == -1) {
             SET_NEXT_STATE (%.DEAD);
             return -1;
@@ -143,12 +143,12 @@
     }
     /* ... else ignore the payload. */
     /* Server is allowed to send any number of NBD_REP_INFO, read next one. */
-    conn->rbuf = &conn->sbuf;
-    conn->rlen = sizeof (conn->sbuf.or.option_reply);
+    h->rbuf = &h->sbuf;
+    h->rlen = sizeof (h->sbuf.or.option_reply);
     SET_NEXT_STATE (%RECV_REPLY);
     return 0;
   case NBD_REP_ERR_UNSUP:
-    debug (conn->h, "server is confused by NBD_OPT_GO, continuing anyway");
+    debug (h, "server is confused by NBD_OPT_GO, continuing anyway");
     SET_NEXT_STATE (%^OPT_EXPORT_NAME.START);
     return 0;
   default:
diff --git a/generator/states-newstyle-opt-set-meta-context.c b/generator/states-newstyle-opt-set-meta-context.c
index 78503d1..1cd65c3 100644
--- a/generator/states-newstyle-opt-set-meta-context.c
+++ b/generator/states-newstyle-opt-set-meta-context.c
@@ -27,14 +27,14 @@
    * Also we skip the group if the client didn't request any metadata
    * contexts.
    */
-  if (!conn->structured_replies ||
+  if (!h->structured_replies ||
       h->request_meta_contexts == NULL ||
       nbd_internal_string_list_length (h->request_meta_contexts) == 0) {
     SET_NEXT_STATE (%FINISH);
     return 0;
   }
 
-  assert (conn->meta_contexts == NULL);
+  assert (h->meta_contexts == NULL);
 
   /* Calculate the length of the option request data. */
   len = 4 /* exportname len */ + strlen (h->export_name) + 4 /* nr queries */;
@@ -42,94 +42,94 @@
   for (i = 0; i < nr_queries; ++i)
     len += 4 /* length of query */ + strlen (h->request_meta_contexts[i]);
 
-  conn->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
-  conn->sbuf.option.option = htobe32 (NBD_OPT_SET_META_CONTEXT);
-  conn->sbuf.option.optlen = htobe32 (len);
-  conn->wbuf = &conn->sbuf;
-  conn->wlen = sizeof (conn->sbuf.option);
+  h->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
+  h->sbuf.option.option = htobe32 (NBD_OPT_SET_META_CONTEXT);
+  h->sbuf.option.optlen = htobe32 (len);
+  h->wbuf = &h->sbuf;
+  h->wlen = sizeof (h->sbuf.option);
   SET_NEXT_STATE (%SEND);
   return 0;
 
  NEWSTYLE.OPT_SET_META_CONTEXT.SEND:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->sbuf.len = htobe32 (strlen (h->export_name));
-    conn->wbuf = &conn->sbuf.len;
-    conn->wlen = sizeof conn->sbuf.len;
+    h->sbuf.len = htobe32 (strlen (h->export_name));
+    h->wbuf = &h->sbuf.len;
+    h->wlen = sizeof h->sbuf.len;
     SET_NEXT_STATE (%SEND_EXPORTNAMELEN);
   }
   return 0;
 
  NEWSTYLE.OPT_SET_META_CONTEXT.SEND_EXPORTNAMELEN:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->wbuf = h->export_name;
-    conn->wlen = strlen (h->export_name);
+    h->wbuf = h->export_name;
+    h->wlen = strlen (h->export_name);
     SET_NEXT_STATE (%SEND_EXPORTNAME);
   }
   return 0;
 
  NEWSTYLE.OPT_SET_META_CONTEXT.SEND_EXPORTNAME:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->sbuf.nrqueries =
+    h->sbuf.nrqueries =
       htobe32 (nbd_internal_string_list_length (h->request_meta_contexts));
-    conn->wbuf = &conn->sbuf;
-    conn->wlen = sizeof conn->sbuf.nrqueries;
+    h->wbuf = &h->sbuf;
+    h->wlen = sizeof h->sbuf.nrqueries;
     SET_NEXT_STATE (%SEND_NRQUERIES);
   }
   return 0;
 
  NEWSTYLE.OPT_SET_META_CONTEXT.SEND_NRQUERIES:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->querynum = 0;
+    h->querynum = 0;
     SET_NEXT_STATE (%PREPARE_NEXT_QUERY);
   }
   return 0;
 
  NEWSTYLE.OPT_SET_META_CONTEXT.PREPARE_NEXT_QUERY:
-  const char *query = h->request_meta_contexts[conn->querynum];
+  const char *query = h->request_meta_contexts[h->querynum];
 
   if (query == NULL) { /* end of list of requested meta contexts */
     SET_NEXT_STATE (%PREPARE_FOR_REPLY);
     return 0;
   }
 
-  conn->sbuf.len = htobe32 (strlen (query));
-  conn->wbuf = &conn->sbuf.len;
-  conn->wlen = sizeof conn->sbuf.len;
+  h->sbuf.len = htobe32 (strlen (query));
+  h->wbuf = &h->sbuf.len;
+  h->wlen = sizeof h->sbuf.len;
   SET_NEXT_STATE (%SEND_QUERYLEN);
   return 0;
 
  NEWSTYLE.OPT_SET_META_CONTEXT.SEND_QUERYLEN:
-  const char *query = h->request_meta_contexts[conn->querynum];
+  const char *query = h->request_meta_contexts[h->querynum];
 
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->wbuf = query;
-    conn->wlen = strlen (query);
+    h->wbuf = query;
+    h->wlen = strlen (query);
     SET_NEXT_STATE (%SEND_QUERY);
   }
   return 0;
 
  NEWSTYLE.OPT_SET_META_CONTEXT.SEND_QUERY:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->querynum++;
+    h->querynum++;
     SET_NEXT_STATE (%PREPARE_NEXT_QUERY);
   }
   return 0;
 
  NEWSTYLE.OPT_SET_META_CONTEXT.PREPARE_FOR_REPLY:
-  conn->rbuf = &conn->sbuf.or.option_reply;
-  conn->rlen = sizeof conn->sbuf.or.option_reply;
+  h->rbuf = &h->sbuf.or.option_reply;
+  h->rlen = sizeof h->sbuf.or.option_reply;
   SET_NEXT_STATE (%RECV_REPLY);
   return 0;
 
@@ -138,15 +138,15 @@
   uint32_t option;
   uint32_t reply;
   uint32_t len;
-  const uint32_t maxlen = sizeof conn->sbuf.or.payload.context;
+  const uint32_t maxlen = sizeof h->sbuf.or.payload.context;
 
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    magic = be64toh (conn->sbuf.or.option_reply.magic);
-    option = be32toh (conn->sbuf.or.option_reply.option);
-    reply = be32toh (conn->sbuf.or.option_reply.reply);
-    len = be32toh (conn->sbuf.or.option_reply.replylen);
+    magic = be64toh (h->sbuf.or.option_reply.magic);
+    option = be32toh (h->sbuf.or.option_reply.option);
+    reply = be32toh (h->sbuf.or.option_reply.reply);
+    len = be32toh (h->sbuf.or.option_reply.replylen);
     if (magic != NBD_REP_MAGIC || option != NBD_OPT_SET_META_CONTEXT) {
       SET_NEXT_STATE (%.DEAD);
       set_error (0, "handshake: invalid option reply magic or option");
@@ -164,18 +164,18 @@
     case NBD_REP_META_CONTEXT:  /* A context. */
       /* If it's too long, skip over it. */
       if (len > maxlen)
-        conn->rbuf = NULL;
+        h->rbuf = NULL;
       else
-        conn->rbuf = &conn->sbuf.or.payload.context;
-      conn->rlen = len;
+        h->rbuf = &h->sbuf.or.payload.context;
+      h->rlen = len;
       SET_NEXT_STATE (%RECV_REPLY_PAYLOAD);
       break;
     default:
       /* Anything else is an error, ignore it */
-      debug (conn->h, "handshake: unexpected error from "
+      debug (h, "handshake: unexpected error from "
              "NBD_OPT_SET_META_CONTEXT (%" PRIu32 ")", reply);
-      conn->rbuf = NULL;
-      conn->rlen = len;
+      h->rbuf = NULL;
+      h->rlen = len;
       SET_NEXT_STATE (%RECV_SKIP_PAYLOAD);
     }
   }
@@ -185,11 +185,11 @@
   struct meta_context *meta_context;
   uint32_t len;
 
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    if (conn->rbuf != NULL) {
-      len = be32toh (conn->sbuf.or.option_reply.replylen);
+    if (h->rbuf != NULL) {
+      len = be32toh (h->sbuf.or.option_reply.replylen);
 
       meta_context = malloc (sizeof *meta_context);
       if (meta_context == NULL) {
@@ -198,7 +198,7 @@
         return -1;
       }
       /* String payload is not NUL-terminated. */
-      meta_context->name = strndup (conn->sbuf.or.payload.context.str, len);
+      meta_context->name = strndup (h->sbuf.or.payload.context.str, len);
       if (meta_context->name == NULL) {
         set_error (errno, "strdup");
         SET_NEXT_STATE (%.DEAD);
@@ -206,18 +206,18 @@
         return -1;
       }
       meta_context->context_id =
-        be32toh (conn->sbuf.or.payload.context.context.context_id);
+        be32toh (h->sbuf.or.payload.context.context.context_id);
       debug (h, "negotiated %s with context ID %" PRIu32,
              meta_context->name, meta_context->context_id);
-      meta_context->next = conn->meta_contexts;
-      conn->meta_contexts = meta_context;
+      meta_context->next = h->meta_contexts;
+      h->meta_contexts = meta_context;
     }
     SET_NEXT_STATE (%PREPARE_FOR_REPLY);
   }
   return 0;
 
  NEWSTYLE.OPT_SET_META_CONTEXT.RECV_SKIP_PAYLOAD:
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:  SET_NEXT_STATE (%FINISH);
     /* XXX: capture instead of skip server's payload to NBD_REP_ERR*? */
diff --git a/generator/states-newstyle-opt-starttls.c b/generator/states-newstyle-opt-starttls.c
index f56553c..c5eba91 100644
--- a/generator/states-newstyle-opt-starttls.c
+++ b/generator/states-newstyle-opt-starttls.c
@@ -26,20 +26,20 @@
     return 0;
   }
 
-  conn->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
-  conn->sbuf.option.option = htobe32 (NBD_OPT_STARTTLS);
-  conn->sbuf.option.optlen = 0;
-  conn->wbuf = &conn->sbuf;
-  conn->wlen = sizeof (conn->sbuf.option);
+  h->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
+  h->sbuf.option.option = htobe32 (NBD_OPT_STARTTLS);
+  h->sbuf.option.optlen = 0;
+  h->wbuf = &h->sbuf;
+  h->wlen = sizeof (h->sbuf.option);
   SET_NEXT_STATE (%SEND);
   return 0;
 
  NEWSTYLE.OPT_STARTTLS.SEND:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->rbuf = &conn->sbuf;
-    conn->rlen = sizeof (conn->sbuf.or.option_reply);
+    h->rbuf = &h->sbuf;
+    h->rlen = sizeof (h->sbuf.or.option_reply);
     SET_NEXT_STATE (%RECV_REPLY);
   }
   return 0;
@@ -47,19 +47,19 @@
  NEWSTYLE.OPT_STARTTLS.RECV_REPLY:
   uint32_t len;
 
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
     /* Discard the payload if there is one. */
-    len = be32toh (conn->sbuf.or.option_reply.replylen);
-    conn->rbuf = NULL;
-    conn->rlen = len;
+    len = be32toh (h->sbuf.or.option_reply.replylen);
+    h->rbuf = NULL;
+    h->rlen = len;
     SET_NEXT_STATE (%SKIP_REPLY_PAYLOAD);
   }
   return 0;
 
  NEWSTYLE.OPT_STARTTLS.SKIP_REPLY_PAYLOAD:
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:  SET_NEXT_STATE (%CHECK_REPLY);
   }
@@ -72,10 +72,10 @@
   uint32_t len;
   struct socket *new_sock;
 
-  magic = be64toh (conn->sbuf.or.option_reply.magic);
-  option = be32toh (conn->sbuf.or.option_reply.option);
-  reply = be32toh (conn->sbuf.or.option_reply.reply);
-  len = be32toh (conn->sbuf.or.option_reply.replylen);
+  magic = be64toh (h->sbuf.or.option_reply.magic);
+  option = be32toh (h->sbuf.or.option_reply.option);
+  reply = be32toh (h->sbuf.or.option_reply.reply);
+  len = be32toh (h->sbuf.or.option_reply.replylen);
   if (magic != NBD_REP_MAGIC || option != NBD_OPT_STARTTLS) {
     SET_NEXT_STATE (%.DEAD);
     set_error (0, "handshake: invalid option reply magic or option");
@@ -89,13 +89,13 @@
       return -1;
     }
 
-    new_sock = nbd_internal_crypto_create_session (conn, conn->sock);
+    new_sock = nbd_internal_crypto_create_session (h, h->sock);
     if (new_sock == NULL) {
       SET_NEXT_STATE (%.DEAD);
       return -1;
     }
-    conn->sock = new_sock;
-    if (nbd_internal_crypto_is_reading (conn))
+    h->sock = new_sock;
+    if (nbd_internal_crypto_is_reading (h))
       SET_NEXT_STATE (%TLS_HANDSHAKE_READ);
     else
       SET_NEXT_STATE (%TLS_HANDSHAKE_WRITE);
@@ -103,7 +103,7 @@
 
   default:
     if (!NBD_REP_IS_ERR (reply))
-      debug (conn->h,
+      debug (h,
              "server is confused by NBD_OPT_STARTTLS, continuing anyway");
 
     /* Server refused to upgrade to TLS.  If h->tls is not require (2)
@@ -116,7 +116,7 @@
       return -1;
     }
 
-    debug (conn->h,
+    debug (h,
            "server refused TLS (%s), continuing with unencrypted connection",
            reply == NBD_REP_ERR_POLICY ? "policy" : "not supported");
     /* XXX: capture instead of skip server's payload to NBD_REP_ERR*? */
@@ -128,21 +128,21 @@
  NEWSTYLE.OPT_STARTTLS.TLS_HANDSHAKE_READ:
   int r;
 
-  r = nbd_internal_crypto_handshake (conn);
+  r = nbd_internal_crypto_handshake (h);
   if (r == -1) {
     SET_NEXT_STATE (%.DEAD);
     return -1;
   }
   if (r == 0) {
     /* Finished handshake. */
-    nbd_internal_crypto_debug_tls_enabled (conn);
+    nbd_internal_crypto_debug_tls_enabled (h);
 
     /* Continue with option negotiation. */
     SET_NEXT_STATE (%^OPT_STRUCTURED_REPLY.START);
     return 0;
   }
   /* Continue handshake. */
-  if (nbd_internal_crypto_is_reading (conn))
+  if (nbd_internal_crypto_is_reading (h))
     SET_NEXT_STATE (%TLS_HANDSHAKE_READ);
   else
     SET_NEXT_STATE (%TLS_HANDSHAKE_WRITE);
@@ -151,21 +151,21 @@
  NEWSTYLE.OPT_STARTTLS.TLS_HANDSHAKE_WRITE:
   int r;
 
-  r = nbd_internal_crypto_handshake (conn);
+  r = nbd_internal_crypto_handshake (h);
   if (r == -1) {
     SET_NEXT_STATE (%.DEAD);
     return -1;
   }
   if (r == 0) {
     /* Finished handshake. */
-    debug (conn->h, "connection is using TLS");
+    debug (h, "connection is using TLS");
 
     /* Continue with option negotiation. */
     SET_NEXT_STATE (%^OPT_STRUCTURED_REPLY.START);
     return 0;
   }
   /* Continue handshake. */
-  if (nbd_internal_crypto_is_reading (conn))
+  if (nbd_internal_crypto_is_reading (h))
     SET_NEXT_STATE (%TLS_HANDSHAKE_READ);
   else
     SET_NEXT_STATE (%TLS_HANDSHAKE_WRITE);
diff --git a/generator/states-newstyle-opt-structured-reply.c b/generator/states-newstyle-opt-structured-reply.c
index 91acdcc..36f9eb3 100644
--- a/generator/states-newstyle-opt-structured-reply.c
+++ b/generator/states-newstyle-opt-structured-reply.c
@@ -20,20 +20,20 @@
 
 /* STATE MACHINE */ {
  NEWSTYLE.OPT_STRUCTURED_REPLY.START:
-  conn->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
-  conn->sbuf.option.option = htobe32 (NBD_OPT_STRUCTURED_REPLY);
-  conn->sbuf.option.optlen = htobe32 (0);
-  conn->wbuf = &conn->sbuf;
-  conn->wlen = sizeof conn->sbuf.option;
+  h->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
+  h->sbuf.option.option = htobe32 (NBD_OPT_STRUCTURED_REPLY);
+  h->sbuf.option.optlen = htobe32 (0);
+  h->wbuf = &h->sbuf;
+  h->wlen = sizeof h->sbuf.option;
   SET_NEXT_STATE (%SEND);
   return 0;
 
  NEWSTYLE.OPT_STRUCTURED_REPLY.SEND:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    conn->rbuf = &conn->sbuf;
-    conn->rlen = sizeof conn->sbuf.or.option_reply;
+    h->rbuf = &h->sbuf;
+    h->rlen = sizeof h->sbuf.or.option_reply;
     SET_NEXT_STATE (%RECV_REPLY);
   }
   return 0;
@@ -41,19 +41,19 @@
  NEWSTYLE.OPT_STRUCTURED_REPLY.RECV_REPLY:
   uint32_t len;
 
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
     /* Discard the payload if there is one. */
-    len = be32toh (conn->sbuf.or.option_reply.replylen);
-    conn->rbuf = NULL;
-    conn->rlen = len;
+    len = be32toh (h->sbuf.or.option_reply.replylen);
+    h->rbuf = NULL;
+    h->rlen = len;
     SET_NEXT_STATE (%SKIP_REPLY_PAYLOAD);
   }
   return 0;
 
  NEWSTYLE.OPT_STRUCTURED_REPLY.SKIP_REPLY_PAYLOAD:
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:  SET_NEXT_STATE (%CHECK_REPLY);
   }
@@ -64,9 +64,9 @@
   uint32_t option;
   uint32_t reply;
 
-  magic = be64toh (conn->sbuf.or.option_reply.magic);
-  option = be32toh (conn->sbuf.or.option_reply.option);
-  reply = be32toh (conn->sbuf.or.option_reply.reply);
+  magic = be64toh (h->sbuf.or.option_reply.magic);
+  option = be32toh (h->sbuf.or.option_reply.option);
+  reply = be32toh (h->sbuf.or.option_reply.reply);
   if (magic != NBD_REP_MAGIC || option != NBD_OPT_STRUCTURED_REPLY) {
     SET_NEXT_STATE (%.DEAD);
     set_error (0, "handshake: invalid option reply magic or option");
@@ -74,18 +74,18 @@
   }
   switch (reply) {
   case NBD_REP_ACK:
-    if (conn->sbuf.or.option_reply.replylen != 0) {
+    if (h->sbuf.or.option_reply.replylen != 0) {
       SET_NEXT_STATE (%.DEAD);
       set_error (0, "handshake: invalid option reply length");
       return -1;
     }
-    debug (conn->h, "negotiated structured replies on this connection");
-    conn->structured_replies = true;
+    debug (h, "negotiated structured replies on this connection");
+    h->structured_replies = true;
     break;
   default:
     /* XXX: capture instead of skip server's payload to NBD_REP_ERR*? */
-    debug (conn->h, "structured replies are not supported by this server");
-    conn->structured_replies = false;
+    debug (h, "structured replies are not supported by this server");
+    h->structured_replies = false;
     break;
   }
 
diff --git a/generator/states-newstyle.c b/generator/states-newstyle.c
index 276f415..6d81ab8 100644
--- a/generator/states-newstyle.c
+++ b/generator/states-newstyle.c
@@ -20,13 +20,13 @@
 
 /* STATE MACHINE */ {
  NEWSTYLE.START:
-  conn->rbuf = &conn->gflags;
-  conn->rlen = 2;
+  h->rbuf = &h->gflags;
+  h->rlen = 2;
   SET_NEXT_STATE (%RECV_GFLAGS);
   return 0;
 
  NEWSTYLE.RECV_GFLAGS:
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:  SET_NEXT_STATE (%CHECK_GFLAGS);
   }
@@ -35,8 +35,8 @@
  NEWSTYLE.CHECK_GFLAGS:
   uint32_t cflags;
 
-  conn->gflags = be16toh (conn->gflags);
-  if ((conn->gflags & NBD_FLAG_FIXED_NEWSTYLE) == 0 &&
+  h->gflags = be16toh (h->gflags);
+  if ((h->gflags & NBD_FLAG_FIXED_NEWSTYLE) == 0 &&
       h->tls == 2) {
     SET_NEXT_STATE (%.DEAD);
     set_error (ENOTSUP, "handshake: server is not fixed newstyle, "
@@ -44,19 +44,19 @@
     return -1;
   }
 
-  cflags = conn->gflags & (NBD_FLAG_FIXED_NEWSTYLE|NBD_FLAG_NO_ZEROES);
-  conn->sbuf.cflags = htobe32 (cflags);
-  conn->wbuf = &conn->sbuf;
-  conn->wlen = 4;
+  cflags = h->gflags & (NBD_FLAG_FIXED_NEWSTYLE|NBD_FLAG_NO_ZEROES);
+  h->sbuf.cflags = htobe32 (cflags);
+  h->wbuf = &h->sbuf;
+  h->wlen = 4;
   SET_NEXT_STATE (%SEND_CFLAGS);
   return 0;
 
  NEWSTYLE.SEND_CFLAGS:
-  switch (send_from_wbuf (conn)) {
+  switch (send_from_wbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
     /* Start sending options. */
-    if ((conn->gflags & NBD_FLAG_FIXED_NEWSTYLE) == 0)
+    if ((h->gflags & NBD_FLAG_FIXED_NEWSTYLE) == 0)
       SET_NEXT_STATE (%OPT_EXPORT_NAME.START);
     else
       SET_NEXT_STATE (%OPT_STARTTLS.START);
diff --git a/generator/states-oldstyle.c b/generator/states-oldstyle.c
index 29cb341..b5618af 100644
--- a/generator/states-oldstyle.c
+++ b/generator/states-oldstyle.c
@@ -23,15 +23,15 @@
   /* We've already read the first 16 bytes of the handshake, we must
    * now read the remainder.
    */
-  conn->rbuf = &conn->sbuf.old_handshake;
-  conn->rlen = sizeof conn->sbuf.old_handshake;
-  conn->rbuf += 16;
-  conn->rlen -= 16;
+  h->rbuf = &h->sbuf.old_handshake;
+  h->rlen = sizeof h->sbuf.old_handshake;
+  h->rbuf += 16;
+  h->rlen -= 16;
   SET_NEXT_STATE (%RECV_REMAINING);
   return 0;
 
  OLDSTYLE.RECV_REMAINING:
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:  SET_NEXT_STATE (%CHECK);
   }
@@ -42,14 +42,14 @@
   uint16_t gflags, eflags;
 
   /* We already checked the magic and version in MAGIC.CHECK_MAGIC. */
-  exportsize = be64toh (conn->sbuf.old_handshake.exportsize);
-  gflags = be16toh (conn->sbuf.old_handshake.gflags);
-  eflags = be16toh (conn->sbuf.old_handshake.eflags);
+  exportsize = be64toh (h->sbuf.old_handshake.exportsize);
+  gflags = be16toh (h->sbuf.old_handshake.gflags);
+  eflags = be16toh (h->sbuf.old_handshake.eflags);
 
-  conn->gflags = gflags;
-  debug (conn->h, "gflags: 0x%" PRIx16, gflags);
+  h->gflags = gflags;
+  debug (h, "gflags: 0x%" PRIx16, gflags);
 
-  if (nbd_internal_set_size_and_flags (conn->h, exportsize, eflags) == -1) {
+  if (nbd_internal_set_size_and_flags (h, exportsize, eflags) == -1) {
     SET_NEXT_STATE (%.DEAD);
     return -1;
   }
diff --git a/generator/states-reply-simple.c b/generator/states-reply-simple.c
index b7396b4..e170482 100644
--- a/generator/states-reply-simple.c
+++ b/generator/states-reply-simple.c
@@ -24,11 +24,11 @@
   uint32_t error;
   uint64_t handle;
 
-  error = be32toh (conn->sbuf.simple_reply.error);
-  handle = be64toh (conn->sbuf.simple_reply.handle);
+  error = be32toh (h->sbuf.simple_reply.error);
+  handle = be64toh (h->sbuf.simple_reply.handle);
 
   /* Find the command amongst the commands in flight. */
-  for (cmd = conn->cmds_in_flight; cmd != NULL; cmd = cmd->next) {
+  for (cmd = h->cmds_in_flight; cmd != NULL; cmd = cmd->next) {
     if (cmd->handle == handle)
       break;
   }
@@ -39,7 +39,7 @@
     return -1;
   }
 
-  if (cmd->type == NBD_CMD_READ && conn->structured_replies) {
+  if (cmd->type == NBD_CMD_READ && h->structured_replies) {
     set_error (0, "server sent unexpected simple reply for read");
     SET_NEXT_STATE(%.DEAD);
     return 0;
@@ -47,8 +47,8 @@
 
   cmd->error = error;
   if (cmd->error == 0 && cmd->type == NBD_CMD_READ) {
-    conn->rbuf = cmd->data;
-    conn->rlen = cmd->count;
+    h->rbuf = cmd->data;
+    h->rlen = cmd->count;
     SET_NEXT_STATE (%RECV_READ_PAYLOAD);
   }
   else {
@@ -57,7 +57,7 @@
   return 0;
 
  REPLY.SIMPLE_REPLY.RECV_READ_PAYLOAD:
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:  SET_NEXT_STATE (%^FINISH_COMMAND);
   }
diff --git a/generator/states-reply-structured.c b/generator/states-reply-structured.c
index 27d333e..e6c1a8a 100644
--- a/generator/states-reply-structured.c
+++ b/generator/states-reply-structured.c
@@ -23,20 +23,20 @@
   /* We've only read the simple_reply.  The structured_reply is longer,
    * so read the remaining part.
    */
-  if (!conn->structured_replies) {
+  if (!h->structured_replies) {
     set_error (0, "server sent unexpected structured reply");
     SET_NEXT_STATE(%.DEAD);
     return 0;
   }
-  conn->rbuf = &conn->sbuf;
-  conn->rbuf += sizeof conn->sbuf.simple_reply;
-  conn->rlen = sizeof conn->sbuf.sr.structured_reply;
-  conn->rlen -= sizeof conn->sbuf.simple_reply;
+  h->rbuf = &h->sbuf;
+  h->rbuf += sizeof h->sbuf.simple_reply;
+  h->rlen = sizeof h->sbuf.sr.structured_reply;
+  h->rlen -= sizeof h->sbuf.simple_reply;
   SET_NEXT_STATE (%RECV_REMAINING);
   return 0;
 
  REPLY.STRUCTURED_REPLY.RECV_REMAINING:
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:  SET_NEXT_STATE (%CHECK);
   }
@@ -48,13 +48,13 @@
   uint64_t handle;
   uint32_t length;
 
-  flags = be16toh (conn->sbuf.sr.structured_reply.flags);
-  type = be16toh (conn->sbuf.sr.structured_reply.type);
-  handle = be64toh (conn->sbuf.sr.structured_reply.handle);
-  length = be32toh (conn->sbuf.sr.structured_reply.length);
+  flags = be16toh (h->sbuf.sr.structured_reply.flags);
+  type = be16toh (h->sbuf.sr.structured_reply.type);
+  handle = be64toh (h->sbuf.sr.structured_reply.handle);
+  length = be32toh (h->sbuf.sr.structured_reply.length);
 
   /* Find the command amongst the commands in flight. */
-  for (cmd = conn->cmds_in_flight; cmd != NULL; cmd = cmd->next) {
+  for (cmd = h->cmds_in_flight; cmd != NULL; cmd = cmd->next) {
     if (cmd->handle == handle)
       break;
   }
@@ -69,13 +69,13 @@
   }
 
   if (NBD_REPLY_TYPE_IS_ERR (type)) {
-    if (length < sizeof conn->sbuf.sr.payload.error) {
+    if (length < sizeof h->sbuf.sr.payload.error) {
       SET_NEXT_STATE (%.DEAD);
       set_error (0, "too short length in structured reply error");
       return -1;
     }
-    conn->rbuf = &conn->sbuf.sr.payload.error;
-    conn->rlen = sizeof conn->sbuf.sr.payload.error;
+    h->rbuf = &h->sbuf.sr.payload.error;
+    h->rlen = sizeof h->sbuf.sr.payload.error;
     SET_NEXT_STATE (%RECV_ERROR);
     return 0;
   }
@@ -94,13 +94,13 @@
     return 0;
   }
   else if (type == NBD_REPLY_TYPE_OFFSET_DATA) {
-    if (length < sizeof conn->sbuf.sr.payload.offset_data) {
+    if (length < sizeof h->sbuf.sr.payload.offset_data) {
       SET_NEXT_STATE (%.DEAD);
       set_error (0, "too short length in NBD_REPLY_TYPE_OFFSET_DATA");
       return -1;
     }
-    conn->rbuf = &conn->sbuf.sr.payload.offset_data;
-    conn->rlen = sizeof conn->sbuf.sr.payload.offset_data;
+    h->rbuf = &h->sbuf.sr.payload.offset_data;
+    h->rlen = sizeof h->sbuf.sr.payload.offset_data;
     SET_NEXT_STATE (%RECV_OFFSET_DATA);
     return 0;
   }
@@ -110,8 +110,8 @@
       set_error (0, "invalid length in NBD_REPLY_TYPE_NONE");
       return -1;
     }
-    conn->rbuf = &conn->sbuf.sr.payload.offset_hole;
-    conn->rlen = sizeof conn->sbuf.sr.payload.offset_hole;
+    h->rbuf = &h->sbuf.sr.payload.offset_hole;
+    h->rlen = sizeof h->sbuf.sr.payload.offset_hole;
     SET_NEXT_STATE (%RECV_OFFSET_HOLE);
     return 0;
   }
@@ -130,15 +130,15 @@
     /* We read the context ID followed by all the entries into a
      * single array and deal with it at the end.
      */
-    free (conn->bs_entries);
-    conn->bs_entries = malloc (length);
-    if (conn->bs_entries == NULL) {
+    free (h->bs_entries);
+    h->bs_entries = malloc (length);
+    if (h->bs_entries == NULL) {
       SET_NEXT_STATE (%.DEAD);
       set_error (errno, "malloc");
       return -1;
     }
-    conn->rbuf = conn->bs_entries;
-    conn->rlen = length;
+    h->rbuf = h->bs_entries;
+    h->rlen = length;
     SET_NEXT_STATE (%RECV_BS_ENTRIES);
     return 0;
   }
@@ -149,12 +149,12 @@
   }
 
  REPLY.STRUCTURED_REPLY.RECV_ERROR:
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
     /* We skip the human readable error for now. XXX */
-    conn->rbuf = NULL;
-    conn->rlen = be16toh (conn->sbuf.sr.payload.error.len);
+    h->rbuf = NULL;
+    h->rlen = be16toh (h->sbuf.sr.payload.error.len);
     SET_NEXT_STATE (%RECV_ERROR_MESSAGE);
   }
   return 0;
@@ -165,15 +165,15 @@
   uint64_t handle;
   uint32_t error;
 
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    flags = be16toh (conn->sbuf.sr.structured_reply.flags);
-    handle = be64toh (conn->sbuf.sr.structured_reply.handle);
-    error = be32toh (conn->sbuf.sr.payload.error.error);
+    flags = be16toh (h->sbuf.sr.structured_reply.flags);
+    handle = be64toh (h->sbuf.sr.structured_reply.handle);
+    error = be32toh (h->sbuf.sr.payload.error.error);
 
     /* Find the command amongst the commands in flight. */
-    for (cmd = conn->cmds_in_flight; cmd != NULL; cmd = cmd->next) {
+    for (cmd = h->cmds_in_flight; cmd != NULL; cmd = cmd->next) {
       if (cmd->handle == handle)
         break;
     }
@@ -194,15 +194,15 @@
   uint64_t offset;
   uint32_t length;
 
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    handle = be64toh (conn->sbuf.sr.structured_reply.handle);
-    length = be32toh (conn->sbuf.sr.structured_reply.length);
-    offset = be64toh (conn->sbuf.sr.payload.offset_data.offset);
+    handle = be64toh (h->sbuf.sr.structured_reply.handle);
+    length = be32toh (h->sbuf.sr.structured_reply.length);
+    offset = be64toh (h->sbuf.sr.payload.offset_data.offset);
 
     /* Find the command amongst the commands in flight. */
-    for (cmd = conn->cmds_in_flight; cmd != NULL; cmd = cmd->next) {
+    for (cmd = h->cmds_in_flight; cmd != NULL; cmd = cmd->next) {
       if (cmd->handle == handle)
         break;
     }
@@ -244,8 +244,8 @@
     }
 
     /* Set up to receive the data directly to the user buffer. */
-    conn->rbuf = cmd->data + offset;
-    conn->rlen = length;
+    h->rbuf = cmd->data + offset;
+    h->rlen = length;
     SET_NEXT_STATE (%RECV_OFFSET_DATA_DATA);
   }
   return 0;
@@ -253,10 +253,10 @@
  REPLY.STRUCTURED_REPLY.RECV_OFFSET_DATA_DATA:
   uint16_t flags;
 
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    flags = be16toh (conn->sbuf.sr.structured_reply.flags);
+    flags = be16toh (h->sbuf.sr.structured_reply.flags);
 
     if (flags & NBD_REPLY_FLAG_DONE)
       SET_NEXT_STATE (%^FINISH_COMMAND);
@@ -272,16 +272,16 @@
   uint64_t offset;
   uint32_t length;
 
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    handle = be64toh (conn->sbuf.sr.structured_reply.handle);
-    flags = be16toh (conn->sbuf.sr.structured_reply.flags);
-    offset = be64toh (conn->sbuf.sr.payload.offset_hole.offset);
-    length = be32toh (conn->sbuf.sr.payload.offset_hole.length);
+    handle = be64toh (h->sbuf.sr.structured_reply.handle);
+    flags = be16toh (h->sbuf.sr.structured_reply.flags);
+    offset = be64toh (h->sbuf.sr.payload.offset_hole.offset);
+    length = be32toh (h->sbuf.sr.payload.offset_hole.length);
 
     /* Find the command amongst the commands in flight. */
-    for (cmd = conn->cmds_in_flight; cmd != NULL; cmd = cmd->next) {
+    for (cmd = h->cmds_in_flight; cmd != NULL; cmd = cmd->next) {
       if (cmd->handle == handle)
         break;
     }
@@ -337,33 +337,33 @@
   uint32_t context_id;
   struct meta_context *meta_context;
 
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0:
-    handle = be64toh (conn->sbuf.sr.structured_reply.handle);
-    flags = be16toh (conn->sbuf.sr.structured_reply.flags);
-    length = be32toh (conn->sbuf.sr.structured_reply.length);
+    handle = be64toh (h->sbuf.sr.structured_reply.handle);
+    flags = be16toh (h->sbuf.sr.structured_reply.flags);
+    length = be32toh (h->sbuf.sr.structured_reply.length);
 
     /* Find the command amongst the commands in flight. */
-    for (cmd = conn->cmds_in_flight; cmd != NULL; cmd = cmd->next) {
+    for (cmd = h->cmds_in_flight; cmd != NULL; cmd = cmd->next) {
       if (cmd->handle == handle)
         break;
     }
     /* guaranteed by CHECK */
     assert (cmd);
     assert (cmd->extent_fn);
-    assert (conn->bs_entries);
+    assert (h->bs_entries);
     assert (length >= 12);
 
     /* Need to byte-swap the entries returned, but apart from that we
      * don't validate them.
      */
     for (i = 0; i < length/4; ++i)
-      conn->bs_entries[i] = be32toh (conn->bs_entries[i]);
+      h->bs_entries[i] = be32toh (h->bs_entries[i]);
 
     /* Look up the context ID. */
-    context_id = conn->bs_entries[0];
-    for (meta_context = conn->meta_contexts;
+    context_id = h->bs_entries[0];
+    for (meta_context = h->meta_contexts;
          meta_context;
          meta_context = meta_context->next)
       if (context_id == meta_context->context_id)
@@ -372,7 +372,7 @@
     if (meta_context)
       /* Call the caller's extent function. */
       cmd->extent_fn (cmd->data, meta_context->name, cmd->offset,
-                      &conn->bs_entries[1], (length-4) / 4);
+                      &h->bs_entries[1], (length-4) / 4);
     else
       /* Emit a debug message, but ignore it. */
       debug (h, "server sent unexpected meta context ID %" PRIu32,
diff --git a/generator/states-reply.c b/generator/states-reply.c
index 45362d4..5be3431 100644
--- a/generator/states-reply.c
+++ b/generator/states-reply.c
@@ -33,10 +33,10 @@
    * check the magic in CHECK_SIMPLE_OR_STRUCTURED_REPLY below.
    * This works because the structured_reply header is larger.
    */
-  conn->rbuf = &conn->sbuf;
-  conn->rlen = sizeof conn->sbuf.simple_reply;
+  h->rbuf = &h->sbuf;
+  h->rlen = sizeof h->sbuf.simple_reply;
 
-  r = conn->sock->ops->recv (conn->sock, conn->rbuf, conn->rlen);
+  r = h->sock->ops->recv (h->sock, h->rbuf, h->rlen);
   if (r == -1) {
     /* This should never happen because when we enter this state we
      * should have notification that the socket is ready to read.
@@ -56,13 +56,13 @@
     return 0;
   }
 
-  conn->rbuf += r;
-  conn->rlen -= r;
+  h->rbuf += r;
+  h->rlen -= r;
   SET_NEXT_STATE (%RECV_REPLY);
   return 0;
 
  REPLY.RECV_REPLY:
-  switch (recv_into_rbuf (conn)) {
+  switch (recv_into_rbuf (h)) {
   case -1: SET_NEXT_STATE (%.DEAD); return -1;
   case 0: SET_NEXT_STATE (%CHECK_SIMPLE_OR_STRUCTURED_REPLY);
   }
@@ -71,7 +71,7 @@
  REPLY.CHECK_SIMPLE_OR_STRUCTURED_REPLY:
   uint32_t magic;
 
-  magic = be32toh (conn->sbuf.simple_reply.magic);
+  magic = be32toh (h->sbuf.simple_reply.magic);
   if (magic == NBD_SIMPLE_REPLY_MAGIC) {
     SET_NEXT_STATE (%SIMPLE_REPLY.START);
     return 0;
@@ -93,9 +93,9 @@
   /* NB: This works for both simple and structured replies because the
    * handle is stored at the same offset.
    */
-  handle = be64toh (conn->sbuf.simple_reply.handle);
+  handle = be64toh (h->sbuf.simple_reply.handle);
   /* Find the command amongst the commands in flight. */
-  for (cmd = conn->cmds_in_flight, prev_cmd = NULL;
+  for (cmd = h->cmds_in_flight, prev_cmd = NULL;
        cmd != NULL;
        prev_cmd = cmd, cmd = cmd->next) {
     if (cmd->handle == handle)
@@ -107,16 +107,16 @@
   if (prev_cmd != NULL)
     prev_cmd->next = cmd->next;
   else
-    conn->cmds_in_flight = cmd->next;
+    h->cmds_in_flight = cmd->next;
   cmd->next = NULL;
-  if (conn->cmds_done) {
-    prev_cmd = conn->cmds_done;
+  if (h->cmds_done) {
+    prev_cmd = h->cmds_done;
     while (prev_cmd->next)
       prev_cmd = prev_cmd->next;
     prev_cmd->next = cmd;
   }
   else
-    conn->cmds_done = cmd;
+    h->cmds_done = cmd;
 
   SET_NEXT_STATE (%.READY);
   return 0;
diff --git a/generator/states.c b/generator/states.c
index 22ce853..834fa44 100644
--- a/generator/states.c
+++ b/generator/states.c
@@ -39,29 +39,29 @@
 /*#define DUMP_PACKETS 1*/
 
 static int
-recv_into_rbuf (struct nbd_connection *conn)
+recv_into_rbuf (struct nbd_handle *h)
 {
   ssize_t r;
   char buf[BUFSIZ];
   void *rbuf;
   size_t rlen;
 
-  if (conn->rlen == 0)
+  if (h->rlen == 0)
     return 0;                   /* move to next state */
 
-  /* As a special case conn->rbuf is allowed to be NULL, meaning
+  /* As a special case h->rbuf is allowed to be NULL, meaning
    * throw away the data.
    */
-  if (conn->rbuf) {
-    rbuf = conn->rbuf;
-    rlen = conn->rlen;
+  if (h->rbuf) {
+    rbuf = h->rbuf;
+    rlen = h->rlen;
   }
   else {
     rbuf = &buf;
-    rlen = conn->rlen > sizeof buf ? sizeof buf : conn->rlen;
+    rlen = h->rlen > sizeof buf ? sizeof buf : h->rlen;
   }
 
-  r = conn->sock->ops->recv (conn->sock, rbuf, rlen);
+  r = h->sock->ops->recv (h->sock, rbuf, rlen);
   if (r == -1) {
     if (errno == EAGAIN || errno == EWOULDBLOCK)
       return 1;                 /* more data */
@@ -73,35 +73,35 @@ recv_into_rbuf (struct nbd_connection *conn)
     return -1;
   }
 #ifdef DUMP_PACKETS
-  if (conn->rbuf != NULL)
-    nbd_internal_hexdump (conn->rbuf, r, stderr);
+  if (h->rbuf != NULL)
+    nbd_internal_hexdump (h->rbuf, r, stderr);
 #endif
-  if (conn->rbuf)
-    conn->rbuf += r;
-  conn->rlen -= r;
-  if (conn->rlen == 0)
+  if (h->rbuf)
+    h->rbuf += r;
+  h->rlen -= r;
+  if (h->rlen == 0)
     return 0;                   /* move to next state */
   else
     return 1;                   /* more data */
 }
 
 static int
-send_from_wbuf (struct nbd_connection *conn)
+send_from_wbuf (struct nbd_handle *h)
 {
   ssize_t r;
 
-  if (conn->wlen == 0)
+  if (h->wlen == 0)
     return 0;                   /* move to next state */
-  r = conn->sock->ops->send (conn->sock, conn->wbuf, conn->wlen);
+  r = h->sock->ops->send (h->sock, h->wbuf, h->wlen);
   if (r == -1) {
     if (errno == EAGAIN || errno == EWOULDBLOCK)
       return 1;                 /* more data */
     /* sock->ops->send called set_error already. */
     return -1;
   }
-  conn->wbuf += r;
-  conn->wlen -= r;
-  if (conn->wlen == 0)
+  h->wbuf += r;
+  h->wlen -= r;
+  if (h->wlen == 0)
     return 0;                   /* move to next state */
   else
     return 1;                   /* more data */
@@ -111,21 +111,21 @@ send_from_wbuf (struct nbd_connection *conn)
 
 /* STATE MACHINE */ {
  READY:
-  if (conn->cmds_to_issue)
+  if (h->cmds_to_issue)
     SET_NEXT_STATE (%ISSUE_COMMAND.START);
   return 0;
 
  DEAD:
-  if (conn->sock) {
-    conn->sock->ops->close (conn->sock);
-    conn->sock = NULL;
+  if (h->sock) {
+    h->sock->ops->close (h->sock);
+    h->sock = NULL;
   }
   return 0;
 
  CLOSED:
-  if (conn->sock) {
-    conn->sock->ops->close (conn->sock);
-    conn->sock = NULL;
+  if (h->sock) {
+    h->sock->ops->close (h->sock);
+    h->sock = NULL;
   }
   return 0;
 
diff --git a/lib/aio.c b/lib/aio.c
index c952a35..5adc3cd 100644
--- a/lib/aio.c
+++ b/lib/aio.c
@@ -27,31 +27,31 @@
 #include "internal.h"
 
 int
-nbd_unlocked_aio_get_fd (struct nbd_connection *conn)
+nbd_unlocked_aio_get_fd (struct nbd_handle *h)
 {
-  if (!conn->sock) {
+  if (!h->sock) {
     set_error (EINVAL, "connection is not in a connected state");
     return -1;
   }
-  return conn->sock->ops->get_fd (conn->sock);
+  return h->sock->ops->get_fd (h->sock);
 }
 
 int
-nbd_unlocked_aio_notify_read (struct nbd_connection *conn)
+nbd_unlocked_aio_notify_read (struct nbd_handle *h)
 {
-  return nbd_internal_run (conn->h, conn, notify_read);
+  return nbd_internal_run (h, notify_read);
 }
 
 int
-nbd_unlocked_aio_notify_write (struct nbd_connection *conn)
+nbd_unlocked_aio_notify_write (struct nbd_handle *h)
 {
-  return nbd_internal_run (conn->h, conn, notify_write);
+  return nbd_internal_run (h, notify_write);
 }
 
 int
-nbd_unlocked_aio_is_created (struct nbd_connection *conn)
+nbd_unlocked_aio_is_created (struct nbd_handle *h)
 {
-  return conn->state == STATE_START;
+  return h->state == STATE_START;
 }
 
 static int
@@ -73,17 +73,17 @@ is_connecting_group (enum state_group group)
 }
 
 int
-nbd_unlocked_aio_is_connecting (struct nbd_connection *conn)
+nbd_unlocked_aio_is_connecting (struct nbd_handle *h)
 {
-  enum state_group group = nbd_internal_state_group (conn->state);
+  enum state_group group = nbd_internal_state_group (h->state);
 
   return is_connecting_group (group);
 }
 
 int
-nbd_unlocked_aio_is_ready (struct nbd_connection *conn)
+nbd_unlocked_aio_is_ready (struct nbd_handle *h)
 {
-  return conn->state == STATE_READY;
+  return h->state == STATE_READY;
 }
 
 static int
@@ -101,27 +101,27 @@ is_processing_group (enum state_group group)
 }
 
 int
-nbd_unlocked_aio_is_processing (struct nbd_connection *conn)
+nbd_unlocked_aio_is_processing (struct nbd_handle *h)
 {
-  enum state_group group = nbd_internal_state_group (conn->state);
+  enum state_group group = nbd_internal_state_group (h->state);
 
   return is_processing_group (group);
 }
 
 int
-nbd_unlocked_aio_is_dead (struct nbd_connection *conn)
+nbd_unlocked_aio_is_dead (struct nbd_handle *h)
 {
-  return conn->state == STATE_DEAD;
+  return h->state == STATE_DEAD;
 }
 
 int
-nbd_unlocked_aio_is_closed (struct nbd_connection *conn)
+nbd_unlocked_aio_is_closed (struct nbd_handle *h)
 {
-  return conn->state == STATE_CLOSED;
+  return h->state == STATE_CLOSED;
 }
 
 int
-nbd_unlocked_aio_command_completed (struct nbd_connection *conn,
+nbd_unlocked_aio_command_completed (struct nbd_handle *h,
                                     int64_t handle)
 {
   struct command_in_flight *prev_cmd, *cmd;
@@ -134,7 +134,7 @@ nbd_unlocked_aio_command_completed (struct nbd_connection *conn,
   }
 
   /* Find the command amongst the completed commands. */
-  for (cmd = conn->cmds_done, prev_cmd = NULL;
+  for (cmd = h->cmds_done, prev_cmd = NULL;
        cmd != NULL;
        prev_cmd = cmd, cmd = cmd->next) {
     if (cmd->handle == handle)
@@ -150,7 +150,7 @@ nbd_unlocked_aio_command_completed (struct nbd_connection *conn,
   if (prev_cmd != NULL)
     prev_cmd->next = cmd->next;
   else
-    conn->cmds_done = cmd->next;
+    h->cmds_done = cmd->next;
 
   free (cmd);
 
@@ -166,12 +166,12 @@ nbd_unlocked_aio_command_completed (struct nbd_connection *conn,
 }
 
 int64_t
-nbd_unlocked_aio_peek_command_completed (struct nbd_connection *conn)
+nbd_unlocked_aio_peek_command_completed (struct nbd_handle *h)
 {
-  if (conn->cmds_done != NULL)
-    return conn->cmds_done->handle;
+  if (h->cmds_done != NULL)
+    return h->cmds_done->handle;
 
-  if (conn->cmds_in_flight != NULL || conn->cmds_to_issue != NULL) {
+  if (h->cmds_in_flight != NULL || h->cmds_to_issue != NULL) {
     set_error (0, "no in-flight command has completed yet");
     return 0;
   }
diff --git a/lib/connect.c b/lib/connect.c
index 61d945f..71c3cbf 100644
--- a/lib/connect.c
+++ b/lib/connect.c
@@ -33,18 +33,18 @@
 #include "internal.h"
 
 static int
-error_unless_ready (struct nbd_connection *conn)
+error_unless_ready (struct nbd_handle *h)
 {
-  if (nbd_unlocked_aio_is_ready (conn))
+  if (nbd_unlocked_aio_is_ready (h))
     return 0;
 
   /* Why did it fail? */
-  if (nbd_unlocked_aio_is_closed (conn)) {
+  if (nbd_unlocked_aio_is_closed (h)) {
     set_error (0, "connection is closed");
     return -1;
   }
 
-  if (nbd_unlocked_aio_is_dead (conn))
+  if (nbd_unlocked_aio_is_dead (h))
     /* Don't set the error here, keep the error set when
      * the connection died.
      */
@@ -52,77 +52,37 @@ error_unless_ready (struct nbd_connection *conn)
 
   /* Should probably never happen. */
   set_error (0, "connection in an unexpected state (%s)",
-             nbd_internal_state_short_string (conn->state));
+             nbd_internal_state_short_string (h->state));
   return -1;
 }
 
 static int
-wait_all_connected (struct nbd_handle *h)
+wait_until_connected (struct nbd_handle *h)
 {
-  size_t i;
-
-  for (;;) {
-    bool all_done = true;
-
-    /* Are any not yet connected? */
-    for (i = 0; i < h->multi_conn; ++i) {
-      if (nbd_unlocked_aio_is_connecting (h->conns[i])) {
-        all_done = false;
-        break;
-      }
-    }
-
-    if (all_done)
-      break;
-
+  while (nbd_unlocked_aio_is_connecting (h)) {
     if (nbd_unlocked_poll (h, -1) == -1)
       return -1;
   }
 
-  /* All connections should be in the READY state, unless there was an
-   * error on one of them.
-   */
-  for (i = 0; i < h->multi_conn; ++i) {
-    if (error_unless_ready (h->conns[i]) == -1)
-      return -1;
-  }
-
-  return 0;
+  return error_unless_ready (h);
 }
 
-/* For all connections in the initial state, start connecting them to
- * a Unix domain socket.  Wait until all connections are in the READY
- * state.
- */
+/* Connect to a Unix domain socket. */
 int
 nbd_unlocked_connect_unix (struct nbd_handle *h, const char *sockpath)
 {
-  size_t i;
   struct sockaddr_un sun;
   socklen_t len;
-  bool started;
 
   sun.sun_family = AF_UNIX;
   memset (sun.sun_path, 0, sizeof (sun.sun_path));
   strncpy (sun.sun_path, sockpath, sizeof (sun.sun_path) - 1);
   len = sizeof (sun.sun_family) + strlen (sun.sun_path) + 1;
 
-  started = false;
-  for (i = 0; i < h->multi_conn; ++i) {
-    if (nbd_unlocked_aio_is_created (h->conns[i])) {
-      if (nbd_unlocked_aio_connect (h->conns[i],
-                                    (struct sockaddr *) &sun, len) == -1)
-        return -1;
-      started = true;
-    }
-  }
-
-  if (!started) {
-    set_error (0, "no connections in this handle were in the created state, this is likely to be caused by a programming error in the calling program");
+  if (nbd_unlocked_aio_connect (h, (struct sockaddr *) &sun, len) == -1)
     return -1;
-  }
 
-  return wait_all_connected (h);
+  return wait_until_connected (h);
 }
 
 /* Connect to a TCP port. */
@@ -130,98 +90,90 @@ int
 nbd_unlocked_connect_tcp (struct nbd_handle *h,
                           const char *hostname, const char *port)
 {
-  size_t i;
-  bool started;
-
-  started = false;
-  for (i = 0; i < h->multi_conn; ++i) {
-    if (nbd_unlocked_aio_is_created (h->conns[i])) {
-      if (nbd_unlocked_aio_connect_tcp (h->conns[i], hostname, port) == -1)
-        return -1;
-      started = true;
-    }
-  }
-
-  if (!started) {
-    set_error (0, "no connections in this handle were in the created state, this is likely to be caused by a programming error in the calling program");
+  if (nbd_unlocked_aio_connect_tcp (h, hostname, port) == -1)
     return -1;
-  }
 
-  return wait_all_connected (h);
+  return wait_until_connected (h);
 }
 
 /* Connect to a local command. */
 int
 nbd_unlocked_connect_command (struct nbd_handle *h, char **argv)
 {
-  if (h->multi_conn > 1) {
-    set_error (EINVAL, "multi-conn cannot be used when connecting to a command");
+  if (nbd_unlocked_aio_connect_command (h, argv) == -1)
     return -1;
-  }
 
-  if (!nbd_unlocked_aio_is_created (h->conns[0])) {
-    set_error (0, "first connection in this handle is not in the created state, this is likely to be caused by a programming error in the calling program");
-    return -1;
-  }
+  return wait_until_connected (h);
+}
 
-  if (nbd_unlocked_aio_connect_command (h->conns[0], argv) == -1)
+static int
+error_unless_start (struct nbd_handle *h)
+{
+  if (!nbd_unlocked_aio_is_created (h)) {
+    set_error (EINVAL, "connection is not in the initially created state, "
+               "this is likely to be caused by a programming error "
+               "in the calling program");
     return -1;
-
-  while (nbd_unlocked_aio_is_connecting (h->conns[0])) {
-    if (nbd_unlocked_poll (h, -1) == -1)
-      return -1;
   }
 
-  return error_unless_ready (h->conns[0]);
+  return 0;
 }
 
 int
-nbd_unlocked_aio_connect (struct nbd_connection *conn,
+nbd_unlocked_aio_connect (struct nbd_handle *h,
                           const struct sockaddr *addr, socklen_t len)
 {
-  memcpy (&conn->connaddr, addr, len);
-  conn->connaddrlen = len;
+  if (error_unless_start (h) == -1)
+    return -1;
 
-  return nbd_internal_run (conn->h, conn, cmd_connect_sockaddr);
+  memcpy (&h->connaddr, addr, len);
+  h->connaddrlen = len;
+
+  return nbd_internal_run (h, cmd_connect_sockaddr);
 }
 
 int
-nbd_unlocked_aio_connect_tcp (struct nbd_connection *conn,
+nbd_unlocked_aio_connect_tcp (struct nbd_handle *h,
                               const char *hostname, const char *port)
 {
-  if (conn->hostname)
-    free (conn->hostname);
-  conn->hostname = strdup (hostname);
-  if (!conn->hostname) {
+  if (error_unless_start (h) == -1)
+    return -1;
+
+  if (h->hostname)
+    free (h->hostname);
+  h->hostname = strdup (hostname);
+  if (!h->hostname) {
     set_error (errno, "strdup");
     return -1;
   }
-  if (conn->port)
-    free (conn->port);
-  conn->port = strdup (port);
-  if (!conn->port) {
+  if (h->port)
+    free (h->port);
+  h->port = strdup (port);
+  if (!h->port) {
     set_error (errno, "strdup");
     return -1;
   }
 
-  return nbd_internal_run (conn->h, conn, cmd_connect_tcp);
+  return nbd_internal_run (h, cmd_connect_tcp);
 }
 
 int
-nbd_unlocked_aio_connect_command (struct nbd_connection *conn,
-                                  char **argv)
+nbd_unlocked_aio_connect_command (struct nbd_handle *h, char **argv)
 {
   char **copy;
 
+  if (error_unless_start (h) == -1)
+    return -1;
+
   copy = nbd_internal_copy_string_list (argv);
   if (!copy) {
     set_error (errno, "copy_string_list");
     return -1;
   }
 
-  if (conn->argv)
-    nbd_internal_free_string_list (conn->argv);
-  conn->argv = copy;
+  if (h->argv)
+    nbd_internal_free_string_list (h->argv);
+  h->argv = copy;
 
-  return nbd_internal_run (conn->h, conn, cmd_connect_command);
+  return nbd_internal_run (h, cmd_connect_command);
 }
diff --git a/lib/crypto.c b/lib/crypto.c
index 3ab0238..57db18f 100644
--- a/lib/crypto.c
+++ b/lib/crypto.c
@@ -267,7 +267,7 @@ lookup_key (const char *pskfile, const char *username,
 }
 
 static gnutls_psk_client_credentials_t
-set_up_psk_credentials (struct nbd_connection *conn, gnutls_session_t session)
+set_up_psk_credentials (struct nbd_handle *h, gnutls_session_t session)
 {
   int err;
   const char prio[] = TLS_PRIORITY ":" "+ECDHE-PSK:+DHE-PSK:+PSK";
@@ -281,11 +281,11 @@ set_up_psk_credentials (struct nbd_connection *conn, gnutls_session_t session)
     goto error;
   }
 
-  username = nbd_unlocked_get_tls_username (conn->h);
+  username = nbd_unlocked_get_tls_username (h);
   if (username == NULL)
     goto error;
 
-  if (lookup_key (conn->h->tls_psk_file, username, &key) == -1)
+  if (lookup_key (h->tls_psk_file, username, &key) == -1)
     goto error;
 
   err = gnutls_psk_allocate_client_credentials (&ret);
@@ -404,7 +404,7 @@ load_certificates (const char *path, gnutls_certificate_credentials_t *ret)
 }
 
 static gnutls_certificate_credentials_t
-set_up_certificate_credentials (struct nbd_connection *conn,
+set_up_certificate_credentials (struct nbd_handle *h,
                                 gnutls_session_t session, bool *is_error)
 {
   int err;
@@ -419,8 +419,8 @@ set_up_certificate_credentials (struct nbd_connection *conn,
   }
 
   /* Try to load the certificates from the directory. */
-  if (conn->h->tls_certificates) {
-    if (load_certificates (conn->h->tls_certificates, &ret) == -1)
+  if (h->tls_certificates) {
+    if (load_certificates (h->tls_certificates, &ret) == -1)
       goto error;
     if (ret)
       goto found_certificates;
@@ -459,10 +459,10 @@ set_up_certificate_credentials (struct nbd_connection *conn,
 
  found_certificates:
 #ifdef HAVE_GNUTLS_SESSION_SET_VERIFY_CERT
-  if (conn->hostname && conn->h->tls_verify_peer)
-    gnutls_session_set_verify_cert (session, conn->hostname, 0);
+  if (h->hostname && h->tls_verify_peer)
+    gnutls_session_set_verify_cert (session, h->hostname, 0);
 #else
-  debug (conn->h, "ignoring nbd_set_tls_verify_peer, this requires GnuTLS >= 3.4.6");
+  debug (h, "ignoring nbd_set_tls_verify_peer, this requires GnuTLS >= 3.4.6");
 #endif
 
   err = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, ret);
@@ -482,7 +482,7 @@ set_up_certificate_credentials (struct nbd_connection *conn,
 }
 
 static gnutls_certificate_credentials_t
-set_up_system_CA (struct nbd_connection *conn, gnutls_session_t session)
+set_up_system_CA (struct nbd_handle *h, gnutls_session_t session)
 {
   int err;
   gnutls_certificate_credentials_t ret = NULL;
@@ -524,7 +524,7 @@ set_up_system_CA (struct nbd_connection *conn, gnutls_session_t session)
  * ops.
  */
 struct socket *
-nbd_internal_crypto_create_session (struct nbd_connection *conn,
+nbd_internal_crypto_create_session (struct nbd_handle *h,
                                     struct socket *oldsock)
 {
   int err;
@@ -540,9 +540,9 @@ nbd_internal_crypto_create_session (struct nbd_connection *conn,
   }
 
   /* If we have the server name, pass SNI. */
-  if (conn->hostname) {
+  if (h->hostname) {
     err = gnutls_server_name_set (session, GNUTLS_NAME_DNS,
-                                  conn->hostname, strlen (conn->hostname));
+                                  h->hostname, strlen (h->hostname));
     if (err < 0) {
       set_error (errno, "gnutls_server_name_set: %s", gnutls_strerror (err));
       gnutls_deinit (session);
@@ -550,8 +550,8 @@ nbd_internal_crypto_create_session (struct nbd_connection *conn,
     }
   }
 
-  if (conn->h->tls_psk_file) {
-    pskcreds = set_up_psk_credentials (conn, session);
+  if (h->tls_psk_file) {
+    pskcreds = set_up_psk_credentials (h, session);
     if (pskcreds == NULL) {
       gnutls_deinit (session);
       return NULL;
@@ -560,11 +560,11 @@ nbd_internal_crypto_create_session (struct nbd_connection *conn,
   else {
     bool is_error = false;
 
-    xcreds = set_up_certificate_credentials (conn, session, &is_error);
+    xcreds = set_up_certificate_credentials (h, session, &is_error);
     if (xcreds == NULL) {
       if (!is_error) {
         /* Fallback case: use system CA. */
-        xcreds = set_up_system_CA (conn, session);
+        xcreds = set_up_system_CA (h, session);
         if (xcreds == NULL)
           is_error = true;
       }
@@ -602,10 +602,10 @@ nbd_internal_crypto_create_session (struct nbd_connection *conn,
 
 /* Return the read/write direction. */
 bool
-nbd_internal_crypto_is_reading (struct nbd_connection *conn)
+nbd_internal_crypto_is_reading (struct nbd_handle *h)
 {
-  assert (conn->sock->u.tls.session);
-  return gnutls_record_get_direction (conn->sock->u.tls.session) == 0;
+  assert (h->sock->u.tls.session);
+  return gnutls_record_get_direction (h->sock->u.tls.session) == 0;
 }
 
 /* Continue with the TLS handshake.  Returns 0 if the handshake
@@ -613,11 +613,11 @@ nbd_internal_crypto_is_reading (struct nbd_connection *conn)
  * there was a GnuTLS error.
  */
 int
-nbd_internal_crypto_handshake (struct nbd_connection *conn)
+nbd_internal_crypto_handshake (struct nbd_handle *h)
 {
   int err;
   gnutls_handshake_description_t in, out;
-  const gnutls_session_t session = conn->sock->u.tls.session;
+  const gnutls_session_t session = h->sock->u.tls.session;
 
   assert (session);
   err = gnutls_handshake (session);
@@ -642,15 +642,15 @@ nbd_internal_crypto_handshake (struct nbd_connection *conn)
  * useful debugging information.
  */
 void
-nbd_internal_crypto_debug_tls_enabled (struct nbd_connection *conn)
+nbd_internal_crypto_debug_tls_enabled (struct nbd_handle *h)
 {
-  if (conn->h->debug) {
-    const gnutls_session_t session = conn->sock->u.tls.session;
+  if (h->debug) {
+    const gnutls_session_t session = h->sock->u.tls.session;
     const gnutls_cipher_algorithm_t cipher = gnutls_cipher_get (session);
     const gnutls_kx_algorithm_t kx = gnutls_kx_get (session);
     const gnutls_mac_algorithm_t mac = gnutls_mac_get (session);
 
-    debug (conn->h,
+    debug (h,
            "connection is using TLS: "
            "cipher %s (%zu bits) key exchange %s mac %s (%zu bits)",
            gnutls_cipher_get_name (cipher),
@@ -668,25 +668,25 @@ nbd_internal_crypto_debug_tls_enabled (struct nbd_connection *conn)
  * !HAVE_GNUTLS.
  */
 int
-nbd_internal_crypto_create_session (struct nbd_connection *conn)
+nbd_internal_crypto_create_session (struct nbd_handle *h)
 {
   abort ();
 }
 
 bool
-nbd_internal_crypto_is_reading (struct nbd_connection *conn)
+nbd_internal_crypto_is_reading (struct nbd_handle *h)
 {
   abort ();
 }
 
 int
-nbd_internal_crypto_handshake (struct nbd_connection *conn)
+nbd_internal_crypto_handshake (struct nbd_handle *h)
 {
   abort ();
 }
 
 void
-nbd_internal_crypto_debug_tls_enabled (struct nbd_connection *conn)
+nbd_internal_crypto_debug_tls_enabled (struct nbd_handle *h)
 {
   abort ();
 }
diff --git a/lib/disconnect.c b/lib/disconnect.c
index a4338d7..23e95dd 100644
--- a/lib/disconnect.c
+++ b/lib/disconnect.c
@@ -28,29 +28,15 @@
 int
 nbd_unlocked_shutdown (struct nbd_handle *h)
 {
-  size_t i;
 
-  for (i = 0; i < h->multi_conn; ++i) {
-    if (nbd_unlocked_aio_is_ready (h->conns[i]) ||
-        nbd_unlocked_aio_is_processing (h->conns[i])) {
-      if (nbd_unlocked_aio_disconnect (h->conns[i]) == -1)
-        return -1;
-    }
+  if (nbd_unlocked_aio_is_ready (h) ||
+      nbd_unlocked_aio_is_processing (h)) {
+    if (nbd_unlocked_aio_disconnect (h) == -1)
+      return -1;
   }
 
-  /* Wait until all sockets are closed or dead. */
-  for (;;) {
-    bool finished = true;
-
-    for (i = 0; i < h->multi_conn; ++i) {
-      if (!nbd_unlocked_aio_is_closed (h->conns[i]) &&
-          !nbd_unlocked_aio_is_dead (h->conns[i]))
-        finished = false;
-    }
-
-    if (finished)
-      break;
-
+  while (!nbd_unlocked_aio_is_closed (h) &&
+         !nbd_unlocked_aio_is_dead (h)) {
     if (nbd_unlocked_poll (h, -1) == -1)
       return -1;
   }
@@ -59,11 +45,11 @@ nbd_unlocked_shutdown (struct nbd_handle *h)
 }
 
 int
-nbd_unlocked_aio_disconnect (struct nbd_connection *conn)
+nbd_unlocked_aio_disconnect (struct nbd_handle *h)
 {
   int64_t id;
 
-  id = nbd_internal_command_common (conn, 0, NBD_CMD_DISC, 0, 0, NULL, NULL);
+  id = nbd_internal_command_common (h, 0, NBD_CMD_DISC, 0, 0, NULL, NULL);
   if (id == -1)
     return -1;
 
diff --git a/lib/flags.c b/lib/flags.c
index cd0e7ba..421a7d2 100644
--- a/lib/flags.c
+++ b/lib/flags.c
@@ -42,18 +42,6 @@ nbd_internal_set_size_and_flags (struct nbd_handle *h,
     return -1;
   }
 
-  /* It is unsafe to connect to a server with multi-conn set unless
-   * the server says it is safe to do so.
-   */
-  if (h->multi_conn > 1 &&
-      (eflags & NBD_FLAG_CAN_MULTI_CONN) == 0 &&
-      (eflags & NBD_FLAG_READ_ONLY) != 0) {
-    set_error (EINVAL, "handshake: multi-conn is set on this handle, "
-               "but the server does not advertize multi-conn support "
-               "so disconnecting because it is not safe to continue");
-    return -1;
-  }
-
   h->exportsize = exportsize;
   h->eflags = eflags;
   return 0;
@@ -122,29 +110,9 @@ nbd_unlocked_can_cache (struct nbd_handle *h)
 int
 nbd_unlocked_can_meta_context (struct nbd_handle *h, const char *name)
 {
-  struct nbd_connection *conn = NULL;
-  int i;
   struct meta_context *meta_context;
 
-  /* Unlike other can_FOO, this is not tracked in h->eflags, but is a
-   * per-connection result. Find first ready connection, and assume
-   * that all other connections will have the same set of contexts
-   * (although not necessarily the same ordering or context ids).
-   */
-  for (i = 0; i < h->multi_conn; ++i) {
-    if (!nbd_unlocked_aio_is_created (h->conns[i]) &&
-        !nbd_unlocked_aio_is_connecting (h->conns[i])) {
-      conn = h->conns[i];
-      break;
-    }
-  }
-
-  if (conn == NULL) {
-    set_error (ENOTCONN, "handshake is not yet complete");
-    return -1;
-  }
-
-  for (meta_context = conn->meta_contexts;
+  for (meta_context = h->meta_contexts;
        meta_context;
        meta_context = meta_context->next)
     if (strcmp (meta_context->name, name) == 0)
diff --git a/lib/handle.c b/lib/handle.c
index 24e58e4..01b8c79 100644
--- a/lib/handle.c
+++ b/lib/handle.c
@@ -28,28 +28,6 @@
 
 #include "internal.h"
 
-static struct nbd_connection *
-create_conn (struct nbd_handle *h)
-{
-  struct nbd_connection *conn;
-
-  conn = calloc (1, sizeof *conn);
-  if (conn == NULL)
-    return NULL;
-
-  conn->id = h->unique++;
-  conn->state = STATE_START;
-  conn->pid = -1;
-  conn->h = h;
-
-  if (nbd_internal_run (h, conn, cmd_create) == -1) {
-    free (conn);
-    return NULL;
-  }
-
-  return conn;
-}
-
 static void
 free_cmd_list (struct command_in_flight *list)
 {
@@ -61,37 +39,10 @@ free_cmd_list (struct command_in_flight *list)
   }
 }
 
-static void
-close_conn (struct nbd_connection *conn)
-{
-  struct meta_context *m, *m_next;
-
-  free (conn->bs_entries);
-  for (m = conn->meta_contexts; m != NULL; m = m_next) {
-    m_next = m->next;
-    free (m->name);
-    free (m);
-  }
-  free_cmd_list (conn->cmds_to_issue);
-  free_cmd_list (conn->cmds_in_flight);
-  free_cmd_list (conn->cmds_done);
-  nbd_internal_free_string_list (conn->argv);
-  free (conn->hostname);
-  free (conn->port);
-  if (conn->result)
-    freeaddrinfo (conn->result);
-  if (conn->sock)
-    conn->sock->ops->close (conn->sock);
-  if (conn->pid >= 0) /* XXX kill it? */
-    waitpid (conn->pid, NULL, 0);
-  free (conn);
-}
-
 struct nbd_handle *
 nbd_create (void)
 {
   struct nbd_handle *h;
-  struct nbd_connection *conn;
   const char *s;
 
   h = calloc (1, sizeof *h);
@@ -100,36 +51,30 @@ nbd_create (void)
     goto error1;
   }
 
-  errno = pthread_mutex_init (&h->lock, NULL);
-  if (errno != 0) {
-    set_error (errno, "pthread_mutex_init");
-    goto error1;
-  }
-
-  h->export_name = strdup ("");
-  if (h->export_name == NULL) {
-    set_error (errno, "strdup");
-    goto error2;
-  }
-
-  h->conns = malloc (sizeof (struct nbd_connection *));
-  if (h->conns == NULL) {
-    set_error (errno, "malloc");
-    goto error2;
-  }
-
-  /* Handles are created with a single connection. */
-  conn = create_conn (h);
-  if (conn == NULL) goto error2;
-
-  h->conns[0] = conn;
-  h->multi_conn = 1;
   h->unique = 1;
   h->tls_verify_peer = true;
 
   s = getenv ("LIBNBD_DEBUG");
   h->debug = s && strcmp (s, "1") == 0;
 
+  h->state = STATE_START;
+  h->pid = -1;
+
+  h->export_name = strdup ("");
+  if (h->export_name == NULL) {
+    set_error (errno, "strdup");
+    goto error1;
+  }
+
+  errno = pthread_mutex_init (&h->lock, NULL);
+  if (errno != 0) {
+    set_error (errno, "pthread_mutex_init");
+    goto error1;
+  }
+
+  if (nbd_internal_run (h, cmd_create) == -1)
+    goto error2;
+
   return h;
 
  error2:
@@ -137,7 +82,6 @@ nbd_create (void)
  error1:
   if (h) {
     free (h->export_name);
-    free (h->conns);
     free (h);
   }
   return NULL;
@@ -146,11 +90,27 @@ nbd_create (void)
 void
 nbd_close (struct nbd_handle *h)
 {
-  size_t i;
+  struct meta_context *m, *m_next;
+
+  free (h->bs_entries);
+  for (m = h->meta_contexts; m != NULL; m = m_next) {
+    m_next = m->next;
+    free (m->name);
+    free (m);
+  }
+  free_cmd_list (h->cmds_to_issue);
+  free_cmd_list (h->cmds_in_flight);
+  free_cmd_list (h->cmds_done);
+  nbd_internal_free_string_list (h->argv);
+  free (h->hostname);
+  free (h->port);
+  if (h->result)
+    freeaddrinfo (h->result);
+  if (h->sock)
+    h->sock->ops->close (h->sock);
+  if (h->pid >= 0) /* XXX kill it? */
+    waitpid (h->pid, NULL, 0);
 
-  for (i = 0; i < h->multi_conn; ++i)
-    close_conn (h->conns[i]);
-  free (h->conns);
   free (h->export_name);
   free (h->tls_certificates);
   free (h->tls_username);
@@ -160,112 +120,6 @@ nbd_close (struct nbd_handle *h)
   free (h);
 }
 
-struct nbd_connection *
-nbd_get_connection (struct nbd_handle *h, unsigned i)
-{
-  struct nbd_connection *conn;
-
-  pthread_mutex_lock (&h->lock);
-  nbd_internal_reset_error ("nbd_get_connection");
-
-  if (i >= h->multi_conn) {
-    set_error (0, "%u > number of connections", i);
-    pthread_mutex_unlock (&h->lock);
-    return NULL;
-  }
-
-  conn = h->conns[i];
-  pthread_mutex_unlock (&h->lock);
-  return conn;
-}
-
-int
-nbd_connection_close (struct nbd_connection *conn)
-{
-  struct nbd_handle *h;
-  size_t i;
-
-  if (conn == NULL)
-    return 0;
-
-  h = conn->h;
-  pthread_mutex_lock (&h->lock);
-  nbd_internal_reset_error ("nbd_connection_close");
-
-  /* We have to find the connection in the list of connections of the
-   * parent handle, so we can drop the old pointer and create a new
-   * connection.
-   */
-  for (i = 0; i < h->multi_conn; ++i)
-    if (conn == h->conns[i])
-      goto found;
-
-  /* This should never happen (I think?) so it may be appropriate
-   * to abort here.
-   */
-  set_error (EINVAL, "connection not found in parent handle");
-  pthread_mutex_unlock (&h->lock);
-  return -1;
-
- found:
-  h->conns[i] = create_conn (h);
-  if (h->conns[i] == NULL) {
-    set_error (errno, "create_conn");
-    pthread_mutex_unlock (&h->lock);
-    return -1;
-  }
-
-  close_conn (conn);
-  pthread_mutex_unlock (&h->lock);
-  return 0;
-}
-
-int
-nbd_unlocked_set_multi_conn (struct nbd_handle *h, unsigned multi_conn)
-{
-  struct nbd_connection **new_conns;
-  size_t i;
-
-  if (multi_conn < 1) {
-    set_error (EINVAL, "multi_conn parameter must be >= 1");
-    return -1;
-  }
-
-  /* We try to be careful to leave the handle in a valid state even if
-   * a memory allocation fails.
-   */
-  new_conns = realloc (h->conns, multi_conn * sizeof (struct nbd_connection *));
-  if (new_conns == NULL) {
-    set_error (errno, "realloc");
-    return -1;
-  }
-
-  /* If we're growing the array, allocate the new connections. */
-  for (i = h->multi_conn; i < multi_conn; ++i) {
-    new_conns[i] = create_conn (h);
-    if (new_conns[i] == NULL) {
-      set_error (errno, "create_conn");
-      for (--i; i >= h->multi_conn; --i)
-        close_conn (new_conns[i]);
-      return -1;
-    }
-  }
-
-  /* If we're reducing the array, close the extra connections. */
-  for (i = multi_conn; i < h->multi_conn; ++i)
-    close_conn (h->conns[i]);
-
-  h->conns = new_conns;
-  h->multi_conn = multi_conn;
-  return 0;
-}
-
-int
-nbd_unlocked_get_multi_conn (struct nbd_handle *h)
-{
-  return h->multi_conn;
-}
-
 int
 nbd_unlocked_set_export_name (struct nbd_handle *h, const char *export_name)
 {
@@ -301,14 +155,11 @@ nbd_unlocked_request_meta_context (struct nbd_handle *h, const char *name)
   char *copy;
   size_t len;
   char **list;
-  int i;
 
-  for (i = 0; i < h->multi_conn; ++i) {
-    if (!nbd_unlocked_aio_is_created (h->conns[i])) {
-      set_error (EINVAL,
-                 "requesting meta context must occur before connection");
-      return -1;
-    }
+  if (!nbd_unlocked_aio_is_created (h)) {
+    set_error (EINVAL,
+               "requesting meta context must occur before connection");
+    return -1;
   }
 
   copy = strdup (name);
diff --git a/lib/internal.h b/lib/internal.h
index f0705ef..adf65f3 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -42,19 +42,9 @@ struct socket;
 struct command_in_flight;
 
 struct nbd_handle {
-  /* Lock protecting concurrent access to either this handle or the
-   * connections owned by the handle.
-   */
+  /* Lock protecting concurrent access to the handle. */
   pthread_mutex_t lock;
 
-  /* Connection(s).  Usually 1 but there may be several.  The length
-   * of the list is multi_conn.  The elements are never NULL.  If a
-   * connection is closed then it is replaced with a newly created
-   * connection immediately.
-   */
-  struct nbd_connection **conns;
-  unsigned multi_conn;
-
   char *export_name;            /* Export name, never NULL. */
 
   /* TLS settings. */
@@ -67,6 +57,9 @@ struct nbd_handle {
   /* Desired metadata contexts. */
   char **request_meta_contexts;
 
+  /* Global flags from the server. */
+  uint16_t gflags;
+
   /* Export size and per-export flags, received during handshake.  NB:
    * These are *both* *only* valid if eflags != 0.  This is because
    * all servers should set NBD_FLAG_HAS_FLAGS, so eflags should
@@ -81,23 +74,11 @@ struct nbd_handle {
   bool debug;
   void *debug_data;
   void (*debug_fn) (void *, const char *, const char *);
-};
-
-/* This corresponds to a single socket connection to a remote server.
- * Usually there is one of these in the handle, but for multi_conn
- * there may be several.
- */
-struct nbd_connection {
-  struct nbd_handle *h;         /* Parent handle. */
-
-  /* To avoid leaking addresses in debug messages, and to make debug
-   * easier to read, give this a unique ID used in debug.
-   */
-  int64_t id;
 
   enum state state;             /* State machine. */
 
   bool structured_replies;      /* If we negotiated NBD_OPT_STRUCTURED_REPLY */
+
   /* Linked list of negotiated metadata contexts. */
   struct meta_context *meta_contexts;
 
@@ -177,9 +158,6 @@ struct nbd_connection {
   /* When receiving block status, this is used. */
   uint32_t *bs_entries;
 
-  /* Global flags from the server. */
-  uint16_t gflags;
-
   /* When issuing a command, the first list contains commands waiting
    * to be issued.  The second list contains commands which have been
    * issued and waiting for replies.  The third list contains commands
@@ -233,10 +211,10 @@ struct command_in_flight {
 };
 
 /* crypto.c */
-extern struct socket *nbd_internal_crypto_create_session (struct nbd_connection *, struct socket *oldsock);
-extern bool nbd_internal_crypto_is_reading (struct nbd_connection *);
-extern int nbd_internal_crypto_handshake (struct nbd_connection *);
-extern void nbd_internal_crypto_debug_tls_enabled (struct nbd_connection *);
+extern struct socket *nbd_internal_crypto_create_session (struct nbd_handle *, struct socket *oldsock);
+extern bool nbd_internal_crypto_is_reading (struct nbd_handle *);
+extern int nbd_internal_crypto_handshake (struct nbd_handle *);
+extern void nbd_internal_crypto_debug_tls_enabled (struct nbd_handle *);
 
 /* debug.c */
 extern void nbd_internal_debug (struct nbd_handle *h, const char *fs, ...);
@@ -275,7 +253,7 @@ extern int nbd_internal_errno_of_nbd_error (uint32_t error);
 extern const char *nbd_internal_name_of_nbd_cmd (uint16_t type);
 
 /* rw.c */
-extern int64_t nbd_internal_command_common (struct nbd_connection *conn,
+extern int64_t nbd_internal_command_common (struct nbd_handle *h,
                                             uint16_t flags, uint16_t type,
                                             uint64_t offset, uint64_t count,
                                             void *data, extent_fn extent);
@@ -284,8 +262,7 @@ extern int64_t nbd_internal_command_common (struct nbd_connection *conn,
 struct socket *nbd_internal_socket_create (int fd);
 
 /* states.c */
-extern int nbd_internal_run (struct nbd_handle *h, struct nbd_connection *conn,
-                             enum external_event ev);
+extern int nbd_internal_run (struct nbd_handle *h, enum external_event ev);
 extern const char *nbd_internal_state_short_string (enum state state);
 extern enum state_group nbd_internal_state_group (enum state state);
 extern enum state_group nbd_internal_state_group_parent (enum state_group group);
diff --git a/lib/poll.c b/lib/poll.c
index c7165f8..e3b832b 100644
--- a/lib/poll.c
+++ b/lib/poll.c
@@ -30,53 +30,48 @@
 int
 nbd_unlocked_poll (struct nbd_handle *h, int timeout)
 {
-  struct pollfd fds[h->multi_conn];
-  size_t i;
+  struct pollfd fds[1];
   int r;
 
-  for (i = 0; i < h->multi_conn; ++i) {
-    /* fd might be negative, and poll will ignore it. */
-    fds[i].fd = nbd_unlocked_aio_get_fd (h->conns[i]);
-    switch (nbd_unlocked_aio_get_direction (h->conns[i])) {
-    case LIBNBD_AIO_DIRECTION_READ:
-      fds[i].events = POLLIN;
-      break;
-    case LIBNBD_AIO_DIRECTION_WRITE:
-      fds[i].events = POLLOUT;
-      break;
-    case LIBNBD_AIO_DIRECTION_BOTH:
-      fds[i].events = POLLIN|POLLOUT;
-      break;
-    }
-    fds[i].revents = 0;
+  /* fd might be negative, and poll will ignore it. */
+  fds[0].fd = nbd_unlocked_aio_get_fd (h);
+  switch (nbd_unlocked_aio_get_direction (h)) {
+  case LIBNBD_AIO_DIRECTION_READ:
+    fds[0].events = POLLIN;
+    break;
+  case LIBNBD_AIO_DIRECTION_WRITE:
+    fds[0].events = POLLOUT;
+    break;
+  case LIBNBD_AIO_DIRECTION_BOTH:
+    fds[0].events = POLLIN|POLLOUT;
+    break;
   }
+  fds[0].revents = 0;
 
   /* Note that it's not safe to release the handle lock here, as it
    * would allow other threads to close file descriptors which we have
    * passed to poll.
    */
-  r = poll (fds, h->multi_conn, timeout);
+  r = poll (fds, 1, timeout);
   if (r == -1) {
     set_error (errno, "poll");
     return -1;
   }
 
-  for (i = 0; i < h->multi_conn; ++i) {
-    r = 0;
-    /* POLLIN and POLLOUT might both be set.  However we shouldn't
-     * call both nbd_aio_notify_read and nbd_aio_notify_write at this
-     * time since the first might change the handle state, making the
-     * second notification invalid.  Nothing bad happens by ignoring
-     * one of the notifications since if it's still valid it will be
-     * picked up by a subsequent poll.
-     */
-    if ((fds[i].revents & POLLIN) != 0)
-      r = nbd_unlocked_aio_notify_read (h->conns[i]);
-    else if ((fds[i].revents & POLLOUT) != 0)
-      r = nbd_unlocked_aio_notify_write (h->conns[i]);
-    if (r == -1)
-      return -1;
-  }
+  /* POLLIN and POLLOUT might both be set.  However we shouldn't call
+   * both nbd_aio_notify_read and nbd_aio_notify_write at this time
+   * since the first might change the handle state, making the second
+   * notification invalid.  Nothing bad happens by ignoring one of the
+   * notifications since if it's still valid it will be picked up by a
+   * subsequent poll.
+   */
+  r = 0;
+  if ((fds[0].revents & POLLIN) != 0)
+    r = nbd_unlocked_aio_notify_read (h);
+  else if ((fds[0].revents & POLLOUT) != 0)
+    r = nbd_unlocked_aio_notify_write (h);
+  if (r == -1)
+    return -1;
 
   return 0;
 }
diff --git a/lib/rw.c b/lib/rw.c
index 014c078..82261a0 100644
--- a/lib/rw.c
+++ b/lib/rw.c
@@ -28,233 +28,131 @@
 
 #include "internal.h"
 
-/* For synchronous functions, this picks which connection to use.  It
- * has simple round-robin behaviour, but ignores connections which are
- * dead or not ready.  It will return an error if there are no
- * suitable connections.
- */
-static struct nbd_connection *
-pick_connection (struct nbd_handle *h)
+static int
+wait_for_command (struct nbd_handle *h, int64_t handle)
 {
-  size_t i, j;
-  struct nbd_connection *conn = NULL;
-  int error = ENOTCONN;
+  int r;
 
-  i = h->unique % h->multi_conn;
-  for (j = 0; j < h->multi_conn; ++j) {
-    if (nbd_unlocked_aio_is_ready (h->conns[i])) {
-      conn = h->conns[i];
-      break;
-    }
-    /* At least one connection is busy, not dead and not sitting in
-     * the initial state.
-     */
-    if (!nbd_unlocked_aio_is_created (h->conns[i]) &&
-        !nbd_unlocked_aio_is_dead (h->conns[i]))
-      error = EBUSY;
-
-    ++i;
-    if (i >= h->multi_conn)
-      i = 0;
-  }
-
-  if (conn == NULL) {
-    set_error (error, "no connection(s) are ready to issue commands");
-    return NULL;
+  while ((r = nbd_unlocked_aio_command_completed (h, handle)) == 0) {
+    if (nbd_unlocked_poll (h, -1) == -1)
+      return -1;
   }
 
-  return conn;
+  return r == -1 ? -1 : 0;
 }
 
-/* Issue a read command on any connection and wait for the reply. */
+/* Issue a read command and wait for the reply. */
 int
 nbd_unlocked_pread (struct nbd_handle *h, void *buf,
                     size_t count, uint64_t offset)
 {
-  struct nbd_connection *conn;
   int64_t ch;
-  int r;
 
-  conn = pick_connection (h);
-  if (conn == NULL)
-    return -1;
-
-  ch = nbd_unlocked_aio_pread (conn, buf, count, offset);
+  ch = nbd_unlocked_aio_pread (h, buf, count, offset);
   if (ch == -1)
     return -1;
 
-  while ((r = nbd_unlocked_aio_command_completed (conn, ch)) == 0) {
-    if (nbd_unlocked_poll (h, -1) == -1)
-      return -1;
-  }
-
-  return r == -1 ? -1 : 0;
+  return wait_for_command (h, ch);
 }
 
-/* Issue a write command on any connection and wait for the reply. */
+/* Issue a write command and wait for the reply. */
 int
 nbd_unlocked_pwrite (struct nbd_handle *h, const void *buf,
                      size_t count, uint64_t offset, uint32_t flags)
 {
-  struct nbd_connection *conn;
   int64_t ch;
-  int r;
 
-  conn = pick_connection (h);
-  if (conn == NULL)
-    return -1;
-
-  ch = nbd_unlocked_aio_pwrite (conn, buf, count, offset, flags);
+  ch = nbd_unlocked_aio_pwrite (h, buf, count, offset, flags);
   if (ch == -1)
     return -1;
 
-  while ((r = nbd_unlocked_aio_command_completed (conn, ch)) == 0) {
-    if (nbd_unlocked_poll (h, -1) == -1)
-      return -1;
-  }
-
-  return r == -1 ? -1 : 0;
+  return wait_for_command (h, ch);
 }
 
-/* Issue a flush command on any connection and wait for the reply. */
+/* Issue a flush command and wait for the reply. */
 int
 nbd_unlocked_flush (struct nbd_handle *h)
 {
-  struct nbd_connection *conn;
   int64_t ch;
-  int r;
 
-  conn = pick_connection (h);
-  if (conn == NULL)
-    return -1;
-
-  ch = nbd_unlocked_aio_flush (conn);
+  ch = nbd_unlocked_aio_flush (h);
   if (ch == -1)
     return -1;
 
-  while ((r = nbd_unlocked_aio_command_completed (conn, ch)) == 0) {
-    if (nbd_unlocked_poll (h, -1) == -1)
-      return -1;
-  }
-
-  return r == -1 ? -1 : 0;
+  return wait_for_command (h, ch);
 }
 
-/* Issue a trim command on any connection and wait for the reply. */
+/* Issue a trim command and wait for the reply. */
 int
 nbd_unlocked_trim (struct nbd_handle *h,
                    uint64_t count, uint64_t offset, uint32_t flags)
 {
-  struct nbd_connection *conn;
   int64_t ch;
-  int r;
 
-  conn = pick_connection (h);
-  if (conn == NULL)
-    return -1;
-
-  ch = nbd_unlocked_aio_trim (conn, count, offset, flags);
+  ch = nbd_unlocked_aio_trim (h, count, offset, flags);
   if (ch == -1)
     return -1;
 
-  while ((r = nbd_unlocked_aio_command_completed (conn, ch)) == 0) {
-    if (nbd_unlocked_poll (h, -1) == -1)
-      return -1;
-  }
-
-  return r == -1 ? -1 : 0;
+  return wait_for_command (h, ch);
 }
 
-/* Issue a cache command on any connection and wait for the reply. */
+/* Issue a cache command and wait for the reply. */
 int
 nbd_unlocked_cache (struct nbd_handle *h,
                     uint64_t count, uint64_t offset)
 {
-  struct nbd_connection *conn;
   int64_t ch;
-  int r;
 
-  conn = pick_connection (h);
-  if (conn == NULL)
-    return -1;
-
-  ch = nbd_unlocked_aio_cache (conn, count, offset);
+  ch = nbd_unlocked_aio_cache (h, count, offset);
   if (ch == -1)
     return -1;
 
-  while ((r = nbd_unlocked_aio_command_completed (conn, ch)) == 0) {
-    if (nbd_unlocked_poll (h, -1) == -1)
-      return -1;
-  }
-
-  return r == -1 ? -1 : 0;
+  return wait_for_command (h, ch);
 }
 
-/* Issue a zero command on any connection and wait for the reply. */
+/* Issue a zero command and wait for the reply. */
 int
 nbd_unlocked_zero (struct nbd_handle *h,
                    uint64_t count, uint64_t offset, uint32_t flags)
 {
-  struct nbd_connection *conn;
   int64_t ch;
-  int r;
 
-  conn = pick_connection (h);
-  if (conn == NULL)
-    return -1;
-
-  ch = nbd_unlocked_aio_zero (conn, count, offset, flags);
+  ch = nbd_unlocked_aio_zero (h, count, offset, flags);
   if (ch == -1)
     return -1;
 
-  while ((r = nbd_unlocked_aio_command_completed (conn, ch)) == 0) {
-    if (nbd_unlocked_poll (h, -1) == -1)
-      return -1;
-  }
-
-  return r == -1 ? -1 : 0;
+  return wait_for_command (h, ch);
 }
 
-/* Issue a block status command on any connection and wait for the reply. */
+/* Issue a block status command and wait for the reply. */
 int
 nbd_unlocked_block_status (struct nbd_handle *h,
                            uint64_t count, uint64_t offset, uint32_t flags,
                            void *data,
                            extent_fn extent)
 {
-  struct nbd_connection *conn;
   int64_t ch;
-  int r;
 
-  conn = pick_connection (h);
-  if (conn == NULL)
-    return -1;
-
-  ch = nbd_unlocked_aio_block_status (conn, count, offset, flags, data, extent);
+  ch = nbd_unlocked_aio_block_status (h, count, offset, flags, data, extent);
   if (ch == -1)
     return -1;
 
-  while ((r = nbd_unlocked_aio_command_completed (conn, ch)) == 0) {
-    if (nbd_unlocked_poll (h, -1) == -1)
-      return -1;
-  }
-
-  return r == -1 ? -1 : 0;
+  return wait_for_command (h, ch);
 }
 
 int64_t
-nbd_internal_command_common (struct nbd_connection *conn,
+nbd_internal_command_common (struct nbd_handle *h,
                              uint16_t flags, uint16_t type,
                              uint64_t offset, uint64_t count, void *data,
                              extent_fn extent)
 {
   struct command_in_flight *cmd, *prev_cmd;
 
-  if (!nbd_unlocked_aio_is_ready (conn) &&
-      !nbd_unlocked_aio_is_processing (conn)) {
+  if (!nbd_unlocked_aio_is_ready (h) &&
+      !nbd_unlocked_aio_is_processing (h)) {
     set_error (0, "command request %s is invalid in state %s",
                nbd_internal_name_of_nbd_cmd (type),
-               nbd_internal_state_short_string (conn->state));
+               nbd_internal_state_short_string (h->state));
     return -1;
   }
 
@@ -290,7 +188,7 @@ nbd_internal_command_common (struct nbd_connection *conn,
   }
   cmd->flags = flags;
   cmd->type = type;
-  cmd->handle = conn->h->unique++;
+  cmd->handle = h->unique++;
   cmd->offset = offset;
   cmd->count = count;
   cmd->data = data;
@@ -304,24 +202,24 @@ nbd_internal_command_common (struct nbd_connection *conn,
    * in the highly intensive loopback case.  For TLS we get a
    * performance gain, go figure.
    */
-  if (conn->structured_replies && cmd->data && type == NBD_CMD_READ)
+  if (h->structured_replies && cmd->data && type == NBD_CMD_READ)
     memset (cmd->data, 0, cmd->count);
 
   /* Add the command to the end of the queue. Kick the state machine
    * if there is no other command being processed, otherwise, it will
    * be handled automatically on a future cycle around to READY.
    */
-  if (conn->cmds_to_issue != NULL) {
-    assert (nbd_unlocked_aio_is_processing (conn));
-    prev_cmd = conn->cmds_to_issue;
+  if (h->cmds_to_issue != NULL) {
+    assert (nbd_unlocked_aio_is_processing (h));
+    prev_cmd = h->cmds_to_issue;
     while (prev_cmd->next)
       prev_cmd = prev_cmd->next;
     prev_cmd->next = cmd;
   }
   else {
-    conn->cmds_to_issue = cmd;
-    if (nbd_unlocked_aio_is_ready (conn) &&
-        nbd_internal_run (conn->h, conn, cmd_issue) == -1)
+    h->cmds_to_issue = cmd;
+    if (nbd_unlocked_aio_is_ready (h) &&
+        nbd_internal_run (h, cmd_issue) == -1)
       return -1;
   }
 
@@ -329,19 +227,19 @@ nbd_internal_command_common (struct nbd_connection *conn,
 }
 
 int64_t
-nbd_unlocked_aio_pread (struct nbd_connection *conn, void *buf,
+nbd_unlocked_aio_pread (struct nbd_handle *h, void *buf,
                         size_t count, uint64_t offset)
 {
-  return nbd_internal_command_common (conn, 0, NBD_CMD_READ, offset, count,
+  return nbd_internal_command_common (h, 0, NBD_CMD_READ, offset, count,
                                       buf, NULL);
 }
 
 int64_t
-nbd_unlocked_aio_pwrite (struct nbd_connection *conn, const void *buf,
+nbd_unlocked_aio_pwrite (struct nbd_handle *h, const void *buf,
                          size_t count, uint64_t offset,
                          uint32_t flags)
 {
-  if (nbd_unlocked_read_only (conn->h) == 1) {
+  if (nbd_unlocked_read_only (h) == 1) {
     set_error (EINVAL, "server does not support write operations");
     return -1;
   }
@@ -352,33 +250,33 @@ nbd_unlocked_aio_pwrite (struct nbd_connection *conn, const void *buf,
   }
 
   if ((flags & LIBNBD_CMD_FLAG_FUA) != 0 &&
-      nbd_unlocked_can_fua (conn->h) != 1) {
+      nbd_unlocked_can_fua (h) != 1) {
     set_error (EINVAL, "server does not support the FUA flag");
     return -1;
   }
 
-  return nbd_internal_command_common (conn, flags, NBD_CMD_WRITE, offset, count,
+  return nbd_internal_command_common (h, flags, NBD_CMD_WRITE, offset, count,
                                       (void *) buf, NULL);
 }
 
 int64_t
-nbd_unlocked_aio_flush (struct nbd_connection *conn)
+nbd_unlocked_aio_flush (struct nbd_handle *h)
 {
-  if (nbd_unlocked_can_flush (conn->h) != 1) {
+  if (nbd_unlocked_can_flush (h) != 1) {
     set_error (EINVAL, "server does not support flush operations");
     return -1;
   }
 
-  return nbd_internal_command_common (conn, 0, NBD_CMD_FLUSH, 0, 0,
+  return nbd_internal_command_common (h, 0, NBD_CMD_FLUSH, 0, 0,
                                       NULL, NULL);
 }
 
 int64_t
-nbd_unlocked_aio_trim (struct nbd_connection *conn,
+nbd_unlocked_aio_trim (struct nbd_handle *h,
                        uint64_t count, uint64_t offset,
                        uint32_t flags)
 {
-  if (nbd_unlocked_read_only (conn->h) == 1) {
+  if (nbd_unlocked_read_only (h) == 1) {
     set_error (EINVAL, "server does not support write operations");
     return -1;
   }
@@ -389,7 +287,7 @@ nbd_unlocked_aio_trim (struct nbd_connection *conn,
   }
 
   if ((flags & LIBNBD_CMD_FLAG_FUA) != 0 &&
-      nbd_unlocked_can_fua (conn->h) != 1) {
+      nbd_unlocked_can_fua (h) != 1) {
     set_error (EINVAL, "server does not support the FUA flag");
     return -1;
   }
@@ -399,33 +297,33 @@ nbd_unlocked_aio_trim (struct nbd_connection *conn,
     return -1;
   }
 
-  return nbd_internal_command_common (conn, flags, NBD_CMD_TRIM, offset, count,
+  return nbd_internal_command_common (h, flags, NBD_CMD_TRIM, offset, count,
                                       NULL, NULL);
 }
 
 int64_t
-nbd_unlocked_aio_cache (struct nbd_connection *conn,
+nbd_unlocked_aio_cache (struct nbd_handle *h,
                         uint64_t count, uint64_t offset)
 {
   /* Actually according to the NBD protocol document, servers do exist
    * that support NBD_CMD_CACHE but don't advertize the
    * NBD_FLAG_SEND_CACHE bit, but we ignore those.
    */
-  if (nbd_unlocked_can_cache (conn->h) != 1) {
+  if (nbd_unlocked_can_cache (h) != 1) {
     set_error (EINVAL, "server does not support cache operations");
     return -1;
   }
 
-  return nbd_internal_command_common (conn, 0, NBD_CMD_CACHE, offset, count,
+  return nbd_internal_command_common (h, 0, NBD_CMD_CACHE, offset, count,
                                       NULL, NULL);
 }
 
 int64_t
-nbd_unlocked_aio_zero (struct nbd_connection *conn,
+nbd_unlocked_aio_zero (struct nbd_handle *h,
                        uint64_t count, uint64_t offset,
                        uint32_t flags)
 {
-  if (nbd_unlocked_read_only (conn->h) == 1) {
+  if (nbd_unlocked_read_only (h) == 1) {
     set_error (EINVAL, "server does not support write operations");
     return -1;
   }
@@ -436,7 +334,7 @@ nbd_unlocked_aio_zero (struct nbd_connection *conn,
   }
 
   if ((flags & LIBNBD_CMD_FLAG_FUA) != 0 &&
-      nbd_unlocked_can_fua (conn->h) != 1) {
+      nbd_unlocked_can_fua (h) != 1) {
     set_error (EINVAL, "server does not support the FUA flag");
     return -1;
   }
@@ -446,23 +344,23 @@ nbd_unlocked_aio_zero (struct nbd_connection *conn,
     return -1;
   }
 
-  return nbd_internal_command_common (conn, flags, NBD_CMD_WRITE_ZEROES, offset,
+  return nbd_internal_command_common (h, flags, NBD_CMD_WRITE_ZEROES, offset,
                                       count, NULL, NULL);
 }
 
 int64_t
-nbd_unlocked_aio_block_status (struct nbd_connection *conn,
+nbd_unlocked_aio_block_status (struct nbd_handle *h,
                                uint64_t count, uint64_t offset,
                                uint32_t flags,
                                void *data,
                                extent_fn extent)
 {
-  if (!conn->structured_replies) {
+  if (!h->structured_replies) {
     set_error (ENOTSUP, "server does not support structured replies");
     return -1;
   }
 
-  if (conn->meta_contexts == NULL) {
+  if (h->meta_contexts == NULL) {
     set_error (ENOTSUP, "did not negotiate any metadata contexts, "
                "either you did not call nbd_request_meta_context before "
                "connecting or the server does not support it");
@@ -479,6 +377,6 @@ nbd_unlocked_aio_block_status (struct nbd_connection *conn,
     return -1;
   }
 
-  return nbd_internal_command_common (conn, flags, NBD_CMD_BLOCK_STATUS, offset,
+  return nbd_internal_command_common (h, flags, NBD_CMD_BLOCK_STATUS, offset,
                                       count, data, extent);
 }
-- 
2.21.0




More information about the Libguestfs mailing list