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