+/****************************************************************************
+ * Copyright (c) 2016 Free Software Foundation, Inc. *
+ * *
+ * Permission is hereby granted, free of charge, to any person obtaining a *
+ * copy of this software and associated documentation files (the *
+ * "Software"), to deal in the Software without restriction, including *
+ * without limitation the rights to use, copy, modify, merge, publish, *
+ * distribute, distribute with modifications, sublicense, and/or sell *
+ * copies of the Software, and to permit persons to whom the Software is *
+ * furnished to do so, subject to the following conditions: *
+ * *
+ * The above copyright notice and this permission notice shall be included *
+ * in all copies or substantial portions of the Software. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS *
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
+ * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR *
+ * THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
+ * *
+ * Except as contained in this notice, the name(s) of the above copyright *
+ * holders shall not be used in advertising or otherwise to promote the *
+ * sale, use or other dealings in this Software without prior written *
+ * authorization. *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Author: Thomas E. Dickey *
+ ****************************************************************************/
+
+#include <reset_cmd.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#if HAVE_SIZECHANGE
+# if !defined(sun) || !TERMIOS
+# if HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+# endif
+# endif
+#endif
+
+#if NEED_PTEM_H
+/* they neglected to define struct winsize in termios.h -- it's only
+ in termio.h */
+#include <sys/stream.h>
+#include <sys/ptem.h>
+#endif
+
+MODULE_ID("$Id: reset_cmd.c,v 1.5 2016/08/06 21:04:54 tom Exp $")
+
+/*
+ * SCO defines TIOCGSIZE and the corresponding struct. Other systems (SunOS,
+ * Solaris, IRIX) define TIOCGWINSZ and struct winsize.
+ */
+#ifdef TIOCGSIZE
+# define IOCTL_GET_WINSIZE TIOCGSIZE
+# define IOCTL_SET_WINSIZE TIOCSSIZE
+# define STRUCT_WINSIZE struct ttysize
+# define WINSIZE_ROWS(n) n.ts_lines
+# define WINSIZE_COLS(n) n.ts_cols
+#else
+# ifdef TIOCGWINSZ
+# define IOCTL_GET_WINSIZE TIOCGWINSZ
+# define IOCTL_SET_WINSIZE TIOCSWINSZ
+# define STRUCT_WINSIZE struct winsize
+# define WINSIZE_ROWS(n) n.ws_row
+# define WINSIZE_COLS(n) n.ws_col
+# endif
+#endif
+
+static int my_fd;
+static FILE *my_file;
+static TTY original_settings;
+
+static bool can_restore = FALSE;
+static bool use_reset = FALSE; /* invoked as reset */
+static bool use_init = FALSE; /* invoked as init */
+
+static void
+exit_error(void)
+{
+ restore_tty_settings();
+ (void) fprintf(my_file, "\n");
+ fflush(my_file);
+ ExitProgram(EXIT_FAILURE);
+ /* NOTREACHED */
+}
+
+static void
+failed(const char *msg)
+{
+ char temp[BUFSIZ];
+
+ _nc_STRCPY(temp, _nc_progname, sizeof(temp));
+ _nc_STRCAT(temp, ": ", sizeof(temp));
+ perror(strncat(temp, msg, sizeof(temp) - strlen(temp) - 2));
+ exit_error();
+ /* NOTREACHED */
+}
+
+static bool
+get_tty_settings(int fd, TTY * tty_settings)
+{
+ bool success = TRUE;
+ my_fd = fd;
+ if (fd < 0 || GET_TTY(my_fd, tty_settings) < 0) {
+ success = FALSE;
+ }
+ return success;
+}
+
+static bool
+cat_file(char *file)
+{
+ FILE *fp;
+ size_t nr;
+ char buf[BUFSIZ];
+ bool sent = FALSE;
+
+ if (file != 0) {
+ if ((fp = fopen(file, "r")) == 0)
+ failed(file);
+
+ while ((nr = fread(buf, sizeof(char), sizeof(buf), fp)) != 0) {
+ if (fwrite(buf, sizeof(char), nr, my_file) != nr) {
+ failed(file);
+ }
+ sent = TRUE;
+ }
+ fclose(fp);
+ }
+ return sent;
+}
+
+static int
+out_char(int c)
+{
+ return putc(c, my_file);
+}
+
+/**************************************************************************
+ * Mode-setting logic
+ **************************************************************************/
+
+/* some BSD systems have these built in, some systems are missing
+ * one or more definitions. The safest solution is to override unless the
+ * commonly-altered ones are defined.
+ */
+#if !(defined(CERASE) && defined(CINTR) && defined(CKILL) && defined(CQUIT))
+#undef CEOF
+#undef CERASE
+#undef CINTR
+#undef CKILL
+#undef CLNEXT
+#undef CRPRNT
+#undef CQUIT
+#undef CSTART
+#undef CSTOP
+#undef CSUSP
+#endif
+
+/* control-character defaults */
+#ifndef CEOF
+#define CEOF CTRL('D')
+#endif
+#ifndef CERASE
+#define CERASE CTRL('H')
+#endif
+#ifndef CINTR
+#define CINTR 127 /* ^? */
+#endif
+#ifndef CKILL
+#define CKILL CTRL('U')
+#endif
+#ifndef CLNEXT
+#define CLNEXT CTRL('v')
+#endif
+#ifndef CRPRNT
+#define CRPRNT CTRL('r')
+#endif
+#ifndef CQUIT
+#define CQUIT CTRL('\\')
+#endif
+#ifndef CSTART
+#define CSTART CTRL('Q')
+#endif
+#ifndef CSTOP
+#define CSTOP CTRL('S')
+#endif
+#ifndef CSUSP
+#define CSUSP CTRL('Z')
+#endif
+
+#if defined(_POSIX_VDISABLE)
+#define DISABLED(val) (((_POSIX_VDISABLE != -1) \
+ && ((val) == _POSIX_VDISABLE)) \
+ || ((val) <= 0))
+#else
+#define DISABLED(val) ((int)(val) <= 0)
+#endif
+
+#define CHK(val, dft) (unsigned char) (DISABLED(val) ? dft : val)
+
+#define reset_char(item, value) \
+ tty_settings->c_cc[item] = CHK(tty_settings->c_cc[item], value)
+
+/*
+ * Reset the terminal mode bits to a sensible state. Very useful after
+ * a child program dies in raw mode.
+ */
+void
+reset_tty_settings(TTY * tty_settings)
+{
+#ifdef TERMIOS
+ tcgetattr(my_fd, tty_settings);
+#else
+ stty(my_fd, tty_settings);
+#endif
+
+#ifdef TERMIOS
+#if defined(VDISCARD) && defined(CDISCARD)
+ reset_char(VDISCARD, CDISCARD);
+#endif
+ reset_char(VEOF, CEOF);
+ reset_char(VERASE, CERASE);
+#if defined(VFLUSH) && defined(CFLUSH)
+ reset_char(VFLUSH, CFLUSH);
+#endif
+ reset_char(VINTR, CINTR);
+ reset_char(VKILL, CKILL);
+#if defined(VLNEXT) && defined(CLNEXT)
+ reset_char(VLNEXT, CLNEXT);
+#endif
+ reset_char(VQUIT, CQUIT);
+#if defined(VREPRINT) && defined(CRPRNT)
+ reset_char(VREPRINT, CRPRNT);
+#endif
+#if defined(VSTART) && defined(CSTART)
+ reset_char(VSTART, CSTART);
+#endif
+#if defined(VSTOP) && defined(CSTOP)
+ reset_char(VSTOP, CSTOP);
+#endif
+#if defined(VSUSP) && defined(CSUSP)
+ reset_char(VSUSP, CSUSP);
+#endif
+#if defined(VWERASE) && defined(CWERASE)
+ reset_char(VWERASE, CWERASE);
+#endif
+
+ tty_settings->c_iflag &= ~((unsigned) (IGNBRK
+ | PARMRK
+ | INPCK
+ | ISTRIP
+ | INLCR
+ | IGNCR
+#ifdef IUCLC
+ | IUCLC
+#endif
+#ifdef IXANY
+ | IXANY
+#endif
+ | IXOFF));
+
+ tty_settings->c_iflag |= (BRKINT
+ | IGNPAR
+ | ICRNL
+ | IXON
+#ifdef IMAXBEL
+ | IMAXBEL
+#endif
+ );
+
+ tty_settings->c_oflag &= ~((unsigned) (0
+#ifdef OLCUC
+ | OLCUC
+#endif
+#ifdef OCRNL
+ | OCRNL
+#endif
+#ifdef ONOCR
+ | ONOCR
+#endif
+#ifdef ONLRET
+ | ONLRET
+#endif
+#ifdef OFILL
+ | OFILL
+#endif
+#ifdef OFDEL
+ | OFDEL
+#endif
+#ifdef NLDLY
+ | NLDLY
+#endif
+#ifdef CRDLY
+ | CRDLY
+#endif
+#ifdef TABDLY
+ | TABDLY
+#endif
+#ifdef BSDLY
+ | BSDLY
+#endif
+#ifdef VTDLY
+ | VTDLY
+#endif
+#ifdef FFDLY
+ | FFDLY
+#endif
+ ));
+
+ tty_settings->c_oflag |= (OPOST
+#ifdef ONLCR
+ | ONLCR
+#endif
+ );
+
+ tty_settings->c_cflag &= ~((unsigned) (CSIZE
+ | CSTOPB
+ | PARENB
+ | PARODD
+ | CLOCAL));
+ tty_settings->c_cflag |= (CS8 | CREAD);
+ tty_settings->c_lflag &= ~((unsigned) (ECHONL
+ | NOFLSH
+#ifdef TOSTOP
+ | TOSTOP
+#endif
+#ifdef ECHOPTR
+ | ECHOPRT
+#endif
+#ifdef XCASE
+ | XCASE
+#endif
+ ));
+
+ tty_settings->c_lflag |= (ISIG
+ | ICANON
+ | ECHO
+ | ECHOE
+ | ECHOK
+#ifdef ECHOCTL
+ | ECHOCTL
+#endif
+#ifdef ECHOKE
+ | ECHOKE
+#endif
+ );
+#endif
+
+ SET_TTY(my_fd, tty_settings);
+}
+
+/*
+ * Returns a "good" value for the erase character. This is loosely based on
+ * the BSD4.4 logic.
+ */
+static int
+default_erase(void)
+{
+ int result;
+
+ if (over_strike
+ && key_backspace != 0
+ && strlen(key_backspace) == 1) {
+ result = key_backspace[0];
+ } else {
+ result = CERASE;
+ }
+
+ return result;
+}
+
+/*
+ * Update the values of the erase, interrupt, and kill characters in the TTY
+ * parameter.
+ *
+ * SVr4 tset (e.g., Solaris 2.5) only modifies the intr, quit or erase
+ * characters if they're unset, or if we specify them as options. This differs
+ * from BSD 4.4 tset, which always sets erase.
+ */
+void
+set_control_chars(TTY * tty_settings, int my_erase, int my_intr, int my_kill)
+{
+ if (DISABLED(tty_settings->c_cc[VERASE]) || my_erase >= 0) {
+ tty_settings->c_cc[VERASE] = UChar((my_erase >= 0)
+ ? my_erase
+ : default_erase());
+ }
+
+ if (DISABLED(tty_settings->c_cc[VINTR]) || my_intr >= 0) {
+ tty_settings->c_cc[VINTR] = UChar((my_intr >= 0)
+ ? my_intr
+ : CINTR);
+ }
+
+ if (DISABLED(tty_settings->c_cc[VKILL]) || my_kill >= 0) {
+ tty_settings->c_cc[VKILL] = UChar((my_kill >= 0)
+ ? my_kill
+ : CKILL);
+ }
+}
+
+/*
+ * Set up various conversions in the TTY parameter, including parity, tabs,
+ * returns, echo, and case, according to the termcap entry.
+ */
+void
+set_conversions(TTY * tty_settings)
+{
+#ifdef ONLCR
+ tty_settings->c_oflag |= ONLCR;
+#endif
+ tty_settings->c_iflag |= ICRNL;
+ tty_settings->c_lflag |= ECHO;
+#ifdef OXTABS
+ tty_settings->c_oflag |= OXTABS;
+#endif /* OXTABS */
+
+ /* test used to be tgetflag("NL") */
+ if (newline != (char *) 0 && newline[0] == '\n' && !newline[1]) {
+ /* Newline, not linefeed. */
+#ifdef ONLCR
+ tty_settings->c_oflag &= ~((unsigned) ONLCR);
+#endif
+ tty_settings->c_iflag &= ~((unsigned) ICRNL);
+ }
+#ifdef OXTABS
+ /* test used to be tgetflag("pt") */
+ if (has_hardware_tabs) /* Print tabs. */
+ tty_settings->c_oflag &= ~OXTABS;
+#endif /* OXTABS */
+ tty_settings->c_lflag |= (ECHOE | ECHOK);
+}
+
+/*
+ * Set the hardware tabs on the terminal, using the 'ct' (clear all tabs),
+ * 'st' (set one tab) and 'ch' (horizontal cursor addressing) capabilities.
+ * This is done before 'if' and 'is', so they can recover in case of error.
+ *
+ * Return TRUE if we set any tab stops, FALSE if not.
+ */
+static bool
+reset_tabstops(int wide)
+{
+ if ((init_tabs != 8) && (set_tab && clear_all_tabs)) {
+ int c;
+
+ (void) putc('\r', my_file); /* Force to left margin. */
+ tputs(clear_all_tabs, 0, out_char);
+
+ for (c = 8; c < wide; c += 8) {
+ /* Get to the right column. In BSD tset, this used to try a bunch
+ * of half-clever things with cup and hpa, for an average saving of
+ * somewhat less than two character times per tab stop, less than
+ * .01 sec at 2400cps. We lost all this cruft because it seemed to
+ * be introducing some odd bugs.
+ * -----------12345678----------- */
+ (void) fputs(" ", my_file);
+ tputs(set_tab, 0, out_char);
+ }
+ putc('\r', my_file);
+ return (TRUE);
+ }
+ return (FALSE);
+}
+
+static bool
+sent_string(const char *s)
+{
+ bool sent = FALSE;
+ if (s != 0) {
+ tputs(s, 0, out_char);
+ sent = TRUE;
+ }
+ return sent;
+}
+
+#define PUTCHAR(c) fputc(c, my_file)
+
+/* Output startup string. */
+bool
+send_init_strings(TTY * old_settings)
+{
+ int i;
+ bool need_flush = FALSE;
+
+#ifdef TAB3
+ if (old_settings != 0 &&
+ old_settings->c_oflag & (TAB3 | ONLCR | OCRNL | ONLRET)) {
+ old_settings->c_oflag &= (TAB3 | ONLCR | OCRNL | ONLRET);
+ SET_TTY(my_fd, old_settings);
+ }
+#endif
+ if (use_reset || use_init) {
+ if (init_prog != 0) {
+ IGNORE_RC(system(init_prog));
+ }
+
+ need_flush |= sent_string((use_reset && (reset_1string != 0))
+ ? reset_1string
+ : init_1string);
+
+ need_flush |= sent_string((use_reset && (reset_2string != 0))
+ ? reset_2string
+ : init_2string);
+
+ if (set_lr_margin != 0) {
+ need_flush |= sent_string(TPARM_2(set_lr_margin, 0,
+ columns - 1));
+ } else if (set_left_margin_parm != 0
+ && set_right_margin_parm != 0) {
+ need_flush |= sent_string(TPARM_1(set_left_margin_parm, 0));
+ need_flush |= sent_string(TPARM_1(set_right_margin_parm,
+ columns - 1));
+ } else if (clear_margins != 0
+ && set_left_margin != 0
+ && set_right_margin != 0) {
+ need_flush |= sent_string(clear_margins);
+ if (carriage_return != 0) {
+ need_flush |= sent_string(carriage_return);
+ } else {
+ PUTCHAR('\r');
+ }
+ need_flush |= sent_string(set_left_margin);
+ if (parm_right_cursor) {
+ need_flush |= sent_string(TPARM_1(parm_right_cursor,
+ columns - 1));
+ } else {
+ for (i = 0; i < columns - 1; i++) {
+ PUTCHAR(' ');
+ }
+ }
+ need_flush |= sent_string(set_right_margin);
+ if (carriage_return != 0) {
+ need_flush |= sent_string(carriage_return);
+ } else {
+ PUTCHAR('\r');
+ }
+ }
+
+ need_flush |= reset_tabstops(columns);
+
+ need_flush |= cat_file((use_reset && reset_file) ? reset_file : init_file);
+
+ need_flush |= sent_string((use_reset && (reset_3string != 0))
+ ? reset_3string
+ : init_3string);
+ }
+
+ return need_flush;
+}
+
+/*
+ * Tell the user if a control key has been changed from the default value.
+ */
+static void
+show_tty_change(TTY * old_settings,
+ TTY * new_settings,
+ const char *name,
+ int which,
+ unsigned def)
+{
+ unsigned older, newer;
+ char *p;
+
+ newer = new_settings->c_cc[which];
+ older = old_settings->c_cc[which];
+
+ if (older == newer && older == def)
+ return;
+
+ (void) fprintf(stderr, "%s %s ", name, older == newer ? "is" : "set to");
+
+ if (DISABLED(newer)) {
+ (void) fprintf(stderr, "undef.\n");
+ /*
+ * Check 'delete' before 'backspace', since the key_backspace value
+ * is ambiguous.
+ */
+ } else if (newer == 0177) {
+ (void) fprintf(stderr, "delete.\n");
+ } else if ((p = key_backspace) != 0
+ && newer == (unsigned char) p[0]
+ && p[1] == '\0') {
+ (void) fprintf(stderr, "backspace.\n");
+ } else if (newer < 040) {
+ newer ^= 0100;
+ (void) fprintf(stderr, "control-%c (^%c).\n", UChar(newer), UChar(newer));
+ } else
+ (void) fprintf(stderr, "%c.\n", UChar(newer));
+}
+
+/**************************************************************************
+ * Miscellaneous.
+ **************************************************************************/
+
+void
+reset_start(FILE *fp, bool is_reset, bool is_init)
+{
+ my_file = fp;
+ use_reset = is_reset;
+ use_init = is_init;
+}
+
+void
+reset_flush(void)
+{
+ if (my_file != 0)
+ fflush(my_file);
+}
+
+void
+print_tty_chars(TTY * old_settings, TTY * new_settings)
+{
+ show_tty_change(old_settings, new_settings, "Erase", VERASE, CERASE);
+ show_tty_change(old_settings, new_settings, "Kill", VKILL, CKILL);
+ show_tty_change(old_settings, new_settings, "Interrupt", VINTR, CINTR);
+}
+
+/*
+ * Open a file descriptor on the current terminal, to obtain its settings.
+ * stderr is less likely to be redirected than stdout; try that first.
+ */
+int
+save_tty_settings(TTY * tty_settings)
+{
+ if (!get_tty_settings(STDERR_FILENO, tty_settings) &&
+ !get_tty_settings(STDOUT_FILENO, tty_settings) &&
+ !get_tty_settings(STDIN_FILENO, tty_settings) &&
+ !get_tty_settings(open("/dev/tty", O_RDWR), tty_settings)) {
+ failed("terminal attributes");
+ }
+ can_restore = TRUE;
+ original_settings = *tty_settings;
+ return my_fd;
+}
+
+void
+restore_tty_settings(void)
+{
+ if (can_restore)
+ SET_TTY(my_fd, &original_settings);
+}
+
+/* Set the modes if they've changed. */
+void
+update_tty_settings(TTY * old_settings, TTY * new_settings)
+{
+ if (memcmp(new_settings, old_settings, sizeof(TTY))) {
+ SET_TTY(my_fd, new_settings);
+ }
+}
+
+#if HAVE_SIZECHANGE
+/* Set window size if not set already */
+void
+set_window_size(int fd, int high, int wide)
+{
+ STRUCT_WINSIZE win;
+ (void) ioctl(fd, IOCTL_GET_WINSIZE, &win);
+ if (WINSIZE_ROWS(win) == 0 &&
+ WINSIZE_COLS(win) == 0 &&
+ high > 0 && wide > 0) {
+ WINSIZE_ROWS(win) = (unsigned short) high;
+ WINSIZE_COLS(win) = (unsigned short) wide;
+ (void) ioctl(fd, IOCTL_SET_WINSIZE, &win);
+ }
+}
+#endif