Clear the terminal flag ECHOCTL before starting, and set it again at the end, similar to how TOSTOP is dealt with (#187).

This commit is contained in:
Andrew Wood
2026-04-26 20:01:22 +01:00
parent 57f0b91d16
commit 8b4fa8ade7
3 changed files with 134 additions and 1 deletions
+2
View File
@@ -67,6 +67,7 @@ extern "C" {
struct pvipccursorstate_s {
int y_topmost; /* terminal row of topmost "pv" instance */
bool tty_tostop_added; /* whether any instance had to set TOSTOP on the terminal */
bool tty_echoctl_cleared; /* whether any instance had to clear ECHOCTL on the terminal */
};
/*
@@ -272,6 +273,7 @@ struct pvstate_s {
volatile sig_atomic_t terminal_resized; /* whether terminal size needs re-reading */
volatile sig_atomic_t trigger_exit; /* whether an immediate abort is required */
volatile sig_atomic_t clear_tty_tostop_on_exit; /* whether to clear tty TOSTOP on exit */
volatile sig_atomic_t set_tty_echoctl_on_exit; /* whether to set tty ECHOCTL on exit */
volatile sig_atomic_t suspend_stderr; /* whether writing to stderr is suspended */
volatile sig_atomic_t skip_next_sigcont; /* whether to ignore the next SIGCONT */
volatile sig_atomic_t pipe_closed; /* whether the output pipe was closed */
+27
View File
@@ -438,6 +438,18 @@ void pv_crs_init(pvcursorstate_t cursor, readonly_pvcontrol_t control, pvtransie
cursor->shared->tty_tostop_added = true;
}
#ifdef ECHOCTL
/*
* If the terminal ECHCTL attribute was already cleared by this
* process, set the flag in shared memory to let the other instances
* know.
*/
if ((!cursor->noipc) && (1 == flags->set_tty_echoctl_on_exit) && (NULL != cursor->shared)) {
debug("%s", "propagating local set_tty_echoctl_on_exit true value to shared tty_echoctl_cleared flag");
cursor->shared->tty_echoctl_cleared = true;
}
#endif
/*
* If IPC is not being used, the current Y needs to be determined.
* If IPC is being used, then the pv_crs_ipcinit() function takes
@@ -689,6 +701,21 @@ void pv_crs_fini(pvcursorstate_t cursor, readonly_pvcontrol_t control, pvtransie
}
}
#ifdef ECHOCTL
/*
* If any other "pv -c" instances have cleared the terminal ECHOCTL
* attribute, set the local flag so pv_sig_fini() will know about
* it.
*/
if ((!cursor->noipc) && (NULL != cursor->shared) && cursor->shared->tty_echoctl_cleared) {
if (0 == flags->set_tty_echoctl_on_exit) {
debug("%s",
"propagating shared tty_echoctl_cleared true value to local set_tty_echoctl_on_exit flag");
flags->set_tty_echoctl_on_exit = 1;
}
}
#endif /* ECHOCTL */
pv_crs_ipccount(cursor);
if (NULL != cursor->shared) {
(void) shmdt(cursor->shared);
+105 -1
View File
@@ -77,6 +77,61 @@ static void pv_sig_ensure_tty_tostop()
}
}
#ifdef ECHOCTL
/*
* Check whether the terminal attribute ECHOCTL is set. If it is, clear it,
* and record that fact by setting "set_tty_echoctl_on_exit" to true, so
* that pv_sig_fini() knows to clear it again.
*
* In "-c" mode with IPC, that flag is propagated to other PV instances via
* the shared "tty_echoctl_cleared" flag, so those instances can set their
* own on-exit flag, meaning that if any of the PV instances clear it, the
* last one to exit will set it.
*/
static void pv_sig_ensure_tty_noechoctl()
{
struct termios terminal_attributes;
if (NULL == pv_sig_state)
return;
/* Can't look at terminal flags if backgrounded. */
if (1 == pv_sig_state->flags.suspend_stderr)
return;
if (0 != tcgetattr(STDERR_FILENO, &terminal_attributes)) {
debug("%s: %s", "failed to read terminal attributes", strerror(errno));
return;
}
/* Can't set terminal flags if backgrounded. */
if (1 == pv_sig_state->flags.suspend_stderr)
return;
if (0 != (terminal_attributes.c_lflag & ECHOCTL)) {
terminal_attributes.c_lflag -= ECHOCTL;
if (0 == tcsetattr(STDERR_FILENO, TCSANOW, &terminal_attributes)) {
pv_sig_state->flags.set_tty_echoctl_on_exit = 1;
debug("%s", "cleared terminal ECHOCTL attribute");
#if HAVE_IPC
/*
* In "-c" mode with IPC, make all "pv -c" instances
* aware that ECHOCTL was cleared, so the last one
* can set it on exit.
*/
if (pv_sig_state->control.cursor && (NULL != pv_sig_state->cursor.shared)
&& (!pv_sig_state->cursor.noipc)) {
pv_sig_state->cursor.shared->tty_echoctl_cleared = true;
}
#endif
} else {
debug("%s: %s", "failed to clear terminal ECHOCTL attribute", strerror(errno));
}
}
}
#endif /* ECHOCTL */
/*
* Handle SIGTTOU (tty output for background process) by setting the flag to
* suspend writes to stderr, to stop the terminal being interfered with when
@@ -475,6 +530,11 @@ void pv_sig_init(pvstate_t state)
*/
pv_sig_ensure_tty_tostop();
#ifdef ECHOCTL
/* Clear the ECHOCTL terminal attribute if it's set. */
pv_sig_ensure_tty_noechoctl();
#endif
/*
* Handle SIGALRM by doing nothing, allowing alarms or interval
* timers to interrupt blocking writes (returning EINTR).
@@ -495,6 +555,7 @@ void pv_sig_init(pvstate_t state)
void pv_sig_fini( /*@unused@ */ __attribute__((unused)) pvstate_t state)
{
bool need_to_clear_tostop = false;
bool need_to_set_echoctl = false;
if (NULL == pv_sig_state)
return;
@@ -540,7 +601,7 @@ void pv_sig_fini( /*@unused@ */ __attribute__((unused)) pvstate_t state)
if (need_to_clear_tostop && pv_in_foreground()) {
struct termios terminal_attributes;
debug("%s", "about to to clear TOSTOP terminal attribute if it is set");
debug("%s", "about to clear TOSTOP terminal attribute if it is set");
if (0 != tcgetattr(STDERR_FILENO, &terminal_attributes)) {
debug("%s: %s", "tcgetattr", strerror(errno));
@@ -555,6 +616,49 @@ void pv_sig_fini( /*@unused@ */ __attribute__((unused)) pvstate_t state)
pv_sig_state->flags.clear_tty_tostop_on_exit = 0;
}
#ifdef ECHOCTL
need_to_set_echoctl = (1 == pv_sig_state->flags.set_tty_echoctl_on_exit) ? true : false;
if (pv_sig_state->control.cursor) {
#ifdef HAVE_IPC
/*
* Don't set ECHOCTL if other "pv -c" instances were still
* running when pv_crs_fini() ran.
*/
if (pv_sig_state->control.cursor && pv_sig_state->cursor.pvcount > 1) {
need_to_set_echoctl = false;
}
#else /* !HAVE_IPC */
/*
* Without IPC there's no way to tell whether other "pv -c"
* instances in the pipeline have finished, so set ECHOCTL
* anyway.
*/
#endif /* !HAVE_IPC */
}
debug("%s=%s", "need_to_set_echoctl", need_to_set_echoctl ? "true" : "false");
if (need_to_set_echoctl && pv_in_foreground()) {
struct termios terminal_attributes;
debug("%s", "about to set ECHOCTL terminal attribute if it is not set");
if (0 != tcgetattr(STDERR_FILENO, &terminal_attributes)) {
debug("%s: %s", "tcgetattr", strerror(errno));
} else if (0 == (terminal_attributes.c_lflag & ECHOCTL)) {
terminal_attributes.c_lflag |= ECHOCTL;
if (0 == tcsetattr(STDERR_FILENO, TCSANOW, &terminal_attributes)) {
debug("%s", "set TOSTOP terminal attribute");
} else {
debug("%s: %s", "failed to set TOSTOP terminal attribute", strerror(errno));
}
}
pv_sig_state->flags.clear_tty_tostop_on_exit = 0;
}
#endif /* ECHOCTL */
}