#include "first.h" #include "network_backends.h" #include "network.h" #include "log.h" #include "sys-mmap.h" #include #include #include #include #include #define MMAP_CHUNK_SIZE (512*1024) off_t mmap_align_offset(off_t start) { static long pagesize = 0; if (0 == pagesize) { pagesize = sysconf(_SC_PAGESIZE); force_assert(pagesize < MMAP_CHUNK_SIZE); } force_assert(start >= (start % pagesize)); return start - (start % pagesize); } #if defined(USE_MMAP) static volatile int sigbus_jmp_valid; static sigjmp_buf sigbus_jmp; static void sigbus_handler(int sig) { UNUSED(sig); if (sigbus_jmp_valid) siglongjmp(sigbus_jmp, 1); log_failed_assert(__FILE__, __LINE__, "SIGBUS"); } #if 0 /* read mmap()ed data into local buffer */ #define LOCAL_BUFFERING 1 #endif int network_write_file_chunk_mmap(server *srv, connection *con, int fd, chunkqueue *cq, off_t *p_max_bytes) { chunk* const c = cq->first; off_t offset, toSend, file_end; ssize_t r; size_t mmap_offset, mmap_avail; const char *data; force_assert(NULL != c); force_assert(FILE_CHUNK == c->type); force_assert(c->offset >= 0 && c->offset <= c->file.length); offset = c->file.start + c->offset; toSend = c->file.length - c->offset; if (toSend > *p_max_bytes) toSend = *p_max_bytes; file_end = c->file.start + c->file.length; /* offset to file end in this chunk */ if (0 == toSend) { chunkqueue_remove_finished_chunks(cq); return 0; } if (0 != network_open_file_chunk(srv, con, cq)) return -1; /* setup SIGBUS handler, but don't activate sigbus_jmp_valid yet */ if (0 != sigsetjmp(sigbus_jmp, 1)) { sigbus_jmp_valid = 0; log_error_write(srv, __FILE__, __LINE__, "sbd", "SIGBUS in mmap:", c->file.name, c->file.fd); munmap(c->file.mmap.start, c->file.mmap.length); c->file.mmap.start = MAP_FAILED; #ifdef LOCAL_BUFFERING buffer_reset(c->mem); #endif return -1; } signal(SIGBUS, sigbus_handler); /* mmap the buffer if offset is outside old mmap area or not mapped at all */ if (MAP_FAILED == c->file.mmap.start || offset < c->file.mmap.offset || offset >= (off_t)(c->file.mmap.offset + c->file.mmap.length)) { if (MAP_FAILED != c->file.mmap.start) { munmap(c->file.mmap.start, c->file.mmap.length); c->file.mmap.start = MAP_FAILED; } /* Optimizations for the future: * * adaptive mem-mapping * the problem: * we mmap() the whole file. If someone has alot large files and 32bit * machine the virtual address area will be unrun and we will have a failing * mmap() call. * solution: * only mmap 16M in one chunk and move the window as soon as we have finished * the first 8M * * read-ahead buffering * the problem: * sending out several large files in parallel trashes the read-ahead of the * kernel leading to long wait-for-seek times. * solutions: (increasing complexity) * 1. use madvise * 2. use a internal read-ahead buffer in the chunk-structure * 3. use non-blocking IO for file-transfers * */ c->file.mmap.offset = mmap_align_offset(offset); /* all mmap()ed areas are MMAP_CHUNK_SIZE except the last which might be smaller */ c->file.mmap.length = MMAP_CHUNK_SIZE; if (c->file.mmap.offset > file_end - (off_t)c->file.mmap.length) { c->file.mmap.length = file_end - c->file.mmap.offset; } if (MAP_FAILED == (c->file.mmap.start = mmap(NULL, c->file.mmap.length, PROT_READ, MAP_SHARED, c->file.fd, c->file.mmap.offset))) { log_error_write(srv, __FILE__, __LINE__, "ssbdoo", "mmap failed:", strerror(errno), c->file.name, c->file.fd, c->file.mmap.offset, (off_t) c->file.mmap.length); return -1; } #if defined(LOCAL_BUFFERING) sigbus_jmp_valid = 1; buffer_copy_string_len(c->mem, c->file.mmap.start, c->file.mmap.length); sigbus_jmp_valid = 0; #else # if defined(HAVE_MADVISE) /* don't advise files < 64Kb */ if (c->file.mmap.length > (64*1024)) { /* darwin 7 is returning EINVAL all the time and I don't know how to * detect this at runtime. * * ignore the return value for now */ madvise(c->file.mmap.start, c->file.mmap.length, MADV_WILLNEED); } # endif #endif } force_assert(offset >= c->file.mmap.offset); mmap_offset = offset - c->file.mmap.offset; force_assert(c->file.mmap.length > mmap_offset); mmap_avail = c->file.mmap.length - mmap_offset; if (toSend > (off_t) mmap_avail) toSend = mmap_avail; #if defined(LOCAL_BUFFERING) data = c->mem->ptr + mmap_offset; #else data = c->file.mmap.start + mmap_offset; #endif sigbus_jmp_valid = 1; #if defined(__WIN32) r = send(fd, data, toSend, 0); #else /* __WIN32 */ r = write(fd, data, toSend); #endif /* __WIN32 */ sigbus_jmp_valid = 0; #if defined(__WIN32) if (r < 0) { int lastError = WSAGetLastError(); switch (lastError) { case WSAEINTR: case WSAEWOULDBLOCK: break; case WSAECONNRESET: case WSAETIMEDOUT: case WSAECONNABORTED: return -2; default: log_error_write(srv, __FILE__, __LINE__, "sdd", "send failed: ", lastError, fd); return -1; } } #else /* __WIN32 */ if (r < 0) { switch (errno) { case EAGAIN: case EINTR: break; case EPIPE: case ECONNRESET: return -2; default: log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed:", strerror(errno), fd); return -1; } } #endif /* __WIN32 */ if (r >= 0) { *p_max_bytes -= r; chunkqueue_mark_written(cq, r); } return (r > 0 && r == toSend) ? 0 : -3; } #endif /* USE_MMAP */