-/*
+/*-
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ * Copyright 2010 Nexenta Systems, Inc. All rights reserved.
* Copyright (c) 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* 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.
* 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).
+ */
-#if !defined(BUILTIN) && !defined(SHELL)
+#ifndef SHELL
#ifndef lint
static char const copyright[] =
"@(#) Copyright (c) 1989, 1993\n\
static char const sccsid[] = "@(#)printf.c 8.1 (Berkeley) 7/20/93";
#endif
static const char rcsid[] =
- "$FreeBSD: src/usr.bin/printf/printf.c,v 1.37 2005/08/05 08:18:00 stefanf Exp $";
+ "$FreeBSD: head/usr.bin/printf/printf.c 279503 2015-03-01 21:46:55Z jilles $";
#endif /* not lint */
#include <sys/types.h>
+#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
+#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
+#include <wchar.h>
#ifdef SHELL
-#define main printfcmd
+#define main printfcmd
#include "bltin/bltin.h"
-#include "memalloc.h"
-#else
-#define warnx1(a, b, c) warnx(a)
-#define warnx2(a, b, c) warnx(a, b)
-#define warnx3(a, b, c) warnx(a, b, c)
+#include "options.h"
#endif
-#ifndef BUILTIN
-#include <locale.h>
-#endif
-
-#define PF(f, func) do { \
- char *b = NULL; \
- if (havewidth) \
- if (haveprec) \
+#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); \
- } \
+ 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 *doformat(char *, int *);
+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 getnum(intmax_t *, uintmax_t *, int);
static const char
*getstr(void);
-static char *mknum(char *, int);
+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(int argc, char *argv[])
-#else
main(int argc, char *argv[])
-#endif
{
size_t len;
- int ch, chopped, end, rval;
+ int end, rval;
char *format, *fmt, *start;
+#ifndef SHELL
+ int ch;
-#ifndef BUILTIN
- (void) setlocale(LC_NUMERIC, "");
+ (void) setlocale(LC_ALL, "");
#endif
+
+#ifdef SHELL
+ nextopt("");
+ argc -= argptr - argv;
+ argv = argptr;
+#else
while ((ch = getopt(argc, argv, "")) != -1)
switch (ch) {
case '?':
}
argc -= optind;
argv += optind;
+#endif
if (argc < 1) {
usage();
return (1);
}
+#ifdef SHELL
+ INTOFF;
+#endif
/*
* Basic algorithm is to scan the format string for conversion
* specifications -- once one is found, find out if the field
* up the format string.
*/
fmt = format = *argv;
- chopped = escape(fmt, 1, &len); /* backslash interpretation */
+ escape(fmt, 1, &len); /* backslash interpretation */
rval = end = 0;
gargv = ++argv;
+
for (;;) {
+ maxargv = gargv;
+
+ myargv = gargv;
+ for (myargc = 0; gargv[myargc]; myargc++)
+ /* nop */;
start = fmt;
while (fmt < format + len) {
if (fmt[0] == '%') {
putchar('%');
fmt += 2;
} else {
- fmt = doformat(fmt, &rval);
- if (fmt == NULL)
- return (1);
+ fmt = printf_doformat(fmt, &rval);
+ if (fmt == NULL || fmt == end_fmt) {
+#ifdef SHELL
+ INTON;
+#endif
+ return (fmt == NULL ? 1 : rval);
+ }
end = 0;
}
start = fmt;
} else
fmt++;
+ if (gargv > maxargv)
+ maxargv = gargv;
}
+ gargv = maxargv;
if (end == 1) {
- warnx1("missing format character", NULL, NULL);
+ warnx("missing format character");
+#ifdef SHELL
+ INTON;
+#endif
return (1);
}
fwrite(start, 1, fmt - start, stdout);
- if (chopped || !*gargv)
+ if (!*gargv) {
+#ifdef SHELL
+ INTON;
+#endif
return (rval);
+ }
/* Restart at the beginning of the format string. */
fmt = format;
end = 1;
static char *
-doformat(char *start, int *rval)
+printf_doformat(char *fmt, int *rval)
{
static const char skip1[] = "#'-+ 0";
- static const char skip2[] = "0123456789";
- char *fmt;
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 {
+ gargv = &myargv[myargc];
+ }
+ if (gargv > maxargv)
+ maxargv = gargv;
+ fmt += l + 1;
+
+ /* save format argument */
+ fargv = gargv;
+ } else {
+ fargv = NULL;
+ }
- fmt = start + 1;
/* skip to field width */
- fmt += strspn(fmt, skip1);
+ while (*fmt && strchr(skip1, *fmt) != NULL) {
+ *dptr++ = *fmt++;
+ *dptr = 0;
+ }
+
if (*fmt == '*') {
+
+ 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 (getint(&fieldwidth))
return (NULL);
+ if (gargv > maxargv)
+ maxargv = gargv;
havewidth = 1;
- ++fmt;
+
+ *dptr++ = '*';
+ *dptr = 0;
} else {
havewidth = 0;
/* skip to possible '.', get following precision */
- fmt += strspn(fmt, skip2);
+ while (isdigit(*fmt)) {
+ *dptr++ = *fmt++;
+ *dptr = 0;
+ }
}
+
if (*fmt == '.') {
/* precision present? */
- ++fmt;
+ fmt++;
+ *dptr++ = '.';
+
if (*fmt == '*') {
+
+ 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 (getint(&precision))
return (NULL);
+ if (gargv > maxargv)
+ maxargv = gargv;
haveprec = 1;
- ++fmt;
+ *dptr++ = '*';
+ *dptr = 0;
} else {
haveprec = 0;
/* skip to conversion char */
- fmt += strspn(fmt, skip2);
+ while (isdigit(*fmt)) {
+ *dptr++ = *fmt++;
+ *dptr = 0;
+ }
}
} else
haveprec = 0;
if (!*fmt) {
- warnx1("missing format character", NULL, NULL);
+ warnx("missing format character");
return (NULL);
}
+ *dptr++ = *fmt;
+ *dptr = 0;
/*
* Look for a length modifier. POSIX doesn't have these, so
mod_ldbl = 1;
fmt++;
if (!strchr("aAeEfFgG", *fmt)) {
- warnx2("bad modifier L for %%%c", *fmt, NULL);
+ warnx("bad modifier L for %%%c", *fmt);
return (NULL);
}
} else {
mod_ldbl = 0;
}
+ /* save the current arg offset, and set to the format arg */
+ if (fargv != NULL) {
+ gargv = fargv;
+ }
+
convch = *fmt;
nextch = *++fmt;
+
*fmt = '\0';
switch (convch) {
case 'b': {
char *p;
int getout;
-#ifdef SHELL
- p = savestr(getstr());
-#else
p = strdup(getstr());
-#endif
if (p == NULL) {
- warnx2("%s", strerror(ENOMEM), NULL);
+ warnx("%s", strerror(ENOMEM));
return (NULL);
}
getout = escape(p, 0, &len);
- *(fmt - 1) = 's';
- PF(start, p);
- *(fmt - 1) = 'b';
-#ifdef SHELL
- ckfree(p);
-#else
+ fputs(p, stdout);
free(p);
-#endif
if (getout)
- return (fmt);
+ return (end_fmt);
break;
}
case 'c': {
break;
}
default:
- warnx2("illegal format character %c", convch, NULL);
+ warnx("illegal format character %c", convch);
return (NULL);
}
*fmt = nextch;
+ /* return the gargv to the next element */
return (fmt);
}
static char *
-mknum(char *str, int ch)
+mknum(char *str, char ch)
{
static char *copy;
static size_t copy_size;
len = strlen(str) + 2;
if (len > copy_size) {
newlen = ((len + 1023) >> 10) << 10;
-#ifdef SHELL
- if ((newcopy = ckrealloc(copy, newlen)) == NULL)
-#else
- if ((newcopy = realloc(copy, newlen)) == NULL)
-#endif
- {
- warnx2("%s", strerror(ENOMEM), NULL);
+ if ((newcopy = realloc(copy, newlen)) == NULL) {
+ warnx("%s", strerror(ENOMEM));
return (NULL);
}
copy = newcopy;
static int
escape(char *fmt, int percent, size_t *len)
{
- char *save, *store;
- int value, c;
+ char *save, *store, c;
+ int value;
- for (save = store = fmt; (c = *fmt); ++fmt, ++store) {
+ for (save = store = fmt; ((c = *fmt) != 0); ++fmt, ++store) {
if (c != '\\') {
*store = c;
continue;
*store = '\b';
break;
case 'c':
- *store = '\0';
- *len = store - save;
- return (1);
+ if (!percent) {
+ *store = '\0';
+ *len = store - save;
+ return (1);
+ }
+ *store = 'c';
+ break;
case 'f': /* form-feed */
*store = '\f';
break;
/* octal constant */
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
- for (c = *fmt == '0' ? 4 : 3, value = 0;
+ c = (!percent && *fmt == '0') ? 4 : 3;
+ for (value = 0;
c-- && *fmt >= '0' && *fmt <= '7'; ++fmt) {
value <<= 3;
value += *fmt - '0';
*store++ = '%';
*store = '%';
} else
- *store = value;
+ *store = (char)value;
break;
default:
*store = *fmt;
return (1);
rval = 0;
if (val < INT_MIN || val > INT_MAX) {
- warnx3("%s: %s", *gargv, strerror(ERANGE));
+ warnx("%s: %s", *gargv, strerror(ERANGE));
rval = 1;
}
*ip = (int)val;
int rval;
if (!*gargv) {
- *ip = 0;
+ *ip = *uip = 0;
return (0);
}
if (**gargv == '"' || **gargv == '\'') {
else
*uip = strtoumax(*gargv, &ep, 0);
if (ep == *gargv) {
- warnx2("%s: expected numeric value", *gargv, NULL);
+ warnx("%s: expected numeric value", *gargv);
rval = 1;
}
else if (*ep != '\0') {
- warnx2("%s: not completely converted", *gargv, NULL);
+ warnx("%s: not completely converted", *gargv);
rval = 1;
}
if (errno == ERANGE) {
- warnx3("%s: %s", *gargv, strerror(ERANGE));
+ warnx("%s: %s", *gargv, strerror(ERANGE));
rval = 1;
}
++gargv;
else
*dp = strtod(*gargv, &ep);
if (ep == *gargv) {
- warnx2("%s: expected numeric value", *gargv, NULL);
+ warnx("%s: expected numeric value", *gargv);
rval = 1;
} else if (*ep != '\0') {
- warnx2("%s: not completely converted", *gargv, NULL);
+ warnx("%s: not completely converted", *gargv);
rval = 1;
}
if (errno == ERANGE) {
- warnx3("%s: %s", *gargv, strerror(ERANGE));
+ warnx("%s: %s", *gargv, strerror(ERANGE));
rval = 1;
}
++gargv;
asciicode(void)
{
int ch;
-
- ch = **gargv;
- if (ch == '\'' || ch == '"')
- ch = (*gargv)[1];
+ 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);
}