summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/plugins/xfer/xfer-dcc.c193
1 files changed, 132 insertions, 61 deletions
diff --git a/src/plugins/xfer/xfer-dcc.c b/src/plugins/xfer/xfer-dcc.c
index 644518ad9..f477f918b 100644
--- a/src/plugins/xfer/xfer-dcc.c
+++ b/src/plugins/xfer/xfer-dcc.c
@@ -24,6 +24,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
+#include <sys/select.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <time.h>
@@ -173,18 +174,61 @@ xfer_dcc_send_file_child (struct t_xfer *xfer)
}
/*
+ * Sends ACK to sender using current position in file received.
+ *
+ * Returns:
+ * 2: ACK sent successfully (the 4 bytes)
+ * 1: ACK not sent, but we consider it's not a problem
+ * 0: ACK not sent with socket error (DCC will be closed)
+ */
+
+int
+xfer_dcc_recv_file_send_ack (struct t_xfer *xfer)
+{
+ int length, num_sent, total_sent;
+ uint32_t pos;
+ const void *ptr_buf;
+
+ pos = htonl (xfer->pos);
+ ptr_buf = &pos;
+ length = 4;
+ total_sent = 0;
+ num_sent = send (xfer->sock, ptr_buf, length, 0);
+ if (num_sent > 0)
+ total_sent += num_sent;
+ while (total_sent < length)
+ {
+ if ((num_sent == -1) && (errno != EAGAIN) && (errno != EWOULDBLOCK))
+ return 0;
+
+ /* if we can't send ACK now, just return with partial failure code */
+ if (total_sent == 0)
+ return 1;
+
+ /* at least one byte has been sent, we must send whole ACK */
+ usleep (1000);
+ num_sent = send (xfer->sock, ptr_buf + total_sent,
+ length - total_sent, 0);
+ if (num_sent > 0)
+ total_sent += num_sent;
+ }
+
+ /* ACK successfully sent */
+ return 2;
+}
+
+/*
* Child process for receiving file with DCC protocol.
*/
void
xfer_dcc_recv_file_child (struct t_xfer *xfer)
{
- int flags, num_read, num_sent, total_sent, length;
+ int flags, num_read, ack_enabled, ready;
static char buffer[XFER_BLOCKSIZE_MAX];
- uint32_t pos;
- const void *ptr_buf;
time_t last_sent, new_time;
- unsigned long long bytes_remaining;
+ unsigned long long pos_last_ack;
+ fd_set read_fds, write_fds, except_fds;
/* first connect to sender (blocking) */
if (!weechat_network_connect_to (xfer->proxy, xfer->sock,
@@ -206,80 +250,107 @@ xfer_dcc_recv_file_child (struct t_xfer *xfer)
fcntl (xfer->sock, F_SETFL, flags | O_NONBLOCK);
last_sent = time (NULL);
+ ack_enabled = 1;
+ pos_last_ack = 0;
+
while (1)
{
- bytes_remaining = xfer->size - xfer->pos;
- num_read = recv (xfer->sock, buffer,
- (bytes_remaining >= sizeof (buffer)) ? sizeof (buffer) : bytes_remaining,
- 0);
- if (num_read == -1)
+ /* wait until there is something to read on socket (or error) */
+ FD_ZERO (&read_fds);
+ FD_ZERO (&write_fds);
+ FD_ZERO (&except_fds);
+ FD_SET (xfer->sock, &read_fds);
+ ready = select (xfer->sock + 1, &read_fds, &write_fds, &except_fds, NULL);
+ if (ready == 0)
{
- /* socket is temporarily not available (sender is not fast ?!) */
- if ((errno == EAGAIN) || (errno == EWOULDBLOCK) || (errno == EINTR))
- usleep (1000);
- else
- {
- xfer_network_write_pipe (xfer, XFER_STATUS_FAILED,
- XFER_ERROR_RECV_BLOCK);
- return;
- }
+ xfer_network_write_pipe (xfer, XFER_STATUS_FAILED,
+ XFER_ERROR_RECV_BLOCK);
+ return;
}
- else
+
+ /* read maximum data on socket (until nothing is available) */
+ while (1)
{
- if (num_read == 0)
+ num_read = recv (xfer->sock, buffer, sizeof (buffer), 0);
+ if (num_read == -1)
{
- xfer_network_write_pipe (xfer, XFER_STATUS_FAILED,
- XFER_ERROR_RECV_BLOCK);
- return;
+ if ((errno != EAGAIN) && (errno != EWOULDBLOCK) && (errno != EINTR))
+ {
+ xfer_network_write_pipe (xfer, XFER_STATUS_FAILED,
+ XFER_ERROR_RECV_BLOCK);
+ return;
+ }
+ /*
+ * no more data available on socket: exit look, send ACK, and
+ * wait for new data on socket
+ */
+ break;
}
-
- if (write (xfer->file, buffer, num_read) == -1)
+ else
{
- xfer_network_write_pipe (xfer, XFER_STATUS_FAILED,
- XFER_ERROR_WRITE_LOCAL);
- return;
- }
+ if (num_read == 0)
+ {
+ xfer_network_write_pipe (xfer, XFER_STATUS_FAILED,
+ XFER_ERROR_RECV_BLOCK);
+ return;
+ }
- xfer->pos += (unsigned long long) num_read;
- pos = htonl (xfer->pos);
-
- /* send the ACK (and ensure the 4 bytes are sent) */
- ptr_buf = &pos;
- length = 4;
- total_sent = 0;
- num_sent = send (xfer->sock, ptr_buf, length, 0);
- if (num_sent > 0)
- total_sent += num_sent;
- while (total_sent < length)
- {
- if ((num_sent == -1) && (errno != EAGAIN)
- && (errno != EWOULDBLOCK))
+ if (write (xfer->file, buffer, num_read) == -1)
{
xfer_network_write_pipe (xfer, XFER_STATUS_FAILED,
- XFER_ERROR_SEND_ACK);
+ XFER_ERROR_WRITE_LOCAL);
return;
}
- usleep (1000);
- num_sent = send (xfer->sock, ptr_buf + total_sent,
- length - total_sent, 0);
- if (num_sent > 0)
- total_sent += num_sent;
- }
- /* file received ok? */
- if (xfer->pos >= xfer->size)
- {
- xfer_network_write_pipe (xfer, XFER_STATUS_DONE,
- XFER_NO_ERROR);
- return;
+ xfer->pos += (unsigned long long) num_read;
+
+ /* file received ok? */
+ if (xfer->pos >= xfer->size)
+ {
+ /*
+ * extra delay before sending ACK, otherwise the send of ACK
+ * may fail
+ */
+ usleep (100000);
+
+ /* send ACK to sender without checking return code (file OK) */
+ xfer_dcc_recv_file_send_ack (xfer);
+
+ /* set status done and return */
+ xfer_network_write_pipe (xfer, XFER_STATUS_DONE,
+ XFER_NO_ERROR);
+ return;
+ }
+
+ /* update status of DCC (parent process) */
+ new_time = time (NULL);
+ if (last_sent != new_time)
+ {
+ last_sent = new_time;
+ xfer_network_write_pipe (xfer, XFER_STATUS_ACTIVE,
+ XFER_NO_ERROR);
+ }
}
+ }
- new_time = time (NULL);
- if (last_sent != new_time)
+ /* send ACK to sender (if needed) */
+ if (ack_enabled && (xfer->pos > pos_last_ack))
+ {
+ switch (xfer_dcc_recv_file_send_ack (xfer))
{
- last_sent = new_time;
- xfer_network_write_pipe (xfer, XFER_STATUS_ACTIVE,
- XFER_NO_ERROR);
+ case 0:
+ /* send error, socket down? */
+ xfer_network_write_pipe (xfer, XFER_STATUS_FAILED,
+ XFER_ERROR_SEND_ACK);
+ return;
+ case 1:
+ /* send error, not fatal (buffer full?): disable ACKs */
+ ack_enabled = 0;
+ break;
+ case 2:
+ /* send OK: save position in file as last ACK sent */
+ pos_last_ack = xfer->pos;
+ break;
}
}
}