Rev 41539 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
/* vsprintf with automatic memory allocation.Copyright (C) 1999, 2002-2006 Free Software Foundation, Inc.This program is free software; you can redistribute it and/or modify itunder the terms of the GNU Library General Public License as publishedby the Free Software Foundation; either version 2, or (at your option)any later version.This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNULibrary General Public License for more details.You should have received a copy of the GNU Library General PublicLicense along with this program; if not, write to the Free SoftwareFoundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,USA. *//* Tell glibc's <stdio.h> to provide a prototype for snprintf().This must come before <config.h> because <config.h> may include<features.h>, and once <features.h> has been included, it's too late. */#ifndef _GNU_SOURCE# define _GNU_SOURCE 1#endif#ifdef HAVE_CONFIG_H# include <config.h>#endif#ifndef IN_LIBINTL# include <alloca.h>#endif/* Specification. */#if WIDE_CHAR_VERSION# include "vasnwprintf.h"#else# include "vasnprintf.h"#endif#include <stdio.h> /* snprintf(), sprintf() */#include <stdlib.h> /* abort(), malloc(), realloc(), free() */#include <string.h> /* memcpy(), strlen() */#include <errno.h> /* errno */#include <limits.h> /* CHAR_BIT */#include <float.h> /* DBL_MAX_EXP, LDBL_MAX_EXP */#if WIDE_CHAR_VERSION# include "wprintf-parse.h"#else# include "printf-parse.h"#endif/* Checked size_t computations. */#include "xsize.h"#ifdef HAVE_WCHAR_T# ifdef HAVE_WCSLEN# define local_wcslen wcslen# else/* Solaris 2.5.1 has wcslen() in a separate library libw.so. To avoida dependency towards this library, here is a local substitute.Define this substitute only once, even if this file is includedtwice in the same compilation unit. */# ifndef local_wcslen_defined# define local_wcslen_defined 1static size_tlocal_wcslen (const wchar_t *s){const wchar_t *ptr;for (ptr = s; *ptr != (wchar_t) 0; ptr++);return ptr - s;}# endif# endif#endif#if WIDE_CHAR_VERSION# define VASNPRINTF vasnwprintf# define CHAR_T wchar_t# define DIRECTIVE wchar_t_directive# define DIRECTIVES wchar_t_directives# define PRINTF_PARSE wprintf_parse# define USE_SNPRINTF 1# if HAVE_DECL__SNWPRINTF/* On Windows, the function swprintf() has a different signature thanon Unix; we use the _snwprintf() function instead. */# define SNPRINTF _snwprintf# else/* Unix. */# define SNPRINTF swprintf# endif#else# define VASNPRINTF vasnprintf# define CHAR_T char# define DIRECTIVE char_directive# define DIRECTIVES char_directives# define PRINTF_PARSE printf_parse# define USE_SNPRINTF (HAVE_DECL__SNPRINTF || HAVE_SNPRINTF)# if HAVE_DECL__SNPRINTF/* Windows. */# define SNPRINTF _snprintf# else/* Unix. */# define SNPRINTF snprintf# endif#endifCHAR_T *VASNPRINTF (CHAR_T *resultbuf, size_t *lengthp, const CHAR_T *format, va_list args){DIRECTIVES d;arguments a;if (PRINTF_PARSE (format, &d, &a) < 0){errno = EINVAL;return NULL;}#define CLEANUP() \free (d.dir); \if (a.arg) \free (a.arg);if (printf_fetchargs (args, &a) < 0){CLEANUP ();errno = EINVAL;return NULL;}{size_t buf_neededlength;CHAR_T *buf;CHAR_T *buf_malloced;const CHAR_T *cp;size_t i;DIRECTIVE *dp;/* Output string accumulator. */CHAR_T *result;size_t allocated;size_t length;/* Allocate a small buffer that will hold a directive passed tosprintf or snprintf. */buf_neededlength =xsum4 (7, d.max_width_length, d.max_precision_length, 6);#if HAVE_ALLOCAif (buf_neededlength < 4000 / sizeof (CHAR_T)){buf = (CHAR_T *) alloca (buf_neededlength * sizeof (CHAR_T));buf_malloced = NULL;}else#endif{size_t buf_memsize = xtimes (buf_neededlength, sizeof (CHAR_T));if (size_overflow_p (buf_memsize))goto out_of_memory_1;buf = (CHAR_T *) malloc (buf_memsize);if (buf == NULL)goto out_of_memory_1;buf_malloced = buf;}if (resultbuf != NULL){result = resultbuf;allocated = *lengthp;}else{result = NULL;allocated = 0;}length = 0;/* Invariants:result is either == resultbuf or == NULL or malloc-allocated.If length > 0, then result != NULL. *//* Ensures that allocated >= needed. Aborts through a jump toout_of_memory if needed is SIZE_MAX or otherwise too big. */#define ENSURE_ALLOCATION(needed) \if ((needed) > allocated) \{ \size_t memory_size; \CHAR_T *memory; \\allocated = (allocated > 0 ? xtimes (allocated, 2) : 12); \if ((needed) > allocated) \allocated = (needed); \memory_size = xtimes (allocated, sizeof (CHAR_T)); \if (size_overflow_p (memory_size)) \goto out_of_memory; \if (result == resultbuf || result == NULL) \memory = (CHAR_T *) malloc (memory_size); \else \memory = (CHAR_T *) realloc (result, memory_size); \if (memory == NULL) \goto out_of_memory; \if (result == resultbuf && length > 0) \memcpy (memory, result, length * sizeof (CHAR_T)); \result = memory; \}for (cp = format, i = 0, dp = &d.dir[0]; ; cp = dp->dir_end, i++, dp++){if (cp != dp->dir_start){size_t n = dp->dir_start - cp;size_t augmented_length = xsum (length, n);ENSURE_ALLOCATION (augmented_length);memcpy (result + length, cp, n * sizeof (CHAR_T));length = augmented_length;}if (i == d.count)break;/* Execute a single directive. */if (dp->conversion == '%'){size_t augmented_length;if (!(dp->arg_index == ARG_NONE))abort ();augmented_length = xsum (length, 1);ENSURE_ALLOCATION (augmented_length);result[length] = '%';length = augmented_length;}else{if (!(dp->arg_index != ARG_NONE))abort ();if (dp->conversion == 'n'){switch (a.arg[dp->arg_index].type){case TYPE_COUNT_SCHAR_POINTER:*a.arg[dp->arg_index].a.a_count_schar_pointer = length;break;case TYPE_COUNT_SHORT_POINTER:*a.arg[dp->arg_index].a.a_count_short_pointer = length;break;case TYPE_COUNT_INT_POINTER:*a.arg[dp->arg_index].a.a_count_int_pointer = length;break;case TYPE_COUNT_LONGINT_POINTER:*a.arg[dp->arg_index].a.a_count_longint_pointer = length;break;#ifdef HAVE_LONG_LONG_INTcase TYPE_COUNT_LONGLONGINT_POINTER:*a.arg[dp->arg_index].a.a_count_longlongint_pointer = length;break;#endifdefault:abort ();}}else{arg_type type = a.arg[dp->arg_index].type;CHAR_T *p;unsigned int prefix_count;int prefixes[2];#if !USE_SNPRINTFsize_t tmp_length;CHAR_T tmpbuf[700];CHAR_T *tmp;/* Allocate a temporary buffer of sufficient size for callingsprintf. */{size_t width;size_t precision;width = 0;if (dp->width_start != dp->width_end){if (dp->width_arg_index != ARG_NONE){int arg;if (!(a.arg[dp->width_arg_index].type == TYPE_INT))abort ();arg = a.arg[dp->width_arg_index].a.a_int;width = (arg < 0 ? (unsigned int) (-arg) : arg);}else{const CHAR_T *digitp = dp->width_start;dowidth = xsum (xtimes (width, 10), *digitp++ - '0');while (digitp != dp->width_end);}}precision = 6;if (dp->precision_start != dp->precision_end){if (dp->precision_arg_index != ARG_NONE){int arg;if (!(a.arg[dp->precision_arg_index].type == TYPE_INT))abort ();arg = a.arg[dp->precision_arg_index].a.a_int;precision = (arg < 0 ? 0 : arg);}else{const CHAR_T *digitp = dp->precision_start + 1;precision = 0;while (digitp != dp->precision_end)precision = xsum (xtimes (precision, 10), *digitp++ - '0');}}switch (dp->conversion){case 'd': case 'i': case 'u':# ifdef HAVE_LONG_LONG_INTif (type == TYPE_LONGLONGINT || type == TYPE_ULONGLONGINT)tmp_length =(unsigned int) (sizeof (unsigned long long) * CHAR_BIT* 0.30103 /* binary -> decimal */)+ 1; /* turn floor into ceil */else# endifif (type == TYPE_LONGINT || type == TYPE_ULONGINT)tmp_length =(unsigned int) (sizeof (unsigned long) * CHAR_BIT* 0.30103 /* binary -> decimal */)+ 1; /* turn floor into ceil */elsetmp_length =(unsigned int) (sizeof (unsigned int) * CHAR_BIT* 0.30103 /* binary -> decimal */)+ 1; /* turn floor into ceil */if (tmp_length < precision)tmp_length = precision;/* Multiply by 2, as an estimate for FLAG_GROUP. */tmp_length = xsum (tmp_length, tmp_length);/* Add 1, to account for a leading sign. */tmp_length = xsum (tmp_length, 1);break;case 'o':# ifdef HAVE_LONG_LONG_INTif (type == TYPE_LONGLONGINT || type == TYPE_ULONGLONGINT)tmp_length =(unsigned int) (sizeof (unsigned long long) * CHAR_BIT* 0.333334 /* binary -> octal */)+ 1; /* turn floor into ceil */else# endifif (type == TYPE_LONGINT || type == TYPE_ULONGINT)tmp_length =(unsigned int) (sizeof (unsigned long) * CHAR_BIT* 0.333334 /* binary -> octal */)+ 1; /* turn floor into ceil */elsetmp_length =(unsigned int) (sizeof (unsigned int) * CHAR_BIT* 0.333334 /* binary -> octal */)+ 1; /* turn floor into ceil */if (tmp_length < precision)tmp_length = precision;/* Add 1, to account for a leading sign. */tmp_length = xsum (tmp_length, 1);break;case 'x': case 'X':# ifdef HAVE_LONG_LONG_INTif (type == TYPE_LONGLONGINT || type == TYPE_ULONGLONGINT)tmp_length =(unsigned int) (sizeof (unsigned long long) * CHAR_BIT* 0.25 /* binary -> hexadecimal */)+ 1; /* turn floor into ceil */else# endifif (type == TYPE_LONGINT || type == TYPE_ULONGINT)tmp_length =(unsigned int) (sizeof (unsigned long) * CHAR_BIT* 0.25 /* binary -> hexadecimal */)+ 1; /* turn floor into ceil */elsetmp_length =(unsigned int) (sizeof (unsigned int) * CHAR_BIT* 0.25 /* binary -> hexadecimal */)+ 1; /* turn floor into ceil */if (tmp_length < precision)tmp_length = precision;/* Add 2, to account for a leading sign or alternate form. */tmp_length = xsum (tmp_length, 2);break;case 'f': case 'F':# ifdef HAVE_LONG_DOUBLEif (type == TYPE_LONGDOUBLE)tmp_length =(unsigned int) (LDBL_MAX_EXP* 0.30103 /* binary -> decimal */* 2 /* estimate for FLAG_GROUP */)+ 1 /* turn floor into ceil */+ 10; /* sign, decimal point etc. */else# endiftmp_length =(unsigned int) (DBL_MAX_EXP* 0.30103 /* binary -> decimal */* 2 /* estimate for FLAG_GROUP */)+ 1 /* turn floor into ceil */+ 10; /* sign, decimal point etc. */tmp_length = xsum (tmp_length, precision);break;case 'e': case 'E': case 'g': case 'G':case 'a': case 'A':tmp_length =12; /* sign, decimal point, exponent etc. */tmp_length = xsum (tmp_length, precision);break;case 'c':# if defined HAVE_WINT_T && !WIDE_CHAR_VERSIONif (type == TYPE_WIDE_CHAR)tmp_length = MB_CUR_MAX;else# endiftmp_length = 1;break;case 's':# ifdef HAVE_WCHAR_Tif (type == TYPE_WIDE_STRING){tmp_length =local_wcslen (a.arg[dp->arg_index].a.a_wide_string);# if !WIDE_CHAR_VERSIONtmp_length = xtimes (tmp_length, MB_CUR_MAX);# endif}else# endiftmp_length = strlen (a.arg[dp->arg_index].a.a_string);break;case 'p':tmp_length =(unsigned int) (sizeof (void *) * CHAR_BIT* 0.25 /* binary -> hexadecimal */)+ 1 /* turn floor into ceil */+ 2; /* account for leading 0x */break;default:abort ();}if (tmp_length < width)tmp_length = width;tmp_length = xsum (tmp_length, 1); /* account for trailing NUL */}if (tmp_length <= sizeof (tmpbuf) / sizeof (CHAR_T))tmp = tmpbuf;else{size_t tmp_memsize = xtimes (tmp_length, sizeof (CHAR_T));if (size_overflow_p (tmp_memsize))/* Overflow, would lead to out of memory. */goto out_of_memory;tmp = (CHAR_T *) malloc (tmp_memsize);if (tmp == NULL)/* Out of memory. */goto out_of_memory;}#endif/* Construct the format string for calling snprintf orsprintf. */p = buf;*p++ = '%';if (dp->flags & FLAG_GROUP)*p++ = '\'';if (dp->flags & FLAG_LEFT)*p++ = '-';if (dp->flags & FLAG_SHOWSIGN)*p++ = '+';if (dp->flags & FLAG_SPACE)*p++ = ' ';if (dp->flags & FLAG_ALT)*p++ = '#';if (dp->flags & FLAG_ZERO)*p++ = '0';if (dp->width_start != dp->width_end){size_t n = dp->width_end - dp->width_start;memcpy (p, dp->width_start, n * sizeof (CHAR_T));p += n;}if (dp->precision_start != dp->precision_end){size_t n = dp->precision_end - dp->precision_start;memcpy (p, dp->precision_start, n * sizeof (CHAR_T));p += n;}switch (type){#ifdef HAVE_LONG_LONG_INTcase TYPE_LONGLONGINT:case TYPE_ULONGLONGINT:*p++ = 'l';/*FALLTHROUGH*/#endifcase TYPE_LONGINT:case TYPE_ULONGINT:#ifdef HAVE_WINT_Tcase TYPE_WIDE_CHAR:#endif#ifdef HAVE_WCHAR_Tcase TYPE_WIDE_STRING:#endif*p++ = 'l';break;#ifdef HAVE_LONG_DOUBLEcase TYPE_LONGDOUBLE:*p++ = 'L';break;#endifdefault:break;}*p = dp->conversion;#if USE_SNPRINTFp[1] = '%';p[2] = 'n';p[3] = '\0';#elsep[1] = '\0';#endif/* Construct the arguments for calling snprintf or sprintf. */prefix_count = 0;if (dp->width_arg_index != ARG_NONE){if (!(a.arg[dp->width_arg_index].type == TYPE_INT))abort ();prefixes[prefix_count++] = a.arg[dp->width_arg_index].a.a_int;}if (dp->precision_arg_index != ARG_NONE){if (!(a.arg[dp->precision_arg_index].type == TYPE_INT))abort ();prefixes[prefix_count++] = a.arg[dp->precision_arg_index].a.a_int;}#if USE_SNPRINTF/* Prepare checking whether snprintf returns the countvia %n. */ENSURE_ALLOCATION (xsum (length, 1));result[length] = '\0';#endiffor (;;){size_t maxlen;int count;int retcount;maxlen = allocated - length;count = -1;retcount = 0;#if USE_SNPRINTF# define SNPRINTF_BUF(arg) \switch (prefix_count) \{ \case 0: \retcount = SNPRINTF (result + length, maxlen, buf, \arg, &count); \break; \case 1: \retcount = SNPRINTF (result + length, maxlen, buf, \prefixes[0], arg, &count); \break; \case 2: \retcount = SNPRINTF (result + length, maxlen, buf, \prefixes[0], prefixes[1], arg, \&count); \break; \default: \abort (); \}#else# define SNPRINTF_BUF(arg) \switch (prefix_count) \{ \case 0: \count = sprintf (tmp, buf, arg); \break; \case 1: \count = sprintf (tmp, buf, prefixes[0], arg); \break; \case 2: \count = sprintf (tmp, buf, prefixes[0], prefixes[1],\arg); \break; \default: \abort (); \}#endifswitch (type){case TYPE_SCHAR:{int arg = a.arg[dp->arg_index].a.a_schar;SNPRINTF_BUF (arg);}break;case TYPE_UCHAR:{unsigned int arg = a.arg[dp->arg_index].a.a_uchar;SNPRINTF_BUF (arg);}break;case TYPE_SHORT:{int arg = a.arg[dp->arg_index].a.a_short;SNPRINTF_BUF (arg);}break;case TYPE_USHORT:{unsigned int arg = a.arg[dp->arg_index].a.a_ushort;SNPRINTF_BUF (arg);}break;case TYPE_INT:{int arg = a.arg[dp->arg_index].a.a_int;SNPRINTF_BUF (arg);}break;case TYPE_UINT:{unsigned int arg = a.arg[dp->arg_index].a.a_uint;SNPRINTF_BUF (arg);}break;case TYPE_LONGINT:{long int arg = a.arg[dp->arg_index].a.a_longint;SNPRINTF_BUF (arg);}break;case TYPE_ULONGINT:{unsigned long int arg = a.arg[dp->arg_index].a.a_ulongint;SNPRINTF_BUF (arg);}break;#ifdef HAVE_LONG_LONG_INTcase TYPE_LONGLONGINT:{long long int arg = a.arg[dp->arg_index].a.a_longlongint;SNPRINTF_BUF (arg);}break;case TYPE_ULONGLONGINT:{unsigned long long int arg = a.arg[dp->arg_index].a.a_ulonglongint;SNPRINTF_BUF (arg);}break;#endifcase TYPE_DOUBLE:{double arg = a.arg[dp->arg_index].a.a_double;SNPRINTF_BUF (arg);}break;#ifdef HAVE_LONG_DOUBLEcase TYPE_LONGDOUBLE:{long double arg = a.arg[dp->arg_index].a.a_longdouble;SNPRINTF_BUF (arg);}break;#endifcase TYPE_CHAR:{int arg = a.arg[dp->arg_index].a.a_char;SNPRINTF_BUF (arg);}break;#ifdef HAVE_WINT_Tcase TYPE_WIDE_CHAR:{wint_t arg = a.arg[dp->arg_index].a.a_wide_char;SNPRINTF_BUF (arg);}break;#endifcase TYPE_STRING:{const char *arg = a.arg[dp->arg_index].a.a_string;SNPRINTF_BUF (arg);}break;#ifdef HAVE_WCHAR_Tcase TYPE_WIDE_STRING:{const wchar_t *arg = a.arg[dp->arg_index].a.a_wide_string;SNPRINTF_BUF (arg);}break;#endifcase TYPE_POINTER:{void *arg = a.arg[dp->arg_index].a.a_pointer;SNPRINTF_BUF (arg);}break;default:abort ();}#if USE_SNPRINTF/* Portability: Not all implementations of snprintf()are ISO C 99 compliant. Determine the number ofbytes that snprintf() has produced or would haveproduced. */if (count >= 0){/* Verify that snprintf() has NUL-terminated itsresult. */if (count < maxlen && result[length + count] != '\0')abort ();/* Portability hack. */if (retcount > count)count = retcount;}else{/* snprintf() doesn't understand the '%n'directive. */if (p[1] != '\0'){/* Don't use the '%n' directive; instead, lookat the snprintf() return value. */p[1] = '\0';continue;}else{/* Look at the snprintf() return value. */if (retcount < 0){/* HP-UX 10.20 snprintf() is doubly deficient:It doesn't understand the '%n' directive,*and* it returns -1 (rather than the lengththat would have been required) when thebuffer is too small. */size_t bigger_need =xsum (xtimes (allocated, 2), 12);ENSURE_ALLOCATION (bigger_need);continue;}elsecount = retcount;}}#endif/* Attempt to handle failure. */if (count < 0){if (!(result == resultbuf || result == NULL))free (result);if (buf_malloced != NULL)free (buf_malloced);CLEANUP ();errno = EINVAL;return NULL;}#if !USE_SNPRINTFif (count >= tmp_length)/* tmp_length was incorrectly calculated - fix thecode above! */abort ();#endif/* Make room for the result. */if (count >= maxlen){/* Need at least count bytes. But allocateproportionally, to avoid looping eternally ifsnprintf() reports a too small count. */size_t n =xmax (xsum (length, count), xtimes (allocated, 2));ENSURE_ALLOCATION (n);#if USE_SNPRINTFcontinue;#endif}#if USE_SNPRINTF/* The snprintf() result did fit. */#else/* Append the sprintf() result. */memcpy (result + length, tmp, count * sizeof (CHAR_T));if (tmp != tmpbuf)free (tmp);#endiflength += count;break;}}}}/* Add the final NUL. */ENSURE_ALLOCATION (xsum (length, 1));result[length] = '\0';if (result != resultbuf && length + 1 < allocated){/* Shrink the allocated memory if possible. */CHAR_T *memory;memory = (CHAR_T *) realloc (result, (length + 1) * sizeof (CHAR_T));if (memory != NULL)result = memory;}if (buf_malloced != NULL)free (buf_malloced);CLEANUP ();*lengthp = length;/* Note that we can produce a big string of a length > INT_MAX. POSIXsays that snprintf() fails with errno = EOVERFLOW in this case, butthat's only because snprintf() returns an 'int'. This function doesnot have this limitation. */return result;out_of_memory:if (!(result == resultbuf || result == NULL))free (result);if (buf_malloced != NULL)free (buf_malloced);out_of_memory_1:CLEANUP ();errno = ENOMEM;return NULL;}}#undef SNPRINTF#undef USE_SNPRINTF#undef PRINTF_PARSE#undef DIRECTIVES#undef DIRECTIVE#undef CHAR_T#undef VASNPRINTF