/****************************************************************************
* Copyright 2019,2020 Thomas E. Dickey *
* Copyright 2016,2017 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 <tty_settings.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.19 2020/02/02 23:34:34 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 FILE *my_file;
static bool use_reset = FALSE; /* invoked as reset */
static bool use_init = FALSE; /* invoked as init */
static void
failed(const char *msg)
{
int code = errno;
(void) fprintf(stderr, "%s: %s: %s\n", _nc_progname, msg, strerror(code));
restore_tty_settings();
(void) fprintf(my_file, "\n");
fflush(my_file);
ExitProgram(ErrSystem(code));
/* NOTREACHED */
}
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(int fd, TTY * tty_settings)
{
GET_TTY(fd, tty_settings);
#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(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
&& VALID_STRING(key_backspace)
&& 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 (VALID_STRING(newline) && 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 (VALID_STRING(set_tab) && VALID_STRING(clear_all_tabs))
tty_settings->c_oflag &= ~OXTABS;
#endif /* OXTABS */
tty_settings->c_lflag |= (ECHOE | ECHOK);
}
static bool
sent_string(const char *s)
{
bool sent = FALSE;
if (VALID_STRING(s)) {
tputs(s, 0, out_char);
sent = TRUE;
}
return sent;
}
static bool
to_left_margin(void)
{
if (VALID_STRING(carriage_return)) {
sent_string(carriage_return);
} else {
out_char('\r');
}
return TRUE;
}
/*
* 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)
&& VALID_NUMERIC(init_tabs)
&& VALID_STRING(set_tab)
&& VALID_STRING(clear_all_tabs)) {
int c;
to_left_margin();
tputs(clear_all_tabs, 0, out_char);
if (init_tabs > 1) {
if (init_tabs > wide)
init_tabs = (short) wide;
for (c = init_tabs; c < wide; c += init_tabs) {
fprintf(my_file, "%*s", init_tabs, " ");
tputs(set_tab, 0, out_char);
}
to_left_margin();
}
return (TRUE);
}
return (FALSE);
}
/* Output startup string. */
bool
send_init_strings(int fd GCC_UNUSED, TTY * old_settings)
{
int i;
bool need_flush = FALSE;
(void) old_settings;
#ifdef TAB3
if (old_settings != 0 &&
old_settings->c_oflag & (TAB3 | ONLCR | OCRNL | ONLRET)) {
old_settings->c_oflag &= (TAB3 | ONLCR | OCRNL | ONLRET);
SET_TTY(fd, old_settings);
}
#endif
if (use_reset || use_init) {
if (VALID_STRING(init_prog)) {
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 (VALID_STRING(clear_margins)) {
need_flush |= sent_string(clear_margins);
} else
#if defined(set_lr_margin)
if (VALID_STRING(set_lr_margin)) {
need_flush |= sent_string(TPARM_2(set_lr_margin, 0,
columns - 1));
} else
#endif
#if defined(set_left_margin_parm) && defined(set_right_margin_parm)
if (VALID_STRING(set_left_margin_parm)
&& VALID_STRING(set_right_margin_parm)) {
need_flush |= sent_string(TPARM_1(set_left_margin_parm, 0));
need_flush |= sent_string(TPARM_1(set_right_margin_parm,
columns - 1));
} else
#endif
if (VALID_STRING(set_left_margin)
&& VALID_STRING(set_right_margin)) {
need_flush |= to_left_margin();
need_flush |= sent_string(set_left_margin);
if (VALID_STRING(parm_right_cursor)) {
need_flush |= sent_string(TPARM_1(parm_right_cursor,
columns - 1));
} else {
for (i = 0; i < columns - 1; i++) {
out_char(' ');
need_flush = TRUE;
}
}
need_flush |= sent_string(set_right_margin);
need_flush |= to_left_margin();
}
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);
}
#if HAVE_SIZECHANGE
/*
* Set window size if not set already, but update our copy of the values if the
* size was set.
*/
void
set_window_size(int fd, short *high, short *wide)
{
STRUCT_WINSIZE win;
(void) ioctl(fd, IOCTL_GET_WINSIZE, &win);
if (WINSIZE_ROWS(win) == 0 &&
WINSIZE_COLS(win) == 0) {
if (*high > 0 && *wide > 0) {
WINSIZE_ROWS(win) = (unsigned short) *high;
WINSIZE_COLS(win) = (unsigned short) *wide;
(void) ioctl(fd, IOCTL_SET_WINSIZE, &win);
}
} else if (WINSIZE_ROWS(win) > 0 &&
WINSIZE_COLS(win) > 0) {
*high = (short) WINSIZE_ROWS(win);
*wide = (short) WINSIZE_COLS(win);
}
}
#endif