xref: /openbsd-src/usr.bin/printf/printf.c (revision 91f110e064cd7c194e59e019b83bb7496c1c84d4)
1 /*	$OpenBSD: printf.c,v 1.19 2013/11/20 20:46:47 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((unsigned char)*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 	case '\0':
355 		warnx("null escape sequence");
356 		rval = 1;
357 		return 0;
358 
359 	default:
360 		putchar(*str);
361 		warnx("unknown escape sequence `\\%c'", *str);
362 		rval = 1;
363 	}
364 
365 	return 1;
366 }
367 
368 static char *
369 mklong(const char *str, int ch)
370 {
371 	static char *copy;
372 	static int copysize;
373 	int len;
374 
375 	len = strlen(str) + 2;
376 	if (copysize < len) {
377 		char *newcopy;
378 		copysize = len + 256;
379 
380 		newcopy = realloc(copy, copysize);
381 		if (newcopy == NULL) {
382 			copysize = 0;
383 			free(copy);
384 			copy = NULL;
385 			return (NULL);
386 		}
387 		copy = newcopy;
388 	}
389 	(void) memmove(copy, str, len - 3);
390 	copy[len - 3] = 'l';
391 	copy[len - 2] = ch;
392 	copy[len - 1] = '\0';
393 	return (copy);
394 }
395 
396 static int
397 getchr(void)
398 {
399 	if (!*gargv)
400 		return((int)'\0');
401 	return((int)**gargv++);
402 }
403 
404 static char *
405 getstr(void)
406 {
407 	if (!*gargv)
408 		return("");
409 	return(*gargv++);
410 }
411 
412 static char *number = "+-.0123456789";
413 static int
414 getint(void)
415 {
416 	if (!*gargv)
417 		return(0);
418 
419 	if (strchr(number, **gargv))
420 		return(atoi(*gargv++));
421 
422 	return 0;
423 }
424 
425 static long
426 getlong(void)
427 {
428 	long val;
429 	char *ep;
430 
431 	if (!*gargv)
432 		return(0L);
433 
434 	if (**gargv == '\"' || **gargv == '\'')
435 		return (long) *((*gargv++)+1);
436 
437 	errno = 0;
438 	val = strtol (*gargv, &ep, 0);
439 	check_conversion(*gargv++, ep);
440 	return val;
441 }
442 
443 static unsigned long
444 getulong(void)
445 {
446 	unsigned long val;
447 	char *ep;
448 
449 	if (!*gargv)
450 		return(0UL);
451 
452 	if (**gargv == '\"' || **gargv == '\'')
453 		return (unsigned long) *((*gargv++)+1);
454 
455 	errno = 0;
456 	val = strtoul (*gargv, &ep, 0);
457 	check_conversion(*gargv++, ep);
458 	return val;
459 }
460 
461 static double
462 getdouble(void)
463 {
464 	double val;
465 	char *ep;
466 
467 	if (!*gargv)
468 		return(0.0);
469 
470 	if (**gargv == '\"' || **gargv == '\'')
471 		return (double) *((*gargv++)+1);
472 
473 	errno = 0;
474 	val = strtod (*gargv, &ep);
475 	check_conversion(*gargv++, ep);
476 	return val;
477 }
478 
479 static void
480 check_conversion(const char *s, const char *ep)
481 {
482 	if (*ep) {
483 		if (ep == s)
484 			warnx ("%s: expected numeric value", s);
485 		else
486 			warnx ("%s: not completely converted", s);
487 		rval = 1;
488 	} else if (errno == ERANGE) {
489 		warnx ("%s: %s", s, strerror(ERANGE));
490 		rval = 1;
491 	}
492 }
493 
494 static void
495 usage(void)
496 {
497 	(void)fprintf(stderr, "usage: printf format [arg ...]\n");
498 }
499