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