Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

687 lines (600 sloc) 17.454 kb
/*
+----------------------------------------------------------------------+
| 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 = NULL;
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) = NULL;
}
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") : NULL;
if (old_cwd != cwd && chdir(old_cwd.c_str())) {
// only here if we can't change the cwd back
}
if (f == NULL) {
Logger::Error("Light process failed popen: %d (%s).", errno,
strerror(errno));
fprintf(fout, "error\n");
fflush(fout);
} else {
fprintf(fout, "success\n%lld\n", (int64)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 fptr = 0;
read_buf(fin, buf);
sscanf(buf, "%lld", &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, NULL, envp);
free(envp);
} else {
execl("/bin/sh", "sh", "-c", cmd, NULL);
}
_exit(127);
} else if (child > 0) {
// successfully created the child process
fprintf(fout, "%lld\n", (int64)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 p = -1;
int options = 0;
int timeout = 0;
sscanf(buf, "%lld %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, "%lld %d\n", (int64)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;
LightProcess::LightProcess()
: m_shadowProcess(0), m_fin(NULL), m_fout(NULL), m_afdt_fd(-1),
m_afdt_lfd(-1) { }
LightProcess::~LightProcess() {
}
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;
}
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, NULL, 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 NULL;
}
int64 fptr = 0;
read_buf(g_procs[id].m_fin, buf);
sscanf(buf, "%lld", &fptr);
if (!fptr) {
Logger::Error("Light process failed to return the file pointer.");
return NULL;
}
int fd = recv_fd(g_procs[id].m_afdt_fd);
if (fd < 0) {
Logger::Error("Light process failed to send the file descriptor.");
return NULL;
}
FILE *f = fdopen(fd, type);
g_procs[id].m_popenMap[(int64)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, int64>::iterator it = g_procs[id].m_popenMap.find((int64)f);
if (it == g_procs[id].m_popenMap.end()) {
// try to close it with normal pclose
return ::pclose(f);
}
int64 f2 = it->second;
g_procs[id].m_popenMap.erase((int64)f);
fclose(f);
fprintf(g_procs[id].m_fout, "pclose\n%lld\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);
assert(Available());
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 pid = -1;
sscanf(buf, "%lld", &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%lld %d %d\n", (int64)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 ret;
int stat;
sscanf(buf, "%lld %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);
}
}
///////////////////////////////////////////////////////////////////////////////
}
Jump to Line
Something went wrong with that request. Please try again.