#if defined (HAVE_LIMITS_H)
# include <limits.h>
#else
- /* Assume 32-bit ints and longs. */
-# define LONG_MAX 2147483647L
-# define LONG_MIN (-2147483647L-1)
+ /* Assume 32-bit ints. */
# define INT_MAX 2147483647
# define INT_MIN (-2147483647-1)
#endif
#include <stdio.h>
-#include <ctype.h>
+#include <chartypes.h>
+
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
#include "../bashansi.h"
+
+#define NEED_STRTOIMAX_DECL
+
#include "../shell.h"
#include "stdc.h"
#include "bashgetopt.h"
+#include "common.h"
+
+/* This should use the ISO C constant format strings; I'll do that later. */
+#if SIZEOF_LONG < SIZEOF_LONG_LONG
+# define INTMAX_CONV "ll"
+#else
+# define INTMAX_CONV "l"
+#endif
#if !defined (errno)
extern int errno;
#define PF(f, func) \
do { \
- if (fieldwidth && precision) \
- (void)printf(f, fieldwidth, precision, func); \
- else if (fieldwidth && precision == 0) \
- (void)printf(f, fieldwidth, func); \
- else if (precision) \
- (void)printf(f, precision, func); \
+ if (have_fieldwidth && have_precision) \
+ tw += printf(f, fieldwidth, precision, func); \
+ else if (have_fieldwidth) \
+ tw += printf(f, fieldwidth, func); \
+ else if (have_precision) \
+ tw += printf(f, precision, func); \
else \
- (void)printf(f, func); \
+ tw += printf(f, func); \
} while (0)
+/* We free the buffer used by mklong() if it's `too big'. */
#define PRETURN(value) \
- do { /* free (format); */ fflush (stdout); return (value); } while (0)
-
-#define SKIP1 "#-+ 0"
-#define SKIP2 "*0123456789"
+ do \
+ { \
+ if (conv_bufsize > 4096 ) \
+ { \
+ free(conv_buf); \
+ conv_bufsize = 0; \
+ conv_buf = 0; \
+ } \
+ fflush (stdout); \
+ return (value); \
+ } \
+ while (0)
+
+#define SKIP1 "#'-+ 0"
+#define LENMODS "hjlLtz"
static void printstr __P((char *, char *, int, int, int));
static int tescape __P((char *, int, char *, int *));
static char *bexpand __P((char *, int, int *, int *));
-static char *mklong __P((char *, int));
+static char *mklong __P((char *, char *));
static int getchr __P((void));
static char *getstr __P((void));
static int getint __P((void));
-static int getlong __P((long *));
-static int getulong __P((unsigned long *));
+static long getlong __P((void));
+static unsigned long getulong __P((void));
+#if defined (HAVE_LONG_LONG)
+static long long getllong __P((void));
+static unsigned long long getullong __P((void));
+#endif
+static intmax_t getintmax __P((void));
+static uintmax_t getuintmax __P((void));
static double getdouble __P((void));
+#if defined (HAVE_LONG_DOUBLE) && HAVE_DECL_STRTOLD
+static long double getldouble __P((void));
+#endif
static int asciicode __P((void));
static WORD_LIST *garglist;
static int retval;
+static int conversion_error;
-extern char *backslash_quote ();
+static char *conv_buf;
+static size_t conv_bufsize;
int
printf_builtin (list)
WORD_LIST *list;
{
- int ch, end, fieldwidth, precision, foundmod, fmtlen;
- char convch, nextch, *format, *fmt, *start;
+ int ch, fieldwidth, precision;
+ int have_fieldwidth, have_precision;
+ long tw;
+ char convch, thisch, nextch, *format, *modstart, *fmt, *start;
+ conversion_error = 0;
retval = EXECUTION_SUCCESS;
reset_internal_getopt ();
- while ((ch = internal_getopt (list, "")) != -1)
+ if (internal_getopt (list, "") != -1)
{
- switch (ch)
- {
- case '?':
- default:
- builtin_usage();
- return (EX_USAGE);
- }
+ builtin_usage();
+ return (EX_USAGE);
}
list = loptend;
format strings are reused as necessary to use up the provided
arguments, arguments of zero/null string are provided to use
up the format string. */
-
do
{
+ tw = 0;
/* find next format specification */
for (fmt = format; *fmt; fmt++)
{
- precision = fieldwidth = foundmod = 0;
+ precision = fieldwidth = 0;
+ have_fieldwidth = have_precision = 0;
+
if (*fmt == '\\')
{
fmt++;
- /* A NULL third argument to tescape means to not do special
+ /* A NULL fourth argument to tescape means to not do special
processing for \c. */
fmt += tescape (fmt, 1, &nextch, (int *)NULL);
putchar (nextch);
/* found format specification, skip to field width */
for (; *fmt && strchr(SKIP1, *fmt); ++fmt)
;
- fieldwidth = (*fmt == '*') ? getint () : 0;
- /* skip to possible '.', get following precision */
- for (; *fmt && strchr(SKIP2, *fmt); ++fmt)
- ;
+ /* Skip optional field width. */
+ if (*fmt == '*')
+ {
+ fmt++;
+ have_fieldwidth = 1;
+ fieldwidth = getint ();
+ }
+ else
+ while (DIGIT (*fmt))
+ fmt++;
+
+ /* Skip optional '.' and precision */
if (*fmt == '.')
{
++fmt;
- precision = (*fmt == '*') ? getint () : 0;
+ if (*fmt == '*')
+ {
+ fmt++;
+ have_precision = 1;
+ precision = getint ();
+ }
+ else
+ while (DIGIT (*fmt))
+ fmt++;
}
- /* skip to conversion char */
- for (; *fmt && strchr(SKIP2, *fmt); ++fmt)
- ;
-
/* skip possible format modifiers */
- if (*fmt == 'l' || *fmt == 'L' || *fmt == 'h')
- {
- fmt++;
- foundmod = 1;
- }
+ modstart = fmt;
+ while (*fmt && strchr (LENMODS, *fmt))
+ fmt++;
if (*fmt == 0)
{
}
convch = *fmt;
- nextch = fmt[1];
- fmt[1] = '\0';
+ thisch = modstart[0];
+ nextch = modstart[1];
+ modstart[0] = convch;
+ modstart[1] = '\0';
+
switch(convch)
{
case 'c':
break;
}
+ case 'n':
+ {
+ char *var;
+
+ var = getstr ();
+ if (var && *var)
+ {
+ if (legal_identifier (var))
+ bind_var_to_int (var, tw);
+ else
+ {
+ builtin_error ("%s: invalid variable name", var);
+ PRETURN (EXECUTION_FAILURE);
+ }
+ }
+ break;
+ }
+
case 'b': /* expand escapes in argument */
{
char *p, *xp;
char *p, *xp;
p = getstr ();
- xp = backslash_quote (p);
+ xp = sh_backslash_quote (p);
if (xp)
{
/* Use printstr to get fieldwidth and precision right. */
case 'd':
case 'i':
{
- long p;
char *f;
+#if defined (HAVE_LONG_LONG)
+ if (thisch == 'l' && nextch == 'l')
+ {
+ long long p;
+
+ p = getllong ();
+ f = mklong (start, "ll");
+ PF(f, p);
+ }
+ else
+#endif
+ if (thisch == 'j')
+ {
+ intmax_t p;
- if (foundmod == 0 && ((f = mklong (start, convch)) == NULL))
- PRETURN (EXECUTION_FAILURE);
+ p = getintmax ();
+ f = mklong (start, INTMAX_CONV);
+ PF(f, p);
+ }
else
- f = start;
- if (getlong (&p))
- PRETURN (EXECUTION_FAILURE);
- PF(f, p);
+ {
+ long p;
+
+ p = getlong ();
+ f = mklong (start, "l");
+ PF(f, p);
+ }
break;
}
case 'x':
case 'X':
{
- unsigned long p;
char *f;
+#if defined (HAVE_LONG_LONG)
+ if (thisch == 'l' && nextch == 'l')
+ {
+ unsigned long long p;
- if (foundmod == 0 && ((f = mklong (start, convch)) == NULL))
- PRETURN (EXECUTION_FAILURE);
+ p = getullong ();
+ f = mklong (start, "ll");
+ PF(f, p);
+ }
else
- f = start;
- if (getulong (&p))
- PRETURN (EXECUTION_FAILURE);
- PF (f, p);
+#endif
+ if (thisch == 'j')
+ {
+ uintmax_t p;
+
+ p = getuintmax ();
+ f = mklong (start, INTMAX_CONV);
+ PF(f, p);
+ }
+ else
+ {
+ unsigned long p;
+
+ p = getulong ();
+ f = mklong (start, "l");
+ PF (f, p);
+ }
break;
}
case 'e':
case 'E':
case 'f':
+ case 'F':
case 'g':
case 'G':
+#if defined (HAVE_PRINTF_A_FORMAT)
+ case 'a':
+ case 'A':
+#endif
{
- double p;
+ char *f;
+#if defined (HAVE_LONG_DOUBLE) && HAVE_DECL_STRTOLD
+ if (thisch == 'L')
+ {
+ long double p;
- p = getdouble ();
- PF(start, p);
+ p = getldouble ();
+ f = mklong (start, "L");
+ PF (f, p);
+ }
+ else
+#endif
+ {
+ double p;
+
+ p = getdouble ();
+ f = mklong (start, "");
+ PF (f, p);
+ }
break;
}
PRETURN (EXECUTION_FAILURE);
}
- fmt[1] = nextch;
+ modstart[0] = thisch;
+ modstart[1] = nextch;
}
}
while (garglist && garglist != list->next);
+ if (conversion_error)
+ retval = EXECUTION_FAILURE;
+
PRETURN (retval);
}
if (*fmt == '%')
fmt++;
- ljust = fw = pr = 0;
+ ljust = fw = 0;
+ pr = -1;
/* skip flags */
- while (*fmt == '#' || *fmt == '-' || *fmt == '+' || *fmt == ' ' || *fmt == '0')
+ while (strchr (SKIP1, *fmt))
{
if (*fmt == '-')
ljust = 1;
{
fmt++;
fw = fieldwidth;
+ if (fw < 0)
+ {
+ fw = -fw;
+ ljust = 1;
+ }
}
- else if (isdigit (*fmt))
+ else if (DIGIT (*fmt))
{
fw = *fmt++ - '0';
- while (isdigit (*fmt))
+ while (DIGIT (*fmt))
fw = (fw * 10) + (*fmt++ - '0');
}
fmt++;
pr = precision;
}
- else if (isdigit (*fmt))
+ else if (DIGIT (*fmt))
{
pr = *fmt++ - '0';
- while (isdigit (*fmt))
+ while (DIGIT (*fmt))
pr = (pr * 10) + (*fmt++ - '0');
}
}
#endif
/* chars from string to print */
- nc = (pr > 0 && pr <= len) ? pr : len;
+ nc = (pr >= 0 && pr <= len) ? pr : len;
padlen = fw - nc;
if (padlen < 0)
recognize `\c' and use that as a string terminator. If we see \c, set
*SAWC to 1 before returning. LEN is the length of STRING. */
-#ifdef isoctal
-#undef isoctal
-#endif
-
-#define isoctal(c) ((c) >= '0' && (c) <= '7')
-
-#define OCTVALUE(c) ((c) - '0')
-
-#ifndef isxdigit
-# define isxdigit(c) (isdigit((c)) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F'))
-#endif
-
-#define HEXVALUE(c) \
- ((c) >= 'a' && (c) <= 'f' ? (c)-'a'+10 : (c) >= 'A' && (c) <= 'F' ? (c)-'A'+10 : (c)-'0')
-
/* Translate a single backslash-escape sequence starting at ESTART (the
character after the backslash) and return the number of characters
consumed by the sequence. CP is the place to return the translated
case 'b': *cp = '\b'; break;
- case 'e': *cp = '\033'; break; /* ESC -- non-ANSI */
+ case 'e':
+ case 'E': *cp = '\033'; break; /* ESC -- non-ANSI */
case 'f': *cp = '\f'; break;
case 'v': *cp = '\v'; break;
/* %b octal constants are `\0' followed by one, two, or three
- octal digits... */
+ octal digits... */
case '0':
- for (temp = 3, evalue = 0; isoctal (*p) && temp--; p++)
+ for (temp = 3, evalue = 0; ISOCTAL (*p) && temp--; p++)
evalue = (evalue * 8) + OCTVALUE (*p);
- *cp = evalue;
+ *cp = evalue & 0xFF;
break;
/* but, as an extension, the other echo-like octal escape
sequences are supported as well. */
case '1': case '2': case '3': case '4':
case '5': case '6': case '7':
- for (temp = 2, evalue = c - '0'; isoctal (*p) && temp--; p++)
+ for (temp = 2, evalue = c - '0'; ISOCTAL (*p) && temp--; p++)
evalue = (evalue * 8) + OCTVALUE (*p);
- *cp = evalue;
+ *cp = evalue & 0xFF;
break;
/* And, as another extension, we allow \xNNN, where each N is a
hex digit. */
case 'x':
- for (temp = 3, evalue = 0; isxdigit (*p) && temp--; p++)
+ for (temp = 2, evalue = 0; ISXDIGIT ((unsigned char)*p) && temp--; p++)
evalue = (evalue * 16) + HEXVALUE (*p);
- if (temp == 3)
+ if (temp == 2)
{
builtin_error ("missing hex digit for \\x");
*cp = '\\';
return 0;
}
- *cp = evalue;
+ *cp = evalue & 0xFF;
break;
case '\\': /* \\ -> \ */
return ((char *)NULL);
}
- ret = xmalloc (len + 1);
+ ret = (char *)xmalloc (len + 1);
for (r = ret, s = string; s && *s; )
{
c = *s++;
}
static char *
-mklong (str, ch)
+mklong (str, modifiers)
char *str;
- int ch;
+ char *modifiers;
{
- static char copy[64];
- int len;
-
- len = strlen (str) + 2;
- FASTCOPY (str, copy, len - 3);
- copy[len - 3] = 'l';
- copy[len - 2] = ch;
- copy[len - 1] = '\0';
- return (copy);
+ size_t len, slen, mlen;
+
+ slen = strlen (str);
+ mlen = strlen (modifiers);
+ len = slen + mlen + 1;
+
+ if (len > conv_bufsize)
+ {
+ conv_bufsize = (((len + 1023) >> 10) << 10);
+ conv_buf = (char *)xrealloc (conv_buf, conv_bufsize);
+ }
+
+ FASTCOPY (str, conv_buf, slen - 1);
+ FASTCOPY (modifiers, conv_buf + slen - 1, mlen);
+
+ conv_buf[len - 2] = str[slen - 1];
+ conv_buf[len - 1] = '\0';
+ return (conv_buf);
}
static int
{
long ret;
- if (getlong (&ret))
- return (0);
+ ret = getlong ();
if (ret > INT_MAX)
{
- builtin_error ("%s: %s", garglist->word->word, strerror(ERANGE));
- return (0);
+ builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
+ ret = INT_MAX;
+ }
+ else if (ret < INT_MIN)
+ {
+ builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
+ ret = INT_MIN;
}
return ((int)ret);
}
-static int
-getlong (lp)
- long *lp;
+static long
+getlong ()
{
long ret;
char *ep;
if (garglist == 0)
- {
- *lp = 0L;
- return (0);
- }
+ return (0);
if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
- {
- *lp = (long)asciicode ();
- return (0);
- }
+ return asciicode ();
errno = 0;
- /* If we use 0 as the third argument, we can handle octal and hex, which
- legal_number does not. (This was
- if (legal_number (garglist->word->word, &ret) == 0)
- ) */
ret = strtol (garglist->word->word, &ep, 0);
- if (*ep != '\0')
+
+ if (*ep)
{
builtin_error ("%s: invalid number", garglist->word->word);
- return (1);
+ /* POSIX.2 says ``...a diagnostic message shall be written to standard
+ error, and the utility shall not exit with a zero exit status, but
+ shall continue processing any remaining operands and shall write the
+ value accumulated at the time the error was detected to standard
+ output.'' Yecch. */
+ ret = 0;
+ conversion_error = 1;
}
else if (errno == ERANGE)
- {
- builtin_error ("%s: %s", garglist->word->word, strerror(ERANGE));
- return (1);
- }
+ builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
- *lp = ret;
garglist = garglist->next;
- return (0);
+ return (ret);
}
-static int
-getulong (ulp)
- unsigned long *ulp;
+static unsigned long
+getulong ()
{
unsigned long ret;
char *ep;
if (garglist == 0)
+ return (0);
+
+ if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
+ return asciicode ();
+
+ errno = 0;
+ ret = strtoul (garglist->word->word, &ep, 0);
+
+ if (*ep)
{
- *ulp = (unsigned long)0;
- return (0);
+ builtin_error ("%s: invalid number", garglist->word->word);
+ /* Same thing about POSIX.2 conversion error requirements as getlong(). */
+ ret = 0;
+ conversion_error = 1;
}
+ else if (errno == ERANGE)
+ builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
+
+ garglist = garglist->next;
+ return (ret);
+}
+
+#if defined (HAVE_LONG_LONG)
+
+static long long
+getllong ()
+{
+ long long ret;
+ char *ep;
+
+ if (garglist == 0)
+ return (0);
if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
+ return asciicode ();
+
+ errno = 0;
+ ret = strtoll (garglist->word->word, &ep, 0);
+
+ if (*ep)
{
- *ulp = (unsigned long)asciicode ();
- return (0);
+ builtin_error ("%s: invalid number", garglist->word->word);
+ /* POSIX.2 says ``...a diagnostic message shall be written to standard
+ error, and the utility shall not exit with a zero exit status, but
+ shall continue processing any remaining operands and shall write the
+ value accumulated at the time the error was detected to standard
+ output.'' Yecch. */
+ ret = 0;
+ conversion_error = 1;
}
+ else if (errno == ERANGE)
+ builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
+
+ garglist = garglist->next;
+ return (ret);
+}
+
+static unsigned long long
+getullong ()
+{
+ unsigned long long ret;
+ char *ep;
+
+ if (garglist == 0)
+ return (0);
+
+ if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
+ return asciicode ();
errno = 0;
- ret = strtoul (garglist->word->word, &ep, 0);
+ ret = strtoull (garglist->word->word, &ep, 0);
if (*ep)
{
builtin_error ("%s: invalid number", garglist->word->word);
- return (1);
+ /* Same thing about POSIX.2 conversion error requirements as getlong(). */
+ ret = 0;
+ conversion_error = 1;
+ }
+ else if (errno == ERANGE)
+ builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
+
+ garglist = garglist->next;
+ return (ret);
+}
+
+#endif /* HAVE_LONG_LONG */
+
+static intmax_t
+getintmax ()
+{
+ intmax_t ret;
+ char *ep;
+
+ if (garglist == 0)
+ return (0);
+
+ if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
+ return asciicode ();
+
+ errno = 0;
+ ret = strtoimax (garglist->word->word, &ep, 0);
+
+ if (*ep)
+ {
+ builtin_error ("%s: invalid number", garglist->word->word);
+ /* POSIX.2 says ``...a diagnostic message shall be written to standard
+ error, and the utility shall not exit with a zero exit status, but
+ shall continue processing any remaining operands and shall write the
+ value accumulated at the time the error was detected to standard
+ output.'' Yecch. */
+ ret = 0;
+ conversion_error = 1;
}
else if (errno == ERANGE)
+ builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
+
+ garglist = garglist->next;
+ return (ret);
+}
+
+static uintmax_t
+getuintmax ()
+{
+ uintmax_t ret;
+ char *ep;
+
+ if (garglist == 0)
+ return (0);
+
+ if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
+ return asciicode ();
+
+ errno = 0;
+ ret = strtoumax (garglist->word->word, &ep, 0);
+
+ if (*ep)
{
- builtin_error ("%s: %s", garglist->word->word, strerror(ERANGE));
- return (1);
+ builtin_error ("%s: invalid number", garglist->word->word);
+ /* Same thing about POSIX.2 conversion error requirements as getlong(). */
+ ret = 0;
+ conversion_error = 1;
}
+ else if (errno == ERANGE)
+ builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
- *ulp = ret;
garglist = garglist->next;
- return (0);
+ return (ret);
}
static double
getdouble ()
{
double ret;
+ char *ep;
if (garglist == 0)
- return ((double)0);
+ return (0);
if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
- return ((double)asciicode ());
+ return asciicode ();
+
+ errno = 0;
+ ret = strtod (garglist->word->word, &ep);
+
+ if (*ep)
+ {
+ builtin_error ("%s: invalid number", garglist->word->word);
+ /* Same thing about POSIX.2 conversion error requirements. */
+ ret = 0;
+ conversion_error = 1;
+ }
+ else if (errno == ERANGE)
+ builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
+
+ garglist = garglist->next;
+ return (ret);
+}
+
+#if defined (HAVE_LONG_DOUBLE) && HAVE_DECL_STRTOLD
+static long double
+getldouble ()
+{
+ long double ret;
+ char *ep;
+
+ if (garglist == 0)
+ return (0);
+
+ if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"')
+ return (asciicode ());
+
+ errno = 0;
+ ret = strtold (garglist->word->word, &ep);
+
+ if (*ep)
+ {
+ builtin_error ("%s: invalid number", garglist->word->word);
+ /* Same thing about POSIX.2 conversion error requirements. */
+ ret = 0;
+ conversion_error = 1;
+ }
+ else if (errno == ERANGE)
+ builtin_error ("warning: %s: %s", garglist->word->word, strerror(ERANGE));
- /* This should use strtod if it is available. */
- ret = atof (garglist->word->word);
garglist = garglist->next;
return (ret);
}
+#endif /* HAVE_LONG_DOUBLE && HAVE_DECL_STRTOLD */
/* NO check is needed for garglist here. */
static int