From 7cf2d923617659d216db3210f6247740f7dde1d8 Mon Sep 17 00:00:00 2001 From: Chet Ramey Date: Fri, 30 May 2025 10:14:41 -0400 Subject: [PATCH] change history file reading not to skip blank lines in multiline history entries; changes for issues discovered via static analysis --- CHANGES | 15 ++++++++++++ NEWS | 3 +++ aclocal.m4 | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++ display.c | 6 +---- histexpand.c | 3 +-- histfile.c | 25 +++++++++++++++++--- history.c | 9 ++++---- input.c | 2 +- readline.c | 2 ++ search.c | 2 +- shell.c | 2 ++ terminal.c | 16 +++++++++---- util.c | 2 +- 13 files changed, 131 insertions(+), 21 deletions(-) diff --git a/CHANGES b/CHANGES index 03ee607..8c89f1e 100644 --- a/CHANGES +++ b/CHANGES @@ -108,6 +108,18 @@ jj. Fixes for some use-after-free of the undo list errors when stacking multiple kk. Fixes to ensure that completion-prefix-display-length and colored-completion-prefix are mutually exclusive. +ll. Fixed a bug that allowed a history search to change the current history + list position. + +mm. Fixed a bug that allowed ^G to retain a saved command to execute. + +nn. Updates to new export-completions command to allow filename suffixes. + +oo. Fixed a redisplay bug with prompts containing multiple sequences of + invisible characters that are longer than the screen width. + +pp. The history library no longer skips blank lines while it is reading a + multiline history entry from a history file. 2. New Features in Readline @@ -153,6 +165,9 @@ l. The default value for `readline-colored-completion-prefix' no longer has a m. There is a new bindable command, `export-completions', which writes the possible completions for a word to the standard output in a defined format. +n. Readline can reset its idea of the screen dimensions when executing after + a SIGCONT. + ------------------------------------------------------------------------------- This document details the changes between this version, readline-8.2, and the previous version, readline-8.1. diff --git a/NEWS b/NEWS index 81fa526..1fc6989 100644 --- a/NEWS +++ b/NEWS @@ -45,6 +45,9 @@ l. The default value for `readline-colored-completion-prefix' no longer has a m. There is a new bindable command, `export-completions', which writes the possible completions for a word to the standard output in a defined format. +n. Readline can reset its idea of the screen dimensions when executing after + a SIGCONT. + ------------------------------------------------------------------------------- This is a terse description of the new features added to readline-8.2 since the release of readline-8.1. diff --git a/aclocal.m4 b/aclocal.m4 index bd59866..ae2d8ae 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -2240,3 +2240,68 @@ else fi AC_DEFINE_UNQUOTED([FNMATCH_EQUIV_FALLBACK], [$bash_cv_fnmatch_equiv_value], [Whether fnmatch can be used for bracket equivalence classes]) ]) + +AC_DEFUN([BASH_FUNC_STRCHRNUL], +[ + AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS]) + AC_CACHE_CHECK([whether strchrnul works], + [bash_cv_func_strchrnul_works], + [AC_RUN_IFELSE([AC_LANG_PROGRAM( +[[ +#include +]], +[[const char *buf = "abc"; + return strchrnul (buf, 'd') != buf + 3; +]] +)], +[bash_cv_func_strchrnul_works=yes], [bash_cv_func_strchrnul_works=no], +[bash_cv_func_strchrnul_works=no] +)]) + +if test "$bash_cv_func_strchrnul_works" = "no"; then +AC_LIBOBJ([strchrnul]) +fi +]) + +# AM_PROG_INSTALL_STRIP +# --------------------- +# One issue with vendor 'install' (even GNU) is that you can't +# specify the program used to strip binaries. This is especially +# annoying in cross-compiling environments, where the build's strip +# is unlikely to handle the host's binaries. +# Fortunately install-sh will honor a STRIPPROG variable, so we +# always use install-sh in "make install-strip", and initialize +# STRIPPROG with the value of the STRIP variable (set by the user). +AC_DEFUN([AM_PROG_INSTALL_STRIP], +[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl +# Installed binaries are usually stripped using 'strip' when the user +# run "make install-strip". However 'strip' might not be the right +# tool to use in cross-compilation environments, therefore Automake +# will honor the 'STRIP' environment variable to overrule this program. +dnl Don't test for $cross_compiling = yes, because it might be 'maybe'. +#if test "$cross_compiling" != no; then + AC_CHECK_TOOL([STRIP], [strip], :) +#fi +INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" +AC_SUBST([INSTALL_STRIP_PROGRAM])]) + +AC_DEFUN([AM_AUX_DIR_EXPAND], +[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl +# Expand $ac_aux_dir to an absolute path. +am_aux_dir=`cd "$ac_aux_dir" && pwd` +]) + +# AM_PROG_INSTALL_SH +# ------------------ +# Define $install_sh. +AC_DEFUN([AM_PROG_INSTALL_SH], +[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl +if test x"${install_sh+set}" != xset; then + case $am_aux_dir in + *\ * | *\ *) + install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;; + *) + install_sh="\${SHELL} $am_aux_dir/install-sh" + esac +fi +AC_SUBST([install_sh])]) diff --git a/display.c b/display.c index 021dc3f..9aa8c7b 100644 --- a/display.c +++ b/display.c @@ -809,7 +809,7 @@ rl_redisplay (void) { int in, out, c, linenum, cursor_linenum; int inv_botlin, lb_botlin, lb_linenum, o_cpos; - int newlines, lpos, temp, num, prompt_lines_estimate; + int newlines, lpos, temp, num; char *prompt_this_line; char cur_face; int hl_begin, hl_end; @@ -1014,9 +1014,6 @@ rl_redisplay (void) on the first and last prompt lines; change that to use local_prompt_invis_chars */ - /* This is zero-based, used to set the newlines */ - prompt_lines_estimate = lpos / _rl_screenwidth; - /* what if lpos is already >= _rl_screenwidth before we start drawing the contents of the command line? */ if (lpos >= _rl_screenwidth) @@ -1908,7 +1905,6 @@ update_line (char *old, char *old_face, char *new, char *new_face, int current_l if (newwidth > 0) { int i, j; - char *optr; puts_face (new, new_face, newbytes); _rl_last_c_pos = newwidth; diff --git a/histexpand.c b/histexpand.c index fc0008e..085b454 100644 --- a/histexpand.c +++ b/histexpand.c @@ -679,7 +679,7 @@ history_expand_internal (const char *string, int start, int qc, int *end_index_p case 's': { char *new_event; - int delimiter, failed, si, l_temp, ws, we; + int delimiter, failed, si, l_temp, we; if (c == 's') { @@ -778,7 +778,6 @@ history_expand_internal (const char *string, int start, int qc, int *end_index_p { for (; temp[si] && fielddelim (temp[si]); si++) ; - ws = si; we = history_tokenize_word (temp, si); } diff --git a/histfile.c b/histfile.c index 9a25914..11bdd84 100644 --- a/histfile.c +++ b/histfile.c @@ -182,6 +182,7 @@ history_filename (const char *filename) return (return_val); } +#if defined (DEBUG) static char * history_backupfile (const char *filename) { @@ -208,6 +209,7 @@ history_backupfile (const char *filename) ret[len+1] = '\0'; return ret; } +#endif static char * history_tempfile (const char *filename) @@ -267,6 +269,7 @@ read_history_range (const char *filename, int from, int to) register char *line_start, *line_end, *p; char *input, *buffer, *bufend, *last_ts; int file, current_line, chars_read, has_timestamps, reset_comment_char; + int skipblanks, default_skipblanks; struct stat finfo; size_t file_size; #if defined (EFBIG) @@ -380,6 +383,9 @@ read_history_range (const char *filename, int from, int to) has_timestamps = HIST_TIMESTAMP_START (buffer); history_multiline_entries += has_timestamps && history_write_timestamps; + /* default is to skip blank lines unless history entries are multiline */ + default_skipblanks = history_multiline_entries == 0; + /* Skip lines until we are at FROM. */ if (has_timestamps) last_ts = buffer; @@ -405,6 +411,8 @@ read_history_range (const char *filename, int from, int to) } } + skipblanks = default_skipblanks; + /* If there are lines left to gobble, then gobble them now. */ for (line_end = line_start; line_end < bufend; line_end++) if (*line_end == '\n') @@ -415,10 +423,16 @@ read_history_range (const char *filename, int from, int to) else *line_end = '\0'; - if (*line_start) + if (*line_start || skipblanks == 0) { if (HIST_TIMESTAMP_START(line_start) == 0) { + /* If we have multiline entries (default_skipblanks == 0), we + don't want to skip blanks here, since we turned that on at + the last timestamp line. Consider doing this even if + default_skipblanks == 1 in order not to lose blank lines in + commands. */ + skipblanks = default_skipblanks; if (last_ts == NULL && history_length > 0 && history_multiline_entries) _hs_append_history_line (history_length - 1, line_start); else @@ -433,6 +447,9 @@ read_history_range (const char *filename, int from, int to) { last_ts = line_start; current_line--; + /* Even if we're not skipping blank lines by default, we want + to skip leading blank lines after a timestamp. */ + skipblanks = 1; } } @@ -470,6 +487,7 @@ history_rename (const char *old, const char *new) #endif } +#if defined (DEBUG) /* Save FILENAME to BACK, handling case where FILENAME is a symlink (e.g., ~/.bash_history -> .histfiles/.bash_history.$HOSTNAME) */ static int @@ -488,6 +506,7 @@ histfile_backup (const char *filename, const char *back) #endif return (history_rename (filename, back)); } +#endif /* Restore ORIG from BACKUP handling case where ORIG is a symlink (e.g., ~/.bash_history -> .histfiles/.bash_history.$HOSTNAME) */ @@ -708,13 +727,13 @@ static int history_write_slow (int fd, HIST_ENTRY **the_history, int nelements, int overwrite) { FILE *fp; - int i, j, e; + int i, e; fp = fdopen (fd, overwrite ? "w" : "a"); if (fp == 0) return -1; - for (j = 0, i = history_length - nelements; i < history_length; i++) + for (i = history_length - nelements; i < history_length; i++) { if (history_write_timestamps && the_history[i]->timestamp && the_history[i]->timestamp[0]) fprintf (fp, "%s\n", the_history[i]->timestamp); diff --git a/history.c b/history.c index aade5e3..2d0400b 100644 --- a/history.c +++ b/history.c @@ -202,7 +202,7 @@ using_history (void) int history_total_bytes (void) { - register int i, result; + int i, result; for (i = result = 0; the_history && the_history[i]; i++) result += HISTENT_BYTES (the_history[i]); @@ -545,7 +545,7 @@ void _hs_replace_history_data (int which, histdata_t *old, histdata_t *new) { HIST_ENTRY *entry; - register int i, last; + int i, last; if (which < -2 || which >= history_length || history_length == 0 || the_history == 0) return; @@ -581,7 +581,7 @@ _hs_replace_history_data (int which, histdata_t *old, histdata_t *new) int _hs_search_history_data (histdata_t *needle) { - register int i; + int i; HIST_ENTRY *entry; if (history_length == 0 || the_history == 0) @@ -605,10 +605,11 @@ HIST_ENTRY * remove_history (int which) { HIST_ENTRY *return_value; - register int i; #if 1 int nentries; HIST_ENTRY **start, **end; +#else + int i; #endif if (which < 0 || which >= history_length || history_length == 0 || the_history == 0) diff --git a/input.c b/input.c index 4eefb27..e6a39e2 100644 --- a/input.c +++ b/input.c @@ -921,7 +921,7 @@ rl_getc (FILE *stream) /* fprintf(stderr, "rl_getc: result = %d errno = %d\n", result, errno); */ -handle_error: + /* Handle errors here. */ osig = _rl_caught_signal; ostate = rl_readline_state; diff --git a/readline.c b/readline.c index 03ab24a..e77f89d 100644 --- a/readline.c +++ b/readline.c @@ -1366,6 +1366,7 @@ readline_default_bindings (void) rl_tty_set_default_bindings (_rl_keymap); } +#if defined (DEBUG) /* Reset the default bindings for the terminal special characters we're interested in back to rl_insert and read the new ones. */ static void @@ -1377,6 +1378,7 @@ reset_default_bindings (void) rl_tty_set_default_bindings (_rl_keymap); } } +#endif /* Bind some common arrow key sequences in MAP. */ static void diff --git a/search.c b/search.c index 8036175..a7ab947 100644 --- a/search.c +++ b/search.c @@ -590,7 +590,6 @@ rl_history_search_internal (int count, int dir) { HIST_ENTRY *temp; int ret, oldpos, newcol; - char *t; oldpos = where_history (); /* where are we now? */ temp = (HIST_ENTRY *)NULL; @@ -650,6 +649,7 @@ rl_history_search_internal (int count, int dir) else { #if 0 + char *t; t = strstr (rl_line_buffer, history_search_string); /* XXX */ rl_point = t ? (int)(t - rl_line_buffer) + _rl_history_search_len : rl_end; #else diff --git a/shell.c b/shell.c index 7c0f955..6f943de 100644 --- a/shell.c +++ b/shell.c @@ -118,8 +118,10 @@ sh_single_quote (char *string) /* Set the environment variables LINES and COLUMNS to lines and cols, respectively. */ static char setenv_buf[INT_STRLEN_BOUND (int) + 1]; +#if defined (HAVE_PUTENV) && !defined (HAVE_SETENV) static char putenv_buf1[INT_STRLEN_BOUND (int) + 6 + 1]; /* sizeof("LINES=") == 6 */ static char putenv_buf2[INT_STRLEN_BOUND (int) + 8 + 1]; /* sizeof("COLUMNS=") == 8 */ +#endif void sh_set_lines_and_columns (int lines, int cols) diff --git a/terminal.c b/terminal.c index 2c70553..e644e09 100644 --- a/terminal.c +++ b/terminal.c @@ -102,12 +102,20 @@ static char *term_string_buffer = (char *)NULL; static int tcap_initialized; -#if !defined (__linux__) && !defined (NCURSES_VERSION) -# if defined (__EMX__) || defined (NEED_EXTERN_PC) +/* Systems for which PC/BC/UP are defined in the curses library and need an + extern definition here. */ +#if !defined (__linux__) && !defined (__gnu_hurd__) && !defined (NCURSES_VERSION) +# define NEED_EXTERN_PC +#endif /* !__linux__ && !__gnu_hurd__ && !NCURSES_VERSION */ + +#if defined (__EMX__) +# define NEED_EXTERN_PC +#endif + +#if defined (NEED_EXTERN_PC) extern -# endif /* __EMX__ || NEED_EXTERN_PC */ +# endif /* NEED_EXTERN_PC */ char PC, *BC, *UP; -#endif /* !__linux__ && !NCURSES_VERSION */ /* Some strings to control terminal actions. These are output by tputs (). */ char *_rl_term_clreol; diff --git a/util.c b/util.c index 0a5df9b..908a05f 100644 --- a/util.c +++ b/util.c @@ -292,7 +292,7 @@ _rl_strpbrk (const char *string1, const char *string2) register const char *scan; #if defined (HANDLE_MULTIBYTE) mbstate_t ps; - register int i, v; + int v; memset (&ps, 0, sizeof (mbstate_t)); #endif -- 2.47.2