Re: [BUG] precmd not executed after Ctrl-C on some builtin
Mikael Magnusson <[email protected]>
| Newsgroups | gmane.comp.shells.zsh.devel |
|---|---|
| Message-ID | <CAHYJk3T22AQHYnn5oY3ezUAwQzOgpkQnSxqWcTyGDyvn59YtNA@mail.gmail.com> |
On Wed, May 13, 2026 at 1:35 AM Vincent Lefevre <[email protected]> wrote: > > With zsh 5.9 and 5.9.0.3-test (1328291abbb80e90dc4473a4396daffb0e919827), > after interrupting some commands with Ctrl-C, precmd is not executed > before the prompt is displayed: > > qaa% mkfifo fifo > qaa% precmd() { echo precmd > /dev/tty } > precmd > qaa% pwd > /home/vinc17 > precmd > qaa% pwd >> fifo > ^C% > qaa% read > ^C% > precmd > qaa% { pwd >> fifo } > ^C% > precmd > qaa% > > The issue occurs with commands of the form "some_builtin >> fifo". > As seen with "read" (for which the issue does not occur), the issue > is not just that a builtin is blocking. With the following debug print in place, diff --git i/Src/utils.c w/Src/utils.c index 94844e4229..799bb099d1 100644 --- i/Src/utils.c +++ w/Src/utils.c @@ -1575,6 +1575,9 @@ preprompt(void) if (errflag) return; /* If a shell function named "precmd" exists, * * then execute it. */ + dputs("DEBUG: queueing_enabled=%d front=%d rear=%d", + queueing_enabled, queue_front, queue_rear); + callhookfunc("precmd", NULL, 1, NULL); if (errflag) return; We get this output for the above tests: % precmd() { echo precmd > /dev/tty } DEBUG: queueing_enabled=1 front=0 rear=0 precmd % read ^C% DEBUG: queueing_enabled=1 front=0 rear=0 precmd % echo >> fifo ^C% DEBUG: queueing_enabled=1 front=0 rear=1 <- probably not good? % # i just pressed enter here without this comment DEBUG: queueing_enabled=1 front=1 rear=1 precmd We can also notice that if we set a TRAPINT() { true } handler, the precmd runs normally too. In signals.c, we set errflag |= ERRFLAG_INT when there's no trap handler, otherwise we don't. So the theory is that the signal handler runs when we execute the precmd hook, and we abort execution, even though we just checked that errflag is 0 before trying to run it (I manually added it in the context in the diff above). There's a bit in init.c that resets all errors, seen in the patch below, and the patch makes it so that this test works as expected, and the rest of the tests also pass. I'm not totally sure this is the right thing to do though, I usually don't mess with signal handler stuff. % precmd() { echo precmd > /dev/tty } DEBUG: queueing_enabled=1 front=0 rear=0 precmd % read ^C% DEBUG: queueing_enabled=1 front=0 rear=0 precmd % echo >> fifo ^C% DEBUG: queueing_enabled=1 front=1 rear=1 precmd diff --git a/Src/init.c b/Src/init.c index 7c3b824611..a422036c71 100644 --- a/Src/init.c +++ b/Src/init.c @@ -117,35 +117,38 @@ loop(int toplevel, int justonce) queue_signals(); pushheap(); if (!toplevel) zcontext_save(); for (;;) { freeheap(); if (stophist == 3) /* re-entry via preprompt() */ hend(NULL); hbegin(1); /* init history mech */ if (isset(SHINSTDIN)) { setblock_stdin(); if (interact && toplevel) { - int hstop = stophist; + int hstop = stophist, q; stophist = 3; /* * Reset all errors including the interrupt error status * immediately, so preprompt runs regardless of what * just happened. We'll reset again below as a * precaution to ensure we get back to the command line * no matter what. */ + q = queue_signal_level(); + dont_queue_signals(); + restore_queue_signals(q); errflag = 0; preprompt(); if (stophist != 3) hbegin(1); else stophist = hstop; /* * Reset all errors, including user interrupts. * This is what allows ^C in an interactive shell * to return us to the command line. */ errflag = 0; } -- Mikael Magnusson