<p>Neels Hofmeyr <strong>merged</strong> this change.</p><p><a href="https://gerrit.osmocom.org/12880">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Harald Welte: Looks good to me, approved
  Jenkins Builder: Verified

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">add OSMO_STRBUF_PRINTF()<br><br>We are using macros like this or different workarounds in libmsc. In the course<br>of implementing inter-MSC handover, I am encountering yet another such<br>situation of appending multiple strings to a limited char buffer. Standardize.<br><br>Add a unit test to utils_test.c.<br><br>Change-Id: I2497514e26c5e7a5d88985fc7e58343be1a027b2<br>---<br>M include/osmocom/core/utils.h<br>M tests/utils/utils_test.c<br>M tests/utils/utils_test.ok<br>3 files changed, 188 insertions(+), 0 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/include/osmocom/core/utils.h b/include/osmocom/core/utils.h</span><br><span>index fe360b3..16159d3 100644</span><br><span>--- a/include/osmocom/core/utils.h</span><br><span>+++ b/include/osmocom/core/utils.h</span><br><span>@@ -145,4 +145,89 @@</span><br><span> </span><br><span> const char osmo_luhn(const char* in, int in_len);</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/*! State for OSMO_STRBUF_APPEND() and OSMO_STRBUF_PRINTF(). See there for examples. */</span><br><span style="color: hsl(120, 100%, 40%);">+struct osmo_strbuf {</span><br><span style="color: hsl(120, 100%, 40%);">+    /*! Point to the start of a string buffer. */</span><br><span style="color: hsl(120, 100%, 40%);">+ char *buf;</span><br><span style="color: hsl(120, 100%, 40%);">+    /*! Total sizeof() the buffer buf points at. */</span><br><span style="color: hsl(120, 100%, 40%);">+       size_t len;</span><br><span style="color: hsl(120, 100%, 40%);">+   /*! Current writing position in buf (end of the string written so far). */</span><br><span style="color: hsl(120, 100%, 40%);">+    char *pos;</span><br><span style="color: hsl(120, 100%, 40%);">+    /*! After all OSMO_STRBUF_APPEND operations, reflects the total number of characters that would be written had</span><br><span style="color: hsl(120, 100%, 40%);">+         * buf been large enough. Like snprintf()'s return value, this does not include the terminating nul character.</span><br><span style="color: hsl(120, 100%, 40%);">+     * Hence, to allocate an adequately sized buffer, add 1 to this number. */</span><br><span style="color: hsl(120, 100%, 40%);">+    size_t chars_needed;</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*! Append a string to a buffer, as printed by an snprintf()-like function and with similar bounds checking.</span><br><span style="color: hsl(120, 100%, 40%);">+ * Make sure to never write past the end of the buffer, and collect the total size that would be needed.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *     // an example function implementation to append: write N spaces.</span><br><span style="color: hsl(120, 100%, 40%);">+ *     int print_spaces(char *dst, size_t dst_len, int n)</span><br><span style="color: hsl(120, 100%, 40%);">+ *     {</span><br><span style="color: hsl(120, 100%, 40%);">+ *             int i;</span><br><span style="color: hsl(120, 100%, 40%);">+ *             if (n < 0)</span><br><span style="color: hsl(120, 100%, 40%);">+ *                     return -EINVAL;</span><br><span style="color: hsl(120, 100%, 40%);">+ *             for (i = 0; i < n && i < dst_len; i++)</span><br><span style="color: hsl(120, 100%, 40%);">+ *                     dst[i] = ' ';</span><br><span style="color: hsl(120, 100%, 40%);">+ *             if (dst_len)</span><br><span style="color: hsl(120, 100%, 40%);">+ *                     dst[OSMO_MIN(dst_len - 1, n)] = '\0';</span><br><span style="color: hsl(120, 100%, 40%);">+ *             // return the n that we would have liked to write if space were available:</span><br><span style="color: hsl(120, 100%, 40%);">+ *             return n;</span><br><span style="color: hsl(120, 100%, 40%);">+ *     }</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *     // append above spaces as well as an snprintf()</span><br><span style="color: hsl(120, 100%, 40%);">+ *     void strbuf_example()</span><br><span style="color: hsl(120, 100%, 40%);">+ *     {</span><br><span style="color: hsl(120, 100%, 40%);">+ *             char buf[23];</span><br><span style="color: hsl(120, 100%, 40%);">+ *             struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *             OSMO_STRBUF_APPEND(sb, print_spaces, 5);</span><br><span style="color: hsl(120, 100%, 40%);">+ *             OSMO_STRBUF_APPEND(sb, snprintf, "The answer is %d but what is the question?", 42);</span><br><span style="color: hsl(120, 100%, 40%);">+ *             OSMO_STRBUF_APPEND(sb, print_spaces, 423423);</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *             printf("%s\n", buf);</span><br><span style="color: hsl(120, 100%, 40%);">+ *             printf("would have needed %zu bytes\n", sb.chars_needed);</span><br><span style="color: hsl(120, 100%, 40%);">+ *     }</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[inout] STRBUF  A struct osmo_strbuf instance.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[in] func  A function with a signature of int func(char *dst, size_t dst_len [, args]) with semantics like</span><br><span style="color: hsl(120, 100%, 40%);">+ *                  snprintf().</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[in] args  Arguments passed to func, if any.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+#define OSMO_STRBUF_APPEND(STRBUF, func, args...) do { \</span><br><span style="color: hsl(120, 100%, 40%);">+               if (!(STRBUF).pos) \</span><br><span style="color: hsl(120, 100%, 40%);">+                  (STRBUF).pos = (STRBUF).buf; \</span><br><span style="color: hsl(120, 100%, 40%);">+                size_t remain = (STRBUF).buf ? (STRBUF).len - ((STRBUF).pos - (STRBUF).buf) : 0; \</span><br><span style="color: hsl(120, 100%, 40%);">+            int l = func((STRBUF).pos, remain, ##args); \</span><br><span style="color: hsl(120, 100%, 40%);">+         if (l < 0 || l > remain) \</span><br><span style="color: hsl(120, 100%, 40%);">+                      (STRBUF).pos = (STRBUF).buf + (STRBUF).len; \</span><br><span style="color: hsl(120, 100%, 40%);">+         else \</span><br><span style="color: hsl(120, 100%, 40%);">+                        (STRBUF).pos += l; \</span><br><span style="color: hsl(120, 100%, 40%);">+          if (l > 0) \</span><br><span style="color: hsl(120, 100%, 40%);">+                       (STRBUF).chars_needed += l; \</span><br><span style="color: hsl(120, 100%, 40%);">+ } while(0)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*! Shortcut for OSMO_STRBUF_APPEND() invocation using snprintf().</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *     int strbuf_example2(char *buf, size_t buflen)</span><br><span style="color: hsl(120, 100%, 40%);">+ *     {</span><br><span style="color: hsl(120, 100%, 40%);">+ *             int i;</span><br><span style="color: hsl(120, 100%, 40%);">+ *             struct osmo_strbuf sb = { .buf = buf, .len = buflen };</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *             OSMO_STRBUF_PRINTF(sb, "T minus");</span><br><span style="color: hsl(120, 100%, 40%);">+ *             for (i = 10; i; i--)</span><br><span style="color: hsl(120, 100%, 40%);">+ *                     OSMO_STRBUF_PRINTF(sb, " %d", i);</span><br><span style="color: hsl(120, 100%, 40%);">+ *             OSMO_STRBUF_PRINTF(sb, " ... Lift off!");</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ *             return sb.chars_needed;</span><br><span style="color: hsl(120, 100%, 40%);">+ *     }</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[inout] STRBUF  A struct osmo_strbuf instance.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[in] fmt  Format string passed to snprintf.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[in] args  Additional arguments passed to snprintf, if any.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+#define OSMO_STRBUF_PRINTF(STRBUF, fmt, args...) \</span><br><span style="color: hsl(120, 100%, 40%);">+      OSMO_STRBUF_APPEND(STRBUF, snprintf, fmt, ##args)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*! @} */</span><br><span>diff --git a/tests/utils/utils_test.c b/tests/utils/utils_test.c</span><br><span>index 822861f..d592fe0 100644</span><br><span>--- a/tests/utils/utils_test.c</span><br><span>+++ b/tests/utils/utils_test.c</span><br><span>@@ -33,6 +33,7 @@</span><br><span> #include <netinet/in.h></span><br><span> #include <arpa/inet.h></span><br><span> #include <errno.h></span><br><span style="color: hsl(120, 100%, 40%);">+#include <limits.h></span><br><span> </span><br><span> static void hexdump_test(void)</span><br><span> {</span><br><span>@@ -936,6 +937,90 @@</span><br><span>  OSMO_ASSERT(ok);</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/* Copy of the examples from OSMO_STRBUF_APPEND() */</span><br><span style="color: hsl(120, 100%, 40%);">+int print_spaces(char *dst, size_t dst_len, int argument)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ int i;</span><br><span style="color: hsl(120, 100%, 40%);">+        if (argument < 0)</span><br><span style="color: hsl(120, 100%, 40%);">+          return -EINVAL;</span><br><span style="color: hsl(120, 100%, 40%);">+       for (i = 0; i < argument && i < dst_len; i++)</span><br><span style="color: hsl(120, 100%, 40%);">+           dst[i] = ' ';</span><br><span style="color: hsl(120, 100%, 40%);">+ if (dst_len)</span><br><span style="color: hsl(120, 100%, 40%);">+          dst[OSMO_MIN(dst_len - 1, argument)] = '\0';</span><br><span style="color: hsl(120, 100%, 40%);">+  return argument;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+void strbuf_example(char *buf, size_t buflen)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+       struct osmo_strbuf sb = { .buf = buf, .len = buflen };</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      OSMO_STRBUF_APPEND(sb, print_spaces, 5);</span><br><span style="color: hsl(120, 100%, 40%);">+      OSMO_STRBUF_APPEND(sb, snprintf, "The answer is %d but what is the question?", 42);</span><br><span style="color: hsl(120, 100%, 40%);">+ OSMO_STRBUF_APPEND(sb, print_spaces, 423423);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       printf("%s\n", buf);</span><br><span style="color: hsl(120, 100%, 40%);">+        printf("would have needed %zu bytes\n", sb.chars_needed);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/* Copy of the examples from OSMO_STRBUF_PRINTF() */</span><br><span style="color: hsl(120, 100%, 40%);">+int strbuf_example2(char *buf, size_t buflen)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+      int i;</span><br><span style="color: hsl(120, 100%, 40%);">+        struct osmo_strbuf sb = { .buf = buf, .len = buflen };</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      OSMO_STRBUF_PRINTF(sb, "T minus");</span><br><span style="color: hsl(120, 100%, 40%);">+  for (i = 10; i; i--)</span><br><span style="color: hsl(120, 100%, 40%);">+          OSMO_STRBUF_PRINTF(sb, " %d", i);</span><br><span style="color: hsl(120, 100%, 40%);">+   OSMO_STRBUF_PRINTF(sb, " ... Lift off!");</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return sb.chars_needed;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+int strbuf_cascade(char *buf, size_t buflen)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      OSMO_STRBUF_APPEND(sb, strbuf_example2);</span><br><span style="color: hsl(120, 100%, 40%);">+      OSMO_STRBUF_PRINTF(sb, " -- ");</span><br><span style="color: hsl(120, 100%, 40%);">+     OSMO_STRBUF_APPEND(sb, strbuf_example2);</span><br><span style="color: hsl(120, 100%, 40%);">+      OSMO_STRBUF_PRINTF(sb, " -- ");</span><br><span style="color: hsl(120, 100%, 40%);">+     OSMO_STRBUF_APPEND(sb, strbuf_example2);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    return sb.chars_needed;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+void strbuf_test()</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+   char buf[256];</span><br><span style="color: hsl(120, 100%, 40%);">+        int rc;</span><br><span style="color: hsl(120, 100%, 40%);">+       printf("\n%s\n", __func__);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       printf("OSMO_STRBUF_APPEND():\n");</span><br><span style="color: hsl(120, 100%, 40%);">+  strbuf_example(buf, 23);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    printf("\nOSMO_STRBUF_PRINTF():\n");</span><br><span style="color: hsl(120, 100%, 40%);">+        rc = strbuf_example2(buf, 23);</span><br><span style="color: hsl(120, 100%, 40%);">+        printf("1: (need %d chars, had size=23) %s\n", rc, buf);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  rc = strbuf_example2(buf, rc);</span><br><span style="color: hsl(120, 100%, 40%);">+        printf("2: (need %d chars, had size=%d) %s\n", rc, rc, buf);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      rc = strbuf_example2(buf, rc + 1);</span><br><span style="color: hsl(120, 100%, 40%);">+    printf("3: (need %d chars, had size=%d+1) %s\n", rc, rc, buf);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    rc = strbuf_example2(buf, 0);</span><br><span style="color: hsl(120, 100%, 40%);">+ snprintf(buf, sizeof(buf), "0x2b 0x2b 0x2b...");</span><br><span style="color: hsl(120, 100%, 40%);">+    printf("4: (need %d chars, had size=0) %s\n", rc, buf);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   rc = strbuf_example2(NULL, 99);</span><br><span style="color: hsl(120, 100%, 40%);">+       printf("5: (need %d chars, had NULL buffer)\n", rc);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      printf("\ncascade:\n");</span><br><span style="color: hsl(120, 100%, 40%);">+     rc = strbuf_cascade(buf, sizeof(buf));</span><br><span style="color: hsl(120, 100%, 40%);">+        printf("(need %d chars)\n%s\n", rc, buf);</span><br><span style="color: hsl(120, 100%, 40%);">+   rc = strbuf_cascade(buf, 63);</span><br><span style="color: hsl(120, 100%, 40%);">+ printf("(need %d chars, had size=63) %s\n", rc, buf);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span> </span><br><span> int main(int argc, char **argv)</span><br><span> {</span><br><span>@@ -954,5 +1039,6 @@</span><br><span>        isqrt_test();</span><br><span>        osmo_sockaddr_to_str_and_uint_test();</span><br><span>        osmo_str_tolowupper_test();</span><br><span style="color: hsl(120, 100%, 40%);">+   strbuf_test();</span><br><span>       return 0;</span><br><span> }</span><br><span>diff --git a/tests/utils/utils_test.ok b/tests/utils/utils_test.ok</span><br><span>index 8d7ced8..1215ddd 100644</span><br><span>--- a/tests/utils/utils_test.ok</span><br><span>+++ b/tests/utils/utils_test.ok</span><br><span>@@ -325,3 +325,20 @@</span><br><span>                    = 62, "ABCDEFGHIJKLMNOPQRSTUVWXYZA"</span><br><span> osmo_str_toupper_buf(28, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()", in-place)</span><br><span>                    = 27, "ABCDEFGHIJKLMNOPQRSTUVWXYZA"</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+strbuf_test</span><br><span style="color: hsl(120, 100%, 40%);">+OSMO_STRBUF_APPEND():</span><br><span style="color: hsl(120, 100%, 40%);">+     The answer is 42 </span><br><span style="color: hsl(120, 100%, 40%);">+would have needed 423470 bytes</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+OSMO_STRBUF_PRINTF():</span><br><span style="color: hsl(120, 100%, 40%);">+1: (need 42 chars, had size=23) T minus 10 9 8 7 6 5 4</span><br><span style="color: hsl(120, 100%, 40%);">+2: (need 42 chars, had size=42) T minus 10 9 8 7 6 5 4 3 2 1 ... Lift off</span><br><span style="color: hsl(120, 100%, 40%);">+3: (need 42 chars, had size=42+1) T minus 10 9 8 7 6 5 4 3 2 1 ... Lift off!</span><br><span style="color: hsl(120, 100%, 40%);">+4: (need 42 chars, had size=0) 0x2b 0x2b 0x2b...</span><br><span style="color: hsl(120, 100%, 40%);">+5: (need 42 chars, had NULL buffer)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+cascade:</span><br><span style="color: hsl(120, 100%, 40%);">+(need 134 chars)</span><br><span style="color: hsl(120, 100%, 40%);">+T minus 10 9 8 7 6 5 4 3 2 1 ... Lift off! -- T minus 10 9 8 7 6 5 4 3 2 1 ... Lift off! -- T minus 10 9 8 7 6 5 4 3 2 1 ... Lift off!</span><br><span style="color: hsl(120, 100%, 40%);">+(need 134 chars, had size=63) T minus 10 9 8 7 6 5 4 3 2 1 ... Lift off! -- T minus 10 9 8 7</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/12880">change 12880</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/12880"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: libosmocore </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: I2497514e26c5e7a5d88985fc7e58343be1a027b2 </div>
<div style="display:none"> Gerrit-Change-Number: 12880 </div>
<div style="display:none"> Gerrit-PatchSet: 5 </div>
<div style="display:none"> Gerrit-Owner: Neels Hofmeyr <nhofmeyr@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: Harald Welte <laforge@gnumonks.org> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder (1000002) </div>
<div style="display:none"> Gerrit-Reviewer: Neels Hofmeyr <nhofmeyr@sysmocom.de> </div>
<div style="display:none"> Gerrit-CC: Max <msuraev@sysmocom.de> </div>