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

[fedora-virt] [PATCH] Remove receive callbacks (libguestfs)



From commit message:
========
This patch fixes a class of race conditions characterised by the
following sequence of events:

LIBRARY                                 DAEMON
send download request
                                        receive download request
                                        respond with download response
                                        start sending file chunks
set reply callback to 'download'
run main loop

At this stage the download reply callback receives both the download
reply and some file chunks. The current architecture doesn't provide a clean way
to prevent this from happening.

This patch fixes the above problem by changing the socket receive
handler to do nothing but buffering, and provides 2 new apis:

guestfs_get_reply
guestfs_free_reply

These will always de-queue exactly 1 message, which is always what is
wanted.
=======

This is a fairly invasive patch. Note that it still uses the main loop for reading and writing data to the socket.

The patch also looks a lot bigger than it really is, because it changes the auto-generated code. I'm separately planning to remove auto-generated code from git.

Matt
--
Matthew Booth, RHCA, RHCSS
Red Hat Engineering, Virtualisation Team

M:       +44 (0)7977 267231
GPG ID:  D33C3490
GPG FPR: 3733 612D 2D05 5458 8A8A 1600 3441 EA19 D33C 3490
>From 2e155ead570e8aba98d8763cebaa50cfa8a73da6 Mon Sep 17 00:00:00 2001
From: Matthew Booth <mbooth redhat com>
Date: Sat, 27 Jun 2009 22:05:48 +0100
Subject: [PATCH] Remove receive callbacks

This patch fixes a class of race conditions characterised by the
following sequence of events:

LIBRARY                                 DAEMON
send download request
                                        receive download request
                                        respond with download response
                                        start sending file chunks
set reply callback to 'download'
run main loop

At this stage the download reply callback receives both the download
reply and some file chunks. The current architecture doesn't provide a clean way
to prevent this from happening.

This patch fixes the above problem by changing the socket receive
handler to do nothing but buffering, and provides 2 new apis:

guestfs_get_reply
guestfs_free_reply

These will always de-queue exactly 1 message, which is always what is
wanted.
---
 src/generator.ml      |  207 +-
 src/guestfs-actions.c |15436 +++++++++++++++++++++++--------------------------
 src/guestfs.c         |  668 +--
 src/guestfs.h         |   13 +-
 4 files changed, 7510 insertions(+), 8814 deletions(-)

diff --git a/src/generator.ml b/src/generator.ml
index 69fd706..c6e25ec 100755
--- a/src/generator.ml
+++ b/src/generator.ml
@@ -3616,81 +3616,6 @@ check_state (guestfs_h *g, const char *caller)
   List.iter (
     fun (shortname, style, _, _, _, _, _) ->
       let name = "guestfs_" ^ shortname in
-
-      (* Generate the context struct which stores the high-level
-       * state between callback functions.
-       *)
-      pr "struct %s_ctx {\n" shortname;
-      pr "  /* This flag is set by the callbacks, so we know we've done\n";
-      pr "   * the callbacks as expected, and in the right sequence.\n";
-      pr "   * 0 = not called, 1 = reply_cb called.\n";
-      pr "   */\n";
-      pr "  int cb_sequence;\n";
-      pr "  struct guestfs_message_header hdr;\n";
-      pr "  struct guestfs_message_error err;\n";
-      (match fst style with
-       | RErr -> ()
-       | RConstString _ ->
-	   failwithf "RConstString cannot be returned from a daemon function"
-       | RInt _ | RInt64 _
-       | RBool _ | RString _ | RStringList _
-       | RIntBool _
-       | RPVList _ | RVGList _ | RLVList _
-       | RStat _ | RStatVFS _
-       | RHashtable _ ->
-	   pr "  struct %s_ret ret;\n" name
-      );
-      pr "};\n";
-      pr "\n";
-
-      (* Generate the reply callback function. *)
-      pr "static void %s_reply_cb (guestfs_h *g, void *data, XDR *xdr)\n" shortname;
-      pr "{\n";
-      pr "  guestfs_main_loop *ml = guestfs_get_main_loop (g);\n";
-      pr "  struct %s_ctx *ctx = (struct %s_ctx *) data;\n" shortname shortname;
-      pr "\n";
-      pr "  /* This should definitely not happen. */\n";
-      pr "  if (ctx->cb_sequence != 0) {\n";
-      pr "    ctx->cb_sequence = 9999;\n";
-      pr "    error (g, \"%%s: internal error: reply callback called twice\", \"%s\");\n" name;
-      pr "    return;\n";
-      pr "  }\n";
-      pr "\n";
-      pr "  ml->main_loop_quit (ml, g);\n";
-      pr "\n";
-      pr "  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {\n";
-      pr "    error (g, \"%%s: failed to parse reply header\", \"%s\");\n" name;
-      pr "    return;\n";
-      pr "  }\n";
-      pr "  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {\n";
-      pr "    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {\n";
-      pr "      error (g, \"%%s: failed to parse reply error\", \"%s\");\n"
-	name;
-      pr "      return;\n";
-      pr "    }\n";
-      pr "    goto done;\n";
-      pr "  }\n";
-
-      (match fst style with
-       | RErr -> ()
-       | RConstString _ ->
-	   failwithf "RConstString cannot be returned from a daemon function"
-       | RInt _ | RInt64 _
-       | RBool _ | RString _ | RStringList _
-       | RIntBool _
-       | RPVList _ | RVGList _ | RLVList _
-       | RStat _ | RStatVFS _
-       | RHashtable _ ->
-	    pr "  if (!xdr_%s_ret (xdr, &ctx->ret)) {\n" name;
-	    pr "    error (g, \"%%s: failed to parse reply\", \"%s\");\n" name;
-	    pr "    return;\n";
-	    pr "  }\n";
-      );
-
-      pr " done:\n";
-      pr "  ctx->cb_sequence = 1;\n";
-      pr "}\n\n";
-
       (* Generate the action stub. *)
       generate_prototype ~extern:false ~semicolon:false ~newline:true
 	~handle:"g" name style;
@@ -3713,15 +3638,26 @@ check_state (guestfs_h *g, const char *caller)
        | _ -> pr "  struct %s_args args;\n" name
       );
 
-      pr "  struct %s_ctx ctx;\n" shortname;
-      pr "  guestfs_main_loop *ml = guestfs_get_main_loop (g);\n";
+      pr "  struct guestfs_message_header hdr = {};\n";
+      pr "  struct guestfs_message_error err = {};\n";
+      (match fst style with
+       | RErr -> ()
+       | RConstString _ ->
+	   failwithf "RConstString cannot be returned from a daemon function"
+       | RInt _ | RInt64 _
+       | RBool _ | RString _ | RStringList _
+       | RIntBool _
+       | RPVList _ | RVGList _ | RLVList _
+       | RStat _ | RStatVFS _
+       | RHashtable _ ->
+	   pr "  struct %s_ret ret = {};\n" name
+      );
+
       pr "  int serial;\n";
       pr "\n";
       pr "  if (check_state (g, \"%s\") == -1) return %s;\n" name error_code;
       pr "  guestfs_set_busy (g);\n";
       pr "\n";
-      pr "  memset (&ctx, 0, sizeof ctx);\n";
-      pr "\n";
 
       (* Send the main header and arguments. *)
       (match snd style with
@@ -3756,7 +3692,6 @@ check_state (guestfs_h *g, const char *caller)
       pr "\n";
 
       (* Send any additional files (FileIn) requested. *)
-      let need_read_reply_label = ref false in
       List.iter (
 	function
 	| FileIn n ->
@@ -3768,82 +3703,128 @@ check_state (guestfs_h *g, const char *caller)
 	    pr "      guestfs_end_busy (g);\n";
 	    pr "      return %s;\n" error_code;
 	    pr "    }\n";
-	    pr "    if (r == -2) /* daemon cancelled */\n";
-	    pr "      goto read_reply;\n";
-	    need_read_reply_label := true;
 	    pr "  }\n";
 	    pr "\n";
 	| _ -> ()
       ) (snd style);
 
       (* Wait for the reply from the remote end. *)
-      if !need_read_reply_label then pr " read_reply:\n";
-      pr "  guestfs__switch_to_receiving (g);\n";
-      pr "  ctx.cb_sequence = 0;\n";
-      pr "  guestfs_set_reply_callback (g, %s_reply_cb, &ctx);\n" shortname;
-      pr "  (void) ml->main_loop_run (ml, g);\n";
-      pr "  guestfs_set_reply_callback (g, NULL, NULL);\n";
-      pr "  if (ctx.cb_sequence != 1) {\n";
-      pr "    error (g, \"%%s reply failed, see earlier error messages\", \"%s\");\n" name;
-      pr "    guestfs_end_busy (g);\n";
-      pr "    return %s;\n" error_code;
+      pr "  guestfs_reply_t reply;\n";
+      pr "\n";
+      pr "  for (;;) {\n";
+      pr "    guestfs_get_reply (g, &reply, 1);\n";
+      pr "\n";
+      pr "    if (GUESTFS_CANCEL_FLAG == reply.len) {\n";
+      pr "      /* This message was delayed from a previous file transaction. */\n";
+      pr "      continue;\n";
+      pr "    }\n";
+      pr "\n";
+      pr "    if (GUESTFS_LAUNCH_FLAG == reply.len) {\n";
+      pr "      error (g, \"%%s reply failed, received unexpected launch message\",\n";
+      pr "             \"%s\");\n" name;
+      pr "      guestfs_end_busy (g);\n";
+      pr "      return %s;\n" error_code;
+      pr "    }\n";
+      pr "\n";
+      pr "    if (0 == reply.len) {\n";
+      pr "      error (g, \"%%s reply failed, see earlier error messages\", \"%s\");\n" name;
+      pr "      guestfs_end_busy (g);\n";
+      pr "      return %s;\n" error_code;
+      pr "    }\n";
+      pr "\n";
+      pr "    break;\n";
       pr "  }\n";
       pr "\n";
 
-      pr "  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_%s, serial) == -1) {\n"
+      pr "  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {\n";
+      pr "    error (g, \"%%s: failed to parse reply header\", \"%s\");\n" name;
+      pr "    goto recv_error;\n";
+      pr "  }\n";
+      pr "\n";
+      pr "  if (hdr.status == GUESTFS_STATUS_ERROR) {\n";
+      pr "    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {\n";
+      pr "      error (g, \"%%s: failed to parse reply error\", \"%s\");\n"
+	name;
+      pr "      goto recv_error;\n";
+      pr "    }\n";
+      pr "  }\n";
+
+      (match fst style with
+       | RErr -> ()
+       | RConstString _ ->
+	   failwithf "RConstString cannot be returned from a daemon function"
+       | RInt _ | RInt64 _
+       | RBool _ | RString _ | RStringList _
+       | RIntBool _
+       | RPVList _ | RVGList _ | RLVList _
+       | RStat _ | RStatVFS _
+       | RHashtable _ ->
+	    pr "  else if (!xdr_%s_ret (&reply.xdr, &ret)) {\n" name;
+	    pr "    error (g, \"%%s: failed to parse reply\", \"%s\");\n" name;
+	    pr "    goto recv_error;\n";
+	    pr "  }\n";
+      );
+
+      pr "  if (check_reply_header (g, &hdr, GUESTFS_PROC_%s, serial) == -1) {\n"
 	(String.uppercase shortname);
-      pr "    guestfs_end_busy (g);\n";
-      pr "    return %s;\n" error_code;
+      pr "    goto recv_error;\n";
       pr "  }\n";
       pr "\n";
 
-      pr "  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {\n";
-      pr "    error (g, \"%%s\", ctx.err.error_message);\n";
-      pr "    free (ctx.err.error_message);\n";
-      pr "    guestfs_end_busy (g);\n";
-      pr "    return %s;\n" error_code;
+      pr "  if (hdr.status == GUESTFS_STATUS_ERROR) {\n";
+      pr "    error (g, \"%%s\", err.error_message);\n";
+      pr "    free (err.error_message);\n";
+      pr "    goto recv_error;\n";
       pr "  }\n";
       pr "\n";
 
+      pr "  guestfs_free_reply (g, &reply);\n\n";
+
       (* Expecting to receive further files (FileOut)? *)
       List.iter (
 	function
 	| FileOut n ->
 	    pr "  if (guestfs__receive_file_sync (g, %s) == -1) {\n" n;
-	    pr "    guestfs_end_busy (g);\n";
-	    pr "    return %s;\n" error_code;
+            pr "    guestfs_end_busy (g);\n";
+            pr "    return %s;\n" error_code;
 	    pr "  }\n";
 	    pr "\n";
 	| _ -> ()
       ) (snd style);
 
-      pr "  guestfs_end_busy (g);\n";
+      pr "  guestfs_end_busy (g);\n\n";
 
       (match fst style with
        | RErr -> pr "  return 0;\n"
        | RInt n | RInt64 n | RBool n ->
-	   pr "  return ctx.ret.%s;\n" n
+	   pr "  return ret.%s;\n" n
        | RConstString _ ->
 	   failwithf "RConstString cannot be returned from a daemon function"
        | RString n ->
-	   pr "  return ctx.ret.%s; /* caller will free */\n" n
+	   pr "  return ret.%s; /* caller will free */\n" n
        | RStringList n | RHashtable n ->
 	   pr "  /* caller will free this, but we need to add a NULL entry */\n";
-	   pr "  ctx.ret.%s.%s_val =\n" n n;
-	   pr "    safe_realloc (g, ctx.ret.%s.%s_val,\n" n n;
-	   pr "                  sizeof (char *) * (ctx.ret.%s.%s_len + 1));\n"
+	   pr "  ret.%s.%s_val =\n" n n;
+	   pr "    safe_realloc (g, ret.%s.%s_val,\n" n n;
+	   pr "                  sizeof (char *) * (ret.%s.%s_len + 1));\n"
 	     n n;
-	   pr "  ctx.ret.%s.%s_val[ctx.ret.%s.%s_len] = NULL;\n" n n n n;
-	   pr "  return ctx.ret.%s.%s_val;\n" n n
+	   pr "  ret.%s.%s_val[ret.%s.%s_len] = NULL;\n" n n n n;
+	   pr "  return ret.%s.%s_val;\n" n n
        | RIntBool _ ->
 	   pr "  /* caller with free this */\n";
-	   pr "  return safe_memdup (g, &ctx.ret, sizeof (ctx.ret));\n"
+	   pr "  return safe_memdup (g, &ret, sizeof (ret));\n"
        | RPVList n | RVGList n | RLVList n
        | RStat n | RStatVFS n ->
 	   pr "  /* caller will free this */\n";
-	   pr "  return safe_memdup (g, &ctx.ret.%s, sizeof (ctx.ret.%s));\n" n n
+	   pr "  return safe_memdup (g, &ret.%s, sizeof (ret.%s));\n" n n
       );
 
+      pr "\n";
+      pr " recv_error:\n";
+      pr "  guestfs_free_reply (g, &reply);\n";
+      pr "  guestfs_end_busy (g);\n";
+      pr "  return %s;\n" error_code;
+
       pr "}\n\n"
   ) daemon_functions
 
diff --git a/src/guestfs-actions.c b/src/guestfs-actions.c
index a46b339..8df6170 100644
--- a/src/guestfs-actions.c
+++ b/src/guestfs-actions.c
@@ -83,59 +83,18 @@ check_state (guestfs_h *g, const char *caller)
   return 0;
 }
 
-struct mount_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
-
-static void mount_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mount_ctx *ctx = (struct mount_ctx *) data;
-
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mount");
-    return;
-  }
-
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mount");
-    return;
-  }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mount");
-      return;
-    }
-    goto done;
-  }
- done:
-  ctx->cb_sequence = 1;
-}
-
 int guestfs_mount (guestfs_h *g,
 		const char *device,
 		const char *mountpoint)
 {
   struct guestfs_mount_args args;
-  struct mount_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mount") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   args.mountpoint = (char *) mountpoint;
   serial = guestfs__send_sync (g, GUESTFS_PROC_MOUNT,
@@ -145,168 +104,150 @@ int guestfs_mount (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mount_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mount");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MOUNT, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct sync_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mount");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void sync_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct sync_ctx *ctx = (struct sync_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mount");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_sync");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_sync");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mount");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_sync");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mount");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MOUNT, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_sync (guestfs_h *g)
 {
-  struct sync_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_sync") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_SYNC, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, sync_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_sync");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SYNC, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct touch_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_sync");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void touch_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct touch_ctx *ctx = (struct touch_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_sync");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_touch");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_touch");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_sync");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_touch");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_sync");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SYNC, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_touch (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_touch_args args;
-  struct touch_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_touch") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_TOUCH,
         (xdrproc_t) xdr_guestfs_touch_args, (char *) &args);
@@ -315,90 +256,77 @@ int guestfs_touch (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, touch_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_touch");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_TOUCH, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct cat_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_cat_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_touch");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void cat_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct cat_ctx *ctx = (struct cat_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_touch");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_cat");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_cat");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_touch");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_cat");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_touch");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_cat_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_cat");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_TOUCH, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char *guestfs_cat (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_cat_args args;
-  struct cat_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_cat_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_cat") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_CAT,
         (xdrproc_t) xdr_guestfs_cat_args, (char *) &args);
@@ -407,90 +335,81 @@ char *guestfs_cat (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, cat_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_cat");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_CAT, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.content; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct ll_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_ll_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_cat");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void ll_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct ll_ctx *ctx = (struct ll_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_cat");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_ll");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_ll");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_cat");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_ll");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_cat");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_ll_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_ll");
-    return;
+  else if (!xdr_guestfs_cat_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_cat");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_CAT, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.content; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char *guestfs_ll (guestfs_h *g,
 		const char *directory)
 {
   struct guestfs_ll_args args;
-  struct ll_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_ll_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_ll") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.directory = (char *) directory;
   serial = guestfs__send_sync (g, GUESTFS_PROC_LL,
         (xdrproc_t) xdr_guestfs_ll_args, (char *) &args);
@@ -499,90 +418,81 @@ char *guestfs_ll (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, ll_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_ll");
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_LL, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  guestfs_end_busy (g);
-  return ctx.ret.listing; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct ls_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_ls_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_ll");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void ls_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct ls_ctx *ctx = (struct ls_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_ll");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_ls");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_ls");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_ll");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_ls");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_ll");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_ls_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_ls");
-    return;
+  else if (!xdr_guestfs_ll_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_ll");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_LL, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.listing; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_ls (guestfs_h *g,
 		const char *directory)
 {
   struct guestfs_ls_args args;
-  struct ls_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_ls_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_ls") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.directory = (char *) directory;
   serial = guestfs__send_sync (g, GUESTFS_PROC_LS,
         (xdrproc_t) xdr_guestfs_ls_args, (char *) &args);
@@ -591,827 +501,746 @@ char **guestfs_ls (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, ls_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_ls");
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_LS, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.listing.listing_val =
-    safe_realloc (g, ctx.ret.listing.listing_val,
-                  sizeof (char *) * (ctx.ret.listing.listing_len + 1));
-  ctx.ret.listing.listing_val[ctx.ret.listing.listing_len] = NULL;
-  return ctx.ret.listing.listing_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct list_devices_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_list_devices_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_ls");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void list_devices_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct list_devices_ctx *ctx = (struct list_devices_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_ls");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_list_devices");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_list_devices");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_ls");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_list_devices");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_ls");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_list_devices_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_list_devices");
-    return;
+  else if (!xdr_guestfs_ls_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_ls");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_LS, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.listing.listing_val =
+    safe_realloc (g, ret.listing.listing_val,
+                  sizeof (char *) * (ret.listing.listing_len + 1));
+  ret.listing.listing_val[ret.listing.listing_len] = NULL;
+  return ret.listing.listing_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_list_devices (guestfs_h *g)
 {
-  struct list_devices_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_list_devices_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_list_devices") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_LIST_DEVICES, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, list_devices_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_list_devices");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_LIST_DEVICES, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.devices.devices_val =
-    safe_realloc (g, ctx.ret.devices.devices_val,
-                  sizeof (char *) * (ctx.ret.devices.devices_len + 1));
-  ctx.ret.devices.devices_val[ctx.ret.devices.devices_len] = NULL;
-  return ctx.ret.devices.devices_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct list_partitions_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_list_partitions_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_list_devices");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void list_partitions_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct list_partitions_ctx *ctx = (struct list_partitions_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_list_devices");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_list_partitions");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_list_partitions");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_list_devices");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_list_partitions");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_list_devices");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_list_partitions_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_list_partitions");
-    return;
+  else if (!xdr_guestfs_list_devices_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_list_devices");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_LIST_DEVICES, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.devices.devices_val =
+    safe_realloc (g, ret.devices.devices_val,
+                  sizeof (char *) * (ret.devices.devices_len + 1));
+  ret.devices.devices_val[ret.devices.devices_len] = NULL;
+  return ret.devices.devices_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_list_partitions (guestfs_h *g)
 {
-  struct list_partitions_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_list_partitions_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_list_partitions") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_LIST_PARTITIONS, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, list_partitions_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_list_partitions");
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_LIST_PARTITIONS, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.partitions.partitions_val =
-    safe_realloc (g, ctx.ret.partitions.partitions_val,
-                  sizeof (char *) * (ctx.ret.partitions.partitions_len + 1));
-  ctx.ret.partitions.partitions_val[ctx.ret.partitions.partitions_len] = NULL;
-  return ctx.ret.partitions.partitions_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct pvs_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_pvs_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_list_partitions");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void pvs_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct pvs_ctx *ctx = (struct pvs_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_list_partitions");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_pvs");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_pvs");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_list_partitions");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_pvs");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_list_partitions");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_pvs_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_pvs");
-    return;
+  else if (!xdr_guestfs_list_partitions_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_list_partitions");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_LIST_PARTITIONS, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.partitions.partitions_val =
+    safe_realloc (g, ret.partitions.partitions_val,
+                  sizeof (char *) * (ret.partitions.partitions_len + 1));
+  ret.partitions.partitions_val[ret.partitions.partitions_len] = NULL;
+  return ret.partitions.partitions_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_pvs (guestfs_h *g)
 {
-  struct pvs_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_pvs_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_pvs") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_PVS, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, pvs_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_pvs");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_PVS, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.physvols.physvols_val =
-    safe_realloc (g, ctx.ret.physvols.physvols_val,
-                  sizeof (char *) * (ctx.ret.physvols.physvols_len + 1));
-  ctx.ret.physvols.physvols_val[ctx.ret.physvols.physvols_len] = NULL;
-  return ctx.ret.physvols.physvols_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct vgs_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_vgs_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_pvs");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void vgs_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct vgs_ctx *ctx = (struct vgs_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_pvs");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_vgs");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_vgs");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_pvs");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_vgs");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_pvs");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_vgs_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_vgs");
-    return;
+  else if (!xdr_guestfs_pvs_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_pvs");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_PVS, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.physvols.physvols_val =
+    safe_realloc (g, ret.physvols.physvols_val,
+                  sizeof (char *) * (ret.physvols.physvols_len + 1));
+  ret.physvols.physvols_val[ret.physvols.physvols_len] = NULL;
+  return ret.physvols.physvols_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_vgs (guestfs_h *g)
 {
-  struct vgs_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_vgs_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_vgs") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_VGS, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, vgs_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_vgs");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_VGS, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.volgroups.volgroups_val =
-    safe_realloc (g, ctx.ret.volgroups.volgroups_val,
-                  sizeof (char *) * (ctx.ret.volgroups.volgroups_len + 1));
-  ctx.ret.volgroups.volgroups_val[ctx.ret.volgroups.volgroups_len] = NULL;
-  return ctx.ret.volgroups.volgroups_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct lvs_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_lvs_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_vgs");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void lvs_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct lvs_ctx *ctx = (struct lvs_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_vgs");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_lvs");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_lvs");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_vgs");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_lvs");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_vgs");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_lvs_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_lvs");
-    return;
+  else if (!xdr_guestfs_vgs_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_vgs");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_VGS, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.volgroups.volgroups_val =
+    safe_realloc (g, ret.volgroups.volgroups_val,
+                  sizeof (char *) * (ret.volgroups.volgroups_len + 1));
+  ret.volgroups.volgroups_val[ret.volgroups.volgroups_len] = NULL;
+  return ret.volgroups.volgroups_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_lvs (guestfs_h *g)
 {
-  struct lvs_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_lvs_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_lvs") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_LVS, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, lvs_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_lvs");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_LVS, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.logvols.logvols_val =
-    safe_realloc (g, ctx.ret.logvols.logvols_val,
-                  sizeof (char *) * (ctx.ret.logvols.logvols_len + 1));
-  ctx.ret.logvols.logvols_val[ctx.ret.logvols.logvols_len] = NULL;
-  return ctx.ret.logvols.logvols_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct pvs_full_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_pvs_full_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_lvs");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void pvs_full_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct pvs_full_ctx *ctx = (struct pvs_full_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_lvs");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_pvs_full");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_pvs_full");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_lvs");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_pvs_full");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_lvs");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_pvs_full_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_pvs_full");
-    return;
+  else if (!xdr_guestfs_lvs_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_lvs");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_LVS, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.logvols.logvols_val =
+    safe_realloc (g, ret.logvols.logvols_val,
+                  sizeof (char *) * (ret.logvols.logvols_len + 1));
+  ret.logvols.logvols_val[ret.logvols.logvols_len] = NULL;
+  return ret.logvols.logvols_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 struct guestfs_lvm_pv_list *guestfs_pvs_full (guestfs_h *g)
 {
-  struct pvs_full_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_pvs_full_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_pvs_full") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_PVS_FULL, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, pvs_full_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_pvs_full");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_PVS_FULL, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this */
-  return safe_memdup (g, &ctx.ret.physvols, sizeof (ctx.ret.physvols));
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct vgs_full_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_vgs_full_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_pvs_full");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void vgs_full_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct vgs_full_ctx *ctx = (struct vgs_full_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_pvs_full");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_vgs_full");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_vgs_full");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_pvs_full");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_vgs_full");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_pvs_full");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_vgs_full_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_vgs_full");
-    return;
+  else if (!xdr_guestfs_pvs_full_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_pvs_full");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_PVS_FULL, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this */
+  return safe_memdup (g, &ret.physvols, sizeof (ret.physvols));
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 struct guestfs_lvm_vg_list *guestfs_vgs_full (guestfs_h *g)
 {
-  struct vgs_full_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_vgs_full_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_vgs_full") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_VGS_FULL, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, vgs_full_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_vgs_full");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_VGS_FULL, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this */
-  return safe_memdup (g, &ctx.ret.volgroups, sizeof (ctx.ret.volgroups));
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct lvs_full_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_lvs_full_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_vgs_full");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void lvs_full_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct lvs_full_ctx *ctx = (struct lvs_full_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_vgs_full");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_lvs_full");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_lvs_full");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_vgs_full");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_lvs_full");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_vgs_full");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_lvs_full_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_lvs_full");
-    return;
+  else if (!xdr_guestfs_vgs_full_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_vgs_full");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_VGS_FULL, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this */
+  return safe_memdup (g, &ret.volgroups, sizeof (ret.volgroups));
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 struct guestfs_lvm_lv_list *guestfs_lvs_full (guestfs_h *g)
 {
-  struct lvs_full_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_lvs_full_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_lvs_full") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_LVS_FULL, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, lvs_full_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_lvs_full");
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_LVS_FULL, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this */
-  return safe_memdup (g, &ctx.ret.logvols, sizeof (ctx.ret.logvols));
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct read_lines_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_read_lines_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_lvs_full");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void read_lines_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct read_lines_ctx *ctx = (struct read_lines_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_lvs_full");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_read_lines");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_read_lines");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_lvs_full");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_read_lines");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_lvs_full");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_read_lines_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_read_lines");
-    return;
+  else if (!xdr_guestfs_lvs_full_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_lvs_full");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_LVS_FULL, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this */
+  return safe_memdup (g, &ret.logvols, sizeof (ret.logvols));
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_read_lines (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_read_lines_args args;
-  struct read_lines_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_read_lines_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_read_lines") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_READ_LINES,
         (xdrproc_t) xdr_guestfs_read_lines_args, (char *) &args);
@@ -1420,75 +1249,72 @@ char **guestfs_read_lines (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, read_lines_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_read_lines");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_READ_LINES, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.lines.lines_val =
-    safe_realloc (g, ctx.ret.lines.lines_val,
-                  sizeof (char *) * (ctx.ret.lines.lines_len + 1));
-  ctx.ret.lines.lines_val[ctx.ret.lines.lines_len] = NULL;
-  return ctx.ret.lines.lines_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct aug_init_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_read_lines");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void aug_init_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct aug_init_ctx *ctx = (struct aug_init_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_read_lines");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_aug_init");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_aug_init");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_read_lines");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_aug_init");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_read_lines");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_read_lines_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_read_lines");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_READ_LINES, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.lines.lines_val =
+    safe_realloc (g, ret.lines.lines_val,
+                  sizeof (char *) * (ret.lines.lines_len + 1));
+  ret.lines.lines_val[ret.lines.lines_len] = NULL;
+  return ret.lines.lines_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_aug_init (guestfs_h *g,
@@ -1496,15 +1322,13 @@ int guestfs_aug_init (guestfs_h *g,
 		int flags)
 {
   struct guestfs_aug_init_args args;
-  struct aug_init_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_aug_init") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.root = (char *) root;
   args.flags = flags;
   serial = guestfs__send_sync (g, GUESTFS_PROC_AUG_INIT,
@@ -1514,158 +1338,137 @@ int guestfs_aug_init (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, aug_init_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_aug_init");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_AUG_INIT, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct aug_close_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_aug_init");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void aug_close_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct aug_close_ctx *ctx = (struct aug_close_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_aug_init");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_aug_close");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_aug_close");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_aug_init");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_aug_close");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_aug_init");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_AUG_INIT, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_aug_close (guestfs_h *g)
 {
-  struct aug_close_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_aug_close") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_AUG_CLOSE, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, aug_close_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_aug_close");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_AUG_CLOSE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct aug_defvar_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_aug_defvar_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_aug_close");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void aug_defvar_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct aug_defvar_ctx *ctx = (struct aug_defvar_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_aug_close");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_aug_defvar");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_aug_defvar");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_aug_close");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_aug_defvar");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_aug_close");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_aug_defvar_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_aug_defvar");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_AUG_CLOSE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_aug_defvar (guestfs_h *g,
@@ -1673,15 +1476,14 @@ int guestfs_aug_defvar (guestfs_h *g,
 		const char *expr)
 {
   struct guestfs_aug_defvar_args args;
-  struct aug_defvar_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_aug_defvar_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_aug_defvar") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.name = (char *) name;
   args.expr = expr ? (char **) &expr : NULL;
   serial = guestfs__send_sync (g, GUESTFS_PROC_AUG_DEFVAR,
@@ -1691,75 +1493,67 @@ int guestfs_aug_defvar (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, aug_defvar_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_aug_defvar");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_AUG_DEFVAR, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.nrnodes;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct aug_defnode_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_aug_defnode_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_aug_defvar");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void aug_defnode_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct aug_defnode_ctx *ctx = (struct aug_defnode_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_aug_defvar");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_aug_defnode");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_aug_defnode");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_aug_defvar");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_aug_defnode");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_aug_defvar");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_aug_defnode_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_aug_defnode");
-    return;
+  else if (!xdr_guestfs_aug_defvar_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_aug_defvar");
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_AUG_DEFVAR, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.nrnodes;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 struct guestfs_int_bool *guestfs_aug_defnode (guestfs_h *g,
@@ -1768,15 +1562,14 @@ struct guestfs_int_bool *guestfs_aug_defnode (guestfs_h *g,
 		const char *val)
 {
   struct guestfs_aug_defnode_args args;
-  struct aug_defnode_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_aug_defnode_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_aug_defnode") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.name = (char *) name;
   args.expr = (char *) expr;
   args.val = (char *) val;
@@ -1787,91 +1580,82 @@ struct guestfs_int_bool *guestfs_aug_defnode (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, aug_defnode_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_aug_defnode");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_AUG_DEFNODE, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller with free this */
-  return safe_memdup (g, &ctx.ret, sizeof (ctx.ret));
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct aug_get_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_aug_get_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_aug_defnode");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void aug_get_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct aug_get_ctx *ctx = (struct aug_get_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_aug_defnode");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_aug_get");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_aug_get");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_aug_defnode");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_aug_get");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_aug_defnode");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_aug_get_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_aug_get");
-    return;
+  else if (!xdr_guestfs_aug_defnode_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_aug_defnode");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_AUG_DEFNODE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller with free this */
+  return safe_memdup (g, &ret, sizeof (ret));
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char *guestfs_aug_get (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_aug_get_args args;
-  struct aug_get_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_aug_get_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_aug_get") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_AUG_GET,
         (xdrproc_t) xdr_guestfs_aug_get_args, (char *) &args);
@@ -1880,70 +1664,67 @@ char *guestfs_aug_get (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, aug_get_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_aug_get");
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_AUG_GET, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  guestfs_end_busy (g);
-  return ctx.ret.val; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct aug_set_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_aug_get");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void aug_set_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct aug_set_ctx *ctx = (struct aug_set_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_aug_get");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_aug_set");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_aug_set");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_aug_get");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_aug_set");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_aug_get");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_aug_get_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_aug_get");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_AUG_GET, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.val; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_aug_set (guestfs_h *g,
@@ -1951,15 +1732,13 @@ int guestfs_aug_set (guestfs_h *g,
 		const char *val)
 {
   struct guestfs_aug_set_args args;
-  struct aug_set_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_aug_set") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   args.val = (char *) val;
   serial = guestfs__send_sync (g, GUESTFS_PROC_AUG_SET,
@@ -1969,70 +1748,63 @@ int guestfs_aug_set (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, aug_set_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_aug_set");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_AUG_SET, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct aug_insert_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_aug_set");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void aug_insert_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct aug_insert_ctx *ctx = (struct aug_insert_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_aug_set");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_aug_insert");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_aug_insert");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_aug_set");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_aug_insert");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_aug_set");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_AUG_SET, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_aug_insert (guestfs_h *g,
@@ -2041,15 +1813,13 @@ int guestfs_aug_insert (guestfs_h *g,
 		int before)
 {
   struct guestfs_aug_insert_args args;
-  struct aug_insert_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_aug_insert") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   args.label = (char *) label;
   args.before = before;
@@ -2060,90 +1830,77 @@ int guestfs_aug_insert (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, aug_insert_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_aug_insert");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_AUG_INSERT, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct aug_rm_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_aug_rm_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_aug_insert");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void aug_rm_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct aug_rm_ctx *ctx = (struct aug_rm_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_aug_insert");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_aug_rm");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_aug_rm");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_aug_insert");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_aug_rm");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_aug_insert");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_aug_rm_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_aug_rm");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_AUG_INSERT, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_aug_rm (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_aug_rm_args args;
-  struct aug_rm_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_aug_rm_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_aug_rm") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_AUG_RM,
         (xdrproc_t) xdr_guestfs_aug_rm_args, (char *) &args);
@@ -2152,70 +1909,67 @@ int guestfs_aug_rm (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, aug_rm_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_aug_rm");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_AUG_RM, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return ctx.ret.nrnodes;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct aug_mv_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_aug_rm");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void aug_mv_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct aug_mv_ctx *ctx = (struct aug_mv_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_aug_rm");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_aug_mv");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_aug_mv");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_aug_rm");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_aug_mv");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_aug_rm");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_aug_rm_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_aug_rm");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_AUG_RM, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.nrnodes;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_aug_mv (guestfs_h *g,
@@ -2223,15 +1977,13 @@ int guestfs_aug_mv (guestfs_h *g,
 		const char *dest)
 {
   struct guestfs_aug_mv_args args;
-  struct aug_mv_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_aug_mv") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.src = (char *) src;
   args.dest = (char *) dest;
   serial = guestfs__send_sync (g, GUESTFS_PROC_AUG_MV,
@@ -2241,90 +1993,77 @@ int guestfs_aug_mv (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, aug_mv_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_aug_mv");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_AUG_MV, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct aug_match_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_aug_match_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_aug_mv");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void aug_match_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct aug_match_ctx *ctx = (struct aug_match_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_aug_mv");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_aug_match");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_aug_match");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_aug_mv");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_aug_match");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_aug_mv");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_aug_match_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_aug_match");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_AUG_MV, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char **guestfs_aug_match (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_aug_match_args args;
-  struct aug_match_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_aug_match_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_aug_match") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_AUG_MATCH,
         (xdrproc_t) xdr_guestfs_aug_match_args, (char *) &args);
@@ -2333,261 +2072,234 @@ char **guestfs_aug_match (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, aug_match_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_aug_match");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_AUG_MATCH, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.matches.matches_val =
-    safe_realloc (g, ctx.ret.matches.matches_val,
-                  sizeof (char *) * (ctx.ret.matches.matches_len + 1));
-  ctx.ret.matches.matches_val[ctx.ret.matches.matches_len] = NULL;
-  return ctx.ret.matches.matches_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct aug_save_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_aug_match");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void aug_save_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct aug_save_ctx *ctx = (struct aug_save_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_aug_match");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_aug_save");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_aug_save");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_aug_match");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_aug_save");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_aug_match");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_aug_match_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_aug_match");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_AUG_MATCH, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.matches.matches_val =
+    safe_realloc (g, ret.matches.matches_val,
+                  sizeof (char *) * (ret.matches.matches_len + 1));
+  ret.matches.matches_val[ret.matches.matches_len] = NULL;
+  return ret.matches.matches_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_aug_save (guestfs_h *g)
 {
-  struct aug_save_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_aug_save") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_AUG_SAVE, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, aug_save_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_aug_save");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_AUG_SAVE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct aug_load_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_aug_save");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void aug_load_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct aug_load_ctx *ctx = (struct aug_load_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_aug_save");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_aug_load");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_aug_load");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_aug_save");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_aug_load");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_aug_save");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_AUG_SAVE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_aug_load (guestfs_h *g)
 {
-  struct aug_load_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_aug_load") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_AUG_LOAD, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, aug_load_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_aug_load");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_AUG_LOAD, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct aug_ls_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_aug_ls_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_aug_load");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void aug_ls_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct aug_ls_ctx *ctx = (struct aug_ls_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_aug_load");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_aug_ls");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_aug_ls");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_aug_load");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_aug_ls");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_aug_load");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_aug_ls_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_aug_ls");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_AUG_LOAD, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char **guestfs_aug_ls (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_aug_ls_args args;
-  struct aug_ls_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_aug_ls_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_aug_ls") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_AUG_LS,
         (xdrproc_t) xdr_guestfs_aug_ls_args, (char *) &args);
@@ -2596,90 +2308,85 @@ char **guestfs_aug_ls (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, aug_ls_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_aug_ls");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_AUG_LS, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.matches.matches_val =
-    safe_realloc (g, ctx.ret.matches.matches_val,
-                  sizeof (char *) * (ctx.ret.matches.matches_len + 1));
-  ctx.ret.matches.matches_val[ctx.ret.matches.matches_len] = NULL;
-  return ctx.ret.matches.matches_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct rm_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_aug_ls");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void rm_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct rm_ctx *ctx = (struct rm_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_aug_ls");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_rm");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_rm");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_aug_ls");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_rm");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_aug_ls");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_aug_ls_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_aug_ls");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_AUG_LS, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.matches.matches_val =
+    safe_realloc (g, ret.matches.matches_val,
+                  sizeof (char *) * (ret.matches.matches_len + 1));
+  ret.matches.matches_val[ret.matches.matches_len] = NULL;
+  return ret.matches.matches_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_rm (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_rm_args args;
-  struct rm_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_rm") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_RM,
         (xdrproc_t) xdr_guestfs_rm_args, (char *) &args);
@@ -2688,85 +2395,76 @@ int guestfs_rm (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, rm_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_rm");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_RM, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct rmdir_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_rm");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void rmdir_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct rmdir_ctx *ctx = (struct rmdir_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_rm");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_rmdir");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_rmdir");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_rm");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_rmdir");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_rm");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_RM, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_rmdir (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_rmdir_args args;
-  struct rmdir_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_rmdir") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_RMDIR,
         (xdrproc_t) xdr_guestfs_rmdir_args, (char *) &args);
@@ -2775,85 +2473,76 @@ int guestfs_rmdir (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, rmdir_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_rmdir");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_RMDIR, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct rm_rf_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_rmdir");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void rm_rf_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct rm_rf_ctx *ctx = (struct rm_rf_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_rmdir");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_rm_rf");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_rm_rf");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_rmdir");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_rm_rf");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_rmdir");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_RMDIR, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_rm_rf (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_rm_rf_args args;
-  struct rm_rf_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_rm_rf") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_RM_RF,
         (xdrproc_t) xdr_guestfs_rm_rf_args, (char *) &args);
@@ -2862,85 +2551,76 @@ int guestfs_rm_rf (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, rm_rf_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_rm_rf");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_RM_RF, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mkdir_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_rm_rf");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mkdir_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mkdir_ctx *ctx = (struct mkdir_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_rm_rf");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mkdir");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mkdir");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_rm_rf");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mkdir");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_rm_rf");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_RM_RF, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mkdir (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_mkdir_args args;
-  struct mkdir_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mkdir") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_MKDIR,
         (xdrproc_t) xdr_guestfs_mkdir_args, (char *) &args);
@@ -2949,85 +2629,76 @@ int guestfs_mkdir (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mkdir_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mkdir");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MKDIR, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mkdir_p_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mkdir");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mkdir_p_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mkdir_p_ctx *ctx = (struct mkdir_p_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mkdir");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mkdir_p");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mkdir_p");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mkdir");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mkdir_p");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mkdir");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MKDIR, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mkdir_p (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_mkdir_p_args args;
-  struct mkdir_p_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mkdir_p") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_MKDIR_P,
         (xdrproc_t) xdr_guestfs_mkdir_p_args, (char *) &args);
@@ -3036,70 +2707,63 @@ int guestfs_mkdir_p (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mkdir_p_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mkdir_p");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MKDIR_P, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct chmod_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mkdir_p");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void chmod_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct chmod_ctx *ctx = (struct chmod_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mkdir_p");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_chmod");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_chmod");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mkdir_p");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_chmod");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mkdir_p");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MKDIR_P, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_chmod (guestfs_h *g,
@@ -3107,15 +2771,13 @@ int guestfs_chmod (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_chmod_args args;
-  struct chmod_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_chmod") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.mode = mode;
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_CHMOD,
@@ -3125,70 +2787,63 @@ int guestfs_chmod (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, chmod_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_chmod");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_CHMOD, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct chown_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_chmod");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void chown_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct chown_ctx *ctx = (struct chown_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_chmod");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_chown");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_chown");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_chmod");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_chown");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_chmod");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_CHMOD, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_chown (guestfs_h *g,
@@ -3197,15 +2852,13 @@ int guestfs_chown (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_chown_args args;
-  struct chown_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_chown") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.owner = owner;
   args.group = group;
   args.path = (char *) path;
@@ -3216,90 +2869,77 @@ int guestfs_chown (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, chown_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_chown");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_CHOWN, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct exists_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_exists_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_chown");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void exists_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct exists_ctx *ctx = (struct exists_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_chown");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_exists");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_exists");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_chown");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_exists");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_chown");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_exists_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_exists");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_CHOWN, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_exists (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_exists_args args;
-  struct exists_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_exists_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_exists") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_EXISTS,
         (xdrproc_t) xdr_guestfs_exists_args, (char *) &args);
@@ -3308,90 +2948,81 @@ int guestfs_exists (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, exists_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_exists");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_EXISTS, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.existsflag;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct is_file_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_is_file_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_exists");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void is_file_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct is_file_ctx *ctx = (struct is_file_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_exists");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_is_file");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_is_file");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_exists");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_is_file");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_exists");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_is_file_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_is_file");
-    return;
+  else if (!xdr_guestfs_exists_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_exists");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_EXISTS, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.existsflag;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_is_file (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_is_file_args args;
-  struct is_file_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_is_file_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_is_file") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_IS_FILE,
         (xdrproc_t) xdr_guestfs_is_file_args, (char *) &args);
@@ -3400,90 +3031,81 @@ int guestfs_is_file (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, is_file_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_is_file");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_IS_FILE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.fileflag;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct is_dir_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_is_dir_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_is_file");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void is_dir_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct is_dir_ctx *ctx = (struct is_dir_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_is_file");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_is_dir");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_is_dir");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_is_file");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_is_dir");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_is_file");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_is_dir_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_is_dir");
-    return;
+  else if (!xdr_guestfs_is_file_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_is_file");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_IS_FILE, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.fileflag;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_is_dir (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_is_dir_args args;
-  struct is_dir_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_is_dir_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_is_dir") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_IS_DIR,
         (xdrproc_t) xdr_guestfs_is_dir_args, (char *) &args);
@@ -3492,85 +3114,80 @@ int guestfs_is_dir (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, is_dir_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_is_dir");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_IS_DIR, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.dirflag;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct pvcreate_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_is_dir");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void pvcreate_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct pvcreate_ctx *ctx = (struct pvcreate_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_is_dir");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_pvcreate");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_pvcreate");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_is_dir");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_pvcreate");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_is_dir");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_is_dir_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_is_dir");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_IS_DIR, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.dirflag;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_pvcreate (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_pvcreate_args args;
-  struct pvcreate_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_pvcreate") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_PVCREATE,
         (xdrproc_t) xdr_guestfs_pvcreate_args, (char *) &args);
@@ -3579,70 +3196,63 @@ int guestfs_pvcreate (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, pvcreate_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_pvcreate");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_PVCREATE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct vgcreate_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_pvcreate");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void vgcreate_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct vgcreate_ctx *ctx = (struct vgcreate_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_pvcreate");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_vgcreate");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_vgcreate");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_pvcreate");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_vgcreate");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_pvcreate");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_PVCREATE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_vgcreate (guestfs_h *g,
@@ -3650,15 +3260,13 @@ int guestfs_vgcreate (guestfs_h *g,
 		char * const* const physvols)
 {
   struct guestfs_vgcreate_args args;
-  struct vgcreate_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_vgcreate") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.volgroup = (char *) volgroup;
   args.physvols.physvols_val = (char **) physvols;
   for (args.physvols.physvols_len = 0; physvols[args.physvols.physvols_len]; args.physvols.physvols_len++) ;
@@ -3669,70 +3277,63 @@ int guestfs_vgcreate (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, vgcreate_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_vgcreate");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_VGCREATE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct lvcreate_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_vgcreate");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void lvcreate_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct lvcreate_ctx *ctx = (struct lvcreate_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_vgcreate");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_lvcreate");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_lvcreate");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_vgcreate");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_lvcreate");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_vgcreate");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_VGCREATE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_lvcreate (guestfs_h *g,
@@ -3741,15 +3342,13 @@ int guestfs_lvcreate (guestfs_h *g,
 		int mbytes)
 {
   struct guestfs_lvcreate_args args;
-  struct lvcreate_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_lvcreate") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.logvol = (char *) logvol;
   args.volgroup = (char *) volgroup;
   args.mbytes = mbytes;
@@ -3760,70 +3359,63 @@ int guestfs_lvcreate (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, lvcreate_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_lvcreate");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_LVCREATE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mkfs_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_lvcreate");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mkfs_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mkfs_ctx *ctx = (struct mkfs_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_lvcreate");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mkfs");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mkfs");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_lvcreate");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mkfs");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_lvcreate");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_LVCREATE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mkfs (guestfs_h *g,
@@ -3831,15 +3423,13 @@ int guestfs_mkfs (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_mkfs_args args;
-  struct mkfs_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mkfs") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.fstype = (char *) fstype;
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_MKFS,
@@ -3849,70 +3439,63 @@ int guestfs_mkfs (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mkfs_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mkfs");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MKFS, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct sfdisk_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mkfs");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void sfdisk_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct sfdisk_ctx *ctx = (struct sfdisk_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mkfs");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_sfdisk");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_sfdisk");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mkfs");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_sfdisk");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mkfs");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MKFS, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_sfdisk (guestfs_h *g,
@@ -3923,15 +3506,13 @@ int guestfs_sfdisk (guestfs_h *g,
 		char * const* const lines)
 {
   struct guestfs_sfdisk_args args;
-  struct sfdisk_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_sfdisk") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   args.cyls = cyls;
   args.heads = heads;
@@ -3945,70 +3526,63 @@ int guestfs_sfdisk (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, sfdisk_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_sfdisk");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SFDISK, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct write_file_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_sfdisk");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void write_file_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct write_file_ctx *ctx = (struct write_file_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_sfdisk");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_write_file");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_write_file");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_sfdisk");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_write_file");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_sfdisk");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SFDISK, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_write_file (guestfs_h *g,
@@ -4017,15 +3591,13 @@ int guestfs_write_file (guestfs_h *g,
 		int size)
 {
   struct guestfs_write_file_args args;
-  struct write_file_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_write_file") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   args.content = (char *) content;
   args.size = size;
@@ -4036,85 +3608,76 @@ int guestfs_write_file (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, write_file_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_write_file");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_WRITE_FILE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct umount_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_write_file");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void umount_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct umount_ctx *ctx = (struct umount_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_write_file");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_umount");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_umount");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_write_file");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_umount");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_write_file");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_WRITE_FILE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_umount (guestfs_h *g,
 		const char *pathordevice)
 {
   struct guestfs_umount_args args;
-  struct umount_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_umount") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.pathordevice = (char *) pathordevice;
   serial = guestfs__send_sync (g, GUESTFS_PROC_UMOUNT,
         (xdrproc_t) xdr_guestfs_umount_args, (char *) &args);
@@ -4123,349 +3686,309 @@ int guestfs_umount (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, umount_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_umount");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_UMOUNT, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mounts_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_mounts_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_umount");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mounts_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mounts_ctx *ctx = (struct mounts_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_umount");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mounts");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mounts");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_umount");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mounts");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_umount");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_mounts_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_mounts");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_UMOUNT, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char **guestfs_mounts (guestfs_h *g)
 {
-  struct mounts_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_mounts_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_mounts") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_MOUNTS, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mounts_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mounts");
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MOUNTS, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.devices.devices_val =
-    safe_realloc (g, ctx.ret.devices.devices_val,
-                  sizeof (char *) * (ctx.ret.devices.devices_len + 1));
-  ctx.ret.devices.devices_val[ctx.ret.devices.devices_len] = NULL;
-  return ctx.ret.devices.devices_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct umount_all_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mounts");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void umount_all_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct umount_all_ctx *ctx = (struct umount_all_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mounts");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_umount_all");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_umount_all");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mounts");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_umount_all");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mounts");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_mounts_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_mounts");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MOUNTS, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.devices.devices_val =
+    safe_realloc (g, ret.devices.devices_val,
+                  sizeof (char *) * (ret.devices.devices_len + 1));
+  ret.devices.devices_val[ret.devices.devices_len] = NULL;
+  return ret.devices.devices_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_umount_all (guestfs_h *g)
 {
-  struct umount_all_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_umount_all") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_UMOUNT_ALL, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, umount_all_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_umount_all");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_UMOUNT_ALL, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct lvm_remove_all_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_umount_all");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void lvm_remove_all_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct lvm_remove_all_ctx *ctx = (struct lvm_remove_all_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_umount_all");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_lvm_remove_all");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_lvm_remove_all");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_umount_all");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_lvm_remove_all");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_umount_all");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_UMOUNT_ALL, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_lvm_remove_all (guestfs_h *g)
 {
-  struct lvm_remove_all_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_lvm_remove_all") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_LVM_REMOVE_ALL, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, lvm_remove_all_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_lvm_remove_all");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_LVM_REMOVE_ALL, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct file_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_file_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_lvm_remove_all");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void file_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct file_ctx *ctx = (struct file_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_lvm_remove_all");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_file");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_file");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_lvm_remove_all");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_file");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_lvm_remove_all");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_file_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_file");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_LVM_REMOVE_ALL, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char *guestfs_file (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_file_args args;
-  struct file_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_file_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_file") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_FILE,
         (xdrproc_t) xdr_guestfs_file_args, (char *) &args);
@@ -4474,90 +3997,81 @@ char *guestfs_file (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, file_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_file");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_FILE, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.description; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct command_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_command_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_file");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void command_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct command_ctx *ctx = (struct command_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_file");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_command");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_command");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_file");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_command");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_file");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_command_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_command");
-    return;
+  else if (!xdr_guestfs_file_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_file");
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_FILE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.description; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char *guestfs_command (guestfs_h *g,
 		char * const* const arguments)
 {
   struct guestfs_command_args args;
-  struct command_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_command_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_command") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.arguments.arguments_val = (char **) arguments;
   for (args.arguments.arguments_len = 0; arguments[args.arguments.arguments_len]; args.arguments.arguments_len++) ;
   serial = guestfs__send_sync (g, GUESTFS_PROC_COMMAND,
@@ -4567,90 +4081,81 @@ char *guestfs_command (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, command_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_command");
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_COMMAND, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  guestfs_end_busy (g);
-  return ctx.ret.output; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct command_lines_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_command_lines_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_command");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void command_lines_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct command_lines_ctx *ctx = (struct command_lines_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_command");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_command_lines");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_command_lines");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_command");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_command_lines");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_command");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_command_lines_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_command_lines");
-    return;
+  else if (!xdr_guestfs_command_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_command");
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_COMMAND, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.output; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_command_lines (guestfs_h *g,
 		char * const* const arguments)
 {
   struct guestfs_command_lines_args args;
-  struct command_lines_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_command_lines_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_command_lines") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.arguments.arguments_val = (char **) arguments;
   for (args.arguments.arguments_len = 0; arguments[args.arguments.arguments_len]; args.arguments.arguments_len++) ;
   serial = guestfs__send_sync (g, GUESTFS_PROC_COMMAND_LINES,
@@ -4660,95 +4165,86 @@ char **guestfs_command_lines (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, command_lines_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_command_lines");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_COMMAND_LINES, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.lines.lines_val =
-    safe_realloc (g, ctx.ret.lines.lines_val,
-                  sizeof (char *) * (ctx.ret.lines.lines_len + 1));
-  ctx.ret.lines.lines_val[ctx.ret.lines.lines_len] = NULL;
-  return ctx.ret.lines.lines_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct stat_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_stat_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_command_lines");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void stat_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct stat_ctx *ctx = (struct stat_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_command_lines");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_stat");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_stat");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_command_lines");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_stat");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_command_lines");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_stat_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_stat");
-    return;
+  else if (!xdr_guestfs_command_lines_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_command_lines");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_COMMAND_LINES, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.lines.lines_val =
+    safe_realloc (g, ret.lines.lines_val,
+                  sizeof (char *) * (ret.lines.lines_len + 1));
+  ret.lines.lines_val[ret.lines.lines_len] = NULL;
+  return ret.lines.lines_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 struct guestfs_stat *guestfs_stat (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_stat_args args;
-  struct stat_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_stat_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_stat") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_STAT,
         (xdrproc_t) xdr_guestfs_stat_args, (char *) &args);
@@ -4757,91 +4253,82 @@ struct guestfs_stat *guestfs_stat (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, stat_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_stat");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_STAT, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this */
-  return safe_memdup (g, &ctx.ret.statbuf, sizeof (ctx.ret.statbuf));
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct lstat_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_lstat_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_stat");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void lstat_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct lstat_ctx *ctx = (struct lstat_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_stat");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_lstat");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_lstat");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_stat");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_lstat");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_stat");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_lstat_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_lstat");
-    return;
+  else if (!xdr_guestfs_stat_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_stat");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_STAT, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this */
+  return safe_memdup (g, &ret.statbuf, sizeof (ret.statbuf));
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 struct guestfs_stat *guestfs_lstat (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_lstat_args args;
-  struct lstat_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_lstat_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_lstat") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_LSTAT,
         (xdrproc_t) xdr_guestfs_lstat_args, (char *) &args);
@@ -4850,91 +4337,82 @@ struct guestfs_stat *guestfs_lstat (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, lstat_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_lstat");
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_LSTAT, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this */
-  return safe_memdup (g, &ctx.ret.statbuf, sizeof (ctx.ret.statbuf));
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct statvfs_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_statvfs_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_lstat");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void statvfs_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct statvfs_ctx *ctx = (struct statvfs_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_lstat");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_statvfs");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_statvfs");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_lstat");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_statvfs");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_lstat");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_statvfs_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_statvfs");
-    return;
+  else if (!xdr_guestfs_lstat_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_lstat");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_LSTAT, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this */
+  return safe_memdup (g, &ret.statbuf, sizeof (ret.statbuf));
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 struct guestfs_statvfs *guestfs_statvfs (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_statvfs_args args;
-  struct statvfs_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_statvfs_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_statvfs") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_STATVFS,
         (xdrproc_t) xdr_guestfs_statvfs_args, (char *) &args);
@@ -4943,91 +4421,82 @@ struct guestfs_statvfs *guestfs_statvfs (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, statvfs_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_statvfs");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_STATVFS, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this */
-  return safe_memdup (g, &ctx.ret.statbuf, sizeof (ctx.ret.statbuf));
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct tune2fs_l_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_tune2fs_l_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_statvfs");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void tune2fs_l_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct tune2fs_l_ctx *ctx = (struct tune2fs_l_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_statvfs");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_tune2fs_l");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_tune2fs_l");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_statvfs");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_tune2fs_l");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_statvfs");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_tune2fs_l_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_tune2fs_l");
-    return;
+  else if (!xdr_guestfs_statvfs_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_statvfs");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_STATVFS, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this */
+  return safe_memdup (g, &ret.statbuf, sizeof (ret.statbuf));
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_tune2fs_l (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_tune2fs_l_args args;
-  struct tune2fs_l_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_tune2fs_l_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_tune2fs_l") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_TUNE2FS_L,
         (xdrproc_t) xdr_guestfs_tune2fs_l_args, (char *) &args);
@@ -5036,90 +4505,85 @@ char **guestfs_tune2fs_l (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, tune2fs_l_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_tune2fs_l");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_TUNE2FS_L, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.superblock.superblock_val =
-    safe_realloc (g, ctx.ret.superblock.superblock_val,
-                  sizeof (char *) * (ctx.ret.superblock.superblock_len + 1));
-  ctx.ret.superblock.superblock_val[ctx.ret.superblock.superblock_len] = NULL;
-  return ctx.ret.superblock.superblock_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct blockdev_setro_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_tune2fs_l");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void blockdev_setro_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct blockdev_setro_ctx *ctx = (struct blockdev_setro_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_tune2fs_l");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_blockdev_setro");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_blockdev_setro");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_tune2fs_l");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_blockdev_setro");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_tune2fs_l");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_tune2fs_l_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_tune2fs_l");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_TUNE2FS_L, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.superblock.superblock_val =
+    safe_realloc (g, ret.superblock.superblock_val,
+                  sizeof (char *) * (ret.superblock.superblock_len + 1));
+  ret.superblock.superblock_val[ret.superblock.superblock_len] = NULL;
+  return ret.superblock.superblock_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_blockdev_setro (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_blockdev_setro_args args;
-  struct blockdev_setro_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_blockdev_setro") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_BLOCKDEV_SETRO,
         (xdrproc_t) xdr_guestfs_blockdev_setro_args, (char *) &args);
@@ -5128,85 +4592,76 @@ int guestfs_blockdev_setro (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, blockdev_setro_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_setro");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_BLOCKDEV_SETRO, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct blockdev_setrw_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_blockdev_setro");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void blockdev_setrw_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct blockdev_setrw_ctx *ctx = (struct blockdev_setrw_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_setro");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_blockdev_setrw");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_blockdev_setrw");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_blockdev_setro");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_blockdev_setrw");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_blockdev_setro");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_BLOCKDEV_SETRO, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_blockdev_setrw (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_blockdev_setrw_args args;
-  struct blockdev_setrw_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_blockdev_setrw") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_BLOCKDEV_SETRW,
         (xdrproc_t) xdr_guestfs_blockdev_setrw_args, (char *) &args);
@@ -5215,90 +4670,77 @@ int guestfs_blockdev_setrw (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, blockdev_setrw_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_setrw");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_BLOCKDEV_SETRW, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct blockdev_getro_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_blockdev_getro_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_blockdev_setrw");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void blockdev_getro_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct blockdev_getro_ctx *ctx = (struct blockdev_getro_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_setrw");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_blockdev_getro");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_blockdev_getro");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_blockdev_setrw");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_blockdev_getro");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_blockdev_setrw");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_blockdev_getro_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_blockdev_getro");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_BLOCKDEV_SETRW, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_blockdev_getro (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_blockdev_getro_args args;
-  struct blockdev_getro_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_blockdev_getro_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_blockdev_getro") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_BLOCKDEV_GETRO,
         (xdrproc_t) xdr_guestfs_blockdev_getro_args, (char *) &args);
@@ -5307,90 +4749,81 @@ int guestfs_blockdev_getro (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, blockdev_getro_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_getro");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_BLOCKDEV_GETRO, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.ro;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct blockdev_getss_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_blockdev_getss_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_blockdev_getro");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void blockdev_getss_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct blockdev_getss_ctx *ctx = (struct blockdev_getss_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_getro");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_blockdev_getss");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_blockdev_getss");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_blockdev_getro");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_blockdev_getss");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_blockdev_getro");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_blockdev_getss_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_blockdev_getss");
-    return;
+  else if (!xdr_guestfs_blockdev_getro_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_blockdev_getro");
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_BLOCKDEV_GETRO, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.ro;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_blockdev_getss (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_blockdev_getss_args args;
-  struct blockdev_getss_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_blockdev_getss_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_blockdev_getss") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_BLOCKDEV_GETSS,
         (xdrproc_t) xdr_guestfs_blockdev_getss_args, (char *) &args);
@@ -5399,90 +4832,81 @@ int guestfs_blockdev_getss (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, blockdev_getss_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_getss");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_BLOCKDEV_GETSS, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.sectorsize;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct blockdev_getbsz_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_blockdev_getbsz_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_blockdev_getss");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void blockdev_getbsz_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct blockdev_getbsz_ctx *ctx = (struct blockdev_getbsz_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_getss");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_blockdev_getbsz");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_blockdev_getbsz");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_blockdev_getss");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_blockdev_getbsz");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_blockdev_getss");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_blockdev_getbsz_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_blockdev_getbsz");
-    return;
+  else if (!xdr_guestfs_blockdev_getss_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_blockdev_getss");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_BLOCKDEV_GETSS, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.sectorsize;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_blockdev_getbsz (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_blockdev_getbsz_args args;
-  struct blockdev_getbsz_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_blockdev_getbsz_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_blockdev_getbsz") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_BLOCKDEV_GETBSZ,
         (xdrproc_t) xdr_guestfs_blockdev_getbsz_args, (char *) &args);
@@ -5491,70 +4915,67 @@ int guestfs_blockdev_getbsz (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, blockdev_getbsz_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_getbsz");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_BLOCKDEV_GETBSZ, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.blocksize;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct blockdev_setbsz_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_blockdev_getbsz");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void blockdev_setbsz_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct blockdev_setbsz_ctx *ctx = (struct blockdev_setbsz_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_getbsz");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_blockdev_setbsz");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_blockdev_setbsz");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_blockdev_getbsz");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_blockdev_setbsz");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_blockdev_getbsz");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_blockdev_getbsz_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_blockdev_getbsz");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_BLOCKDEV_GETBSZ, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.blocksize;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_blockdev_setbsz (guestfs_h *g,
@@ -5562,15 +4983,13 @@ int guestfs_blockdev_setbsz (guestfs_h *g,
 		int blocksize)
 {
   struct guestfs_blockdev_setbsz_args args;
-  struct blockdev_setbsz_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_blockdev_setbsz") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   args.blocksize = blocksize;
   serial = guestfs__send_sync (g, GUESTFS_PROC_BLOCKDEV_SETBSZ,
@@ -5580,90 +4999,77 @@ int guestfs_blockdev_setbsz (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, blockdev_setbsz_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_setbsz");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_BLOCKDEV_SETBSZ, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct blockdev_getsz_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_blockdev_getsz_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_blockdev_setbsz");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void blockdev_getsz_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct blockdev_getsz_ctx *ctx = (struct blockdev_getsz_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_setbsz");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_blockdev_getsz");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_blockdev_getsz");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_blockdev_setbsz");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_blockdev_getsz");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_blockdev_setbsz");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_blockdev_getsz_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_blockdev_getsz");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_BLOCKDEV_SETBSZ, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int64_t guestfs_blockdev_getsz (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_blockdev_getsz_args args;
-  struct blockdev_getsz_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_blockdev_getsz_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_blockdev_getsz") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_BLOCKDEV_GETSZ,
         (xdrproc_t) xdr_guestfs_blockdev_getsz_args, (char *) &args);
@@ -5672,90 +5078,81 @@ int64_t guestfs_blockdev_getsz (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, blockdev_getsz_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_getsz");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_BLOCKDEV_GETSZ, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.sizeinsectors;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct blockdev_getsize64_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_blockdev_getsize64_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_blockdev_getsz");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void blockdev_getsize64_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct blockdev_getsize64_ctx *ctx = (struct blockdev_getsize64_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_getsz");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_blockdev_getsize64");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_blockdev_getsize64");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_blockdev_getsz");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_blockdev_getsize64");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_blockdev_getsz");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_blockdev_getsize64_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_blockdev_getsize64");
-    return;
+  else if (!xdr_guestfs_blockdev_getsz_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_blockdev_getsz");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_BLOCKDEV_GETSZ, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.sizeinsectors;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int64_t guestfs_blockdev_getsize64 (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_blockdev_getsize64_args args;
-  struct blockdev_getsize64_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_blockdev_getsize64_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_blockdev_getsize64") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_BLOCKDEV_GETSIZE64,
         (xdrproc_t) xdr_guestfs_blockdev_getsize64_args, (char *) &args);
@@ -5764,85 +5161,80 @@ int64_t guestfs_blockdev_getsize64 (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, blockdev_getsize64_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_getsize64");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_BLOCKDEV_GETSIZE64, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.sizeinbytes;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct blockdev_flushbufs_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_blockdev_getsize64");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void blockdev_flushbufs_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct blockdev_flushbufs_ctx *ctx = (struct blockdev_flushbufs_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_getsize64");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_blockdev_flushbufs");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_blockdev_flushbufs");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_blockdev_getsize64");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_blockdev_flushbufs");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_blockdev_getsize64");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_blockdev_getsize64_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_blockdev_getsize64");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_BLOCKDEV_GETSIZE64, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.sizeinbytes;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_blockdev_flushbufs (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_blockdev_flushbufs_args args;
-  struct blockdev_flushbufs_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_blockdev_flushbufs") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_BLOCKDEV_FLUSHBUFS,
         (xdrproc_t) xdr_guestfs_blockdev_flushbufs_args, (char *) &args);
@@ -5851,85 +5243,76 @@ int guestfs_blockdev_flushbufs (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, blockdev_flushbufs_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_flushbufs");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_BLOCKDEV_FLUSHBUFS, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct blockdev_rereadpt_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_blockdev_flushbufs");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void blockdev_rereadpt_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct blockdev_rereadpt_ctx *ctx = (struct blockdev_rereadpt_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_flushbufs");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_blockdev_rereadpt");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_blockdev_rereadpt");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_blockdev_flushbufs");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_blockdev_rereadpt");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_blockdev_flushbufs");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_BLOCKDEV_FLUSHBUFS, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_blockdev_rereadpt (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_blockdev_rereadpt_args args;
-  struct blockdev_rereadpt_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_blockdev_rereadpt") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_BLOCKDEV_REREADPT,
         (xdrproc_t) xdr_guestfs_blockdev_rereadpt_args, (char *) &args);
@@ -5938,70 +5321,63 @@ int guestfs_blockdev_rereadpt (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, blockdev_rereadpt_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_rereadpt");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_BLOCKDEV_REREADPT, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct upload_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_blockdev_rereadpt");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void upload_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct upload_ctx *ctx = (struct upload_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_blockdev_rereadpt");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_upload");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_upload");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_blockdev_rereadpt");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_upload");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_blockdev_rereadpt");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_BLOCKDEV_REREADPT, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_upload (guestfs_h *g,
@@ -6009,15 +5385,13 @@ int guestfs_upload (guestfs_h *g,
 		const char *remotefilename)
 {
   struct guestfs_upload_args args;
-  struct upload_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_upload") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.remotefilename = (char *) remotefilename;
   serial = guestfs__send_sync (g, GUESTFS_PROC_UPLOAD,
         (xdrproc_t) xdr_guestfs_upload_args, (char *) &args);
@@ -6034,75 +5408,65 @@ int guestfs_upload (guestfs_h *g,
       guestfs_end_busy (g);
       return -1;
     }
-    if (r == -2) /* daemon cancelled */
-      goto read_reply;
-  }
-
- read_reply:
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, upload_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_upload");
-    guestfs_end_busy (g);
-    return -1;
   }
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_UPLOAD, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct download_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_upload");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void download_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct download_ctx *ctx = (struct download_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_upload");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_download");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_download");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_upload");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_download");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_upload");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_UPLOAD, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_download (guestfs_h *g,
@@ -6110,15 +5474,13 @@ int guestfs_download (guestfs_h *g,
 		const char *filename)
 {
   struct guestfs_download_args args;
-  struct download_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_download") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.remotefilename = (char *) remotefilename;
   serial = guestfs__send_sync (g, GUESTFS_PROC_DOWNLOAD,
         (xdrproc_t) xdr_guestfs_download_args, (char *) &args);
@@ -6127,80 +5489,68 @@ int guestfs_download (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, download_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_download");
-    guestfs_end_busy (g);
-    return -1;
+  guestfs_reply_t reply;
+
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
+
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
+
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_download");
+      guestfs_end_busy (g);
+      return -1;
+    }
+
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_download");
+      guestfs_end_busy (g);
+      return -1;
+    }
+
+    break;
   }
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_DOWNLOAD, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_download");
+    goto recv_error;
   }
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_download");
+      goto recv_error;
+    }
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_DOWNLOAD, serial) == -1) {
+    goto recv_error;
   }
 
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
   if (guestfs__receive_file_sync (g, filename) == -1) {
     guestfs_end_busy (g);
     return -1;
   }
 
   guestfs_end_busy (g);
-  return 0;
-}
-
-struct checksum_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_checksum_ret ret;
-};
-
-static void checksum_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct checksum_ctx *ctx = (struct checksum_ctx *) data;
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_checksum");
-    return;
-  }
-
-  ml->main_loop_quit (ml, g);
+  return 0;
 
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_checksum");
-    return;
-  }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_checksum");
-      return;
-    }
-    goto done;
-  }
-  if (!xdr_guestfs_checksum_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_checksum");
-    return;
-  }
- done:
-  ctx->cb_sequence = 1;
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char *guestfs_checksum (guestfs_h *g,
@@ -6208,15 +5558,14 @@ char *guestfs_checksum (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_checksum_args args;
-  struct checksum_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_checksum_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_checksum") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.csumtype = (char *) csumtype;
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_CHECKSUM,
@@ -6226,70 +5575,67 @@ char *guestfs_checksum (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, checksum_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_checksum");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_CHECKSUM, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.checksum; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct tar_in_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_checksum");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void tar_in_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct tar_in_ctx *ctx = (struct tar_in_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_checksum");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_tar_in");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_tar_in");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_checksum");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_tar_in");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_checksum");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_checksum_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_checksum");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_CHECKSUM, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.checksum; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_tar_in (guestfs_h *g,
@@ -6297,15 +5643,13 @@ int guestfs_tar_in (guestfs_h *g,
 		const char *directory)
 {
   struct guestfs_tar_in_args args;
-  struct tar_in_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_tar_in") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.directory = (char *) directory;
   serial = guestfs__send_sync (g, GUESTFS_PROC_TAR_IN,
         (xdrproc_t) xdr_guestfs_tar_in_args, (char *) &args);
@@ -6322,75 +5666,65 @@ int guestfs_tar_in (guestfs_h *g,
       guestfs_end_busy (g);
       return -1;
     }
-    if (r == -2) /* daemon cancelled */
-      goto read_reply;
-  }
-
- read_reply:
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, tar_in_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_tar_in");
-    guestfs_end_busy (g);
-    return -1;
   }
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_TAR_IN, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct tar_out_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_tar_in");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void tar_out_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct tar_out_ctx *ctx = (struct tar_out_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_tar_in");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_tar_out");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_tar_out");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_tar_in");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_tar_out");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_tar_in");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_TAR_IN, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_tar_out (guestfs_h *g,
@@ -6398,15 +5732,13 @@ int guestfs_tar_out (guestfs_h *g,
 		const char *tarfile)
 {
   struct guestfs_tar_out_args args;
-  struct tar_out_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_tar_out") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.directory = (char *) directory;
   serial = guestfs__send_sync (g, GUESTFS_PROC_TAR_OUT,
         (xdrproc_t) xdr_guestfs_tar_out_args, (char *) &args);
@@ -6415,75 +5747,68 @@ int guestfs_tar_out (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, tar_out_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_tar_out");
-    guestfs_end_busy (g);
-    return -1;
+  guestfs_reply_t reply;
+
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
+
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
+
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_tar_out");
+      guestfs_end_busy (g);
+      return -1;
+    }
+
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_tar_out");
+      guestfs_end_busy (g);
+      return -1;
+    }
+
+    break;
   }
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_TAR_OUT, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_tar_out");
+    goto recv_error;
   }
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_tar_out");
+      goto recv_error;
+    }
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_TAR_OUT, serial) == -1) {
+    goto recv_error;
   }
 
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
   if (guestfs__receive_file_sync (g, tarfile) == -1) {
     guestfs_end_busy (g);
     return -1;
   }
 
   guestfs_end_busy (g);
-  return 0;
-}
-
-struct tgz_in_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
-
-static void tgz_in_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct tgz_in_ctx *ctx = (struct tgz_in_ctx *) data;
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_tgz_in");
-    return;
-  }
-
-  ml->main_loop_quit (ml, g);
+  return 0;
 
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_tgz_in");
-    return;
-  }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_tgz_in");
-      return;
-    }
-    goto done;
-  }
- done:
-  ctx->cb_sequence = 1;
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_tgz_in (guestfs_h *g,
@@ -6491,15 +5816,13 @@ int guestfs_tgz_in (guestfs_h *g,
 		const char *directory)
 {
   struct guestfs_tgz_in_args args;
-  struct tgz_in_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_tgz_in") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.directory = (char *) directory;
   serial = guestfs__send_sync (g, GUESTFS_PROC_TGZ_IN,
         (xdrproc_t) xdr_guestfs_tgz_in_args, (char *) &args);
@@ -6516,75 +5839,65 @@ int guestfs_tgz_in (guestfs_h *g,
       guestfs_end_busy (g);
       return -1;
     }
-    if (r == -2) /* daemon cancelled */
-      goto read_reply;
   }
 
- read_reply:
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, tgz_in_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_tgz_in");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_TGZ_IN, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct tgz_out_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_tgz_in");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void tgz_out_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct tgz_out_ctx *ctx = (struct tgz_out_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_tgz_in");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_tgz_out");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_tgz_out");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_tgz_in");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_tgz_out");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_tgz_in");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_TGZ_IN, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_tgz_out (guestfs_h *g,
@@ -6592,15 +5905,13 @@ int guestfs_tgz_out (guestfs_h *g,
 		const char *tarball)
 {
   struct guestfs_tgz_out_args args;
-  struct tgz_out_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_tgz_out") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.directory = (char *) directory;
   serial = guestfs__send_sync (g, GUESTFS_PROC_TGZ_OUT,
         (xdrproc_t) xdr_guestfs_tgz_out_args, (char *) &args);
@@ -6609,75 +5920,68 @@ int guestfs_tgz_out (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, tgz_out_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_tgz_out");
-    guestfs_end_busy (g);
-    return -1;
+  guestfs_reply_t reply;
+
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
+
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
+
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_tgz_out");
+      guestfs_end_busy (g);
+      return -1;
+    }
+
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_tgz_out");
+      guestfs_end_busy (g);
+      return -1;
+    }
+
+    break;
   }
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_TGZ_OUT, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_tgz_out");
+    goto recv_error;
   }
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_tgz_out");
+      goto recv_error;
+    }
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_TGZ_OUT, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
 
+  guestfs_free_reply (g, &reply);
+
   if (guestfs__receive_file_sync (g, tarball) == -1) {
     guestfs_end_busy (g);
     return -1;
   }
 
   guestfs_end_busy (g);
-  return 0;
-}
-
-struct mount_ro_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
 
-static void mount_ro_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mount_ro_ctx *ctx = (struct mount_ro_ctx *) data;
-
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mount_ro");
-    return;
-  }
-
-  ml->main_loop_quit (ml, g);
+  return 0;
 
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mount_ro");
-    return;
-  }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mount_ro");
-      return;
-    }
-    goto done;
-  }
- done:
-  ctx->cb_sequence = 1;
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mount_ro (guestfs_h *g,
@@ -6685,15 +5989,13 @@ int guestfs_mount_ro (guestfs_h *g,
 		const char *mountpoint)
 {
   struct guestfs_mount_ro_args args;
-  struct mount_ro_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mount_ro") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   args.mountpoint = (char *) mountpoint;
   serial = guestfs__send_sync (g, GUESTFS_PROC_MOUNT_RO,
@@ -6703,70 +6005,63 @@ int guestfs_mount_ro (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mount_ro_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mount_ro");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MOUNT_RO, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mount_options_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mount_ro");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mount_options_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mount_options_ctx *ctx = (struct mount_options_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mount_ro");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mount_options");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mount_options");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mount_ro");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mount_options");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mount_ro");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MOUNT_RO, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mount_options (guestfs_h *g,
@@ -6775,15 +6070,13 @@ int guestfs_mount_options (guestfs_h *g,
 		const char *mountpoint)
 {
   struct guestfs_mount_options_args args;
-  struct mount_options_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mount_options") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.options = (char *) options;
   args.device = (char *) device;
   args.mountpoint = (char *) mountpoint;
@@ -6794,70 +6087,63 @@ int guestfs_mount_options (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mount_options_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mount_options");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MOUNT_OPTIONS, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mount_vfs_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mount_options");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mount_vfs_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mount_vfs_ctx *ctx = (struct mount_vfs_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mount_options");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mount_vfs");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mount_vfs");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mount_options");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mount_vfs");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mount_options");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MOUNT_OPTIONS, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mount_vfs (guestfs_h *g,
@@ -6867,15 +6153,13 @@ int guestfs_mount_vfs (guestfs_h *g,
 		const char *mountpoint)
 {
   struct guestfs_mount_vfs_args args;
-  struct mount_vfs_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mount_vfs") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.options = (char *) options;
   args.vfstype = (char *) vfstype;
   args.device = (char *) device;
@@ -6887,75 +6171,63 @@ int guestfs_mount_vfs (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mount_vfs_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mount_vfs");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MOUNT_VFS, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct debug_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_debug_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mount_vfs");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void debug_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct debug_ctx *ctx = (struct debug_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mount_vfs");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_debug");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_debug");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mount_vfs");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_debug");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mount_vfs");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_debug_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_debug");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MOUNT_VFS, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char *guestfs_debug (guestfs_h *g,
@@ -6963,15 +6235,14 @@ char *guestfs_debug (guestfs_h *g,
 		char * const* const extraargs)
 {
   struct guestfs_debug_args args;
-  struct debug_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_debug_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_debug") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.subcmd = (char *) subcmd;
   args.extraargs.extraargs_val = (char **) extraargs;
   for (args.extraargs.extraargs_len = 0; extraargs[args.extraargs.extraargs_len]; args.extraargs.extraargs_len++) ;
@@ -6982,85 +6253,80 @@ char *guestfs_debug (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, debug_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_debug");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_DEBUG, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.result; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct lvremove_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_debug");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void lvremove_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct lvremove_ctx *ctx = (struct lvremove_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_debug");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_lvremove");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_lvremove");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_debug");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_lvremove");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_debug");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_debug_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_debug");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_DEBUG, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.result; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_lvremove (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_lvremove_args args;
-  struct lvremove_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_lvremove") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_LVREMOVE,
         (xdrproc_t) xdr_guestfs_lvremove_args, (char *) &args);
@@ -7069,85 +6335,76 @@ int guestfs_lvremove (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, lvremove_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_lvremove");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_LVREMOVE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct vgremove_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_lvremove");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void vgremove_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct vgremove_ctx *ctx = (struct vgremove_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_lvremove");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_vgremove");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_vgremove");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_lvremove");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_vgremove");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_lvremove");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_LVREMOVE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_vgremove (guestfs_h *g,
 		const char *vgname)
 {
   struct guestfs_vgremove_args args;
-  struct vgremove_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_vgremove") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.vgname = (char *) vgname;
   serial = guestfs__send_sync (g, GUESTFS_PROC_VGREMOVE,
         (xdrproc_t) xdr_guestfs_vgremove_args, (char *) &args);
@@ -7156,85 +6413,76 @@ int guestfs_vgremove (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, vgremove_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_vgremove");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_VGREMOVE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct pvremove_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_vgremove");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void pvremove_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct pvremove_ctx *ctx = (struct pvremove_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_vgremove");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_pvremove");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_pvremove");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_vgremove");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_pvremove");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_vgremove");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_VGREMOVE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_pvremove (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_pvremove_args args;
-  struct pvremove_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_pvremove") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_PVREMOVE,
         (xdrproc_t) xdr_guestfs_pvremove_args, (char *) &args);
@@ -7243,70 +6491,63 @@ int guestfs_pvremove (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, pvremove_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_pvremove");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_PVREMOVE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct set_e2label_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_pvremove");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void set_e2label_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct set_e2label_ctx *ctx = (struct set_e2label_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_pvremove");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_set_e2label");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_set_e2label");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_pvremove");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_set_e2label");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_pvremove");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_PVREMOVE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_set_e2label (guestfs_h *g,
@@ -7314,15 +6555,13 @@ int guestfs_set_e2label (guestfs_h *g,
 		const char *label)
 {
   struct guestfs_set_e2label_args args;
-  struct set_e2label_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_set_e2label") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   args.label = (char *) label;
   serial = guestfs__send_sync (g, GUESTFS_PROC_SET_E2LABEL,
@@ -7332,90 +6571,77 @@ int guestfs_set_e2label (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, set_e2label_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_set_e2label");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SET_E2LABEL, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct get_e2label_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_get_e2label_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_set_e2label");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void get_e2label_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct get_e2label_ctx *ctx = (struct get_e2label_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_set_e2label");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_get_e2label");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_get_e2label");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_set_e2label");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_get_e2label");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_set_e2label");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_get_e2label_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_get_e2label");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SET_E2LABEL, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char *guestfs_get_e2label (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_get_e2label_args args;
-  struct get_e2label_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_get_e2label_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_get_e2label") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_GET_E2LABEL,
         (xdrproc_t) xdr_guestfs_get_e2label_args, (char *) &args);
@@ -7424,70 +6650,67 @@ char *guestfs_get_e2label (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, get_e2label_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_get_e2label");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_GET_E2LABEL, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.label; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct set_e2uuid_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_get_e2label");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void set_e2uuid_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct set_e2uuid_ctx *ctx = (struct set_e2uuid_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_get_e2label");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_set_e2uuid");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_set_e2uuid");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_get_e2label");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_set_e2uuid");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_get_e2label");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_get_e2label_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_get_e2label");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_GET_E2LABEL, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.label; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_set_e2uuid (guestfs_h *g,
@@ -7495,15 +6718,13 @@ int guestfs_set_e2uuid (guestfs_h *g,
 		const char *uuid)
 {
   struct guestfs_set_e2uuid_args args;
-  struct set_e2uuid_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_set_e2uuid") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   args.uuid = (char *) uuid;
   serial = guestfs__send_sync (g, GUESTFS_PROC_SET_E2UUID,
@@ -7513,90 +6734,77 @@ int guestfs_set_e2uuid (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, set_e2uuid_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_set_e2uuid");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SET_E2UUID, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct get_e2uuid_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_get_e2uuid_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_set_e2uuid");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void get_e2uuid_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct get_e2uuid_ctx *ctx = (struct get_e2uuid_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_set_e2uuid");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_get_e2uuid");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_get_e2uuid");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_set_e2uuid");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_get_e2uuid");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_set_e2uuid");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_get_e2uuid_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_get_e2uuid");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SET_E2UUID, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char *guestfs_get_e2uuid (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_get_e2uuid_args args;
-  struct get_e2uuid_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_get_e2uuid_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_get_e2uuid") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_GET_E2UUID,
         (xdrproc_t) xdr_guestfs_get_e2uuid_args, (char *) &args);
@@ -7605,75 +6813,67 @@ char *guestfs_get_e2uuid (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, get_e2uuid_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_get_e2uuid");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_GET_E2UUID, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.uuid; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct fsck_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_fsck_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_get_e2uuid");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void fsck_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct fsck_ctx *ctx = (struct fsck_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_get_e2uuid");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_fsck");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_fsck");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_get_e2uuid");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_fsck");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_get_e2uuid");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_fsck_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_fsck");
-    return;
+  else if (!xdr_guestfs_get_e2uuid_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_get_e2uuid");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_GET_E2UUID, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.uuid; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_fsck (guestfs_h *g,
@@ -7681,15 +6881,14 @@ int guestfs_fsck (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_fsck_args args;
-  struct fsck_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_fsck_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_fsck") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.fstype = (char *) fstype;
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_FSCK,
@@ -7699,85 +6898,80 @@ int guestfs_fsck (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, fsck_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_fsck");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_FSCK, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.status;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct zero_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_fsck");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void zero_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct zero_ctx *ctx = (struct zero_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_fsck");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_zero");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_zero");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_fsck");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_zero");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_fsck");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_fsck_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_fsck");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_FSCK, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.status;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_zero (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_zero_args args;
-  struct zero_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_zero") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_ZERO,
         (xdrproc_t) xdr_guestfs_zero_args, (char *) &args);
@@ -7786,70 +6980,63 @@ int guestfs_zero (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, zero_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_zero");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_ZERO, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct grub_install_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_zero");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void grub_install_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct grub_install_ctx *ctx = (struct grub_install_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_zero");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_grub_install");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_grub_install");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_zero");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_grub_install");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_zero");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_ZERO, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_grub_install (guestfs_h *g,
@@ -7857,15 +7044,13 @@ int guestfs_grub_install (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_grub_install_args args;
-  struct grub_install_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_grub_install") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.root = (char *) root;
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_GRUB_INSTALL,
@@ -7875,70 +7060,63 @@ int guestfs_grub_install (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, grub_install_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_grub_install");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_GRUB_INSTALL, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct cp_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_grub_install");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void cp_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct cp_ctx *ctx = (struct cp_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_grub_install");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_cp");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_cp");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_grub_install");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_cp");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_grub_install");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_GRUB_INSTALL, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_cp (guestfs_h *g,
@@ -7946,15 +7124,13 @@ int guestfs_cp (guestfs_h *g,
 		const char *dest)
 {
   struct guestfs_cp_args args;
-  struct cp_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_cp") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.src = (char *) src;
   args.dest = (char *) dest;
   serial = guestfs__send_sync (g, GUESTFS_PROC_CP,
@@ -7964,70 +7140,63 @@ int guestfs_cp (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, cp_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_cp");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_CP, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct cp_a_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_cp");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void cp_a_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct cp_a_ctx *ctx = (struct cp_a_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_cp");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_cp_a");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_cp_a");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_cp");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_cp_a");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_cp");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_CP, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_cp_a (guestfs_h *g,
@@ -8035,15 +7204,13 @@ int guestfs_cp_a (guestfs_h *g,
 		const char *dest)
 {
   struct guestfs_cp_a_args args;
-  struct cp_a_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_cp_a") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.src = (char *) src;
   args.dest = (char *) dest;
   serial = guestfs__send_sync (g, GUESTFS_PROC_CP_A,
@@ -8053,70 +7220,63 @@ int guestfs_cp_a (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, cp_a_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_cp_a");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_CP_A, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mv_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_cp_a");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mv_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mv_ctx *ctx = (struct mv_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_cp_a");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mv");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mv");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_cp_a");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mv");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_cp_a");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_CP_A, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mv (guestfs_h *g,
@@ -8124,15 +7284,13 @@ int guestfs_mv (guestfs_h *g,
 		const char *dest)
 {
   struct guestfs_mv_args args;
-  struct mv_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mv") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.src = (char *) src;
   args.dest = (char *) dest;
   serial = guestfs__send_sync (g, GUESTFS_PROC_MV,
@@ -8142,85 +7300,76 @@ int guestfs_mv (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mv_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mv");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MV, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct drop_caches_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mv");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void drop_caches_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct drop_caches_ctx *ctx = (struct drop_caches_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mv");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_drop_caches");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_drop_caches");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mv");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_drop_caches");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mv");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MV, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_drop_caches (guestfs_h *g,
 		int whattodrop)
 {
   struct guestfs_drop_caches_args args;
-  struct drop_caches_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_drop_caches") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.whattodrop = whattodrop;
   serial = guestfs__send_sync (g, GUESTFS_PROC_DROP_CACHES,
         (xdrproc_t) xdr_guestfs_drop_caches_args, (char *) &args);
@@ -8229,246 +7378,216 @@ int guestfs_drop_caches (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, drop_caches_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_drop_caches");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_DROP_CACHES, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct dmesg_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_dmesg_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_drop_caches");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void dmesg_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct dmesg_ctx *ctx = (struct dmesg_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_drop_caches");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_dmesg");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_dmesg");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_drop_caches");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_dmesg");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_drop_caches");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_dmesg_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_dmesg");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_DROP_CACHES, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char *guestfs_dmesg (guestfs_h *g)
 {
-  struct dmesg_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_dmesg_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_dmesg") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_DMESG, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, dmesg_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_dmesg");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_DMESG, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.kmsgs; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct ping_daemon_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_dmesg");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void ping_daemon_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct ping_daemon_ctx *ctx = (struct ping_daemon_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_dmesg");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_ping_daemon");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_ping_daemon");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_dmesg");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_ping_daemon");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_dmesg");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_dmesg_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_dmesg");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_DMESG, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.kmsgs; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_ping_daemon (guestfs_h *g)
 {
-  struct ping_daemon_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_ping_daemon") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_PING_DAEMON, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, ping_daemon_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_ping_daemon");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_PING_DAEMON, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct equal_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_equal_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_ping_daemon");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void equal_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct equal_ctx *ctx = (struct equal_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_ping_daemon");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_equal");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_equal");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_ping_daemon");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_equal");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_ping_daemon");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_equal_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_equal");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_PING_DAEMON, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_equal (guestfs_h *g,
@@ -8476,15 +7595,14 @@ int guestfs_equal (guestfs_h *g,
 		const char *file2)
 {
   struct guestfs_equal_args args;
-  struct equal_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_equal_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_equal") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.file1 = (char *) file1;
   args.file2 = (char *) file2;
   serial = guestfs__send_sync (g, GUESTFS_PROC_EQUAL,
@@ -8494,90 +7612,81 @@ int guestfs_equal (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, equal_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_equal");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_EQUAL, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return ctx.ret.equality;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct strings_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_strings_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_equal");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void strings_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct strings_ctx *ctx = (struct strings_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_equal");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_strings");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_strings");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_equal");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_strings");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_equal");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_strings_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_strings");
-    return;
+  else if (!xdr_guestfs_equal_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_equal");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_EQUAL, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.equality;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char **guestfs_strings (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_strings_args args;
-  struct strings_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_strings_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_strings") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_STRINGS,
         (xdrproc_t) xdr_guestfs_strings_args, (char *) &args);
@@ -8586,80 +7695,72 @@ char **guestfs_strings (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, strings_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_strings");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_STRINGS, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.stringsout.stringsout_val =
-    safe_realloc (g, ctx.ret.stringsout.stringsout_val,
-                  sizeof (char *) * (ctx.ret.stringsout.stringsout_len + 1));
-  ctx.ret.stringsout.stringsout_val[ctx.ret.stringsout.stringsout_len] = NULL;
-  return ctx.ret.stringsout.stringsout_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct strings_e_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_strings_e_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_strings");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void strings_e_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct strings_e_ctx *ctx = (struct strings_e_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_strings");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_strings_e");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_strings_e");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_strings");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_strings_e");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_strings");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_strings_e_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_strings_e");
-    return;
+  else if (!xdr_guestfs_strings_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_strings");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_STRINGS, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.stringsout.stringsout_val =
+    safe_realloc (g, ret.stringsout.stringsout_val,
+                  sizeof (char *) * (ret.stringsout.stringsout_len + 1));
+  ret.stringsout.stringsout_val[ret.stringsout.stringsout_len] = NULL;
+  return ret.stringsout.stringsout_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_strings_e (guestfs_h *g,
@@ -8667,15 +7768,14 @@ char **guestfs_strings_e (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_strings_e_args args;
-  struct strings_e_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_strings_e_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_strings_e") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.encoding = (char *) encoding;
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_STRINGS_E,
@@ -8685,95 +7785,86 @@ char **guestfs_strings_e (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, strings_e_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_strings_e");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_STRINGS_E, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.stringsout.stringsout_val =
-    safe_realloc (g, ctx.ret.stringsout.stringsout_val,
-                  sizeof (char *) * (ctx.ret.stringsout.stringsout_len + 1));
-  ctx.ret.stringsout.stringsout_val[ctx.ret.stringsout.stringsout_len] = NULL;
-  return ctx.ret.stringsout.stringsout_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct hexdump_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_hexdump_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_strings_e");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void hexdump_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct hexdump_ctx *ctx = (struct hexdump_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_strings_e");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_hexdump");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_hexdump");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_strings_e");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_hexdump");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_strings_e");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_hexdump_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_hexdump");
-    return;
+  else if (!xdr_guestfs_strings_e_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_strings_e");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_STRINGS_E, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.stringsout.stringsout_val =
+    safe_realloc (g, ret.stringsout.stringsout_val,
+                  sizeof (char *) * (ret.stringsout.stringsout_len + 1));
+  ret.stringsout.stringsout_val[ret.stringsout.stringsout_len] = NULL;
+  return ret.stringsout.stringsout_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char *guestfs_hexdump (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_hexdump_args args;
-  struct hexdump_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_hexdump_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_hexdump") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_HEXDUMP,
         (xdrproc_t) xdr_guestfs_hexdump_args, (char *) &args);
@@ -8782,85 +7873,80 @@ char *guestfs_hexdump (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, hexdump_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_hexdump");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_HEXDUMP, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.dump; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct zerofree_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_hexdump");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void zerofree_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct zerofree_ctx *ctx = (struct zerofree_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_hexdump");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_zerofree");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_zerofree");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_hexdump");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_zerofree");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_hexdump");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_hexdump_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_hexdump");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_HEXDUMP, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.dump; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_zerofree (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_zerofree_args args;
-  struct zerofree_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_zerofree") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_ZEROFREE,
         (xdrproc_t) xdr_guestfs_zerofree_args, (char *) &args);
@@ -8869,85 +7955,76 @@ int guestfs_zerofree (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, zerofree_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_zerofree");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_ZEROFREE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct pvresize_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_zerofree");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void pvresize_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct pvresize_ctx *ctx = (struct pvresize_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_zerofree");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_pvresize");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_pvresize");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_zerofree");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_pvresize");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_zerofree");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_ZEROFREE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_pvresize (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_pvresize_args args;
-  struct pvresize_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_pvresize") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_PVRESIZE,
         (xdrproc_t) xdr_guestfs_pvresize_args, (char *) &args);
@@ -8956,70 +8033,63 @@ int guestfs_pvresize (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, pvresize_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_pvresize");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_PVRESIZE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct sfdisk_N_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_pvresize");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void sfdisk_N_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct sfdisk_N_ctx *ctx = (struct sfdisk_N_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_pvresize");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_sfdisk_N");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_sfdisk_N");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_pvresize");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_sfdisk_N");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_pvresize");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_PVRESIZE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_sfdisk_N (guestfs_h *g,
@@ -9031,15 +8101,13 @@ int guestfs_sfdisk_N (guestfs_h *g,
 		const char *line)
 {
   struct guestfs_sfdisk_N_args args;
-  struct sfdisk_N_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_sfdisk_N") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   args.partnum = partnum;
   args.cyls = cyls;
@@ -9053,90 +8121,77 @@ int guestfs_sfdisk_N (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, sfdisk_N_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_sfdisk_N");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SFDISK_N, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct sfdisk_l_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_sfdisk_l_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_sfdisk_N");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void sfdisk_l_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct sfdisk_l_ctx *ctx = (struct sfdisk_l_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_sfdisk_N");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_sfdisk_l");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_sfdisk_l");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_sfdisk_N");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_sfdisk_l");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_sfdisk_N");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_sfdisk_l_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_sfdisk_l");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SFDISK_N, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char *guestfs_sfdisk_l (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_sfdisk_l_args args;
-  struct sfdisk_l_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_sfdisk_l_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_sfdisk_l") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_SFDISK_L,
         (xdrproc_t) xdr_guestfs_sfdisk_l_args, (char *) &args);
@@ -9145,90 +8200,81 @@ char *guestfs_sfdisk_l (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, sfdisk_l_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_sfdisk_l");
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SFDISK_L, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.partitions; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct sfdisk_kernel_geometry_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_sfdisk_kernel_geometry_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_sfdisk_l");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void sfdisk_kernel_geometry_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct sfdisk_kernel_geometry_ctx *ctx = (struct sfdisk_kernel_geometry_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_sfdisk_l");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_sfdisk_kernel_geometry");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_sfdisk_kernel_geometry");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_sfdisk_l");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_sfdisk_kernel_geometry");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_sfdisk_l");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_sfdisk_kernel_geometry_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_sfdisk_kernel_geometry");
-    return;
+  else if (!xdr_guestfs_sfdisk_l_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_sfdisk_l");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SFDISK_L, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.partitions; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char *guestfs_sfdisk_kernel_geometry (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_sfdisk_kernel_geometry_args args;
-  struct sfdisk_kernel_geometry_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_sfdisk_kernel_geometry_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_sfdisk_kernel_geometry") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_SFDISK_KERNEL_GEOMETRY,
         (xdrproc_t) xdr_guestfs_sfdisk_kernel_geometry_args, (char *) &args);
@@ -9237,90 +8283,81 @@ char *guestfs_sfdisk_kernel_geometry (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, sfdisk_kernel_geometry_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_sfdisk_kernel_geometry");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SFDISK_KERNEL_GEOMETRY, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.partitions; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct sfdisk_disk_geometry_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_sfdisk_disk_geometry_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_sfdisk_kernel_geometry");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void sfdisk_disk_geometry_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct sfdisk_disk_geometry_ctx *ctx = (struct sfdisk_disk_geometry_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_sfdisk_kernel_geometry");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_sfdisk_disk_geometry");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_sfdisk_disk_geometry");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_sfdisk_kernel_geometry");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_sfdisk_disk_geometry");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_sfdisk_kernel_geometry");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_sfdisk_disk_geometry_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_sfdisk_disk_geometry");
-    return;
+  else if (!xdr_guestfs_sfdisk_kernel_geometry_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_sfdisk_kernel_geometry");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SFDISK_KERNEL_GEOMETRY, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.partitions; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char *guestfs_sfdisk_disk_geometry (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_sfdisk_disk_geometry_args args;
-  struct sfdisk_disk_geometry_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_sfdisk_disk_geometry_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_sfdisk_disk_geometry") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_SFDISK_DISK_GEOMETRY,
         (xdrproc_t) xdr_guestfs_sfdisk_disk_geometry_args, (char *) &args);
@@ -9329,85 +8366,80 @@ char *guestfs_sfdisk_disk_geometry (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, sfdisk_disk_geometry_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_sfdisk_disk_geometry");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SFDISK_DISK_GEOMETRY, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.partitions; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct vg_activate_all_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_sfdisk_disk_geometry");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void vg_activate_all_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct vg_activate_all_ctx *ctx = (struct vg_activate_all_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_sfdisk_disk_geometry");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_vg_activate_all");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_vg_activate_all");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_sfdisk_disk_geometry");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_vg_activate_all");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_sfdisk_disk_geometry");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_sfdisk_disk_geometry_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_sfdisk_disk_geometry");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SFDISK_DISK_GEOMETRY, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.partitions; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_vg_activate_all (guestfs_h *g,
 		int activate)
 {
   struct guestfs_vg_activate_all_args args;
-  struct vg_activate_all_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_vg_activate_all") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.activate = activate;
   serial = guestfs__send_sync (g, GUESTFS_PROC_VG_ACTIVATE_ALL,
         (xdrproc_t) xdr_guestfs_vg_activate_all_args, (char *) &args);
@@ -9416,70 +8448,63 @@ int guestfs_vg_activate_all (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, vg_activate_all_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_vg_activate_all");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_VG_ACTIVATE_ALL, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct vg_activate_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_vg_activate_all");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void vg_activate_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct vg_activate_ctx *ctx = (struct vg_activate_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_vg_activate_all");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_vg_activate");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_vg_activate");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_vg_activate_all");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_vg_activate");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_vg_activate_all");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_VG_ACTIVATE_ALL, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_vg_activate (guestfs_h *g,
@@ -9487,15 +8512,13 @@ int guestfs_vg_activate (guestfs_h *g,
 		char * const* const volgroups)
 {
   struct guestfs_vg_activate_args args;
-  struct vg_activate_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_vg_activate") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.activate = activate;
   args.volgroups.volgroups_val = (char **) volgroups;
   for (args.volgroups.volgroups_len = 0; volgroups[args.volgroups.volgroups_len]; args.volgroups.volgroups_len++) ;
@@ -9506,70 +8529,63 @@ int guestfs_vg_activate (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, vg_activate_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_vg_activate");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_VG_ACTIVATE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct lvresize_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_vg_activate");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void lvresize_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct lvresize_ctx *ctx = (struct lvresize_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_vg_activate");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_lvresize");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_lvresize");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_vg_activate");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_lvresize");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_vg_activate");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_VG_ACTIVATE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_lvresize (guestfs_h *g,
@@ -9577,15 +8593,13 @@ int guestfs_lvresize (guestfs_h *g,
 		int mbytes)
 {
   struct guestfs_lvresize_args args;
-  struct lvresize_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_lvresize") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   args.mbytes = mbytes;
   serial = guestfs__send_sync (g, GUESTFS_PROC_LVRESIZE,
@@ -9595,85 +8609,76 @@ int guestfs_lvresize (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, lvresize_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_lvresize");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_LVRESIZE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct resize2fs_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_lvresize");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void resize2fs_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct resize2fs_ctx *ctx = (struct resize2fs_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_lvresize");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_resize2fs");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_resize2fs");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_lvresize");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_resize2fs");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_lvresize");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_LVRESIZE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_resize2fs (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_resize2fs_args args;
-  struct resize2fs_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_resize2fs") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_RESIZE2FS,
         (xdrproc_t) xdr_guestfs_resize2fs_args, (char *) &args);
@@ -9682,90 +8687,77 @@ int guestfs_resize2fs (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, resize2fs_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_resize2fs");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_RESIZE2FS, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct find_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_find_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_resize2fs");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void find_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct find_ctx *ctx = (struct find_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_resize2fs");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_find");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_find");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_resize2fs");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_find");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_resize2fs");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_find_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_find");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_RESIZE2FS, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char **guestfs_find (guestfs_h *g,
 		const char *directory)
 {
   struct guestfs_find_args args;
-  struct find_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_find_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_find") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.directory = (char *) directory;
   serial = guestfs__send_sync (g, GUESTFS_PROC_FIND,
         (xdrproc_t) xdr_guestfs_find_args, (char *) &args);
@@ -9774,90 +8766,85 @@ char **guestfs_find (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, find_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_find");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_FIND, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.names.names_val =
-    safe_realloc (g, ctx.ret.names.names_val,
-                  sizeof (char *) * (ctx.ret.names.names_len + 1));
-  ctx.ret.names.names_val[ctx.ret.names.names_len] = NULL;
-  return ctx.ret.names.names_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct e2fsck_f_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_find");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void e2fsck_f_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct e2fsck_f_ctx *ctx = (struct e2fsck_f_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_find");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_e2fsck_f");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_e2fsck_f");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_find");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_e2fsck_f");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_find");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_find_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_find");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_FIND, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.names.names_val =
+    safe_realloc (g, ret.names.names_val,
+                  sizeof (char *) * (ret.names.names_len + 1));
+  ret.names.names_val[ret.names.names_len] = NULL;
+  return ret.names.names_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_e2fsck_f (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_e2fsck_f_args args;
-  struct e2fsck_f_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_e2fsck_f") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_E2FSCK_F,
         (xdrproc_t) xdr_guestfs_e2fsck_f_args, (char *) &args);
@@ -9866,85 +8853,76 @@ int guestfs_e2fsck_f (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, e2fsck_f_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_e2fsck_f");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_E2FSCK_F, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct sleep_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_e2fsck_f");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void sleep_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct sleep_ctx *ctx = (struct sleep_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_e2fsck_f");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_sleep");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_sleep");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_e2fsck_f");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_sleep");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_e2fsck_f");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_E2FSCK_F, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_sleep (guestfs_h *g,
 		int secs)
 {
   struct guestfs_sleep_args args;
-  struct sleep_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_sleep") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.secs = secs;
   serial = guestfs__send_sync (g, GUESTFS_PROC_SLEEP,
         (xdrproc_t) xdr_guestfs_sleep_args, (char *) &args);
@@ -9953,75 +8931,63 @@ int guestfs_sleep (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, sleep_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_sleep");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SLEEP, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct ntfs_3g_probe_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_ntfs_3g_probe_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_sleep");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void ntfs_3g_probe_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct ntfs_3g_probe_ctx *ctx = (struct ntfs_3g_probe_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_sleep");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_ntfs_3g_probe");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_ntfs_3g_probe");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_sleep");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_ntfs_3g_probe");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_sleep");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_ntfs_3g_probe_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_ntfs_3g_probe");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SLEEP, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_ntfs_3g_probe (guestfs_h *g,
@@ -10029,15 +8995,14 @@ int guestfs_ntfs_3g_probe (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_ntfs_3g_probe_args args;
-  struct ntfs_3g_probe_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_ntfs_3g_probe_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_ntfs_3g_probe") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.rw = rw;
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_NTFS_3G_PROBE,
@@ -10047,90 +9012,81 @@ int guestfs_ntfs_3g_probe (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, ntfs_3g_probe_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_ntfs_3g_probe");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_NTFS_3G_PROBE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return ctx.ret.status;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct sh_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_sh_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_ntfs_3g_probe");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void sh_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct sh_ctx *ctx = (struct sh_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_ntfs_3g_probe");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_sh");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_sh");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_ntfs_3g_probe");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_sh");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_ntfs_3g_probe");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_sh_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_sh");
-    return;
+  else if (!xdr_guestfs_ntfs_3g_probe_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_ntfs_3g_probe");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_NTFS_3G_PROBE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.status;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char *guestfs_sh (guestfs_h *g,
 		const char *command)
 {
   struct guestfs_sh_args args;
-  struct sh_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_sh_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_sh") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.command = (char *) command;
   serial = guestfs__send_sync (g, GUESTFS_PROC_SH,
         (xdrproc_t) xdr_guestfs_sh_args, (char *) &args);
@@ -10139,90 +9095,81 @@ char *guestfs_sh (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, sh_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_sh");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SH, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.output; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct sh_lines_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_sh_lines_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_sh");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void sh_lines_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct sh_lines_ctx *ctx = (struct sh_lines_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_sh");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_sh_lines");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_sh_lines");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_sh");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_sh_lines");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_sh");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_sh_lines_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_sh_lines");
-    return;
+  else if (!xdr_guestfs_sh_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_sh");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SH, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.output; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_sh_lines (guestfs_h *g,
 		const char *command)
 {
   struct guestfs_sh_lines_args args;
-  struct sh_lines_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_sh_lines_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_sh_lines") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.command = (char *) command;
   serial = guestfs__send_sync (g, GUESTFS_PROC_SH_LINES,
         (xdrproc_t) xdr_guestfs_sh_lines_args, (char *) &args);
@@ -10231,95 +9178,86 @@ char **guestfs_sh_lines (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, sh_lines_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_sh_lines");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SH_LINES, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.lines.lines_val =
-    safe_realloc (g, ctx.ret.lines.lines_val,
-                  sizeof (char *) * (ctx.ret.lines.lines_len + 1));
-  ctx.ret.lines.lines_val[ctx.ret.lines.lines_len] = NULL;
-  return ctx.ret.lines.lines_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct glob_expand_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_glob_expand_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_sh_lines");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void glob_expand_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct glob_expand_ctx *ctx = (struct glob_expand_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_sh_lines");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_glob_expand");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_glob_expand");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_sh_lines");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_glob_expand");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_sh_lines");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_glob_expand_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_glob_expand");
-    return;
+  else if (!xdr_guestfs_sh_lines_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_sh_lines");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SH_LINES, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.lines.lines_val =
+    safe_realloc (g, ret.lines.lines_val,
+                  sizeof (char *) * (ret.lines.lines_len + 1));
+  ret.lines.lines_val[ret.lines.lines_len] = NULL;
+  return ret.lines.lines_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_glob_expand (guestfs_h *g,
 		const char *pattern)
 {
   struct guestfs_glob_expand_args args;
-  struct glob_expand_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_glob_expand_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_glob_expand") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.pattern = (char *) pattern;
   serial = guestfs__send_sync (g, GUESTFS_PROC_GLOB_EXPAND,
         (xdrproc_t) xdr_guestfs_glob_expand_args, (char *) &args);
@@ -10328,90 +9266,85 @@ char **guestfs_glob_expand (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, glob_expand_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_glob_expand");
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_GLOB_EXPAND, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.paths.paths_val =
-    safe_realloc (g, ctx.ret.paths.paths_val,
-                  sizeof (char *) * (ctx.ret.paths.paths_len + 1));
-  ctx.ret.paths.paths_val[ctx.ret.paths.paths_len] = NULL;
-  return ctx.ret.paths.paths_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct scrub_device_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_glob_expand");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void scrub_device_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct scrub_device_ctx *ctx = (struct scrub_device_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_glob_expand");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_scrub_device");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_scrub_device");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_glob_expand");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_scrub_device");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_glob_expand");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_glob_expand_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_glob_expand");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_GLOB_EXPAND, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.paths.paths_val =
+    safe_realloc (g, ret.paths.paths_val,
+                  sizeof (char *) * (ret.paths.paths_len + 1));
+  ret.paths.paths_val[ret.paths.paths_len] = NULL;
+  return ret.paths.paths_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_scrub_device (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_scrub_device_args args;
-  struct scrub_device_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_scrub_device") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_SCRUB_DEVICE,
         (xdrproc_t) xdr_guestfs_scrub_device_args, (char *) &args);
@@ -10420,85 +9353,76 @@ int guestfs_scrub_device (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, scrub_device_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_scrub_device");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SCRUB_DEVICE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct scrub_file_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_scrub_device");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void scrub_file_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct scrub_file_ctx *ctx = (struct scrub_file_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_scrub_device");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_scrub_file");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_scrub_file");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_scrub_device");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_scrub_file");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_scrub_device");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SCRUB_DEVICE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_scrub_file (guestfs_h *g,
 		const char *file)
 {
   struct guestfs_scrub_file_args args;
-  struct scrub_file_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_scrub_file") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.file = (char *) file;
   serial = guestfs__send_sync (g, GUESTFS_PROC_SCRUB_FILE,
         (xdrproc_t) xdr_guestfs_scrub_file_args, (char *) &args);
@@ -10507,85 +9431,76 @@ int guestfs_scrub_file (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, scrub_file_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_scrub_file");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SCRUB_FILE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct scrub_freespace_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_scrub_file");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void scrub_freespace_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct scrub_freespace_ctx *ctx = (struct scrub_freespace_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_scrub_file");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_scrub_freespace");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_scrub_freespace");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_scrub_file");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_scrub_freespace");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_scrub_file");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SCRUB_FILE, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_scrub_freespace (guestfs_h *g,
 		const char *dir)
 {
   struct guestfs_scrub_freespace_args args;
-  struct scrub_freespace_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_scrub_freespace") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.dir = (char *) dir;
   serial = guestfs__send_sync (g, GUESTFS_PROC_SCRUB_FREESPACE,
         (xdrproc_t) xdr_guestfs_scrub_freespace_args, (char *) &args);
@@ -10594,90 +9509,77 @@ int guestfs_scrub_freespace (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, scrub_freespace_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_scrub_freespace");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_SCRUB_FREESPACE, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mkdtemp_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_mkdtemp_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_scrub_freespace");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mkdtemp_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mkdtemp_ctx *ctx = (struct mkdtemp_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_scrub_freespace");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mkdtemp");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mkdtemp");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_scrub_freespace");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mkdtemp");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_scrub_freespace");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_mkdtemp_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_mkdtemp");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_SCRUB_FREESPACE, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char *guestfs_mkdtemp (guestfs_h *g,
 		const char *template)
 {
   struct guestfs_mkdtemp_args args;
-  struct mkdtemp_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_mkdtemp_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_mkdtemp") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.template = (char *) template;
   serial = guestfs__send_sync (g, GUESTFS_PROC_MKDTEMP,
         (xdrproc_t) xdr_guestfs_mkdtemp_args, (char *) &args);
@@ -10686,90 +9588,81 @@ char *guestfs_mkdtemp (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mkdtemp_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mkdtemp");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MKDTEMP, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.dir; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct wc_l_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_wc_l_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mkdtemp");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void wc_l_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct wc_l_ctx *ctx = (struct wc_l_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mkdtemp");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_wc_l");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_wc_l");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mkdtemp");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_wc_l");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mkdtemp");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_wc_l_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_wc_l");
-    return;
+  else if (!xdr_guestfs_mkdtemp_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_mkdtemp");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MKDTEMP, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.dir; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_wc_l (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_wc_l_args args;
-  struct wc_l_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_wc_l_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_wc_l") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_WC_L,
         (xdrproc_t) xdr_guestfs_wc_l_args, (char *) &args);
@@ -10778,90 +9671,81 @@ int guestfs_wc_l (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, wc_l_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_wc_l");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_WC_L, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.lines;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct wc_w_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_wc_w_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_wc_l");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void wc_w_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct wc_w_ctx *ctx = (struct wc_w_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_wc_l");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_wc_w");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_wc_w");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_wc_l");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_wc_w");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_wc_l");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_wc_w_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_wc_w");
-    return;
+  else if (!xdr_guestfs_wc_l_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_wc_l");
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_WC_L, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.lines;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_wc_w (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_wc_w_args args;
-  struct wc_w_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_wc_w_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_wc_w") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_WC_W,
         (xdrproc_t) xdr_guestfs_wc_w_args, (char *) &args);
@@ -10870,90 +9754,81 @@ int guestfs_wc_w (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, wc_w_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_wc_w");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_WC_W, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.words;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct wc_c_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_wc_c_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_wc_w");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void wc_c_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct wc_c_ctx *ctx = (struct wc_c_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_wc_w");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_wc_c");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_wc_c");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_wc_w");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_wc_c");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_wc_w");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_wc_c_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_wc_c");
-    return;
+  else if (!xdr_guestfs_wc_w_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_wc_w");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_WC_W, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.words;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_wc_c (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_wc_c_args args;
-  struct wc_c_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_wc_c_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_wc_c") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_WC_C,
         (xdrproc_t) xdr_guestfs_wc_c_args, (char *) &args);
@@ -10962,90 +9837,81 @@ int guestfs_wc_c (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, wc_c_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_wc_c");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_WC_C, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.chars;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct head_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_head_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_wc_c");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void head_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct head_ctx *ctx = (struct head_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_wc_c");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_head");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_head");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_wc_c");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_head");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_wc_c");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_head_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_head");
-    return;
+  else if (!xdr_guestfs_wc_c_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_wc_c");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_WC_C, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.chars;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char **guestfs_head (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_head_args args;
-  struct head_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_head_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_head") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_HEAD,
         (xdrproc_t) xdr_guestfs_head_args, (char *) &args);
@@ -11054,80 +9920,72 @@ char **guestfs_head (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, head_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_head");
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_HEAD, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.lines.lines_val =
-    safe_realloc (g, ctx.ret.lines.lines_val,
-                  sizeof (char *) * (ctx.ret.lines.lines_len + 1));
-  ctx.ret.lines.lines_val[ctx.ret.lines.lines_len] = NULL;
-  return ctx.ret.lines.lines_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct head_n_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_head_n_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_head");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void head_n_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct head_n_ctx *ctx = (struct head_n_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_head");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_head_n");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_head_n");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_head");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_head_n");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_head");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_head_n_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_head_n");
-    return;
+  else if (!xdr_guestfs_head_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_head");
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_HEAD, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.lines.lines_val =
+    safe_realloc (g, ret.lines.lines_val,
+                  sizeof (char *) * (ret.lines.lines_len + 1));
+  ret.lines.lines_val[ret.lines.lines_len] = NULL;
+  return ret.lines.lines_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_head_n (guestfs_h *g,
@@ -11135,15 +9993,14 @@ char **guestfs_head_n (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_head_n_args args;
-  struct head_n_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_head_n_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_head_n") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.nrlines = nrlines;
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_HEAD_N,
@@ -11153,95 +10010,86 @@ char **guestfs_head_n (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, head_n_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_head_n");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_HEAD_N, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.lines.lines_val =
-    safe_realloc (g, ctx.ret.lines.lines_val,
-                  sizeof (char *) * (ctx.ret.lines.lines_len + 1));
-  ctx.ret.lines.lines_val[ctx.ret.lines.lines_len] = NULL;
-  return ctx.ret.lines.lines_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct tail_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_tail_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_head_n");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void tail_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct tail_ctx *ctx = (struct tail_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_head_n");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_tail");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_tail");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_head_n");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_tail");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_head_n");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_tail_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_tail");
-    return;
+  else if (!xdr_guestfs_head_n_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_head_n");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_HEAD_N, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.lines.lines_val =
+    safe_realloc (g, ret.lines.lines_val,
+                  sizeof (char *) * (ret.lines.lines_len + 1));
+  ret.lines.lines_val[ret.lines.lines_len] = NULL;
+  return ret.lines.lines_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_tail (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_tail_args args;
-  struct tail_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_tail_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_tail") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_TAIL,
         (xdrproc_t) xdr_guestfs_tail_args, (char *) &args);
@@ -11250,80 +10098,72 @@ char **guestfs_tail (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, tail_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_tail");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_TAIL, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.lines.lines_val =
-    safe_realloc (g, ctx.ret.lines.lines_val,
-                  sizeof (char *) * (ctx.ret.lines.lines_len + 1));
-  ctx.ret.lines.lines_val[ctx.ret.lines.lines_len] = NULL;
-  return ctx.ret.lines.lines_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct tail_n_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_tail_n_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_tail");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void tail_n_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct tail_n_ctx *ctx = (struct tail_n_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_tail");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_tail_n");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_tail_n");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_tail");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_tail_n");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_tail");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_tail_n_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_tail_n");
-    return;
+  else if (!xdr_guestfs_tail_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_tail");
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_TAIL, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.lines.lines_val =
+    safe_realloc (g, ret.lines.lines_val,
+                  sizeof (char *) * (ret.lines.lines_len + 1));
+  ret.lines.lines_val[ret.lines.lines_len] = NULL;
+  return ret.lines.lines_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char **guestfs_tail_n (guestfs_h *g,
@@ -11331,15 +10171,14 @@ char **guestfs_tail_n (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_tail_n_args args;
-  struct tail_n_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_tail_n_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_tail_n") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.nrlines = nrlines;
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_TAIL_N,
@@ -11349,271 +10188,244 @@ char **guestfs_tail_n (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, tail_n_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_tail_n");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_TAIL_N, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.lines.lines_val =
-    safe_realloc (g, ctx.ret.lines.lines_val,
-                  sizeof (char *) * (ctx.ret.lines.lines_len + 1));
-  ctx.ret.lines.lines_val[ctx.ret.lines.lines_len] = NULL;
-  return ctx.ret.lines.lines_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct df_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_df_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_tail_n");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void df_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct df_ctx *ctx = (struct df_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_tail_n");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_df");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_df");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_tail_n");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_df");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_tail_n");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_df_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_df");
-    return;
+  else if (!xdr_guestfs_tail_n_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_tail_n");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_TAIL_N, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.lines.lines_val =
+    safe_realloc (g, ret.lines.lines_val,
+                  sizeof (char *) * (ret.lines.lines_len + 1));
+  ret.lines.lines_val[ret.lines.lines_len] = NULL;
+  return ret.lines.lines_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char *guestfs_df (guestfs_h *g)
 {
-  struct df_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_df_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_df") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_DF, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, df_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_df");
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_DF, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  guestfs_end_busy (g);
-  return ctx.ret.output; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct df_h_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_df_h_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_df");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void df_h_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct df_h_ctx *ctx = (struct df_h_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_df");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_df_h");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_df_h");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_df");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_df_h");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_df");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_df_h_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_df_h");
-    return;
+  else if (!xdr_guestfs_df_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_df");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_DF, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.output; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 char *guestfs_df_h (guestfs_h *g)
 {
-  struct df_h_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_df_h_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_df_h") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   serial = guestfs__send_sync (g, GUESTFS_PROC_DF_H, NULL, NULL);
   if (serial == -1) {
     guestfs_end_busy (g);
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, df_h_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_df_h");
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_DF_H, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.output; /* caller will free */
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct du_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_du_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_df_h");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void du_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct du_ctx *ctx = (struct du_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_df_h");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_du");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_du");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_df_h");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_du");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_df_h");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_du_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_du");
-    return;
+  else if (!xdr_guestfs_df_h_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_df_h");
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_DF_H, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.output; /* caller will free */
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int64_t guestfs_du (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_du_args args;
-  struct du_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_du_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_du") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_DU,
         (xdrproc_t) xdr_guestfs_du_args, (char *) &args);
@@ -11622,90 +10434,81 @@ int64_t guestfs_du (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, du_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_du");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_DU, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return ctx.ret.sizekb;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct initrd_list_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_initrd_list_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_du");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void initrd_list_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct initrd_list_ctx *ctx = (struct initrd_list_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_du");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_initrd_list");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_initrd_list");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_du");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_initrd_list");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_du");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_initrd_list_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_initrd_list");
-    return;
+  else if (!xdr_guestfs_du_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_du");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_DU, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.sizekb;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 char **guestfs_initrd_list (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_initrd_list_args args;
-  struct initrd_list_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_initrd_list_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_initrd_list") == -1) return NULL;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_INITRD_LIST,
         (xdrproc_t) xdr_guestfs_initrd_list_args, (char *) &args);
@@ -11714,75 +10517,72 @@ char **guestfs_initrd_list (guestfs_h *g,
     return NULL;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, initrd_list_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_initrd_list");
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_INITRD_LIST, serial) == -1) {
-    guestfs_end_busy (g);
-    return NULL;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return NULL;
-  }
-
-  guestfs_end_busy (g);
-  /* caller will free this, but we need to add a NULL entry */
-  ctx.ret.filenames.filenames_val =
-    safe_realloc (g, ctx.ret.filenames.filenames_val,
-                  sizeof (char *) * (ctx.ret.filenames.filenames_len + 1));
-  ctx.ret.filenames.filenames_val[ctx.ret.filenames.filenames_len] = NULL;
-  return ctx.ret.filenames.filenames_val;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mount_loop_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_initrd_list");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-static void mount_loop_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mount_loop_ctx *ctx = (struct mount_loop_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_initrd_list");
+      guestfs_end_busy (g);
+      return NULL;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mount_loop");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mount_loop");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_initrd_list");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mount_loop");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_initrd_list");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  else if (!xdr_guestfs_initrd_list_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_initrd_list");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_INITRD_LIST, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  /* caller will free this, but we need to add a NULL entry */
+  ret.filenames.filenames_val =
+    safe_realloc (g, ret.filenames.filenames_val,
+                  sizeof (char *) * (ret.filenames.filenames_len + 1));
+  ret.filenames.filenames_val[ret.filenames.filenames_len] = NULL;
+  return ret.filenames.filenames_val;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return NULL;
 }
 
 int guestfs_mount_loop (guestfs_h *g,
@@ -11790,15 +10590,13 @@ int guestfs_mount_loop (guestfs_h *g,
 		const char *mountpoint)
 {
   struct guestfs_mount_loop_args args;
-  struct mount_loop_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mount_loop") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.file = (char *) file;
   args.mountpoint = (char *) mountpoint;
   serial = guestfs__send_sync (g, GUESTFS_PROC_MOUNT_LOOP,
@@ -11808,85 +10606,76 @@ int guestfs_mount_loop (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mount_loop_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mount_loop");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MOUNT_LOOP, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mkswap_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mount_loop");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mkswap_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mkswap_ctx *ctx = (struct mkswap_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mount_loop");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mkswap");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mkswap");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mount_loop");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mkswap");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mount_loop");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MOUNT_LOOP, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mkswap (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_mkswap_args args;
-  struct mkswap_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mkswap") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_MKSWAP,
         (xdrproc_t) xdr_guestfs_mkswap_args, (char *) &args);
@@ -11895,70 +10684,63 @@ int guestfs_mkswap (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mkswap_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mkswap");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MKSWAP, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mkswap_L_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mkswap");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mkswap_L_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mkswap_L_ctx *ctx = (struct mkswap_L_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mkswap");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mkswap_L");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mkswap_L");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mkswap");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mkswap_L");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mkswap");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MKSWAP, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mkswap_L (guestfs_h *g,
@@ -11966,15 +10748,13 @@ int guestfs_mkswap_L (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_mkswap_L_args args;
-  struct mkswap_L_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mkswap_L") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.label = (char *) label;
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_MKSWAP_L,
@@ -11984,70 +10764,63 @@ int guestfs_mkswap_L (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mkswap_L_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mkswap_L");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MKSWAP_L, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mkswap_U_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mkswap_L");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mkswap_U_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mkswap_U_ctx *ctx = (struct mkswap_U_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mkswap_L");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mkswap_U");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mkswap_U");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mkswap_L");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mkswap_U");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mkswap_L");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MKSWAP_L, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mkswap_U (guestfs_h *g,
@@ -12055,15 +10828,13 @@ int guestfs_mkswap_U (guestfs_h *g,
 		const char *device)
 {
   struct guestfs_mkswap_U_args args;
-  struct mkswap_U_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mkswap_U") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.uuid = (char *) uuid;
   args.device = (char *) device;
   serial = guestfs__send_sync (g, GUESTFS_PROC_MKSWAP_U,
@@ -12073,70 +10844,63 @@ int guestfs_mkswap_U (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mkswap_U_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mkswap_U");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MKSWAP_U, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mknod_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mkswap_U");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mknod_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mknod_ctx *ctx = (struct mknod_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mkswap_U");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mknod");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mknod");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mkswap_U");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mknod");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mkswap_U");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MKSWAP_U, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mknod (guestfs_h *g,
@@ -12146,15 +10910,13 @@ int guestfs_mknod (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_mknod_args args;
-  struct mknod_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mknod") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.mode = mode;
   args.devmajor = devmajor;
   args.devminor = devminor;
@@ -12166,70 +10928,63 @@ int guestfs_mknod (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mknod_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mknod");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MKNOD, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mkfifo_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mknod");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mkfifo_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mkfifo_ctx *ctx = (struct mkfifo_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mknod");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mkfifo");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mkfifo");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mknod");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mkfifo");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mknod");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MKNOD, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mkfifo (guestfs_h *g,
@@ -12237,15 +10992,13 @@ int guestfs_mkfifo (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_mkfifo_args args;
-  struct mkfifo_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mkfifo") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.mode = mode;
   args.path = (char *) path;
   serial = guestfs__send_sync (g, GUESTFS_PROC_MKFIFO,
@@ -12255,70 +11008,63 @@ int guestfs_mkfifo (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mkfifo_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mkfifo");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MKFIFO, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mknod_b_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mkfifo");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mknod_b_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mknod_b_ctx *ctx = (struct mknod_b_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mkfifo");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mknod_b");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mknod_b");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mkfifo");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mknod_b");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mkfifo");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MKFIFO, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mknod_b (guestfs_h *g,
@@ -12328,15 +11074,13 @@ int guestfs_mknod_b (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_mknod_b_args args;
-  struct mknod_b_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mknod_b") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.mode = mode;
   args.devmajor = devmajor;
   args.devminor = devminor;
@@ -12348,70 +11092,63 @@ int guestfs_mknod_b (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mknod_b_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mknod_b");
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MKNOD_B, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct mknod_c_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mknod_b");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void mknod_c_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct mknod_c_ctx *ctx = (struct mknod_c_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mknod_b");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_mknod_c");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_mknod_c");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mknod_b");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_mknod_c");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mknod_b");
+      goto recv_error;
     }
-    goto done;
   }
- done:
-  ctx->cb_sequence = 1;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MKNOD_B, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_mknod_c (guestfs_h *g,
@@ -12421,15 +11158,13 @@ int guestfs_mknod_c (guestfs_h *g,
 		const char *path)
 {
   struct guestfs_mknod_c_args args;
-  struct mknod_c_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
   int serial;
 
   if (check_state (g, "guestfs_mknod_c") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.mode = mode;
   args.devmajor = devmajor;
   args.devminor = devminor;
@@ -12441,90 +11176,77 @@ int guestfs_mknod_c (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, mknod_c_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_mknod_c");
-    guestfs_end_busy (g);
-    return -1;
-  }
+  guestfs_reply_t reply;
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_MKNOD_C, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
-  }
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
-  }
-
-  guestfs_end_busy (g);
-  return 0;
-}
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
 
-struct umask_ctx {
-  /* This flag is set by the callbacks, so we know we've done
-   * the callbacks as expected, and in the right sequence.
-   * 0 = not called, 1 = reply_cb called.
-   */
-  int cb_sequence;
-  struct guestfs_message_header hdr;
-  struct guestfs_message_error err;
-  struct guestfs_umask_ret ret;
-};
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_mknod_c");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-static void umask_reply_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct umask_ctx *ctx = (struct umask_ctx *) data;
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_mknod_c");
+      guestfs_end_busy (g);
+      return -1;
+    }
 
-  /* This should definitely not happen. */
-  if (ctx->cb_sequence != 0) {
-    ctx->cb_sequence = 9999;
-    error (g, "%s: internal error: reply callback called twice", "guestfs_umask");
-    return;
+    break;
   }
 
-  ml->main_loop_quit (ml, g);
-
-  if (!xdr_guestfs_message_header (xdr, &ctx->hdr)) {
-    error (g, "%s: failed to parse reply header", "guestfs_umask");
-    return;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_mknod_c");
+    goto recv_error;
   }
-  if (ctx->hdr.status == GUESTFS_STATUS_ERROR) {
-    if (!xdr_guestfs_message_error (xdr, &ctx->err)) {
-      error (g, "%s: failed to parse reply error", "guestfs_umask");
-      return;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_mknod_c");
+      goto recv_error;
     }
-    goto done;
   }
-  if (!xdr_guestfs_umask_ret (xdr, &ctx->ret)) {
-    error (g, "%s: failed to parse reply", "guestfs_umask");
-    return;
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_MKNOD_C, serial) == -1) {
+    goto recv_error;
   }
- done:
-  ctx->cb_sequence = 1;
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
+  }
+
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return 0;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
+  guestfs_end_busy (g);
+  return -1;
 }
 
 int guestfs_umask (guestfs_h *g,
 		int mask)
 {
   struct guestfs_umask_args args;
-  struct umask_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+  struct guestfs_message_header hdr = {};
+  struct guestfs_message_error err = {};
+  struct guestfs_umask_ret ret = {};
   int serial;
 
   if (check_state (g, "guestfs_umask") == -1) return -1;
   guestfs_set_busy (g);
 
-  memset (&ctx, 0, sizeof ctx);
-
   args.mask = mask;
   serial = guestfs__send_sync (g, GUESTFS_PROC_UMASK,
         (xdrproc_t) xdr_guestfs_umask_args, (char *) &args);
@@ -12533,30 +11255,66 @@ int guestfs_umask (guestfs_h *g,
     return -1;
   }
 
-  guestfs__switch_to_receiving (g);
-  ctx.cb_sequence = 0;
-  guestfs_set_reply_callback (g, umask_reply_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
-  if (ctx.cb_sequence != 1) {
-    error (g, "%s reply failed, see earlier error messages", "guestfs_umask");
-    guestfs_end_busy (g);
-    return -1;
+  guestfs_reply_t reply;
+
+  for (;;) {
+    guestfs_get_reply (g, &reply, 1);
+
+    if (GUESTFS_CANCEL_FLAG == reply.len) {
+      /* This message was delayed from a previous file transaction. */
+      continue;
+    }
+
+    if (GUESTFS_LAUNCH_FLAG == reply.len) {
+      error (g, "%s reply failed, received unexpected launch message",
+             "guestfs_umask");
+      guestfs_end_busy (g);
+      return -1;
+    }
+
+    if (0 == reply.len) {
+      error (g, "%s reply failed, see earlier error messages", "guestfs_umask");
+      guestfs_end_busy (g);
+      return -1;
+    }
+
+    break;
   }
 
-  if (check_reply_header (g, &ctx.hdr, GUESTFS_PROC_UMASK, serial) == -1) {
-    guestfs_end_busy (g);
-    return -1;
+  if (!xdr_guestfs_message_header (&reply.xdr, &hdr)) {
+    error (g, "%s: failed to parse reply header", "guestfs_umask");
+    goto recv_error;
   }
 
-  if (ctx.hdr.status == GUESTFS_STATUS_ERROR) {
-    error (g, "%s", ctx.err.error_message);
-    free (ctx.err.error_message);
-    guestfs_end_busy (g);
-    return -1;
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    if (!xdr_guestfs_message_error (&reply.xdr, &err)) {
+      error (g, "%s: failed to parse reply error", "guestfs_umask");
+      goto recv_error;
+    }
+  }
+  else if (!xdr_guestfs_umask_ret (&reply.xdr, &ret)) {
+    error (g, "%s: failed to parse reply", "guestfs_umask");
+    goto recv_error;
+  }
+  if (check_reply_header (g, &hdr, GUESTFS_PROC_UMASK, serial) == -1) {
+    goto recv_error;
+  }
+
+  if (hdr.status == GUESTFS_STATUS_ERROR) {
+    error (g, "%s", err.error_message);
+    free (err.error_message);
+    goto recv_error;
   }
 
+  guestfs_free_reply (g, &reply);
+
+  guestfs_end_busy (g);
+
+  return ret.oldmask;
+
+ recv_error:
+  guestfs_free_reply (g, &reply);
   guestfs_end_busy (g);
-  return ctx.ret.oldmask;
+  return -1;
 }
 
diff --git a/src/guestfs.c b/src/guestfs.c
index 2d4db66..6f4c83e 100644
--- a/src/guestfs.c
+++ b/src/guestfs.c
@@ -21,6 +21,7 @@
 #define _BSD_SOURCE /* for mkdtemp, usleep */
 #define _GNU_SOURCE /* for vasprintf, GNU strerror_r, strchrnul */
 
+#include <assert.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdarg.h>
@@ -77,8 +78,10 @@
 
 static void default_error_cb (guestfs_h *g, void *data, const char *msg);
 static void stdout_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, int watch, int fd, int events);
-static void sock_read_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, int watch, int fd, int events);
-static void sock_write_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, int watch, int fd, int events);
+static void sock_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data, int watch, int fd, int events);
+static void sock_read (guestfs_h *g);
+static void sock_write (guestfs_h *g);
+static int sock_update_events (guestfs_h *g);
 
 static void close_handles (void);
 
@@ -160,6 +163,9 @@ struct guestfs_h
   int stdout_watch;		/* Watches qemu stdout for log messages. */
   int sock_watch;		/* Watches daemon comm socket. */
 
+  int sock_events;              /* events we're listening for on the comm
+                                   socket */
+
   char *tmpdir;			/* Temporary directory containing socket. */
 
   char *qemu_help, *qemu_version; /* Output of qemu -help, qemu -version. */
@@ -184,21 +190,18 @@ struct guestfs_h
   void *                     error_cb_data;
   guestfs_send_cb            send_cb;
   void *                     send_cb_data;
-  guestfs_reply_cb           reply_cb;
-  void *                     reply_cb_data;
   guestfs_log_message_cb     log_message_cb;
   void *                     log_message_cb_data;
   guestfs_subprocess_quit_cb subprocess_quit_cb;
   void *                     subprocess_quit_cb_data;
-  guestfs_launch_done_cb     launch_done_cb;
-  void *                     launch_done_cb_data;
 
   /* Main loop used by this handle. */
   guestfs_main_loop *main_loop;
 
   /* Messages sent and received from the daemon. */
   char *msg_in;
-  int msg_in_size, msg_in_allocated;
+  size_t msg_in_size, msg_in_pos, msg_in_consumed, msg_in_len;
+
   char *msg_out;
   int msg_out_size, msg_out_pos;
 
@@ -227,6 +230,8 @@ guestfs_create (void)
   g->stdout_watch = -1;
   g->sock_watch = -1;
 
+  g->sock_events = 0;
+
   g->abort_cb = abort;
   g->error_cb = default_error_cb;
   g->error_cb_data = NULL;
@@ -264,6 +269,11 @@ guestfs_create (void)
   } else
     g->memsize = 500;
 
+  /* Initialise the message receive buffer */
+  g->msg_in_size = GUESTFS_MESSAGE_MAX + sizeof (g->msg_in_len);
+  g->msg_in = safe_malloc (g, g->msg_in_size);
+  g->msg_in_pos = g->msg_in_consumed = 0;
+
   g->main_loop = guestfs_get_default_main_loop ();
 
   /* Start with large serial numbers so they are easy to spot
@@ -289,9 +299,10 @@ guestfs_create (void)
   return g;
 
  error:
-  free (g->path);
-  free (g->qemu);
-  free (g->append);
+  if (g->msg_in) free (g->msg_in);
+  if (g->path)   free (g->path);
+  if (g->qemu)   free (g->qemu);
+  if (g->append) free (g->append);
   free (g);
   return NULL;
 }
@@ -1122,10 +1133,6 @@ guestfs_launch (guestfs_h *g)
 
  connected:
   /* Watch the file descriptors. */
-  free (g->msg_in);
-  g->msg_in = NULL;
-  g->msg_in_size = g->msg_in_allocated = 0;
-
   free (g->msg_out);
   g->msg_out = NULL;
   g->msg_out_size = 0;
@@ -1140,7 +1147,9 @@ guestfs_launch (guestfs_h *g)
     goto cleanup3;
   }
 
-  if (guestfs__switch_to_receiving (g) == -1)
+  g->sock_events = GUESTFS_HANDLE_READABLE |
+                   GUESTFS_HANDLE_ERROR | GUESTFS_HANDLE_HANGUP;
+  if (sock_update_events (g) == -1)
     goto cleanup3;
 
   g->state = LAUNCHING;
@@ -1298,20 +1307,10 @@ qemu_supports (guestfs_h *g, const char *option)
   return g->qemu_help && strstr (g->qemu_help, option) != NULL;
 }
 
-static void
-finish_wait_ready (guestfs_h *g, void *vp)
-{
-  if (g->verbose)
-    fprintf (stderr, "finish_wait_ready called, %p, vp = %p\n", g, vp);
-
-  *((int *)vp) = 1;
-  g->main_loop->main_loop_quit (g->main_loop, g);
-}
-
 int
 guestfs_wait_ready (guestfs_h *g)
 {
-  int finished = 0, r;
+  guestfs_reply_t reply = {};
 
   if (g->state == READY) return 0;
 
@@ -1325,29 +1324,29 @@ guestfs_wait_ready (guestfs_h *g)
     return -1;
   }
 
-  g->launch_done_cb = finish_wait_ready;
-  g->launch_done_cb_data = &finished;
-  r = g->main_loop->main_loop_run (g->main_loop, g);
-  g->launch_done_cb = NULL;
-  g->launch_done_cb_data = NULL;
-
-  if (r == -1) return -1;
-
-  if (finished != 1) {
-    error (g, _("guestfs_wait_ready failed, see earlier error messages"));
+  guestfs_get_reply (g, &reply, 1);
+  if (0 == reply.len) {
+    error (g, _("guestfs_wait_ready: error receiving reply"));
+    return -1;
+  }
+  if (GUESTFS_LAUNCH_FLAG != reply.len) {
+    error (g, _("guestfs_wait_ready: received non-launch reply"));
     return -1;
   }
+  guestfs_free_reply (g, &reply);
 
   /* This is possible in some really strange situations, such as
    * guestfsd starts up OK but then qemu immediately exits.  Check for
    * it because the caller is probably expecting to be able to send
    * commands after this function returns.
    */
-  if (g->state != READY) {
-    error (g, _("qemu launched and contacted daemon, but state != READY"));
+  if (g->state != LAUNCHING) {
+    error (g, _("qemu launched and contacted daemon, but state != LAUNCHING"));
     return -1;
   }
 
+  g->state = READY;
+
   return 0;
 }
 
@@ -1473,7 +1472,7 @@ guestfs_free_lvm_lv_list (struct guestfs_lvm_lv_list *x)
   free (x);
 }
 
-/* We don't know if stdout_event or sock_read_event will be the
+/* We don't know if stdout_event or sock_read will be the
  * first to receive EOF if the qemu process dies.  This function
  * has the common cleanup code for both.
  */
@@ -1551,175 +1550,218 @@ stdout_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data,
     g->log_message_cb (g, g->log_message_cb_data, buf, n);
 }
 
-/* The function is called whenever we can read something on the
- * guestfsd (daemon inside the guest) communication socket.
- */
-static void
-sock_read_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data,
-		 int watch, int fd, int events)
+void
+guestfs_get_reply (guestfs_h *g, guestfs_reply_t *msg,
+                   unsigned char blocking)
 {
-  XDR xdr;
-  u_int32_t len;
-  int n;
+  msg->len = 0;
 
-  if (g->verbose)
-    fprintf (stderr,
-	     "sock_read_event: %p g->state = %d, fd = %d, events = 0x%x\n",
-	     g, g->state, fd, events);
-
-  if (g->sock != fd) {
-    error (g, _("sock_read_event: internal error: %d != %d"), g->sock, fd);
+  /* Not in the expected state. */
+  if (g->state != BUSY && g->state != LAUNCHING) {
+    error (g, _("state %d != BUSY && g->state != LAUNCHING"), g->state);
     return;
   }
 
-  if (g->msg_in_size <= g->msg_in_allocated) {
-    g->msg_in_allocated += 4096;
-    g->msg_in = safe_realloc (g, g->msg_in, g->msg_in_allocated);
+  /* Execute the main loop until we've received at least 1 complete message */
+  for (;;) {
+    struct guestfs_main_loop *ml = guestfs_get_main_loop(g);
+
+    size_t available = g->msg_in_pos - g->msg_in_consumed;
+
+    if (available >= sizeof (g->msg_in_len)) {
+      xdrmem_create (&msg->xdr, g->msg_in + g->msg_in_consumed,
+                     g->msg_in_pos - g->msg_in_consumed, XDR_DECODE);
+
+      /* Read the message length */
+      if (!xdr_uint32_t (&msg->xdr, &g->msg_in_len)) {
+        error (g, _("can't decode length"));
+        xdr_destroy(&msg->xdr);
+        return;
+      }
+
+      if (g->verbose) {
+        fprintf(stderr, "message length is %u\n", g->msg_in_len);
+      }
+
+      /* Special cases for messages which are represented by magic message
+       * lengths */
+      if (GUESTFS_LAUNCH_FLAG == g->msg_in_len ||
+          GUESTFS_CANCEL_FLAG == g->msg_in_len) {
+        msg->len = g->msg_in_len;
+        g->msg_in_len = sizeof(g->msg_in_len);
+        break;
+      }
+
+      else if (g->msg_in_len > GUESTFS_MESSAGE_MAX) {
+        error (g, _("message length (%u) > maximum possible size (%d)"),
+               g->msg_in_len, GUESTFS_MESSAGE_MAX);
+        /* We're doomed at this point. */
+        abort();
+      }
+
+      /* Quit the loop if we've got a complete message */
+      else if (available >= g->msg_in_len + sizeof (g->msg_in_len)) {
+        /* Total message length includes the length header itself */
+        g->msg_in_len += sizeof (g->msg_in_len);
+        msg->len = g->msg_in_len;
+        break;
+      }
+
+      xdr_destroy (&msg->xdr);
+    }
+
+    if(!blocking) return;
+
+    ml->main_loop_run (ml, g);
   }
-  n = read (g->sock, g->msg_in + g->msg_in_size,
-	    g->msg_in_allocated - g->msg_in_size);
-  if (n == 0) {
-    /* Disconnected. */
-    child_cleanup (g);
-    return;
+}
+
+void
+guestfs_free_reply (guestfs_h *g, guestfs_reply_t *msg)
+{
+  /* Check that the whole message has been consumed */
+  if (g->msg_in_len != xdr_getpos(&msg->xdr)) {
+    error (g, _("guestfs_free_reply: consumed %u bytes of %u length message"),
+           xdr_getpos(&msg->xdr), g->msg_in_len);
   }
 
-  if (n == -1) {
-    if (errno != EINTR && errno != EAGAIN)
-      perrorf (g, "read");
-    return;
+  xdr_destroy (&msg->xdr);
+
+  g->msg_in_consumed += g->msg_in_len;
+
+  if (g->verbose) {
+    fprintf (stderr, "guestfs_free_reply: consumed %u bytes\n",
+             g->msg_in_consumed);
   }
 
-  g->msg_in_size += n;
+  /* If there's nothing left in the buffer, reset to the beginning */
+  if (g->msg_in_consumed == g->msg_in_pos) {
+    g->msg_in_consumed = 0;
+    g->msg_in_pos = 0;
+  }
 
-  /* Have we got enough of a message to be able to process it yet? */
- again:
-  if (g->msg_in_size < 4) return;
+  /* If we got to the end of the buffer, move to the beginning */
+  else if (g->msg_in_size == g->msg_in_pos) {
+    memmove (g->msg_in, g->msg_in + g->msg_in_consumed,
+             g->msg_in_size - g->msg_in_consumed);
+    g->msg_in_pos = g->msg_in_size - g->msg_in_consumed;
+    g->msg_in_consumed = 0;
+  }
+
+  g->msg_in_len = 0;
 
-  xdrmem_create (&xdr, g->msg_in, g->msg_in_size, XDR_DECODE);
-  if (!xdr_uint32_t (&xdr, &len)) {
-    error (g, _("can't decode length word"));
-    goto cleanup;
+  /* If we were ignoring incoming messages, switch them back on */
+  if (!(g->sock_events & GUESTFS_HANDLE_READABLE)) {
+    g->sock_events |= GUESTFS_HANDLE_READABLE;
+    sock_update_events (g);
   }
+}
 
-  /* Length is normally the length of the message, but when guestfsd
-   * starts up it sends a "magic" value (longer than any possible
-   * message).  Check for this.
-   */
-  if (len == GUESTFS_LAUNCH_FLAG) {
-    if (g->state != LAUNCHING)
-      error (g, _("received magic signature from guestfsd, but in state %d"),
-	     g->state);
-    else if (g->msg_in_size != 4)
-      error (g, _("received magic signature from guestfsd, but msg size is %d"),
-	     g->msg_in_size);
-    else {
-      g->state = READY;
-      if (g->launch_done_cb)
-	g->launch_done_cb (g, g->launch_done_cb_data);
-    }
+/* This function is called whenever an event happens on the communications
+ * socket */
+static void
+sock_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data,
+            int watch, int fd, int events)
+{
+  if (g->verbose)
+    fprintf (stderr,
+	     "sock_event: %p g->state = %d, fd = %d, events = 0x%x\n",
+	     g, g->state, fd, events);
 
-    goto cleanup;
+  if (fd != g->sock) {
+    error (g, _("sock_event: received event for non-sock fd %d"), fd);
+    return;
   }
 
-  /* This can happen if a cancellation happens right at the end
-   * of us sending a FileIn parameter to the daemon.  Discard.  The
-   * daemon should send us an error message next.
-   */
-  if (len == GUESTFS_CANCEL_FLAG) {
-    g->msg_in_size -= 4;
-    memmove (g->msg_in, g->msg_in+4, g->msg_in_size);
-    goto again;
+  if (events & GUESTFS_HANDLE_READABLE) {
+    sock_read (g);
   }
 
-  /* If this happens, it's pretty bad and we've probably lost
-   * synchronization.
-   */
-  if (len > GUESTFS_MESSAGE_MAX) {
-    error (g, _("message length (%u) > maximum possible size (%d)"),
-	   len, GUESTFS_MESSAGE_MAX);
-    goto cleanup;
+  if (events & GUESTFS_HANDLE_WRITABLE) {
+    sock_write (g);
   }
 
-  if (g->msg_in_size-4 < len) return; /* Need more of this message. */
+  if (events & (GUESTFS_HANDLE_ERROR | GUESTFS_HANDLE_HANGUP)) {
+    error (g, _("sock_event: received error on communications socket: %m"));
+    child_cleanup (g);
+  }
+}
 
-  /* Got the full message, begin processing it. */
-#if 0
-  if (g->verbose) {
-    int i, j;
-
-    for (i = 0; i < g->msg_in_size; i += 16) {
-      printf ("%04x: ", i);
-      for (j = i; j < MIN (i+16, g->msg_in_size); ++j)
-	printf ("%02x ", (unsigned char) g->msg_in[j]);
-      for (; j < i+16; ++j)
-	printf ("   ");
-      printf ("|");
-      for (j = i; j < MIN (i+16, g->msg_in_size); ++j)
-	if (isprint (g->msg_in[j]))
-	  printf ("%c", g->msg_in[j]);
-	else
-	  printf (".");
-      for (; j < i+16; ++j)
-	printf (" ");
-      printf ("|\n");
+/* This function reads as much data as is available from the guestfsd
+ * communication socket.
+ */
+static void
+sock_read (guestfs_h *g)
+{
+  guestfs_main_loop *ml = guestfs_get_main_loop (g);
+
+  size_t available = g->msg_in_size - g->msg_in_pos;
+
+  if (g->verbose)
+    fprintf (stderr, "sock_read: before g->msg_in_pos = %d "
+                     "available = %d\n", g->msg_in_pos, available);
+
+  while (available > 0) {
+    ssize_t in;
+    while((in = read(g->sock, g->msg_in + g->msg_in_pos, available)) < 0) {
+      /* Retry if interrupted */
+      if(EINTR == errno) continue;
+
+      if(EWOULDBLOCK != errno) {
+        error (g, _("sock_read_data: error reading from socket: %s"),
+             strerror(errno));
+      }
+
+      break;
+    }
+
+    /* Cleanup if we got an EOF */
+    if (0 == in) {
+      child_cleanup(g);
+      return;
+    }
+
+    /* We got data */
+    else if (in > 0) {
+      available -= in;
+      g->msg_in_pos += in;
+    }
+
+    /* There was some error */
+    else {
+      break;
     }
   }
-#endif
 
-  /* Not in the expected state. */
-  if (g->state != BUSY)
-    error (g, _("state %d != BUSY"), g->state);
-
-  /* Push the message up to the higher layer. */
-  if (g->reply_cb)
-    g->reply_cb (g, g->reply_cb_data, &xdr);
-  else
-    /* This message (probably) should never be printed. */
-    fprintf (stderr, "libguesfs: sock_read_event: !!! dropped message !!!\n");
-
-  g->msg_in_size -= len + 4;
-  memmove (g->msg_in, g->msg_in+len+4, g->msg_in_size);
-  if (g->msg_in_size > 0) goto again;
-
- cleanup:
-  /* Free the message buffer if it's grown excessively large. */
-  if (g->msg_in_allocated > 65536) {
-    free (g->msg_in);
-    g->msg_in = NULL;
-    g->msg_in_size = g->msg_in_allocated = 0;
-  } else
-    g->msg_in_size = 0;
+  if (g->verbose)
+    fprintf (stderr, "sock_read: after g->msg_in_pos = %d "
+                     "available = %d\n", g->msg_in_pos, available);
 
-  xdr_destroy (&xdr);
+  /* Ignore further read events if we've filled the buffer */
+  if (0 == available) {
+    g->sock_events &= ~GUESTFS_HANDLE_READABLE;
+    sock_update_events (g);
+  }
+
+  /* Exit the main loop to give the caller a chance to pick up the message */
+  ml->main_loop_quit (ml, g);
 }
 
 /* The function is called whenever we can write something on the
  * guestfsd (daemon inside the guest) communication socket.
  */
 static void
-sock_write_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data,
-		  int watch, int fd, int events)
+sock_write (guestfs_h *g)
 {
   int n, err;
 
-  if (g->verbose)
-    fprintf (stderr,
-	     "sock_write_event: %p g->state = %d, fd = %d, events = 0x%x\n",
-	     g, g->state, fd, events);
-
-  if (g->sock != fd) {
-    error (g, _("sock_write_event: internal error: %d != %d"), g->sock, fd);
-    return;
-  }
-
   if (g->state != BUSY) {
-    error (g, _("sock_write_event: state %d != BUSY"), g->state);
+    error (g, _("sock_write: state %d != BUSY"), g->state);
     return;
   }
 
   if (g->verbose)
-    fprintf (stderr, "sock_write_event: writing %d bytes ...\n",
+    fprintf (stderr, "sock_write: writing %d bytes ...\n",
 	     g->msg_out_size - g->msg_out_pos);
 
   n = write (g->sock, g->msg_out + g->msg_out_pos,
@@ -1734,7 +1776,7 @@ sock_write_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data,
   }
 
   if (g->verbose)
-    fprintf (stderr, "sock_write_event: wrote %d bytes\n", n);
+    fprintf (stderr, "sock_write: wrote %d bytes\n", n);
 
   g->msg_out_pos += n;
 
@@ -1743,7 +1785,7 @@ sock_write_event (struct guestfs_main_loop *ml, guestfs_h *g, void *data,
     return;
 
   if (g->verbose)
-    fprintf (stderr, "sock_write_event: done writing, calling send_cb\n");
+    fprintf (stderr, "sock_write: done writing, calling send_cb\n");
 
   free (g->msg_out);
   g->msg_out = NULL;
@@ -1763,14 +1805,6 @@ guestfs_set_send_callback (guestfs_h *g,
 }
 
 void
-guestfs_set_reply_callback (guestfs_h *g,
-			    guestfs_reply_cb cb, void *opaque)
-{
-  g->reply_cb = cb;
-  g->reply_cb_data = opaque;
-}
-
-void
 guestfs_set_log_message_callback (guestfs_h *g,
 				  guestfs_log_message_cb cb, void *opaque)
 {
@@ -1786,14 +1820,6 @@ guestfs_set_subprocess_quit_callback (guestfs_h *g,
   g->subprocess_quit_cb_data = opaque;
 }
 
-void
-guestfs_set_launch_done_callback (guestfs_h *g,
-				  guestfs_launch_done_cb cb, void *opaque)
-{
-  g->launch_done_cb = cb;
-  g->launch_done_cb_data = opaque;
-}
-
 /* Access to the handle's main loop and the default main loop. */
 void
 guestfs_set_main_loop (guestfs_h *g, guestfs_main_loop *main_loop)
@@ -1813,35 +1839,15 @@ guestfs_get_default_main_loop (void)
   return (guestfs_main_loop *) &default_main_loop;
 }
 
-/* Change the daemon socket handler so that we are now writing.
- * This sets the handle to sock_write_event.
+/* Update the events which will cause the daemon socket callback to be called.
  */
-int
-guestfs__switch_to_sending (guestfs_h *g)
+static int
+sock_update_events (guestfs_h *g)
 {
-  if (g->sock_watch >= 0) {
-    if (g->main_loop->remove_handle (g->main_loop, g, g->sock_watch) == -1) {
-      error (g, _("remove_handle failed"));
-      g->sock_watch = -1;
-      return -1;
-    }
-  }
-
-  g->sock_watch =
-    g->main_loop->add_handle (g->main_loop, g, g->sock,
-			      GUESTFS_HANDLE_WRITABLE,
-			      sock_write_event, NULL);
-  if (g->sock_watch == -1) {
-    error (g, _("add_handle failed"));
-    return -1;
+  if(g->verbose) {
+    fprintf(stderr, "updating socket events to %d\n", g->sock_events);
   }
 
-  return 0;
-}
-
-int
-guestfs__switch_to_receiving (guestfs_h *g)
-{
   if (g->sock_watch >= 0) {
     if (g->main_loop->remove_handle (g->main_loop, g, g->sock_watch) == -1) {
       error (g, _("remove_handle failed"));
@@ -1852,8 +1858,8 @@ guestfs__switch_to_receiving (guestfs_h *g)
 
   g->sock_watch =
     g->main_loop->add_handle (g->main_loop, g, g->sock,
-			      GUESTFS_HANDLE_READABLE,
-			      sock_read_event, NULL);
+			      g->sock_events,
+			      sock_event, NULL);
   if (g->sock_watch == -1) {
     error (g, _("add_handle failed"));
     return -1;
@@ -1945,7 +1951,9 @@ guestfs__send_sync (guestfs_h *g, int proc_nr,
   xdrmem_create (&xdr, g->msg_out, 4, XDR_ENCODE);
   xdr_uint32_t (&xdr, &len);
 
-  if (guestfs__switch_to_sending (g) == -1)
+  /* Respond to writable events */
+  g->sock_events |= GUESTFS_HANDLE_WRITABLE;
+  if (sock_update_events (g) == -1)
     goto cleanup1;
 
   sent = 0;
@@ -1957,6 +1965,13 @@ guestfs__send_sync (guestfs_h *g, int proc_nr,
     goto cleanup1;
   }
 
+  /* No longer interested in writable events */
+  g->sock_events &= ~(GUESTFS_HANDLE_WRITABLE);
+  if (sock_update_events (g) == -1) {
+    error (g, _("guestfs__send_sync: "
+                "failed to remove socket writable callback\n"));
+  }
+
   return serial;
 
  cleanup1:
@@ -2056,8 +2071,6 @@ send_file_complete_sync (guestfs_h *g)
 /* Send a chunk, cancellation or end of file, synchronously (ie. wait
  * for it to go).
  */
-static int check_for_daemon_cancellation (guestfs_h *g);
-
 static int
 send_file_chunk_sync (guestfs_h *g, int cancel, const char *buf, size_t buflen)
 {
@@ -2067,6 +2080,8 @@ send_file_chunk_sync (guestfs_h *g, int cancel, const char *buf, size_t buflen)
   XDR xdr;
   guestfs_main_loop *ml = guestfs_get_main_loop (g);
 
+  guestfs_reply_t cancellation = {};
+
   if (g->state != BUSY) {
     error (g, _("send_file_chunk_sync: state %d != READY"), g->state);
     return -1;
@@ -2081,11 +2096,15 @@ send_file_chunk_sync (guestfs_h *g, int cancel, const char *buf, size_t buflen)
   }
 
   /* Did the daemon send a cancellation message? */
-  if (check_for_daemon_cancellation (g)) {
+  guestfs_get_reply (g, &cancellation, 0);
+  if (GUESTFS_CANCEL_FLAG == cancellation.len) {
     if (g->verbose)
       fprintf (stderr, "got daemon cancellation\n");
     return -2;
   }
+  if (0 != cancellation.len) {
+    guestfs_free_reply (g, &cancellation);
+  }
 
   /* Allocate the chunk buffer.  Don't use the stack to avoid
    * excessive stack usage and unnecessary copies.
@@ -2116,7 +2135,9 @@ send_file_chunk_sync (guestfs_h *g, int cancel, const char *buf, size_t buflen)
   xdrmem_create (&xdr, g->msg_out, 4, XDR_ENCODE);
   xdr_uint32_t (&xdr, &len);
 
-  if (guestfs__switch_to_sending (g) == -1)
+  /* Respond to socket writable events */
+  g->sock_events |= GUESTFS_HANDLE_WRITABLE;
+  if (sock_update_events (g) == -1)
     goto cleanup1;
 
   sent = 0;
@@ -2128,6 +2149,12 @@ send_file_chunk_sync (guestfs_h *g, int cancel, const char *buf, size_t buflen)
     goto cleanup1;
   }
 
+  g->sock_events &= ~(GUESTFS_HANDLE_WRITABLE);
+  if (sock_update_events (g) == -1) {
+    error (g, _("send_file_chunk_sync: "
+                "failed to remove socket writable callback\n"));
+  }
+
   return 0;
 
  cleanup1:
@@ -2137,52 +2164,6 @@ send_file_chunk_sync (guestfs_h *g, int cancel, const char *buf, size_t buflen)
   return -1;
 }
 
-/* At this point we are sending FileIn file(s) to the guest, and not
- * expecting to read anything, so if we do read anything, it must be
- * a cancellation message.  This checks for this case without blocking.
- */
-static int
-check_for_daemon_cancellation (guestfs_h *g)
-{
-  fd_set rset;
-  struct timeval tv;
-  int r;
-  char buf[4];
-  uint32_t flag;
-  XDR xdr;
-
-  FD_ZERO (&rset);
-  FD_SET (g->sock, &rset);
-  tv.tv_sec = 0;
-  tv.tv_usec = 0;
-  r = select (g->sock+1, &rset, NULL, NULL, &tv);
-  if (r == -1) {
-    perrorf (g, "select");
-    return 0;
-  }
-  if (r == 0)
-    return 0;
-
-  /* Read the message from the daemon. */
-  r = xread (g->sock, buf, sizeof buf);
-  if (r == -1) {
-    perrorf (g, "read");
-    return 0;
-  }
-
-  xdrmem_create (&xdr, buf, sizeof buf, XDR_DECODE);
-  xdr_uint32_t (&xdr, &flag);
-  xdr_destroy (&xdr);
-
-  if (flag != GUESTFS_CANCEL_FLAG) {
-    error (g, _("check_for_daemon_cancellation: read 0x%x from daemon, expected 0x%x\n"),
-	   flag, GUESTFS_CANCEL_FLAG);
-    return 0;
-  }
-
-  return 1;
-}
-
 /* Synchronously receive a file. */
 
 /* Returns -1 = error, 0 = EOF, 1 = more data */
@@ -2194,6 +2175,7 @@ guestfs__receive_file_sync (guestfs_h *g, const char *filename)
   void *buf;
   int fd, r;
   size_t len;
+  size_t total_bytes = 0, total_receives = 0;
 
   fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666);
   if (fd == -1) {
@@ -2203,6 +2185,11 @@ guestfs__receive_file_sync (guestfs_h *g, const char *filename)
 
   /* Receive the file in chunked encoding. */
   while ((r = receive_file_data_sync (g, &buf, &len)) >= 0) {
+    if (g->verbose) {
+      fprintf(stderr, "guestfs__receive_file_sync: "
+                      "writing %zi bytes to %s\n", len, filename);
+    }
+
     if (xwrite (fd, buf, len) == -1) {
       perrorf (g, "%s: write", filename);
       free (buf);
@@ -2210,6 +2197,9 @@ guestfs__receive_file_sync (guestfs_h *g, const char *filename)
     }
     free (buf);
     if (r == 0) break; /* End of file. */
+
+    total_bytes += len;
+    total_receives++;
   }
 
   if (r == -1) {
@@ -2222,6 +2212,12 @@ guestfs__receive_file_sync (guestfs_h *g, const char *filename)
     return -1;
   }
 
+  if (g->verbose) {
+    fprintf(stderr, "guestfs__receive_file_sync: "
+                    "wrote %zi bytes in %zi calls to %s\n",
+                    total_bytes, total_receives, filename);
+  }
+
   return 0;
 
  cancel: ;
@@ -2247,117 +2243,75 @@ guestfs__receive_file_sync (guestfs_h *g, const char *filename)
   return -1;
 }
 
-/* Note that the reply callback can be called multiple times before
- * the main loop quits and we get back to the synchronous code.  So
- * we have to be prepared to save multiple chunks on a list here.
- */
-struct receive_file_ctx {
-  int count;			/* 0 if receive_file_cb not called, or
-				 * else count number of chunks.
-				 */
-  guestfs_chunk *chunks;	/* Array of chunks. */
-};
-
-static void
-free_chunks (struct receive_file_ctx *ctx)
-{
-  int i;
-
-  for (i = 0; i < ctx->count; ++i)
-    free (ctx->chunks[i].data.data_val);
-
-  free (ctx->chunks);
-}
-
-static void
-receive_file_cb (guestfs_h *g, void *data, XDR *xdr)
-{
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  struct receive_file_ctx *ctx = (struct receive_file_ctx *) data;
-  guestfs_chunk chunk;
-
-  if (ctx->count == -1)		/* Parse error occurred previously. */
-    return;
-
-  ml->main_loop_quit (ml, g);
-
-  memset (&chunk, 0, sizeof chunk);
-
-  if (!xdr_guestfs_chunk (xdr, &chunk)) {
-    error (g, _("failed to parse file chunk"));
-    free_chunks (ctx);
-    ctx->chunks = NULL;
-    ctx->count = -1;
-    return;
-  }
-
-  /* Copy the chunk to the list. */
-  ctx->chunks = safe_realloc (g, ctx->chunks,
-			      sizeof (guestfs_chunk) * (ctx->count+1));
-  ctx->chunks[ctx->count] = chunk;
-  ctx->count++;
-}
-
 /* Receive a chunk of file data. */
 /* Returns -1 = error, 0 = EOF, 1 = more data */
 static int
-receive_file_data_sync (guestfs_h *g, void **buf, size_t *len_r)
+receive_file_data_sync (guestfs_h *g, void **buf, size_t *len)
 {
-  struct receive_file_ctx ctx;
-  guestfs_main_loop *ml = guestfs_get_main_loop (g);
-  int i;
-  size_t len;
+  unsigned char block = 1;
 
-  ctx.count = 0;
-  ctx.chunks = NULL;
+  /* Accumulate data in this buffer. */
+  if (buf) *buf = NULL;
+  if (len) *len = 0;
 
-  guestfs_set_reply_callback (g, receive_file_cb, &ctx);
-  (void) ml->main_loop_run (ml, g);
-  guestfs_set_reply_callback (g, NULL, NULL);
+  /* Buffer chunks until one of:
+   * There are no more immediately available
+   * The buffer got too big
+   * We got the last chunk
+   */
+  for(;;) {
+    guestfs_reply_t reply;
+    guestfs_chunk chunk;
+
+    /* Get a reply message if there's one available */
+    guestfs_get_reply (g, &reply, block);
+
+    /* No more replies available right now */
+    if (0 == reply.len) {
+      /* We should have waited until we got at least 1 */
+      if (NULL == *buf) {
+        error (g, _("receive_file_data: error receiving chunk"));
+        guestfs_free_reply (g, &reply);
+        return -1;
+      }
 
-  if (ctx.count == 0) {
-    error (g, _("receive_file_data_sync: reply callback not called\n"));
-    return -1;
-  }
+      break;
+    }
 
-  if (ctx.count == -1) {
-    error (g, _("receive_file_data_sync: parse error in reply callback\n"));
-    /* callback already freed the chunks */
-    return -1;
-  }
+    if (g->verbose)
+      fprintf (stderr, "receive_file_data_sync: got chunk\n");
+    
+    memset (&chunk, 0, sizeof (chunk));
 
-  if (g->verbose)
-    fprintf (stderr, "receive_file_data_sync: got %d chunks\n", ctx.count);
-
-  /* Process each chunk in the list. */
-  if (buf) *buf = NULL;		/* Accumulate data in this buffer. */
-  len = 0;
-
-  for (i = 0; i < ctx.count; ++i) {
-    if (ctx.chunks[i].cancel) {
-      error (g, _("file receive cancelled by daemon"));
-      free_chunks (&ctx);
-      if (buf) free (*buf);
-      if (len_r) *len_r = 0;
+    if (!xdr_guestfs_chunk (&reply.xdr, &chunk)) {
+      error (g, _("receive_file_data_sync: failed to parse file chunk"));
+      guestfs_free_reply (g, &reply);
       return -1;
     }
 
-    if (ctx.chunks[i].data.data_len == 0) { /* end of transfer */
-      free_chunks (&ctx);
-      if (len_r) *len_r = len;
+    /* Copy the chunk into the output buffer */
+    if (len && buf) {
+      *buf = safe_realloc (g, *buf, *len + chunk.data.data_len);
+      memcpy (*buf + *len, chunk.data.data_val, chunk.data.data_len);
+      *len += chunk.data.data_len;
+    }
+
+    guestfs_free_reply (g, &reply);
+
+    /* Received the last chunk */
+    if (0 == chunk.data.data_len) {
       return 0;
     }
 
-    if (buf) {
-      *buf = safe_realloc (g, *buf, len + ctx.chunks[i].data.data_len);
-      memcpy (*buf+len, ctx.chunks[i].data.data_val,
-	      ctx.chunks[i].data.data_len);
+    /* Return early if the the output buffer length is bigger than some
+     * arbitrary size. Note that the output buffer may exceed this size. */
+    if (len && *len > 1024 * 1024) {
+      break;
     }
-    len += ctx.chunks[i].data.data_len;
+
+    block = 0;
   }
 
-  if (len_r) *len_r = len;
-  free_chunks (&ctx);
   return 1;
 }
 
diff --git a/src/guestfs.h b/src/guestfs.h
index b5ed0f7..018183d 100644
--- a/src/guestfs.h
+++ b/src/guestfs.h
@@ -63,13 +63,10 @@ typedef void (*guestfs_send_cb) (guestfs_h *g, void *data);
 typedef void (*guestfs_reply_cb) (guestfs_h *g, void *data, XDR *xdr);
 typedef void (*guestfs_log_message_cb) (guestfs_h *g, void *data, char *buf, int len);
 typedef void (*guestfs_subprocess_quit_cb) (guestfs_h *g, void *data);
-typedef void (*guestfs_launch_done_cb) (guestfs_h *g, void *data);
 
 extern void guestfs_set_send_callback (guestfs_h *g, guestfs_send_cb cb, void *opaque);
-extern void guestfs_set_reply_callback (guestfs_h *g, guestfs_reply_cb cb, void *opaque);
 extern void guestfs_set_log_message_callback (guestfs_h *g, guestfs_log_message_cb cb, void *opaque);
 extern void guestfs_set_subprocess_quit_callback (guestfs_h *g, guestfs_subprocess_quit_cb cb, void *opaque);
-extern void guestfs_set_launch_done_callback (guestfs_h *g, guestfs_launch_done_cb cb, void *opaque);
 
 extern void guestfs_error (guestfs_h *g, const char *fs, ...)
   __attribute__((format (printf,2,3)));
@@ -80,8 +77,14 @@ extern void *guestfs_safe_realloc (guestfs_h *g, void *ptr, int nbytes);
 extern char *guestfs_safe_strdup (guestfs_h *g, const char *str);
 extern void *guestfs_safe_memdup (guestfs_h *g, void *ptr, size_t size);
 
-extern int guestfs__switch_to_sending (guestfs_h *g);
-extern int guestfs__switch_to_receiving (guestfs_h *g);
+typedef struct {
+    u_int32_t len;
+    XDR xdr;
+} guestfs_reply_t;
+
+extern void guestfs_get_reply (guestfs_h *g, guestfs_reply_t *msg,
+                               unsigned char blocking);
+extern void guestfs_free_reply (guestfs_h *g, guestfs_reply_t *msg);
 
 /* These *_sync calls wait until the action is performed, using the
  * main loop.  We should implement asynchronous versions too.
-- 
1.6.2.5


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