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