xref: /netbsd-src/lib/libterminfo/tparm.c (revision 4fee23f98c45552038ad6b5bd05124a41302fb01)
1 /* $NetBSD: tparm.c,v 1.5 2011/03/10 13:39:26 roy Exp $ */
2 
3 /*
4  * Copyright (c) 2009, 2011 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.5 2011/03/10 13:39:26 roy Exp $");
32 
33 #include <assert.h>
34 #include <ctype.h>
35 #include <errno.h>
36 #include <stdarg.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <term_private.h>
41 #include <term.h>
42 
43 static TERMINAL *dumbterm; /* For non thread safe functions */
44 
45 typedef struct {
46 	long nums[20];
47 	char *strings[20];
48 	size_t offset;
49 } TPSTACK;
50 
51 typedef struct {
52 	long num;
53 	char *string;
54 } TPVAR;
55 
56 static int
57 push(long num, char *string, TPSTACK *stack)
58 {
59 	if (stack->offset > sizeof(stack->nums)) {
60 		errno = E2BIG;
61 		return -1;
62 	}
63 	stack->nums[stack->offset] = num;
64 	stack->strings[stack->offset] = string;
65 	stack->offset++;
66 	return 0;
67 }
68 
69 static int
70 pop(long *num, char **string, TPSTACK *stack)
71 {
72 	if (stack->offset == 0) {
73 		if (num)
74 			*num = 0;
75 		if (string)
76 			*string = NULL;
77 		errno = E2BIG;
78 		return -1;
79 	}
80 	stack->offset--;
81 	if (num)
82 		*num = stack->nums[stack->offset];
83 	if (string)
84 		*string = stack->strings[stack->offset];
85 	return 0;
86 }
87 
88 static char *
89 checkbuf(TERMINAL *term, size_t len)
90 {
91 	char *buf;
92 
93 	if (term->_bufpos + len >= term->_buflen) {
94 		len = term->_buflen + BUFSIZ;
95 		buf = realloc(term->_buf, len);
96 		if (buf == NULL)
97 			return 0;
98 		term->_buf = buf;
99 		term->_buflen = len;
100 	}
101 	return term->_buf;
102 }
103 
104 static size_t
105 ochar(TERMINAL *term, int c)
106 {
107 	if (c == 0)
108 		c = 0200;
109 	/* Check we have space and a terminator */
110 	if (checkbuf(term, 2) == NULL)
111 		return 0;
112 	term->_buf[term->_bufpos++] = (char)c;
113 	return 1;
114 }
115 
116 static size_t
117 onum(TERMINAL *term, const char *fmt, long num, int len)
118 {
119 	size_t l;
120 
121 	/* Assume we never have natural number longer than 64 chars */
122 	if (len < 64)
123 		len = 64;
124 	if (checkbuf(term, (size_t)len + 1) == NULL)
125 		return 0;
126 	l = sprintf(term->_buf + term->_bufpos, fmt, num);
127 	term->_bufpos += l;
128 	return l;
129 }
130 
131 static char *
132 _ti_vtparm(TERMINAL *term, const char *str, va_list parms)
133 {
134 	const char *sp;
135 	char c, fmt[64], *fp, *ostr;
136 	long val, val2;
137 	long dnums[26]; /* dynamic variables a-z, not preserved */
138 	size_t l, max;
139 	TPSTACK stack;
140 	TPVAR params[9];
141 	int done, dot, minus, width, precision, olen;
142 	int piss[9]; /* Parameter IS String - piss ;) */
143 
144 	if (str == NULL)
145 		return NULL;
146 
147 	/*
148 	  If not passed a terminal, malloc a dummy one.
149 	  This means we can preserve buffers and variables per terminal and
150 	  still work with non thread safe functions (which sadly are still the
151 	  norm and standard).
152 	*/
153 	if (term == NULL) {
154 		if (dumbterm == NULL) {
155 			dumbterm = malloc(sizeof(*dumbterm));
156 			if (dumbterm == NULL)
157 				return NULL;
158 			dumbterm->_buflen = 0;
159 		}
160 		term = dumbterm;
161 	}
162 
163 	term->_bufpos = 0;
164 	/* Ensure we have an initial buffer */
165 	if (term->_buflen == 0) {
166 		term->_buf = malloc(BUFSIZ);
167 		if (term->_buf == NULL)
168 			return NULL;
169 		term->_buflen = BUFSIZ;
170 	}
171 
172 	/*
173 	  Make a first pass through the string so we can work out
174 	  which parameters are longs and which are char *.
175 	  Basically we only use char * if %p[1-9] is followed by %l or %s.
176 	*/
177 	memset(&piss, 0, sizeof(piss));
178 	max = 0;
179 	sp = str;
180 	while ((c = *sp++) != '\0') {
181 		if (c != '%')
182 			continue;
183 		c = *sp++;
184 		if (c == '\0')
185 			break;
186 		if (c != 'p')
187 			continue;
188 		c = *sp++;
189 		if (c < '1' || c > '9') {
190 			errno = EINVAL;
191 			continue;
192 		}
193 		l = c - '0';
194 		if (l > max)
195 			max = l;
196 		if (*sp != '%')
197 			continue;
198 		/* Skip formatting */
199 		sp++;
200 		while (*sp == '.' || *sp == '#' || *sp == ' ' || *sp == ':' ||
201 		    *sp == '-' || isdigit((unsigned char)*sp))
202 			sp++;
203 		if (*sp == 'l' || *sp == 's')
204 			piss[l - 1] = 1;
205 	}
206 
207 	/* Put our parameters into variables */
208 	memset(&params, 0, sizeof(params));
209 	for (l = 0; l < max; l++) {
210 		if (piss[l] == 0)
211 			params[l].num = va_arg(parms, long);
212 		else
213 			params[l].string = va_arg(parms, char *);
214 	}
215 
216 	term->_bufpos = 0;
217 	memset(&stack, 0, sizeof(stack));
218 	while ((c = *str++) != '\0') {
219 		if (c != '%' || (c = *str++) == '%') {
220 			if (c == '\0')
221 				break;
222 			if (ochar(term, c) == 0)
223 				return NULL;
224 			continue;
225 		}
226 
227 		/* Handle formatting. */
228 		fp = fmt;
229 		*fp++ = '%';
230 		done = dot = minus = width = precision = 0;
231 		val = 0;
232 		while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) {
233 			switch (c) {
234 			case 'c': /* FALLTHROUGH */
235 			case 'd': /* FALLTHROUGH */
236 			case 'o': /* FALLTHROUGH */
237 			case 'x': /* FALLTHROUGH */
238 			case 'X': /* FALLTHROUGH */
239 			case 's':
240 				*fp++ = c;
241 				done = 1;
242 				break;
243 			case '#': /* FALLTHROUGH */
244 			case ' ':
245 				*fp++ = c;
246 				break;
247 			case '.':
248 				*fp++ = c;
249 				if (dot == 0) {
250 					dot = 1;
251 					width = val;
252 				} else
253 					done = 2;
254 				val = 0;
255 				break;
256 			case ':':
257 				minus = 1;
258 				break;
259 			case '-':
260 				if (minus)
261 					*fp++ = c;
262 				else
263 					done = 1;
264 				break;
265 			default:
266 				if (isdigit((unsigned char)c)) {
267 					val = (val * 10) + (c - '0');
268 					if (val > 10000)
269 						done = 2;
270 					else
271 						*fp++ = c;
272 				} else
273 					done = 1;
274 			}
275 			if (done == 0)
276 				c = *str++;
277 		}
278 		if (done == 2) {
279 			/* Found an error in the format */
280 			fp = fmt + 1;
281 			*fp = *str;
282 			olen = 0;
283 		} else {
284 			if (dot == 0)
285 				width = val;
286 			else
287 				precision = val;
288 			olen = (width > precision) ? width : precision;
289 		}
290 		*fp++ = '\0';
291 
292 		/* Handle commands */
293 		switch (c) {
294 		case 'c':
295 			pop(&val, NULL, &stack);
296 			if (ochar(term, (unsigned char)val) == 0)
297 				return NULL;
298 			break;
299 		case 's':
300 			pop(NULL, &ostr, &stack);
301 			if (ostr != NULL) {
302 				l = strlen(ostr);
303 				if (l < (size_t)olen)
304 					l = olen;
305 				if (checkbuf(term, (size_t)(l + 1)) == NULL)
306 					return NULL;
307 				l = sprintf(term->_buf + term->_bufpos,
308 				    fmt, ostr);
309 				term->_bufpos += l;
310 			}
311 			break;
312 		case 'l':
313 			pop(NULL, &ostr, &stack);
314 			if (ostr == NULL)
315 				l = 0;
316 			else
317 				l = strlen(ostr);
318 			if (onum(term, "%d", (long)l, 0) == 0)
319 				return NULL;
320 			break;
321 		case 'd': /* FALLTHROUGH */
322 		case 'o': /* FALLTHROUGH */
323 		case 'x': /* FALLTHROUGH */
324 		case 'X':
325 			pop(&val, NULL, &stack);
326 			if (onum(term, fmt, val, olen) == 0)
327 				return NULL;
328 			break;
329 		case 'p':
330 			if (*str < '1' || *str > '9')
331 				break;
332 			l = *str++ - '1';
333 			if (push(params[l].num, params[l].string, &stack))
334 				return NULL;
335 			break;
336 		case 'P':
337 			pop(&val, NULL, &stack);
338 			if (*str >= 'a' && *str <= 'z')
339 				dnums[*str - 'a'] = val;
340 			else if (*str >= 'A' && *str <= 'Z')
341 				term->_snums[*str - 'A'] = val;
342 			break;
343 		case 'g':
344 			if (*str >= 'a' && *str <= 'z') {
345 				if (push(dnums[*str - 'a'], NULL, &stack))
346 					return NULL;
347 			} else if (*str >= 'A' && *str <= 'Z') {
348 				if (push(term->_snums[*str - 'A'],
349 					NULL, &stack))
350 					return NULL;
351 			}
352 			break;
353 		case 'i':
354 			if (piss[0] == 0)
355 				params[0].num++;
356 			if (piss[1] == 0)
357 				params[1].num++;
358 			break;
359 		case '\'':
360 			if (push((long)(unsigned char)*str++, NULL, &stack))
361 				return NULL;
362 			while (*str != '\0' && *str != '\'')
363 				str++;
364 			if (*str == '\'')
365 				str++;
366 			break;
367 		case '{':
368 			val = 0;
369 			for (; isdigit((unsigned char)*str);  str++)
370 				val = (val * 10) + (*str - '0');
371 			if (push(val, NULL, &stack))
372 				return NULL;
373 			while (*str != '\0' && *str != '}')
374 				str++;
375 			if (*str == '}')
376 				str++;
377 			break;
378 		case '+': /* FALLTHROUGH */
379 		case '-': /* FALLTHROUGH */
380 		case '*': /* FALLTHROUGH */
381 		case '/': /* FALLTHROUGH */
382 		case 'm': /* FALLTHROUGH */
383 		case 'A': /* FALLTHROUGH */
384 		case 'O': /* FALLTHROUGH */
385 		case '&': /* FALLTHROUGH */
386 		case '|': /* FALLTHROUGH */
387 		case '^': /* FALLTHROUGH */
388 		case '=': /* FALLTHROUGH */
389 		case '<': /* FALLTHROUGH */
390 		case '>':
391 			pop(&val, NULL, &stack);
392 			pop(&val2, NULL, &stack);
393 			switch (c) {
394 			case '+':
395 				val = val + val2;
396 				break;
397 			case '-':
398 				val = val2 - val;
399 				break;
400 			case '*':
401 				val = val * val2;
402 				break;
403 			case '/':
404 				val = val ? val2 / val : 0;
405 				break;
406 			case 'm':
407 				val = val ? val2 % val : 0;
408 				break;
409 			case 'A':
410 				val = val && val2;
411 				break;
412 			case 'O':
413 				val = val || val2;
414 				break;
415 			case '&':
416 				val = val & val2;
417 				break;
418 			case '|':
419 				val = val | val2;
420 				break;
421 			case '^':
422 				val = val ^ val2;
423 				break;
424 			case '=':
425 				val = val == val2;
426 				break;
427 			case '<':
428 				val = val2 < val;
429 				break;
430 			case '>':
431 				val = val2 > val;
432 				break;
433 			}
434 			if (push(val, NULL, &stack))
435 				return NULL;
436 			break;
437 		case '!':
438 		case '~':
439 			pop(&val, NULL, &stack);
440 			switch (*str) {
441 			case '!':
442 				val = !val;
443 				break;
444 			case '~':
445 				val = ~val;
446 				break;
447 			}
448 			if (push(val, NULL, &stack))
449 				return NULL;
450 			break;
451 		case '?': /* if */
452 			break;
453 		case 't': /* then */
454 			pop(&val, NULL, &stack);
455 			if (val != 0) {
456 				l = 0;
457 				for (; *str != '\0'; str++) {
458 					if (*str != '%')
459 						continue;
460 					str++;
461 					if (*str == '?')
462 						l++;
463 					else if (*str == ';') {
464 						if (l > 0)
465 							l--;
466 						else
467 							break;
468 					} else if (*str == 'e' && l == 0)
469 						break;
470 				}
471 			}
472 			break;
473 		case 'e': /* else */
474 			l = 0;
475 			for (; *str != '\0'; str++) {
476 				if (*str != '%')
477 					continue;
478 				str++;
479 				if (*str == '?')
480 					l++;
481 				else if (*str == ';') {
482 					if (l > 0)
483 						l--;
484 					else
485 						break;
486 				}
487 			}
488 			break;
489 		case ';': /* fi */
490 			break;
491 		}
492 	}
493 	term->_buf[term->_bufpos] = '\0';
494 	return term->_buf;
495 }
496 
497 char *
498 t_vparm(TERMINAL *term, const char *str, ...)
499 {
500 	va_list va;
501 	char *ret;
502 
503 	_DIAGASSERT(term != NULL);
504 	_DIAGASSERT(str != NULL);
505 
506 	va_start(va, str);
507 	ret = _ti_vtparm(term, str, va);
508 	va_end(va);
509 	return ret;
510 }
511 
512 char *
513 vtparm(const char *str, ...)
514 {
515 	va_list va;
516 	char *ret;
517 
518 	_DIAGASSERT(str != NULL);
519 
520 	va_start(va, str);
521         ret = _ti_vtparm(NULL, str, va);
522 	va_end(va);
523 	return ret;
524 }
525 
526 char *
527 t_parm(TERMINAL *term, const char *str,
528     long p1, long p2, long p3, long p4, long p5,
529     long p6, long p7, long p8, long p9)
530 {
531 
532 	_DIAGASSERT(term != NULL);
533 	_DIAGASSERT(str != NULL);
534 	return t_vparm(term, str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
535 }
536 
537 char *
538 tparm(const char *str,
539     long p1, long p2, long p3, long p4, long p5,
540     long p6, long p7, long p8, long p9)
541 {
542 
543 	_DIAGASSERT(str != NULL);
544 	return t_vparm(NULL, str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
545 }
546