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