int network_write_mem_chunk(server *srv, connection *con, int fd, chunkqueue *cq, off_t *p_max_bytes) { chunk* const c = cq->first; off_t c_len; ssize_t r; UNUSED(con); force_assert(NULL != c); force_assert(MEM_CHUNK == c->type); force_assert(c->offset >= 0 && c->offset <= (off_t)buffer_string_length(c->mem)); c_len = buffer_string_length(c->mem) - c->offset; if (c_len > *p_max_bytes) c_len = *p_max_bytes; if (0 == c_len) { chunkqueue_remove_finished_chunks(cq); return 0; } #if defined(__WIN32) if ((r = send(fd, c->mem->ptr + c->offset, c_len, 0)) < 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 = write(fd, c->mem->ptr + c->offset, c_len)) < 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 == c_len) ? 0 : -3; }
int network_writev_mem_chunks(server *srv, connection *con, int fd, chunkqueue *cq, off_t *p_max_bytes) { struct iovec chunks[MAX_CHUNKS]; size_t num_chunks; off_t max_bytes = *p_max_bytes; off_t toSend; ssize_t r; UNUSED(con); force_assert(NULL != cq->first); force_assert(MEM_CHUNK == cq->first->type); { chunk const *c; toSend = 0; num_chunks = 0; for (c = cq->first; NULL != c && MEM_CHUNK == c->type && num_chunks < MAX_CHUNKS && toSend < max_bytes; c = c->next) { size_t c_len; force_assert(c->offset >= 0 && c->offset <= (off_t)buffer_string_length(c->mem)); c_len = buffer_string_length(c->mem) - c->offset; if (c_len > 0) { toSend += c_len; chunks[num_chunks].iov_base = c->mem->ptr + c->offset; chunks[num_chunks].iov_len = c_len; ++num_chunks; } } } if (0 == num_chunks) { chunkqueue_remove_finished_chunks(cq); return 0; } r = writev(fd, chunks, num_chunks); if (r < 0) switch (errno) { case EAGAIN: case EINTR: break; case EPIPE: case ECONNRESET: return -2; default: log_error_write(srv, __FILE__, __LINE__, "ssd", "writev failed:", strerror(errno), fd); return -1; } if (r >= 0) { *p_max_bytes -= r; chunkqueue_mark_written(cq, r); } return (r > 0 && r == toSend) ? 0 : -3; }
int network_write_file_chunk_sendfile(server *srv, connection *con, int fd, chunkqueue *cq, off_t *p_max_bytes) { chunk* const c = cq->first; off_t offset, written = 0; off_t toSend; int r; 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; if (0 == toSend) { chunkqueue_remove_finished_chunks(cq); return 0; } if (0 != network_open_file_chunk(srv, con, cq)) return -1; /* Darwin sendfile() */ written = toSend; if (-1 == (r = sendfile(c->file.fd, fd, offset, &written, NULL, 0))) { switch(errno) { case EAGAIN: case EINTR: /* for EAGAIN/EINTR written still contains the sent bytes */ break; /* try again later */ case EPIPE: case ENOTCONN: return -2; default: log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile: ", strerror(errno), errno); return -1; } } if (written >= 0) { chunkqueue_mark_written(cq, written); *p_max_bytes -= written; } return (r >= 0 && written == toSend) ? 0 : -3; }
static handler_t cgi_handle_fdevent_send (server *srv, void *ctx, int revents) { handler_ctx *hctx = ctx; connection *con = hctx->remote_conn; /*(joblist only actually necessary here in mod_cgi fdevent send if returning HANDLER_ERROR)*/ joblist_append(srv, con); if (revents & FDEVENT_OUT) { if (0 != cgi_write_request(srv, hctx, hctx->fdtocgi)) { cgi_connection_close(srv, hctx); return HANDLER_ERROR; } /* more request body to be sent to CGI */ } if (revents & FDEVENT_HUP) { /* skip sending remaining data to CGI */ if (con->request.content_length) { chunkqueue *cq = con->request_content_queue; chunkqueue_mark_written(cq, chunkqueue_length(cq)); if (cq->bytes_in != (off_t)con->request.content_length) { con->keep_alive = 0; } } cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/ } else if (revents & FDEVENT_ERR) { /* kill all connections to the cgi process */ #if 1 log_error_write(srv, __FILE__, __LINE__, "s", "cgi-FDEVENT_ERR"); #endif cgi_connection_close(srv, hctx); return HANDLER_ERROR; } return HANDLER_FINISHED; }
int network_write_chunkqueue_openssl(server *srv, connection *con, SSL *ssl, chunkqueue *cq, off_t max_bytes) { /* the remote side closed the connection before without shutdown request * - IE * - wget * if keep-alive is disabled */ if (con->keep_alive == 0) { SSL_set_shutdown(ssl, SSL_RECEIVED_SHUTDOWN); } chunkqueue_remove_finished_chunks(cq); while (max_bytes > 0 && NULL != cq->first) { const char *data; size_t data_len; int r; if (0 != load_next_chunk(srv, con, cq, max_bytes, &data, &data_len)) return -1; /** * SSL_write man-page * * WARNING * When an SSL_write() operation has to be repeated because of * SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, it must be * repeated with the same arguments. */ ERR_clear_error(); r = SSL_write(ssl, data, data_len); if (con->renegotiations > 1 && con->conf.ssl_disable_client_renegotiation) { log_error_write(srv, __FILE__, __LINE__, "s", "SSL: renegotiation initiated by client, killing connection"); return -1; } if (r <= 0) { int ssl_r; unsigned long err; switch ((ssl_r = SSL_get_error(ssl, r))) { case SSL_ERROR_WANT_WRITE: return 0; /* try again later */ case SSL_ERROR_SYSCALL: /* perhaps we have error waiting in our error-queue */ if (0 != (err = ERR_get_error())) { do { log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:", ssl_r, r, ERR_error_string(err, NULL)); } while((err = ERR_get_error())); } else if (r == -1) { /* no, but we have errno */ switch(errno) { case EPIPE: case ECONNRESET: return -2; default: log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL:", ssl_r, r, errno, strerror(errno)); break; } } else { /* neither error-queue nor errno ? */ log_error_write(srv, __FILE__, __LINE__, "sddds", "SSL (error):", ssl_r, r, errno, strerror(errno)); } break; case SSL_ERROR_ZERO_RETURN: /* clean shutdown on the remote side */ if (r == 0) return -2; /* fall through */ default: while((err = ERR_get_error())) { log_error_write(srv, __FILE__, __LINE__, "sdds", "SSL:", ssl_r, r, ERR_error_string(err, NULL)); } break; } return -1; } chunkqueue_mark_written(cq, r); max_bytes -= r; if ((size_t) r < data_len) break; /* try again later */ } return 0; }
static int cgi_write_request(server *srv, handler_ctx *hctx, int fd) { connection *con = hctx->remote_conn; chunkqueue *cq = con->request_content_queue; chunk *c; /* old comment: windows doesn't support select() on pipes - wouldn't be easy to fix for all platforms. * solution: if this is still a problem on windows, then substitute * socketpair() for pipe() and closesocket() for close() on windows. */ for (c = cq->first; c; c = cq->first) { ssize_t r = -1; switch(c->type) { case FILE_CHUNK: r = cgi_write_file_chunk_mmap(srv, con, fd, cq); break; case MEM_CHUNK: if ((r = write(fd, c->mem->ptr + c->offset, buffer_string_length(c->mem) - c->offset)) < 0) { switch(errno) { case EAGAIN: case EINTR: /* ignore and try again */ r = 0; break; case EPIPE: case ECONNRESET: /* connection closed */ r = -2; break; default: /* fatal error */ log_error_write(srv, __FILE__, __LINE__, "ss", "write failed due to: ", strerror(errno)); r = -1; break; } } else if (r > 0) { chunkqueue_mark_written(cq, r); } break; } if (0 == r) break; /*(might block)*/ switch (r) { case -1: /* fatal error */ return -1; case -2: /* connection reset */ log_error_write(srv, __FILE__, __LINE__, "s", "failed to send post data to cgi, connection closed by CGI"); /* skip all remaining data */ chunkqueue_mark_written(cq, chunkqueue_length(cq)); break; default: break; } } if (cq->bytes_out == (off_t)con->request.content_length) { /* sent all request body input */ /* close connection to the cgi-script */ if (-1 == hctx->fdtocgi) { /*(received request body sent in initial send to pipe buffer)*/ --srv->cur_fds; if (close(fd)) { log_error_write(srv, __FILE__, __LINE__, "sds", "cgi stdin close failed ", fd, strerror(errno)); } } else { cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/ } } else { off_t cqlen = cq->bytes_in - cq->bytes_out; if (cq->bytes_in != con->request.content_length && cqlen < 65536 - 16384) { /*(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/ if (!(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_POLLIN)) { con->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN; con->is_readable = 1; /* trigger optimistic read from client */ } } if (-1 == hctx->fdtocgi) { /*(not registered yet)*/ hctx->fdtocgi = fd; hctx->fde_ndx_tocgi = -1; fdevent_register(srv->ev, hctx->fdtocgi, cgi_handle_fdevent_send, hctx); } if (0 == cqlen) { /*(chunkqueue_is_empty(cq))*/ if ((fdevent_event_get_interest(srv->ev, hctx->fdtocgi) & FDEVENT_OUT)) { fdevent_event_set(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi, 0); } } else { /* more request body remains to be sent to CGI so register for fdevents */ fdevent_event_set(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi, FDEVENT_OUT); } } return 0; }
/* similar to network_write_file_chunk_mmap, but doesn't use send on windows (because we're on pipes), * also mmaps and sends complete chunk instead of only small parts - the files * are supposed to be temp files with reasonable chunk sizes. * * Also always use mmap; the files are "trusted", as we created them. */ static ssize_t cgi_write_file_chunk_mmap(server *srv, connection *con, int fd, chunkqueue *cq) { chunk* const c = cq->first; off_t offset, toSend, file_end; ssize_t r; size_t mmap_offset, mmap_avail; 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; 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; } /*(simplified from network_write_no_mmap.c:network_open_file_chunk())*/ UNUSED(con); if (-1 == c->file.fd) { if (-1 == (c->file.fd = fdevent_open_cloexec(c->file.name->ptr, O_RDONLY, 0))) { log_error_write(srv, __FILE__, __LINE__, "ssb", "open failed:", strerror(errno), c->file.name); return -1; } } /* (re)mmap the buffer if range is not covered completely */ if (MAP_FAILED == c->file.mmap.start || offset < c->file.mmap.offset || file_end > (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; } c->file.mmap.offset = mmap_align_offset(offset); 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_PRIVATE, c->file.fd, c->file.mmap.offset))) { if (toSend > 65536) toSend = 65536; data = malloc(toSend); force_assert(data); if (-1 == lseek(c->file.fd, offset, SEEK_SET) || 0 >= (toSend = read(c->file.fd, data, toSend))) { if (-1 == toSend) { log_error_write(srv, __FILE__, __LINE__, "ssbdo", "lseek/read failed:", strerror(errno), c->file.name, c->file.fd, offset); } else { /*(0 == toSend)*/ log_error_write(srv, __FILE__, __LINE__, "sbdo", "unexpected EOF (input truncated?):", c->file.name, c->file.fd, offset); } free(data); return -1; } } } if (MAP_FAILED != c->file.mmap.start) { 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; force_assert(toSend <= (off_t) mmap_avail); data = c->file.mmap.start + mmap_offset; } r = write(fd, data, toSend); if (MAP_FAILED == c->file.mmap.start) free(data); if (r < 0) { switch (errno) { case EAGAIN: case EINTR: return 0; case EPIPE: case ECONNRESET: return -2; default: log_error_write(srv, __FILE__, __LINE__, "ssd", "write failed:", strerror(errno), fd); return -1; } } if (r >= 0) { chunkqueue_mark_written(cq, r); } return r; }
int network_write_file_chunk_sendfile(server *srv, connection *con, int fd, chunkqueue *cq, off_t *p_max_bytes) { chunk* const c = cq->first; off_t offset, written = 0; off_t toSend; int r; 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; if (0 == toSend) { chunkqueue_remove_finished_chunks(cq); return 0; } if (0 != network_open_file_chunk(srv, con, cq)) return -1; /* FreeBSD sendfile() */ if (-1 == (r = sendfile(c->file.fd, fd, offset, toSend, NULL, &written, 0))) { switch(errno) { case EAGAIN: case EINTR: /* for EAGAIN/EINTR written still contains the sent bytes */ break; /* try again later */ case EPIPE: case ENOTCONN: return -2; case EINVAL: case ENOSYS: #if defined(ENOTSUP) \ && (!defined(EOPNOTSUPP) || EOPNOTSUPP != ENOTSUP) case ENOTSUP: #endif #ifdef EOPNOTSUPP case EOPNOTSUPP: #endif #ifdef ESOCKTNOSUPPORT case ESOCKTNOSUPPORT: #endif #ifdef EAFNOSUPPORT case EAFNOSUPPORT: #endif #ifdef USE_MMAP return network_write_file_chunk_mmap(srv, con, fd, cq, p_max_bytes); #else return network_write_file_chunk_no_mmap(srv, con, fd, cq, p_max_bytes); #endif default: log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile: ", strerror(errno), errno); return -1; } } if (written >= 0) { chunkqueue_mark_written(cq, written); *p_max_bytes -= written; } return (r >= 0 && written == toSend) ? 0 : -3; }
static handler_t deflate_compress_response(server *srv, connection *con, handler_ctx *hctx) { off_t len, max; int close_stream; /* move all chunk from write_queue into our in_queue, then adjust * counters since con->write_queue is reused for compressed output */ len = chunkqueue_length(con->write_queue); chunkqueue_remove_finished_chunks(con->write_queue); chunkqueue_append_chunkqueue(hctx->in_queue, con->write_queue); con->write_queue->bytes_in -= len; con->write_queue->bytes_out -= len; max = chunkqueue_length(hctx->in_queue); #if 0 /* calculate max bytes to compress for this call */ if (p->conf.sync_flush && max > (len = p->conf.work_block_size << 10)) { max = len; } #endif /* Compress chunks from in_queue into chunks for write_queue */ while (max) { chunk *c = hctx->in_queue->first; switch(c->type) { case MEM_CHUNK: len = buffer_string_length(c->mem) - c->offset; if (len > max) len = max; if (mod_deflate_compress(srv, con, hctx, (unsigned char *)c->mem->ptr+c->offset, len) < 0) { log_error_write(srv, __FILE__, __LINE__, "s", "compress failed."); return HANDLER_ERROR; } break; case FILE_CHUNK: len = c->file.length - c->offset; if (len > max) len = max; if ((len = mod_deflate_file_chunk(srv, con, hctx, c, len)) < 0) { log_error_write(srv, __FILE__, __LINE__, "s", "compress file chunk failed."); return HANDLER_ERROR; } break; default: log_error_write(srv, __FILE__, __LINE__, "ds", c, "type not known"); return HANDLER_ERROR; } max -= len; chunkqueue_mark_written(hctx->in_queue, len); } /*(currently should always be true)*/ /*(current implementation requires response be complete)*/ close_stream = (con->file_finished && chunkqueue_is_empty(hctx->in_queue)); if (mod_deflate_stream_flush(srv, con, hctx, close_stream) < 0) { log_error_write(srv, __FILE__, __LINE__, "s", "flush error"); return HANDLER_ERROR; } return close_stream ? HANDLER_FINISHED : HANDLER_GO_ON; }
int network_write_file_chunk_sendfile(server *srv, connection *con, int fd, chunkqueue *cq, off_t *p_max_bytes) { chunk* const c = cq->first; ssize_t r; off_t offset; off_t toSend; 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; if (0 == toSend) { chunkqueue_remove_finished_chunks(cq); return 0; } if (0 != network_open_file_chunk(srv, con, cq)) return -1; if (-1 == (r = sendfile(fd, c->file.fd, &offset, toSend))) { switch (errno) { case EAGAIN: case EINTR: break; case EPIPE: case ECONNRESET: return -2; case EINVAL: case ENOSYS: #if defined(ENOTSUP) \ && (!defined(EOPNOTSUPP) || EOPNOTSUPP != ENOTSUP) case ENOTSUP: #endif #ifdef EOPNOTSUPP case EOPNOTSUPP: #endif #ifdef ESOCKTNOSUPPORT case ESOCKTNOSUPPORT: #endif #ifdef EAFNOSUPPORT case EAFNOSUPPORT: #endif #ifdef USE_MMAP return network_write_file_chunk_mmap(srv, con, fd, cq, p_max_bytes); #else return network_write_file_chunk_no_mmap(srv, con, fd, cq, p_max_bytes); #endif default: log_error_write(srv, __FILE__, __LINE__, "ssd", "sendfile failed:", strerror(errno), fd); return -1; } } if (r >= 0) { chunkqueue_mark_written(cq, r); *p_max_bytes -= r; } return (r > 0 && r == toSend) ? 0 : -3; }
int network_write_file_chunk_no_mmap(server *srv, connection *con, int fd, chunkqueue *cq, off_t *p_max_bytes) { chunk* const c = cq->first; off_t offset, toSend; ssize_t r; 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 > 64*1024) toSend = 64*1024; /* max read 64kb in one step */ if (toSend > *p_max_bytes) toSend = *p_max_bytes; if (0 == toSend) { chunkqueue_remove_finished_chunks(cq); return 0; } if (0 != network_open_file_chunk(srv, con, cq)) return -1; buffer_string_prepare_copy(srv->tmp_buf, toSend); if (-1 == lseek(c->file.fd, offset, SEEK_SET)) { log_error_write(srv, __FILE__, __LINE__, "ss", "lseek: ", strerror(errno)); return -1; } if (-1 == (toSend = read(c->file.fd, srv->tmp_buf->ptr, toSend))) { log_error_write(srv, __FILE__, __LINE__, "ss", "read: ", strerror(errno)); return -1; } #if defined(__WIN32) if ((r = send(fd, srv->tmp_buf->ptr, toSend, 0)) < 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 = write(fd, srv->tmp_buf->ptr, toSend)) < 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; }