summaryrefslogtreecommitdiffstats
path: root/lib/libfetch/common.c
diff options
context:
space:
mode:
authordes <des@FreeBSD.org>2012-01-18 15:13:21 +0000
committerdes <des@FreeBSD.org>2012-01-18 15:13:21 +0000
commit939a66af62dc031d0ca35b5e7b1410f8aedc1d7c (patch)
treea2005365ba56694fd925cd9d8f2687ea003a376f /lib/libfetch/common.c
parent7972e45b2052402d891e3dd144472547dac25ec6 (diff)
downloadFreeBSD-src-939a66af62dc031d0ca35b5e7b1410f8aedc1d7c.zip
FreeBSD-src-939a66af62dc031d0ca35b5e7b1410f8aedc1d7c.tar.gz
Fix two issues related to the use of SIGINFO in fetch(1) to display
progress information. The first is that fetch_read() (used in the HTTP code but not the FTP code) can enter an infinite loop if it has previously been interrupted by a signal. The second is that when it is interrupted, fetch_read() will discard any data it may have read up to that point. Luckily, both bugs are extremely timing-sensitive and therefore difficult to trigger. PR: bin/153240 Submitted by: Mark <markjdb@gmail.com> MFC after: 3 weeks
Diffstat (limited to 'lib/libfetch/common.c')
-rw-r--r--lib/libfetch/common.c58
1 files changed, 56 insertions, 2 deletions
diff --git a/lib/libfetch/common.c b/lib/libfetch/common.c
index 0d5f2d6..5dfdcc4 100644
--- a/lib/libfetch/common.c
+++ b/lib/libfetch/common.c
@@ -404,6 +404,34 @@ fetch_ssl_read(SSL *ssl, char *buf, size_t len)
}
#endif
+/*
+ * Cache some data that was read from a socket but cannot be immediately
+ * returned because of an interrupted system call.
+ */
+static int
+fetch_cache_data(conn_t *conn, char *src, size_t nbytes)
+{
+ char *tmp;
+
+ if (conn->cache.size < nbytes) {
+ tmp = realloc(conn->cache.buf, nbytes);
+ if (tmp == NULL) {
+ errno = ENOMEM;
+ fetch_syserr();
+ return (-1);
+ }
+ conn->cache.buf = tmp;
+ conn->cache.size = nbytes;
+ }
+
+ memcpy(conn->cache.buf, src, nbytes);
+ conn->cache.len = nbytes;
+ conn->cache.pos = 0;
+
+ return (0);
+}
+
+
static ssize_t
fetch_socket_read(int sd, char *buf, size_t len)
{
@@ -429,6 +457,7 @@ fetch_read(conn_t *conn, char *buf, size_t len)
fd_set readfds;
ssize_t rlen, total;
int r;
+ char *start;
if (fetchTimeout) {
FD_ZERO(&readfds);
@@ -437,6 +466,24 @@ fetch_read(conn_t *conn, char *buf, size_t len)
}
total = 0;
+ start = buf;
+
+ if (conn->cache.len > 0) {
+ /*
+ * The last invocation of fetch_read was interrupted by a
+ * signal after some data had been read from the socket. Copy
+ * the cached data into the supplied buffer before trying to
+ * read from the socket again.
+ */
+ total = (conn->cache.len < len) ? conn->cache.len : len;
+ memcpy(buf, conn->cache.buf, total);
+
+ conn->cache.len -= total;
+ conn->cache.pos += total;
+ len -= total;
+ buf+= total;
+ }
+
while (len > 0) {
/*
* The socket is non-blocking. Instead of the canonical
@@ -472,6 +519,8 @@ fetch_read(conn_t *conn, char *buf, size_t len)
total += rlen;
continue;
} else if (rlen == FETCH_READ_ERROR) {
+ if (errno == EINTR)
+ fetch_cache_data(conn, start, total);
return (-1);
}
// assert(rlen == FETCH_READ_WAIT);
@@ -492,8 +541,12 @@ fetch_read(conn_t *conn, char *buf, size_t len)
errno = 0;
r = select(conn->sd + 1, &readfds, NULL, NULL, &delta);
if (r == -1) {
- if (errno == EINTR && fetchRestartCalls)
- continue;
+ if (errno == EINTR) {
+ if (fetchRestartCalls)
+ continue;
+ /* Save anything that was read. */
+ fetch_cache_data(conn, start, total);
+ }
fetch_syserr();
return (-1);
}
@@ -677,6 +730,7 @@ fetch_close(conn_t *conn)
if (--conn->ref > 0)
return (0);
ret = close(conn->sd);
+ free(conn->cache.buf);
free(conn->buf);
free(conn);
return (ret);
OpenPOWER on IntegriCloud