xref: /openbsd-src/usr.bin/printf/printf.c (revision c71db3fd5aea3ba7ba6daf4b666a5657f0cef740)
1 /*	$OpenBSD: printf.c,v 1.28 2024/08/26 03:49:06 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 <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 = 0;
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; 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 (c = 2; c-- && isxdigit((unsigned char)*str); str++) {
297 			value <<= 4;
298 			value += hextobin(*str);
299 		}
300 		putchar(value);
301 		return str - start - 1;
302 		/* NOTREACHED */
303 
304 	case '\\':			/* backslash */
305 		putchar('\\');
306 		break;
307 
308 	case '\'':			/* single quote */
309 		putchar('\'');
310 		break;
311 
312 	case '"':			/* double quote */
313 		putchar('"');
314 		break;
315 
316 	case 'a':			/* alert */
317 		putchar('\a');
318 		break;
319 
320 	case 'b':			/* backspace */
321 		putchar('\b');
322 		break;
323 
324 	case 'e':			/* escape */
325 #ifdef __GNUC__
326 		putchar('\e');
327 #else
328 		putchar(033);
329 #endif
330 		break;
331 
332 	case 'f':			/* form-feed */
333 		putchar('\f');
334 		break;
335 
336 	case 'n':			/* newline */
337 		putchar('\n');
338 		break;
339 
340 	case 'r':			/* carriage-return */
341 		putchar('\r');
342 		break;
343 
344 	case 't':			/* tab */
345 		putchar('\t');
346 		break;
347 
348 	case 'v':			/* vertical-tab */
349 		putchar('\v');
350 		break;
351 
352 	case '\0':
353 		warnx("null escape sequence");
354 		rval = 1;
355 		return 0;
356 
357 	default:
358 		putchar(*str);
359 		warnx("unknown escape sequence `\\%c'", *str);
360 		rval = 1;
361 	}
362 
363 	return 1;
364 }
365 
366 static char *
367 mklong(const char *str, int ch)
368 {
369 	static char *copy;
370 	static int copysize;
371 	int len;
372 
373 	len = strlen(str) + 2;
374 	if (copysize < len) {
375 		char *newcopy;
376 		copysize = len + 256;
377 
378 		newcopy = realloc(copy, copysize);
379 		if (newcopy == NULL) {
380 			copysize = 0;
381 			free(copy);
382 			copy = NULL;
383 			return (NULL);
384 		}
385 		copy = newcopy;
386 	}
387 	(void) memmove(copy, str, len - 3);
388 	copy[len - 3] = 'l';
389 	copy[len - 2] = ch;
390 	copy[len - 1] = '\0';
391 	return (copy);
392 }
393 
394 static int
395 getchr(void)
396 {
397 	if (!*gargv)
398 		return((int)'\0');
399 	return((int)**gargv++);
400 }
401 
402 static char *
403 getstr(void)
404 {
405 	if (!*gargv)
406 		return("");
407 	return(*gargv++);
408 }
409 
410 static char *number = "+-.0123456789";
411 static int
412 getint(void)
413 {
414 	if (!*gargv)
415 		return(0);
416 
417 	if (strchr(number, **gargv))
418 		return(atoi(*gargv++));
419 
420 	return 0;
421 }
422 
423 static long
424 getlong(void)
425 {
426 	long val;
427 	char *ep;
428 
429 	if (!*gargv)
430 		return(0L);
431 
432 	if (**gargv == '\"' || **gargv == '\'')
433 		return (unsigned char) *((*gargv++)+1);
434 
435 	errno = 0;
436 	val = strtol(*gargv, &ep, 0);
437 	check_conversion(*gargv++, ep);
438 	return val;
439 }
440 
441 static unsigned long
442 getulong(void)
443 {
444 	unsigned long val;
445 	char *ep;
446 
447 	if (!*gargv)
448 		return(0UL);
449 
450 	if (**gargv == '\"' || **gargv == '\'')
451 		return (unsigned char) *((*gargv++)+1);
452 
453 	errno = 0;
454 	val = strtoul(*gargv, &ep, 0);
455 	check_conversion(*gargv++, ep);
456 	return val;
457 }
458 
459 static double
460 getdouble(void)
461 {
462 	double val;
463 	char *ep;
464 
465 	if (!*gargv)
466 		return(0.0);
467 
468 	if (**gargv == '\"' || **gargv == '\'')
469 		return (unsigned char) *((*gargv++)+1);
470 
471 	errno = 0;
472 	val = strtod(*gargv, &ep);
473 	check_conversion(*gargv++, ep);
474 	return val;
475 }
476 
477 static void
478 check_conversion(const char *s, const char *ep)
479 {
480 	if (*ep) {
481 		if (ep == s)
482 			warnx("%s: expected numeric value", s);
483 		else
484 			warnx("%s: not completely converted", s);
485 		rval = 1;
486 	} else if (errno == ERANGE) {
487 		warnc(ERANGE, "%s", s);
488 		rval = 1;
489 	}
490 }
491 
492 static void __dead
493 usage(void)
494 {
495 	(void)fprintf(stderr, "usage: printf format [argument ...]\n");
496 	exit(1);
497 }
498