]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MAJOR: start to change buffer API
authorWilly Tarreau <w@1wt.eu>
Fri, 29 Jun 2018 16:42:02 +0000 (18:42 +0200)
committerWilly Tarreau <w@1wt.eu>
Thu, 19 Jul 2018 14:23:42 +0000 (16:23 +0200)
This is intentionally the minimal and safest set of changes, some cleanups
area still required. These changes are quite tricky and cannot be
independantly tested, so it's important to keep this patch as bisectable
as possible.

buf_empty and buf_wanted were changed and are now exactly similar since
there's no <p> member in the structure anymore. Given that no test is
ever made in the code to check that buf == &buf_wanted, it may be possible
that we don't need to have two anymore, unless some buf_empty tests have
precedence. This will have to be investigated.

A significant part of this commit affects the HTTP compression code,
which used to deeply manipulate the input and output buffers without
any reasonable solution for a better abstraction. For this reason, if
any regression is met and designates this patch as the culprit, it is
important to run tests which specifically involve compression or which
definitely don't use it in order to spot the issue.

Cc: Olivier Houchard <ohouchard@haproxy.com>
include/common/buf.h
include/common/buffer.h
include/proto/channel.h
src/buffer.c
src/flt_http_comp.c

index d9de0734fcab12b259bb45259ba1be481fea7b29..02a66b36662763a9bc3e7206bbe977af47d0a81e 100644 (file)
 
 /* Structure defining a buffer's head */
 struct buffer {
-       char *p;                    /* buffer's start pointer, separates in and out data */
+       size_t head;                /* start offset of remaining data relative to data */
+       size_t len;                 /* length of data after head */
        size_t size;                /* buffer size in bytes */
-       size_t i;                   /* number of input bytes pending for analysis in the buffer */
-       size_t o;                   /* number of out bytes the sender can consume from this buffer */
+       size_t output;              /* TEMPORARY: part of <len> which is to be forwarded */
        char data[0];               /* <size> bytes of stored data */
 };
 
@@ -73,7 +73,7 @@ static inline char *b_wrap(const struct buffer *b)
 /* b_data() : returns the number of bytes present in the buffer. */
 static inline size_t b_data(const struct buffer *b)
 {
-       return b->i + b->o;
+       return b->len;
 }
 
 /* b_room() : returns the amount of room left in the buffer */
@@ -95,12 +95,12 @@ static inline size_t b_full(const struct buffer *b)
  */
 static inline size_t __b_stop_ofs(const struct buffer *b)
 {
-       return b->p - b->data + b->i;
+       return b->head + b->len;
 }
 
 static inline const char *__b_stop(const struct buffer *b)
 {
-       return b->p + b->i;
+       return b_orig(b) + __b_stop_ofs(b);
 }
 
 static inline size_t b_stop_ofs(const struct buffer *b)
@@ -114,7 +114,7 @@ static inline size_t b_stop_ofs(const struct buffer *b)
 
 static inline const char *b_stop(const struct buffer *b)
 {
-       return b->data + b_stop_ofs(b);
+       return b_orig(b) + b_stop_ofs(b);
 }
 
 
@@ -125,32 +125,27 @@ static inline const char *b_stop(const struct buffer *b)
  */
 static inline size_t __b_peek_ofs(const struct buffer *b, size_t ofs)
 {
-       return b->p - b->data + ofs - b->o;
+       return b->head + ofs;
 }
 
 static inline char *__b_peek(const struct buffer *b, size_t ofs)
 {
-       return b->p - b->o + ofs;
+       return b_orig(b) + __b_peek_ofs(b, ofs);
 }
 
 static inline size_t b_peek_ofs(const struct buffer *b, size_t ofs)
 {
        size_t ret = __b_peek_ofs(b, ofs);
 
-       if (ret >= b->size) {
-               /* wraps either up or down */
-               if ((ssize_t)ret < 0)
-                       ret += b->size;
-               else
-                       ret -= b->size;
-       }
+       if (ret >= b->size)
+               ret -= b->size;
 
        return ret;
 }
 
 static inline char *b_peek(const struct buffer *b, size_t ofs)
 {
-       return (char *)b->data + b_peek_ofs(b, ofs);
+       return b_orig(b) + b_peek_ofs(b, ofs);
 }
 
 
@@ -160,22 +155,22 @@ static inline char *b_peek(const struct buffer *b, size_t ofs)
  */
 static inline size_t __b_head_ofs(const struct buffer *b)
 {
-       return __b_peek_ofs(b, 0);
+       return b->head;
 }
 
 static inline char *__b_head(const struct buffer *b)
 {
-       return __b_peek(b, 0);
+       return b_orig(b) + __b_head_ofs(b);
 }
 
 static inline size_t b_head_ofs(const struct buffer *b)
 {
-       return b_peek_ofs(b, 0);
+       return __b_head_ofs(b);
 }
 
 static inline char *b_head(const struct buffer *b)
 {
-       return b_peek(b, 0);
+       return __b_head(b);
 }
 
 
@@ -248,20 +243,11 @@ static inline int b_almost_full(const struct buffer *b)
 }
 
 /* b_space_wraps() : returns non-zero only if the buffer's free space wraps :
- *  [     |oooo|           ]    => yes
- *  [          |iiii|      ]    => yes
- *  [     |oooo|iiii|      ]    => yes
- *  [oooo|                 ]    => no
- *  [                 |oooo]    => no
- *  [iiii|                 ]    => no
- *  [                 |iiii]    => no
- *  [oooo|iiii|            ]    => no
- *  [            |oooo|iiii]    => no
- *  [iiii|            |oooo]    => no
- *  [oo|iiii|           |oo]    => no
- *  [iiii|           |oo|ii]    => no
- *  [oooooooooo|iiiiiiiiiii]    => no
- *  [iiiiiiiiiiiii|oooooooo]    => no
+ *  [     |xxxx|           ]    => yes
+ *  [xxxx|                 ]    => no
+ *  [                 |xxxx]    => no
+ *  [xxxx|            |xxxx]    => no
+ *  [xxxxxxxxxx|xxxxxxxxxxx]    => no
  *
  *  So the only case where the buffer does not wrap is when there's data either
  *  at the beginning or at the end of the buffer. Thus we have this :
@@ -379,32 +365,29 @@ static inline size_t b_getblk_nc(const struct buffer *buf, const char **blk1, si
 /* b_reset() : resets a buffer. The size is not touched. */
 static inline void b_reset(struct buffer *b)
 {
-       b->o = 0;
-       b->i = 0;
-       b->p = b_orig(b);
+       b->head = 0;
+       b->len  = 0;
+       b->output = 0;
 }
 
 /* b_sub() : decreases the buffer length by <count> */
 static inline void b_sub(struct buffer *b, size_t count)
 {
-       b->i -= count;
+       b->len -= count;
 }
 
 /* b_add() : increase the buffer length by <count> */
 static inline void b_add(struct buffer *b, size_t count)
 {
-       b->i += count;
+       b->len += count;
 }
 
 /* b_set_data() : sets the buffer's length */
 static inline void b_set_data(struct buffer *b, size_t len)
 {
-       if (len >= b->o)
-               b->i = len - b->o;
-       else {
-               b->o = len;
-               b->i = 0;
-       }
+       if (len < b->output)
+               b->output = len;
+       b->len = len;
 }
 
 /* b_del() : skips <del> bytes in a buffer <b>. Covers both the output and the
@@ -413,22 +396,21 @@ static inline void b_set_data(struct buffer *b, size_t len)
  */
 static inline void b_del(struct buffer *b, size_t del)
 {
-       if (del <= b->o) {
-               b->o -= del;
-               del = 0;
-       }
-       if (del) {
-               b->p = b_peek(b, del);
-               b->i -= del;
-               del = 0;
-       }
+       if (del >= b->output)
+               b->output = 0;
+       else
+               b->output -= del;
+       b->len  -= del;
+       b->head += del;
+       if (b->head >= b->size)
+               b->head -= b->size;
 }
 
 /* b_realign_if_empty() : realigns a buffer if it's empty */
 static inline void b_realign_if_empty(struct buffer *b)
 {
        if (!b_data(b))
-               b->p = b->data;
+               b->head = 0;
 }
 
 /* b_slow_realign() : this function realigns a possibly wrapping buffer so that
@@ -470,7 +452,7 @@ static inline void b_slow_realign(struct buffer *b, char *swap, size_t output)
        memcpy(b_orig(b), swap, b_data(b) - output);
        memcpy(b_wrap(b) - output, swap + b_size(b) - output, output);
 
-       b->p = b->data;
+       b->head = b_size(b) - output;
 }
 
 #endif /* _COMMON_BUF_H */
index 86eec168b7d3c3ba2951655a628315f5732bc4a7..fde7b03f29dac08958e15a494dcd1808272ebd66 100644 (file)
@@ -104,16 +104,14 @@ static inline int buffer_almost_full(const struct buffer *buf)
        return b_almost_full(buf);
 }
 
-/* Cut the first <n> pending bytes in a contiguous buffer. It is illegal to
- * call this function with remaining data waiting to be sent (o > 0). The
- * caller must ensure that <n> is smaller than the actual buffer's length.
- * This is mainly used to remove empty lines at the beginning of a request
- * or a response.
+/* Cut the first <n> pending bytes in a contiguous buffer. The caller must
+ * ensure that <n> is smaller than the actual buffer's length. This is mainly
+ * used to remove empty lines at the beginning of a request or a response.
  */
 static inline void bi_fast_delete(struct buffer *buf, int n)
 {
-       buf->i -= n;
-       buf->p += n;
+       buf->len  -= n;
+       buf->head += n;
 }
 
 /* This function writes the string <str> at position <pos> which must be in
@@ -128,16 +126,16 @@ static inline int buffer_replace(struct buffer *b, char *pos, char *end, const c
        return buffer_replace2(b, pos, end, str, strlen(str));
 }
 
-/* Tries to write char <c> into output data at buffer <b>. Supports wrapping.
- * Data are truncated if buffer is full.
+/* Tries to append char <c> at the end of buffer <b>. Supports wrapping. Data
+ * are truncated if buffer is full.
  */
 static inline void bo_putchr(struct buffer *b, char c)
 {
        if (b_data(b) == b->size)
                return;
        *b_tail(b) = c;
-       b->p = b_peek(b, b->o + 1);
-       b->o++;
+       b->len++;
+       b->output++;
 }
 
 /* Tries to append block <blk> at the end of buffer <b>. Supports wrapping.
@@ -158,13 +156,12 @@ static inline unsigned int bo_putblk(struct buffer *b, const char *blk, unsigned
                half = len;
 
        memcpy(b_tail(b), blk, half);
-       b->p = b_peek(b, b->o + half);
-       b->o += half;
+       b->len += half;
        if (len > half) {
                memcpy(b_tail(b), blk + half, len - half);
-               b->p = b_peek(b, b->o + len - half);
-               b->o += len - half;
+               b->len += len - half;
        }
+       b->output += len;
        return len;
 }
 
@@ -194,7 +191,7 @@ static inline void bi_putchr(struct buffer *b, char c)
        if (b_data(b) == b->size)
                return;
        *b_tail(b) = c;
-       b->i++;
+       b->len++;
 }
 
 /* Tries to append block <blk> at the end of buffer <b>. Supports wrapping.
@@ -215,10 +212,10 @@ static inline unsigned int bi_putblk(struct buffer *b, const char *blk, unsigned
                half = len;
 
        memcpy(b_tail(b), blk, half);
-       b->i += half;
+       b->len += half;
        if (len > half) {
                memcpy(b_tail(b), blk + half, len - half);
-               b->i += len - half;
+               b->len += len - half;
        }
        return len;
 }
@@ -443,7 +440,7 @@ static inline int bi_istput(struct buffer *b, const struct ist ist)
                return r.len < b->size ? 0 : -1;
 
        p = b_tail(b);
-       b->i += r.len;
+       b->len += r.len;
        while (r.len--) {
                *p++ = *r.ptr++;
                if (unlikely(p == end))
@@ -472,8 +469,8 @@ static inline int bo_istput(struct buffer *b, const struct ist ist)
                return r.len < b->size ? 0 : -1;
 
        p = b_tail(b);
-       b->p = b_peek(b, b->o + r.len);
-       b->o += r.len;
+       b->len += r.len;
+       b->output += r.len;
        while (r.len--) {
                *p++ = *r.ptr++;
                if (unlikely(p == end))
index 8ca669578f4d77aead33544c1b1924949a42db27..291bb08f886621a1c057bff9870d0ac603a4fcaa 100644 (file)
@@ -128,7 +128,7 @@ static inline size_t c_full(const struct channel *c)
 /* co_data() : returns the amount of output data in the channel's buffer */
 static inline size_t co_data(const struct channel *c)
 {
-       return c->buf->o;
+       return c->buf->output;
 }
 
 /* ci_data() : returns the amount of input data in the channel's buffer */
@@ -170,11 +170,7 @@ static inline char *c_ptr(const struct channel *c, ssize_t ofs)
  */
 static inline void c_adv(struct channel *c, size_t adv)
 {
-       struct buffer *b = c->buf;
-
-       b->p = c_ptr(c, adv);
-       b->i -= adv;
-       b->o += adv;
+       c->buf->output += adv;
 }
 
 /* c_rew() : rewinds the channel's buffer by <adv> bytes, which means that the
@@ -184,11 +180,7 @@ static inline void c_adv(struct channel *c, size_t adv)
  */
 static inline void c_rew(struct channel *c, size_t adv)
 {
-       struct buffer *b = c->buf;
-
-       b->p = c_ptr(c, (int)-adv);
-       b->i += adv;
-       b->o -= adv;
+       c->buf->output -= adv;
 }
 
 /* c_realign_if_empty() : realign the channel's buffer if it's empty */
@@ -200,7 +192,8 @@ static inline void c_realign_if_empty(struct channel *chn)
 /* Sets the amount of output for the channel */
 static inline void co_set_data(struct channel *c, size_t output)
 {
-       c->buf->o = output;
+       c->buf->len += output - c->buf->output;
+       c->buf->output = output;
 }
 
 
@@ -750,7 +743,7 @@ static inline void channel_truncate(struct channel *chn)
        if (!ci_data(chn))
                return;
 
-       chn->buf->i = 0;
+       chn->buf->len = co_data(chn);
 }
 
 /* This function realigns a possibly wrapping channel buffer so that the input
index a1ffec7a5dd769dcde9879ea76eba6b5e681328a..306f011d1b221afa5564a6f99b90bb47d46f759f 100644 (file)
@@ -28,8 +28,8 @@ struct pool_head *pool_head_buffer;
  * what channel wants a buffer. They can reliably be exchanged, the split
  * between the two is only an optimization.
  */
-struct buffer buf_empty  = { .p = buf_empty.data };
-struct buffer buf_wanted = { .p = buf_wanted.data };
+struct buffer buf_empty  = {  };
+struct buffer buf_wanted = {  };
 
 /* list of objects waiting for at least one buffer */
 struct list buffer_wq = LIST_HEAD_INIT(buffer_wq);
index 4972088c0c2a11ff794acfb3215bc197de4a16df..f8c82c4b7d8a3a83176591d29ae741d911639cbe 100644 (file)
@@ -38,6 +38,7 @@ static struct pool_head *pool_head_comp_state = NULL;
 
 static THREAD_LOCAL struct buffer *tmpbuf = &buf_empty;
 static THREAD_LOCAL struct buffer *zbuf   = &buf_empty;
+static THREAD_LOCAL unsigned int buf_output;
 
 struct comp_state {
        struct comp_ctx  *comp_ctx;   /* compression context */
@@ -56,13 +57,14 @@ static int select_compression_response_header(struct comp_state *st,
                                              struct stream *s,
                                              struct http_msg *msg);
 
-static int http_compression_buffer_init(struct channel *inc, struct buffer *out);
+static int http_compression_buffer_init(struct channel *inc, struct buffer *out, unsigned int *out_len);
 static int http_compression_buffer_add_data(struct comp_state *st,
                                            struct buffer *in,
+                                           int in_out,
                                            struct buffer *out, int sz);
 static int http_compression_buffer_end(struct comp_state *st, struct stream *s,
                                       struct channel *chn, struct buffer **out,
-                                      int end);
+                                      unsigned int *out_len, int end);
 
 /***********************************************************************/
 static int
@@ -191,7 +193,7 @@ comp_http_data(struct stream *s, struct filter *filter, struct http_msg *msg)
 
                b_reset(tmpbuf);
                c_adv(chn, fwd);
-               ret = http_compression_buffer_init(chn, zbuf);
+               ret = http_compression_buffer_init(chn, zbuf, &buf_output);
                c_rew(chn, fwd);
                if (ret < 0) {
                        msg->chn->flags |= CF_WAKE_WRITE;
@@ -216,7 +218,7 @@ comp_http_data(struct stream *s, struct filter *filter, struct http_msg *msg)
        }
        else {
                c_adv(chn, *nxt);
-               ret = http_compression_buffer_add_data(st, chn->buf, zbuf, len);
+               ret = http_compression_buffer_add_data(st, chn->buf, co_data(chn), zbuf, len);
                c_rew(chn, *nxt);
                if (ret < 0)
                        return ret;
@@ -242,7 +244,7 @@ comp_http_chunk_trailers(struct stream *s, struct filter *filter,
 
                        b_reset(tmpbuf);
                        c_adv(chn, fwd);
-                       http_compression_buffer_init(chn, zbuf);
+                       http_compression_buffer_init(chn, zbuf, &buf_output);
                        c_rew(chn, fwd);
                        st->initialized = 1;
                }
@@ -296,17 +298,18 @@ comp_http_forward_data(struct stream *s, struct filter *filter,
        }
 
        if (msg->flags & HTTP_MSGF_TE_CHNK) {
-               ret = http_compression_buffer_add_data(st, tmpbuf, zbuf, tmpbuf->i);
-               if (ret != tmpbuf->i) {
+               ret = http_compression_buffer_add_data(st, tmpbuf, 0,
+                   zbuf, b_data(tmpbuf));
+               if (ret != b_data(tmpbuf)) {
                        ha_warning("HTTP compression failed: Must consume %u bytes but only %d bytes consumed\n",
-                                  (unsigned int)tmpbuf->i, ret);
+                                  (unsigned int)b_data(tmpbuf), ret);
                        return -1;
                }
        }
 
        st->consumed = len - st->hdrs_len - st->tlrs_len;
        c_adv(msg->chn, flt_rsp_fwd(filter) + st->hdrs_len);
-       ret = http_compression_buffer_end(st, s, msg->chn, &zbuf, msg->msg_state >= HTTP_MSG_TRAILERS);
+       ret = http_compression_buffer_end(st, s, msg->chn, &zbuf, &buf_output, msg->msg_state >= HTTP_MSG_TRAILERS);
        c_rew(msg->chn, flt_rsp_fwd(filter) + st->hdrs_len);
        if (ret < 0)
                return ret;
@@ -601,7 +604,7 @@ http_emit_chunk_size(char *end, unsigned int chksz)
  * Init HTTP compression
  */
 static int
-http_compression_buffer_init(struct channel *inc, struct buffer *out)
+http_compression_buffer_init(struct channel *inc, struct buffer *out, unsigned int *out_len)
 {
        /* output stream requires at least 10 bytes for the gzip header, plus
         * at least 8 bytes for the gzip trailer (crc+len), plus a possible
@@ -616,9 +619,8 @@ http_compression_buffer_init(struct channel *inc, struct buffer *out)
         * cancel the operation later, it's cheap.
         */
        b_reset(out);
-       out->o = co_data(inc);
-       out->p += out->o;
-       out->i = 10;
+       *out_len = co_data(inc);
+       out->head += *out_len + 10;
        return 0;
 }
 
@@ -627,7 +629,7 @@ http_compression_buffer_init(struct channel *inc, struct buffer *out)
  */
 static int
 http_compression_buffer_add_data(struct comp_state *st, struct buffer *in,
-                                struct buffer *out, int sz)
+                                int in_out, struct buffer *out, int sz)
 {
        int consumed_data = 0;
        int data_process_len;
@@ -643,15 +645,15 @@ http_compression_buffer_add_data(struct comp_state *st, struct buffer *in,
        data_process_len = MIN(b_room(out), sz);
 
        block1 = data_process_len;
-       if (block1 > b_contig_data(in, in->o))
-               block1 = b_contig_data(in, in->o);
+       if (block1 > b_contig_data(in, in_out))
+               block1 = b_contig_data(in, in_out);
        block2 = data_process_len - block1;
 
        /* compressors return < 0 upon error or the amount of bytes read */
-       consumed_data = st->comp_algo->add_data(st->comp_ctx, b_peek(in, in->o), block1, out);
+       consumed_data = st->comp_algo->add_data(st->comp_ctx, b_head(in) + in_out, block1, out);
        if (consumed_data != block1 || !block2)
                goto end;
-       consumed_data = st->comp_algo->add_data(st->comp_ctx, in->data, block2, out);
+       consumed_data = st->comp_algo->add_data(st->comp_ctx, b_peek(in, 0), block2, out);
        if (consumed_data < 0)
                goto end;
        consumed_data += block1;
@@ -667,11 +669,12 @@ http_compression_buffer_add_data(struct comp_state *st, struct buffer *in,
 static int
 http_compression_buffer_end(struct comp_state *st, struct stream *s,
                            struct channel *chn, struct buffer **out,
-                           int end)
+                           unsigned int *buf_out, int end)
 {
-       struct buffer *ib = chn->buf, *ob = *out;
+       struct buffer *ob = *out;
        char *tail;
        int   to_forward, left;
+       unsigned int tmp_out;
 
 #if defined(USE_SLZ) || defined(USE_ZLIB)
        int ret;
@@ -701,35 +704,38 @@ http_compression_buffer_end(struct comp_state *st, struct stream *s,
         *       +---------+---+------------+-----------+
         *     data        p                           size
         *
-        * <out> is the room reserved to copy ib->o. It starts at ob->data and
-        * has not yet been filled. <c> is the room reserved to write the chunk
-        * size (10 bytes). <comp_in> is the compressed equivalent of the data
-        * part of ib->i. <empty> is the amount of empty bytes at the end of
-        * the buffer, into which we may have to copy the remaining bytes from
-        * ib->i after the data (chunk size, trailers, ...).
+        * <out> is the room reserved to copy the channel output. It starts at
+        * ob->data and has not yet been filled. <c> is the room reserved to
+        * write the chunk size (10 bytes). <comp_in> is the compressed
+        * equivalent of the data part of ib->len. <empty> is the amount of
+        * empty bytes at the end of  the buffer, into which we may have to
+        * copy the remaining bytes from ib->len after the data
+        * (chunk size, trailers, ...).
         */
 
        /* Write real size at the begining of the chunk, no need of wrapping.
         * We write the chunk using a dynamic length and adjust ob->p and ob->i
         * accordingly afterwards. That will move <out> away from <data>.
         */
-       left = 10 - http_emit_chunk_size(ob->p + 10, ob->i - 10);
-       ob->p += left;
-       ob->i -= left;
-
-       /* Copy previous data from ib->o into ob->o */
-       if (ib->o > 0) {
-               left = b_contig_data(ib, 0);
-               if (left > ib->o)
-                       left = ib->o;
-
-               memcpy(ob->p - ob->o, b_head(ib), left);
-               if (ib->o - left) /* second part of the buffer */
-                       memcpy(ob->p - ob->o + left, ib->data, ib->o - left);
+       left = http_emit_chunk_size(b_head(ob), b_data(ob));
+       b_add(ob, left);
+       ob->head -= *buf_out + (left);
+       /* Copy previous data from chn into ob */
+       if (co_data(chn) > 0) {
+               left = b_contig_data(chn->buf, 0);
+               if (left > *buf_out)
+                       left = *buf_out;
+
+               memcpy(b_head(ob), co_head(chn), left);
+               b_add(ob, left);
+               if (co_data(chn) - left) {/* second part of the buffer */
+                       memcpy(b_head(ob) + left, b_orig(chn->buf), co_data(chn) - left);
+                       b_add(ob, co_data(chn) - left);
+               }
        }
 
        /* chunked encoding requires CRLF after data */
-       tail = ob->p + ob->i;
+       tail = b_tail(ob);
        *tail++ = '\r';
        *tail++ = '\n';
 
@@ -751,8 +757,8 @@ http_compression_buffer_end(struct comp_state *st, struct stream *s,
                }
        }
 
-       ob->i = tail - ob->p;
-       to_forward = ob->i;
+       b_add(ob, tail - b_tail(ob));
+       to_forward = b_data(ob) - *buf_out;
 
        /* update input rate */
        if (st->comp_ctx && st->comp_ctx->cur_lvl > 0) {
@@ -766,19 +772,22 @@ http_compression_buffer_end(struct comp_state *st, struct stream *s,
 
        /* copy the remaining data in the tmp buffer. */
        c_adv(chn, st->consumed);
-       if (ib->i > 0) {
+       if (b_data(chn->buf) - co_data(chn) > 0) {
                left = ci_contig_data(chn);
-               memcpy(ob->p + ob->i, ci_head(chn), left);
+               memcpy(b_tail(ob), ci_head(chn), left);
                b_add(ob, left);
-               if (ib->i - left) {
-                       memcpy(ob->p + ob->i, ib->data, ib->i - left);
-                       b_add(ob, ib->i - left);
+               if (b_data(chn->buf) - (co_data(chn) + left)) {
+                       memcpy(b_tail(ob), b_orig(chn->buf), b_data(chn->buf) - left);
+                       b_add(ob, b_data(chn->buf) - left);
                }
        }
-
        /* swap the buffers */
+       *out = chn->buf;
        chn->buf = ob;
-       *out = ib;
+       tmp_out = chn->buf->output;
+       chn->buf->output = *buf_out;
+       *buf_out = tmp_out;
+
 
 
        if (st->comp_ctx && st->comp_ctx->cur_lvl > 0) {