xref: /netbsd-src/lib/libterminfo/tparm.c (revision 7330f729ccf0bd976a06f95fad452fe774fc7fd1)
1 /* $NetBSD: tparm.c,v 1.17 2017/05/04 09:42:23 roy Exp $ */
2 
3 /*
4  * Copyright (c) 2009, 2011, 2013 The NetBSD Foundation, Inc.
5  *
6  * This code is derived from software contributed to The NetBSD Foundation
7  * by Roy Marples.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include <sys/cdefs.h>
31 __RCSID("$NetBSD: tparm.c,v 1.17 2017/05/04 09:42:23 roy Exp $");
32 #include <sys/param.h>
33 
34 #include <assert.h>
35 #include <ctype.h>
36 #include <errno.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <term_private.h>
42 #include <term.h>
43 
44 #define LONG_STR_MAX ((CHAR_BIT * sizeof(long)) / 3)
45 #define BUFINC 128	/* Size to increament the terminal buffer by */
46 
47 #define VA_LONG_LONG	1
48 #define VA_CHAR_INT	2
49 //#define VA_CHAR_LONG	3	/* No need for this yet */
50 
51 static TERMINAL *dumbterm; /* For non thread safe functions */
52 
53 typedef struct {
54 	long nums[20];
55 	char *strings[20];
56 	size_t offset;
57 } TPSTACK;
58 
59 typedef struct {
60 	long num;
61 	char *string;
62 } TPVAR;
63 
64 static int
65 push(long num, char *string, TPSTACK *stack)
66 {
67 	if (stack->offset >= sizeof(stack->nums)) {
68 		errno = E2BIG;
69 		return -1;
70 	}
71 	stack->nums[stack->offset] = num;
72 	stack->strings[stack->offset] = string;
73 	stack->offset++;
74 	return 0;
75 }
76 
77 static int
78 pop(long *num, char **string, TPSTACK *stack)
79 {
80 	if (stack->offset == 0) {
81 		if (num)
82 			*num = 0;
83 		if (string)
84 			*string = NULL;
85 		errno = E2BIG;
86 		return -1;
87 	}
88 	stack->offset--;
89 	if (num)
90 		*num = stack->nums[stack->offset];
91 	if (string)
92 		*string = stack->strings[stack->offset];
93 	return 0;
94 }
95 
96 static char *
97 checkbuf(TERMINAL *term, size_t len)
98 {
99 	char *buf;
100 
101 	if (term->_bufpos + len >= term->_buflen) {
102 		len = term->_buflen + MAX(len, BUFINC);
103 		buf = realloc(term->_buf, len);
104 		if (buf == NULL)
105 			return NULL;
106 		term->_buf = buf;
107 		term->_buflen = len;
108 	}
109 	return term->_buf;
110 }
111 
112 static size_t
113 ochar(TERMINAL *term, int c)
114 {
115 	if (c == 0)
116 		c = 0200;
117 	/* Check we have space and a terminator */
118 	if (checkbuf(term, 2) == NULL)
119 		return 0;
120 	term->_buf[term->_bufpos++] = (char)c;
121 	return 1;
122 }
123 
124 static size_t
125 onum(TERMINAL *term, const char *fmt, int num, size_t len)
126 {
127 	int l;
128 	size_t r;
129 
130 	if (len < LONG_STR_MAX)
131 		len = LONG_STR_MAX;
132 	if (checkbuf(term, len + 2) == NULL)
133 		return 0;
134 	l = snprintf(term->_buf + term->_bufpos, len + 2, fmt, num);
135 	if (l == -1)
136 		return 0;
137 	r = (size_t)l;
138 	term->_bufpos += r;
139 	return r;
140 }
141 
142 /*
143   Make a pass through the string so we can work out
144   which parameters are ints and which are char *.
145   Basically we only use char * if %p[1-9] is followed by %l or %s.
146 */
147 int
148 _ti_parm_analyse(const char *str, int *piss, int piss_len)
149 {
150 	int nparm, lpop;
151 	char c;
152 
153 	nparm = 0;
154 	lpop = -1;
155 	while ((c = *str++) != '\0') {
156 		if (c != '%')
157 			continue;
158 		c = *str++;
159 		switch (c) {
160 			case 'l': /* FALLTHROUGH */
161 			case 's':
162 				if (lpop > 0) {
163 					if (lpop <= piss_len)
164 						piss[lpop - 1] = 1;
165 					else if (piss)
166 						errno = E2BIG;
167 				}
168 				break;
169 			case 'p':
170 				c = *str++;
171 				if (c < '1' || c > '9') {
172 					errno = EINVAL;
173 					continue;
174 				} else {
175 					lpop = c - '0';
176 					if (lpop > nparm)
177 						nparm = lpop;
178 				}
179 				break;
180 			default:
181 				lpop = -1;
182 		}
183 	}
184 
185 	return nparm;
186 }
187 
188 static char *
189 _ti_tiparm(TERMINAL *term, const char *str, int va_type, va_list parms)
190 {
191 	char c, fmt[64], *fp, *ostr;
192 	long val, val2;
193 	long dnums[26]; /* dynamic variables a-z, not preserved */
194 	size_t l, max, width, precision, olen;
195 	TPSTACK stack;
196 	TPVAR params[TPARM_MAX];
197 	unsigned int done, dot, minus;
198 	int piss[TPARM_MAX]; /* Parameter IS String - piss ;) */
199 
200 	if (str == NULL)
201 		return NULL;
202 
203 	/*
204 	  If not passed a terminal, malloc a dummy one.
205 	  This means we can preserve buffers and variables per terminal and
206 	  still work with non thread safe functions (which sadly are still the
207 	  norm and standard).
208 	*/
209 	if (term == NULL) {
210 		if (dumbterm == NULL) {
211 			dumbterm = malloc(sizeof(*dumbterm));
212 			if (dumbterm == NULL)
213 				return NULL;
214 			dumbterm->_buflen = 0;
215 		}
216 		term = dumbterm;
217 	}
218 
219 	term->_bufpos = 0;
220 	/* Ensure we have an initial buffer */
221 	if (term->_buflen == 0) {
222 		term->_buf = malloc(BUFINC);
223 		if (term->_buf == NULL)
224 			return NULL;
225 		term->_buflen = BUFINC;
226 	}
227 
228 	memset(&piss, 0, sizeof(piss));
229 	max = (size_t)_ti_parm_analyse(str, piss, TPARM_MAX);
230 
231 	/* Put our parameters into variables */
232 	memset(&params, 0, sizeof(params));
233 	for (l = 0; l < max; l++) {
234 		if (piss[l]) {
235 			if (va_type == VA_LONG_LONG) {
236 				/* This only works if char * fits into a long
237 				 * on this platform. */
238 				if (sizeof(char *) <= sizeof(long)/*CONSTCOND*/)
239 					params[l].string =
240 					    (char *)va_arg(parms, long);
241 				else {
242 					errno = ENOTSUP;
243 					return NULL;
244 				}
245 			} else
246 				params[l].string = va_arg(parms, char *);
247 		} else {
248 			if (va_type == VA_CHAR_INT)
249 				params[l].num = (long)va_arg(parms, int);
250 			else
251 				params[l].num = va_arg(parms, long);
252 		}
253 	}
254 
255 	memset(&stack, 0, sizeof(stack));
256 	while ((c = *str++) != '\0') {
257 		if (c != '%' || (c = *str++) == '%') {
258 			if (c == '\0')
259 				break;
260 			if (ochar(term, c) == 0)
261 				return NULL;
262 			continue;
263 		}
264 
265 		/* Handle formatting. */
266 		fp = fmt;
267 		*fp++ = '%';
268 		done = dot = minus = width = precision = 0;
269 		val = 0;
270 		while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) {
271 			switch (c) {
272 			case 'c': /* FALLTHROUGH */
273 			case 's':
274 				*fp++ = c;
275 				done = 1;
276 				break;
277 			case 'd': /* FALLTHROUGH */
278 			case 'o': /* FALLTHROUGH */
279 			case 'x': /* FALLTHROUGH */
280 			case 'X': /* FALLTHROUGH */
281 				*fp++ = 'l';
282 				*fp++ = c;
283 				done = 1;
284 				break;
285 			case '#': /* FALLTHROUGH */
286 			case ' ':
287 				*fp++ = c;
288 				break;
289 			case '.':
290 				*fp++ = c;
291 				if (dot == 0) {
292 					dot = 1;
293 					width = (size_t)val;
294 				} else
295 					done = 2;
296 				val = 0;
297 				break;
298 			case ':':
299 				minus = 1;
300 				break;
301 			case '-':
302 				if (minus)
303 					*fp++ = c;
304 				else
305 					done = 1;
306 				break;
307 			default:
308 				if (isdigit((unsigned char)c)) {
309 					val = (val * 10) + (c - '0');
310 					if (val > 10000)
311 						done = 2;
312 					else
313 						*fp++ = c;
314 				} else
315 					done = 1;
316 			}
317 			if (done == 0)
318 				c = *str++;
319 		}
320 		if (done == 2) {
321 			/* Found an error in the format */
322 			fp = fmt + 1;
323 			*fp = *str;
324 			olen = 0;
325 		} else {
326 			if (dot == 0)
327 				width = (size_t)val;
328 			else
329 				precision = (size_t)val;
330 			olen = MAX(width, precision);
331 		}
332 		*fp++ = '\0';
333 
334 		/* Handle commands */
335 		switch (c) {
336 		case 'c':
337 			pop(&val, NULL, &stack);
338 			if (ochar(term, (unsigned char)val) == 0)
339 				return NULL;
340 			break;
341 		case 's':
342 			pop(NULL, &ostr, &stack);
343 			if (ostr != NULL) {
344 				int r;
345 
346 				l = strlen(ostr);
347 				if (l < (size_t)olen)
348 					l = olen;
349 				if (checkbuf(term, (size_t)(l + 1)) == NULL)
350 					return NULL;
351 				r = snprintf(term->_buf + term->_bufpos, l + 1,
352 				    fmt, ostr);
353 				if (r != -1)
354 					term->_bufpos += (size_t)r;
355 			}
356 			break;
357 		case 'l':
358 			pop(NULL, &ostr, &stack);
359 			if (ostr == NULL)
360 				l = 0;
361 			else
362 				l = strlen(ostr);
363 #ifdef NCURSES_COMPAT_57
364 			if (onum(term, "%ld", (long)l, 0) == 0)
365 				return NULL;
366 #else
367 			push((long)l, NULL, &stack);
368 #endif
369 			break;
370 		case 'd': /* FALLTHROUGH */
371 		case 'o': /* FALLTHROUGH */
372 		case 'x': /* FALLTHROUGH */
373 		case 'X':
374 			pop(&val, NULL, &stack);
375 			if (onum(term, fmt, (int)val, olen) == 0)
376 				return NULL;
377 			break;
378 		case 'p':
379 			if (*str < '1' || *str > '9')
380 				break;
381 			l = (size_t)(*str++ - '1');
382 			if (push(params[l].num, params[l].string, &stack))
383 				return NULL;
384 			break;
385 		case 'P':
386 			pop(&val, NULL, &stack);
387 			if (*str >= 'a' && *str <= 'z')
388 				dnums[*str - 'a'] = val;
389 			else if (*str >= 'A' && *str <= 'Z')
390 				term->_snums[*str - 'A'] = val;
391 			break;
392 		case 'g':
393 			if (*str >= 'a' && *str <= 'z') {
394 				if (push(dnums[*str - 'a'], NULL, &stack))
395 					return NULL;
396 			} else if (*str >= 'A' && *str <= 'Z') {
397 				if (push(term->_snums[*str - 'A'],
398 					NULL, &stack))
399 					return NULL;
400 			}
401 			break;
402 		case 'i':
403 			if (piss[0] == 0)
404 				params[0].num++;
405 			if (piss[1] == 0)
406 				params[1].num++;
407 			break;
408 		case '\'':
409 			if (push((long)(unsigned char)*str++, NULL, &stack))
410 				return NULL;
411 			while (*str != '\0' && *str != '\'')
412 				str++;
413 			if (*str == '\'')
414 				str++;
415 			break;
416 		case '{':
417 			val = 0;
418 			for (; isdigit((unsigned char)*str);  str++)
419 				val = (val * 10) + (*str - '0');
420 			if (push(val, NULL, &stack))
421 				return NULL;
422 			while (*str != '\0' && *str != '}')
423 				str++;
424 			if (*str == '}')
425 				str++;
426 			break;
427 		case '+': /* FALLTHROUGH */
428 		case '-': /* FALLTHROUGH */
429 		case '*': /* FALLTHROUGH */
430 		case '/': /* FALLTHROUGH */
431 		case 'm': /* FALLTHROUGH */
432 		case 'A': /* FALLTHROUGH */
433 		case 'O': /* FALLTHROUGH */
434 		case '&': /* FALLTHROUGH */
435 		case '|': /* FALLTHROUGH */
436 		case '^': /* FALLTHROUGH */
437 		case '=': /* FALLTHROUGH */
438 		case '<': /* FALLTHROUGH */
439 		case '>':
440 			pop(&val, NULL, &stack);
441 			pop(&val2, NULL, &stack);
442 			switch (c) {
443 			case '+':
444 				val = val + val2;
445 				break;
446 			case '-':
447 				val = val2 - val;
448 				break;
449 			case '*':
450 				val = val * val2;
451 				break;
452 			case '/':
453 				val = val ? val2 / val : 0;
454 				break;
455 			case 'm':
456 				val = val ? val2 % val : 0;
457 				break;
458 			case 'A':
459 				val = val && val2;
460 				break;
461 			case 'O':
462 				val = val || val2;
463 				break;
464 			case '&':
465 				val = val & val2;
466 				break;
467 			case '|':
468 				val = val | val2;
469 				break;
470 			case '^':
471 				val = val ^ val2;
472 				break;
473 			case '=':
474 				val = val == val2;
475 				break;
476 			case '<':
477 				val = val2 < val;
478 				break;
479 			case '>':
480 				val = val2 > val;
481 				break;
482 			}
483 			if (push(val, NULL, &stack))
484 				return NULL;
485 			break;
486 		case '!':
487 		case '~':
488 			pop(&val, NULL, &stack);
489 			switch (c) {
490 			case '!':
491 				val = !val;
492 				break;
493 			case '~':
494 				val = ~val;
495 				break;
496 			}
497 			if (push(val, NULL, &stack))
498 				return NULL;
499 			break;
500 		case '?': /* if */
501 			break;
502 		case 't': /* then */
503 			pop(&val, NULL, &stack);
504 			if (val == 0) {
505 				l = 0;
506 				for (; *str != '\0'; str++) {
507 					if (*str != '%')
508 						continue;
509 					str++;
510 					if (*str == '?')
511 						l++;
512 					else if (*str == ';') {
513 						if (l > 0)
514 							l--;
515 						else {
516 							str++;
517 							break;
518 						}
519 					} else if (*str == 'e' && l == 0) {
520 						str++;
521 						break;
522 					}
523 				}
524 			}
525 			break;
526 		case 'e': /* else */
527 			l = 0;
528 			for (; *str != '\0'; str++) {
529 				if (*str != '%')
530 					continue;
531 				str++;
532 				if (*str == '?')
533 					l++;
534 				else if (*str == ';') {
535 					if (l > 0)
536 						l--;
537 					else {
538 						str++;
539 						break;
540 					}
541 				}
542 			}
543 			break;
544 		case ';': /* fi */
545 			break;
546 		}
547 	}
548 	term->_buf[term->_bufpos] = '\0';
549 	return term->_buf;
550 }
551 
552 char *
553 ti_tiparm(TERMINAL *term, const char *str, ...)
554 {
555 	va_list va;
556 	char *ret;
557 
558 	_DIAGASSERT(term != NULL);
559 	_DIAGASSERT(str != NULL);
560 
561 	va_start(va, str);
562 	ret = _ti_tiparm(term, str, VA_CHAR_INT, va);
563 	va_end(va);
564 	return ret;
565 }
566 
567 char *
568 tiparm(const char *str, ...)
569 {
570 	va_list va;
571 	char *ret;
572 
573 	_DIAGASSERT(str != NULL);
574 
575 	va_start(va, str);
576 	ret = _ti_tiparm(NULL, str, VA_CHAR_INT, va);
577 	va_end(va);
578 	return ret;
579 }
580 
581 #ifdef VA_CHAR_LONG
582 char *
583 ti_tlparm(TERMINAL *term, const char *str, ...)
584 {
585 	va_list va;
586 	char *ret;
587 
588 	_DIAGASSERT(term != NULL);
589 	_DIAGASSERT(str != NULL);
590 
591 	va_start(va, str);
592 	ret = _ti_tiparm(term, str, VA_CHAR_LONG, va);
593 	va_end(va);
594 	return ret;
595 }
596 
597 char *
598 tlparm(const char *str, ...)
599 {
600 	va_list va;
601 	char *ret;
602 
603 	_DIAGASSERT(str != NULL);
604 
605 	va_start(va, str);
606 	ret = _ti_tiparm(NULL, str, VA_CHAR_LONG, va);
607 	va_end(va);
608 	return ret;
609 }
610 #endif
611 
612 static char *
613 _tparm(const char *str, ...)
614 {
615 	va_list va;
616 	char *ret;
617 
618 	_DIAGASSERT(str != NULL);
619 
620 	va_start(va, str);
621 	ret = _ti_tiparm(NULL, str, VA_LONG_LONG, va);
622 	va_end(va);
623 	return ret;
624 }
625 
626 char *
627 tparm(const char *str,
628     long p1, long p2, long p3, long p4, long p5,
629     long p6, long p7, long p8, long p9)
630 {
631 
632 	return _tparm(str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
633 }
634