diff --git a/include/haproxy/ncbuf-t.h b/include/haproxy/ncbuf-t.h index 538f33ea6..05bebe6f0 100644 --- a/include/haproxy/ncbuf-t.h +++ b/include/haproxy/ncbuf-t.h @@ -87,4 +87,10 @@ struct ncbuf { ncb_sz_t head; }; +enum ncb_ret { + NCB_RET_OK = 0, /* no error */ + + NCB_RET_GAP_SIZE, /* operation would create a too small gap */ +}; + #endif /* _HAPROXY_NCBUF_T_H */ diff --git a/include/haproxy/ncbuf.h b/include/haproxy/ncbuf.h index f0352aff4..c935c1eb6 100644 --- a/include/haproxy/ncbuf.h +++ b/include/haproxy/ncbuf.h @@ -18,4 +18,7 @@ int ncb_is_full(const struct ncbuf *buf); ncb_sz_t ncb_data(const struct ncbuf *buf, ncb_sz_t offset); +enum ncb_ret ncb_add(struct ncbuf *buf, ncb_sz_t off, + const char *data, ncb_sz_t len); + #endif /* _HAPROXY_NCBUF_H */ diff --git a/src/ncbuf.c b/src/ncbuf.c index f50e72571..592240755 100644 --- a/src/ncbuf.c +++ b/src/ncbuf.c @@ -1,5 +1,11 @@ #include +#include + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + #ifdef DEBUG_DEV # include #else @@ -81,6 +87,13 @@ static ncb_sz_t ncb_read_off(const struct ncbuf *buf, char *st) return off; } +/* Add to the offset stored at in . Support wrapping. */ +static void ncb_inc_off(const struct ncbuf *buf, char *st, ncb_sz_t off) +{ + const ncb_sz_t old = ncb_read_off(buf, st); + ncb_write_off(buf, st, old + off); +} + /* Returns true if a gap cannot be inserted at : a reduced gap must be used. */ static int ncb_off_reduced(const struct ncbuf *b, ncb_sz_t off) { @@ -192,6 +205,120 @@ static ncb_sz_t ncb_blk_off(const struct ncb_blk blk, ncb_sz_t off) return off - blk.off; } +/* Simulate insertion in of of length at offset . This + * ensures that minimal block size are respected for newly formed gaps. + * must be the block where the insert operation begins. + * + * Returns NCB_RET_OK if insertion can proceed. + */ +static enum ncb_ret ncb_check_insert(const struct ncbuf *buf, + struct ncb_blk blk, ncb_sz_t off, + const char *data, ncb_sz_t len) +{ + ncb_sz_t off_blk = ncb_blk_off(blk, off); + ncb_sz_t to_copy; + ncb_sz_t left = len; + + /* If insertion starts in a gap, it must leave enough space to keep the + * gap header. + */ + if (left && (blk.flag & NCB_BK_F_GAP)) { + if (off_blk < NCB_GAP_MIN_SZ) + return NCB_RET_GAP_SIZE; + } + + while (left) { + off_blk = ncb_blk_off(blk, off); + to_copy = MIN(left, blk.sz - off_blk); + + if (blk.flag & NCB_BK_F_GAP && off_blk + to_copy < blk.sz) { + /* Insertion must leave enough space for a new gap + * header if stopped in a middle of a gap. + */ + const ncb_sz_t gap_sz = blk.sz - (off_blk + to_copy); + if (gap_sz < NCB_GAP_MIN_SZ && !ncb_blk_is_last(buf, blk)) + return NCB_RET_GAP_SIZE; + } + + left -= to_copy; + data += to_copy; + off += to_copy; + + blk = ncb_blk_next(buf, blk); + } + + return NCB_RET_OK; +} + +/* Fill new of length inside an already existing data at + * offset . Offset is relative to so it cannot be greater than the + * block size. + */ +static ncb_sz_t ncb_fill_data_blk(const struct ncbuf *buf, + struct ncb_blk blk, ncb_sz_t off, + const char *data, ncb_sz_t len) +{ + const ncb_sz_t to_copy = MIN(len, blk.sz - off); + + BUG_ON_HOT(off > blk.sz); + /* This can happens due to previous ncb_blk_find() usage. In this + * case the current fill is a noop. + */ + if (off == blk.sz) + return 0; + + return to_copy; +} + +/* Fill the gap starting at with new of length . + * is relative to so it cannot be greater than the block size. + */ +static ncb_sz_t ncb_fill_gap_blk(const struct ncbuf *buf, + struct ncb_blk blk, ncb_sz_t off, + const char *data, ncb_sz_t len) +{ + const ncb_sz_t to_copy = MIN(len, blk.sz - off); + char *ptr; + + BUG_ON_HOT(off > blk.sz); + /* This can happens due to previous ncb_blk_find() usage. In this + * case the current fill is a noop. + */ + if (off == blk.sz) + return 0; + + /* A new gap must be created if insertion stopped before gap end. */ + if (off + to_copy < blk.sz) { + const ncb_sz_t gap_off = blk.off + off + to_copy; + const ncb_sz_t gap_sz = blk.sz - off - to_copy; + + BUG_ON_HOT(!ncb_off_reduced(buf, gap_off) && + blk.off + blk.sz - gap_off < NCB_GAP_MIN_SZ); + + /* write the new gap header unless this is a reduced gap. */ + if (!ncb_off_reduced(buf, gap_off)) { + char *gap_ptr = ncb_peek(buf, gap_off + NCB_GAP_SZ_OFF); + char *gap_data_ptr = ncb_peek(buf, gap_off + NCB_GAP_SZ_DATA_OFF); + + ncb_write_off(buf, gap_ptr, gap_sz); + ncb_write_off(buf, gap_data_ptr, blk.sz_data); + } + } + + /* fill the gap with new data */ + ptr = ncb_peek(buf, blk.off + off); + if (ptr + to_copy >= ncb_wrap(buf)) { + ncb_sz_t sz1 = ncb_wrap(buf) - ptr; + memcpy(ptr, data, sz1); + memcpy(ncb_orig(buf), data + sz1, to_copy - sz1); + } + else { + memcpy(ptr, data, to_copy); + } + + return to_copy; +} + /* ******** public API ******** */ int ncb_is_null(const struct ncbuf *buf) @@ -306,3 +433,86 @@ ncb_sz_t ncb_data(const struct ncbuf *buf, ncb_sz_t off) return blk.sz - off_blk; } + +/* Add a new block at of size in at offset . + * + * Returns NCB_RET_OK on success. On error the following codes are returned : + * - NCB_RET_GAP_SIZE : cannot add data because the gap formed is too small + */ +enum ncb_ret ncb_add(struct ncbuf *buf, ncb_sz_t off, + const char *data, ncb_sz_t len) +{ + struct ncb_blk blk; + ncb_sz_t left = len; + enum ncb_ret ret; + char *new_sz; + + if (!len) + return NCB_RET_OK; + + BUG_ON_HOT(off + len > ncb_size(buf)); + + /* Get block where insertion begins. */ + blk = ncb_blk_find(buf, off); + + /* Check if insertion is possible. */ + ret = ncb_check_insert(buf, blk, off, data, len); + if (ret != NCB_RET_OK) + return ret; + + if (blk.flag & NCB_BK_F_GAP) { + /* Reduce gap size if insertion begins in a gap. Gap data size + * is reset and will be recalculated during insertion. + */ + const ncb_sz_t gap_sz = off - blk.off; + BUG_ON_HOT(gap_sz < NCB_GAP_MIN_SZ); + + /* pointer to data size to increase. */ + new_sz = ncb_peek(buf, blk.off + NCB_GAP_SZ_DATA_OFF); + + ncb_write_off(buf, blk.sz_ptr, gap_sz); + ncb_write_off(buf, new_sz, 0); + } + else { + /* pointer to data size to increase. */ + new_sz = blk.sz_ptr; + } + + /* insert data */ + while (left) { + struct ncb_blk next; + const ncb_sz_t off_blk = ncb_blk_off(blk, off); + ncb_sz_t done; + + /* retrieve the next block. This is necessary to do this + * before overwritting a gap. + */ + next = ncb_blk_next(buf, blk); + + if (blk.flag & NCB_BK_F_GAP) { + done = ncb_fill_gap_blk(buf, blk, off_blk, data, left); + + /* update the inserted data block size */ + if (off + done == blk.off + blk.sz) { + /* merge next data block if insertion reached gap end */ + ncb_inc_off(buf, new_sz, done + blk.sz_data); + } + else { + /* insertion stopped before gap end */ + ncb_inc_off(buf, new_sz, done); + } + } + else { + done = ncb_fill_data_blk(buf, blk, off_blk, data, left); + } + + BUG_ON_HOT(done > blk.sz || done > left); + left -= done; + data += done; + off += done; + + blk = next; + } + + return NCB_RET_OK; +}