From 2fe2e8197eb8f05c22a23b9d1e115d74febc8325 Mon Sep 17 00:00:00 2001 From: Chet Ramey Date: Thu, 11 Mar 2021 18:41:17 -0500 Subject: [PATCH] application-settable readline timeouts --- CWRU/CWRU.chlog | 69 ++++++ builtins/read.def | 14 ++ doc/bash.1 | 2 +- doc/bashref.texi | 2 +- lib/readline/callback.c | 8 + lib/readline/doc/rltech.texi | 30 +++ lib/readline/doc/version.texi | 8 +- lib/readline/examples/rl-test-timeout | 6 + lib/readline/examples/rl-timeout.c | 245 +++++++++++++++++++++ lib/readline/input.c | 306 +++++++++++++++++++++++++- lib/readline/parens.c | 4 + lib/readline/readline.c | 12 + lib/readline/readline.h | 11 +- lib/readline/rlprivate.h | 7 + lib/readline/rltty.c | 2 +- lib/readline/signals.c | 2 + lib/readline/util.c | 3 +- tests/read.right | 9 + tests/read.tests | 2 +- tests/read7.sub | 3 + 20 files changed, 724 insertions(+), 21 deletions(-) create mode 100644 lib/readline/examples/rl-test-timeout create mode 100644 lib/readline/examples/rl-timeout.c diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index 3b24c38f..409e68bc 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -9757,3 +9757,72 @@ builtins/read.def using SIGALRM - edit_line: simulate receiving SIGALRM if readline times out (not used yet) + + 3/11 + ---- +lib/readline/readline.c + - rl_initialize: call _rl_timeout_init to set things up for any timeout + that was set with rl_set_timeout + - readline_internal_charloop: if we longjmped because of a timeout, + make sure to set rl_done/RL_STATE_DONE and return; we are + abandoning this call to readline(). The readline timeout changes + were based on work contributed by Koichi Murase + + +lib/readline/readline.h + - extern declarations for new timeout functions and hook + - rl_clear_timeout: new define + +lib/readline/callback.c + - rl_callback_read_char: if we longjmped because of a timeout, + make sure to set rl_done/RL_STATE_DONE and return; we are + abandoning this call to readline() + +lib/readline/util.c + - _rl_abort_internal: if we time out, don't ring the bell; let the + caller handle it + +lib/readline/input.c + - extern declarations for public and readline-library-private functions + and hooks to implement timeouts + - rl_set_timeout,rl_timeout_remaining: new public functions + - _rl_timeout_select: new function, uses select/pselect to implement + read timeouts that take timeouts set with rl_set_timeout into account; + calling hook function if a timeout occurs + - rl_gather_tyi, _rl_input_available: use _rl_timeout_select, taking + any existing timeout into consideration if it expires before the + timeout passed as an argument + - rl_getc: use _rl_timeout_select and handle any timeouts by calling + _rl_timehout_handle + - set_alarm,reset_alarm: new functions to implement timeouts using + SIGALRM for systems that lack a working select/pselect + - _rl_timeout_init: new function, sets things up for reading input + with a specified timeout + - _rl_timeout_handle: a timeout handler; calls any event hook and + sets up to abort the current readline() call + - _rl_timeout_handle_sigalrm: a timeout handler for systems using + SIGALRM to implement timeouts + +lib/readline/parens.c + - rl_insert_close: use _rl_timeout_select to take timeouts into account + +lib/readline/rlprivate.h + - extern declarations for readline-library-private timeout functions + +lib/readline/rltty.c + - rl_deprep_terminal: don't print a newline after the bracketed paste + disable sequence if we timed out + +lib/readline/signals.c + - _rl_handle_signal: if sig is SIGALRM, call _rl_timeout_handle_sigalrm() + +lib/readline/doc/rltech.texi + - rl_set_timeout,rl_timeout_remaining: document new public functions + - RL_STATE_TIMEOUT: document new possible state value for rl_readline_state + - rl_timeout_event_hook: document new hook function, called when + readline times out + +builtins/read.def + - read_builtin: changes to use the readline timeout functions to + implement timeouts with `read -e'; these use rl_set_timeout and + sh_timer structs together diff --git a/builtins/read.def b/builtins/read.def index e19f1c91..c8e2edaf 100644 --- a/builtins/read.def +++ b/builtins/read.def @@ -126,6 +126,7 @@ static int set_itext PARAMS((void)); static char *edit_line PARAMS((char *, char *)); static void set_eol_delim PARAMS((int)); static void reset_eol_delim PARAMS((char *)); +static void set_readline_timeout PARAMS((sh_timer *t, time_t, long)); #endif static SHELL_VAR *bind_read_variable PARAMS((char *, char *)); #if defined (HANDLE_MULTIBYTE) @@ -511,7 +512,9 @@ read_builtin (list) { add_unwind_protect (reset_attempted_completion_function, (char *)NULL); add_unwind_protect (bashline_reset_event_hook, (char *)NULL); + set_readline_timeout (read_timeout, tmsec, tmusec); } + else #endif shtimer_set (read_timeout, tmsec, tmusec); } @@ -1209,6 +1212,17 @@ edit_line (p, itext) return ret; } +static void +set_readline_timeout (t, sec, usec) + sh_timer *t; + time_t sec; + long usec; +{ + t->tmout.tv_sec = sec; + t->tmout.tv_usec = usec; + rl_set_timeout (sec, usec); +} + static int old_delim_ctype; static rl_command_func_t *old_delim_func; static int old_newline_ctype; diff --git a/doc/bash.1 b/doc/bash.1 index 5ef5d4f6..0b55bed2 100644 --- a/doc/bash.1 +++ b/doc/bash.1 @@ -10870,7 +10870,7 @@ or are unset, they lose their special properties, even if they are subsequently reset. The exit status is true unless a .I name -is readonly. +is readonly or may not be unset. .TP \fBwait\fP [\fB\-fn\fP] [\fP\-p\fP \fIvarname\fP] [\fIid ...\fP] Wait for each specified child process and return its termination status. diff --git a/doc/bashref.texi b/doc/bashref.texi index d5fca9b9..24a5f360 100644 --- a/doc/bashref.texi +++ b/doc/bashref.texi @@ -4033,7 +4033,7 @@ unset. Readonly variables and functions may not be unset. Some shell variables lose their special behavior if they are unset; such behavior is noted in the description of the individual variables. -The return status is zero unless a @var{name} is readonly. +The return status is zero unless a @var{name} is readonly or may not be unset. @end table @node Bash Builtins diff --git a/lib/readline/callback.c b/lib/readline/callback.c index 66e8d113..cfff6502 100644 --- a/lib/readline/callback.c +++ b/lib/readline/callback.c @@ -147,6 +147,14 @@ rl_callback_read_char (void) (*rl_redisplay_function) (); _rl_want_redisplay = 0; memcpy ((void *)_rl_top_level, (void *)olevel, sizeof (procenv_t)); + + /* If we longjmped because of a timeout, handle it here. */ + if (RL_ISSTATE (RL_STATE_TIMEOUT)) + { + RL_SETSTATE (RL_STATE_DONE); + rl_done = 1; + } + CALLBACK_READ_RETURN (); } diff --git a/lib/readline/doc/rltech.texi b/lib/readline/doc/rltech.texi index bbf57c23..a4402256 100644 --- a/lib/readline/doc/rltech.texi +++ b/lib/readline/doc/rltech.texi @@ -455,6 +455,11 @@ If non-zero, this is the address of a function to call if a read system call is interrupted when Readline is reading terminal input. @end deftypevar +@deftypevar {rl_hook_func_t *} rl_timeout_event_hook +If non-zero, this is the address of a function to call if Readline times +out while reading input. +@end deftypevar + @deftypevar {rl_hook_func_t *} rl_input_available_hook If non-zero, Readline will use this function's return value when it needs to determine whether or not there is available input on the current input @@ -588,6 +593,10 @@ the current call to @code{readline()}. @item RL_STATE_DONE Readline has read a key sequence bound to @code{accept-line} and is about to return the line to the caller. +@item RL_STATE_TIMEOUT +Readline has timed out (it did not receive a line or specified number of +characters before the timeout duration specified by @code{rl_set_timeout} +elapsed) and is returning that status to the caller. @end table @end deftypevar @@ -1151,6 +1160,27 @@ The default waiting period is one-tenth of a second. Returns the old timeout value. @end deftypefun +@deftypefun int rl_set_timeout (unsigned int secs, unsigned int usecs) +Set a timeout for subsequent calls to @code{readline()}. If Readline does +not read a complete line, or the number of characters specified by +@code{rl_num_chars_to_read}, before the duration specfied by @var{secs} +(in seconds) and @var{usecs} (microseconds), it returns and sets +@code{RL_STATE_TIMEOUT} in @code{rl_readline_state}. +Passing 0 for @code{secs} and @code{usecs} cancels any previously set +timeout; the convenience macro @code{rl_clear_timeout()} is shorthand +for this. +Returns 0 if the timeout is set successfully. +@end deftypefun + +@deftypefun int rl_timeout_remaining (unsigned int *secs, unsigned int *usecs) +Return the number of seconds and microseconds remaining in the current +timeout duration in @code{*secs} and @code{*usecs}, respectively. +Returns -1 on error or when there is no timeout set, 0 when the timeout has +expired (leaving @code{*secs} and @code{*usecs} unchanged), and 1 if the +timeout has not expired. If @code{secs} and @code{usecs} are @code{NULL}, +the return value indicates whether the timeout has expired. +@end deftypefun + @node Terminal Management @subsection Terminal Management diff --git a/lib/readline/doc/version.texi b/lib/readline/doc/version.texi index abb9cb6b..a935f835 100644 --- a/lib/readline/doc/version.texi +++ b/lib/readline/doc/version.texi @@ -1,10 +1,10 @@ @ignore -Copyright (C) 1988-2020 Free Software Foundation, Inc. +Copyright (C) 1988-2021 Free Software Foundation, Inc. @end ignore @set EDITION 8.1 @set VERSION 8.1 -@set UPDATED 29 October 2020 -@set UPDATED-MONTH October 2020 +@set UPDATED 10 March 2021 +@set UPDATED-MONTH March 2021 -@set LASTCHANGE Thu Oct 29 16:49:01 EDT 2020 +@set LASTCHANGE Wed Mar 10 15:43:49 EST 2021 diff --git a/lib/readline/examples/rl-test-timeout b/lib/readline/examples/rl-test-timeout new file mode 100644 index 00000000..2c9de1a7 --- /dev/null +++ b/lib/readline/examples/rl-test-timeout @@ -0,0 +1,6 @@ +./rl-timeout readline1 0.5 +./rl-timeout readline2 0.25 + +./rl-timeout callback1 0.5 +./rl-timeout callback2 0.5 + diff --git a/lib/readline/examples/rl-timeout.c b/lib/readline/examples/rl-timeout.c new file mode 100644 index 00000000..b8a24baf --- /dev/null +++ b/lib/readline/examples/rl-timeout.c @@ -0,0 +1,245 @@ +/* rl-timeout: test various readline builtin timeouts. */ + +/* Copyright (C) 2021 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library (Readline), a library for + reading lines of text with interactive input and history editing. + + Readline 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. + + Readline 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 readline. If not, see . +*/ + +/* Standard include files. stdio.h is required. */ +#include +#include +#include +#include + +/* Used for select(2) */ +#include +#include + +#include +#include + +/* Standard readline include files. */ +#if defined (READLINE_LIBRARY) +# include "readline.h" +# include "history.h" +#else +# include +# include +#endif + +extern int errno; + +static void cb_linehandler (char *); + +int timeout_secs = 1, timeout_usecs = 0; +int running; +const char *prompt = "rl-timeout$ "; + +/* **************************************************************** */ +/* */ +/* Example 1: readline () with rl_readline_state */ +/* */ +/* **************************************************************** */ + +void +rltest_timeout_readline1 () +{ + const char *temp; + + rl_set_timeout (timeout_secs, timeout_usecs); + temp = readline (prompt); + if (RL_ISSTATE (RL_STATE_TIMEOUT)) + printf ("timeout\n"); + else if (temp == NULL) + printf ("no input line\n"); + else + printf ("input line: %s\n", temp); + free ((void *) temp); +} + +/* **************************************************************** */ +/* */ +/* Example 2: readline () with rl_timeout_event_hook */ +/* */ +/* **************************************************************** */ + +static int +timeout_handler () +{ + printf ("timeout\n"); + return READERR; +} + +void +rltest_timeout_readline2 () +{ + const char *temp; + + rl_set_timeout (timeout_secs, timeout_usecs); + rl_timeout_event_hook = timeout_handler; + temp = readline (prompt); + if (temp == NULL) + printf ("no input line\n"); + else + printf ("input line: %s\n", temp); + free ((void *)temp); +} + +/* **************************************************************** */ +/* */ +/* Example 3: rl_callback_* () with rl_timeout_remaining */ +/* */ +/* **************************************************************** */ + +/* Callback function called for each line when accept-line executed, EOF + seen, or EOF character read. This sets a flag and returns; it could + also call exit(3). */ +static void +cb_linehandler (char *line) +{ + /* Can use ^D (stty eof) or `exit' to exit. */ + if (line == NULL || strcmp (line, "exit") == 0) + { + if (line == 0) + printf ("\n"); + printf ("exit\n"); + /* This function needs to be called to reset the terminal settings, + and calling it from the line handler keeps one extra prompt from + being displayed. */ + rl_callback_handler_remove (); + + running = 0; + } + else + { + if (*line) + add_history (line); + printf ("input line: %s\n", line); + free (line); + } +} + +void +rltest_timeout_callback1 () +{ + fd_set fds; + int r; + unsigned sec, usec; + + rl_set_timeout (timeout_secs, timeout_usecs); + rl_callback_handler_install (prompt, cb_linehandler); + running = 1; + while (running) + { + FD_ZERO (&fds); + FD_SET (fileno (rl_instream), &fds); + r = rl_timeout_remaining (&sec, &usec); + if (r == 1) + { + struct timeval timeout = {sec, usec}; + r = select (FD_SETSIZE, &fds, NULL, NULL, &timeout); + } + if (r < 0 && errno != EINTR) + { + perror ("rl-timeout: select"); + rl_callback_handler_remove (); + break; + } + else if (r == 0) + { + printf ("rl-timeout: timeout\n"); + rl_callback_handler_remove (); + break; + } + + if (FD_ISSET (fileno (rl_instream), &fds)) + rl_callback_read_char (); + } + + printf ("rl-timeout: Event loop has exited\n"); +} + +/* **************************************************************** */ +/* */ +/* Example 4: rl_callback_* () with rl_timeout_event_hook */ +/* */ +/* **************************************************************** */ + +static int +cb_timeouthandler () +{ + printf ("timeout\n"); + rl_callback_handler_remove (); + running = 0; + return READERR; +} + +void +rltest_timeout_callback2 () +{ + int r; + + rl_set_timeout (timeout_secs, timeout_usecs); + rl_timeout_event_hook = cb_timeouthandler; + rl_callback_handler_install (prompt, cb_linehandler); + running = 1; + while (running) + rl_callback_read_char (); + + printf ("rl-timeout: Event loop has exited\n"); +} + +int +main (int argc, char **argv) +{ + if (argc >= 2) + { + if (argc >= 3) + { + double timeout = atof (argv[2]); + if (timeout <= 0.0) + { + fprintf (stderr, "rl-timeout: specify a positive number for timeout.\n"); + return 2; + } + else if (timeout > UINT_MAX) + { + fprintf (stderr, "rl-timeout: timeout too large.\n"); + return 2; + } + timeout_secs = (unsigned) timeout; + timeout_usecs = (unsigned) ((timeout - timeout_secs) * 1000000 + 0.5); + } + + if (strcmp (argv[1], "readline1") == 0) + rltest_timeout_readline1 (); + else if (strcmp (argv[1], "readline2") == 0) + rltest_timeout_readline2 (); + else if (strcmp (argv[1], "callback1") == 0) + rltest_timeout_callback1 (); + else if (strcmp (argv[1], "callback2") == 0) + rltest_timeout_callback2 (); + else + return 2; + } + else + { + fprintf (stderr, "usage: rl-timeout [readline1 | readline2 | callback1 | callback2] [timeout]\n"); + return 2; + } + return 0; +} diff --git a/lib/readline/input.c b/lib/readline/input.c index b836cf10..930efa4f 100644 --- a/lib/readline/input.c +++ b/lib/readline/input.c @@ -50,6 +50,7 @@ #include #include "posixselect.h" +#include "posixtime.h" #if defined (FIONREAD_IN_SYS_IOCTL) # include @@ -89,6 +90,9 @@ rl_hook_func_t *rl_event_hook = (rl_hook_func_t *)NULL; /* A function to call if a read(2) is interrupted by a signal. */ rl_hook_func_t *rl_signal_event_hook = (rl_hook_func_t *)NULL; +/* A function to call when readline times out after a time is specified. */ +rl_hook_func_t *rl_timeout_event_hook = (rl_hook_func_t *)NULL; + /* A function to replace _rl_input_available for applications using the callback interface. */ rl_hook_func_t *rl_input_available_hook = (rl_hook_func_t *)NULL; @@ -132,6 +136,36 @@ win32_isatty (int fd) #define isatty(x) win32_isatty(x) #endif +/* Readline timeouts */ + +/* I don't know how to set a timeout for _getch() in MinGW32, so we use + SIGALRM. */ +#if (defined (HAVE_PSELECT) || defined (HAVE_SELECT)) && !defined (__MINGW32__) +# define RL_TIMEOUT_USE_SELECT +#else +# define RL_TIMEOUT_USE_SIGALRM +#endif + +int rl_set_timeout (unsigned int, unsigned int); +int rl_timeout_remaining (unsigned int *, unsigned int *); + +int _rl_timeout_init (void); +int _rl_timeout_sigalrm_handler (void); +int _rl_timeout_select (int, fd_set *, fd_set *, fd_set *, const struct timeval *, const sigset_t *); + +static void _rl_timeout_handle (void); +#if defined (RL_TIMEOUT_USE_SIGALRM) +static int set_alarm (unsigned int *, unsigned int *); +static void reset_alarm (void); +#endif + +/* We implement timeouts as a future time using a supplied interval + (timeout_duration) from when the timeout is set (timeout_point). + That allows us to easily determine whether the timeout has occurred + and compute the time remaining until it does. */ +static struct timeval timeout_point; +static struct timeval timeout_duration; + /* **************************************************************** */ /* */ /* Character Input Buffering */ @@ -223,13 +257,17 @@ rl_gather_tyi (void) input = 0; tty = fileno (rl_instream); -#if defined (HAVE_SELECT) +#if defined (HAVE_PSELECT) || defined (HAVE_SELECT) FD_ZERO (&readfds); FD_ZERO (&exceptfds); FD_SET (tty, &readfds); FD_SET (tty, &exceptfds); USEC_TO_TIMEVAL (_keyboard_input_timeout, timeout); +#if defined (RL_TIMEOUT_USE_SELECT) + result = _rl_timeout_select (tty + 1, &readfds, (fd_set *)NULL, &exceptfds, &timeout, NULL); +#else result = select (tty + 1, &readfds, (fd_set *)NULL, &exceptfds, &timeout); +#endif if (result <= 0) return 0; /* Nothing to read. */ #endif @@ -330,11 +368,11 @@ rl_set_keyboard_input_timeout (int u) int _rl_input_available (void) { -#if defined(HAVE_SELECT) +#if defined (HAVE_PSELECT) || defined (HAVE_SELECT) fd_set readfds, exceptfds; struct timeval timeout; #endif -#if !defined (HAVE_SELECT) && defined(FIONREAD) +#if !defined (HAVE_SELECT) && defined (FIONREAD) int chars_avail; #endif int tty; @@ -344,13 +382,17 @@ _rl_input_available (void) tty = fileno (rl_instream); -#if defined (HAVE_SELECT) +#if defined (HAVE_PSELECT) || defined (HAVE_SELECT) FD_ZERO (&readfds); FD_ZERO (&exceptfds); FD_SET (tty, &readfds); FD_SET (tty, &exceptfds); USEC_TO_TIMEVAL (_keyboard_input_timeout, timeout); +# if defined (RL_TIMEOUT_USE_SELECT) + return (_rl_timeout_select (tty + 1, &readfds, (fd_set *)NULL, &exceptfds, &timeout, NULL) > 0); +# else return (select (tty + 1, &readfds, (fd_set *)NULL, &exceptfds, &timeout) > 0); +# endif #else #if defined (FIONREAD) @@ -463,6 +505,242 @@ rl_clear_pending_input (void) return 0; } +/* **************************************************************** */ +/* */ +/* Timeout utility */ +/* */ +/* **************************************************************** */ + +#if defined (RL_TIMEOUT_USE_SIGALRM) +# if defined (HAVE_SETITIMER) + +static int +set_alarm (unsigned int *secs, unsigned int *usecs) +{ + struct itimerval it; + + timerclear (&it.it_interval); + timerset (&it.it_value, *secs, *usecs); + return setitimer (ITIMER_REAL, &it, NULL); +} + +static void +reset_alarm () +{ + struct itimerval it; + + timerclear (&it.it_interval); + timerclear (&it.it_value); + setitimer (ITIMER_REAL, &it, NULL); +} +# else +static int +set_alarm (unsigned int *secs, unsigned int *usecs) +{ + if (*secs == 0 || *usecs >= USEC_PER_SEC / 2) + (*secs)++; + *usecs = 0; + + return alarm (*secs); +} +static void +reset_alarm () +{ + alarm (0); +} +# endif +#endif + +/* Set a timeout which will be used for the next call of `readline + ()'. When (0, 0) are specified the timeout is cleared. */ +int +rl_set_timeout (unsigned int secs, unsigned int usecs) +{ + timeout_duration.tv_sec = secs + usecs / USEC_PER_SEC; + timeout_duration.tv_usec = usecs % USEC_PER_SEC; + + return 0; +} + +/* Start measuring the time. Returns 0 on success. Returns -1 on + error. */ +int +_rl_timeout_init (void) +{ + unsigned int secs, usecs; + + /* Clear the timeout state of the previous edit */ + RL_UNSETSTATE(RL_STATE_TIMEOUT); + timerclear (&timeout_point); + + /* Return 0 when timeout is unset. */ + if (timerisunset (&timeout_duration)) + return 0; + + /* Return -1 on gettimeofday error. */ + if (gettimeofday(&timeout_point, 0) != 0) + { + timerclear (&timeout_point); + return -1; + } + + secs = timeout_duration.tv_sec; + usecs = timeout_duration.tv_usec; + +#if defined (RL_TIMEOUT_USE_SIGALRM) + /* If select(2)/pselect(2) is unavailable, use SIGALRM. */ + if (set_alarm (&secs, &usecs) < 0) + return -1; +#endif + + timeout_point.tv_sec += secs; + timeout_point.tv_usec += usecs; + if (timeout_point.tv_usec >= USEC_PER_SEC) + { + timeout_point.tv_sec++; + timeout_point.tv_usec -= USEC_PER_SEC; + } + + return 0; +} + +/* Get the remaining time until the scheduled timeout. Returns -1 on + error or no timeout set with secs and usecs unchanged. Returns 0 + on an expired timeout with secs and usecs unchanged. Returns 1 + when the timeout has not yet expired. The remaining time is stored + in secs and usecs. When NULL is specified to either of the + arguments, just the expiration is tested. */ +int +rl_timeout_remaining (unsigned int *secs, unsigned int *usecs) +{ + struct timeval current_time; + + /* Return -1 when timeout is unset. */ + if (timerisunset (&timeout_point)) + { + errno = 0; + return -1; + } + + /* Return -1 on error. errno is set by gettimeofday. */ + if (gettimeofday(¤t_time, 0) != 0) + return -1; + + /* Return 0 when timeout has already expired. */ + /* could use timercmp (&timeout_point, ¤t_time, <) here */ + if (current_time.tv_sec > timeout_point.tv_sec || + (current_time.tv_sec == timeout_point.tv_sec && + current_time.tv_usec >= timeout_point.tv_usec)) + return 0; + + if (secs && usecs) + { + *secs = timeout_point.tv_sec - current_time.tv_sec; + *usecs = timeout_point.tv_usec - current_time.tv_usec; + if (timeout_point.tv_usec < current_time.tv_usec) + { + (*secs)--; + *usecs += USEC_PER_SEC; + } + } + + return 1; +} + +/* This should only be called if RL_TIMEOUT_USE_SELECT is defined. */ + +#if defined (HAVE_PSELECT) || defined (HAVE_SELECT) +int +_rl_timeout_select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *timeout, const sigset_t *sigmask) +{ + int result; +#if defined (HAVE_PSELECT) + struct timespec ts; +#else + sigset_t origmask; + struct timeval tv; +#endif + int tmout_status; + struct timeval tmout; + unsigned int sec, usec; + + /* When the remaining time for rl_timeout is shorter than the + keyboard input timeout, replace `timeout' with the remaining time + for `rl_timeout' and set `tmout_status = 1'. */ + tmout_status = rl_timeout_remaining (&sec, &usec); + tmout.tv_sec = sec; + tmout.tv_usec = usec; + + if (tmout_status == 0) + _rl_timeout_handle (); + else if (tmout_status == 1) + { + if (timeout == NULL || timercmp (&tmout, timeout, <)) + timeout = &tmout; + else + tmout_status = -1; + } + +#if defined (HAVE_PSELECT) + if (timeout) + { + TIMEVAL_TO_TIMESPEC (timeout, &ts); + result = pselect (nfds, readfds, writefds, exceptfds, &ts, sigmask); + } + else + result = pselect (nfds, readfds, writefds, exceptfds, NULL, sigmask); +#else + if (sigmask) + sigprocmask (SIG_SETMASK, sigmask, &origmask); + + if (timeout) + { + tv.tv_sec = timeout->tv_sec; + tv.tv_usec = timeout->tv_usec; + result = select (nfds, readfds, writefds, exceptfds, &tv); + } + else + result = select (nfds, readfds, writefds, exceptfds, NULL); + + if (sigmask) + sigprocmask (SIG_SETMASK, &origmask, NULL); +#endif + + if (tmout_status == 1 && result == 0) + _rl_timeout_handle (); + + return result; +} +#endif + +static void +_rl_timeout_handle () +{ + if (rl_timeout_event_hook) + (*rl_timeout_event_hook) (); + + RL_SETSTATE(RL_STATE_TIMEOUT); + _rl_abort_internal (); +} + +int +_rl_timeout_handle_sigalrm () +{ +#if defined (RL_TIMEOUT_USE_SIGALRM) + if (timerisunset (&timeout_point)) + return -1; + + /* Reset `timeout_point' to the current time to ensure that later + calls of `rl_timeout_pending ()' return 0 (timeout expired). */ + if (gettimeofday(&timeout_point, 0) != 0) + timerclear (&timeout_point); + + reset_alarm (); + + _rl_timeout_handle (); +#endif + return -1; +} /* **************************************************************** */ /* */ /* Character Input */ @@ -526,11 +804,13 @@ rl_getc (FILE *stream) { int result; unsigned char c; + int fd; #if defined (HAVE_PSELECT) sigset_t empty_set; fd_set readfds; #endif + fd = fileno (stream); while (1) { RL_CHECK_SIGNALS (); @@ -538,23 +818,27 @@ rl_getc (FILE *stream) /* We know at this point that _rl_caught_signal == 0 */ #if defined (__MINGW32__) - if (isatty (fileno (stream))) + if (isatty (fd) return (_getch ()); /* "There is no error return." */ #endif result = 0; -#if defined (HAVE_PSELECT) +#if defined (HAVE_PSELECT) || defined (HAVE_SELECT) + /* At this point, if we have pselect, we're using select/pselect for the + timeouts. We handled MinGW above. */ FD_ZERO (&readfds); - FD_SET (fileno (stream), &readfds); + FD_SET (fd, &readfds); # if defined (HANDLE_SIGNALS) - result = pselect (fileno (stream) + 1, &readfds, NULL, NULL, NULL, &_rl_orig_sigset); + result = _rl_timeout_select (fd + 1, &readfds, NULL, NULL, NULL, &_rl_orig_sigset); # else sigemptyset (&empty_set); sigprocmask (SIG_BLOCK, (sigset_t *)NULL, &empty_set); - result = pselect (fileno (stream) + 1, &readfds, NULL, NULL, NULL, &empty_set); + result = _rl_timeout_select (fd + 1, &readfds, NULL, NULL, NULL, &empty_set); # endif /* HANDLE_SIGNALS */ + if (result == 0) + _rl_timeout_handle (); /* check the timeout */ #endif if (result >= 0) - result = read (fileno (stream), &c, sizeof (unsigned char)); + result = read (fd, &c, sizeof (unsigned char)); if (result == sizeof (unsigned char)) return (c); @@ -583,7 +867,7 @@ rl_getc (FILE *stream) if (errno == X_EWOULDBLOCK || errno == X_EAGAIN) { - if (sh_unset_nodelay_mode (fileno (stream)) < 0) + if (sh_unset_nodelay_mode (fd) < 0) return (EOF); continue; } diff --git a/lib/readline/parens.c b/lib/readline/parens.c index b8c5ac84..57ce7045 100644 --- a/lib/readline/parens.c +++ b/lib/readline/parens.c @@ -135,7 +135,11 @@ rl_insert_close (int count, int invoking_key) orig_point = rl_point; rl_point = match_point; (*rl_redisplay_function) (); +# if defined (RL_TIMEOUT_USE_SELECT) + ready = _rl_timeout_select (1, &readfds, (fd_set *)NULL, (fd_set *)NULL, &timer, NULL); +# else ready = select (1, &readfds, (fd_set *)NULL, (fd_set *)NULL, &timer); +# endif rl_point = orig_point; #else /* !HAVE_SELECT */ _rl_insert_char (count, invoking_key); diff --git a/lib/readline/readline.c b/lib/readline/readline.c index de06af1d..4002fe51 100644 --- a/lib/readline/readline.c +++ b/lib/readline/readline.c @@ -577,6 +577,15 @@ readline_internal_charloop (void) { (*rl_redisplay_function) (); _rl_want_redisplay = 0; + + /* If we longjmped because of a timeout, handle it here. */ + if (RL_ISSTATE (RL_STATE_TIMEOUT)) + { + RL_SETSTATE (RL_STATE_DONE); + rl_done = 1; + return 1; + } + /* If we get here, we're not being called from something dispatched from _rl_callback_read_char(), which sets up its own value of _rl_top_level (saving and restoring the old, of course), so @@ -1148,6 +1157,9 @@ _rl_subseq_result (int r, Keymap map, int key, int got_subseq) int rl_initialize (void) { + /* Initialize the timeout first to get the precise start time. */ + _rl_timeout_init (); + /* If we have never been called before, initialize the terminal and data structures. */ if (rl_initialized == 0) diff --git a/lib/readline/readline.h b/lib/readline/readline.h index e7e94419..ab33823d 100644 --- a/lib/readline/readline.h +++ b/lib/readline/readline.h @@ -448,6 +448,13 @@ extern int rl_read_key (void); extern int rl_getc (FILE *); extern int rl_set_keyboard_input_timeout (int); +/* Functions to set and reset timeouts. */ +extern int rl_set_timeout (unsigned int, unsigned int); +extern int rl_timeout_remaining (unsigned int *, unsigned int *); + +#undef rl_clear_timeout +#define rl_clear_timeout() rl_set_timeout (0, 0) + /* `Public' utility functions . */ extern void rl_extend_line_buffer (int); extern int rl_ding (void); @@ -599,6 +606,8 @@ extern rl_hook_func_t *rl_event_hook; /* The address of a function to call if a read is interrupted by a signal. */ extern rl_hook_func_t *rl_signal_event_hook; +extern rl_hook_func_t *rl_timeout_event_hook; + /* The address of a function to call if Readline needs to know whether or not there is data available from the current input source. */ extern rl_hook_func_t *rl_input_available_hook; @@ -906,7 +915,7 @@ extern int rl_persistent_signal_handlers; #define RL_STATE_REDISPLAYING 0x1000000 /* updating terminal display */ #define RL_STATE_DONE 0x2000000 /* done; accepted line */ -#define RL_STATE_TIMEOUT 0x4000000 /* readline() timed out */ +#define RL_STATE_TIMEOUT 0x4000000 #define RL_SETSTATE(x) (rl_readline_state |= (x)) #define RL_UNSETSTATE(x) (rl_readline_state &= ~(x)) diff --git a/lib/readline/rlprivate.h b/lib/readline/rlprivate.h index d42b638c..86a29127 100644 --- a/lib/readline/rlprivate.h +++ b/lib/readline/rlprivate.h @@ -301,6 +301,13 @@ extern void _rl_insert_typein (int); extern int _rl_unget_char (int); extern int _rl_pushed_input_available (void); +extern int _rl_timeout_init (void); +extern int _rl_timeout_handle_sigalrm (void); +#if defined (_POSIXSELECT_H_) +/* use as a sentinel for fd_set, struct timeval, and sigset_t definitions */ +extern int _rl_timeout_select (int, fd_set *, fd_set *, fd_set *, const struct timeval *, const sigset_t *); +#endif + /* isearch.c */ extern _rl_search_cxt *_rl_scxt_alloc (int, int); extern void _rl_scxt_dispose (_rl_search_cxt *, int); diff --git a/lib/readline/rltty.c b/lib/readline/rltty.c index e96fa8fb..4a492001 100644 --- a/lib/readline/rltty.c +++ b/lib/readline/rltty.c @@ -692,7 +692,7 @@ rl_deprep_terminal (void) if (terminal_prepped & TPX_BRACKPASTE) { fprintf (rl_outstream, BRACK_PASTE_FINI); - if (_rl_eof_found) + if (_rl_eof_found && (RL_ISSTATE (RL_STATE_TIMEOUT) == 0)) fprintf (rl_outstream, "\n"); } diff --git a/lib/readline/signals.c b/lib/readline/signals.c index af1bda2d..2bbec3ec 100644 --- a/lib/readline/signals.c +++ b/lib/readline/signals.c @@ -267,6 +267,8 @@ _rl_handle_signal (int sig) case SIGTERM: #if defined (SIGALRM) case SIGALRM: + if (sig == SIGALRM) + _rl_timeout_handle_sigalrm (); #endif #if defined (SIGQUIT) case SIGQUIT: diff --git a/lib/readline/util.c b/lib/readline/util.c index 1576b55d..2cd7b3ce 100644 --- a/lib/readline/util.c +++ b/lib/readline/util.c @@ -98,7 +98,8 @@ _rl_walphabetic (wchar_t wc) int _rl_abort_internal (void) { - rl_ding (); + if (RL_ISSTATE (RL_STATE_TIMEOUT) == 0) + rl_ding (); /* Don't ring the bell on a timeout */ rl_clear_message (); _rl_reset_argument (); rl_clear_pending_input (); diff --git a/tests/read.right b/tests/read.right index 5000d380..72c0cc41 100644 --- a/tests/read.right +++ b/tests/read.right @@ -67,3 +67,12 @@ FOO 0 0 1 +[?2004h[?2004l timeout 1: ok +unset or null 1 +[?2004h[?2004l timeout 2: ok +unset or null 2 +[?2004h[?2004l timeout 3: ok +unset or null 3 +timeout 4: ok +abcde +abcde diff --git a/tests/read.tests b/tests/read.tests index 5efae20b..d91f79d4 100644 --- a/tests/read.tests +++ b/tests/read.tests @@ -111,4 +111,4 @@ ${THIS_SH} ./read5.sub ${THIS_SH} ./read6.sub # test behavior of readline timeouts -# ${THIS_SH} ./read7.sub +${THIS_SH} ./read7.sub diff --git a/tests/read7.sub b/tests/read7.sub index 469f3c81..d47882fe 100644 --- a/tests/read7.sub +++ b/tests/read7.sub @@ -53,3 +53,6 @@ echo abcde | { read -e -t 0.5 a echo $a } + +read -e -t .0001 a <<