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

Richard W.M. Jones rjones at redhat.com
Thu Jul 2 10:07:47 UTC 2009


On Wed, Jul 01, 2009 at 06:19:13PM +0100, Matthew Booth wrote:
> 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 at 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 _ ->

You'll get some conflicts when you rebase, because I
added another case (RDirentList).

> -	   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);

The size of the length word is always 4 bytes - it's defined
that way in the XDR standard.

> +  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);

This is wrong.  free (NULL) is always correct, so we just need to
do:

free (g->msg_in);

etc. (without the 'if').

>    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;
> +  }

This convention for indicating errors is a little odd.  Why doesn't
guestfs_get_reply return 0 / -1 ?

> +  if (GUESTFS_LAUNCH_FLAG != reply.len) {
> +    error (g, _("guestfs_wait_ready: received non-launch reply"));
>      return -1;
>    }
> +  guestfs_free_reply (g, &reply);

Does this leak the reply message on the error path?

>    /* 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)) {

As above, I think you mean '>= 4' here.  And also some places below too.

> +      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;
>  }

The way git has formatted this patch makes it really hard to
follow what has changed ...

> 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.

Rich.

-- 
Richard Jones, Emerging Technologies, Red Hat  http://et.redhat.com/~rjones
virt-p2v converts physical machines to virtual machines.  Boot with a
live CD or over the network (PXE) and turn machines into Xen guests.
http://et.redhat.com/~rjones/virt-p2v




More information about the Fedora-virt mailing list