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

Reply via email to