Arquivos
hhvm/hphp/util/light_process.cpp
T
bsimmers 547a45231d Shut down if we lose a LightProcess child
This should only happen if there's a bug in our code and the
child process crashes, or if one gets killed by the OOM killer. In
either case, it's probably not safe for the parent process to continue
uninterrupted, so shut down.
2013-04-12 11:22:44 -07:00

723 linhas
18 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "light_process.h"
#include "process.h"
#include "util.h"
#include "logger.h"
#include <afdt.h>
#include <string>
#include <vector>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <poll.h>
#include <pwd.h>
#include <signal.h>
#include <boost/scoped_array.hpp>
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
// helper functions
static const unsigned int BUFFER_SIZE = 4096;
Mutex LightProcess::s_mutex;
static void read_buf(FILE *fin, char *buf) {
if (!fgets(buf, BUFFER_SIZE, fin)) {
buf[0] = '\0';
return;
}
// get rid of '\n'
buf[strlen(buf) - 1] = '\0';
}
static bool send_fd(int afdt_fd, int fd) {
afdt_error_t err;
errno = 0;
int ret = afdt_send_fd_msg(afdt_fd, 0, 0, fd, &err);
if (ret < 0 && errno == 0) {
// Set non-empty errno if afdt_send_fd_msg doesn't set one on error
errno = EPROTO;
}
return ret >= 0;
}
static int recv_fd(int afdt_fd) {
int fd;
afdt_error_t err;
uint8_t afdt_buf[AFDT_MSGLEN];
uint32_t afdt_len;
errno = 0;
if (afdt_recv_fd_msg(afdt_fd, afdt_buf, &afdt_len, &fd, &err) < 0) {
if (errno == 0) {
// Set non-empty errno if afdt_send_fd_msg doesn't set one on error
errno = EPROTO;
}
return -1;
}
return fd;
}
static char **build_envp(const vector<string> &env) {
char **envp = nullptr;
int size = env.size();
if (size) {
envp = (char **)malloc((size + 1) * sizeof(char *));
int j = 0;
for (unsigned int i = 0; i < env.size(); i++, j++) {
*(envp + j) = (char *)env[i].c_str();
}
*(envp + j) = nullptr;
}
return envp;
}
static void close_fds(const vector<int> &fds) {
for (unsigned int i = 0; i < fds.size(); i++) {
::close(fds[i]);
}
}
///////////////////////////////////////////////////////////////////////////////
// shadow process tasks
static void do_popen(FILE *fin, FILE *fout, int afdt_fd) {
char buf[BUFFER_SIZE];
char cwd[BUFFER_SIZE];
if (!fgets(buf, BUFFER_SIZE, fin)) buf[0] = '\0';
bool read_only = (buf[0] == 'r');
read_buf(fin, buf);
string old_cwd = Process::GetCurrentDirectory();
read_buf(fin, cwd);
if (old_cwd != cwd) {
if (chdir(cwd)) {
// Ignore chdir failures, because the compiled version might not have the
// directory any more.
Logger::Warning("Light Process failed chdir to %s.", cwd);
}
}
FILE *f = buf[0] ? ::popen(buf, read_only ? "r" : "w") : nullptr;
if (old_cwd != cwd && chdir(old_cwd.c_str())) {
// only here if we can't change the cwd back
}
if (f == nullptr) {
Logger::Error("Light process failed popen: %d (%s).", errno,
strerror(errno));
fprintf(fout, "error\n");
fflush(fout);
} else {
fprintf(fout, "success\n%" PRId64 "\n", (int64_t)f);
fflush(fout);
int fd = fileno(f);
send_fd(afdt_fd, fd);
}
}
static void do_pclose(FILE *fin, FILE *fout) {
char buf[BUFFER_SIZE];
int64_t fptr = 0;
read_buf(fin, buf);
sscanf(buf, "%" PRId64, &fptr);
FILE *f = (FILE *)fptr;
int ret = ::pclose(f);
fprintf(fout, "%d\n", ret);
if (ret < 0) {
fprintf(fout, "%d\n", errno);
}
fflush(fout);
}
static void do_proc_open(FILE *fin, FILE *fout, int afdt_fd) {
char cmd[BUFFER_SIZE];
read_buf(fin, cmd);
if (strlen(cmd) == 0) {
fprintf(fout, "error\n%d\n", ENOENT);
fflush(fout);
return;
}
char cwd[BUFFER_SIZE];
read_buf(fin, cwd);
char buf[BUFFER_SIZE];
int env_size = 0;
vector<string> env;
read_buf(fin, buf);
sscanf(buf, "%d", &env_size);
for (int i = 0; i < env_size; i++) {
read_buf(fin, buf);
env.push_back(buf);
}
int pipe_size = 0;
read_buf(fin, buf);
sscanf(buf, "%d", &pipe_size);
vector<int> pvals;
for (int i = 0; i < pipe_size; i++) {
int fd_value;
read_buf(fin, buf);
sscanf(buf, "%d", &fd_value);
pvals.push_back(fd_value);
}
vector<int> pkeys;
for (int i = 0; i < pipe_size; i++) {
int fd = recv_fd(afdt_fd);
if (fd < 0) {
fprintf(fout, "error\n%d\n", EPROTO);
fflush(fout);
close_fds(pkeys);
return;
}
pkeys.push_back(fd);
}
// now ready to start the child process
pid_t child = fork();
if (child == 0) {
for (int i = 0; i < pipe_size; i++) {
dup2(pkeys[i], pvals[i]);
}
if (strlen(cwd) > 0 && chdir(cwd)) {
// non-zero for error
// chdir failed, the working directory remains unchanged
}
if (!env.empty()) {
char **envp = build_envp(env);
execle("/bin/sh", "sh", "-c", cmd, nullptr, envp);
free(envp);
} else {
execl("/bin/sh", "sh", "-c", cmd, nullptr);
}
_exit(127);
} else if (child > 0) {
// successfully created the child process
fprintf(fout, "%" PRId64 "\n", (int64_t)child);
fflush(fout);
} else {
// failed creating the child process
fprintf(fout, "error\n%d\n", errno);
fflush(fout);
}
close_fds(pkeys);
}
static pid_t waited = 0;
static void kill_handler(int sig) {
if (sig == SIGALRM && waited) {
kill(waited, SIGKILL);
}
}
static void do_waitpid(FILE *fin, FILE *fout) {
char buf[BUFFER_SIZE];
read_buf(fin, buf);
int64_t p = -1;
int options = 0;
int timeout = 0;
sscanf(buf, "%" PRId64 " %d %d", &p, &options, &timeout);
pid_t pid = (pid_t)p;
int stat;
if (timeout > 0) {
waited = pid;
signal(SIGALRM, kill_handler);
alarm(timeout);
}
pid_t ret = ::waitpid(pid, &stat, options);
alarm(0); // cancel the previous alarm if not triggered yet
waited = 0;
fprintf(fout, "%" PRId64 " %d\n", (int64_t)ret, stat);
if (ret < 0) {
fprintf(fout, "%d\n", errno);
}
fflush(fout);
}
static void do_change_user(FILE *fin, FILE *fout) {
char uname[BUFFER_SIZE];
read_buf(fin, uname);
if (strlen(uname) > 0) {
struct passwd *pw = getpwnam(uname);
if (pw) {
if (pw->pw_gid) {
setgid(pw->pw_gid);
}
if (pw->pw_uid) {
setuid(pw->pw_uid);
}
}
}
}
///////////////////////////////////////////////////////////////////////////////
// light-weight process
static boost::scoped_array<LightProcess> g_procs;
static int g_procsCount = 0;
static bool s_handlerInited = false;
static LightProcess::LostChildHandler s_lostChildHandler;
LightProcess::LightProcess()
: m_shadowProcess(0), m_fin(nullptr), m_fout(nullptr), m_afdt_fd(-1),
m_afdt_lfd(-1) { }
LightProcess::~LightProcess() {
}
void LightProcess::SigChldHandler(int sig, siginfo_t* info, void* ctx) {
if (info->si_code != CLD_EXITED &&
info->si_code != CLD_KILLED &&
info->si_code != CLD_DUMPED) {
return;
}
pid_t pid = info->si_pid;
for (int i = 0; i < g_procsCount; ++i) {
if (g_procs[i].m_shadowProcess == pid) {
// The exited process was a light process. Notify the callback, if any.
if (s_lostChildHandler) {
s_lostChildHandler(pid);
}
break;
}
}
}
void LightProcess::Initialize(const std::string &prefix, int count,
const vector<int> &inherited_fds) {
if (prefix.empty() || count <= 0) {
return;
}
if (Available()) {
// already initialized
return;
}
if (!s_handlerInited) {
struct sigaction sa;
struct sigaction old_sa;
sa.sa_sigaction = &LightProcess::SigChldHandler;
sa.sa_flags = SA_SIGINFO | SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &sa, &old_sa) != 0) {
Logger::Error("Couldn't install SIGCHLD handler");
abort();
}
s_handlerInited = true;
}
g_procs.reset(new LightProcess[count]);
g_procsCount = count;
for (int i = 0; i < count; i++) {
if (!g_procs[i].initShadow(prefix, i, inherited_fds)) {
for (int j = 0; j < i; j++) {
g_procs[j].closeShadow();
}
g_procs.reset();
g_procsCount = 0;
break;
}
}
}
bool LightProcess::initShadow(const std::string &prefix, int id,
const vector<int> &inherited_fds) {
Lock lock(m_procMutex);
std::ostringstream os;
os << prefix << "." << getpid() << "." << id;
m_afdtFilename = os.str();
// remove the possible leftover
remove(m_afdtFilename.c_str());
afdt_error_t err;
m_afdt_lfd = afdt_listen(m_afdtFilename.c_str(), &err);
if (m_afdt_lfd < 0) {
Logger::Warning("Unable to afdt_listen");
return false;
}
CPipe p1, p2;
if (!p1.open() || !p2.open()) {
Logger::Warning("Unable to create pipe: %d %s", errno,
Util::safe_strerror(errno).c_str());
return false;
}
pid_t child = fork();
if (child == 0) {
// child
pid_t sid = setsid();
if (sid < 0) {
Logger::Warning("Unable to setsid");
exit(-1);
}
m_afdt_fd =
afdt_connect(m_afdtFilename.c_str(), &err);
if (m_afdt_fd < 0) {
Logger::Warning("Unable to afdt_connect");
exit(-1);
}
int fd1 = p1.detachOut();
int fd2 = p2.detachIn();
p1.close();
p2.close();
// don't hold on to previous light processes' pipes, inherited
// fds, or the afdt listening socket
for (int i = 0; i < id; i++) {
g_procs[i].closeFiles();
}
close_fds(inherited_fds);
::close(m_afdt_lfd);
runShadow(fd1, fd2);
} else if (child < 0) {
// failed
Logger::Warning("Unable to fork lightly: %d %s", errno,
Util::safe_strerror(errno).c_str());
return false;
} else {
// parent
m_fin = fdopen(p2.detachOut(), "r");
m_fout = fdopen(p1.detachIn(), "w");
m_shadowProcess = child;
sockaddr addr;
socklen_t addrlen;
m_afdt_fd = accept(m_afdt_lfd, &addr, &addrlen);
if (m_afdt_fd < 0) {
Logger::Warning("Unable to establish afdt connection");
closeShadow();
return false;
}
}
return true;
}
void LightProcess::Close() {
for (int i = 0; i < g_procsCount; i++) {
g_procs[i].closeShadow();
}
g_procs.reset();
g_procsCount = 0;
}
void LightProcess::closeShadow() {
Lock lock(m_procMutex);
if (m_shadowProcess) {
fprintf(m_fout, "exit\n");
fflush(m_fout);
fclose(m_fin);
fclose(m_fout);
// removes the "zombie" process, so not to interfere with later waits
::waitpid(m_shadowProcess, nullptr, 0);
}
if (!m_afdtFilename.empty()) {
remove(m_afdtFilename.c_str());
}
if (m_afdt_fd >= 0) {
::close(m_afdt_fd);
m_afdt_fd = -1;
}
m_shadowProcess = 0;
}
void LightProcess::closeFiles() {
fclose(m_fin);
fclose(m_fout);
::close(m_afdt_fd);
::close(m_afdt_lfd);
}
bool LightProcess::Available() {
return g_procsCount > 0;
}
void LightProcess::runShadow(int fdin, int fdout) {
FILE *fin = fdopen(fdin, "r");
FILE *fout = fdopen(fdout, "w");
char buf[BUFFER_SIZE];
pollfd pfd[1];
pfd[0].fd = fdin;
pfd[0].events = POLLIN;
while (true) {
int ret = poll(pfd, 1, -1);
if (ret < 0 && errno == EINTR) {
continue;
}
if (pfd[0].revents & POLLHUP) {
// no more command can come in
Logger::Error("Lost parent, LightProcess exiting");
break;
}
else if (pfd[0].revents & POLLIN) {
if (!fgets(buf, BUFFER_SIZE, fin)) buf[0] = '\0';
if (strncmp(buf, "exit", 4) == 0) {
Logger::Info("LightProces exiting upon request");
break;
} else if (strncmp(buf, "popen", 5) == 0) {
do_popen(fin, fout, m_afdt_fd);
} else if (strncmp(buf, "pclose", 6) == 0) {
do_pclose(fin, fout);
} else if (strncmp(buf, "proc_open", 9) == 0) {
do_proc_open(fin, fout, m_afdt_fd);
} else if (strncmp(buf, "waitpid", 7) == 0) {
do_waitpid(fin, fout);
} else if (strncmp(buf, "change_user", 11) == 0) {
do_change_user(fin, fout);
}
}
}
fclose(fin);
fclose(fout);
::close(m_afdt_fd);
remove(m_afdtFilename.c_str());
exit(0);
}
int LightProcess::GetId() {
return (long)pthread_self() % g_procsCount;
}
FILE *LightProcess::popen(const char *cmd, const char *type,
const char *cwd /* = NULL */) {
if (!Available()) {
// fallback to normal popen
Logger::Verbose("Light-weight fork not available; "
"use the heavy one instead.");
} else {
FILE *f = LightPopenImpl(cmd, type, cwd);
if (f) {
return f;
}
Logger::Verbose("Light-weight fork failed; use the heavy one instead.");
}
return HeavyPopenImpl(cmd, type, cwd);
}
FILE *LightProcess::HeavyPopenImpl(const char *cmd, const char *type,
const char *cwd) {
if (cwd && *cwd) {
string old_cwd = Process::GetCurrentDirectory();
if (old_cwd != cwd) {
Lock lock(s_mutex);
if (chdir(cwd)) {
Logger::Warning("Failed to chdir to %s.", cwd);
}
FILE *f = ::popen(cmd, type);
if (chdir(old_cwd.c_str())) {
// error occured changing cwd back
}
return f;
}
}
return ::popen(cmd, type);
}
FILE *LightProcess::LightPopenImpl(const char *cmd, const char *type,
const char *cwd) {
int id = GetId();
Lock lock(g_procs[id].m_procMutex);
fprintf(g_procs[id].m_fout, "popen\n%s\n%s\n%s\n", type, cmd, cwd);
fflush(g_procs[id].m_fout);
char buf[BUFFER_SIZE];
read_buf(g_procs[id].m_fin, buf);
if (strncmp(buf, "error", 5) == 0) {
return nullptr;
}
int64_t fptr = 0;
read_buf(g_procs[id].m_fin, buf);
sscanf(buf, "%" PRId64, &fptr);
if (!fptr) {
Logger::Error("Light process failed to return the file pointer.");
return nullptr;
}
int fd = recv_fd(g_procs[id].m_afdt_fd);
if (fd < 0) {
Logger::Error("Light process failed to send the file descriptor.");
return nullptr;
}
FILE *f = fdopen(fd, type);
g_procs[id].m_popenMap[(int64_t)f] = fptr;
return f;
}
int LightProcess::pclose(FILE *f) {
if (!Available()) {
return ::pclose(f);
}
int id = GetId();
Lock lock(g_procs[id].m_procMutex);
std::map<int64_t, int64_t>::iterator it = g_procs[id].m_popenMap.find((int64_t)f);
if (it == g_procs[id].m_popenMap.end()) {
// try to close it with normal pclose
return ::pclose(f);
}
int64_t f2 = it->second;
g_procs[id].m_popenMap.erase((int64_t)f);
fclose(f);
fprintf(g_procs[id].m_fout, "pclose\n%" PRId64 "\n", f2);
fflush(g_procs[id].m_fout);
char buf[BUFFER_SIZE];
read_buf(g_procs[id].m_fin, buf);
int ret = -1;
sscanf(buf, "%d", &ret);
if (ret < 0) {
read_buf(g_procs[id].m_fin, buf);
sscanf(buf, "%d", &errno);
}
return ret;
}
pid_t LightProcess::proc_open(const char *cmd, const vector<int> &created,
const vector<int> &desired,
const char *cwd, const vector<string> &env) {
int id = GetId();
Lock lock(g_procs[id].m_procMutex);
always_assert(Available());
always_assert(created.size() == desired.size());
if (fprintf(g_procs[id].m_fout, "proc_open\n%s\n%s\n", cmd, cwd) <= 0) {
Logger::Error("Failed to send command proc_open");
return -1;
}
fprintf(g_procs[id].m_fout, "%d\n", (int)env.size());
for (unsigned int i = 0; i < env.size(); i++) {
fprintf(g_procs[id].m_fout, "%s\n", env[i].c_str());
}
fprintf(g_procs[id].m_fout, "%d\n", (int)created.size());
for (unsigned int i = 0; i < desired.size(); i++) {
fprintf(g_procs[id].m_fout, "%d\n", desired[i]);
}
fflush(g_procs[id].m_fout);
bool error_send = false;
int save_errno = 0;
for (unsigned int i = 0; i < created.size(); i++) {
if (!send_fd(g_procs[id].m_afdt_fd, created[i])) {
error_send = true;
save_errno = errno;
break;
}
}
char buf[BUFFER_SIZE];
read_buf(g_procs[id].m_fin, buf);
if (strncmp(buf, "error", 5) == 0) {
read_buf(g_procs[id].m_fin, buf);
sscanf(buf, "%d", &errno);
if (error_send) {
// On this error, the receiver side returns dummy errno,
// use the sender side errno here.
errno = save_errno;
}
return -1;
}
int64_t pid = -1;
sscanf(buf, "%" PRId64, &pid);
assert(pid);
return (pid_t)pid;
}
pid_t LightProcess::waitpid(pid_t pid, int *stat_loc, int options,
int timeout) {
if (!Available()) {
// light process is not really there
return ::waitpid(pid, stat_loc, options);
}
int id = GetId();
Lock lock(g_procs[id].m_procMutex);
fprintf(g_procs[id].m_fout, "waitpid\n%" PRId64 " %d %d\n", (int64_t)pid, options,
timeout);
fflush(g_procs[id].m_fout);
char buf[BUFFER_SIZE];
read_buf(g_procs[id].m_fin, buf);
if (!buf[0]) return -1;
int64_t ret;
int stat;
sscanf(buf, "%" PRId64 " %d", &ret, &stat);
*stat_loc = stat;
if (ret < 0) {
read_buf(g_procs[id].m_fin, buf);
sscanf(buf, "%d", &errno);
}
return (pid_t)ret;
}
pid_t LightProcess::pcntl_waitpid(pid_t pid, int *stat_loc, int options) {
if (!Available()) {
return ::waitpid(pid, stat_loc, options);
}
int id = GetId();
Lock lock(g_procs[id].m_procMutex);
pid_t p = ::waitpid(pid, stat_loc, options);
if (p == g_procs[id].m_shadowProcess) {
// got the shadow process, wait again
p = ::waitpid(pid, stat_loc, options);
}
return p;
}
void LightProcess::ChangeUser(const string &username) {
if (username.empty()) return;
for (int i = 0; i < g_procsCount; i++) {
Lock lock(g_procs[i].m_procMutex);
fprintf(g_procs[i].m_fout, "change_user\n%s\n", username.c_str());
fflush(g_procs[i].m_fout);
}
}
void LightProcess::SetLostChildHandler(const LostChildHandler& handler) {
s_lostChildHandler = handler;
}
///////////////////////////////////////////////////////////////////////////////
}