printf.c revision 1.25 1 /* $NetBSD: printf.c,v 1.25 2002/11/24 22:35:45 christos Exp $ */
2
3 /*
4 * Copyright (c) 1989, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software developed by the University of
18 * California, Berkeley and its contributors.
19 * 4. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
34 */
35
36 #include <sys/cdefs.h>
37 #ifndef lint
38 #if !defined(BUILTIN) && !defined(SHELL)
39 __COPYRIGHT("@(#) Copyright (c) 1989, 1993\n\
40 The Regents of the University of California. All rights reserved.\n");
41 #endif
42 #endif
43
44 #ifndef lint
45 #if 0
46 static char sccsid[] = "@(#)printf.c 8.2 (Berkeley) 3/22/95";
47 #else
48 __RCSID("$NetBSD: printf.c,v 1.25 2002/11/24 22:35:45 christos Exp $");
49 #endif
50 #endif /* not lint */
51
52 #include <sys/types.h>
53
54 #include <ctype.h>
55 #include <err.h>
56 #include <errno.h>
57 #include <inttypes.h>
58 #include <limits.h>
59 #include <locale.h>
60 #include <stdarg.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 #include <unistd.h>
65
66 #ifdef __GNUC__
67 #define ESCAPE '\e'
68 #else
69 #define ESCAPE 033
70 #endif
71
72 static char *conv_escape_str(char *);
73 static char *conv_escape(char *, char *);
74 static char *conv_expand(const char *);
75 static int getchr(void);
76 static double getdouble(void);
77 static int getwidth(void);
78 static intmax_t getintmax(void);
79 static uintmax_t getuintmax(void);
80 static char *getstr(void);
81 static char *mklong(const char *, int);
82 static void check_conversion(const char *, const char *);
83 static void usage(void);
84
85 static int rval;
86 static char **gargv;
87
88 #ifdef BUILTIN /* csh builtin */
89 #define main progprintf
90 #endif
91
92 #ifdef SHELL /* sh (aka ash) builtin */
93 #define main printfcmd
94 #include "../../bin/sh/bltin/bltin.h"
95 #endif /* SHELL */
96
97 #define PF(f, func) { \
98 if (fieldwidth != -1) { \
99 if (precision != -1) \
100 (void)printf(f, fieldwidth, precision, func); \
101 else \
102 (void)printf(f, fieldwidth, func); \
103 } else if (precision != -1) \
104 (void)printf(f, precision, func); \
105 else \
106 (void)printf(f, func); \
107 }
108
109 int main(int, char **);
110 int main(int argc, char *argv[])
111 {
112 char *fmt, *start;
113 int fieldwidth, precision;
114 char nextch;
115 char *format;
116 int ch;
117
118 #if !defined(SHELL) && !defined(BUILTIN)
119 (void)setlocale (LC_ALL, "");
120 #endif
121
122 while ((ch = getopt(argc, argv, "")) != -1) {
123 switch (ch) {
124 case '?':
125 default:
126 usage();
127 return (1);
128 }
129 }
130 argc -= optind;
131 argv += optind;
132
133 if (argc < 1) {
134 usage();
135 return (1);
136 }
137
138 format = *argv;
139 gargv = ++argv;
140
141 #define SKIP1 "#-+ 0"
142 #define SKIP2 "*0123456789"
143 do {
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.
148 * Note, format strings are reused as necessary to use up the
149 * provided arguments, arguments of zero/null string are
150 * provided to use up the format string.
151 */
152
153 /* find next format specification */
154 for (fmt = format; (ch = *fmt++) ;) {
155 if (ch == '\\') {
156 char c_ch;
157 fmt = conv_escape(fmt, &c_ch);
158 putchar(c_ch);
159 continue;
160 }
161 if (ch != '%' || (*fmt == '%' && ++fmt)) {
162 (void)putchar(ch);
163 continue;
164 }
165
166 /* Ok - we've found a format specification,
167 Save its address for a later printf(). */
168 start = fmt - 1;
169
170 /* skip to field width */
171 fmt += strspn(fmt, SKIP1);
172 fieldwidth = *fmt == '*' ? getwidth() : -1;
173
174 /* skip to possible '.', get following precision */
175 fmt += strspn(fmt, SKIP2);
176 if (*fmt == '.')
177 ++fmt;
178 precision = *fmt == '*' ? getwidth() : -1;
179
180 fmt += strspn(fmt, SKIP2);
181
182 ch = *fmt;
183 if (!ch) {
184 warnx("missing format character");
185 return (1);
186 }
187 /* null terminate format string to we can use it
188 as an argument to printf. */
189 nextch = fmt[1];
190 fmt[1] = 0;
191 switch (ch) {
192
193 case 'B': {
194 const char *p = conv_expand(getstr());
195 *fmt = 's';
196 PF(start, p);
197 break;
198 }
199 case 'b': {
200 char *p = conv_escape_str(getstr());
201 *fmt = 's';
202 PF(start, p);
203 break;
204 }
205 case 'c': {
206 char p = getchr();
207 PF(start, p);
208 break;
209 }
210 case 's': {
211 char *p = getstr();
212 PF(start, p);
213 break;
214 }
215 case 'd':
216 case 'i': {
217 intmax_t p = getintmax();
218 char *f = mklong(start, ch);
219 PF(f, p);
220 break;
221 }
222 case 'o':
223 case 'u':
224 case 'x':
225 case 'X': {
226 uintmax_t p = getuintmax();
227 char *f = mklong(start, ch);
228 PF(f, p);
229 break;
230 }
231 case 'e':
232 case 'E':
233 case 'f':
234 case 'g':
235 case 'G': {
236 double p = getdouble();
237 PF(start, p);
238 break;
239 }
240 default:
241 warnx("%s: invalid directive", start);
242 return (1);
243 }
244 *fmt++ = ch;
245 *fmt = nextch;
246 /* escape if a \c was encountered */
247 if (rval & 0x100)
248 return (rval & ~0x100);
249 }
250 } while (gargv != argv && *gargv);
251
252 return (rval);
253 }
254
255
256 /*
257 * Print SysV echo(1) style escape string
258 * Halts processing string if a \c escape is encountered.
259 */
260 static char *
261 conv_escape_str(char *str)
262 {
263 int value;
264 int ch;
265 static char *conv_str;
266 char *cp;
267
268 /* convert string into a temporary buffer... */
269 if (conv_str)
270 free(conv_str);
271 conv_str = malloc(strlen(str) + 4);
272 if (!conv_str)
273 return "<no memory>";
274 cp = conv_str;
275
276 while ((ch = *str++)) {
277 if (ch != '\\') {
278 *cp++ = ch;
279 continue;
280 }
281
282 ch = *str++;
283 if (ch == 'c') {
284 /* \c as in SYSV echo - abort all processing.... */
285 rval |= 0x100;
286 break;
287 }
288
289 /*
290 * %b string octal constants are not like those in C.
291 * They start with a \0, and are followed by 0, 1, 2,
292 * or 3 octal digits.
293 */
294 if (ch == '0') {
295 char octnum[4], *oct_end;
296 octnum[0] = str[0];
297 octnum[1] = str[1];
298 octnum[2] = str[2];
299 octnum[3] = 0;
300 *cp++ = strtoul(octnum, &oct_end, 8);
301 str += oct_end - octnum;
302 continue;
303 }
304
305 /* \[M][^|-]C as defined by vis(3) */
306 if (ch == 'M' && *str == '-') {
307 *cp++ = 0200 | str[1];
308 str += 2;
309 continue;
310 }
311 if (ch == 'M' && *str == '^') {
312 str++;
313 value = 0200;
314 ch = '^';
315 } else
316 value = 0;
317 if (ch == '^') {
318 ch = *str++;
319 if (ch == '?')
320 value |= 0177;
321 else
322 value |= ch & 037;
323 *cp++ = value;
324 continue;
325 }
326
327 /* Finally test for sequences valid in the format string */
328 str = conv_escape(str - 1, cp);
329 cp++;
330 }
331 *cp = 0;
332
333 return conv_str;
334 }
335
336 /*
337 * Print "standard" escape characters
338 */
339 static char *
340 conv_escape(char *str, char *conv_ch)
341 {
342 int value;
343 int ch;
344 char num_buf[4], *num_end;
345
346 ch = *str++;
347
348 switch (ch) {
349 case '0': case '1': case '2': case '3':
350 case '4': case '5': case '6': case '7':
351 num_buf[0] = ch;
352 ch = str[0];
353 num_buf[1] = ch;
354 num_buf[2] = ch ? str[1] : 0;
355 num_buf[3] = 0;
356 value = strtoul(num_buf, &num_end, 8);
357 str += num_end - (num_buf + 1);
358 break;
359
360 case 'x':
361 /* Hexadecimal character constants are not required to be
362 supported (by SuS v1) because there is no consistent
363 way to detect the end of the constant.
364 Supporting 2 byte constants is a compromise. */
365 ch = str[0];
366 num_buf[0] = ch;
367 num_buf[1] = ch ? str[1] : 0;
368 num_buf[2] = 0;
369 value = strtoul(num_buf, &num_end, 16);
370 str += num_end - num_buf;
371 break;
372
373 case '\\': value = '\\'; break; /* backslash */
374 case '\'': value = '\''; break; /* single quote */
375 case '"': value = '"'; break; /* double quote */
376 case 'a': value = '\a'; break; /* alert */
377 case 'b': value = '\b'; break; /* backspace */
378 case 'e': value = ESCAPE; break; /* escape */
379 case 'f': value = '\f'; break; /* form-feed */
380 case 'n': value = '\n'; break; /* newline */
381 case 'r': value = '\r'; break; /* carriage-return */
382 case 't': value = '\t'; break; /* tab */
383 case 'v': value = '\v'; break; /* vertical-tab */
384
385 default:
386 warnx("unknown escape sequence `\\%c'", *str);
387 rval = 1;
388 value = *str;
389 break;
390 }
391
392 *conv_ch = value;
393 return str;
394 }
395
396 /* expand a string so that everything is printable */
397
398 static char *
399 conv_expand(const char *str)
400 {
401 static char *conv_str;
402 char *cp;
403 int ch;
404
405 if (conv_str)
406 free(conv_str);
407 /* get a buffer that is definitely large enough.... */
408 conv_str = malloc(4 * strlen(str) + 1);
409 if (!conv_str)
410 return "<no memory>";
411 cp = conv_str;
412
413 while ((ch = *(unsigned char *)str++)) {
414 switch (ch) {
415 /* Use C escapes for expected control characters */
416 case '\\': ch = '\\'; break; /* backslash */
417 case '\'': ch = '\''; break; /* single quote */
418 case '"': ch = '"'; break; /* double quote */
419 case '\a': ch = 'a'; break; /* alert */
420 case '\b': ch = 'b'; break; /* backspace */
421 case ESCAPE: ch = 'e'; break; /* escape */
422 case '\f': ch = 'f'; break; /* form-feed */
423 case '\n': ch = 'n'; break; /* newline */
424 case '\r': ch = 'r'; break; /* carriage-return */
425 case '\t': ch = 't'; break; /* tab */
426 case '\v': ch = 'v'; break; /* vertical-tab */
427 default:
428 /* Copy anything printable */
429 if (isprint(ch)) {
430 *cp++ = ch;
431 continue;
432 }
433 /* Use vis(3) encodings for the rest */
434 *cp++ = '\\';
435 if (ch & 0200) {
436 *cp++ = 'M';
437 ch &= ~0200;
438 }
439 if (ch == 0177) {
440 *cp++ = '^';
441 *cp++ = '?';
442 continue;
443 }
444 if (ch < 040) {
445 *cp++ = '^';
446 *cp++ = ch | 0100;
447 continue;
448 }
449 *cp++ = '-';
450 *cp++ = ch;
451 continue;
452 }
453 *cp++ = '\\';
454 *cp++ = ch;
455 }
456
457 *cp = 0;
458 return conv_str;
459 }
460
461 static char *
462 mklong(const char *str, int ch)
463 {
464 static char copy[64];
465 size_t len;
466
467 len = strlen(str) + 2;
468 if (len > sizeof copy) {
469 warnx("format %s too complex\n", str);
470 len = 4;
471 }
472 (void)memmove(copy, str, len - 3);
473 copy[len - 3] = 'j';
474 copy[len - 2] = ch;
475 copy[len - 1] = '\0';
476 return (copy);
477 }
478
479 static int
480 getchr(void)
481 {
482 if (!*gargv)
483 return ('\0');
484 return ((int)**gargv++);
485 }
486
487 static char *
488 getstr(void)
489 {
490 if (!*gargv)
491 return ("");
492 return (*gargv++);
493 }
494
495 static int
496 getwidth(void)
497 {
498 long val;
499 char *s, *ep;
500
501 s = *gargv;
502 if (!*gargv)
503 return (0);
504 gargv++;
505
506 errno = 0;
507 val = strtoul(s, &ep, 0);
508 check_conversion(s, ep);
509
510 /* Arbitrarily 'restrict' field widths to 1Mbyte */
511 if (val < 0 || val > 1 << 20) {
512 warnx("%s: invalid field width", s);
513 return 0;
514 }
515
516 return val;
517 }
518
519 static intmax_t
520 getintmax(void)
521 {
522 intmax_t val;
523 char *cp, *ep;
524
525 cp = *gargv;
526 if (cp == NULL)
527 return 0;
528 gargv++;
529
530 if (*cp == '\"' || *cp == '\'')
531 return *(cp+1);
532
533 errno = 0;
534 val = strtoimax(cp, &ep, 0);
535 check_conversion(cp, ep);
536 return val;
537 }
538
539 static uintmax_t
540 getuintmax(void)
541 {
542 uintmax_t val;
543 char *cp, *ep;
544
545 cp = *gargv;
546 if (cp == NULL)
547 return 0;
548 gargv++;
549
550 if (*cp == '\"' || *cp == '\'')
551 return *(cp+1);
552
553 /* strtoumax won't error -ve values */
554 while (isspace(*(unsigned char *)cp))
555 cp++;
556 if (*cp == '-') {
557 warnx("%s: expected positive numeric value", cp);
558 rval = 1;
559 return 0;
560 }
561
562 errno = 0;
563 val = strtoumax(cp, &ep, 0);
564 check_conversion(cp, ep);
565 return val;
566 }
567
568 static double
569 getdouble(void)
570 {
571 double val;
572 char *ep;
573
574 if (!*gargv)
575 return (0.0);
576
577 if (**gargv == '\"' || **gargv == '\'')
578 return (double) *((*gargv++)+1);
579
580 errno = 0;
581 val = strtod(*gargv, &ep);
582 check_conversion(*gargv++, ep);
583 return val;
584 }
585
586 static void
587 check_conversion(const char *s, const char *ep)
588 {
589 if (*ep) {
590 if (ep == s)
591 warnx("%s: expected numeric value", s);
592 else
593 warnx("%s: not completely converted", s);
594 rval = 1;
595 } else if (errno == ERANGE) {
596 warnx("%s: %s", s, strerror(ERANGE));
597 rval = 1;
598 }
599 }
600
601 static void
602 usage(void)
603 {
604 (void)fprintf(stderr, "usage: printf format [arg ...]\n");
605 }
606