Configuration Information [Automatically generated, do not change]: Machine: x86_64 OS: linux-gnu Compiler: gcc Compilation CFLAGS: -O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection uname output: Linux chatoyancy 5.18.17-200.fc36.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Aug 11 14:36:06 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux Machine Type: x86_64-redhat-linux-gnu
Bash Version: 5.1 Patch Level: 16 Release Status: release Description: In Bash versions from 4.3 to the current devel, after calling `trap' for SIGINT in completion functions specified to `complete -F', readline is left in a strange state where C-c does not respond and the first character of the next user command is dropped. This is caused by missing `rl_set_signals ()' after using `trap xxx INT' in the completion functions. Repeat-By: Here is a reduced test case. $ bash --norc $ _cmd0() { trap - INT; } && complete -F _cmd0 cmd0 $ cmd0 a <---- here, press [TAB][C-c][RET] bash: md0: command not found $ In the above case, there is no response to [C-c] although we are expecting [C-c] to print `^C' and clear the command line. Also, the subsequent [RET] causes the execution of the command, but somehow the first character of the command line is missing. We can observe similar behaviors also with the following cases (where the number 1000000 may be adjusted depending on the speed of the machine so that C-c is received within the for loop). $ _cmd1() { trap 'echo INT:$FUNCNAME' INT; for ((i=0;i<1000000;i++)); do true; done; trap - INT; } && complete -F _cmd1 cmd1 $ cmd1 a[TAB][C-c] $ _cmd2() { trap 'echo INT:$FUNCNAME; trap - INT' INT; for ((i=0;i<1000000;i++)); do true; done; trap - INT; } && complete -F _cmd2 cmd2 $ cmd2 a[TAB][C-c] $ _cmd3() { trap 'echo INT:$FUNCNAME; trap - INT; kill -INT $$' INT; for ((i=0;i<1000000;i++)); do true; done; trap - INT; } && complete -F _cmd3 cmd3 $ cmd3 a[TAB][C-c] $ _cmd4() { compgen -F _cmd3 &>/dev/null; } && complete -F _cmd4 cmd4 $ cmd4 a[TAB][C-c] Fix: I attach a patch [0001-fix-rlsig-compfunctrap.patch.txt] that explains an essential part of a possible fix. * I would like to perform `rl_clear_signals ()' and `rl_set_signals ()' only when it is called from the built-in programmable completion directly caused by the user key inputs but not when it is called by `compgen', so we need a reliable way to test it (see the code comment starting with XXX in the patch). The case where `compgen -F xxx 2>/dev/null' is called inside another completion function `yyy' with `complete -F yyy' should also be taken care of (cf the test case of cmd4). I excluded a concrete implementation for the test from the above patch because this is not an essential part of the fix and also there are multiple possible implementations. I attach an example patch [0002-example-flags.patch.txt] of implementing it by adding a parameter `flags' to `gen_compspec_completions' and `gen_shell_function_matches'. * The change in `pcomplete.c' solves test cases cmd1..cmd3, but test cases cmd3 and cmd4 remain not working. This is because `trap - INT; kill -INT $$' makes the control to ``longjmp'' from `jump_to_top_level () @ sig.c' to the top-level `reader_loop () @ eval.c' without running the cleanup code in `readline () @ lib/readline/readline.c', particularly `rl_clear_signals ()'. Because of this missing call to `rl_clear_signals ()', the next call to `rl_set_signals ()' in `readline ()' does nothing, so the signals for readline are not set up. I initially naively thought we could also set up an unwind frame in `readline ()' but realized that the unwind mechanism is implemented in Bash, so readline cannot rely on it. Instead, the patch clears the readline signals in `reader_loop ()'---the longjmp destination. -- Koichi
From e1b08619bbc0f4249271fc66c4049973b256b58d Mon Sep 17 00:00:00 2001 From: Koichi Murase <myoga.mur...@gmail.com> Date: Thu, 1 Sep 2022 19:04:22 +0900 Subject: [PATCH 1/4] fix readline signal setups after trap INT in compfunc --- eval.c | 8 ++++++++ pcomplete.c | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/eval.c b/eval.c index 17fbf736..c0b45079 100644 --- a/eval.c +++ b/eval.c @@ -48,6 +48,10 @@ # include "bashhist.h" #endif +#if defined (READLINE) +# include <readline/readline.h> +#endif + static void send_pwd_to_eterm PARAMS((void)); static sighandler alrm_catcher PARAMS((int)); @@ -119,6 +123,10 @@ reader_loop () current_command = (COMMAND *)NULL; } +#if defined (READLINE) + if (interactive_shell) + rl_clear_signals (); +#endif restore_sigmask (); break; diff --git a/pcomplete.c b/pcomplete.c index 9612406d..f7b2811c 100644 --- a/pcomplete.c +++ b/pcomplete.c @@ -1123,6 +1123,12 @@ gen_shell_function_matches (cs, cmd, text, line, ind, lwords, nw, cw, foundp) #if defined (ARRAY_VARS) ARRAY *a; #endif + /* XXX - Are there any robust way to test if it is directly invoked for the + programmable completions but not for the compgen builtin (called directly + by users or called while the programmable completions inside another -F)? + Maybe, we can add another parameter, such as FLAGS, to this function and + `gen_compspec_completions ()'? */ + int performed_for_programmable_completions = lwords != NULL; found = 0; if (foundp) @@ -1151,6 +1157,11 @@ gen_shell_function_matches (cs, cmd, text, line, ind, lwords, nw, cw, foundp) pps = &ps; save_parser_state (pps); begin_unwind_frame ("gen-shell-function-matches"); + if (performed_for_programmable_completions) + { + rl_clear_signals (); + add_unwind_protect (rl_set_signals, (char *)NULL); + } add_unwind_protect (restore_parser_state, (char *)pps); add_unwind_protect (dispose_words, (char *)cmdlist); add_unwind_protect (unbind_compfunc_variables, (char *)0); @@ -1158,6 +1169,8 @@ gen_shell_function_matches (cs, cmd, text, line, ind, lwords, nw, cw, foundp) fval = execute_shell_function (f, cmdlist); discard_unwind_frame ("gen-shell-function-matches"); + if (performed_for_programmable_completions) + rl_set_signals (); restore_parser_state (pps); found = fval != EX_NOTFOUND; -- 2.37.2
From 8aa4358d6c296110b5547623f05ef2eb13156b89 Mon Sep 17 00:00:00 2001 From: Koichi Murase <myoga.mur...@gmail.com> Date: Fri, 2 Sep 2022 18:33:51 +0900 Subject: [PATCH 2/4] Example: implement progcomp flag by a new parameter FLAGS --- pcomplete.c | 51 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/pcomplete.c b/pcomplete.c index f7b2811c..d12c0cf3 100644 --- a/pcomplete.c +++ b/pcomplete.c @@ -124,7 +124,7 @@ static STRINGLIST *gen_wordlist_matches PARAMS((COMPSPEC *, const char *)); static STRINGLIST *gen_shell_function_matches PARAMS((COMPSPEC *, const char *, const char *, char *, int, WORD_LIST *, - int, int, int *)); + int, int, int *, int)); static STRINGLIST *gen_command_matches PARAMS((COMPSPEC *, const char *, const char *, char *, int, WORD_LIST *, @@ -135,6 +135,20 @@ static STRINGLIST *gen_progcomp_completions PARAMS((const char *, const char *, int, int, int *, int *, COMPSPEC **)); +/* This flag is supposed to be passed to the parameter, FLAGS, of + `gen_compspec_completions_internal ()' and + `gen_shell_function_matches ()'. This flag is specified when the + compspec completion is directly caused by the programmable + completion system from inside readline. This flag is not specified + when the compspec completion is called externally using the + interface `gen_compspec_completions ()' including the case from the + `compgen' builtin. */ +#define GENCOMPSPEC_PROGCOMP 0x1 +static STRINGLIST *gen_compspec_completions_internal PARAMS((COMPSPEC *, + const char *, + const char *, int, int, + int *, int)); + static char *pcomp_filename_completion_function PARAMS((const char *, int)); #if defined (ARRAY_VARS) @@ -1103,7 +1117,7 @@ build_arg_list (cmd, cname, text, lwords, ind) variable, this does nothing if arrays are not compiled into the shell. */ static STRINGLIST * -gen_shell_function_matches (cs, cmd, text, line, ind, lwords, nw, cw, foundp) +gen_shell_function_matches (cs, cmd, text, line, ind, lwords, nw, cw, foundp, flags) COMPSPEC *cs; const char *cmd; const char *text; @@ -1112,6 +1126,7 @@ gen_shell_function_matches (cs, cmd, text, line, ind, lwords, nw, cw, foundp) WORD_LIST *lwords; int nw, cw; int *foundp; + int flags; { char *funcname; STRINGLIST *sl; @@ -1123,12 +1138,6 @@ gen_shell_function_matches (cs, cmd, text, line, ind, lwords, nw, cw, foundp) #if defined (ARRAY_VARS) ARRAY *a; #endif - /* XXX - Are there any robust way to test if it is directly invoked for the - programmable completions but not for the compgen builtin (called directly - by users or called while the programmable completions inside another -F)? - Maybe, we can add another parameter, such as FLAGS, to this function and - `gen_compspec_completions ()'? */ - int performed_for_programmable_completions = lwords != NULL; found = 0; if (foundp) @@ -1157,7 +1166,7 @@ gen_shell_function_matches (cs, cmd, text, line, ind, lwords, nw, cw, foundp) pps = &ps; save_parser_state (pps); begin_unwind_frame ("gen-shell-function-matches"); - if (performed_for_programmable_completions) + if (flags & GENCOMPSPEC_PROGCOMP) { rl_clear_signals (); add_unwind_protect (rl_set_signals, (char *)NULL); @@ -1169,7 +1178,7 @@ gen_shell_function_matches (cs, cmd, text, line, ind, lwords, nw, cw, foundp) fval = execute_shell_function (f, cmdlist); discard_unwind_frame ("gen-shell-function-matches"); - if (performed_for_programmable_completions) + if (flags & GENCOMPSPEC_PROGCOMP) rl_set_signals (); restore_parser_state (pps); @@ -1332,6 +1341,18 @@ gen_compspec_completions (cs, cmd, word, start, end, foundp) const char *word; int start, end; int *foundp; +{ + return gen_compspec_completions_internal (cs, cmd, word, start, end, foundp, 0); +} + +static STRINGLIST * +gen_compspec_completions_internal (cs, cmd, word, start, end, foundp, flags) + COMPSPEC *cs; + const char *cmd; + const char *word; + int start, end; + int *foundp; + int flags; { STRINGLIST *ret, *tmatches; char *line; @@ -1343,8 +1364,8 @@ gen_compspec_completions (cs, cmd, word, start, end, foundp) found = 1; #ifdef DEBUG - debug_printf ("gen_compspec_completions (%s, %s, %d, %d)", cmd, word, start, end); - debug_printf ("gen_compspec_completions: %s -> %p", cmd, cs); + debug_printf ("gen_compspec_completions_internal (%s, %s, %d, %d)", cmd, word, start, end); + debug_printf ("gen_compspec_completions_internal: %s -> %p", cmd, cs); #endif ret = gen_action_completions (cs, word); #ifdef DEBUG @@ -1435,7 +1456,7 @@ gen_compspec_completions (cs, cmd, word, start, end, foundp) if (cs->funcname) { foundf = 0; - tmatches = gen_shell_function_matches (cs, cmd, word, line, pcomp_ind - start, lwords, nw, cw, &foundf); + tmatches = gen_shell_function_matches (cs, cmd, word, line, pcomp_ind - start, lwords, nw, cw, &foundf, flags); if (foundf != 0) found = foundf; if (tmatches) @@ -1443,7 +1464,7 @@ gen_compspec_completions (cs, cmd, word, start, end, foundp) #ifdef DEBUG if (progcomp_debug) { - debug_printf ("gen_shell_function_matches (%p, %s, %s, %p, %d, %d) -->", cs, cmd, word, lwords, nw, cw); + debug_printf ("gen_shell_function_matches (%p, %s, %s, %p, %d, %d, %d) -->", cs, cmd, word, lwords, nw, cw, flags); strlist_print (tmatches, "\t"); rl_on_new_line (); } @@ -1607,7 +1628,7 @@ gen_progcomp_completions (ocmd, cmd, word, start, end, foundp, retryp, lastcs) pcomp_curcmd = cmd; pcomp_curtxt = word; - ret = gen_compspec_completions (cs, cmd, word, start, end, foundp); + ret = gen_compspec_completions_internal (cs, cmd, word, start, end, foundp, GENCOMPSPEC_PROGCOMP); pcomp_curcs = oldcs; pcomp_curcmd = oldcmd; -- 2.37.2