neels has uploaded this change for review.
util: add osmo_strbuf macros to manipulate the strbuf tail
Upcoming patch adopts osmo_strbuf in logging.c, which sometimes needs to
steal and re-add trailing newline characters, and also needs to let
ctime_r() write to the buffer before updating the osmo_strbuf state.
Related: OS#6284
Related: Ib577a5e0d7450ce93ff21f37ba3262704cbf4752
Change-Id: I997707c328eab3ffa00a78fdb9a0a2cbe18404b4
---
M include/osmocom/core/utils.h
M tests/utils/utils_test.c
M tests/utils/utils_test.ok
3 files changed, 154 insertions(+), 0 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/libosmocore refs/changes/07/35207/1
diff --git a/include/osmocom/core/utils.h b/include/osmocom/core/utils.h
index 2a3670b..d90bf6f 100644
--- a/include/osmocom/core/utils.h
+++ b/include/osmocom/core/utils.h
@@ -284,6 +284,10 @@
/*! Return remaining space for characters and terminating nul in the given struct osmo_strbuf. */
#define OSMO_STRBUF_REMAIN(STRBUF) ((STRBUF).buf ? (STRBUF).len - ((STRBUF).pos - (STRBUF).buf) : 0)
+/*! Return number of actual characters contained in struct osmo_strbuf (without terminating nul). */
+#define OSMO_STRBUF_CHAR_COUNT(STRBUF) ((STRBUF).buf && (STRBUF).pos > (STRBUF).buf ? \
+ OSMO_MIN((STRBUF).pos - (STRBUF).buf, (STRBUF).len - 1) : 0)
+
/*! Like OSMO_STRBUF_APPEND(), but for function signatures that return the char* buffer instead of a length.
* When using this function, the final STRBUF.chars_needed may not reflect the actual number of characters needed, since
* that number cannot be obtained from this kind of function signature.
@@ -307,6 +311,41 @@
(STRBUF).chars_needed += _sb_l; \
} while(0)
+/*! Remove up to N chars from the end of an osmo_strbuf.
+ * |--char-count---| - - chars_needed - - |
+ * |<---------drop----------|
+ */
+#define OSMO_STRBUF_DROP_TAIL(STRBUF, N_CHARS) do { \
+ if ((STRBUF).pos > (STRBUF).buf) { \
+ size_t _sb_drop_n = (STRBUF).chars_needed; \
+ _sb_drop_n = OSMO_MIN(_sb_drop_n, N_CHARS); \
+ (STRBUF).chars_needed -= _sb_drop_n; \
+ \
+ if ((STRBUF).chars_needed < OSMO_STRBUF_CHAR_COUNT(STRBUF)) { \
+ (STRBUF).pos = (STRBUF).buf + (STRBUF).chars_needed; \
+ *(STRBUF).pos = '\0'; \
+ } \
+ } \
+ } while (0)
+
+/* Let osmo_strbuf know that N_CHARS characters (excluding nul) were written to STRBUF.pos.
+ * Advance STRBUF.pos and STRBUF.chars_needed by at most N_CHARS, or up to STRBUF.len - 1.
+ * Ensure nul termination. */
+#define OSMO_STRBUF_ADDED_TAIL(STRBUF, N_CHARS) \
+ do { \
+ if (!(STRBUF).pos) \
+ (STRBUF).pos = (STRBUF).buf; \
+ (STRBUF).chars_needed += N_CHARS; \
+ size_t _sb_added = OSMO_STRBUF_REMAIN(STRBUF); \
+ if (_sb_added) \
+ _sb_added--; \
+ _sb_added = OSMO_MIN(_sb_added, N_CHARS); \
+ if (_sb_added) \
+ (STRBUF).pos += _sb_added; \
+ if ((STRBUF).pos < (STRBUF).buf + (STRBUF).len) \
+ *(STRBUF).pos = '\0'; \
+ } while (0)
+
bool osmo_str_startswith(const char *str, const char *startswith_str);
int osmo_float_str_to_int(int64_t *val, const char *str, unsigned int precision);
diff --git a/tests/utils/utils_test.c b/tests/utils/utils_test.c
index 0b7bfe4..bdeedb5 100644
--- a/tests/utils/utils_test.c
+++ b/tests/utils/utils_test.c
@@ -31,6 +31,7 @@
#include <errno.h>
#include <limits.h>
#include <inttypes.h>
+#include <string.h>
static void hexdump_test(void)
{
@@ -1306,6 +1307,50 @@
printf("%zu: %s (need=%zu)\n", sb.len, buf, sb.chars_needed);
}
+void strbuf_test_tail_for_buflen(size_t buflen)
+{
+ char buf[buflen];
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ printf("\n%s(%zu)\n", __func__, buflen);
+
+#define SHOW(N) \
+ printf(#N ": %s sb.chars_needed=%zu sb.pos=&sb.buf[%d]\n", \
+ osmo_quote_str(buf, -1), sb.chars_needed, (int)(sb.pos - sb.buf))
+
+ /* shorten in steps using OSMO_STRBUF_DROP_TAIL(), removing and re-adding a trailing newline. */
+ OSMO_STRBUF_PRINTF(sb, "banananana\n");
+ SHOW(1);
+ OSMO_STRBUF_DROP_TAIL(sb, 3);
+ SHOW(2);
+ OSMO_STRBUF_PRINTF(sb, "\n");
+ SHOW(3);
+ OSMO_STRBUF_DROP_TAIL(sb, 3);
+ SHOW(4);
+ OSMO_STRBUF_PRINTF(sb, "\n");
+ SHOW(5);
+
+ /* drop trailing newline */
+ OSMO_STRBUF_DROP_TAIL(sb, 1);
+ SHOW(6);
+
+ /* test writing something to the end and letting OSMO_STRBUF_ADDED_TAIL() know later */
+ int n = OSMO_MIN(6, OSMO_STRBUF_REMAIN(sb));
+ if (n)
+ memcpy(sb.pos, "bread\n", n);
+ OSMO_STRBUF_ADDED_TAIL(sb, 6);
+ SHOW(7);
+}
+
+void strbuf_test_tail(void)
+{
+ strbuf_test_tail_for_buflen(64);
+ strbuf_test_tail_for_buflen(32);
+ strbuf_test_tail_for_buflen(16);
+ strbuf_test_tail_for_buflen(8);
+ strbuf_test_tail_for_buflen(4);
+ strbuf_test_tail_for_buflen(1);
+}
+
static void startswith_test_str(const char *str, const char *startswith_str, bool expect_rc)
{
bool rc = osmo_str_startswith(str, startswith_str);
@@ -2152,6 +2197,7 @@
osmo_str_tolowupper_test();
strbuf_test();
strbuf_test_nolen();
+ strbuf_test_tail();
startswith_test();
name_c_impl_test();
osmo_print_n_test();
diff --git a/tests/utils/utils_test.ok b/tests/utils/utils_test.ok
index 3f453e9..6f5d46b 100644
--- a/tests/utils/utils_test.ok
+++ b/tests/utils/utils_test.ok
@@ -470,6 +470,60 @@
more: 0001011100101010000 (need=19)
10: 000101110 (need=9)
+strbuf_test_tail_for_buflen(64)
+1: "banananana\n" sb.chars_needed=11 sb.pos=&sb.buf[11]
+2: "bananana" sb.chars_needed=8 sb.pos=&sb.buf[8]
+3: "bananana\n" sb.chars_needed=9 sb.pos=&sb.buf[9]
+4: "banana" sb.chars_needed=6 sb.pos=&sb.buf[6]
+5: "banana\n" sb.chars_needed=7 sb.pos=&sb.buf[7]
+6: "banana" sb.chars_needed=6 sb.pos=&sb.buf[6]
+7: "bananabread\n" sb.chars_needed=12 sb.pos=&sb.buf[12]
+
+strbuf_test_tail_for_buflen(32)
+1: "banananana\n" sb.chars_needed=11 sb.pos=&sb.buf[11]
+2: "bananana" sb.chars_needed=8 sb.pos=&sb.buf[8]
+3: "bananana\n" sb.chars_needed=9 sb.pos=&sb.buf[9]
+4: "banana" sb.chars_needed=6 sb.pos=&sb.buf[6]
+5: "banana\n" sb.chars_needed=7 sb.pos=&sb.buf[7]
+6: "banana" sb.chars_needed=6 sb.pos=&sb.buf[6]
+7: "bananabread\n" sb.chars_needed=12 sb.pos=&sb.buf[12]
+
+strbuf_test_tail_for_buflen(16)
+1: "banananana\n" sb.chars_needed=11 sb.pos=&sb.buf[11]
+2: "bananana" sb.chars_needed=8 sb.pos=&sb.buf[8]
+3: "bananana\n" sb.chars_needed=9 sb.pos=&sb.buf[9]
+4: "banana" sb.chars_needed=6 sb.pos=&sb.buf[6]
+5: "banana\n" sb.chars_needed=7 sb.pos=&sb.buf[7]
+6: "banana" sb.chars_needed=6 sb.pos=&sb.buf[6]
+7: "bananabread\n" sb.chars_needed=12 sb.pos=&sb.buf[12]
+
+strbuf_test_tail_for_buflen(8)
+1: "bananan" sb.chars_needed=11 sb.pos=&sb.buf[8]
+2: "bananan" sb.chars_needed=8 sb.pos=&sb.buf[8]
+3: "bananan" sb.chars_needed=9 sb.pos=&sb.buf[8]
+4: "banana" sb.chars_needed=6 sb.pos=&sb.buf[6]
+5: "banana\n" sb.chars_needed=7 sb.pos=&sb.buf[7]
+6: "banana" sb.chars_needed=6 sb.pos=&sb.buf[6]
+7: "bananab" sb.chars_needed=12 sb.pos=&sb.buf[7]
+
+strbuf_test_tail_for_buflen(4)
+1: "ban" sb.chars_needed=11 sb.pos=&sb.buf[4]
+2: "ban" sb.chars_needed=8 sb.pos=&sb.buf[4]
+3: "ban" sb.chars_needed=9 sb.pos=&sb.buf[4]
+4: "ban" sb.chars_needed=6 sb.pos=&sb.buf[4]
+5: "ban" sb.chars_needed=7 sb.pos=&sb.buf[4]
+6: "ban" sb.chars_needed=6 sb.pos=&sb.buf[4]
+7: "ban" sb.chars_needed=12 sb.pos=&sb.buf[4]
+
+strbuf_test_tail_for_buflen(1)
+1: "" sb.chars_needed=11 sb.pos=&sb.buf[1]
+2: "" sb.chars_needed=8 sb.pos=&sb.buf[1]
+3: "" sb.chars_needed=9 sb.pos=&sb.buf[1]
+4: "" sb.chars_needed=6 sb.pos=&sb.buf[1]
+5: "" sb.chars_needed=7 sb.pos=&sb.buf[1]
+6: "" sb.chars_needed=6 sb.pos=&sb.buf[1]
+7: "" sb.chars_needed=12 sb.pos=&sb.buf[1]
+
startswith_test()
osmo_str_startswith(NULL, NULL) == true
osmo_str_startswith("", NULL) == true
To view, visit change 35207. To unsubscribe, or for help writing mail filters, visit settings.