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; }