MEDIUM: channel: implement a zero-copy buffer transfer

bi_swpbuf() swaps the buffer passed in argument with the one attached to
the channel, but only if this last one is empty. The idea is to avoid a
copy when buffers can simply be swapped.
diff --git a/include/proto/channel.h b/include/proto/channel.h
index 1cee05a..e38e375 100644
--- a/include/proto/channel.h
+++ b/include/proto/channel.h
@@ -43,6 +43,7 @@
 
 /* SI-to-channel functions working with buffers */
 int bi_putblk(struct channel *chn, const char *str, int len);
+struct buffer *bi_swpbuf(struct channel *chn, struct buffer *buf);
 int bi_putchr(struct channel *chn, char c);
 int bo_inject(struct channel *chn, const char *msg, int len);
 int bo_getline(struct channel *chn, char *str, int len);
diff --git a/src/channel.c b/src/channel.c
index 2f98396..04301fb 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -196,6 +196,51 @@
 	return len;
 }
 
+/* Tries to copy the whole buffer <buf> into the channel's buffer after length
+ * controls. It will only succeed if the target buffer is empty, in which case
+ * it will simply swap the buffers. The buffer not attached to the channel is
+ * returned so that the caller can store it locally. The chn->buf->o and
+ * to_forward pointers are updated. If the output buffer is a dummy buffer or
+ * if it still contains data <buf> is returned, indicating that nothing could
+ * be done. Channel flag READ_PARTIAL is updated if some data can be transferred.
+ * The chunk's length is updated with the number of bytes sent. On errors, NULL
+ * is returned. Note that only buf->i is considered.
+ */
+struct buffer *bi_swpbuf(struct channel *chn, struct buffer *buf)
+{
+	struct buffer *old;
+
+	if (unlikely(channel_input_closed(chn)))
+		return NULL;
+
+	if (!chn->buf->size || !buffer_empty(chn->buf)) {
+		chn->flags |= CF_WAKE_WRITE;
+		return buf;
+	}
+
+	old = chn->buf;
+	chn->buf = buf;
+
+	if (!buf->i)
+		return old;
+
+	chn->total += buf->i;
+
+	if (chn->to_forward) {
+		unsigned long fwd = buf->i;
+		if (chn->to_forward != CHN_INFINITE_FORWARD) {
+			if (fwd > chn->to_forward)
+				fwd = chn->to_forward;
+			chn->to_forward -= fwd;
+		}
+		b_adv(chn->buf, fwd);
+	}
+
+	/* notify that some data was read from the SI into the buffer */
+	chn->flags |= CF_READ_PARTIAL;
+	return old;
+}
+
 /* Gets one text line out of a channel's buffer from a stream interface.
  * Return values :
  *   >0 : number of bytes read. Includes the \n if present before len or end.