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.19 2020/02/02 23:34:34 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 (DISABLED(tty_settings->c_cc[VERASE]) || my_erase >= 0) {
367	tty_settings->c_cc[VERASE] = UChar((my_erase >= 0)
368					   ? my_erase
369					   : default_erase());
370    }
371
372    if (DISABLED(tty_settings->c_cc[VINTR]) || my_intr >= 0) {
373	tty_settings->c_cc[VINTR] = UChar((my_intr >= 0)
374					  ? my_intr
375					  : CINTR);
376    }
377
378    if (DISABLED(tty_settings->c_cc[VKILL]) || my_kill >= 0) {
379	tty_settings->c_cc[VKILL] = UChar((my_kill >= 0)
380					  ? my_kill
381					  : CKILL);
382    }
383}
384
385/*
386 * Set up various conversions in the TTY parameter, including parity, tabs,
387 * returns, echo, and case, according to the termcap entry.
388 */
389void
390set_conversions(TTY * tty_settings)
391{
392#ifdef ONLCR
393    tty_settings->c_oflag |= ONLCR;
394#endif
395    tty_settings->c_iflag |= ICRNL;
396    tty_settings->c_lflag |= ECHO;
397#ifdef OXTABS
398    tty_settings->c_oflag |= OXTABS;
399#endif /* OXTABS */
400
401    /* test used to be tgetflag("NL") */
402    if (VALID_STRING(newline) && newline[0] == '\n' && !newline[1]) {
403	/* Newline, not linefeed. */
404#ifdef ONLCR
405	tty_settings->c_oflag &= ~((unsigned) ONLCR);
406#endif
407	tty_settings->c_iflag &= ~((unsigned) ICRNL);
408    }
409#ifdef OXTABS
410    /* test used to be tgetflag("pt") */
411    if (VALID_STRING(set_tab) && VALID_STRING(clear_all_tabs))
412	tty_settings->c_oflag &= ~OXTABS;
413#endif /* OXTABS */
414    tty_settings->c_lflag |= (ECHOE | ECHOK);
415}
416
417static bool
418sent_string(const char *s)
419{
420    bool sent = FALSE;
421    if (VALID_STRING(s)) {
422	tputs(s, 0, out_char);
423	sent = TRUE;
424    }
425    return sent;
426}
427
428static bool
429to_left_margin(void)
430{
431    if (VALID_STRING(carriage_return)) {
432	sent_string(carriage_return);
433    } else {
434	out_char('\r');
435    }
436    return TRUE;
437}
438
439/*
440 * Set the hardware tabs on the terminal, using the 'ct' (clear all tabs),
441 * 'st' (set one tab) and 'ch' (horizontal cursor addressing) capabilities.
442 * This is done before 'if' and 'is', so they can recover in case of error.
443 *
444 * Return TRUE if we set any tab stops, FALSE if not.
445 */
446static bool
447reset_tabstops(int wide)
448{
449    if ((init_tabs != 8)
450	&& VALID_NUMERIC(init_tabs)
451	&& VALID_STRING(set_tab)
452	&& VALID_STRING(clear_all_tabs)) {
453	int c;
454
455	to_left_margin();
456	tputs(clear_all_tabs, 0, out_char);
457	if (init_tabs > 1) {
458	    if (init_tabs > wide)
459		init_tabs = (short) wide;
460	    for (c = init_tabs; c < wide; c += init_tabs) {
461		fprintf(my_file, "%*s", init_tabs, " ");
462		tputs(set_tab, 0, out_char);
463	    }
464	    to_left_margin();
465	}
466	return (TRUE);
467    }
468    return (FALSE);
469}
470
471/* Output startup string. */
472bool
473send_init_strings(int fd GCC_UNUSED, TTY * old_settings)
474{
475    int i;
476    bool need_flush = FALSE;
477
478    (void) old_settings;
479#ifdef TAB3
480    if (old_settings != 0 &&
481	old_settings->c_oflag & (TAB3 | ONLCR | OCRNL | ONLRET)) {
482	old_settings->c_oflag &= (TAB3 | ONLCR | OCRNL | ONLRET);
483	SET_TTY(fd, old_settings);
484    }
485#endif
486    if (use_reset || use_init) {
487	if (VALID_STRING(init_prog)) {
488	    IGNORE_RC(system(init_prog));
489	}
490
491	need_flush |= sent_string((use_reset && (reset_1string != 0))
492				  ? reset_1string
493				  : init_1string);
494
495	need_flush |= sent_string((use_reset && (reset_2string != 0))
496				  ? reset_2string
497				  : init_2string);
498
499	if (VALID_STRING(clear_margins)) {
500	    need_flush |= sent_string(clear_margins);
501	} else
502#if defined(set_lr_margin)
503	if (VALID_STRING(set_lr_margin)) {
504	    need_flush |= sent_string(TPARM_2(set_lr_margin, 0,
505					      columns - 1));
506	} else
507#endif
508#if defined(set_left_margin_parm) && defined(set_right_margin_parm)
509	    if (VALID_STRING(set_left_margin_parm)
510		&& VALID_STRING(set_right_margin_parm)) {
511	    need_flush |= sent_string(TPARM_1(set_left_margin_parm, 0));
512	    need_flush |= sent_string(TPARM_1(set_right_margin_parm,
513					      columns - 1));
514	} else
515#endif
516	    if (VALID_STRING(set_left_margin)
517		&& VALID_STRING(set_right_margin)) {
518	    need_flush |= to_left_margin();
519	    need_flush |= sent_string(set_left_margin);
520	    if (VALID_STRING(parm_right_cursor)) {
521		need_flush |= sent_string(TPARM_1(parm_right_cursor,
522						  columns - 1));
523	    } else {
524		for (i = 0; i < columns - 1; i++) {
525		    out_char(' ');
526		    need_flush = TRUE;
527		}
528	    }
529	    need_flush |= sent_string(set_right_margin);
530	    need_flush |= to_left_margin();
531	}
532
533	need_flush |= reset_tabstops(columns);
534
535	need_flush |= cat_file((use_reset && reset_file) ? reset_file : init_file);
536
537	need_flush |= sent_string((use_reset && (reset_3string != 0))
538				  ? reset_3string
539				  : init_3string);
540    }
541
542    return need_flush;
543}
544
545/*
546 * Tell the user if a control key has been changed from the default value.
547 */
548static void
549show_tty_change(TTY * old_settings,
550		TTY * new_settings,
551		const char *name,
552		int which,
553		unsigned def)
554{
555    unsigned older, newer;
556    char *p;
557
558    newer = new_settings->c_cc[which];
559    older = old_settings->c_cc[which];
560
561    if (older == newer && older == def)
562	return;
563
564    (void) fprintf(stderr, "%s %s ", name, older == newer ? "is" : "set to");
565
566    if (DISABLED(newer)) {
567	(void) fprintf(stderr, "undef.\n");
568	/*
569	 * Check 'delete' before 'backspace', since the key_backspace value
570	 * is ambiguous.
571	 */
572    } else if (newer == 0177) {
573	(void) fprintf(stderr, "delete.\n");
574    } else if ((p = key_backspace) != 0
575	       && newer == (unsigned char) p[0]
576	       && p[1] == '\0') {
577	(void) fprintf(stderr, "backspace.\n");
578    } else if (newer < 040) {
579	newer ^= 0100;
580	(void) fprintf(stderr, "control-%c (^%c).\n", UChar(newer), UChar(newer));
581    } else
582	(void) fprintf(stderr, "%c.\n", UChar(newer));
583}
584
585/**************************************************************************
586 * Miscellaneous.
587 **************************************************************************/
588
589void
590reset_start(FILE *fp, bool is_reset, bool is_init)
591{
592    my_file = fp;
593    use_reset = is_reset;
594    use_init = is_init;
595}
596
597void
598reset_flush(void)
599{
600    if (my_file != 0)
601	fflush(my_file);
602}
603
604void
605print_tty_chars(TTY * old_settings, TTY * new_settings)
606{
607    show_tty_change(old_settings, new_settings, "Erase", VERASE, CERASE);
608    show_tty_change(old_settings, new_settings, "Kill", VKILL, CKILL);
609    show_tty_change(old_settings, new_settings, "Interrupt", VINTR, CINTR);
610}
611
612#if HAVE_SIZECHANGE
613/*
614 * Set window size if not set already, but update our copy of the values if the
615 * size was set.
616 */
617void
618set_window_size(int fd, short *high, short *wide)
619{
620    STRUCT_WINSIZE win;
621    (void) ioctl(fd, IOCTL_GET_WINSIZE, &win);
622    if (WINSIZE_ROWS(win) == 0 &&
623	WINSIZE_COLS(win) == 0) {
624	if (*high > 0 && *wide > 0) {
625	    WINSIZE_ROWS(win) = (unsigned short) *high;
626	    WINSIZE_COLS(win) = (unsigned short) *wide;
627	    (void) ioctl(fd, IOCTL_SET_WINSIZE, &win);
628	}
629    } else if (WINSIZE_ROWS(win) > 0 &&
630	       WINSIZE_COLS(win) > 0) {
631	*high = (short) WINSIZE_ROWS(win);
632	*wide = (short) WINSIZE_COLS(win);
633    }
634}
635#endif
636