diff options
author | des <des@FreeBSD.org> | 2012-01-18 15:13:21 +0000 |
---|---|---|
committer | des <des@FreeBSD.org> | 2012-01-18 15:13:21 +0000 |
commit | 939a66af62dc031d0ca35b5e7b1410f8aedc1d7c (patch) | |
tree | a2005365ba56694fd925cd9d8f2687ea003a376f /lib/libfetch/common.c | |
parent | 7972e45b2052402d891e3dd144472547dac25ec6 (diff) | |
download | FreeBSD-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.c | 58 |
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); |