(Disclosure: I performed a search for terminal, terminal settings, tcsetattr in the bash-bug mailing list, without finding a discussion. My apologies if this is a known issue or was already discussed.)
Hi, according to POSIX Part A, Base Definitions (line 726-728, pg 20, Part A: Base Definitions-IEEE and The Open Group.), a job control shell has the following responsibility: "When a foreground (not background) job stops, the shell must sample and remember the current terminal settings so that it can restore them later when it continues the stopped job in the foreground (via the tcgetattr( ) and tcsetattr( ) functions)." Does bash implement this functionality? If implemented correctly, I would expect the program appended below to not fail its assertions. However, it fails - tested with bash 4.4 on CentOS 8 using a pty created by sshd. I also looked at the bash 5.0 source code and could not spot where bash implements this. I was curious how other shells handle this. It also doesn't work with the version of zsh (5.5.1) installed on CentOS 8. It "works" with the version of ksh installed on CentOS 8 (93u+), but then ksh fails to restore the terminal state afterwards (leaves it in the state the program placed it when it exits). I first noticed this when students in my class's shell project failed to properly implement terminal handling, but programs such vi/vim still continued to work even when stopped and later placed back in the foreground. I discovered that these programs implement a SIGCONT handler where they restore their terminal state when continued, but I wrote this off as an attempt to be robust in the presence of buggy shells. I had not checked the bash code base, however. Finally, I got around to adding a test (below) and to my surprise discovered that bash fails this test as well. Am I reading the right version of the POSIX standard? Could anyone shed some light on this? - Godmar Code follows: // ts_test.c /* Test that terminal state is properly restored when a process is stopped and restored. */ #include <stdio.h> #include <termios.h> #include <unistd.h> #include <fcntl.h> #include <assert.h> #include <signal.h> #define CTRL_D 4 #define CTRL_E 5 int main() { int terminal_fd = open(ctermid(NULL), O_RDWR); assert (terminal_fd != -1); // Step 1. make a change to the terminal state: // change VEOF from Ctrl-D to Ctrl-E struct termios saved_tty_state; int rc = tcgetattr(terminal_fd, &saved_tty_state); assert (rc == 0); assert (saved_tty_state.c_cc[VEOF] == CTRL_D); // ^D saved_tty_state.c_cc[VEOF] = CTRL_E; // ^E rc = tcsetattr(terminal_fd, TCSANOW, &saved_tty_state); assert (rc == 0); // Step 2. Suspend and let user resume printf("This job should now stop, please run 'fg' to continue it\n"); raise(SIGTSTP); printf("Job now continuing...\n"); // Step 3. // Expect that job control shell saved the terminal state rc = tcgetattr(terminal_fd, &saved_tty_state); assert (rc == 0); if (saved_tty_state.c_cc[VEOF] != CTRL_E) { printf("I expected a POSIX job control shell to preserve my terminal settings\n"); printf("VEOF was not saved, it is %d...\n", saved_tty_state.c_cc[VEOF]); } assert (saved_tty_state.c_cc[VEOF] == CTRL_E); // ^E }