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