/*
- * Copyright 2012 Facebook, Inc.
+ * Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include "folly/Subprocess.h"
+#include <sys/prctl.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
#include <glog/logging.h>
#include "folly/Conv.h"
+#include "folly/Exception.h"
#include "folly/ScopeGuard.h"
#include "folly/String.h"
-#include "folly/experimental/io/Cursor.h"
+#include "folly/io/Cursor.h"
extern char** environ;
+constexpr int kExecFailure = 127;
+constexpr int kChildFailure = 126;
+
namespace folly {
ProcessReturnCode::State ProcessReturnCode::state() const {
return d;
}
-// Helper to throw std::system_error
-void throwSystemError(int err, const char* msg) __attribute__((noreturn));
-void throwSystemError(int err, const char* msg) {
- throw std::system_error(err, std::system_category(), msg);
-}
-
-// Helper to throw std::system_error from errno
-void throwSystemError(const char* msg) __attribute__((noreturn));
-void throwSystemError(const char* msg) {
- throwSystemError(errno, msg);
-}
-
-// Check a Posix return code (0 on success, error number on error), throw
-// on error.
-void checkPosixError(int err, const char* msg) {
- if (err != 0) {
- throwSystemError(err, msg);
- }
-}
-
-// Check a traditional Uinx return code (-1 and sets errno on error), throw
-// on error.
-void checkUnixError(ssize_t ret, const char* msg) {
- if (ret == -1) {
- throwSystemError(msg);
- }
-}
-
// Check a wait() status, throw on non-successful
void checkStatus(ProcessReturnCode returnCode) {
if (returnCode.state() != ProcessReturnCode::EXITED ||
}
Subprocess::~Subprocess() {
- if (returnCode_.state() == ProcessReturnCode::RUNNING) {
- LOG(ERROR) << "Subprocess destroyed without reaping; killing child.";
- try {
- kill();
- wait();
- } catch (...) {
- LOG(FATAL) << "Killing child failed, terminating: "
- << exceptionStr(std::current_exception());
- }
- }
- try {
- closeAll();
- } catch (...) {
- LOG(FATAL) << "close failed, terminating: "
- << exceptionStr(std::current_exception());
- }
+ CHECK_NE(returnCode_.state(), ProcessReturnCode::RUNNING)
+ << "Subprocess destroyed without reaping child";
}
namespace {
// Parent work, pre-fork: create pipes
std::vector<int> childFds;
+
+ // If we throw, don't leak file descriptors
+ auto guard = makeGuard([&] {
+ // These are only pipes, closing them shouldn't fail
+ for (int cfd : childFds) {
+ CHECK_ERR(::close(cfd));
+ }
+ for (auto& p : this->pipes_) {
+ CHECK_ERR(::close(p.parentFd));
+ }
+ });
+
for (auto& p : options.fdActions_) {
if (p.second == PIPE_IN || p.second == PIPE_OUT) {
int fds[2];
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) _exit(kChildFailure);
+
runChild(executable, argVec, envVec, options);
// This should never return, but there's nothing else we can do here.
- abort();
+ _exit(kExecFailure);
}
+ // 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;
+
+ // Restore signal mask; do this even if vfork fails!
+ r = pthread_sigmask(SIG_SETMASK, &oldSignals, nullptr);
+ CHECK_EQ(r, 0) << "pthread_sigmask: " << errnoStr(r); // shouldn't fail
+ checkUnixError(pid, savedErrno, "vfork");
- // In parent
- checkUnixError(pid, "vfork");
+ // Child is alive. We can't throw any more, as we can't figure out
+ // what to do with the child.
+ guard.dismiss();
pid_ = pid;
returnCode_ = ProcessReturnCode(RV_RUNNING);
- // Parent work, post-fork: close child's ends of pipes
+ // Parent work, post-fork: close child's ends of pipes; closing them
+ // shouldn't fail.
for (int f : childFds) {
- closeChecked(f);
+ CHECK_ERR(::close(f));
}
}
namespace {
-// Checked version of close() to use in the child: abort() on error
+// Checked version of close() to use in the child: _exit(126) on error
void childClose(int fd) {
int r = ::close(fd);
- if (r == -1) abort();
+ if (r == -1) _exit(kChildFailure);
}
-// Checked version of dup2() to use in the child: abort() on error
+// Checked version of dup2() to use in the child: _exit(126) on error
void childDup2(int oldfd, int newfd) {
int r = ::dup2(oldfd, newfd);
- if (r == -1) abort();
+ if (r == -1) _exit(kChildFailure);
}
} // namespace
}
}
+ // Opt to receive signal on parent death, if requested
+ if (options.parentDeathSignal_ != 0) {
+ int r = prctl(PR_SET_PDEATHSIG, options.parentDeathSignal_, 0, 0, 0);
+ if (r == -1) {
+ _exit(kChildFailure);
+ }
+ }
+
// Now, finally, exec.
int r;
if (options.usePath_) {
} else {
::execve(executable, argv, env);
}
-
- // If we're here, something's wrong.
- abort();
}
ProcessReturnCode Subprocess::poll() {
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);
+ pid_ = -1;
return returnCode_;
}
checkUnixError(r, "kill");
}
-namespace {
-void setNonBlocking(int fd) {
- int flags = ::fcntl(fd, F_GETFL);
- checkUnixError(flags, "fcntl");
- int r = ::fcntl(fd, F_SETFL, flags | O_NONBLOCK);
- checkUnixError(r, "fcntl");
+pid_t Subprocess::pid() const {
+ return pid_;
}
+namespace {
+
std::pair<const uint8_t*, size_t> queueFront(const IOBufQueue& queue) {
auto* p = queue.front();
if (!p) return std::make_pair(nullptr, 0);
} // namespace
std::pair<std::string, std::string> Subprocess::communicate(
- int flags,
+ const CommunicateFlags& flags,
StringPiece data) {
IOBufQueue dataQueue;
dataQueue.wrapBuffer(data.data(), data.size());
}
std::pair<IOBufQueue, IOBufQueue> Subprocess::communicateIOBuf(
- int flags,
+ const CommunicateFlags& flags,
IOBufQueue data) {
std::pair<IOBufQueue, IOBufQueue> out;
- auto readCallback = [&, flags] (int pfd, int cfd) {
- if (cfd == 1 && (flags & READ_STDOUT)) {
+ auto readCallback = [&] (int pfd, int cfd) -> bool {
+ if (cfd == 1 && flags.readStdout_) {
return handleRead(pfd, out.first);
- } else if (cfd == 2 && (flags & READ_STDERR)) {
+ } else if (cfd == 2 && flags.readStderr_) {
return handleRead(pfd, out.second);
} else {
// Don't close the file descriptor, the child might not like SIGPIPE,
}
};
- auto writeCallback = [&, flags] (int pfd, int cfd) {
- if (cfd == 0 && (flags & WRITE_STDIN)) {
+ auto writeCallback = [&] (int pfd, int cfd) -> bool {
+ if (cfd == 0 && flags.writeStdin_) {
return handleWrite(pfd, data);
} else {
// If we don't want to write to this fd, just close it.