src/daemon/common.[ch]: Reimplement strjoin().
authorFlorian Forster <octo@collectd.org>
Thu, 15 Sep 2016 06:59:16 +0000 (08:59 +0200)
committerFlorian Forster <octo@collectd.org>
Thu, 15 Sep 2016 07:03:00 +0000 (09:03 +0200)
This new implementation truncates fields rather than aborting when there
is more space in the output buffer. Since strjoin() is mostly used to
fill plugin and type instances, which are otherwise usually filled with
sstrncpy(), i.e. also truncate the string rather than erroring out.

The unit test has also been rewritten to test the new functionality.

The new functions have been formatted with clang-format.

Fixes: #1792

src/daemon/common.c
src/daemon/common.h
src/daemon/common_test.c

index d1d8bf1..d53f1ec 100644 (file)
@@ -334,52 +334,60 @@ int strsplit (char *string, char **fields, size_t size)
        return ((int) i);
 }
 
-int strjoin (char *buffer, size_t buffer_size,
-               char **fields, size_t fields_num,
-               const char *sep)
-{
-       size_t avail;
-       char *ptr;
-       size_t sep_len;
+int strjoin(char *buffer, size_t buffer_size, char **fields, size_t fields_num,
+            const char *sep) {
+  size_t avail = 0;
+  char *ptr = buffer;
+  size_t sep_len = 0;
 
-       if ((buffer_size < 1) || (fields_num == 0))
-               return (-1);
+  size_t buffer_req = 0;
 
-       memset (buffer, 0, buffer_size);
-       ptr = buffer;
-       avail = buffer_size - 1;
+  if (((fields_num != 0) && (fields == NULL)) ||
+      ((buffer_size != 0) && (buffer == NULL)))
+    return (-EINVAL);
 
-       sep_len = 0;
-       if (sep != NULL)
-               sep_len = strlen (sep);
+  if (buffer != NULL)
+    buffer[0] = 0;
 
-       for (size_t i = 0; i < fields_num; i++)
-       {
-               size_t field_len;
+  if (buffer_size != 0)
+    avail = buffer_size - 1;
 
-               if ((i > 0) && (sep_len > 0))
-               {
-                       if (avail < sep_len)
-                               return (-1);
+  if (sep != NULL)
+    sep_len = strlen(sep);
 
-                       memcpy (ptr, sep, sep_len);
-                       ptr += sep_len;
-                       avail -= sep_len;
-               }
-               if (avail == 0)
-                       return (-1);
+  for (size_t i = 0; i < fields_num; i++) {
+    size_t field_len = strlen(fields[i]);
 
-               field_len = strlen (fields[i]);
-               if (avail < field_len)
-                       field_len = avail;
+    if (i != 0)
+      buffer_req += sep_len;
+    buffer_req += field_len;
 
-               memcpy (ptr, fields[i], field_len);
-               ptr += field_len;
-               avail -= field_len;
-       }
+    if ((i != 0) && (sep_len > 0)) {
+      if (sep_len >= avail) {
+        /* prevent subsequent iterations from writing to the
+         * buffer. */
+        avail = 0;
+        continue;
+      }
+
+      memcpy(ptr, sep, sep_len);
+
+      ptr += sep_len;
+      avail -= sep_len;
+    }
+
+    if (field_len > avail)
+      field_len = avail;
+
+    memcpy(ptr, fields[i], field_len);
+    ptr += field_len;
+
+    avail -= field_len;
+    if (ptr != NULL)
+      *ptr = 0;
+  }
 
-       assert (buffer[buffer_size - 1] == 0);
-       return ((int) ((buffer_size - 1) - avail));
+  return (int)buffer_req;
 }
 
 int escape_string (char *buffer, size_t buffer_size)
index 720e5f1..9a9ae0d 100644 (file)
@@ -147,10 +147,12 @@ int strsplit (char *string, char **fields, size_t size);
  *   is equivalent to the Perl built-in `join'.
  *
  * PARAMETERS
- *   `dst'         Buffer where the result is stored.
+ *   `dst'         Buffer where the result is stored. Can be NULL if you need to
+ *                 determine the required buffer size only.
  *   `dst_len'     Length of the destination buffer. No more than this many
  *                 bytes will be written to the memory pointed to by `dst',
- *                 including the trailing null-byte.
+ *                 including the trailing null-byte. Must be zero if dst is
+ *                 NULL.
  *   `fields'      Array of strings to be joined.
  *   `fields_num'  Number of elements in the `fields' array.
  *   `sep'         String to be inserted between any two elements of `fields'.
@@ -158,9 +160,10 @@ int strsplit (char *string, char **fields, size_t size);
  *                 Instead of passing "" (empty string) one can pass NULL.
  *
  * RETURN VALUE
- *   Returns the number of characters in `dst', NOT including the trailing
- *   null-byte. If an error occurred (empty array or `dst' too small) a value
- *   smaller than zero will be returned.
+ *   Returns the number of characters in the resulting string, excluding a
+ *   tailing null byte. If this value is greater than or equal to "dst_len", the
+ *   result in "dst" is truncated (but still null terminated). On error a
+ *   negative value is returned.
  */
 int strjoin (char *dst, size_t dst_len, char **fields, size_t fields_num, const char *sep);
 
index 202ddf6..44e198d 100644 (file)
@@ -148,46 +148,54 @@ DEF_TEST(strsplit)
   return (0);
 }
 
-DEF_TEST(strjoin)
-{
-  char buffer[16];
-  char *fields[4];
-  int status;
-
-  fields[0] = "foo";
-  fields[1] = "bar";
-  fields[2] = "baz";
-  fields[3] = "qux";
-
-  status = strjoin (buffer, sizeof (buffer), fields, 2, "!");
-  OK(status == 7);
-  EXPECT_EQ_STR ("foo!bar", buffer);
-
-  status = strjoin (buffer, sizeof (buffer), fields, 1, "!");
-  OK(status == 3);
-  EXPECT_EQ_STR ("foo", buffer);
-
-  status = strjoin (buffer, sizeof (buffer), fields, 0, "!");
-  OK(status < 0);
-
-  status = strjoin (buffer, sizeof (buffer), fields, 2, "rcht");
-  OK(status == 10);
-  EXPECT_EQ_STR ("foorchtbar", buffer);
-
-  status = strjoin (buffer, sizeof (buffer), fields, 4, "");
-  OK(status == 12);
-  EXPECT_EQ_STR ("foobarbazqux", buffer);
+DEF_TEST(strjoin) {
+  struct {
+    char **fields;
+    size_t fields_num;
+    char *separator;
+
+    int want_return;
+    char *want_buffer;
+  } cases
+      [] = {
+          /* Normal case. */
+          {(char *[]){"foo", "bar"}, 2, "!", 7, "foo!bar"},
+          /* One field only. */
+          {(char *[]){"foo"}, 1, "!", 3, "foo"},
+          /* No fields at all. */
+          {NULL, 0, "!", 0, ""},
+          /* Longer separator. */
+          {(char *[]){"foo", "bar"}, 2, "rcht", 10, "foorchtbar"},
+          /* Empty separator. */
+          {(char *[]){"foo", "bar"}, 2, "", 6, "foobar"},
+          /* NULL separator. */
+          {(char *[]){"foo", "bar"}, 2, NULL, 6, "foobar"},
+          /* buffer not large enough -> string is truncated. */
+          {(char *[]){"aaaaaa", "bbbbbb", "c!"}, 3, "-", 16, "aaaaaa-bbbbbb-c"},
+          /* buffer not large enough -> last field fills buffer completely. */
+          {(char *[]){"aaaaaaa", "bbbbbbb", "!"}, 3, "-", 17,
+           "aaaaaaa-bbbbbbb"},
+          /* buffer not large enough -> string does *not* end in separator. */
+          {(char *[]){"aaaa", "bbbb", "cccc", "!"}, 4, "-", 16,
+           "aaaa-bbbb-cccc"},
+          /* buffer not large enough -> string does not end with partial
+             separator. */
+          {(char *[]){"aaaaaa", "bbbbbb", "!"}, 3, "+-", 17, "aaaaaa+-bbbbbb"},
+      };
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
+    char buffer[16];
+    int status;
 
-  status = strjoin (buffer, sizeof (buffer), fields, 4, "!");
-  OK(status == 15);
-  EXPECT_EQ_STR ("foo!bar!baz!qux", buffer);
+    memset(buffer, 0xFF, sizeof(buffer));
+    status = strjoin(buffer, sizeof(buffer), cases[i].fields,
+                     cases[i].fields_num, cases[i].separator);
+    EXPECT_EQ_INT(cases[i].want_return, status);
+    EXPECT_EQ_STR(cases[i].want_buffer, buffer);
+  }
 
-  fields[0] = "0123";
-  fields[1] = "4567";
-  fields[2] = "8901";
-  fields[3] = "2345";
-  status = strjoin (buffer, sizeof (buffer), fields, 4, "-");
-  OK(status < 0);
+  /* use (NULL, 0) to determine required buffer size. */
+  EXPECT_EQ_INT(3, strjoin(NULL, 0, (char *[]){"a", "b"}, 2, "-"));
 
   return (0);
 }