xref: /freebsd/usr.bin/printf/printf.c (revision 19fae0f66023a97a9b464b3beeeabb2081f575b3)
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 		PF(start, p);
387 		/* Restore format for next loop. */
388 
389 		free(p);
390 		if (getout)
391 			return (end_fmt);
392 		break;
393 	}
394 	case 'c': {
395 		char p;
396 
397 		p = getchr();
398 		if (p != '\0')
399 			PF(start, p);
400 		break;
401 	}
402 	case 's': {
403 		const char *p;
404 
405 		p = getstr();
406 		PF(start, p);
407 		break;
408 	}
409 	case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': {
410 		char *f;
411 		intmax_t val;
412 		uintmax_t uval;
413 		int signedconv;
414 
415 		signedconv = (convch == 'd' || convch == 'i');
416 		if ((f = mknum(start, convch)) == NULL)
417 			return (NULL);
418 		if (getnum(&val, &uval, signedconv))
419 			*rval = 1;
420 		if (signedconv)
421 			PF(f, val);
422 		else
423 			PF(f, uval);
424 		break;
425 	}
426 	case 'e': case 'E':
427 	case 'f': case 'F':
428 	case 'g': case 'G':
429 	case 'a': case 'A': {
430 		long double p;
431 
432 		if (getfloating(&p, mod_ldbl))
433 			*rval = 1;
434 		if (mod_ldbl)
435 			PF(start, p);
436 		else
437 			PF(start, (double)p);
438 		break;
439 	}
440 	default:
441 		warnx("illegal format character %c", convch);
442 		return (NULL);
443 	}
444 	*fmt = nextch;
445 	/* return the gargv to the next element */
446 	return (fmt);
447 }
448 
449 static char *
450 mknum(char *str, char ch)
451 {
452 	static char *copy;
453 	static size_t copy_size;
454 	char *newcopy;
455 	size_t len, newlen;
456 
457 	len = strlen(str) + 2;
458 	if (len > copy_size) {
459 		newlen = ((len + 1023) >> 10) << 10;
460 		if ((newcopy = realloc(copy, newlen)) == NULL) {
461 			warnx("%s", strerror(ENOMEM));
462 			return (NULL);
463 		}
464 		copy = newcopy;
465 		copy_size = newlen;
466 	}
467 
468 	memmove(copy, str, len - 3);
469 	copy[len - 3] = 'j';
470 	copy[len - 2] = ch;
471 	copy[len - 1] = '\0';
472 	return (copy);
473 }
474 
475 static int
476 escape(char *fmt, int percent, size_t *len)
477 {
478 	char *save, *store, c;
479 	int value;
480 
481 	for (save = store = fmt; ((c = *fmt) != 0); ++fmt, ++store) {
482 		if (c != '\\') {
483 			*store = c;
484 			continue;
485 		}
486 		switch (*++fmt) {
487 		case '\0':		/* EOS, user error */
488 			*store = '\\';
489 			*++store = '\0';
490 			*len = store - save;
491 			return (0);
492 		case '\\':		/* backslash */
493 		case '\'':		/* single quote */
494 			*store = *fmt;
495 			break;
496 		case 'a':		/* bell/alert */
497 			*store = '\a';
498 			break;
499 		case 'b':		/* backspace */
500 			*store = '\b';
501 			break;
502 		case 'c':
503 			if (!percent) {
504 				*store = '\0';
505 				*len = store - save;
506 				return (1);
507 			}
508 			*store = 'c';
509 			break;
510 		case 'f':		/* form-feed */
511 			*store = '\f';
512 			break;
513 		case 'n':		/* newline */
514 			*store = '\n';
515 			break;
516 		case 'r':		/* carriage-return */
517 			*store = '\r';
518 			break;
519 		case 't':		/* horizontal tab */
520 			*store = '\t';
521 			break;
522 		case 'v':		/* vertical tab */
523 			*store = '\v';
524 			break;
525 					/* octal constant */
526 		case '0': case '1': case '2': case '3':
527 		case '4': case '5': case '6': case '7':
528 			c = (!percent && *fmt == '0') ? 4 : 3;
529 			for (value = 0;
530 			    c-- && *fmt >= '0' && *fmt <= '7'; ++fmt) {
531 				value <<= 3;
532 				value += *fmt - '0';
533 			}
534 			--fmt;
535 			if (percent && value == '%') {
536 				*store++ = '%';
537 				*store = '%';
538 			} else
539 				*store = (char)value;
540 			break;
541 		default:
542 			*store = *fmt;
543 			break;
544 		}
545 	}
546 	*store = '\0';
547 	*len = store - save;
548 	return (0);
549 }
550 
551 static int
552 getchr(void)
553 {
554 	if (!*gargv)
555 		return ('\0');
556 	return ((int)**gargv++);
557 }
558 
559 static const char *
560 getstr(void)
561 {
562 	if (!*gargv)
563 		return ("");
564 	return (*gargv++);
565 }
566 
567 static int
568 getint(int *ip)
569 {
570 	intmax_t val;
571 	uintmax_t uval;
572 	int rval;
573 
574 	if (getnum(&val, &uval, 1))
575 		return (1);
576 	rval = 0;
577 	if (val < INT_MIN || val > INT_MAX) {
578 		warnx("%s: %s", *gargv, strerror(ERANGE));
579 		rval = 1;
580 	}
581 	*ip = (int)val;
582 	return (rval);
583 }
584 
585 static int
586 getnum(intmax_t *ip, uintmax_t *uip, int signedconv)
587 {
588 	char *ep;
589 	int rval;
590 
591 	if (!*gargv) {
592 		*ip = *uip = 0;
593 		return (0);
594 	}
595 	if (**gargv == '"' || **gargv == '\'') {
596 		if (signedconv)
597 			*ip = asciicode();
598 		else
599 			*uip = asciicode();
600 		return (0);
601 	}
602 	rval = 0;
603 	errno = 0;
604 	if (signedconv)
605 		*ip = strtoimax(*gargv, &ep, 0);
606 	else
607 		*uip = strtoumax(*gargv, &ep, 0);
608 	if (ep == *gargv) {
609 		warnx("%s: expected numeric value", *gargv);
610 		rval = 1;
611 	}
612 	else if (*ep != '\0') {
613 		warnx("%s: not completely converted", *gargv);
614 		rval = 1;
615 	}
616 	if (errno == ERANGE) {
617 		warnx("%s: %s", *gargv, strerror(ERANGE));
618 		rval = 1;
619 	}
620 	++gargv;
621 	return (rval);
622 }
623 
624 static int
625 getfloating(long double *dp, int mod_ldbl)
626 {
627 	char *ep;
628 	int rval;
629 
630 	if (!*gargv) {
631 		*dp = 0.0;
632 		return (0);
633 	}
634 	if (**gargv == '"' || **gargv == '\'') {
635 		*dp = asciicode();
636 		return (0);
637 	}
638 	rval = 0;
639 	errno = 0;
640 	if (mod_ldbl)
641 		*dp = strtold(*gargv, &ep);
642 	else
643 		*dp = strtod(*gargv, &ep);
644 	if (ep == *gargv) {
645 		warnx("%s: expected numeric value", *gargv);
646 		rval = 1;
647 	} else if (*ep != '\0') {
648 		warnx("%s: not completely converted", *gargv);
649 		rval = 1;
650 	}
651 	if (errno == ERANGE) {
652 		warnx("%s: %s", *gargv, strerror(ERANGE));
653 		rval = 1;
654 	}
655 	++gargv;
656 	return (rval);
657 }
658 
659 static int
660 asciicode(void)
661 {
662 	int ch;
663 	wchar_t wch;
664 	mbstate_t mbs;
665 
666 	ch = (unsigned char)**gargv;
667 	if (ch == '\'' || ch == '"') {
668 		memset(&mbs, 0, sizeof(mbs));
669 		switch (mbrtowc(&wch, *gargv + 1, MB_LEN_MAX, &mbs)) {
670 		case (size_t)-2:
671 		case (size_t)-1:
672 			wch = (unsigned char)gargv[0][1];
673 			break;
674 		case 0:
675 			wch = 0;
676 			break;
677 		}
678 		ch = wch;
679 	}
680 	++gargv;
681 	return (ch);
682 }
683 
684 static void
685 usage(void)
686 {
687 	(void)fprintf(stderr, "usage: printf format [arguments ...]\n");
688 }
689