/* 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 it
  under the terms of the GNU Library General Public License as published
  by 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 of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.

  You should have received a copy of the GNU Library General Public
  License along with this program; if not, write to the Free Software
  Foundation, 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

#include <config.h>
#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 avoid
     a dependency towards this library, here is a local substitute.
     Define this substitute only once, even if this file is included
     twice in the same compilation unit.  */
#  ifndef local_wcslen_defined
#   define local_wcslen_defined 1
static size_t
local_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 than
     on 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
#endif

CHAR_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 to
      sprintf or snprintf.  */
   buf_neededlength =
     xsum4 (7, d.max_width_length, d.max_precision_length, 6);
#if HAVE_ALLOCA
   if (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 to
      out_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_INT
                 case TYPE_COUNT_LONGLONGINT_POINTER:
                   *a.arg[dp->arg_index].a.a_count_longlongint_pointer = length;
                   break;
#endif
                 default:
                   abort ();
                 }
             }
           else
             {
               arg_type type = a.arg[dp->arg_index].type;
               CHAR_T *p;
               unsigned int prefix_count;
               int prefixes[2];
#if !USE_SNPRINTF
               size_t tmp_length;
               CHAR_T tmpbuf[700];
               CHAR_T *tmp;

               /* Allocate a temporary buffer of sufficient size for calling
                  sprintf.  */
               {
                 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;

                         do
                           width = 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_INT
                     if (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
# endif
                     if (type == TYPE_LONGINT || type == TYPE_ULONGINT)
                       tmp_length =
                         (unsigned int) (sizeof (unsigned long) * CHAR_BIT
                                         * 0.30103 /* binary -> decimal */
                                        )
                         + 1; /* turn floor into ceil */
                     else
                       tmp_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_INT
                     if (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
# endif
                     if (type == TYPE_LONGINT || type == TYPE_ULONGINT)
                       tmp_length =
                         (unsigned int) (sizeof (unsigned long) * CHAR_BIT
                                         * 0.333334 /* binary -> octal */
                                        )
                         + 1; /* turn floor into ceil */
                     else
                       tmp_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_INT
                     if (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
# endif
                     if (type == TYPE_LONGINT || type == TYPE_ULONGINT)
                       tmp_length =
                         (unsigned int) (sizeof (unsigned long) * CHAR_BIT
                                         * 0.25 /* binary -> hexadecimal */
                                        )
                         + 1; /* turn floor into ceil */
                     else
                       tmp_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_DOUBLE
                     if (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
# endif
                       tmp_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_VERSION
                     if (type == TYPE_WIDE_CHAR)
                       tmp_length = MB_CUR_MAX;
                     else
# endif
                       tmp_length = 1;
                     break;

                   case 's':
# ifdef HAVE_WCHAR_T
                     if (type == TYPE_WIDE_STRING)
                       {
                         tmp_length =
                           local_wcslen (a.arg[dp->arg_index].a.a_wide_string);

#  if !WIDE_CHAR_VERSION
                         tmp_length = xtimes (tmp_length, MB_CUR_MAX);
#  endif
                       }
                     else
# endif
                       tmp_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 or
                  sprintf.  */
               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_INT
                 case TYPE_LONGLONGINT:
                 case TYPE_ULONGLONGINT:
                   *p++ = 'l';
                   /*FALLTHROUGH*/
#endif
                 case TYPE_LONGINT:
                 case TYPE_ULONGINT:
#ifdef HAVE_WINT_T
                 case TYPE_WIDE_CHAR:
#endif
#ifdef HAVE_WCHAR_T
                 case TYPE_WIDE_STRING:
#endif
                   *p++ = 'l';
                   break;
#ifdef HAVE_LONG_DOUBLE
                 case TYPE_LONGDOUBLE:
                   *p++ = 'L';
                   break;
#endif
                 default:
                   break;
                 }
               *p = dp->conversion;
#if USE_SNPRINTF
               p[1] = '%';
               p[2] = 'n';
               p[3] = '\0';
#else
               p[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 count
                  via %n.  */
               ENSURE_ALLOCATION (xsum (length, 1));
               result[length] = '\0';
#endif

               for (;;)
                 {
                   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 ();                                           \
                     }
#endif

                   switch (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_INT
                     case 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;
#endif
                     case TYPE_DOUBLE:
                       {
                         double arg = a.arg[dp->arg_index].a.a_double;
                         SNPRINTF_BUF (arg);
                       }
                       break;
#ifdef HAVE_LONG_DOUBLE
                     case TYPE_LONGDOUBLE:
                       {
                         long double arg = a.arg[dp->arg_index].a.a_longdouble;
                         SNPRINTF_BUF (arg);
                       }
                       break;
#endif
                     case TYPE_CHAR:
                       {
                         int arg = a.arg[dp->arg_index].a.a_char;
                         SNPRINTF_BUF (arg);
                       }
                       break;
#ifdef HAVE_WINT_T
                     case TYPE_WIDE_CHAR:
                       {
                         wint_t arg = a.arg[dp->arg_index].a.a_wide_char;
                         SNPRINTF_BUF (arg);
                       }
                       break;
#endif
                     case TYPE_STRING:
                       {
                         const char *arg = a.arg[dp->arg_index].a.a_string;
                         SNPRINTF_BUF (arg);
                       }
                       break;
#ifdef HAVE_WCHAR_T
                     case TYPE_WIDE_STRING:
                       {
                         const wchar_t *arg = a.arg[dp->arg_index].a.a_wide_string;
                         SNPRINTF_BUF (arg);
                       }
                       break;
#endif
                     case 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 of
                      bytes that snprintf() has produced or would have
                      produced.  */
                   if (count >= 0)
                     {
                       /* Verify that snprintf() has NUL-terminated its
                          result.  */
                       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, look
                              at 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 length
                                  that would have been required) when the
                                  buffer is too small.  */
                               size_t bigger_need =
                                 xsum (xtimes (allocated, 2), 12);
                               ENSURE_ALLOCATION (bigger_need);
                               continue;
                             }
                           else
                             count = 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_SNPRINTF
                   if (count >= tmp_length)
                     /* tmp_length was incorrectly calculated - fix the
                        code above!  */
                     abort ();
#endif

                   /* Make room for the result.  */
                   if (count >= maxlen)
                     {
                       /* Need at least count bytes.  But allocate
                          proportionally, to avoid looping eternally if
                          snprintf() reports a too small count.  */
                       size_t n =
                         xmax (xsum (length, count), xtimes (allocated, 2));

                       ENSURE_ALLOCATION (n);
#if USE_SNPRINTF
                       continue;
#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);
#endif

                   length += 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.  POSIX
      says that snprintf() fails with errno = EOVERFLOW in this case, but
      that's only because snprintf() returns an 'int'.  This function does
      not 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