apr_status_t apr_socket_sendfile()

in network_io/unix/sendrecv.c [543:663]


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)
{
    off_t nbytes = 0;
    int rv;
#if defined(__FreeBSD_version) && __FreeBSD_version < 460001
    int i;
#endif
    struct sf_hdtr headerstruct;
    apr_size_t bytes_to_send = *len;
    apr_status_t arv;

    /* Until further notice. */
    *len = 0;

    /* Ignore flags for now. */
    flags = 0;

    if (!hdtr) {
        hdtr = &no_hdtr;
    }

#if defined(__FreeBSD_version) && __FreeBSD_version < 460001
    else if (hdtr->numheaders) {

        /* On early versions of FreeBSD sendfile, the number of bytes to send
         * must include the length of the headers.  Don't look at the man page
         * for this :(  Instead, look at the logic in
         * src/sys/kern/uipc_syscalls::sendfile().
         *
         * This was fixed in the middle of 4.6-STABLE
         */
        for (i = 0; i < hdtr->numheaders; i++) {
            bytes_to_send += hdtr->headers[i].iov_len;
        }
    }
#endif

    headerstruct.headers = hdtr->headers;
    headerstruct.hdr_cnt = hdtr->numheaders;
    headerstruct.trailers = hdtr->trailers;
    headerstruct.trl_cnt = hdtr->numtrailers;

    /* FreeBSD can send the headers/footers as part of the system call */
    do {
        if (sock->options & APR_INCOMPLETE_WRITE) {
            sock->options &= ~APR_INCOMPLETE_WRITE;
            arv = apr_wait_for_io_or_timeout(NULL, sock, 0);
            if (arv != APR_SUCCESS) {
                return arv;
            }
        }
        if (bytes_to_send) {
            /* We won't dare call sendfile() if we don't have
             * header or file bytes to send because bytes_to_send == 0
             * means send the whole file.
             */
            rv = sendfile(file->filedes, /* file to be sent */
                          sock->socketdes, /* socket */
                          *offset,       /* where in the file to start */
                          bytes_to_send, /* number of bytes to send */
                          &headerstruct, /* Headers/footers */
                          &nbytes,       /* number of bytes written */
                          flags);        /* undefined, set to 0 */

            if (rv == -1) {
                if (errno == EAGAIN) {
                    if (sock->timeout > 0) {
                        sock->options |= APR_INCOMPLETE_WRITE;
                    }
                    /* FreeBSD's sendfile can return -1/EAGAIN even if it
                     * sent bytes.  Sanitize the result so we get normal EAGAIN
                     * semantics w.r.t. bytes sent.
                     */
                    if (nbytes) {
                        *len += nbytes;
                        /* normal exit for a big file & non-blocking io */
                        return APR_SUCCESS;
                    }
                    if (sock->timeout <= 0) {
                        /* normal exit for a non-blocking io which would block */
                        break;
                    }
                }
            }
            else {       /* rv == 0 (or the kernel is broken) */
                if (nbytes == 0) {
                    /* Most likely the file got smaller after the stat.
                     * Return an error so the caller can do the Right Thing.
                     */
                    return APR_EOF;
                }
                *len += nbytes;
            }
        }
        else {
            /* just trailer bytes... use writev()
             */
            rv = writev(sock->socketdes,
                        hdtr->trailers,
                        hdtr->numtrailers);
            if (rv > 0) {
                *len += rv;
            }
            else if (rv == -1 && errno == EAGAIN) {
                if (sock->timeout > 0) {
                    sock->options |= APR_INCOMPLETE_WRITE;
                }
                else {
                    break;
                }
            }
        }
    } while (rv == -1 && (errno == EINTR || errno == EAGAIN));

    if (rv == -1) {
        return errno;
    }
    return APR_SUCCESS;
}