/*
- * Copyright 2013 Facebook, Inc.
+ * Copyright 2015 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* limitations under the License.
*/
-#include "folly/Subprocess.h"
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <folly/Subprocess.h>
+
+#if __linux__
#include <sys/prctl.h>
+#endif
#include <fcntl.h>
#include <poll.h>
+
#include <unistd.h>
-#include <wait.h>
#include <array>
#include <algorithm>
#include <glog/logging.h>
-#include "folly/Conv.h"
-#include "folly/Exception.h"
-#include "folly/FileUtil.h"
-#include "folly/ScopeGuard.h"
-#include "folly/String.h"
-#include "folly/io/Cursor.h"
+#include <folly/Conv.h>
+#include <folly/Exception.h>
+#include <folly/ScopeGuard.h>
+#include <folly/String.h>
+#include <folly/io/Cursor.h>
extern char** environ;
void ProcessReturnCode::enforce(State expected) const {
State s = state();
if (s != expected) {
- throw std::logic_error(to<std::string>("Invalid state ", s,
- " expected ", expected));
+ throw std::logic_error(to<std::string>(
+ "Bad use of ProcessReturnCode; state is ", s, " expected ", expected
+ ));
}
}
// Copy pointers to the given strings in a format suitable for posix_spawn
std::unique_ptr<const char*[]> cloneStrings(const std::vector<std::string>& s) {
std::unique_ptr<const char*[]> d(new const char*[s.size() + 1]);
- for (int i = 0; i < s.size(); i++) {
+ for (size_t i = 0; i < s.size(); i++) {
d[i] = s[i].c_str();
}
d[s.size()] = nullptr;
int errnoValue;
};
-void childError(int errFd, int errCode, int errnoValue) FOLLY_NORETURN;
+FOLLY_NORETURN void childError(int errFd, int errCode, int errnoValue);
void childError(int errFd, int errCode, int errnoValue) {
ChildErrorInfo info = {errCode, errnoValue};
// Write the error information over the pipe to our parent process.
// Create a pipe to use to receive error information from the child,
// in case it fails before calling exec()
int errFds[2];
- int r = ::pipe(errFds);
- checkUnixError(r, "pipe");
+#if FOLLY_HAVE_PIPE2
+ checkUnixError(::pipe2(errFds, O_CLOEXEC), "pipe2");
+#else
+ checkUnixError(::pipe(errFds), "pipe");
+#endif
SCOPE_EXIT {
CHECK_ERR(::close(errFds[0]));
if (errFds[1] >= 0) {
CHECK_ERR(::close(errFds[1]));
}
};
+
+#if !FOLLY_HAVE_PIPE2
// Ask the child to close the read end of the error pipe.
- options.fdActions_[errFds[0]] = CLOSE;
+ checkUnixError(fcntl(errFds[0], F_SETFD, FD_CLOEXEC), "set FD_CLOEXEC");
// Set the close-on-exec flag on the write side of the pipe.
// This way the pipe will be closed automatically in the child if execve()
// succeeds. If the exec fails the child can write error information to the
// pipe.
- r = fcntl(errFds[1], F_SETFD, FD_CLOEXEC);
- checkUnixError(r, "set FD_CLOEXEC");
+ checkUnixError(fcntl(errFds[1], F_SETFD, FD_CLOEXEC), "set FD_CLOEXEC");
+#endif
// Perform the actual work of setting up pipes then forking and
// executing the child.
for (auto& p : options.fdActions_) {
if (p.second == PIPE_IN || p.second == PIPE_OUT) {
int fds[2];
+ // We're setting both ends of the pipe as close-on-exec. The child
+ // doesn't need to reset the flag on its end, as we always dup2() the fd,
+ // and dup2() fds don't share the close-on-exec flag.
+#if FOLLY_HAVE_PIPE2
+ r = ::pipe2(fds, O_CLOEXEC);
+ checkUnixError(r, "pipe2");
+#else
r = ::pipe(fds);
checkUnixError(r, "pipe");
+ r = fcntl(fds[0], F_SETFD, FD_CLOEXEC);
+ checkUnixError(r, "set FD_CLOEXEC");
+ r = fcntl(fds[1], F_SETFD, FD_CLOEXEC);
+ checkUnixError(r, "set FD_CLOEXEC");
+#endif
PipeInfo pinfo;
pinfo.direction = p.second;
int cfd;
//
// The parent also unblocks all signals as soon as vfork() returns.
sigset_t allBlocked;
- r = ::sigfillset(&allBlocked);
+ r = sigfillset(&allBlocked);
checkUnixError(r, "sigfillset");
sigset_t oldSignals;
CHECK_EQ(r, 0) << "pthread_sigmask: " << errnoStr(r); // shouldn't fail
};
+ // Call c_str() here, as it's not necessarily safe after fork.
+ const char* childDir =
+ options.childDir_.empty() ? nullptr : options.childDir_.c_str();
pid_t pid = vfork();
if (pid == 0) {
- int errnoValue = prepareChild(options, &oldSignals);
+ int errnoValue = prepareChild(options, &oldSignals, childDir);
if (errnoValue != 0) {
childError(errFd, kChildFailure, errnoValue);
}
}
int Subprocess::prepareChild(const Options& options,
- const sigset_t* sigmask) const {
+ const sigset_t* sigmask,
+ const char* childDir) const {
// 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, sigmask, nullptr);
- if (r != 0) {
- return r; // pthread_sigmask() returns an errno value
+
+ {
+ // Unblock signals; restore signal mask.
+ int r = pthread_sigmask(SIG_SETMASK, sigmask, nullptr);
+ if (r != 0) {
+ return r; // pthread_sigmask() returns an errno value
+ }
}
- // Close parent's ends of all pipes
- for (auto& p : pipes_) {
- r = ::close(p.parentFd);
- if (r == -1) {
+ // Change the working directory, if one is given
+ if (childDir) {
+ if (::chdir(childDir) == -1) {
return errno;
}
}
+ // We don't have to explicitly close the parent's end of all pipes,
+ // as they all have the FD_CLOEXEC flag set and will be closed at
+ // exec time.
+
// Close all fds that we're supposed to close.
- // Note that we're ignoring errors here, in case some of these
- // fds were set to close on exec.
for (auto& p : options.fdActions_) {
if (p.second == CLOSE) {
- ::close(p.first);
- } else {
- r = ::dup2(p.second, p.first);
- if (r == -1) {
+ if (::close(p.first) == -1) {
+ return errno;
+ }
+ } else if (p.second != p.first) {
+ if (::dup2(p.second, p.first) == -1) {
return errno;
}
}
}
}
+#if __linux__
// Opt to receive signal on parent death, if requested
if (options.parentDeathSignal_ != 0) {
- r = prctl(PR_SET_PDEATHSIG, options.parentDeathSignal_, 0, 0, 0);
- if (r == -1) {
+ if (prctl(PR_SET_PDEATHSIG, options.parentDeathSignal_, 0, 0, 0) == -1) {
+ return errno;
+ }
+ }
+#endif
+
+ if (options.processGroupLeader_) {
+ if (setpgrp() == -1) {
return errno;
}
}
char** argv, char** env,
const Options& options) const {
// Now, finally, exec.
- int r;
if (options.usePath_) {
::execvp(executable, argv);
} else {
return true; // EOF
}
- ssize_t n;
- do {
- n = ::write(fd, p.first, p.second);
- } while (n == -1 && errno == EINTR);
+ ssize_t n = writeNoInt(fd, p.first, p.second);
if (n == -1 && errno == EAGAIN) {
return false;
}
bool handleRead(int fd, IOBufQueue& queue) {
for (;;) {
auto p = queue.preallocate(100, 65000);
- ssize_t n;
- do {
- n = ::read(fd, p.first, p.second);
- } while (n == -1 && errno == EINTR);
+ ssize_t n = readNoInt(fd, p.first, p.second);
if (n == -1 && errno == EAGAIN) {
return false;
}
static std::unique_ptr<char[]> buf(new char[bufSize]);
for (;;) {
- ssize_t n;
- do {
- n = ::read(fd, buf.get(), bufSize);
- } while (n == -1 && errno == EINTR);
+ ssize_t n = readNoInt(fd, buf.get(), bufSize);
if (n == -1 && errno == EAGAIN) {
return false;
}
pfd.fd = p.parentFd;
// Yes, backwards, PIPE_IN / PIPE_OUT are defined from the
// child's point of view.
- pfd.events = (p.direction == PIPE_IN ? POLLOUT : POLLIN);
+ if (!p.enabled) {
+ // Still keeping fd in watched set so we get notified of POLLHUP /
+ // POLLERR
+ pfd.events = 0;
+ } else if (p.direction == PIPE_IN) {
+ pfd.events = POLLOUT;
+ } else {
+ pfd.events = POLLIN;
+ }
fds.push_back(pfd);
}
} while (r == -1 && errno == EINTR);
checkUnixError(r, "poll");
- for (int i = 0; i < pipes_.size(); ++i) {
+ for (size_t i = 0; i < pipes_.size(); ++i) {
auto& p = pipes_[i];
DCHECK_EQ(fds[i].fd, p.parentFd);
short events = fds[i].revents;
}
}
- if (events & POLLIN) {
+ // Call read callback on POLLHUP, to give it a chance to read (and act
+ // on) end of file
+ if (events & (POLLIN | POLLHUP)) {
DCHECK(!(events & POLLOUT));
if (readCallback(p.parentFd, p.childFd)) {
toClose.push_back(i);
}
}
+void Subprocess::enableNotifications(int childFd, bool enabled) {
+ pipes_[findByChildFd(childFd)].enabled = enabled;
+}
+
+bool Subprocess::notificationsEnabled(int childFd) const {
+ return pipes_[findByChildFd(childFd)].enabled;
+}
+
int Subprocess::findByChildFd(int childFd) const {
auto pos = std::lower_bound(
pipes_.begin(), pipes_.end(), childFd,
} // namespace
} // namespace folly
-