From: Chet Ramey Date: Fri, 1 Aug 2025 20:26:31 +0000 (-0400) Subject: history library can now read history from non-regular files; fix for readline char... X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c1d9c088531eef0797e78c66b899d895862de80c;p=thirdparty%2Fbash.git history library can now read history from non-regular files; fix for readline char search and macros; better fix for PROMPT_COMMAND and aliases ending in newlines; fix for casting COMMAND * and SIMPLE_COM * when parsing |&; fix to avoid undefined behavior when performing left and right arithmetic shifts --- diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index 6252eb41..bf82f2a3 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -11417,3 +11417,67 @@ builtins/getopt.c,builtins/getopt.h builtins/getopts.def - dogetopts: call sh_getopt_reset when binding the name variable fails for some reason + + 7/18 + ---- +lib/readline/text.c + - _rl_char_search: make sure that character search arguments are + added to any macro currently being defined + Report from A4-Tacks + +lib/readline/histfile.c + - read_history_slow: new function that just reads the history file in + 4096-byte chunks until EOF, used when the history file isn't a + regular file. + - read_history_range: use read_history_slow when the history file isn't + a regular file + Suggested by several, most recently by in 2/25 + + 7/19 + ---- +parse.y,input.h,eval.c,array.h + - revert changes from 7/6 based on report from + Sam James + +parse.y + - exec_restore_parser_state: version of restore_parser_state that + doesn't restore the pushed string list, since some calls to + parse_and_execute may modify it out from underneath us + - parse_comsub: use exec_restore_parser_state instead of inline code + (the version in parse_compound_assignment is a little more complex) + - parser_unset_string_list: extern function to set pushed_string_list + to NULL from outside parse.y + - execute_variable_command: set pushed_string_list to NULL after + saving the parser state, since we don't want parse_and_execute to + pop a string off the list that was there before it was called + Fixes bug reported by Carl Johnson + +shell.h + - exec_restore_parser_state: extern declaration + - parser_unset_string_list: extern declaration + +trap.c + - _run_trap_internal: use parser_unset_string_list after saving + parser_state to fix bug with DEBUG trap if it's invoked during + an alias ending with a newline (similar to issue from 7/6) + - run_pending_traps: call parser_unset_string_list as above + + 7/21 + ---- +bashline.c + - bash_execute_unix_command,edit_and_execute_command: use + parser_unset_string_list after saving parser_state; executing a + command while readline is active shouldn't modify any existing + parser state + + 7/25 + ---- +command.h + - revert changes to COMMAND and SIMPLE_COM from 7/16 + From a report by Jessica Clarke + +expr.c + - expassign,expshift: avoid C23 undefined behavior when performing + left and right arithmetic shifts + From https://savannah.gnu.org/patch/?10532 + bkallus diff --git a/array.h b/array.h index 169584a5..47712e88 100644 --- a/array.h +++ b/array.h @@ -177,6 +177,6 @@ extern arrayind_t element_back (ARRAY *, arrayind_t); #define ALL_ELEMENT_SUB(c) ((c) == '@' || (c) == '*') /* In eval.c, but uses ARRAY * */ -extern int execute_array_command (ARRAY *, void *, int); +extern int execute_array_command (ARRAY *, void *); #endif /* _ARRAY_H_ */ diff --git a/bashline.c b/bashline.c index 776fe64b..a980718f 100644 --- a/bashline.c +++ b/bashline.c @@ -984,6 +984,7 @@ edit_and_execute_command (int count, int c, int editing_mode, const char *edit_c (*rl_deprep_term_function) (); rl_clear_signals (); save_parser_state (&ps); + parser_unset_string_list (); r = parse_and_execute (command, (editing_mode == VI_EDITING_MODE) ? "v" : "C-xC-e", SEVAL_NOHIST); restore_parser_state (&ps); @@ -4680,6 +4681,7 @@ bash_execute_unix_command (int count, int key) begin_unwind_frame ("execute-unix-command"); save_parser_state (&ps); + parser_unset_string_list (); rl_clear_signals (); add_unwind_protect (uw_unbind_readline_variables, 0); add_unwind_protect (uw_restore_parser_state, &ps); diff --git a/command.h b/command.h index 2c25b341..189b00de 100644 --- a/command.h +++ b/command.h @@ -195,6 +195,7 @@ typedef struct element { /* What a command looks like. */ typedef struct command { + enum command_type type; /* FOR CASE WHILE IF CONNECTION or SIMPLE. */ int flags; /* Flags controlling execution environment. */ int line; /* line number the command starts on */ REDIRECT *redirects; /* Special redirects for FOR CASE, etc. */ @@ -222,7 +223,6 @@ typedef struct command { struct subshell_com *Subshell; struct coproc_com *Coproc; } value; - enum command_type type; /* FOR CASE WHILE IF CONNECTION SIMPLE, etc. */ } COMMAND; /* Structure used to represent the CONNECTION type. */ @@ -337,9 +337,9 @@ typedef struct cond_com { typedef struct simple_com { int flags; /* See description of CMD flags. */ int line; /* line number the command starts on */ - REDIRECT *redirects; /* Redirections to perform. */ WORD_LIST *words; /* The program name, the arguments, variable assignments, etc. */ + REDIRECT *redirects; /* Redirections to perform. */ } SIMPLE_COM; /* The "function definition" command. */ diff --git a/configure b/configure index a7aa8ba5..ccc7ea18 100755 --- a/configure +++ b/configure @@ -12749,7 +12749,8 @@ else case e in #( # defined sleep(n) _sleep ((n) * 1000) #endif #include - GL_MDA_DEFINES + + #ifndef O_NOATIME #define O_NOATIME 0 #endif diff --git a/eval.c b/eval.c index e0a5da96..bbf5d88b 100644 --- a/eval.c +++ b/eval.c @@ -283,7 +283,7 @@ send_pwd_to_eterm (void) #if defined (ARRAY_VARS) /* Caller ensures that A has a non-zero number of elements */ int -execute_array_command (ARRAY *a, void *v, int flags) +execute_array_command (ARRAY *a, void *v) { char *tag; char **argv; @@ -295,7 +295,7 @@ execute_array_command (ARRAY *a, void *v, int flags) for (i = 0; i < argc; i++) { if (argv[i] && argv[i][0]) - execute_variable_command (argv[i], tag, flags); + execute_variable_command (argv[i], tag); } strvec_dispose (argv); return 0; @@ -318,7 +318,7 @@ execute_prompt_command (void) if (array_p (pcv)) { if ((pcmds = array_cell (pcv)) && array_num_elements (pcmds) > 0) - execute_array_command (pcmds, "PROMPT_COMMAND", 0); + execute_array_command (pcmds, "PROMPT_COMMAND"); return; } else if (assoc_p (pcv)) @@ -327,7 +327,7 @@ execute_prompt_command (void) command_to_execute = value_cell (pcv); if (command_to_execute && *command_to_execute) - execute_variable_command (command_to_execute, "PROMPT_COMMAND", 0); + execute_variable_command (command_to_execute, "PROMPT_COMMAND"); } /* Call the YACC-generated parser and return the status of the parse. diff --git a/examples/loadables/strptime.c b/examples/loadables/strptime.c index 1857f9ed..bcd67555 100644 --- a/examples/loadables/strptime.c +++ b/examples/loadables/strptime.c @@ -188,7 +188,8 @@ strptime_builtin (WORD_LIST *list) { char *s; struct tm t, *tm; - time_t now, secs; + time_t now; + intmax_t secs; char *datestr, *format; int i, opt; @@ -227,7 +228,7 @@ strptime_builtin (WORD_LIST *list) if (STREQ (datestr, date_time_modifiers[i].shorthand)) { secs = now + date_time_modifiers[i].incr; - printf ("%ld\n", secs); + printf ("%jd\n", secs); return (EXECUTION_SUCCESS); } } @@ -265,7 +266,7 @@ strptime_builtin (WORD_LIST *list) if (s && *s) builtin_warning("%s: not completely converted (%s)", datestr, s); - printf ("%ld\n", secs); + printf ("%jd\n", secs); return (EXECUTION_SUCCESS); } diff --git a/expr.c b/expr.c index c1f2e33e..22a3cacf 100644 --- a/expr.c +++ b/expr.c @@ -145,10 +145,6 @@ lowest precedence. */ #define EXP_LOWEST expcomma -#ifndef MAX_INT_LEN -# define MAX_INT_LEN 32 -#endif - struct lvalue { char *tokstr; /* possibly-rewritten lvalue if not NULL */ @@ -581,10 +577,10 @@ expassign (void) lvalue -= value; break; case LSH: - lvalue <<= value; + lvalue = (uintmax_t)lvalue << (value & (TYPE_WIDTH(uintmax_t) - 1)); break; case RSH: - lvalue >>= value; + lvalue >>= (value & (TYPE_WIDTH(uintmax_t) - 1)); break; case BAND: lvalue &= value; @@ -841,7 +837,7 @@ expcompare (void) static intmax_t expshift (void) { - register intmax_t val1, val2; + intmax_t val1, val2; val1 = expaddsub (); @@ -853,9 +849,9 @@ expshift (void) val2 = expaddsub (); if (op == LSH) - val1 = val1 << val2; + val1 = (uintmax_t)val1 << (val2 & (TYPE_WIDTH(uintmax_t) - 1)); else - val1 = val1 >> val2; + val1 = val1 >> (val2 & (TYPE_WIDTH(uintmax_t) - 1)); lasttok = NUM; } diff --git a/input.h b/input.h index f0c2cfc4..592b6cb9 100644 --- a/input.h +++ b/input.h @@ -99,7 +99,7 @@ extern int stream_on_stack (enum stream_type); extern char *read_secondary_line (int); extern int find_reserved_word (const char *); extern void gather_here_documents (void); -extern void execute_variable_command (const char *, const char *, int); +extern void execute_variable_command (const char *, const char *); extern int *save_token_state (void); extern void restore_token_state (int *); diff --git a/lib/readline/histfile.c b/lib/readline/histfile.c index 11bdd84b..235243c3 100644 --- a/lib/readline/histfile.c +++ b/lib/readline/histfile.c @@ -58,6 +58,7 @@ # include #endif +#include #include #if defined (__EMX__) @@ -145,6 +146,9 @@ static int histfile_backup (const char *, const char *); static int histfile_restore (const char *, const char *); static int history_rename (const char *, const char *); +static int history_write_slow (int, HIST_ENTRY **, int, int); +static ssize_t history_read_slow (int, char **); + /* Return the string that should be used in the place of this filename. This only matters when you don't specify the filename to read_history (), or write_history (). */ @@ -258,6 +262,69 @@ read_history (const char *filename) return (read_history_range (filename, 0, -1)); } +#define RBUFSIZE 4096 + +/* Read from a non-regular file until EOF, assuming we can't trust the file + size as reported by fstat. */ +static ssize_t +history_read_slow (int fd, char **bufp) +{ + char *ret, *r; + size_t retsize, retlen; + char rbuf[RBUFSIZE]; + ssize_t nr, nw; + + if (bufp == 0) + return -1; + + retsize = RBUFSIZE; + ret = malloc(retsize); + if (ret == 0) + return -1; + retlen = 0; + + while (nr = read (fd, rbuf, sizeof (rbuf))) + { + if (nr < 0) + { + free (ret); + *bufp = NULL; + return -1; + } + + if (retlen >= retsize - nr - 1) + { + retsize *= 2; + r = realloc (ret, retsize); + if (r == 0) + { + free(ret); + *bufp = NULL; + return -1; + } + ret = r; + } + memcpy (ret + retlen, rbuf, nr); + retlen += nr; + } + if (retlen + 1 >= retsize) + { + retsize += 1; + r = realloc (ret, retsize); + if (r == 0) + { + free (ret); + *bufp = NULL; + return -1; + } + ret = r; + } + ret[retlen] = '\0'; + + *bufp = ret; + return (ssize_t)retlen; +} + /* Read a range of lines from FILENAME, adding them to the history list. Start reading at the FROM'th line and end at the TO'th. If FROM is zero, start at the beginning. If TO is less than FROM, read @@ -294,12 +361,15 @@ read_history_range (const char *filename, int from, int to) if (S_ISREG (finfo.st_mode) == 0) { -#ifdef EFTYPE - errno = EFTYPE; -#else - errno = EINVAL; -#endif - goto error_and_exit; + chars_read = history_read_slow (file, &buffer); + if (chars_read == 0) + { + free (buffer); + free (input); + close (file); + return 0; + } + goto after_file_read; } else { @@ -341,6 +411,8 @@ read_history_range (const char *filename, int from, int to) chars_read = read (file, buffer, file_size); #endif + +after_file_read: if (chars_read < 0) { error_and_exit: diff --git a/lib/readline/misc.c b/lib/readline/misc.c index 49ed74b9..34c22b8d 100644 --- a/lib/readline/misc.c +++ b/lib/readline/misc.c @@ -137,6 +137,7 @@ _rl_arg_dispatch (_rl_arg_cxt cxt, int c) else { key = _rl_bracketed_read_key (); + /* XXX - add to macro def? */ rl_restore_prompt (); rl_clear_message (); RL_UNSETSTATE(RL_STATE_NUMERICARG); diff --git a/lib/readline/text.c b/lib/readline/text.c index e8d4f5f6..d8425896 100644 --- a/lib/readline/text.c +++ b/lib/readline/text.c @@ -1786,13 +1786,17 @@ static int _rl_char_search (int count, int fdir, int bdir) { char mbchar[MB_LEN_MAX]; - int mb_len; + int mb_len, i; mb_len = _rl_read_mbchar (mbchar, MB_LEN_MAX); if (mb_len <= 0) return 1; + if (RL_ISSTATE (RL_STATE_MACRODEF)) + for (i = 0; i < mb_len; i++) + _rl_add_macro_char (mbchar[i]); + if (count < 0) return (_rl_char_search_internal (-count, bdir, mbchar, mb_len)); else @@ -1805,9 +1809,13 @@ _rl_char_search (int count, int fdir, int bdir) int c; c = _rl_bracketed_read_key (); + if (c < 0) return 1; + if (RL_ISSTATE (RL_STATE_MACRODEF)) + _rl_add_macro_char (c); + if (count < 0) return (_rl_char_search_internal (-count, bdir, c)); else diff --git a/m4/fcntl-o.m4 b/m4/fcntl-o.m4 index 4dcde9e0..ff98244b 100644 --- a/m4/fcntl-o.m4 +++ b/m4/fcntl-o.m4 @@ -32,7 +32,8 @@ AC_DEFUN([gl_FCNTL_O_FLAGS], # defined sleep(n) _sleep ((n) * 1000) #endif #include - ]GL_MDA_DEFINES[ + ] + [ #ifndef O_NOATIME #define O_NOATIME 0 #endif diff --git a/parse.y b/parse.y index 0aa8377a..462953b7 100644 --- a/parse.y +++ b/parse.y @@ -1473,23 +1473,22 @@ pipeline: pipeline '|' newline_list pipeline | pipeline BAR_AND newline_list pipeline { /* Make cmd1 |& cmd2 equivalent to cmd1 2>&1 | cmd2 */ - COMMAND *tc; REDIRECTEE rd, sd; - REDIRECT *r; + REDIRECT *r, **rp; - tc = $1->type == cm_simple ? (COMMAND *)$1->value.Simple : $1; + rp = $1->type == cm_simple ? &$1->value.Simple->redirects : &$1->redirects; sd.dest = 2; rd.dest = 1; r = make_redirection (sd, r_duplicating_output, rd, 0); - if (tc->redirects) + if (*rp) { register REDIRECT *t; - for (t = tc->redirects; t->next; t = t->next) + for (t = *rp; t->next; t = t->next) ; t->next = r; } else - tc->redirects = r; + *rp = r; $$ = command_connect ($1, $4, '|'); } @@ -2166,6 +2165,12 @@ parser_restore_alias (void) #endif } +void +parser_unset_string_list (void) +{ + pushed_string_list = (STRING_SAVER *)NULL; +} + #if defined (ALIAS) /* Before freeing AP, make sure that there aren't any cases of pointer aliasing that could cause us to reference freed memory later on. */ @@ -3008,19 +3013,18 @@ discard_until (int character) } void -execute_variable_command (const char *command, const char *vname, int flags) +execute_variable_command (const char *command, const char *vname) { char *last_lastarg; sh_parser_state_t ps; - if (flags) - save_parser_state (&ps); + save_parser_state (&ps); + pushed_string_list = (STRING_SAVER *)NULL; last_lastarg = save_lastarg (); parse_and_execute (savestring (command), vname, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOOPTIMIZE|SEVAL_NOTIFY); - if (flags) - restore_parser_state (&ps); + restore_parser_state (&ps); bind_lastarg (last_lastarg); FREE (last_lastarg); @@ -4459,7 +4463,6 @@ parse_comsub (int qc, int open, int close, size_t *lenp, int flags) char *ret, *tcmd; size_t retlen; sh_parser_state_t ps; - STRING_SAVER *saved_strings; COMMAND *saved_global, *parsed_command; /* Posix interp 217 says arithmetic expressions have precedence, so @@ -4625,9 +4628,7 @@ INTERNAL_DEBUG(("current_token (%d) != shell_eof_token (%c)", current_token, she /* We don't want to restore the old pushed string list, since we might have used it to consume additional input from an alias while parsing this command substitution. */ - saved_strings = pushed_string_list; - restore_parser_state (&ps); - pushed_string_list = saved_strings; + exec_restore_parser_state (&ps); simplecmd_lineno = save_lineno; @@ -7355,6 +7356,20 @@ uw_restore_parser_state (void *ps) restore_parser_state (ps); } +/* Special version of restore parser state for cases where we called + parse_and_execute(), which may have modified the pushed string list + out from underneath us. We may need to use this in other places, + like running traps while processing aliases. */ +void +exec_restore_parser_state (sh_parser_state_t *ps) +{ + STRING_SAVER *ss; + + ss = pushed_string_list; + restore_parser_state (ps); + pushed_string_list = ss; +} + /* Free the parts of a parser state struct that have allocated memory. */ void flush_parser_state (sh_parser_state_t *ps) diff --git a/shell.h b/shell.h index 74d6fd97..f8f81879 100644 --- a/shell.h +++ b/shell.h @@ -247,6 +247,8 @@ extern sh_parser_state_t *save_parser_state (sh_parser_state_t *); extern void restore_parser_state (sh_parser_state_t *); extern void flush_parser_state (sh_parser_state_t *); extern void uw_restore_parser_state (void *); +extern void exec_restore_parser_state (sh_parser_state_t *); +extern void parser_unset_string_list (void); extern sh_input_line_state_t *save_input_line_state (sh_input_line_state_t *); extern void restore_input_line_state (sh_input_line_state_t *); diff --git a/tests/new-exp2.sub b/tests/new-exp2.sub index 8dfe788f..b4a52817 100644 --- a/tests/new-exp2.sub +++ b/tests/new-exp2.sub @@ -17,7 +17,7 @@ export LANG=C # test out the new $(< filename) code # it should be exactly equivalent to $(cat filename) -FILENAME=$TMPDIR/bashtmp.x$$ +FILENAME=$TMPDIR/bashtmp.x$$ ; rm -f $TMPDIR/bashtmp.x* trap 'rm -f $FILENAME' 0 diff --git a/tests/redir.right b/tests/redir.right index c5d3779c..b3f1ba28 100644 --- a/tests/redir.right +++ b/tests/redir.right @@ -156,6 +156,8 @@ bix () echo foo 2>&1 | cat } foo +foo +foo :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 1 7 diff --git a/tests/redir9.sub b/tests/redir9.sub index 9050a94f..4c13a7ce 100644 --- a/tests/redir9.sub +++ b/tests/redir9.sub @@ -61,3 +61,7 @@ echo foo |& cat type bix bix + +echo foo |& tee $TMPDIR/bar +cat $TMPDIR/bar +rm -f $TMPDIR/bar diff --git a/trap.c b/trap.c index 1b3365f4..aba08fea 100644 --- a/trap.c +++ b/trap.c @@ -467,6 +467,7 @@ run_pending_traps (void) trap_command = savestring (old_trap); save_parser_state (&pstate); + parser_unset_string_list (); save_subst_varlist = subst_assign_varlist; subst_assign_varlist = 0; save_tempenv = temporary_env; @@ -1160,6 +1161,8 @@ _run_trap_internal (int sig, char *tag) #endif save_parser_state (&pstate); + parser_unset_string_list (); + save_subst_varlist = subst_assign_varlist; subst_assign_varlist = 0; save_tempenv = temporary_env;