|  | 
 | /* | 
 |  * Copyright (C) Igor Sysoev | 
 |  * Copyright (C) Nginx, Inc. | 
 |  */ | 
 |  | 
 |  | 
 | #include <ngx_config.h> | 
 | #include <ngx_core.h> | 
 | #include <ngx_event.h> | 
 |  | 
 |  | 
 | #if 0 | 
 | #define NGX_SENDFILE_LIMIT  4096 | 
 | #endif | 
 |  | 
 | /* | 
 |  * When DIRECTIO is enabled FreeBSD, Solaris, and MacOSX read directly | 
 |  * to an application memory from a device if parameters are aligned | 
 |  * to device sector boundary (512 bytes).  They fallback to usual read | 
 |  * operation if the parameters are not aligned. | 
 |  * Linux allows DIRECTIO only if the parameters are aligned to a filesystem | 
 |  * sector boundary, otherwise it returns EINVAL.  The sector size is | 
 |  * usually 512 bytes, however, on XFS it may be 4096 bytes. | 
 |  */ | 
 |  | 
 | #define NGX_NONE            1 | 
 |  | 
 |  | 
 | static ngx_inline ngx_int_t | 
 |     ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf); | 
 | #if (NGX_HAVE_AIO_SENDFILE) | 
 | static ngx_int_t ngx_output_chain_aio_setup(ngx_output_chain_ctx_t *ctx, | 
 |     ngx_file_t *file); | 
 | #endif | 
 | static ngx_int_t ngx_output_chain_add_copy(ngx_pool_t *pool, | 
 |     ngx_chain_t **chain, ngx_chain_t *in); | 
 | static ngx_int_t ngx_output_chain_align_file_buf(ngx_output_chain_ctx_t *ctx, | 
 |     off_t bsize); | 
 | static ngx_int_t ngx_output_chain_get_buf(ngx_output_chain_ctx_t *ctx, | 
 |     off_t bsize); | 
 | static ngx_int_t ngx_output_chain_copy_buf(ngx_output_chain_ctx_t *ctx); | 
 |  | 
 |  | 
 | ngx_int_t | 
 | ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in) | 
 | { | 
 |     off_t         bsize; | 
 |     ngx_int_t     rc, last; | 
 |     ngx_chain_t  *cl, *out, **last_out; | 
 |  | 
 |     if (ctx->in == NULL && ctx->busy == NULL | 
 | #if (NGX_HAVE_FILE_AIO || NGX_THREADS) | 
 |         && !ctx->aio | 
 | #endif | 
 |        ) | 
 |     { | 
 |         /* | 
 |          * the short path for the case when the ctx->in and ctx->busy chains | 
 |          * are empty, the incoming chain is empty too or has the single buf | 
 |          * that does not require the copy | 
 |          */ | 
 |  | 
 |         if (in == NULL) { | 
 |             return ctx->output_filter(ctx->filter_ctx, in); | 
 |         } | 
 |  | 
 |         if (in->next == NULL | 
 | #if (NGX_SENDFILE_LIMIT) | 
 |             && !(in->buf->in_file && in->buf->file_last > NGX_SENDFILE_LIMIT) | 
 | #endif | 
 |             && ngx_output_chain_as_is(ctx, in->buf)) | 
 |         { | 
 |             return ctx->output_filter(ctx->filter_ctx, in); | 
 |         } | 
 |     } | 
 |  | 
 |     /* add the incoming buf to the chain ctx->in */ | 
 |  | 
 |     if (in) { | 
 |         if (ngx_output_chain_add_copy(ctx->pool, &ctx->in, in) == NGX_ERROR) { | 
 |             return NGX_ERROR; | 
 |         } | 
 |     } | 
 |  | 
 |     out = NULL; | 
 |     last_out = &out; | 
 |     last = NGX_NONE; | 
 |  | 
 |     for ( ;; ) { | 
 |  | 
 | #if (NGX_HAVE_FILE_AIO || NGX_THREADS) | 
 |         if (ctx->aio) { | 
 |             return NGX_AGAIN; | 
 |         } | 
 | #endif | 
 |  | 
 |         while (ctx->in) { | 
 |  | 
 |             /* | 
 |              * cycle while there are the ctx->in bufs | 
 |              * and there are the free output bufs to copy in | 
 |              */ | 
 |  | 
 |             bsize = ngx_buf_size(ctx->in->buf); | 
 |  | 
 |             if (bsize == 0 && !ngx_buf_special(ctx->in->buf)) { | 
 |  | 
 |                 ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0, | 
 |                               "zero size buf in output " | 
 |                               "t:%d r:%d f:%d %p %p-%p %p %O-%O", | 
 |                               ctx->in->buf->temporary, | 
 |                               ctx->in->buf->recycled, | 
 |                               ctx->in->buf->in_file, | 
 |                               ctx->in->buf->start, | 
 |                               ctx->in->buf->pos, | 
 |                               ctx->in->buf->last, | 
 |                               ctx->in->buf->file, | 
 |                               ctx->in->buf->file_pos, | 
 |                               ctx->in->buf->file_last); | 
 |  | 
 |                 ngx_debug_point(); | 
 |  | 
 |                 ctx->in = ctx->in->next; | 
 |  | 
 |                 continue; | 
 |             } | 
 |  | 
 |             if (ngx_output_chain_as_is(ctx, ctx->in->buf)) { | 
 |  | 
 |                 /* move the chain link to the output chain */ | 
 |  | 
 |                 cl = ctx->in; | 
 |                 ctx->in = cl->next; | 
 |  | 
 |                 *last_out = cl; | 
 |                 last_out = &cl->next; | 
 |                 cl->next = NULL; | 
 |  | 
 |                 continue; | 
 |             } | 
 |  | 
 |             if (ctx->buf == NULL) { | 
 |  | 
 |                 rc = ngx_output_chain_align_file_buf(ctx, bsize); | 
 |  | 
 |                 if (rc == NGX_ERROR) { | 
 |                     return NGX_ERROR; | 
 |                 } | 
 |  | 
 |                 if (rc != NGX_OK) { | 
 |  | 
 |                     if (ctx->free) { | 
 |  | 
 |                         /* get the free buf */ | 
 |  | 
 |                         cl = ctx->free; | 
 |                         ctx->buf = cl->buf; | 
 |                         ctx->free = cl->next; | 
 |  | 
 |                         ngx_free_chain(ctx->pool, cl); | 
 |  | 
 |                     } else if (out || ctx->allocated == ctx->bufs.num) { | 
 |  | 
 |                         break; | 
 |  | 
 |                     } else if (ngx_output_chain_get_buf(ctx, bsize) != NGX_OK) { | 
 |                         return NGX_ERROR; | 
 |                     } | 
 |                 } | 
 |             } | 
 |  | 
 |             rc = ngx_output_chain_copy_buf(ctx); | 
 |  | 
 |             if (rc == NGX_ERROR) { | 
 |                 return rc; | 
 |             } | 
 |  | 
 |             if (rc == NGX_AGAIN) { | 
 |                 if (out) { | 
 |                     break; | 
 |                 } | 
 |  | 
 |                 return rc; | 
 |             } | 
 |  | 
 |             /* delete the completed buf from the ctx->in chain */ | 
 |  | 
 |             if (ngx_buf_size(ctx->in->buf) == 0) { | 
 |                 ctx->in = ctx->in->next; | 
 |             } | 
 |  | 
 |             cl = ngx_alloc_chain_link(ctx->pool); | 
 |             if (cl == NULL) { | 
 |                 return NGX_ERROR; | 
 |             } | 
 |  | 
 |             cl->buf = ctx->buf; | 
 |             cl->next = NULL; | 
 |             *last_out = cl; | 
 |             last_out = &cl->next; | 
 |             ctx->buf = NULL; | 
 |         } | 
 |  | 
 |         if (out == NULL && last != NGX_NONE) { | 
 |  | 
 |             if (ctx->in) { | 
 |                 return NGX_AGAIN; | 
 |             } | 
 |  | 
 |             return last; | 
 |         } | 
 |  | 
 |         last = ctx->output_filter(ctx->filter_ctx, out); | 
 |  | 
 |         if (last == NGX_ERROR || last == NGX_DONE) { | 
 |             return last; | 
 |         } | 
 |  | 
 |         ngx_chain_update_chains(ctx->pool, &ctx->free, &ctx->busy, &out, | 
 |                                 ctx->tag); | 
 |         last_out = &out; | 
 |     } | 
 | } | 
 |  | 
 |  | 
 | static ngx_inline ngx_int_t | 
 | ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf) | 
 | { | 
 |     ngx_uint_t  sendfile; | 
 |  | 
 |     if (ngx_buf_special(buf)) { | 
 |         return 1; | 
 |     } | 
 |  | 
 | #if (NGX_THREADS) | 
 |     if (buf->in_file) { | 
 |         buf->file->thread_handler = ctx->thread_handler; | 
 |         buf->file->thread_ctx = ctx->filter_ctx; | 
 |     } | 
 | #endif | 
 |  | 
 |     if (buf->in_file && buf->file->directio) { | 
 |         return 0; | 
 |     } | 
 |  | 
 |     sendfile = ctx->sendfile; | 
 |  | 
 | #if (NGX_SENDFILE_LIMIT) | 
 |  | 
 |     if (buf->in_file && buf->file_pos >= NGX_SENDFILE_LIMIT) { | 
 |         sendfile = 0; | 
 |     } | 
 |  | 
 | #endif | 
 |  | 
 |     if (!sendfile) { | 
 |  | 
 |         if (!ngx_buf_in_memory(buf)) { | 
 |             return 0; | 
 |         } | 
 |  | 
 |         buf->in_file = 0; | 
 |     } | 
 |  | 
 | #if (NGX_HAVE_AIO_SENDFILE) | 
 |     if (ctx->aio_preload && buf->in_file) { | 
 |         (void) ngx_output_chain_aio_setup(ctx, buf->file); | 
 |     } | 
 | #endif | 
 |  | 
 |     if (ctx->need_in_memory && !ngx_buf_in_memory(buf)) { | 
 |         return 0; | 
 |     } | 
 |  | 
 |     if (ctx->need_in_temp && (buf->memory || buf->mmap)) { | 
 |         return 0; | 
 |     } | 
 |  | 
 |     return 1; | 
 | } | 
 |  | 
 |  | 
 | #if (NGX_HAVE_AIO_SENDFILE) | 
 |  | 
 | static ngx_int_t | 
 | ngx_output_chain_aio_setup(ngx_output_chain_ctx_t *ctx, ngx_file_t *file) | 
 | { | 
 |     ngx_event_aio_t  *aio; | 
 |  | 
 |     if (file->aio == NULL && ngx_file_aio_init(file, ctx->pool) != NGX_OK) { | 
 |         return NGX_ERROR; | 
 |     } | 
 |  | 
 |     aio = file->aio; | 
 |  | 
 |     aio->data = ctx->filter_ctx; | 
 |     aio->preload_handler = ctx->aio_preload; | 
 |  | 
 |     return NGX_OK; | 
 | } | 
 |  | 
 | #endif | 
 |  | 
 |  | 
 | static ngx_int_t | 
 | ngx_output_chain_add_copy(ngx_pool_t *pool, ngx_chain_t **chain, | 
 |     ngx_chain_t *in) | 
 | { | 
 |     ngx_chain_t  *cl, **ll; | 
 | #if (NGX_SENDFILE_LIMIT) | 
 |     ngx_buf_t    *b, *buf; | 
 | #endif | 
 |  | 
 |     ll = chain; | 
 |  | 
 |     for (cl = *chain; cl; cl = cl->next) { | 
 |         ll = &cl->next; | 
 |     } | 
 |  | 
 |     while (in) { | 
 |  | 
 |         cl = ngx_alloc_chain_link(pool); | 
 |         if (cl == NULL) { | 
 |             return NGX_ERROR; | 
 |         } | 
 |  | 
 | #if (NGX_SENDFILE_LIMIT) | 
 |  | 
 |         buf = in->buf; | 
 |  | 
 |         if (buf->in_file | 
 |             && buf->file_pos < NGX_SENDFILE_LIMIT | 
 |             && buf->file_last > NGX_SENDFILE_LIMIT) | 
 |         { | 
 |             /* split a file buf on two bufs by the sendfile limit */ | 
 |  | 
 |             b = ngx_calloc_buf(pool); | 
 |             if (b == NULL) { | 
 |                 return NGX_ERROR; | 
 |             } | 
 |  | 
 |             ngx_memcpy(b, buf, sizeof(ngx_buf_t)); | 
 |  | 
 |             if (ngx_buf_in_memory(buf)) { | 
 |                 buf->pos += (ssize_t) (NGX_SENDFILE_LIMIT - buf->file_pos); | 
 |                 b->last = buf->pos; | 
 |             } | 
 |  | 
 |             buf->file_pos = NGX_SENDFILE_LIMIT; | 
 |             b->file_last = NGX_SENDFILE_LIMIT; | 
 |  | 
 |             cl->buf = b; | 
 |  | 
 |         } else { | 
 |             cl->buf = buf; | 
 |             in = in->next; | 
 |         } | 
 |  | 
 | #else | 
 |         cl->buf = in->buf; | 
 |         in = in->next; | 
 |  | 
 | #endif | 
 |  | 
 |         cl->next = NULL; | 
 |         *ll = cl; | 
 |         ll = &cl->next; | 
 |     } | 
 |  | 
 |     return NGX_OK; | 
 | } | 
 |  | 
 |  | 
 | static ngx_int_t | 
 | ngx_output_chain_align_file_buf(ngx_output_chain_ctx_t *ctx, off_t bsize) | 
 | { | 
 |     size_t      size; | 
 |     ngx_buf_t  *in; | 
 |  | 
 |     in = ctx->in->buf; | 
 |  | 
 |     if (in->file == NULL || !in->file->directio) { | 
 |         return NGX_DECLINED; | 
 |     } | 
 |  | 
 |     ctx->directio = 1; | 
 |  | 
 |     size = (size_t) (in->file_pos - (in->file_pos & ~(ctx->alignment - 1))); | 
 |  | 
 |     if (size == 0) { | 
 |  | 
 |         if (bsize >= (off_t) ctx->bufs.size) { | 
 |             return NGX_DECLINED; | 
 |         } | 
 |  | 
 |         size = (size_t) bsize; | 
 |  | 
 |     } else { | 
 |         size = (size_t) ctx->alignment - size; | 
 |  | 
 |         if ((off_t) size > bsize) { | 
 |             size = (size_t) bsize; | 
 |         } | 
 |     } | 
 |  | 
 |     ctx->buf = ngx_create_temp_buf(ctx->pool, size); | 
 |     if (ctx->buf == NULL) { | 
 |         return NGX_ERROR; | 
 |     } | 
 |  | 
 |     /* | 
 |      * we do not set ctx->buf->tag, because we do not want | 
 |      * to reuse the buf via ctx->free list | 
 |      */ | 
 |  | 
 | #if (NGX_HAVE_ALIGNED_DIRECTIO) | 
 |     ctx->unaligned = 1; | 
 | #endif | 
 |  | 
 |     return NGX_OK; | 
 | } | 
 |  | 
 |  | 
 | static ngx_int_t | 
 | ngx_output_chain_get_buf(ngx_output_chain_ctx_t *ctx, off_t bsize) | 
 | { | 
 |     size_t       size; | 
 |     ngx_buf_t   *b, *in; | 
 |     ngx_uint_t   recycled; | 
 |  | 
 |     in = ctx->in->buf; | 
 |     size = ctx->bufs.size; | 
 |     recycled = 1; | 
 |  | 
 |     if (in->last_in_chain) { | 
 |  | 
 |         if (bsize < (off_t) size) { | 
 |  | 
 |             /* | 
 |              * allocate a small temp buf for a small last buf | 
 |              * or its small last part | 
 |              */ | 
 |  | 
 |             size = (size_t) bsize; | 
 |             recycled = 0; | 
 |  | 
 |         } else if (!ctx->directio | 
 |                    && ctx->bufs.num == 1 | 
 |                    && (bsize < (off_t) (size + size / 4))) | 
 |         { | 
 |             /* | 
 |              * allocate a temp buf that equals to a last buf, | 
 |              * if there is no directio, the last buf size is lesser | 
 |              * than 1.25 of bufs.size and the temp buf is single | 
 |              */ | 
 |  | 
 |             size = (size_t) bsize; | 
 |             recycled = 0; | 
 |         } | 
 |     } | 
 |  | 
 |     b = ngx_calloc_buf(ctx->pool); | 
 |     if (b == NULL) { | 
 |         return NGX_ERROR; | 
 |     } | 
 |  | 
 |     if (ctx->directio) { | 
 |  | 
 |         /* | 
 |          * allocate block aligned to a disk sector size to enable | 
 |          * userland buffer direct usage conjunctly with directio | 
 |          */ | 
 |  | 
 |         b->start = ngx_pmemalign(ctx->pool, size, (size_t) ctx->alignment); | 
 |         if (b->start == NULL) { | 
 |             return NGX_ERROR; | 
 |         } | 
 |  | 
 |     } else { | 
 |         b->start = ngx_palloc(ctx->pool, size); | 
 |         if (b->start == NULL) { | 
 |             return NGX_ERROR; | 
 |         } | 
 |     } | 
 |  | 
 |     b->pos = b->start; | 
 |     b->last = b->start; | 
 |     b->end = b->last + size; | 
 |     b->temporary = 1; | 
 |     b->tag = ctx->tag; | 
 |     b->recycled = recycled; | 
 |  | 
 |     ctx->buf = b; | 
 |     ctx->allocated++; | 
 |  | 
 |     return NGX_OK; | 
 | } | 
 |  | 
 |  | 
 | static ngx_int_t | 
 | ngx_output_chain_copy_buf(ngx_output_chain_ctx_t *ctx) | 
 | { | 
 |     off_t        size; | 
 |     ssize_t      n; | 
 |     ngx_buf_t   *src, *dst; | 
 |     ngx_uint_t   sendfile; | 
 |  | 
 |     src = ctx->in->buf; | 
 |     dst = ctx->buf; | 
 |  | 
 |     size = ngx_buf_size(src); | 
 |     size = ngx_min(size, dst->end - dst->pos); | 
 |  | 
 |     sendfile = ctx->sendfile & !ctx->directio; | 
 |  | 
 | #if (NGX_SENDFILE_LIMIT) | 
 |  | 
 |     if (src->in_file && src->file_pos >= NGX_SENDFILE_LIMIT) { | 
 |         sendfile = 0; | 
 |     } | 
 |  | 
 | #endif | 
 |  | 
 |     if (ngx_buf_in_memory(src)) { | 
 |         ngx_memcpy(dst->pos, src->pos, (size_t) size); | 
 |         src->pos += (size_t) size; | 
 |         dst->last += (size_t) size; | 
 |  | 
 |         if (src->in_file) { | 
 |  | 
 |             if (sendfile) { | 
 |                 dst->in_file = 1; | 
 |                 dst->file = src->file; | 
 |                 dst->file_pos = src->file_pos; | 
 |                 dst->file_last = src->file_pos + size; | 
 |  | 
 |             } else { | 
 |                 dst->in_file = 0; | 
 |             } | 
 |  | 
 |             src->file_pos += size; | 
 |  | 
 |         } else { | 
 |             dst->in_file = 0; | 
 |         } | 
 |  | 
 |         if (src->pos == src->last) { | 
 |             dst->flush = src->flush; | 
 |             dst->last_buf = src->last_buf; | 
 |             dst->last_in_chain = src->last_in_chain; | 
 |         } | 
 |  | 
 |     } else { | 
 |  | 
 | #if (NGX_HAVE_ALIGNED_DIRECTIO) | 
 |  | 
 |         if (ctx->unaligned) { | 
 |             if (ngx_directio_off(src->file->fd) == NGX_FILE_ERROR) { | 
 |                 ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, ngx_errno, | 
 |                               ngx_directio_off_n " \"%s\" failed", | 
 |                               src->file->name.data); | 
 |             } | 
 |         } | 
 |  | 
 | #endif | 
 |  | 
 | #if (NGX_HAVE_FILE_AIO) | 
 |         if (ctx->aio_handler) { | 
 |             n = ngx_file_aio_read(src->file, dst->pos, (size_t) size, | 
 |                                   src->file_pos, ctx->pool); | 
 |             if (n == NGX_AGAIN) { | 
 |                 ctx->aio_handler(ctx, src->file); | 
 |                 return NGX_AGAIN; | 
 |             } | 
 |  | 
 |         } else | 
 | #endif | 
 | #if (NGX_THREADS) | 
 |         if (ctx->thread_handler) { | 
 |             src->file->thread_task = ctx->thread_task; | 
 |             src->file->thread_handler = ctx->thread_handler; | 
 |             src->file->thread_ctx = ctx->filter_ctx; | 
 |  | 
 |             n = ngx_thread_read(src->file, dst->pos, (size_t) size, | 
 |                                 src->file_pos, ctx->pool); | 
 |             if (n == NGX_AGAIN) { | 
 |                 ctx->thread_task = src->file->thread_task; | 
 |                 return NGX_AGAIN; | 
 |             } | 
 |  | 
 |         } else | 
 | #endif | 
 |         { | 
 |             n = ngx_read_file(src->file, dst->pos, (size_t) size, | 
 |                               src->file_pos); | 
 |         } | 
 |  | 
 | #if (NGX_HAVE_ALIGNED_DIRECTIO) | 
 |  | 
 |         if (ctx->unaligned) { | 
 |             ngx_err_t  err; | 
 |  | 
 |             err = ngx_errno; | 
 |  | 
 |             if (ngx_directio_on(src->file->fd) == NGX_FILE_ERROR) { | 
 |                 ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, ngx_errno, | 
 |                               ngx_directio_on_n " \"%s\" failed", | 
 |                               src->file->name.data); | 
 |             } | 
 |  | 
 |             ngx_set_errno(err); | 
 |  | 
 |             ctx->unaligned = 0; | 
 |         } | 
 |  | 
 | #endif | 
 |  | 
 |         if (n == NGX_ERROR) { | 
 |             return (ngx_int_t) n; | 
 |         } | 
 |  | 
 |         if (n != size) { | 
 |             ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0, | 
 |                           ngx_read_file_n " read only %z of %O from \"%s\"", | 
 |                           n, size, src->file->name.data); | 
 |             return NGX_ERROR; | 
 |         } | 
 |  | 
 |         dst->last += n; | 
 |  | 
 |         if (sendfile) { | 
 |             dst->in_file = 1; | 
 |             dst->file = src->file; | 
 |             dst->file_pos = src->file_pos; | 
 |             dst->file_last = src->file_pos + n; | 
 |  | 
 |         } else { | 
 |             dst->in_file = 0; | 
 |         } | 
 |  | 
 |         src->file_pos += n; | 
 |  | 
 |         if (src->file_pos == src->file_last) { | 
 |             dst->flush = src->flush; | 
 |             dst->last_buf = src->last_buf; | 
 |             dst->last_in_chain = src->last_in_chain; | 
 |         } | 
 |     } | 
 |  | 
 |     return NGX_OK; | 
 | } | 
 |  | 
 |  | 
 | ngx_int_t | 
 | ngx_chain_writer(void *data, ngx_chain_t *in) | 
 | { | 
 |     ngx_chain_writer_ctx_t *ctx = data; | 
 |  | 
 |     off_t              size; | 
 |     ngx_chain_t       *cl, *ln, *chain; | 
 |     ngx_connection_t  *c; | 
 |  | 
 |     c = ctx->connection; | 
 |  | 
 |     for (size = 0; in; in = in->next) { | 
 |  | 
 | #if 1 | 
 |         if (ngx_buf_size(in->buf) == 0 && !ngx_buf_special(in->buf)) { | 
 |  | 
 |             ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0, | 
 |                           "zero size buf in chain writer " | 
 |                           "t:%d r:%d f:%d %p %p-%p %p %O-%O", | 
 |                           in->buf->temporary, | 
 |                           in->buf->recycled, | 
 |                           in->buf->in_file, | 
 |                           in->buf->start, | 
 |                           in->buf->pos, | 
 |                           in->buf->last, | 
 |                           in->buf->file, | 
 |                           in->buf->file_pos, | 
 |                           in->buf->file_last); | 
 |  | 
 |             ngx_debug_point(); | 
 |  | 
 |             continue; | 
 |         } | 
 | #endif | 
 |  | 
 |         size += ngx_buf_size(in->buf); | 
 |  | 
 |         ngx_log_debug2(NGX_LOG_DEBUG_CORE, c->log, 0, | 
 |                        "chain writer buf fl:%d s:%uO", | 
 |                        in->buf->flush, ngx_buf_size(in->buf)); | 
 |  | 
 |         cl = ngx_alloc_chain_link(ctx->pool); | 
 |         if (cl == NULL) { | 
 |             return NGX_ERROR; | 
 |         } | 
 |  | 
 |         cl->buf = in->buf; | 
 |         cl->next = NULL; | 
 |         *ctx->last = cl; | 
 |         ctx->last = &cl->next; | 
 |     } | 
 |  | 
 |     ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0, | 
 |                    "chain writer in: %p", ctx->out); | 
 |  | 
 |     for (cl = ctx->out; cl; cl = cl->next) { | 
 |  | 
 | #if 1 | 
 |         if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) { | 
 |  | 
 |             ngx_log_error(NGX_LOG_ALERT, ctx->pool->log, 0, | 
 |                           "zero size buf in chain writer " | 
 |                           "t:%d r:%d f:%d %p %p-%p %p %O-%O", | 
 |                           cl->buf->temporary, | 
 |                           cl->buf->recycled, | 
 |                           cl->buf->in_file, | 
 |                           cl->buf->start, | 
 |                           cl->buf->pos, | 
 |                           cl->buf->last, | 
 |                           cl->buf->file, | 
 |                           cl->buf->file_pos, | 
 |                           cl->buf->file_last); | 
 |  | 
 |             ngx_debug_point(); | 
 |  | 
 |             continue; | 
 |         } | 
 | #endif | 
 |  | 
 |         size += ngx_buf_size(cl->buf); | 
 |     } | 
 |  | 
 |     if (size == 0 && !c->buffered) { | 
 |         return NGX_OK; | 
 |     } | 
 |  | 
 |     chain = c->send_chain(c, ctx->out, ctx->limit); | 
 |  | 
 |     ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0, | 
 |                    "chain writer out: %p", chain); | 
 |  | 
 |     if (chain == NGX_CHAIN_ERROR) { | 
 |         return NGX_ERROR; | 
 |     } | 
 |  | 
 |     for (cl = ctx->out; cl && cl != chain; /* void */) { | 
 |         ln = cl; | 
 |         cl = cl->next; | 
 |         ngx_free_chain(ctx->pool, ln); | 
 |     } | 
 |  | 
 |     ctx->out = chain; | 
 |  | 
 |     if (ctx->out == NULL) { | 
 |         ctx->last = &ctx->out; | 
 |  | 
 |         if (!c->buffered) { | 
 |             return NGX_OK; | 
 |         } | 
 |     } | 
 |  | 
 |     return NGX_AGAIN; | 
 | } |