xref: /openbsd-src/usr.bin/printf/printf.c (revision 7bbe964f6b7d22ad07ca46292495604f942eba4e)
1 /*	$OpenBSD: printf.c,v 1.17 2009/10/27 23:59:41 deraadt Exp $	*/
2 
3 /*
4  * Copyright (c) 1989 The Regents of the University of California.
5  * 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. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <ctype.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <limits.h>
37 #include <locale.h>
38 #include <errno.h>
39 #include <err.h>
40 
41 static int	 print_escape_str(const char *);
42 static int	 print_escape(const char *);
43 
44 static int	 getchr(void);
45 static double	 getdouble(void);
46 static int	 getint(void);
47 static long	 getlong(void);
48 static unsigned long getulong(void);
49 static char	*getstr(void);
50 static char	*mklong(const char *, int);
51 static void      check_conversion(const char *, const char *);
52 static void	 usage(void);
53 
54 static int	rval;
55 static char  **gargv;
56 
57 #define isodigit(c)	((c) >= '0' && (c) <= '7')
58 #define octtobin(c)	((c) - '0')
59 #define hextobin(c)	((c) >= 'A' && (c) <= 'F' ? c - 'A' + 10 : (c) >= 'a' && (c) <= 'f' ? c - 'a' + 10 : c - '0')
60 
61 #define PF(f, func) { \
62 	if (fieldwidth) \
63 		if (precision) \
64 			(void)printf(f, fieldwidth, precision, func); \
65 		else \
66 			(void)printf(f, fieldwidth, func); \
67 	else if (precision) \
68 		(void)printf(f, precision, func); \
69 	else \
70 		(void)printf(f, func); \
71 }
72 
73 int
74 main(int argc, char *argv[])
75 {
76 	char *fmt, *start;
77 	int fieldwidth, precision;
78 	char convch, nextch;
79 	char *format;
80 
81 	setlocale (LC_ALL, "");
82 
83 	/* Need to accept/ignore "--" option. */
84 	if (argc > 1 && strcmp(argv[1], "--") == 0) {
85 		argc--;
86 		argv++;
87 	}
88 
89 	if (argc < 2) {
90 		usage();
91 		return (1);
92 	}
93 
94 	format = *++argv;
95 	gargv = ++argv;
96 
97 #define SKIP1	"#-+ 0"
98 #define SKIP2	"0123456789"
99 	do {
100 		/*
101 		 * Basic algorithm is to scan the format string for conversion
102 		 * specifications -- once one is found, find out if the field
103 		 * width or precision is a '*'; if it is, gather up value.
104 		 * Note, format strings are reused as necessary to use up the
105 		 * provided arguments, arguments of zero/null string are
106 		 * provided to use up the format string.
107 		 */
108 
109 		/* find next format specification */
110 		for (fmt = format; *fmt; fmt++) {
111 			switch (*fmt) {
112 			case '%':
113 				start = fmt++;
114 
115 				if (*fmt == '%') {
116 					putchar ('%');
117 					break;
118 				} else if (*fmt == 'b') {
119 					char *p = getstr();
120 					if (print_escape_str(p)) {
121 						return (rval);
122 					}
123 					break;
124 				}
125 
126 				/* skip to field width */
127 				for (; strchr(SKIP1, *fmt); ++fmt)
128 					;
129 				if (*fmt == '*') {
130 					++fmt;
131 					fieldwidth = getint();
132 				} else
133 					fieldwidth = 0;
134 
135 				/* skip to field precision */
136 				for (; strchr(SKIP2, *fmt); ++fmt)
137 					;
138 				precision = 0;
139 				if (*fmt == '.') {
140 					++fmt;
141 					if (*fmt == '*') {
142 						++fmt;
143 						precision = getint();
144 					}
145 					for (; strchr(SKIP2, *fmt); ++fmt)
146 						;
147 				}
148 
149 				if (!*fmt) {
150 					warnx ("missing format character");
151 					return(1);
152 				}
153 
154 				convch = *fmt;
155 				nextch = *(fmt + 1);
156 				*(fmt + 1) = '\0';
157 				switch(convch) {
158 				case 'c': {
159 					char p = getchr();
160 					PF(start, p);
161 					break;
162 				}
163 				case 's': {
164 					char *p = getstr();
165 					PF(start, p);
166 					break;
167 				}
168 				case 'd':
169 				case 'i': {
170 					long p;
171 					char *f = mklong(start, convch);
172 					if (!f) {
173 						warnx("out of memory");
174 						return (1);
175 					}
176 					p = getlong();
177 					PF(f, p);
178 					break;
179 				}
180 				case 'o':
181 				case 'u':
182 				case 'x':
183 				case 'X': {
184 					unsigned long p;
185 					char *f = mklong(start, convch);
186 					if (!f) {
187 						warnx("out of memory");
188 						return (1);
189 					}
190 					p = getulong();
191 					PF(f, p);
192 					break;
193 				}
194 				case 'a':
195 				case 'A':
196 				case 'e':
197 				case 'E':
198 				case 'f':
199 				case 'F':
200 				case 'g':
201 				case 'G': {
202 					double p = getdouble();
203 					PF(start, p);
204 					break;
205 				}
206 				default:
207 					warnx ("%s: invalid directive", start);
208 					return(1);
209 				}
210 				*(fmt + 1) = nextch;
211 				break;
212 
213 			case '\\':
214 				fmt += print_escape(fmt);
215 				break;
216 
217 			default:
218 				putchar (*fmt);
219 				break;
220 			}
221 		}
222 	} while (gargv > argv && *gargv);
223 
224 	return (rval);
225 }
226 
227 
228 /*
229  * Print SysV echo(1) style escape string
230  *	Halts processing string and returns 1 if a \c escape is encountered.
231  */
232 static int
233 print_escape_str(const char *str)
234 {
235 	int value;
236 	int c;
237 
238 	while (*str) {
239 		if (*str == '\\') {
240 			str++;
241 			/*
242 			 * %b string octal constants are not like those in C.
243 			 * They start with a \0, and are followed by 0, 1, 2,
244 			 * or 3 octal digits.
245 			 */
246 			if (*str == '0') {
247 				str++;
248 				for (c = 3, value = 0; c-- && isodigit(*str); str++) {
249 					value <<= 3;
250 					value += octtobin(*str);
251 				}
252 				putchar (value);
253 				str--;
254 			} else if (*str == 'c') {
255 				return 1;
256 			} else {
257 				str--;
258 				str += print_escape(str);
259 			}
260 		} else {
261 			putchar (*str);
262 		}
263 		str++;
264 	}
265 
266 	return 0;
267 }
268 
269 /*
270  * Print "standard" escape characters
271  */
272 static int
273 print_escape(const char *str)
274 {
275 	const char *start = str;
276 	int value;
277 	int c;
278 
279 	str++;
280 
281 	switch (*str) {
282 	case '0': case '1': case '2': case '3':
283 	case '4': case '5': case '6': case '7':
284 		for (c = 3, value = 0; c-- && isodigit(*str); str++) {
285 			value <<= 3;
286 			value += octtobin(*str);
287 		}
288 		putchar(value);
289 		return str - start - 1;
290 		/* NOTREACHED */
291 
292 	case 'x':
293 		str++;
294 		for (value = 0; isxdigit(*str); str++) {
295 			value <<= 4;
296 			value += hextobin(*str);
297 		}
298 		if (value > UCHAR_MAX) {
299 			warnx ("escape sequence out of range for character");
300 			rval = 1;
301 		}
302 		putchar (value);
303 		return str - start - 1;
304 		/* NOTREACHED */
305 
306 	case '\\':			/* backslash */
307 		putchar('\\');
308 		break;
309 
310 	case '\'':			/* single quote */
311 		putchar('\'');
312 		break;
313 
314 	case '"':			/* double quote */
315 		putchar('"');
316 		break;
317 
318 	case 'a':			/* alert */
319 		putchar('\a');
320 		break;
321 
322 	case 'b':			/* backspace */
323 		putchar('\b');
324 		break;
325 
326 	case 'e':			/* escape */
327 #ifdef __GNUC__
328 		putchar('\e');
329 #else
330 		putchar(033);
331 #endif
332 		break;
333 
334 	case 'f':			/* form-feed */
335 		putchar('\f');
336 		break;
337 
338 	case 'n':			/* newline */
339 		putchar('\n');
340 		break;
341 
342 	case 'r':			/* carriage-return */
343 		putchar('\r');
344 		break;
345 
346 	case 't':			/* tab */
347 		putchar('\t');
348 		break;
349 
350 	case 'v':			/* vertical-tab */
351 		putchar('\v');
352 		break;
353 
354 	default:
355 		putchar(*str);
356 		warnx("unknown escape sequence `\\%c'", *str);
357 		rval = 1;
358 	}
359 
360 	return 1;
361 }
362 
363 static char *
364 mklong(const char *str, int ch)
365 {
366 	static char *copy;
367 	static int copysize;
368 	int len;
369 
370 	len = strlen(str) + 2;
371 	if (copysize < len) {
372 		char *newcopy;
373 		copysize = len + 256;
374 
375 		newcopy = realloc(copy, copysize);
376 		if (newcopy == NULL) {
377 			copysize = 0;
378 			free(copy);
379 			copy = NULL;
380 			return (NULL);
381 		}
382 		copy = newcopy;
383 	}
384 	(void) memmove(copy, str, len - 3);
385 	copy[len - 3] = 'l';
386 	copy[len - 2] = ch;
387 	copy[len - 1] = '\0';
388 	return (copy);
389 }
390 
391 static int
392 getchr(void)
393 {
394 	if (!*gargv)
395 		return((int)'\0');
396 	return((int)**gargv++);
397 }
398 
399 static char *
400 getstr(void)
401 {
402 	if (!*gargv)
403 		return("");
404 	return(*gargv++);
405 }
406 
407 static char *number = "+-.0123456789";
408 static int
409 getint(void)
410 {
411 	if (!*gargv)
412 		return(0);
413 
414 	if (strchr(number, **gargv))
415 		return(atoi(*gargv++));
416 
417 	return 0;
418 }
419 
420 static long
421 getlong(void)
422 {
423 	long val;
424 	char *ep;
425 
426 	if (!*gargv)
427 		return(0L);
428 
429 	if (**gargv == '\"' || **gargv == '\'')
430 		return (long) *((*gargv++)+1);
431 
432 	errno = 0;
433 	val = strtol (*gargv, &ep, 0);
434 	check_conversion(*gargv++, ep);
435 	return val;
436 }
437 
438 static unsigned long
439 getulong(void)
440 {
441 	unsigned long val;
442 	char *ep;
443 
444 	if (!*gargv)
445 		return(0UL);
446 
447 	if (**gargv == '\"' || **gargv == '\'')
448 		return (unsigned long) *((*gargv++)+1);
449 
450 	errno = 0;
451 	val = strtoul (*gargv, &ep, 0);
452 	check_conversion(*gargv++, ep);
453 	return val;
454 }
455 
456 static double
457 getdouble(void)
458 {
459 	double val;
460 	char *ep;
461 
462 	if (!*gargv)
463 		return(0.0);
464 
465 	if (**gargv == '\"' || **gargv == '\'')
466 		return (double) *((*gargv++)+1);
467 
468 	errno = 0;
469 	val = strtod (*gargv, &ep);
470 	check_conversion(*gargv++, ep);
471 	return val;
472 }
473 
474 static void
475 check_conversion(const char *s, const char *ep)
476 {
477 	if (*ep) {
478 		if (ep == s)
479 			warnx ("%s: expected numeric value", s);
480 		else
481 			warnx ("%s: not completely converted", s);
482 		rval = 1;
483 	} else if (errno == ERANGE) {
484 		warnx ("%s: %s", s, strerror(ERANGE));
485 		rval = 1;
486 	}
487 }
488 
489 static void
490 usage(void)
491 {
492 	(void)fprintf(stderr, "usage: printf format [arg ...]\n");
493 }
494