Date: Sun, 12 Jan 2003 13:10:45 +0000 From: "Richard Dawe" Sender: rich AT phekda DOT freeserve DOT co DOT uk To: djgpp-workers AT delorie DOT com X-Mailer: Emacs 21.3.50 (via feedmail 8.3.emacs20_6 I) and Blat ver 1.8.6 Subject: strlcat & strlcpy, revision 3 [PATCH] Message-Id: Reply-To: djgpp-workers AT delorie DOT com Hello. Below is revision 3 of the strlcat, strlcpy patch. Changes: * Suggest what might happen with overlapping buffers. The behaviour is undefined, since we don't cope with overlapping buffers. * strncat always nul-terminates. Remove a comment about it not doing that from the strlcat info page. (Thanks for catching that Eli. I confused that particular behaviour from strncpy with strncat.) OK to commit? Bye, Rich =] Index: include/string.h =================================================================== RCS file: /cvs/djgpp/djgpp/include/string.h,v retrieving revision 1.5 diff -p -c -3 -r1.5 string.h *** include/string.h 5 Dec 2000 14:05:53 -0000 1.5 --- include/string.h 12 Jan 2003 13:06:27 -0000 *************** *** 1,3 **** --- 1,5 ---- + /* Copyright (C) 2003 DJ Delorie, see COPYING.DJ for details */ + /* Copyright (C) 2002 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1998 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ *************** char * rindex(const char *_string, int *** 62,67 **** --- 64,71 ---- char * stpcpy(char *_dest, const char *_src); char * stpncpy(char *_dest, const char *_src, size_t _n); char * strdup(const char *_s); + size_t strlcat(char *_dest, const char *_src, size_t _size); + size_t strlcpy(char *_dest, const char *_src, size_t _size); char * strlwr(char *_s); int strcasecmp(const char *_s1, const char *_s2); int stricmp(const char *_s1, const char *_s2); Index: src/libc/compat/string/makefile =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/compat/string/makefile,v retrieving revision 1.6 diff -p -c -3 -r1.6 makefile *** src/libc/compat/string/makefile 17 Oct 2002 23:00:25 -0000 1.6 --- src/libc/compat/string/makefile 12 Jan 2003 13:06:27 -0000 *************** *** 1,3 **** --- 1,5 ---- + # Copyright (C) 2003 DJ Delorie, see COPYING.DJ for details + # Copyright (C) 2002 DJ Delorie, see COPYING.DJ for details # Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details # Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details # Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details *************** SRC += stpncpy.c *** 11,16 **** --- 13,20 ---- SRC += strcasec.S SRC += strdup.c SRC += stricmp.c + SRC += strlcat.c + SRC += strlcpy.c SRC += strlwr.c SRC += strncase.S SRC += strnicmp.c Index: src/docs/kb/wc204.txi =================================================================== RCS file: /cvs/djgpp/djgpp/src/docs/kb/wc204.txi,v retrieving revision 1.132 diff -p -c -3 -r1.132 wc204.txi *** src/docs/kb/wc204.txi 8 Jan 2003 20:18:00 -0000 1.132 --- src/docs/kb/wc204.txi 12 Jan 2003 13:06:32 -0000 *************** to @code{getenv} which are not needed. *** 836,838 **** --- 836,842 ---- @findex strtof The function @code{strtof} was added. + + @findex strlcat + @findex strlcpy + The functions @code{strlcat} and @code{strlcpy} were added. Index: tests/libc/compat/string/makefile =================================================================== RCS file: /cvs/djgpp/djgpp/tests/libc/compat/string/makefile,v retrieving revision 1.2 diff -p -c -3 -r1.2 makefile *** tests/libc/compat/string/makefile 3 Aug 2000 11:17:12 -0000 1.2 --- tests/libc/compat/string/makefile 12 Jan 2003 13:06:32 -0000 *************** TOP=../.. *** 3,7 **** --- 3,9 ---- SRC += ffs.c SRC += stpcpy.c SRC += stpncpy.c + SRC += t-stlcat.c + SRC += t-stlcpy.c include $(TOP)/../makefile.inc *** /dev/null Sun Jan 12 13:07:53 2003 --- src/libc/compat/string/strlcat.c Thu Jan 9 21:16:20 2003 *************** *** 0 **** --- 1,32 ---- + /* Copyright (C) 2003 DJ Delorie, see COPYING.DJ for details */ + /* Copyright (C) 2002 DJ Delorie, see COPYING.DJ for details */ + #include + + size_t + strlcat (char *dst, const char *src, size_t size) + { + const size_t srclen = strlen(src); + size_t dstlen = 0; + size_t i; + + /* Find the length of dst. Don't go past the size'th element. */ + for (i = 0; i < size; i++) { if (dst[i] == '\0') break; } + + if (i == size) + /* dst is not nul terminated. As per OpenBSD and NetBSD, return + * the sum of the buffer length and srclen. */ + return(size + srclen); + else + dstlen = i; + + if ((dstlen + srclen) < size) { + /* Enough space - just copy the string. */ + strcpy(dst + dstlen, src); + } else { + /* Truncate the string to fit. */ + memcpy(dst + dstlen, src, size - dstlen - 1); + dst[size - 1] = '\0'; + } + + return(dstlen + srclen); + } *** /dev/null Sun Jan 12 13:07:53 2003 --- src/libc/compat/string/strlcat.txh Sun Jan 12 13:05:34 2003 *************** *** 0 **** --- 1,61 ---- + @node strlcat, string + @subheading Syntax + + @example + #include + + size_t strlcat (char *dest, const char *src, size_t size); + @end example + + @subheading Description + + Concatenate characters from @var{src} to @var{dest} and nul-terminate + the resulting string. As much of @var{src} is copied into @var{dest} + as there is space for. + + @var{size} should be the size of the destination string buffer @var{dest} + plus the space for the nul-terminator. @var{size} may be computed + in many cases using the @code{sizeof} operator. + + @code{strlcat} may be used as a less ambiguous alternative + to @code{strncat} (@pxref{strncat}). + + If @var{dest} is not nul-terminated, then @var{dest} is not modified. + + @code{strlcat} will not examine more than @var{size} characters + of @var{dest}. This is to avoid overrunning the buffer @var{dest}. + + If @var{dest} and @var{src} are overlapping buffers, the behavior + is undefined. One possible result is a buffer overrun - accessing + out-of-bounds memory. + + @subheading Return Value + + The length of the string that @code{strlcat} tried to create is returned, + whether or not @code{strlcat} could store it in @var{dest}. If all + of @var{src} was concatenated to @var{dst}, the return value will be less + than @var{size}. + + If @var{dest} is not nul-terminated, then @code{strlcat} will consider + @var{dest} to be @var{size} in length and return @var{size} plus + the length of @var{src}. + + @subheading Portability + + @portability !ansi, !posix + + @subheading Example + + The following example shows how you can check that + the destination string buffer was large enough to store + the source string concatenated to the destination string. + In this case @code{somestring} is truncated, when it is concatenated + to @code{buf}. + + @example + const char somestring[] = "bar"; + char buf[5] = "foo"; + + if (strlcat(buf, somestring, sizeof(buf)) >= sizeof(buf)) + puts("somestring was truncated, when concatenating to buf."); + @end example *** /dev/null Sun Jan 12 13:07:53 2003 --- src/libc/compat/string/strlcpy.c Thu Jan 9 20:59:12 2003 *************** *** 0 **** --- 1,24 ---- + /* Copyright (C) 2003 DJ Delorie, see COPYING.DJ for details */ + /* Copyright (C) 2002 DJ Delorie, see COPYING.DJ for details */ + #include + + size_t + strlcpy (char *dst, const char *src, size_t size) + { + const size_t srclen = strlen(src); + const size_t ret = srclen; + + if (!size) + return(ret); /* No space at all. */ + + if (srclen < size) { + /* Enough space - just copy the string. */ + strcpy(dst, src); + } else { + /* Truncate the string to fit. */ + memcpy(dst, src, size - 1); + dst[size - 1] = '\0'; + } + + return(ret); + } *** /dev/null Sun Jan 12 13:07:53 2003 --- src/libc/compat/string/strlcpy.txh Sun Jan 12 12:55:50 2003 *************** *** 0 **** --- 1,50 ---- + @node strlcpy, string + @subheading Syntax + + @example + #include + + size_t strlcpy (char *dest, const char *src, size_t size); + @end example + + @subheading Description + + Copy characters from @var{src} to @var{dest} and nul-terminate + the resulting string. Up to @code{@var{size} - 1} characters are + copied to @var{dest}. + + @var{size} should be the size of the destination string buffer @var{dest} + plus the space for the nul-terminator. @var{size} may be computed + in many cases using the @code{sizeof} operator. + + @code{strlcpy} is a less ambiguous version of @code{strncpy} + (@pxref{strncpy}). Unlike @code{strncpy}, @code{strlcpy} @emph{always} + nul-terminates the destination @var{dest} for non-zero sizes @var{size}. + + If @var{dest} and @var{src} are overlapping buffers, the behavior + is undefined. One possible result is a buffer overrun - accessing + out-of-bounds memory. + + @subheading Return Value + + The length of the string that @code{strlcpy} tried to create is returned, + whether or not @code{strlcpy} could store it in @var{dest}. If all + of @var{src} was copied, the return value will be less than @var{size}. + + @subheading Portability + + @portability !ansi, !posix + + @subheading Example + + The following example shows how you can check that + the destination string buffer was large enough to store the source string. + In this case @code{somestring} is truncated to fit into @code{buf}. + + @example + const char somestring[] = "foo"; + char buf[3]; + + if (strlcpy(buf, somestring, sizeof(buf)) >= sizeof(buf)) + puts("somestring was truncated, when copying to buf."); + @end example *** /dev/null Sun Jan 12 13:07:53 2003 --- tests/libc/compat/string/t-stlcat.c Thu Jan 9 21:20:14 2003 *************** *** 0 **** --- 1,91 ---- + #include + #include + #include + #include + + #define FILL_CHAR 'X' + + static void + check_too_small (const char *somestring, + char *buf2, const size_t buf2size, + const size_t fakesize) + { + const size_t len_somestring = strlen(somestring); + int i; + + assert(fakesize < buf2size); + + memset(buf2, FILL_CHAR, buf2size); + strcpy(buf2, somestring); + + assert(strlcat(buf2, somestring, fakesize) >= fakesize); + assert(strncmp(buf2, somestring, len_somestring) == 0); + assert(strncmp(buf2 + len_somestring, somestring, + fakesize - len_somestring - 1) == 0); + assert(buf2[fakesize - 1] == '\0'); + for (i = fakesize; i < buf2size; i++) { + assert(buf2[i] == FILL_CHAR); + } + } + + int + main (void) + { + const char somestring[] = "somestring"; + const size_t len_somestring = strlen(somestring); + char buf[22]; /* More than big enough to contain two somestrings. */ + char buf2[20]; /* Just too small to contain two somestrings. */ + int i; + + /* Try a zero size. Check that the buffer is untouched. */ + memset(buf, FILL_CHAR, sizeof(buf)); + buf[0] = '\0'; /* Make buf a valid string. */ + + assert(strlcat(buf, somestring, 0) == strlen(somestring)); + for (i = 1; i < sizeof(buf); i++) { + assert(buf[i] == FILL_CHAR); + } + + /* Check that two strings fit into buf. Check that it hasn't overrun. */ + memset(buf, FILL_CHAR, sizeof(buf)); + strcpy(buf, somestring); + + assert(strlcat(buf, somestring, sizeof(buf)) < sizeof(buf)); + assert(strncmp(buf, somestring, len_somestring) == 0); + assert(strncmp(buf + len_somestring, somestring, len_somestring) == 0); + assert(buf[sizeof(buf) - 1] == FILL_CHAR); + + /* Check that two strings just fail to fit into buf2. Check + * its nul-termination. */ + memset(buf2, FILL_CHAR, sizeof(buf2)); + strcpy(buf2, somestring); + + assert(strlcat(buf2, somestring, sizeof(buf2)) >= sizeof(buf2)); + assert(strncmp(buf2, somestring, len_somestring) == 0); + assert(strncmp(buf2 + len_somestring, somestring, + sizeof(buf2) - len_somestring - 1) == 0); + assert(buf2[sizeof(buf2) - 1] == '\0'); + + /* Copy somestring into a way-too-small buffer. Lie about the size + * of the buffer and check for buffer overrun. */ + + /* Case 1: None of the source string will fit. */ + check_too_small(somestring, buf2, sizeof(buf2), sizeof(somestring)); + + /* Case 2: Some of the source string will fit. */ + check_too_small(somestring, buf2, sizeof(buf2), sizeof(somestring) + 1); + check_too_small(somestring, buf2, sizeof(buf2), sizeof(somestring) + 2); + check_too_small(somestring, buf2, sizeof(buf2), sizeof(somestring) + 4); + + /* Check that copying into a buffer that is not nul-terminated + * returns size of destination buffer plus the length of string + * we're concatenating. */ + memset(buf, FILL_CHAR, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + + assert( strlcat(buf, somestring, sizeof(buf) - 1) + == (sizeof(buf) - 1 + strlen(somestring))); + + puts("PASS"); + return(EXIT_SUCCESS); + } *** /dev/null Sun Jan 12 13:07:53 2003 --- tests/libc/compat/string/t-stlcpy.c Tue Dec 17 18:00:22 2002 *************** *** 0 **** --- 1,57 ---- + #include + #include + #include + #include + + #define FILL_CHAR 'X' + + int + main (void) + { + const char somestring[] = "somestring"; + char buf[12]; /* More than big enough to contain somestring */ + char buf2[10]; /* Just too small to contain somestring. */ + int i; + + /* Try a zero size. Check that the buffer is untouched. */ + memset(buf, FILL_CHAR, sizeof(buf)); + + assert(strlcpy(buf, somestring, 0) == strlen(somestring)); + for (i = 0; i < sizeof(buf); i++) { + assert(buf[i] == FILL_CHAR); + } + + /* Copy somestring into a large-enough buffer. Check that it hasn't + * overrun. */ + memset(buf, FILL_CHAR, sizeof(buf)); + + assert(strlcpy(buf, somestring, sizeof(buf)) < sizeof(buf)); + assert(strcmp(buf, somestring) == 0); + assert(buf[sizeof(buf) - 1] == FILL_CHAR); + + /* Copy somestring into a just-too-small buffer. Check + * its nul-termination. */ + memset(buf2, FILL_CHAR, sizeof(buf2)); + + assert(strlcpy(buf2, somestring, sizeof(buf2)) == sizeof(buf2)); + assert(strncmp(buf2, somestring, sizeof(buf2) - 1) == 0); + assert(buf2[sizeof(buf2) - 1] == '\0'); + + /* Copy somestring into a way-too-small buffer. Lie about the size + * of the buffer and check for buffer overrun. */ + #define MADE_UP_SIZE 3 + assert(MADE_UP_SIZE < sizeof(buf2)); + + memset(buf2, FILL_CHAR, sizeof(buf2)); + + assert(strlcpy(buf2, somestring, MADE_UP_SIZE) >= MADE_UP_SIZE); + assert(strncmp(buf2, somestring, MADE_UP_SIZE - 1) == 0); + assert(buf2[MADE_UP_SIZE - 1] == '\0'); + for (i = MADE_UP_SIZE; i < sizeof(buf2); i++) { + assert(buf2[i] == FILL_CHAR); + } + #undef MADE_UP_SIZE + + puts("PASS"); + return(EXIT_SUCCESS); + }