]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/printf/printf.c
MFC r337458, r337618:
[FreeBSD/FreeBSD.git] / usr.bin / printf / printf.c
1 /*-
2  * Copyright 2018 Staysail Systems, Inc. <info@staysail.tech>
3  * Copyright 2014 Garrett D'Amore <garrett@damore.org>
4  * Copyright 2010 Nexenta Systems, Inc.  All rights reserved.
5  * Copyright (c) 1989, 1993
6  *      The Regents of the University of California.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 4. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 /*
33  * Important: This file is used both as a standalone program /usr/bin/printf
34  * and as a builtin for /bin/sh (#define SHELL).
35  */
36
37 #ifndef SHELL
38 #ifndef lint
39 static char const copyright[] =
40 "@(#) Copyright (c) 1989, 1993\n\
41         The Regents of the University of California.  All rights reserved.\n";
42 #endif /* not lint */
43 #endif
44
45 #ifndef lint
46 #if 0
47 static char const sccsid[] = "@(#)printf.c      8.1 (Berkeley) 7/20/93";
48 #endif
49 static const char rcsid[] =
50   "$FreeBSD$";
51 #endif /* not lint */
52
53 #include <sys/types.h>
54
55 #include <ctype.h>
56 #include <err.h>
57 #include <errno.h>
58 #include <inttypes.h>
59 #include <limits.h>
60 #include <locale.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <unistd.h>
65 #include <wchar.h>
66
67 #ifdef SHELL
68 #define main printfcmd
69 #include "bltin/bltin.h"
70 #include "options.h"
71 #endif
72
73 #define PF(f, func) do {                                                \
74         char *b = NULL;                                                 \
75         if (havewidth)                                                  \
76                 if (haveprec)                                           \
77                         (void)asprintf(&b, f, fieldwidth, precision, func); \
78                 else                                                    \
79                         (void)asprintf(&b, f, fieldwidth, func);        \
80         else if (haveprec)                                              \
81                 (void)asprintf(&b, f, precision, func);                 \
82         else                                                            \
83                 (void)asprintf(&b, f, func);                            \
84         if (b) {                                                        \
85                 (void)fputs(b, stdout);                                 \
86                 free(b);                                                \
87         }                                                               \
88 } while (0)
89
90 static int       asciicode(void);
91 static char     *printf_doformat(char *, int *);
92 static int       escape(char *, int, size_t *);
93 static int       getchr(void);
94 static int       getfloating(long double *, int);
95 static int       getint(int *);
96 static int       getnum(intmax_t *, uintmax_t *, int);
97 static const char
98                 *getstr(void);
99 static char     *mknum(char *, char);
100 static void      usage(void);
101
102 static const char digits[] = "0123456789";
103
104 static char end_fmt[1];
105
106 static int  myargc;
107 static char **myargv;
108 static char **gargv;
109 static char **maxargv;
110
111 int
112 main(int argc, char *argv[])
113 {
114         size_t len;
115         int end, rval;
116         char *format, *fmt, *start;
117 #ifndef SHELL
118         int ch;
119
120         (void) setlocale(LC_ALL, "");
121 #endif
122
123 #ifdef SHELL
124         nextopt("");
125         argc -= argptr - argv;
126         argv = argptr;
127 #else
128         while ((ch = getopt(argc, argv, "")) != -1)
129                 switch (ch) {
130                 case '?':
131                 default:
132                         usage();
133                         return (1);
134                 }
135         argc -= optind;
136         argv += optind;
137 #endif
138
139         if (argc < 1) {
140                 usage();
141                 return (1);
142         }
143
144 #ifdef SHELL
145         INTOFF;
146 #endif
147         /*
148          * Basic algorithm is to scan the format string for conversion
149          * specifications -- once one is found, find out if the field
150          * width or precision is a '*'; if it is, gather up value.  Note,
151          * format strings are reused as necessary to use up the provided
152          * arguments, arguments of zero/null string are provided to use
153          * up the format string.
154          */
155         fmt = format = *argv;
156         escape(fmt, 1, &len);           /* backslash interpretation */
157         rval = end = 0;
158         gargv = ++argv;
159
160         for (;;) {
161                 maxargv = gargv;
162
163                 myargv = gargv;
164                 for (myargc = 0; gargv[myargc]; myargc++)
165                         /* nop */;
166                 start = fmt;
167                 while (fmt < format + len) {
168                         if (fmt[0] == '%') {
169                                 fwrite(start, 1, fmt - start, stdout);
170                                 if (fmt[1] == '%') {
171                                         /* %% prints a % */
172                                         putchar('%');
173                                         fmt += 2;
174                                 } else {
175                                         fmt = printf_doformat(fmt, &rval);
176                                         if (fmt == NULL || fmt == end_fmt) {
177 #ifdef SHELL
178                                                 INTON;
179 #endif
180                                                 return (fmt == NULL ? 1 : rval);
181                                         }
182                                         end = 0;
183                                 }
184                                 start = fmt;
185                         } else
186                                 fmt++;
187                         if (gargv > maxargv)
188                                 maxargv = gargv;
189                 }
190                 gargv = maxargv;
191
192                 if (end == 1) {
193                         warnx("missing format character");
194 #ifdef SHELL
195                         INTON;
196 #endif
197                         return (1);
198                 }
199                 fwrite(start, 1, fmt - start, stdout);
200                 if (!*gargv) {
201 #ifdef SHELL
202                         INTON;
203 #endif
204                         return (rval);
205                 }
206                 /* Restart at the beginning of the format string. */
207                 fmt = format;
208                 end = 1;
209         }
210         /* NOTREACHED */
211 }
212
213
214 static char *
215 printf_doformat(char *fmt, int *rval)
216 {
217         static const char skip1[] = "#'-+ 0";
218         int fieldwidth, haveprec, havewidth, mod_ldbl, precision;
219         char convch, nextch;
220         char start[strlen(fmt) + 1];
221         char **fargv;
222         char *dptr;
223         int l;
224
225         dptr = start;
226         *dptr++ = '%';
227         *dptr = 0;
228
229         fmt++;
230
231         /* look for "n$" field index specifier */
232         l = strspn(fmt, digits);
233         if ((l > 0) && (fmt[l] == '$')) {
234                 int idx = atoi(fmt);
235                 if (idx <= myargc) {
236                         gargv = &myargv[idx - 1];
237                 } else {
238                         gargv = &myargv[myargc];
239                 }
240                 if (gargv > maxargv)
241                         maxargv = gargv;
242                 fmt += l + 1;
243
244                 /* save format argument */
245                 fargv = gargv;
246         } else {
247                 fargv = NULL;
248         }
249
250         /* skip to field width */
251         while (*fmt && strchr(skip1, *fmt) != NULL) {
252                 *dptr++ = *fmt++;
253                 *dptr = 0;
254         }
255
256         if (*fmt == '*') {
257
258                 fmt++;
259                 l = strspn(fmt, digits);
260                 if ((l > 0) && (fmt[l] == '$')) {
261                         int idx = atoi(fmt);
262                         if (fargv == NULL) {
263                                 warnx("incomplete use of n$");
264                                 return (NULL);
265                         }
266                         if (idx <= myargc) {
267                                 gargv = &myargv[idx - 1];
268                         } else {
269                                 gargv = &myargv[myargc];
270                         }
271                         fmt += l + 1;
272                 } else if (fargv != NULL) {
273                         warnx("incomplete use of n$");
274                         return (NULL);
275                 }
276
277                 if (getint(&fieldwidth))
278                         return (NULL);
279                 if (gargv > maxargv)
280                         maxargv = gargv;
281                 havewidth = 1;
282
283                 *dptr++ = '*';
284                 *dptr = 0;
285         } else {
286                 havewidth = 0;
287
288                 /* skip to possible '.', get following precision */
289                 while (isdigit(*fmt)) {
290                         *dptr++ = *fmt++;
291                         *dptr = 0;
292                 }
293         }
294
295         if (*fmt == '.') {
296                 /* precision present? */
297                 fmt++;
298                 *dptr++ = '.';
299
300                 if (*fmt == '*') {
301
302                         fmt++;
303                         l = strspn(fmt, digits);
304                         if ((l > 0) && (fmt[l] == '$')) {
305                                 int idx = atoi(fmt);
306                                 if (fargv == NULL) {
307                                         warnx("incomplete use of n$");
308                                         return (NULL);
309                                 }
310                                 if (idx <= myargc) {
311                                         gargv = &myargv[idx - 1];
312                                 } else {
313                                         gargv = &myargv[myargc];
314                                 }
315                                 fmt += l + 1;
316                         } else if (fargv != NULL) {
317                                 warnx("incomplete use of n$");
318                                 return (NULL);
319                         }
320
321                         if (getint(&precision))
322                                 return (NULL);
323                         if (gargv > maxargv)
324                                 maxargv = gargv;
325                         haveprec = 1;
326                         *dptr++ = '*';
327                         *dptr = 0;
328                 } else {
329                         haveprec = 0;
330
331                         /* skip to conversion char */
332                         while (isdigit(*fmt)) {
333                                 *dptr++ = *fmt++;
334                                 *dptr = 0;
335                         }
336                 }
337         } else
338                 haveprec = 0;
339         if (!*fmt) {
340                 warnx("missing format character");
341                 return (NULL);
342         }
343         *dptr++ = *fmt;
344         *dptr = 0;
345
346         /*
347          * Look for a length modifier.  POSIX doesn't have these, so
348          * we only support them for floating-point conversions, which
349          * are extensions.  This is useful because the L modifier can
350          * be used to gain extra range and precision, while omitting
351          * it is more likely to produce consistent results on different
352          * architectures.  This is not so important for integers
353          * because overflow is the only bad thing that can happen to
354          * them, but consider the command  printf %a 1.1
355          */
356         if (*fmt == 'L') {
357                 mod_ldbl = 1;
358                 fmt++;
359                 if (!strchr("aAeEfFgG", *fmt)) {
360                         warnx("bad modifier L for %%%c", *fmt);
361                         return (NULL);
362                 }
363         } else {
364                 mod_ldbl = 0;
365         }
366
367         /* save the current arg offset, and set to the format arg */
368         if (fargv != NULL) {
369                 gargv = fargv;
370         }
371
372         convch = *fmt;
373         nextch = *++fmt;
374
375         *fmt = '\0';
376         switch (convch) {
377         case 'b': {
378                 size_t len;
379                 char *p;
380                 int getout;
381
382                 /* Convert "b" to "s" for output. */
383                 start[strlen(start) - 1] = 's';
384                 if ((p = strdup(getstr())) == NULL) {
385                         warnx("%s", strerror(ENOMEM));
386                         return (NULL);
387                 }
388                 getout = escape(p, 0, &len);
389                 PF(start, p);
390                 /* Restore format for next loop. */
391
392                 free(p);
393                 if (getout)
394                         return (end_fmt);
395                 break;
396         }
397         case 'c': {
398                 char p;
399
400                 p = getchr();
401                 PF(start, p);
402                 break;
403         }
404         case 's': {
405                 const char *p;
406
407                 p = getstr();
408                 PF(start, p);
409                 break;
410         }
411         case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': {
412                 char *f;
413                 intmax_t val;
414                 uintmax_t uval;
415                 int signedconv;
416
417                 signedconv = (convch == 'd' || convch == 'i');
418                 if ((f = mknum(start, convch)) == NULL)
419                         return (NULL);
420                 if (getnum(&val, &uval, signedconv))
421                         *rval = 1;
422                 if (signedconv)
423                         PF(f, val);
424                 else
425                         PF(f, uval);
426                 break;
427         }
428         case 'e': case 'E':
429         case 'f': case 'F':
430         case 'g': case 'G':
431         case 'a': case 'A': {
432                 long double p;
433
434                 if (getfloating(&p, mod_ldbl))
435                         *rval = 1;
436                 if (mod_ldbl)
437                         PF(start, p);
438                 else
439                         PF(start, (double)p);
440                 break;
441         }
442         default:
443                 warnx("illegal format character %c", convch);
444                 return (NULL);
445         }
446         *fmt = nextch;
447         /* return the gargv to the next element */
448         return (fmt);
449 }
450
451 static char *
452 mknum(char *str, char ch)
453 {
454         static char *copy;
455         static size_t copy_size;
456         char *newcopy;
457         size_t len, newlen;
458
459         len = strlen(str) + 2;
460         if (len > copy_size) {
461                 newlen = ((len + 1023) >> 10) << 10;
462                 if ((newcopy = realloc(copy, newlen)) == NULL) {
463                         warnx("%s", strerror(ENOMEM));
464                         return (NULL);
465                 }
466                 copy = newcopy;
467                 copy_size = newlen;
468         }
469
470         memmove(copy, str, len - 3);
471         copy[len - 3] = 'j';
472         copy[len - 2] = ch;
473         copy[len - 1] = '\0';
474         return (copy);
475 }
476
477 static int
478 escape(char *fmt, int percent, size_t *len)
479 {
480         char *save, *store, c;
481         int value;
482
483         for (save = store = fmt; ((c = *fmt) != 0); ++fmt, ++store) {
484                 if (c != '\\') {
485                         *store = c;
486                         continue;
487                 }
488                 switch (*++fmt) {
489                 case '\0':              /* EOS, user error */
490                         *store = '\\';
491                         *++store = '\0';
492                         *len = store - save;
493                         return (0);
494                 case '\\':              /* backslash */
495                 case '\'':              /* single quote */
496                         *store = *fmt;
497                         break;
498                 case 'a':               /* bell/alert */
499                         *store = '\a';
500                         break;
501                 case 'b':               /* backspace */
502                         *store = '\b';
503                         break;
504                 case 'c':
505                         if (!percent) {
506                                 *store = '\0';
507                                 *len = store - save;
508                                 return (1);
509                         }
510                         *store = 'c';
511                         break;
512                 case 'f':               /* form-feed */
513                         *store = '\f';
514                         break;
515                 case 'n':               /* newline */
516                         *store = '\n';
517                         break;
518                 case 'r':               /* carriage-return */
519                         *store = '\r';
520                         break;
521                 case 't':               /* horizontal tab */
522                         *store = '\t';
523                         break;
524                 case 'v':               /* vertical tab */
525                         *store = '\v';
526                         break;
527                                         /* octal constant */
528                 case '0': case '1': case '2': case '3':
529                 case '4': case '5': case '6': case '7':
530                         c = (!percent && *fmt == '0') ? 4 : 3;
531                         for (value = 0;
532                             c-- && *fmt >= '0' && *fmt <= '7'; ++fmt) {
533                                 value <<= 3;
534                                 value += *fmt - '0';
535                         }
536                         --fmt;
537                         if (percent && value == '%') {
538                                 *store++ = '%';
539                                 *store = '%';
540                         } else
541                                 *store = (char)value;
542                         break;
543                 default:
544                         *store = *fmt;
545                         break;
546                 }
547         }
548         *store = '\0';
549         *len = store - save;
550         return (0);
551 }
552
553 static int
554 getchr(void)
555 {
556         if (!*gargv)
557                 return ('\0');
558         return ((int)**gargv++);
559 }
560
561 static const char *
562 getstr(void)
563 {
564         if (!*gargv)
565                 return ("");
566         return (*gargv++);
567 }
568
569 static int
570 getint(int *ip)
571 {
572         intmax_t val;
573         uintmax_t uval;
574         int rval;
575
576         if (getnum(&val, &uval, 1))
577                 return (1);
578         rval = 0;
579         if (val < INT_MIN || val > INT_MAX) {
580                 warnx("%s: %s", *gargv, strerror(ERANGE));
581                 rval = 1;
582         }
583         *ip = (int)val;
584         return (rval);
585 }
586
587 static int
588 getnum(intmax_t *ip, uintmax_t *uip, int signedconv)
589 {
590         char *ep;
591         int rval;
592
593         if (!*gargv) {
594                 *ip = *uip = 0;
595                 return (0);
596         }
597         if (**gargv == '"' || **gargv == '\'') {
598                 if (signedconv)
599                         *ip = asciicode();
600                 else
601                         *uip = asciicode();
602                 return (0);
603         }
604         rval = 0;
605         errno = 0;
606         if (signedconv)
607                 *ip = strtoimax(*gargv, &ep, 0);
608         else
609                 *uip = strtoumax(*gargv, &ep, 0);
610         if (ep == *gargv) {
611                 warnx("%s: expected numeric value", *gargv);
612                 rval = 1;
613         }
614         else if (*ep != '\0') {
615                 warnx("%s: not completely converted", *gargv);
616                 rval = 1;
617         }
618         if (errno == ERANGE) {
619                 warnx("%s: %s", *gargv, strerror(ERANGE));
620                 rval = 1;
621         }
622         ++gargv;
623         return (rval);
624 }
625
626 static int
627 getfloating(long double *dp, int mod_ldbl)
628 {
629         char *ep;
630         int rval;
631
632         if (!*gargv) {
633                 *dp = 0.0;
634                 return (0);
635         }
636         if (**gargv == '"' || **gargv == '\'') {
637                 *dp = asciicode();
638                 return (0);
639         }
640         rval = 0;
641         errno = 0;
642         if (mod_ldbl)
643                 *dp = strtold(*gargv, &ep);
644         else
645                 *dp = strtod(*gargv, &ep);
646         if (ep == *gargv) {
647                 warnx("%s: expected numeric value", *gargv);
648                 rval = 1;
649         } else if (*ep != '\0') {
650                 warnx("%s: not completely converted", *gargv);
651                 rval = 1;
652         }
653         if (errno == ERANGE) {
654                 warnx("%s: %s", *gargv, strerror(ERANGE));
655                 rval = 1;
656         }
657         ++gargv;
658         return (rval);
659 }
660
661 static int
662 asciicode(void)
663 {
664         int ch;
665         wchar_t wch;
666         mbstate_t mbs;
667
668         ch = (unsigned char)**gargv;
669         if (ch == '\'' || ch == '"') {
670                 memset(&mbs, 0, sizeof(mbs));
671                 switch (mbrtowc(&wch, *gargv + 1, MB_LEN_MAX, &mbs)) {
672                 case (size_t)-2:
673                 case (size_t)-1:
674                         wch = (unsigned char)gargv[0][1];
675                         break;
676                 case 0:
677                         wch = 0;
678                         break;
679                 }
680                 ch = wch;
681         }
682         ++gargv;
683         return (ch);
684 }
685
686 static void
687 usage(void)
688 {
689         (void)fprintf(stderr, "usage: printf format [arguments ...]\n");
690 }