From: Tudor Bosman Date: Sat, 3 Nov 2012 00:04:57 +0000 (-0700) Subject: Retry wait() on EINTR; clean up signal handling X-Git-Tag: v0.22.0~1156 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=dcbf91499be96d53467edd3e27485a1fba467291;p=folly.git Retry wait() on EINTR; clean up signal handling Test Plan: subprocess_test Reviewed By: delong.j@fb.com FB internal diff: D619713 --- diff --git a/folly/Subprocess.cpp b/folly/Subprocess.cpp index 034165c3..009c5c0e 100644 --- a/folly/Subprocess.cpp +++ b/folly/Subprocess.cpp @@ -128,6 +128,11 @@ void checkUnixError(ssize_t ret, const char* msg) { throwSystemError(msg); } } +void checkUnixError(ssize_t ret, int savedErrno, const char* msg) { + if (ret == -1) { + throwSystemError(savedErrno, msg); + } +} // Check a wait() status, throw on non-successful void checkStatus(ProcessReturnCode returnCode) { @@ -288,15 +293,53 @@ void Subprocess::spawn( envVec = environ; } + // Block all signals around vfork; see http://ewontfix.com/7/. + // + // As the child may run in the same address space as the parent until + // the actual execve() system call, any (custom) signal handlers that + // the parent has might alter parent's memory if invoked in the child, + // with undefined results. So we block all signals in the parent before + // vfork(), which will cause them to be blocked in the child as well (we + // rely on the fact that Linux, just like all sane implementations, only + // clones the calling thread). Then, in the child, we reset all signals + // to their default dispositions (while still blocked), and unblock them + // (so the exec()ed process inherits the parent's signal mask) + // + // The parent also unblocks all signals as soon as vfork() returns. + sigset_t allBlocked; + int r = ::sigfillset(&allBlocked); + checkUnixError(r, "sigfillset"); + sigset_t oldSignals; + r = pthread_sigmask(SIG_SETMASK, &allBlocked, &oldSignals); + checkPosixError(r, "pthread_sigmask"); + pid_t pid = vfork(); if (pid == 0) { + // While all signals are blocked, we must reset their + // dispositions to default. + for (int sig = 1; sig < NSIG; ++sig) { + ::signal(sig, SIG_DFL); + } + // Unblock signals; restore signal mask. + int r = pthread_sigmask(SIG_SETMASK, &oldSignals, nullptr); + if (r != 0) abort(); + runChild(executable, argVec, envVec, options); // This should never return, but there's nothing else we can do here. abort(); } + // In parent. We want to restore the signal mask even if vfork fails, + // so we'll save errno here, restore the signal mask, and only then + // throw. + int savedErrno = errno; - // In parent - checkUnixError(pid, "vfork"); + // Restore signal mask; do this even if vfork fails! + // We only check for errors from pthread_sigmask after we recorded state + // that the child is alive, so we know to reap it. + r = pthread_sigmask(SIG_SETMASK, &oldSignals, nullptr); + checkUnixError(pid, savedErrno, "vfork"); + + // Child is alive pid_ = pid; returnCode_ = ProcessReturnCode(RV_RUNNING); @@ -304,6 +347,8 @@ void Subprocess::spawn( for (int f : childFds) { closeChecked(f); } + + checkPosixError(r, "pthread_sigmask"); } namespace { @@ -389,8 +434,12 @@ ProcessReturnCode Subprocess::wait() { returnCode_.enforce(ProcessReturnCode::RUNNING); DCHECK_GT(pid_, 0); int status; - pid_t found = ::waitpid(pid_, &status, 0); + pid_t found; + do { + found = ::waitpid(pid_, &status, 0); + } while (found == -1 && errno == EINTR); checkUnixError(found, "waitpid"); + DCHECK_EQ(found, pid_); returnCode_ = ProcessReturnCode(status); return returnCode_; }