]> git.ipfire.org Git - thirdparty/collectd.git/commitdiff
common: Overhaul the `sstrncpy` implementation. 4233/head
authorFlorian Forster <octo@collectd.org>
Fri, 12 Jan 2024 16:51:07 +0000 (17:51 +0100)
committerFlorian Forster <octo@collectd.org>
Mon, 15 Jan 2024 07:40:06 +0000 (08:40 +0100)
Properly check all arguments and behave in a sane manner, i.e. don't crash.

I went back and forth a few times on whether to return `NULL` or `dest` when `n == 0`.

*   On the one hand, `n == 0` is not really an error and a situation that could
    naturally occur, e.g. when you're implementing code that appends to the end
    of a string.
*   On the other hand, if we return `NULL` when `n` is zero we can guarantee
    that we will either return `NULL` or a null terminated string.

Ultimately I decided to go with the stronger guarantee, i.e.

```c
if (n == 0) {
  return NULL;
}
```

src/utils/common/common.c
src/utils/common/common.h
src/utils/common/common_test.c

index 31e6146af798ce64d658594692a58c6c9df12f4f..afa49d4db369acf16fa8c49b6d301816fa574985 100644 (file)
@@ -85,11 +85,13 @@ static pthread_mutex_t strerror_r_lock = PTHREAD_MUTEX_INITIALIZER;
 #endif
 
 char *sstrncpy(char *dest, const char *src, size_t n) {
-  if (n > 0) {
-    strncpy(dest, src, n - 1);
-    dest[n - 1] = 0;
+  if (dest == NULL || n == 0) {
+    return NULL;
   }
 
+  strncpy(dest, src != NULL ? src : "", n - 1);
+  dest[n - 1] = 0;
+
   return dest;
 } /* char *sstrncpy */
 
index 1f21949067499d2ba73984dd92020d64a683b9fd..069e1b6797b1929f20d5e82293b96494fdba318e 100644 (file)
@@ -64,6 +64,13 @@ struct value_to_rate_state_s {
 };
 typedef struct value_to_rate_state_s value_to_rate_state_t;
 
+/* sstrncpy is a safe alternative to strncpy(3). It differs from strncpy(3) in
+ * the following ways:
+ * - If dest is NULL or n is zero, NULL is returned and no writes will take
+ *   place.
+ * - If src is NULL it behaves as if an empty string ("") was provided.
+ * - A null byte is written to dest[n-1] unconditionally. That means the return
+ *   value is either NULL or a null terminated string. */
 char *sstrncpy(char *dest, const char *src, size_t n);
 
 __attribute__((format(printf, 3, 4))) int ssnprintf(char *str, size_t size,
index fabbf2fb87795b752088f8c5492937f549ce11f8..58a11b2b9850257c84cae4731ce4ed3240b1069b 100644 (file)
@@ -42,28 +42,55 @@ kstat_ctl_t *kc;
 #endif /* HAVE_LIBKSTAT */
 
 DEF_TEST(sstrncpy) {
-  char buffer[16] = "";
-  char *ptr = &buffer[4];
-  char *ret;
-
-  buffer[0] = buffer[1] = buffer[2] = buffer[3] = 0xff;
-  buffer[12] = buffer[13] = buffer[14] = buffer[15] = 0xff;
-
-  ret = sstrncpy(ptr, "foobar", 8);
-  OK(ret == ptr);
-  EXPECT_EQ_STR("foobar", ptr);
-  OK(buffer[3] == buffer[12]);
-
-  ret = sstrncpy(ptr, "abc", 8);
-  OK(ret == ptr);
-  EXPECT_EQ_STR("abc", ptr);
-  OK(buffer[3] == buffer[12]);
-
-  ret = sstrncpy(ptr, "collectd", 8);
-  OK(ret == ptr);
-  OK(ptr[7] == 0);
-  EXPECT_EQ_STR("collect", ptr);
-  OK(buffer[3] == buffer[12]);
+  struct {
+    char const *name;
+    char const *src;
+    size_t size;
+    char const *want;
+  } cases[] = {
+      {
+          .name = "normal copy",
+          .src = "Hello, world!",
+          .size = 16,
+          .want = "Hello, world!",
+      },
+      {
+          .name = "truncated copy",
+          .src = "Hello, world!",
+          .size = 8,
+          .want = "Hello, ",
+      },
+      {
+          .name = "NULL source is treated like an empty string",
+          .src = NULL,
+          .size = 8,
+          .want = "",
+      },
+      {
+          .name = "size is zero",
+          .src = "test",
+          .size = 0,
+          .want = NULL,
+      },
+  };
+
+  for (size_t i = 0; i < STATIC_ARRAY_SIZE(cases); i++) {
+    printf("## Case %zu: %s\n", i + 1, cases[i].name);
+
+    char dest[cases[i].size + 1];
+    memset(dest, 0xff, sizeof(dest));
+
+    char *want_ret = (cases[i].size == 0) ? NULL : dest;
+    EXPECT_EQ_PTR(want_ret, sstrncpy(dest, cases[i].src, cases[i].size));
+    EXPECT_EQ_INT((char)0xff, dest[sizeof(dest) - 1]);
+    if (cases[i].want == NULL) {
+      continue;
+    }
+    EXPECT_EQ_STR(cases[i].want, dest);
+  }
+
+  printf("## Case %zu: dest is NULL\n", STATIC_ARRAY_SIZE(cases) + 1);
+  EXPECT_EQ_PTR(NULL, sstrncpy(NULL, "test", 23));
 
   return 0;
 }