1/* vi: set sw=4 ts=4: */
2/* printf - format and print data
3
4   Copyright 1999 Dave Cinege
5   Portions copyright (C) 1990-1996 Free Software Foundation, Inc.
6
7   Licensed under GPL v2 or later, see file LICENSE in this tarball for details.
8*/
9
10/* Usage: printf format [argument...]
11
12   A front end to the printf function that lets it be used from the shell.
13
14   Backslash escapes:
15
16   \" = double quote
17   \\ = backslash
18   \a = alert (bell)
19   \b = backspace
20   \c = produce no further output
21   \f = form feed
22   \n = new line
23   \r = carriage return
24   \t = horizontal tab
25   \v = vertical tab
26   \0ooo = octal number (ooo is 0 to 3 digits)
27   \xhhh = hexadecimal number (hhh is 1 to 3 digits)
28
29   Additional directive:
30
31   %b = print an argument string, interpreting backslash escapes
32
33   The 'format' argument is re-used as many times as necessary
34   to convert all of the given arguments.
35
36   David MacKenzie <djm@gnu.ai.mit.edu> */
37
38
39//   19990508 Busy Boxed! Dave Cinege
40
41#include "libbb.h"
42
43typedef void (*converter)(const char *arg, void *result);
44
45static void multiconvert(const char *arg, void *result, converter convert)
46{
47	char s[sizeof(int)*3 + 2];
48
49	if (*arg == '"' || *arg == '\'') {
50		sprintf(s, "%d", (unsigned char)arg[1]);
51		arg = s;
52	}
53	convert(arg, result);
54	/* if there was conversion error, print unconverted string */
55	if (errno)
56		fputs(arg, stderr);
57}
58
59static void conv_strtoul(const char *arg, void *result)
60{
61	*(unsigned long*)result = bb_strtoul(arg, NULL, 0);
62}
63static void conv_strtol(const char *arg, void *result)
64{
65	*(long*)result = bb_strtol(arg, NULL, 0);
66}
67static void conv_strtod(const char *arg, void *result)
68{
69	char *end;
70	/* Well, this one allows leading whitespace... so what */
71	/* What I like much less is that "-" is accepted too! :( */
72	*(double*)result = strtod(arg, &end);
73	if (end[0]) errno = ERANGE;
74}
75
76static unsigned long my_xstrtoul(const char *arg)
77{
78	unsigned long result;
79	multiconvert(arg, &result, conv_strtoul);
80	return result;
81}
82
83static long my_xstrtol(const char *arg)
84{
85	long result;
86	multiconvert(arg, &result, conv_strtol);
87	return result;
88}
89
90static double my_xstrtod(const char *arg)
91{
92	double result;
93	multiconvert(arg, &result, conv_strtod);
94	return result;
95}
96
97static void print_esc_string(char *str)
98{
99	for (; *str; str++) {
100		if (*str == '\\') {
101			str++;
102			putchar(bb_process_escape_sequence((const char **)&str));
103		} else {
104			putchar(*str);
105		}
106
107	}
108}
109
110static void print_direc(char *start, size_t length, int field_width, int precision,
111		const char *argument)
112{
113	char *p;		/* Null-terminated copy of % directive. */
114
115	p = xmalloc((unsigned) (length + 1));
116	strncpy(p, start, length);
117	p[length] = 0;
118
119	switch (p[length - 1]) {
120	case 'd':
121	case 'i':
122		if (field_width < 0) {
123			if (precision < 0)
124				printf(p, my_xstrtol(argument));
125			else
126				printf(p, precision, my_xstrtol(argument));
127		} else {
128			if (precision < 0)
129				printf(p, field_width, my_xstrtol(argument));
130			else
131				printf(p, field_width, precision, my_xstrtol(argument));
132		}
133		break;
134	case 'o':
135	case 'u':
136	case 'x':
137	case 'X':
138		if (field_width < 0) {
139			if (precision < 0)
140				printf(p, my_xstrtoul(argument));
141			else
142				printf(p, precision, my_xstrtoul(argument));
143		} else {
144			if (precision < 0)
145				printf(p, field_width, my_xstrtoul(argument));
146			else
147				printf(p, field_width, precision, my_xstrtoul(argument));
148		}
149		break;
150	case 'f':
151	case 'e':
152	case 'E':
153	case 'g':
154	case 'G':
155		if (field_width < 0) {
156			if (precision < 0)
157				printf(p, my_xstrtod(argument));
158			else
159				printf(p, precision, my_xstrtod(argument));
160		} else {
161			if (precision < 0)
162				printf(p, field_width, my_xstrtod(argument));
163			else
164				printf(p, field_width, precision, my_xstrtod(argument));
165		}
166		break;
167	case 'c':
168		printf(p, *argument);
169		break;
170	case 's':
171		if (field_width < 0) {
172			if (precision < 0)
173				printf(p, argument);
174			else
175				printf(p, precision, argument);
176		} else {
177			if (precision < 0)
178				printf(p, field_width, argument);
179			else
180				printf(p, field_width, precision, argument);
181		}
182		break;
183	}
184
185	free(p);
186}
187
188/* Print the text in FORMAT, using ARGV (with ARGC elements) for
189   arguments to any '%' directives.
190   Return the number of elements of ARGV used.  */
191
192static int print_formatted(char *format, int argc, char **argv)
193{
194	int save_argc = argc;   /* Preserve original value.  */
195	char *f;                /* Pointer into 'format'.  */
196	char *direc_start;      /* Start of % directive.  */
197	size_t direc_length;    /* Length of % directive.  */
198	int field_width;        /* Arg to first '*', or -1 if none.  */
199	int precision;          /* Arg to second '*', or -1 if none.  */
200
201	for (f = format; *f; ++f) {
202		switch (*f) {
203		case '%':
204			direc_start = f++;
205			direc_length = 1;
206			field_width = precision = -1;
207			if (*f == '%') {
208				putchar('%');
209				break;
210			}
211			if (*f == 'b') {
212				if (argc > 0) {
213					print_esc_string(*argv);
214					++argv;
215					--argc;
216				}
217				break;
218			}
219			if (strchr("-+ #", *f)) {
220				++f;
221				++direc_length;
222			}
223			if (*f == '*') {
224				++f;
225				++direc_length;
226				if (argc > 0) {
227					field_width = my_xstrtoul(*argv);
228					++argv;
229					--argc;
230				} else
231					field_width = 0;
232			} else {
233				while (isdigit(*f)) {
234					++f;
235					++direc_length;
236				}
237			}
238			if (*f == '.') {
239				++f;
240				++direc_length;
241				if (*f == '*') {
242					++f;
243					++direc_length;
244					if (argc > 0) {
245						precision = my_xstrtoul(*argv);
246						++argv;
247						--argc;
248					} else
249						precision = 0;
250				} else
251					while (isdigit(*f)) {
252						++f;
253						++direc_length;
254					}
255			}
256			if (*f == 'l' || *f == 'L' || *f == 'h') {
257				++f;
258				++direc_length;
259			}
260			/*
261			if (!strchr ("diouxXfeEgGcs", *f))
262			fprintf(stderr, "%%%c: invalid directive", *f);
263			*/
264			++direc_length;
265			if (argc > 0) {
266				print_direc(direc_start, direc_length, field_width,
267							precision, *argv);
268				++argv;
269				--argc;
270			} else
271				print_direc(direc_start, direc_length, field_width,
272							precision, "");
273			break;
274		case '\\':
275			if (*++f == 'c')
276				exit(0);
277			putchar(bb_process_escape_sequence((const char **)&f));
278			f--;
279			break;
280		default:
281			putchar(*f);
282		}
283	}
284
285	return save_argc - argc;
286}
287
288int printf_main(int argc, char **argv);
289int printf_main(int argc, char **argv)
290{
291	char *format;
292	int args_used;
293
294	if (argc <= 1 || argv[1][0] == '-') {
295		bb_show_usage();
296	}
297
298	format = argv[1];
299	argc -= 2;
300	argv += 2;
301
302	do {
303		args_used = print_formatted(format, argc, argv);
304		argc -= args_used;
305		argv += args_used;
306	} while (args_used > 0 && argc > 0);
307
308/*	if (argc > 0)
309		fprintf(stderr, "excess args ignored");
310*/
311
312	return EXIT_SUCCESS;
313}
314