Skip to content

Commit 96bfb2d

Browse files
peffgitster
authored andcommitted
run-command: unify signal and regular logic for wait_or_whine()
Since 507d780 (pager: don't use unsafe functions in signal handlers, 2015-09-04), we have a separate code path in wait_or_whine() for the case that we're in a signal handler. But that code path misses some of the cases handled by the main logic. This was improved in be8fc53 (pager: properly log pager exit code when signalled, 2021-02-02), but that covered only case: actually returning the correct error code. But there are some other cases: - if waitpid() returns failure, we wouldn't notice and would look at uninitialized garbage in the status variable; it's not clear if it's possible to trigger this or not - if the process exited by signal, then we would still report "-1" rather than the correct signal code This latter case even had a test added in be8fc53, but it doesn't work reliably. It sets the pager command to: >pager-used; test-tool sigchain The latter command will die by signal, but because there are multiple commands, there will be a shell in between. And it's the shell whose waitpid() call will see the signal death, and it will then exit with code 143, which is what Git will see. To make matters even more confusing, some shells (such as bash) will realize that there's nothing for the shell to do after test-tool finishes, and will turn it into an exec. So the test was only checking what it thought when /bin/sh points to a shell like bash (we're relying on the shell used internally by Git to spawn sub-commands here, so even running the test under bash would not be enough). This patch adjusts the tests to explicitly call "exec" in the pager command, which produces a consistent outcome regardless of shell. Note that without the code change in this patch it _should_ fail reliably, but doesn't. That test, like its siblings, tries to trigger SIGPIPE in the git-log process writing to the pager, but only do so racily. That will be fixed in a follow-on patch. For the code change here, we have two options: - we can teach the in_signal code to handle WIFSIGNALED() - we can stop returning early when in_signal is set, and instead annotate individual calls that we need to skip in this case The former is a simpler patch, but means we're essentially duplicating all of the logic. So instead I went with the latter. The result is a bigger patch, and we do run the risk of new code being added but forgetting to handle in_signal. But in the long run it seems more maintainable. I've skipped any non-trivial calls for the in_signal case, like calling error(). We'll also skip the call to clear_child_for_cleanup(), as we were before. This is arguably the wrong thing to do, since we wouldn't want to try to clean it up again. But: - we can't call it as-is, because it calls free(), which we must avoid in a signal handler (we'd have to pass in_signal so it can skip the free() call) - we'll only go through the list of children to clean once, since our cleanup_children_on_signal() handler pops itself after running (and then re-raises, so eventually we'd just exit). So this cleanup only matters if a process is on the cleanup list _and_ it has a separate handler to clean itself up. Which is questionable in the first place (and AFAIK we do not do). - double-cleanup isn't actually that bad anyway. waitpid() will just return an error, which we won't even report because of in_signal. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent be8fc53 commit 96bfb2d

File tree

2 files changed

+10
-11
lines changed

2 files changed

+10
-11
lines changed

run-command.c

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -551,20 +551,17 @@ static int wait_or_whine(pid_t pid, const char *argv0, int in_signal)
551551

552552
while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
553553
; /* nothing */
554-
if (in_signal) {
555-
if (WIFEXITED(status))
556-
code = WEXITSTATUS(status);
557-
return code;
558-
}
559554

560555
if (waiting < 0) {
561556
failed_errno = errno;
562-
error_errno("waitpid for %s failed", argv0);
557+
if (!in_signal)
558+
error_errno("waitpid for %s failed", argv0);
563559
} else if (waiting != pid) {
564-
error("waitpid is confused (%s)", argv0);
560+
if (!in_signal)
561+
error("waitpid is confused (%s)", argv0);
565562
} else if (WIFSIGNALED(status)) {
566563
code = WTERMSIG(status);
567-
if (code != SIGINT && code != SIGQUIT && code != SIGPIPE)
564+
if (!in_signal && code != SIGINT && code != SIGQUIT && code != SIGPIPE)
568565
error("%s died of signal %d", argv0, code);
569566
/*
570567
* This return value is chosen so that code & 0xff
@@ -575,10 +572,12 @@ static int wait_or_whine(pid_t pid, const char *argv0, int in_signal)
575572
} else if (WIFEXITED(status)) {
576573
code = WEXITSTATUS(status);
577574
} else {
578-
error("waitpid is confused (%s)", argv0);
575+
if (!in_signal)
576+
error("waitpid is confused (%s)", argv0);
579577
}
580578

581-
clear_child_for_cleanup(pid);
579+
if (!in_signal)
580+
clear_child_for_cleanup(pid);
582581

583582
errno = failed_errno;
584583
return code;

t/t7006-pager.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -767,7 +767,7 @@ test_expect_success TTY 'git attempts to page to nonexisting pager command, gets
767767

768768
test_expect_success TTY 'git returns SIGPIPE on propagated signals from pager' '
769769
test_when_finished "rm pager-used trace.normal" &&
770-
test_config core.pager ">pager-used; test-tool sigchain" &&
770+
test_config core.pager ">pager-used; exec test-tool sigchain" &&
771771
GIT_TRACE2="$(pwd)/trace.normal" &&
772772
export GIT_TRACE2 &&
773773
test_when_finished "unset GIT_TRACE2" &&

0 commit comments

Comments
 (0)