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