diff options
Diffstat (limited to 'base/zygote_manager.cc')
-rw-r--r-- | base/zygote_manager.cc | 828 |
1 files changed, 828 insertions, 0 deletions
diff --git a/base/zygote_manager.cc b/base/zygote_manager.cc new file mode 100644 index 0000000..2310c2d --- /dev/null +++ b/base/zygote_manager.cc @@ -0,0 +1,828 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/zygote_manager.h" + +#if defined(OS_LINUX) + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdlib.h> +#include <sys/file.h> // for flock() +#include <sys/stat.h> +#include <sys/uio.h> // for struct iovec +#include <sys/wait.h> +#include <unistd.h> // for ssize_t + +#include <string> + +#include "base/eintr_wrapper.h" +#include "base/file_descriptor_shuffle.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/process_util.h" +#include "base/reserved_file_descriptors.h" +#include "base/singleton.h" + +using file_util::Delete; + +// See comment below, where sigaction is called. +static void SIGCHLDHandler(int signal) { +} + +namespace base { + +const char ZygoteManager::kZMagic[] = "zygo"; + +ZygoteManager::~ZygoteManager() { + if (server_fd_ != -1) { + close(server_fd_); + server_fd_ = -1; + } + if (client_fd_ != -1) { + close(client_fd_); + client_fd_ = -1; + } + if (lockfd_ != -1) { + close(lockfd_); + lockfd_ = -1; + } + if (canary_fd_ != -1) { + // Wake up the poll() in ReadAndHandleMessage() + close(canary_fd_); + canary_fd_ = -1; + } +} + +// Runs in client process +ZygoteManager* ZygoteManager::Get() { + static bool checked = false; + static bool enabled = false; + if (!checked) { + enabled = (getenv("ENABLE_ZYGOTE_MANAGER") != NULL); + // sanity check - make sure all the places that relaunch chrome + // have been zygotified. + if (enabled) + DCHECK(getenv("ZYGOTE_MANAGER_STARTED") == NULL) + << "fork/exec used instead of LongFork"; + (void) setenv("ZYGOTE_MANAGER_STARTED", "1", 1); + checked = true; + } + if (!enabled) + return NULL; + return Singleton<ZygoteManager>::get(); +} + +// Runs in zygote manager process +int ZygoteManager::UnpickleHeader(const Pickle& reply, void** iter) { + std::string magic; + if (!reply.ReadString(iter, &magic) || magic != std::string(kZMagic)) { + LOG(ERROR) << "reply didn't start with " << kZMagic; + return ZMBAD; + } + pid_t clientpid = (pid_t) -1; + if (!reply.ReadInt(iter, &clientpid)) { + LOG(ERROR) << "Can't read client pid"; + return ZMBAD; + } + if (clientpid != getpid()) { + LOG(ERROR) << "got client pid " << clientpid << ", expected " << getpid(); + DCHECK(clientpid == getpid()); + return ZMBAD; + } + int kind; + if (!reply.ReadInt(iter, &kind)) { + LOG(ERROR) << "can't read kind"; + return ZMBAD; + } + return kind; +} + +// Runs in client process (only used in unit test) +bool ZygoteManager::Ping(base::TimeDelta* delta) { + if (client_fd_ == -1) + return false; + + Pickle pickle; + pickle.WriteString(kZMagic); + pickle.WriteInt(getpid()); + pickle.WriteInt(ZMPING); + + int bytes_sent; + int bytes_read = -1; + + TimeTicks time_sent = TimeTicks::HighResNow(); + + // Lock fork server, send the pickle, wait for the reply, unlock + if (flock(lockfd_, LOCK_EX)) + LOG(ERROR) << "flock failed, errno " << errno; + bytes_sent = HANDLE_EINTR(write(client_fd_, + const_cast<void *>(pickle.data()), pickle.size())); + if (bytes_sent > 0) { + bytes_read = HANDLE_EINTR(read(client_fd_, msg_buf_, kMAX_MSG_LEN)); + } + if (flock(lockfd_, LOCK_UN)) + LOG(ERROR) << "flock failed, errno " << errno; + + TimeTicks time_received = TimeTicks::HighResNow(); + + if (bytes_sent < 1) { + LOG(ERROR) << "Can't send to zm, errno " << errno; + return false; + } + if (bytes_read < 1) { + LOG(ERROR) << "Can't get from zm, errno " << errno; + return false; + } + + // Unpickle the reply + Pickle reply(msg_buf_, bytes_read); + void* iter = NULL; + int kind = UnpickleHeader(reply, &iter); + if (kind != ZMPINGED) { + LOG(ERROR) << "reply wrong kind " << kind; + return false; + } + *delta = TimeTicks::HighResNow() - time_sent; + LOG(INFO) << "Round trip time in microseconds: " << delta->InMicroseconds(); + return true; +} + +// Runs in zygote manager process +void ZygoteManager::PingHandler(const Pickle& request, void* iter, + Pickle* reply, std::vector<std::string>** newargv) { + reply->WriteInt(ZMPINGED); +} + +// Runs in browser process, called only by base::ForkApp() +pid_t ZygoteManager::LongFork(const std::vector<std::string>& argv, + const file_handle_mapping_vector& fds_to_remap) { + if (client_fd_ == -1) + return -1; + + Pickle pickle; + + // Encode the arguments and the desired remote fd numbers in the pickle, + // and the fds in a separate buffer + pickle.WriteString(kZMagic); + pickle.WriteInt(getpid()); + pickle.WriteInt(ZMFORK); + pickle.WriteInt(argv.size()); + std::vector<std::string>::const_iterator argi; + for (argi = argv.begin(); argi != argv.end(); ++argi) + pickle.WriteString(*argi); + pickle.WriteInt(fds_to_remap.size()); + + // Wrap the pickle and the fds together in a msghdr + ::msghdr msg; + memset(&msg, 0, sizeof(msg)); + struct iovec iov; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf_; + msg.msg_controllen = CMSG_LEN(sizeof(int) * fds_to_remap.size()); + struct cmsghdr* cmsg; + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = msg.msg_controllen; + int* wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); + for (size_t i = 0; i < fds_to_remap.size(); i++) { + pickle.WriteInt(fds_to_remap[i].second); + wire_fds[i] = fds_to_remap[i].first; + } + iov.iov_base = const_cast<void *>(pickle.data()); + iov.iov_len = pickle.size(); + + int bytes_sent; + int bytes_read = -1; + + // Lock fork server, send the pickle, wait for the reply, unlock + if (flock(lockfd_, LOCK_EX)) + LOG(ERROR) << "flock failed, errno " << errno; + bytes_sent = HANDLE_EINTR(sendmsg(client_fd_, &msg, MSG_WAITALL)); + if (bytes_sent > 0) { + bytes_read = HANDLE_EINTR(read(client_fd_, msg_buf_, kMAX_MSG_LEN)); + } + if (flock(lockfd_, LOCK_UN)) + LOG(ERROR) << "flock failed, errno " << errno; + + if (bytes_sent < 1) { + LOG(ERROR) << "Can't send to zm, errno " << errno << ", fd " << client_fd_; + return (pid_t) -1; + } + if (bytes_read < 1) { + LOG(ERROR) << "Can't get from zm, errno " << errno; + return (pid_t) -1; + } + + // Unpickle the reply + Pickle reply(msg_buf_, bytes_read); + void* iter = NULL; + int kind = UnpickleHeader(reply, &iter); + if (kind != ZMFORKED) { + LOG(ERROR) << "reply wrong kind " << kind; + return (pid_t) -1; + } + pid_t newpid = (pid_t) -1; + int pid_errno; + if (!reply.ReadInt(&iter, &newpid) || !reply.ReadInt(&iter, &pid_errno)) { + LOG(ERROR) << "fork failed, can't read pid/errno"; + return (pid_t) -1; + } + if ((newpid == (pid_t) -1) || pid_errno != 0) { + LOG(ERROR) << "fork failed, pid " << newpid << ", errno " << pid_errno; + return (pid_t) -1; + } + return newpid; +} + +// Runs in zygote manager process +bool ZygoteManager::LongForkHandler(const Pickle& request, void* iter, + Pickle* reply, std::vector<std::string>** newargv, + const int wire_fds[], int num_wire_fds) { + file_handle_mapping_vector fds_to_remap; + pid_t childpid; + + reply->WriteInt(ZMFORKED); + + // Unpickle commandline for new child + std::vector<std::string>* argv = new std::vector<std::string>; + int argc; + request.ReadInt(&iter, &argc); + for (int i = 0; i < argc; i++) { + std::string arg; + if (!request.ReadString(&iter, &arg)) { + LOG(ERROR) << "can't read arg?"; + goto error_reply; + } + argv->push_back(arg); + } + // Unpickle file descriptor map for new child + int numfds; + request.ReadInt(&iter, &numfds); + DCHECK(numfds == num_wire_fds); + if (numfds != num_wire_fds) { + LOG(ERROR) << "numfds " << numfds << " != num_wire_fds " << num_wire_fds; + goto error_reply; + } + for (int i = 0; i < numfds; i++) { + int fd; + if (!request.ReadInt(&iter, &fd)) { + LOG(ERROR) << "can't read fd?"; + goto error_reply; + } + fds_to_remap.push_back(std::pair<int, int>(wire_fds[i], fd)); + } + + // Mitosis! + childpid = fork(); + + if (childpid != 0) { + // parent + // first off, close our copy of the child's file descriptors + for (file_handle_mapping_vector::const_iterator + it = fds_to_remap.begin(); it != fds_to_remap.end(); ++it) { + close(it->first); + } + + // Finish formatting the reply + reply->WriteInt(childpid); + if (childpid == (pid_t) -1) { + reply->WriteInt(errno); + return false; + } else { + reply->WriteInt(0); + } + } else { + // child + // Apply file descriptor map + InjectiveMultimap fd_shuffle; + for (file_handle_mapping_vector::const_iterator + it = fds_to_remap.begin(); it != fds_to_remap.end(); ++it) { + fd_shuffle.push_back(InjectionArc(it->first, it->second, false)); + } + + // Avoid closing descriptor children will need to contact fork server. + fd_shuffle.push_back(InjectionArc(client_fd_, client_fd_, false)); + // Avoid closing log descriptor we're using + int logfd = logging::GetLoggingFileDescriptor(); + if (logfd != -1) + fd_shuffle.push_back(InjectionArc(logfd, logfd, false)); + // And of course avoid closing the cached fds. + std::map<std::string, int>::iterator i; + for (i = cached_fds_.begin(); i != cached_fds_.end(); ++i) { + fd_shuffle.push_back(InjectionArc(i->second, i->second, false)); + } + + // If there is any clash in the mapping, this function will DCHECK. + if (!ShuffleFileDescriptors(fd_shuffle)) + exit(127); + + // Open this after shuffle to avoid using reserved slots. + lockfd_ = open(lockfile_.c_str(), O_RDWR, 0); + if (lockfd_ == -1) { + // TODO(dkegel): real error handling + exit(126); + } + // Mark it as not to be closed. + fd_shuffle.push_back(InjectionArc(lockfd_, lockfd_, false)); + + // Also closes reserved fds. + CloseSuperfluousFds(fd_shuffle); + + *newargv = argv; + // Because *newargv is set, we will return to main instead of looping + } + return true; + + error_reply: + reply->WriteInt(-1); + reply->WriteInt(-1); + for (int i=0; i<num_wire_fds; i++) + close(wire_fds[i]); + return false; +} + +// Runs in browser process, called by ProcessWatcher::EnsureProcessTerminated(). +void ZygoteManager::EnsureProcessTerminated(pid_t childpid) { + if (client_fd_ == -1) + return; + + Pickle pickle; + + pickle.WriteString(kZMagic); + pickle.WriteInt(getpid()); + pickle.WriteInt(ZMREAP); + pickle.WriteInt(childpid); + + int bytes_sent = HANDLE_EINTR( + write(client_fd_, const_cast<void*>(pickle.data()), pickle.size())); + + if (bytes_sent < 1) { + LOG(ERROR) << "Can't send to zm, errno " << errno << ", fd " << client_fd_; + } +} + +// Runs in zygote manager process +void ZygoteManager::EnsureProcessTerminatedHandler(const Pickle& request, + void* iter) { + pid_t childpid; + request.ReadInt(&iter, &childpid); + NOTIMPLEMENTED(); + // TODO(dkegel): put childpid on a watch list, and terminate it + // after a while as chrome/common/process_watcher does. +} + +static bool ValidateFilename(const std::string& filename) { + // We only have to open one kind of file, but we don't know + // the directory it's in, so be as restrictive as we can within + // those bounds. + + static const char* allowed_prefix = "/"; + if (filename.compare(0, strlen(allowed_prefix), allowed_prefix) != 0) { + LOG(ERROR) << "filename did not start with " << allowed_prefix; + return false; + } + static const char* allowed_suffix = ".pak"; + if ((filename.length() <= strlen(allowed_suffix)) || + (filename.compare(filename.length() - strlen(allowed_suffix), + strlen(allowed_suffix), allowed_suffix) != 0)) { + LOG(ERROR) << "filename did not end in " << allowed_suffix; + return false; + } + if (filename.find("../") != std::string::npos) { + LOG(ERROR) << "filename contained relative component"; + return false; + } + static const char* forbidden_prefixes[] = { + "/var/", "/tmp/", "/etc/", "/dev/", "/proc/", 0 }; + for (const char** p = forbidden_prefixes; + *p; p++) { + if (filename.compare(0, strlen(*p), *p) == 0) { + LOG(ERROR) << "filename began with " << *p; + return false; + } + } + return true; +} + +// Runs in browser process +int ZygoteManager::OpenFile(const std::string& filename) { + // For security reasons, we only support .pak files, + // and only in certain locations. + if (!ValidateFilename(filename)) { + LOG(INFO) << "ZygoteManager: filename " << filename << " disallowed."; + return -1; + } + + std::map<std::string, int>::iterator mapiter = cached_fds_.find(filename); + if (mapiter != cached_fds_.end()) + return mapiter->second; + + if (client_fd_ == -1) + return -1; + + Pickle pickle; + + pickle.WriteString(kZMagic); + pickle.WriteInt(getpid()); + pickle.WriteInt(ZMOPEN); + pickle.WriteString(filename); + + // Get ready to receive fds + ::msghdr msg = {0}; + struct iovec iov = {msg_buf_, kMAX_MSG_LEN}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf_; + msg.msg_controllen = kMAX_CMSG_LEN; + + ssize_t bytes_sent; + ssize_t bytes_read = 0; + + if (flock(lockfd_, LOCK_EX)) + LOG(ERROR) << "flock failed, errno " << errno; + bytes_sent = HANDLE_EINTR( + write(client_fd_, const_cast<void *>(pickle.data()), pickle.size())); + if (bytes_sent > 0) { + bytes_read = HANDLE_EINTR(recvmsg(client_fd_, &msg, MSG_WAITALL)); + } + if (flock(lockfd_, LOCK_UN)) + LOG(ERROR) << "flock failed, errno " << errno; + + if (bytes_sent < 1) { + LOG(ERROR) << "Can't send to zm, errno " << errno << ", fd " << client_fd_; + return -1; + } + if (bytes_read < 1) { + LOG(ERROR) << "Can't get from zm, errno " << errno; + return -1; + } + + // Locate the sole block of sent file descriptors within the list of + // control messages + const int* wire_fds = NULL; + unsigned num_wire_fds = 0; + if (msg.msg_controllen > 0) { + struct cmsghdr* cmsg; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); + assert(payload_len % sizeof(int) == 0); + wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); + num_wire_fds = payload_len / sizeof(int); + break; + } + } + } + DCHECK(!(msg.msg_flags & MSG_CTRUNC)); + + // Unpickle the reply + Pickle reply(msg_buf_, bytes_read); + void* iter = NULL; + int kind = UnpickleHeader(reply, &iter); + if (kind != ZMOPENED) { + LOG(ERROR) << "reply wrong kind " << kind; + goto error_close; + } + int newfd_errno; + if (!reply.ReadInt(&iter, &newfd_errno)) { + LOG(ERROR) << "open failed, can't read errno"; + goto error_close; + } + if (newfd_errno != 0) { + LOG(ERROR) << "open failed, errno " << newfd_errno; + goto error_close; + } + if (num_wire_fds != 1) { + LOG(ERROR) << "open failed, reply wrong number fds " << num_wire_fds; + goto error_close; + } + if (wire_fds[0] == -1) { + LOG(ERROR) << "open failed, fd -1"; + NOTREACHED(); + return -1; + } + return wire_fds[0]; + + error_close: + for (unsigned i=0; i<num_wire_fds; i++) + close(wire_fds[i]); + return -1; +} + +// Runs in zygote manager process +bool ZygoteManager::OpenFileHandler(const Pickle& request, void* iter, + Pickle* reply, ::msghdr* msg) { + reply->WriteInt(ZMOPENED); + + std::string filename; + if (!request.ReadString(&iter, &filename)) { + LOG(ERROR) << "no filename?"; + return false; + } + if (!ValidateFilename(filename)) { + // Fake a unix error code + reply->WriteInt(EPERM); + return false; + } + + std::map<std::string, int>::iterator i; + int newfd; + std::map<std::string, int>::iterator mapiter = cached_fds_.find(filename); + if (mapiter == cached_fds_.end()) { + // Verify that file is a plain file + struct stat statbuf; + if (lstat(filename.c_str(), &statbuf) != 0) { + LOG(ERROR) << "can't stat " << filename << ", errno " << errno; + return false; + } + if (!S_ISREG(statbuf.st_mode)) { + LOG(ERROR) << "not regular file " << filename; + // Fake a unix error code + reply->WriteInt(EISDIR); + return false; + } + newfd = open(filename.c_str(), O_RDONLY, 0); + if (newfd != -1) { + cached_fds_[filename] = newfd; + } else { + LOG(ERROR) << "can't open " << filename << ", errno " << errno; + } + } else { + newfd = mapiter->second; + } + if (newfd == -1) { + reply->WriteInt(errno); + } else { + reply->WriteInt(0); + msg->msg_control = cmsg_buf_; + msg->msg_controllen = CMSG_LEN(sizeof(int)); + struct cmsghdr* cmsg; + cmsg = CMSG_FIRSTHDR(msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = msg->msg_controllen; + int* wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); + wire_fds[0] = newfd; + } + + return true; +} + +// Runs in zygote manager process +bool ZygoteManager::ReadAndHandleMessage(std::vector<std::string>** newargv) { + // Wait for activity either on canary fd or main fd. + struct pollfd watcher[2]; + memset(watcher, 0, sizeof(watcher)); + watcher[0].fd = canary_fd_; + watcher[0].events = POLLIN|POLLHUP; + watcher[1].fd = server_fd_; + watcher[1].events = POLLIN; + // Wait at most one minute. This lets us detect case where + // canary socket is closed abruptly because the main client aborted. + // Also lets us reap dead children once a minute even if we don't get SIGCHLD. + // We'd like to wait less time, but that's hard on battery life. + // Note: handle EINTR manually here, not with wrapper, as we need + // to return when we're interrupted so caller can reap promptly. + int nactive = poll(watcher, 2, 60*1000); + + if (nactive == -1) { + if (errno == EINTR) { + LOG(INFO) << "poll interrupted"; + // Probably SIGCHLD. Return to main loop so it can reap. + return true; + } + LOG(ERROR) << "poll failed, errno " << errno << ", aborting"; + return false; + } + + // If it was the canary, exit + if (watcher[0].revents != 0) { + LOG(INFO) << "notified of peer destruction, exiting"; + return false; + } + if ((watcher[1].revents & POLLIN) != POLLIN) { + // spurious wakeup? + return true; + } + + ssize_t bytes_read = 0; + struct msghdr msg = {0}; + struct iovec iov = {msg_buf_, kMAX_MSG_LEN}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf_; + msg.msg_controllen = kMAX_CMSG_LEN; + bytes_read = HANDLE_EINTR(recvmsg(server_fd_, &msg, MSG_WAITALL)); + if (bytes_read == 0) { + LOG(ERROR) << "got EOF, aborting"; + return false; + } + if (bytes_read == -1) { + LOG(ERROR) << "got errno " << errno << ", aborting"; + return false; + } + + // Locate the sole block of sent file descriptors within the list of + // control messages + const int* wire_fds = NULL; + unsigned num_wire_fds = 0; + if (msg.msg_controllen > 0) { + struct cmsghdr* cmsg; + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); + assert(payload_len % sizeof(int) == 0); + wire_fds = reinterpret_cast<int*>(CMSG_DATA(cmsg)); + num_wire_fds = payload_len / sizeof(int); + break; + } + } + } + DCHECK(!(msg.msg_flags & MSG_CTRUNC)); + + // Unpickle/parse message + Pickle pickle(msg_buf_, bytes_read); + void* iter = NULL; + std::string magic; + if (!pickle.ReadString(&iter, &magic) || magic != std::string(kZMagic)) { + LOG(ERROR) << "msg didn't start with " << kZMagic << ", got " << magic; + for (unsigned i=0; i<num_wire_fds; i++) + close(wire_fds[i]); + return true; + } + pid_t clientpid = (pid_t) -1; + pickle.ReadInt(&iter, &clientpid); + int kind; + pickle.ReadInt(&iter, &kind); + + Pickle reply; + reply.WriteString(kZMagic); + reply.WriteInt(clientpid); + + struct msghdr replymsg = {0}; + memset(&replymsg, 0, sizeof(replymsg)); + + switch (kind) { + case ZMPING: + DCHECK_EQ(0U, num_wire_fds); + PingHandler(pickle, iter, &reply, newargv); + break; + case ZMFORK: + // TODO(dkegel): real error handling + (void) LongForkHandler(pickle, iter, &reply, newargv, wire_fds, + num_wire_fds); + if (*newargv != NULL) { + // Child. Just return to caller, who will return from SetLongFork. + return true; + } + break; + case ZMREAP: + DCHECK_EQ(0U, num_wire_fds); + EnsureProcessTerminatedHandler(pickle, iter); + // no reply to this message + return true; + case ZMOPEN: + DCHECK_EQ(0U, num_wire_fds); + // TODO(dkegel): real error handling + (void) OpenFileHandler(pickle, iter, &reply, &replymsg); + break; + default: + // TODO(dkegel): real error handling + LOG(ERROR) << "Unknown message kind " << kind; + DCHECK_EQ(0U, num_wire_fds); + break; + } + + struct iovec riov = {const_cast<void *>(reply.data()), reply.size()}; + replymsg.msg_iov = &riov; + replymsg.msg_iovlen = 1; + + int bytes_sent; + bytes_sent = HANDLE_EINTR(sendmsg(server_fd_, &replymsg, MSG_WAITALL)); + if (bytes_sent != static_cast<int>(riov.iov_len)) { + // TODO(dkegel): real error handling + LOG(ERROR) << "Can't send reply."; + return false; + } + return true; +} + +// Called only by ChromeMain(), forks the zygote manager process. +std::vector<std::string>* ZygoteManager::Start() { + DCHECK(lockfd_ == -1); + DCHECK(canary_fd_ == -1); + DCHECK(server_fd_ == -1); + DCHECK(client_fd_ == -1); + + int pipe_fds[2]; + + // Avoid using the reserved fd slots. + int reserved_fds[kReservedFds]; + for (int i=0; i < kReservedFds; i++) + reserved_fds[i] = open("/dev/null", O_RDONLY, 0); + + // Create the main communications pipe. + int err = HANDLE_EINTR(socketpair(AF_UNIX, SOCK_DGRAM, 0, pipe_fds)); + if (err != 0) { + // TODO(dkegel): real error handling + exit(99); + } + server_fd_ = pipe_fds[1]; + client_fd_ = pipe_fds[0]; + + // Create the pipe used only to relay destruction event server. + // Must be SOCK_STREAM so close() is sensed by poll(). + err = HANDLE_EINTR(socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds)); + if (err != 0) { + // TODO(dkegel): real error handling + exit(99); + } + + // Create lock file. + // TODO(dkegel): get rid of this + char lockfile[256]; + strcpy(lockfile, "/tmp/zygote_manager_lock.XXXXXX"); + lockfd_ = mkstemp(lockfile); + if (lockfd_ == -1) { + // TODO(dkegel): real error handling + exit(99); + } + lockfile_.assign(lockfile); + + // Fork a fork server. + pid_t childpid = fork(); + + if (childpid) { + for (int i=0; i < kReservedFds; i++) + close(reserved_fds[i]); + + // Original parent. Continues on with the main program + // and becomes the first client. + close(server_fd_); + server_fd_ = -1; + + close(pipe_fds[1]); + canary_fd_ = pipe_fds[0]; + + // Return now to indicate this is the original process. + return NULL; + } else { + close(lockfd_); + + close(pipe_fds[0]); + canary_fd_ = pipe_fds[1]; + + // We need to accept SIGCHLD, even though our handler is a no-op because + // otherwise we cannot wait on children. (According to POSIX 2001.) + // (And otherwise poll() might not wake up on SIGCHLD.) + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = SIGCHLDHandler; + CHECK(sigaction(SIGCHLD, &action, NULL) == 0); + + // Original child. Acts as the server. + while (true) { + std::vector<std::string>* newargv = NULL; + if (!ReadAndHandleMessage(&newargv)) + break; + if (newargv) { + // Return new commandline to show caller this is a new child process. + return newargv; + } + // Server process continues around loop. + + // Reap children. + while (true) { + int status = -1; + pid_t reaped = waitpid(-1, &status, WNOHANG); + if (reaped != -1 && reaped != 0) { + LOG(INFO) << "Reaped pid " << reaped; + continue; + } + break; + } + } + // Server cleanup after EOF or error reading from the socket. + // Chrome doesn't seem to get here in practice. + Delete(FilePath(lockfile_), false); + // TODO(dkegel): real error handling + LOG(INFO) << "exiting. " << cached_fds_.size() << " cached fds."; + std::map<std::string, int>::iterator i; + for (i = cached_fds_.begin(); i != cached_fds_.end(); ++i) { + LOG(INFO) << "Closing fd " << i->second << " filename " << i->first; + close(i->second); + } + exit(-1); + } +} +} +#endif // defined(OS_LINUX) |