| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @file core_filters.c |
| * @brief Core input/output network filters. |
| */ |
| |
| #include "apr.h" |
| #include "apr_strings.h" |
| #include "apr_lib.h" |
| #include "apr_fnmatch.h" |
| #include "apr_hash.h" |
| #include "apr_thread_proc.h" /* for RLIMIT stuff */ |
| |
| #define APR_WANT_IOVEC |
| #define APR_WANT_STRFUNC |
| #define APR_WANT_MEMFUNC |
| #include "apr_want.h" |
| |
| #include "ap_config.h" |
| #include "httpd.h" |
| #include "http_config.h" |
| #include "http_core.h" |
| #include "http_protocol.h" /* For index_of_response(). Grump. */ |
| #include "http_request.h" |
| #include "http_vhost.h" |
| #include "http_main.h" /* For the default_handler below... */ |
| #include "http_log.h" |
| #include "util_md5.h" |
| #include "http_connection.h" |
| #include "apr_buckets.h" |
| #include "util_filter.h" |
| #include "util_ebcdic.h" |
| #include "mpm_common.h" |
| #include "scoreboard.h" |
| #include "mod_core.h" |
| #include "ap_listen.h" |
| |
| #include "mod_so.h" /* for ap_find_loaded_module_symbol */ |
| |
| #define AP_MIN_SENDFILE_BYTES (256) |
| |
| /** |
| * Remove all zero length buckets from the brigade. |
| */ |
| #define BRIGADE_NORMALIZE(b) \ |
| do { \ |
| apr_bucket *e = APR_BRIGADE_FIRST(b); \ |
| do { \ |
| if (e->length == 0 && !APR_BUCKET_IS_METADATA(e)) { \ |
| apr_bucket *d; \ |
| d = APR_BUCKET_NEXT(e); \ |
| apr_bucket_delete(e); \ |
| e = d; \ |
| } \ |
| else { \ |
| e = APR_BUCKET_NEXT(e); \ |
| } \ |
| } while (!APR_BRIGADE_EMPTY(b) && (e != APR_BRIGADE_SENTINEL(b))); \ |
| } while (0) |
| |
| /* we know core's module_index is 0 */ |
| #undef APLOG_MODULE_INDEX |
| #define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX |
| |
| struct core_output_filter_ctx { |
| apr_bucket_brigade *buffered_bb; |
| apr_bucket_brigade *tmp_flush_bb; |
| apr_pool_t *deferred_write_pool; |
| apr_size_t bytes_written; |
| }; |
| |
| struct core_filter_ctx { |
| apr_bucket_brigade *b; |
| apr_bucket_brigade *tmpbb; |
| }; |
| |
| |
| apr_status_t ap_core_input_filter(ap_filter_t *f, apr_bucket_brigade *b, |
| ap_input_mode_t mode, apr_read_type_e block, |
| apr_off_t readbytes) |
| { |
| apr_status_t rv; |
| core_net_rec *net = f->ctx; |
| core_ctx_t *ctx = net->in_ctx; |
| const char *str; |
| apr_size_t len; |
| |
| if (mode == AP_MODE_INIT) { |
| /* |
| * this mode is for filters that might need to 'initialize' |
| * a connection before reading request data from a client. |
| * NNTP over SSL for example needs to handshake before the |
| * server sends the welcome message. |
| * such filters would have changed the mode before this point |
| * is reached. however, protocol modules such as NNTP should |
| * not need to know anything about SSL. given the example, if |
| * SSL is not in the filter chain, AP_MODE_INIT is a noop. |
| */ |
| return APR_SUCCESS; |
| } |
| |
| if (!ctx) |
| { |
| net->in_ctx = ctx = apr_palloc(f->c->pool, sizeof(*ctx)); |
| ctx->b = apr_brigade_create(f->c->pool, f->c->bucket_alloc); |
| ctx->tmpbb = apr_brigade_create(f->c->pool, f->c->bucket_alloc); |
| /* seed the brigade with the client socket. */ |
| rv = ap_run_insert_network_bucket(f->c, ctx->b, net->client_socket); |
| if (rv != APR_SUCCESS) |
| return rv; |
| } |
| else if (APR_BRIGADE_EMPTY(ctx->b)) { |
| return APR_EOF; |
| } |
| |
| /* ### This is bad. */ |
| BRIGADE_NORMALIZE(ctx->b); |
| |
| /* check for empty brigade again *AFTER* BRIGADE_NORMALIZE() |
| * If we have lost our socket bucket (see above), we are EOF. |
| * |
| * Ideally, this should be returning SUCCESS with EOS bucket, but |
| * some higher-up APIs (spec. read_request_line via ap_rgetline) |
| * want an error code. */ |
| if (APR_BRIGADE_EMPTY(ctx->b)) { |
| return APR_EOF; |
| } |
| |
| if (mode == AP_MODE_GETLINE) { |
| /* we are reading a single LF line, e.g. the HTTP headers */ |
| rv = apr_brigade_split_line(b, ctx->b, block, HUGE_STRING_LEN); |
| /* We should treat EAGAIN here the same as we do for EOF (brigade is |
| * empty). We do this by returning whatever we have read. This may |
| * or may not be bogus, but is consistent (for now) with EOF logic. |
| */ |
| if (APR_STATUS_IS_EAGAIN(rv) && block == APR_NONBLOCK_READ) { |
| rv = APR_SUCCESS; |
| } |
| return rv; |
| } |
| |
| /* ### AP_MODE_PEEK is a horrific name for this mode because we also |
| * eat any CRLFs that we see. That's not the obvious intention of |
| * this mode. Determine whether anyone actually uses this or not. */ |
| if (mode == AP_MODE_EATCRLF) { |
| apr_bucket *e; |
| const char *c; |
| |
| /* The purpose of this loop is to ignore any CRLF (or LF) at the end |
| * of a request. Many browsers send extra lines at the end of POST |
| * requests. We use the PEEK method to determine if there is more |
| * data on the socket, so that we know if we should delay sending the |
| * end of one request until we have served the second request in a |
| * pipelined situation. We don't want to actually delay sending a |
| * response if the server finds a CRLF (or LF), becuause that doesn't |
| * mean that there is another request, just a blank line. |
| */ |
| while (1) { |
| if (APR_BRIGADE_EMPTY(ctx->b)) |
| return APR_EOF; |
| |
| e = APR_BRIGADE_FIRST(ctx->b); |
| |
| rv = apr_bucket_read(e, &str, &len, APR_NONBLOCK_READ); |
| |
| if (rv != APR_SUCCESS) |
| return rv; |
| |
| c = str; |
| while (c < str + len) { |
| if (*c == APR_ASCII_LF) |
| c++; |
| else if (*c == APR_ASCII_CR && *(c + 1) == APR_ASCII_LF) |
| c += 2; |
| else |
| return APR_SUCCESS; |
| } |
| |
| /* If we reach here, we were a bucket just full of CRLFs, so |
| * just toss the bucket. */ |
| /* FIXME: Is this the right thing to do in the core? */ |
| apr_bucket_delete(e); |
| } |
| return APR_SUCCESS; |
| } |
| |
| /* If mode is EXHAUSTIVE, we want to just read everything until the end |
| * of the brigade, which in this case means the end of the socket. |
| * To do this, we attach the brigade that has currently been setaside to |
| * the brigade that was passed down, and send that brigade back. |
| * |
| * NOTE: This is VERY dangerous to use, and should only be done with |
| * extreme caution. FWLIW, this would be needed by an MPM like Perchild; |
| * such an MPM can easily request the socket and all data that has been |
| * read, which means that it can pass it to the correct child process. |
| */ |
| if (mode == AP_MODE_EXHAUSTIVE) { |
| apr_bucket *e; |
| |
| /* Tack on any buckets that were set aside. */ |
| APR_BRIGADE_CONCAT(b, ctx->b); |
| |
| /* Since we've just added all potential buckets (which will most |
| * likely simply be the socket bucket) we know this is the end, |
| * so tack on an EOS too. */ |
| /* We have read until the brigade was empty, so we know that we |
| * must be EOS. */ |
| e = apr_bucket_eos_create(f->c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(b, e); |
| return APR_SUCCESS; |
| } |
| |
| /* read up to the amount they specified. */ |
| if (mode == AP_MODE_READBYTES || mode == AP_MODE_SPECULATIVE) { |
| apr_bucket *e; |
| |
| AP_DEBUG_ASSERT(readbytes > 0); |
| |
| e = APR_BRIGADE_FIRST(ctx->b); |
| rv = apr_bucket_read(e, &str, &len, block); |
| |
| if (APR_STATUS_IS_EAGAIN(rv) && block == APR_NONBLOCK_READ) { |
| /* getting EAGAIN for a blocking read is an error; for a |
| * non-blocking read, return an empty brigade. */ |
| return APR_SUCCESS; |
| } |
| else if (rv != APR_SUCCESS) { |
| return rv; |
| } |
| else if (block == APR_BLOCK_READ && len == 0) { |
| /* We wanted to read some bytes in blocking mode. We read |
| * 0 bytes. Hence, we now assume we are EOS. |
| * |
| * When we are in normal mode, return an EOS bucket to the |
| * caller. |
| * When we are in speculative mode, leave ctx->b empty, so |
| * that the next call returns an EOS bucket. |
| */ |
| apr_bucket_delete(e); |
| |
| if (mode == AP_MODE_READBYTES) { |
| e = apr_bucket_eos_create(f->c->bucket_alloc); |
| APR_BRIGADE_INSERT_TAIL(b, e); |
| } |
| return APR_SUCCESS; |
| } |
| |
| /* Have we read as much data as we wanted (be greedy)? */ |
| if (len < readbytes) { |
| apr_size_t bucket_len; |
| |
| rv = APR_SUCCESS; |
| /* We already registered the data in e in len */ |
| e = APR_BUCKET_NEXT(e); |
| while ((len < readbytes) && (rv == APR_SUCCESS) |
| && (e != APR_BRIGADE_SENTINEL(ctx->b))) { |
| /* Check for the availability of buckets with known length */ |
| if (e->length != -1) { |
| len += e->length; |
| e = APR_BUCKET_NEXT(e); |
| } |
| else { |
| /* |
| * Read from bucket, but non blocking. If there isn't any |
| * more data, well than this is fine as well, we will |
| * not wait for more since we already got some and we are |
| * only checking if there isn't more. |
| */ |
| rv = apr_bucket_read(e, &str, &bucket_len, |
| APR_NONBLOCK_READ); |
| if (rv == APR_SUCCESS) { |
| len += bucket_len; |
| e = APR_BUCKET_NEXT(e); |
| } |
| } |
| } |
| } |
| |
| /* We can only return at most what we read. */ |
| if (len < readbytes) { |
| readbytes = len; |
| } |
| |
| rv = apr_brigade_partition(ctx->b, readbytes, &e); |
| if (rv != APR_SUCCESS) { |
| return rv; |
| } |
| |
| /* Must do move before CONCAT */ |
| ctx->tmpbb = apr_brigade_split_ex(ctx->b, e, ctx->tmpbb); |
| |
| if (mode == AP_MODE_READBYTES) { |
| APR_BRIGADE_CONCAT(b, ctx->b); |
| } |
| else if (mode == AP_MODE_SPECULATIVE) { |
| apr_bucket *copy_bucket; |
| |
| for (e = APR_BRIGADE_FIRST(ctx->b); |
| e != APR_BRIGADE_SENTINEL(ctx->b); |
| e = APR_BUCKET_NEXT(e)) |
| { |
| rv = apr_bucket_copy(e, ©_bucket); |
| if (rv != APR_SUCCESS) { |
| return rv; |
| } |
| APR_BRIGADE_INSERT_TAIL(b, copy_bucket); |
| } |
| } |
| |
| /* Take what was originally there and place it back on ctx->b */ |
| APR_BRIGADE_CONCAT(ctx->b, ctx->tmpbb); |
| } |
| return APR_SUCCESS; |
| } |
| |
| static void setaside_remaining_output(ap_filter_t *f, |
| core_output_filter_ctx_t *ctx, |
| apr_bucket_brigade *bb, |
| conn_rec *c); |
| |
| static apr_status_t send_brigade_nonblocking(apr_socket_t *s, |
| apr_bucket_brigade *bb, |
| apr_size_t *bytes_written, |
| conn_rec *c); |
| |
| static void remove_empty_buckets(apr_bucket_brigade *bb); |
| |
| static apr_status_t send_brigade_blocking(apr_socket_t *s, |
| apr_bucket_brigade *bb, |
| apr_size_t *bytes_written, |
| conn_rec *c); |
| |
| static apr_status_t writev_nonblocking(apr_socket_t *s, |
| struct iovec *vec, apr_size_t nvec, |
| apr_bucket_brigade *bb, |
| apr_size_t *cumulative_bytes_written, |
| conn_rec *c); |
| |
| #if APR_HAS_SENDFILE |
| static apr_status_t sendfile_nonblocking(apr_socket_t *s, |
| apr_bucket *bucket, |
| apr_size_t *cumulative_bytes_written, |
| conn_rec *c); |
| #endif |
| |
| /* XXX: Should these be configurable parameters? */ |
| #define THRESHOLD_MIN_WRITE 4096 |
| #define THRESHOLD_MAX_BUFFER 65536 |
| #define MAX_REQUESTS_IN_PIPELINE 5 |
| |
| /* Optional function coming from mod_logio, used for logging of output |
| * traffic |
| */ |
| extern APR_OPTIONAL_FN_TYPE(ap_logio_add_bytes_out) *ap__logio_add_bytes_out; |
| |
| apr_status_t ap_core_output_filter(ap_filter_t *f, apr_bucket_brigade *new_bb) |
| { |
| conn_rec *c = f->c; |
| core_net_rec *net = f->ctx; |
| core_output_filter_ctx_t *ctx = net->out_ctx; |
| apr_bucket_brigade *bb = NULL; |
| apr_bucket *bucket, *next, *flush_upto = NULL; |
| apr_size_t bytes_in_brigade, non_file_bytes_in_brigade; |
| int eor_buckets_in_brigade, morphing_bucket_in_brigade; |
| apr_status_t rv; |
| |
| /* Fail quickly if the connection has already been aborted. */ |
| if (c->aborted) { |
| if (new_bb != NULL) { |
| apr_brigade_cleanup(new_bb); |
| } |
| return APR_ECONNABORTED; |
| } |
| |
| if (ctx == NULL) { |
| ctx = apr_pcalloc(c->pool, sizeof(*ctx)); |
| net->out_ctx = (core_output_filter_ctx_t *)ctx; |
| /* |
| * Need to create tmp brigade with correct lifetime. Passing |
| * NULL to apr_brigade_split_ex would result in a brigade |
| * allocated from bb->pool which might be wrong. |
| */ |
| ctx->tmp_flush_bb = apr_brigade_create(c->pool, c->bucket_alloc); |
| /* same for buffered_bb and ap_save_brigade */ |
| ctx->buffered_bb = apr_brigade_create(c->pool, c->bucket_alloc); |
| } |
| |
| if (new_bb != NULL) |
| bb = new_bb; |
| |
| if ((ctx->buffered_bb != NULL) && |
| !APR_BRIGADE_EMPTY(ctx->buffered_bb)) { |
| if (new_bb != NULL) { |
| APR_BRIGADE_PREPEND(bb, ctx->buffered_bb); |
| } |
| else { |
| bb = ctx->buffered_bb; |
| } |
| c->data_in_output_filters = 0; |
| } |
| else if (new_bb == NULL) { |
| return APR_SUCCESS; |
| } |
| |
| /* Scan through the brigade and decide whether to attempt a write, |
| * and how much to write, based on the following rules: |
| * |
| * 1) The new_bb is null: Do a nonblocking write of as much as |
| * possible: do a nonblocking write of as much data as possible, |
| * then save the rest in ctx->buffered_bb. (If new_bb == NULL, |
| * it probably means that the MPM is doing asynchronous write |
| * completion and has just determined that this connection |
| * is writable.) |
| * |
| * 2) Determine if and up to which bucket we need to do a blocking |
| * write: |
| * |
| * a) The brigade contains a flush bucket: Do a blocking write |
| * of everything up that point. |
| * |
| * b) The request is in CONN_STATE_HANDLER state, and the brigade |
| * contains at least THRESHOLD_MAX_BUFFER bytes in non-file |
| * buckets: Do blocking writes until the amount of data in the |
| * buffer is less than THRESHOLD_MAX_BUFFER. (The point of this |
| * rule is to provide flow control, in case a handler is |
| * streaming out lots of data faster than the data can be |
| * sent to the client.) |
| * |
| * c) The request is in CONN_STATE_HANDLER state, and the brigade |
| * contains at least MAX_REQUESTS_IN_PIPELINE EOR buckets: |
| * Do blocking writes until less than MAX_REQUESTS_IN_PIPELINE EOR |
| * buckets are left. (The point of this rule is to prevent too many |
| * FDs being kept open by pipelined requests, possibly allowing a |
| * DoS). |
| * |
| * d) The brigade contains a morphing bucket: If there was no other |
| * reason to do a blocking write yet, try reading the bucket. If its |
| * contents fit into memory before THRESHOLD_MAX_BUFFER is reached, |
| * everything is fine. Otherwise we need to do a blocking write the |
| * up to and including the morphing bucket, because ap_save_brigade() |
| * would read the whole bucket into memory later on. |
| * |
| * 3) Actually do the blocking write up to the last bucket determined |
| * by rules 2a-d. The point of doing only one flush is to make as |
| * few calls to writev() as possible. |
| * |
| * 4) If the brigade contains at least THRESHOLD_MIN_WRITE |
| * bytes: Do a nonblocking write of as much data as possible, |
| * then save the rest in ctx->buffered_bb. |
| */ |
| |
| if (new_bb == NULL) { |
| rv = send_brigade_nonblocking(net->client_socket, bb, |
| &(ctx->bytes_written), c); |
| if (rv != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(rv)) { |
| /* The client has aborted the connection */ |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c, |
| "core_output_filter: writing data to the network"); |
| apr_brigade_cleanup(bb); |
| c->aborted = 1; |
| return rv; |
| } |
| setaside_remaining_output(f, ctx, bb, c); |
| return APR_SUCCESS; |
| } |
| |
| bytes_in_brigade = 0; |
| non_file_bytes_in_brigade = 0; |
| eor_buckets_in_brigade = 0; |
| morphing_bucket_in_brigade = 0; |
| |
| for (bucket = APR_BRIGADE_FIRST(bb); bucket != APR_BRIGADE_SENTINEL(bb); |
| bucket = next) { |
| next = APR_BUCKET_NEXT(bucket); |
| |
| if (!APR_BUCKET_IS_METADATA(bucket)) { |
| if (bucket->length == (apr_size_t)-1) { |
| /* |
| * A setaside of morphing buckets would read everything into |
| * memory. Instead, we will flush everything up to and |
| * including this bucket. |
| */ |
| morphing_bucket_in_brigade = 1; |
| } |
| else { |
| bytes_in_brigade += bucket->length; |
| if (!APR_BUCKET_IS_FILE(bucket)) |
| non_file_bytes_in_brigade += bucket->length; |
| } |
| } |
| else if (AP_BUCKET_IS_EOR(bucket)) { |
| eor_buckets_in_brigade++; |
| } |
| |
| if (APR_BUCKET_IS_FLUSH(bucket) |
| || non_file_bytes_in_brigade >= THRESHOLD_MAX_BUFFER |
| || morphing_bucket_in_brigade |
| || eor_buckets_in_brigade > MAX_REQUESTS_IN_PIPELINE) { |
| /* this segment of the brigade MUST be sent before returning. */ |
| |
| if (APLOGctrace6(c)) { |
| char *reason = APR_BUCKET_IS_FLUSH(bucket) ? |
| "FLUSH bucket" : |
| (non_file_bytes_in_brigade >= THRESHOLD_MAX_BUFFER) ? |
| "THRESHOLD_MAX_BUFFER" : |
| morphing_bucket_in_brigade ? "morphing bucket" : |
| "MAX_REQUESTS_IN_PIPELINE"; |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, c, |
| "core_output_filter: flushing because of %s", |
| reason); |
| } |
| /* |
| * Defer the actual blocking write to avoid doing many writes. |
| */ |
| flush_upto = next; |
| |
| bytes_in_brigade = 0; |
| non_file_bytes_in_brigade = 0; |
| eor_buckets_in_brigade = 0; |
| morphing_bucket_in_brigade = 0; |
| } |
| } |
| |
| if (flush_upto != NULL) { |
| ctx->tmp_flush_bb = apr_brigade_split_ex(bb, flush_upto, |
| ctx->tmp_flush_bb); |
| rv = send_brigade_blocking(net->client_socket, bb, |
| &(ctx->bytes_written), c); |
| if (rv != APR_SUCCESS) { |
| /* The client has aborted the connection */ |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c, |
| "core_output_filter: writing data to the network"); |
| apr_brigade_cleanup(bb); |
| c->aborted = 1; |
| return rv; |
| } |
| APR_BRIGADE_CONCAT(bb, ctx->tmp_flush_bb); |
| } |
| |
| if (bytes_in_brigade >= THRESHOLD_MIN_WRITE) { |
| rv = send_brigade_nonblocking(net->client_socket, bb, |
| &(ctx->bytes_written), c); |
| if ((rv != APR_SUCCESS) && (!APR_STATUS_IS_EAGAIN(rv))) { |
| /* The client has aborted the connection */ |
| ap_log_cerror(APLOG_MARK, APLOG_TRACE1, rv, c, |
| "core_output_filter: writing data to the network"); |
| apr_brigade_cleanup(bb); |
| c->aborted = 1; |
| return rv; |
| } |
| } |
| |
| setaside_remaining_output(f, ctx, bb, c); |
| return APR_SUCCESS; |
| } |
| |
| /* |
| * This function assumes that either ctx->buffered_bb == NULL, or |
| * ctx->buffered_bb is empty, or ctx->buffered_bb == bb |
| */ |
| static void setaside_remaining_output(ap_filter_t *f, |
| core_output_filter_ctx_t *ctx, |
| apr_bucket_brigade *bb, |
| conn_rec *c) |
| { |
| if (bb == NULL) { |
| return; |
| } |
| remove_empty_buckets(bb); |
| if (!APR_BRIGADE_EMPTY(bb)) { |
| c->data_in_output_filters = 1; |
| if (bb != ctx->buffered_bb) { |
| if (!ctx->deferred_write_pool) { |
| apr_pool_create(&ctx->deferred_write_pool, c->pool); |
| apr_pool_tag(ctx->deferred_write_pool, "deferred_write"); |
| } |
| ap_save_brigade(f, &(ctx->buffered_bb), &bb, |
| ctx->deferred_write_pool); |
| } |
| } |
| else if (ctx->deferred_write_pool) { |
| /* |
| * There are no more requests in the pipeline. We can just clear the |
| * pool. |
| */ |
| apr_pool_clear(ctx->deferred_write_pool); |
| } |
| } |
| |
| #ifndef APR_MAX_IOVEC_SIZE |
| #define MAX_IOVEC_TO_WRITE 16 |
| #else |
| #if APR_MAX_IOVEC_SIZE > 16 |
| #define MAX_IOVEC_TO_WRITE 16 |
| #else |
| #define MAX_IOVEC_TO_WRITE APR_MAX_IOVEC_SIZE |
| #endif |
| #endif |
| |
| static apr_status_t send_brigade_nonblocking(apr_socket_t *s, |
| apr_bucket_brigade *bb, |
| apr_size_t *bytes_written, |
| conn_rec *c) |
| { |
| apr_bucket *bucket, *next; |
| apr_status_t rv; |
| struct iovec vec[MAX_IOVEC_TO_WRITE]; |
| apr_size_t nvec = 0; |
| |
| remove_empty_buckets(bb); |
| |
| for (bucket = APR_BRIGADE_FIRST(bb); |
| bucket != APR_BRIGADE_SENTINEL(bb); |
| bucket = next) { |
| next = APR_BUCKET_NEXT(bucket); |
| #if APR_HAS_SENDFILE |
| if (APR_BUCKET_IS_FILE(bucket)) { |
| apr_bucket_file *file_bucket = (apr_bucket_file *)(bucket->data); |
| apr_file_t *fd = file_bucket->fd; |
| /* Use sendfile to send this file unless: |
| * - the platform doesn't support sendfile, |
| * - the file is too small for sendfile to be useful, or |
| * - sendfile is disabled in the httpd config via "EnableSendfile off" |
| */ |
| |
| if ((apr_file_flags_get(fd) & APR_SENDFILE_ENABLED) && |
| (bucket->length >= AP_MIN_SENDFILE_BYTES)) { |
| if (nvec > 0) { |
| (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 1); |
| rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); |
| if (rv != APR_SUCCESS) { |
| (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 0); |
| return rv; |
| } |
| } |
| rv = sendfile_nonblocking(s, bucket, bytes_written, c); |
| if (nvec > 0) { |
| (void)apr_socket_opt_set(s, APR_TCP_NOPUSH, 0); |
| nvec = 0; |
| } |
| if (rv != APR_SUCCESS) { |
| return rv; |
| } |
| break; |
| } |
| } |
| #endif /* APR_HAS_SENDFILE */ |
| /* didn't sendfile */ |
| if (!APR_BUCKET_IS_METADATA(bucket)) { |
| const char *data; |
| apr_size_t length; |
| |
| /* Non-blocking read first, in case this is a morphing |
| * bucket type. */ |
| rv = apr_bucket_read(bucket, &data, &length, APR_NONBLOCK_READ); |
| if (APR_STATUS_IS_EAGAIN(rv)) { |
| /* Read would block; flush any pending data and retry. */ |
| if (nvec) { |
| rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); |
| if (rv) { |
| return rv; |
| } |
| nvec = 0; |
| } |
| |
| rv = apr_bucket_read(bucket, &data, &length, APR_BLOCK_READ); |
| } |
| if (rv != APR_SUCCESS) { |
| return rv; |
| } |
| |
| /* reading may have split the bucket, so recompute next: */ |
| next = APR_BUCKET_NEXT(bucket); |
| vec[nvec].iov_base = (char *)data; |
| vec[nvec].iov_len = length; |
| nvec++; |
| if (nvec == MAX_IOVEC_TO_WRITE) { |
| rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); |
| nvec = 0; |
| if (rv != APR_SUCCESS) { |
| return rv; |
| } |
| break; |
| } |
| } |
| } |
| |
| if (nvec > 0) { |
| rv = writev_nonblocking(s, vec, nvec, bb, bytes_written, c); |
| if (rv != APR_SUCCESS) { |
| return rv; |
| } |
| } |
| |
| remove_empty_buckets(bb); |
| |
| return APR_SUCCESS; |
| } |
| |
| static void remove_empty_buckets(apr_bucket_brigade *bb) |
| { |
| apr_bucket *bucket; |
| while (((bucket = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) && |
| (APR_BUCKET_IS_METADATA(bucket) || (bucket->length == 0))) { |
| apr_bucket_delete(bucket); |
| } |
| } |
| |
| static apr_status_t send_brigade_blocking(apr_socket_t *s, |
| apr_bucket_brigade *bb, |
| apr_size_t *bytes_written, |
| conn_rec *c) |
| { |
| apr_status_t rv; |
| |
| rv = APR_SUCCESS; |
| while (!APR_BRIGADE_EMPTY(bb)) { |
| rv = send_brigade_nonblocking(s, bb, bytes_written, c); |
| if (rv != APR_SUCCESS) { |
| if (APR_STATUS_IS_EAGAIN(rv)) { |
| /* Wait until we can send more data */ |
| apr_int32_t nsds; |
| apr_interval_time_t timeout; |
| apr_pollfd_t pollset; |
| |
| pollset.p = c->pool; |
| pollset.desc_type = APR_POLL_SOCKET; |
| pollset.reqevents = APR_POLLOUT; |
| pollset.desc.s = s; |
| apr_socket_timeout_get(s, &timeout); |
| do { |
| rv = apr_poll(&pollset, 1, &nsds, timeout); |
| } while (APR_STATUS_IS_EINTR(rv)); |
| if (rv != APR_SUCCESS) { |
| break; |
| } |
| } |
| else { |
| break; |
| } |
| } |
| } |
| return rv; |
| } |
| |
| static apr_status_t writev_nonblocking(apr_socket_t *s, |
| struct iovec *vec, apr_size_t nvec, |
| apr_bucket_brigade *bb, |
| apr_size_t *cumulative_bytes_written, |
| conn_rec *c) |
| { |
| apr_status_t rv = APR_SUCCESS, arv; |
| apr_size_t bytes_written = 0, bytes_to_write = 0; |
| apr_size_t i, offset; |
| apr_interval_time_t old_timeout; |
| |
| arv = apr_socket_timeout_get(s, &old_timeout); |
| if (arv != APR_SUCCESS) { |
| return arv; |
| } |
| arv = apr_socket_timeout_set(s, 0); |
| if (arv != APR_SUCCESS) { |
| return arv; |
| } |
| |
| for (i = 0; i < nvec; i++) { |
| bytes_to_write += vec[i].iov_len; |
| } |
| offset = 0; |
| while (bytes_written < bytes_to_write) { |
| apr_size_t n = 0; |
| rv = apr_socket_sendv(s, vec + offset, nvec - offset, &n); |
| if (n > 0) { |
| bytes_written += n; |
| for (i = offset; i < nvec; ) { |
| apr_bucket *bucket = APR_BRIGADE_FIRST(bb); |
| if (APR_BUCKET_IS_METADATA(bucket)) { |
| apr_bucket_delete(bucket); |
| } |
| else if (n >= vec[i].iov_len) { |
| apr_bucket_delete(bucket); |
| offset++; |
| n -= vec[i++].iov_len; |
| } |
| else { |
| apr_bucket_split(bucket, n); |
| apr_bucket_delete(bucket); |
| vec[i].iov_len -= n; |
| vec[i].iov_base = (char *) vec[i].iov_base + n; |
| break; |
| } |
| } |
| } |
| if (rv != APR_SUCCESS) { |
| break; |
| } |
| } |
| if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) { |
| ap__logio_add_bytes_out(c, bytes_written); |
| } |
| *cumulative_bytes_written += bytes_written; |
| |
| arv = apr_socket_timeout_set(s, old_timeout); |
| if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) { |
| return arv; |
| } |
| else { |
| return rv; |
| } |
| } |
| |
| #if APR_HAS_SENDFILE |
| |
| static apr_status_t sendfile_nonblocking(apr_socket_t *s, |
| apr_bucket *bucket, |
| apr_size_t *cumulative_bytes_written, |
| conn_rec *c) |
| { |
| apr_status_t rv = APR_SUCCESS; |
| apr_bucket_file *file_bucket; |
| apr_file_t *fd; |
| apr_size_t file_length; |
| apr_off_t file_offset; |
| apr_size_t bytes_written = 0; |
| |
| if (!APR_BUCKET_IS_FILE(bucket)) { |
| ap_log_error(APLOG_MARK, APLOG_ERR, rv, c->base_server, APLOGNO(00006) |
| "core_filter: sendfile_nonblocking: " |
| "this should never happen"); |
| return APR_EGENERAL; |
| } |
| file_bucket = (apr_bucket_file *)(bucket->data); |
| fd = file_bucket->fd; |
| file_length = bucket->length; |
| file_offset = bucket->start; |
| |
| if (bytes_written < file_length) { |
| apr_size_t n = file_length - bytes_written; |
| apr_status_t arv; |
| apr_interval_time_t old_timeout; |
| |
| arv = apr_socket_timeout_get(s, &old_timeout); |
| if (arv != APR_SUCCESS) { |
| return arv; |
| } |
| arv = apr_socket_timeout_set(s, 0); |
| if (arv != APR_SUCCESS) { |
| return arv; |
| } |
| rv = apr_socket_sendfile(s, fd, NULL, &file_offset, &n, 0); |
| if (rv == APR_SUCCESS) { |
| bytes_written += n; |
| file_offset += n; |
| } |
| arv = apr_socket_timeout_set(s, old_timeout); |
| if ((arv != APR_SUCCESS) && (rv == APR_SUCCESS)) { |
| rv = arv; |
| } |
| } |
| if ((ap__logio_add_bytes_out != NULL) && (bytes_written > 0)) { |
| ap__logio_add_bytes_out(c, bytes_written); |
| } |
| *cumulative_bytes_written += bytes_written; |
| if ((bytes_written < file_length) && (bytes_written > 0)) { |
| apr_bucket_split(bucket, bytes_written); |
| apr_bucket_delete(bucket); |
| } |
| else if (bytes_written == file_length) { |
| apr_bucket_delete(bucket); |
| } |
| return rv; |
| } |
| |
| #endif |