Files
haproxy/src/buf.c
Willy Tarreau ac66df4e2e REORG: buffers: move some of the heavy functions from buf.h to buf.c
Over time, some of the buffer management functions grew quite a bit,
and were still forced to remain inlined since all defined in buf.h.
Let's create buf.c and move the heaviest ones there. All those moved
here were above 200 bytes.
2024-10-12 16:29:15 +02:00

733 lines
19 KiB
C

/*
* Simple buffer handling - heavy functions definitions
*
* Most of the low-level operations are in buf.h, but this file centralizes
* heavier functions that shouldn't be inlined.
*
* SPDX-License-Identifier: GPL-2.0-or-later.
*
*/
#include <sys/types.h>
#include <string.h>
#include <haproxy/api.h>
#include <haproxy/buf.h>
/* b_getblk_ofs() : gets one full block of data at once from a buffer, starting
* from offset <offset> after the buffer's area, and for exactly <len> bytes.
* As a convenience to avoid complex checks in callers, the offset is allowed
* to exceed a valid one by no more than one buffer size, and will automatically
* be wrapped. The caller is responsible for ensuring that <len> doesn't exceed
* the known length of the available data at this position, otherwise undefined
* data will be returned. This is meant to be used on concurrently accessed
* buffers, so that a reader can read a known area while the buffer is being fed
* and trimmed. The function guarantees never to use ->head nor ->data. The
* buffer is left unaffected. It always returns the number of bytes copied.
*/
size_t b_getblk_ofs(const struct buffer *buf, char *blk, size_t len, size_t offset)
{
size_t firstblock;
if (offset >= buf->size)
offset -= buf->size;
BUG_ON(offset >= buf->size);
firstblock = buf->size - offset;
if (firstblock >= len)
firstblock = len;
memcpy(blk, b_orig(buf) + offset, firstblock);
if (len > firstblock)
memcpy(blk + firstblock, b_orig(buf), len - firstblock);
return len;
}
/* b_getblk() : gets one full block of data at once from a buffer, starting
* from offset <offset> after the buffer's head, and limited to no more than
* <len> bytes. The caller is responsible for ensuring that neither <offset>
* nor <offset>+<len> exceed the total number of bytes available in the buffer.
* Return values :
* >0 : number of bytes read, equal to requested size.
* =0 : not enough data available. <blk> is left undefined.
* The buffer is left unaffected.
*/
size_t b_getblk(const struct buffer *buf, char *blk, size_t len, size_t offset)
{
size_t firstblock;
BUG_ON(buf->data > buf->size);
BUG_ON(offset > buf->data);
BUG_ON(offset + len > buf->data);
if (len + offset > b_data(buf))
return 0;
firstblock = b_wrap(buf) - b_head(buf);
if (firstblock > offset) {
if (firstblock >= len + offset) {
memcpy(blk, b_head(buf) + offset, len);
return len;
}
memcpy(blk, b_head(buf) + offset, firstblock - offset);
memcpy(blk + firstblock - offset, b_orig(buf), len - firstblock + offset);
return len;
}
memcpy(blk, b_orig(buf) + offset - firstblock, len);
return len;
}
/* b_getblk_nc() : gets one or two blocks of data at once from a buffer,
* starting from offset <ofs> after the beginning of its output, and limited to
* no more than <max> bytes. The caller is responsible for ensuring that
* neither <ofs> nor <ofs>+<max> exceed the total number of bytes available in
* the buffer. Return values :
* >0 : number of blocks filled (1 or 2). blk1 is always filled before blk2.
* =0 : not enough data available. <blk*> are left undefined.
* The buffer is left unaffected. Unused buffers are left in an undefined state.
*/
size_t b_getblk_nc(const struct buffer *buf, const char **blk1, size_t *len1, const char **blk2, size_t *len2, size_t ofs, size_t max)
{
size_t l1;
BUG_ON_HOT(buf->data > buf->size);
BUG_ON_HOT(ofs > buf->data);
BUG_ON_HOT(ofs + max > buf->data);
if (!max)
return 0;
*blk1 = b_peek(buf, ofs);
l1 = b_wrap(buf) - *blk1;
if (l1 < max) {
*len1 = l1;
*len2 = max - l1;
*blk2 = b_orig(buf);
return 2;
}
*len1 = max;
return 1;
}
/* Locates the longest part of the buffer that is composed exclusively of
* characters not in the <delim> set, and delimited by one of these characters,
* and returns the initial part and the first of such delimiters. A single
* escape character in <escape> may be specified so that when not 0 and found,
* the character that follows it is never taken as a delimiter. Note that
* <delim> cannot contain the zero byte, hence this function is not usable with
* byte zero as a delimiter.
*
* Return values :
* >0 : number of bytes read. Includes the sep if present before len or end.
* =0 : no sep before end found. <str> is left undefined.
*
* The buffer is left unaffected. Unused buffers are left in an undefined state.
*/
size_t b_getdelim(const struct buffer *buf, size_t offset, size_t count,
char *str, size_t len, const char *delim, char escape)
{
uchar delim_map[256 / 8];
int found, escaped;
uint pos, bit;
size_t ret, max;
uchar b;
char *p;
ret = 0;
p = b_peek(buf, offset);
max = len;
if (!count || offset+count > b_data(buf))
goto out;
if (max > count) {
max = count;
str[max-1] = 0;
}
/* create the byte map */
memset(delim_map, 0, sizeof(delim_map));
while ((b = *delim)) {
pos = b >> 3;
bit = b & 7;
delim_map[pos] |= 1 << bit;
delim++;
}
found = escaped = 0;
while (max) {
*str++ = b = *p;
ret++;
max--;
if (escape && (escaped || *p == escape)) {
escaped = !escaped;
goto skip;
}
pos = b >> 3;
bit = b & 7;
if (delim_map[pos] & (1 << bit)) {
found = 1;
break;
}
skip:
p = b_next(buf, p);
}
if (ret > 0 && !found)
ret = 0;
out:
if (max)
*str = 0;
return ret;
}
/* Gets one text line out of aa buffer.
* Return values :
* >0 : number of bytes read. Includes the \n if present before len or end.
* =0 : no '\n' before end found. <str> is left undefined.
*
* The buffer is left unaffected. Unused buffers are left in an undefined state.
*/
size_t b_getline(const struct buffer *buf, size_t offset, size_t count,
char *str, size_t len)
{
size_t ret, max;
char *p;
ret = 0;
p = b_peek(buf, offset);
max = len;
if (!count || offset+count > b_data(buf))
goto out;
if (max > count) {
max = count;
str[max-1] = 0;
}
while (max) {
*str++ = *p;
ret++;
max--;
if (*p == '\n')
break;
p = b_next(buf, p);
}
if (ret > 0 && *(str-1) != '\n')
ret = 0;
out:
if (max)
*str = 0;
return ret;
}
/* b_slow_realign() : this function realigns a possibly wrapping buffer so that
* the part remaining to be parsed is contiguous and starts at the beginning of
* the buffer and the already parsed output part ends at the end of the buffer.
* This provides the best conditions since it allows the largest inputs to be
* processed at once and ensures that once the output data leaves, the whole
* buffer is available at once. The number of output bytes supposedly present
* at the beginning of the buffer and which need to be moved to the end must be
* passed in <output>. A temporary swap area at least as large as b->size must
* be provided in <swap>. It's up to the caller to ensure <output> is no larger
* than the difference between the whole buffer's length and its input.
*/
void b_slow_realign(struct buffer *b, char *swap, size_t output)
{
size_t block1 = output;
size_t block2 = 0;
BUG_ON_HOT(b->data > b->size);
/* process output data in two steps to cover wrapping */
if (block1 > b_size(b) - b_head_ofs(b)) {
block2 = b_peek_ofs(b, block1);
block1 -= block2;
}
memcpy(swap + b_size(b) - output, b_head(b), block1);
memcpy(swap + b_size(b) - block2, b_orig(b), block2);
/* process input data in two steps to cover wrapping */
block1 = b_data(b) - output;
block2 = 0;
if (block1 > b_tail_ofs(b)) {
block2 = b_tail_ofs(b);
block1 = block1 - block2;
}
memcpy(swap, b_peek(b, output), block1);
memcpy(swap + block1, b_orig(b), block2);
/* reinject changes into the buffer */
memcpy(b_orig(b), swap, b_data(b) - output);
memcpy(b_wrap(b) - output, swap + b_size(b) - output, output);
b->head = (output ? b_size(b) - output : 0);
}
/* b_slow_realign_ofs() : this function realigns a possibly wrapping buffer
* setting its new head at <ofs>. Depending of the <ofs> value, the resulting
* buffer may also wrap. A temporary swap area at least as large as b->size must
* be provided in <swap>. It's up to the caller to ensuze <ofs> is not larger
* than b->size.
*/
void b_slow_realign_ofs(struct buffer *b, char *swap, size_t ofs)
{
size_t block1 = b_data(b);
size_t block2 = 0;
BUG_ON_HOT(b->data > b->size);
BUG_ON_HOT(ofs > b->size);
if (__b_tail_ofs(b) >= b_size(b)) {
block2 = b_tail_ofs(b);
block1 -= block2;
}
memcpy(swap, b_head(b), block1);
memcpy(swap + block1, b_orig(b), block2);
block1 = b_data(b);
block2 = 0;
if (block1 > b_size(b) - ofs) {
block1 = b_size(b) - ofs;
block2 = b_data(b) - block1;
}
memcpy(b_orig(b) + ofs, swap, block1);
memcpy(b_orig(b), swap + block1, block2);
b->head = ofs;
}
/* b_putblk_ofs(): puts one full block of data of length <len> from <blk> into
* the buffer, starting from absolute offset <offset> after the buffer's area.
* As a convenience to avoid complex checks in callers, the offset is allowed
* to exceed a valid one by no more than one buffer size, and will automatically
* be wrapped. The caller is responsible for ensuring that <len> doesn't exceed
* the known length of the available room at this position, otherwise data may
* be overwritten. The buffer's length is *not* updated, so generally the caller
* will have updated it before calling this function. This is meant to be used
* on concurrently accessed buffers, so that a writer can append data while a
* reader is blocked by other means from reaching the current area The function
* guarantees never to use ->head nor ->data. It always returns the number of
* bytes copied.
*/
size_t b_putblk_ofs(struct buffer *buf, char *blk, size_t len, size_t offset)
{
size_t firstblock;
if (offset >= buf->size)
offset -= buf->size;
BUG_ON(offset >= buf->size);
firstblock = buf->size - offset;
if (firstblock >= len)
firstblock = len;
memcpy(b_orig(buf) + offset, blk, firstblock);
if (len > firstblock)
memcpy(b_orig(buf), blk + firstblock, len - firstblock);
return len;
}
/* __b_putblk() : tries to append <len> bytes from block <blk> to the end of
* buffer <b> without checking for free space (it's up to the caller to do it).
* Supports wrapping. It must not be called with len == 0.
*/
void __b_putblk(struct buffer *b, const char *blk, size_t len)
{
size_t half = b_contig_space(b);
BUG_ON(b_data(b) + len > b_size(b));
if (half > len)
half = len;
memcpy(b_tail(b), blk, half);
if (len > half)
memcpy(b_peek(b, b_data(b) + half), blk + half, len - half);
b->data += len;
}
/* b_xfer() : transfers at most <count> bytes from buffer <src> to buffer <dst>
* and returns the number of bytes copied. The bytes are removed from <src> and
* added to <dst>. The caller is responsible for ensuring that <count> is not
* larger than b_room(dst). Whenever possible (if the destination is empty and
* at least as much as the source was requested), the buffers are simply
* swapped instead of copied.
*/
size_t b_xfer(struct buffer *dst, struct buffer *src, size_t count)
{
size_t ret, block1, block2;
ret = 0;
if (!count)
goto leave;
ret = b_data(src);
if (!ret)
goto leave;
if (ret > count)
ret = count;
else if (!b_data(dst)) {
/* zero copy is possible by just swapping buffers */
struct buffer tmp = *dst;
*dst = *src;
*src = tmp;
goto leave;
}
block1 = b_contig_data(src, 0);
if (block1 > ret)
block1 = ret;
block2 = ret - block1;
if (block1)
__b_putblk(dst, b_head(src), block1);
if (block2)
__b_putblk(dst, b_peek(src, block1), block2);
b_del(src, ret);
leave:
return ret;
}
/* b_ncat() : Copy <count> from <src> buffer at the end of <dst> buffer.
* The caller is responsible for ensuring that <count> is not larger than
* b_room(dst).
* Returns the number of bytes copied.
*/
size_t b_ncat(struct buffer *dst, const struct buffer *src, size_t count)
{
size_t ret, block1, block2;
ret = 0;
if (!count)
goto leave;
ret = b_data(src);
if (!ret)
goto leave;
if (ret > count)
ret = count;
block1 = b_contig_data(src, 0);
if (block1 > ret)
block1 = ret;
block2 = ret - block1;
if (block1)
__b_putblk(dst, b_head(src), block1);
if (block2)
__b_putblk(dst, b_peek(src, block1), block2);
leave:
return ret;
}
/* Moves <len> bytes from absolute position <src> of buffer <b> by <shift>
* bytes, while supporting wrapping of both the source and the destination.
* The position is relative to the buffer's origin and may overlap with the
* target position. The <shift>'s absolute value must be strictly lower than
* the buffer's size. The main purpose is to aggregate data block during
* parsing while removing unused delimiters. The buffer's length is not
* modified, and the caller must take care of size adjustments and holes by
* itself.
*/
void b_move(const struct buffer *b, size_t src, size_t len, ssize_t shift)
{
char *orig = b_orig(b);
size_t size = b_size(b);
size_t dst = src + size + shift;
size_t cnt;
BUG_ON(len > size);
if (dst >= size)
dst -= size;
if (shift < 0) {
BUG_ON(-shift >= size);
/* copy from left to right */
for (; (cnt = len); len -= cnt) {
if (cnt > size - src)
cnt = size - src;
if (cnt > size - dst)
cnt = size - dst;
memmove(orig + dst, orig + src, cnt);
dst += cnt;
src += cnt;
if (dst >= size)
dst -= size;
if (src >= size)
src -= size;
}
}
else if (shift > 0) {
BUG_ON(shift >= size);
/* copy from right to left */
for (; (cnt = len); len -= cnt) {
size_t src_end = src + len;
size_t dst_end = dst + len;
if (dst_end > size)
dst_end -= size;
if (src_end > size)
src_end -= size;
if (cnt > dst_end)
cnt = dst_end;
if (cnt > src_end)
cnt = src_end;
memmove(orig + dst_end - cnt, orig + src_end - cnt, cnt);
}
}
}
/* b_rep_blk() : writes the block <blk> at position <pos> which must be in
* buffer <b>, and moves the part between <end> and the buffer's tail just
* after the end of the copy of <blk>. This effectively replaces the part
* located between <pos> and <end> with a copy of <blk> of length <len>. The
* buffer's length is automatically updated. This is used to replace a block
* with another one inside a buffer. The shift value (positive or negative) is
* returned. If there's no space left, the move is not done. If <len> is null,
* the <blk> pointer is allowed to be null, in order to erase a block.
*/
int b_rep_blk(struct buffer *b, char *pos, char *end, const char *blk, size_t len)
{
int delta;
BUG_ON(pos < b->area || pos >= b->area + b->size);
delta = len - (end - pos);
if (__b_tail(b) + delta > b_wrap(b))
return 0; /* no space left */
if (b_data(b) &&
b_tail(b) + delta > b_head(b) &&
b_head(b) >= b_tail(b))
return 0; /* no space left before wrapping data */
/* first, protect the end of the buffer */
memmove(end + delta, end, b_tail(b) - end);
/* now, copy blk over pos */
if (len)
memcpy(pos, blk, len);
b_add(b, delta);
b_realign_if_empty(b);
return delta;
}
/* b_insert_blk(): inserts the block <blk> at the absolute offset <off> moving
* data between this offset and the buffer's tail just after the end of the copy
* of <blk>. The buffer's length is automatically updated. It Supports
* wrapping. If there are not enough space to perform the copy, 0 is
* returned. Otherwise, the number of bytes copied is returned
*/
int b_insert_blk(struct buffer *b, size_t off, const char *blk, size_t len)
{
size_t pos;
if (!len || len > b_room(b))
return 0; /* nothing to copy or not enough space left */
pos = b_peek_ofs(b, off);
if (pos == b_tail_ofs(b))
__b_putblk(b, blk, len);
else {
size_t delta = b_data(b) - off;
/* first, protect the end of the buffer */
b_move(b, pos, delta, len);
/* change the amount of data in the buffer during the copy */
b_sub(b, delta);
__b_putblk(b, blk, len);
b_add(b, delta);
}
return len;
}
/* __b_put_varint(): encode 64-bit value <v> as a varint into buffer <b>. The
* caller must have checked that the encoded value fits in the buffer so that
* there are no length checks. Wrapping is supported. You don't want to use
* this function but b_put_varint() instead.
*/
void __b_put_varint(struct buffer *b, uint64_t v)
{
size_t data = b->data;
size_t size = b_size(b);
char *wrap = b_wrap(b);
char *tail = b_tail(b);
BUG_ON_HOT(data >= size);
if (v >= 0xF0) {
/* more than one byte, first write the 4 least significant
* bits, then follow with 7 bits per byte.
*/
*tail = v | 0xF0;
v = (v - 0xF0) >> 4;
while (1) {
if (++tail == wrap)
tail -= size;
data++;
if (v < 0x80)
break;
*tail = v | 0x80;
v = (v - 0x80) >> 7;
}
}
/* last byte */
*tail = v;
BUG_ON_HOT(data >= size);
data++;
b->data = data;
}
/* b_put_varint(): try to encode value <v> as a varint into buffer <b>. Returns
* the number of bytes written in case of success, or 0 if there is not enough
* room. Wrapping is supported. No partial writes will be performed.
*/
int b_put_varint(struct buffer *b, uint64_t v)
{
size_t data = b->data;
size_t size = b_size(b);
char *wrap = b_wrap(b);
char *tail = b_tail(b);
if (data != size && v >= 0xF0) {
BUG_ON_HOT(data > size);
/* more than one byte, first write the 4 least significant
* bits, then follow with 7 bits per byte.
*/
*tail = v | 0xF0;
v = (v - 0xF0) >> 4;
while (1) {
if (++tail == wrap)
tail -= size;
data++;
if (data == size || v < 0x80)
break;
*tail = v | 0x80;
v = (v - 0x80) >> 7;
}
}
/* last byte */
if (data == size)
return 0;
*tail = v;
data++;
size = data - b->data;
b->data = data;
return size;
}
/* b_get_varint(): try to decode a varint from buffer <b> into value <vptr>.
* Returns the number of bytes read in case of success, or 0 if there were not
* enough bytes. Wrapping is supported. No partial reads will be performed.
*/
int b_get_varint(struct buffer *b, uint64_t *vptr)
{
const uint8_t *head = (const uint8_t *)b_head(b);
const uint8_t *wrap = (const uint8_t *)b_wrap(b);
size_t data = b->data;
size_t size = b_size(b);
uint64_t v = 0;
int bits = 0;
if (data != 0 && (*head >= 0xF0)) {
v = *head;
bits += 4;
while (1) {
if (++head == wrap)
head -= size;
data--;
if (!data || !(*head & 0x80))
break;
v += (uint64_t)*head << bits;
bits += 7;
}
}
/* last byte */
if (!data)
return 0;
v += (uint64_t)*head << bits;
*vptr = v;
data--;
size = b->data - data;
b_del(b, size);
return size;
}
/* b_peek_varint(): try to decode a varint from buffer <b> at offset <ofs>
* relative to head, into value <vptr>. Returns the number of bytes parsed in
* case of success, or 0 if there were not enough bytes, in which case the
* contents of <vptr> are not updated. Wrapping is supported. The buffer's head
* will NOT be updated. It is illegal to call this function with <ofs> greater
* than b->data.
*/
int b_peek_varint(struct buffer *b, size_t ofs, uint64_t *vptr)
{
const uint8_t *head = (const uint8_t *)b_peek(b, ofs);
const uint8_t *wrap = (const uint8_t *)b_wrap(b);
size_t data = b_data(b) - ofs;
size_t size = b_size(b);
uint64_t v = 0;
int bits = 0;
BUG_ON_HOT(ofs > b_data(b));
if (data != 0 && (*head >= 0xF0)) {
v = *head;
bits += 4;
while (1) {
if (++head == wrap)
head -= size;
data--;
if (!data || !(*head & 0x80))
break;
v += (uint64_t)*head << bits;
bits += 7;
}
}
/* last byte */
if (!data)
return 0;
v += (uint64_t)*head << bits;
*vptr = v;
data--;
size = b->data - ofs - data;
return size;
}