1 /**************************************************************************** 2 * Copyright (c) 1998-2012,2013 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 /* 36 * comp_parse.c -- parser driver loop and use handling. 37 * 38 * Use this code by calling _nc_read_entry_source() on as many source 39 * files as you like (either terminfo or termcap syntax). If you 40 * want use-resolution, call _nc_resolve_uses2(). To free the list 41 * storage, do _nc_free_entries(). 42 */ 43 44 #include <curses.priv.h> 45 46 #include <ctype.h> 47 48 #include <tic.h> 49 50 MODULE_ID("$Id: comp_parse.c,v 1.90 2013/08/31 15:22:31 tom Exp $") 51 52 static void sanity_check2(TERMTYPE *, bool); 53 NCURSES_IMPEXP void NCURSES_API(*_nc_check_termtype2) (TERMTYPE *, bool) = sanity_check2; 54 55 /* obsolete: 20040705 */ 56 static void sanity_check(TERMTYPE *); 57 NCURSES_IMPEXP void NCURSES_API(*_nc_check_termtype) (TERMTYPE *) = sanity_check; 58 59 static void fixup_acsc(TERMTYPE *, int); 60 61 static void 62 enqueue(ENTRY * ep) 63 /* add an entry to the in-core list */ 64 { 65 ENTRY *newp = _nc_copy_entry(ep); 66 67 if (newp == 0) 68 _nc_err_abort(MSG_NO_MEMORY); 69 70 newp->last = _nc_tail; 71 _nc_tail = newp; 72 73 newp->next = 0; 74 if (newp->last) 75 newp->last->next = newp; 76 } 77 78 static char * 79 force_bar(char *dst, char *src) 80 { 81 if (strchr(src, '|') == 0) { 82 size_t len = strlen(src); 83 if (len > MAX_NAME_SIZE) 84 len = MAX_NAME_SIZE; 85 (void) strncpy(dst, src, len); 86 _nc_STRCPY(dst + len, "|", MAX_NAME_SIZE); 87 src = dst; 88 } 89 return src; 90 } 91 #define ForceBar(dst, src) ((strchr(src, '|') == 0) ? force_bar(dst, src) : src) 92 93 #if NCURSES_USE_TERMCAP && NCURSES_XNAMES 94 static char * 95 skip_index(char *name) 96 { 97 char *bar = strchr(name, '|'); 98 99 if (bar != 0 && (bar - name) == 2) 100 name = bar + 1; 101 102 return name; 103 } 104 #endif 105 106 static bool 107 check_collisions(char *n1, char *n2, int counter) 108 { 109 char *pstart, *qstart, *pend, *qend; 110 char nc1[MAX_NAME_SIZE + 2]; 111 char nc2[MAX_NAME_SIZE + 2]; 112 113 n1 = ForceBar(nc1, n1); 114 n2 = ForceBar(nc2, n2); 115 116 #if NCURSES_USE_TERMCAP && NCURSES_XNAMES 117 if ((_nc_syntax == SYN_TERMCAP) && _nc_user_definable) { 118 n1 = skip_index(n1); 119 n2 = skip_index(n2); 120 } 121 #endif 122 123 for (pstart = n1; (pend = strchr(pstart, '|')); pstart = pend + 1) { 124 for (qstart = n2; (qend = strchr(qstart, '|')); qstart = qend + 1) { 125 if ((pend - pstart == qend - qstart) 126 && memcmp(pstart, qstart, (size_t) (pend - pstart)) == 0) { 127 if (counter > 0) 128 (void) fprintf(stderr, "Name collision '%.*s' between\n", 129 (int) (pend - pstart), pstart); 130 return (TRUE); 131 } 132 } 133 } 134 135 return (FALSE); 136 } 137 138 static char * 139 next_name(char *name) 140 { 141 if (*name != '\0') 142 ++name; 143 return name; 144 } 145 146 static char * 147 name_ending(char *name) 148 { 149 if (*name == '\0') { 150 name = 0; 151 } else { 152 while (*name != '\0' && *name != '|') 153 ++name; 154 } 155 return name; 156 } 157 158 /* 159 * Essentially, find the conflict reported in check_collisions() and remove 160 * it from the second name, unless that happens to be the last alias. 161 */ 162 static bool 163 remove_collision(char *n1, char *n2) 164 { 165 char *p2 = n2; 166 char *pstart, *qstart, *pend, *qend; 167 bool removed = FALSE; 168 169 #if NCURSES_USE_TERMCAP && NCURSES_XNAMES 170 if ((_nc_syntax == SYN_TERMCAP) && _nc_user_definable) { 171 n1 = skip_index(n1); 172 p2 = n2 = skip_index(n2); 173 } 174 #endif 175 176 for (pstart = n1; (pend = name_ending(pstart)); pstart = next_name(pend)) { 177 for (qstart = n2; (qend = name_ending(qstart)); qstart = next_name(qend)) { 178 if ((pend - pstart == qend - qstart) 179 && memcmp(pstart, qstart, (size_t) (pend - pstart)) == 0) { 180 if (qstart != p2 || *qend == '|') { 181 if (*qend == '|') 182 ++qend; 183 while ((*qstart++ = *qend++) != '\0') ; 184 fprintf(stderr, "...now\t%s\n", p2); 185 } else { 186 fprintf(stderr, "Cannot remove alias '%.*s'\n", 187 (int) (qend - qstart), qstart); 188 } 189 removed = TRUE; 190 break; 191 } 192 } 193 } 194 195 return removed; 196 } 197 198 /* do any of the aliases in a pair of terminal names match? */ 199 NCURSES_EXPORT(bool) 200 _nc_entry_match(char *n1, char *n2) 201 { 202 return check_collisions(n1, n2, 0); 203 } 204 205 /**************************************************************************** 206 * 207 * Entry compiler and resolution logic 208 * 209 ****************************************************************************/ 210 211 NCURSES_EXPORT(void) 212 _nc_read_entry_source(FILE *fp, char *buf, 213 int literal, bool silent, 214 bool(*hook) (ENTRY *)) 215 /* slurp all entries in the given file into core */ 216 { 217 ENTRY thisentry; 218 bool oldsuppress = _nc_suppress_warnings; 219 int immediate = 0; 220 221 if (silent) 222 _nc_suppress_warnings = TRUE; /* shut the lexer up, too */ 223 224 _nc_reset_input(fp, buf); 225 for (;;) { 226 memset(&thisentry, 0, sizeof(thisentry)); 227 if (_nc_parse_entry(&thisentry, literal, silent) == ERR) 228 break; 229 if (!isalnum(UChar(thisentry.tterm.term_names[0]))) 230 _nc_err_abort("terminal names must start with letter or digit"); 231 232 /* 233 * This can be used for immediate compilation of entries with no "use=" 234 * references to disk. That avoids consuming a lot of memory when the 235 * resolution code could fetch entries off disk. 236 */ 237 if (hook != NULLHOOK && (*hook) (&thisentry)) { 238 immediate++; 239 } else { 240 enqueue(&thisentry); 241 /* 242 * The enqueued entry is copied with _nc_copy_termtype(), so we can 243 * free some of the data from thisentry, i.e., the arrays. 244 */ 245 FreeIfNeeded(thisentry.tterm.Booleans); 246 FreeIfNeeded(thisentry.tterm.Numbers); 247 FreeIfNeeded(thisentry.tterm.Strings); 248 #if NCURSES_XNAMES 249 FreeIfNeeded(thisentry.tterm.ext_Names); 250 #endif 251 } 252 } 253 254 if (_nc_tail) { 255 /* set up the head pointer */ 256 for (_nc_head = _nc_tail; _nc_head->last; _nc_head = _nc_head->last) 257 continue; 258 259 DEBUG(1, ("head = %s", _nc_head->tterm.term_names)); 260 DEBUG(1, ("tail = %s", _nc_tail->tterm.term_names)); 261 } 262 #ifdef TRACE 263 else if (!immediate) 264 DEBUG(1, ("no entries parsed")); 265 #endif 266 267 _nc_suppress_warnings = oldsuppress; 268 } 269 270 NCURSES_EXPORT(int) 271 _nc_resolve_uses2(bool fullresolve, bool literal) 272 /* try to resolve all use capabilities */ 273 { 274 ENTRY *qp, *rp, *lastread = 0; 275 bool keepgoing; 276 unsigned i; 277 int unresolved, total_unresolved, multiples; 278 279 DEBUG(2, ("RESOLUTION BEGINNING")); 280 281 /* 282 * Check for multiple occurrences of the same name. 283 */ 284 multiples = 0; 285 for_entry_list(qp) { 286 int matchcount = 0; 287 288 for_entry_list(rp) { 289 if (qp > rp 290 && check_collisions(qp->tterm.term_names, 291 rp->tterm.term_names, 292 matchcount + 1)) { 293 if (!matchcount++) { 294 (void) fprintf(stderr, "\t%s\n", rp->tterm.term_names); 295 } 296 (void) fprintf(stderr, "and\t%s\n", qp->tterm.term_names); 297 if (!remove_collision(rp->tterm.term_names, 298 qp->tterm.term_names)) { 299 ++multiples; 300 } 301 } 302 } 303 } 304 if (multiples > 0) 305 return (FALSE); 306 307 DEBUG(2, ("NO MULTIPLE NAME OCCURRENCES")); 308 309 /* 310 * First resolution stage: compute link pointers corresponding to names. 311 */ 312 total_unresolved = 0; 313 _nc_curr_col = -1; 314 for_entry_list(qp) { 315 unresolved = 0; 316 for (i = 0; i < qp->nuses; i++) { 317 bool foundit; 318 char *child = _nc_first_name(qp->tterm.term_names); 319 char *lookfor = qp->uses[i].name; 320 long lookline = qp->uses[i].line; 321 322 foundit = FALSE; 323 324 _nc_set_type(child); 325 326 /* first, try to resolve from in-core records */ 327 for_entry_list(rp) { 328 if (rp != qp 329 && _nc_name_match(rp->tterm.term_names, lookfor, "|")) { 330 DEBUG(2, ("%s: resolving use=%s (in core)", 331 child, lookfor)); 332 333 qp->uses[i].link = rp; 334 foundit = TRUE; 335 } 336 } 337 338 /* if that didn't work, try to merge in a compiled entry */ 339 if (!foundit) { 340 TERMTYPE thisterm; 341 char filename[PATH_MAX]; 342 343 memset(&thisterm, 0, sizeof(thisterm)); 344 if (_nc_read_entry(lookfor, filename, &thisterm) == 1) { 345 DEBUG(2, ("%s: resolving use=%s (compiled)", 346 child, lookfor)); 347 348 TYPE_MALLOC(ENTRY, 1, rp); 349 rp->tterm = thisterm; 350 rp->nuses = 0; 351 rp->next = lastread; 352 lastread = rp; 353 354 qp->uses[i].link = rp; 355 foundit = TRUE; 356 } 357 } 358 359 /* no good, mark this one unresolvable and complain */ 360 if (!foundit) { 361 unresolved++; 362 total_unresolved++; 363 364 _nc_curr_line = (int) lookline; 365 _nc_warning("resolution of use=%s failed", lookfor); 366 qp->uses[i].link = 0; 367 } 368 } 369 } 370 if (total_unresolved) { 371 /* free entries read in off disk */ 372 _nc_free_entries(lastread); 373 return (FALSE); 374 } 375 376 DEBUG(2, ("NAME RESOLUTION COMPLETED OK")); 377 378 /* 379 * OK, at this point all (char *) references in `name' members 380 * have been successfully converted to (ENTRY *) pointers in 381 * `link' members. Time to do the actual merges. 382 */ 383 if (fullresolve) { 384 do { 385 TERMTYPE merged; 386 387 keepgoing = FALSE; 388 389 for_entry_list(qp) { 390 if (qp->nuses > 0) { 391 DEBUG(2, ("%s: attempting merge", 392 _nc_first_name(qp->tterm.term_names))); 393 /* 394 * If any of the use entries we're looking for is 395 * incomplete, punt. We'll catch this entry on a 396 * subsequent pass. 397 */ 398 for (i = 0; i < qp->nuses; i++) 399 if (qp->uses[i].link->nuses) { 400 DEBUG(2, ("%s: use entry %d unresolved", 401 _nc_first_name(qp->tterm.term_names), i)); 402 goto incomplete; 403 } 404 405 /* 406 * First, make sure there is no garbage in the 407 * merge block. As a side effect, copy into 408 * the merged entry the name field and string 409 * table pointer. 410 */ 411 _nc_copy_termtype(&merged, &(qp->tterm)); 412 413 /* 414 * Now merge in each use entry in the proper 415 * (reverse) order. 416 */ 417 for (; qp->nuses; qp->nuses--) 418 _nc_merge_entry(&merged, 419 &qp->uses[qp->nuses - 1].link->tterm); 420 421 /* 422 * Now merge in the original entry. 423 */ 424 _nc_merge_entry(&merged, &qp->tterm); 425 426 /* 427 * Replace the original entry with the merged one. 428 */ 429 FreeIfNeeded(qp->tterm.Booleans); 430 FreeIfNeeded(qp->tterm.Numbers); 431 FreeIfNeeded(qp->tterm.Strings); 432 #if NCURSES_XNAMES 433 FreeIfNeeded(qp->tterm.ext_Names); 434 #endif 435 qp->tterm = merged; 436 _nc_wrap_entry(qp, TRUE); 437 438 /* 439 * We know every entry is resolvable because name resolution 440 * didn't bomb. So go back for another pass. 441 */ 442 /* FALLTHRU */ 443 incomplete: 444 keepgoing = TRUE; 445 } 446 } 447 } while 448 (keepgoing); 449 450 DEBUG(2, ("MERGES COMPLETED OK")); 451 } 452 453 /* 454 * We'd like to free entries read in off disk at this point, but can't. 455 * The merge_entry() code doesn't copy the strings in the use entries, 456 * it just aliases them. If this ever changes, do a 457 * free_entries(lastread) here. 458 */ 459 460 DEBUG(2, ("RESOLUTION FINISHED")); 461 462 if (fullresolve) 463 if (_nc_check_termtype != 0) { 464 _nc_curr_col = -1; 465 for_entry_list(qp) { 466 _nc_curr_line = (int) qp->startline; 467 _nc_set_type(_nc_first_name(qp->tterm.term_names)); 468 /* 469 * tic overrides this function pointer to provide more verbose 470 * checking. 471 */ 472 if (_nc_check_termtype2 != sanity_check2) { 473 SCREEN *save_SP = SP; 474 SCREEN fake_sp; 475 TERMINAL fake_tm; 476 TERMINAL *save_tm = cur_term; 477 478 /* 479 * Setup so that tic can use ordinary terminfo interface 480 * to obtain capability information. 481 */ 482 memset(&fake_sp, 0, sizeof(fake_sp)); 483 memset(&fake_tm, 0, sizeof(fake_tm)); 484 fake_sp._term = &fake_tm; 485 fake_tm.type = qp->tterm; 486 _nc_set_screen(&fake_sp); 487 set_curterm(&fake_tm); 488 489 _nc_check_termtype2(&qp->tterm, literal); 490 491 _nc_set_screen(save_SP); 492 set_curterm(save_tm); 493 } else { 494 fixup_acsc(&qp->tterm, literal); 495 } 496 } 497 DEBUG(2, ("SANITY CHECK FINISHED")); 498 } 499 500 return (TRUE); 501 } 502 503 /* obsolete: 20040705 */ 504 NCURSES_EXPORT(int) 505 _nc_resolve_uses(bool fullresolve) 506 { 507 return _nc_resolve_uses2(fullresolve, FALSE); 508 } 509 510 /* 511 * This bit of legerdemain turns all the terminfo variable names into 512 * references to locations in the arrays Booleans, Numbers, and Strings --- 513 * precisely what's needed. 514 */ 515 516 #undef CUR 517 #define CUR tp-> 518 519 static void 520 fixup_acsc(TERMTYPE *tp, int literal) 521 { 522 if (!literal) { 523 if (acs_chars == 0 524 && enter_alt_charset_mode != 0 525 && exit_alt_charset_mode != 0) 526 acs_chars = strdup(VT_ACSC); 527 } 528 } 529 530 static void 531 sanity_check2(TERMTYPE *tp, bool literal) 532 { 533 if (!PRESENT(exit_attribute_mode)) { 534 #ifdef __UNUSED__ /* this casts too wide a net */ 535 bool terminal_entry = !strchr(tp->term_names, '+'); 536 if (terminal_entry && 537 (PRESENT(set_attributes) 538 || PRESENT(enter_standout_mode) 539 || PRESENT(enter_underline_mode) 540 || PRESENT(enter_blink_mode) 541 || PRESENT(enter_bold_mode) 542 || PRESENT(enter_dim_mode) 543 || PRESENT(enter_secure_mode) 544 || PRESENT(enter_protected_mode) 545 || PRESENT(enter_reverse_mode))) 546 _nc_warning("no exit_attribute_mode"); 547 #endif /* __UNUSED__ */ 548 PAIRED(enter_standout_mode, exit_standout_mode); 549 PAIRED(enter_underline_mode, exit_underline_mode); 550 PAIRED(enter_italics_mode, exit_italics_mode); 551 } 552 553 /* we do this check/fix in postprocess_termcap(), but some packagers 554 * prefer to bypass it... 555 */ 556 if (!literal) { 557 fixup_acsc(tp, literal); 558 ANDMISSING(enter_alt_charset_mode, acs_chars); 559 ANDMISSING(exit_alt_charset_mode, acs_chars); 560 } 561 562 /* listed in structure-member order of first argument */ 563 PAIRED(enter_alt_charset_mode, exit_alt_charset_mode); 564 ANDMISSING(enter_blink_mode, exit_attribute_mode); 565 ANDMISSING(enter_bold_mode, exit_attribute_mode); 566 PAIRED(exit_ca_mode, enter_ca_mode); 567 PAIRED(enter_delete_mode, exit_delete_mode); 568 ANDMISSING(enter_dim_mode, exit_attribute_mode); 569 PAIRED(enter_insert_mode, exit_insert_mode); 570 ANDMISSING(enter_secure_mode, exit_attribute_mode); 571 ANDMISSING(enter_protected_mode, exit_attribute_mode); 572 ANDMISSING(enter_reverse_mode, exit_attribute_mode); 573 PAIRED(from_status_line, to_status_line); 574 PAIRED(meta_off, meta_on); 575 576 PAIRED(prtr_on, prtr_off); 577 PAIRED(save_cursor, restore_cursor); 578 PAIRED(enter_xon_mode, exit_xon_mode); 579 PAIRED(enter_am_mode, exit_am_mode); 580 ANDMISSING(label_off, label_on); 581 #ifdef remove_clock 582 PAIRED(display_clock, remove_clock); 583 #endif 584 ANDMISSING(set_color_pair, initialize_pair); 585 } 586 587 /* obsolete: 20040705 */ 588 static void 589 sanity_check(TERMTYPE *tp) 590 { 591 sanity_check2(tp, FALSE); 592 } 593 594 #if NO_LEAKS 595 NCURSES_EXPORT(void) 596 _nc_leaks_tic(void) 597 { 598 _nc_alloc_entry_leaks(); 599 _nc_captoinfo_leaks(); 600 _nc_comp_scan_leaks(); 601 #if BROKEN_LINKER || USE_REENTRANT 602 _nc_names_leaks(); 603 _nc_codes_leaks(); 604 #endif 605 _nc_tic_expand(0, FALSE, 0); 606 } 607 608 NCURSES_EXPORT(void) 609 _nc_free_tic(int code) 610 { 611 _nc_leaks_tic(); 612 _nc_free_tinfo(code); 613 } 614 #endif 615