summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--run-command.c68
1 files changed, 68 insertions, 0 deletions
diff --git a/run-command.c b/run-command.c
index df1edd963f..a97d7bf9f3 100644
--- a/run-command.c
+++ b/run-command.c
@@ -215,6 +215,7 @@ enum child_errcode {
CHILD_ERR_CHDIR,
CHILD_ERR_DUP2,
CHILD_ERR_CLOSE,
+ CHILD_ERR_SIGPROCMASK,
CHILD_ERR_ENOENT,
CHILD_ERR_SILENT,
CHILD_ERR_ERRNO
@@ -303,6 +304,9 @@ static void child_err_spew(struct child_process *cmd, struct child_err *cerr)
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;
@@ -398,7 +402,54 @@ static char **prep_childenv(const char *const *deltaenv)
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)
{
@@ -523,6 +574,7 @@ fail_pipe:
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;
@@ -536,6 +588,7 @@ fail_pipe:
prepare_cmd(&argv, cmd);
childenv = prep_childenv(cmd->env);
+ atfork_prepare(&as);
/*
* NOTE: In order to prevent deadlocking when using threads special
@@ -549,6 +602,7 @@ fail_pipe:
cmd->pid = fork();
failed_errno = errno;
if (!cmd->pid) {
+ int sig;
/*
* Ensure the default die/error/warn routines do not get
* called, they can take stdio locks and malloc.
@@ -597,6 +651,19 @@ fail_pipe:
child_die(CHILD_ERR_CHDIR);
/*
+ * restore default signal handlers here, in case
+ * we catch a signal right before execve below
+ */
+ for (sig = 1; sig < NSIG; sig++) {
+ /* ignored signals get reset to SIG_DFL on execve */
+ if (signal(sig, SIG_DFL) == SIG_IGN)
+ signal(sig, SIG_IGN);
+ }
+
+ if (sigprocmask(SIG_SETMASK, &as.old, NULL) != 0)
+ child_die(CHILD_ERR_SIGPROCMASK);
+
+ /*
* Attempt to exec using the command and arguments starting at
* argv.argv[1]. argv.argv[0] contains SHELL_PATH which will
* be used in the event exec failed with ENOEXEC at which point
@@ -616,6 +683,7 @@ fail_pipe:
child_die(CHILD_ERR_ERRNO);
}
}
+ atfork_parent(&as);
if (cmd->pid < 0)
error_errno("cannot fork() for %s", cmd->argv[0]);
else if (cmd->clean_on_exit)