static apr_status_t last_not_included(apr_bucket_brigade *bb, apr_size_t maxlen, int same_alloc, int *pfile_buckets_allowed, apr_bucket **pend) { apr_bucket *b; apr_status_t status = APR_SUCCESS; int files_allowed = pfile_buckets_allowed? *pfile_buckets_allowed : 0; if (maxlen > 0) { /* Find the bucket, up to which we reach maxlen/mem bytes */ for (b = APR_BRIGADE_FIRST(bb); (b != APR_BRIGADE_SENTINEL(bb)); b = APR_BUCKET_NEXT(b)) { if (APR_BUCKET_IS_METADATA(b)) { /* included */ } else { if (maxlen == 0) { *pend = b; return status; } if (b->length == ((apr_size_t)-1)) { const char *ign; apr_size_t ilen; status = apr_bucket_read(b, &ign, &ilen, APR_BLOCK_READ); if (status != APR_SUCCESS) { return status; } } if (same_alloc && APR_BUCKET_IS_FILE(b)) { /* we like it move it, always */ } else if (files_allowed > 0 && APR_BUCKET_IS_FILE(b)) { /* this has no memory footprint really unless * it is read, disregard it in length count, * unless we do not move the file buckets */ --files_allowed; } else if (maxlen < b->length) { apr_bucket_split(b, maxlen); maxlen = 0; } else { maxlen -= b->length; } } } } *pend = APR_BRIGADE_SENTINEL(bb); return status; }
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; }
static apr_off_t calc_buffered(h2_bucket_beam *beam) { apr_off_t len = 0; apr_bucket *b; for (b = H2_BLIST_FIRST(&beam->red); b != H2_BLIST_SENTINEL(&beam->red); b = APR_BUCKET_NEXT(b)) { if (b->length == ((apr_size_t)-1)) { /* do not count */ } else if (APR_BUCKET_IS_FILE(b)) { /* if unread, has no real mem footprint. how to test? */ } else { len += b->length; } } return len; }
apr_off_t h2_beam_get_mem_used(h2_bucket_beam *beam) { apr_bucket *b; apr_off_t l = 0; h2_beam_lock bl; if (enter_yellow(beam, &bl) == APR_SUCCESS) { for (b = H2_BLIST_FIRST(&beam->red); b != H2_BLIST_SENTINEL(&beam->red); b = APR_BUCKET_NEXT(b)) { if (APR_BUCKET_IS_FILE(b)) { /* do not count */ } else { /* should all have determinate length */ l += b->length; } } leave_yellow(beam, &bl); } return l; }
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; }
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; int loglevel = ap_get_conn_module_loglevel(c, APLOG_MODULE_INDEX); /* 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 (loglevel >= APLOG_TRACE6) { 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, "will flush because of %s", reason); ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, "seen in brigade%s: bytes: %" APR_SIZE_T_FMT ", non-file bytes: %" APR_SIZE_T_FMT ", eor " "buckets: %d, morphing buckets: %d", flush_upto == NULL ? " so far" : " since last flush point", bytes_in_brigade, non_file_bytes_in_brigade, eor_buckets_in_brigade, morphing_bucket_in_brigade); } /* * 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); if (loglevel >= APLOG_TRACE8) { ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, "flushing now"); } 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; } if (loglevel >= APLOG_TRACE8) { ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, "total bytes written: %" APR_SIZE_T_FMT, ctx->bytes_written); } APR_BRIGADE_CONCAT(bb, ctx->tmp_flush_bb); } if (loglevel >= APLOG_TRACE8) { ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, "brigade contains: bytes: %" APR_SIZE_T_FMT ", non-file bytes: %" APR_SIZE_T_FMT ", eor buckets: %d, morphing buckets: %d", bytes_in_brigade, non_file_bytes_in_brigade, eor_buckets_in_brigade, morphing_bucket_in_brigade); } 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; } if (loglevel >= APLOG_TRACE8) { ap_log_cerror(APLOG_MARK, APLOG_TRACE8, 0, c, "tried nonblocking write, total bytes " "written: %" APR_SIZE_T_FMT, ctx->bytes_written); } } setaside_remaining_output(f, ctx, bb, c); return APR_SUCCESS; }
apr_status_t ap_core_output_filter(ap_filter_t *f, apr_bucket_brigade *b) { apr_status_t rv; apr_bucket_brigade *more; conn_rec *c = f->c; core_net_rec *net = f->ctx; core_output_filter_ctx_t *ctx = net->out_ctx; apr_read_type_e eblock = APR_NONBLOCK_READ; apr_pool_t *input_pool = b->p; /* Fail quickly if the connection has already been aborted. */ if (c->aborted) { apr_brigade_cleanup(b); return APR_ECONNABORTED; } if (ctx == NULL) { ctx = apr_pcalloc(c->pool, sizeof(*ctx)); net->out_ctx = ctx; } /* If we have a saved brigade, concatenate the new brigade to it */ if (ctx->b) { APR_BRIGADE_CONCAT(ctx->b, b); b = ctx->b; ctx->b = NULL; } /* Perform multiple passes over the brigade, sending batches of output to the connection. */ while (b && !APR_BRIGADE_EMPTY(b)) { apr_size_t nbytes = 0; apr_bucket *last_e = NULL; /* initialized for debugging */ apr_bucket *e; /* one group of iovecs per pass over the brigade */ apr_size_t nvec = 0; apr_size_t nvec_trailers = 0; struct iovec vec[MAX_IOVEC_TO_WRITE]; struct iovec vec_trailers[MAX_IOVEC_TO_WRITE]; /* one file per pass over the brigade */ apr_file_t *fd = NULL; apr_size_t flen = 0; apr_off_t foffset = 0; /* keep track of buckets that we've concatenated * to avoid small writes */ apr_bucket *last_merged_bucket = NULL; /* tail of brigade if we need another pass */ more = NULL; /* Iterate over the brigade: collect iovecs and/or a file */ for (e = APR_BRIGADE_FIRST(b); e != APR_BRIGADE_SENTINEL(b); e = APR_BUCKET_NEXT(e)) { /* keep track of the last bucket processed */ last_e = e; if (APR_BUCKET_IS_EOS(e) || AP_BUCKET_IS_EOC(e)) { break; } else if (APR_BUCKET_IS_FLUSH(e)) { if (e != APR_BRIGADE_LAST(b)) { more = apr_brigade_split(b, APR_BUCKET_NEXT(e)); } break; } /* It doesn't make any sense to use sendfile for a file bucket * that represents 10 bytes. */ else if (APR_BUCKET_IS_FILE(e) && (e->length >= AP_MIN_SENDFILE_BYTES)) { apr_bucket_file *a = e->data; /* We can't handle more than one file bucket at a time * so we split here and send the file we have already * found. */ if (fd) { more = apr_brigade_split(b, e); break; } fd = a->fd; flen = e->length; foffset = e->start; } else { const char *str; apr_size_t n; rv = apr_bucket_read(e, &str, &n, eblock); if (APR_STATUS_IS_EAGAIN(rv)) { /* send what we have so far since we shouldn't expect more * output for a while... next time we read, block */ more = apr_brigade_split(b, e); eblock = APR_BLOCK_READ; break; } eblock = APR_NONBLOCK_READ; if (n) { if (!fd) { if (nvec == MAX_IOVEC_TO_WRITE) { /* woah! too many. buffer them up, for use later. */ apr_bucket *temp, *next; apr_bucket_brigade *temp_brig; if (nbytes >= AP_MIN_BYTES_TO_WRITE) { /* We have enough data in the iovec * to justify doing a writev */ more = apr_brigade_split(b, e); break; } /* Create a temporary brigade as a means * of concatenating a bunch of buckets together */ temp_brig = apr_brigade_create(f->c->pool, f->c->bucket_alloc); if (last_merged_bucket) { /* If we've concatenated together small * buckets already in a previous pass, * the initial buckets in this brigade * are heap buckets that may have extra * space left in them (because they * were created by apr_brigade_write()). * We can take advantage of this by * building the new temp brigade out of * these buckets, so that the content * in them doesn't have to be copied again. */ APR_BRIGADE_PREPEND(b, temp_brig); brigade_move(temp_brig, b, APR_BUCKET_NEXT(last_merged_bucket)); } temp = APR_BRIGADE_FIRST(b); while (temp != e) { apr_bucket *d; rv = apr_bucket_read(temp, &str, &n, APR_BLOCK_READ); apr_brigade_write(temp_brig, NULL, NULL, str, n); d = temp; temp = APR_BUCKET_NEXT(temp); apr_bucket_delete(d); } nvec = 0; nbytes = 0; temp = APR_BRIGADE_FIRST(temp_brig); APR_BUCKET_REMOVE(temp); APR_BRIGADE_INSERT_HEAD(b, temp); apr_bucket_read(temp, &str, &n, APR_BLOCK_READ); vec[nvec].iov_base = (char*) str; vec[nvec].iov_len = n; nvec++; /* Just in case the temporary brigade has * multiple buckets, recover the rest of * them and put them in the brigade that * we're sending. */ for (next = APR_BRIGADE_FIRST(temp_brig); next != APR_BRIGADE_SENTINEL(temp_brig); next = APR_BRIGADE_FIRST(temp_brig)) { APR_BUCKET_REMOVE(next); APR_BUCKET_INSERT_AFTER(temp, next); temp = next; apr_bucket_read(next, &str, &n, APR_BLOCK_READ); vec[nvec].iov_base = (char*) str; vec[nvec].iov_len = n; nvec++; } apr_brigade_destroy(temp_brig); last_merged_bucket = temp; e = temp; last_e = e; } else { vec[nvec].iov_base = (char*) str; vec[nvec].iov_len = n; nvec++; } } else { /* The bucket is a trailer to a file bucket */ if (nvec_trailers == MAX_IOVEC_TO_WRITE) { /* woah! too many. stop now. */ more = apr_brigade_split(b, e); break; } vec_trailers[nvec_trailers].iov_base = (char*) str; vec_trailers[nvec_trailers].iov_len = n; nvec_trailers++; } nbytes += n; } } } /* Completed iterating over the brigade, now determine if we want * to buffer the brigade or send the brigade out on the network. * * Save if we haven't accumulated enough bytes to send, the connection * is not about to be closed, and: * * 1) we didn't see a file, we don't have more passes over the * brigade to perform, AND we didn't stop at a FLUSH bucket. * (IOW, we will save plain old bytes such as HTTP headers) * or * 2) we hit the EOS and have a keep-alive connection * (IOW, this response is a bit more complex, but we save it * with the hope of concatenating with another response) */ if (nbytes + flen < AP_MIN_BYTES_TO_WRITE && !AP_BUCKET_IS_EOC(last_e) && ((!fd && !more && !APR_BUCKET_IS_FLUSH(last_e)) || (APR_BUCKET_IS_EOS(last_e) && c->keepalive == AP_CONN_KEEPALIVE))) { /* NEVER save an EOS in here. If we are saving a brigade with * an EOS bucket, then we are doing keepalive connections, and * we want to process to second request fully. */ if (APR_BUCKET_IS_EOS(last_e)) { apr_bucket *bucket; int file_bucket_saved = 0; apr_bucket_delete(last_e); for (bucket = APR_BRIGADE_FIRST(b); bucket != APR_BRIGADE_SENTINEL(b); bucket = APR_BUCKET_NEXT(bucket)) { /* Do a read on each bucket to pull in the * data from pipe and socket buckets, so * that we don't leave their file descriptors * open indefinitely. Do the same for file * buckets, with one exception: allow the * first file bucket in the brigade to remain * a file bucket, so that we don't end up * doing an mmap+memcpy every time a client * requests a <8KB file over a keepalive * connection. */ if (APR_BUCKET_IS_FILE(bucket) && !file_bucket_saved) { file_bucket_saved = 1; } else { const char *buf; apr_size_t len = 0; rv = apr_bucket_read(bucket, &buf, &len, APR_BLOCK_READ); if (rv != APR_SUCCESS) { ap_log_cerror(APLOG_MARK, APLOG_ERR, rv, c, "core_output_filter:" " Error reading from bucket."); return HTTP_INTERNAL_SERVER_ERROR; } } } } 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->b, &b, ctx->deferred_write_pool); return APR_SUCCESS; } if (fd) { apr_hdtr_t hdtr; apr_size_t bytes_sent; #if APR_HAS_SENDFILE apr_int32_t flags = 0; #endif memset(&hdtr, '\0', sizeof(hdtr)); if (nvec) { hdtr.numheaders = nvec; hdtr.headers = vec; } if (nvec_trailers) { hdtr.numtrailers = nvec_trailers; hdtr.trailers = vec_trailers; } #if APR_HAS_SENDFILE if (apr_file_flags_get(fd) & APR_SENDFILE_ENABLED) { if (c->keepalive == AP_CONN_CLOSE && APR_BUCKET_IS_EOS(last_e)) { /* Prepare the socket to be reused */ flags |= APR_SENDFILE_DISCONNECT_SOCKET; } rv = sendfile_it_all(net, /* the network information */ fd, /* the file to send */ &hdtr, /* header and trailer iovecs */ foffset, /* offset in the file to begin sending from */ flen, /* length of file */ nbytes + flen, /* total length including headers */ &bytes_sent, /* how many bytes were sent */ flags); /* apr_sendfile flags */ } else #endif { rv = emulate_sendfile(net, fd, &hdtr, foffset, flen, &bytes_sent); } if (logio_add_bytes_out && bytes_sent > 0) logio_add_bytes_out(c, bytes_sent); fd = NULL; } else { apr_size_t bytes_sent; rv = writev_it_all(net->client_socket, vec, nvec, nbytes, &bytes_sent); if (logio_add_bytes_out && bytes_sent > 0) logio_add_bytes_out(c, bytes_sent); } apr_brigade_cleanup(b); /* drive cleanups for resources which were set aside * this may occur before or after termination of the request which * created the resource */ if (ctx->deferred_write_pool) { if (more && more->p == ctx->deferred_write_pool) { /* "more" belongs to the deferred_write_pool, * which is about to be cleared. */ if (APR_BRIGADE_EMPTY(more)) { more = NULL; } else { /* uh oh... change more's lifetime * to the input brigade's lifetime */ apr_bucket_brigade *tmp_more = more; more = NULL; ap_save_brigade(f, &more, &tmp_more, input_pool); } } apr_pool_clear(ctx->deferred_write_pool); } if (rv != APR_SUCCESS) { ap_log_cerror(APLOG_MARK, APLOG_INFO, rv, c, "core_output_filter: writing data to the network"); if (more) apr_brigade_cleanup(more); /* No need to check for SUCCESS, we did that above. */ if (!APR_STATUS_IS_EAGAIN(rv)) { c->aborted = 1; return APR_ECONNABORTED; } return APR_SUCCESS; } b = more; more = NULL; } /* end while () */ return APR_SUCCESS; }
static apr_status_t store_body(cache_handle_t *h, request_rec *r, apr_bucket_brigade *b) { apr_status_t rv; cache_object_t *obj = h->cache_obj; cache_object_t *tobj = NULL; mem_cache_object_t *mobj = (mem_cache_object_t*) obj->vobj; apr_read_type_e eblock = APR_BLOCK_READ; apr_bucket *e; char *cur; int eos = 0; if (mobj->type == CACHE_TYPE_FILE) { apr_file_t *file = NULL; int fd = 0; int other = 0; /* We can cache an open file descriptor if: * - the brigade contains one and only one file_bucket && * - the brigade is complete && * - the file_bucket is the last data bucket in the brigade */ for (e = APR_BRIGADE_FIRST(b); e != APR_BRIGADE_SENTINEL(b); e = APR_BUCKET_NEXT(e)) { if (APR_BUCKET_IS_EOS(e)) { eos = 1; } else if (APR_BUCKET_IS_FILE(e)) { apr_bucket_file *a = e->data; fd++; file = a->fd; } else { other++; } } if (fd == 1 && !other && eos) { apr_file_t *tmpfile; const char *name; /* Open a new XTHREAD handle to the file */ apr_file_name_get(&name, file); mobj->flags = ((APR_SENDFILE_ENABLED & apr_file_flags_get(file)) | APR_READ | APR_BINARY | APR_XTHREAD | APR_FILE_NOCLEANUP); rv = apr_file_open(&tmpfile, name, mobj->flags, APR_OS_DEFAULT, r->pool); if (rv != APR_SUCCESS) { return rv; } apr_file_inherit_unset(tmpfile); apr_os_file_get(&(mobj->fd), tmpfile); /* Open for business */ ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, "mem_cache: Cached file: %s with key: %s", name, obj->key); obj->complete = 1; return APR_SUCCESS; } /* Content not suitable for fd caching. Cache in-memory instead. */ mobj->type = CACHE_TYPE_HEAP; } /* * FD cacheing is not enabled or the content was not * suitable for fd caching. */ if (mobj->m == NULL) { mobj->m = malloc(mobj->m_len); if (mobj->m == NULL) { return APR_ENOMEM; } obj->count = 0; } cur = (char*) mobj->m + obj->count; /* Iterate accross the brigade and populate the cache storage */ for (e = APR_BRIGADE_FIRST(b); e != APR_BRIGADE_SENTINEL(b); e = APR_BUCKET_NEXT(e)) { const char *s; apr_size_t len; if (APR_BUCKET_IS_EOS(e)) { const char *cl_header = apr_table_get(r->headers_out, "Content-Length"); if (cl_header) { apr_int64_t cl = apr_atoi64(cl_header); if ((errno == 0) && (obj->count != cl)) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "mem_cache: URL %s didn't receive complete response, not caching", h->cache_obj->key); return APR_EGENERAL; } } if (mobj->m_len > obj->count) { /* Caching a streamed response. Reallocate a buffer of the * correct size and copy the streamed response into that * buffer */ mobj->m = realloc(mobj->m, obj->count); if (!mobj->m) { return APR_ENOMEM; } /* Now comes the crufty part... there is no way to tell the * cache that the size of the object has changed. We need * to remove the object, update the size and re-add the * object, all under protection of the lock. */ if (sconf->lock) { apr_thread_mutex_lock(sconf->lock); } /* Has the object been ejected from the cache? */ tobj = (cache_object_t *) cache_find(sconf->cache_cache, obj->key); if (tobj == obj) { /* Object is still in the cache, remove it, update the len field then * replace it under protection of sconf->lock. */ cache_remove(sconf->cache_cache, obj); /* For illustration, cache no longer has reference to the object * so decrement the refcount * apr_atomic_dec32(&obj->refcount); */ mobj->m_len = obj->count; cache_insert(sconf->cache_cache, obj); /* For illustration, cache now has reference to the object, so * increment the refcount * apr_atomic_inc32(&obj->refcount); */ } else if (tobj) { /* Different object with the same key found in the cache. Doing nothing * here will cause the object refcount to drop to 0 in decrement_refcount * and the object will be cleaned up. */ } else { /* Object has been ejected from the cache, add it back to the cache */ mobj->m_len = obj->count; cache_insert(sconf->cache_cache, obj); apr_atomic_inc32(&obj->refcount); } if (sconf->lock) { apr_thread_mutex_unlock(sconf->lock); } } /* Open for business */ ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server, "mem_cache: Cached url: %s", obj->key); obj->complete = 1; break; } rv = apr_bucket_read(e, &s, &len, eblock); if (rv != APR_SUCCESS) { return rv; } if (len) { /* Check for buffer (max_streaming_buffer_size) overflow */ if ((obj->count + len) > mobj->m_len) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "mem_cache: URL %s exceeds the MCacheMaxStreamingBuffer (%" APR_SIZE_T_FMT ") limit and will not be cached.", obj->key, mobj->m_len); return APR_ENOMEM; } else { memcpy(cur, s, len); cur+=len; obj->count+=len; } } /* This should not fail, but if it does, we are in BIG trouble * cause we just stomped all over the heap. */ AP_DEBUG_ASSERT(obj->count <= mobj->m_len); } return APR_SUCCESS; }
apr_status_t h2_util_move(apr_bucket_brigade *to, apr_bucket_brigade *from, apr_size_t maxlen, int *pfile_handles_allowed, const char *msg) { apr_status_t status = APR_SUCCESS; int same_alloc; AP_DEBUG_ASSERT(to); AP_DEBUG_ASSERT(from); same_alloc = (to->bucket_alloc == from->bucket_alloc); if (!FILE_MOVE) { pfile_handles_allowed = NULL; } if (!APR_BRIGADE_EMPTY(from)) { apr_bucket *b, *end; status = last_not_included(from, maxlen, same_alloc, pfile_handles_allowed, &end); if (status != APR_SUCCESS) { return status; } while (!APR_BRIGADE_EMPTY(from) && status == APR_SUCCESS) { b = APR_BRIGADE_FIRST(from); if (b == end) { break; } if (same_alloc || (b->list == to->bucket_alloc)) { /* both brigades use the same bucket_alloc and auto-cleanups * have the same life time. It's therefore safe to just move * directly. */ APR_BUCKET_REMOVE(b); APR_BRIGADE_INSERT_TAIL(to, b); #if LOG_BUCKETS ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p, "h2_util_move: %s, passed bucket(same bucket_alloc) " "%ld-%ld, type=%s", msg, (long)b->start, (long)b->length, APR_BUCKET_IS_METADATA(b)? (APR_BUCKET_IS_EOS(b)? "EOS": (APR_BUCKET_IS_FLUSH(b)? "FLUSH" : "META")) : (APR_BUCKET_IS_FILE(b)? "FILE" : "DATA")); #endif } else if (DEEP_COPY) { /* we have not managed the magic of passing buckets from * one thread to another. Any attempts result in * cleanup of pools scrambling memory. */ if (APR_BUCKET_IS_METADATA(b)) { if (APR_BUCKET_IS_EOS(b)) { APR_BRIGADE_INSERT_TAIL(to, apr_bucket_eos_create(to->bucket_alloc)); } else if (APR_BUCKET_IS_FLUSH(b)) { APR_BRIGADE_INSERT_TAIL(to, apr_bucket_flush_create(to->bucket_alloc)); } else { /* ignore */ } } else if (pfile_handles_allowed && *pfile_handles_allowed > 0 && APR_BUCKET_IS_FILE(b)) { /* We do not want to read files when passing buckets, if * we can avoid it. However, what we've come up so far * is not working corrently, resulting either in crashes or * too many open file descriptors. */ apr_bucket_file *f = (apr_bucket_file *)b->data; apr_file_t *fd = f->fd; int setaside = (f->readpool != to->p); #if LOG_BUCKETS ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p, "h2_util_move: %s, moving FILE bucket %ld-%ld " "from=%lx(p=%lx) to=%lx(p=%lx), setaside=%d", msg, (long)b->start, (long)b->length, (long)from, (long)from->p, (long)to, (long)to->p, setaside); #endif if (setaside) { status = apr_file_setaside(&fd, fd, to->p); if (status != APR_SUCCESS) { ap_log_perror(APLOG_MARK, APLOG_ERR, status, to->p, APLOGNO(02947) "h2_util: %s, setaside FILE", msg); return status; } } apr_brigade_insert_file(to, fd, b->start, b->length, to->p); --(*pfile_handles_allowed); } else { const char *data; apr_size_t len; status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ); if (status == APR_SUCCESS && len > 0) { status = apr_brigade_write(to, NULL, NULL, data, len); #if LOG_BUCKETS ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p, "h2_util_move: %s, copied bucket %ld-%ld " "from=%lx(p=%lx) to=%lx(p=%lx)", msg, (long)b->start, (long)b->length, (long)from, (long)from->p, (long)to, (long)to->p); #endif } } apr_bucket_delete(b); } else { apr_bucket_setaside(b, to->p); APR_BUCKET_REMOVE(b); APR_BRIGADE_INSERT_TAIL(to, b); #if LOG_BUCKETS ap_log_perror(APLOG_MARK, LOG_LEVEL, 0, to->p, "h2_util_move: %s, passed setaside bucket %ld-%ld " "from=%lx(p=%lx) to=%lx(p=%lx)", msg, (long)b->start, (long)b->length, (long)from, (long)from->p, (long)to, (long)to->p); #endif } } } return status; }
apr_status_t h2_beam_receive(h2_bucket_beam *beam, apr_bucket_brigade *bb, apr_read_type_e block, apr_off_t readbytes) { h2_beam_lock bl; apr_bucket *bred, *bgreen, *ng; int transferred = 0; apr_status_t status = APR_SUCCESS; apr_off_t remain = readbytes; /* Called from the green thread to take buckets from the beam */ if (enter_yellow(beam, &bl) == APR_SUCCESS) { transfer: if (beam->aborted) { if (beam->green && !APR_BRIGADE_EMPTY(beam->green)) { apr_brigade_cleanup(beam->green); } status = APR_ECONNABORTED; goto leave; } /* transfer enough buckets from our green brigade, if we have one */ while (beam->green && !APR_BRIGADE_EMPTY(beam->green) && (readbytes <= 0 || remain >= 0)) { bgreen = APR_BRIGADE_FIRST(beam->green); if (readbytes > 0 && bgreen->length > 0 && remain <= 0) { break; } APR_BUCKET_REMOVE(bgreen); APR_BRIGADE_INSERT_TAIL(bb, bgreen); remain -= bgreen->length; ++transferred; } /* transfer from our red brigade, transforming red buckets to * green ones until we have enough */ while (!H2_BLIST_EMPTY(&beam->red) && (readbytes <= 0 || remain >= 0)) { bred = H2_BLIST_FIRST(&beam->red); bgreen = NULL; if (readbytes > 0 && bred->length > 0 && remain <= 0) { break; } if (APR_BUCKET_IS_METADATA(bred)) { if (APR_BUCKET_IS_EOS(bred)) { bgreen = apr_bucket_eos_create(bb->bucket_alloc); beam->close_sent = 1; } else if (APR_BUCKET_IS_FLUSH(bred)) { bgreen = apr_bucket_flush_create(bb->bucket_alloc); } else { /* put red into hold, no green sent out */ } } else if (APR_BUCKET_IS_FILE(bred)) { /* This is set aside into the target brigade pool so that * any read operation messes with that pool and not * the red one. */ apr_bucket_file *f = (apr_bucket_file *)bred->data; apr_file_t *fd = f->fd; int setaside = (f->readpool != bb->p); if (setaside) { status = apr_file_setaside(&fd, fd, bb->p); if (status != APR_SUCCESS) { goto leave; } ++beam->files_beamed; } ng = apr_brigade_insert_file(bb, fd, bred->start, bred->length, bb->p); #if APR_HAS_MMAP /* disable mmap handling as this leads to segfaults when * the underlying file is changed while memory pointer has * been handed out. See also PR 59348 */ apr_bucket_file_enable_mmap(ng, 0); #endif remain -= bred->length; ++transferred; APR_BUCKET_REMOVE(bred); H2_BLIST_INSERT_TAIL(&beam->hold, bred); ++transferred; continue; } else { /* create a "green" standin bucket. we took care about the * underlying red bucket and its data when we placed it into * the red brigade. * the beam bucket will notify us on destruction that bred is * no longer needed. */ bgreen = h2_beam_bucket_create(beam, bred, bb->bucket_alloc, beam->buckets_sent++); } /* Place the red bucket into our hold, to be destroyed when no * green bucket references it any more. */ APR_BUCKET_REMOVE(bred); H2_BLIST_INSERT_TAIL(&beam->hold, bred); beam->received_bytes += bred->length; if (bgreen) { APR_BRIGADE_INSERT_TAIL(bb, bgreen); remain -= bgreen->length; ++transferred; } } if (readbytes > 0 && remain < 0) { /* too much, put some back */ remain = readbytes; for (bgreen = APR_BRIGADE_FIRST(bb); bgreen != APR_BRIGADE_SENTINEL(bb); bgreen = APR_BUCKET_NEXT(bgreen)) { remain -= bgreen->length; if (remain < 0) { apr_bucket_split(bgreen, bgreen->length+remain); beam->green = apr_brigade_split_ex(bb, APR_BUCKET_NEXT(bgreen), beam->green); break; } } } if (beam->closed && (!beam->green || APR_BRIGADE_EMPTY(beam->green)) && H2_BLIST_EMPTY(&beam->red)) { /* beam is closed and we have nothing more to receive */ if (!beam->close_sent) { apr_bucket *b = apr_bucket_eos_create(bb->bucket_alloc); APR_BRIGADE_INSERT_TAIL(bb, b); beam->close_sent = 1; ++transferred; status = APR_SUCCESS; } } if (transferred) { status = APR_SUCCESS; } else if (beam->closed) { status = APR_EOF; } else if (block == APR_BLOCK_READ && bl.mutex && beam->m_cond) { status = wait_cond(beam, bl.mutex); if (status != APR_SUCCESS) { goto leave; } goto transfer; } else { status = APR_EAGAIN; } leave: leave_yellow(beam, &bl); } return status; }
static apr_status_t append_bucket(h2_bucket_beam *beam, apr_bucket *bred, apr_read_type_e block, apr_pool_t *pool, h2_beam_lock *pbl) { const char *data; apr_size_t len; apr_off_t space_left = 0; apr_status_t status; if (APR_BUCKET_IS_METADATA(bred)) { if (APR_BUCKET_IS_EOS(bred)) { beam->closed = 1; } APR_BUCKET_REMOVE(bred); H2_BLIST_INSERT_TAIL(&beam->red, bred); return APR_SUCCESS; } else if (APR_BUCKET_IS_FILE(bred)) { /* file bucket lengths do not really count */ } else { space_left = calc_space_left(beam); if (space_left > 0 && bred->length == ((apr_size_t)-1)) { const char *data; status = apr_bucket_read(bred, &data, &len, APR_BLOCK_READ); if (status != APR_SUCCESS) { return status; } } if (space_left < bred->length) { status = r_wait_space(beam, block, pbl, &space_left); if (status != APR_SUCCESS) { return status; } if (space_left <= 0) { return APR_EAGAIN; } } /* space available, maybe need bucket split */ } /* The fundamental problem is that reading a red bucket from * a green thread is a total NO GO, because the bucket might use * its pool/bucket_alloc from a foreign thread and that will * corrupt. */ status = APR_ENOTIMPL; if (beam->closed && bred->length > 0) { status = APR_EOF; } else if (APR_BUCKET_IS_TRANSIENT(bred)) { /* this takes care of transient buckets and converts them * into heap ones. Other bucket types might or might not be * affected by this. */ status = apr_bucket_setaside(bred, pool); } else if (APR_BUCKET_IS_HEAP(bred)) { /* For heap buckets read from a green thread is fine. The * data will be there and live until the bucket itself is * destroyed. */ status = APR_SUCCESS; } else if (APR_BUCKET_IS_POOL(bred)) { /* pool buckets are bastards that register at pool cleanup * to morph themselves into heap buckets. That may happen anytime, * even after the bucket data pointer has been read. So at * any time inside the green thread, the pool bucket memory * may disappear. yikes. */ status = apr_bucket_read(bred, &data, &len, APR_BLOCK_READ); if (status == APR_SUCCESS) { apr_bucket_heap_make(bred, data, len, NULL); } } else if (APR_BUCKET_IS_FILE(bred)) { /* For file buckets the problem is their internal readpool that * is used on the first read to allocate buffer/mmap. * Since setting aside a file bucket will de-register the * file cleanup function from the previous pool, we need to * call that from a red thread. * Additionally, we allow callbacks to prevent beaming file * handles across. The use case for this is to limit the number * of open file handles and rather use a less efficient beam * transport. */ apr_file_t *fd = ((apr_bucket_file *)bred->data)->fd; int can_beam = 1; if (beam->last_beamed != fd && beam->can_beam_fn) { can_beam = beam->can_beam_fn(beam->can_beam_ctx, beam, fd); } if (can_beam) { beam->last_beamed = fd; status = apr_bucket_setaside(bred, pool); } /* else: enter ENOTIMPL case below */ } if (status == APR_ENOTIMPL) { /* we have no knowledge about the internals of this bucket, * but hope that after read, its data stays immutable for the * lifetime of the bucket. (see pool bucket handling above for * a counter example). * We do the read while in a red thread, so that the bucket may * use pools/allocators safely. */ if (space_left < APR_BUCKET_BUFF_SIZE) { space_left = APR_BUCKET_BUFF_SIZE; } if (space_left < bred->length) { apr_bucket_split(bred, space_left); } status = apr_bucket_read(bred, &data, &len, APR_BLOCK_READ); if (status == APR_SUCCESS) { status = apr_bucket_setaside(bred, pool); } } if (status != APR_SUCCESS && status != APR_ENOTIMPL) { return status; } APR_BUCKET_REMOVE(bred); H2_BLIST_INSERT_TAIL(&beam->red, bred); beam->sent_bytes += bred->length; return APR_SUCCESS; }