@matheusbsbs / matheus-bagliano / commits / 2b541bf8be
Fork 0

start_command: detect execvp failures early

Previously, failures during execvp could be detected only by finish_command. However, in some situations it is beneficial for the parent process to know earlier that the child process will not run. The idea to use a pipe to signal failures to the parent process and the test case were lifted from patches by Ilari Liusvaara. Signed-off-by: Johannes Sixt <j6t@kdbg.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Sixt committed Jan 10, 2010 at 14:11 UTC 2b541bf8be2bbd6cc8daf8e3d5d4a8ee30b2ce4e
4 files changed +96 -1
Makefile
+1
@@ -1785,6 +1785,7 @@ TEST_PROGRAMS += test-genrandom$X
1785 TEST_PROGRAMS += test-match-trees$X
1786 TEST_PROGRAMS += test-parse-options$X
1787 TEST_PROGRAMS += test-path-utils$X
1788 +TEST_PROGRAMS += test-run-command$X
1789 TEST_PROGRAMS += test-sha1$X
1790 TEST_PROGRAMS += test-sigchain$X
1791
run-command.c
+46 -1
@@ -17,6 +17,12 @@ static inline void dup_devnull(int to)
17
18 #ifndef WIN32
19 static int child_err = 2;
20 +static int child_notifier = -1;
21 +
22 +static void notify_parent(void)
23 +{
24 + write(child_notifier, "", 1);
25 +}
26
27 static NORETURN void die_child(const char *err, va_list params)
28 {
@@ -142,6 +148,11 @@ fail_pipe:
148 trace_argv_printf(cmd->argv, "trace: run_command:");
149
150 #ifndef WIN32
151 +{
152 + int notify_pipe[2];
153 + if (pipe(notify_pipe))
154 + notify_pipe[0] = notify_pipe[1] = -1;
155 +
156 fflush(NULL);
157 cmd->pid = fork();
158 if (!cmd->pid) {
@@ -156,6 +167,11 @@ fail_pipe:
167 }
168 set_die_routine(die_child);
169
170 + close(notify_pipe[0]);
171 + set_cloexec(notify_pipe[1]);
172 + child_notifier = notify_pipe[1];
173 + atexit(notify_parent);
174 +
175 if (cmd->no_stdin)
176 dup_devnull(0);
177 else if (need_in) {
@@ -196,8 +212,16 @@ fail_pipe:
212 unsetenv(*cmd->env);
213 }
214 }
199 - if (cmd->preexec_cb)
215 + if (cmd->preexec_cb) {
216 + /*
217 + * We cannot predict what the pre-exec callback does.
218 + * Forgo parent notification.
219 + */
220 + close(child_notifier);
221 + child_notifier = -1;
222 +
223 cmd->preexec_cb();
224 + }
225 if (cmd->git_cmd) {
226 execv_git_cmd(cmd->argv);
227 } else {
@@ -215,6 +239,27 @@ fail_pipe:
239 if (cmd->pid < 0)
240 error("cannot fork() for %s: %s", cmd->argv[0],
241 strerror(failed_errno = errno));
242 +
243 + /*
244 + * Wait for child's execvp. If the execvp succeeds (or if fork()
245 + * failed), EOF is seen immediately by the parent. Otherwise, the
246 + * child process sends a single byte.
247 + * Note that use of this infrastructure is completely advisory,
248 + * therefore, we keep error checks minimal.
249 + */
250 + close(notify_pipe[1]);
251 + if (read(notify_pipe[0], &notify_pipe[1], 1) == 1) {
252 + /*
253 + * At this point we know that fork() succeeded, but execvp()
254 + * failed. Errors have been reported to our stderr.
255 + */
256 + wait_or_whine(cmd->pid, cmd->argv[0],
257 + cmd->silent_exec_failure);
258 + failed_errno = errno;
259 + cmd->pid = -1;
260 + }
261 + close(notify_pipe[0]);
262 +}
263 #else
264 {
265 int s0 = -1, s1 = -1, s2 = -1; /* backups of stdin, stdout, stderr */
t/t0061-run-command.sh new
+14
@@ -0,0 +1,14 @@
1 +#!/bin/sh
2 +#
3 +# Copyright (c) 2009 Ilari Liusvaara
4 +#
5 +
6 +test_description='Test run command'
7 +
8 +. ./test-lib.sh
9 +
10 +test_expect_success 'start_command reports ENOENT' '
11 + test-run-command start-command-ENOENT ./does-not-exist
12 +'
13 +
14 +test_done
test-run-command.c new
+35
@@ -0,0 +1,35 @@
1 +/*
2 + * test-run-command.c: test run command API.
3 + *
4 + * (C) 2009 Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
5 + *
6 + * This code is free software; you can redistribute it and/or modify
7 + * it under the terms of the GNU General Public License version 2 as
8 + * published by the Free Software Foundation.
9 + */
10 +
11 +#include "git-compat-util.h"
12 +#include "run-command.h"
13 +#include <string.h>
14 +#include <errno.h>
15 +
16 +int main(int argc, char **argv)
17 +{
18 + struct child_process proc;
19 +
20 + memset(&proc, 0, sizeof(proc));
21 +
22 + if (argc < 3)
23 + return 1;
24 + proc.argv = (const char **)argv+2;
25 +
26 + if (!strcmp(argv[1], "start-command-ENOENT")) {
27 + if (start_command(&proc) < 0 && errno == ENOENT)
28 + return 0;
29 + fprintf(stderr, "FAIL %s\n", argv[1]);
30 + return 1;
31 + }
32 +
33 + fprintf(stderr, "check usage\n");
34 + return 1;
35 +}