d877e058168fc341a35c5e063c5a52a94c4997eb
[oweals/busybox.git] / coreutils / printf.c
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
40 //   19990508 Busy Boxed! Dave Cinege
41
42 #include "libbb.h"
43
44 typedef void (*converter)(const char *arg, void *result);
45
46 static void multiconvert(const char *arg, void *result, converter convert)
47 {
48         char s[sizeof(int)*3 + 2];
49
50         if (*arg == '"' || *arg == '\'') {
51                 sprintf(s, "%d", (unsigned char)arg[1]);
52                 arg = s;
53         }
54         convert(arg, result);
55         /* if there was conversion error, print unconverted string */
56         if (errno)
57                 fputs(arg, stderr);
58 }
59
60 static void conv_strtoul(const char *arg, void *result)
61 {
62         *(unsigned long*)result = bb_strtoul(arg, NULL, 0);
63 }
64 static void conv_strtol(const char *arg, void *result)
65 {
66         *(long*)result = bb_strtol(arg, NULL, 0);
67 }
68 static void conv_strtod(const char *arg, void *result)
69 {
70         char *end;
71         /* Well, this one allows leading whitespace... so what */
72         /* What I like much less is that "-" is accepted too! :( */
73         *(double*)result = strtod(arg, &end);
74         if (end[0]) errno = ERANGE;
75 }
76
77 static unsigned long my_xstrtoul(const char *arg)
78 {
79         unsigned long result;
80         multiconvert(arg, &result, conv_strtoul);
81         return result;
82 }
83
84 static long my_xstrtol(const char *arg)
85 {
86         long result;
87         multiconvert(arg, &result, conv_strtol);
88         return result;
89 }
90
91 static double my_xstrtod(const char *arg)
92 {
93         double result;
94         multiconvert(arg, &result, conv_strtod);
95         return result;
96 }
97
98 static void print_esc_string(char *str)
99 {
100         for (; *str; str++) {
101                 if (*str == '\\') {
102                         str++;
103                         bb_putchar(bb_process_escape_sequence((const char **)&str));
104                 } else {
105                         bb_putchar(*str);
106                 }
107
108         }
109 }
110
111 static void print_direc(char *format, unsigned fmt_length,
112                 int field_width, int precision,
113                 const char *argument)
114 {
115         char saved;
116
117         saved = format[fmt_length];
118         format[fmt_length] = '\0';
119
120         switch (format[fmt_length - 1]) {
121         case 'd':
122         case 'i':
123                 if (field_width < 0) {
124                         if (precision < 0)
125                                 printf(format, my_xstrtol(argument));
126                         else
127                                 printf(format, precision, my_xstrtol(argument));
128                 } else {
129                         if (precision < 0)
130                                 printf(format, field_width, my_xstrtol(argument));
131                         else
132                                 printf(format, field_width, precision, my_xstrtol(argument));
133                 }
134                 break;
135         case 'o':
136         case 'u':
137         case 'x':
138         case 'X':
139                 if (field_width < 0) {
140                         if (precision < 0)
141                                 printf(format, my_xstrtoul(argument));
142                         else
143                                 printf(format, precision, my_xstrtoul(argument));
144                 } else {
145                         if (precision < 0)
146                                 printf(format, field_width, my_xstrtoul(argument));
147                         else
148                                 printf(format, field_width, precision, my_xstrtoul(argument));
149                 }
150                 break;
151         case 'f':
152         case 'e':
153         case 'E':
154         case 'g':
155         case 'G':
156                 if (field_width < 0) {
157                         if (precision < 0)
158                                 printf(format, my_xstrtod(argument));
159                         else
160                                 printf(format, precision, my_xstrtod(argument));
161                 } else {
162                         if (precision < 0)
163                                 printf(format, field_width, my_xstrtod(argument));
164                         else
165                                 printf(format, field_width, precision, my_xstrtod(argument));
166                 }
167                 break;
168         case 'c':
169                 printf(format, *argument);
170                 break;
171         case 's':
172                 if (field_width < 0) {
173                         if (precision < 0)
174                                 printf(format, argument);
175                         else
176                                 printf(format, precision, argument);
177                 } else {
178                         if (precision < 0)
179                                 printf(format, field_width, argument);
180                         else
181                                 printf(format, field_width, precision, argument);
182                 }
183                 break;
184         }
185
186         format[fmt_length] = saved;
187 }
188
189 /* Print the text in FORMAT, using ARGV for arguments to any '%' directives.
190    Return advanced ARGV.  */
191 static char **print_formatted(char *f, char **argv)
192 {
193         char *direc_start;      /* Start of % directive.  */
194         unsigned direc_length;  /* Length of % directive.  */
195         int field_width;        /* Arg to first '*', or -1 if none.  */
196         int precision;          /* Arg to second '*', or -1 if none.  */
197         char **saved_argv = argv;
198
199         for (; *f; ++f) {
200                 switch (*f) {
201                 case '%':
202                         direc_start = f++;
203                         direc_length = 1;
204                         field_width = precision = -1;
205                         if (*f == '%') {
206                                 bb_putchar('%');
207                                 break;
208                         }
209                         if (*f == 'b') {
210                                 if (*argv) {
211                                         print_esc_string(*argv);
212                                         ++argv;
213                                 }
214                                 break;
215                         }
216                         if (strchr("-+ #", *f)) {
217                                 ++f;
218                                 ++direc_length;
219                         }
220                         if (*f == '*') {
221                                 ++f;
222                                 ++direc_length;
223                                 if (*argv) {
224                                         field_width = my_xstrtoul(*argv);
225                                         ++argv;
226                                 } else
227                                         field_width = 0;
228                         } else {
229                                 while (isdigit(*f)) {
230                                         ++f;
231                                         ++direc_length;
232                                 }
233                         }
234                         if (*f == '.') {
235                                 ++f;
236                                 ++direc_length;
237                                 if (*f == '*') {
238                                         ++f;
239                                         ++direc_length;
240                                         if (*argv) {
241                                                 precision = my_xstrtoul(*argv);
242                                                 ++argv;
243                                         } else
244                                                 precision = 0;
245                                 } else
246                                         while (isdigit(*f)) {
247                                                 ++f;
248                                                 ++direc_length;
249                                         }
250                         }
251                         if (*f == 'l' || *f == 'L' || *f == 'h') {
252                                 ++f;
253                                 ++direc_length;
254                         }
255                         /* needed - try "printf %" without it */
256                         if (!strchr("diouxXfeEgGcs", *f)) {
257                                 bb_error_msg("invalid directive '%s'", direc_start);
258                                 /* causes main() to exit with error */
259                                 return saved_argv - 1;
260                         }
261                         ++direc_length;
262                         if (*argv) {
263                                 print_direc(direc_start, direc_length, field_width,
264                                                         precision, *argv);
265                                 ++argv;
266                         } else
267                                 print_direc(direc_start, direc_length, field_width,
268                                                         precision, "");
269                         break;
270                 case '\\':
271                         if (*++f == 'c') {
272                                 return saved_argv; /* causes main() to exit */
273                         }
274                         bb_putchar(bb_process_escape_sequence((const char **)&f));
275                         f--;
276                         break;
277                 default:
278                         bb_putchar(*f);
279                 }
280         }
281
282         return argv;
283 }
284
285 int printf_main(int argc UNUSED_PARAM, char **argv)
286 {
287         char *format;
288         char **argv2;
289
290         /* We must check that stdout is not closed.
291          * The reason for this is highly non-obvious.
292          * printf_main is used from shell.
293          * Shell must correctly handle 'printf "%s" foo'
294          * if stdout is closed. With stdio, output gets shoveled into
295          * stdout buffer, and even fflush cannot clear it out. It seems that
296          * even if libc receives EBADF on write attempts, it feels determined
297          * to output data no matter what. So it will try later,
298          * and possibly will clobber future output. Not good. */
299         if (dup2(1, 1) != 1)
300                 return -1;
301
302         /* bash builtin errors out on "printf '-%s-\n' foo",
303          * coreutils-6.9 works. Both work with "printf -- '-%s-\n' foo".
304          * We will mimic coreutils. */
305         if (argv[1] && argv[1][0] == '-' && argv[1][1] == '-' && !argv[1][2])
306                 argv++;
307         if (!argv[1])
308                 bb_show_usage();
309
310         format = argv[1];
311         argv2 = argv + 2;
312
313         do {
314                 argv = argv2;
315                 argv2 = print_formatted(format, argv);
316         } while (argv2 > argv && *argv2);
317
318         /* coreutils compat (bash doesn't do this):
319         if (*argv)
320                 fprintf(stderr, "excess args ignored");
321         */
322
323         return (argv2 < argv); /* if true, print_formatted errored out */
324 }