1 /**************************************************************************** 2 * Copyright (c) 1998-2014,2015 Free Software Foundation, Inc. * 3 * * 4 * Permission is hereby granted, free of charge, to any person obtaining a * 5 * copy of this software and associated documentation files (the * 6 * "Software"), to deal in the Software without restriction, including * 7 * without limitation the rights to use, copy, modify, merge, publish, * 8 * distribute, distribute with modifications, sublicense, and/or sell * 9 * copies of the Software, and to permit persons to whom the Software is * 10 * furnished to do so, subject to the following conditions: * 11 * * 12 * The above copyright notice and this permission notice shall be included * 13 * in all copies or substantial portions of the Software. * 14 * * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 18 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 21 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 22 * * 23 * Except as contained in this notice, the name(s) of the above copyright * 24 * holders shall not be used in advertising or otherwise to promote the * 25 * sale, use or other dealings in this Software without prior written * 26 * authorization. * 27 ****************************************************************************/ 28 29 /**************************************************************************** 30 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * 31 * and: Eric S. Raymond <esr@snark.thyrsus.com> * 32 * and: Thomas E. Dickey 1996 on * 33 ****************************************************************************/ 34 35 #define __INTERNAL_CAPS_VISIBLE 36 #include <progs.priv.h> 37 38 #include "dump_entry.h" 39 #include "termsort.c" /* this C file is generated */ 40 #include <parametrized.h> /* so is this */ 41 42 MODULE_ID("$Id: dump_entry.c,v 1.124 2015/11/28 22:54:33 tom Exp $") 43 44 #define DISCARD(string) string = ABSENT_STRING 45 #define PRINTF (void) printf 46 47 #define OkIndex(index,array) ((int)(index) >= 0 && (int)(index) < (int) SIZEOF(array)) 48 49 typedef struct { 50 char *text; 51 size_t used; 52 size_t size; 53 } DYNBUF; 54 55 static int tversion; /* terminfo version */ 56 static int outform; /* output format to use */ 57 static int sortmode; /* sort mode to use */ 58 static int width = 60; /* max line width for listings */ 59 static int height = 65535; /* max number of lines for listings */ 60 static int column; /* current column, limited by 'width' */ 61 static int oldcol; /* last value of column before wrap */ 62 static bool pretty; /* true if we format if-then-else strings */ 63 static bool checking; /* true if we are checking for tic */ 64 static int quickdump; /* true if we are dumping compiled data */ 65 66 static char *save_sgr; 67 68 static DYNBUF outbuf; 69 static DYNBUF tmpbuf; 70 71 /* indirection pointers for implementing sort and display modes */ 72 static const PredIdx *bool_indirect, *num_indirect, *str_indirect; 73 static NCURSES_CONST char *const *bool_names; 74 static NCURSES_CONST char *const *num_names; 75 static NCURSES_CONST char *const *str_names; 76 77 static const char *separator = "", *trailer = ""; 78 static int indent = 8; 79 80 /* cover various ports and variants of terminfo */ 81 #define V_ALLCAPS 0 /* all capabilities (SVr4, XSI, ncurses) */ 82 #define V_SVR1 1 /* SVR1, Ultrix */ 83 #define V_HPUX 2 /* HP/UX */ 84 #define V_AIX 3 /* AIX */ 85 #define V_BSD 4 /* BSD */ 86 87 #if NCURSES_XNAMES 88 #define OBSOLETE(n) (!_nc_user_definable && (n[0] == 'O' && n[1] == 'T')) 89 #else 90 #define OBSOLETE(n) (n[0] == 'O' && n[1] == 'T') 91 #endif 92 93 #define isObsolete(f,n) ((f == F_TERMINFO || f == F_VARIABLE) && OBSOLETE(n)) 94 95 #if NCURSES_XNAMES 96 #define BoolIndirect(j) ((j >= BOOLCOUNT) ? (j) : ((sortmode == S_NOSORT) ? j : bool_indirect[j])) 97 #define NumIndirect(j) ((j >= NUMCOUNT) ? (j) : ((sortmode == S_NOSORT) ? j : num_indirect[j])) 98 #define StrIndirect(j) ((j >= STRCOUNT) ? (j) : ((sortmode == S_NOSORT) ? j : str_indirect[j])) 99 #else 100 #define BoolIndirect(j) ((sortmode == S_NOSORT) ? (j) : bool_indirect[j]) 101 #define NumIndirect(j) ((sortmode == S_NOSORT) ? (j) : num_indirect[j]) 102 #define StrIndirect(j) ((sortmode == S_NOSORT) ? (j) : str_indirect[j]) 103 #endif 104 105 static void failed(const char *) GCC_NORETURN; 106 107 static void 108 failed(const char *s) 109 { 110 perror(s); 111 ExitProgram(EXIT_FAILURE); 112 } 113 114 static void 115 strncpy_DYN(DYNBUF * dst, const char *src, size_t need) 116 { 117 size_t want = need + dst->used + 1; 118 if (want > dst->size) { 119 dst->size += (want + 1024); /* be generous */ 120 dst->text = typeRealloc(char, dst->size, dst->text); 121 if (dst->text == 0) 122 failed("strncpy_DYN"); 123 } 124 (void) strncpy(dst->text + dst->used, src, need); 125 dst->used += need; 126 dst->text[dst->used] = 0; 127 } 128 129 static void 130 strcpy_DYN(DYNBUF * dst, const char *src) 131 { 132 if (src == 0) { 133 dst->used = 0; 134 strcpy_DYN(dst, ""); 135 } else { 136 strncpy_DYN(dst, src, strlen(src)); 137 } 138 } 139 140 #if NO_LEAKS 141 static void 142 free_DYN(DYNBUF * p) 143 { 144 if (p->text != 0) 145 free(p->text); 146 p->text = 0; 147 p->size = 0; 148 p->used = 0; 149 } 150 151 void 152 _nc_leaks_dump_entry(void) 153 { 154 free_DYN(&outbuf); 155 free_DYN(&tmpbuf); 156 } 157 #endif 158 159 #define NameTrans(check,result) \ 160 if ((np->nte_index <= OK_ ## check) \ 161 && check[np->nte_index]) \ 162 return (result[np->nte_index]) 163 164 NCURSES_CONST char * 165 nametrans(const char *name) 166 /* translate a capability name to termcap from terminfo */ 167 { 168 const struct name_table_entry *np; 169 170 if ((np = _nc_find_entry(name, _nc_get_hash_table(0))) != 0) { 171 switch (np->nte_type) { 172 case BOOLEAN: 173 NameTrans(bool_from_termcap, boolcodes); 174 break; 175 176 case NUMBER: 177 NameTrans(num_from_termcap, numcodes); 178 break; 179 180 case STRING: 181 NameTrans(str_from_termcap, strcodes); 182 break; 183 } 184 } 185 186 return (0); 187 } 188 189 void 190 dump_init(const char *version, 191 int mode, 192 int sort, 193 int twidth, 194 int theight, 195 unsigned traceval, 196 bool formatted, 197 bool check, 198 int quick) 199 /* set up for entry display */ 200 { 201 width = twidth; 202 height = theight; 203 pretty = formatted; 204 checking = check; 205 quickdump = (quick & 3); 206 207 /* versions */ 208 if (version == 0) 209 tversion = V_ALLCAPS; 210 else if (!strcmp(version, "SVr1") || !strcmp(version, "SVR1") 211 || !strcmp(version, "Ultrix")) 212 tversion = V_SVR1; 213 else if (!strcmp(version, "HP")) 214 tversion = V_HPUX; 215 else if (!strcmp(version, "AIX")) 216 tversion = V_AIX; 217 else if (!strcmp(version, "BSD")) 218 tversion = V_BSD; 219 else 220 tversion = V_ALLCAPS; 221 222 /* implement display modes */ 223 switch (outform = mode) { 224 case F_LITERAL: 225 case F_TERMINFO: 226 bool_names = boolnames; 227 num_names = numnames; 228 str_names = strnames; 229 separator = (twidth > 0 && theight > 1) ? ", " : ","; 230 trailer = "\n\t"; 231 break; 232 233 case F_VARIABLE: 234 bool_names = boolfnames; 235 num_names = numfnames; 236 str_names = strfnames; 237 separator = (twidth > 0 && theight > 1) ? ", " : ","; 238 trailer = "\n\t"; 239 break; 240 241 case F_TERMCAP: 242 case F_TCONVERR: 243 bool_names = boolcodes; 244 num_names = numcodes; 245 str_names = strcodes; 246 separator = ":"; 247 trailer = "\\\n\t:"; 248 break; 249 } 250 indent = 8; 251 252 /* implement sort modes */ 253 switch (sortmode = sort) { 254 case S_NOSORT: 255 if (traceval) 256 (void) fprintf(stderr, 257 "%s: sorting by term structure order\n", _nc_progname); 258 break; 259 260 case S_TERMINFO: 261 if (traceval) 262 (void) fprintf(stderr, 263 "%s: sorting by terminfo name order\n", _nc_progname); 264 bool_indirect = bool_terminfo_sort; 265 num_indirect = num_terminfo_sort; 266 str_indirect = str_terminfo_sort; 267 break; 268 269 case S_VARIABLE: 270 if (traceval) 271 (void) fprintf(stderr, 272 "%s: sorting by C variable order\n", _nc_progname); 273 bool_indirect = bool_variable_sort; 274 num_indirect = num_variable_sort; 275 str_indirect = str_variable_sort; 276 break; 277 278 case S_TERMCAP: 279 if (traceval) 280 (void) fprintf(stderr, 281 "%s: sorting by termcap name order\n", _nc_progname); 282 bool_indirect = bool_termcap_sort; 283 num_indirect = num_termcap_sort; 284 str_indirect = str_termcap_sort; 285 break; 286 } 287 288 if (traceval) 289 (void) fprintf(stderr, 290 "%s: width = %d, tversion = %d, outform = %d\n", 291 _nc_progname, width, tversion, outform); 292 } 293 294 static TERMTYPE *cur_type; 295 296 static int 297 dump_predicate(PredType type, PredIdx idx) 298 /* predicate function to use for ordinary decompilation */ 299 { 300 switch (type) { 301 case BOOLEAN: 302 return (cur_type->Booleans[idx] == FALSE) 303 ? FAIL : cur_type->Booleans[idx]; 304 305 case NUMBER: 306 return (cur_type->Numbers[idx] == ABSENT_NUMERIC) 307 ? FAIL : cur_type->Numbers[idx]; 308 309 case STRING: 310 return (cur_type->Strings[idx] != ABSENT_STRING) 311 ? (int) TRUE : FAIL; 312 } 313 314 return (FALSE); /* pacify compiler */ 315 } 316 317 static void set_obsolete_termcaps(TERMTYPE *tp); 318 319 /* is this the index of a function key string? */ 320 #define FNKEY(i) \ 321 (((i) >= STR_IDX(key_f0) && \ 322 (i) <= STR_IDX(key_f9)) || \ 323 ((i) >= STR_IDX(key_f11) && \ 324 (i) <= STR_IDX(key_f63))) 325 326 /* 327 * If we configure with a different Caps file, the offsets into the arrays 328 * will change. So we use an address expression. 329 */ 330 #define BOOL_IDX(name) (PredType) (&(name) - &(CUR Booleans[0])) 331 #define NUM_IDX(name) (PredType) (&(name) - &(CUR Numbers[0])) 332 #define STR_IDX(name) (PredType) (&(name) - &(CUR Strings[0])) 333 334 static bool 335 version_filter(PredType type, PredIdx idx) 336 /* filter out capabilities we may want to suppress */ 337 { 338 switch (tversion) { 339 case V_ALLCAPS: /* SVr4, XSI Curses */ 340 return (TRUE); 341 342 case V_SVR1: /* System V Release 1, Ultrix */ 343 switch (type) { 344 case BOOLEAN: 345 return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE); 346 case NUMBER: 347 return ((idx <= NUM_IDX(width_status_line)) ? TRUE : FALSE); 348 case STRING: 349 return ((idx <= STR_IDX(prtr_non)) ? TRUE : FALSE); 350 } 351 break; 352 353 case V_HPUX: /* Hewlett-Packard */ 354 switch (type) { 355 case BOOLEAN: 356 return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE); 357 case NUMBER: 358 return ((idx <= NUM_IDX(label_width)) ? TRUE : FALSE); 359 case STRING: 360 if (idx <= STR_IDX(prtr_non)) 361 return (TRUE); 362 else if (FNKEY(idx)) /* function keys */ 363 return (TRUE); 364 else if (idx == STR_IDX(plab_norm) 365 || idx == STR_IDX(label_on) 366 || idx == STR_IDX(label_off)) 367 return (TRUE); 368 else 369 return (FALSE); 370 } 371 break; 372 373 case V_AIX: /* AIX */ 374 switch (type) { 375 case BOOLEAN: 376 return ((idx <= BOOL_IDX(xon_xoff)) ? TRUE : FALSE); 377 case NUMBER: 378 return ((idx <= NUM_IDX(width_status_line)) ? TRUE : FALSE); 379 case STRING: 380 if (idx <= STR_IDX(prtr_non)) 381 return (TRUE); 382 else if (FNKEY(idx)) /* function keys */ 383 return (TRUE); 384 else 385 return (FALSE); 386 } 387 break; 388 389 #define is_termcap(type) (OkIndex(idx, type##_from_termcap) && \ 390 type##_from_termcap[idx]) 391 392 case V_BSD: /* BSD */ 393 switch (type) { 394 case BOOLEAN: 395 return is_termcap(bool); 396 case NUMBER: 397 return is_termcap(num); 398 case STRING: 399 return is_termcap(str); 400 } 401 break; 402 } 403 404 return (FALSE); /* pacify the compiler */ 405 } 406 407 static void 408 trim_trailing(void) 409 { 410 while (outbuf.used > 0 && outbuf.text[outbuf.used - 1] == ' ') 411 outbuf.text[--outbuf.used] = '\0'; 412 } 413 414 static void 415 force_wrap(void) 416 { 417 oldcol = column; 418 trim_trailing(); 419 strcpy_DYN(&outbuf, trailer); 420 column = indent; 421 } 422 423 static void 424 wrap_concat(const char *src) 425 { 426 size_t need = strlen(src); 427 size_t want = strlen(separator) + need; 428 429 if (column > indent 430 && column + (int) want > width) { 431 force_wrap(); 432 } 433 strcpy_DYN(&outbuf, src); 434 strcpy_DYN(&outbuf, separator); 435 column += (int) need; 436 } 437 438 #define IGNORE_SEP_TRAIL(first,last,sep_trail) \ 439 if ((size_t)(last - first) > sizeof(sep_trail)-1 \ 440 && !strncmp(first, sep_trail, sizeof(sep_trail)-1)) \ 441 first += sizeof(sep_trail)-2 442 443 /* Returns the nominal length of the buffer assuming it is termcap format, 444 * i.e., the continuation sequence is treated as a single character ":". 445 * 446 * There are several implementations of termcap which read the text into a 447 * fixed-size buffer. Generally they strip the newlines from the text, but may 448 * not do it until after the buffer is read. Also, "tc=" resolution may be 449 * expanded in the same buffer. This function is useful for measuring the size 450 * of the best fixed-buffer implementation; the worst case may be much worse. 451 */ 452 #ifdef TEST_TERMCAP_LENGTH 453 static int 454 termcap_length(const char *src) 455 { 456 static const char pattern[] = ":\\\n\t:"; 457 458 int len = 0; 459 const char *const t = src + strlen(src); 460 461 while (*src != '\0') { 462 IGNORE_SEP_TRAIL(src, t, pattern); 463 src++; 464 len++; 465 } 466 return len; 467 } 468 #else 469 #define termcap_length(src) strlen(src) 470 #endif 471 472 static void 473 indent_DYN(DYNBUF * buffer, int level) 474 { 475 int n; 476 477 for (n = 0; n < level; n++) 478 strncpy_DYN(buffer, "\t", (size_t) 1); 479 } 480 481 bool 482 has_params(const char *src) 483 { 484 bool result = FALSE; 485 int len = (int) strlen(src); 486 int n; 487 bool ifthen = FALSE; 488 bool params = FALSE; 489 490 for (n = 0; n < len - 1; ++n) { 491 if (!strncmp(src + n, "%p", (size_t) 2)) { 492 params = TRUE; 493 } else if (!strncmp(src + n, "%;", (size_t) 2)) { 494 ifthen = TRUE; 495 result = params; 496 break; 497 } 498 } 499 if (!ifthen) { 500 result = ((len > 50) && params); 501 } 502 return result; 503 } 504 505 static char * 506 fmt_complex(TERMTYPE *tterm, const char *capability, char *src, int level) 507 { 508 bool percent = FALSE; 509 bool params = has_params(src); 510 511 while (*src != '\0') { 512 switch (*src) { 513 case '^': 514 percent = FALSE; 515 strncpy_DYN(&tmpbuf, src++, (size_t) 1); 516 break; 517 case '\\': 518 percent = FALSE; 519 strncpy_DYN(&tmpbuf, src++, (size_t) 1); 520 break; 521 case '%': 522 percent = TRUE; 523 break; 524 case '?': /* "if" */ 525 case 't': /* "then" */ 526 case 'e': /* "else" */ 527 if (percent) { 528 percent = FALSE; 529 tmpbuf.text[tmpbuf.used - 1] = '\n'; 530 /* treat a "%e" as else-if, on the same level */ 531 if (*src == 'e') { 532 indent_DYN(&tmpbuf, level); 533 strncpy_DYN(&tmpbuf, "%", (size_t) 1); 534 strncpy_DYN(&tmpbuf, src, (size_t) 1); 535 src++; 536 params = has_params(src); 537 if (!params && *src != '\0' && *src != '%') { 538 strncpy_DYN(&tmpbuf, "\n", (size_t) 1); 539 indent_DYN(&tmpbuf, level + 1); 540 } 541 } else { 542 indent_DYN(&tmpbuf, level + 1); 543 strncpy_DYN(&tmpbuf, "%", (size_t) 1); 544 strncpy_DYN(&tmpbuf, src, (size_t) 1); 545 if (*src++ == '?') { 546 src = fmt_complex(tterm, capability, src, level + 1); 547 if (*src != '\0' && *src != '%') { 548 strncpy_DYN(&tmpbuf, "\n", (size_t) 1); 549 indent_DYN(&tmpbuf, level + 1); 550 } 551 } else if (level == 1) { 552 if (checking) 553 _nc_warning("%s: %%%c without %%? in %s", 554 _nc_first_name(tterm->term_names), 555 *src, capability); 556 } 557 } 558 continue; 559 } 560 break; 561 case ';': /* "endif" */ 562 if (percent) { 563 percent = FALSE; 564 if (level > 1) { 565 tmpbuf.text[tmpbuf.used - 1] = '\n'; 566 indent_DYN(&tmpbuf, level); 567 strncpy_DYN(&tmpbuf, "%", (size_t) 1); 568 strncpy_DYN(&tmpbuf, src++, (size_t) 1); 569 if (src[0] == '%' 570 && src[1] != '\0' 571 && (strchr("?e;", src[1])) == 0) { 572 tmpbuf.text[tmpbuf.used++] = '\n'; 573 indent_DYN(&tmpbuf, level); 574 } 575 return src; 576 } 577 if (checking) 578 _nc_warning("%s: %%; without %%? in %s", 579 _nc_first_name(tterm->term_names), 580 capability); 581 } 582 break; 583 case 'p': 584 if (percent && params) { 585 tmpbuf.text[tmpbuf.used - 1] = '\n'; 586 indent_DYN(&tmpbuf, level + 1); 587 strncpy_DYN(&tmpbuf, "%", (size_t) 1); 588 } 589 params = FALSE; 590 percent = FALSE; 591 break; 592 case ' ': 593 strncpy_DYN(&tmpbuf, "\\s", (size_t) 2); 594 ++src; 595 continue; 596 default: 597 percent = FALSE; 598 break; 599 } 600 strncpy_DYN(&tmpbuf, src++, (size_t) 1); 601 } 602 return src; 603 } 604 605 #define SAME_CAP(n,cap) (&tterm->Strings[n] == &cap) 606 #define EXTRA_CAP 20 607 608 int 609 fmt_entry(TERMTYPE *tterm, 610 PredFunc pred, 611 int content_only, 612 int suppress_untranslatable, 613 int infodump, 614 int numbers) 615 { 616 PredIdx i, j; 617 char buffer[MAX_TERMINFO_LENGTH + EXTRA_CAP]; 618 char *capability; 619 NCURSES_CONST char *name; 620 int predval, len; 621 PredIdx num_bools = 0; 622 PredIdx num_values = 0; 623 PredIdx num_strings = 0; 624 bool outcount = 0; 625 626 #define WRAP_CONCAT \ 627 wrap_concat(buffer); \ 628 outcount = TRUE 629 630 len = 12; /* terminfo file-header */ 631 632 if (pred == 0) { 633 cur_type = tterm; 634 pred = dump_predicate; 635 } 636 637 strcpy_DYN(&outbuf, 0); 638 if (content_only) { 639 column = indent; /* FIXME: workaround to prevent empty lines */ 640 } else { 641 strcpy_DYN(&outbuf, tterm->term_names); 642 643 /* 644 * Colon is legal in terminfo descriptions, but not in termcap. 645 */ 646 if (!infodump) { 647 char *p = outbuf.text; 648 while (*p) { 649 if (*p == ':') { 650 *p = '='; 651 } 652 ++p; 653 } 654 } 655 strcpy_DYN(&outbuf, separator); 656 column = (int) outbuf.used; 657 if (height > 1) 658 force_wrap(); 659 } 660 661 for_each_boolean(j, tterm) { 662 i = BoolIndirect(j); 663 name = ExtBoolname(tterm, (int) i, bool_names); 664 assert(strlen(name) < sizeof(buffer) - EXTRA_CAP); 665 666 if (!version_filter(BOOLEAN, i)) 667 continue; 668 else if (isObsolete(outform, name)) 669 continue; 670 671 predval = pred(BOOLEAN, i); 672 if (predval != FAIL) { 673 _nc_STRCPY(buffer, name, sizeof(buffer)); 674 if (predval <= 0) 675 _nc_STRCAT(buffer, "@", sizeof(buffer)); 676 else if (i + 1 > num_bools) 677 num_bools = i + 1; 678 WRAP_CONCAT; 679 } 680 } 681 682 if (column != indent && height > 1) 683 force_wrap(); 684 685 for_each_number(j, tterm) { 686 i = NumIndirect(j); 687 name = ExtNumname(tterm, (int) i, num_names); 688 assert(strlen(name) < sizeof(buffer) - EXTRA_CAP); 689 690 if (!version_filter(NUMBER, i)) 691 continue; 692 else if (isObsolete(outform, name)) 693 continue; 694 695 predval = pred(NUMBER, i); 696 if (predval != FAIL) { 697 if (tterm->Numbers[i] < 0) { 698 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) 699 "%s@", name); 700 } else { 701 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) 702 "%s#%d", name, tterm->Numbers[i]); 703 if (i + 1 > num_values) 704 num_values = i + 1; 705 } 706 WRAP_CONCAT; 707 } 708 } 709 710 if (column != indent && height > 1) 711 force_wrap(); 712 713 len += (int) (num_bools 714 + num_values * 2 715 + strlen(tterm->term_names) + 1); 716 if (len & 1) 717 len++; 718 719 #undef CUR 720 #define CUR tterm-> 721 if (outform == F_TERMCAP) { 722 if (termcap_reset != ABSENT_STRING) { 723 if (init_3string != ABSENT_STRING 724 && !strcmp(init_3string, termcap_reset)) 725 DISCARD(init_3string); 726 727 if (reset_2string != ABSENT_STRING 728 && !strcmp(reset_2string, termcap_reset)) 729 DISCARD(reset_2string); 730 } 731 } 732 733 for_each_string(j, tterm) { 734 i = StrIndirect(j); 735 name = ExtStrname(tterm, (int) i, str_names); 736 assert(strlen(name) < sizeof(buffer) - EXTRA_CAP); 737 738 capability = tterm->Strings[i]; 739 740 if (!version_filter(STRING, i)) 741 continue; 742 else if (isObsolete(outform, name)) 743 continue; 744 745 #if NCURSES_XNAMES 746 /* 747 * Extended names can be longer than 2 characters, but termcap programs 748 * cannot read those (filter them out). 749 */ 750 if (outform == F_TERMCAP && (strlen(name) > 2)) 751 continue; 752 #endif 753 754 if (outform == F_TERMCAP) { 755 /* 756 * Some older versions of vi want rmir/smir to be defined 757 * for ich/ich1 to work. If they're not defined, force 758 * them to be output as defined and empty. 759 */ 760 if (PRESENT(insert_character) || PRESENT(parm_ich)) { 761 if (SAME_CAP(i, enter_insert_mode) 762 && enter_insert_mode == ABSENT_STRING) { 763 _nc_STRCPY(buffer, "im=", sizeof(buffer)); 764 WRAP_CONCAT; 765 continue; 766 } 767 768 if (SAME_CAP(i, exit_insert_mode) 769 && exit_insert_mode == ABSENT_STRING) { 770 _nc_STRCPY(buffer, "ei=", sizeof(buffer)); 771 WRAP_CONCAT; 772 continue; 773 } 774 } 775 /* 776 * termcap applications such as screen will be confused if sgr0 777 * is translated to a string containing rmacs. Filter that out. 778 */ 779 if (PRESENT(exit_attribute_mode)) { 780 if (SAME_CAP(i, exit_attribute_mode)) { 781 char *trimmed_sgr0; 782 char *my_sgr = set_attributes; 783 784 set_attributes = save_sgr; 785 786 trimmed_sgr0 = _nc_trim_sgr0(tterm); 787 if (strcmp(capability, trimmed_sgr0)) 788 capability = trimmed_sgr0; 789 else { 790 if (trimmed_sgr0 != exit_attribute_mode) 791 free(trimmed_sgr0); 792 } 793 794 set_attributes = my_sgr; 795 } 796 } 797 } 798 799 predval = pred(STRING, i); 800 buffer[0] = '\0'; 801 802 if (predval != FAIL) { 803 if (capability != ABSENT_STRING 804 && i + 1 > num_strings) 805 num_strings = i + 1; 806 807 if (!VALID_STRING(capability)) { 808 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) 809 "%s@", name); 810 WRAP_CONCAT; 811 } else if (outform == F_TERMCAP || outform == F_TCONVERR) { 812 char *srccap = _nc_tic_expand(capability, TRUE, numbers); 813 int params = (((i < (int) SIZEOF(parametrized)) && 814 (i < STRCOUNT)) 815 ? parametrized[i] 816 : ((*srccap == 'k') 817 ? 0 818 : has_params(srccap))); 819 char *cv = _nc_infotocap(name, srccap, params); 820 821 if (cv == 0) { 822 if (outform == F_TCONVERR) { 823 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) 824 "%s=!!! %s WILL NOT CONVERT !!!", 825 name, srccap); 826 } else if (suppress_untranslatable) { 827 continue; 828 } else { 829 char *s = srccap, *d = buffer; 830 _nc_SPRINTF(d, _nc_SLIMIT(sizeof(buffer)) "..%s=", name); 831 d += strlen(d); 832 while ((*d = *s++) != 0) { 833 if (*d == ':') { 834 *d++ = '\\'; 835 *d = ':'; 836 } else if (*d == '\\') { 837 *++d = *s++; 838 } 839 d++; 840 } 841 } 842 } else { 843 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) 844 "%s=%s", name, cv); 845 } 846 len += (int) strlen(capability) + 1; 847 WRAP_CONCAT; 848 } else { 849 char *src = _nc_tic_expand(capability, 850 outform == F_TERMINFO, numbers); 851 852 strcpy_DYN(&tmpbuf, 0); 853 strcpy_DYN(&tmpbuf, name); 854 strcpy_DYN(&tmpbuf, "="); 855 if (pretty 856 && (outform == F_TERMINFO 857 || outform == F_VARIABLE)) { 858 fmt_complex(tterm, name, src, 1); 859 } else { 860 strcpy_DYN(&tmpbuf, src); 861 } 862 len += (int) strlen(capability) + 1; 863 wrap_concat(tmpbuf.text); 864 outcount = TRUE; 865 } 866 } 867 /* e.g., trimmed_sgr0 */ 868 if (capability != ABSENT_STRING && 869 capability != CANCELLED_STRING && 870 capability != tterm->Strings[i]) 871 free(capability); 872 } 873 len += (int) (num_strings * 2); 874 875 /* 876 * This piece of code should be an effective inverse of the functions 877 * postprocess_terminfo() and postprocess_terminfo() in parse_entry.c. 878 * Much more work should be done on this to support dumping termcaps. 879 */ 880 if (tversion == V_HPUX) { 881 if (VALID_STRING(memory_lock)) { 882 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) 883 "meml=%s", memory_lock); 884 WRAP_CONCAT; 885 } 886 if (VALID_STRING(memory_unlock)) { 887 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) 888 "memu=%s", memory_unlock); 889 WRAP_CONCAT; 890 } 891 } else if (tversion == V_AIX) { 892 if (VALID_STRING(acs_chars)) { 893 bool box_ok = TRUE; 894 const char *acstrans = "lqkxjmwuvtn"; 895 const char *cp; 896 char *tp, *sp, boxchars[11]; 897 898 tp = boxchars; 899 for (cp = acstrans; *cp; cp++) { 900 sp = (strchr) (acs_chars, *cp); 901 if (sp) 902 *tp++ = sp[1]; 903 else { 904 box_ok = FALSE; 905 break; 906 } 907 } 908 tp[0] = '\0'; 909 910 if (box_ok) { 911 char *tmp = _nc_tic_expand(boxchars, 912 (outform == F_TERMINFO), 913 numbers); 914 _nc_STRCPY(buffer, "box1=", sizeof(buffer)); 915 while (*tmp != '\0') { 916 size_t have = strlen(buffer); 917 size_t next = strlen(tmp); 918 size_t want = have + next + 1; 919 size_t last = next; 920 char save = '\0'; 921 922 /* 923 * If the expanded string is too long for the buffer, 924 * chop it off and save the location where we chopped it. 925 */ 926 if (want >= sizeof(buffer)) { 927 save = tmp[last]; 928 tmp[last] = '\0'; 929 } 930 _nc_STRCAT(buffer, tmp, sizeof(buffer)); 931 932 /* 933 * If we chopped the buffer, replace the missing piece and 934 * shift everything to append the remainder. 935 */ 936 if (save != '\0') { 937 next = 0; 938 tmp[last] = save; 939 while ((tmp[next] = tmp[last + next]) != '\0') { 940 ++next; 941 } 942 } else { 943 break; 944 } 945 } 946 WRAP_CONCAT; 947 } 948 } 949 } 950 951 /* 952 * kludge: trim off trailer to avoid an extra blank line 953 * in infocmp -u output when there are no string differences 954 */ 955 if (outcount) { 956 bool trimmed = FALSE; 957 j = (PredIdx) outbuf.used; 958 if (j >= 2 959 && outbuf.text[j - 1] == '\t' 960 && outbuf.text[j - 2] == '\n') { 961 outbuf.used -= 2; 962 trimmed = TRUE; 963 } else if (j >= 4 964 && outbuf.text[j - 1] == ':' 965 && outbuf.text[j - 2] == '\t' 966 && outbuf.text[j - 3] == '\n' 967 && outbuf.text[j - 4] == '\\') { 968 outbuf.used -= 4; 969 trimmed = TRUE; 970 } 971 if (trimmed) { 972 outbuf.text[outbuf.used] = '\0'; 973 column = oldcol; 974 strcpy_DYN(&outbuf, " "); 975 } 976 } 977 #if 0 978 fprintf(stderr, "num_bools = %d\n", num_bools); 979 fprintf(stderr, "num_values = %d\n", num_values); 980 fprintf(stderr, "num_strings = %d\n", num_strings); 981 fprintf(stderr, "term_names=%s, len=%d, strlen(outbuf)=%d, outbuf=%s\n", 982 tterm->term_names, len, outbuf.used, outbuf.text); 983 #endif 984 /* 985 * Here's where we use infodump to trigger a more stringent length check 986 * for termcap-translation purposes. 987 * Return the length of the raw entry, without tc= expansions, 988 * It gives an idea of which entries are deadly to even *scan past*, 989 * as opposed to *use*. 990 */ 991 return (infodump ? len : (int) termcap_length(outbuf.text)); 992 } 993 994 static bool 995 kill_string(TERMTYPE *tterm, char *cap) 996 { 997 unsigned n; 998 for (n = 0; n < NUM_STRINGS(tterm); ++n) { 999 if (cap == tterm->Strings[n]) { 1000 tterm->Strings[n] = ABSENT_STRING; 1001 return TRUE; 1002 } 1003 } 1004 return FALSE; 1005 } 1006 1007 static char * 1008 find_string(TERMTYPE *tterm, char *name) 1009 { 1010 PredIdx n; 1011 for (n = 0; n < NUM_STRINGS(tterm); ++n) { 1012 if (version_filter(STRING, n) 1013 && !strcmp(name, strnames[n])) { 1014 char *cap = tterm->Strings[n]; 1015 if (VALID_STRING(cap)) { 1016 return cap; 1017 } 1018 break; 1019 } 1020 } 1021 return ABSENT_STRING; 1022 } 1023 1024 /* 1025 * This is used to remove function-key labels from a termcap entry to 1026 * make it smaller. 1027 */ 1028 static int 1029 kill_labels(TERMTYPE *tterm, int target) 1030 { 1031 int n; 1032 int result = 0; 1033 char *cap; 1034 char name[10]; 1035 1036 for (n = 0; n <= 10; ++n) { 1037 _nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "lf%d", n); 1038 if ((cap = find_string(tterm, name)) != ABSENT_STRING 1039 && kill_string(tterm, cap)) { 1040 target -= (int) (strlen(cap) + 5); 1041 ++result; 1042 if (target < 0) 1043 break; 1044 } 1045 } 1046 return result; 1047 } 1048 1049 /* 1050 * This is used to remove function-key definitions from a termcap entry to 1051 * make it smaller. 1052 */ 1053 static int 1054 kill_fkeys(TERMTYPE *tterm, int target) 1055 { 1056 int n; 1057 int result = 0; 1058 char *cap; 1059 char name[10]; 1060 1061 for (n = 60; n >= 0; --n) { 1062 _nc_SPRINTF(name, _nc_SLIMIT(sizeof(name)) "kf%d", n); 1063 if ((cap = find_string(tterm, name)) != ABSENT_STRING 1064 && kill_string(tterm, cap)) { 1065 target -= (int) (strlen(cap) + 5); 1066 ++result; 1067 if (target < 0) 1068 break; 1069 } 1070 } 1071 return result; 1072 } 1073 1074 /* 1075 * Check if the given acsc string is a 1-1 mapping, i.e., just-like-vt100. 1076 * Also, since this is for termcap, we only care about the line-drawing map. 1077 */ 1078 #define isLine(c) (strchr("lmkjtuvwqxn", c) != 0) 1079 1080 static bool 1081 one_one_mapping(const char *mapping) 1082 { 1083 bool result = TRUE; 1084 1085 if (mapping != ABSENT_STRING) { 1086 int n = 0; 1087 while (mapping[n] != '\0') { 1088 if (isLine(mapping[n]) && 1089 mapping[n] != mapping[n + 1]) { 1090 result = FALSE; 1091 break; 1092 } 1093 n += 2; 1094 } 1095 } 1096 return result; 1097 } 1098 1099 #define FMT_ENTRY() \ 1100 fmt_entry(tterm, pred, \ 1101 0, \ 1102 suppress_untranslatable, \ 1103 infodump, numbers) 1104 1105 #define SHOW_WHY PRINTF 1106 1107 static bool 1108 purged_acs(TERMTYPE *tterm) 1109 { 1110 bool result = FALSE; 1111 1112 if (VALID_STRING(acs_chars)) { 1113 if (!one_one_mapping(acs_chars)) { 1114 enter_alt_charset_mode = ABSENT_STRING; 1115 exit_alt_charset_mode = ABSENT_STRING; 1116 SHOW_WHY("# (rmacs/smacs removed for consistency)\n"); 1117 } 1118 result = TRUE; 1119 } 1120 return result; 1121 } 1122 1123 #ifndef BOOTSTRAPPING 1124 static void 1125 encode_b64(char *target, char *source, unsigned state, int *saved) 1126 { 1127 /* RFC-4648 */ 1128 static const char data[] = 1129 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 1130 "abcdefghijklmnopqrstuvwxyz" 1131 "0123456789" "-_"; 1132 int ch = UChar(source[state]); 1133 1134 switch (state % 3) { 1135 case 0: 1136 *target++ = data[ch & 077]; 1137 *saved = (ch >> 6) & 3; 1138 break; 1139 case 1: 1140 *target++ = data[((ch << 2) | *saved) & 077]; 1141 *saved = (ch >> 4) & 017; 1142 break; 1143 case 2: 1144 *target++ = data[((ch << 4) | *saved) & 077]; 1145 *target++ = data[(ch >> 2) & 077]; 1146 *saved = 0; 1147 break; 1148 } 1149 *target = '\0'; 1150 } 1151 #endif 1152 1153 /* 1154 * Dump a single entry. 1155 */ 1156 void 1157 dump_entry(TERMTYPE *tterm, 1158 int suppress_untranslatable, 1159 int limited, 1160 int numbers, 1161 PredFunc pred) 1162 { 1163 TERMTYPE save_tterm; 1164 int len, critlen; 1165 const char *legend; 1166 bool infodump; 1167 1168 #ifndef BOOTSTRAPPING 1169 if (quickdump) { 1170 char bigbuf[65536]; 1171 unsigned n; 1172 unsigned offset = 0; 1173 separator = ""; 1174 trailer = "\n"; 1175 indent = 0; 1176 if (_nc_write_object(tterm, bigbuf, &offset, sizeof(bigbuf)) == OK) { 1177 char numbuf[80]; 1178 if (quickdump & 1) { 1179 if (outbuf.used) 1180 wrap_concat("\n"); 1181 wrap_concat("hex:"); 1182 for (n = 0; n < offset; ++n) { 1183 sprintf(numbuf, "%02X", UChar(bigbuf[n])); 1184 wrap_concat(numbuf); 1185 } 1186 } 1187 if (quickdump & 2) { 1188 int value = 0; 1189 if (outbuf.used) 1190 wrap_concat("\n"); 1191 wrap_concat("b64:"); 1192 for (n = 0; n < offset; ++n) { 1193 encode_b64(numbuf, bigbuf, n, &value); 1194 wrap_concat(numbuf); 1195 } 1196 switch (n % 3) { 1197 case 0: 1198 break; 1199 case 1: 1200 wrap_concat("==="); 1201 break; 1202 case 2: 1203 wrap_concat("=="); 1204 break; 1205 } 1206 } 1207 } 1208 return; 1209 } 1210 #endif 1211 1212 if (outform == F_TERMCAP || outform == F_TCONVERR) { 1213 critlen = MAX_TERMCAP_LENGTH; 1214 legend = "older termcap"; 1215 infodump = FALSE; 1216 set_obsolete_termcaps(tterm); 1217 } else { 1218 critlen = MAX_TERMINFO_LENGTH; 1219 legend = "terminfo"; 1220 infodump = TRUE; 1221 } 1222 1223 save_sgr = set_attributes; 1224 1225 if ((FMT_ENTRY() > critlen) 1226 && limited) { 1227 1228 save_tterm = *tterm; 1229 if (!suppress_untranslatable) { 1230 SHOW_WHY("# (untranslatable capabilities removed to fit entry within %d bytes)\n", 1231 critlen); 1232 suppress_untranslatable = TRUE; 1233 } 1234 if (FMT_ENTRY() > critlen) { 1235 /* 1236 * We pick on sgr because it's a nice long string capability that 1237 * is really just an optimization hack. Another good candidate is 1238 * acsc since it is both long and unused by BSD termcap. 1239 */ 1240 bool changed = FALSE; 1241 1242 #if NCURSES_XNAMES 1243 /* 1244 * Extended names are most likely function-key definitions. Drop 1245 * those first. 1246 */ 1247 unsigned n; 1248 for (n = STRCOUNT; n < NUM_STRINGS(tterm); n++) { 1249 const char *name = ExtStrname(tterm, (int) n, strnames); 1250 1251 if (VALID_STRING(tterm->Strings[n])) { 1252 set_attributes = ABSENT_STRING; 1253 /* we remove long names anyway - only report the short */ 1254 if (strlen(name) <= 2) { 1255 SHOW_WHY("# (%s removed to fit entry within %d bytes)\n", 1256 name, 1257 critlen); 1258 } 1259 changed = TRUE; 1260 if (FMT_ENTRY() <= critlen) 1261 break; 1262 } 1263 } 1264 #endif 1265 if (VALID_STRING(set_attributes)) { 1266 set_attributes = ABSENT_STRING; 1267 SHOW_WHY("# (sgr removed to fit entry within %d bytes)\n", 1268 critlen); 1269 changed = TRUE; 1270 } 1271 if (!changed || (FMT_ENTRY() > critlen)) { 1272 if (purged_acs(tterm)) { 1273 acs_chars = ABSENT_STRING; 1274 SHOW_WHY("# (acsc removed to fit entry within %d bytes)\n", 1275 critlen); 1276 changed = TRUE; 1277 } 1278 } 1279 if (!changed || (FMT_ENTRY() > critlen)) { 1280 int oldversion = tversion; 1281 1282 tversion = V_BSD; 1283 SHOW_WHY("# (terminfo-only capabilities suppressed to fit entry within %d bytes)\n", 1284 critlen); 1285 1286 len = FMT_ENTRY(); 1287 if (len > critlen 1288 && kill_labels(tterm, len - critlen)) { 1289 SHOW_WHY("# (some labels capabilities suppressed to fit entry within %d bytes)\n", 1290 critlen); 1291 len = FMT_ENTRY(); 1292 } 1293 if (len > critlen 1294 && kill_fkeys(tterm, len - critlen)) { 1295 SHOW_WHY("# (some function-key capabilities suppressed to fit entry within %d bytes)\n", 1296 critlen); 1297 len = FMT_ENTRY(); 1298 } 1299 if (len > critlen) { 1300 (void) fprintf(stderr, 1301 "warning: %s entry is %d bytes long\n", 1302 _nc_first_name(tterm->term_names), 1303 len); 1304 SHOW_WHY("# WARNING: this entry, %d bytes long, may core-dump %s libraries!\n", 1305 len, legend); 1306 } 1307 tversion = oldversion; 1308 } 1309 set_attributes = save_sgr; 1310 *tterm = save_tterm; 1311 } 1312 } else if (!version_filter(STRING, STR_IDX(acs_chars))) { 1313 save_tterm = *tterm; 1314 if (purged_acs(tterm)) { 1315 (void) FMT_ENTRY(); 1316 } 1317 *tterm = save_tterm; 1318 } 1319 } 1320 1321 void 1322 dump_uses(const char *name, bool infodump) 1323 /* dump "use=" clauses in the appropriate format */ 1324 { 1325 char buffer[MAX_TERMINFO_LENGTH]; 1326 1327 if (outform == F_TERMCAP || outform == F_TCONVERR) 1328 trim_trailing(); 1329 _nc_SPRINTF(buffer, _nc_SLIMIT(sizeof(buffer)) 1330 "%s%s", infodump ? "use=" : "tc=", name); 1331 wrap_concat(buffer); 1332 } 1333 1334 int 1335 show_entry(void) 1336 { 1337 /* 1338 * Trim any remaining whitespace. 1339 */ 1340 if (outbuf.used != 0) { 1341 bool infodump = (outform != F_TERMCAP && outform != F_TCONVERR); 1342 char delim = (char) (infodump ? ',' : ':'); 1343 int j; 1344 1345 for (j = (int) outbuf.used - 1; j > 0; --j) { 1346 char ch = outbuf.text[j]; 1347 if (ch == '\n') { 1348 ; 1349 } else if (isspace(UChar(ch))) { 1350 outbuf.used = (size_t) j; 1351 } else if (!infodump && ch == '\\') { 1352 outbuf.used = (size_t) j; 1353 } else if (ch == delim && (j == 0 || outbuf.text[j - 1] != '\\')) { 1354 outbuf.used = (size_t) (j + 1); 1355 } else { 1356 break; 1357 } 1358 } 1359 outbuf.text[outbuf.used] = '\0'; 1360 } 1361 if (outbuf.text != 0) { 1362 (void) fputs(outbuf.text, stdout); 1363 putchar('\n'); 1364 } 1365 return (int) outbuf.used; 1366 } 1367 1368 void 1369 compare_entry(PredHook hook, 1370 TERMTYPE *tp GCC_UNUSED, 1371 bool quiet) 1372 /* compare two entries */ 1373 { 1374 PredIdx i, j; 1375 NCURSES_CONST char *name; 1376 1377 if (!quiet) 1378 fputs(" comparing booleans.\n", stdout); 1379 for_each_boolean(j, tp) { 1380 i = BoolIndirect(j); 1381 name = ExtBoolname(tp, (int) i, bool_names); 1382 1383 if (isObsolete(outform, name)) 1384 continue; 1385 1386 (*hook) (CMP_BOOLEAN, i, name); 1387 } 1388 1389 if (!quiet) 1390 fputs(" comparing numbers.\n", stdout); 1391 for_each_number(j, tp) { 1392 i = NumIndirect(j); 1393 name = ExtNumname(tp, (int) i, num_names); 1394 1395 if (isObsolete(outform, name)) 1396 continue; 1397 1398 (*hook) (CMP_NUMBER, i, name); 1399 } 1400 1401 if (!quiet) 1402 fputs(" comparing strings.\n", stdout); 1403 for_each_string(j, tp) { 1404 i = StrIndirect(j); 1405 name = ExtStrname(tp, (int) i, str_names); 1406 1407 if (isObsolete(outform, name)) 1408 continue; 1409 1410 (*hook) (CMP_STRING, i, name); 1411 } 1412 1413 /* (void) fputs(" comparing use entries.\n", stdout); */ 1414 (*hook) (CMP_USE, 0, "use"); 1415 1416 } 1417 1418 #define NOTSET(s) ((s) == 0) 1419 1420 /* 1421 * This bit of legerdemain turns all the terminfo variable names into 1422 * references to locations in the arrays Booleans, Numbers, and Strings --- 1423 * precisely what's needed. 1424 */ 1425 #undef CUR 1426 #define CUR tp-> 1427 1428 static void 1429 set_obsolete_termcaps(TERMTYPE *tp) 1430 { 1431 #include "capdefaults.c" 1432 } 1433 1434 /* 1435 * Convert an alternate-character-set string to canonical form: sorted and 1436 * unique. 1437 */ 1438 void 1439 repair_acsc(TERMTYPE *tp) 1440 { 1441 if (VALID_STRING(acs_chars)) { 1442 size_t n, m; 1443 char mapped[256]; 1444 char extra = 0; 1445 unsigned source; 1446 unsigned target; 1447 bool fix_needed = FALSE; 1448 1449 for (n = 0, source = 0; acs_chars[n] != 0; n++) { 1450 target = UChar(acs_chars[n]); 1451 if (source >= target) { 1452 fix_needed = TRUE; 1453 break; 1454 } 1455 source = target; 1456 if (acs_chars[n + 1]) 1457 n++; 1458 } 1459 if (fix_needed) { 1460 memset(mapped, 0, sizeof(mapped)); 1461 for (n = 0; acs_chars[n] != 0; n++) { 1462 source = UChar(acs_chars[n]); 1463 if ((target = (unsigned char) acs_chars[n + 1]) != 0) { 1464 mapped[source] = (char) target; 1465 n++; 1466 } else { 1467 extra = (char) source; 1468 } 1469 } 1470 for (n = m = 0; n < sizeof(mapped); n++) { 1471 if (mapped[n]) { 1472 acs_chars[m++] = (char) n; 1473 acs_chars[m++] = mapped[n]; 1474 } 1475 } 1476 if (extra) 1477 acs_chars[m++] = extra; /* garbage in, garbage out */ 1478 acs_chars[m] = 0; 1479 } 1480 } 1481 } 1482