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