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