Merge branch 'js/exec-error-report'
* js/exec-error-report: Improve error message when a transport helper was not found start_command: detect execvp failures early run-command: move wait_or_whine earlier start_command: report child process setup errors to the parent's stderr Conflicts: Makefile
This commit is contained in:
1
Makefile
1
Makefile
@ -1778,6 +1778,7 @@ TEST_PROGRAMS_NEED_X += test-genrandom
|
|||||||
TEST_PROGRAMS_NEED_X += test-match-trees
|
TEST_PROGRAMS_NEED_X += test-match-trees
|
||||||
TEST_PROGRAMS_NEED_X += test-parse-options
|
TEST_PROGRAMS_NEED_X += test-parse-options
|
||||||
TEST_PROGRAMS_NEED_X += test-path-utils
|
TEST_PROGRAMS_NEED_X += test-path-utils
|
||||||
|
TEST_PROGRAMS_NEED_X += test-run-command
|
||||||
TEST_PROGRAMS_NEED_X += test-sha1
|
TEST_PROGRAMS_NEED_X += test-sha1
|
||||||
TEST_PROGRAMS_NEED_X += test-sigchain
|
TEST_PROGRAMS_NEED_X += test-sigchain
|
||||||
TEST_PROGRAMS_NEED_X += test-index-version
|
TEST_PROGRAMS_NEED_X += test-index-version
|
||||||
|
175
run-command.c
175
run-command.c
@ -61,6 +61,78 @@ static int execv_shell_cmd(const char **argv)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
static int child_err = 2;
|
||||||
|
static int child_notifier = -1;
|
||||||
|
|
||||||
|
static void notify_parent(void)
|
||||||
|
{
|
||||||
|
write(child_notifier, "", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static NORETURN void die_child(const char *err, va_list params)
|
||||||
|
{
|
||||||
|
char msg[4096];
|
||||||
|
int len = vsnprintf(msg, sizeof(msg), err, params);
|
||||||
|
if (len > sizeof(msg))
|
||||||
|
len = sizeof(msg);
|
||||||
|
|
||||||
|
write(child_err, "fatal: ", 7);
|
||||||
|
write(child_err, msg, len);
|
||||||
|
write(child_err, "\n", 1);
|
||||||
|
exit(128);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void set_cloexec(int fd)
|
||||||
|
{
|
||||||
|
int flags = fcntl(fd, F_GETFD);
|
||||||
|
if (flags >= 0)
|
||||||
|
fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
|
||||||
|
{
|
||||||
|
int status, code = -1;
|
||||||
|
pid_t waiting;
|
||||||
|
int failed_errno = 0;
|
||||||
|
|
||||||
|
while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
|
||||||
|
; /* nothing */
|
||||||
|
|
||||||
|
if (waiting < 0) {
|
||||||
|
failed_errno = errno;
|
||||||
|
error("waitpid for %s failed: %s", argv0, strerror(errno));
|
||||||
|
} else if (waiting != pid) {
|
||||||
|
error("waitpid is confused (%s)", argv0);
|
||||||
|
} else if (WIFSIGNALED(status)) {
|
||||||
|
code = WTERMSIG(status);
|
||||||
|
error("%s died of signal %d", argv0, code);
|
||||||
|
/*
|
||||||
|
* This return value is chosen so that code & 0xff
|
||||||
|
* mimics the exit code that a POSIX shell would report for
|
||||||
|
* a program that died from this signal.
|
||||||
|
*/
|
||||||
|
code -= 128;
|
||||||
|
} else if (WIFEXITED(status)) {
|
||||||
|
code = WEXITSTATUS(status);
|
||||||
|
/*
|
||||||
|
* Convert special exit code when execvp failed.
|
||||||
|
*/
|
||||||
|
if (code == 127) {
|
||||||
|
code = -1;
|
||||||
|
failed_errno = ENOENT;
|
||||||
|
if (!silent_exec_failure)
|
||||||
|
error("cannot run %s: %s", argv0,
|
||||||
|
strerror(ENOENT));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error("waitpid is confused (%s)", argv0);
|
||||||
|
}
|
||||||
|
errno = failed_errno;
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
int start_command(struct child_process *cmd)
|
int start_command(struct child_process *cmd)
|
||||||
{
|
{
|
||||||
int need_in, need_out, need_err;
|
int need_in, need_out, need_err;
|
||||||
@ -122,9 +194,30 @@ fail_pipe:
|
|||||||
trace_argv_printf(cmd->argv, "trace: run_command:");
|
trace_argv_printf(cmd->argv, "trace: run_command:");
|
||||||
|
|
||||||
#ifndef WIN32
|
#ifndef WIN32
|
||||||
|
{
|
||||||
|
int notify_pipe[2];
|
||||||
|
if (pipe(notify_pipe))
|
||||||
|
notify_pipe[0] = notify_pipe[1] = -1;
|
||||||
|
|
||||||
fflush(NULL);
|
fflush(NULL);
|
||||||
cmd->pid = fork();
|
cmd->pid = fork();
|
||||||
if (!cmd->pid) {
|
if (!cmd->pid) {
|
||||||
|
/*
|
||||||
|
* Redirect the channel to write syscall error messages to
|
||||||
|
* before redirecting the process's stderr so that all die()
|
||||||
|
* in subsequent call paths use the parent's stderr.
|
||||||
|
*/
|
||||||
|
if (cmd->no_stderr || need_err) {
|
||||||
|
child_err = dup(2);
|
||||||
|
set_cloexec(child_err);
|
||||||
|
}
|
||||||
|
set_die_routine(die_child);
|
||||||
|
|
||||||
|
close(notify_pipe[0]);
|
||||||
|
set_cloexec(notify_pipe[1]);
|
||||||
|
child_notifier = notify_pipe[1];
|
||||||
|
atexit(notify_parent);
|
||||||
|
|
||||||
if (cmd->no_stdin)
|
if (cmd->no_stdin)
|
||||||
dup_devnull(0);
|
dup_devnull(0);
|
||||||
else if (need_in) {
|
else if (need_in) {
|
||||||
@ -165,8 +258,16 @@ fail_pipe:
|
|||||||
unsetenv(*cmd->env);
|
unsetenv(*cmd->env);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cmd->preexec_cb)
|
if (cmd->preexec_cb) {
|
||||||
|
/*
|
||||||
|
* We cannot predict what the pre-exec callback does.
|
||||||
|
* Forgo parent notification.
|
||||||
|
*/
|
||||||
|
close(child_notifier);
|
||||||
|
child_notifier = -1;
|
||||||
|
|
||||||
cmd->preexec_cb();
|
cmd->preexec_cb();
|
||||||
|
}
|
||||||
if (cmd->git_cmd) {
|
if (cmd->git_cmd) {
|
||||||
execv_git_cmd(cmd->argv);
|
execv_git_cmd(cmd->argv);
|
||||||
} else if (cmd->use_shell) {
|
} else if (cmd->use_shell) {
|
||||||
@ -174,13 +275,39 @@ fail_pipe:
|
|||||||
} else {
|
} else {
|
||||||
execvp(cmd->argv[0], (char *const*) cmd->argv);
|
execvp(cmd->argv[0], (char *const*) cmd->argv);
|
||||||
}
|
}
|
||||||
trace_printf("trace: exec '%s' failed: %s\n", cmd->argv[0],
|
/*
|
||||||
strerror(errno));
|
* Do not check for cmd->silent_exec_failure; the parent
|
||||||
|
* process will check it when it sees this exit code.
|
||||||
|
*/
|
||||||
|
if (errno == ENOENT)
|
||||||
exit(127);
|
exit(127);
|
||||||
|
else
|
||||||
|
die_errno("cannot exec '%s'", cmd->argv[0]);
|
||||||
}
|
}
|
||||||
if (cmd->pid < 0)
|
if (cmd->pid < 0)
|
||||||
error("cannot fork() for %s: %s", cmd->argv[0],
|
error("cannot fork() for %s: %s", cmd->argv[0],
|
||||||
strerror(failed_errno = errno));
|
strerror(failed_errno = errno));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait for child's execvp. If the execvp succeeds (or if fork()
|
||||||
|
* failed), EOF is seen immediately by the parent. Otherwise, the
|
||||||
|
* child process sends a single byte.
|
||||||
|
* Note that use of this infrastructure is completely advisory,
|
||||||
|
* therefore, we keep error checks minimal.
|
||||||
|
*/
|
||||||
|
close(notify_pipe[1]);
|
||||||
|
if (read(notify_pipe[0], ¬ify_pipe[1], 1) == 1) {
|
||||||
|
/*
|
||||||
|
* At this point we know that fork() succeeded, but execvp()
|
||||||
|
* failed. Errors have been reported to our stderr.
|
||||||
|
*/
|
||||||
|
wait_or_whine(cmd->pid, cmd->argv[0],
|
||||||
|
cmd->silent_exec_failure);
|
||||||
|
failed_errno = errno;
|
||||||
|
cmd->pid = -1;
|
||||||
|
}
|
||||||
|
close(notify_pipe[0]);
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
int fhin = 0, fhout = 1, fherr = 2;
|
int fhin = 0, fhout = 1, fherr = 2;
|
||||||
@ -271,48 +398,6 @@ fail_pipe:
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
|
|
||||||
{
|
|
||||||
int status, code = -1;
|
|
||||||
pid_t waiting;
|
|
||||||
int failed_errno = 0;
|
|
||||||
|
|
||||||
while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
|
|
||||||
; /* nothing */
|
|
||||||
|
|
||||||
if (waiting < 0) {
|
|
||||||
failed_errno = errno;
|
|
||||||
error("waitpid for %s failed: %s", argv0, strerror(errno));
|
|
||||||
} else if (waiting != pid) {
|
|
||||||
error("waitpid is confused (%s)", argv0);
|
|
||||||
} else if (WIFSIGNALED(status)) {
|
|
||||||
code = WTERMSIG(status);
|
|
||||||
error("%s died of signal %d", argv0, code);
|
|
||||||
/*
|
|
||||||
* This return value is chosen so that code & 0xff
|
|
||||||
* mimics the exit code that a POSIX shell would report for
|
|
||||||
* a program that died from this signal.
|
|
||||||
*/
|
|
||||||
code -= 128;
|
|
||||||
} else if (WIFEXITED(status)) {
|
|
||||||
code = WEXITSTATUS(status);
|
|
||||||
/*
|
|
||||||
* Convert special exit code when execvp failed.
|
|
||||||
*/
|
|
||||||
if (code == 127) {
|
|
||||||
code = -1;
|
|
||||||
failed_errno = ENOENT;
|
|
||||||
if (!silent_exec_failure)
|
|
||||||
error("cannot run %s: %s", argv0,
|
|
||||||
strerror(ENOENT));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error("waitpid is confused (%s)", argv0);
|
|
||||||
}
|
|
||||||
errno = failed_errno;
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
int finish_command(struct child_process *cmd)
|
int finish_command(struct child_process *cmd)
|
||||||
{
|
{
|
||||||
return wait_or_whine(cmd->pid, cmd->argv[0], cmd->silent_exec_failure);
|
return wait_or_whine(cmd->pid, cmd->argv[0], cmd->silent_exec_failure);
|
||||||
|
14
t/t0061-run-command.sh
Executable file
14
t/t0061-run-command.sh
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (c) 2009 Ilari Liusvaara
|
||||||
|
#
|
||||||
|
|
||||||
|
test_description='Test run command'
|
||||||
|
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
test_expect_success 'start_command reports ENOENT' '
|
||||||
|
test-run-command start-command-ENOENT ./does-not-exist
|
||||||
|
'
|
||||||
|
|
||||||
|
test_done
|
35
test-run-command.c
Normal file
35
test-run-command.c
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* test-run-command.c: test run command API.
|
||||||
|
*
|
||||||
|
* (C) 2009 Ilari Liusvaara <ilari.liusvaara@elisanet.fi>
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 2 as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "git-compat-util.h"
|
||||||
|
#include "run-command.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct child_process proc;
|
||||||
|
|
||||||
|
memset(&proc, 0, sizeof(proc));
|
||||||
|
|
||||||
|
if (argc < 3)
|
||||||
|
return 1;
|
||||||
|
proc.argv = (const char **)argv+2;
|
||||||
|
|
||||||
|
if (!strcmp(argv[1], "start-command-ENOENT")) {
|
||||||
|
if (start_command(&proc) < 0 && errno == ENOENT)
|
||||||
|
return 0;
|
||||||
|
fprintf(stderr, "FAIL %s\n", argv[1]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "check usage\n");
|
||||||
|
return 1;
|
||||||
|
}
|
@ -102,6 +102,7 @@ static struct child_process *get_helper(struct transport *transport)
|
|||||||
int refspec_nr = 0;
|
int refspec_nr = 0;
|
||||||
int refspec_alloc = 0;
|
int refspec_alloc = 0;
|
||||||
int duped;
|
int duped;
|
||||||
|
int code;
|
||||||
|
|
||||||
if (data->helper)
|
if (data->helper)
|
||||||
return data->helper;
|
return data->helper;
|
||||||
@ -111,13 +112,18 @@ static struct child_process *get_helper(struct transport *transport)
|
|||||||
helper->out = -1;
|
helper->out = -1;
|
||||||
helper->err = 0;
|
helper->err = 0;
|
||||||
helper->argv = xcalloc(4, sizeof(*helper->argv));
|
helper->argv = xcalloc(4, sizeof(*helper->argv));
|
||||||
strbuf_addf(&buf, "remote-%s", data->name);
|
strbuf_addf(&buf, "git-remote-%s", data->name);
|
||||||
helper->argv[0] = strbuf_detach(&buf, NULL);
|
helper->argv[0] = strbuf_detach(&buf, NULL);
|
||||||
helper->argv[1] = transport->remote->name;
|
helper->argv[1] = transport->remote->name;
|
||||||
helper->argv[2] = remove_ext_force(transport->url);
|
helper->argv[2] = remove_ext_force(transport->url);
|
||||||
helper->git_cmd = 1;
|
helper->git_cmd = 0;
|
||||||
if (start_command(helper))
|
helper->silent_exec_failure = 1;
|
||||||
die("Unable to run helper: git %s", helper->argv[0]);
|
code = start_command(helper);
|
||||||
|
if (code < 0 && errno == ENOENT)
|
||||||
|
die("Unable to find remote helper for '%s'", data->name);
|
||||||
|
else if (code != 0)
|
||||||
|
exit(code);
|
||||||
|
|
||||||
data->helper = helper;
|
data->helper = helper;
|
||||||
data->no_disconnect_req = 0;
|
data->no_disconnect_req = 0;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user