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