Hi,
Bug reported by miod@, how to reproduce:

  $ command -v r
  alias r='fc -s'
  $ sleep 5
  $ r sleep
  ^C # abort sleep before finishing
  $ r sleep
  ksh: fc: history function called recursively

The c_fc() function has some internal state used to prevent recursive
invocations that gets out of sync when the re-executed command is
aborted by SIGINT. My proposal is to temporarily register a SIGINT
handler before re-executing the command in order to reset the call depth
counter upon signal delivery.

Comments? OK?

Index: history.c
===================================================================
RCS file: /cvs/src/bin/ksh/history.c,v
retrieving revision 1.80
diff -u -p -r1.80 history.c
--- history.c   15 Jan 2018 22:30:38 -0000      1.80
+++ history.c   28 Oct 2018 19:10:54 -0000
@@ -36,6 +36,8 @@ static char   **hist_get(const char *, i
 static char   **hist_get_oldest(void);
 static void    histbackup(void);
 
+static void    hist_sigint(int);
+
 static FILE    *histfh;
 static char   **histbase;      /* actual start of the history[] allocation */
 static char   **current;       /* current position in history[] */
@@ -46,6 +48,8 @@ static int    ignorespace;    /* ditch lines s
 static Source  *hist_source;
 static uint32_t        line_co;
 
+static volatile sig_atomic_t   fc_depth;
+
 static struct stat last_sb;
 
 int
@@ -58,9 +62,8 @@ c_fc(char **wp)
        int optc, ret;
        char *first = NULL, *last = NULL;
        char **hfirst, **hlast, **hp;
-       static int depth;
 
-       if (depth != 0) {
+       if (fc_depth != 0) {
                bi_errorf("history function called recursively");
                return 1;
        }
@@ -70,6 +73,8 @@ c_fc(char **wp)
                return 1;
        }
 
+       setsig(&sigtraps[SIGINT], hist_sigint, SS_ONESHOT);
+
        while ((optc = ksh_getopt(wp, &builtin_opt,
            "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1)
                switch (optc) {
@@ -146,9 +151,9 @@ c_fc(char **wp)
                    hist_get_newest(false);
                if (!hp)
                        return 1;
-               depth++;
+               fc_depth++;
                ret = hist_replace(hp, pat, rep, gflag);
-               depth--;
+               fc_depth--;
                return ret;
        }
 
@@ -268,9 +273,9 @@ c_fc(char **wp)
                shf_close(shf);
                *xp = '\0';
                strip_nuls(Xstring(xs, xp), Xlength(xs, xp));
-               depth++;
+               fc_depth++;
                ret = hist_execute(Xstring(xs, xp));
-               depth--;
+               fc_depth--;
                return ret;
        }
 }
@@ -852,4 +857,10 @@ void
 hist_finish(void)
 {
        history_close();
+}
+
+static void
+hist_sigint(int signo)
+{
+       fc_depth = 0;
 }
Index: sh.h
===================================================================
RCS file: /cvs/src/bin/ksh/sh.h,v
retrieving revision 1.73
diff -u -p -r1.73 sh.h
--- sh.h        18 May 2018 13:25:20 -0000      1.73
+++ sh.h        28 Oct 2018 19:10:54 -0000
@@ -230,6 +230,7 @@ typedef struct trap {
 #define TF_TTY_INTR    BIT(7)  /* tty generated signal (see j_waitj) */
 #define TF_CHANGED     BIT(8)  /* used by runtrap() to detect trap changes */
 #define TF_FATAL       BIT(9)  /* causes termination if not trapped */
+#define TF_ONESHOT     BIT(10) /* temporary trap handler registered */
 
 /* values for setsig()/setexecsig() flags argument */
 #define SS_RESTORE_MASK        0x3     /* how to restore a signal before an 
exec() */
@@ -240,6 +241,7 @@ typedef struct trap {
 #define SS_FORCE       BIT(3)  /* set signal even if original signal ignored */
 #define SS_USER                BIT(4)  /* user is doing the set (ie, trap 
command) */
 #define SS_SHTRAP      BIT(5)  /* trap for internal use (CHLD,ALRM,WINCH) */
+#define SS_ONESHOT     BIT(6)  /* register temporary trap handler */
 
 #define SIGEXIT_       0       /* for trap EXIT */
 #define SIGERR_                NSIG    /* for trap ERR */
Index: trap.c
===================================================================
RCS file: /cvs/src/bin/ksh/trap.c,v
retrieving revision 1.32
diff -u -p -r1.32 trap.c
--- trap.c      15 Mar 2018 16:51:29 -0000      1.32
+++ trap.c      28 Oct 2018 19:10:54 -0000
@@ -13,6 +13,8 @@
 
 Trap sigtraps[NSIG + 1];
 
+static sig_t   traphandler(Trap *);
+
 static struct sigaction Sigact_ign, Sigact_trap;
 
 void
@@ -117,6 +119,7 @@ void
 trapsig(int i)
 {
        Trap *p = &sigtraps[i];
+       sig_t f;
        int errno_ = errno;
 
        trap = p->set = 1;
@@ -126,8 +129,9 @@ trapsig(int i)
                fatal_trap = 1;
                intrsig = 1;
        }
-       if (p->shtrap)
-               (*p->shtrap)(i);
+       f = traphandler(p);
+       if (f)
+               (*f)(i);
        errno = errno_;
 }
 
@@ -200,10 +204,13 @@ runtraps(int flag)
                intrsig = 0;
        if (flag & TF_FATAL)
                fatal_trap = 0;
-       for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
+       for (p = sigtraps, i = NSIG+1; --i >= 0; p++) {
+               (void)traphandler(p); /* clear one shot handler */
+
                if (p->set && (!flag ||
                    ((p->flags & flag) && p->trap == NULL)))
                        runtrap(p);
+       }
 }
 
 void
@@ -354,6 +361,14 @@ setsig(Trap *p, sig_t f, int flags)
        if (p->signal == SIGEXIT_ || p->signal == SIGERR_)
                return 1;
 
+       if ((flags & SS_ONESHOT) == SS_ONESHOT) {
+               if (p->shtrap != NULL)
+                       return 0;
+               p->flags |= TF_ONESHOT;
+               p->shtrap = f;
+               return 1;
+       }
+
        /* First time setting this signal?  If so, get and note the current
         * setting.
         */
@@ -420,4 +435,21 @@ setexecsig(Trap *p, int restore)
                p->flags |= TF_EXEC_IGN;
                break;
        }
+}
+
+/*
+ * Returns the signal handler associated with the given trap.
+ * If the handler is registered as a one shot, it's disassociated with the 
trap.
+ */
+static sig_t
+traphandler(Trap *p)
+{
+       sig_t f;
+
+       f = p->shtrap;
+       if (p->flags & TF_ONESHOT) {
+               p->flags &= ~TF_ONESHOT;
+               p->shtrap = NULL;
+       }
+       return f;
 }

Reply via email to