1 /* $NetBSD: tparm.c,v 1.19 2021/08/27 18:40:28 rillig 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.19 2021/08/27 18:40:28 rillig 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 increment 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
push(long num,char * string,TPSTACK * stack)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
pop(long * num,char ** string,TPSTACK * stack)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 *
checkbuf(TERMINAL * term,size_t len)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
ochar(TERMINAL * term,int c)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
onum(TERMINAL * term,const char * fmt,int num,size_t len)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
_ti_parm_analyse(const char * str,int * piss,int piss_len)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':
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 *
_ti_tiparm(TERMINAL * term,const char * str,int va_type,va_list parms)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(¶ms, 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))
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 = 0;
269 width = precision = 0;
270 val = 0;
271 while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) {
272 switch (c) {
273 case 'c':
274 case 's':
275 *fp++ = c;
276 done = 1;
277 break;
278 case 'd':
279 case 'o':
280 case 'x':
281 case 'X':
282 *fp++ = 'l';
283 *fp++ = c;
284 done = 1;
285 break;
286 case '#':
287 case ' ':
288 *fp++ = c;
289 break;
290 case '.':
291 *fp++ = c;
292 if (dot == 0) {
293 dot = 1;
294 width = (size_t)val;
295 } else
296 done = 2;
297 val = 0;
298 break;
299 case ':':
300 minus = 1;
301 break;
302 case '-':
303 if (minus)
304 *fp++ = c;
305 else
306 done = 1;
307 break;
308 default:
309 if (isdigit((unsigned char)c)) {
310 val = (val * 10) + (c - '0');
311 if (val > 10000)
312 done = 2;
313 else
314 *fp++ = c;
315 } else
316 done = 1;
317 }
318 if (done == 0)
319 c = *str++;
320 }
321 if (done == 2) {
322 /* Found an error in the format */
323 fp = fmt + 1;
324 *fp = *str;
325 olen = 0;
326 } else {
327 if (dot == 0)
328 width = (size_t)val;
329 else
330 precision = (size_t)val;
331 olen = MAX(width, precision);
332 }
333 *fp++ = '\0';
334
335 /* Handle commands */
336 switch (c) {
337 case 'c':
338 pop(&val, NULL, &stack);
339 if (ochar(term, (unsigned char)val) == 0)
340 return NULL;
341 break;
342 case 's':
343 pop(NULL, &ostr, &stack);
344 if (ostr != NULL) {
345 int r;
346
347 l = strlen(ostr);
348 if (l < (size_t)olen)
349 l = olen;
350 if (checkbuf(term, (size_t)(l + 1)) == NULL)
351 return NULL;
352 r = snprintf(term->_buf + term->_bufpos, l + 1,
353 fmt, ostr);
354 if (r != -1)
355 term->_bufpos += (size_t)r;
356 }
357 break;
358 case 'l':
359 pop(NULL, &ostr, &stack);
360 if (ostr == NULL)
361 l = 0;
362 else
363 l = strlen(ostr);
364 #ifdef NCURSES_COMPAT_57
365 if (onum(term, "%ld", (long)l, 0) == 0)
366 return NULL;
367 #else
368 push((long)l, NULL, &stack);
369 #endif
370 break;
371 case 'd':
372 case 'o':
373 case 'x':
374 case 'X':
375 pop(&val, NULL, &stack);
376 if (onum(term, fmt, (int)val, olen) == 0)
377 return NULL;
378 break;
379 case 'p':
380 if (*str < '1' || *str > '9')
381 break;
382 l = (size_t)(*str++ - '1');
383 if (push(params[l].num, params[l].string, &stack))
384 return NULL;
385 break;
386 case 'P':
387 pop(&val, NULL, &stack);
388 if (*str >= 'a' && *str <= 'z')
389 dnums[*str - 'a'] = val;
390 else if (*str >= 'A' && *str <= 'Z')
391 term->_snums[*str - 'A'] = val;
392 break;
393 case 'g':
394 if (*str >= 'a' && *str <= 'z') {
395 if (push(dnums[*str - 'a'], NULL, &stack))
396 return NULL;
397 } else if (*str >= 'A' && *str <= 'Z') {
398 if (push(term->_snums[*str - 'A'],
399 NULL, &stack))
400 return NULL;
401 }
402 break;
403 case 'i':
404 if (piss[0] == 0)
405 params[0].num++;
406 if (piss[1] == 0)
407 params[1].num++;
408 break;
409 case '\'':
410 if (push((long)(unsigned char)*str++, NULL, &stack))
411 return NULL;
412 while (*str != '\0' && *str != '\'')
413 str++;
414 if (*str == '\'')
415 str++;
416 break;
417 case '{':
418 val = 0;
419 for (; isdigit((unsigned char)*str); str++)
420 val = (val * 10) + (*str - '0');
421 if (push(val, NULL, &stack))
422 return NULL;
423 while (*str != '\0' && *str != '}')
424 str++;
425 if (*str == '}')
426 str++;
427 break;
428 case '+':
429 case '-':
430 case '*':
431 case '/':
432 case 'm':
433 case 'A':
434 case 'O':
435 case '&':
436 case '|':
437 case '^':
438 case '=':
439 case '<':
440 case '>':
441 pop(&val, NULL, &stack);
442 pop(&val2, NULL, &stack);
443 switch (c) {
444 case '+':
445 val = val + val2;
446 break;
447 case '-':
448 val = val2 - val;
449 break;
450 case '*':
451 val = val * val2;
452 break;
453 case '/':
454 val = val ? val2 / val : 0;
455 break;
456 case 'm':
457 val = val ? val2 % val : 0;
458 break;
459 case 'A':
460 val = val && val2;
461 break;
462 case 'O':
463 val = val || val2;
464 break;
465 case '&':
466 val = val & val2;
467 break;
468 case '|':
469 val = val | val2;
470 break;
471 case '^':
472 val = val ^ val2;
473 break;
474 case '=':
475 val = val == val2;
476 break;
477 case '<':
478 val = val2 < val;
479 break;
480 case '>':
481 val = val2 > val;
482 break;
483 }
484 if (push(val, NULL, &stack))
485 return NULL;
486 break;
487 case '!':
488 case '~':
489 pop(&val, NULL, &stack);
490 switch (c) {
491 case '!':
492 val = !val;
493 break;
494 case '~':
495 val = ~val;
496 break;
497 }
498 if (push(val, NULL, &stack))
499 return NULL;
500 break;
501 case '?': /* if */
502 break;
503 case 't': /* then */
504 pop(&val, NULL, &stack);
505 if (val == 0) {
506 l = 0;
507 for (; *str != '\0'; str++) {
508 if (*str != '%')
509 continue;
510 str++;
511 if (*str == '?')
512 l++;
513 else if (*str == ';') {
514 if (l > 0)
515 l--;
516 else {
517 str++;
518 break;
519 }
520 } else if (*str == 'e' && l == 0) {
521 str++;
522 break;
523 }
524 }
525 }
526 break;
527 case 'e': /* else */
528 l = 0;
529 for (; *str != '\0'; str++) {
530 if (*str != '%')
531 continue;
532 str++;
533 if (*str == '?')
534 l++;
535 else if (*str == ';') {
536 if (l > 0)
537 l--;
538 else {
539 str++;
540 break;
541 }
542 }
543 }
544 break;
545 case ';': /* fi */
546 break;
547 }
548 }
549 term->_buf[term->_bufpos] = '\0';
550 return term->_buf;
551 }
552
553 char *
ti_tiparm(TERMINAL * term,const char * str,...)554 ti_tiparm(TERMINAL *term, const char *str, ...)
555 {
556 va_list va;
557 char *ret;
558
559 _DIAGASSERT(term != NULL);
560 _DIAGASSERT(str != NULL);
561
562 va_start(va, str);
563 ret = _ti_tiparm(term, str, VA_CHAR_INT, va);
564 va_end(va);
565 return ret;
566 }
567
568 char *
tiparm(const char * str,...)569 tiparm(const char *str, ...)
570 {
571 va_list va;
572 char *ret;
573
574 _DIAGASSERT(str != NULL);
575
576 va_start(va, str);
577 ret = _ti_tiparm(NULL, str, VA_CHAR_INT, va);
578 va_end(va);
579 return ret;
580 }
581
582 #ifdef VA_CHAR_LONG
583 char *
ti_tlparm(TERMINAL * term,const char * str,...)584 ti_tlparm(TERMINAL *term, const char *str, ...)
585 {
586 va_list va;
587 char *ret;
588
589 _DIAGASSERT(term != NULL);
590 _DIAGASSERT(str != NULL);
591
592 va_start(va, str);
593 ret = _ti_tiparm(term, str, VA_CHAR_LONG, va);
594 va_end(va);
595 return ret;
596 }
597
598 char *
tlparm(const char * str,...)599 tlparm(const char *str, ...)
600 {
601 va_list va;
602 char *ret;
603
604 _DIAGASSERT(str != NULL);
605
606 va_start(va, str);
607 ret = _ti_tiparm(NULL, str, VA_CHAR_LONG, va);
608 va_end(va);
609 return ret;
610 }
611 #endif
612
613 static char *
_tparm(const char * str,...)614 _tparm(const char *str, ...)
615 {
616 va_list va;
617 char *ret;
618
619 _DIAGASSERT(str != NULL);
620
621 va_start(va, str);
622 ret = _ti_tiparm(NULL, str, VA_LONG_LONG, va);
623 va_end(va);
624 return ret;
625 }
626
627 char *
tparm(const char * str,long p1,long p2,long p3,long p4,long p5,long p6,long p7,long p8,long p9)628 tparm(const char *str,
629 long p1, long p2, long p3, long p4, long p5,
630 long p6, long p7, long p8, long p9)
631 {
632
633 return _tparm(str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
634 }
635