]> git.saurik.com Git - apple/shell_cmds.git/blame - printf/printf.c
shell_cmds-216.40.4.tar.gz
[apple/shell_cmds.git] / printf / printf.c
CommitLineData
71aad674 1/*-
47d9aef8
A
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright 2018 Staysail Systems, Inc. <info@staysail.tech>
71aad674
A
5 * Copyright 2014 Garrett D'Amore <garrett@damore.org>
6 * Copyright 2010 Nexenta Systems, Inc. All rights reserved.
44bd5ea7
A
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.
47d9aef8 18 * 3. Neither the name of the University nor the names of its contributors
44bd5ea7
A
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 */
71aad674
A
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 */
44bd5ea7 38
71aad674 39#ifndef SHELL
ddb4a88b
A
40#ifndef lint
41static 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 */
44bd5ea7
A
45#endif
46
47#ifndef lint
48#if 0
ddb4a88b 49static char const sccsid[] = "@(#)printf.c 8.1 (Berkeley) 7/20/93";
44bd5ea7 50#endif
ddb4a88b 51static const char rcsid[] =
47d9aef8 52 "$FreeBSD$";
44bd5ea7
A
53#endif /* not lint */
54
55#include <sys/types.h>
56
71aad674 57#include <ctype.h>
44bd5ea7
A
58#include <err.h>
59#include <errno.h>
ddb4a88b 60#include <inttypes.h>
44bd5ea7 61#include <limits.h>
71aad674 62#include <locale.h>
44bd5ea7
A
63#include <stdio.h>
64#include <stdlib.h>
44bd5ea7
A
65#include <string.h>
66#include <unistd.h>
71aad674 67#include <wchar.h>
44bd5ea7 68
44bd5ea7 69#ifdef SHELL
71aad674 70#define main printfcmd
44bd5ea7 71#include "bltin/bltin.h"
71aad674 72#include "options.h"
44bd5ea7 73#endif
44bd5ea7 74
71aad674 75#define PF(f, func) do { \
71aad674
A
76 if (havewidth) \
77 if (haveprec) \
47d9aef8 78 (void)printf(f, fieldwidth, precision, func); \
71aad674 79 else \
47d9aef8 80 (void)printf(f, fieldwidth, func); \
71aad674 81 else if (haveprec) \
47d9aef8 82 (void)printf(f, precision, func); \
71aad674 83 else \
47d9aef8 84 (void)printf(f, func); \
ddb4a88b
A
85} while (0)
86
87static int asciicode(void);
71aad674 88static char *printf_doformat(char *, int *);
ddb4a88b
A
89static int escape(char *, int, size_t *);
90static int getchr(void);
91static int getfloating(long double *, int);
92static int getint(int *);
93static int getnum(intmax_t *, uintmax_t *, int);
94static const char
95 *getstr(void);
71aad674 96static char *mknum(char *, char);
ddb4a88b
A
97static void usage(void);
98
71aad674
A
99static const char digits[] = "0123456789";
100
101static char end_fmt[1];
102
103static int myargc;
104static char **myargv;
ddb4a88b 105static char **gargv;
71aad674 106static char **maxargv;
44bd5ea7
A
107
108int
ddb4a88b 109main(int argc, char *argv[])
44bd5ea7 110{
ddb4a88b 111 size_t len;
71aad674 112 int end, rval;
ddb4a88b 113 char *format, *fmt, *start;
71aad674
A
114#ifndef SHELL
115 int ch;
44bd5ea7 116
71aad674 117 (void) setlocale(LC_ALL, "");
44bd5ea7 118#endif
71aad674
A
119
120#ifdef SHELL
121 nextopt("");
122 argc -= argptr - argv;
123 argv = argptr;
124#else
ddb4a88b 125 while ((ch = getopt(argc, argv, "")) != -1)
44bd5ea7
A
126 switch (ch) {
127 case '?':
128 default:
129 usage();
130 return (1);
131 }
44bd5ea7
A
132 argc -= optind;
133 argv += optind;
71aad674 134#endif
44bd5ea7
A
135
136 if (argc < 1) {
137 usage();
138 return (1);
139 }
140
71aad674
A
141#ifdef SHELL
142 INTOFF;
143#endif
ddb4a88b
A
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;
71aad674 153 escape(fmt, 1, &len); /* backslash interpretation */
ddb4a88b 154 rval = end = 0;
44bd5ea7 155 gargv = ++argv;
71aad674 156
ddb4a88b 157 for (;;) {
71aad674
A
158 maxargv = gargv;
159
160 myargv = gargv;
161 for (myargc = 0; gargv[myargc]; myargc++)
162 /* nop */;
ddb4a88b
A
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 {
71aad674
A
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 }
ddb4a88b 179 end = 0;
44bd5ea7 180 }
ddb4a88b
A
181 start = fmt;
182 } else
183 fmt++;
71aad674
A
184 if (gargv > maxargv)
185 maxargv = gargv;
44bd5ea7 186 }
71aad674 187 gargv = maxargv;
44bd5ea7 188
ddb4a88b 189 if (end == 1) {
71aad674
A
190 warnx("missing format character");
191#ifdef SHELL
192 INTON;
193#endif
ddb4a88b
A
194 return (1);
195 }
196 fwrite(start, 1, fmt - start, stdout);
71aad674
A
197 if (!*gargv) {
198#ifdef SHELL
199 INTON;
200#endif
ddb4a88b 201 return (rval);
71aad674 202 }
ddb4a88b
A
203 /* Restart at the beginning of the format string. */
204 fmt = format;
205 end = 1;
206 }
207 /* NOTREACHED */
44bd5ea7
A
208}
209
210
ddb4a88b 211static char *
71aad674 212printf_doformat(char *fmt, int *rval)
44bd5ea7 213{
ddb4a88b 214 static const char skip1[] = "#'-+ 0";
ddb4a88b
A
215 int fieldwidth, haveprec, havewidth, mod_ldbl, precision;
216 char convch, nextch;
71aad674
A
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 }
ddb4a88b 246
ddb4a88b 247 /* skip to field width */
71aad674
A
248 while (*fmt && strchr(skip1, *fmt) != NULL) {
249 *dptr++ = *fmt++;
250 *dptr = 0;
251 }
252
ddb4a88b 253 if (*fmt == '*') {
71aad674
A
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
ddb4a88b
A
274 if (getint(&fieldwidth))
275 return (NULL);
71aad674
A
276 if (gargv > maxargv)
277 maxargv = gargv;
ddb4a88b 278 havewidth = 1;
71aad674
A
279
280 *dptr++ = '*';
281 *dptr = 0;
ddb4a88b
A
282 } else {
283 havewidth = 0;
284
285 /* skip to possible '.', get following precision */
71aad674
A
286 while (isdigit(*fmt)) {
287 *dptr++ = *fmt++;
288 *dptr = 0;
289 }
ddb4a88b 290 }
71aad674 291
ddb4a88b
A
292 if (*fmt == '.') {
293 /* precision present? */
71aad674
A
294 fmt++;
295 *dptr++ = '.';
296
ddb4a88b 297 if (*fmt == '*') {
71aad674
A
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
ddb4a88b
A
318 if (getint(&precision))
319 return (NULL);
71aad674
A
320 if (gargv > maxargv)
321 maxargv = gargv;
ddb4a88b 322 haveprec = 1;
71aad674
A
323 *dptr++ = '*';
324 *dptr = 0;
44bd5ea7 325 } else {
ddb4a88b
A
326 haveprec = 0;
327
328 /* skip to conversion char */
71aad674
A
329 while (isdigit(*fmt)) {
330 *dptr++ = *fmt++;
331 *dptr = 0;
332 }
44bd5ea7 333 }
ddb4a88b
A
334 } else
335 haveprec = 0;
336 if (!*fmt) {
71aad674 337 warnx("missing format character");
ddb4a88b 338 return (NULL);
44bd5ea7 339 }
71aad674
A
340 *dptr++ = *fmt;
341 *dptr = 0;
44bd5ea7 342
ddb4a88b
A
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)) {
71aad674 357 warnx("bad modifier L for %%%c", *fmt);
ddb4a88b 358 return (NULL);
44bd5ea7 359 }
ddb4a88b
A
360 } else {
361 mod_ldbl = 0;
362 }
44bd5ea7 363
71aad674
A
364 /* save the current arg offset, and set to the format arg */
365 if (fargv != NULL) {
366 gargv = fargv;
367 }
368
ddb4a88b
A
369 convch = *fmt;
370 nextch = *++fmt;
71aad674 371
ddb4a88b
A
372 *fmt = '\0';
373 switch (convch) {
374 case 'b': {
375 size_t len;
376 char *p;
377 int getout;
44bd5ea7 378
47d9aef8
A
379 /* Convert "b" to "s" for output. */
380 start[strlen(start) - 1] = 's';
381 if ((p = strdup(getstr())) == NULL) {
71aad674 382 warnx("%s", strerror(ENOMEM));
ddb4a88b
A
383 return (NULL);
384 }
385 getout = escape(p, 0, &len);
47d9aef8
A
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
ddb4a88b 398 free(p);
ddb4a88b 399 if (getout)
71aad674 400 return (end_fmt);
44bd5ea7 401 break;
ddb4a88b
A
402 }
403 case 'c': {
404 char p;
44bd5ea7 405
ddb4a88b 406 p = getchr();
47d9aef8
A
407 if (p != '\0')
408 PF(start, p);
44bd5ea7 409 break;
ddb4a88b
A
410 }
411 case 's': {
412 const char *p;
44bd5ea7 413
ddb4a88b
A
414 p = getstr();
415 PF(start, p);
44bd5ea7 416 break;
ddb4a88b
A
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);
44bd5ea7 433 break;
ddb4a88b
A
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);
44bd5ea7 447 break;
ddb4a88b 448 }
44bd5ea7 449 default:
71aad674 450 warnx("illegal format character %c", convch);
ddb4a88b 451 return (NULL);
44bd5ea7 452 }
ddb4a88b 453 *fmt = nextch;
71aad674 454 /* return the gargv to the next element */
ddb4a88b 455 return (fmt);
44bd5ea7
A
456}
457
458static char *
71aad674 459mknum(char *str, char ch)
44bd5ea7 460{
ddb4a88b
A
461 static char *copy;
462 static size_t copy_size;
463 char *newcopy;
464 size_t len, newlen;
44bd5ea7
A
465
466 len = strlen(str) + 2;
ddb4a88b
A
467 if (len > copy_size) {
468 newlen = ((len + 1023) >> 10) << 10;
71aad674
A
469 if ((newcopy = realloc(copy, newlen)) == NULL) {
470 warnx("%s", strerror(ENOMEM));
ddb4a88b
A
471 return (NULL);
472 }
473 copy = newcopy;
474 copy_size = newlen;
475 }
476
477 memmove(copy, str, len - 3);
478 copy[len - 3] = 'j';
44bd5ea7
A
479 copy[len - 2] = ch;
480 copy[len - 1] = '\0';
ddb4a88b
A
481 return (copy);
482}
483
484static int
485escape(char *fmt, int percent, size_t *len)
486{
71aad674
A
487 char *save, *store, c;
488 int value;
ddb4a88b 489
71aad674 490 for (save = store = fmt; ((c = *fmt) != 0); ++fmt, ++store) {
ddb4a88b
A
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':
71aad674
A
512 if (!percent) {
513 *store = '\0';
514 *len = store - save;
515 return (1);
516 }
517 *store = 'c';
518 break;
ddb4a88b
A
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':
71aad674
A
537 c = (!percent && *fmt == '0') ? 4 : 3;
538 for (value = 0;
ddb4a88b
A
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
71aad674 548 *store = (char)value;
ddb4a88b
A
549 break;
550 default:
551 *store = *fmt;
552 break;
553 }
554 }
555 *store = '\0';
556 *len = store - save;
557 return (0);
44bd5ea7
A
558}
559
560static int
ddb4a88b 561getchr(void)
44bd5ea7
A
562{
563 if (!*gargv)
564 return ('\0');
565 return ((int)**gargv++);
566}
567
ddb4a88b
A
568static const char *
569getstr(void)
44bd5ea7
A
570{
571 if (!*gargv)
572 return ("");
573 return (*gargv++);
574}
575
44bd5ea7 576static int
ddb4a88b 577getint(int *ip)
44bd5ea7 578{
ddb4a88b
A
579 intmax_t val;
580 uintmax_t uval;
581 int rval;
44bd5ea7 582
ddb4a88b
A
583 if (getnum(&val, &uval, 1))
584 return (1);
585 rval = 0;
586 if (val < INT_MIN || val > INT_MAX) {
71aad674 587 warnx("%s: %s", *gargv, strerror(ERANGE));
ddb4a88b
A
588 rval = 1;
589 }
590 *ip = (int)val;
591 return (rval);
44bd5ea7
A
592}
593
ddb4a88b
A
594static int
595getnum(intmax_t *ip, uintmax_t *uip, int signedconv)
44bd5ea7 596{
44bd5ea7 597 char *ep;
ddb4a88b 598 int rval;
44bd5ea7 599
ddb4a88b 600 if (!*gargv) {
71aad674 601 *ip = *uip = 0;
ddb4a88b
A
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;
44bd5ea7 612 errno = 0;
ddb4a88b
A
613 if (signedconv)
614 *ip = strtoimax(*gargv, &ep, 0);
615 else
616 *uip = strtoumax(*gargv, &ep, 0);
617 if (ep == *gargv) {
71aad674 618 warnx("%s: expected numeric value", *gargv);
ddb4a88b
A
619 rval = 1;
620 }
621 else if (*ep != '\0') {
71aad674 622 warnx("%s: not completely converted", *gargv);
ddb4a88b
A
623 rval = 1;
624 }
625 if (errno == ERANGE) {
71aad674 626 warnx("%s: %s", *gargv, strerror(ERANGE));
ddb4a88b
A
627 rval = 1;
628 }
629 ++gargv;
630 return (rval);
44bd5ea7
A
631}
632
ddb4a88b
A
633static int
634getfloating(long double *dp, int mod_ldbl)
44bd5ea7 635{
44bd5ea7 636 char *ep;
ddb4a88b 637 int rval;
44bd5ea7 638
ddb4a88b
A
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;
44bd5ea7 648 errno = 0;
ddb4a88b
A
649 if (mod_ldbl)
650 *dp = strtold(*gargv, &ep);
651 else
652 *dp = strtod(*gargv, &ep);
653 if (ep == *gargv) {
71aad674 654 warnx("%s: expected numeric value", *gargv);
ddb4a88b
A
655 rval = 1;
656 } else if (*ep != '\0') {
71aad674 657 warnx("%s: not completely converted", *gargv);
ddb4a88b
A
658 rval = 1;
659 }
660 if (errno == ERANGE) {
71aad674 661 warnx("%s: %s", *gargv, strerror(ERANGE));
ddb4a88b
A
662 rval = 1;
663 }
664 ++gargv;
665 return (rval);
44bd5ea7
A
666}
667
ddb4a88b
A
668static int
669asciicode(void)
44bd5ea7 670{
ddb4a88b 671 int ch;
71aad674
A
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 }
ddb4a88b
A
689 ++gargv;
690 return (ch);
44bd5ea7
A
691}
692
693static void
ddb4a88b 694usage(void)
44bd5ea7 695{
ddb4a88b 696 (void)fprintf(stderr, "usage: printf format [arguments ...]\n");
44bd5ea7 697}