+#endif
+
+ return 0;
+}
+
+/* This is very much like recv_from_daemon above, but g->sock is
+ * a listening socket and we are accepting a new connection on
+ * that socket instead of reading anything. Returns the newly
+ * accepted socket.
+ */
+static int
+accept_from_daemon (guestfs_h *g)
+{
+ fd_set rset, rset2;
+
+ if (g->verbose)
+ fprintf (stderr,
+ "accept_from_daemon: %p g->state = %d\n", g, g->state);
+
+ FD_ZERO (&rset);
+
+ FD_SET (g->fd[1], &rset); /* Read qemu stdout for log messages & EOF. */
+ FD_SET (g->sock, &rset); /* Read socket for accept. */
+
+ int max_fd = MAX (g->sock, g->fd[1]);
+ int sock = -1;
+
+ while (sock == -1) {
+ rset2 = rset;
+ int r = select (max_fd+1, &rset2, NULL, NULL, NULL);
+ if (r == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ perrorf (g, "select");
+ return -1;
+ }
+
+ if (FD_ISSET (g->fd[1], &rset2)) {
+ if (read_log_message_or_eof (g, g->fd[1]) == -1)
+ return -1;
+ }
+ if (FD_ISSET (g->sock, &rset2)) {
+ sock = accept (g->sock, NULL, NULL);
+ if (sock == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ perrorf (g, "accept");
+ return -1;
+ }
+ }
+ }
+
+ return sock;
+}
+
+int
+guestfs___send (guestfs_h *g, int proc_nr, xdrproc_t xdrp, char *args)
+{
+ struct guestfs_message_header hdr;
+ XDR xdr;
+ u_int32_t len;
+ int serial = g->msg_next_serial++;
+ int r;
+ char *msg_out;
+ size_t msg_out_size;
+
+ if (g->state != BUSY) {
+ error (g, _("guestfs___send: state %d != BUSY"), g->state);
+ return -1;
+ }
+
+ /* We have to allocate this message buffer on the heap because
+ * it is quite large (although will be mostly unused). We
+ * can't allocate it on the stack because in some environments
+ * we have quite limited stack space available, notably when
+ * running in the JVM.
+ */
+ msg_out = safe_malloc (g, GUESTFS_MESSAGE_MAX + 4);
+ xdrmem_create (&xdr, msg_out + 4, GUESTFS_MESSAGE_MAX, XDR_ENCODE);
+
+ /* Serialize the header. */
+ hdr.prog = GUESTFS_PROGRAM;
+ hdr.vers = GUESTFS_PROTOCOL_VERSION;
+ hdr.proc = proc_nr;
+ hdr.direction = GUESTFS_DIRECTION_CALL;
+ hdr.serial = serial;
+ hdr.status = GUESTFS_STATUS_OK;
+
+ if (!xdr_guestfs_message_header (&xdr, &hdr)) {
+ error (g, _("xdr_guestfs_message_header failed"));
+ goto cleanup1;
+ }
+
+ /* Serialize the args. If any, because some message types
+ * have no parameters.
+ */
+ if (xdrp) {
+ if (!(*xdrp) (&xdr, args)) {
+ error (g, _("dispatch failed to marshal args"));
+ goto cleanup1;
+ }
+ }
+
+ /* Get the actual length of the message, resize the buffer to match
+ * the actual length, and write the length word at the beginning.
+ */
+ len = xdr_getpos (&xdr);
+ xdr_destroy (&xdr);
+
+ msg_out = safe_realloc (g, msg_out, len + 4);
+ msg_out_size = len + 4;
+
+ xdrmem_create (&xdr, msg_out, 4, XDR_ENCODE);
+ xdr_uint32_t (&xdr, &len);
+
+ again:
+ r = send_to_daemon (g, msg_out, msg_out_size);
+ if (r == -2) /* Ignore stray daemon cancellations. */
+ goto again;
+ if (r == -1)
+ goto cleanup1;
+ free (msg_out);
+
+ return serial;
+
+ cleanup1:
+ free (msg_out);
+ return -1;
+}
+
+static int cancel = 0; /* XXX Implement file cancellation. */
+static int send_file_chunk (guestfs_h *g, int cancel, const char *buf, size_t len);
+static int send_file_data (guestfs_h *g, const char *buf, size_t len);
+static int send_file_cancellation (guestfs_h *g);
+static int send_file_complete (guestfs_h *g);
+
+/* Send a file.
+ * Returns:
+ * 0 OK
+ * -1 error
+ * -2 daemon cancelled (we must read the error message)
+ */
+int
+guestfs___send_file (guestfs_h *g, const char *filename)
+{
+ char buf[GUESTFS_MAX_CHUNK_SIZE];
+ int fd, r, err;
+
+ fd = open (filename, O_RDONLY);
+ if (fd == -1) {
+ perrorf (g, "open: %s", filename);
+ send_file_cancellation (g);
+ /* Daemon sees cancellation and won't reply, so caller can
+ * just return here.
+ */
+ return -1;
+ }
+
+ /* Send file in chunked encoding. */
+ while (!cancel) {
+ r = read (fd, buf, sizeof buf);
+ if (r == -1 && (errno == EINTR || errno == EAGAIN))
+ continue;
+ if (r <= 0) break;
+ err = send_file_data (g, buf, r);
+ if (err < 0) {
+ if (err == -2) /* daemon sent cancellation */
+ send_file_cancellation (g);
+ return err;
+ }
+ }
+
+ if (cancel) { /* cancel from either end */
+ send_file_cancellation (g);
+ return -1;
+ }
+
+ if (r == -1) {
+ perrorf (g, "read: %s", filename);
+ send_file_cancellation (g);
+ return -1;
+ }
+
+ /* End of file, but before we send that, we need to close
+ * the file and check for errors.
+ */
+ if (close (fd) == -1) {
+ perrorf (g, "close: %s", filename);
+ send_file_cancellation (g);
+ return -1;
+ }
+
+ return send_file_complete (g);
+}
+
+/* Send a chunk of file data. */
+static int
+send_file_data (guestfs_h *g, const char *buf, size_t len)
+{
+ return send_file_chunk (g, 0, buf, len);
+}
+
+/* Send a cancellation message. */
+static int
+send_file_cancellation (guestfs_h *g)
+{
+ return send_file_chunk (g, 1, NULL, 0);
+}
+
+/* Send a file complete chunk. */
+static int
+send_file_complete (guestfs_h *g)
+{
+ char buf[1];
+ return send_file_chunk (g, 0, buf, 0);
+}
+
+static int
+send_file_chunk (guestfs_h *g, int cancel, const char *buf, size_t buflen)
+{
+ u_int32_t len;
+ int r;
+ guestfs_chunk chunk;
+ XDR xdr;
+ char *msg_out;
+ size_t msg_out_size;
+
+ if (g->state != BUSY) {
+ error (g, _("send_file_chunk: state %d != READY"), g->state);
+ return -1;
+ }
+
+ /* Allocate the chunk buffer. Don't use the stack to avoid
+ * excessive stack usage and unnecessary copies.
+ */
+ msg_out = safe_malloc (g, GUESTFS_MAX_CHUNK_SIZE + 4 + 48);
+ xdrmem_create (&xdr, msg_out + 4, GUESTFS_MAX_CHUNK_SIZE + 48, XDR_ENCODE);
+
+ /* Serialize the chunk. */
+ chunk.cancel = cancel;
+ chunk.data.data_len = buflen;
+ chunk.data.data_val = (char *) buf;
+
+ if (!xdr_guestfs_chunk (&xdr, &chunk)) {
+ error (g, _("xdr_guestfs_chunk failed (buf = %p, buflen = %zu)"),
+ buf, buflen);
+ xdr_destroy (&xdr);
+ goto cleanup1;
+ }
+
+ len = xdr_getpos (&xdr);
+ xdr_destroy (&xdr);
+
+ /* Reduce the size of the outgoing message buffer to the real length. */
+ msg_out = safe_realloc (g, msg_out, len + 4);
+ msg_out_size = len + 4;
+
+ xdrmem_create (&xdr, msg_out, 4, XDR_ENCODE);
+ xdr_uint32_t (&xdr, &len);
+
+ r = send_to_daemon (g, msg_out, msg_out_size);
+
+ /* Did the daemon send a cancellation message? */
+ if (r == -2) {
+ if (g->verbose)
+ fprintf (stderr, "got daemon cancellation\n");
+ return -2;
+ }
+
+ if (r == -1)
+ goto cleanup1;
+
+ free (msg_out);
+
+ return 0;
+
+ cleanup1:
+ free (msg_out);
+ return -1;
+}
+
+/* Receive a reply. */
+int
+guestfs___recv (guestfs_h *g, const char *fn,
+ guestfs_message_header *hdr,
+ guestfs_message_error *err,
+ xdrproc_t xdrp, char *ret)
+{
+ XDR xdr;
+ void *buf;
+ uint32_t size;
+ int r;
+
+ again:
+ r = recv_from_daemon (g, &size, &buf);
+ if (r == -1)
+ return -1;
+
+ /* 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 (size == GUESTFS_CANCEL_FLAG)
+ goto again;
+
+ if (size == GUESTFS_LAUNCH_FLAG) {
+ error (g, "%s: received unexpected launch flag from daemon when expecting reply", fn);
+ return -1;
+ }
+
+ xdrmem_create (&xdr, buf, size, XDR_DECODE);
+
+ if (!xdr_guestfs_message_header (&xdr, hdr)) {
+ error (g, "%s: failed to parse reply header", fn);
+ xdr_destroy (&xdr);
+ free (buf);
+ return -1;
+ }
+ if (hdr->status == GUESTFS_STATUS_ERROR) {
+ if (!xdr_guestfs_message_error (&xdr, err)) {
+ error (g, "%s: failed to parse reply error", fn);
+ xdr_destroy (&xdr);
+ free (buf);
+ return -1;
+ }
+ } else {
+ if (xdrp && ret && !xdrp (&xdr, ret)) {
+ error (g, "%s: failed to parse reply", fn);
+ xdr_destroy (&xdr);
+ free (buf);
+ return -1;
+ }
+ }
+ xdr_destroy (&xdr);
+ free (buf);
+
+ return 0;
+}
+
+/* Receive a file. */
+
+/* Returns -1 = error, 0 = EOF, > 0 = more data */
+static ssize_t receive_file_data (guestfs_h *g, void **buf);
+
+int
+guestfs___recv_file (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 (g, &buf)) > 0) {
+ if (xwrite (fd, buf, r) == -1) {
+ perrorf (g, "%s: write", filename);
+ free (buf);
+ goto cancel;
+ }
+ free (buf);
+ }
+
+ 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;
+
+ if (g->verbose)
+ fprintf (stderr, "%s: waiting for daemon to acknowledge cancellation\n",
+ __func__);
+
+ 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 (receive_file_data (g, NULL) > 0)
+ ; /* just discard it */
+
+ return -1;
+}
+
+/* Receive a chunk of file data. */
+/* Returns -1 = error, 0 = EOF, > 0 = more data */
+static ssize_t
+receive_file_data (guestfs_h *g, void **buf_r)
+{
+ int r;
+ void *buf;
+ uint32_t len;
+ XDR xdr;
+ guestfs_chunk chunk;
+
+ r = recv_from_daemon (g, &len, &buf);
+ if (r == -1) {
+ error (g, _("receive_file_data: parse error in reply callback"));
+ return -1;
+ }
+
+ if (len == GUESTFS_LAUNCH_FLAG || len == GUESTFS_CANCEL_FLAG) {
+ error (g, _("receive_file_data: unexpected flag received when reading file chunks"));
+ return -1;
+ }
+
+ memset (&chunk, 0, sizeof chunk);
+
+ xdrmem_create (&xdr, buf, len, XDR_DECODE);
+ if (!xdr_guestfs_chunk (&xdr, &chunk)) {
+ error (g, _("failed to parse file chunk"));
+ free (buf);
+ return -1;
+ }
+ xdr_destroy (&xdr);
+ /* After decoding, the original buffer is no longer used. */
+ free (buf);
+
+ if (chunk.cancel) {
+ error (g, _("file receive cancelled by daemon"));
+ free (chunk.data.data_val);
+ return -1;
+ }
+
+ if (chunk.data.data_len == 0) { /* end of transfer */
+ free (chunk.data.data_val);
+ return 0;
+ }
+
+ if (buf_r) *buf_r = chunk.data.data_val;
+ else free (chunk.data.data_val); /* else caller frees */