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