X-Git-Url: https://git.saurik.com/apple/shell_cmds.git/blobdiff_plain/44bd5ea795281151bc7b81a65d2dd42c6b8914d8..deb63bfb0b6f1eb3b14b2a2adb33a6f90d11095a:/printf/printf.c diff --git a/printf/printf.c b/printf/printf.c index 9ac5fbd..438b85e 100644 --- a/printf/printf.c +++ b/printf/printf.c @@ -1,6 +1,6 @@ -/* $NetBSD: printf.c,v 1.19 1998/02/03 03:10:15 perry Exp $ */ - -/* +/*- + * Copyright 2014 Garrett D'Amore + * Copyright 2010 Nexenta Systems, Inc. All rights reserved. * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * @@ -12,10 +12,6 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * This product includes software developed by the University of - * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. @@ -32,21 +28,25 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ +/* + * Important: This file is used both as a standalone program /usr/bin/printf + * and as a builtin for /bin/sh (#define SHELL). + */ -#include +#ifndef SHELL #ifndef lint -#if !defined(BUILTIN) && !defined(SHELL) -__COPYRIGHT("@(#) Copyright (c) 1989, 1993\n\ - The Regents of the University of California. All rights reserved.\n"); -#endif +static char const copyright[] = +"@(#) Copyright (c) 1989, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ #endif #ifndef lint #if 0 -static char sccsid[] = "@(#)printf.c 8.2 (Berkeley) 3/22/95"; -#else -__RCSID("$NetBSD: printf.c,v 1.19 1998/02/03 03:10:15 perry Exp $"); +static char const sccsid[] = "@(#)printf.c 8.1 (Berkeley) 7/20/93"; #endif +static const char rcsid[] = + "$FreeBSD: head/usr.bin/printf/printf.c 279503 2015-03-01 21:46:55Z jilles $"; #endif /* not lint */ #include @@ -54,497 +54,633 @@ __RCSID("$NetBSD: printf.c,v 1.19 1998/02/03 03:10:15 perry Exp $"); #include #include #include +#include #include #include #include #include -#include #include #include - -static int print_escape_str __P((const char *)); -static size_t print_escape __P((const char *)); - -static int getchr __P((void)); -static double getdouble __P((void)); -static int getint __P((void)); -static long getlong __P((void)); -static unsigned long getulong __P ((void)); -static char *getstr __P((void)); -static char *mklong __P((const char *, int)); -static void check_conversion __P((const char *, const char *)); -static void usage __P((void)); - -static int rval; -static char **gargv; - -#ifdef BUILTIN -int progprintf __P((int, char **)); -#else -int main __P((int, char **)); -#endif - -#define isodigit(c) ((c) >= '0' && (c) <= '7') -#define octtobin(c) ((c) - '0') -#define hextobin(c) ((c) >= 'A' && (c) <= 'F' ? c - 'A' + 10 : (c) >= 'a' && (c) <= 'f' ? c - 'a' + 10 : c - '0') +#include #ifdef SHELL -#define main printfcmd -#ifdef __APPLE__ +#define main printfcmd #include "bltin/bltin.h" -#else -#include "../../bin/sh/bltin/bltin.h" -#endif - -#ifdef __STDC__ -#include -#else -#include -#endif - -static void warnx __P((const char *fmt, ...)); - -static void -#ifdef __STDC__ -warnx(const char *fmt, ...) -#else -warnx(fmt, va_alist) - const char *fmt; - va_dcl -#endif -{ - - char buf[64]; - va_list ap; - -#ifdef __STDC__ - va_start(ap, fmt); -#else - va_start(ap); +#include "options.h" #endif - vsprintf(buf, fmt, ap); - va_end(ap); - error(buf); -} -#endif /* SHELL */ - -#define PF(f, func) { \ - if (fieldwidth) \ - if (precision) \ - (void)printf(f, fieldwidth, precision, func); \ - else \ - (void)printf(f, fieldwidth, func); \ - else if (precision) \ - (void)printf(f, precision, func); \ - else \ - (void)printf(f, func); \ -} +#define PF(f, func) do { \ + char *b = NULL; \ + if (havewidth) \ + if (haveprec) \ + (void)asprintf(&b, f, fieldwidth, precision, func); \ + else \ + (void)asprintf(&b, f, fieldwidth, func); \ + else if (haveprec) \ + (void)asprintf(&b, f, precision, func); \ + else \ + (void)asprintf(&b, f, func); \ + if (b) { \ + (void)fputs(b, stdout); \ + free(b); \ + } \ +} while (0) + +static int asciicode(void); +static char *printf_doformat(char *, int *); +static int escape(char *, int, size_t *); +static int getchr(void); +static int getfloating(long double *, int); +static int getint(int *); +static int getnum(intmax_t *, uintmax_t *, int); +static const char + *getstr(void); +static char *mknum(char *, char); +static void usage(void); + +static const char digits[] = "0123456789"; + +static char end_fmt[1]; + +static int myargc; +static char **myargv; +static char **gargv; +static char **maxargv; int -#ifdef BUILTIN -progprintf(argc, argv) -#else -main(argc, argv) -#endif - int argc; - char *argv[]; +main(int argc, char *argv[]) { - char *fmt, *start; - int fieldwidth, precision; - char convch, nextch; - char *format; + size_t len; + int end, rval; + char *format, *fmt, *start; +#ifndef SHELL int ch; -#if !defined(SHELL) && !defined(BUILTIN) - (void)setlocale (LC_ALL, ""); + (void) setlocale(LC_ALL, ""); #endif - while ((ch = getopt(argc, argv, "")) != -1) { +#ifdef SHELL + nextopt(""); + argc -= argptr - argv; + argv = argptr; +#else + while ((ch = getopt(argc, argv, "")) != -1) switch (ch) { case '?': default: usage(); return (1); } - } argc -= optind; argv += optind; +#endif if (argc < 1) { usage(); return (1); } - format = *argv; +#ifdef SHELL + INTOFF; +#endif + /* + * Basic algorithm is to scan the format string for conversion + * specifications -- once one is found, find out if the field + * width or precision is a '*'; if it is, gather up value. Note, + * 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. + */ + fmt = format = *argv; + escape(fmt, 1, &len); /* backslash interpretation */ + rval = end = 0; gargv = ++argv; -#define SKIP1 "#-+ 0" -#define SKIP2 "*0123456789" - do { - /* - * Basic algorithm is to scan the format string for conversion - * specifications -- once one is found, find out if the field - * width or precision is a '*'; if it is, gather up value. - * Note, 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. - */ - - /* find next format specification */ - for (fmt = format; *fmt; fmt++) { - switch (*fmt) { - case '%': - start = fmt++; - - if (*fmt == '%') { - (void)putchar('%'); - break; - } else if (*fmt == 'b') { - char *p = getstr(); - if (print_escape_str(p)) { - return (rval); + for (;;) { + maxargv = gargv; + + myargv = gargv; + for (myargc = 0; gargv[myargc]; myargc++) + /* nop */; + start = fmt; + while (fmt < format + len) { + if (fmt[0] == '%') { + fwrite(start, 1, fmt - start, stdout); + if (fmt[1] == '%') { + /* %% prints a % */ + putchar('%'); + fmt += 2; + } else { + fmt = printf_doformat(fmt, &rval); + if (fmt == NULL || fmt == end_fmt) { +#ifdef SHELL + INTON; +#endif + return (fmt == NULL ? 1 : rval); } - break; - } - - /* skip to field width */ - for (; strchr(SKIP1, *fmt); ++fmt) ; - fieldwidth = *fmt == '*' ? getint() : 0; - - /* skip to possible '.', get following precision */ - for (; strchr(SKIP2, *fmt); ++fmt) ; - if (*fmt == '.') - ++fmt; - precision = *fmt == '*' ? getint() : 0; - - for (; strchr(SKIP2, *fmt); ++fmt) ; - if (!*fmt) { - warnx ("missing format character"); - return(1); - } - - convch = *fmt; - nextch = *(fmt + 1); - *(fmt + 1) = '\0'; - switch(convch) { - case 'c': { - char p = getchr(); - PF(start, p); - break; - } - case 's': { - char *p = getstr(); - PF(start, p); - break; - } - case 'd': - case 'i': { - char *f = mklong(start, convch); - long p = getlong(); - PF(f, p); - break; - } - case 'o': - case 'u': - case 'x': - case 'X': { - char *f = mklong(start, convch); - unsigned long p = getulong(); - PF(f, p); - break; + end = 0; } - case 'e': - case 'E': - case 'f': - case 'g': - case 'G': { - double p = getdouble(); - PF(start, p); - break; - } - default: - warnx ("%s: invalid directive", start); - return(1); - } - *(fmt + 1) = nextch; - break; - - case '\\': - fmt += print_escape(fmt); - break; - - default: - (void)putchar(*fmt); - break; - } + start = fmt; + } else + fmt++; + if (gargv > maxargv) + maxargv = gargv; } - } while (gargv > argv && *gargv); + gargv = maxargv; - return (rval); + if (end == 1) { + warnx("missing format character"); +#ifdef SHELL + INTON; +#endif + return (1); + } + fwrite(start, 1, fmt - start, stdout); + if (!*gargv) { +#ifdef SHELL + INTON; +#endif + return (rval); + } + /* Restart at the beginning of the format string. */ + fmt = format; + end = 1; + } + /* NOTREACHED */ } -/* - * Print SysV echo(1) style escape string - * Halts processing string and returns 1 if a \c escape is encountered. - */ -static int -print_escape_str(str) - const char *str; +static char * +printf_doformat(char *fmt, int *rval) { - int value; - int c; - - while (*str) { - if (*str == '\\') { - str++; - /* - * %b string octal constants are not like those in C. - * They start with a \0, and are followed by 0, 1, 2, - * or 3 octal digits. - */ - if (*str == '0') { - str++; - for (c = 3, value = 0; c-- && isodigit(*str); str++) { - value <<= 3; - value += octtobin(*str); - } - (void)putchar(value); - str--; - } else if (*str == 'c') { - return 1; - } else { - str--; - str += print_escape(str); - } + static const char skip1[] = "#'-+ 0"; + int fieldwidth, haveprec, havewidth, mod_ldbl, precision; + char convch, nextch; + char start[strlen(fmt) + 1]; + char **fargv; + char *dptr; + int l; + + dptr = start; + *dptr++ = '%'; + *dptr = 0; + + fmt++; + + /* look for "n$" field index specifier */ + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (idx <= myargc) { + gargv = &myargv[idx - 1]; } else { - (void)putchar(*str); + gargv = &myargv[myargc]; } - str++; + if (gargv > maxargv) + maxargv = gargv; + fmt += l + 1; + + /* save format argument */ + fargv = gargv; + } else { + fargv = NULL; } - return 0; -} - -/* - * Print "standard" escape characters - */ -static size_t -print_escape(str) - const char *str; -{ - const char *start = str; - int value; - int c; + /* skip to field width */ + while (*fmt && strchr(skip1, *fmt) != NULL) { + *dptr++ = *fmt++; + *dptr = 0; + } - str++; + if (*fmt == '*') { - switch (*str) { - case '0': case '1': case '2': case '3': - case '4': case '5': case '6': case '7': - for (c = 3, value = 0; c-- && isodigit(*str); str++) { - value <<= 3; - value += octtobin(*str); - } - (void)putchar(value); - return str - start - 1; - /* NOTREACHED */ - - case 'x': - str++; - for (value = 0; isxdigit(*str); str++) { - value <<= 4; - value += hextobin(*str); + fmt++; + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (fargv == NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + fmt += l + 1; + } else if (fargv != NULL) { + warnx("incomplete use of n$"); + return (NULL); } - if (value > UCHAR_MAX) { - warnx ("escape sequence out of range for character"); - rval = 1; + + if (getint(&fieldwidth)) + return (NULL); + if (gargv > maxargv) + maxargv = gargv; + havewidth = 1; + + *dptr++ = '*'; + *dptr = 0; + } else { + havewidth = 0; + + /* skip to possible '.', get following precision */ + while (isdigit(*fmt)) { + *dptr++ = *fmt++; + *dptr = 0; } - (void)putchar (value); - return str - start - 1; - /* NOTREACHED */ + } - case '\\': /* backslash */ - (void)putchar('\\'); - break; + if (*fmt == '.') { + /* precision present? */ + fmt++; + *dptr++ = '.'; - case '\'': /* single quote */ - (void)putchar('\''); - break; + if (*fmt == '*') { - case '"': /* double quote */ - (void)putchar('"'); - break; + fmt++; + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (fargv == NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + fmt += l + 1; + } else if (fargv != NULL) { + warnx("incomplete use of n$"); + return (NULL); + } - case 'a': /* alert */ -#ifdef __STDC__ - (void)putchar('\a'); -#else - (void)putchar(007); -#endif - break; + if (getint(&precision)) + return (NULL); + if (gargv > maxargv) + maxargv = gargv; + haveprec = 1; + *dptr++ = '*'; + *dptr = 0; + } else { + haveprec = 0; - case 'b': /* backspace */ - (void)putchar('\b'); - break; + /* skip to conversion char */ + while (isdigit(*fmt)) { + *dptr++ = *fmt++; + *dptr = 0; + } + } + } else + haveprec = 0; + if (!*fmt) { + warnx("missing format character"); + return (NULL); + } + *dptr++ = *fmt; + *dptr = 0; + + /* + * Look for a length modifier. POSIX doesn't have these, so + * we only support them for floating-point conversions, which + * are extensions. This is useful because the L modifier can + * be used to gain extra range and precision, while omitting + * it is more likely to produce consistent results on different + * architectures. This is not so important for integers + * because overflow is the only bad thing that can happen to + * them, but consider the command printf %a 1.1 + */ + if (*fmt == 'L') { + mod_ldbl = 1; + fmt++; + if (!strchr("aAeEfFgG", *fmt)) { + warnx("bad modifier L for %%%c", *fmt); + return (NULL); + } + } else { + mod_ldbl = 0; + } - case 'e': /* escape */ -#ifdef __GNUC__ - (void)putchar('\e'); -#else - (void)putchar(033); -#endif - break; + /* save the current arg offset, and set to the format arg */ + if (fargv != NULL) { + gargv = fargv; + } - case 'f': /* form-feed */ - (void)putchar('\f'); - break; + convch = *fmt; + nextch = *++fmt; - case 'n': /* newline */ - (void)putchar('\n'); - break; + *fmt = '\0'; + switch (convch) { + case 'b': { + size_t len; + char *p; + int getout; - case 'r': /* carriage-return */ - (void)putchar('\r'); + p = strdup(getstr()); + if (p == NULL) { + warnx("%s", strerror(ENOMEM)); + return (NULL); + } + getout = escape(p, 0, &len); + fputs(p, stdout); + free(p); + if (getout) + return (end_fmt); break; + } + case 'c': { + char p; - case 't': /* tab */ - (void)putchar('\t'); + p = getchr(); + PF(start, p); break; + } + case 's': { + const char *p; - case 'v': /* vertical-tab */ - (void)putchar('\v'); + p = getstr(); + PF(start, p); break; - - default: - (void)putchar(*str); - warnx("unknown escape sequence `\\%c'", *str); - rval = 1; + } + case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': { + char *f; + intmax_t val; + uintmax_t uval; + int signedconv; + + signedconv = (convch == 'd' || convch == 'i'); + if ((f = mknum(start, convch)) == NULL) + return (NULL); + if (getnum(&val, &uval, signedconv)) + *rval = 1; + if (signedconv) + PF(f, val); + else + PF(f, uval); break; } - - return 1; + case 'e': case 'E': + case 'f': case 'F': + case 'g': case 'G': + case 'a': case 'A': { + long double p; + + if (getfloating(&p, mod_ldbl)) + *rval = 1; + if (mod_ldbl) + PF(start, p); + else + PF(start, (double)p); + break; + } + default: + warnx("illegal format character %c", convch); + return (NULL); + } + *fmt = nextch; + /* return the gargv to the next element */ + return (fmt); } static char * -mklong(str, ch) - const char *str; - char ch; +mknum(char *str, char ch) { - static char copy[64]; - size_t len; + static char *copy; + static size_t copy_size; + char *newcopy; + size_t len, newlen; len = strlen(str) + 2; - (void)memmove(copy, str, len - 3); - copy[len - 3] = 'l'; + if (len > copy_size) { + newlen = ((len + 1023) >> 10) << 10; + if ((newcopy = realloc(copy, newlen)) == NULL) { + warnx("%s", strerror(ENOMEM)); + return (NULL); + } + copy = newcopy; + copy_size = newlen; + } + + memmove(copy, str, len - 3); + copy[len - 3] = 'j'; copy[len - 2] = ch; copy[len - 1] = '\0'; - return (copy); + return (copy); } static int -getchr() +escape(char *fmt, int percent, size_t *len) +{ + char *save, *store, c; + int value; + + for (save = store = fmt; ((c = *fmt) != 0); ++fmt, ++store) { + if (c != '\\') { + *store = c; + continue; + } + switch (*++fmt) { + case '\0': /* EOS, user error */ + *store = '\\'; + *++store = '\0'; + *len = store - save; + return (0); + case '\\': /* backslash */ + case '\'': /* single quote */ + *store = *fmt; + break; + case 'a': /* bell/alert */ + *store = '\a'; + break; + case 'b': /* backspace */ + *store = '\b'; + break; + case 'c': + if (!percent) { + *store = '\0'; + *len = store - save; + return (1); + } + *store = 'c'; + break; + case 'f': /* form-feed */ + *store = '\f'; + break; + case 'n': /* newline */ + *store = '\n'; + break; + case 'r': /* carriage-return */ + *store = '\r'; + break; + case 't': /* horizontal tab */ + *store = '\t'; + break; + case 'v': /* vertical tab */ + *store = '\v'; + break; + /* octal constant */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c = (!percent && *fmt == '0') ? 4 : 3; + for (value = 0; + c-- && *fmt >= '0' && *fmt <= '7'; ++fmt) { + value <<= 3; + value += *fmt - '0'; + } + --fmt; + if (percent && value == '%') { + *store++ = '%'; + *store = '%'; + } else + *store = (char)value; + break; + default: + *store = *fmt; + break; + } + } + *store = '\0'; + *len = store - save; + return (0); +} + +static int +getchr(void) { if (!*gargv) return ('\0'); return ((int)**gargv++); } -static char * -getstr() +static const char * +getstr(void) { if (!*gargv) return (""); return (*gargv++); } -static char *Number = "+-.0123456789"; static int -getint() +getint(int *ip) { - if (!*gargv) - return(0); + intmax_t val; + uintmax_t uval; + int rval; - if (strchr(Number, **gargv)) - return(atoi(*gargv++)); - - return 0; -} - -static long -getlong() -{ - long val; - char *ep; - - if (!*gargv) - return(0L); - - if (**gargv == '\"' || **gargv == '\'') - return (long) *((*gargv++)+1); - - errno = 0; - val = strtol (*gargv, &ep, 0); - check_conversion(*gargv++, ep); - return val; + if (getnum(&val, &uval, 1)) + return (1); + rval = 0; + if (val < INT_MIN || val > INT_MAX) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + *ip = (int)val; + return (rval); } -static unsigned long -getulong() +static int +getnum(intmax_t *ip, uintmax_t *uip, int signedconv) { - unsigned long val; char *ep; + int rval; - if (!*gargv) - return(0UL); - - if (**gargv == '\"' || **gargv == '\'') - return (unsigned long) *((*gargv++)+1); - + if (!*gargv) { + *ip = *uip = 0; + return (0); + } + if (**gargv == '"' || **gargv == '\'') { + if (signedconv) + *ip = asciicode(); + else + *uip = asciicode(); + return (0); + } + rval = 0; errno = 0; - val = strtoul (*gargv, &ep, 0); - check_conversion(*gargv++, ep); - return val; + if (signedconv) + *ip = strtoimax(*gargv, &ep, 0); + else + *uip = strtoumax(*gargv, &ep, 0); + if (ep == *gargv) { + warnx("%s: expected numeric value", *gargv); + rval = 1; + } + else if (*ep != '\0') { + warnx("%s: not completely converted", *gargv); + rval = 1; + } + if (errno == ERANGE) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + ++gargv; + return (rval); } -static double -getdouble() +static int +getfloating(long double *dp, int mod_ldbl) { - double val; char *ep; + int rval; - if (!*gargv) - return(0.0); - - if (**gargv == '\"' || **gargv == '\'') - return (double) *((*gargv++)+1); - + if (!*gargv) { + *dp = 0.0; + return (0); + } + if (**gargv == '"' || **gargv == '\'') { + *dp = asciicode(); + return (0); + } + rval = 0; errno = 0; - val = strtod (*gargv, &ep); - check_conversion(*gargv++, ep); - return val; + if (mod_ldbl) + *dp = strtold(*gargv, &ep); + else + *dp = strtod(*gargv, &ep); + if (ep == *gargv) { + warnx("%s: expected numeric value", *gargv); + rval = 1; + } else if (*ep != '\0') { + warnx("%s: not completely converted", *gargv); + rval = 1; + } + if (errno == ERANGE) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + ++gargv; + return (rval); } -static void -check_conversion(s, ep) - const char *s; - const char *ep; +static int +asciicode(void) { - if (*ep) { - if (ep == s) - warnx ("%s: expected numeric value", s); - else - warnx ("%s: not completely converted", s); - rval = 1; - } else if (errno == ERANGE) { - warnx ("%s: %s", s, strerror(ERANGE)); - rval = 1; + int ch; + wchar_t wch; + mbstate_t mbs; + + ch = (unsigned char)**gargv; + if (ch == '\'' || ch == '"') { + memset(&mbs, 0, sizeof(mbs)); + switch (mbrtowc(&wch, *gargv + 1, MB_LEN_MAX, &mbs)) { + case (size_t)-2: + case (size_t)-1: + wch = (unsigned char)gargv[0][1]; + break; + case 0: + wch = 0; + break; + } + ch = wch; } + ++gargv; + return (ch); } static void -usage() +usage(void) { - (void)fprintf(stderr, "usage: printf format [arg ...]\n"); + (void)fprintf(stderr, "usage: printf format [arguments ...]\n"); }