in network_io/unix/sendrecv.c [265:424]
apr_status_t apr_socket_sendfile(apr_socket_t *sock, apr_file_t *file,
apr_hdtr_t *hdtr, apr_off_t *offset,
apr_size_t *len, apr_int32_t flags)
{
int nopush_set = 0, rv, i;
apr_size_t bytes_to_send = *len;
apr_status_t arv;
apr_size_t n;
#if APR_HAS_LARGE_FILES && defined(HAVE_SENDFILE64)
apr_off_t off = *offset;
#define sendfile sendfile64
#elif APR_HAS_LARGE_FILES && SIZEOF_OFF_T == 4
/* 64-bit apr_off_t but no sendfile64(): fail if trying to send
* past the 2Gb limit. */
off_t off;
if ((apr_int64_t)*offset + bytes_to_send > INT_MAX) {
*len = 0;
return EINVAL;
}
off = *offset;
#else
off_t off = *offset;
/* Multiple reports have shown sendfile failing with EINVAL if
* passed a >=2Gb count value on some 64-bit kernels. It won't
* noticably hurt performance to limit each call to <2Gb at a
* time, so avoid that issue here: */
if (sizeof(off_t) == 8 && bytes_to_send > INT_MAX) {
bytes_to_send = INT_MAX;
}
#endif
/* Until further notice. */
*len = 0;
if (!hdtr) {
hdtr = &no_hdtr;
}
else if ((hdtr->numheaders > 0 || hdtr->numtrailers > 0)
&& !apr_is_option_set(sock, APR_TCP_NOPUSH)) {
/* cork before writing headers */
rv = apr_socket_opt_set(sock, APR_TCP_NOPUSH, 1);
if (rv != APR_SUCCESS) {
return rv;
}
nopush_set = 1;
}
if (hdtr->numheaders > 0) {
apr_size_t total_hdrbytes;
/* Now write the headers */
arv = apr_socket_sendv(sock, hdtr->headers, hdtr->numheaders, &n);
*len += n;
if (arv != APR_SUCCESS) {
return arv;
}
/* If this was a partial write and we aren't doing timeouts,
* return now with the partial byte count; this is a non-blocking
* socket.
*/
total_hdrbytes = 0;
for (i = 0; i < hdtr->numheaders; i++) {
total_hdrbytes += hdtr->headers[i].iov_len;
}
if (n < total_hdrbytes) {
if (nopush_set) {
return apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);
}
return APR_SUCCESS;
}
}
if (sock->options & APR_INCOMPLETE_WRITE) {
sock->options &= ~APR_INCOMPLETE_WRITE;
goto do_select;
}
do {
rv = sendfile(sock->socketdes, /* socket */
file->filedes, /* open file descriptor of the file to be sent */
&off, /* where in the file to start */
bytes_to_send); /* number of bytes to send */
} while (rv == -1 && errno == EINTR);
while ((rv == -1) && (errno == EAGAIN || errno == EWOULDBLOCK)
&& (sock->timeout > 0)) {
do_select:
arv = apr_wait_for_io_or_timeout(NULL, sock, 0);
if (arv != APR_SUCCESS) {
return arv;
}
else {
do {
rv = sendfile(sock->socketdes, /* socket */
file->filedes, /* open file descriptor of the file to be sent */
&off, /* where in the file to start */
bytes_to_send); /* number of bytes to send */
} while (rv == -1 && errno == EINTR);
}
}
if (rv == -1) {
arv = errno;
if (nopush_set) {
apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);
}
return arv;
}
*len += rv;
if ((apr_size_t)rv < bytes_to_send) {
if (nopush_set) {
arv = apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);
}
else {
arv = APR_SUCCESS;
}
if (rv > 0) {
/* If this was a partial write, return now with the
* partial byte count; this is a non-blocking socket.
*/
if (sock->timeout > 0) {
sock->options |= APR_INCOMPLETE_WRITE;
}
return arv;
}
else {
/* If the file got smaller mid-request, eventually the offset
* becomes equal to the new file size and the kernel returns 0.
* Make this an error so the caller knows to log something and
* exit.
*/
return APR_EOF;
}
}
/* Now write the footers */
if (hdtr->numtrailers > 0) {
arv = apr_socket_sendv(sock, hdtr->trailers, hdtr->numtrailers, &n);
*len += n;
if (nopush_set) {
apr_socket_opt_set(sock, APR_TCP_NOPUSH, 0);
}
if (arv != APR_SUCCESS) {
return arv;
}
}
return APR_SUCCESS;
}