source: MondoRescue/branches/3.3/mindi-busybox/coreutils/printf.c@ 3906

Last change on this file since 3906 was 3621, checked in by Bruno Cornec, 10 years ago

New 3?3 banch for incorporation of latest busybox 1.25. Changing minor version to handle potential incompatibilities.

File size: 10.4 KB
Line 
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 GPLv2 or later, see file LICENSE in this source tree.
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//usage:#define printf_trivial_usage
42//usage: "FORMAT [ARG]..."
43//usage:#define printf_full_usage "\n\n"
44//usage: "Format and print ARG(s) according to FORMAT (a-la C printf)"
45//usage:
46//usage:#define printf_example_usage
47//usage: "$ printf \"Val=%d\\n\" 5\n"
48//usage: "Val=5\n"
49
50#include "libbb.h"
51
52/* A note on bad input: neither bash 3.2 nor coreutils 6.10 stop on it.
53 * They report it:
54 * bash: printf: XXX: invalid number
55 * printf: XXX: expected a numeric value
56 * bash: printf: 123XXX: invalid number
57 * printf: 123XXX: value not completely converted
58 * but then they use 0 (or partially converted numeric prefix) as a value
59 * and continue. They exit with 1 in this case.
60 * Both accept insane field width/precision (e.g. %9999999999.9999999999d).
61 * Both print error message and assume 0 if %*.*f width/precision is "bad"
62 * (but negative numbers are not "bad").
63 * Both accept negative numbers for %u specifier.
64 *
65 * We try to be compatible.
66 */
67
68typedef void FAST_FUNC (*converter)(const char *arg, void *result);
69
70static int multiconvert(const char *arg, void *result, converter convert)
71{
72 if (*arg == '"' || *arg == '\'') {
73 arg = utoa((unsigned char)arg[1]);
74 }
75 errno = 0;
76 convert(arg, result);
77 if (errno) {
78 bb_error_msg("invalid number '%s'", arg);
79 return 1;
80 }
81 return 0;
82}
83
84static void FAST_FUNC conv_strtoull(const char *arg, void *result)
85{
86 *(unsigned long long*)result = bb_strtoull(arg, NULL, 0);
87 /* both coreutils 6.10 and bash 3.2:
88 * $ printf '%x\n' -2
89 * fffffffffffffffe
90 * Mimic that:
91 */
92 if (errno) {
93 *(unsigned long long*)result = bb_strtoll(arg, NULL, 0);
94 }
95}
96static void FAST_FUNC conv_strtoll(const char *arg, void *result)
97{
98 *(long long*)result = bb_strtoll(arg, NULL, 0);
99}
100static void FAST_FUNC conv_strtod(const char *arg, void *result)
101{
102 char *end;
103 /* Well, this one allows leading whitespace... so what? */
104 /* What I like much less is that "-" accepted too! :( */
105 *(double*)result = strtod(arg, &end);
106 if (end[0]) {
107 errno = ERANGE;
108 *(double*)result = 0;
109 }
110}
111
112/* Callers should check errno to detect errors */
113static unsigned long long my_xstrtoull(const char *arg)
114{
115 unsigned long long result;
116 if (multiconvert(arg, &result, conv_strtoull))
117 result = 0;
118 return result;
119}
120static long long my_xstrtoll(const char *arg)
121{
122 long long result;
123 if (multiconvert(arg, &result, conv_strtoll))
124 result = 0;
125 return result;
126}
127static double my_xstrtod(const char *arg)
128{
129 double result;
130 multiconvert(arg, &result, conv_strtod);
131 return result;
132}
133
134/* Handles %b; return 1 if output is to be short-circuited by \c */
135static int print_esc_string(const char *str)
136{
137 char c;
138 while ((c = *str) != '\0') {
139 str++;
140 if (c == '\\') {
141 /* %b also accepts 4-digit octals of the form \0### */
142 if (*str == '0') {
143 if ((unsigned char)(str[1] - '0') < 8) {
144 /* 2nd char is 0..7: skip leading '0' */
145 str++;
146 }
147 }
148 else if (*str == 'c') {
149 return 1;
150 }
151 {
152 /* optimization: don't force arg to be on-stack,
153 * use another variable for that. */
154 const char *z = str;
155 c = bb_process_escape_sequence(&z);
156 str = z;
157 }
158 }
159 putchar(c);
160 }
161
162 return 0;
163}
164
165static void print_direc(char *format, unsigned fmt_length,
166 int field_width, int precision,
167 const char *argument)
168{
169 long long llv;
170 double dv;
171 char saved;
172 char *have_prec, *have_width;
173
174 saved = format[fmt_length];
175 format[fmt_length] = '\0';
176
177 have_prec = strstr(format, ".*");
178 have_width = strchr(format, '*');
179 if (have_width - 1 == have_prec)
180 have_width = NULL;
181
182 errno = 0;
183
184 switch (format[fmt_length - 1]) {
185 case 'c':
186 printf(format, *argument);
187 break;
188 case 'd':
189 case 'i':
190 llv = my_xstrtoll(argument);
191 print_long:
192 if (!have_width) {
193 if (!have_prec)
194 printf(format, llv);
195 else
196 printf(format, precision, llv);
197 } else {
198 if (!have_prec)
199 printf(format, field_width, llv);
200 else
201 printf(format, field_width, precision, llv);
202 }
203 break;
204 case 'o':
205 case 'u':
206 case 'x':
207 case 'X':
208 llv = my_xstrtoull(argument);
209 /* cheat: unsigned long and long have same width, so... */
210 goto print_long;
211 case 's':
212 /* Are char* and long long the same? */
213 if (sizeof(argument) == sizeof(llv)) {
214 llv = (long long)(ptrdiff_t)argument;
215 goto print_long;
216 } else {
217 /* Hope compiler will optimize it out by moving call
218 * instruction after the ifs... */
219 if (!have_width) {
220 if (!have_prec)
221 printf(format, argument, /*unused:*/ argument, argument);
222 else
223 printf(format, precision, argument, /*unused:*/ argument);
224 } else {
225 if (!have_prec)
226 printf(format, field_width, argument, /*unused:*/ argument);
227 else
228 printf(format, field_width, precision, argument);
229 }
230 break;
231 }
232 case 'f':
233 case 'e':
234 case 'E':
235 case 'g':
236 case 'G':
237 dv = my_xstrtod(argument);
238 if (!have_width) {
239 if (!have_prec)
240 printf(format, dv);
241 else
242 printf(format, precision, dv);
243 } else {
244 if (!have_prec)
245 printf(format, field_width, dv);
246 else
247 printf(format, field_width, precision, dv);
248 }
249 break;
250 } /* switch */
251
252 format[fmt_length] = saved;
253}
254
255/* Handle params for "%*.*f". Negative numbers are ok (compat). */
256static int get_width_prec(const char *str)
257{
258 int v = bb_strtoi(str, NULL, 10);
259 if (errno) {
260 bb_error_msg("invalid number '%s'", str);
261 v = 0;
262 }
263 return v;
264}
265
266/* Print the text in FORMAT, using ARGV for arguments to any '%' directives.
267 Return advanced ARGV. */
268static char **print_formatted(char *f, char **argv, int *conv_err)
269{
270 char *direc_start; /* Start of % directive. */
271 unsigned direc_length; /* Length of % directive. */
272 int field_width; /* Arg to first '*' */
273 int precision; /* Arg to second '*' */
274 char **saved_argv = argv;
275
276 for (; *f; ++f) {
277 switch (*f) {
278 case '%':
279 direc_start = f++;
280 direc_length = 1;
281 field_width = precision = 0;
282 if (*f == '%') {
283 bb_putchar('%');
284 break;
285 }
286 if (*f == 'b') {
287 if (*argv) {
288 if (print_esc_string(*argv))
289 return saved_argv; /* causes main() to exit */
290 ++argv;
291 }
292 break;
293 }
294 if (strchr("-+ #", *f)) {
295 ++f;
296 ++direc_length;
297 }
298 if (*f == '*') {
299 ++f;
300 ++direc_length;
301 if (*argv)
302 field_width = get_width_prec(*argv++);
303 } else {
304 while (isdigit(*f)) {
305 ++f;
306 ++direc_length;
307 }
308 }
309 if (*f == '.') {
310 ++f;
311 ++direc_length;
312 if (*f == '*') {
313 ++f;
314 ++direc_length;
315 if (*argv)
316 precision = get_width_prec(*argv++);
317 } else {
318 while (isdigit(*f)) {
319 ++f;
320 ++direc_length;
321 }
322 }
323 }
324
325 /* Remove "lLhz" size modifiers, repeatedly.
326 * bash does not like "%lld", but coreutils
327 * happily takes even "%Llllhhzhhzd"!
328 * We are permissive like coreutils */
329 while ((*f | 0x20) == 'l' || *f == 'h' || *f == 'z') {
330 overlapping_strcpy(f, f + 1);
331 }
332 /* Add "ll" if integer modifier, then print */
333 {
334 static const char format_chars[] ALIGN1 = "diouxXfeEgGcs";
335 char *p = strchr(format_chars, *f);
336 /* needed - try "printf %" without it */
337 if (p == NULL) {
338 bb_error_msg("%s: invalid format", direc_start);
339 /* causes main() to exit with error */
340 return saved_argv - 1;
341 }
342 ++direc_length;
343 if (p - format_chars <= 5) {
344 /* it is one of "diouxX" */
345 p = xmalloc(direc_length + 3);
346 memcpy(p, direc_start, direc_length);
347 p[direc_length + 1] = p[direc_length - 1];
348 p[direc_length - 1] = 'l';
349 p[direc_length] = 'l';
350 //bb_error_msg("<%s>", p);
351 direc_length += 2;
352 direc_start = p;
353 } else {
354 p = NULL;
355 }
356 if (*argv) {
357 print_direc(direc_start, direc_length, field_width,
358 precision, *argv++);
359 } else {
360 print_direc(direc_start, direc_length, field_width,
361 precision, "");
362 }
363 *conv_err |= errno;
364 free(p);
365 }
366 break;
367 case '\\':
368 if (*++f == 'c') {
369 return saved_argv; /* causes main() to exit */
370 }
371 bb_putchar(bb_process_escape_sequence((const char **)&f));
372 f--;
373 break;
374 default:
375 putchar(*f);
376 }
377 }
378
379 return argv;
380}
381
382int printf_main(int argc UNUSED_PARAM, char **argv)
383{
384 int conv_err;
385 char *format;
386 char **argv2;
387
388 /* We must check that stdout is not closed.
389 * The reason for this is highly non-obvious.
390 * printf_main is used from shell.
391 * Shell must correctly handle 'printf "%s" foo'
392 * if stdout is closed. With stdio, output gets shoveled into
393 * stdout buffer, and even fflush cannot clear it out. It seems that
394 * even if libc receives EBADF on write attempts, it feels determined
395 * to output data no matter what. So it will try later,
396 * and possibly will clobber future output. Not good. */
397// TODO: check fcntl() & O_ACCMODE == O_WRONLY or O_RDWR?
398 if (fcntl(1, F_GETFL) == -1)
399 return 1; /* match coreutils 6.10 (sans error msg to stderr) */
400 //if (dup2(1, 1) != 1) - old way
401 // return 1;
402
403 /* bash builtin errors out on "printf '-%s-\n' foo",
404 * coreutils-6.9 works. Both work with "printf -- '-%s-\n' foo".
405 * We will mimic coreutils. */
406 if (argv[1] && argv[1][0] == '-' && argv[1][1] == '-' && !argv[1][2])
407 argv++;
408 if (!argv[1]) {
409 if (ENABLE_ASH_BUILTIN_PRINTF
410 && applet_name[0] != 'p'
411 ) {
412 bb_error_msg("usage: printf FORMAT [ARGUMENT...]");
413 return 2; /* bash compat */
414 }
415 bb_show_usage();
416 }
417
418 format = argv[1];
419 argv2 = argv + 2;
420
421 conv_err = 0;
422 do {
423 argv = argv2;
424 argv2 = print_formatted(format, argv, &conv_err);
425 } while (argv2 > argv && *argv2);
426
427 /* coreutils compat (bash doesn't do this):
428 if (*argv)
429 fprintf(stderr, "excess args ignored");
430 */
431
432 return (argv2 < argv) /* if true, print_formatted errored out */
433 || conv_err; /* print_formatted saw invalid number */
434}
Note: See TracBrowser for help on using the repository browser.