xref: /openbsd/lib/libcurses/tinfo/lib_setup.c (revision c7ef0cfc)
1 /* $OpenBSD: lib_setup.c,v 1.14 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  *     and: Juergen Pfeifer                         2009                    *
37  ****************************************************************************/
38 
39 /*
40  * Terminal setup routines common to termcap and terminfo:
41  *
42  *		use_env(bool)
43  *		use_tioctl(bool)
44  *		setupterm(char *, int, int *)
45  */
46 
47 #include <curses.priv.h>
48 #include <tic.h>		/* for MAX_NAME_SIZE */
49 
50 #if HAVE_LOCALE_H
51 #include <locale.h>
52 #endif
53 
54 MODULE_ID("$Id: lib_setup.c,v 1.14 2023/10/17 09:52:09 nicm Exp $")
55 
56 /****************************************************************************
57  *
58  * Terminal size computation
59  *
60  ****************************************************************************/
61 
62 #if HAVE_SIZECHANGE
63 # if !defined(sun) || !TERMIOS
64 #  if HAVE_SYS_IOCTL_H
65 #   include <sys/ioctl.h>
66 #  endif
67 # endif
68 #endif
69 
70 #if NEED_PTEM_H
71  /* On SCO, they neglected to define struct winsize in termios.h -- it is only
72   * in termio.h and ptem.h (the former conflicts with other definitions).
73   */
74 # include <sys/stream.h>
75 # include <sys/ptem.h>
76 #endif
77 
78 #if HAVE_LANGINFO_CODESET
79 #include <langinfo.h>
80 #endif
81 
82 /*
83  * SCO defines TIOCGSIZE and the corresponding struct.  Other systems (SunOS,
84  * Solaris, IRIX) define TIOCGWINSZ and struct winsize.
85  */
86 #ifdef TIOCGSIZE
87 # define IOCTL_WINSIZE TIOCGSIZE
88 # define STRUCT_WINSIZE struct ttysize
89 # define WINSIZE_ROWS(n) (int)n.ts_lines
90 # define WINSIZE_COLS(n) (int)n.ts_cols
91 #else
92 # ifdef TIOCGWINSZ
93 #  define IOCTL_WINSIZE TIOCGWINSZ
94 #  define STRUCT_WINSIZE struct winsize
95 #  define WINSIZE_ROWS(n) (int)n.ws_row
96 #  define WINSIZE_COLS(n) (int)n.ws_col
97 # endif
98 #endif
99 
100 /*
101  * Reduce explicit use of "cur_term" global variable.
102  */
103 #undef CUR
104 #define CUR TerminalType(termp).
105 
106 /*
107  * Wrap global variables in this module.
108  */
109 #if USE_REENTRANT
110 
NCURSES_EXPORT(char *)111 NCURSES_EXPORT(char *)
112 NCURSES_PUBLIC_VAR(ttytype) (void)
113 {
114     static char empty[] = "";
115     char *result = empty;
116 
117 #if NCURSES_SP_FUNCS
118     if (CURRENT_SCREEN) {
119 	TERMINAL *termp = TerminalOf(CURRENT_SCREEN);
120 	if (termp != 0) {
121 	    result = TerminalType(termp).term_names;
122 	}
123     }
124 #else
125     if (cur_term != 0) {
126 	result = TerminalType(cur_term).term_names;
127     }
128 #endif
129     return result;
130 }
131 
132 NCURSES_EXPORT(int *)
_nc_ptr_Lines(SCREEN * sp)133 _nc_ptr_Lines(SCREEN *sp)
134 {
135     return ptrLines(sp);
136 }
137 
138 NCURSES_EXPORT(int)
NCURSES_PUBLIC_VAR(LINES)139 NCURSES_PUBLIC_VAR(LINES) (void)
140 {
141     return *_nc_ptr_Lines(CURRENT_SCREEN);
142 }
143 
144 NCURSES_EXPORT(int *)
_nc_ptr_Cols(SCREEN * sp)145 _nc_ptr_Cols(SCREEN *sp)
146 {
147     return ptrCols(sp);
148 }
149 
150 NCURSES_EXPORT(int)
NCURSES_PUBLIC_VAR(COLS)151 NCURSES_PUBLIC_VAR(COLS) (void)
152 {
153     return *_nc_ptr_Cols(CURRENT_SCREEN);
154 }
155 
156 NCURSES_EXPORT(int *)
_nc_ptr_Tabsize(SCREEN * sp)157 _nc_ptr_Tabsize(SCREEN *sp)
158 {
159     return ptrTabsize(sp);
160 }
161 
162 NCURSES_EXPORT(int)
NCURSES_PUBLIC_VAR(TABSIZE)163 NCURSES_PUBLIC_VAR(TABSIZE) (void)
164 {
165     return *_nc_ptr_Tabsize(CURRENT_SCREEN);
166 }
167 #else
168 NCURSES_EXPORT_VAR(char) ttytype[NAMESIZE] = "";
169 NCURSES_EXPORT_VAR(int) LINES = 0;
170 NCURSES_EXPORT_VAR(int) COLS = 0;
171 NCURSES_EXPORT_VAR(int) TABSIZE = 8;
172 #endif
173 
174 #if NCURSES_EXT_FUNCS
175 NCURSES_EXPORT(int)
NCURSES_SP_NAME(set_tabsize)176 NCURSES_SP_NAME(set_tabsize) (NCURSES_SP_DCLx int value)
177 {
178     int code = OK;
179     if (value <= 0) {
180 	code = ERR;
181     } else {
182 #if USE_REENTRANT
183 	if (SP_PARM) {
184 	    SP_PARM->_TABSIZE = value;
185 	} else {
186 	    code = ERR;
187 	}
188 #else
189 	(void) SP_PARM;
190 	TABSIZE = value;
191 #endif
192     }
193     return code;
194 }
195 
196 #if NCURSES_SP_FUNCS
197 NCURSES_EXPORT(int)
set_tabsize(int value)198 set_tabsize(int value)
199 {
200     return NCURSES_SP_NAME(set_tabsize) (CURRENT_SCREEN, value);
201 }
202 #endif
203 #endif /* NCURSES_EXT_FUNCS */
204 
205 #if USE_SIGWINCH
206 /*
207  * If we have a pending SIGWINCH, set the flag in each screen.
208  */
209 NCURSES_EXPORT(int)
_nc_handle_sigwinch(SCREEN * sp)210 _nc_handle_sigwinch(SCREEN *sp)
211 {
212     SCREEN *scan;
213 
214     if (_nc_globals.have_sigwinch) {
215 	_nc_globals.have_sigwinch = 0;
216 
217 	for (each_screen(scan)) {
218 	    scan->_sig_winch = TRUE;
219 	}
220     }
221 
222     return (sp ? sp->_sig_winch : 0);
223 }
224 
225 #endif
226 
227 NCURSES_EXPORT(void)
NCURSES_SP_NAME(use_env)228 NCURSES_SP_NAME(use_env) (NCURSES_SP_DCLx bool f)
229 {
230     START_TRACE();
231     T((T_CALLED("use_env(%p,%d)"), (void *) SP_PARM, (int) f));
232 #if NCURSES_SP_FUNCS
233     if (IsPreScreen(SP_PARM)) {
234 	SP_PARM->_use_env = f;
235     }
236 #else
237     _nc_prescreen.use_env = f;
238 #endif
239     returnVoid;
240 }
241 
242 NCURSES_EXPORT(void)
NCURSES_SP_NAME(use_tioctl)243 NCURSES_SP_NAME(use_tioctl) (NCURSES_SP_DCLx bool f)
244 {
245     START_TRACE();
246     T((T_CALLED("use_tioctl(%p,%d)"), (void *) SP_PARM, (int) f));
247 #if NCURSES_SP_FUNCS
248     if (IsPreScreen(SP_PARM)) {
249 	SP_PARM->use_tioctl = f;
250     }
251 #else
252     _nc_prescreen.use_tioctl = f;
253 #endif
254     returnVoid;
255 }
256 
257 #if NCURSES_SP_FUNCS
258 NCURSES_EXPORT(void)
use_env(bool f)259 use_env(bool f)
260 {
261     START_TRACE();
262     T((T_CALLED("use_env(%d)"), (int) f));
263     _nc_prescreen.use_env = f;
264     returnVoid;
265 }
266 
267 NCURSES_EXPORT(void)
use_tioctl(bool f)268 use_tioctl(bool f)
269 {
270     START_TRACE();
271     T((T_CALLED("use_tioctl(%d)"), (int) f));
272     _nc_prescreen.use_tioctl = f;
273     returnVoid;
274 }
275 #endif
276 
277 NCURSES_EXPORT(void)
_nc_get_screensize(SCREEN * sp,TERMINAL * termp,int * linep,int * colp)278 _nc_get_screensize(SCREEN *sp,
279 #ifdef USE_TERM_DRIVER
280 		   TERMINAL *termp,
281 #endif
282 		   int *linep, int *colp)
283 /* Obtain lines/columns values from the environment and/or terminfo entry */
284 {
285 #ifdef USE_TERM_DRIVER
286     TERMINAL_CONTROL_BLOCK *TCB;
287     int my_tabsize;
288 
289     assert(termp != 0 && linep != 0 && colp != 0);
290     TCB = (TERMINAL_CONTROL_BLOCK *) termp;
291 
292     my_tabsize = TCB->info.tabsize;
293     TCB->drv->td_size(TCB, linep, colp);
294 
295 #if USE_REENTRANT
296     if (sp != 0) {
297 	sp->_TABSIZE = my_tabsize;
298     }
299 #else
300     (void) sp;
301     TABSIZE = my_tabsize;
302 #endif
303     T(("TABSIZE = %d", my_tabsize));
304 #else /* !USE_TERM_DRIVER */
305     TERMINAL *termp = cur_term;
306     int my_tabsize;
307     bool useEnv = _nc_prescreen.use_env;
308     bool useTioctl = _nc_prescreen.use_tioctl;
309 
310 #ifdef EXP_WIN32_DRIVER
311     /* If we are here, then Windows console is used in terminfo mode.
312        We need to figure out the size using the console API
313      */
314     _nc_console_size(linep, colp);
315     T(("screen size: winconsole lines = %d columns = %d", *linep, *colp));
316 #else
317     /* figure out the size of the screen */
318     T(("screen size: terminfo lines = %d columns = %d", lines, columns));
319 
320     *linep = (int) lines;
321     *colp = (int) columns;
322 #endif
323 
324 #if NCURSES_SP_FUNCS
325     if (sp) {
326 	useEnv = sp->_use_env;
327 	useTioctl = sp->use_tioctl;
328     }
329 #endif
330 
331     if (useEnv || useTioctl) {
332 #ifdef __EMX__
333 	{
334 	    int screendata[2];
335 	    _scrsize(screendata);
336 	    *colp = screendata[0];
337 	    *linep = ((sp != 0 && sp->_filtered)
338 		      ? 1
339 		      : screendata[1]);
340 	    T(("EMX screen size: environment LINES = %d COLUMNS = %d",
341 	       *linep, *colp));
342 	}
343 #endif
344 #if HAVE_SIZECHANGE
345 	/* try asking the OS */
346 	if (NC_ISATTY(cur_term->Filedes)) {
347 	    STRUCT_WINSIZE size;
348 
349 	    errno = 0;
350 	    do {
351 		if (ioctl(cur_term->Filedes, IOCTL_WINSIZE, &size) >= 0) {
352 		    *linep = ((sp != 0 && sp->_filtered)
353 			      ? 1
354 			      : WINSIZE_ROWS(size));
355 		    *colp = WINSIZE_COLS(size);
356 		    T(("SYS screen size: environment LINES = %d COLUMNS = %d",
357 		       *linep, *colp));
358 		    break;
359 		}
360 	    } while
361 		(errno == EINTR);
362 	}
363 #endif /* HAVE_SIZECHANGE */
364 
365 	if (useEnv) {
366 	    int value;
367 
368 	    if (useTioctl) {
369 		/*
370 		 * If environment variables are used, update them.
371 		 */
372 		if ((sp == 0 || !sp->_filtered) && _nc_getenv_num("LINES") > 0) {
373 		    _nc_setenv_num("LINES", *linep);
374 		}
375 		if (_nc_getenv_num("COLUMNS") > 0) {
376 		    _nc_setenv_num("COLUMNS", *colp);
377 		}
378 	    }
379 
380 	    /*
381 	     * Finally, look for environment variables.
382 	     *
383 	     * Solaris lets users override either dimension with an environment
384 	     * variable.
385 	     */
386 	    if ((value = _nc_getenv_num("LINES")) > 0) {
387 		*linep = value;
388 		T(("screen size: environment LINES = %d", *linep));
389 	    }
390 	    if ((value = _nc_getenv_num("COLUMNS")) > 0) {
391 		*colp = value;
392 		T(("screen size: environment COLUMNS = %d", *colp));
393 	    }
394 	}
395 
396 	/* if we can't get dynamic info about the size, use static */
397 	if (*linep <= 0) {
398 	    *linep = (int) lines;
399 	}
400 	if (*colp <= 0) {
401 	    *colp = (int) columns;
402 	}
403 
404 	/* the ultimate fallback, assume fixed 24x80 size */
405 	if (*linep <= 0) {
406 	    *linep = 24;
407 	}
408 	if (*colp <= 0) {
409 	    *colp = 80;
410 	}
411 
412 	/*
413 	 * Put the derived values back in the screen-size caps, so
414 	 * tigetnum() and tgetnum() will do the right thing.
415 	 */
416 	lines = (NCURSES_INT2) (*linep);
417 	columns = (NCURSES_INT2) (*colp);
418 #if NCURSES_EXT_NUMBERS
419 #define OldNumber(termp,name) \
420 	(termp)->type.Numbers[(&name - (termp)->type2.Numbers)]
421 	OldNumber(termp, lines) = (short) (*linep);
422 	OldNumber(termp, columns) = (short) (*colp);
423 #endif
424     }
425 
426     T(("screen size is %dx%d", *linep, *colp));
427 
428     if (VALID_NUMERIC(init_tabs))
429 	my_tabsize = (int) init_tabs;
430     else
431 	my_tabsize = 8;
432 
433 #if USE_REENTRANT
434     if (sp != 0)
435 	sp->_TABSIZE = my_tabsize;
436 #else
437     TABSIZE = my_tabsize;
438 #endif
439     T(("TABSIZE = %d", TABSIZE));
440 #endif /* USE_TERM_DRIVER */
441 }
442 
443 #if USE_SIZECHANGE
444 NCURSES_EXPORT(void)
_nc_update_screensize(SCREEN * sp)445 _nc_update_screensize(SCREEN *sp)
446 {
447     int new_lines;
448     int new_cols;
449 
450 #ifdef USE_TERM_DRIVER
451     int old_lines;
452     int old_cols;
453 
454     assert(sp != 0);
455 
456     CallDriver_2(sp, td_getsize, &old_lines, &old_cols);
457 
458 #else
459     TERMINAL *termp = cur_term;
460     int old_lines = lines;
461     int old_cols = columns;
462 #endif
463 
464     if (sp != 0) {
465 	TINFO_GET_SIZE(sp, sp->_term, &new_lines, &new_cols);
466 	/*
467 	 * See is_term_resized() and resizeterm().
468 	 * We're doing it this way because those functions belong to the upper
469 	 * ncurses library, while this resides in the lower terminfo library.
470 	 */
471 	if (sp->_resize != 0) {
472 	    if ((new_lines != old_lines) || (new_cols != old_cols)) {
473 		sp->_resize(NCURSES_SP_ARGx new_lines, new_cols);
474 	    } else if (sp->_sig_winch && (sp->_ungetch != 0)) {
475 		sp->_ungetch(SP_PARM, KEY_RESIZE);	/* so application can know this */
476 	    }
477 	    sp->_sig_winch = FALSE;
478 	}
479     }
480 }
481 #endif /* USE_SIZECHANGE */
482 
483 /****************************************************************************
484  *
485  * Terminal setup
486  *
487  ****************************************************************************/
488 
489 #if NCURSES_USE_DATABASE || NCURSES_USE_TERMCAP
490 /*
491  * Return 1 if entry found, 0 if not found, -1 if database not accessible,
492  * just like tgetent().
493  */
494 int
_nc_setup_tinfo(const char * const tn,TERMTYPE2 * const tp)495 _nc_setup_tinfo(const char *const tn, TERMTYPE2 *const tp)
496 {
497     char filename[PATH_MAX];
498     int status = _nc_read_entry2(tn, filename, tp);
499 
500     /*
501      * If we have an entry, force all of the cancelled strings to null
502      * pointers so we don't have to test them in the rest of the library.
503      * (The terminfo compiler bypasses this logic, since it must know if
504      * a string is cancelled, for merging entries).
505      */
506     if (status == TGETENT_YES) {
507 	unsigned n;
508 	for_each_boolean(n, tp) {
509 	    if (!VALID_BOOLEAN(tp->Booleans[n]))
510 		tp->Booleans[n] = FALSE;
511 	}
512 	for_each_string(n, tp) {
513 	    if (tp->Strings[n] == CANCELLED_STRING)
514 		tp->Strings[n] = ABSENT_STRING;
515 	}
516     }
517     return (status);
518 }
519 #endif
520 
521 /*
522 **	Take the real command character out of the CC environment variable
523 **	and substitute it in for the prototype given in 'command_character'.
524 */
525 void
_nc_tinfo_cmdch(TERMINAL * termp,int proto)526 _nc_tinfo_cmdch(TERMINAL *termp, int proto)
527 {
528     char *tmp;
529 
530     /*
531      * Only use the character if the string is a single character,
532      * since it is fairly common for developers to set the C compiler
533      * name as an environment variable - using the same symbol.
534      */
535     if ((tmp = getenv("CC")) != 0 && strlen(tmp) == 1) {
536 	unsigned i;
537 	char CC = *tmp;
538 
539 	for_each_string(i, &(termp->type)) {
540 	    for (tmp = termp->type.Strings[i]; tmp && *tmp; tmp++) {
541 		if (UChar(*tmp) == proto)
542 		    *tmp = CC;
543 	    }
544 	}
545     }
546 }
547 
548 /*
549  * Find the locale which is in effect.
550  */
551 NCURSES_EXPORT(char *)
_nc_get_locale(void)552 _nc_get_locale(void)
553 {
554     char *env;
555 #if HAVE_LOCALE_H
556     /*
557      * This is preferable to using getenv() since it ensures that we are using
558      * the locale which was actually initialized by the application.
559      */
560     env = setlocale(LC_CTYPE, 0);
561 #else
562     if (((env = getenv("LANG")) != 0 && *env != '\0')
563 	|| ((env = getenv("LC_CTYPE")) != 0 && *env != '\0')
564 	|| ((env = getenv("LC_ALL")) != 0 && *env != '\0')) {
565 	;
566     }
567 #endif
568     T(("_nc_get_locale %s", _nc_visbuf(env)));
569     return env;
570 }
571 
572 /*
573  * Check if we are running in a UTF-8 locale.
574  */
575 NCURSES_EXPORT(int)
_nc_unicode_locale(void)576 _nc_unicode_locale(void)
577 {
578     int result = 0;
579 #if defined(_NC_WINDOWS) && USE_WIDEC_SUPPORT
580     result = 1;
581 #elif HAVE_LANGINFO_CODESET
582     char *env = nl_langinfo(CODESET);
583     result = !strcmp(env, "UTF-8");
584     T(("_nc_unicode_locale(%s) ->%d", env, result));
585 #else
586     char *env = _nc_get_locale();
587     if (env != 0) {
588 	if (strstr(env, ".UTF-8") != 0) {
589 	    result = 1;
590 	    T(("_nc_unicode_locale(%s) ->%d", env, result));
591 	}
592     }
593 #endif
594     return result;
595 }
596 
597 #define CONTROL_N(s) ((s) != 0 && strstr(s, "\016") != 0)
598 #define CONTROL_O(s) ((s) != 0 && strstr(s, "\017") != 0)
599 
600 /*
601  * Check for known broken cases where a UTF-8 locale breaks the alternate
602  * character set.
603  */
604 NCURSES_EXPORT(int)
_nc_locale_breaks_acs(TERMINAL * termp)605 _nc_locale_breaks_acs(TERMINAL *termp)
606 {
607     const char *env_name = "NCURSES_NO_UTF8_ACS";
608     const char *env;
609     int value;
610     int result = 0;
611 
612     T((T_CALLED("_nc_locale_breaks_acs:%d"), result));
613     if (getenv(env_name) != 0) {
614 	result = _nc_getenv_num(env_name);
615     } else if ((value = tigetnum("U8")) >= 0) {
616 	result = value;		/* use extension feature */
617     } else if ((env = getenv("TERM")) != 0) {
618 	if (strstr(env, "linux")) {
619 	    result = 1;		/* always broken */
620 	} else if (strstr(env, "screen") != 0
621 		   && ((env = getenv("TERMCAP")) != 0
622 		       && strstr(env, "screen") != 0)
623 		   && strstr(env, "hhII00") != 0) {
624 	    if (CONTROL_N(enter_alt_charset_mode) ||
625 		CONTROL_O(enter_alt_charset_mode) ||
626 		CONTROL_N(set_attributes) ||
627 		CONTROL_O(set_attributes)) {
628 		result = 1;
629 	    }
630 	}
631     }
632     returnCode(result);
633 }
634 
635 NCURSES_EXPORT(int)
TINFO_SETUP_TERM(TERMINAL ** tp,const char * tname,int Filedes,int * errret,int reuse)636 TINFO_SETUP_TERM(TERMINAL **tp,
637 		 const char *tname,
638 		 int Filedes,
639 		 int *errret,
640 		 int reuse)
641 {
642 #ifdef USE_TERM_DRIVER
643     TERMINAL_CONTROL_BLOCK *TCB = 0;
644 #endif
645     TERMINAL *termp;
646     SCREEN *sp = 0;
647     char *myname;
648     int code = ERR;
649 
650     START_TRACE();
651 
652 #ifdef USE_TERM_DRIVER
653     T((T_CALLED("_nc_setupterm_ex(%p,%s,%d,%p)"),
654        (void *) tp, _nc_visbuf(tname), Filedes, (void *) errret));
655 
656     if (tp == 0) {
657 	ret_error0(TGETENT_ERR,
658 		   "Invalid parameter, internal error.\n");
659     } else
660 	termp = *tp;
661 #else
662     termp = cur_term;
663     T((T_CALLED("setupterm(%s,%d,%p)"), _nc_visbuf(tname), Filedes, (void *) errret));
664 #endif
665 
666     if (tname == 0) {
667 	tname = getenv("TERM");
668 #if defined(EXP_WIN32_DRIVER)
669 	if (!VALID_TERM_ENV(tname, NO_TERMINAL)) {
670 	    T(("Failure with TERM=%s", NonNull(tname)));
671 	    ret_error0(TGETENT_ERR, "TERM environment variable not set.\n");
672 	}
673 #elif defined(USE_TERM_DRIVER)
674 	if (!NonEmpty(tname))
675 	    tname = "unknown";
676 #else
677 	if (!NonEmpty(tname)) {
678 	    T(("Failure with TERM=%s", NonNull(tname)));
679 	    ret_error0(TGETENT_ERR, "TERM environment variable not set.\n");
680 	}
681 #endif
682     }
683     myname = strdup(tname);
684     if (myname == NULL || strlen(myname) > MAX_NAME_SIZE) {
685 	ret_error(TGETENT_ERR,
686 		  "TERM environment must be 1..%d characters.\n",
687 		  MAX_NAME_SIZE,
688 		  free(myname));
689     }
690 
691     T(("your terminal name is %s", myname));
692 
693     /*
694      * Allow output redirection.  This is what SVr3 does.  If stdout is
695      * directed to a file, screen updates go to standard error.
696      */
697     if (Filedes == STDOUT_FILENO && !NC_ISATTY(Filedes))
698 	Filedes = STDERR_FILENO;
699 #if defined(EXP_WIN32_DRIVER)
700     if (Filedes != STDERR_FILENO && NC_ISATTY(Filedes))
701 	_setmode(Filedes, _O_BINARY);
702 #endif
703 
704     /*
705      * Check if we have already initialized to use this terminal.  If so, we
706      * do not need to re-read the terminfo entry, or obtain TTY settings.
707      *
708      * This is an improvement on SVr4 curses.  If an application mixes curses
709      * and termcap calls, it may call both initscr and tgetent.  This is not
710      * really a good thing to do, but can happen if someone tries using ncurses
711      * with the readline library.  The problem we are fixing is that when
712      * tgetent calls setupterm, the resulting Ottyb struct in cur_term is
713      * zeroed.  A subsequent call to endwin uses the zeroed terminal settings
714      * rather than the ones saved in initscr.  So we check if cur_term appears
715      * to contain terminal settings for the same output file as our current
716      * call - and copy those terminal settings.  (SVr4 curses does not do this,
717      * however applications that are working around the problem will still work
718      * properly with this feature).
719      */
720     if (reuse
721 	&& (termp != 0)
722 	&& termp->Filedes == Filedes
723 	&& termp->_termname != 0
724 	&& !strcmp(termp->_termname, myname)
725 	&& _nc_name_match(TerminalType(termp).term_names, myname, "|")) {
726 	T(("reusing existing terminal information and mode-settings"));
727 	code = OK;
728 #ifdef USE_TERM_DRIVER
729 	TCB = (TERMINAL_CONTROL_BLOCK *) termp;
730 #endif
731     } else {
732 #ifdef USE_TERM_DRIVER
733 	TERMINAL_CONTROL_BLOCK *my_tcb;
734 	termp = 0;
735 	if ((my_tcb = typeCalloc(TERMINAL_CONTROL_BLOCK, 1)) != 0)
736 	    termp = &(my_tcb->term);
737 #else
738 	int status;
739 
740 	termp = typeCalloc(TERMINAL, 1);
741 #endif
742 	if (termp == 0) {
743 	    ret_error1(TGETENT_ERR,
744 		       "Not enough memory to create terminal structure.\n",
745 		       myname, free(myname));
746 	}
747 	++_nc_globals.terminal_count;
748 #if HAVE_SYSCONF
749 	{
750 	    long limit;
751 #ifdef LINE_MAX
752 	    limit = LINE_MAX;
753 #else
754 	    limit = _nc_globals.getstr_limit;
755 #endif
756 #ifdef _SC_LINE_MAX
757 	    if (limit < sysconf(_SC_LINE_MAX))
758 		limit = sysconf(_SC_LINE_MAX);
759 #endif
760 	    if (_nc_globals.getstr_limit < (int) limit)
761 		_nc_globals.getstr_limit = (int) limit;
762 	}
763 #endif /* HAVE_SYSCONF */
764 	T(("using %d for getstr limit", _nc_globals.getstr_limit));
765 
766 #ifdef USE_TERM_DRIVER
767 	INIT_TERM_DRIVER();
768 	/*
769 	 * _nc_get_driver() will call td_CanHandle() for each driver, and win_driver
770 	 * needs file descriptor to do the test, so set it before calling.
771 	 */
772 	termp->Filedes = (short) Filedes;
773 	TCB = (TERMINAL_CONTROL_BLOCK *) termp;
774 	code = _nc_globals.term_driver(TCB, myname, errret);
775 	if (code == OK) {
776 	    termp->_termname = strdup(myname);
777 	} else {
778 	    ret_error1(errret ? *errret : TGETENT_ERR,
779 		       "Could not find any driver to handle terminal.\n",
780 		       myname, free(myname));
781 	}
782 #else
783 #if NCURSES_USE_DATABASE || NCURSES_USE_TERMCAP
784 	status = _nc_setup_tinfo(myname, &TerminalType(termp));
785 	T(("_nc_setup_tinfo returns %d", status));
786 #else
787 	T(("no database available"));
788 	status = TGETENT_NO;
789 #endif
790 
791 	/* try fallback list if entry on disk */
792 	if (status != TGETENT_YES) {
793 	    const TERMTYPE2 *fallback = _nc_fallback2(myname);
794 
795 	    if (fallback) {
796 		T(("found fallback entry"));
797 		_nc_copy_termtype2(&(TerminalType(termp)), fallback);
798 		status = TGETENT_YES;
799 	    }
800 	}
801 
802 	if (status != TGETENT_YES) {
803 	    del_curterm(termp);
804 	    if (status == TGETENT_ERR) {
805 		free(myname);
806 		ret_error0(status, "terminals database is inaccessible\n");
807 	    } else if (status == TGETENT_NO) {
808 		ret_error1(status, "unknown terminal type.\n",
809 			   myname, free(myname));
810 	    } else {
811 		free(myname);
812 		ret_error0(status, "unexpected return-code\n");
813 	    }
814 	}
815 #if NCURSES_EXT_NUMBERS
816 	_nc_export_termtype2(&termp->type, &TerminalType(termp));
817 #endif
818 #if !USE_REENTRANT
819 	save_ttytype(termp);
820 #endif
821 
822 	termp->Filedes = (short) Filedes;
823 	termp->_termname = strdup(myname);
824 
825 	set_curterm(termp);
826 
827 	if (command_character)
828 	    _nc_tinfo_cmdch(termp, UChar(*command_character));
829 
830 	/*
831 	 * If an application calls setupterm() rather than initscr() or
832 	 * newterm(), we will not have the def_prog_mode() call in
833 	 * _nc_setupscreen().  Do it now anyway, so we can initialize the
834 	 * baudrate.  Also get the shell-mode so that erasechar() works.
835 	 */
836 	if (NC_ISATTY(Filedes)) {
837 	    NCURSES_SP_NAME(def_shell_mode) (NCURSES_SP_ARG);
838 	    NCURSES_SP_NAME(def_prog_mode) (NCURSES_SP_ARG);
839 	    NCURSES_SP_NAME(baudrate) (NCURSES_SP_ARG);
840 	}
841 	code = OK;
842 #endif
843     }
844 
845 #ifdef USE_TERM_DRIVER
846     *tp = termp;
847     NCURSES_SP_NAME(set_curterm) (sp, termp);
848     TCB->drv->td_init(TCB);
849 #else
850     sp = SP;
851 #endif
852 
853     /*
854      * We should always check the screensize, just in case.
855      */
856     TINFO_GET_SIZE(sp, termp, ptrLines(sp), ptrCols(sp));
857 
858     if (errret)
859 	*errret = TGETENT_YES;
860 
861 #ifndef USE_TERM_DRIVER
862     if (generic_type) {
863 	/*
864 	 * BSD 4.3's termcap contains mis-typed "gn" for wy99.  Do a sanity
865 	 * check before giving up.
866 	 */
867 	if ((VALID_STRING(cursor_address)
868 	     || (VALID_STRING(cursor_down) && VALID_STRING(cursor_home)))
869 	    && VALID_STRING(clear_screen)) {
870 	    ret_error1(TGETENT_YES, "terminal is not really generic.\n",
871 		       myname, free(myname));
872 	} else {
873 	    del_curterm(termp);
874 	    ret_error1(TGETENT_NO, "I need something more specific.\n",
875 		       myname, free(myname));
876 	}
877     } else if (hard_copy) {
878 	ret_error1(TGETENT_YES, "I can't handle hardcopy terminals.\n",
879 		   myname, free(myname));
880     }
881 #endif
882     free(myname);
883     returnCode(code);
884 }
885 
886 #ifdef USE_PTHREADS
887 /*
888  * Returns a non-null pointer unless a new screen should be allocated because
889  * no match was found in the pre-screen cache.
890  */
891 NCURSES_EXPORT(SCREEN *)
_nc_find_prescr(void)892 _nc_find_prescr(void)
893 {
894     SCREEN *result = 0;
895     PRESCREEN_LIST *p;
896     pthread_t id = GetThreadID();
897     for (p = _nc_prescreen.allocated; p != 0; p = p->next) {
898 	if (p->id == id) {
899 	    result = p->sp;
900 	    break;
901 	}
902     }
903     return result;
904 }
905 
906 /*
907  * Tells ncurses to forget that this thread was associated with the pre-screen
908  * cache.  It does not modify the pre-screen cache itself, since that is used
909  * for creating new screens.
910  */
911 NCURSES_EXPORT(void)
_nc_forget_prescr(void)912 _nc_forget_prescr(void)
913 {
914     PRESCREEN_LIST *p, *q;
915     pthread_t id = GetThreadID();
916     _nc_lock_global(screen);
917     for (p = _nc_prescreen.allocated, q = 0; p != 0; q = p, p = p->next) {
918 	if (p->id == id) {
919 	    if (q) {
920 		q->next = p->next;
921 	    } else {
922 		_nc_prescreen.allocated = p->next;
923 	    }
924 	    free(p);
925 	    break;
926 	}
927     }
928     _nc_unlock_global(screen);
929 }
930 #endif /* USE_PTHREADS */
931 
932 #if NCURSES_SP_FUNCS
933 /*
934  * In case of handling multiple screens, we need to have a screen before
935  * initialization in _nc_setupscreen takes place.  This is to extend the
936  * substitute for some of the stuff in _nc_prescreen, especially for slk and
937  * ripoff handling which should be done per screen.
938  */
939 NCURSES_EXPORT(SCREEN *)
new_prescr(void)940 new_prescr(void)
941 {
942     SCREEN *sp;
943 
944     START_TRACE();
945     T((T_CALLED("new_prescr()")));
946 
947     _nc_lock_global(screen);
948     if ((sp = _nc_find_prescr()) == 0) {
949 	sp = _nc_alloc_screen_sp();
950 	T(("_nc_alloc_screen_sp %p", (void *) sp));
951 	if (sp != 0) {
952 #ifdef USE_PTHREADS
953 	    PRESCREEN_LIST *p = typeCalloc(PRESCREEN_LIST, 1);
954 	    if (p != 0) {
955 		p->id = GetThreadID();
956 		p->sp = sp;
957 		p->next = _nc_prescreen.allocated;
958 		_nc_prescreen.allocated = p;
959 	    }
960 #else
961 	    _nc_prescreen.allocated = sp;
962 #endif
963 	    sp->rsp = sp->rippedoff;
964 	    sp->_filtered = _nc_prescreen.filter_mode;
965 	    sp->_use_env = _nc_prescreen.use_env;
966 #if NCURSES_NO_PADDING
967 	    sp->_no_padding = _nc_prescreen._no_padding;
968 #endif
969 	    sp->slk_format = 0;
970 	    sp->_slk = 0;
971 	    sp->_prescreen = TRUE;
972 	    SP_PRE_INIT(sp);
973 #if USE_REENTRANT
974 	    sp->_TABSIZE = _nc_prescreen._TABSIZE;
975 	    sp->_ESCDELAY = _nc_prescreen._ESCDELAY;
976 #endif
977 	}
978     } else {
979 	T(("_nc_alloc_screen_sp %p (reuse)", (void *) sp));
980     }
981     _nc_unlock_global(screen);
982     returnSP(sp);
983 }
984 #endif
985 
986 #ifdef USE_TERM_DRIVER
987 /*
988  * This entrypoint is called from tgetent() to allow a special case of reusing
989  * the same TERMINAL data (see comment).
990  */
991 NCURSES_EXPORT(int)
_nc_setupterm(const char * tname,int Filedes,int * errret,int reuse)992 _nc_setupterm(const char *tname,
993 	      int Filedes,
994 	      int *errret,
995 	      int reuse)
996 {
997     int rc = ERR;
998     TERMINAL *termp = 0;
999 
1000     _nc_init_pthreads();
1001     _nc_lock_global(prescreen);
1002     START_TRACE();
1003     if (TINFO_SETUP_TERM(&termp, tname, Filedes, errret, reuse) == OK) {
1004 	_nc_forget_prescr();
1005 	if (NCURSES_SP_NAME(set_curterm) (CURRENT_SCREEN_PRE, termp) != 0) {
1006 	    rc = OK;
1007 	}
1008     }
1009     _nc_unlock_global(prescreen);
1010 
1011     return rc;
1012 }
1013 #endif
1014 
1015 /*
1016  *	setupterm(termname, Filedes, errret)
1017  *
1018  *	Find and read the appropriate object file for the terminal
1019  *	Make cur_term point to the structure.
1020  */
1021 NCURSES_EXPORT(int)
setupterm(const char * tname,int Filedes,int * errret)1022 setupterm(const char *tname, int Filedes, int *errret)
1023 {
1024     START_TRACE();
1025     return _nc_setupterm(tname, Filedes, errret, FALSE);
1026 }
1027