+ return 0;
+
+ cleanup1:
+ free (g->msg_out);
+ g->msg_out = NULL;
+ g->msg_out_size = 0;
+ 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 */
+static int receive_file_data_sync (guestfs_h *g, void **buf, size_t *len);
+
+int
+guestfs__receive_file_sync (guestfs_h *g, const char *filename)
+{
+ void *buf;
+ int fd, r;
+ size_t len;
+
+ fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0666);
+ if (fd == -1) {
+ perrorf (g, "open: %s", filename);
+ goto cancel;
+ }
+
+ /* Receive the file in chunked encoding. */
+ while ((r = receive_file_data_sync (g, &buf, &len)) >= 0) {
+ if (xwrite (fd, buf, len) == -1) {
+ perrorf (g, "%s: write", filename);
+ free (buf);
+ goto cancel;
+ }
+ free (buf);
+ if (r == 0) break; /* End of file. */
+ }
+
+ if (r == -1) {
+ error (g, _("%s: error in chunked encoding"), filename);
+ return -1;
+ }
+
+ if (close (fd) == -1) {
+ perrorf (g, "close: %s", filename);
+ return -1;
+ }
+
+ return 0;
+
+ cancel: ;
+ /* Send cancellation message to daemon, then wait until it
+ * cancels (just throwing away data).
+ */
+ XDR xdr;
+ char fbuf[4];
+ uint32_t flag = GUESTFS_CANCEL_FLAG;
+
+ xdrmem_create (&xdr, fbuf, sizeof fbuf, XDR_ENCODE);
+ xdr_uint32_t (&xdr, &flag);
+ xdr_destroy (&xdr);
+
+ if (xwrite (g->sock, fbuf, sizeof fbuf) == -1) {
+ perrorf (g, _("write to daemon socket"));
+ return -1;
+ }
+
+ while ((r = receive_file_data_sync (g, NULL, NULL)) > 0)
+ ; /* just discard it */
+
+ 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)
+{
+ struct receive_file_ctx ctx;
+ guestfs_main_loop *ml = guestfs_get_main_loop (g);
+ int i;
+ size_t len;
+
+ ctx.count = 0;
+ ctx.chunks = NULL;
+
+ guestfs_set_reply_callback (g, receive_file_cb, &ctx);
+ (void) ml->main_loop_run (ml, g);
+ guestfs_set_reply_callback (g, NULL, NULL);
+
+ if (ctx.count == 0) {
+ error (g, _("receive_file_data_sync: reply callback not called\n"));
+ return -1;
+ }
+
+ 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 %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;
+ return -1;
+ }
+
+ if (ctx.chunks[i].data.data_len == 0) { /* end of transfer */
+ free_chunks (&ctx);
+ if (len_r) *len_r = 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);
+ }
+ len += ctx.chunks[i].data.data_len;
+ }
+
+ if (len_r) *len_r = len;
+ free_chunks (&ctx);
+ return 1;
+}
+
+/* This is the default main loop implementation, using select(2). */
+
+static int
+select_add_handle (guestfs_main_loop *mlv, guestfs_h *g, int fd, int events,
+ guestfs_handle_event_cb cb, void *data)
+{
+ struct select_main_loop *ml = (struct select_main_loop *) mlv;
+
+ if (fd < 0 || fd >= FD_SETSIZE) {
+ error (g, _("fd %d is out of range"), fd);
+ return -1;
+ }
+
+ if ((events & ~(GUESTFS_HANDLE_READABLE |
+ GUESTFS_HANDLE_WRITABLE |
+ GUESTFS_HANDLE_HANGUP |
+ GUESTFS_HANDLE_ERROR)) != 0) {
+ error (g, _("set of events (0x%x) contains unknown events"), events);
+ return -1;
+ }
+
+ if (events == 0) {
+ error (g, _("set of events is empty"));
+ return -1;
+ }
+
+ if (FD_ISSET (fd, &ml->rset) ||
+ FD_ISSET (fd, &ml->wset) ||
+ FD_ISSET (fd, &ml->xset)) {
+ error (g, _("fd %d is already registered"), fd);
+ return -1;
+ }
+
+ if (cb == NULL) {
+ error (g, _("callback is NULL"));
+ return -1;
+ }
+
+ if ((events & GUESTFS_HANDLE_READABLE))
+ FD_SET (fd, &ml->rset);
+ if ((events & GUESTFS_HANDLE_WRITABLE))
+ FD_SET (fd, &ml->wset);
+ if ((events & GUESTFS_HANDLE_HANGUP) || (events & GUESTFS_HANDLE_ERROR))
+ FD_SET (fd, &ml->xset);
+
+ if (fd > ml->max_fd) {
+ ml->max_fd = fd;
+ ml->handle_cb_data =
+ safe_realloc (g, ml->handle_cb_data,
+ sizeof (struct select_handle_cb_data) * (ml->max_fd+1));
+ }
+ ml->handle_cb_data[fd].cb = cb;
+ ml->handle_cb_data[fd].g = g;
+ ml->handle_cb_data[fd].data = data;
+
+ ml->nr_fds++;
+
+ /* Any integer >= 0 can be the handle, and this is as good as any ... */
+ return fd;
+}
+
+static int
+select_remove_handle (guestfs_main_loop *mlv, guestfs_h *g, int fd)
+{
+ struct select_main_loop *ml = (struct select_main_loop *) mlv;
+
+ if (fd < 0 || fd >= FD_SETSIZE) {
+ error (g, _("fd %d is out of range"), fd);
+ return -1;
+ }
+
+ if (!FD_ISSET (fd, &ml->rset) &&
+ !FD_ISSET (fd, &ml->wset) &&
+ !FD_ISSET (fd, &ml->xset)) {
+ error (g, _("fd %d was not registered"), fd);
+ return -1;
+ }
+
+ FD_CLR (fd, &ml->rset);
+ FD_CLR (fd, &ml->wset);
+ FD_CLR (fd, &ml->xset);
+
+ if (fd == ml->max_fd) {
+ ml->max_fd--;
+ ml->handle_cb_data =
+ safe_realloc (g, ml->handle_cb_data,
+ sizeof (struct select_handle_cb_data) * (ml->max_fd+1));
+ }
+
+ ml->nr_fds--;
+
+ return 0;
+}
+
+static int
+select_add_timeout (guestfs_main_loop *mlv, guestfs_h *g, int interval,
+ guestfs_handle_timeout_cb cb, void *data)
+{
+ //struct select_main_loop *ml = (struct select_main_loop *) mlv;
+
+ abort (); /* XXX not implemented yet */
+}
+
+static int
+select_remove_timeout (guestfs_main_loop *mlv, guestfs_h *g, int timer)
+{
+ //struct select_main_loop *ml = (struct select_main_loop *) mlv;
+
+ abort (); /* XXX not implemented yet */
+}
+
+/* The 'g' parameter is just used for error reporting. Events
+ * for multiple handles can be dispatched by running the main
+ * loop.
+ */
+static int
+select_main_loop_run (guestfs_main_loop *mlv, guestfs_h *g)
+{
+ struct select_main_loop *ml = (struct select_main_loop *) mlv;
+ int fd, r, events;
+ fd_set rset2, wset2, xset2;
+
+ if (ml->is_running) {
+ error (g, _("select_main_loop_run: this cannot be called recursively"));
+ return -1;
+ }
+
+ ml->is_running = 1;
+
+ while (ml->is_running) {
+ if (ml->nr_fds == 0)
+ break;
+
+ rset2 = ml->rset;
+ wset2 = ml->wset;
+ xset2 = ml->xset;
+ r = select (ml->max_fd+1, &rset2, &wset2, &xset2, NULL);
+ if (r == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ perrorf (g, "select");
+ ml->is_running = 0;
+ return -1;
+ }
+
+ for (fd = 0; r > 0 && fd <= ml->max_fd; ++fd) {
+ events = 0;
+ if (FD_ISSET (fd, &rset2))
+ events |= GUESTFS_HANDLE_READABLE;
+ if (FD_ISSET (fd, &wset2))
+ events |= GUESTFS_HANDLE_WRITABLE;
+ if (FD_ISSET (fd, &xset2))
+ events |= GUESTFS_HANDLE_ERROR | GUESTFS_HANDLE_HANGUP;
+ if (events) {
+ r--;
+ ml->handle_cb_data[fd].cb ((guestfs_main_loop *) ml,
+ ml->handle_cb_data[fd].g,
+ ml->handle_cb_data[fd].data,
+ fd, fd, events);
+ }
+ }
+ }
+
+ ml->is_running = 0;
+ return 0;
+}
+
+static int
+select_main_loop_quit (guestfs_main_loop *mlv, guestfs_h *g)
+{
+ struct select_main_loop *ml = (struct select_main_loop *) mlv;
+
+ /* Note that legitimately ml->is_running can be zero when
+ * this function is called.
+ */