convert TMOUT input timeout handling to use native readline timeouts if readline is being used; change TMOUT SIGALRM processing not to run in a signal handler context to exit; fix for adding multiline commands containing |& to the history list; history -r should not change history_lines_this_session if reading from the history file

This commit is contained in:
Chet Ramey
2026-09-08 12:41:47 -04:00
parent 81ddb6474b
commit f26caaa178
13 changed files with 261 additions and 16 deletions
+44
View File
@@ -13316,3 +13316,47 @@ doc/bash.1,doc/bashref.texi,lib/readline/doc/hsuser.texi
maximum in the file to avoid partial history entries or history
entries without a timestamp (if the file has them)
From https://savannah.gnu.org/bugs/?68650
8/28
----
eval.c
- alrm_catcher: change to only be active if readline isn't being used;
all it does now is set a flag (input_timeout_seen)
- read_command: use rl_set_timeout and rl_timeout_event_hook to
implement the timeout specified by $TMOUT if readline is being used;
use SIGALRM only if readline is not being used (--nolineediting)
- alrm_handler: renamed alrm_catcher to this; add call to tcflush to
make sure tty input buffer is empty if we time out reading input
- input_timeout_hook: new function, used by both readline and non-
readline paths, just calls alrm_handler
shell.h
- extern declaration for input_timeout_seen
externs.h
- extern declaration for input_timeout_hook()
quit.h
- CHECK_INPUT_TIMEOUT: new signal macro, calls input_timeout_hook
if we have caught SIGALRM and set input_timeout_seen
parse.y
- yy_stream_get: add calls to CHECK_INPUT_TIMEOUT around call to
stream_getc
input.c
- stream_getc: if we get SIGALRM and set input_timeout_seen, reset
the local buffer variables and return EOF. Caller will handle
checking for input_timeout_seen and calling input_timeout_hook()
parse.y
- no_semi_successors: add BAR_AND to list of tokens that should not
be converted from newline to semicolon
Report from Alberto Millán <hambled@gmail.com>
8/31
----
builtins/history.def
- history_builtin: history -r should increase history_lines_this_session
only if *not* reading from $HISTFILE
+1
View File
@@ -1037,6 +1037,7 @@ tests/assoc16.sub f
tests/assoc17.sub f
tests/assoc18.sub f
tests/assoc19.sub f
tests/assoc20.sub f
tests/attr.tests f
tests/attr.right f
tests/attr1.sub f
+4 -2
View File
@@ -293,7 +293,7 @@ history_builtin (WORD_LIST *list)
if (flags & AFLAG) /* Append session's history to file. */
{
result = maybe_append_history (filename, using_histfile);
if (using_histfile)
if (result == 0 && using_histfile)
history_lines_this_session = 0;
}
else if (flags & WFLAG) /* Write entire history. */
@@ -317,7 +317,9 @@ history_builtin (WORD_LIST *list)
I/O and permission errors. */
if (result > 0)
history_error (filename, result, 1);
if (result == 0)
/* Only act like these are new history entries if we are not reading
from $HISTFILE */
if (result == 0 && using_histfile == 0)
history_lines_this_session += history_lines_read_from_file;
}
else if (flags & NFLAG) /* Read `new' history from file. */
+1 -1
View File
@@ -8939,7 +8939,7 @@ Several shell options settable with the
builtin will modify history expansion behavior
(see the description of the
.B shopt
builtin below).and
builtin below).
If the
.B histverify
shell option is enabled, and
+59 -10
View File
@@ -32,6 +32,10 @@
#include <signal.h>
#if defined (HAVE_TERMIOS_H)
# include <termios.h>
#endif
#include "bashintl.h"
#include "shell.h"
@@ -44,6 +48,12 @@
#include "input.h"
#include "execute_cmd.h"
/* If we are using readline timeouts and timeout hooks */
#if defined (READLINE)
# include "bashline.h"
# include <readline/readline.h>
#endif /* READLINE */
#if defined (HISTORY)
# include "bashhist.h"
#endif
@@ -53,8 +63,12 @@
#endif
static void send_pwd_to_eterm (void);
static void alrm_handler (int);
static sighandler alrm_catcher (int);
int input_timeout_set = 0;
volatile sig_atomic_t input_timeout_seen = 0;
/* Read and execute commands until EOF is reached. This assumes that
the input source has already been initialized. */
int
@@ -254,16 +268,29 @@ pretty_print_loop (void)
}
static sighandler
alrm_catcher(int i)
alrm_catcher (int i)
{
char *msg;
input_timeout_seen = 1;
SIGRETURN (0);
}
msg = _("\007timed out waiting for input: auto-logout\n");
write (1, msg, strlen (msg));
static void
alrm_handler(int i)
{
printf ("\007%s\n", _("timed out waiting for input: auto-logout"));
fflush (stdout);
tcflush (fileno (stdin), TCIFLUSH);
bash_logout (); /* run ~/.bash_logout if this is a login shell */
jump_to_top_level (EXITPROG);
SIGRETURN (0);
}
/* We can use this for both readline and non-readline cases */
int
input_timeout_hook (void)
{
alrm_handler (SIGALRM);
return 0;
}
/* Send an escape sequence to emacs term mode to tell it the
@@ -405,8 +432,19 @@ read_command (void)
tmout_len = (int)strtol (t, &e, 10);
if (e != t && *e == '\0' && tmout_len > 0)
{
old_alrm = set_signal_handler (SIGALRM, alrm_catcher);
alarm (tmout_len);
input_timeout_set = 1;
if (no_line_editing)
{
old_alrm = set_signal_handler (SIGALRM, alrm_catcher);
alarm (tmout_len);
}
#if defined (READLINE)
else
{
rl_timeout_event_hook = input_timeout_hook;
rl_set_timeout (tmout_len, 0);
}
#endif
}
}
}
@@ -416,10 +454,21 @@ read_command (void)
current_command_line_count = 0;
result = parse_command ();
if (interactive && tmout_var && (tmout_len > 0))
if (interactive && input_timeout_set)
{
alarm(0);
set_signal_handler (SIGALRM, old_alrm);
if (no_line_editing)
{
alarm(0);
set_signal_handler (SIGALRM, old_alrm);
}
#if defined (READLINE)
else
{
rl_clear_timeout ();
rl_timeout_event_hook = NULL;
}
#endif
input_timeout_set = 0;
}
return (result);
+1
View File
@@ -102,6 +102,7 @@ extern int reader_loop (void);
extern int pretty_print_loop (void);
extern int parse_command (void);
extern int read_command (void);
extern int input_timeout_hook (void);
/* Functions from braces.c. */
#if defined (BRACE_EXPANSION)
+6 -1
View File
@@ -1,6 +1,6 @@
/* input.c -- functions to perform buffered input with synchronization. */
/* Copyright (C) 1992-2023 Free Software Foundation, Inc.
/* Copyright (C) 1992-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -114,6 +114,11 @@ stream_getc (FILE *stream)
}
else if (interrupt_state || terminating_signal) /* QUIT; */
local_index = local_bufused = 0;
else if (input_timeout_seen) /* CHECK_INPUT_TIMEOUT; */
{
local_index = local_bufused = 0;
return EOF;
}
}
local_index = 0;
}
+3 -1
View File
@@ -1844,12 +1844,14 @@ yy_stream_get (void)
int result;
result = EOF;
CHECK_INPUT_TIMEOUT;
if (bash_input.location.file)
{
/* XXX - don't need terminate_immediately; stream_getc checks
for terminating signals itself if read returns < 0 */
result = stream_getc (bash_input.location.file);
}
CHECK_INPUT_TIMEOUT;
return (result);
}
@@ -6024,7 +6026,7 @@ reset_readline_prompt (void)
semi-colons. When concatenating multiple lines of history, the
newline separator for such tokens is replaced with a space. */
static const int no_semi_successors[] = {
'\n', '{', '(', ')', ';', '&', '|',
'\n', '{', '(', ')', ';', '&', '|', BAR_AND,
CASE, DO, ELSE, IF, SEMI_SEMI, SEMI_AND, SEMI_SEMI_AND, THEN, UNTIL,
WHILE, AND_AND, OR_OR, IN, DOLPAREN, DOLBRACE,
0
+6
View File
@@ -88,4 +88,10 @@ do { \
if (interrupt_state) zreset (); \
} while (0)
#define CHECK_INPUT_TIMEOUT \
do { \
if (input_timeout_seen) \
input_timeout_hook (); \
} while (0)
#endif /* _QUIT_H_ */
+3 -1
View File
@@ -1,6 +1,6 @@
/* shell.h -- The data structures used by the shell */
/* Copyright (C) 1993-2024 Free Software Foundation, Inc.
/* Copyright (C) 1993-2026 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -114,6 +114,8 @@ extern int indirection_level;
extern int shell_compatibility_level;
extern const int default_compatibility_level;
extern int running_under_emacs;
extern int input_timeout_set;
extern volatile sig_atomic_t input_timeout_seen;
extern int pretty_print_mode;
+1
View File
@@ -430,3 +430,4 @@ declare -A aa=([/homes/cj/key]="/homes/cj/Desktop" )
declare -A aa=([/homes/cj/Documents]="/homes/cj/Library" [/homes/cj/key]="/homes/cj/Desktop" )
declare -A aa=([/homes/cj/Documents]="/homes/cj/Library" [/homes/cj/key]="/homes/cj/Desktop:/homes/cj/Documents:/homes/cj/Applications" )
declare -A aa=([/homes/cj/Documents]="/homes/cj/Library" [/homes/cj/key]="/homes/cj/Desktop:/homes/cj/Documents:/homes/cj/Applications" )
assoc20.sub
+3
View File
@@ -275,3 +275,6 @@ test_runsub ./assoc18.sub
# tests with tilde expansion in keys and values post-bash-5.2
test_runsub ./assoc19.sub
# new bash-5.4 kv-pair compound assignment word splitting behavior
test_runsub ./assoc20.sub
+129
View File
@@ -0,0 +1,129 @@
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Originally by Zachary Santer <zsanter@gmail.com>
# Modified by Chet Ramey <chet.ramey@case.edu> for the Bash test suite
#
: ${THIS_SH:=./bash}
set -o nounset -o noglob +o braceexpand
shopt -s lastpipe
unset CDPATH TMOUT
declare -r -x LC_ALL='C'
main () {
local declaration
if (( ${#} == 0 )) then
declaration='declare -A assoc
'
else
declaration="${1} -A "
fi
while (( test_count < 512 )); do
(( ++test_count ))
# Create an associative array compound assignment containing random-length
# quoted arrays, unquoted scalars, and literals
local -i array_count=0
local -i scalar_count=0
local script="\
set -o nounset -o noglob +o braceexpand
shopt -s lastpipe
unset CDPATH TMOUT
declare -r -x LC_ALL='C'
main () {
"
local test_case=''
local assignment_contents=''
while (( ( SRANDOM & 7 ) > 0 )) do
local -i type_selector="$(( SRANDOM & 32767 ))"
if (( type_selector < 10923 )) then
assignment_contents+=" '${SRANDOM//[01]/& }'"
else
local -a list=()
local -i element_count
for (( element_count = SRANDOM & 7; element_count > 0; --element_count )) do
list+=( "${SRANDOM}" )
done
if (( type_selector < 21845 )) then
local array_name="array_$(( array_count++ ))"
list=( "${list[@]//[01]/& }" )
test_case+=" local -a ${array_name}=( ${list[*]@Q} )"$'\n'
assignment_contents+=" \"\${${array_name}[@]}\""
else
local scalar_name="scalar_$(( scalar_count++ ))"
test_case+=" local ${scalar_name}='${list[*]}'"$'\n'
assignment_contents+=" \${${scalar_name}}"
fi
fi
done
test_case+="\
${declaration}assoc=(${assignment_contents} )
set --${assignment_contents}"
script+="${test_case}"'
breakage="false"
if (( ( ( ${#} + 1 ) / 2 ) != ${#assoc[@]} )) then
breakage="true"
fi
while [[ ${breakage} == "false" && ${#} -gt 1 ]] do
if [[ ${assoc[${1}]} != "${2}" ]] then
breakage="true"
fi
shift 2
done
if [[ ${breakage} == "false" && ${#} -eq 1 && -n ${assoc[${1}]} ]] then
breakage="true"
fi
[[ ${breakage} == "false" ]]
exit
}
main
'
if ! ${THIS_SH} -c "${script}"; then
(( ++error_count ))
printf '========\n'
printf '%s\n' "${test_case}"
printf '========\n'
fi
done
}
declare -i test_count=0
declare -i error_count=0
main # local
main export
exit ${error_count}