summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlejandro Colomar <alx@kernel.org>2022-12-06 17:59:26 +0100
committerAlejandro Colomar <alx@kernel.org>2022-12-06 17:59:28 +0100
commitfd2c78322614578793f104889926ca03d1c9d422 (patch)
tree5d4e5da1b9a2b7118d47fe59bd9fafbbeaa1daf7
parentf03a080b00b3f6b38639135822abe5143e75a269 (diff)
string.3, strncat.3: Undeprecate strncat(3)
After some investigation, I found a case where this function is useful: Concatenating an unterminated string into a string. It's not an ideal API for that, but there's no other API that does it. The closest thing, and something that some projects use instead of strncat(3), is calling mempcpy(3) directly. However mempcpy(3) isn't ideal either (it's faster; just that). It even requires a multiline pattern to use correctly, which is a source of bugs. So, suggest using a custom alternative that needs to be defined by the programmer, which handles all the subtle details much better than any of the conventional functions: ustr2stpe(). Signed-off-by: Alejandro Colomar <alx@kernel.org>
-rw-r--r--man3/string.328
-rw-r--r--man3/strncat.3206
2 files changed, 198 insertions, 36 deletions
diff --git a/man3/string.3 b/man3/string.3
index c191d926c..0bd16a0bd 100644
--- a/man3/string.3
+++ b/man3/string.3
@@ -113,6 +113,20 @@ Randomly swap the characters in
Return the length of the string
.IR s .
.TP
+.nf
+.BI "char *strncat(char " dest "[restrict strlen(." dest ") + ." n " + 1],"
+.BI " const char " src "[restrict ." n ],
+.BI " size_t " n );
+.fi
+Append at most
+.I n
+bytes from the unterminated string
+.I src
+to the string
+.IR dest ,
+returning a pointer to
+.IR dest .
+.TP
.BI "int strncmp(const char " s1 [. n "], const char " s2 [. n "], size_t " n );
Compare at most
.I n
@@ -184,20 +198,6 @@ to
.IR dest ,
returning a pointer to the start of
.IR dest .
-.TP
-.nf
-.BI "char *strncat(char " dest "[restrict strlen(." dest ") + strnlen(." src ", ." n ") + 1],"
-.BI " const char " src "[restrict ." n ],
-.BI " size_t " n );
-.fi
-Append at most
-.I n
-bytes from
-.I src
-to
-.IR dest ,
-returning a pointer to
-.IR dest .
.SH DESCRIPTION
The string functions perform operations on null-terminated
strings.
diff --git a/man3/strncat.3 b/man3/strncat.3
index cb64c2803..47c005595 100644
--- a/man3/strncat.3
+++ b/man3/strncat.3
@@ -4,7 +4,7 @@
.\"
.TH strncat 3 (date) "Linux man-pages (unreleased)"
.SH NAME
-strncat \- concatenate two strings
+strncat \- concatenate an unterminated string into a string
.SH LIBRARY
Standard C library
.RI ( libc ", " \-lc )
@@ -12,14 +12,13 @@ Standard C library
.nf
.B #include <string.h>
.PP
-.B [[deprecated]]
-.BI "char *strncat(char " dest "[restrict strlen(." dest ") + strnlen(." src ", ." n ") + 1],"
+.BI "char *strncat(char " dest "[restrict strlen(." dest ") + ." n " + 1],"
.BI " const char " src "[restrict ." n ],
.BI " size_t " n );
.fi
.SH DESCRIPTION
-.BI Note: " Never use this function."
-.PP
+.IR Note :
+This is probably not the function you want to use.
For safe string concatenation, see
.BR strlcat (3bsd).
For copying or concatenating a string into a fixed-length buffer
@@ -36,7 +35,7 @@ to the end of
It always terminates with a null character the string placed in
.IR dest .
.PP
-A simple implementation of
+An implementation of
.BR strncat ()
might be:
.PP
@@ -45,7 +44,15 @@ might be:
char *
strncat(char *dest, const char *src, size_t n)
{
- return memcpy(dest + strlen(dest), src, strnlen(src, n));
+ char *cat, *end;
+ size_t len;
+
+ cat = dest + strlen(dest);
+ len = strnlen(src, n);
+ end = mempcpy(cat, src, len);
+ *end = \(aq\e0\(aq;
+
+ return dest;
}
.EE
.in
@@ -72,27 +79,182 @@ T} Thread safety MT-Safe
.sp 1
.SH STANDARDS
POSIX.1-2001, POSIX.1-2008, C89, C99, SVr4, 4.3BSD.
-.SH BUGS
-All.
-Seriously,
-there's no use case for this function.
+.SH NOTES
+.SS ustr2stpe()
+You may want to write your own function similar to
+.BR strncpy (),
+with the following improvements:
+.IP \(bu 3
+Copy, instead of concatenating.
+There's no equivalent of
+.BR strncat ()
+that copies instead of concatenating.
+.IP \(bu
+Allow chaining the function,
+by returning a suitable pointer.
+.IP \(bu
+Prevent buffer overruns by truncating instead.
+.IP \(bu
+Report truncation,
+by returning a suitable pointer.
+.IP \(bu
+A name that tells what it does:
+Copy from an
+.IR u nterminated
+.IR str ing
+to a
+.IR st ring
+delimited by a pointer to one
+.IR p ast
+the
+.IR e nd
+of the array.
.PP
-It has a
+.in +4n
+.EX
+/* This code is in the public domain */
+char *
+ustr2stpe(char *dst, const char *restrict src, size_t n, char past_end[0])
+{
+ bool trunc;
+ char *end;
+ ptrdiff_t len;
+
+ if (dst == past_end)
+ return past_end;
+
+ trunc = false;
+ len = strnlen(src, n);
+ if (len > past_end \- dst \- 1) {
+ len = past_end \- dst \- 1;
+ trunc = true;
+ }
+
+ end = mempcpy(dst, src, len);
+ *end = \(aq\e0\(aq;
+
+ return trunc ? past_end : end;
+}
+.EE
+.in
+.PP
+That function is not present in libc,
+but it's safer and faster than
+.BR strncpy (3).
+If defined as an inline function,
+there's even a chance that the compiler will collapse it to just a
+.BR mempcpy (3).
+.SH BUGS
+.BR strncat (3)
+has a
.I very
-misleading name.
-This function has no relationship with
+misleading name;
+it has no relationship with
.BR strncpy (3).
.PP
Since it doesn't know the size of the destination buffer,
this function can easily write past the end of the array,
-being an open door to all kinds of crackers.
-.PP
-It can't detect truncation.
-If
-.I src
-is not a null-terminated string,
-this function will succeed silently
-(for whatever definition of succeess you have).
+causing Undefined Behavior.
+.SH EXAMPLES
+The following two programs are equivalent,
+except that the one using
+.BR strncat (3)
+can't protect from buffer overruns.
+One uses
+.BR ustr2stpe (),
+and the other uses
+.BR strncat ().
+.SS ustr2stpe()
+.in +4n
+.\" SRC BEGIN (ustr2stpe.c)
+.EX
+#define _GNU_SOURCE
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define nitems(arr) (sizeof((arr)) / sizeof((arr)[0]))
+
+char *
+my_ustr2stpe(char *dst, const char *restrict src, size_t n, char past_end[0])
+{
+ bool trunc;
+ char *end;
+ ptrdiff_t len;
+
+ if (dst == past_end)
+ return past_end;
+
+ trunc = false;
+ len = strnlen(src, n);
+ if (len > past_end \- dst \- 1) {
+ len = past_end \- dst \- 1;
+ trunc = true;
+ }
+
+ end = mempcpy(dst, src, len);
+ *end = \(aq\e0\(aq;
+
+ return trunc ? past_end : end;
+}
+
+int
+main(void)
+{
+ char pre[4] = "pre.";
+ char *post = ".post";
+ char *src = "some_long_body.post";
+ char dest[100];
+ char *p, *past_end;
+
+ past_end = dest + nitems(dest);
+ p = dest;
+ p = my_ustr2stpe(p, pre, nitems(pre), past_end);
+ p = my_ustr2stpe(p, src, strlen(src) - strlen(post), past_end);
+ p = my_ustr2stpe(p, "", 0, past_end);
+ if (p == past_end)
+ fprintf(stderr, "truncation\en");
+
+ puts(dest); // "pre.some_long_body"
+ exit(EXIT_SUCCESS);
+}
+.EE
+.\" SRC END
+.in
+.SS strncat()
+.in +4n
+.\" SRC BEGIN (strncpy.c)
+.EX
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define nitems(arr) (sizeof((arr)) / sizeof((arr)[0]))
+
+int
+main(void)
+{
+ char pre[4] = "pre.";
+ char *post = ".post";
+ char *src = "some_long_body.post";
+ char dest[100];
+
+ dest[0] = \(aq\e0\(aq;
+ strncat(dest, pre, nitems(pre));
+ strncat(dest, src, strlen(src) \- strlen(post));
+ strncat(dest, "", 0);
+
+ puts(dest); // "pre.some_long_body"
+ exit(EXIT_SUCCESS);
+}
+.EE
+.\" SRC END
+.in
.SH SEE ALSO
+.BR memccpy (3),
+.BR memcpy (3),
+.BR mempcpy (3),
.BR strcpy (3),
.BR string (3)