]> git.ipfire.org Git - thirdparty/kmod.git/commitdiff
strbuf: Allow to start with stack space
authorLucas De Marchi <lucas.de.marchi@gmail.com>
Tue, 12 Nov 2024 16:09:38 +0000 (10:09 -0600)
committerLucas De Marchi <lucas.de.marchi@gmail.com>
Sun, 17 Nov 2024 21:35:13 +0000 (15:35 -0600)
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 <lucas.de.marchi@gmail.com>
Reviewed-by: Emil Velikov <emil.l.velikov@gmail.com>
Link: https://github.com/kmod-project/kmod/pull/239
shared/strbuf.c
shared/strbuf.h
testsuite/test-strbuf.c

index f3a54e23443dac56bfe0883d8a8eb7e0ca5715d2..836101c897cfc030b692d4ce3105df6c9662435f 100644 (file)
@@ -8,6 +8,7 @@
 #include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/param.h>
 
 #include "util.h"
 #include "strbuf.h"
 
 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)
index 2e005e42e04f1ec930c0f3c5f9b44e28e73e4159..b2aa946ef84ede790c461717d680e15495c4b0e4 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <stdbool.h>
 #include <stddef.h>
+#include <alloca.h>
 
 #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);
 
index 9c62349d792d9093b6e5fa6ca5f8c3816869dbfe..1c8f6040e54a6c79714d5f7764b6729d3fc931a6 100644 (file)
@@ -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();