Home | History | Annotate | Line # | Download | only in printf
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