diff --git a/include/lighttpd/network.h b/include/lighttpd/network.h index 1406536..5543e94 100644 --- a/include/lighttpd/network.h +++ b/include/lighttpd/network.h @@ -5,6 +5,10 @@ #error Please include instead of this file #endif +#if defined(USE_LINUX_SENDFILE) || defined(USE_FREEBSD_SENDFILE) || defined(USE_SOLARIS_SENDFILEV) +# define USE_SENDFILE +#endif + typedef enum { NETWORK_STATUS_SUCCESS, /**< some IO was actually done (read/write) or cq was empty for write */ NETWORK_STATUS_FATAL_ERROR, @@ -26,8 +30,10 @@ LI_API network_status_t network_read(vrequest *vr, int fd, chunkqueue *cq); /* use writev for mem chunks, buffered read/write for files */ LI_API network_status_t network_write_writev(vrequest *vr, int fd, chunkqueue *cq, goffset *write_max); +#ifdef USE_SENDFILE /* use sendfile for files, writev for mem chunks */ LI_API network_status_t network_write_sendfile(vrequest *vr, int fd, chunkqueue *cq, goffset *write_max); +#endif /* write backends */ LI_API network_status_t network_backend_write(vrequest *vr, int fd, chunkqueue *cq, goffset *write_max); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 459ef5b..d64196a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -278,7 +278,7 @@ SET(COMMON_SRC module.c network.c network_write.c network_writev.c - network_linux_sendfile.c + network_sendfile.c options.c plugin.c profiler.c diff --git a/src/network.c b/src/network.c index 2594522..57deda6 100644 --- a/src/network.c +++ b/src/network.c @@ -67,9 +67,13 @@ network_status_t network_write(vrequest *vr, int fd, chunkqueue *cq) { } #endif - /* res = network_write_writev(con, fd, cq); */ write_bytes = write_max; + /* TODO: add setup-option to select the backend */ +#ifdef USE_SENDFILE res = network_write_sendfile(vr, fd, cq, &write_bytes); +#else + res = network_write_writev(con, fd, cq, &write_bytes); +#endif #ifdef TCP_CORK if (corked) { diff --git a/src/network_linux_sendfile.c b/src/network_linux_sendfile.c deleted file mode 100644 index e02e5e3..0000000 --- a/src/network_linux_sendfile.c +++ /dev/null @@ -1,100 +0,0 @@ - -#include - -/* first chunk must be a FILE_CHUNK ! */ -network_status_t network_backend_sendfile(vrequest *vr, int fd, chunkqueue *cq, goffset *write_max) { - off_t file_offset, toSend; - ssize_t r; - gboolean did_write_something = FALSE; - chunkiter ci; - chunk *c; - - if (0 == cq->length) return NETWORK_STATUS_FATAL_ERROR; - - do { - ci = chunkqueue_iter(cq); - - if (FILE_CHUNK != (c = chunkiter_chunk(ci))->type) { - return did_write_something ? NETWORK_STATUS_SUCCESS : NETWORK_STATUS_FATAL_ERROR; - } - - switch (chunkfile_open(vr, c->file.file)) { - case HANDLER_GO_ON: - break; - case HANDLER_WAIT_FOR_FD: - return NETWORK_STATUS_WAIT_FOR_FD; - default: - return NETWORK_STATUS_FATAL_ERROR; - } - - file_offset = c->offset + c->file.start; - toSend = c->file.length - c->offset; - if (toSend > *write_max) toSend = *write_max; - - while (-1 == (r = sendfile(fd, c->file.file->fd, &file_offset, toSend))) { - switch (errno) { - case EAGAIN: -#if EWOULDBLOCK != EAGAIN - case EWOULDBLOCK -#endif - return NETWORK_STATUS_WAIT_FOR_EVENT; - case ECONNRESET: - case EPIPE: - return NETWORK_STATUS_CONNECTION_CLOSE; - case EINTR: - break; /* try again */ - case EINVAL: - case ENOSYS: - /* TODO: print a warning? */ - NETWORK_FALLBACK(network_backend_write, write_max); - return NETWORK_STATUS_SUCCESS; - default: - VR_ERROR(vr, "oops, write to fd=%d failed: %s", fd, g_strerror(errno)); - return NETWORK_STATUS_FATAL_ERROR; - } - } - if (0 == r) { - /* don't care about cached stat - file is open */ - struct stat st; - if (-1 == fstat(fd, &st)) { - VR_ERROR(vr, "Couldn't fstat file: %s", g_strerror(errno)); - return NETWORK_STATUS_FATAL_ERROR; - } - - if (file_offset > st.st_size) { - /* file shrinked, close the connection */ - VR_ERROR(vr, "%s", "File shrinked, aborting"); - return NETWORK_STATUS_FATAL_ERROR; - } - return NETWORK_STATUS_WAIT_FOR_EVENT; - } - chunkqueue_skip(cq, r); - *write_max -= r; - did_write_something = TRUE; - - if (0 == cq->length) return NETWORK_STATUS_SUCCESS; - if (r != toSend) return NETWORK_STATUS_WAIT_FOR_EVENT; - } while (*write_max > 0); - - return NETWORK_STATUS_SUCCESS; -} - -network_status_t network_write_sendfile(vrequest *vr, int fd, chunkqueue *cq, goffset *write_max) { - if (cq->length == 0) return NETWORK_STATUS_FATAL_ERROR; - - do { - switch (chunkqueue_first_chunk(cq)->type) { - case MEM_CHUNK: - NETWORK_FALLBACK(network_backend_writev, write_max); - break; - case FILE_CHUNK: - NETWORK_FALLBACK(network_backend_sendfile, write_max); - break; - default: - return NETWORK_STATUS_FATAL_ERROR; - } - - if (cq->length == 0) return NETWORK_STATUS_SUCCESS; - } while (*write_max > 0); - return NETWORK_STATUS_SUCCESS; -} diff --git a/src/network_sendfile.c b/src/network_sendfile.c new file mode 100644 index 0000000..9ab77b8 --- /dev/null +++ b/src/network_sendfile.c @@ -0,0 +1,200 @@ + +#include + +#ifdef USE_SENDFILE + +typedef enum { + NSR_SUCCESS, + NSR_WAIT_FOR_EVENT, + NSR_FALLBACK, + NSR_CLOSE, + NSR_FATAL_ERROR +} network_sendfile_result; + +static network_sendfile_result lighty_sendfile(vrequest *vr, int fd, int filefd, goffset offset, ssize_t len, ssize_t *wrote); + +#if defined(USE_LINUX_SENDFILE) + +static network_sendfile_result lighty_sendfile(vrequest *vr, int fd, int filefd, goffset offset, ssize_t len, ssize_t *wrote) { + ssize_t r; + off_t file_offset = offset; + + while (-1 == (r = sendfile(fd, filefd, &file_offset, len))) { + switch (errno) { + case EAGAIN: +#if EWOULDBLOCK != EAGAIN + case EWOULDBLOCK +#endif + return NSR_WAIT_FOR_EVENT; + case ECONNRESET: + case EPIPE: + return NSR_CLOSE; + case EINTR: + break; /* try again */ + case EINVAL: + case ENOSYS: + /* TODO: print a warning? */ + return NSR_FALLBACK; + default: + VR_ERROR(vr, "oops, write to fd=%d failed: %s", fd, g_strerror(errno)); + return NSR_FATAL_ERROR; + } + } + *wrote = r; + return NSR_SUCCESS; +} + +#elif defined(USE_FREEBSD_SENDFILE) + +static network_sendfile_result lighty_sendfile(vrequest *vr, int fd, int filefd, goffset offset, ssize_t len, ssize_t *wrote) { + off_t file_offset = offset; + off_t r = 0; + + while (-1 == sendfile(filefd, fd, file_offset, len, NULL, &r, 0)) { + switch (errno) { + case EAGAIN: + return NSR_WAIT_FOR_EVENT; + case ENOTCONN: + case EPIPE: + return NSR_CLOSE; + case EINTR: + break; /* try again */ + case EINVAL: + case EOPNOTSUPP: + case ENOTSOCK: + /* TODO: print a warning? */ + return NSR_FALLBACK; + default: + VR_ERROR(vr, "oops, write to fd=%d failed: %s", fd, g_strerror(errno)); + return NSR_FATAL_ERROR; + } + } + *wrote = r; + return NSR_SUCCESS; +} + +#elif defined(USE_SOLARIS_SENDFILEV) + +static network_sendfile_result lighty_sendfile(vrequest *vr, int fd, int filefd, goffset offset, ssize_t len, ssize_t *wrote) { + sendfilevec_t fvec; + + fvec.sfv_fd = filefd; + fvec.sfv_flag = 0; + fvec.sfv_off = offset; + fvec.sfv_len = len; + + while (-1 == (r = sendfilev(fd, &fvec, 1, (size_t*) wrote))) { + switch (errno) { + case EAGAIN: + return NSR_WAIT_FOR_EVENT; + case EPIPE: + return NSR_CLOSE; + case EINTR: + break; /* try again */ + case EAFNOSUPPORT: + case EPROTOTYPE: + /* TODO: print a warning? */ + return NSR_FALLBACK; + default: + VR_ERROR(vr, "oops, write to fd=%d failed: %s", fd, g_strerror(errno)); + return NSR_FATAL_ERROR; + } + } + return NSR_SUCCESS; +} + +#endif + + + +/* first chunk must be a FILE_CHUNK ! */ +network_status_t network_backend_sendfile(vrequest *vr, int fd, chunkqueue *cq, goffset *write_max) { + off_t file_offset, toSend; + ssize_t r; + gboolean did_write_something = FALSE; + chunkiter ci; + chunk *c; + + if (0 == cq->length) return NETWORK_STATUS_FATAL_ERROR; + + do { + ci = chunkqueue_iter(cq); + + if (FILE_CHUNK != (c = chunkiter_chunk(ci))->type) { + return did_write_something ? NETWORK_STATUS_SUCCESS : NETWORK_STATUS_FATAL_ERROR; + } + + switch (chunkfile_open(vr, c->file.file)) { + case HANDLER_GO_ON: + break; + case HANDLER_WAIT_FOR_FD: + return NETWORK_STATUS_WAIT_FOR_FD; + default: + return NETWORK_STATUS_FATAL_ERROR; + } + + file_offset = c->offset + c->file.start; + toSend = c->file.length - c->offset; + if (toSend > *write_max) toSend = *write_max; + + r = 0; + switch (lighty_sendfile(vr, fd, c->file.file->fd, file_offset, toSend, &r)) { + case NSR_SUCCESS: + chunkqueue_skip(cq, r); + *write_max -= r; + break; + case NSR_WAIT_FOR_EVENT: + return NETWORK_STATUS_WAIT_FOR_EVENT; + case NSR_FALLBACK: + NETWORK_FALLBACK(network_backend_write, write_max); + break; + case NSR_CLOSE: + return NETWORK_STATUS_CONNECTION_CLOSE; + case NSR_FATAL_ERROR: + return NETWORK_STATUS_FATAL_ERROR; + } + if (0 == r) { + /* don't care about cached stat - file is open */ + struct stat st; + if (-1 == fstat(fd, &st)) { + VR_ERROR(vr, "Couldn't fstat file: %s", g_strerror(errno)); + return NETWORK_STATUS_FATAL_ERROR; + } + + if (file_offset > st.st_size) { + /* file shrinked, close the connection */ + VR_ERROR(vr, "%s", "File shrinked, aborting"); + return NETWORK_STATUS_FATAL_ERROR; + } + return NETWORK_STATUS_WAIT_FOR_EVENT; + } + did_write_something = TRUE; + + if (0 == cq->length) return NETWORK_STATUS_SUCCESS; + if (r != toSend) return NETWORK_STATUS_WAIT_FOR_EVENT; + } while (*write_max > 0); + + return NETWORK_STATUS_SUCCESS; +} + +network_status_t network_write_sendfile(vrequest *vr, int fd, chunkqueue *cq, goffset *write_max) { + if (cq->length == 0) return NETWORK_STATUS_FATAL_ERROR; + + do { + switch (chunkqueue_first_chunk(cq)->type) { + case MEM_CHUNK: + NETWORK_FALLBACK(network_backend_writev, write_max); + break; + case FILE_CHUNK: + NETWORK_FALLBACK(network_backend_sendfile, write_max); + break; + default: + return NETWORK_STATUS_FATAL_ERROR; + } + + if (cq->length == 0) return NETWORK_STATUS_SUCCESS; + } while (*write_max > 0); + return NETWORK_STATUS_SUCCESS; +} + +#endif diff --git a/src/wscript b/src/wscript index 0696820..18fc1ca 100644 --- a/src/wscript +++ b/src/wscript @@ -23,7 +23,7 @@ common_source=''' module.c network.c network_write.c network_writev.c - network_linux_sendfile.c + network_sendfile.c options.c plugin.c profiler.c