diff options
-rw-r--r-- | base/base.gyp | 6 | ||||
-rw-r--r-- | base/command_line.cc | 11 | ||||
-rw-r--r-- | base/command_line.h | 1 | ||||
-rw-r--r-- | base/file_descriptor_shuffle.cc | 6 | ||||
-rw-r--r-- | base/file_util_posix.cc | 26 | ||||
-rw-r--r-- | base/logging.cc | 24 | ||||
-rw-r--r-- | base/logging.h | 7 | ||||
-rw-r--r-- | base/process_util.h | 8 | ||||
-rw-r--r-- | base/process_util_linux.cc | 19 | ||||
-rw-r--r-- | base/reserved_file_descriptors.h | 36 | ||||
-rw-r--r-- | base/zygote_manager.cc | 828 | ||||
-rw-r--r-- | base/zygote_manager.h | 143 | ||||
-rw-r--r-- | base/zygote_manager_unittest.cc | 266 | ||||
-rw-r--r-- | chrome/app/breakpad_linux.cc | 4 | ||||
-rw-r--r-- | chrome/app/chrome_dll_main.cc | 15 | ||||
-rw-r--r-- | chrome/browser/renderer_host/browser_render_process_host.cc | 7 | ||||
-rw-r--r-- | chrome/common/ipc_channel_posix.cc | 8 | ||||
-rw-r--r-- | chrome/common/process_watcher_posix.cc | 15 |
18 files changed, 1415 insertions, 15 deletions
diff --git a/base/base.gyp b/base/base.gyp index 10536f6..b26c68e 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -417,6 +417,9 @@ # so use idle_timer_none.cc instead. 'idle_timer.cc', ], + 'sources': [ + 'zygote_manager.cc', + ], 'dependencies': [ '../build/util/build_util.gyp:lastchange', '../build/linux/system.gyp:gtk', @@ -681,6 +684,9 @@ ], 'conditions': [ ['OS == "linux"', { + 'sources': [ + 'zygote_manager_unittest.cc', + ], 'sources!': [ 'file_version_info_unittest.cc', # Linux has an implementation of idle_timer, but it's unclear diff --git a/base/command_line.cc b/base/command_line.cc index a1f919a..735620d 100644 --- a/base/command_line.cc +++ b/base/command_line.cc @@ -177,6 +177,17 @@ void CommandLine::Init(int argc, const char* const* argv) { #endif } +// static +void CommandLine::Init(const std::vector<std::string>& argv) { + DCHECK(current_process_commandline_ == NULL); +#if defined(OS_WIN) + current_process_commandline_ = new CommandLine; + current_process_commandline_->ParseFromString(::GetCommandLineW()); +#elif defined(OS_POSIX) + current_process_commandline_ = new CommandLine(argv); +#endif +} + void CommandLine::Terminate() { DCHECK(current_process_commandline_ != NULL); delete current_process_commandline_; diff --git a/base/command_line.h b/base/command_line.h index 8271206..c895b81 100644 --- a/base/command_line.h +++ b/base/command_line.h @@ -50,6 +50,7 @@ class CommandLine { // directly) because we don't trust the CRT's parsing of the command // line, but it still must be called to set up the command line. static void Init(int argc, const char* const* argv); + static void Init(const std::vector<std::string>& argv); // Destroys the current process CommandLine singleton. This is necessary if // you want to reset the base library to its initial state (for example in an diff --git a/base/file_descriptor_shuffle.cc b/base/file_descriptor_shuffle.cc index 28447c9..b26ea7f 100644 --- a/base/file_descriptor_shuffle.cc +++ b/base/file_descriptor_shuffle.cc @@ -21,8 +21,10 @@ bool PerformInjectiveMultimap(const InjectiveMultimap& m_in, int temp_fd = -1; // We DCHECK the injectiveness of the mapping. - for (InjectiveMultimap::iterator j = i + 1; j != m.end(); ++j) - DCHECK(i->dest != j->dest); + for (InjectiveMultimap::iterator j = i + 1; j != m.end(); ++j) { + DCHECK(i->dest != j->dest) << "Both fd " << i->source + << " and " << j->source << " map to " << i->dest; + } const bool is_identity = i->source == i->dest; diff --git a/base/file_util_posix.cc b/base/file_util_posix.cc index 5d44ca3..b9aa206 100644 --- a/base/file_util_posix.cc +++ b/base/file_util_posix.cc @@ -27,6 +27,7 @@ #include "base/logging.h" #include "base/string_util.h" #include "base/time.h" +#include "base/zygote_manager.h" namespace file_util { @@ -625,20 +626,37 @@ MemoryMappedFile::MemoryMappedFile() } bool MemoryMappedFile::MapFileToMemory(const FilePath& file_name) { - file_ = open(file_name.value().c_str(), O_RDONLY); + file_ = -1; +#if defined(OS_LINUX) + base::ZygoteManager* zm = base::ZygoteManager::Get(); + if (zm) { + file_ = zm->OpenFile(file_name.value().c_str()); + if (file_ == -1) { + LOG(INFO) << "Zygote manager can't open " << file_name.value() + << ", retrying locally"; + } + } +#endif // defined(OS_LINUX) if (file_ == -1) + file_ = open(file_name.value().c_str(), O_RDONLY); + if (file_ == -1) { + LOG(ERROR) << "Couldn't open " << file_name.value(); return false; + } struct stat file_stat; - if (fstat(file_, &file_stat) == -1) + if (fstat(file_, &file_stat) == -1) { + LOG(ERROR) << "Couldn't fstat " << file_name.value() << ", errno " << errno; return false; + } length_ = file_stat.st_size; data_ = static_cast<uint8*>( mmap(NULL, length_, PROT_READ, MAP_SHARED, file_, 0)); if (data_ == MAP_FAILED) - data_ = NULL; - return data_ != NULL; + LOG(ERROR) << "Couldn't mmap " << file_name.value() << ", errno " << errno; + + return data_ != MAP_FAILED; } void MemoryMappedFile::CloseHandles() { diff --git a/base/logging.cc b/base/logging.cc index 7420883..b6aa730 100644 --- a/base/logging.cc +++ b/base/logging.cc @@ -19,9 +19,12 @@ typedef HANDLE MutexHandle; #endif #if defined(OS_POSIX) +#include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <string.h> +#include <sys/types.h> +#include <sys/stat.h> #include <unistd.h> #define MAX_PATH PATH_MAX typedef FILE* FileHandle; @@ -37,6 +40,7 @@ typedef pthread_mutex_t* MutexHandle; #include "base/command_line.h" #include "base/debug_util.h" #include "base/lock_impl.h" +#include "base/reserved_file_descriptors.h" #include "base/string_piece.h" #include "base/string_util.h" #include "base/sys_string_conversions.h" @@ -205,7 +209,17 @@ bool InitializeLogFileHandle() { } SetFilePointer(log_file, 0, 0, FILE_END); #elif defined(OS_POSIX) + // Reserve global fd slots. + int reserved_fds[kReservedFds]; + for (int i=0; i < kReservedFds; i++) + reserved_fds[i] = open("/dev/null", O_RDONLY, 0); + log_file = fopen(log_file_name->c_str(), "a"); + + // Release the reserved fds. + for (int i=0; i < kReservedFds; i++) + close(reserved_fds[i]); + if (log_file == NULL) return false; #endif @@ -214,6 +228,16 @@ bool InitializeLogFileHandle() { return true; } +#if defined(OS_LINUX) +int GetLoggingFileDescriptor() { + // No locking needed, since this is only called by the zygote server, + // which is single-threaded. + if (log_file) + return fileno(log_file); + return -1; +} +#endif + void InitLogMutex() { #if defined(OS_WIN) if (!log_mutex) { diff --git a/base/logging.h b/base/logging.h index 6a58cf3..db7faca 100644 --- a/base/logging.h +++ b/base/logging.h @@ -141,6 +141,13 @@ void SetMinLogLevel(int level); // Gets the current log level. int GetMinLogLevel(); +#if defined(OS_LINUX) +// Get the file descriptor used for logging. +// Returns -1 if none open. +// Needed by ZygoteManager. +int GetLoggingFileDescriptor(); +#endif + // Sets the log filter prefix. Any log message below LOG_ERROR severity that // doesn't start with this prefix with be silently ignored. The filter defaults // to NULL (everything is logged) if this function is not called. Messages diff --git a/base/process_util.h b/base/process_util.h index aaa8cbb..b558541 100644 --- a/base/process_util.h +++ b/base/process_util.h @@ -132,6 +132,14 @@ typedef std::vector<std::pair<int, int> > file_handle_mapping_vector; bool LaunchApp(const std::vector<std::string>& argv, const file_handle_mapping_vector& fds_to_remap, bool wait, ProcessHandle* process_handle); + +#if defined(OS_LINUX) +// Like LaunchApp, but if zygote manager is enabled, +// forks the zygote instead of forking and exec'ing. +bool ForkApp(const std::vector<std::string>& argv, + const file_handle_mapping_vector& fds_to_remap, + ProcessHandle* process_handle); +#endif #endif // Executes the application specified by cl. This function delegates to one diff --git a/base/process_util_linux.cc b/base/process_util_linux.cc index ed8d32d..78725cc 100644 --- a/base/process_util_linux.cc +++ b/base/process_util_linux.cc @@ -17,6 +17,7 @@ #include "base/logging.h" #include "base/string_tokenizer.h" #include "base/string_util.h" +#include "base/zygote_manager.h" namespace { @@ -41,6 +42,22 @@ void GetProcStats(pid_t pid, std::vector<std::string>* proc_stats) { namespace base { +bool ForkApp(const std::vector<std::string>& argv, + const file_handle_mapping_vector& fds_to_remap, + ProcessHandle* process_handle) { + ZygoteManager* zm = ZygoteManager::Get(); + if (!zm) + return LaunchApp(argv, fds_to_remap, false, process_handle); + + pid_t pid = zm->LongFork(argv, fds_to_remap); + if (pid < 0) + return false; + + if (process_handle) + *process_handle = pid; + return true; +} + bool LaunchApp(const std::vector<std::string>& argv, const file_handle_mapping_vector& fds_to_remap, bool wait, ProcessHandle* process_handle) { @@ -65,6 +82,8 @@ bool LaunchApp(const std::vector<std::string>& argv, argv_cstr[i] = const_cast<char*>(argv[i].c_str()); argv_cstr[argv.size()] = NULL; execvp(argv_cstr[0], argv_cstr.get()); + LOG(ERROR) << "LaunchApp: exec failed!, argv_cstr[0] " << argv_cstr[0] + << ", errno " << errno; exit(127); } else { if (wait) diff --git a/base/reserved_file_descriptors.h b/base/reserved_file_descriptors.h new file mode 100644 index 0000000..5236a08 --- /dev/null +++ b/base/reserved_file_descriptors.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef BASE_RESERVED_FILE_DESCRIPTORS_H_ +#define BASE_RESERVED_FILE_DESCRIPTORS_H_ + +#if defined(OS_POSIX) + +// Chrome uses predefined file descriptors to communicate with child processes. +// Normally this is a private contract between code that does fork/exec and the +// code it invokes, but in zygote mode, things get a little more interesting. +// It's a huge layering violation for this to be in base, but +// logging and ZygoteManager need kReservedFileDescriptors, so there. + +enum GlobalReservedFds { + // Classic unix file descriptors. + // Let's leave them alone even if we don't use them. + kStdinFd = 0, + kStdoutFd = 1, + kStderrFd = 2, + + // See chrome/common/ipc_channel_posix.cc + kClientChannelFd = 3, + + // See chrome/app/breakpad_linux.cc and + // chrome/browser/renderer_host/browser_render_process_host.cc + kMagicCrashSignalFd = 4, + + // One plus highest fd mentioned in this enum. + kReservedFds = 5 +}; + +#endif + +#endif 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) diff --git a/base/zygote_manager.h b/base/zygote_manager.h new file mode 100644 index 0000000..7936167 --- /dev/null +++ b/base/zygote_manager.h @@ -0,0 +1,143 @@ +// Copyright (c) 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. + +#ifndef BASE_ZYGOTE_MANAGER_H_ +#define BASE_ZYGOTE_MANAGER_H_ + +#include "build/build_config.h" + +#if defined(OS_LINUX) + +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "base/pickle.h" +#include "base/time.h" +#include "base/process_util.h" // for file_handle_mapping_vector + +namespace base { + +class ZygoteManager { + public: + // The normal way to get a ZygoteManager is via this singleton factory. + static ZygoteManager* Get(); + + ZygoteManager() : server_fd_(-1), client_fd_(-1), canary_fd_(-1), + lockfd_(-1) { + } + + ~ZygoteManager(); + + // Measure round trip time. Return true on success. + // Only used during testing. + bool Ping(base::TimeDelta* delta); + + // Start the zygote manager. + // Called only once, but returns many times. + // Returns once in original process and once in each spawned child. + // In original process, returns NULL. + // In child processes, returns the argv to use for the child. + // In Chromium, called from ChromeMain(). + std::vector<std::string>* Start(); + + // Like longjmp() and base::LaunchApp(). + // Ask the fork server to spawn a new process with + // the given commandline and the given file descriptors. + // Returns process id of copy, or -1 on failure. + // In Chromium, called from base::ForkApp(), which is + // called from BrowserRenderProcessHost::Init(). + pid_t LongFork(const std::vector<std::string>& argv, + const file_handle_mapping_vector& fds_to_remap); + + // Tell ZygoteManager that we expect the given process to + // exit on its own soon. If it doesn't die within a few + // seconds, kill it. Does not block (unless pipe to server full). + // In Chromium, called from ProcessWatcher::EnsureProcessTerminated(). + void EnsureProcessTerminated(pid_t childpid); + + // Open a file, or retrieve a previously cached file descriptor + // for this file. The files are opened for readonly access. + // Caution: do not seek file descriptors returned + // by this API, as all children share the same file objects, so + // a seek on one is a seek on all. + // Works even if the file is unlinked after the first call + // (e.g. when an app is updated by the linux system autoupdater). + // Returns file descriptor, or -1 for error. + // In Chromium, called from MemoryMappedFile::MapFileToMemory(). + // Only allows openeing files named .pak in reasonable looking locations. + int OpenFile(const std::string& filename); + + private: + int UnpickleHeader(const Pickle& reply, void** iter); + + // Returns false on EOF + // Sets *newargv to a new commandline if the remote side requested a fork. + bool ReadAndHandleMessage(std::vector<std::string>** newargv); + + void PingHandler(const Pickle& request, void* iter, Pickle* reply, + std::vector<std::string>** newargv); + + bool LongForkHandler(const Pickle& request, void* iter, Pickle* reply, + std::vector<std::string>** newargv, + const int wire_fds[], int num_wire_fds); + + void EnsureProcessTerminatedHandler(const Pickle& request, void* iter); + + bool OpenFileHandler(const Pickle& request, void* iter, Pickle* reply, + ::msghdr* msg); + + // The fd used by the server to receive requests + int server_fd_; + + // The fd used by the clients to send requests + int client_fd_; + + // fd used only to notify server of destruction. + int canary_fd_; + + // Temporary file used only for locking. + // Each client must do its own open for locking to work; + // inherited file descriptors can't lock each other out. + // FIXME: locking is lame. + std::string lockfile_; + int lockfd_; + + enum message_kind_t { ZMPING, ZMPINGED, + ZMFORK, ZMFORKED, + ZMREAP, + ZMOPEN, ZMOPENED, + ZMBAD }; + + // See common/reserved_file_descriptors.h for who uses the reserved + // file descriptors. kReservedFds is one plus the highest fd mentioned there. + // TODO(dkegel): move kReservedFds to reserved_file_descriptors.h + static const int kReservedFds = 5; + + static const int kMAX_MSG_LEN = 2000; + static const int kMAX_CMSG_LEN = 100; + + static const char kZMagic[]; + + char msg_buf_[kMAX_MSG_LEN]; + char cmsg_buf_[kMAX_CMSG_LEN]; + + // Where we remember file descriptors for already-opened files. + // Both client and server maintain this table. + // Client should check the table before requesting the + // server to open a file, as it might have been already + // opened before this client was forked. + std::map<std::string, int> cached_fds_; +}; + +} // namespace base + +#endif // defined(OS_LINUX) + +#endif // BASE_ZYGOTE_MANAGER_H_ diff --git a/base/zygote_manager_unittest.cc b/base/zygote_manager_unittest.cc new file mode 100644 index 0000000..d879d1a --- /dev/null +++ b/base/zygote_manager_unittest.cc @@ -0,0 +1,266 @@ +// Copyright (c) 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" + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "base/eintr_wrapper.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/process_util.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" + +#include "testing/gtest/include/gtest/gtest.h" + +using file_util::Delete; +using file_util::WriteFile; +using file_util::ReadFileToString; +using file_util::GetCurrentDirectory; + +#if defined(OS_LINUX) +// ZygoteManager is only used on Linux at the moment + +typedef testing::Test ZygoteManagerTest; + +TEST_F(ZygoteManagerTest, Ping) { + base::ZygoteManager zm; + + scoped_ptr< std::vector<std::string> > new_argv; + new_argv.reset(zm.Start()); + EXPECT_TRUE(new_argv.get() == NULL); + // Measure round trip time + base::TimeDelta delta; + EXPECT_EQ(zm.Ping(&delta), true); + EXPECT_LT(delta.InMilliseconds(), 5000); +} + +TEST_F(ZygoteManagerTest, SpawnChild) { + base::ZygoteManager zm; + const int kDummyChildExitCode = 39; + + scoped_ptr< std::vector<std::string> > new_argv; + new_argv.reset(zm.Start()); + if (new_argv.get() == NULL) { + // original process + // Launch a child process + std::vector<std::string> myargs; + myargs.push_back(std::string("0thArg")); + myargs.push_back(std::string("1stArg")); + base::file_handle_mapping_vector no_files; + pid_t child = zm.LongFork(myargs, no_files); + EXPECT_NE(child, -1); + EXPECT_NE(child, 0); + LOG(INFO) << "child pid " << child; + + // ZygoteManager doesn't support waiting for exit status + int status; + int err = HANDLE_EINTR(waitpid(child, &status, 0)); + EXPECT_EQ(-1, err); + EXPECT_EQ(ECHILD, errno); + } else { + LOG(INFO) << "Hello from child!"; + // child process + std::string arg0(new_argv.get()->at(0)); + std::string arg1(new_argv.get()->at(1)); + EXPECT_EQ(arg0, std::string("0thArg")); + EXPECT_EQ(arg1, std::string("1stArg")); + exit(kDummyChildExitCode); + } +} + +TEST_F(ZygoteManagerTest, MapFile) { + base::ZygoteManager zm; + const int kDummyChildExitCode = 39; + const int kSpecialFDSlot = 5; + + scoped_ptr< std::vector<std::string> > new_argv; + new_argv.reset(zm.Start()); + if (new_argv.get() == NULL) { + // original process + // Launch a child process + std::vector<std::string> myargs; + myargs.push_back(std::string("0thArg")); + myargs.push_back(std::string("1stArg")); + base::file_handle_mapping_vector fds_to_map; + int fd = open("/tmp/zygote_manager_unittest.tmp", O_CREAT|O_RDWR|O_TRUNC, 0644); + fds_to_map.push_back(std::pair<int, int>(fd, kSpecialFDSlot)); + pid_t child = zm.LongFork(myargs, fds_to_map); + EXPECT_NE(child, -1); + EXPECT_NE(child, 0); + // FIXME: really wait for child + sleep(3); + char buf[100]; + + // Expect fd to be seeked to end, so reading without seeking should fail + off_t loc = lseek(fd, 0L, SEEK_CUR); + EXPECT_EQ(3, loc); + + memset(buf, 0, sizeof(buf)); + int nread = read(fd, buf, 5); + EXPECT_EQ(0, nread); + + // Try again from beginning + lseek(fd, 0L, SEEK_SET); + memset(buf, 0, sizeof(buf)); + nread = read(fd, buf, 2); + EXPECT_EQ(2, nread); + EXPECT_EQ(strncmp(buf, "hi", 2), 0); + close(fd); + } else { + // child process + // Write three bytes; this happens to seek the file descriptor to the end. + int nwritten = write(kSpecialFDSlot, "hi\n", 3); + EXPECT_EQ(3, nwritten); + exit(kDummyChildExitCode); + } +} + +TEST_F(ZygoteManagerTest, OpenFile) { + base::ZygoteManager zm; + + scoped_ptr< std::vector<std::string> > new_argv; + new_argv.reset(zm.Start()); + EXPECT_EQ(NULL, new_argv.get()); + + const char kSomeText[] = "foobar\n"; + + // Verify that we disallow nonabsolute paths. + FilePath badfilepath(FilePath::kCurrentDirectory); + badfilepath = badfilepath.AppendASCII("zygote_manager_test.pak"); + ASSERT_FALSE(badfilepath.IsAbsolute()); + EXPECT_NE(-1, WriteFile(badfilepath, kSomeText, strlen(kSomeText))); + std::string badfilename = WideToASCII(badfilepath.ToWStringHack()); + int fd = zm.OpenFile(badfilename); + EXPECT_EQ(-1, fd); + EXPECT_TRUE(Delete(badfilepath, false)); + + // Verify that we disallow non-plain files. + ASSERT_TRUE(GetCurrentDirectory(&badfilepath)); + badfilepath = badfilepath.AppendASCII("zygote_manager_test.pak"); + std::string badfilenameA = WideToASCII(badfilepath.ToWStringHack()); + EXPECT_EQ(0, mkfifo(badfilenameA.c_str(), 0644)); + badfilename = WideToASCII(badfilepath.ToWStringHack()); + fd = zm.OpenFile(badfilename); + ASSERT_EQ(-1, fd); + EXPECT_TRUE(Delete(badfilepath, false)); + + // Verify that we disallow files not ending in .pak. + ASSERT_TRUE(GetCurrentDirectory(&badfilepath)); + badfilepath = badfilepath.AppendASCII("zygote_manager_test.tmp"); + ASSERT_NE(-1, WriteFile(badfilepath, kSomeText, strlen(kSomeText))); + badfilename = WideToASCII(badfilepath.ToWStringHack()); + fd = zm.OpenFile(badfilename); + ASSERT_EQ(-1, fd); + EXPECT_TRUE(Delete(badfilepath, false)); + + // Verify that we disallow files in /etc + badfilepath = FilePath(FILE_PATH_LITERAL("/")); + badfilepath = badfilepath.AppendASCII("etc"); + badfilepath = badfilepath.AppendASCII("hosts"); + EXPECT_TRUE(badfilepath.IsAbsolute()); + badfilename = WideToASCII(badfilepath.ToWStringHack()); + fd = zm.OpenFile(badfilename); + ASSERT_EQ(-1, fd); + + // Verify that we disallow files in /dev + badfilepath = FilePath(FILE_PATH_LITERAL("/")); + badfilepath = badfilepath.AppendASCII("dev"); + badfilepath = badfilepath.AppendASCII("tty"); + EXPECT_TRUE(badfilepath.IsAbsolute()); + badfilename = WideToASCII(badfilepath.ToWStringHack()); + fd = zm.OpenFile(badfilename); + ASSERT_EQ(-1, fd); + + // Verify that we allow absolute paths with filename ending in .pak, + // and that we can open them a second time even if they were + // deleted after we opened them the first time. + // Because of our restrictive filename checks, can't put + // test file in /tmp, so put it in current directory. + FilePath goodfilepath; + ASSERT_TRUE(GetCurrentDirectory(&goodfilepath)); + goodfilepath = goodfilepath.AppendASCII("zygote_manager_test.pak"); + ASSERT_NE(-1, WriteFile(goodfilepath, kSomeText, strlen(kSomeText))); + std::string goodfilename = WideToASCII(goodfilepath.ToWStringHack()); + for (int i = 0; i < 2; i++) { + fd = zm.OpenFile(goodfilename); + ASSERT_NE(-1, fd); + char buf[sizeof(kSomeText)]; + // Can't use read because it depends on file position. + // (In practice these files are mmapped.) + int nread = pread(fd, buf, strlen(kSomeText), 0); + ASSERT_EQ(strlen(kSomeText), static_cast<size_t>(nread)); + EXPECT_EQ(0, strncmp(buf, kSomeText, strlen(kSomeText))); + EXPECT_EQ(0, close(fd)); + + // oddly, our Delete returns true for nonexistant files. + EXPECT_EQ(true, Delete(goodfilepath, false)); + } +} + +TEST_F(ZygoteManagerTest, ChildOpenFile) { + base::ZygoteManager zm; + const int kDummyChildExitCode = 39; + + const char kSomeText[] = "foobar\n"; + + FilePath resultfilepath(FILE_PATH_LITERAL("/tmp")); + resultfilepath = resultfilepath.AppendASCII("zygote_manager_test_result.tmp"); + EXPECT_EQ(true, Delete(resultfilepath, false)); + + scoped_ptr< std::vector<std::string> > new_argv; + new_argv.reset(zm.Start()); + if (new_argv.get() == NULL) { + // original process + // Launch a child process + std::vector<std::string> myargs; + base::file_handle_mapping_vector no_files; + pid_t child = zm.LongFork(myargs, no_files); + EXPECT_NE(child, -1); + EXPECT_NE(child, 0); + LOG(INFO) << "child pid " << child; + + // Wait for resultfile to be created + std::string result; + int nloops = 0; + while (!ReadFileToString(resultfilepath, &result)) { + sleep(1); + ++nloops; + ASSERT_NE(10, nloops); + } + ASSERT_EQ(result, std::string(kSomeText)); + + EXPECT_EQ(true, Delete(resultfilepath, false)); + + } else { + LOG(INFO) << "Hello from child!"; + + FilePath goodfilepath; + ASSERT_TRUE(GetCurrentDirectory(&goodfilepath)); + goodfilepath = goodfilepath.AppendASCII("zygote_manager_test.pak"); + ASSERT_NE(-1, WriteFile(goodfilepath, kSomeText, strlen(kSomeText))); + std::string goodfilename = WideToASCII(goodfilepath.ToWStringHack()); + int fd = zm.OpenFile(goodfilename); + EXPECT_EQ(true, Delete(goodfilepath, false)); + ASSERT_NE(-1, fd); + char buf[sizeof(kSomeText)]; + // Can't use read because it depends on file position. + // (In practice these files are mmapped.) + int nread = pread(fd, buf, strlen(kSomeText), 0); + ASSERT_EQ(strlen(kSomeText), static_cast<size_t>(nread)); + EXPECT_EQ(0, strncmp(buf, kSomeText, strlen(kSomeText))); + EXPECT_EQ(0, close(fd)); + + ASSERT_NE(-1, WriteFile(resultfilepath, kSomeText, strlen(kSomeText))); + + exit(kDummyChildExitCode); + } +} + +#endif diff --git a/chrome/app/breakpad_linux.cc b/chrome/app/breakpad_linux.cc index 0712241..0ee0bed 100644 --- a/chrome/app/breakpad_linux.cc +++ b/chrome/app/breakpad_linux.cc @@ -14,6 +14,7 @@ #include "base/file_version_info_linux.h" #include "base/path_service.h" #include "base/rand_util.h" +#include "base/reserved_file_descriptors.h" #include "breakpad/linux/directory_reader.h" #include "breakpad/linux/exception_handler.h" #include "breakpad/linux/linux_libc_support.h" @@ -501,8 +502,7 @@ RendererCrashHandler(const void* crash_context, size_t crash_context_size, void EnableRendererCrashDumping() { // When the browser forks off our process, it installs the crash signal file - // descriptor in this slot: - static const int kMagicCrashSignalFd = 4; + // descriptor in slot kMagicCrashSignalFd. // We deliberately leak this object. google_breakpad::ExceptionHandler* handler = diff --git a/chrome/app/chrome_dll_main.cc b/chrome/app/chrome_dll_main.cc index 006d923..b6ba22f 100644 --- a/chrome/app/chrome_dll_main.cc +++ b/chrome/app/chrome_dll_main.cc @@ -45,6 +45,9 @@ #if defined(OS_WIN) #include "base/win_util.h" #endif +#if defined(OS_LINUX) +#include "base/zygote_manager.h" +#endif #if defined(OS_MACOSX) #include "chrome/app/breakpad_mac.h" #elif defined(OS_LINUX) @@ -292,6 +295,18 @@ int ChromeMain(int argc, const char** argv) { // Initialize the command line. #if defined(OS_WIN) CommandLine::Init(0, NULL); +#elif defined(OS_LINUX) + base::ZygoteManager* zm = base::ZygoteManager::Get(); + std::vector<std::string>* zargv = NULL; + if (zm) + zargv = zm->Start(); + if (zargv) { + // Forked child. + CommandLine::Init(*zargv); + } else { + // Original process. + CommandLine::Init(argc, argv); + } #else CommandLine::Init(argc, argv); #endif diff --git a/chrome/browser/renderer_host/browser_render_process_host.cc b/chrome/browser/renderer_host/browser_render_process_host.cc index 771b4d6..9be8769 100644 --- a/chrome/browser/renderer_host/browser_render_process_host.cc +++ b/chrome/browser/renderer_host/browser_render_process_host.cc @@ -22,6 +22,7 @@ #include "base/path_service.h" #include "base/process_util.h" #include "base/rand_util.h" +#include "base/reserved_file_descriptors.h" #include "base/scoped_ptr.h" #include "base/shared_memory.h" #include "base/singleton.h" @@ -345,10 +346,12 @@ bool BrowserRenderProcessHost::Init() { const int crash_signal_fd = Singleton<RenderCrashHandlerHostLinux>()->GetDeathSignalSocket(); if (crash_signal_fd >= 0) - fds_to_map.push_back(std::make_pair(crash_signal_fd, 4)); -#endif + fds_to_map.push_back(std::make_pair(crash_signal_fd, kMagicCrashSignalFd)); + base::ForkApp(cmd_line.argv(), fds_to_map, &process); +#else base::LaunchApp(cmd_line.argv(), fds_to_map, false, &process); #endif +#endif if (!process) { channel_.reset(); return false; diff --git a/chrome/common/ipc_channel_posix.cc b/chrome/common/ipc_channel_posix.cc index b7a9666..52cad9d 100644 --- a/chrome/common/ipc_channel_posix.cc +++ b/chrome/common/ipc_channel_posix.cc @@ -20,6 +20,7 @@ #include "base/lock.h" #include "base/logging.h" #include "base/process_util.h" +#include "base/reserved_file_descriptors.h" #include "base/scoped_ptr.h" #include "base/string_util.h" #include "base/singleton.h" @@ -114,10 +115,6 @@ class PipeMap { ChannelToFDMap map_; }; -// This is the file descriptor number that a client process expects to find its -// IPC socket. -static const int kClientChannelFd = 3; - // Used to map a channel name to the equivalent FD # in the client process. int ChannelNameToClientFD(const std::string& channel_id) { // See the large block comment above PipeMap for the reasoning here. @@ -127,6 +124,9 @@ int ChannelNameToClientFD(const std::string& channel_id) { // If we don't find an entry, we assume that the correct value has been // inserted in the magic slot. + // kClientChannelFd is the file descriptor number that a client process + // expects to find its IPC socket; see reserved_file_descriptors.h. + return kClientChannelFd; } diff --git a/chrome/common/process_watcher_posix.cc b/chrome/common/process_watcher_posix.cc index f1ae4f4..497b80b 100644 --- a/chrome/common/process_watcher_posix.cc +++ b/chrome/common/process_watcher_posix.cc @@ -11,6 +11,7 @@ #include "base/eintr_wrapper.h" #include "base/platform_thread.h" +#include "base/zygote_manager.h" // Return true if the given child is dead. This will also reap the process. // Doesn't block. @@ -69,8 +70,20 @@ class BackgroundReaper : public PlatformThread::Delegate { // static void ProcessWatcher::EnsureProcessTerminated(base::ProcessHandle process) { // If the child is already dead, then there's nothing to do - if (IsChildDead(process)) + const int result = HANDLE_EINTR(waitpid(process, NULL, WNOHANG)); + if (result > 0) return; + if (result == -1) { +#if defined(OS_LINUX) + // If it wasn't our child, maybe it was the zygote manager's child + base::ZygoteManager* zm = base::ZygoteManager::Get(); + if (zm) { + zm->EnsureProcessTerminated(process); + return; + } +#endif // defined(OS_LINUX) + NOTREACHED(); + } BackgroundReaper* reaper = new BackgroundReaper(process); PlatformThread::CreateNonJoinable(0, reaper); |