Files
sdk/runtime/bin/process_linux.cc
T
kustermann@google.com ba62bae621 Fix for leaking file descriptor issue
When calling a non-existent executable, a pair of file descriptors will be
created with pipe(). One end goes to the exitcode handler thread, the other end
goes to the epoll() thread.

If an error occurs during process creation, the pipe is still created and the
exit handler thread writes the exitcode to the pipe, but the other file
descriptor is not added to the epoll() set and is therefore leaked. (i.e.
Process_Start ignores the other end of the pipe if exec() failed in
Proces::Start()).

R=sgjesse@google.com

Review URL: https://codereview.chromium.org//15980006

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@23534 260f80e4-7a28-3924-810f-c04153c831b5
2013-06-03 09:41:36 +00:00

585 lines
18 KiB
C++

// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "platform/globals.h"
#if defined(TARGET_OS_LINUX)
#include "bin/process.h"
#include <errno.h> // NOLINT
#include <fcntl.h> // NOLINT
#include <poll.h> // NOLINT
#include <signal.h> // NOLINT
#include <stdio.h> // NOLINT
#include <stdlib.h> // NOLINT
#include <string.h> // NOLINT
#include <sys/wait.h> // NOLINT
#include <unistd.h> // NOLINT
#include "bin/fdutils.h"
#include "bin/log.h"
#include "bin/thread.h"
extern char **environ;
namespace dart {
namespace bin {
// ProcessInfo is used to map a process id to the file descriptor for
// the pipe used to communicate the exit code of the process to Dart.
// ProcessInfo objects are kept in the static singly-linked
// ProcessInfoList.
class ProcessInfo {
public:
ProcessInfo(pid_t pid, intptr_t fd) : pid_(pid), fd_(fd) { }
~ProcessInfo() {
int closed = TEMP_FAILURE_RETRY(close(fd_));
if (closed != 0) {
FATAL("Failed to close process exit code pipe");
}
}
pid_t pid() { return pid_; }
intptr_t fd() { return fd_; }
ProcessInfo* next() { return next_; }
void set_next(ProcessInfo* info) { next_ = info; }
private:
pid_t pid_;
intptr_t fd_;
ProcessInfo* next_;
};
// Singly-linked list of ProcessInfo objects for all active processes
// started from Dart.
class ProcessInfoList {
public:
static void AddProcess(pid_t pid, intptr_t fd) {
MutexLocker locker(&mutex_);
ProcessInfo* info = new ProcessInfo(pid, fd);
info->set_next(active_processes_);
active_processes_ = info;
}
static intptr_t LookupProcessExitFd(pid_t pid) {
MutexLocker locker(&mutex_);
ProcessInfo* current = active_processes_;
while (current != NULL) {
if (current->pid() == pid) {
return current->fd();
}
current = current->next();
}
return 0;
}
static void RemoveProcess(pid_t pid) {
MutexLocker locker(&mutex_);
ProcessInfo* prev = NULL;
ProcessInfo* current = active_processes_;
while (current != NULL) {
if (current->pid() == pid) {
if (prev == NULL) {
active_processes_ = current->next();
} else {
prev->set_next(current->next());
}
delete current;
return;
}
prev = current;
current = current->next();
}
}
private:
// Linked list of ProcessInfo objects for all active processes
// started from Dart code.
static ProcessInfo* active_processes_;
// Mutex protecting all accesses to the linked list of active
// processes.
static dart::Mutex mutex_;
};
ProcessInfo* ProcessInfoList::active_processes_ = NULL;
dart::Mutex ProcessInfoList::mutex_;
// The exit code handler sets up a separate thread which is signalled
// on SIGCHLD. That separate thread can then get the exit code from
// processes that have exited and communicate it to Dart through the
// event loop.
class ExitCodeHandler {
public:
// Ensure that the ExitCodeHandler has been initialized.
static bool EnsureInitialized() {
// Multiple isolates could be starting processes at the same
// time. Make sure that only one of them initializes the
// ExitCodeHandler.
MutexLocker locker(&mutex_);
if (initialized_) {
return true;
}
// Allocate a pipe that the signal handler can write a byte to and
// that the exit handler thread can poll.
int result = TEMP_FAILURE_RETRY(pipe(sig_chld_fds_));
if (result < 0) {
return false;
}
FDUtils::SetCloseOnExec(sig_chld_fds_[0]);
FDUtils::SetCloseOnExec(sig_chld_fds_[1]);
// Start thread that polls the pipe and handles process exits when
// data is received on the pipe.
result = dart::Thread::Start(ExitCodeHandlerEntry, sig_chld_fds_[0]);
if (result != 0) {
FATAL1("Failed to start exit code handler worker thread %d", result);
}
// Mark write end non-blocking.
FDUtils::SetNonBlocking(sig_chld_fds_[1]);
// Thread started and the ExitCodeHandler is initialized.
initialized_ = true;
return true;
}
// Get the write end of the pipe.
static int WakeUpFd() {
ASSERT(initialized_);
return sig_chld_fds_[1];
}
static void TerminateExitCodeThread() {
MutexLocker locker(&mutex_);
if (!initialized_) {
return;
}
uint8_t data = kThreadTerminateByte;
ssize_t result =
TEMP_FAILURE_RETRY(write(ExitCodeHandler::WakeUpFd(), &data, 1));
if (result < 1) {
perror("Failed to write to wake-up fd to terminate exit code thread");
}
{
MonitorLocker terminate_locker(&thread_terminate_monitor_);
while (!thread_terminated_) {
terminate_locker.Wait();
}
}
}
static void ExitCodeThreadTerminated() {
MonitorLocker locker(&thread_terminate_monitor_);
thread_terminated_ = true;
locker.Notify();
}
private:
static const uint8_t kThreadTerminateByte = 1;
// GetProcessExitCodes is called on a separate thread when a SIGCHLD
// signal is received to retrieve the exit codes and post them to
// dart.
static void GetProcessExitCodes() {
pid_t pid = 0;
int status = 0;
while ((pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG))) > 0) {
int exit_code = 0;
int negative = 0;
if (WIFEXITED(status)) {
exit_code = WEXITSTATUS(status);
}
if (WIFSIGNALED(status)) {
exit_code = WTERMSIG(status);
negative = 1;
}
intptr_t exit_code_fd = ProcessInfoList::LookupProcessExitFd(pid);
if (exit_code_fd != 0) {
int message[2] = { exit_code, negative };
ssize_t result =
FDUtils::WriteToBlocking(exit_code_fd, &message, sizeof(message));
// If the process has been closed, the read end of the exit
// pipe has been closed. It is therefore not a problem that
// write fails with a broken pipe error. Other errors should
// not happen.
if (result != -1 && result != sizeof(message)) {
FATAL("Failed to write entire process exit message");
} else if (result == -1 && errno != EPIPE) {
FATAL1("Failed to write exit code: %d", errno);
}
ProcessInfoList::RemoveProcess(pid);
}
}
}
// Entry point for the separate exit code handler thread started by
// the ExitCodeHandler.
static void ExitCodeHandlerEntry(uword param) {
struct pollfd pollfds;
pollfds.fd = param;
pollfds.events = POLLIN;
while (true) {
int result = TEMP_FAILURE_RETRY(poll(&pollfds, 1, -1));
if (result == -1) {
ASSERT(EAGAIN == EWOULDBLOCK);
if (errno != EWOULDBLOCK) {
perror("ExitCodeHandler poll failed");
}
} else {
// Read the byte from the wake-up fd.
ASSERT(result = 1);
intptr_t data = 0;
ssize_t read_bytes = FDUtils::ReadFromBlocking(pollfds.fd, &data, 1);
if (read_bytes < 1) {
perror("Failed to read from wake-up fd in exit-code handler");
}
if (data == ExitCodeHandler::kThreadTerminateByte) {
ExitCodeThreadTerminated();
return;
}
// Get the exit code from all processes that have died.
GetProcessExitCodes();
}
}
}
static dart::Mutex mutex_;
static bool initialized_;
static int sig_chld_fds_[2];
static bool thread_terminated_;
static dart::Monitor thread_terminate_monitor_;
};
dart::Mutex ExitCodeHandler::mutex_;
bool ExitCodeHandler::initialized_ = false;
int ExitCodeHandler::sig_chld_fds_[2] = { 0, 0 };
bool ExitCodeHandler::thread_terminated_ = false;
dart::Monitor ExitCodeHandler::thread_terminate_monitor_;
static void SetChildOsErrorMessage(char** os_error_message) {
*os_error_message = strdup(strerror(errno));
}
static void SigChldHandler(int process_signal, siginfo_t* siginfo, void* tmp) {
// Save errno so it can be restored at the end.
int entry_errno = errno;
// Signal the exit code handler where the actual processing takes
// place.
ssize_t result =
TEMP_FAILURE_RETRY(write(ExitCodeHandler::WakeUpFd(), "", 1));
if (result < 1) {
perror("Failed to write to wake-up fd in SIGCHLD handler");
}
// Restore errno.
errno = entry_errno;
}
static void ReportChildError(int exec_control_fd) {
// In the case of failure in the child process write the errno and
// the OS error message to the exec control pipe and exit.
int child_errno = errno;
char* os_error_message = strerror(errno);
ASSERT(sizeof(child_errno) == sizeof(errno));
int bytes_written =
FDUtils::WriteToBlocking(
exec_control_fd, &child_errno, sizeof(child_errno));
if (bytes_written == sizeof(child_errno)) {
FDUtils::WriteToBlocking(
exec_control_fd, os_error_message, strlen(os_error_message) + 1);
}
TEMP_FAILURE_RETRY(close(exec_control_fd));
exit(1);
}
int Process::Start(const char* path,
char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
intptr_t environment_length,
intptr_t* in,
intptr_t* out,
intptr_t* err,
intptr_t* id,
intptr_t* exit_event,
char** os_error_message) {
pid_t pid;
int read_in[2]; // Pipe for stdout to child process.
int read_err[2]; // Pipe for stderr to child process.
int write_out[2]; // Pipe for stdin to child process.
int exec_control[2]; // Pipe to get the result from exec.
int result;
bool initialized = ExitCodeHandler::EnsureInitialized();
if (!initialized) {
SetChildOsErrorMessage(os_error_message);
Log::PrintErr(
"Error initializing exit code handler: %s\n",
*os_error_message);
return errno;
}
result = TEMP_FAILURE_RETRY(pipe(read_in));
if (result < 0) {
SetChildOsErrorMessage(os_error_message);
Log::PrintErr("Error pipe creation failed: %s\n", *os_error_message);
return errno;
}
FDUtils::SetCloseOnExec(read_in[0]);
result = TEMP_FAILURE_RETRY(pipe(read_err));
if (result < 0) {
SetChildOsErrorMessage(os_error_message);
TEMP_FAILURE_RETRY(close(read_in[0]));
TEMP_FAILURE_RETRY(close(read_in[1]));
Log::PrintErr("Error pipe creation failed: %s\n", *os_error_message);
return errno;
}
FDUtils::SetCloseOnExec(read_err[0]);
result = TEMP_FAILURE_RETRY(pipe(write_out));
if (result < 0) {
SetChildOsErrorMessage(os_error_message);
TEMP_FAILURE_RETRY(close(read_in[0]));
TEMP_FAILURE_RETRY(close(read_in[1]));
TEMP_FAILURE_RETRY(close(read_err[0]));
TEMP_FAILURE_RETRY(close(read_err[1]));
Log::PrintErr("Error pipe creation failed: %s\n", *os_error_message);
return errno;
}
FDUtils::SetCloseOnExec(write_out[1]);
result = TEMP_FAILURE_RETRY(pipe(exec_control));
if (result < 0) {
SetChildOsErrorMessage(os_error_message);
TEMP_FAILURE_RETRY(close(read_in[0]));
TEMP_FAILURE_RETRY(close(read_in[1]));
TEMP_FAILURE_RETRY(close(read_err[0]));
TEMP_FAILURE_RETRY(close(read_err[1]));
TEMP_FAILURE_RETRY(close(write_out[0]));
TEMP_FAILURE_RETRY(close(write_out[1]));
Log::PrintErr("Error pipe creation failed: %s\n", *os_error_message);
return errno;
}
FDUtils::SetCloseOnExec(exec_control[0]);
FDUtils::SetCloseOnExec(exec_control[1]);
if (result < 0) {
SetChildOsErrorMessage(os_error_message);
TEMP_FAILURE_RETRY(close(read_in[0]));
TEMP_FAILURE_RETRY(close(read_in[1]));
TEMP_FAILURE_RETRY(close(read_err[0]));
TEMP_FAILURE_RETRY(close(read_err[1]));
TEMP_FAILURE_RETRY(close(write_out[0]));
TEMP_FAILURE_RETRY(close(write_out[1]));
TEMP_FAILURE_RETRY(close(exec_control[0]));
TEMP_FAILURE_RETRY(close(exec_control[1]));
Log::PrintErr("fcntl failed: %s\n", *os_error_message);
return errno;
}
char** program_arguments = new char*[arguments_length + 2];
program_arguments[0] = const_cast<char*>(path);
for (int i = 0; i < arguments_length; i++) {
program_arguments[i + 1] = arguments[i];
}
program_arguments[arguments_length + 1] = NULL;
char** program_environment = NULL;
if (environment != NULL) {
program_environment = new char*[environment_length + 1];
for (int i = 0; i < environment_length; i++) {
program_environment[i] = environment[i];
}
program_environment[environment_length] = NULL;
}
struct sigaction act;
bzero(&act, sizeof(act));
act.sa_sigaction = SigChldHandler;
act.sa_flags = SA_NOCLDSTOP | SA_SIGINFO;
if (sigaction(SIGCHLD, &act, 0) != 0) {
perror("Process start: setting signal handler failed");
}
pid = TEMP_FAILURE_RETRY(fork());
if (pid < 0) {
SetChildOsErrorMessage(os_error_message);
delete[] program_arguments;
TEMP_FAILURE_RETRY(close(read_in[0]));
TEMP_FAILURE_RETRY(close(read_in[1]));
TEMP_FAILURE_RETRY(close(read_err[0]));
TEMP_FAILURE_RETRY(close(read_err[1]));
TEMP_FAILURE_RETRY(close(write_out[0]));
TEMP_FAILURE_RETRY(close(write_out[1]));
TEMP_FAILURE_RETRY(close(exec_control[0]));
TEMP_FAILURE_RETRY(close(exec_control[1]));
return errno;
} else if (pid == 0) {
// Wait for parent process before setting up the child process.
char msg;
int bytes_read = FDUtils::ReadFromBlocking(read_in[0], &msg, sizeof(msg));
if (bytes_read != sizeof(msg)) {
perror("Failed receiving notification message");
exit(1);
}
TEMP_FAILURE_RETRY(close(write_out[1]));
TEMP_FAILURE_RETRY(close(read_in[0]));
TEMP_FAILURE_RETRY(close(read_err[0]));
TEMP_FAILURE_RETRY(close(exec_control[0]));
if (TEMP_FAILURE_RETRY(dup2(write_out[0], STDIN_FILENO)) == -1) {
ReportChildError(exec_control[1]);
}
TEMP_FAILURE_RETRY(close(write_out[0]));
if (TEMP_FAILURE_RETRY(dup2(read_in[1], STDOUT_FILENO)) == -1) {
ReportChildError(exec_control[1]);
}
TEMP_FAILURE_RETRY(close(read_in[1]));
if (TEMP_FAILURE_RETRY(dup2(read_err[1], STDERR_FILENO)) == -1) {
ReportChildError(exec_control[1]);
}
TEMP_FAILURE_RETRY(close(read_err[1]));
if (working_directory != NULL &&
TEMP_FAILURE_RETRY(chdir(working_directory)) == -1) {
ReportChildError(exec_control[1]);
}
if (program_environment != NULL) {
environ = program_environment;
}
TEMP_FAILURE_RETRY(
execvp(path, const_cast<char* const*>(program_arguments)));
ReportChildError(exec_control[1]);
}
// The arguments and environment for the spawned process are not needed
// any longer.
delete[] program_arguments;
delete[] program_environment;
int event_fds[2];
result = TEMP_FAILURE_RETRY(pipe(event_fds));
if (result < 0) {
SetChildOsErrorMessage(os_error_message);
TEMP_FAILURE_RETRY(close(read_in[0]));
TEMP_FAILURE_RETRY(close(read_in[1]));
TEMP_FAILURE_RETRY(close(read_err[0]));
TEMP_FAILURE_RETRY(close(read_err[1]));
TEMP_FAILURE_RETRY(close(write_out[0]));
TEMP_FAILURE_RETRY(close(write_out[1]));
Log::PrintErr("Error pipe creation failed: %s\n", *os_error_message);
return errno;
}
FDUtils::SetCloseOnExec(event_fds[0]);
FDUtils::SetCloseOnExec(event_fds[1]);
ProcessInfoList::AddProcess(pid, event_fds[1]);
*exit_event = event_fds[0];
FDUtils::SetNonBlocking(event_fds[0]);
// Notify child process to start.
char msg = '1';
result = FDUtils::WriteToBlocking(read_in[1], &msg, sizeof(msg));
if (result != sizeof(msg)) {
perror("Failed sending notification message");
}
// Read exec result from child. If no data is returned the exec was
// successful and the exec call closed the pipe. Otherwise the errno
// is written to the pipe.
TEMP_FAILURE_RETRY(close(exec_control[1]));
int child_errno;
int bytes_read = -1;
ASSERT(sizeof(child_errno) == sizeof(errno));
bytes_read =
FDUtils::ReadFromBlocking(
exec_control[0], &child_errno, sizeof(child_errno));
if (bytes_read == sizeof(child_errno)) {
static const int kMaxMessageSize = 256;
char* message = static_cast<char*>(malloc(kMaxMessageSize));
bytes_read = FDUtils::ReadFromBlocking(exec_control[0],
message,
kMaxMessageSize);
message[kMaxMessageSize - 1] = '\0';
*os_error_message = message;
}
TEMP_FAILURE_RETRY(close(exec_control[0]));
// Return error code if any failures.
if (bytes_read != 0) {
TEMP_FAILURE_RETRY(close(read_in[0]));
TEMP_FAILURE_RETRY(close(read_in[1]));
TEMP_FAILURE_RETRY(close(read_err[0]));
TEMP_FAILURE_RETRY(close(read_err[1]));
TEMP_FAILURE_RETRY(close(write_out[0]));
TEMP_FAILURE_RETRY(close(write_out[1]));
// Since exec() failed, we're not interested in the exit code.
// We close the reading side of the exit code pipe here.
// GetProcessExitCodes will get a broken pipe error when it tries to write
// to the writing side of the pipe and it will ignore the error.
TEMP_FAILURE_RETRY(close(*exit_event));
*exit_event = -1;
if (bytes_read == -1) {
return errno; // Read failed.
} else {
return child_errno; // Exec failed.
}
}
FDUtils::SetNonBlocking(read_in[0]);
*in = read_in[0];
TEMP_FAILURE_RETRY(close(read_in[1]));
FDUtils::SetNonBlocking(write_out[1]);
*out = write_out[1];
TEMP_FAILURE_RETRY(close(write_out[0]));
FDUtils::SetNonBlocking(read_err[0]);
*err = read_err[0];
TEMP_FAILURE_RETRY(close(read_err[1]));
*id = pid;
return 0;
}
bool Process::Kill(intptr_t id, int signal) {
return (TEMP_FAILURE_RETRY(kill(id, signal)) != -1);
}
void Process::TerminateExitCodeHandler() {
ExitCodeHandler::TerminateExitCodeThread();
}
intptr_t Process::CurrentProcessId() {
return static_cast<intptr_t>(getpid());
}
} // namespace bin
} // namespace dart
#endif // defined(TARGET_OS_LINUX)