Sender: rich AT phekda DOT freeserve DOT co DOT uk Message-ID: <3B081813.B83F96D3@phekda.freeserve.co.uk> Date: Sun, 20 May 2001 20:16:36 +0100 From: Richard Dawe X-Mailer: Mozilla 4.77 [en] (X11; U; Linux 2.2.17 i586) X-Accept-Language: de,fr MIME-Version: 1.0 To: DJGPP workers Subject: Re: snprintf? References: Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit Reply-To: djgpp-workers AT delorie DOT com Hello. Eli Zaretskii wrote: > > On Sun, 20 May 2001, Richard Dawe wrote: > > This is already handled in the patch snprintf-20001119.diff, which I > > included in a mail called "snprintf() diff, take 4?", sent on > > 2000-11-19: > > Sorry I missed that. No worries - it was tucked away. > > > - test the case of errors in format conversions (anything that > > > causes _doprnt return -1). > > > > I've looked at the _doprnt() code and I am unsure how to force a > > format conversion error. Any suggestions? > > Just force _doprint to return -1, it doesn't matter how. Cheat by > replacing _doprint with some fake function, if you have too. > The objective is to see how does snprintf cope with that situation. I took you too literally. I added a test that stubs out _doprnt() with one that fails, as you suggested. > > I just added a couple of tests for padding & precision specifiers and > > that's turned up a couple of problems that I haven't investigated yet. > > You can find a large sample of formats and expected results in > djtst203.zip, in the Cygnus test suite. It actually turned out that my format string was wrong (phew!). I added a couple of tests to check that the buffer was not overflowed for cases where field widths and precisions were used. Anyhow, please find below a diff against latest CVS sources with these changes. The new width tests are at the end of tests/libc/ansi/stdio/tsnprtf.c; the _doprntf() fail test is tests/libc/ansi/stdio/tsnprtf2.c. I also made vsnprintf() zero the FILE structure before filling it in. OK to commit? Thanks, bye, Rich =] -- Richard Dawe http://www.phekda.freeserve.co.uk/richdawe/ *** /develop/djgpp/include/stdio.h Fri Dec 8 22:46:26 2000 --- /develop/djgpp.dev/include/stdio.h Sun May 20 18:57:42 2001 *************** *** 1,3 **** --- 1,4 ---- + /* Copyright (C) 2001 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 */ *************** void rewind(FILE *_stream); *** 97,102 **** --- 98,104 ---- int scanf(const char *_format, ...); void setbuf(FILE *_stream, char *_buf); int setvbuf(FILE *_stream, char *_buf, int _mode, size_t _size); + int snprintf(char *str, size_t n, const char *fmt, ...); int sprintf(char *_s, const char *_format, ...); int sscanf(const char *_s, const char *_format, ...); FILE * tmpfile(void); *************** char * tmpnam(char *_s); *** 104,109 **** --- 106,112 ---- int ungetc(int _c, FILE *_stream); int vfprintf(FILE *_stream, const char *_format, va_list _ap); int vprintf(const char *_format, va_list _ap); + int vsnprintf(char *str, size_t n, const char *fmt, va_list ap); int vsprintf(char *_s, const char *_format, va_list _ap); #ifndef __STRICT_ANSI__ *** /develop/djgpp/src/docs/kb/wc204.txi Sat Apr 14 11:28:04 2001 --- /develop/djgpp.dev/src/docs/kb/wc204.txi Sun May 20 18:57:42 2001 *************** would lose the exit status. *** 375,377 **** --- 375,381 ---- File handles connected to the console device are no longer reported by the @code{select} function as not ready for input when termios functions are used to read those handles in cooked mode. + + @findex snprintf + @findex vsnprintf + New functions @code{snprintf} and @code{vsnprintf} added. *** /develop/djgpp/src/libc/ansi/stdio/flsbuf.c Thu Jun 3 18:27:34 1999 --- /develop/djgpp.dev/src/libc/ansi/stdio/flsbuf.c Sun May 20 18:57:42 2001 *************** *** 1,3 **** --- 1,4 ---- + /* Copyright (C) 2001 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ *************** _flsbuf(int c, FILE *f) *** 27,32 **** --- 28,37 ---- if ((f->_flag&_IOWRT)==0) return EOF; + /* No-op for full string buffers */ + if (f->_flag & _IOSTRG) + return c; + /* if the buffer is not yet allocated, allocate it */ if ((base = f->_base) == NULL && (f->_flag & _IONBF) == 0) { *** /develop/djgpp/src/libc/ansi/stdio/makefile Sun Jun 28 18:44:16 1998 --- /develop/djgpp.dev/src/libc/ansi/stdio/makefile Sun May 20 18:57:42 2001 *************** *** 1,3 **** --- 1,4 ---- + # Copyright (C) 2001 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 TOP=../.. *************** SRC += ungetc.c *** 61,65 **** --- 62,68 ---- SRC += vfprintf.c SRC += vprintf.c SRC += vsprintf.c + SRC += snprintf.c + SRC += vsnprntf.c include $(TOP)/../makefile.inc *** /develop/djgpp/src/libc/ansi/stdio/snprintf.c Sun Nov 12 21:57:18 2000 --- /develop/djgpp.dev/src/libc/ansi/stdio/snprintf.c Sun May 20 18:57:42 2001 *************** *** 1 **** --- 1,16 ---- + /* Copyright (C) 2001 DJ Delorie see COPYING.DJ for details */ + #include + #include + int + snprintf(char *str, size_t n, const char *fmt, ...) + { + va_list ap; + int len; + + va_start(ap, fmt); + len = vsnprintf(str, n, fmt, ap); + va_end(ap); + + return len; + } *** /develop/djgpp/src/libc/ansi/stdio/snprintf.txh Sun Nov 12 21:57:18 2000 --- /develop/djgpp.dev/src/libc/ansi/stdio/snprintf.txh Sun May 20 18:57:42 2001 *************** *** 1 **** --- 1,32 ---- + @node snprintf, stdio + @subheading Syntax + @example + #include + + int snprintf (char *@var{buffer}, size_t @var{n}, const char *@var{format}, + @dots{}); + @end example + + @subheading Description + + This function works similarly to @code{sprintf()} (@pxref{sprintf}), but + the size @var{n} of the @var{buffer} is also taken into account. This + function will write @var{n} - 1 characters. The @var{n}th character is used + for the terminating nul. If @var{n} is zero, @var{buffer} is not touched. + + @subheading Return Value + + The number of characters that would have been written (excluding the trailing + nul) is returned; otherwise -1 is returned to flag encoding or buffer space + errors. + + The maximum accepted value of @var{n} is @code{INT_MAX}. @code{INT_MAX} is + defined in @code{}. -1 is returned and @code{errno} is set to + @code{EFBIG}, if @var{n} is greater than this limit. + + @subheading Portability + + @port-note ansi The buffer size limit is imposed by DJGPP. Other systems may not have this limitation. + + @portability ansi *** /develop/djgpp/src/libc/ansi/stdio/sprintf.txh Sun Sep 27 16:20:50 1998 --- /develop/djgpp.dev/src/libc/ansi/stdio/sprintf.txh Sun May 20 18:57:42 2001 *************** int sprintf(char *buffer, const char *fo *** 12,17 **** --- 12,20 ---- Sends formatted output from the arguments (@dots{}) to the @var{buffer}. @xref{printf}. + To avoid buffer overruns, it is safer to use @code{snprintf()} + (@pxref{snprintf}). + @subheading Return Value The number of characters written. *** /develop/djgpp/src/libc/ansi/stdio/vsnprntf.c Sun Nov 12 21:57:18 2000 --- /develop/djgpp.dev/src/libc/ansi/stdio/vsnprntf.c Sun May 20 19:58:28 2001 *************** *** 1 **** --- 1,44 ---- + /* Copyright (C) 2001 DJ Delorie, see COPYING.DJ for details */ + #include + #include + #include + #include + #include + int + vsnprintf(char *str, size_t n, const char *fmt, va_list ap) + { + FILE _strbuf; + int len; + + /* _cnt is an int in the FILE structure. To prevent wrap-around, we limit + * n to between 0 and INT_MAX inclusively. */ + if (n > INT_MAX) + { + errno = EFBIG; + return -1; + } + + memset(&_strbuf, 0, sizeof(_strbuf)); + _strbuf._flag = _IOWRT | _IOSTRG | _IONTERM; + + /* If n == 0, just querying how much space is needed. */ + if (n > 0) + { + _strbuf._cnt = n - 1; + _strbuf._ptr = str; + } + else + { + _strbuf._cnt = 0; + _strbuf._ptr = NULL; + } + + len = _doprnt(fmt, ap, &_strbuf); + + /* Ensure nul termination */ + if (n > 0) + *_strbuf._ptr = 0; + + return len; + } *** /develop/djgpp/src/libc/ansi/stdio/vsnprntf.txh Sun Nov 12 21:57:18 2000 --- /develop/djgpp.dev/src/libc/ansi/stdio/vsnprntf.txh Sun May 20 18:57:42 2001 *************** *** 1 **** --- 1,33 ---- + @node vsnprintf, stdio + @subheading Syntax + @example + #include + #include + + int vsnprintf (char *@var{buffer}, size_t @var{n}, const char *@var{format}, + va_list @var{ap}); + @end example + + @subheading Description + + This function works similarly to @code{vsprintf()} (@pxref{vsprintf}), but + the size @var{n} of the @var{buffer} is also taken into account. This + function will write @var{n} - 1 characters. The @var{n}th character is used + for the terminating nul. If @var{n} is zero, @var{buffer} is not touched. + + @subheading Return Value + + The number of characters that would have been written (excluding the trailing + nul) is returned; otherwise -1 is returned to flag encoding or buffer space + errors. + + The maximum accepted value of @var{n} is @code{INT_MAX}. @code{INT_MAX} is + defined in @code{}. -1 is returned and @code{errno} is set to + @code{EFBIG}, if @var{n} is greater than this limit. + + @subheading Portability + + @port-note ansi The buffer size limit is imposed by DJGPP. Other systems may not have this limitation. + + @portability ansi *** /develop/djgpp/src/libc/ansi/stdio/vsprintf.txh Sun Sep 27 16:20:50 1998 --- /develop/djgpp.dev/src/libc/ansi/stdio/vsprintf.txh Sun May 20 18:57:42 2001 *************** int vsprintf(char *buffer, const char *f *** 13,18 **** --- 13,21 ---- Sends formatted output from the @var{arguments} to the @var{buffer}. @xref{printf}. @xref{vfprintf}. + To avoid buffer overruns, it is safer to use @code{vsnprintf()} + (@pxref{vsnprintf}). + @subheading Return Value The number of characters written. *** /develop/djgpp/tests/libc/ansi/stdio/makefile Sun Apr 18 16:14:36 1999 --- /develop/djgpp.dev/tests/libc/ansi/stdio/makefile Sun May 20 18:57:42 2001 *************** SRC += printf.c *** 16,20 **** --- 16,22 ---- SRC += sscanf.c SRC += tmpnam.c SRC += tscanf.c + SRC += tsnprtf.c + SRC += tsnprtf2.c include $(TOP)/../makefile.inc *** /develop/djgpp/tests/libc/ansi/stdio/tsnprtf.c Thu Jan 1 00:00:00 1970 --- /develop/djgpp.dev/tests/libc/ansi/stdio/tsnprtf.c Sun May 20 20:11:10 2001 *************** *** 0 **** --- 1,134 ---- + /* + * tsnprtf.c - Test for snprintf() + */ + + #include + #include + #include + + int + main (void) + { + char BIG[] = "Hello this is a too big string for the buffer"; + char holder[24]; + int i, j; + + i = snprintf(holder, sizeof(holder), "%s\n", BIG); + printf("%s\n", BIG); + printf("%s\n", holder); + /* + * We are expecting : + * i == strlen(BIG) + 1 + * meaning the number that would have been written if the buffer was + * large enough (see C9X). + */ + if (i != strlen(BIG) + 1 /* nul */) + { + fprintf(stderr, "FAILED snprintf\n"); + fprintf(stderr, + "sizeof (%ld), snprintf(%d), strlen(%ld)\n", + sizeof(holder), i, strlen(BIG)) ; + exit(EXIT_FAILURE); + } + + /* + * We may have broken sscanf since it is also a string stream + * Lets do a basic test. + */ + { + static char line[] = "25 December 2000\n"; + int day, year; + char month[24]; + + /* we are expecting to read 3 variables */ + if ((i = sscanf(line, "%d %s %d\n", &day, month, &year)) == 3) + { + i = snprintf(holder, sizeof(holder), "%d %s %d\n", day, month, year); + printf(line); + j = printf(holder); + if (i != j) + { + fprintf(stderr, "FAILED snprintf\n"); + fprintf(stderr, "snprintf (%d) != printf (%d)\n", i, j); + exit(EXIT_FAILURE); + } + } + else + { + printf("sscanf (%d)\n", i); + printf("FAILED sscanf\n"); + exit(EXIT_FAILURE); + } + } + + /* Test length estimation - once with buffer, once without. */ + *holder = '\0'; + i = snprintf(holder, 0, "%s", BIG); + if ((i != strlen(BIG)) || (*holder != '\0') /* buffer touched */) + { + fprintf(stderr, "FAILED length estimation (with buffer)\n"); + exit(EXIT_FAILURE); + } + + i = snprintf(NULL, 0, "%s", BIG); + if (i != strlen(BIG)) + { + fprintf(stderr, "FAILED length estimation (no buffer)\n"); + exit(EXIT_FAILURE); + } + + /* Try writing to a 1 byte buffer */ + snprintf(holder, sizeof(holder), "%s", BIG); + i = snprintf(holder, 1, "%s", BIG); + + if ((i < 0) || (*holder != '\0')) + { + fprintf(stderr, "FAILED termination only\n"); + exit(EXIT_FAILURE); + } + + /* Test maximum buffer size */ + i = snprintf(holder, ((size_t) INT_MAX) + 1, "%s", BIG); + + if (i >= 0) + { + fprintf(stderr, "FAILED too large buffer\n"); + exit(EXIT_FAILURE); + } + + /* Test padding a field to larger than buffer size. */ + { + int s = sizeof(holder) * 16; + + i = snprintf(holder, sizeof(holder), "%*s", s, BIG); + + if ((i != s) || ((strlen(holder) + 1) != sizeof(holder))) + { + fprintf(stderr, + "FAILED with padding larger than buffer: %d output, " + "%ld written to buffer\n", + i, strlen(holder)); + exit(EXIT_FAILURE); + } + } + + /* Test precision to larger than buffer size. */ + { + int s = sizeof(holder) * 4; + + i = snprintf(holder, sizeof(holder), "%*.*e", s, s, 1e0); + + if ((i <= s) || ((strlen(holder) + 1) != sizeof(holder))) + { + fprintf(stderr, + "FAILED with precision larger than buffer: %d output, " + "%ld written to buffer\n", + i, strlen(holder)); + exit(EXIT_FAILURE); + } + } + + /* signal success */ + printf("SUCCESS\n"); + return(EXIT_SUCCESS); + } *** /develop/djgpp/tests/libc/ansi/stdio/tsnprtf2.c Thu Jan 1 00:00:00 1970 --- /develop/djgpp.dev/tests/libc/ansi/stdio/tsnprtf2.c Sun May 20 20:09:02 2001 *************** *** 0 **** --- 1,35 ---- + /* + * tsnprtf2.c - Test _doprnt() failure case for snprintf() + */ + + #include + #include + #include + + /* Simulate an encoding error by making _doprnt() fail every time. snprintf() + * and vsnprintf() both invoke _doprnt(). */ + + int + _doprnt (const char *format, void *params, FILE *file) + { + return(-1); + } + + int + main (void) + { + char holder[24]; + int i; + + /* Test handling of encoding errors */ + i = snprintf(holder, sizeof(holder), "%s", "foo"); + + if (i >= 0) + { + fputs("FAILED generating encoding error", stderr); + exit(EXIT_FAILURE); + } + + puts("SUCCESS"); + return(EXIT_SUCCESS); + }