Date: Thu, 09 Jan 2003 21:35:54 +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 2 [PATCH] Message-Id: Reply-To: djgpp-workers AT delorie DOT com Hello. Here's revision 2 of my implementation of strlcat & strlcpy for DJGPP. Changes: * include the fact that the buffers shouldn't overlap in the docs; * cope with dest having no nul-termination (docs and testcase included). Thanks to David F. for the commentary. NetBSD seems to behave the same way as OpenBSD BTW. At least, that's what it seems from the NetBSD man pages I downloaded off the web. OK to commit? Thanks, bye, Rich =] PS: I updated the 2.04 status page: nothing major - just moved a couple of fixed bugs into the resolved section. 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 9 Jan 2003 21:31:23 -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 9 Jan 2003 21:31:23 -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 9 Jan 2003 21:31:28 -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 9 Jan 2003 21:31:28 -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 Thu Jan 9 21:32:42 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 Thu Jan 9 21:32:42 2003 --- src/libc/compat/string/strlcat.txh Thu Jan 9 21:30:02 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}). Unlike @code{strncat}, + @code{strlcat} @emph{always} nul-terminates the destination @var{dest} + for non-zero sizes @var{size}, with one exception. If @var{dest} + is not nul-terminated, then @var{dest} is not modified. + + If @var{dest} and @var{src} are overlapping buffers, the behavior + is undefined. + + @code{strlcat} will not examine more than @var{size} characters + of @var{dest}. This is to avoid overrunning the buffer @var{dest}. + + @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 Thu Jan 9 21:32:42 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 Thu Jan 9 21:32:42 2003 --- src/libc/compat/string/strlcpy.txh Thu Jan 9 20:54:00 2003 *************** *** 0 **** --- 1,49 ---- + @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. + + @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 Thu Jan 9 21:32:42 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 Thu Jan 9 21:32:42 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); + }