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