Merge branch 'bw/forking-and-threading'
The "run-command" API implementation has been made more robust against dead-locking in a threaded environment. * bw/forking-and-threading: usage.c: drop set_error_handle() run-command: restrict PATH search to executable files run-command: expose is_executable function run-command: block signals between fork and execve run-command: add note about forking and threading run-command: handle dup2 and close errors in child run-command: eliminate calls to error handling functions in child run-command: don't die in child when duping /dev/null run-command: prepare child environment before forking string-list: add string_list_remove function run-command: use the async-signal-safe execv instead of execvp run-command: prepare command before forking t0061: run_command executes scripts without a #! line t5550: use write_script to generate post-update hookmaint
commit
7d5e13f652
|
|
@ -450,7 +450,6 @@ extern void (*get_error_routine(void))(const char *err, va_list params);
|
|||
extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
|
||||
extern void (*get_warn_routine(void))(const char *warn, va_list params);
|
||||
extern void set_die_is_recursing_routine(int (*routine)(void));
|
||||
extern void set_error_handle(FILE *);
|
||||
|
||||
extern int starts_with(const char *str, const char *prefix);
|
||||
|
||||
|
|
|
|||
43
help.c
43
help.c
|
|
@ -1,6 +1,7 @@
|
|||
#include "cache.h"
|
||||
#include "builtin.h"
|
||||
#include "exec_cmd.h"
|
||||
#include "run-command.h"
|
||||
#include "levenshtein.h"
|
||||
#include "help.h"
|
||||
#include "common-cmds.h"
|
||||
|
|
@ -96,48 +97,6 @@ static void pretty_print_cmdnames(struct cmdnames *cmds, unsigned int colopts)
|
|||
string_list_clear(&list, 0);
|
||||
}
|
||||
|
||||
static int is_executable(const char *name)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (stat(name, &st) || /* stat, not lstat */
|
||||
!S_ISREG(st.st_mode))
|
||||
return 0;
|
||||
|
||||
#if defined(GIT_WINDOWS_NATIVE)
|
||||
/*
|
||||
* On Windows there is no executable bit. The file extension
|
||||
* indicates whether it can be run as an executable, and Git
|
||||
* has special-handling to detect scripts and launch them
|
||||
* through the indicated script interpreter. We test for the
|
||||
* file extension first because virus scanners may make
|
||||
* it quite expensive to open many files.
|
||||
*/
|
||||
if (ends_with(name, ".exe"))
|
||||
return S_IXUSR;
|
||||
|
||||
{
|
||||
/*
|
||||
* Now that we know it does not have an executable extension,
|
||||
* peek into the file instead.
|
||||
*/
|
||||
char buf[3] = { 0 };
|
||||
int n;
|
||||
int fd = open(name, O_RDONLY);
|
||||
st.st_mode &= ~S_IXUSR;
|
||||
if (fd >= 0) {
|
||||
n = read(fd, buf, 2);
|
||||
if (n == 2)
|
||||
/* look for a she-bang */
|
||||
if (!strcmp(buf, "#!"))
|
||||
st.st_mode |= S_IXUSR;
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return st.st_mode & S_IXUSR;
|
||||
}
|
||||
|
||||
static void list_commands_in_dir(struct cmdnames *cmds,
|
||||
const char *path,
|
||||
const char *prefix)
|
||||
|
|
|
|||
461
run-command.c
461
run-command.c
|
|
@ -117,18 +117,65 @@ static inline void close_pair(int fd[2])
|
|||
close(fd[1]);
|
||||
}
|
||||
|
||||
#ifndef GIT_WINDOWS_NATIVE
|
||||
static inline void dup_devnull(int to)
|
||||
int is_executable(const char *name)
|
||||
{
|
||||
int fd = open("/dev/null", O_RDWR);
|
||||
if (fd < 0)
|
||||
die_errno(_("open /dev/null failed"));
|
||||
if (dup2(fd, to) < 0)
|
||||
die_errno(_("dup2(%d,%d) failed"), fd, to);
|
||||
close(fd);
|
||||
struct stat st;
|
||||
|
||||
if (stat(name, &st) || /* stat, not lstat */
|
||||
!S_ISREG(st.st_mode))
|
||||
return 0;
|
||||
|
||||
#if defined(GIT_WINDOWS_NATIVE)
|
||||
/*
|
||||
* On Windows there is no executable bit. The file extension
|
||||
* indicates whether it can be run as an executable, and Git
|
||||
* has special-handling to detect scripts and launch them
|
||||
* through the indicated script interpreter. We test for the
|
||||
* file extension first because virus scanners may make
|
||||
* it quite expensive to open many files.
|
||||
*/
|
||||
if (ends_with(name, ".exe"))
|
||||
return S_IXUSR;
|
||||
|
||||
{
|
||||
/*
|
||||
* Now that we know it does not have an executable extension,
|
||||
* peek into the file instead.
|
||||
*/
|
||||
char buf[3] = { 0 };
|
||||
int n;
|
||||
int fd = open(name, O_RDONLY);
|
||||
st.st_mode &= ~S_IXUSR;
|
||||
if (fd >= 0) {
|
||||
n = read(fd, buf, 2);
|
||||
if (n == 2)
|
||||
/* look for a she-bang */
|
||||
if (!strcmp(buf, "#!"))
|
||||
st.st_mode |= S_IXUSR;
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return st.st_mode & S_IXUSR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Search $PATH for a command. This emulates the path search that
|
||||
* execvp would perform, without actually executing the command so it
|
||||
* can be used before fork() to prepare to run a command using
|
||||
* execve() or after execvp() to diagnose why it failed.
|
||||
*
|
||||
* The caller should ensure that file contains no directory
|
||||
* separators.
|
||||
*
|
||||
* Returns the path to the command, as found in $PATH or NULL if the
|
||||
* command could not be found. The caller inherits ownership of the memory
|
||||
* used to store the resultant path.
|
||||
*
|
||||
* This should not be used on Windows, where the $PATH search rules
|
||||
* are more complicated (e.g., a search for "foo" should find
|
||||
* "foo.exe").
|
||||
*/
|
||||
static char *locate_in_PATH(const char *file)
|
||||
{
|
||||
const char *p = getenv("PATH");
|
||||
|
|
@ -149,7 +196,7 @@ static char *locate_in_PATH(const char *file)
|
|||
}
|
||||
strbuf_addstr(&buf, file);
|
||||
|
||||
if (!access(buf.buf, F_OK))
|
||||
if (is_executable(buf.buf))
|
||||
return strbuf_detach(&buf, NULL);
|
||||
|
||||
if (!*end)
|
||||
|
|
@ -220,32 +267,249 @@ static const char **prepare_shell_cmd(struct argv_array *out, const char **argv)
|
|||
return out->argv;
|
||||
}
|
||||
|
||||
#ifndef GIT_WINDOWS_NATIVE
|
||||
static int execv_shell_cmd(const char **argv)
|
||||
{
|
||||
struct argv_array nargv = ARGV_ARRAY_INIT;
|
||||
prepare_shell_cmd(&nargv, argv);
|
||||
trace_argv_printf(nargv.argv, "trace: exec:");
|
||||
sane_execvp(nargv.argv[0], (char **)nargv.argv);
|
||||
argv_array_clear(&nargv);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef GIT_WINDOWS_NATIVE
|
||||
static int child_notifier = -1;
|
||||
|
||||
static void notify_parent(void)
|
||||
enum child_errcode {
|
||||
CHILD_ERR_CHDIR,
|
||||
CHILD_ERR_DUP2,
|
||||
CHILD_ERR_CLOSE,
|
||||
CHILD_ERR_SIGPROCMASK,
|
||||
CHILD_ERR_ENOENT,
|
||||
CHILD_ERR_SILENT,
|
||||
CHILD_ERR_ERRNO
|
||||
};
|
||||
|
||||
struct child_err {
|
||||
enum child_errcode err;
|
||||
int syserr; /* errno */
|
||||
};
|
||||
|
||||
static void child_die(enum child_errcode err)
|
||||
{
|
||||
struct child_err buf;
|
||||
|
||||
buf.err = err;
|
||||
buf.syserr = errno;
|
||||
|
||||
/* write(2) on buf smaller than PIPE_BUF (min 512) is atomic: */
|
||||
xwrite(child_notifier, &buf, sizeof(buf));
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
static void child_dup2(int fd, int to)
|
||||
{
|
||||
if (dup2(fd, to) < 0)
|
||||
child_die(CHILD_ERR_DUP2);
|
||||
}
|
||||
|
||||
static void child_close(int fd)
|
||||
{
|
||||
if (close(fd))
|
||||
child_die(CHILD_ERR_CLOSE);
|
||||
}
|
||||
|
||||
static void child_close_pair(int fd[2])
|
||||
{
|
||||
child_close(fd[0]);
|
||||
child_close(fd[1]);
|
||||
}
|
||||
|
||||
/*
|
||||
* parent will make it look like the child spewed a fatal error and died
|
||||
* this is needed to prevent changes to t0061.
|
||||
*/
|
||||
static void fake_fatal(const char *err, va_list params)
|
||||
{
|
||||
vreportf("fatal: ", err, params);
|
||||
}
|
||||
|
||||
static void child_error_fn(const char *err, va_list params)
|
||||
{
|
||||
const char msg[] = "error() should not be called in child\n";
|
||||
xwrite(2, msg, sizeof(msg) - 1);
|
||||
}
|
||||
|
||||
static void child_warn_fn(const char *err, va_list params)
|
||||
{
|
||||
const char msg[] = "warn() should not be called in child\n";
|
||||
xwrite(2, msg, sizeof(msg) - 1);
|
||||
}
|
||||
|
||||
static void NORETURN child_die_fn(const char *err, va_list params)
|
||||
{
|
||||
const char msg[] = "die() should not be called in child\n";
|
||||
xwrite(2, msg, sizeof(msg) - 1);
|
||||
_exit(2);
|
||||
}
|
||||
|
||||
/* this runs in the parent process */
|
||||
static void child_err_spew(struct child_process *cmd, struct child_err *cerr)
|
||||
{
|
||||
static void (*old_errfn)(const char *err, va_list params);
|
||||
|
||||
old_errfn = get_error_routine();
|
||||
set_error_routine(fake_fatal);
|
||||
errno = cerr->syserr;
|
||||
|
||||
switch (cerr->err) {
|
||||
case CHILD_ERR_CHDIR:
|
||||
error_errno("exec '%s': cd to '%s' failed",
|
||||
cmd->argv[0], cmd->dir);
|
||||
break;
|
||||
case CHILD_ERR_DUP2:
|
||||
error_errno("dup2() in child failed");
|
||||
break;
|
||||
case CHILD_ERR_CLOSE:
|
||||
error_errno("close() in child failed");
|
||||
break;
|
||||
case CHILD_ERR_SIGPROCMASK:
|
||||
error_errno("sigprocmask failed restoring signals");
|
||||
break;
|
||||
case CHILD_ERR_ENOENT:
|
||||
error_errno("cannot run %s", cmd->argv[0]);
|
||||
break;
|
||||
case CHILD_ERR_SILENT:
|
||||
break;
|
||||
case CHILD_ERR_ERRNO:
|
||||
error_errno("cannot exec '%s'", cmd->argv[0]);
|
||||
break;
|
||||
}
|
||||
set_error_routine(old_errfn);
|
||||
}
|
||||
|
||||
static void prepare_cmd(struct argv_array *out, const struct child_process *cmd)
|
||||
{
|
||||
if (!cmd->argv[0])
|
||||
die("BUG: command is empty");
|
||||
|
||||
/*
|
||||
* execvp failed. If possible, we'd like to let start_command
|
||||
* know, so failures like ENOENT can be handled right away; but
|
||||
* otherwise, finish_command will still report the error.
|
||||
* Add SHELL_PATH so in the event exec fails with ENOEXEC we can
|
||||
* attempt to interpret the command with 'sh'.
|
||||
*/
|
||||
xwrite(child_notifier, "", 1);
|
||||
argv_array_push(out, SHELL_PATH);
|
||||
|
||||
if (cmd->git_cmd) {
|
||||
argv_array_push(out, "git");
|
||||
argv_array_pushv(out, cmd->argv);
|
||||
} else if (cmd->use_shell) {
|
||||
prepare_shell_cmd(out, cmd->argv);
|
||||
} else {
|
||||
argv_array_pushv(out, cmd->argv);
|
||||
}
|
||||
|
||||
/*
|
||||
* If there are no '/' characters in the command then perform a path
|
||||
* lookup and use the resolved path as the command to exec. If there
|
||||
* are no '/' characters or if the command wasn't found in the path,
|
||||
* have exec attempt to invoke the command directly.
|
||||
*/
|
||||
if (!strchr(out->argv[1], '/')) {
|
||||
char *program = locate_in_PATH(out->argv[1]);
|
||||
if (program) {
|
||||
free((char *)out->argv[1]);
|
||||
out->argv[1] = program;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static char **prep_childenv(const char *const *deltaenv)
|
||||
{
|
||||
extern char **environ;
|
||||
char **childenv;
|
||||
struct string_list env = STRING_LIST_INIT_DUP;
|
||||
struct strbuf key = STRBUF_INIT;
|
||||
const char *const *p;
|
||||
int i;
|
||||
|
||||
/* Construct a sorted string list consisting of the current environ */
|
||||
for (p = (const char *const *) environ; p && *p; p++) {
|
||||
const char *equals = strchr(*p, '=');
|
||||
|
||||
if (equals) {
|
||||
strbuf_reset(&key);
|
||||
strbuf_add(&key, *p, equals - *p);
|
||||
string_list_append(&env, key.buf)->util = (void *) *p;
|
||||
} else {
|
||||
string_list_append(&env, *p)->util = (void *) *p;
|
||||
}
|
||||
}
|
||||
string_list_sort(&env);
|
||||
|
||||
/* Merge in 'deltaenv' with the current environ */
|
||||
for (p = deltaenv; p && *p; p++) {
|
||||
const char *equals = strchr(*p, '=');
|
||||
|
||||
if (equals) {
|
||||
/* ('key=value'), insert or replace entry */
|
||||
strbuf_reset(&key);
|
||||
strbuf_add(&key, *p, equals - *p);
|
||||
string_list_insert(&env, key.buf)->util = (void *) *p;
|
||||
} else {
|
||||
/* otherwise ('key') remove existing entry */
|
||||
string_list_remove(&env, *p, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Create an array of 'char *' to be used as the childenv */
|
||||
childenv = xmalloc((env.nr + 1) * sizeof(char *));
|
||||
for (i = 0; i < env.nr; i++)
|
||||
childenv[i] = env.items[i].util;
|
||||
childenv[env.nr] = NULL;
|
||||
|
||||
string_list_clear(&env, 0);
|
||||
strbuf_release(&key);
|
||||
return childenv;
|
||||
}
|
||||
|
||||
struct atfork_state {
|
||||
#ifndef NO_PTHREADS
|
||||
int cs;
|
||||
#endif
|
||||
sigset_t old;
|
||||
};
|
||||
|
||||
#ifndef NO_PTHREADS
|
||||
static void bug_die(int err, const char *msg)
|
||||
{
|
||||
if (err) {
|
||||
errno = err;
|
||||
die_errno("BUG: %s", msg);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void atfork_prepare(struct atfork_state *as)
|
||||
{
|
||||
sigset_t all;
|
||||
|
||||
if (sigfillset(&all))
|
||||
die_errno("sigfillset");
|
||||
#ifdef NO_PTHREADS
|
||||
if (sigprocmask(SIG_SETMASK, &all, &as->old))
|
||||
die_errno("sigprocmask");
|
||||
#else
|
||||
bug_die(pthread_sigmask(SIG_SETMASK, &all, &as->old),
|
||||
"blocking all signals");
|
||||
bug_die(pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &as->cs),
|
||||
"disabling cancellation");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void atfork_parent(struct atfork_state *as)
|
||||
{
|
||||
#ifdef NO_PTHREADS
|
||||
if (sigprocmask(SIG_SETMASK, &as->old, NULL))
|
||||
die_errno("sigprocmask");
|
||||
#else
|
||||
bug_die(pthread_setcancelstate(as->cs, NULL),
|
||||
"re-enabling cancellation");
|
||||
bug_die(pthread_sigmask(SIG_SETMASK, &as->old, NULL),
|
||||
"restoring signal mask");
|
||||
#endif
|
||||
}
|
||||
#endif /* GIT_WINDOWS_NATIVE */
|
||||
|
||||
static inline void set_cloexec(int fd)
|
||||
{
|
||||
int flags = fcntl(fd, F_GETFD);
|
||||
|
|
@ -281,13 +545,6 @@ static int wait_or_whine(pid_t pid, const char *argv0, int in_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;
|
||||
}
|
||||
} else {
|
||||
error("waitpid is confused (%s)", argv0);
|
||||
}
|
||||
|
|
@ -372,109 +629,149 @@ fail_pipe:
|
|||
#ifndef GIT_WINDOWS_NATIVE
|
||||
{
|
||||
int notify_pipe[2];
|
||||
int null_fd = -1;
|
||||
char **childenv;
|
||||
struct argv_array argv = ARGV_ARRAY_INIT;
|
||||
struct child_err cerr;
|
||||
struct atfork_state as;
|
||||
|
||||
if (pipe(notify_pipe))
|
||||
notify_pipe[0] = notify_pipe[1] = -1;
|
||||
|
||||
if (cmd->no_stdin || cmd->no_stdout || cmd->no_stderr) {
|
||||
null_fd = open("/dev/null", O_RDWR | O_CLOEXEC);
|
||||
if (null_fd < 0)
|
||||
die_errno(_("open /dev/null failed"));
|
||||
set_cloexec(null_fd);
|
||||
}
|
||||
|
||||
prepare_cmd(&argv, cmd);
|
||||
childenv = prep_childenv(cmd->env);
|
||||
atfork_prepare(&as);
|
||||
|
||||
/*
|
||||
* NOTE: In order to prevent deadlocking when using threads special
|
||||
* care should be taken with the function calls made in between the
|
||||
* fork() and exec() calls. No calls should be made to functions which
|
||||
* require acquiring a lock (e.g. malloc) as the lock could have been
|
||||
* held by another thread at the time of forking, causing the lock to
|
||||
* never be released in the child process. This means only
|
||||
* Async-Signal-Safe functions are permitted in the child.
|
||||
*/
|
||||
cmd->pid = fork();
|
||||
failed_errno = errno;
|
||||
if (!cmd->pid) {
|
||||
int sig;
|
||||
/*
|
||||
* 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.
|
||||
* Ensure the default die/error/warn routines do not get
|
||||
* called, they can take stdio locks and malloc.
|
||||
*/
|
||||
if (cmd->no_stderr || need_err) {
|
||||
int child_err = dup(2);
|
||||
set_cloexec(child_err);
|
||||
set_error_handle(fdopen(child_err, "w"));
|
||||
}
|
||||
set_die_routine(child_die_fn);
|
||||
set_error_routine(child_error_fn);
|
||||
set_warn_routine(child_warn_fn);
|
||||
|
||||
close(notify_pipe[0]);
|
||||
set_cloexec(notify_pipe[1]);
|
||||
child_notifier = notify_pipe[1];
|
||||
atexit(notify_parent);
|
||||
|
||||
if (cmd->no_stdin)
|
||||
dup_devnull(0);
|
||||
child_dup2(null_fd, 0);
|
||||
else if (need_in) {
|
||||
dup2(fdin[0], 0);
|
||||
close_pair(fdin);
|
||||
child_dup2(fdin[0], 0);
|
||||
child_close_pair(fdin);
|
||||
} else if (cmd->in) {
|
||||
dup2(cmd->in, 0);
|
||||
close(cmd->in);
|
||||
child_dup2(cmd->in, 0);
|
||||
child_close(cmd->in);
|
||||