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