From d5e390e992578f472294a2ffb416ea7ea8bbc26d Mon Sep 17 00:00:00 2001 From: huntedbytheirs Date: Mon, 31 Aug 2026 21:14:52 -0400 Subject: [PATCH] feat(string): add BSD strlcpy/strlcat at level 2 Exact OpenBSD semantics: strlcpy returns strlen(src) and writes at most size-1 bytes plus NUL; strlcat returns dlen + strlen(src) with dlen = min(size, strlen(dst)) and never reads past size bytes in dst. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- src/string/strlcat.c | 56 ++++++++++++++++++++++++++++++++++++++++++++ src/string/strlcpy.c | 51 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/string/strlcat.c create mode 100644 src/string/strlcpy.c diff --git a/src/string/strlcat.c b/src/string/strlcat.c new file mode 100644 index 0000000..4645aa8 --- /dev/null +++ b/src/string/strlcat.c @@ -0,0 +1,56 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +/* + * Append src to dst, NUL-terminating the result within size bytes. + * Return dlen + strlen(src), where dlen = min(size, strlen(dst)): the length + * the result would have had without truncation. dst is never read past + * size bytes. When size is 0 or dlen == size, nothing is written. + */ +size_t +strlcat(char *dst, const char *src, size_t size) +{ + char *d; + const char *s; + size_t n; + size_t dlen; + + d = dst; + s = src; + n = size; + + /* Find the end of dst, stopping after at most size bytes. */ + while (n != 0 && *d != '\0') + { + d++; + n--; + } + dlen = (size_t)(d - dst); + n = size - dlen; + + if (n != 0) + { + /* Append as much of src as fits, leaving room for the NUL. */ + while (*s != '\0') + { + if (n != 1) + { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + } + + /* Count the unwritten remainder of src. */ + while (*s != '\0') + { + s++; + } + + return dlen + (size_t)(s - src); +} diff --git a/src/string/strlcpy.c b/src/string/strlcpy.c new file mode 100644 index 0000000..e3906dd --- /dev/null +++ b/src/string/strlcpy.c @@ -0,0 +1,51 @@ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +/* + * Copy src to dst, NUL-terminating the result, truncated to fit size bytes. + * Return strlen(src): the length of the string the call tried to create, + * independent of any truncation. When size is 0, nothing is written. + */ +size_t +strlcpy(char *dst, const char *src, size_t size) +{ + const char *orig_src; + size_t nleft; + + orig_src = src; + nleft = size; + + /* Copy as many bytes as will fit, leaving room for the NUL. */ + if (nleft != 0) + { + while (--nleft != 0) + { + *dst = *src; + if (*src == '\0') + { + break; + } + dst++; + src++; + } + } + + /* No room for a NUL (or size was 0): NUL-terminate and measure src. */ + if (nleft == 0) + { + if (size != 0) + { + *dst = '\0'; + } + while (*src != '\0') + { + src++; + } + } + + /* src ends at its NUL either way, so this is strlen(original src). */ + return (size_t)(src - orig_src); +}