1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright (c) 1995-1999 by Sun Microsystems, Inc.
24  * All rights reserved.
25  */
26 
27 /* LINTLIBRARY */
28 
29 /*
30  * setupterm.c
31  *
32  * XCurses Library
33  *
34  * Copyright 1990, 1995 by Mortice Kern Systems Inc.  All rights reserved.
35  *
36  */
37 
38 #ifdef M_RCSID
39 #ifndef lint
40 static char const rcsID[] =
41 "$Header: /team/ps/sun_xcurses/archive/local_changes/xcurses/src/lib/"
42 "libxcurses/src/libc/xcurses/rcs/setup.c 1.16 1998/06/05 14:35:33 "
43 "cbates Exp $";
44 #endif
45 #endif
46 
47 #include <private.h>
48 #include <sys/types.h>
49 #ifdef TIOCGWINSZ
50 #include <sys/ioctl.h>
51 #endif
52 #include <fcntl.h>
53 #include <limits.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
57 
58 TERMINAL	*cur_term;
59 
60 /*
61  * Any version number should be placed in this file, since setupterm()
62  * must be called in order to initialize Curses or Terminfo.
63  */
64 char	__m_curses_version[] = M_CURSES_VERSION;
65 
66 /*
67  * True if __m_setupterm() should use either the window settings from
68  * ioctl(), or the environment variables LINES and COLUMNS to override
69  * the terminfo database entries for 'lines' and 'columns'.
70  *
71  * Call use_env(flag) before either setupterm(), newterm(), or initscr().
72  */
73 static bool	use_environment = TRUE;
74 
75 static const char	e_terminal[] =
76 	"No memory for TERMINAL structure.\n";
77 static const char	e_unknown[] =
78 	"\"%s\": Unknown terminal type.\n";
79 static const char	e_pathmax[] =
80 	"\"%s\": terminfo database path too long.\n";
81 
82 
83 /*
84  * These globals are used so that macro arguments are evaluated
85  * exactly once
86  */
87 /* The downside is that it is not really thread-safe. Oh well... */
88 WINDOW	*__w1;
89 chtype	__cht1;
90 chtype	__cht2;
91 cchar_t	*__pcht1;
92 cchar_t	*__pcht2;
93 
94 /*
95  * Take the real command character out of the CC environment variable
96  * and substitute it in for the prototype given in 'command_character'.
97  */
98 static void
do_prototype(void)99 do_prototype(void)
100 {
101 	int	i, j;
102 	char	proto;
103 	char	*CC;
104 
105 	CC = getenv("CC");
106 	proto = *command_character;
107 
108 	for (i = 0; i < __COUNT_STR; i++)
109 		for (j = 0; cur_term->_str[i][j]; ++j)
110 			if (cur_term->_str[i][j] == proto)
111 				cur_term->_str[i][j] = *CC;
112 }
113 
114 #define	min(a, b)		((a) < (b) ? (a) : (b))
115 
116 /*
117  * Return a number from a terminfo file.  Numbers in a terminfo
118  * file are stored as two bytes with low-high ordering.
119  */
120 static short
getnum(int fd)121 getnum(int fd)
122 {
123 	unsigned char	bytes[2];
124 
125 	if (read(fd, bytes, 2) != 2)
126 		return (SHRT_MIN);
127 
128 	return ((short) (bytes[0] + bytes[1] * 256));
129 }
130 
131 /*
132  * MKS Header format for terminfo database files.
133  *
134  * The header consists of six short integers, stored using VAX/PDP style
135  * byte swapping (least-significant byte first).  The integers are
136  *
137  *  1) magic number (octal 0432);
138  *  2) the size, in bytes, of the names sections;
139  *  3) the number of bytes in the boolean section;
140  *  4) the number of short integers in the numbers section;
141  *  5) the number of offsets (short integers) in the strings section;
142  *  6) the size, in bytes, of the string table.
143  *
144  * Between the boolean and number sections, a null byte is inserted, if
145  * necessary, to ensure that the number section begins on an even byte
146  * offset.  All short integers are aligned on a short word boundary.
147  */
148 
149 #define	__TERMINFO_MAGIC		0432
150 
151 typedef struct {
152 	short	magic;
153 	short	name_size;
154 	short	bool_count;
155 	short	num_count;
156 	short	str_count;
157 	short	str_size;
158 } terminfo_header_t;
159 
160 /*
161  * Read the compiled terminfo entry in the given file into the
162  * structure pointed to by ptr, allocating space for the string
163  * table and placing its address in ptr->str_table.
164  */
165 int
__m_read_terminfo(const char * filename,TERMINAL * tp)166 __m_read_terminfo(const char *filename, TERMINAL *tp)
167 {
168 	int	fd;
169 	short	offset;
170 	size_t	i, len;
171 	terminfo_header_t	header;
172 	unsigned char	ch;
173 
174 	/* Get compiled terminfo file header. */
175 	if ((fd = open(filename, 0)) < 0) {
176 		goto error_1;
177 	}
178 
179 	if ((header.magic = getnum(fd)) != __TERMINFO_MAGIC ||
180 		(header.name_size = getnum(fd)) < 0 ||
181 		(header.bool_count = getnum(fd)) < 0 ||
182 		(header.num_count = getnum(fd)) < 0 ||
183 		(header.str_count = getnum(fd)) < 0 ||
184 		(header.str_size = getnum(fd)) < 0) {
185 		goto error_2;
186 	}
187 
188 
189 	/* Allocate and fetch terminal names. */
190 	len = min(127, header.name_size);
191 	if ((tp->_names = (char *) malloc(len + 1)) == NULL)
192 		goto error_2;
193 	if (read(fd, tp->_names, len) != len)
194 		goto error_3;
195 	tp->_names[len] = '\0';
196 
197 	if (127 < header.name_size)
198 		(void) lseek(fd, (off_t) (header.name_size - 127), SEEK_CUR);
199 
200 	/* Fetch booleans. */
201 	len = min(__COUNT_BOOL, header.bool_count);
202 	if (read(fd, tp->_bool, len) != len)
203 		goto error_3;
204 
205 	if (__COUNT_BOOL < header.bool_count) {
206 		(void) lseek(fd, (off_t) (header.bool_count - __COUNT_BOOL),
207 			SEEK_CUR);
208 	} else {
209 		for (len = header.bool_count; len < __COUNT_BOOL; ++len)
210 			tp->_bool[len] = 0;
211 	}
212 
213 	/* Eat padding byte. */
214 	if ((header.name_size + header.bool_count) % 2 != 0)
215 		(void) read(fd, &ch, sizeof (ch));
216 
217 	/* Fetch numbers. */
218 	len = min(__COUNT_NUM, header.num_count);
219 	for (i = 0; i < len; ++i)
220 		tp->_num[i] = getnum(fd);
221 
222 	if (__COUNT_NUM < header.num_count) {
223 		(void) lseek(fd, (off_t) (2 *
224 			(header.num_count - __COUNT_NUM)), SEEK_CUR);
225 	} else {
226 		for (len = header.num_count; len < __COUNT_NUM; ++len)
227 			tp->_num[len] = -1;
228 	}
229 
230 	/* Allocate and fetch strings. */
231 	if ((tp->_str_table = (char *) malloc(header.str_size)) == NULL)
232 		goto error_3;
233 
234 	/* Read in string offset section setting pointers to strings. */
235 	len = min(__COUNT_STR, header.str_count);
236 	for (i = 0; i < len; ++i) {
237 		if ((offset = getnum(fd)) == SHRT_MIN)
238 			goto error_4;
239 
240 		if (offset < 0)
241 			tp->_str[i] = NULL;
242 		else
243 			tp->_str[i] = tp->_str_table + offset;
244 	}
245 
246 	if (__COUNT_STR < header.str_count) {
247 		(void) lseek(fd, (off_t) (2 *
248 			(header.str_count - __COUNT_STR)), SEEK_CUR);
249 	} else {
250 		for (; i < __COUNT_STR; ++i)
251 			tp->_str[i] = NULL;
252 	}
253 
254 	if (read(fd, tp->_str_table, header.str_size) != header.str_size)
255 		goto error_4;
256 	(void) close(fd);
257 
258 	return (0);
259 error_4:
260 	free(tp->_str_table);
261 error_3:
262 	free(tp->_names);
263 error_2:
264 	(void) close(fd);
265 error_1:
266 	return (-1);
267 }
268 
269 void
use_env(bool bf)270 use_env(bool bf)
271 {
272 	use_environment = bf;
273 }
274 
275 /*
276  * Set up terminal.
277  *
278  * Reads in the terminfo database pointed to by $TERMINFO env. var.
279  * for the given terminal, but does not set up the output virtualization
280  * structues used by CURSES.  If the terminal name pointer is NULL,
281  * the $TERM env. var. is used for the terminal.  All output is to
282  * the given file descriptor which is initialized for output.
283  *
284  * On error, if errret != NULL then setupterm() returns OK
285  * or ERR and stores a status value in the integer pointed to by
286  * errret.  A status of 1 is normal, 0 means the terminal could
287  * not be found, and -1 means the terminfo database could not be
288  * found.  If errret == NULL then setupterm() prints an error
289  * message upon and exit().
290  *
291  * On success, cur_term set to a terminfo structure and OK returned.
292  */
293 int
__m_setupterm(char * termname,int ifd,int ofd,int * err_return)294 __m_setupterm(char *termname, int ifd, int ofd, int *err_return)
295 {
296 	int	err_code = 1;
297 	TERMINAL	*old_term;
298 	const char 	*err_msg;
299 
300 	/*
301 	 * It is possible to call setupterm() for multiple terminals,
302 	 * in which case we have to be able to restore cur_term in
303 	 * case of error.
304 	 */
305 	old_term = cur_term;
306 
307 	cur_term = (TERMINAL *) calloc(1, sizeof (*cur_term));
308 	if (cur_term == NULL) {
309 		err_code = -1;
310 		goto error;
311 	}
312 
313 	if (isatty(cur_term->_ifd = ifd))
314 		cur_term->_flags |= __TERM_ISATTY_IN;
315 	if (isatty(cur_term->_ofd = ofd))
316 		cur_term->_flags |= __TERM_ISATTY_OUT;
317 
318 	cur_term->_shell	= (void *) calloc(1, sizeof (struct termios));
319 	cur_term->_prog		= (void *) calloc(1, sizeof (struct termios));
320 	cur_term->_save		= (void *) calloc(1, sizeof (struct termios));
321 	cur_term->_actual	= (void *) calloc(1, sizeof (struct termios));
322 	cur_term->_term		= NULL;
323 	cur_term->_names	= NULL;
324 	cur_term->_str_table	= NULL;
325 	(void) def_shell_mode();
326 	(void) def_prog_mode();
327 	(void) __m_tty_get(PTERMIOS(_actual));	/* Synch cached value */
328 
329 #ifdef ONLCR
330 	if ((PTERMIOS(_prog)->c_oflag & (OPOST | ONLCR)) == (OPOST | ONLCR))
331 #else
332 	if (PTERMIOS(_prog)->c_oflag & OPOST)
333 #endif
334 		cur_term->_flags |= __TERM_NL_IS_CRLF;
335 
336 	(void) restartterm(termname, ofd, &err_code);
337 error:
338 	switch (err_code) {
339 	case -1:
340 		err_msg = e_terminal;
341 		break;
342 	case 0:
343 		err_msg = e_unknown;
344 		break;
345 	case 1:
346 		break;
347 	case 2:
348 		err_msg = e_pathmax;
349 		err_code = -1;
350 		break;
351 	}
352 
353 	if (err_return != NULL) {
354 		*err_return = err_code;
355 
356 		if (err_code == 1) {
357 			err_code = OK;
358 		} else {
359 			err_code = ERR;
360 			free(cur_term);
361 			cur_term = old_term;
362 		}
363 	} else if (err_code != 1) {
364 		(void) fprintf(stderr, err_msg, termname);
365 		exit(1);
366 	}
367 
368 	return (err_code);
369 }
370 
371 int
setupterm(char * term,int out,int * err)372 setupterm(char *term, int out, int *err)
373 {
374 	int	code;
375 
376 	code = __m_setupterm(term, STDIN_FILENO, out, err);
377 
378 	return (code);
379 }
380 
381 int
del_curterm(TERMINAL * tp)382 del_curterm(TERMINAL *tp)
383 {
384 	if (tp != NULL) {
385 		if (cur_term == tp)
386 			cur_term = NULL;
387 		if (tp->_str_table != NULL)
388 			free(tp->_str_table);
389 		if (tp->_names != NULL)
390 			free(tp->_names);
391 		if (tp->_term != NULL)
392 			free(tp->_term);
393 		if (tp->_pair != (short (*)[2]) 0)
394 			free(tp->_pair);
395 		if (tp->_color != (short (*)[3]) 0)
396 			free(tp->_color);
397 		if (tp->_shell)
398 			free(tp->_shell);
399 		if (tp->_prog)
400 			free(tp->_prog);
401 		if (tp->_save)
402 			free(tp->_save);
403 		if (tp->_actual)
404 			free(tp->_actual);
405 		free(tp);
406 	}
407 
408 	return (OK);
409 }
410 
411 TERMINAL *
set_curterm(TERMINAL * tp)412 set_curterm(TERMINAL *tp)
413 {
414 	TERMINAL	*old;
415 
416 	old = cur_term;
417 	cur_term = tp;
418 
419 	return (old);
420 }
421 
422 int
restartterm(char * tm,int fd,int * err_return)423 restartterm(char *tm, int fd, int *err_return)
424 {
425 	size_t	len;
426 	int	err_code;
427 	const char	*err_msg, *terminfo;
428 	char	*old_names, *old_strings, *old_term, *filename;
429 	static const char	def_termname[] = M_TERM_NAME;
430 	static const char	def_terminfo[] = M_TERMINFO_DIR;
431 
432 	err_code = 1;
433 	filename = NULL;
434 	old_term = cur_term->_term;
435 	old_names = cur_term->_names;
436 	old_strings = cur_term->_str_table;
437 
438 	terminfo = getenv("TERMINFO");
439 	if (terminfo == NULL || terminfo[0] == '\0') {
440 		terminfo = def_terminfo;
441 	} else {
442 		terminfo = (const char *) strdup((char *) terminfo);
443 		if (terminfo == NULL) {
444 			/* Not really true... */
445 			err_msg = e_terminal;
446 			err_code = 2;
447 			goto error;
448 		}
449 	}
450 
451 	if ((tm == NULL) &&
452 		(((tm = getenv("TERM")) == NULL) || (*tm == '\0'))) {
453 		tm = (char *)def_termname;
454 	}
455 
456 	/* Remember the terminal name being loaded. */
457 	cur_term->_term = strdup(tm);
458 	if (cur_term->_term == NULL) {
459 		err_msg = e_terminal;
460 		err_code = 2;
461 		goto error;
462 	}
463 
464 	/* Length of path we're going to construct. */
465 	len = strlen(terminfo) + 3 + strlen(tm);
466 
467 	if ((len > PATH_MAX) ||
468 		((filename = (char *)malloc(PATH_MAX + 1)) == NULL)) {
469 		err_msg = e_pathmax;
470 		err_code = 2;
471 		goto error;
472 	}
473 
474 	/* Construct terminfo filename. */
475 	(void) sprintf(filename, "%s/%c/%s", terminfo, tm[0], tm);
476 
477 	/* Go looking for compiled terminal definition. */
478 	if (__m_read_terminfo(filename, cur_term) < 0) {
479 		/* Length of default terminfo path. */
480 		len = strlen(def_terminfo) + 3 + strlen(tm);
481 
482 		if (len > PATH_MAX) {
483 			err_msg = e_pathmax;
484 			err_code = 2;
485 			goto error;
486 		}
487 
488 		(void) sprintf(filename, "%s/%c/%s", def_terminfo, tm[0], tm);
489 
490 		if (__m_read_terminfo(filename, cur_term) < 0) {
491 			err_msg = e_unknown;
492 			err_code = 0;
493 			goto error;
494 		}
495 	}
496 
497 	if (use_environment) {
498 		char	*env;
499 #ifdef TIOCGWINSZ
500 		/*
501 		 * Use ioctl(TIOCGWINSZ) to get row and column values.  These
502 		 * values may override the default terminfo settings.
503 		 */
504 		{
505 			struct winsize	wininfo;
506 			if (ioctl(fd, TIOCGWINSZ, &wininfo) != -1) {
507 				if (0 < wininfo.ws_col)
508 					columns = wininfo.ws_col;
509 				if (0 < wininfo.ws_row)
510 					lines = wininfo.ws_row;
511 			}
512 		}
513 #endif /* TIOCGWINSZ */
514 
515 		/* Check to see is the user wants a particular size terminal. */
516 		if ((env = getenv("LINES")) != NULL) {
517 			int	nlines = (int) strtol(env, (char **) 0, 10);
518 			if (0 < nlines)
519 				lines = nlines;
520 		}
521 		if ((env = getenv("COLUMNS")) != NULL) {
522 			int ncolumns = (int) strtol(env, (char **) 0, 10);
523 			if (0 < ncolumns)
524 				columns = ncolumns;
525 		}
526 	}
527 
528 	if (command_character != NULL && getenv("CC") != NULL)
529 		do_prototype();
530 
531 	/*
532 	 * If no_color_video is disabled, then assign it a value that
533 	 * permits all attributes in combination with colour.
534 	 */
535 	if (no_color_video == -1)
536 		no_color_video = 0;
537 
538 	__m_mvcur_cost();
539 error:
540 	if (filename != NULL)
541 		free(filename);
542 
543 	if (terminfo != def_terminfo)
544 		free((void *) terminfo);
545 
546 	if (err_return != NULL) {
547 		*err_return = err_code;
548 
549 		if (err_code == 1) {
550 			err_code = OK;
551 		} else {
552 			err_code = ERR;
553 			cur_term->_term = old_term;
554 			cur_term->_names = old_names;
555 			cur_term->_str_table = old_strings;
556 		}
557 	} else if (err_code != 1) {
558 		(void) fprintf(stderr, err_msg, tm);
559 		exit(1);
560 	}
561 
562 	if (err_code == OK) {
563 		if (old_names != NULL)
564 			free(old_names);
565 		if (old_strings != NULL)
566 			free(old_strings);
567 		if (old_term != NULL)
568 			free(old_term);
569 	}
570 
571 	return (err_code);
572 }
573 
574 /*
575  * Get the termios setting for the terminal.  Check the input
576  * file descriptor first, else the output file descriptor.  If
577  * both input and output are both terminals, it is assumed that
578  * they refer to the same terminal.
579  */
580 int
__m_tty_get(struct termios * tp)581 __m_tty_get(struct termios *tp)
582 {
583 	if (tcgetattr(cur_term->_ifd, tp) != 0) {
584 		/*
585 		 * Input was not a terminal, possibly redirected.
586 		 * Check output instead.
587 		 */
588 		if (tcgetattr(cur_term->_ofd, tp) != 0)
589 			return (ERR);
590 	}
591 
592 	return (OK);
593 }
594 
595 int
__m_tty_set_prog_mode(void)596 __m_tty_set_prog_mode(void)
597 {
598 	return (__m_tty_set(PTERMIOS(_prog)));
599 }
600 
601 /*
602  * Restore the termios settings.
603  */
604 int
__m_tty_set(struct termios * tp)605 __m_tty_set(struct termios *tp)
606 {
607 	int	fd;
608 	int	rval;
609 
610 	if (cur_term->_flags & __TERM_ISATTY_OUT) {
611 		fd = cur_term->_ofd;
612 	} else if (cur_term->_flags & __TERM_ISATTY_IN) {
613 		fd = cur_term->_ifd;
614 	} else {
615 		return (OK);
616 	}
617 	if (memcmp(tp, &cur_term->_actual, sizeof (struct termios)) == 0)
618 		return (OK);
619 
620 	*PTERMIOS(_actual) = *tp;
621 
622 	rval = tcsetattr(fd, TCSADRAIN, tp) == 0 ? OK : ERR;
623 
624 	return (rval);
625 }
626 
627 int
def_shell_mode(void)628 def_shell_mode(void)
629 {
630 	return (__m_tty_get(PTERMIOS(_shell)));
631 }
632 
633 int
def_prog_mode(void)634 def_prog_mode(void)
635 {
636 	return (__m_tty_get(PTERMIOS(_prog)));
637 }
638 
639 int
reset_shell_mode(void)640 reset_shell_mode(void)
641 {
642 	return (__m_tty_set(PTERMIOS(_shell)));
643 }
644 
645 int
reset_prog_mode(void)646 reset_prog_mode(void)
647 {
648 	return (__m_tty_set_prog_mode());
649 }
650