Print this page
4818 printf(1) should support n$ width and precision specifiers
4854 printf(1) doesn't support %b and \c properly
Reviewed by: Keith Wesolowski <keith.wesolowski@joyent.com>
Approved by: TBD

@@ -1,6 +1,7 @@
 /*
+ * 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.
  *
  * Redistribution and use in source and binary forms, with or without

@@ -36,10 +37,12 @@
 #include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <alloca.h>
+#include <ctype.h>
 #include <locale.h>
 #include <note.h>
 
 #define warnx1(a, b, c)         warnx(a)
 #define warnx2(a, b, c)         warnx(a, b)

@@ -49,15 +52,10 @@
 
 #define _(x)    gettext(x)
 
 #define PF(f, func) do {                                                \
         char *b = NULL;                                                 \
-        int dollar = 0;                                                 \
-        if (*f == '$')  {                                               \
-                dollar++;                                               \
-                *f = '%';                                               \
-        }                                                               \
         if (havewidth)                                                  \
                 if (haveprec)                                           \
                         (void) asprintf(&b, f, fieldwidth, precision, func); \
                 else                                                    \
                         (void) asprintf(&b, f, fieldwidth, func);       \

@@ -67,12 +65,10 @@
                 (void) asprintf(&b, f, func);                           \
         if (b) {                                                        \
                 (void) fputs(b, stdout);                                \
                 free(b);                                                \
         }                                                               \
-        if (dollar)                                                     \
-                *f = '$';                                               \
 _NOTE(CONSTCOND) } while (0)
 
 static int       asciicode(void);
 static char     *doformat(char *, int *);
 static int       escape(char *, int, size_t *);

@@ -83,19 +79,22 @@
 static const char
                 *getstr(void);
 static char     *mknum(char *, char);
 static void      usage(void);
 
+static const char digits[] = "0123456789";
+
 static int  myargc;
 static char **myargv;
 static char **gargv;
+static char **maxargv;
 
 int
 main(int argc, char *argv[])
 {
         size_t len;
-        int chopped, end, rval;
+        int end, rval;
         char *format, *fmt, *start;
 
         (void) setlocale(LC_ALL, "");
 
         argv++;

@@ -123,16 +122,16 @@
          * 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;
-        chopped = escape(fmt, 1, &len);         /* backslash interpretation */
+        (void) escape(fmt, 1, &len);    /* backslash interpretation */
         rval = end = 0;
         gargv = ++argv;
 
         for (;;) {
-                char **maxargv = gargv;
+                maxargv = gargv;
 
                 myargv = gargv;
                 for (myargc = 0; gargv[myargc]; myargc++)
                         /* nop */;
                 start = fmt;

@@ -161,11 +160,11 @@
                 if (end == 1) {
                         warnx1(_("missing format character"), NULL, NULL);
                         return (1);
                 }
                 (void) fwrite(start, 1, PTRDIFF(fmt, start), stdout);
-                if (chopped || !*gargv)
+                if (!*gargv)
                         return (rval);
                 /* Restart at the beginning of the format string. */
                 fmt = format;
                 end = 1;
         }

@@ -172,68 +171,147 @@
         /* NOTREACHED */
 }
 
 
 static char *
-doformat(char *start, int *rval)
+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;
+        char **fargv;
+        char *dptr;
+        int l;
 
-        fmt = start + 1;
+        start = alloca(strlen(fmt) + 1);
 
+        dptr = start;
+        *dptr++ = '%';
+        *dptr = 0;
+
+        fmt++;
+
         /* look for "n$" field index specifier */
-        fmt += strspn(fmt, skip2);
-        if ((*fmt == '$') && (fmt != (start + 1))) {
-                int idx = atoi(start + 1);
+        l = strspn(fmt, digits);
+        if ((l > 0) && (fmt[l] == '$')) {
+                int idx = atoi(fmt);
                 if (idx <= myargc) {
                         gargv = &myargv[idx - 1];
                 } else {
                         gargv = &myargv[myargc];
                 }
-                start = fmt;
-                fmt++;
+                if (gargv > maxargv) {
+                        maxargv = gargv;
+                }
+                fmt += l + 1;
+
+                /* save format argument */
+                fargv = gargv;
         } else {
-                fmt = start + 1;
+                fargv = NULL;
         }
 
         /* 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) {
+                                warnx1(_("incomplete use of n$"), NULL, NULL);
+                                return (NULL);
+                        }
+                        if (idx <= myargc) {
+                                gargv = &myargv[idx - 1];
+                        } else {
+                                gargv = &myargv[myargc];
+                        }
+                        fmt += l + 1;
+                } else if (fargv != NULL) {
+                        warnx1(_("incomplete use of n$"), NULL, NULL);
+                        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) {
+                                        warnx1(_("incomplete use of n$"),
+                                            NULL, NULL);
+                                        return (NULL);
+                                }
+                                if (idx <= myargc) {
+                                        gargv = &myargv[idx - 1];
+                                } else {
+                                        gargv = &myargv[myargc];
+                                }
+                                fmt += l + 1;
+                        } else if (fargv != NULL) {
+                                warnx1(_("incomplete use of n$"), NULL, NULL);
+                                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);
                 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

@@ -252,12 +330,18 @@
                 }
         } 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': {
                 size_t len;
                 char *p;

@@ -267,17 +351,15 @@
                 if (p == NULL) {
                         warnx2("%s", strerror(ENOMEM), NULL);
                         return (NULL);
                 }
                 getout = escape(p, 0, &len);
-                *(fmt - 1) = 's';
-                PF(start, p);
-                *(fmt - 1) = 'b';
+                (void) fputs(p, stdout);
                 free(p);
 
                 if (getout)
-                        return (fmt);
+                        exit(*rval);
                 break;
         }
         case 'c': {
                 char p;
 

@@ -326,10 +408,12 @@
         default:
                 warnx2(_("illegal format character %c"), convch, NULL);
                 return (NULL);
         }
         *fmt = nextch;
+
+        /* return the gargv to the next element */
         return (fmt);
 }
 
 static char *
 mknum(char *str, char ch)

@@ -383,13 +467,17 @@
                         break;
                 case 'b':               /* backspace */
                         *store = '\b';
                         break;
                 case 'c':
+                        if (!percent) {
                         *store = '\0';
                         *len = PTRDIFF(store, save);
                         return (1);
+                        }
+                        *store = 'c';
+                        break;
                 case 'f':               /* form-feed */
                         *store = '\f';
                         break;
                 case 'n':               /* newline */
                         *store = '\n';