1/* $NetBSD: tparm.c,v 1.7.4.3 2013/03/14 15:48:29 riz 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.7.4.3 2013/03/14 15:48:29 riz 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
50
51static TERMINAL *dumbterm; /* For non thread safe functions */
52
53typedef struct {
54	long nums[20];
55	char *strings[20];
56	size_t offset;
57} TPSTACK;
58
59typedef struct {
60	long num;
61	char *string;
62} TPVAR;
63
64static int
65push(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
77static int
78pop(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
96static char *
97checkbuf(TERMINAL *term, size_t len)
98{
99	char *buf;
100
101	if (term->_bufpos + len >= term->_buflen) {
102		len = term->_buflen + MAX(len, BUFSIZ);
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
112static size_t
113ochar(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
124static size_t
125onum(TERMINAL *term, const char *fmt, int num, unsigned int len)
126{
127	size_t l;
128
129	if (len < LONG_STR_MAX)
130		len = LONG_STR_MAX;
131	if (checkbuf(term, len + 2) == NULL)
132		return 0;
133	l = sprintf(term->_buf + term->_bufpos, fmt, num);
134	term->_bufpos += l;
135	return l;
136}
137
138/*
139  Make a pass through the string so we can work out
140  which parameters are ints and which are char *.
141  Basically we only use char * if %p[1-9] is followed by %l or %s.
142*/
143int
144_ti_parm_analyse(const char *str, int *piss, int piss_len)
145{
146	int nparm, lpop;
147	char c;
148
149	nparm = 0;
150	lpop = -1;
151	while ((c = *str++) != '\0') {
152		if (c != '%')
153			continue;
154		c = *str++;
155		switch (c) {
156			case 'l': /* FALLTHROUGH */
157			case 's':
158				if (lpop > 0) {
159					if (lpop <= piss_len)
160						piss[lpop - 1] = 1;
161					else if (piss)
162						errno = E2BIG;
163				}
164				break;
165			case 'p':
166				c = *str++;
167				if (c < '1' || c > '9') {
168					errno = EINVAL;
169					continue;
170				} else {
171					lpop = c - '0';
172					if (lpop > nparm)
173						nparm = lpop;
174				}
175				break;
176			default:
177				lpop = -1;
178		}
179	}
180
181	return nparm;
182}
183
184static char *
185_ti_tiparm(TERMINAL *term, const char *str, int va_type, va_list parms)
186{
187	char c, fmt[64], *fp, *ostr;
188	long val, val2;
189	long dnums[26]; /* dynamic variables a-z, not preserved */
190	size_t l, max;
191	TPSTACK stack;
192	TPVAR params[TPARM_MAX];
193	unsigned int done, dot, minus, width, precision, olen;
194	int piss[TPARM_MAX]; /* Parameter IS String - piss ;) */
195
196	if (str == NULL)
197		return NULL;
198
199	/*
200	  If not passed a terminal, malloc a dummy one.
201	  This means we can preserve buffers and variables per terminal and
202	  still work with non thread safe functions (which sadly are still the
203	  norm and standard).
204	*/
205	if (term == NULL) {
206		if (dumbterm == NULL) {
207			dumbterm = malloc(sizeof(*dumbterm));
208			if (dumbterm == NULL)
209				return NULL;
210			dumbterm->_buflen = 0;
211		}
212		term = dumbterm;
213	}
214
215	term->_bufpos = 0;
216	/* Ensure we have an initial buffer */
217	if (term->_buflen == 0) {
218		term->_buf = malloc(BUFINC);
219		if (term->_buf == NULL)
220			return NULL;
221		term->_buflen = BUFINC;
222	}
223
224	memset(&piss, 0, sizeof(piss));
225	max = _ti_parm_analyse(str, piss, TPARM_MAX);
226
227	/* Put our parameters into variables */
228	memset(&params, 0, sizeof(params));
229	for (l = 0; l < max; l++) {
230		if (piss[l]) {
231			if (va_type == VA_LONG_LONG) {
232				/* This only works if char * fits into a long
233				 * on this platform. */
234				if (sizeof(char *) <= sizeof(long)/*CONSTCOND*/)
235					params[l].string =
236					    (char *)va_arg(parms, long);
237				else {
238					errno = ENOTSUP;
239					return NULL;
240				}
241			} else
242				params[l].string = va_arg(parms, char *);
243		} else {
244			if (va_type == VA_CHAR_INT)
245				params[l].num = (long)va_arg(parms, int);
246			else
247				params[l].num = va_arg(parms, long);
248		}
249	}
250
251	memset(&stack, 0, sizeof(stack));
252	while ((c = *str++) != '\0') {
253		if (c != '%' || (c = *str++) == '%') {
254			if (c == '\0')
255				break;
256			if (ochar(term, c) == 0)
257				return NULL;
258			continue;
259		}
260
261		/* Handle formatting. */
262		fp = fmt;
263		*fp++ = '%';
264		done = dot = minus = width = precision = 0;
265		val = 0;
266		while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) {
267			switch (c) {
268			case 'c': /* FALLTHROUGH */
269			case 's':
270				*fp++ = c;
271				done = 1;
272				break;
273			case 'd': /* FALLTHROUGH */
274			case 'o': /* FALLTHROUGH */
275			case 'x': /* FALLTHROUGH */
276			case 'X': /* FALLTHROUGH */
277				*fp++ = 'l';
278				*fp++ = c;
279				done = 1;
280				break;
281			case '#': /* FALLTHROUGH */
282			case ' ':
283				*fp++ = c;
284				break;
285			case '.':
286				*fp++ = c;
287				if (dot == 0) {
288					dot = 1;
289					width = val;
290				} else
291					done = 2;
292				val = 0;
293				break;
294			case ':':
295				minus = 1;
296				break;
297			case '-':
298				if (minus)
299					*fp++ = c;
300				else
301					done = 1;
302				break;
303			default:
304				if (isdigit((unsigned char)c)) {
305					val = (val * 10) + (c - '0');
306					if (val > 10000)
307						done = 2;
308					else
309						*fp++ = c;
310				} else
311					done = 1;
312			}
313			if (done == 0)
314				c = *str++;
315		}
316		if (done == 2) {
317			/* Found an error in the format */
318			fp = fmt + 1;
319			*fp = *str;
320			olen = 0;
321		} else {
322			if (dot == 0)
323				width = val;
324			else
325				precision = val;
326			olen = MAX(width, precision);
327		}
328		*fp++ = '\0';
329
330		/* Handle commands */
331		switch (c) {
332		case 'c':
333			pop(&val, NULL, &stack);
334			if (ochar(term, (unsigned char)val) == 0)
335				return NULL;
336			break;
337		case 's':
338			pop(NULL, &ostr, &stack);
339			if (ostr != NULL) {
340				l = strlen(ostr);
341				if (l < (size_t)olen)
342					l = olen;
343				if (checkbuf(term, (size_t)(l + 1)) == NULL)
344					return NULL;
345				l = sprintf(term->_buf + term->_bufpos,
346				    fmt, ostr);
347				term->_bufpos += l;
348			}
349			break;
350		case 'l':
351			pop(NULL, &ostr, &stack);
352			if (ostr == NULL)
353				l = 0;
354			else
355				l = strlen(ostr);
356#ifdef NCURSES_COMPAT_57
357			if (onum(term, "%ld", (long)l, 0) == 0)
358				return NULL;
359#else
360			push((long)l, NULL, &stack);
361#endif
362			break;
363		case 'd': /* FALLTHROUGH */
364		case 'o': /* FALLTHROUGH */
365		case 'x': /* FALLTHROUGH */
366		case 'X':
367			pop(&val, NULL, &stack);
368			if (onum(term, fmt, (int)val, olen) == 0)
369				return NULL;
370			break;
371		case 'p':
372			if (*str < '1' || *str > '9')
373				break;
374			l = *str++ - '1';
375			if (push(params[l].num, params[l].string, &stack))
376				return NULL;
377			break;
378		case 'P':
379			pop(&val, NULL, &stack);
380			if (*str >= 'a' && *str <= 'z')
381				dnums[*str - 'a'] = val;
382			else if (*str >= 'A' && *str <= 'Z')
383				term->_snums[*str - 'A'] = val;
384			break;
385		case 'g':
386			if (*str >= 'a' && *str <= 'z') {
387				if (push(dnums[*str - 'a'], NULL, &stack))
388					return NULL;
389			} else if (*str >= 'A' && *str <= 'Z') {
390				if (push(term->_snums[*str - 'A'],
391					NULL, &stack))
392					return NULL;
393			}
394			break;
395		case 'i':
396			if (piss[0] == 0)
397				params[0].num++;
398			if (piss[1] == 0)
399				params[1].num++;
400			break;
401		case '\'':
402			if (push((long)(unsigned char)*str++, NULL, &stack))
403				return NULL;
404			while (*str != '\0' && *str != '\'')
405				str++;
406			if (*str == '\'')
407				str++;
408			break;
409		case '{':
410			val = 0;
411			for (; isdigit((unsigned char)*str);  str++)
412				val = (val * 10) + (*str - '0');
413			if (push(val, NULL, &stack))
414				return NULL;
415			while (*str != '\0' && *str != '}')
416				str++;
417			if (*str == '}')
418				str++;
419			break;
420		case '+': /* FALLTHROUGH */
421		case '-': /* FALLTHROUGH */
422		case '*': /* FALLTHROUGH */
423		case '/': /* FALLTHROUGH */
424		case 'm': /* FALLTHROUGH */
425		case 'A': /* FALLTHROUGH */
426		case 'O': /* FALLTHROUGH */
427		case '&': /* FALLTHROUGH */
428		case '|': /* FALLTHROUGH */
429		case '^': /* FALLTHROUGH */
430		case '=': /* FALLTHROUGH */
431		case '<': /* FALLTHROUGH */
432		case '>':
433			pop(&val, NULL, &stack);
434			pop(&val2, NULL, &stack);
435			switch (c) {
436			case '+':
437				val = val + val2;
438				break;
439			case '-':
440				val = val2 - val;
441				break;
442			case '*':
443				val = val * val2;
444				break;
445			case '/':
446				val = val ? val2 / val : 0;
447				break;
448			case 'm':
449				val = val ? val2 % val : 0;
450				break;
451			case 'A':
452				val = val && val2;
453				break;
454			case 'O':
455				val = val || val2;
456				break;
457			case '&':
458				val = val & val2;
459				break;
460			case '|':
461				val = val | val2;
462				break;
463			case '^':
464				val = val ^ val2;
465				break;
466			case '=':
467				val = val == val2;
468				break;
469			case '<':
470				val = val2 < val;
471				break;
472			case '>':
473				val = val2 > val;
474				break;
475			}
476			if (push(val, NULL, &stack))
477				return NULL;
478			break;
479		case '!':
480		case '~':
481			pop(&val, NULL, &stack);
482			switch (c) {
483			case '!':
484				val = !val;
485				break;
486			case '~':
487				val = ~val;
488				break;
489			}
490			if (push(val, NULL, &stack))
491				return NULL;
492			break;
493		case '?': /* if */
494			break;
495		case 't': /* then */
496			pop(&val, NULL, &stack);
497			if (val == 0) {
498				l = 0;
499				for (; *str != '\0'; str++) {
500					if (*str != '%')
501						continue;
502					str++;
503					if (*str == '?')
504						l++;
505					else if (*str == ';') {
506						if (l > 0)
507							l--;
508						else {
509							str++;
510							break;
511						}
512					} else if (*str == 'e' && l == 0) {
513						str++;
514						break;
515					}
516				}
517			}
518			break;
519		case 'e': /* else */
520			l = 0;
521			for (; *str != '\0'; str++) {
522				if (*str != '%')
523					continue;
524				str++;
525				if (*str == '?')
526					l++;
527				else if (*str == ';') {
528					if (l > 0)
529						l--;
530					else {
531						str++;
532						break;
533					}
534				}
535			}
536			break;
537		case ';': /* fi */
538			break;
539		}
540	}
541	term->_buf[term->_bufpos] = '\0';
542	return term->_buf;
543}
544
545char *
546ti_tiparm(TERMINAL *term, const char *str, ...)
547{
548	va_list va;
549	char *ret;
550
551	_DIAGASSERT(term != NULL);
552	_DIAGASSERT(str != NULL);
553
554	va_start(va, str);
555	ret = _ti_tiparm(term, str, VA_CHAR_INT, va);
556	va_end(va);
557	return ret;
558}
559
560char *
561tiparm(const char *str, ...)
562{
563	va_list va;
564	char *ret;
565
566	_DIAGASSERT(str != NULL);
567
568	va_start(va, str);
569	ret = _ti_tiparm(NULL, str, VA_CHAR_INT, va);
570	va_end(va);
571	return ret;
572}
573
574#ifdef VA_CHAR_LONG
575char *
576ti_tlparm(TERMINAL *term, const char *str, ...)
577{
578	va_list va;
579	char *ret;
580
581	_DIAGASSERT(term != NULL);
582	_DIAGASSERT(str != NULL);
583
584	va_start(va, str);
585	ret = _ti_tiparm(term, str, VA_CHAR_LONG, va);
586	va_end(va);
587	return ret;
588}
589
590char *
591tlparm(const char *str, ...)
592{
593	va_list va;
594	char *ret;
595
596	_DIAGASSERT(str != NULL);
597
598	va_start(va, str);
599	ret = _ti_tiparm(NULL, str, VA_CHAR_LONG, va);
600	va_end(va);
601	return ret;
602}
603#endif
604
605static char *
606_tparm(const char *str, ...)
607{
608	va_list va;
609	char *ret;
610
611	_DIAGASSERT(str != NULL);
612
613	va_start(va, str);
614	ret = _ti_tiparm(NULL, str, VA_LONG_LONG, va);
615	va_end(va);
616	return ret;
617}
618
619char *
620tparm(const char *str,
621    long p1, long p2, long p3, long p4, long p5,
622    long p6, long p7, long p8, long p9)
623{
624
625	return _tparm(str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
626}
627