xref: /freebsd-src/contrib/ncurses/progs/reset_cmd.c (revision aae38d10b4eebf81c0942947e8b83a9bb8651d88)
1 /****************************************************************************
2  * Copyright (c) 2016-2017,2019 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28 
29 /****************************************************************************
30  *  Author: Thomas E. Dickey                                                *
31  ****************************************************************************/
32 
33 #include <reset_cmd.h>
34 #include <tty_settings.h>
35 
36 #include <errno.h>
37 #include <stdio.h>
38 #include <fcntl.h>
39 
40 #if HAVE_SIZECHANGE
41 # if !defined(sun) || !TERMIOS
42 #  if HAVE_SYS_IOCTL_H
43 #   include <sys/ioctl.h>
44 #  endif
45 # endif
46 #endif
47 
48 #if NEED_PTEM_H
49 /* they neglected to define struct winsize in termios.h -- it's only
50    in termio.h	*/
51 #include <sys/stream.h>
52 #include <sys/ptem.h>
53 #endif
54 
55 MODULE_ID("$Id: reset_cmd.c,v 1.18 2019/07/13 21:35:13 tom Exp $")
56 
57 /*
58  * SCO defines TIOCGSIZE and the corresponding struct.  Other systems (SunOS,
59  * Solaris, IRIX) define TIOCGWINSZ and struct winsize.
60  */
61 #ifdef TIOCGSIZE
62 # define IOCTL_GET_WINSIZE TIOCGSIZE
63 # define IOCTL_SET_WINSIZE TIOCSSIZE
64 # define STRUCT_WINSIZE struct ttysize
65 # define WINSIZE_ROWS(n) n.ts_lines
66 # define WINSIZE_COLS(n) n.ts_cols
67 #else
68 # ifdef TIOCGWINSZ
69 #  define IOCTL_GET_WINSIZE TIOCGWINSZ
70 #  define IOCTL_SET_WINSIZE TIOCSWINSZ
71 #  define STRUCT_WINSIZE struct winsize
72 #  define WINSIZE_ROWS(n) n.ws_row
73 #  define WINSIZE_COLS(n) n.ws_col
74 # endif
75 #endif
76 
77 static FILE *my_file;
78 
79 static bool use_reset = FALSE;	/* invoked as reset */
80 static bool use_init = FALSE;	/* invoked as init */
81 
82 static void
83 failed(const char *msg)
84 {
85     int code = errno;
86 
87     (void) fprintf(stderr, "%s: %s: %s\n", _nc_progname, msg, strerror(code));
88     restore_tty_settings();
89     (void) fprintf(my_file, "\n");
90     fflush(my_file);
91     ExitProgram(ErrSystem(code));
92     /* NOTREACHED */
93 }
94 
95 static bool
96 cat_file(char *file)
97 {
98     FILE *fp;
99     size_t nr;
100     char buf[BUFSIZ];
101     bool sent = FALSE;
102 
103     if (file != 0) {
104 	if ((fp = fopen(file, "r")) == 0)
105 	    failed(file);
106 
107 	while ((nr = fread(buf, sizeof(char), sizeof(buf), fp)) != 0) {
108 	    if (fwrite(buf, sizeof(char), nr, my_file) != nr) {
109 		failed(file);
110 	    }
111 	    sent = TRUE;
112 	}
113 	fclose(fp);
114     }
115     return sent;
116 }
117 
118 static int
119 out_char(int c)
120 {
121     return putc(c, my_file);
122 }
123 
124 /**************************************************************************
125  * Mode-setting logic
126  **************************************************************************/
127 
128 /* some BSD systems have these built in, some systems are missing
129  * one or more definitions. The safest solution is to override unless the
130  * commonly-altered ones are defined.
131  */
132 #if !(defined(CERASE) && defined(CINTR) && defined(CKILL) && defined(CQUIT))
133 #undef CEOF
134 #undef CERASE
135 #undef CINTR
136 #undef CKILL
137 #undef CLNEXT
138 #undef CRPRNT
139 #undef CQUIT
140 #undef CSTART
141 #undef CSTOP
142 #undef CSUSP
143 #endif
144 
145 /* control-character defaults */
146 #ifndef CEOF
147 #define CEOF	CTRL('D')
148 #endif
149 #ifndef CERASE
150 #define CERASE	CTRL('H')
151 #endif
152 #ifndef CINTR
153 #define CINTR	127		/* ^? */
154 #endif
155 #ifndef CKILL
156 #define CKILL	CTRL('U')
157 #endif
158 #ifndef CLNEXT
159 #define CLNEXT  CTRL('v')
160 #endif
161 #ifndef CRPRNT
162 #define CRPRNT  CTRL('r')
163 #endif
164 #ifndef CQUIT
165 #define CQUIT	CTRL('\\')
166 #endif
167 #ifndef CSTART
168 #define CSTART	CTRL('Q')
169 #endif
170 #ifndef CSTOP
171 #define CSTOP	CTRL('S')
172 #endif
173 #ifndef CSUSP
174 #define CSUSP	CTRL('Z')
175 #endif
176 
177 #if defined(_POSIX_VDISABLE)
178 #define DISABLED(val)   (((_POSIX_VDISABLE != -1) \
179 		       && ((val) == _POSIX_VDISABLE)) \
180 		      || ((val) <= 0))
181 #else
182 #define DISABLED(val)   ((int)(val) <= 0)
183 #endif
184 
185 #define CHK(val, dft)   (unsigned char) (DISABLED(val) ? dft : val)
186 
187 #define reset_char(item, value) \
188     tty_settings->c_cc[item] = CHK(tty_settings->c_cc[item], value)
189 
190 /*
191  * Reset the terminal mode bits to a sensible state.  Very useful after
192  * a child program dies in raw mode.
193  */
194 void
195 reset_tty_settings(int fd, TTY * tty_settings)
196 {
197     GET_TTY(fd, tty_settings);
198 
199 #ifdef TERMIOS
200 #if defined(VDISCARD) && defined(CDISCARD)
201     reset_char(VDISCARD, CDISCARD);
202 #endif
203     reset_char(VEOF, CEOF);
204     reset_char(VERASE, CERASE);
205 #if defined(VFLUSH) && defined(CFLUSH)
206     reset_char(VFLUSH, CFLUSH);
207 #endif
208     reset_char(VINTR, CINTR);
209     reset_char(VKILL, CKILL);
210 #if defined(VLNEXT) && defined(CLNEXT)
211     reset_char(VLNEXT, CLNEXT);
212 #endif
213     reset_char(VQUIT, CQUIT);
214 #if defined(VREPRINT) && defined(CRPRNT)
215     reset_char(VREPRINT, CRPRNT);
216 #endif
217 #if defined(VSTART) && defined(CSTART)
218     reset_char(VSTART, CSTART);
219 #endif
220 #if defined(VSTOP) && defined(CSTOP)
221     reset_char(VSTOP, CSTOP);
222 #endif
223 #if defined(VSUSP) && defined(CSUSP)
224     reset_char(VSUSP, CSUSP);
225 #endif
226 #if defined(VWERASE) && defined(CWERASE)
227     reset_char(VWERASE, CWERASE);
228 #endif
229 
230     tty_settings->c_iflag &= ~((unsigned) (IGNBRK
231 					   | PARMRK
232 					   | INPCK
233 					   | ISTRIP
234 					   | INLCR
235 					   | IGNCR
236 #ifdef IUCLC
237 					   | IUCLC
238 #endif
239 #ifdef IXANY
240 					   | IXANY
241 #endif
242 					   | IXOFF));
243 
244     tty_settings->c_iflag |= (BRKINT
245 			      | IGNPAR
246 			      | ICRNL
247 			      | IXON
248 #ifdef IMAXBEL
249 			      | IMAXBEL
250 #endif
251 	);
252 
253     tty_settings->c_oflag &= ~((unsigned) (0
254 #ifdef OLCUC
255 					   | OLCUC
256 #endif
257 #ifdef OCRNL
258 					   | OCRNL
259 #endif
260 #ifdef ONOCR
261 					   | ONOCR
262 #endif
263 #ifdef ONLRET
264 					   | ONLRET
265 #endif
266 #ifdef OFILL
267 					   | OFILL
268 #endif
269 #ifdef OFDEL
270 					   | OFDEL
271 #endif
272 #ifdef NLDLY
273 					   | NLDLY
274 #endif
275 #ifdef CRDLY
276 					   | CRDLY
277 #endif
278 #ifdef TABDLY
279 					   | TABDLY
280 #endif
281 #ifdef BSDLY
282 					   | BSDLY
283 #endif
284 #ifdef VTDLY
285 					   | VTDLY
286 #endif
287 #ifdef FFDLY
288 					   | FFDLY
289 #endif
290 			       ));
291 
292     tty_settings->c_oflag |= (OPOST
293 #ifdef ONLCR
294 			      | ONLCR
295 #endif
296 	);
297 
298     tty_settings->c_cflag &= ~((unsigned) (CSIZE
299 					   | CSTOPB
300 					   | PARENB
301 					   | PARODD
302 					   | CLOCAL));
303     tty_settings->c_cflag |= (CS8 | CREAD);
304     tty_settings->c_lflag &= ~((unsigned) (ECHONL
305 					   | NOFLSH
306 #ifdef TOSTOP
307 					   | TOSTOP
308 #endif
309 #ifdef ECHOPTR
310 					   | ECHOPRT
311 #endif
312 #ifdef XCASE
313 					   | XCASE
314 #endif
315 			       ));
316 
317     tty_settings->c_lflag |= (ISIG
318 			      | ICANON
319 			      | ECHO
320 			      | ECHOE
321 			      | ECHOK
322 #ifdef ECHOCTL
323 			      | ECHOCTL
324 #endif
325 #ifdef ECHOKE
326 			      | ECHOKE
327 #endif
328 	);
329 #endif
330 
331     SET_TTY(fd, tty_settings);
332 }
333 
334 /*
335  * Returns a "good" value for the erase character.  This is loosely based on
336  * the BSD4.4 logic.
337  */
338 static int
339 default_erase(void)
340 {
341     int result;
342 
343     if (over_strike
344 	&& VALID_STRING(key_backspace)
345 	&& strlen(key_backspace) == 1) {
346 	result = key_backspace[0];
347     } else {
348 	result = CERASE;
349     }
350 
351     return result;
352 }
353 
354 /*
355  * Update the values of the erase, interrupt, and kill characters in the TTY
356  * parameter.
357  *
358  * SVr4 tset (e.g., Solaris 2.5) only modifies the intr, quit or erase
359  * characters if they're unset, or if we specify them as options.  This differs
360  * from BSD 4.4 tset, which always sets erase.
361  */
362 void
363 set_control_chars(TTY * tty_settings, int my_erase, int my_intr, int my_kill)
364 {
365     if (DISABLED(tty_settings->c_cc[VERASE]) || my_erase >= 0) {
366 	tty_settings->c_cc[VERASE] = UChar((my_erase >= 0)
367 					   ? my_erase
368 					   : default_erase());
369     }
370 
371     if (DISABLED(tty_settings->c_cc[VINTR]) || my_intr >= 0) {
372 	tty_settings->c_cc[VINTR] = UChar((my_intr >= 0)
373 					  ? my_intr
374 					  : CINTR);
375     }
376 
377     if (DISABLED(tty_settings->c_cc[VKILL]) || my_kill >= 0) {
378 	tty_settings->c_cc[VKILL] = UChar((my_kill >= 0)
379 					  ? my_kill
380 					  : CKILL);
381     }
382 }
383 
384 /*
385  * Set up various conversions in the TTY parameter, including parity, tabs,
386  * returns, echo, and case, according to the termcap entry.
387  */
388 void
389 set_conversions(TTY * tty_settings)
390 {
391 #ifdef ONLCR
392     tty_settings->c_oflag |= ONLCR;
393 #endif
394     tty_settings->c_iflag |= ICRNL;
395     tty_settings->c_lflag |= ECHO;
396 #ifdef OXTABS
397     tty_settings->c_oflag |= OXTABS;
398 #endif /* OXTABS */
399 
400     /* test used to be tgetflag("NL") */
401     if (VALID_STRING(newline) && newline[0] == '\n' && !newline[1]) {
402 	/* Newline, not linefeed. */
403 #ifdef ONLCR
404 	tty_settings->c_oflag &= ~((unsigned) ONLCR);
405 #endif
406 	tty_settings->c_iflag &= ~((unsigned) ICRNL);
407     }
408 #ifdef OXTABS
409     /* test used to be tgetflag("pt") */
410     if (VALID_STRING(set_tab) && VALID_STRING(clear_all_tabs))
411 	tty_settings->c_oflag &= ~OXTABS;
412 #endif /* OXTABS */
413     tty_settings->c_lflag |= (ECHOE | ECHOK);
414 }
415 
416 static bool
417 sent_string(const char *s)
418 {
419     bool sent = FALSE;
420     if (VALID_STRING(s)) {
421 	tputs(s, 0, out_char);
422 	sent = TRUE;
423     }
424     return sent;
425 }
426 
427 static bool
428 to_left_margin(void)
429 {
430     if (VALID_STRING(carriage_return)) {
431 	sent_string(carriage_return);
432     } else {
433 	out_char('\r');
434     }
435     return TRUE;
436 }
437 
438 /*
439  * Set the hardware tabs on the terminal, using the 'ct' (clear all tabs),
440  * 'st' (set one tab) and 'ch' (horizontal cursor addressing) capabilities.
441  * This is done before 'if' and 'is', so they can recover in case of error.
442  *
443  * Return TRUE if we set any tab stops, FALSE if not.
444  */
445 static bool
446 reset_tabstops(int wide)
447 {
448     if ((init_tabs != 8)
449 	&& VALID_NUMERIC(init_tabs)
450 	&& VALID_STRING(set_tab)
451 	&& VALID_STRING(clear_all_tabs)) {
452 	int c;
453 
454 	to_left_margin();
455 	tputs(clear_all_tabs, 0, out_char);
456 	if (init_tabs > 1) {
457 	    if (init_tabs > wide)
458 		init_tabs = (short) wide;
459 	    for (c = init_tabs; c < wide; c += init_tabs) {
460 		fprintf(my_file, "%*s", init_tabs, " ");
461 		tputs(set_tab, 0, out_char);
462 	    }
463 	    to_left_margin();
464 	}
465 	return (TRUE);
466     }
467     return (FALSE);
468 }
469 
470 /* Output startup string. */
471 bool
472 send_init_strings(int fd GCC_UNUSED, TTY * old_settings)
473 {
474     int i;
475     bool need_flush = FALSE;
476 
477     (void) old_settings;
478 #ifdef TAB3
479     if (old_settings != 0 &&
480 	old_settings->c_oflag & (TAB3 | ONLCR | OCRNL | ONLRET)) {
481 	old_settings->c_oflag &= (TAB3 | ONLCR | OCRNL | ONLRET);
482 	SET_TTY(fd, old_settings);
483     }
484 #endif
485     if (use_reset || use_init) {
486 	if (VALID_STRING(init_prog)) {
487 	    IGNORE_RC(system(init_prog));
488 	}
489 
490 	need_flush |= sent_string((use_reset && (reset_1string != 0))
491 				  ? reset_1string
492 				  : init_1string);
493 
494 	need_flush |= sent_string((use_reset && (reset_2string != 0))
495 				  ? reset_2string
496 				  : init_2string);
497 
498 	if (VALID_STRING(clear_margins)) {
499 	    need_flush |= sent_string(clear_margins);
500 	} else
501 #if defined(set_lr_margin)
502 	if (VALID_STRING(set_lr_margin)) {
503 	    need_flush |= sent_string(TPARM_2(set_lr_margin, 0,
504 					      columns - 1));
505 	} else
506 #endif
507 #if defined(set_left_margin_parm) && defined(set_right_margin_parm)
508 	    if (VALID_STRING(set_left_margin_parm)
509 		&& VALID_STRING(set_right_margin_parm)) {
510 	    need_flush |= sent_string(TPARM_1(set_left_margin_parm, 0));
511 	    need_flush |= sent_string(TPARM_1(set_right_margin_parm,
512 					      columns - 1));
513 	} else
514 #endif
515 	    if (VALID_STRING(set_left_margin)
516 		&& VALID_STRING(set_right_margin)) {
517 	    need_flush |= to_left_margin();
518 	    need_flush |= sent_string(set_left_margin);
519 	    if (VALID_STRING(parm_right_cursor)) {
520 		need_flush |= sent_string(TPARM_1(parm_right_cursor,
521 						  columns - 1));
522 	    } else {
523 		for (i = 0; i < columns - 1; i++) {
524 		    out_char(' ');
525 		    need_flush = TRUE;
526 		}
527 	    }
528 	    need_flush |= sent_string(set_right_margin);
529 	    need_flush |= to_left_margin();
530 	}
531 
532 	need_flush |= reset_tabstops(columns);
533 
534 	need_flush |= cat_file((use_reset && reset_file) ? reset_file : init_file);
535 
536 	need_flush |= sent_string((use_reset && (reset_3string != 0))
537 				  ? reset_3string
538 				  : init_3string);
539     }
540 
541     return need_flush;
542 }
543 
544 /*
545  * Tell the user if a control key has been changed from the default value.
546  */
547 static void
548 show_tty_change(TTY * old_settings,
549 		TTY * new_settings,
550 		const char *name,
551 		int which,
552 		unsigned def)
553 {
554     unsigned older, newer;
555     char *p;
556 
557     newer = new_settings->c_cc[which];
558     older = old_settings->c_cc[which];
559 
560     if (older == newer && older == def)
561 	return;
562 
563     (void) fprintf(stderr, "%s %s ", name, older == newer ? "is" : "set to");
564 
565     if (DISABLED(newer)) {
566 	(void) fprintf(stderr, "undef.\n");
567 	/*
568 	 * Check 'delete' before 'backspace', since the key_backspace value
569 	 * is ambiguous.
570 	 */
571     } else if (newer == 0177) {
572 	(void) fprintf(stderr, "delete.\n");
573     } else if ((p = key_backspace) != 0
574 	       && newer == (unsigned char) p[0]
575 	       && p[1] == '\0') {
576 	(void) fprintf(stderr, "backspace.\n");
577     } else if (newer < 040) {
578 	newer ^= 0100;
579 	(void) fprintf(stderr, "control-%c (^%c).\n", UChar(newer), UChar(newer));
580     } else
581 	(void) fprintf(stderr, "%c.\n", UChar(newer));
582 }
583 
584 /**************************************************************************
585  * Miscellaneous.
586  **************************************************************************/
587 
588 void
589 reset_start(FILE *fp, bool is_reset, bool is_init)
590 {
591     my_file = fp;
592     use_reset = is_reset;
593     use_init = is_init;
594 }
595 
596 void
597 reset_flush(void)
598 {
599     if (my_file != 0)
600 	fflush(my_file);
601 }
602 
603 void
604 print_tty_chars(TTY * old_settings, TTY * new_settings)
605 {
606     show_tty_change(old_settings, new_settings, "Erase", VERASE, CERASE);
607     show_tty_change(old_settings, new_settings, "Kill", VKILL, CKILL);
608     show_tty_change(old_settings, new_settings, "Interrupt", VINTR, CINTR);
609 }
610 
611 #if HAVE_SIZECHANGE
612 /*
613  * Set window size if not set already, but update our copy of the values if the
614  * size was set.
615  */
616 void
617 set_window_size(int fd, short *high, short *wide)
618 {
619     STRUCT_WINSIZE win;
620     (void) ioctl(fd, IOCTL_GET_WINSIZE, &win);
621     if (WINSIZE_ROWS(win) == 0 &&
622 	WINSIZE_COLS(win) == 0) {
623 	if (*high > 0 && *wide > 0) {
624 	    WINSIZE_ROWS(win) = (unsigned short) *high;
625 	    WINSIZE_COLS(win) = (unsigned short) *wide;
626 	    (void) ioctl(fd, IOCTL_SET_WINSIZE, &win);
627 	}
628     } else if (WINSIZE_ROWS(win) > 0 &&
629 	       WINSIZE_COLS(win) > 0) {
630 	*high = (short) WINSIZE_ROWS(win);
631 	*wide = (short) WINSIZE_COLS(win);
632     }
633 }
634 #endif
635