From 09825cbe5d87b208f814e8d941a4692d1aaab46a Mon Sep 17 00:00:00 2001 From: Gustavo Sverzut Barbieri Date: Mon, 19 Dec 2016 18:35:38 -0200 Subject: [PATCH] eina_binbuf: allow expand & usage of extra bytes. Some code needs to read directly into eina_binbuf to avoid an extra copy from eina_binbuf_append* variants. This can be achieved by using eina_binbuf_expand(), which returns a write-able slice of the spare bytes. Once they are used, eina_binbuf_use() should be called to increment "buf->len", which is used by all other binbuf functions such as eina_binbuf_length_get() or eina_binbuf_append_slice(). --- src/lib/eina/eina_binbuf.h | 56 ++++++++++++++++++ src/lib/eina/eina_binbuf_template_c.x | 15 +++++ src/lib/eina/eina_strbuf_common.c | 82 +++++++++++++++++++++++++++ src/lib/eina/eina_strbuf_common.h | 9 +++ src/tests/eina/eina_test_binbuf.c | 69 ++++++++++++++++++++++ 5 files changed, 231 insertions(+) diff --git a/src/lib/eina/eina_binbuf.h b/src/lib/eina/eina_binbuf.h index 051c5d172d..e10728e11a 100644 --- a/src/lib/eina/eina_binbuf.h +++ b/src/lib/eina/eina_binbuf.h @@ -129,6 +129,58 @@ EAPI void eina_binbuf_free(Eina_Binbuf *buf) EINA_ARG_NONNULL(1); */ EAPI void eina_binbuf_reset(Eina_Binbuf *buf) EINA_ARG_NONNULL(1); +/** + * @brief Expand a buffer, making room for at least @a minimum_unused_space. + * + * One of the properties of the buffer is that it may overallocate + * space, thus it may have more than eina_binbuf_length_get() bytes + * allocated. How much depends on buffer growing logic, but this + * function allows one to request a minimum amount of bytes to be + * allocated at the end of the buffer. + * + * This is particularly useful to write directly to buffer's memory + * (ie: a call to read(2)). After the bytes are used call + * eina_binbuf_use() to mark them as such, so eina_binbuf_length_get() + * will consider the new bytes. + * + * @param buf The Buffer to expand. + * @param minimum_unused_space The minimum unused allocated space, in + * bytes, at the end of the buffer. Zero can be used to query + * the available slice of unused bytes. + * + * @return The slice of unused bytes. The slice length may be zero if + * @a minimum_unused_space couldn't be allocated, otherwise it + * will be at least @a minimum_unused_space. After bytes are used, + * mark them as such using eina_binbuf_use(). + * + * @see eina_binbuf_rw_slice_get() + * @see eina_binbuf_use() + * + * @since 1.19 + */ +EAPI Eina_Rw_Slice eina_binbuf_expand(Eina_Binbuf *buf, size_t minimum_unused_space) EINA_ARG_NONNULL(1); + +/** + * @brief Mark more bytes as used. + * + * This function should be used after eina_binbuf_expand(), marking + * the extra bytes returned there as used, then they will be + * considered in all other functions, such as eina_binbuf_length_get(). + * + * @param buf The buffer to mark extra bytes as used. + * @param extra_bytes the number of bytes to be considered used, must + * be between zero and the length of the slice returned by + * eina_binbuf_expand(). + * + * @return #EINA_TRUE on success, #EINA_FALSE on failure, such as @a + * extra_bytes is too big or @a buf is NULL. + * + * @see eina_binbuf_expand() + * + * @since 1.19 + */ +EAPI Eina_Bool eina_binbuf_use(Eina_Binbuf *buf, size_t extra_bytes) EINA_ARG_NONNULL(1); + /** * @brief Append a string of exact length to a buffer, reallocating as necessary. * @@ -330,6 +382,10 @@ EAPI Eina_Slice eina_binbuf_slice_get(const Eina_Binbuf *buf) EINA_WARN_UNUSED_R * @return a read-write slice for the current contents. It may become * invalid as soon as the @a buf is changed with calls such as * eina_binbuf_append(), eina_binbuf_remove() + * + * @see eina_binbuf_expand() + * + * @since 1.19 */ EAPI Eina_Rw_Slice eina_binbuf_rw_slice_get(const Eina_Binbuf *buf) EINA_WARN_UNUSED_RESULT EINA_ARG_NONNULL(1); diff --git a/src/lib/eina/eina_binbuf_template_c.x b/src/lib/eina/eina_binbuf_template_c.x index f074f04525..72b6fd7972 100644 --- a/src/lib/eina/eina_binbuf_template_c.x +++ b/src/lib/eina/eina_binbuf_template_c.x @@ -100,6 +100,21 @@ _FUNC_EXPAND(reset)(_STRBUF_STRUCT_NAME *buf) eina_strbuf_common_reset(_STRBUF_CSIZE, buf); } +EAPI Eina_Rw_Slice +_FUNC_EXPAND(expand)(_STRBUF_STRUCT_NAME *buf, size_t minimum_unused_space) +{ + Eina_Rw_Slice ret = {.len = 0, .mem = NULL}; + EINA_MAGIC_CHECK_STRBUF(buf, ret); + return eina_strbuf_common_expand(_STRBUF_CSIZE, buf, minimum_unused_space); +} + +EAPI Eina_Bool +_FUNC_EXPAND(use)(_STRBUF_STRUCT_NAME *buf, size_t extra_bytes) +{ + EINA_MAGIC_CHECK_STRBUF(buf, EINA_FALSE); + return eina_strbuf_common_use(buf, extra_bytes); +} + EAPI Eina_Bool _FUNC_EXPAND(append_length)(_STRBUF_STRUCT_NAME *buf, const _STRBUF_DATA_TYPE *str, size_t length) { diff --git a/src/lib/eina/eina_strbuf_common.c b/src/lib/eina/eina_strbuf_common.c index 7b9aa9f0d7..3a18d4d4d0 100644 --- a/src/lib/eina/eina_strbuf_common.c +++ b/src/lib/eina/eina_strbuf_common.c @@ -378,6 +378,88 @@ eina_strbuf_common_reset(size_t csize, Eina_Strbuf *buf) memset(buf->buf, 0, csize); } +/** + * @internal + * @brief Expand a buffer, making room for at least @a minimum_unused_space. + * + * One of the properties of the buffer is that it may overallocate + * space, thus it may have more than eina_strbuf_common_length_get() bytes + * allocated. How much depends on buffer growing logic, but this + * function allows one to request a minimum amount of bytes to be + * allocated at the end of the buffer. + * + * This is particularly useful to write directly to buffer's memory + * (ie: a call to read(2)). After the bytes are used call + * eina_strbuf_common_use() to mark them as such, so + * eina_strbuf_common_length_get() will consider the new bytes. + * + * @param csize the character size + * @param buf The Buffer to expand. + * @param minimum_unused_space The minimum unused allocated space, in + * bytes, at the end of the buffer. Zero can be used to query + * the available slice of unused bytes. + * + * @return The slice of unused bytes. The slice length may be zero if + * @a minimum_unused_space couldn't be allocated, otherwise it + * will be at least @a minimum_unused_space. After bytes are used, + * mark them as such using eina_strbuf_common_use(). + * + * @see eina_strbuf_common_rw_slice_get() + * @see eina_strbuf_common_use() + * + * @since 1.19 + */ +Eina_Rw_Slice +eina_strbuf_common_expand(size_t csize, + Eina_Strbuf *buf, + size_t minimum_unused_space) +{ + Eina_Rw_Slice ret = { .mem = NULL, .len = 0 }; + + if (EINA_LIKELY(buf->len + minimum_unused_space < buf->size)) goto end; + + if (EINA_UNLIKELY(!_eina_strbuf_common_grow(csize, buf, buf->len + minimum_unused_space))) + return ret; + + end: + ret.mem = (unsigned char *)buf->buf + (buf->len * csize); + ret.len = buf->size - buf->len - 1; + return ret; +} + +/** + * @internal + * @brief Mark more bytes as used. + * + * This function should be used after eina_strbuf_common_expand(), + * marking the extra bytes returned there as used, then they will be + * considered in all other functions, such as + * eina_strbuf_common_length_get(). + * + * @param csize the character size + * @param buf The buffer to mark extra bytes as used. + * @param extra_bytes the number of bytes to be considered used, must + * be between zero and the length of the slice returned by + * eina_strbuf_common_expand(). + * + * @return #EINA_TRUE on success, #EINA_FALSE on failure, such as @a + * extra_bytes is too big or @a buf is NULL. + * + * @see eina_strbuf_common_expand() + * + * @since 1.19 + */ +Eina_Bool +eina_strbuf_common_use(Eina_Strbuf *buf, + size_t extra_bytes) +{ + if (EINA_UNLIKELY(buf->size < buf->len + extra_bytes + 1)) + return EINA_FALSE; + + buf->len += extra_bytes; + return EINA_TRUE; +} + /** * @internal * @brief Append a string to a buffer, reallocating as necessary. diff --git a/src/lib/eina/eina_strbuf_common.h b/src/lib/eina/eina_strbuf_common.h index ec86f25e51..931cae456d 100644 --- a/src/lib/eina/eina_strbuf_common.h +++ b/src/lib/eina/eina_strbuf_common.h @@ -52,6 +52,15 @@ void eina_strbuf_common_free(Eina_Strbuf *buf); void eina_strbuf_common_reset(size_t csize, Eina_Strbuf *buf); + +Eina_Rw_Slice +eina_strbuf_common_expand(size_t csize, + Eina_Strbuf *buf, + size_t minimum_unused_space); +Eina_Bool +eina_strbuf_common_use(Eina_Strbuf *buf, + size_t extra_bytes); + Eina_Bool eina_strbuf_common_append(size_t csize, Eina_Strbuf *buf, diff --git a/src/tests/eina/eina_test_binbuf.c b/src/tests/eina/eina_test_binbuf.c index 653d9ae617..ce3225d722 100644 --- a/src/tests/eina/eina_test_binbuf.c +++ b/src/tests/eina/eina_test_binbuf.c @@ -306,6 +306,74 @@ START_TEST(binbuf_realloc) } END_TEST +START_TEST(binbuf_expand) +{ + Eina_Binbuf *buf; + Eina_Rw_Slice rw_slice; + Eina_Slice ro_slice; + Eina_Slice hello_world = EINA_SLICE_STR_LITERAL("Hello World"); + Eina_Slice hi_there = EINA_SLICE_STR_LITERAL("Hi There"); + size_t i; + Eina_Bool r; + + eina_init(); + + buf = eina_binbuf_new(); + fail_if(!buf); + + rw_slice = eina_binbuf_rw_slice_get(buf); + ck_assert_int_eq(rw_slice.len, 0); + + /* force it to grow to 'Hello World' */ + r = eina_binbuf_append_slice(buf, hello_world); + ck_assert_int_eq(r, EINA_TRUE); + + ro_slice = eina_binbuf_slice_get(buf); + ck_assert_int_eq(ro_slice.len, hello_world.len); + ck_assert_int_eq(eina_slice_compare(ro_slice, hello_world), 0); + + /* reset doesn't change allocated size, 'Hi there' will fit */ + eina_binbuf_reset(buf); + rw_slice = eina_binbuf_expand(buf, hi_there.len); + ck_assert_int_ge(rw_slice.len, hi_there.len); + /* access bytes directly */ + rw_slice = eina_rw_slice_copy(rw_slice, hi_there); + r = eina_binbuf_use(buf, rw_slice.len); + ck_assert_int_eq(r, EINA_TRUE); + ck_assert_int_eq(eina_slice_compare(eina_binbuf_slice_get(buf), hi_there), 0); + + /* start with 'Hello World */ + eina_binbuf_reset(buf); + r = eina_binbuf_append_slice(buf, hello_world); + ck_assert_int_eq(r, EINA_TRUE); + + /* force it to realloc */ + rw_slice = eina_binbuf_expand(buf, 8192); + ck_assert_int_ge(rw_slice.len, 8192); + ck_assert_ptr_ne(rw_slice.mem, NULL); + + memset(rw_slice.mem, 0xfe, rw_slice.len); + + r = eina_binbuf_use(buf, rw_slice.len); + ck_assert_int_eq(r, EINA_TRUE); + + r = eina_binbuf_use(buf, 1); /* would go too much */ + ck_assert_int_eq(r, EINA_FALSE); + + ro_slice = eina_binbuf_slice_get(buf); + ck_assert_int_eq(ro_slice.len, hello_world.len + rw_slice.len); + ck_assert_int_eq(memcmp(ro_slice.mem, hello_world.mem, hello_world.len), 0); + + for (i = hello_world.len; i < ro_slice.len; i++) + ck_assert_int_eq(ro_slice.bytes[i], 0xfe); + + eina_binbuf_free(buf); + + eina_shutdown(); +} +END_TEST + + void eina_test_binbuf(TCase *tc) { @@ -315,4 +383,5 @@ eina_test_binbuf(TCase *tc) tcase_add_test(tc, binbuf_realloc); tcase_add_test(tc, binbuf_manage_simple); tcase_add_test(tc, binbuf_manage_read_only_simple); + tcase_add_test(tc, binbuf_expand); }