[Libguestfs] [PATCH libnbd 1/4] api: Combine callback and user_data into a single struct.

Richard W.M. Jones rjones at redhat.com
Tue Aug 13 22:36:58 UTC 2019


The definition of functions that take a callback is changed so that
the callback and user_data are combined into a single structure, eg:

  int64_t nbd_aio_pread (struct nbd_handle *h,
            void *buf, size_t count, uint64_t offset,
-           int (*completion_callback) (/*..*/), void *user_data,
+           nbd_completion_callback completion_callback,
            uint32_t flags);

Several nbd_*_callback structures are defined.  The one corresponding
to the example above is:

  typedef struct {
    void *user_data;
    int (*callback) (unsigned valid_flag, void *user_data, int *error);
  } nbd_completion_callback;

The nbd_aio_pread function can now be called using:

  nbd_aio_pread (nbd, buf, sizeof buf, offset,
                 (nbd_completion_callback) { .callback = my_fn,
                                             .user_data = my_data },
                 0);

Note that the whole structure is passed by value not by reference.

For OClosure only, a NULL callback can be passed using this macro:

  nbd_aio_pread (nbd, buf, sizeof buf, offset,
                 NBD_NULL_CALLBACK(completion), 0);
---
 docs/libnbd.pod                      | 27 ++++++---
 examples/batched-read-write.c        |  5 +-
 examples/glib-main-loop.c            |  6 +-
 examples/strict-structured-reads.c   |  3 +-
 examples/threaded-reads-and-writes.c |  6 +-
 generator/generator                  | 89 ++++++++++++++--------------
 generator/states-reply-simple.c      | 13 ++--
 generator/states-reply-structured.c  | 64 ++++++++++----------
 generator/states-reply.c             |  8 +--
 generator/states.c                   |  8 +--
 interop/dirty-bitmap.c               | 11 +++-
 interop/structured-read.c            |  9 ++-
 lib/aio.c                            | 21 ++++---
 lib/debug.c                          | 16 ++---
 lib/internal.h                       |  3 -
 lib/rw.c                             | 60 ++++++++-----------
 tests/aio-parallel-load.c            |  6 +-
 tests/aio-parallel.c                 |  6 +-
 tests/closure-lifetimes.c            | 14 +++--
 tests/errors.c                       |  9 ++-
 tests/meta-base-allocation.c         | 11 +++-
 tests/oldstyle.c                     |  6 +-
 tests/server-death.c                 |  7 ++-
 23 files changed, 226 insertions(+), 182 deletions(-)

diff --git a/docs/libnbd.pod b/docs/libnbd.pod
index b38def0..9177825 100644
--- a/docs/libnbd.pod
+++ b/docs/libnbd.pod
@@ -598,14 +598,25 @@ will use your login name):
 
 =head1 CALLBACKS
 
-Some libnbd calls take function pointers (eg.
-C<nbd_set_debug_callback>, C<nbd_aio_pread>).  Libnbd can call these
-functions while processing.
-
-Callbacks have an opaque C<void *user_data> pointer.  This is passed
-as the second parameter to the callback.  The opaque pointer is only
-used from the C API, since in other languages you can use closures to
-achieve the same outcome.
+Some libnbd calls take callbacks (eg.  C<nbd_set_debug_callback>,
+C<nbd_aio_pread>).  Libnbd can call these functions while processing.
+
+In the C API these libnbd calls take a structure which contains the
+function pointer and an optional opaque C<void *user_data> pointer:
+
+ nbd_aio_pread (nbd, buf, sizeof buf, offset,
+                (nbd_completion_callback) { .callback = my_fn,
+                                            .user_data = my_data },
+                0);
+
+If you don't want the callback, either set C<.callback> to C<NULL> or
+use the equivalent macro C<NBD_NULL_CALLBACK()> defined in C<libnbd.h>:
+
+ nbd_aio_pread (nbd, buf, sizeof buf, offset,
+                NBD_NULL_CALLBACK(completion), 0);
+
+From other languages the structure and opaque pointer are not needed
+because you can use closures to achieve the same effect.
 
 =head2 Callback lifetimes
 
diff --git a/examples/batched-read-write.c b/examples/batched-read-write.c
index 378c2e0..a6063af 100644
--- a/examples/batched-read-write.c
+++ b/examples/batched-read-write.c
@@ -53,12 +53,13 @@ try_deadlock (void *arg)
 
   /* Issue commands. */
   cookies[0] = nbd_aio_pread (nbd, in, packetsize, 0,
-                              NULL, NULL, 0);
+                              NBD_NULL_CALLBACK(completion), 0);
   if (cookies[0] == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
     goto error;
   }
-  cookies[1] = nbd_aio_pwrite (nbd, out, packetsize, packetsize, NULL, NULL, 0);
+  cookies[1] = nbd_aio_pwrite (nbd, out, packetsize, packetsize,
+                               NBD_NULL_CALLBACK(completion), 0);
   if (cookies[1] == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
     goto error;
diff --git a/examples/glib-main-loop.c b/examples/glib-main-loop.c
index 7b4d215..a8e8ceb 100644
--- a/examples/glib-main-loop.c
+++ b/examples/glib-main-loop.c
@@ -384,7 +384,8 @@ read_data (gpointer user_data)
 
   if (nbd_aio_pread (gssrc->nbd, buffers[i].data,
                      BUFFER_SIZE, buffers[i].offset,
-                     finished_read, &buffers[i], 0) == -1) {
+                     (nbd_completion_callback) { .callback = finished_read, .user_data = &buffers[i] },
+                     0) == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
     exit (EXIT_FAILURE);
   }
@@ -428,7 +429,8 @@ write_data (gpointer user_data)
   buffer->state = BUFFER_WRITING;
   if (nbd_aio_pwrite (gsdest->nbd, buffer->data,
                       BUFFER_SIZE, buffer->offset,
-                      finished_write, buffer, 0) == -1) {
+                      (nbd_completion_callback) { .callback = finished_write, .user_data = buffer },
+                      0) == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
     exit (EXIT_FAILURE);
   }
diff --git a/examples/strict-structured-reads.c b/examples/strict-structured-reads.c
index 4bc63b8..d7c3e1b 100644
--- a/examples/strict-structured-reads.c
+++ b/examples/strict-structured-reads.c
@@ -236,7 +236,8 @@ main (int argc, char *argv[])
     *d = (struct data) { .offset = offset, .count = maxsize, .flags = flags,
                          .remaining = r, };
     if (nbd_aio_pread_structured (nbd, buf, sizeof buf, offset,
-                                  read_chunk, d, read_verify, d,
+                                  (nbd_chunk_callback) { .callback = read_chunk, .user_data = d },
+                                  (nbd_completion_callback) { .callback = read_verify, .user_data = d },
                                   flags) == -1) {
       fprintf (stderr, "%s\n", nbd_get_error ());
       exit (EXIT_FAILURE);
diff --git a/examples/threaded-reads-and-writes.c b/examples/threaded-reads-and-writes.c
index 7626a02..6ae1dd2 100644
--- a/examples/threaded-reads-and-writes.c
+++ b/examples/threaded-reads-and-writes.c
@@ -252,9 +252,11 @@ start_thread (void *arg)
       offset = rand () % (exportsize - size);
       cmd = rand () & 1;
       if (cmd == 0)
-        cookie = nbd_aio_pwrite (nbd, buf, size, offset, NULL, NULL, 0);
+        cookie = nbd_aio_pwrite (nbd, buf, size, offset,
+                                 NBD_NULL_CALLBACK(completion), 0);
       else
-        cookie = nbd_aio_pread (nbd, buf, size, offset, NULL, NULL, 0);
+        cookie = nbd_aio_pread (nbd, buf, size, offset,
+                                NBD_NULL_CALLBACK(completion), 0);
       if (cookie == -1) {
         fprintf (stderr, "%s\n", nbd_get_error ());
         goto error;
diff --git a/generator/generator b/generator/generator
index 4d3d7ad..ea32929 100755
--- a/generator/generator
+++ b/generator/generator
@@ -3226,10 +3226,7 @@ let rec print_arg_list ?(handle = false) ?(types = true) args optargs =
          pr "%s" len
       | Closure { cbname; cbargs } ->
          if types then pr "nbd_%s_callback " cbname;
-         pr "%s_callback" cbname;
-         pr ", ";
-         if types then pr "void *";
-         pr "%s_user_data" cbname
+         pr "%s_callback" cbname
       | Enum (n, _) ->
          if types then pr "int ";
          pr "%s" n
@@ -3271,10 +3268,7 @@ let rec print_arg_list ?(handle = false) ?(types = true) args optargs =
       match optarg with
       | OClosure { cbname; cbargs } ->
          if types then pr "nbd_%s_callback " cbname;
-         pr "%s_callback" cbname;
-         pr ", ";
-         if types then pr "void *";
-         pr "%s_user_data" cbname
+         pr "%s_callback" cbname
       | OFlags (n, _) ->
          if types then pr "uint32_t ";
          pr "%s" n
@@ -3337,14 +3331,19 @@ let print_cbarg_list ?(valid_flag = true) ?(types = true) cbargs =
   ) cbargs;
   pr ")"
 
-(* Callback typedefs in <libnbd.h> *)
-let print_closure_typedefs () =
+(* Callback structs/typedefs in <libnbd.h> *)
+let print_closure_structs () =
   List.iter (
     fun { cbname; cbargs } ->
-      pr "typedef int (*nbd_%s_callback) " cbname;
+      pr "typedef struct {\n";
+      pr "  void *user_data;\n";
+      pr "  int (*callback) ";
       print_cbarg_list cbargs;
       pr ";\n";
+      pr "} nbd_%s_callback;\n" cbname;
+      pr "\n";
   ) all_closures;
+  pr "#define NBD_NULL_CALLBACK(name) ((nbd_## name ##_callback) { .callback = NULL })\n";
   pr "\n"
 
 let print_extern_and_define name args optargs ret =
@@ -3434,7 +3433,7 @@ let generate_include_libnbd_h () =
   pr "extern int nbd_get_errno (void);\n";
   pr "#define LIBNBD_HAVE_NBD_GET_ERRNO 1\n";
   pr "\n";
-  print_closure_typedefs ();
+  print_closure_structs ();
   List.iter (
     fun (name, { args; optargs; ret }) ->
       print_extern_and_define name args optargs ret
@@ -3566,7 +3565,7 @@ let generate_lib_api_c () =
          let value = match errcode with
            | Some value -> value
            | None -> assert false in
-         pr "  if (%s_callback == NULL) {\n" cbname;
+         pr "  if (%s_callback.callback == NULL) {\n" cbname;
          pr "    set_error (EFAULT, \"%%s cannot be NULL\", \"%s\");\n" cbname;
          pr "    ret = %s;\n" value;
          pr "    goto out;\n";
@@ -3678,7 +3677,8 @@ let generate_lib_api_c () =
     ) args;
     List.iter (
       function
-      | OClosure { cbname } -> pr ", %s_callback ? \"<fun>\" : \"NULL\"" cbname
+      | OClosure { cbname } ->
+         pr ", %s_callback.callback ? \"<fun>\" : \"NULL\"" cbname
       | OFlags (n, _) -> pr ", %s" n
     ) optargs;
     pr ");\n"
@@ -4156,7 +4156,8 @@ let print_python_binding name { args; optargs; ret; may_set_error } =
           n;
        pr "  struct py_aio_buffer *%s_buf;\n" n
     | Closure { cbname } ->
-       pr "  PyObject *%s_user_data;\n" cbname
+       pr "  nbd_%s_callback %s = { .callback = %s_wrapper };\n"
+         cbname cbname cbname
     | Enum (n, _) -> pr "  int %s;\n" n
     | Flags (n, _) ->
        pr "  uint32_t %s_u32;\n" n;
@@ -4188,7 +4189,8 @@ let print_python_binding name { args; optargs; ret; may_set_error } =
   List.iter (
     function
     | OClosure { cbname } ->
-       pr "  PyObject *%s_user_data;\n" cbname
+       pr "  nbd_%s_callback %s = { .callback = %s_wrapper };\n"
+         cbname cbname cbname
     | OFlags (n, _) ->
        pr "  uint32_t %s_u32;\n" n;
        pr "  unsigned int %s; /* really uint32_t */\n" n
@@ -4231,7 +4233,7 @@ let print_python_binding name { args; optargs; ret; may_set_error } =
     | BytesIn (n, _) | BytesPersistIn (n, _)
     | BytesPersistOut (n, _) -> pr ", &%s" n
     | BytesOut (_, count) -> pr ", &%s" count
-    | Closure { cbname } -> pr ", &%s_user_data" cbname
+    | Closure { cbname } -> pr ", &%s.user_data" cbname
     | Enum (n, _) -> pr ", &%s" n
     | Flags (n, _) -> pr ", &%s" n
     | Int n -> pr ", &%s" n
@@ -4246,7 +4248,7 @@ let print_python_binding name { args; optargs; ret; may_set_error } =
   ) args;
   List.iter (
     function
-    | OClosure { cbname } -> pr ", &%s_user_data" cbname
+    | OClosure { cbname } -> pr ", &%s.user_data" cbname
     | OFlags (n, _) -> pr ", &%s" n
   ) optargs;
   pr "))\n";
@@ -4263,8 +4265,8 @@ let print_python_binding name { args; optargs; ret; may_set_error } =
        pr "  %s_buf = nbd_internal_py_get_aio_buffer (%s);\n" n n
     | Closure { cbname } ->
        pr "  /* Increment refcount since pointer may be saved by libnbd. */\n";
-       pr "  Py_INCREF (%s_user_data);\n" cbname;
-       pr "  if (!PyCallable_Check (%s_user_data)) {\n" cbname;
+       pr "  Py_INCREF (%s.user_data);\n" cbname;
+       pr "  if (!PyCallable_Check (%s.user_data)) {\n" cbname;
        pr "    PyErr_SetString (PyExc_TypeError,\n";
        pr "                     \"callback parameter %s is not callable\");\n" cbname;
        pr "    return NULL;\n";
@@ -4289,15 +4291,17 @@ let print_python_binding name { args; optargs; ret; may_set_error } =
   List.iter (
     function
     | OClosure { cbname } ->
-       pr "  if (%s_user_data != Py_None) {\n" cbname;
+       pr "  if (%s.user_data != Py_None) {\n" cbname;
        pr "    /* Increment refcount since pointer may be saved by libnbd. */\n";
-       pr "    Py_INCREF (%s_user_data);\n" cbname;
-       pr "    if (!PyCallable_Check (%s_user_data)) {\n" cbname;
+       pr "    Py_INCREF (%s.user_data);\n" cbname;
+       pr "    if (!PyCallable_Check (%s.user_data)) {\n" cbname;
        pr "      PyErr_SetString (PyExc_TypeError,\n";
        pr "                       \"callback parameter %s is not callable\");\n" cbname;
        pr "      return NULL;\n";
        pr "    }\n";
-       pr "  }\n"
+       pr "  }\n";
+       pr "  else\n";
+       pr "    %s.callback = NULL; /* we're not going to call it */\n" cbname
     | OFlags (n, _) -> pr "  %s_u32 = %s;\n" n n
   ) optargs;
 
@@ -4310,9 +4314,7 @@ let print_python_binding name { args; optargs; ret; may_set_error } =
     | BytesOut (n, count) -> pr ", %s, %s" n count
     | BytesPersistIn (n, _)
     | BytesPersistOut (n, _) -> pr ", %s_buf->data, %s_buf->len" n n
-    | Closure { cbname } ->
-       pr ", %s_wrapper" cbname;
-       pr ", %s_user_data" cbname
+    | Closure { cbname } -> pr ", %s" cbname
     | Enum (n, _) -> pr ", %s" n
     | Flags (n, _) -> pr ", %s_u32" n
     | Int n -> pr ", %s" n
@@ -4327,9 +4329,7 @@ let print_python_binding name { args; optargs; ret; may_set_error } =
   ) args;
   List.iter (
     function
-    | OClosure { cbname } ->
-       pr ", %s_user_data != Py_None ? %s_wrapper : NULL" cbname cbname;
-       pr ", %s_user_data != Py_None ? %s_user_data : NULL" cbname cbname
+    | OClosure { cbname } -> pr ", %s" cbname
     | OFlags (n, _) -> pr ", %s_u32" n
   ) optargs;
   pr ");\n";
@@ -5125,17 +5125,18 @@ let print_ocaml_binding (name, { args; optargs; ret }) =
   List.iter (
     function
     | OClosure { cbname } ->
-       pr "  const void *%s_callback = NULL;\n" cbname;
-       pr "  value *%s_user_data = NULL;\n" cbname;
+       pr "  nbd_%s_callback %s_callback = {0};\n" cbname cbname;
        pr "  if (%sv != Val_int (0)) { /* Some closure */\n" cbname;
        pr "    /* The function may save a reference to the closure, so we\n";
        pr "     * must treat it as a possible GC root.\n";
        pr "     */\n";
-       pr "    %s_user_data = malloc (sizeof (value));\n" cbname;
-       pr "    if (%s_user_data == NULL) caml_raise_out_of_memory ();\n" cbname;
-       pr "    *%s_user_data = Field (%sv, 0);\n" cbname cbname;
-       pr "    caml_register_generational_global_root (%s_user_data);\n" cbname;
-       pr "    %s_callback = %s_wrapper;\n" cbname cbname;
+       pr "    %s_callback.user_data = malloc (sizeof (value));\n" cbname;
+       pr "    if (%s_callback.user_data == NULL)\n" cbname;
+       pr "      caml_raise_out_of_memory ();\n";
+       pr "    *(value *)%s_callback.user_data = Field (%sv, 0);\n"
+         cbname cbname;
+       pr "    caml_register_generational_global_root (%s_callback.user_data);\n" cbname;
+       pr "    %s_callback.callback = %s_wrapper;\n" cbname cbname;
        pr "  }\n";
     | OFlags (n, { flag_prefix }) ->
        pr "  uint32_t %s;\n" n;
@@ -5168,12 +5169,14 @@ let print_ocaml_binding (name, { args; optargs; ret }) =
        pr "  /* The function may save a reference to the closure, so we\n";
        pr "   * must treat it as a possible GC root.\n";
        pr "   */\n";
-       pr "  value *%s_user_data;\n" cbname;
-       pr "  %s_user_data = malloc (sizeof (value));\n" cbname;
-       pr "  if (%s_user_data == NULL) caml_raise_out_of_memory ();\n" cbname;
-       pr "  *%s_user_data = %sv;\n" cbname cbname;
-       pr "  caml_register_generational_global_root (%s_user_data);\n" cbname;
-       pr "  const void *%s_callback = %s_wrapper;\n" cbname cbname
+       pr "  nbd_%s_callback %s_callback = { .callback = %s_wrapper };\n"
+         cbname cbname cbname;
+       pr "  %s_callback.user_data = malloc (sizeof (value));\n" cbname;
+       pr "  if (%s_callback.user_data == NULL) caml_raise_out_of_memory ();\n"
+         cbname;
+       pr "  *(value *)%s_callback.user_data = %sv;\n" cbname cbname;
+       pr "  caml_register_generational_global_root (%s_callback.user_data);\n"
+         cbname
     | Enum (n, { enum_prefix }) ->
        pr "  int %s = %s_val (%sv);\n" n enum_prefix n
     | Flags (n, { flag_prefix }) ->
diff --git a/generator/states-reply-simple.c b/generator/states-reply-simple.c
index 9b249ab..f1d3c62 100644
--- a/generator/states-reply-simple.c
+++ b/generator/states-reply-simple.c
@@ -60,16 +60,17 @@
   case 0:
     /* guaranteed by START */
     assert (cmd);
-    if (cmd->cb.fn.chunk) {
+    if (cmd->cb.fn.chunk.callback) {
       int error = 0;
 
       assert (cmd->error == 0);
-      if (cmd->cb.fn.chunk (LIBNBD_CALLBACK_VALID|LIBNBD_CALLBACK_FREE,
-                            cmd->cb.fn_user_data,
-                            cmd->data, cmd->count,
-                            cmd->offset, LIBNBD_READ_DATA, &error) == -1)
+      if (cmd->cb.fn.chunk.callback (LIBNBD_CALLBACK_VALID|LIBNBD_CALLBACK_FREE,
+                                     cmd->cb.fn.chunk.user_data,
+                                     cmd->data, cmd->count,
+                                     cmd->offset, LIBNBD_READ_DATA,
+                                     &error) == -1)
         cmd->error = error ? error : EPROTO;
-      cmd->cb.fn.chunk = NULL; /* because we've freed it */
+      cmd->cb.fn.chunk.callback = NULL; /* because we've freed it */
     }
 
     SET_NEXT_STATE (%^FINISH_COMMAND);
diff --git a/generator/states-reply-structured.c b/generator/states-reply-structured.c
index cdd9f10..92d6b5f 100644
--- a/generator/states-reply-structured.c
+++ b/generator/states-reply-structured.c
@@ -168,7 +168,7 @@ valid_flags (struct nbd_handle *h)
       set_error (0, "invalid length in NBD_REPLY_TYPE_BLOCK_STATUS");
       return 0;
     }
-    if (cmd->cb.fn.extent == NULL) {
+    if (cmd->cb.fn.extent.callback == NULL) {
       SET_NEXT_STATE (%.DEAD);
       set_error (0, "not expecting NBD_REPLY_TYPE_BLOCK_STATUS here");
       return 0;
@@ -304,7 +304,7 @@ valid_flags (struct nbd_handle *h)
                    offset, cmd->offset, cmd->count);
         return 0;
       }
-      if (cmd->type == NBD_CMD_READ && cmd->cb.fn.chunk) {
+      if (cmd->type == NBD_CMD_READ && cmd->cb.fn.chunk.callback) {
         int scratch = error;
         unsigned valid = valid_flags (h);
 
@@ -312,13 +312,14 @@ valid_flags (struct nbd_handle *h)
          * current error rather than any earlier one. If the callback fails
          * without setting errno, then use the server's error below.
          */
-        if (cmd->cb.fn.chunk (valid, cmd->cb.fn_user_data,
-                              cmd->data + (offset - cmd->offset),
-                              0, offset, LIBNBD_READ_ERROR, &scratch) == -1)
+        if (cmd->cb.fn.chunk.callback (valid, cmd->cb.fn.chunk.user_data,
+                                       cmd->data + (offset - cmd->offset),
+                                       0, offset, LIBNBD_READ_ERROR,
+                                       &scratch) == -1)
           if (cmd->error == 0)
             cmd->error = scratch;
         if (valid & LIBNBD_CALLBACK_FREE)
-          cmd->cb.fn.chunk = NULL; /* because we've freed it */
+          cmd->cb.fn.chunk.callback = NULL; /* because we've freed it */
       }
     }
 
@@ -398,18 +399,18 @@ valid_flags (struct nbd_handle *h)
     offset = be64toh (h->sbuf.sr.payload.offset_data.offset);
 
     assert (cmd); /* guaranteed by CHECK */
-    if (cmd->cb.fn.chunk) {
+    if (cmd->cb.fn.chunk.callback) {
       int error = cmd->error;
       unsigned valid = valid_flags (h);
 
-      if (cmd->cb.fn.chunk (valid, cmd->cb.fn_user_data,
-                            cmd->data + (offset - cmd->offset),
-                            length - sizeof offset, offset,
-                           LIBNBD_READ_DATA, &error) == -1)
+      if (cmd->cb.fn.chunk.callback (valid, cmd->cb.fn.chunk.user_data,
+                                     cmd->data + (offset - cmd->offset),
+                                     length - sizeof offset, offset,
+                                     LIBNBD_READ_DATA, &error) == -1)
         if (cmd->error == 0)
           cmd->error = error ? error : EPROTO;
       if (valid & LIBNBD_CALLBACK_FREE)
-        cmd->cb.fn.chunk = NULL; /* because we've freed it */
+        cmd->cb.fn.chunk.callback = NULL; /* because we've freed it */
     }
 
     SET_NEXT_STATE (%FINISH);
@@ -463,18 +464,18 @@ valid_flags (struct nbd_handle *h)
      * them as an extension, and this works even when length == 0.
      */
     memset (cmd->data + offset, 0, length);
-    if (cmd->cb.fn.chunk) {
+    if (cmd->cb.fn.chunk.callback) {
       int error = cmd->error;
       unsigned valid = valid_flags (h);
 
-      if (cmd->cb.fn.chunk (valid, cmd->cb.fn_user_data,
-                            cmd->data + offset, length,
-                            cmd->offset + offset,
-                            LIBNBD_READ_HOLE, &error) == -1)
+      if (cmd->cb.fn.chunk.callback (valid, cmd->cb.fn.chunk.user_data,
+                                     cmd->data + offset, length,
+                                     cmd->offset + offset,
+                                     LIBNBD_READ_HOLE, &error) == -1)
         if (cmd->error == 0)
           cmd->error = error ? error : EPROTO;
       if (valid & LIBNBD_CALLBACK_FREE)
-        cmd->cb.fn.chunk = NULL; /* because we've freed it */
+        cmd->cb.fn.chunk.callback = NULL; /* because we've freed it */
     }
 
     SET_NEXT_STATE(%FINISH);
@@ -499,7 +500,7 @@ valid_flags (struct nbd_handle *h)
 
     assert (cmd); /* guaranteed by CHECK */
     assert (cmd->type == NBD_CMD_BLOCK_STATUS);
-    assert (cmd->cb.fn.extent);
+    assert (cmd->cb.fn.extent.callback);
     assert (h->bs_entries);
     assert (length >= 12);
 
@@ -522,13 +523,14 @@ valid_flags (struct nbd_handle *h)
       int error = cmd->error;
       unsigned valid = valid_flags (h);
 
-      if (cmd->cb.fn.extent (valid, cmd->cb.fn_user_data,
-                             meta_context->name, cmd->offset,
-                             &h->bs_entries[1], (length-4) / 4, &error) == -1)
+      if (cmd->cb.fn.extent.callback (valid, cmd->cb.fn.extent.user_data,
+                                      meta_context->name, cmd->offset,
+                                      &h->bs_entries[1], (length-4) / 4,
+                                      &error) == -1)
         if (cmd->error == 0)
           cmd->error = error ? error : EPROTO;
       if (valid & LIBNBD_CALLBACK_FREE)
-        cmd->cb.fn.extent = NULL; /* because we've freed it */
+        cmd->cb.fn.extent.callback = NULL; /* because we've freed it */
     }
     else
       /* Emit a debug message, but ignore it. */
@@ -545,13 +547,15 @@ valid_flags (struct nbd_handle *h)
 
   flags = be16toh (h->sbuf.sr.structured_reply.flags);
   if (flags & NBD_REPLY_FLAG_DONE) {
-    if (cmd->type == NBD_CMD_BLOCK_STATUS && cmd->cb.fn.extent)
-      cmd->cb.fn.extent (LIBNBD_CALLBACK_FREE, cmd->cb.fn_user_data,
-                         NULL, 0, NULL, 0, NULL);
-    if (cmd->type == NBD_CMD_READ && cmd->cb.fn.chunk)
-      cmd->cb.fn.chunk (LIBNBD_CALLBACK_FREE, cmd->cb.fn_user_data,
-                        NULL, 0, 0, 0, NULL);
-    cmd->cb.fn.chunk = NULL;
+    if (cmd->type == NBD_CMD_BLOCK_STATUS && cmd->cb.fn.extent.callback)
+      cmd->cb.fn.extent.callback (LIBNBD_CALLBACK_FREE,
+                                  cmd->cb.fn.extent.user_data,
+                                  NULL, 0, NULL, 0, NULL);
+    if (cmd->type == NBD_CMD_READ && cmd->cb.fn.chunk.callback)
+      cmd->cb.fn.chunk.callback (LIBNBD_CALLBACK_FREE,
+                                 cmd->cb.fn.chunk.user_data,
+                                 NULL, 0, 0, 0, NULL);
+    cmd->cb.fn.chunk.callback = NULL;
     SET_NEXT_STATE (%^FINISH_COMMAND);
   }
   else {
diff --git a/generator/states-reply.c b/generator/states-reply.c
index 09adfed..e6b479a 100644
--- a/generator/states-reply.c
+++ b/generator/states-reply.c
@@ -168,14 +168,14 @@ save_reply_state (struct nbd_handle *h)
   retire = cmd->type == NBD_CMD_DISC;
 
   /* Notify the user */
-  if (cmd->cb.completion) {
+  if (cmd->cb.completion.callback) {
     int error = cmd->error;
     int r;
 
     assert (cmd->type != NBD_CMD_DISC);
-    r = cmd->cb.completion (LIBNBD_CALLBACK_VALID|LIBNBD_CALLBACK_FREE,
-                            cmd->cb.user_data, &error);
-    cmd->cb.completion = NULL; /* because we've freed it */
+    r = cmd->cb.completion.callback (LIBNBD_CALLBACK_VALID|LIBNBD_CALLBACK_FREE,
+                                     cmd->cb.completion.user_data, &error);
+    cmd->cb.completion.callback = NULL; /* because we've freed it */
     switch (r) {
     case -1:
       if (error)
diff --git a/generator/states.c b/generator/states.c
index 9ed57ae..22b0304 100644
--- a/generator/states.c
+++ b/generator/states.c
@@ -121,14 +121,14 @@ void abort_commands (struct nbd_handle *h,
     bool retire = cmd->type == NBD_CMD_DISC;
 
     next = cmd->next;
-    if (cmd->cb.completion) {
+    if (cmd->cb.completion.callback) {
       int error = cmd->error ? cmd->error : ENOTCONN;
       int r;
 
       assert (cmd->type != NBD_CMD_DISC);
-      r = cmd->cb.completion (LIBNBD_CALLBACK_VALID|LIBNBD_CALLBACK_FREE,
-                              cmd->cb.user_data, &error);
-      cmd->cb.completion = NULL; /* because we've freed it */
+      r = cmd->cb.completion.callback (LIBNBD_CALLBACK_VALID|LIBNBD_CALLBACK_FREE,
+                                       cmd->cb.completion.user_data, &error);
+      cmd->cb.completion.callback = NULL; /* because we've freed it */
       switch (r) {
       case -1:
         if (error)
diff --git a/interop/dirty-bitmap.c b/interop/dirty-bitmap.c
index aca0564..5a22adc 100644
--- a/interop/dirty-bitmap.c
+++ b/interop/dirty-bitmap.c
@@ -140,14 +140,17 @@ main (int argc, char *argv[])
   }
 
   data = (struct data) { .count = 2, };
-  if (nbd_block_status (nbd, exportsize, 0, cb, &data, 0) == -1) {
+  if (nbd_block_status (nbd, exportsize, 0,
+                        (nbd_extent_callback) { .callback = cb, .user_data = &data },
+                        0) == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
     exit (EXIT_FAILURE);
   }
   assert (data.seen_base && data.seen_dirty);
 
   data = (struct data) { .req_one = true, .count = 2, };
-  if (nbd_block_status (nbd, exportsize, 0, cb, &data,
+  if (nbd_block_status (nbd, exportsize, 0,
+                        (nbd_extent_callback) { .callback = cb, .user_data = &data },
                         LIBNBD_CMD_FLAG_REQ_ONE) == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
     exit (EXIT_FAILURE);
@@ -156,7 +159,9 @@ main (int argc, char *argv[])
 
   /* Trigger a failed callback, to prove connection stays up. */
   data = (struct data) { .count = 2, .fail = true, };
-  if (nbd_block_status (nbd, exportsize, 0, cb, &data, 0) != -1) {
+  if (nbd_block_status (nbd, exportsize, 0,
+                        (nbd_extent_callback) { .callback = cb, .user_data = &data },
+                        0) != -1) {
     fprintf (stderr, "unexpected block status success\n");
     exit (EXIT_FAILURE);
   }
diff --git a/interop/structured-read.c b/interop/structured-read.c
index 0b189d1..31aadbe 100644
--- a/interop/structured-read.c
+++ b/interop/structured-read.c
@@ -147,7 +147,8 @@ main (int argc, char *argv[])
 
   memset (rbuf, 2, sizeof rbuf);
   data = (struct data) { .count = 2, };
-  if (nbd_pread_structured (nbd, rbuf, sizeof rbuf, 2048, read_cb, &data,
+  if (nbd_pread_structured (nbd, rbuf, sizeof rbuf, 2048,
+                            (nbd_chunk_callback) { .callback = read_cb, .user_data = &data },
                             0) == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
     exit (EXIT_FAILURE);
@@ -157,7 +158,8 @@ main (int argc, char *argv[])
   /* Repeat with DF flag. */
   memset (rbuf, 2, sizeof rbuf);
   data = (struct data) { .df = true, .count = 1, };
-  if (nbd_pread_structured (nbd, rbuf, sizeof rbuf, 2048, read_cb, &data,
+  if (nbd_pread_structured (nbd, rbuf, sizeof rbuf, 2048,
+                            (nbd_chunk_callback) { .callback = read_cb, .user_data = &data },
                             LIBNBD_CMD_FLAG_DF) == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
     exit (EXIT_FAILURE);
@@ -170,7 +172,8 @@ main (int argc, char *argv[])
    */
   memset (rbuf, 2, sizeof rbuf);
   data = (struct data) { .count = 2, .fail = true, };
-  if (nbd_pread_structured (nbd, rbuf, sizeof rbuf, 2048, read_cb, &data,
+  if (nbd_pread_structured (nbd, rbuf, sizeof rbuf, 2048,
+                            (nbd_chunk_callback) { .callback = read_cb, .user_data = &data },
                             0) != -1) {
     fprintf (stderr, "unexpected pread callback success\n");
     exit (EXIT_FAILURE);
diff --git a/lib/aio.c b/lib/aio.c
index c141de6..5017ee6 100644
--- a/lib/aio.c
+++ b/lib/aio.c
@@ -32,15 +32,18 @@ void
 nbd_internal_retire_and_free_command (struct command *cmd)
 {
   /* Free the callbacks. */
-  if (cmd->type == NBD_CMD_BLOCK_STATUS && cmd->cb.fn.extent)
-    cmd->cb.fn.extent (LIBNBD_CALLBACK_FREE, cmd->cb.fn_user_data,
-                       NULL, 0, NULL, 0, NULL);
-  if (cmd->type == NBD_CMD_READ && cmd->cb.fn.chunk)
-    cmd->cb.fn.chunk (LIBNBD_CALLBACK_FREE, cmd->cb.fn_user_data,
-                      NULL, 0, 0, 0, NULL);
-  if (cmd->cb.completion)
-    cmd->cb.completion (LIBNBD_CALLBACK_FREE, cmd->cb.user_data,
-                        NULL);
+  if (cmd->type == NBD_CMD_BLOCK_STATUS && cmd->cb.fn.extent.callback)
+    cmd->cb.fn.extent.callback (LIBNBD_CALLBACK_FREE,
+                                cmd->cb.fn.extent.user_data,
+                                NULL, 0, NULL, 0, NULL);
+  if (cmd->type == NBD_CMD_READ && cmd->cb.fn.chunk.callback)
+    cmd->cb.fn.chunk.callback (LIBNBD_CALLBACK_FREE,
+                               cmd->cb.fn.chunk.user_data,
+                               NULL, 0, 0, 0, NULL);
+  if (cmd->cb.completion.callback)
+    cmd->cb.completion.callback (LIBNBD_CALLBACK_FREE,
+                                 cmd->cb.completion.user_data,
+                                 NULL);
 
   free (cmd);
 }
diff --git a/lib/debug.c b/lib/debug.c
index c1decb2..7753394 100644
--- a/lib/debug.c
+++ b/lib/debug.c
@@ -41,23 +41,22 @@ nbd_unlocked_get_debug (struct nbd_handle *h)
 int
 nbd_unlocked_clear_debug_callback (struct nbd_handle *h)
 {
-  if (h->debug_callback)
+  if (h->debug_callback.callback)
     /* ignore return value */
-    h->debug_callback (LIBNBD_CALLBACK_FREE, h->debug_data, NULL, NULL);
-  h->debug_callback = NULL;
-  h->debug_data = NULL;
+    h->debug_callback.callback (LIBNBD_CALLBACK_FREE,
+                                h->debug_callback.user_data, NULL, NULL);
+  h->debug_callback.callback = NULL;
   return 0;
 }
 
 int
 nbd_unlocked_set_debug_callback (struct nbd_handle *h,
-                                 nbd_debug_callback debug_callback, void *data)
+                                 nbd_debug_callback debug_callback)
 {
   /* This can't fail at the moment - see implementation above. */
   nbd_unlocked_clear_debug_callback (h);
 
   h->debug_callback = debug_callback;
-  h->debug_data = data;
   return 0;
 }
 
@@ -87,9 +86,10 @@ nbd_internal_debug (struct nbd_handle *h, const char *fs, ...)
   if (r == -1)
     goto out;
 
-  if (h->debug_callback)
+  if (h->debug_callback.callback)
     /* ignore return value */
-    h->debug_callback (LIBNBD_CALLBACK_VALID, h->debug_data, context, msg);
+    h->debug_callback.callback (LIBNBD_CALLBACK_VALID,
+                                h->debug_callback.user_data, context, msg);
   else
     fprintf (stderr, "libnbd: debug: %s: %s: %s\n",
              h->hname, context ? : "unknown", msg);
diff --git a/lib/internal.h b/lib/internal.h
index 301b798..5996a4f 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -85,7 +85,6 @@ struct nbd_handle {
   /* For debugging. */
   bool debug;
   nbd_debug_callback debug_callback;
-  void *debug_data;
 
   /* State machine.
    *
@@ -257,9 +256,7 @@ struct command_cb {
     nbd_extent_callback extent;
     nbd_chunk_callback chunk;
   } fn;
-  void *fn_user_data; /* associated with one of the fn callbacks above */
   nbd_completion_callback completion;
-  void *user_data; /* associated with the completion callback */
 };
 
 struct command {
diff --git a/lib/rw.c b/lib/rw.c
index dbd4e8c..9881701 100644
--- a/lib/rw.c
+++ b/lib/rw.c
@@ -49,7 +49,8 @@ nbd_unlocked_pread (struct nbd_handle *h, void *buf,
 {
   int64_t cookie;
 
-  cookie = nbd_unlocked_aio_pread (h, buf, count, offset, NULL, NULL, flags);
+  cookie = nbd_unlocked_aio_pread (h, buf, count, offset,
+                                   NBD_NULL_CALLBACK(completion), flags);
   if (cookie == -1)
     return -1;
 
@@ -60,14 +61,15 @@ nbd_unlocked_pread (struct nbd_handle *h, void *buf,
 int
 nbd_unlocked_pread_structured (struct nbd_handle *h, void *buf,
                                size_t count, uint64_t offset,
-                               nbd_chunk_callback chunk, void *user_data,
+                               nbd_chunk_callback chunk,
                                uint32_t flags)
 {
   int64_t cookie;
 
   cookie = nbd_unlocked_aio_pread_structured (h, buf, count, offset,
-                                              chunk, user_data,
-                                              NULL, NULL, flags);
+                                              chunk,
+                                              NBD_NULL_CALLBACK(completion),
+                                              flags);
   if (cookie == -1)
     return -1;
 
@@ -81,7 +83,8 @@ nbd_unlocked_pwrite (struct nbd_handle *h, const void *buf,
 {
   int64_t cookie;
 
-  cookie = nbd_unlocked_aio_pwrite (h, buf, count, offset, NULL, NULL, flags);
+  cookie = nbd_unlocked_aio_pwrite (h, buf, count, offset,
+                                    NBD_NULL_CALLBACK(completion), flags);
   if (cookie == -1)
     return -1;
 
@@ -94,7 +97,7 @@ nbd_unlocked_flush (struct nbd_handle *h, uint32_t flags)
 {
   int64_t cookie;
 
-  cookie = nbd_unlocked_aio_flush (h, NULL, NULL, flags);
+  cookie = nbd_unlocked_aio_flush (h, NBD_NULL_CALLBACK(completion), flags);
   if (cookie == -1)
     return -1;
 
@@ -108,7 +111,8 @@ nbd_unlocked_trim (struct nbd_handle *h,
 {
   int64_t cookie;
 
-  cookie = nbd_unlocked_aio_trim (h, count, offset, NULL, NULL, flags);
+  cookie = nbd_unlocked_aio_trim (h, count, offset,
+                                  NBD_NULL_CALLBACK(completion), flags);
   if (cookie == -1)
     return -1;
 
@@ -122,7 +126,8 @@ nbd_unlocked_cache (struct nbd_handle *h,
 {
   int64_t cookie;
 
-  cookie = nbd_unlocked_aio_cache (h, count, offset, NULL, NULL, flags);
+  cookie = nbd_unlocked_aio_cache (h, count, offset,
+                                   NBD_NULL_CALLBACK(completion), flags);
   if (cookie == -1)
     return -1;
 
@@ -136,7 +141,8 @@ nbd_unlocked_zero (struct nbd_handle *h,
 {
   int64_t cookie;
 
-  cookie = nbd_unlocked_aio_zero (h, count, offset, NULL, NULL, flags);
+  cookie = nbd_unlocked_aio_zero (h, count, offset,
+                                  NBD_NULL_CALLBACK(completion), flags);
   if (cookie == -1)
     return -1;
 
@@ -147,13 +153,13 @@ nbd_unlocked_zero (struct nbd_handle *h,
 int
 nbd_unlocked_block_status (struct nbd_handle *h,
                            uint64_t count, uint64_t offset,
-                           nbd_extent_callback extent, void *user_data,
+                           nbd_extent_callback extent,
                            uint32_t flags)
 {
   int64_t cookie;
 
-  cookie = nbd_unlocked_aio_block_status (h, count, offset, extent, user_data,
-                                          NULL, NULL, flags);
+  cookie = nbd_unlocked_aio_block_status (h, count, offset, extent,
+                                          NBD_NULL_CALLBACK(completion), flags);
   if (cookie == -1)
     return -1;
 
@@ -257,10 +263,9 @@ int64_t
 nbd_unlocked_aio_pread (struct nbd_handle *h, void *buf,
                         size_t count, uint64_t offset,
                         nbd_completion_callback completion,
-                        void *user_data,
                         uint32_t flags)
 {
-  struct command_cb cb = { .completion = completion, .user_data = user_data, };
+  struct command_cb cb = { .completion = completion };
 
   /* We could silently accept flag DF, but it really only makes sense
    * with callbacks, because otherwise there is no observable change
@@ -279,15 +284,11 @@ int64_t
 nbd_unlocked_aio_pread_structured (struct nbd_handle *h, void *buf,
                                    size_t count, uint64_t offset,
                                    nbd_chunk_callback chunk,
-                                   void *read_user_data,
                                    nbd_completion_callback completion,
-                                   void *callback_user_data,
                                    uint32_t flags)
 {
   struct command_cb cb = { .fn.chunk = chunk,
-                           .fn_user_data = read_user_data,
-                           .completion = completion,
-                           .user_data = callback_user_data, };
+                           .completion = completion };
 
   if ((flags & ~LIBNBD_CMD_FLAG_DF) != 0) {
     set_error (EINVAL, "invalid flag: %" PRIu32, flags);
@@ -308,10 +309,9 @@ int64_t
 nbd_unlocked_aio_pwrite (struct nbd_handle *h, const void *buf,
                          size_t count, uint64_t offset,
                          nbd_completion_callback completion,
-                         void *user_data,
                          uint32_t flags)
 {
-  struct command_cb cb = { .completion = completion, .user_data = user_data, };
+  struct command_cb cb = { .completion = completion };
 
   if (nbd_unlocked_is_read_only (h) == 1) {
     set_error (EINVAL, "server does not support write operations");
@@ -336,10 +336,9 @@ nbd_unlocked_aio_pwrite (struct nbd_handle *h, const void *buf,
 int64_t
 nbd_unlocked_aio_flush (struct nbd_handle *h,
                         nbd_completion_callback completion,
-                        void *user_data,
                         uint32_t flags)
 {
-  struct command_cb cb = { .completion = completion, .user_data = user_data, };
+  struct command_cb cb = { .completion = completion };
 
   if (nbd_unlocked_can_flush (h) != 1) {
     set_error (EINVAL, "server does not support flush operations");
@@ -359,10 +358,9 @@ int64_t
 nbd_unlocked_aio_trim (struct nbd_handle *h,
                        uint64_t count, uint64_t offset,
                        nbd_completion_callback completion,
-                       void *user_data,
                        uint32_t flags)
 {
-  struct command_cb cb = { .completion = completion, .user_data = user_data, };
+  struct command_cb cb = { .completion = completion };
 
   if (nbd_unlocked_is_read_only (h) == 1) {
     set_error (EINVAL, "server does not support write operations");
@@ -393,10 +391,9 @@ int64_t
 nbd_unlocked_aio_cache (struct nbd_handle *h,
                         uint64_t count, uint64_t offset,
                         nbd_completion_callback completion,
-                        void *user_data,
                         uint32_t flags)
 {
-  struct command_cb cb = { .completion = completion, .user_data = user_data, };
+  struct command_cb cb = { .completion = completion };
 
   /* Actually according to the NBD protocol document, servers do exist
    * that support NBD_CMD_CACHE but don't advertise the
@@ -420,10 +417,9 @@ int64_t
 nbd_unlocked_aio_zero (struct nbd_handle *h,
                        uint64_t count, uint64_t offset,
                        nbd_completion_callback completion,
-                       void *user_data,
                        uint32_t flags)
 {
-  struct command_cb cb = { .completion = completion, .user_data = user_data, };
+  struct command_cb cb = { .completion = completion };
 
   if (nbd_unlocked_is_read_only (h) == 1) {
     set_error (EINVAL, "server does not support write operations");
@@ -454,15 +450,11 @@ int64_t
 nbd_unlocked_aio_block_status (struct nbd_handle *h,
                                uint64_t count, uint64_t offset,
                                nbd_extent_callback extent,
-                               void *extent_user_data,
                                nbd_completion_callback completion,
-                               void *callback_user_data,
                                uint32_t flags)
 {
   struct command_cb cb = { .fn.extent = extent,
-                           .fn_user_data = extent_user_data,
-                           .completion = completion,
-                           .user_data = callback_user_data };
+                           .completion = completion };
 
   if (!h->structured_replies) {
     set_error (ENOTSUP, "server does not support structured replies");
diff --git a/tests/aio-parallel-load.c b/tests/aio-parallel-load.c
index 1f48324..0d67c36 100644
--- a/tests/aio-parallel-load.c
+++ b/tests/aio-parallel-load.c
@@ -255,11 +255,13 @@ start_thread (void *arg)
       offset = rand () % (EXPORTSIZE - buf_size);
       cmd = rand () & 1;
       if (cmd == 0) {
-        cookie = nbd_aio_pwrite (nbd, buf, buf_size, offset, NULL, NULL, 0);
+        cookie = nbd_aio_pwrite (nbd, buf, buf_size, offset,
+                                 NBD_NULL_CALLBACK(completion), 0);
         status->bytes_sent += buf_size;
       }
       else {
-        cookie = nbd_aio_pread (nbd, buf, buf_size, offset, NULL, NULL, 0);
+        cookie = nbd_aio_pread (nbd, buf, buf_size, offset,
+                                NBD_NULL_CALLBACK(completion), 0);
         status->bytes_received += buf_size;
       }
       if (cookie == -1) {
diff --git a/tests/aio-parallel.c b/tests/aio-parallel.c
index fb4d695..f6f13e6 100644
--- a/tests/aio-parallel.c
+++ b/tests/aio-parallel.c
@@ -271,12 +271,14 @@ start_thread (void *arg)
         + (rand () % (status->length[i] - BUFFERSIZE));
       cmd = rand () & 1;
       if (cmd == 0) {
-        cookie = nbd_aio_pwrite (nbd, buf, BUFFERSIZE, offset, NULL, NULL, 0);
+        cookie = nbd_aio_pwrite (nbd, buf, BUFFERSIZE, offset,
+                                 NBD_NULL_CALLBACK(completion), 0);
         status->bytes_sent += BUFFERSIZE;
         memcpy (&ramdisk[offset], buf, BUFFERSIZE);
       }
       else {
-        cookie = nbd_aio_pread (nbd, buf, BUFFERSIZE, offset, NULL, NULL, 0);
+        cookie = nbd_aio_pread (nbd, buf, BUFFERSIZE, offset,
+                                NBD_NULL_CALLBACK(completion), 0);
         status->bytes_received += BUFFERSIZE;
       }
       if (cookie == -1) {
diff --git a/tests/closure-lifetimes.c b/tests/closure-lifetimes.c
index 60809d4..e21a0e9 100644
--- a/tests/closure-lifetimes.c
+++ b/tests/closure-lifetimes.c
@@ -101,10 +101,10 @@ main (int argc, char *argv[])
   nbd = nbd_create ();
   if (nbd == NULL) NBD_ERROR;
 
-  nbd_set_debug_callback (nbd, debug_fn, NULL);
+  nbd_set_debug_callback (nbd, (nbd_debug_callback) { .callback = debug_fn });
   assert (debug_fn_free == 0);
 
-  nbd_set_debug_callback (nbd, debug_fn, NULL);
+  nbd_set_debug_callback (nbd, (nbd_debug_callback) { .callback = debug_fn});
   assert (debug_fn_free == 1);
 
   debug_fn_free = 0;
@@ -117,8 +117,9 @@ main (int argc, char *argv[])
   if (nbd_connect_command (nbd, nbdkit) == -1) NBD_ERROR;
 
   cookie = nbd_aio_pread_structured (nbd, buf, sizeof buf, 0,
-                                     read_cb, NULL,
-                                     completion_cb, NULL, 0);
+                                     (nbd_chunk_callback) { .callback = read_cb },
+                                     (nbd_completion_callback) { .callback = completion_cb },
+                                     0);
   if (cookie == -1) NBD_ERROR;
   assert (read_cb_free == 0);
   assert (completion_cb_free == 0);
@@ -144,8 +145,9 @@ main (int argc, char *argv[])
   if (nbd_connect_command (nbd, nbdkit_delay) == -1) NBD_ERROR;
 
   cookie = nbd_aio_pread_structured (nbd, buf, sizeof buf, 0,
-                                     read_cb, NULL,
-                                     completion_cb, NULL, 0);
+                                     (nbd_chunk_callback) { .callback = read_cb },
+                                     (nbd_completion_callback) { .callback = completion_cb },
+                                     0);
   if (cookie == -1) NBD_ERROR;
   nbd_kill_command (nbd, 0);
   nbd_close (nbd);
diff --git a/tests/errors.c b/tests/errors.c
index e442738..21ec919 100644
--- a/tests/errors.c
+++ b/tests/errors.c
@@ -222,7 +222,8 @@ main (int argc, char *argv[])
     exit (EXIT_FAILURE);
   }
   check (ERANGE, "nbd_pread: ");
-  if (nbd_aio_pwrite (nbd, buf, MAXSIZE, 0, NULL, NULL, 0) != -1) {
+  if (nbd_aio_pwrite (nbd, buf, MAXSIZE, 0,
+                      NBD_NULL_CALLBACK(completion), 0) != -1) {
     fprintf (stderr, "%s: test failed: "
              "nbd_aio_pwrite did not fail with oversize request\n",
              argv[0]);
@@ -245,11 +246,13 @@ main (int argc, char *argv[])
    * command at a time and stalls on the first), then queue multiple
    * disconnects.
    */
-  if (nbd_aio_pwrite (nbd, buf, 2 * 1024 * 1024, 0, NULL, NULL, 0) == -1) {
+  if (nbd_aio_pwrite (nbd, buf, 2 * 1024 * 1024, 0,
+                      NBD_NULL_CALLBACK(completion), 0) == -1) {
     fprintf (stderr, "%s: %s\n", argv[0], nbd_get_error ());
     exit (EXIT_FAILURE);
   }
-  if (nbd_aio_pwrite (nbd, buf, 2 * 1024 * 1024, 0, NULL, NULL, 0) == -1) {
+  if (nbd_aio_pwrite (nbd, buf, 2 * 1024 * 1024, 0,
+                      NBD_NULL_CALLBACK(completion), 0) == -1) {
     fprintf (stderr, "%s: %s\n", argv[0], nbd_get_error ());
     exit (EXIT_FAILURE);
   }
diff --git a/tests/meta-base-allocation.c b/tests/meta-base-allocation.c
index c36a77f..d4e331c 100644
--- a/tests/meta-base-allocation.c
+++ b/tests/meta-base-allocation.c
@@ -101,19 +101,24 @@ main (int argc, char *argv[])
 
   /* Read the block status. */
   id = 1;
-  if (nbd_block_status (nbd, 65536, 0, check_extent, &id, 0) == -1) {
+  if (nbd_block_status (nbd, 65536, 0,
+                        (nbd_extent_callback) { .callback = check_extent, .user_data = &id },
+                        0) == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
     exit (EXIT_FAILURE);
   }
 
   id = 2;
-  if (nbd_block_status (nbd, 1024, 32768-512, check_extent, &id, 0) == -1) {
+  if (nbd_block_status (nbd, 1024, 32768-512,
+                        (nbd_extent_callback) { .callback = check_extent, .user_data = &id },
+                        0) == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
     exit (EXIT_FAILURE);
   }
 
   id = 3;
-  if (nbd_block_status (nbd, 1024, 32768-512, check_extent, &id,
+  if (nbd_block_status (nbd, 1024, 32768-512,
+                        (nbd_extent_callback) { .callback = check_extent, .user_data = &id },
                         LIBNBD_CMD_FLAG_REQ_ONE) == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
     exit (EXIT_FAILURE);
diff --git a/tests/oldstyle.c b/tests/oldstyle.c
index afbda61..ff2ee97 100644
--- a/tests/oldstyle.c
+++ b/tests/oldstyle.c
@@ -131,7 +131,8 @@ main (int argc, char *argv[])
   /* Test again for callback operation. */
   memset (rbuf, 0, sizeof rbuf);
   if (nbd_pread_structured (nbd, rbuf, sizeof rbuf, 2 * sizeof rbuf,
-                            pread_cb, &calls, 0) == -1) {
+                            (nbd_chunk_callback) { .callback = pread_cb, .user_data = &calls },
+                            0) == -1) {
     fprintf (stderr, "%s\n", nbd_get_error ());
     exit (EXIT_FAILURE);
   }
@@ -147,7 +148,8 @@ main (int argc, char *argv[])
 
   /* Also test that callback errors are reflected correctly. */
   if (nbd_pread_structured (nbd, rbuf, sizeof rbuf, 2 * sizeof rbuf,
-                            pread_cb, &calls, 0) != -1) {
+                            (nbd_chunk_callback) { .callback = pread_cb, .user_data = &calls },
+                            0) != -1) {
     fprintf (stderr, "%s: expected failure from callback\n", argv[0]);
     exit (EXIT_FAILURE);
   }
diff --git a/tests/server-death.c b/tests/server-death.c
index 1559753..f7684ac 100644
--- a/tests/server-death.c
+++ b/tests/server-death.c
@@ -77,12 +77,15 @@ main (int argc, char *argv[])
   /* Issue a read and trim that should not complete yet. Set up the
    * trim to auto-retire via callback.
    */
-  if ((cookie = nbd_aio_pread (nbd, buf, sizeof buf, 0, NULL, NULL, 0)) == -1) {
+  if ((cookie = nbd_aio_pread (nbd, buf, sizeof buf, 0,
+                               NBD_NULL_CALLBACK(completion), 0)) == -1) {
     fprintf (stderr, "%s: test failed: nbd_aio_pread: %s\n", argv[0],
              nbd_get_error ());
     exit (EXIT_FAILURE);
   }
-  if (nbd_aio_trim (nbd, sizeof buf, 0, callback, NULL, 0) == -1) {
+  if (nbd_aio_trim (nbd, sizeof buf, 0,
+                    (nbd_completion_callback) { .callback = callback },
+                    0) == -1) {
     fprintf (stderr, "%s: test failed: nbd_aio_trim: %s\n", argv[0],
              nbd_get_error ());
     exit (EXIT_FAILURE);
-- 
2.22.0




More information about the Libguestfs mailing list