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