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], ¬ify_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
+}