Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions lib/tty/tty-ncurses.c
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,15 @@ tty_init (gboolean mouse_enable, gboolean is_xterm)
ESCDELAY = 200;
#endif

tcgetattr (STDIN_FILENO, &mode);
// use Ctrl-g to generate SIGINT
mode.c_cc[VINTR] = CTRL ('g'); // ^g
// disable SIGQUIT to allow use Ctrl-\ key
mode.c_cc[VQUIT] = NULL_VALUE;
tcsetattr (STDIN_FILENO, TCSANOW, &mode);
// don't apply an uninitialized structure if the terminal modes cannot be read
if (tcgetattr (STDIN_FILENO, &mode) == 0)
{
// use Ctrl-g to generate SIGINT
mode.c_cc[VINTR] = CTRL ('g'); // ^g
// disable SIGQUIT to allow use Ctrl-\ key
mode.c_cc[VQUIT] = NULL_VALUE;
tcsetattr (STDIN_FILENO, TCSANOW, &mode);
}

// curses remembers the "in-program" modes after this call
def_prog_mode ();
Expand Down
15 changes: 13 additions & 2 deletions lib/tty/tty-slang.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

#include <config.h>

#include <errno.h>
#include <limits.h> // MB_LEN_MAX
#include <stdio.h>
#include <stdlib.h>
Expand Down Expand Up @@ -249,9 +250,19 @@ tty_init (gboolean mouse_enable, gboolean is_xterm)
exit (EXIT_FAILURE);
}

tcgetattr (fileno (stdin), &boot_mode);
// the terminal modes are restored from boot_mode before running external commands
if (tcgetattr (fileno (stdin), &boot_mode) != 0)
{
fprintf (stderr, _ ("Cannot get terminal settings: %s\n"), unix_error_string (errno));
exit (EXIT_FAILURE);
}

// 255 = ignore abort char; XCTRL('g') for abort char = ^g
SLang_init_tty (XCTRL ('g'), 1, 0);
if (SLang_init_tty (XCTRL ('g'), 1, 0) != 0)
{
fprintf (stderr, _ ("Cannot initialize the terminal: %s\n"), unix_error_string (errno));
exit (EXIT_FAILURE);
}

if (mc_global.tty.ugly_line_drawing)
SLtt_Has_Alt_Charset = 0;
Expand Down
40 changes: 40 additions & 0 deletions lib/tty/tty.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <config.h>

#include <errno.h>
#include <fcntl.h> // open()
#include <signal.h>
#include <stdarg.h>
#include <stdlib.h>
Expand Down Expand Up @@ -143,6 +144,45 @@ tty_check_xterm_compat (const gboolean force_xterm)

return FALSE;
}

/* --------------------------------------------------------------------------------------------- */
/**
* Make sure that the standard input is a terminal.
*
* MC does not read data from stdin, but it reads the keyboard from there: the ncurses backend
* and the key reader use stdin, and both backends save the initial terminal modes from it.
* If stdin is redirected (e.g. "mc < /dev/null" or "find ... | xargs mcedit"), attach it to the
* controlling terminal, like "xargs -o" does. Like S-Lang, fall back to stderr if there is
* no controlling terminal.
*
* @return TRUE if stdin is a terminal now, FALSE otherwise
*/
gboolean
tty_stdin_to_terminal (void)
{
int fd;

if (isatty (STDIN_FILENO))
return TRUE;

fd = open ("/dev/tty", O_RDWR);
if (fd == -1 && isatty (STDERR_FILENO))
fd = dup (STDERR_FILENO);
if (fd == -1)
return FALSE;

if (fd != STDIN_FILENO)
{
const int ret = dup2 (fd, STDIN_FILENO);

close (fd);
if (ret == -1)
return FALSE;
}

return isatty (STDIN_FILENO) != 0;
}

/* --------------------------------------------------------------------------------------------- */

extern void
Expand Down
1 change: 1 addition & 0 deletions lib/tty/tty.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ extern void tty_beep (void);
/* {{{ Input }}} */

extern gboolean tty_check_xterm_compat (gboolean force_xterm);
extern gboolean tty_stdin_to_terminal (void);
extern void tty_init (gboolean mouse_enable, gboolean is_xterm);
extern void tty_shutdown (void);

Expand Down
9 changes: 9 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,15 @@ main (int argc, char *argv[])
goto startup_exit_ok;
}

if (!tty_stdin_to_terminal ())
{
mc_propagate_error (&mcerror, 0, "%s",
_ ("Standard input is not a terminal and there is no controlling "
"terminal to read the keyboard from"));
mc_event_deinit (NULL);
goto startup_exit_falure;
}

if (!events_init (&mcerror))
goto startup_exit_falure;

Expand Down
174 changes: 174 additions & 0 deletions tests/lib/tty.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@

#include "tests/mctest.h"

#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#include <termios.h>

#include "lib/strutil.h"
#include "lib/util.h"
Expand Down Expand Up @@ -109,6 +117,168 @@ END_TEST

/* --------------------------------------------------------------------------------------------- */

/* Open a new pseudo-terminal and return the descriptor of its slave side. If @controlling is
TRUE, the calling process must be a session leader, and the slave becomes its controlling
terminal. The master descriptor is stored in @master. */
static int
open_pty_slave (int *master, gboolean controlling)
{
int slave;

*master = posix_openpt (O_RDWR | O_NOCTTY);
if (*master == -1 || grantpt (*master) != 0 || unlockpt (*master) != 0)
return -1;

slave = open (ptsname (*master), O_RDWR | (controlling ? 0 : O_NOCTTY));
#ifdef TIOCSCTTY
if (slave != -1 && controlling && ioctl (slave, TIOCSCTTY, 0) == -1)
return -1;
#endif

return slave;
}

/* --------------------------------------------------------------------------------------------- */

static gboolean
same_file (int fd1, int fd2)
{
struct stat st1, st2;

return fstat (fd1, &st1) == 0 && fstat (fd2, &st2) == 0 && st1.st_dev == st2.st_dev
&& st1.st_ino == st2.st_ino;
}

/* --------------------------------------------------------------------------------------------- */

/* Check that a key typed on the terminal with the @master side can be read from stdin */
static gboolean
key_reaches_stdin (int master)
{
char buf[8] = "";

alarm (5); // don't hang the test if stdin is not that terminal
return write (master, "k\n", 2) == 2 && read (STDIN_FILENO, buf, sizeof (buf)) == 2
&& buf[0] == 'k';
}

/* --------------------------------------------------------------------------------------------- */

/* Session setups for tty_stdin_to_terminal() tests */
typedef enum
{
STDIN_IS_TTY, // stdin is a terminal, no controlling terminal
STDIN_REDIRECTED, // stdin is /dev/null, there is a controlling terminal
STDIN_REDIRECTED_NO_CTTY_STDERR_TTY, // stdin is /dev/null, no ctty, stderr is a terminal
NO_TERMINAL_AT_ALL, // stdin and stderr are /dev/null, no controlling terminal
} stdin_setup_t;

/* Run tty_stdin_to_terminal() in a new session prepared according to @setup and return
0 if the result is as expected, a positive code of the failed check otherwise. */
static int
run_stdin_to_terminal (stdin_setup_t setup)
{
pid_t pid;
int status;

pid = fork ();
if (pid == 0)
{
int master, slave, devnull, saved_stdin;
gboolean ret;

if (setsid () == -1)
_exit (10);

slave = open_pty_slave (&master, setup == STDIN_REDIRECTED);
devnull = open ("/dev/null", O_RDWR);
if (slave == -1 || devnull == -1)
_exit (11);

dup2 (setup == STDIN_IS_TTY ? slave : devnull, STDIN_FILENO);
dup2 (setup == STDIN_REDIRECTED_NO_CTTY_STDERR_TTY ? slave : devnull, STDERR_FILENO);
saved_stdin = dup (STDIN_FILENO);

ret = tty_stdin_to_terminal ();

if (setup == NO_TERMINAL_AT_ALL)
// must fail and leave stdin untouched
_exit (ret ? 1 : (same_file (STDIN_FILENO, saved_stdin) ? 0 : 2));

if (!ret)
_exit (3);
if (!isatty (STDIN_FILENO))
_exit (4);
// stdin must be the pty: the original one, /dev/tty or stderr
if (!key_reaches_stdin (master))
_exit (5);
_exit (0);
}

if (pid == -1 || waitpid (pid, &status, 0) != pid || !WIFEXITED (status))
return 100;

return WEXITSTATUS (status);
}

/* --------------------------------------------------------------------------------------------- */

START_TEST (test_tty_stdin_to_terminal_stdin_is_tty)
{
// given: stdin is a terminal

// when
const int actual_result = run_stdin_to_terminal (STDIN_IS_TTY);

// then: stdin is left as is
ck_assert_int_eq (actual_result, 0);
}
END_TEST

/* --------------------------------------------------------------------------------------------- */

START_TEST (test_tty_stdin_to_terminal_redirected)
{
// given: "mc < /dev/null" run from a terminal

// when
const int actual_result = run_stdin_to_terminal (STDIN_REDIRECTED);

// then: stdin is the controlling terminal
ck_assert_int_eq (actual_result, 0);
}
END_TEST

/* --------------------------------------------------------------------------------------------- */

START_TEST (test_tty_stdin_to_terminal_stderr_fallback)
{
// given: stdin is redirected, no controlling terminal, stderr is a terminal

// when
const int actual_result = run_stdin_to_terminal (STDIN_REDIRECTED_NO_CTTY_STDERR_TTY);

// then: stdin is the terminal of stderr
ck_assert_int_eq (actual_result, 0);
}
END_TEST

/* --------------------------------------------------------------------------------------------- */

START_TEST (test_tty_stdin_to_terminal_no_terminal)
{
// given: no terminal at all

// when
const int actual_result = run_stdin_to_terminal (NO_TERMINAL_AT_ALL);

// then: failure is reported and stdin is left as is
ck_assert_int_eq (actual_result, 0);
}
END_TEST

/* --------------------------------------------------------------------------------------------- */

int
main (void)
{
Expand All @@ -121,6 +291,10 @@ main (void)
tcase_add_test (tc_core, test_tty_check_term_set_empty);
tcase_add_test (tc_core, test_tty_check_term_non_xterm);
tcase_add_test (tc_core, test_tty_check_term_xterm_like);
tcase_add_test (tc_core, test_tty_stdin_to_terminal_stdin_is_tty);
tcase_add_test (tc_core, test_tty_stdin_to_terminal_redirected);
tcase_add_test (tc_core, test_tty_stdin_to_terminal_stderr_fallback);
tcase_add_test (tc_core, test_tty_stdin_to_terminal_no_terminal);
// ***********************************

return mctest_run_all (tc_core);
Expand Down
Loading