From: Lucas De Marchi Date: Tue, 12 Nov 2024 16:09:38 +0000 (-0600) Subject: strbuf: Allow to start with stack space X-Git-Tag: v34~90 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=1e36bfada5bafd3f7d4f32191724a69c57c07e7f;p=thirdparty%2Fkmod.git strbuf: Allow to start with stack space This is the main functionality of the scratchbuf implementation: it starts with a buffer on stack that covers most of the calls, but also has a fallback to allocate the buffer if it grows beyond the initial size. Port that functionality from scratchbuf to strbuf, so the former can be eventually removed. Signed-off-by: Lucas De Marchi Reviewed-by: Emil Velikov Link: https://github.com/kmod-project/kmod/pull/239 --- diff --git a/shared/strbuf.c b/shared/strbuf.c index f3a54e23..836101c8 100644 --- a/shared/strbuf.c +++ b/shared/strbuf.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "util.h" #include "strbuf.h" @@ -16,12 +17,17 @@ static bool buf_realloc(struct strbuf *buf, size_t sz) { - void *tmp; + void *tmp = realloc(buf->heap ? buf->bytes : NULL, sz); - tmp = realloc(buf->bytes, sz); - if (sz > 0 && tmp == NULL) - return false; + if (sz > 0) { + if (tmp == NULL) + return false; + + if (!buf->heap) + memcpy(tmp, buf->bytes, MIN(buf->size, sz)); + } + buf->heap = true; buf->bytes = tmp; buf->size = sz; @@ -44,11 +50,13 @@ void strbuf_init(struct strbuf *buf) buf->bytes = NULL; buf->size = 0; buf->used = 0; + buf->heap = true; } void strbuf_release(struct strbuf *buf) { - free(buf->bytes); + if (buf->heap) + free(buf->bytes); } char *strbuf_steal(struct strbuf *buf) diff --git a/shared/strbuf.h b/shared/strbuf.h index 2e005e42..b2aa946e 100644 --- a/shared/strbuf.h +++ b/shared/strbuf.h @@ -2,6 +2,7 @@ #include #include +#include #include "macro.h" @@ -12,8 +13,29 @@ struct strbuf { char *bytes; size_t size; size_t used; + bool heap; }; +/* + * Declare and initialize strbuf without any initial storage + */ +#define DECLARE_STRBUF(name__) \ + _cleanup_strbuf_ struct strbuf name__ = { \ + .heap = true, \ + } + +/* + * Declare and initialize strbuf with an initial buffer on stack. The @sz__ must be a + * build-time constant, as if the buffer had been declared on stack. + */ +#define DECLARE_STRBUF_WITH_STACK(name__, sz__) \ + assert_cc(__builtin_constant_p(sz__)); \ + char name__##_storage__[sz__]; \ + _cleanup_strbuf_ struct strbuf name__ = { \ + .bytes = name__##_storage__, \ + .size = sz__, \ + } + void strbuf_init(struct strbuf *buf); void strbuf_release(struct strbuf *buf); @@ -34,8 +56,9 @@ void strbuf_clear(struct strbuf *buf); char *strbuf_steal(struct strbuf *buf); /* - * Return a C string owned by the buffer invalidated if the buffer is - * changed). + * Return a C string owned by the buffer. It becomes an invalid + * pointer if strbuf is changed. It may also not survive a return + * from current function if it was initialized with stack space */ const char *strbuf_str(struct strbuf *buf); diff --git a/testsuite/test-strbuf.c b/testsuite/test-strbuf.c index 9c62349d..1c8f6040 100644 --- a/testsuite/test-strbuf.c +++ b/testsuite/test-strbuf.c @@ -106,4 +106,61 @@ static int test_strbuf_steal(const struct test *t) } DEFINE_TEST(test_strbuf_steal, .description = "test strbuf_steal with cleanup"); +static int test_strbuf_with_stack(const struct test *t) +{ + const char test[] = "test-something-small"; + const char *stack_buf; + char *p; + DECLARE_STRBUF_WITH_STACK(buf, 256); + DECLARE_STRBUF_WITH_STACK(buf2, sizeof(test) + 1); + DECLARE_STRBUF_WITH_STACK(buf3, sizeof(test) + 1); + + strbuf_pushchars(&buf, test); + assert_return(streq(test, strbuf_str(&buf)), EXIT_FAILURE); + p = strbuf_steal(&buf); + assert_return(streq(test, p), EXIT_FAILURE); + free(p); + + strbuf_pushchars(&buf2, test); + assert_return(streq(test, strbuf_str(&buf2)), EXIT_FAILURE); + /* It fits on stack, but when we steal, we get a copy on heap */ + p = strbuf_steal(&buf2); + assert_return(streq(test, p), EXIT_FAILURE); + free(p); + + /* + * Check assumption about buffer being on stack vs heap is indeed valid. + * Not to be done in real code. + */ + strbuf_clear(&buf3); + stack_buf = buf3.bytes; + strbuf_pushchars(&buf3, test); + assert_return(stack_buf == buf3.bytes, EXIT_FAILURE); + + assert_return(streq(test, strbuf_str(&buf3)), EXIT_FAILURE); + assert_return(stack_buf == buf3.bytes, EXIT_FAILURE); + + strbuf_pushchars(&buf3, "-overflow"); + assert_return(stack_buf != buf3.bytes, EXIT_FAILURE); + + return 0; +} +DEFINE_TEST(test_strbuf_with_stack, .description = "test strbuf with stack"); + +static int test_strbuf_with_heap(const struct test *t) +{ + DECLARE_STRBUF(heapbuf); + + assert_return(heapbuf.bytes == NULL, EXIT_FAILURE); + assert_return(heapbuf.size == 0, EXIT_FAILURE); + assert_return(heapbuf.used == 0, EXIT_FAILURE); + strbuf_pushchars(&heapbuf, "-overflow"); + assert_return(heapbuf.bytes != NULL, EXIT_FAILURE); + assert_return(heapbuf.size != 0, EXIT_FAILURE); + assert_return(heapbuf.used != 0, EXIT_FAILURE); + + return 0; +} +DEFINE_TEST(test_strbuf_with_heap, .description = "test strbuf with heap only"); + TESTSUITE_MAIN();