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