version 1.1.1.2, 2014/06/15 20:20:06
|
version 1.1.1.3, 2016/11/02 10:35:00
|
Line 1
|
Line 1
|
|
#include "first.h" |
|
|
#include "network_backends.h" |
#include "network_backends.h" |
|
|
#ifdef USE_LINUX_SENDFILE | #if defined(USE_LINUX_SENDFILE) |
|
|
#include "network.h" |
#include "network.h" |
#include "fdevent.h" |
|
#include "log.h" |
#include "log.h" |
#include "stat_cache.h" |
|
|
|
#include <sys/types.h> | #include <sys/sendfile.h> |
#include <sys/socket.h> | |
#include <sys/stat.h> | |
#include <sys/time.h> | |
#include <sys/resource.h> | |
|
|
#include <netinet/in.h> |
|
#include <netinet/tcp.h> |
|
|
|
#include <errno.h> |
#include <errno.h> |
#include <fcntl.h> |
|
#include <unistd.h> |
|
#include <netdb.h> |
|
#include <string.h> |
#include <string.h> |
#include <stdlib.h> |
|
#include <fcntl.h> |
|
|
|
/* on linux 2.4.29 + debian/ubuntu we have crashes if this is enabled */ | int network_write_file_chunk_sendfile(server *srv, connection *con, int fd, chunkqueue *cq, off_t *p_max_bytes) { |
#undef HAVE_POSIX_FADVISE | chunk* const c = cq->first; |
| ssize_t r; |
| off_t offset; |
| off_t toSend; |
|
|
int network_write_chunkqueue_linuxsendfile(server *srv, connection *con, int fd, chunkqueue *cq, off_t max_bytes) { | force_assert(NULL != c); |
chunk *c; | force_assert(FILE_CHUNK == c->type); |
| force_assert(c->offset >= 0 && c->offset <= c->file.length); |
|
|
for(c = cq->first; (max_bytes > 0) && (NULL != c); c = c->next) { | offset = c->file.start + c->offset; |
int chunk_finished = 0; | toSend = c->file.length - c->offset; |
| if (toSend > *p_max_bytes) toSend = *p_max_bytes; |
|
|
switch(c->type) { | if (0 == toSend) { |
case MEM_CHUNK: { | chunkqueue_remove_finished_chunks(cq); |
char * offset; | return 0; |
off_t toSend; | } |
ssize_t r; | |
|
|
size_t num_chunks, i; | if (0 != network_open_file_chunk(srv, con, cq)) return -1; |
struct iovec chunks[UIO_MAXIOV]; | |
chunk *tc; | |
size_t num_bytes = 0; | |
|
|
/* build writev list | if (-1 == (r = sendfile(fd, c->file.fd, &offset, toSend))) { |
* | switch (errno) { |
* 1. limit: num_chunks < UIO_MAXIOV | case EAGAIN: |
* 2. limit: num_bytes < max_bytes | case EINTR: |
*/ | |
for (num_chunks = 0, tc = c; | |
tc && tc->type == MEM_CHUNK && num_chunks < UIO_MAXIOV; | |
tc = tc->next, num_chunks++); | |
| |
for (tc = c, i = 0; i < num_chunks; tc = tc->next, i++) { | |
if (tc->mem->used == 0) { | |
chunks[i].iov_base = tc->mem->ptr; | |
chunks[i].iov_len = 0; | |
} else { | |
offset = tc->mem->ptr + tc->offset; | |
toSend = tc->mem->used - 1 - tc->offset; | |
| |
chunks[i].iov_base = offset; | |
| |
/* protect the return value of writev() */ | |
if (toSend > max_bytes || | |
(off_t) num_bytes + toSend > max_bytes) { | |
chunks[i].iov_len = max_bytes - num_bytes; | |
| |
num_chunks = i + 1; | |
break; | |
} else { | |
chunks[i].iov_len = toSend; | |
} | |
| |
num_bytes += toSend; | |
} | |
} | |
| |
if ((r = writev(fd, chunks, num_chunks)) < 0) { | |
switch (errno) { | |
case EAGAIN: | |
case EINTR: | |
r = 0; | |
break; | |
case EPIPE: | |
case ECONNRESET: | |
return -2; | |
default: | |
log_error_write(srv, __FILE__, __LINE__, "ssd", | |
"writev failed:", strerror(errno), fd); | |
| |
return -1; | |
} | |
} | |
| |
/* check which chunks have been written */ | |
cq->bytes_out += r; | |
max_bytes -= r; | |
| |
for(i = 0, tc = c; i < num_chunks; i++, tc = tc->next) { | |
if (r >= (ssize_t)chunks[i].iov_len) { | |
/* written */ | |
r -= chunks[i].iov_len; | |
tc->offset += chunks[i].iov_len; | |
| |
if (chunk_finished) { | |
/* skip the chunks from further touches */ | |
c = c->next; | |
} else { | |
/* chunks_written + c = c->next is done in the for()*/ | |
chunk_finished = 1; | |
} | |
} else { | |
/* partially written */ | |
| |
tc->offset += r; | |
chunk_finished = 0; | |
| |
break; | |
} | |
} | |
| |
break; |
break; |
} | case EPIPE: |
case FILE_CHUNK: { | case ECONNRESET: |
ssize_t r; | return -2; |
off_t offset; | case EINVAL: |
off_t toSend; | case ENOSYS: |
stat_cache_entry *sce = NULL; | #if defined(ENOTSUP) \ |
| && (!defined(EOPNOTSUPP) || EOPNOTSUPP != ENOTSUP) |
offset = c->file.start + c->offset; | case ENOTSUP: |
toSend = c->file.length - c->offset; | #endif |
if (toSend > max_bytes) toSend = max_bytes; | #ifdef EOPNOTSUPP |
| case EOPNOTSUPP: |
/* open file if not already opened */ | #endif |
if (-1 == c->file.fd) { | #ifdef ESOCKTNOSUPPORT |
if (-1 == (c->file.fd = open(c->file.name->ptr, O_RDONLY))) { | case ESOCKTNOSUPPORT: |
log_error_write(srv, __FILE__, __LINE__, "ss", "open failed: ", strerror(errno)); | #endif |
| #ifdef EAFNOSUPPORT |
return -1; | case EAFNOSUPPORT: |
} | #endif |
fd_close_on_exec(c->file.fd); | #ifdef USE_MMAP |
#ifdef HAVE_POSIX_FADVISE | return network_write_file_chunk_mmap(srv, con, fd, cq, p_max_bytes); |
/* tell the kernel that we want to stream the file */ | #else |
if (-1 == posix_fadvise(c->file.fd, 0, 0, POSIX_FADV_SEQUENTIAL)) { | return network_write_file_chunk_no_mmap(srv, con, fd, cq, p_max_bytes); |
if (ENOSYS != errno) { | #endif |
log_error_write(srv, __FILE__, __LINE__, "ssd", | |
"posix_fadvise failed:", strerror(errno), c->file.fd); | |
} | |
} | |
#endif | |
} | |
| |
if (-1 == (r = sendfile(fd, c->file.fd, &offset, toSend))) { | |
switch (errno) { | |
case EAGAIN: | |
case EINTR: | |
/* ok, we can't send more, let's try later again */ | |
r = 0; | |
break; | |
case EPIPE: | |
case ECONNRESET: | |
return -2; | |
default: | |
log_error_write(srv, __FILE__, __LINE__, "ssd", | |
"sendfile failed:", strerror(errno), fd); | |
return -1; | |
} | |
} else if (r == 0) { | |
int oerrno = errno; | |
/* We got an event to write but we wrote nothing | |
* | |
* - the file shrinked -> error | |
* - the remote side closed inbetween -> remote-close */ | |
| |
if (HANDLER_ERROR == stat_cache_get_entry(srv, con, c->file.name, &sce)) { | |
/* file is gone ? */ | |
return -1; | |
} | |
| |
if (offset > sce->st.st_size) { | |
/* file shrinked, close the connection */ | |
errno = oerrno; | |
| |
return -1; | |
} | |
| |
errno = oerrno; | |
return -2; | |
} | |
| |
#ifdef HAVE_POSIX_FADVISE | |
#if 0 | |
#define K * 1024 | |
#define M * 1024 K | |
#define READ_AHEAD 4 M | |
/* check if we need a new chunk */ | |
if ((c->offset & ~(READ_AHEAD - 1)) != ((c->offset + r) & ~(READ_AHEAD - 1))) { | |
/* tell the kernel that we want to stream the file */ | |
if (-1 == posix_fadvise(c->file.fd, (c->offset + r) & ~(READ_AHEAD - 1), READ_AHEAD, POSIX_FADV_NOREUSE)) { | |
log_error_write(srv, __FILE__, __LINE__, "ssd", | |
"posix_fadvise failed:", strerror(errno), c->file.fd); | |
} | |
} | |
#endif | |
#endif | |
| |
c->offset += r; | |
cq->bytes_out += r; | |
max_bytes -= r; | |
| |
if (c->offset == c->file.length) { | |
chunk_finished = 1; | |
| |
/* chunk_free() / chunk_reset() will cleanup for us but it is a ok to be faster :) */ | |
| |
if (c->file.fd != -1) { | |
close(c->file.fd); | |
c->file.fd = -1; | |
} | |
} | |
| |
break; | |
} | |
default: |
default: |
| log_error_write(srv, __FILE__, __LINE__, "ssd", |
log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known"); | "sendfile failed:", strerror(errno), fd); |
| |
return -1; |
return -1; |
} |
} |
|
} |
|
|
if (!chunk_finished) { | if (r >= 0) { |
/* not finished yet */ | chunkqueue_mark_written(cq, r); |
| *p_max_bytes -= r; |
break; | |
} | |
} |
} |
|
|
return 0; | return (r > 0 && r == toSend) ? 0 : -3; |
} |
} |
|
|
#endif | #endif /* USE_LINUX_SENDFILE */ |
#if 0 | |
network_linuxsendfile_init(void) { | |
p->write = network_linuxsendfile_write_chunkset; | |
} | |
#endif | |