1 /* $NetBSD: tparm.c,v 1.18 2020/03/27 15:11:57 christos 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.18 2020/03/27 15:11:57 christos 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(¶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)/*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 = 0; 269 width = precision = 0; 270 val = 0; 271 while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) { 272 switch (c) { 273 case 'c': /* FALLTHROUGH */ 274 case 's': 275 *fp++ = c; 276 done = 1; 277 break; 278 case 'd': /* FALLTHROUGH */ 279 case 'o': /* FALLTHROUGH */ 280 case 'x': /* FALLTHROUGH */ 281 case 'X': /* FALLTHROUGH */ 282 *fp++ = 'l'; 283 *fp++ = c; 284 done = 1; 285 break; 286 case '#': /* FALLTHROUGH */ 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': /* FALLTHROUGH */ 372 case 'o': /* FALLTHROUGH */ 373 case 'x': /* FALLTHROUGH */ 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 '+': /* FALLTHROUGH */ 429 case '-': /* FALLTHROUGH */ 430 case '*': /* FALLTHROUGH */ 431 case '/': /* FALLTHROUGH */ 432 case 'm': /* FALLTHROUGH */ 433 case 'A': /* FALLTHROUGH */ 434 case 'O': /* FALLTHROUGH */ 435 case '&': /* FALLTHROUGH */ 436 case '|': /* FALLTHROUGH */ 437 case '^': /* FALLTHROUGH */ 438 case '=': /* FALLTHROUGH */ 439 case '<': /* FALLTHROUGH */ 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 * 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 * 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 * 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 * 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 * 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 * 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