+/* snprintf - formatted output to strings, with bounds checking and allocation */
+
/*
build a test version with
gcc -g -DDRIVER -I../.. -I../../include -o test-snprintf snprintf.c fmtu*long.o
Unix snprintf implementation.
derived from inetutils/libinetutils/snprintf.c Version 1.1
- Copyright (C) 2001 Free Software Foundation, Inc.
+ Copyright (C) 2001,2006,2010,2012 Free Software Foundation, Inc.
+
+ This file is part of GNU Bash, the Bourne Again SHell.
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General License as published by
- the Free Software Foundation; either version 2 of the License, or
+ Bash is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
+
+ Bash 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 General License for more details.
-
- You should have received a copy of the GNU General License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bash. If not, see <http://www.gnu.org/licenses/>.
- Revision History:
+ Original (pre-bash) Revision History:
1.1:
* added changes from Miles Bader
/*
* Currently doesn't handle (and bash/readline doesn't use):
- * *M$ width, precision specifications
- * %N$ numbered argument conversions
- * inf, nan floating values imperfect (if isinf(), isnan() not in libc)
- * support for `F' is imperfect, since underlying printf may not handle it
+ * * *M$ width, precision specifications
+ * * %N$ numbered argument conversions
+ * * support for `F' is imperfect with ldfallback(), since underlying
+ * printf may not handle it -- should ideally have another autoconf test
*/
#define FLOATING_POINT
# include <config.h>
#endif
+/* GCC 4.2 on Snow Leopard doesn't like the snprintf prototype */
+#if defined(DEBUG) && !defined (MACOSX)
+# undef HAVE_SNPRINTF
+# undef HAVE_ASPRINTF
+
+# define HAVE_SNPRINTF 0
+# define HAVE_ASPRINTF 0
+#endif
+
#if defined(DRIVER) && !defined(HAVE_CONFIG_H)
#define HAVE_LONG_LONG
#define HAVE_LONG_DOUBLE
#define HAVE_PRINTF_A_FORMAT
#endif
#define HAVE_ISINF_IN_LIBC
+#define HAVE_ISNAN_IN_LIBC
#define PREFER_STDARG
#define HAVE_STRINGIZE
#define HAVE_LIMITS_H
#define intmax_t long
#endif
-#if !defined (HAVE_SNPRINTF) || !defined (HAVE_ASPRINTF)
+#if !HAVE_SNPRINTF || !HAVE_ASPRINTF
#include <bashtypes.h>
static char *groupnum __P((char *));
+#if defined (HAVE_LONG_DOUBLE)
+# define LONGDOUBLE long double
+#else
+# define LONGDOUBLE double
+#endif
+
+#ifndef isnan
+ static inline int isnan_f (float x) { return x != x; }
+ static inline int isnan_d (double x) { return x != x; }
+ static inline int isnan_ld (LONGDOUBLE x) { return x != x; }
+ # define isnan(x) \
+ (sizeof (x) == sizeof (LONGDOUBLE) ? isnan_ld (x) \
+ : sizeof (x) == sizeof (double) ? isnan_d (x) \
+ : isnan_f (x))
+#endif
+
+#ifndef isinf
+ static inline int isinf_f (float x) { return !isnan (x) && isnan (x - x); }
+ static inline int isinf_d (double x) { return !isnan (x) && isnan (x - x); }
+ static inline int isinf_ld (LONGDOUBLE x) { return !isnan (x) && isnan (x - x); }
+ # define isinf(x) \
+ (sizeof (x) == sizeof (LONGDOUBLE) ? isinf_ld (x) \
+ : sizeof (x) == sizeof (double) ? isinf_d (x) \
+ : isinf_f (x))
+#endif
+
#ifdef DRIVER
static void memory_error_and_abort ();
static void *xmalloc __P((size_t));
while (0)
#define PUT_PLUS(d, p, zero) \
- if ((d) > zero && (p)->justify == RIGHT) \
+ if (((p)->flags & PF_PLUS) && (d) > zero) \
PUT_CHAR('+', p)
#define PUT_SPACE(d, p, zero) \
for (; (p)->width > 0; (p)->width--) \
PUT_CHAR((p)->pad, p)
+/* pad with zeros from decimal precision */
+#define PAD_ZERO(p) \
+ if ((p)->precision > 0) \
+ for (; (p)->precision > 0; (p)->precision--) \
+ PUT_CHAR('0', p)
+
/* if width and prec. in the args */
#define STAR_ARGS(p) \
+ do { \
if ((p)->flags & PF_STAR_W) \
- (p)->width = GETARG (int); \
+ { \
+ (p)->width = GETARG (int); \
+ if ((p)->width < 0) \
+ { \
+ (p)->flags |= PF_LADJUST; \
+ (p)->justify = LEFT; \
+ (p)->width = -(p)->width; \
+ } \
+ } \
if ((p)->flags & PF_STAR_P) \
- (p)->precision = GETARG (int)
+ { \
+ (p)->precision = GETARG (int); \
+ if ((p)->precision < 0) \
+ { \
+ (p)->flags &= ~PF_STAR_P; \
+ (p)->precision = NOT_FOUND; \
+ } \
+ } \
+ } while (0)
-#if defined (HAVE_LOCALE_H)
+#if defined (HAVE_LOCALE_H) && defined (HAVE_LOCALECONV)
# define GETLOCALEDATA(d, t, g) \
do \
{ \
if (lv) \
{ \
if (lv->decimal_point && lv->decimal_point[0]) \
- (d) = lv->decimal_point[0]; \
+ (d) = lv->decimal_point[0]; \
if (lv->thousands_sep && lv->thousands_sep[0]) \
- (t) = lv->thousands_sep[0]; \
+ (t) = lv->thousands_sep[0]; \
(g) = lv->grouping ? lv->grouping : ""; \
if (*(g) == '\0' || *(g) == CHAR_MAX || (t) == -1) (g) = 0; \
} \
10^x ~= r
* log_10(200) = 2;
* log_10(250) = 2;
+ *
+ * NOTE: do not call this with r == 0 -- an infinite loop results.
*/
static int
log_10(r)
/*
* return an ascii representation of the integral part of the number
* and set fract to be an ascii representation of the fraction part
- * the container for the fraction and the integral part or staticly
+ * the container for the fraction and the integral part or statically
* declare with fix size
*/
static char *
register int i, j;
double ip, fp; /* integer and fraction part */
double fraction;
- int digits = MAX_INT - 1;
+ int digits, sign;
static char integral_part[MAX_INT];
static char fraction_part[MAX_FRACT];
- double sign;
int ch;
/* taking care of the obvious case: 0.0 */
{
integral_part[0] = '0';
integral_part[1] = '\0';
- fraction_part[0] = '0';
- fraction_part[1] = '\0';
+ /* The fractional part has to take the precision into account */
+ for (ch = 0; ch < precision-1; ch++)
+ fraction_part[ch] = '0';
+ fraction_part[ch] = '0';
+ fraction_part[ch+1] = '\0';
if (fract)
*fract = fraction_part;
return integral_part;
}
+ /* -0 is tricky */
+ sign = (number == -0.) ? '-' : ((number < 0.) ? '-' : '+');
+ digits = MAX_INT - 1;
+
/* for negative numbers */
- if ((sign = number) < 0.)
+ if (sign == '-')
{
number = -number;
digits--; /* sign consume one digit */
integral_part[i] = '9';
/* put the sign ? */
- if (sign < 0.)
+ if (sign == '-')
integral_part[i++] = '-';
integral_part[i] = '\0';
long sd;
int flags;
+ /* An explicit precision turns off the zero-padding flag and sets the
+ pad character back to space. */
+ if ((p->flags & PF_ZEROPAD) && p->precision >= 0 && (p->flags & PF_DOT))
+ {
+ p->flags &= ~PF_ZEROPAD;
+ p->pad = ' ';
+ }
+
sd = d; /* signed for ' ' padding in base 10 */
- flags = (*p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0;
+ flags = 0;
+ flags = (*p->pf == 'x' || *p->pf == 'X' || *p->pf == 'o' || *p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0;
if (*p->pf == 'X')
flags |= FL_HEXUPPER;
{
GETLOCALEDATA(decpoint, thoussep, grouping);
if (grouping && (t = groupnum (tmp)))
- tmp = t;
+ tmp = t;
}
- p->width -= strlen(tmp);
+ /* need to add one for any `+', but we only add one in base 10 */
+ p->width -= strlen(tmp) + (base == 10 && d > 0 && (p->flags & PF_PLUS));
PAD_RIGHT(p);
+ if ((p->flags & PF_DOT) && p->precision > 0)
+ {
+ p->precision -= strlen(tmp);
+ PAD_ZERO(p);
+ }
+
switch (base)
{
case 10:
long long sd;
int flags;
+ /* An explicit precision turns off the zero-padding flag and sets the
+ pad character back to space. */
+ if ((p->flags & PF_ZEROPAD) && p->precision >= 0 && (p->flags & PF_DOT))
+ {
+ p->flags &= ~PF_ZEROPAD;
+ p->pad = ' ';
+ }
+
sd = d; /* signed for ' ' padding in base 10 */
- flags = (*p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0;
+ flags = (*p->pf == 'x' || *p->pf == 'X' || *p->pf == 'o' || *p->pf == 'u' || *p->pf == 'U') ? FL_UNSIGNED : 0;
if (*p->pf == 'X')
flags |= FL_HEXUPPER;
{
GETLOCALEDATA(decpoint, thoussep, grouping);
if (grouping && (t = groupnum (tmp)))
- tmp = t;
+ tmp = t;
}
- p->width -= strlen(tmp);
+ /* need to add one for any `+', but we only add one in base 10 */
+ p->width -= strlen(tmp) + (base == 10 && d > 0 && (p->flags & PF_PLUS));
PAD_RIGHT(p);
+ if ((p->flags & PF_DOT) && p->precision > 0)
+ {
+ p->precision -= strlen(tmp);
+ PAD_ZERO(p);
+ }
+
switch (base)
{
case 10:
PUT_CHAR(*tmp, p);
tmp++;
}
+
PAD_LEFT(p);
}
{
len = wcsrtombs (NULL, &ws, 0, &mbs);
if (len != (size_t)-1)
- {
+ {
memset (&mbs, '\0', sizeof (mbstate_t));
os = (char *)xmalloc (len + 1);
(void)wcsrtombs (os, &ws, len + 1, &mbs);
- }
+ }
}
if (len == (size_t)-1)
{
#ifdef FLOATING_POINT
-#ifndef HAVE_ISINF_IN_LIBC
-/* Half-assed versions, since we don't want to link with libm. */
-static int
-isinf(d)
- double d;
-{
-#ifdef DBL_MAX
- if (d < DBL_MIN)
- return -1;
- else if (d > DBL_MAX)
- return 1;
- else
-#endif
- return 0;
-}
-
-static int
-isnan(d)
- double d;
-{
- return 0;
-}
-#endif
-
/* Check for [+-]infinity and NaN. If MODE == 1, we check for Infinity, else
(mode == 2) we check for NaN. This does the necessary printing. Returns
1 if Inf or Nan, 0 if not. */
char *tmp, *tmp2, *t;
int i;
- if (chkinfnan(p, d, 1) || chkinfnan(p, d, 2))
+ if (d != 0 && (chkinfnan(p, d, 1) || chkinfnan(p, d, 2)))
return; /* already printed nan or inf */
GETLOCALEDATA(decpoint, thoussep, grouping);
if ((p->flags & PF_THOUSANDS) && grouping && (t = groupnum (tmp)))
tmp = t;
+ if ((*p->pf == 'g' || *p->pf == 'G') && (p->flags & PF_ALTFORM) == 0)
+ {
+ /* smash the trailing zeros unless altform */
+ for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--)
+ tmp2[i] = '\0';
+ if (tmp2[0] == '\0')
+ p->precision = 0;
+ }
+
/* calculate the padding. 1 for the dot */
p->width = p->width -
+ /* XXX - should this be d>0. && (p->flags & PF_PLUS) ? */
+#if 0
((d > 0. && p->justify == RIGHT) ? 1:0) -
+#else
+ ((d > 0. && (p->flags & PF_PLUS)) ? 1:0) -
+#endif
((p->flags & PF_SPACE) ? 1:0) -
- strlen(tmp) - p->precision - 1;
- PAD_RIGHT(p);
- PUT_PLUS(d, p, 0.);
+ strlen(tmp) - p->precision -
+ ((p->precision != 0 || (p->flags & PF_ALTFORM)) ? 1 : 0); /* radix char */
+
+ if (p->pad == ' ')
+ {
+ PAD_RIGHT(p);
+ PUT_PLUS(d, p, 0.);
+ }
+ else
+ {
+ if (*tmp == '-')
+ PUT_CHAR(*tmp++, p);
+ PUT_PLUS(d, p, 0.);
+ PAD_RIGHT(p);
+ }
PUT_SPACE(d, p, 0.);
while (*tmp)
- { /* the integral */
- PUT_CHAR(*tmp, p);
+ {
+ PUT_CHAR(*tmp, p); /* the integral */
tmp++;
}
FREE (t);
if (p->precision != 0 || (p->flags & PF_ALTFORM))
PUT_CHAR(decpoint, p); /* put the '.' */
- if ((*p->pf == 'g' || *p->pf == 'G') && (p->flags & PF_ALTFORM) == 0)
- /* smash the trailing zeros unless altform */
- for (i = strlen(tmp2) - 1; i >= 0 && tmp2[i] == '0'; i--)
- tmp2[i] = '\0';
-
for (; *tmp2; tmp2++)
PUT_CHAR(*tmp2, p); /* the fraction */
char *tmp, *tmp2;
int j, i;
- if (chkinfnan(p, d, 1) || chkinfnan(p, d, 2))
+ if (d != 0 && (chkinfnan(p, d, 1) || chkinfnan(p, d, 2)))
return; /* already printed nan or inf */
GETLOCALEDATA(decpoint, thoussep, grouping);
DEF_PREC(p);
- j = log_10(d);
- d = d / pow_10(j); /* get the Mantissa */
- d = ROUND(d, p);
+ if (d == 0.)
+ j = 0;
+ else
+ {
+ j = log_10(d);
+ d = d / pow_10(j); /* get the Mantissa */
+ d = ROUND(d, p);
+ }
tmp = dtoa(d, p->precision, &tmp2);
/* 1 for unit, 1 for the '.', 1 for 'e|E',
- * 1 for '+|-', 2 for 'exp' */
+ * 1 for '+|-', 2 for 'exp' (but no `.' if precision == 0 */
/* calculate how much padding need */
p->width = p->width -
+ /* XXX - should this be d>0. && (p->flags & PF_PLUS) ? */
+#if 0
((d > 0. && p->justify == RIGHT) ? 1:0) -
- ((p->flags & PF_SPACE) ? 1:0) - p->precision - 6;
+#else
+ ((d > 0. && (p->flags & PF_PLUS)) ? 1:0) -
+#endif
+ (p->precision != 0 || (p->flags & PF_ALTFORM)) -
+ ((p->flags & PF_SPACE) ? 1:0) - p->precision - 5;
- PAD_RIGHT(p);
- PUT_PLUS(d, p, 0.);
+ if (p->pad == ' ')
+ {
+ PAD_RIGHT(p);
+ PUT_PLUS(d, p, 0.);
+ }
+ else
+ {
+ if (*tmp == '-')
+ PUT_CHAR(*tmp++, p);
+ PUT_PLUS(d, p, 0.);
+ PAD_RIGHT(p);
+ }
PUT_SPACE(d, p, 0.);
while (*tmp)
PUT_CHAR('E', p);
/* the sign of the exp */
- if (j > 0)
+ if (j >= 0)
PUT_CHAR('+', p);
else
{
PUT_CHAR(*tmp, p);
tmp++;
}
+
PAD_LEFT(p);
}
#endif
else if (*g == CHAR_MAX)
{
do
- *--re = *--se;
+ *--re = *--se;
while (se > s);
break;
}
wint_t wc;
#endif
const char *convstart;
+ int negprec;
/* Sanity check, the string length must be >= 0. C99 actually says that
LENGTH can be zero here, in the case of snprintf/vsnprintf (it's never
decpoint = thoussep = 0;
grouping = 0;
+ negprec = 0;
for (; c = *(data->pf); data->pf++)
{
if (c != '%')
case '#':
data->flags |= PF_ALTFORM;
continue;
- case '0':
- data->flags |= PF_ZEROPAD;
- data->pad = '0';
- continue;
case '*':
if (data->flags & PF_DOT)
data->flags |= PF_STAR_P;
data->flags |= PF_STAR_W;
continue;
case '-':
- data->flags |= PF_LADJUST;
- data->justify = LEFT;
+ if ((data->flags & PF_DOT) == 0)
+ {
+ data->flags |= PF_LADJUST;
+ data->justify = LEFT;
+ }
+ else
+ negprec = 1;
continue;
case ' ':
if ((data->flags & PF_PLUS) == 0)
data->flags |= PF_SPACE;
continue;
case '+':
- data->flags |= PF_PLUS;
- data->justify = RIGHT;
+ if ((data->flags & PF_DOT) == 0)
+ {
+ data->flags |= PF_PLUS;
+ if ((data->flags & PF_LADJUST) == 0)
+ data->justify = RIGHT;
+ }
continue;
case '\'':
data->flags |= PF_THOUSANDS;
continue;
+ case '0':
+ /* If we're not specifying precision (in which case we've seen
+ a `.') and we're not performing left-adjustment (in which
+ case the `0' is ignored), a `0' is taken as the zero-padding
+ flag. */
+ if ((data->flags & (PF_DOT|PF_LADJUST)) == 0)
+ {
+ data->flags |= PF_ZEROPAD;
+ data->pad = '0';
+ continue;
+ }
case '1': case '2': case '3':
case '4': case '5': case '6':
case '7': case '8': case '9':
if (n < 0)
n = 0;
if (data->flags & PF_DOT)
- data->precision = n;
+ data->precision = negprec ? NOT_FOUND : n;
else
data->width = n;
continue;
STAR_ARGS(data);
DEF_PREC(data);
d = GETDOUBLE(data);
- i = log_10(d);
+ i = (d != 0.) ? log_10(d) : -1;
/*
* for '%g|%G' ANSI: use f if exponent
* is in the range or [-4,p] exclusively
else
{
/* reduce precision by 1 because of leading digit before
- decimal point in e format. */
- data->precision--;
+ decimal point in e format, unless specified as 0. */
+ if (data->precision > 0)
+ data->precision--;
exponent(data, d);
}
state = 0;
char fmtbuf[FALLBACK_FMTSIZE], *obuf;
int fl;
- obuf = (char *)xmalloc(LFALLBACK_BASE + (data->precision < 6 ? 6 : data->precision) + 2);
+ fl = LFALLBACK_BASE + (data->precision < 6 ? 6 : data->precision) + 2;
+ obuf = (char *)xmalloc (fl);
fl = fe - fs + 1;
strncpy (fmtbuf, fs, fl);
fmtbuf[fl] = '\0';
- sprintf (obuf, fmtbuf, ld);
+
+ if ((data->flags & PF_STAR_W) && (data->flags & PF_STAR_P))
+ sprintf (obuf, fmtbuf, data->width, data->precision, ld);
+ else if (data->flags & PF_STAR_W)
+ sprintf (obuf, fmtbuf, data->width, ld);
+ else if (data->flags & PF_STAR_P)
+ sprintf (obuf, fmtbuf, data->precision, ld);
+ else
+ sprintf (obuf, fmtbuf, ld);
+
for (x = obuf; *x; x++)
PUT_CHAR (*x, data);
xfree (obuf);
fl = fe - fs + 1;
strncpy (fmtbuf, fs, fl);
fmtbuf[fl] = '\0';
- sprintf (obuf, fmtbuf, d);
+
+ if ((data->flags & PF_STAR_W) && (data->flags & PF_STAR_P))
+ sprintf (obuf, fmtbuf, data->width, data->precision, d);
+ else if (data->flags & PF_STAR_W)
+ sprintf (obuf, fmtbuf, data->width, d);
+ else if (data->flags & PF_STAR_P)
+ sprintf (obuf, fmtbuf, data->precision, d);
+ else
+ sprintf (obuf, fmtbuf, d);
+
for (x = obuf; *x; x++)
PUT_CHAR (*x, data);
}
#endif /* FLOATING_POINT */
-#ifndef HAVE_SNPRINTF
+#if !HAVE_SNPRINTF
int
#if defined (__STDC__)
#endif /* HAVE_SNPRINTF */
-#ifndef HAVE_ASPRINTF
+#if !HAVE_ASPRINTF
int
#if defined (__STDC__)
return rval;
}
-#endif
+#endif /* !HAVE_ASPRINTF */
-#endif
+#endif /* !HAVE_SNPRINTF || !HAVE_ASPRINTF */
#ifdef DRIVER