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