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