1 /* $OpenBSD: comp_parse.c,v 1.13 2023/10/17 09:52:09 nicm Exp $ */ 2 3 /**************************************************************************** 4 * Copyright 2018-2022,2023 Thomas E. Dickey * 5 * Copyright 1998-2016,2017 Free Software Foundation, Inc. * 6 * * 7 * Permission is hereby granted, free of charge, to any person obtaining a * 8 * copy of this software and associated documentation files (the * 9 * "Software"), to deal in the Software without restriction, including * 10 * without limitation the rights to use, copy, modify, merge, publish, * 11 * distribute, distribute with modifications, sublicense, and/or sell * 12 * copies of the Software, and to permit persons to whom the Software is * 13 * furnished to do so, subject to the following conditions: * 14 * * 15 * The above copyright notice and this permission notice shall be included * 16 * in all copies or substantial portions of the Software. * 17 * * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 21 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 22 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 23 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 24 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 25 * * 26 * Except as contained in this notice, the name(s) of the above copyright * 27 * holders shall not be used in advertising or otherwise to promote the * 28 * sale, use or other dealings in this Software without prior written * 29 * authorization. * 30 ****************************************************************************/ 31 32 /**************************************************************************** 33 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * 34 * and: Eric S. Raymond <esr@snark.thyrsus.com> * 35 * and: Thomas E. Dickey 1996-on * 36 ****************************************************************************/ 37 38 /* 39 * comp_parse.c -- parser driver loop and use handling. 40 * 41 * Use this code by calling _nc_read_entry_source() on as many source 42 * files as you like (either terminfo or termcap syntax). If you 43 * want use-resolution, call _nc_resolve_uses2(). To free the list 44 * storage, do _nc_free_entries(). 45 */ 46 47 #include <curses.priv.h> 48 49 #include <ctype.h> 50 51 #include <tic.h> 52 53 MODULE_ID("$Id: comp_parse.c,v 1.13 2023/10/17 09:52:09 nicm Exp $") 54 55 static void sanity_check2(TERMTYPE2 *, bool); 56 NCURSES_IMPEXP void (NCURSES_API *_nc_check_termtype2) (TERMTYPE2 *, bool) = sanity_check2; 57 58 static void fixup_acsc(TERMTYPE2 *, int); 59 60 static void 61 enqueue(ENTRY * ep) 62 /* add an entry to the in-core list */ 63 { 64 ENTRY *newp; 65 66 DEBUG(2, (T_CALLED("enqueue(ep=%p)"), (void *) ep)); 67 68 newp = _nc_copy_entry(ep); 69 if (newp == 0) 70 _nc_err_abort(MSG_NO_MEMORY); 71 72 newp->last = _nc_tail; 73 _nc_tail = newp; 74 75 newp->next = 0; 76 if (newp->last) 77 newp->last->next = newp; 78 DEBUG(2, (T_RETURN(""))); 79 } 80 81 #define NAMEBUFFER_SIZE (MAX_NAME_SIZE + 2) 82 83 static char * 84 force_bar(char *dst, char *src) 85 { 86 if (strchr(src, '|') == 0) { 87 size_t len = strlen(src); 88 if (len > MAX_NAME_SIZE) 89 len = MAX_NAME_SIZE; 90 _nc_STRNCPY(dst, src, MAX_NAME_SIZE); 91 _nc_STRCPY(dst + len, "|", NAMEBUFFER_SIZE - len); 92 src = dst; 93 } 94 return src; 95 } 96 #define ForceBar(dst, src) ((strchr(src, '|') == 0) ? force_bar(dst, src) : src) 97 98 #if NCURSES_USE_TERMCAP && NCURSES_XNAMES 99 static char * 100 skip_index(char *name) 101 { 102 char *bar = strchr(name, '|'); 103 104 if (bar != 0 && (bar - name) == 2) 105 name = bar + 1; 106 107 return name; 108 } 109 #endif 110 111 static bool 112 check_collisions(char *n1, char *n2, int counter) 113 { 114 char *pstart, *qstart, *pend, *qend; 115 char nc1[NAMEBUFFER_SIZE]; 116 char nc2[NAMEBUFFER_SIZE]; 117 118 n1 = ForceBar(nc1, n1); 119 n2 = ForceBar(nc2, n2); 120 121 #if NCURSES_USE_TERMCAP && NCURSES_XNAMES 122 if ((_nc_syntax == SYN_TERMCAP) && _nc_user_definable) { 123 n1 = skip_index(n1); 124 n2 = skip_index(n2); 125 } 126 #endif 127 128 for (pstart = n1; (pend = strchr(pstart, '|')); pstart = pend + 1) { 129 for (qstart = n2; (qend = strchr(qstart, '|')); qstart = qend + 1) { 130 if ((pend - pstart == qend - qstart) 131 && memcmp(pstart, qstart, (size_t) (pend - pstart)) == 0) { 132 if (counter > 0) 133 (void) fprintf(stderr, "Name collision '%.*s' between\n", 134 (int) (pend - pstart), pstart); 135 return (TRUE); 136 } 137 } 138 } 139 140 return (FALSE); 141 } 142 143 static char * 144 next_name(char *name) 145 { 146 if (*name != '\0') 147 ++name; 148 return name; 149 } 150 151 static char * 152 name_ending(char *name) 153 { 154 if (*name == '\0') { 155 name = 0; 156 } else { 157 while (*name != '\0' && *name != '|') 158 ++name; 159 } 160 return name; 161 } 162 163 /* 164 * Essentially, find the conflict reported in check_collisions() and remove 165 * it from the second name, unless that happens to be the last alias. 166 */ 167 static bool 168 remove_collision(char *n1, char *n2) 169 { 170 char *p2 = n2; 171 char *pstart, *qstart, *pend, *qend; 172 bool removed = FALSE; 173 174 #if NCURSES_USE_TERMCAP && NCURSES_XNAMES 175 if ((_nc_syntax == SYN_TERMCAP) && _nc_user_definable) { 176 n1 = skip_index(n1); 177 p2 = n2 = skip_index(n2); 178 } 179 #endif 180 181 for (pstart = n1; (pend = name_ending(pstart)); pstart = next_name(pend)) { 182 for (qstart = n2; (qend = name_ending(qstart)); qstart = next_name(qend)) { 183 if ((pend - pstart == qend - qstart) 184 && memcmp(pstart, qstart, (size_t) (pend - pstart)) == 0) { 185 if (qstart != p2 || *qend == '|') { 186 if (*qend == '|') 187 ++qend; 188 while ((*qstart++ = *qend++) != '\0') ; 189 fprintf(stderr, "...now\t%s\n", p2); 190 removed = TRUE; 191 } else { 192 fprintf(stderr, "Cannot remove alias '%.*s'\n", 193 (int) (qend - qstart), qstart); 194 } 195 break; 196 } 197 } 198 } 199 200 return removed; 201 } 202 203 /* do any of the aliases in a pair of terminal names match? */ 204 NCURSES_EXPORT(bool) 205 _nc_entry_match(char *n1, char *n2) 206 { 207 return check_collisions(n1, n2, 0); 208 } 209 210 /**************************************************************************** 211 * 212 * Entry compiler and resolution logic 213 * 214 ****************************************************************************/ 215 216 NCURSES_EXPORT(void) 217 _nc_read_entry_source(FILE *fp, char *buf, 218 int literal, bool silent, 219 bool(*hook) (ENTRY *)) 220 /* slurp all entries in the given file into core */ 221 { 222 ENTRY thisentry; 223 bool oldsuppress = _nc_suppress_warnings; 224 int immediate = 0; 225 226 DEBUG(2, 227 (T_CALLED("_nc_read_entry_source(" 228 "file=%p, buf=%p, literal=%d, silent=%d, hook=%#" 229 PRIxPTR ")"), 230 (void *) fp, buf, literal, silent, CASTxPTR(hook))); 231 232 if (silent) 233 _nc_suppress_warnings = TRUE; /* shut the lexer up, too */ 234 235 _nc_reset_input(fp, buf); 236 for (;;) { 237 memset(&thisentry, 0, sizeof(thisentry)); 238 if (_nc_parse_entry(&thisentry, literal, silent) == ERR) 239 break; 240 if (!isalnum(UChar(thisentry.tterm.term_names[0]))) 241 _nc_err_abort("terminal names must start with letter or digit"); 242 243 /* 244 * This can be used for immediate compilation of entries with no "use=" 245 * references to disk. That avoids consuming a lot of memory when the 246 * resolution code could fetch entries off disk. 247 */ 248 if (hook != NULLHOOK && (*hook) (&thisentry)) { 249 immediate++; 250 } else { 251 enqueue(&thisentry); 252 /* 253 * The enqueued entry is copied with _nc_copy_termtype(), so we can 254 * free some of the data from thisentry, i.e., the arrays. 255 */ 256 FreeIfNeeded(thisentry.tterm.Booleans); 257 FreeIfNeeded(thisentry.tterm.Numbers); 258 FreeIfNeeded(thisentry.tterm.Strings); 259 FreeIfNeeded(thisentry.tterm.str_table); 260 #if NCURSES_XNAMES 261 FreeIfNeeded(thisentry.tterm.ext_Names); 262 FreeIfNeeded(thisentry.tterm.ext_str_table); 263 #endif 264 } 265 } 266 267 if (_nc_tail) { 268 /* set up the head pointer */ 269 for (_nc_head = _nc_tail; _nc_head->last; _nc_head = _nc_head->last) { 270 /* EMPTY */ ; 271 } 272 273 DEBUG(2, ("head = %s", _nc_head->tterm.term_names)); 274 DEBUG(2, ("tail = %s", _nc_tail->tterm.term_names)); 275 } 276 #ifdef TRACE 277 else if (!immediate) 278 DEBUG(2, ("no entries parsed")); 279 #endif 280 281 _nc_suppress_warnings = oldsuppress; 282 DEBUG(2, (T_RETURN(""))); 283 } 284 285 #if 0 && NCURSES_XNAMES 286 static unsigned 287 find_capname(TERMTYPE2 *p, const char *name) 288 { 289 unsigned num_names = NUM_EXT_NAMES(p); 290 unsigned n; 291 if (name != 0) { 292 for (n = 0; n < num_names; ++n) { 293 if (!strcmp(p->ext_Names[n], name)) 294 break; 295 } 296 } else { 297 n = num_names + 1; 298 } 299 return n; 300 } 301 302 static int 303 extended_captype(TERMTYPE2 *p, unsigned which) 304 { 305 int result = UNDEF; 306 unsigned limit = 0; 307 limit += p->ext_Booleans; 308 if (limit != 0 && which < limit) { 309 result = BOOLEAN; 310 } else { 311 limit += p->ext_Numbers; 312 if (limit != 0 && which < limit) { 313 result = NUMBER; 314 } else { 315 limit += p->ext_Strings; 316 if (limit != 0 && which < limit) { 317 result = ((p->Strings[STRCOUNT + which] != CANCELLED_STRING) 318 ? STRING 319 : CANCEL); 320 } else if (which >= limit) { 321 result = CANCEL; 322 } 323 } 324 } 325 return result; 326 } 327 328 static const char * 329 name_of_captype(int which) 330 { 331 const char *result = "?"; 332 switch (which) { 333 case BOOLEAN: 334 result = "boolean"; 335 break; 336 case NUMBER: 337 result = "number"; 338 break; 339 case STRING: 340 result = "string"; 341 break; 342 } 343 return result; 344 } 345 346 #define valid_TERMTYPE2(p) \ 347 ((p) != 0 && \ 348 (p)->term_names != 0 && \ 349 (p)->ext_Names != 0) 350 351 /* 352 * Disallow changing the type of an extended capability when doing a "use" 353 * if one or the other is a string. 354 */ 355 static int 356 invalid_merge(TERMTYPE2 *to, TERMTYPE2 *from) 357 { 358 int rc = FALSE; 359 if (valid_TERMTYPE2(to) 360 && valid_TERMTYPE2(from)) { 361 char *to_name = _nc_first_name(to->term_names); 362 char *from_name = strdup(_nc_first_name(from->term_names)); 363 unsigned num_names = NUM_EXT_NAMES(from); 364 unsigned n; 365 366 for (n = 0; n < num_names; ++n) { 367 const char *capname = from->ext_Names[n]; 368 int tt = extended_captype(to, find_capname(to, capname)); 369 int tf = extended_captype(from, n); 370 371 if (tt <= STRING 372 && tf <= STRING 373 && (tt == STRING) != (tf == STRING)) { 374 if (from_name != 0 && strcmp(to_name, from_name)) { 375 _nc_warning("merge of %s to %s changes type of %s from %s to %s", 376 from_name, 377 to_name, 378 from->ext_Names[n], 379 name_of_captype(tf), 380 name_of_captype(tt)); 381 } else { 382 _nc_warning("merge of %s changes type of %s from %s to %s", 383 to_name, 384 from->ext_Names[n], 385 name_of_captype(tf), 386 name_of_captype(tt)); 387 } 388 rc = TRUE; 389 } 390 } 391 free(from_name); 392 } 393 return rc; 394 } 395 #define validate_merge(p, q) \ 396 if (invalid_merge(&((p)->tterm), &((q)->tterm))) \ 397 return FALSE 398 #else 399 #define validate_merge(p, q) /* nothing */ 400 #endif 401 402 NCURSES_EXPORT(int) 403 _nc_resolve_uses2(bool fullresolve, bool literal) 404 /* try to resolve all use capabilities */ 405 { 406 ENTRY *qp, *rp, *lastread = 0; 407 bool keepgoing; 408 unsigned i, j; 409 int unresolved, total_unresolved, multiples; 410 411 DEBUG(2, (T_CALLED("_nc_resolve_uses2"))); 412 413 /* 414 * Check for multiple occurrences of the same name. 415 */ 416 multiples = 0; 417 for_entry_list(qp) { 418 int matchcount = 0; 419 420 for_entry_list2(rp, qp->next) { 421 if (qp > rp 422 && check_collisions(qp->tterm.term_names, 423 rp->tterm.term_names, 424 matchcount + 1)) { 425 if (!matchcount++) { 426 (void) fprintf(stderr, "\t%s\n", rp->tterm.term_names); 427 } 428 (void) fprintf(stderr, "and\t%s\n", qp->tterm.term_names); 429 if (!remove_collision(rp->tterm.term_names, 430 qp->tterm.term_names)) { 431 ++multiples; 432 } 433 } 434 } 435 } 436 if (multiples > 0) { 437 DEBUG(2, (T_RETURN("false"))); 438 return (FALSE); 439 } 440 441 DEBUG(2, ("NO MULTIPLE NAME OCCURRENCES")); 442 443 /* 444 * First resolution stage: compute link pointers corresponding to names. 445 */ 446 total_unresolved = 0; 447 _nc_curr_col = -1; 448 for_entry_list(qp) { 449 unresolved = 0; 450 for (i = 0; i < qp->nuses; i++) { 451 bool foundit; 452 char *child = _nc_first_name(qp->tterm.term_names); 453 char *lookfor = qp->uses[i].name; 454 long lookline = qp->uses[i].line; 455 456 if (lookfor == 0) 457 continue; 458 459 foundit = FALSE; 460 461 _nc_set_type(child); 462 463 /* first, try to resolve from in-core records */ 464 for_entry_list(rp) { 465 if (rp != qp 466 && _nc_name_match(rp->tterm.term_names, lookfor, "|")) { 467 DEBUG(2, ("%s: resolving use=%s %p (in core)", 468 child, lookfor, lookfor)); 469 470 qp->uses[i].link = rp; 471 foundit = TRUE; 472 473 /* verify that there are no earlier uses */ 474 for (j = 0; j < i; ++j) { 475 if (qp->uses[j].link != NULL 476 && !strcmp(qp->uses[j].link->tterm.term_names, 477 rp->tterm.term_names)) { 478 _nc_warning("duplicate use=%s", lookfor); 479 break; 480 } 481 } 482 } 483 } 484 485 /* if that didn't work, try to merge in a compiled entry */ 486 if (!foundit) { 487 TERMTYPE2 thisterm; 488 char filename[PATH_MAX]; 489 490 memset(&thisterm, 0, sizeof(thisterm)); 491 if (_nc_read_entry2(lookfor, filename, &thisterm) == 1) { 492 DEBUG(2, ("%s: resolving use=%s (compiled)", 493 child, lookfor)); 494 495 TYPE_MALLOC(ENTRY, 1, rp); 496 rp->tterm = thisterm; 497 rp->nuses = 0; 498 rp->next = lastread; 499 lastread = rp; 500 501 qp->uses[i].link = rp; 502 foundit = TRUE; 503 504 /* verify that there are no earlier uses */ 505 for (j = 0; j < i; ++j) { 506 if (qp->uses[j].link != NULL 507 && !strcmp(qp->uses[j].link->tterm.term_names, 508 rp->tterm.term_names)) { 509 _nc_warning("duplicate use=%s", lookfor); 510 break; 511 } 512 } 513 } 514 } 515 516 /* no good, mark this one unresolvable and complain */ 517 if (!foundit) { 518 unresolved++; 519 total_unresolved++; 520 521 _nc_curr_line = (int) lookline; 522 _nc_warning("resolution of use=%s failed", lookfor); 523 qp->uses[i].link = 0; 524 } 525 } 526 } 527 if (total_unresolved) { 528 /* free entries read in off disk */ 529 _nc_free_entries(lastread); 530 DEBUG(2, (T_RETURN("false"))); 531 return (FALSE); 532 } 533 534 DEBUG(2, ("NAME RESOLUTION COMPLETED OK")); 535 536 /* 537 * OK, at this point all (char *) references in `name' members 538 * have been successfully converted to (ENTRY *) pointers in 539 * `link' members. Time to do the actual merges. 540 */ 541 if (fullresolve) { 542 do { 543 ENTRY merged; 544 545 keepgoing = FALSE; 546 547 for_entry_list(qp) { 548 if (qp->nuses > 0) { 549 DEBUG(2, ("%s: attempting merge of %d entries", 550 _nc_first_name(qp->tterm.term_names), 551 qp->nuses)); 552 /* 553 * If any of the use entries we're looking for is 554 * incomplete, punt. We'll catch this entry on a 555 * subsequent pass. 556 */ 557 for (i = 0; i < qp->nuses; i++) { 558 if (qp->uses[i].link 559 && qp->uses[i].link->nuses) { 560 DEBUG(2, ("%s: use entry %d unresolved", 561 _nc_first_name(qp->tterm.term_names), i)); 562 goto incomplete; 563 } 564 } 565 566 /* 567 * First, make sure there is no garbage in the 568 * merge block. As a side effect, copy into 569 * the merged entry the name field and string 570 * table pointer. 571 */ 572 _nc_copy_termtype2(&(merged.tterm), &(qp->tterm)); 573 574 /* 575 * Now merge in each use entry in the proper 576 * (reverse) order. 577 */ 578 for (; qp->nuses; qp->nuses--) { 579 int n = (int) (qp->nuses - 1); 580 validate_merge(&merged, qp->uses[n].link); 581 _nc_merge_entry(&merged, qp->uses[n].link); 582 free(qp->uses[n].name); 583 } 584 585 /* 586 * Now merge in the original entry. 587 */ 588 validate_merge(&merged, qp); 589 _nc_merge_entry(&merged, qp); 590 591 /* 592 * Replace the original entry with the merged one. 593 */ 594 FreeIfNeeded(qp->tterm.Booleans); 595 FreeIfNeeded(qp->tterm.Numbers); 596 FreeIfNeeded(qp->tterm.Strings); 597 FreeIfNeeded(qp->tterm.str_table); 598 #if NCURSES_XNAMES 599 FreeIfNeeded(qp->tterm.ext_Names); 600 FreeIfNeeded(qp->tterm.ext_str_table); 601 #endif 602 qp->tterm = merged.tterm; 603 _nc_wrap_entry(qp, TRUE); 604 605 /* 606 * We know every entry is resolvable because name resolution 607 * didn't bomb. So go back for another pass. 608 */ 609 /* FALLTHRU */ 610 incomplete: 611 keepgoing = TRUE; 612 } 613 } 614 } while 615 (keepgoing); 616 617 DEBUG(2, ("MERGES COMPLETED OK")); 618 } 619 620 DEBUG(2, ("RESOLUTION FINISHED")); 621 622 if (fullresolve) { 623 _nc_curr_col = -1; 624 for_entry_list(qp) { 625 _nc_curr_line = (int) qp->startline; 626 _nc_set_type(_nc_first_name(qp->tterm.term_names)); 627 /* 628 * tic overrides this function pointer to provide more verbose 629 * checking. 630 */ 631 if (_nc_check_termtype2 != sanity_check2) { 632 SCREEN *save_SP = SP; 633 SCREEN fake_sp; 634 TERMINAL fake_tm; 635 TERMINAL *save_tm = cur_term; 636 637 /* 638 * Setup so that tic can use ordinary terminfo interface to 639 * obtain capability information. 640 */ 641 memset(&fake_sp, 0, sizeof(fake_sp)); 642 memset(&fake_tm, 0, sizeof(fake_tm)); 643 fake_sp._term = &fake_tm; 644 TerminalType(&fake_tm) = qp->tterm; 645 _nc_set_screen(&fake_sp); 646 set_curterm(&fake_tm); 647 648 _nc_check_termtype2(&qp->tterm, literal); 649 650 /* 651 * Checking calls tparm, which can allocate memory. Fix leaks. 652 */ 653 #define TPS(name) fake_tm.tparm_state.name 654 FreeAndNull(TPS(out_buff)); 655 FreeAndNull(TPS(fmt_buff)); 656 #undef TPS 657 658 _nc_set_screen(save_SP); 659 set_curterm(save_tm); 660 } else { 661 fixup_acsc(&qp->tterm, literal); 662 } 663 } 664 DEBUG(2, ("SANITY CHECK FINISHED")); 665 } 666 667 DEBUG(2, (T_RETURN("true"))); 668 return (TRUE); 669 } 670 671 /* 672 * This bit of legerdemain turns all the terminfo variable names into 673 * references to locations in the arrays Booleans, Numbers, and Strings --- 674 * precisely what's needed. 675 */ 676 677 #undef CUR 678 #define CUR tp-> 679 680 static void 681 fixup_acsc(TERMTYPE2 *tp, int literal) 682 { 683 if (!literal) { 684 if (acs_chars == ABSENT_STRING 685 && PRESENT(enter_alt_charset_mode) 686 && PRESENT(exit_alt_charset_mode)) 687 acs_chars = strdup(VT_ACSC); 688 } 689 } 690 691 static void 692 sanity_check2(TERMTYPE2 *tp, bool literal) 693 { 694 if (!PRESENT(exit_attribute_mode)) { 695 #ifdef __UNUSED__ /* this casts too wide a net */ 696 bool terminal_entry = !strchr(tp->term_names, '+'); 697 if (terminal_entry && 698 (PRESENT(set_attributes) 699 || PRESENT(enter_standout_mode) 700 || PRESENT(enter_underline_mode) 701 || PRESENT(enter_blink_mode) 702 || PRESENT(enter_bold_mode) 703 || PRESENT(enter_dim_mode) 704 || PRESENT(enter_secure_mode) 705 || PRESENT(enter_protected_mode) 706 || PRESENT(enter_reverse_mode))) 707 _nc_warning("no exit_attribute_mode"); 708 #endif /* __UNUSED__ */ 709 PAIRED(enter_standout_mode, exit_standout_mode); 710 PAIRED(enter_underline_mode, exit_underline_mode); 711 #if defined(enter_italics_mode) && defined(exit_italics_mode) 712 PAIRED(enter_italics_mode, exit_italics_mode); 713 #endif 714 } 715 716 /* we do this check/fix in postprocess_termcap(), but some packagers 717 * prefer to bypass it... 718 */ 719 if (!literal) { 720 fixup_acsc(tp, literal); 721 ANDMISSING(enter_alt_charset_mode, acs_chars); 722 ANDMISSING(exit_alt_charset_mode, acs_chars); 723 } 724 725 /* listed in structure-member order of first argument */ 726 PAIRED(enter_alt_charset_mode, exit_alt_charset_mode); 727 ANDMISSING(enter_blink_mode, exit_attribute_mode); 728 ANDMISSING(enter_bold_mode, exit_attribute_mode); 729 PAIRED(exit_ca_mode, enter_ca_mode); 730 PAIRED(enter_delete_mode, exit_delete_mode); 731 ANDMISSING(enter_dim_mode, exit_attribute_mode); 732 PAIRED(enter_insert_mode, exit_insert_mode); 733 ANDMISSING(enter_secure_mode, exit_attribute_mode); 734 ANDMISSING(enter_protected_mode, exit_attribute_mode); 735 ANDMISSING(enter_reverse_mode, exit_attribute_mode); 736 PAIRED(from_status_line, to_status_line); 737 PAIRED(meta_off, meta_on); 738 739 PAIRED(prtr_on, prtr_off); 740 PAIRED(save_cursor, restore_cursor); 741 PAIRED(enter_xon_mode, exit_xon_mode); 742 PAIRED(enter_am_mode, exit_am_mode); 743 ANDMISSING(label_off, label_on); 744 #if defined(display_clock) && defined(remove_clock) 745 PAIRED(display_clock, remove_clock); 746 #endif 747 ANDMISSING(set_color_pair, initialize_pair); 748 } 749 750 #if NO_LEAKS 751 NCURSES_EXPORT(void) 752 _nc_leaks_tic(void) 753 { 754 T((T_CALLED("_nc_leaks_tic()"))); 755 _nc_globals.leak_checking = TRUE; 756 _nc_alloc_entry_leaks(); 757 _nc_captoinfo_leaks(); 758 _nc_comp_scan_leaks(); 759 #if BROKEN_LINKER || USE_REENTRANT 760 _nc_names_leaks(); 761 _nc_codes_leaks(); 762 #endif 763 _nc_tic_expand(0, FALSE, 0); 764 T((T_RETURN(""))); 765 } 766 767 NCURSES_EXPORT(void) 768 _nc_free_tic(int code) 769 { 770 T((T_CALLED("_nc_free_tic(%d)"), code)); 771 _nc_leaks_tic(); 772 exit_terminfo(code); 773 } 774 #endif 775