dart-sdk/runtime/bin/process_macos.cc
asiva be3c482bc3 Reinitialize the static fields in the corresponding Init functions so
that we do not have any state mismatch when the Dart VM is initialzed
and cleaned up multiple times.

TEST=existing unit test in the engine which is failing in a flaky manner

Change-Id: I073ca2b53a8c3d386be46b5222d547a29513714a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/207642
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Siva Annamalai <asiva@google.com>
2021-07-21 18:12:34 +00:00

1200 lines
33 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(DART_HOST_OS_MACOS)
#include "bin/process.h"
#if !DART_HOST_OS_IOS
#include <crt_externs.h> // NOLINT
#endif
#include <errno.h> // NOLINT
#include <fcntl.h> // NOLINT
#include <mach/mach.h> // NOLINT
#include <poll.h> // NOLINT
#include <signal.h> // NOLINT
#include <stdio.h> // NOLINT
#include <stdlib.h> // NOLINT
#include <string.h> // NOLINT
#include <unistd.h> // NOLINT
#include "bin/dartutils.h"
#include "bin/fdutils.h"
#include "bin/lockers.h"
#include "bin/namespace.h"
#include "bin/thread.h"
#include "platform/syslog.h"
#include "platform/signal_blocker.h"
#include "platform/utils.h"
namespace dart {
namespace bin {
int Process::global_exit_code_ = 0;
Mutex* Process::global_exit_code_mutex_ = nullptr;
Process::ExitHook Process::exit_hook_ = NULL;
// 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 = 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_;
DISALLOW_COPY_AND_ASSIGN(ProcessInfo);
};
// Singly-linked list of ProcessInfo objects for all active processes
// started from Dart.
class ProcessInfoList {
public:
static void Init();
static void Cleanup();
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 Mutex* mutex_;
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(ProcessInfoList);
};
ProcessInfo* ProcessInfoList::active_processes_ = NULL;
Mutex* ProcessInfoList::mutex_ = nullptr;
// The exit code handler sets up a separate thread which waits for child
// processes to terminate. 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:
static void Init();
static void Cleanup();
// Notify the ExitCodeHandler that another process exists.
static void ProcessStarted() {
// Multiple isolates could be starting processes at the same
// time. Make sure that only one ExitCodeHandler thread exists.
MonitorLocker locker(monitor_);
process_count_++;
monitor_->Notify();
if (running_) {
return;
}
// Start thread that handles process exits when wait returns.
int result =
Thread::Start("dart:io Process.start", ExitCodeHandlerEntry, 0);
if (result != 0) {
FATAL1("Failed to start exit code handler worker thread %d", result);
}
running_ = true;
}
static void TerminateExitCodeThread() {
MonitorLocker locker(monitor_);
if (!running_) {
return;
}
// Set terminate_done_ to false, so we can use it as a guard for our
// monitor.
running_ = false;
// Fork to wake up waitpid.
if (TEMP_FAILURE_RETRY(fork()) == 0) {
exit(0);
}
monitor_->Notify();
while (!terminate_done_) {
monitor_->Wait(Monitor::kNoTimeout);
}
}
private:
// Entry point for the separate exit code handler thread started by
// the ExitCodeHandler.
static void ExitCodeHandlerEntry(uword param) {
pid_t pid = 0;
int status = 0;
while (true) {
{
MonitorLocker locker(monitor_);
while (running_ && process_count_ == 0) {
monitor_->Wait(Monitor::kNoTimeout);
}
if (!running_) {
terminate_done_ = true;
monitor_->Notify();
return;
}
}
if ((pid = TEMP_FAILURE_RETRY(wait(&status))) > 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);
{
MonitorLocker locker(monitor_);
process_count_--;
}
}
} else if (pid < 0) {
FATAL1("Wait for process exit failed: %d", errno);
}
}
}
static bool terminate_done_;
static int process_count_;
static bool running_;
static Monitor* monitor_;
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(ExitCodeHandler);
};
bool ExitCodeHandler::running_ = false;
int ExitCodeHandler::process_count_ = 0;
bool ExitCodeHandler::terminate_done_ = false;
Monitor* ExitCodeHandler::monitor_ = nullptr;
class ProcessStarter {
public:
ProcessStarter(const char* path,
char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
intptr_t environment_length,
ProcessStartMode mode,
intptr_t* in,
intptr_t* out,
intptr_t* err,
intptr_t* id,
intptr_t* exit_event,
char** os_error_message)
: path_(path),
working_directory_(working_directory),
mode_(mode),
in_(in),
out_(out),
err_(err),
id_(id),
exit_event_(exit_event),
os_error_message_(os_error_message) {
read_in_[0] = -1;
read_in_[1] = -1;
read_err_[0] = -1;
read_err_[1] = -1;
write_out_[0] = -1;
write_out_[1] = -1;
exec_control_[0] = -1;
exec_control_[1] = -1;
program_arguments_ = reinterpret_cast<char**>(Dart_ScopeAllocate(
(arguments_length + 2) * sizeof(*program_arguments_)));
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;
program_environment_ = NULL;
if (environment != NULL) {
program_environment_ = reinterpret_cast<char**>(Dart_ScopeAllocate(
(environment_length + 1) * sizeof(*program_environment_)));
for (int i = 0; i < environment_length; i++) {
program_environment_[i] = environment[i];
}
program_environment_[environment_length] = NULL;
}
}
int Start() {
// Create pipes required.
int err = CreatePipes();
if (err != 0) {
return err;
}
// Fork to create the new process.
pid_t pid = TEMP_FAILURE_RETRY(fork());
if (pid < 0) {
// Failed to fork.
return CleanupAndReturnError();
} else if (pid == 0) {
// This runs in the new process.
NewProcess();
}
// This runs in the original process.
// If the child process is not started in detached mode, be sure to
// listen for exit-codes, now that we have a non detached child process
// and also Register this child process.
if (Process::ModeIsAttached(mode_)) {
ExitCodeHandler::ProcessStarted();
err = RegisterProcess(pid);
if (err != 0) {
return err;
}
}
// Notify child process to start. This is done to delay the call to exec
// until the process is registered above, and we are ready to receive the
// exit code.
char msg = '1';
int bytes_written =
FDUtils::WriteToBlocking(read_in_[1], &msg, sizeof(msg));
if (bytes_written != sizeof(msg)) {
return CleanupAndReturnError();
}
// Read the result of executing the child process.
close(exec_control_[1]);
exec_control_[1] = -1;
if (Process::ModeIsAttached(mode_)) {
err = ReadExecResult();
} else {
err = ReadDetachedExecResult(&pid);
}
close(exec_control_[0]);
exec_control_[0] = -1;
// Return error code if any failures.
if (err != 0) {
if (Process::ModeIsAttached(mode_)) {
// 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.
close(*exit_event_);
*exit_event_ = -1;
}
CloseAllPipes();
return err;
}
if (Process::ModeHasStdio(mode_)) {
// Connect stdio, stdout and stderr.
FDUtils::SetNonBlocking(read_in_[0]);
*in_ = read_in_[0];
close(read_in_[1]);
FDUtils::SetNonBlocking(write_out_[1]);
*out_ = write_out_[1];
close(write_out_[0]);
FDUtils::SetNonBlocking(read_err_[0]);
*err_ = read_err_[0];
close(read_err_[1]);
} else {
// Close all fds.
close(read_in_[0]);
close(read_in_[1]);
ASSERT(write_out_[0] == -1);
ASSERT(write_out_[1] == -1);
ASSERT(read_err_[0] == -1);
ASSERT(read_err_[1] == -1);
}
ASSERT(exec_control_[0] == -1);
ASSERT(exec_control_[1] == -1);
*id_ = pid;
return 0;
}
private:
int CreatePipes() {
int result;
result = TEMP_FAILURE_RETRY(pipe(exec_control_));
if (result < 0) {
return CleanupAndReturnError();
}
FDUtils::SetCloseOnExec(exec_control_[0]);
FDUtils::SetCloseOnExec(exec_control_[1]);
// For a detached process the pipe to connect stdout is still used for
// signaling when to do the first fork.
result = TEMP_FAILURE_RETRY(pipe(read_in_));
if (result < 0) {
return CleanupAndReturnError();
}
FDUtils::SetCloseOnExec(read_in_[0]);
FDUtils::SetCloseOnExec(read_in_[1]);
// For detached processes the pipe to connect stderr and stdin are not used.
if (Process::ModeHasStdio(mode_)) {
result = TEMP_FAILURE_RETRY(pipe(read_err_));
if (result < 0) {
return CleanupAndReturnError();
}
FDUtils::SetCloseOnExec(read_err_[0]);
FDUtils::SetCloseOnExec(read_err_[1]);
result = TEMP_FAILURE_RETRY(pipe(write_out_));
if (result < 0) {
return CleanupAndReturnError();
}
FDUtils::SetCloseOnExec(write_out_[0]);
FDUtils::SetCloseOnExec(write_out_[1]);
}
return 0;
}
void NewProcess() {
// 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);
}
if (Process::ModeIsAttached(mode_)) {
ExecProcess();
} else {
ExecDetachedProcess();
}
}
void ExecProcess() {
if (mode_ == kNormal) {
if (TEMP_FAILURE_RETRY(dup2(write_out_[0], STDIN_FILENO)) == -1) {
ReportChildError();
}
if (TEMP_FAILURE_RETRY(dup2(read_in_[1], STDOUT_FILENO)) == -1) {
ReportChildError();
}
if (TEMP_FAILURE_RETRY(dup2(read_err_[1], STDERR_FILENO)) == -1) {
ReportChildError();
}
} else {
ASSERT(mode_ == kInheritStdio);
}
if (working_directory_ != NULL &&
TEMP_FAILURE_RETRY(chdir(working_directory_)) == -1) {
ReportChildError();
}
#if !DART_HOST_OS_IOS
if (program_environment_ != NULL) {
// On MacOS you have to do a bit of magic to get to the
// environment strings.
char*** environ = _NSGetEnviron();
*environ = program_environment_;
}
#endif
execvp(path_, const_cast<char* const*>(program_arguments_));
ReportChildError();
}
void ExecDetachedProcess() {
if (mode_ == kDetached) {
ASSERT(write_out_[0] == -1);
ASSERT(write_out_[1] == -1);
ASSERT(read_err_[0] == -1);
ASSERT(read_err_[1] == -1);
// For a detached process the pipe to connect stdout is only used for
// signaling when to do the first fork.
close(read_in_[0]);
read_in_[0] = -1;
close(read_in_[1]);
read_in_[1] = -1;
} else {
// Don't close any fds if keeping stdio open to the detached process.
ASSERT(mode_ == kDetachedWithStdio);
}
// Fork once more to start a new session.
pid_t pid = TEMP_FAILURE_RETRY(fork());
if (pid < 0) {
ReportChildError();
} else if (pid == 0) {
// Start a new session.
if (TEMP_FAILURE_RETRY(setsid()) == -1) {
ReportChildError();
} else {
// Do a final fork to not be the session leader.
pid = TEMP_FAILURE_RETRY(fork());
if (pid < 0) {
ReportChildError();
} else if (pid == 0) {
if (mode_ == kDetached) {
SetupDetached();
} else {
SetupDetachedWithStdio();
}
if ((working_directory_ != NULL) &&
(TEMP_FAILURE_RETRY(chdir(working_directory_)) == -1)) {
ReportChildError();
}
#if !DART_HOST_OS_IOS
if (program_environment_ != NULL) {
// On MacOS you have to do a bit of magic to get to the
// environment strings.
char*** environ = _NSGetEnviron();
*environ = program_environment_;
}
#endif
// Report the final PID and do the exec.
ReportPid(getpid()); // getpid cannot fail.
execvp(path_, const_cast<char* const*>(program_arguments_));
ReportChildError();
} else {
// Exit the intermeiate process.
exit(0);
}
}
} else {
// Exit the intermeiate process.
exit(0);
}
}
int RegisterProcess(pid_t pid) {
int result;
int event_fds[2];
result = TEMP_FAILURE_RETRY(pipe(event_fds));
if (result < 0) {
return CleanupAndReturnError();
}
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]);
return 0;
}
int ReadExecResult() {
int child_errno;
int bytes_read = -1;
// 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.
bytes_read = FDUtils::ReadFromBlocking(exec_control_[0], &child_errno,
sizeof(child_errno));
if (bytes_read == sizeof(child_errno)) {
ReadChildError();
return child_errno;
} else if (bytes_read == -1) {
return errno;
}
return 0;
}
int ReadDetachedExecResult(pid_t* pid) {
int child_errno;
int bytes_read = -1;
// Read exec result from child. If only pid data is returned the exec was
// successful and the exec call closed the pipe. Otherwise the errno
// is written to the pipe as well.
int result[2];
bytes_read =
FDUtils::ReadFromBlocking(exec_control_[0], result, sizeof(result));
if (bytes_read == sizeof(int)) {
*pid = result[0];
} else if (bytes_read == 2 * sizeof(int)) {
*pid = result[0];
child_errno = result[1];
ReadChildError();
return child_errno;
} else if (bytes_read == -1) {
return errno;
}
return 0;
}
void SetupDetached() {
ASSERT(mode_ == kDetached);
// Close all open file descriptors except for exec_control_[1].
int max_fds = sysconf(_SC_OPEN_MAX);
if (max_fds == -1) {
max_fds = _POSIX_OPEN_MAX;
}
for (int fd = 0; fd < max_fds; fd++) {
if (fd != exec_control_[1]) {
close(fd);
}
}
// Re-open stdin, stdout and stderr and connect them to /dev/null.
// The loop above should already have closed all of them, so
// creating new file descriptors should start at STDIN_FILENO.
int fd = TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR));
if (fd != STDIN_FILENO) {
ReportChildError();
}
if (TEMP_FAILURE_RETRY(dup2(STDIN_FILENO, STDOUT_FILENO)) !=
STDOUT_FILENO) {
ReportChildError();
}
if (TEMP_FAILURE_RETRY(dup2(STDIN_FILENO, STDERR_FILENO)) !=
STDERR_FILENO) {
ReportChildError();
}
}
void SetupDetachedWithStdio() {
// Close all open file descriptors except for
// exec_control_[1], write_out_[0], read_in_[1] and
// read_err_[1].
int max_fds = sysconf(_SC_OPEN_MAX);
if (max_fds == -1) {
max_fds = _POSIX_OPEN_MAX;
}
for (int fd = 0; fd < max_fds; fd++) {
if ((fd != exec_control_[1]) && (fd != write_out_[0]) &&
(fd != read_in_[1]) && (fd != read_err_[1])) {
close(fd);
}
}
if (TEMP_FAILURE_RETRY(dup2(write_out_[0], STDIN_FILENO)) == -1) {
ReportChildError();
}
close(write_out_[0]);
if (TEMP_FAILURE_RETRY(dup2(read_in_[1], STDOUT_FILENO)) == -1) {
ReportChildError();
}
close(read_in_[1]);
if (TEMP_FAILURE_RETRY(dup2(read_err_[1], STDERR_FILENO)) == -1) {
ReportChildError();
}
close(read_err_[1]);
}
int CleanupAndReturnError() {
int actual_errno = errno;
// If CleanupAndReturnError is called without an actual errno make
// sure to return an error anyway.
if (actual_errno == 0) {
actual_errno = EPERM;
}
SetChildOsErrorMessage();
CloseAllPipes();
return actual_errno;
}
void SetChildOsErrorMessage() {
const int kBufferSize = 1024;
char* error_message = DartUtils::ScopedCString(kBufferSize);
Utils::StrError(errno, error_message, kBufferSize);
*os_error_message_ = error_message;
}
void ReportChildError() {
// 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;
const int kBufferSize = 1024;
char os_error_message[kBufferSize];
Utils::StrError(errno, os_error_message, kBufferSize);
int bytes_written = FDUtils::WriteToBlocking(exec_control_[1], &child_errno,
sizeof(child_errno));
if (bytes_written == sizeof(child_errno)) {
FDUtils::WriteToBlocking(exec_control_[1], os_error_message,
strlen(os_error_message) + 1);
}
close(exec_control_[1]);
exit(1);
}
void ReportPid(int pid) {
// In the case of starting a detached process the actual pid of that process
// is communicated using the exec control pipe.
int bytes_written =
FDUtils::WriteToBlocking(exec_control_[1], &pid, sizeof(pid));
ASSERT(bytes_written == sizeof(int));
USE(bytes_written);
}
void ReadChildError() {
const int kMaxMessageSize = 256;
char* message = DartUtils::ScopedCString(kMaxMessageSize);
if (message != NULL) {
FDUtils::ReadFromBlocking(exec_control_[0], message, kMaxMessageSize);
message[kMaxMessageSize - 1] = '\0';
*os_error_message_ = message;
} else {
// Could not get error message. It will be NULL.
ASSERT(*os_error_message_ == NULL);
}
}
void ClosePipe(int* fds) {
for (int i = 0; i < 2; i++) {
if (fds[i] != -1) {
close(fds[i]);
fds[i] = -1;
}
}
}
void CloseAllPipes() {
ClosePipe(exec_control_);
ClosePipe(read_in_);
ClosePipe(read_err_);
ClosePipe(write_out_);
}
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.
char** program_arguments_;
char** program_environment_;
const char* path_;
const char* working_directory_;
ProcessStartMode mode_;
intptr_t* in_;
intptr_t* out_;
intptr_t* err_;
intptr_t* id_;
intptr_t* exit_event_;
char** os_error_message_;
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(ProcessStarter);
};
int Process::Start(Namespace* namespc,
const char* path,
char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
intptr_t environment_length,
ProcessStartMode mode,
intptr_t* in,
intptr_t* out,
intptr_t* err,
intptr_t* id,
intptr_t* exit_event,
char** os_error_message) {
ProcessStarter starter(path, arguments, arguments_length, working_directory,
environment, environment_length, mode, in, out, err,
id, exit_event, os_error_message);
return starter.Start();
}
static bool CloseProcessBuffers(struct pollfd* fds, int alive) {
int e = errno;
for (int i = 0; i < alive; i++) {
close(fds[i].fd);
}
errno = e;
return false;
}
bool Process::Wait(intptr_t pid,
intptr_t in,
intptr_t out,
intptr_t err,
intptr_t exit_event,
ProcessResult* result) {
// Close input to the process right away.
close(in);
// There is no return from this function using Dart_PropagateError
// as memory used by the buffer lists is freed through their
// destructors.
BufferList out_data;
BufferList err_data;
union {
uint8_t bytes[8];
int32_t ints[2];
} exit_code_data;
struct pollfd fds[3];
fds[0].fd = out;
fds[1].fd = err;
fds[2].fd = exit_event;
for (int i = 0; i < 3; i++) {
fds[i].events = POLLIN;
}
int alive = 3;
while (alive > 0) {
// Blocking call waiting for events from the child process.
if (TEMP_FAILURE_RETRY(poll(fds, alive, -1)) <= 0) {
return CloseProcessBuffers(fds, alive);
}
// Process incoming data.
for (int i = 0; i < alive; i++) {
intptr_t avail;
if ((fds[i].revents & (POLLNVAL | POLLERR)) != 0) {
return CloseProcessBuffers(fds, alive);
}
if ((fds[i].revents & POLLIN) != 0) {
avail = FDUtils::AvailableBytes(fds[i].fd);
// On Mac OS POLLIN can be set with zero available
// bytes. POLLHUP is most likely also set in this case.
if (avail > 0) {
if (fds[i].fd == out) {
if (!out_data.Read(out, avail)) {
return CloseProcessBuffers(fds, alive);
}
} else if (fds[i].fd == err) {
if (!err_data.Read(err, avail)) {
return CloseProcessBuffers(fds, alive);
}
} else if (fds[i].fd == exit_event) {
if (avail == 8) {
intptr_t b =
TEMP_FAILURE_RETRY(read(exit_event, exit_code_data.bytes, 8));
if (b != 8) {
return CloseProcessBuffers(fds, alive);
}
}
} else {
UNREACHABLE();
}
}
}
if (((fds[i].revents & POLLHUP) != 0) ||
(((fds[i].revents & POLLIN) != 0) && (avail == 0))) {
// Remove the pollfd from the list of pollfds.
close(fds[i].fd);
alive--;
if (i < alive) {
fds[i] = fds[alive];
}
// Process the same index again.
i--;
continue;
}
}
}
// All handles closed and all data read.
result->set_stdout_data(out_data.GetData());
result->set_stderr_data(err_data.GetData());
DEBUG_ASSERT(out_data.IsEmpty());
DEBUG_ASSERT(err_data.IsEmpty());
// Calculate the exit code.
intptr_t exit_code = exit_code_data.ints[0];
intptr_t negative = exit_code_data.ints[1];
if (negative != 0) {
exit_code = -exit_code;
}
result->set_exit_code(exit_code);
return true;
}
static int SignalMap(intptr_t id) {
switch (static_cast<ProcessSignals>(id)) {
case kSighup:
return SIGHUP;
case kSigint:
return SIGINT;
case kSigquit:
return SIGQUIT;
case kSigill:
return SIGILL;
case kSigtrap:
return SIGTRAP;
case kSigabrt:
return SIGABRT;
case kSigbus:
return SIGBUS;
case kSigfpe:
return SIGFPE;
case kSigkill:
return SIGKILL;
case kSigusr1:
return SIGUSR1;
case kSigsegv:
return SIGSEGV;
case kSigusr2:
return SIGUSR2;
case kSigpipe:
return SIGPIPE;
case kSigalrm:
return SIGALRM;
case kSigterm:
return SIGTERM;
case kSigchld:
return SIGCHLD;
case kSigcont:
return SIGCONT;
case kSigstop:
return SIGSTOP;
case kSigtstp:
return SIGTSTP;
case kSigttin:
return SIGTTIN;
case kSigttou:
return SIGTTOU;
case kSigurg:
return SIGURG;
case kSigxcpu:
return SIGXCPU;
case kSigxfsz:
return SIGXFSZ;
case kSigvtalrm:
return SIGVTALRM;
case kSigprof:
return SIGPROF;
case kSigwinch:
return SIGWINCH;
case kSigpoll:
return -1;
case kSigsys:
return SIGSYS;
}
return -1;
}
bool Process::Kill(intptr_t id, int signal) {
return (TEMP_FAILURE_RETRY(kill(id, SignalMap(signal))) != -1);
}
void Process::TerminateExitCodeHandler() {
ExitCodeHandler::TerminateExitCodeThread();
}
intptr_t Process::CurrentProcessId() {
return static_cast<intptr_t>(getpid());
}
int64_t Process::CurrentRSS() {
struct mach_task_basic_info info;
mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT;
kern_return_t result =
task_info(mach_task_self(), MACH_TASK_BASIC_INFO,
reinterpret_cast<task_info_t>(&info), &infoCount);
if (result != KERN_SUCCESS) {
return -1;
}
return info.resident_size;
}
int64_t Process::MaxRSS() {
struct rusage usage;
usage.ru_maxrss = 0;
int r = getrusage(RUSAGE_SELF, &usage);
if (r < 0) {
return -1;
}
return usage.ru_maxrss;
}
static Mutex* signal_mutex = nullptr;
static SignalInfo* signal_handlers = NULL;
static const int kSignalsCount = 7;
static const int kSignals[kSignalsCount] = {
SIGHUP, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGWINCH,
SIGQUIT // Allow VMService to listen on SIGQUIT.
};
SignalInfo::~SignalInfo() {
close(fd_);
}
static void SignalHandler(int signal) {
MutexLocker lock(signal_mutex);
const SignalInfo* handler = signal_handlers;
while (handler != NULL) {
if (handler->signal() == signal) {
int value = 0;
VOID_TEMP_FAILURE_RETRY(write(handler->fd(), &value, 1));
}
handler = handler->next();
}
}
intptr_t Process::SetSignalHandler(intptr_t signal) {
signal = SignalMap(signal);
if (signal == -1) {
return -1;
}
bool found = false;
for (int i = 0; i < kSignalsCount; i++) {
if (kSignals[i] == signal) {
found = true;
break;
}
}
if (!found) {
return -1;
}
int fds[2];
if (NO_RETRY_EXPECTED(pipe(fds)) != 0) {
return -1;
}
if (!FDUtils::SetCloseOnExec(fds[0]) || !FDUtils::SetCloseOnExec(fds[1]) ||
!FDUtils::SetNonBlocking(fds[0])) {
close(fds[0]);
close(fds[1]);
return -1;
}
ThreadSignalBlocker blocker(kSignalsCount, kSignals);
MutexLocker lock(signal_mutex);
SignalInfo* handler = signal_handlers;
bool listen = true;
sa_handler_t oldact_handler = nullptr;
while (handler != NULL) {
if (handler->signal() == signal) {
oldact_handler = handler->oldact();
listen = false;
break;
}
handler = handler->next();
}
if (listen) {
struct sigaction act = {};
act.sa_handler = SignalHandler;
sigemptyset(&act.sa_mask);
for (int i = 0; i < kSignalsCount; i++) {
sigaddset(&act.sa_mask, kSignals[i]);
}
struct sigaction oldact = {};
intptr_t status = NO_RETRY_EXPECTED(sigaction(signal, &act, &oldact));
if (status < 0) {
close(fds[0]);
close(fds[1]);
return -1;
}
oldact_handler = oldact.sa_handler;
}
signal_handlers =
new SignalInfo(fds[1], signal, oldact_handler, signal_handlers);
return fds[0];
}
void Process::ClearSignalHandler(intptr_t signal, Dart_Port port) {
signal = SignalMap(signal);
if (signal == -1) {
return;
}
ThreadSignalBlocker blocker(kSignalsCount, kSignals);
MutexLocker lock(signal_mutex);
SignalInfo* handler = signal_handlers;
sa_handler_t oldact_handler = SIG_DFL;
bool any_removed = false;
bool any_remaining = false;
while (handler != NULL) {
bool remove = false;
if (handler->signal() == signal) {
if ((port == ILLEGAL_PORT) || (handler->port() == port)) {
if (signal_handlers == handler) {
signal_handlers = handler->next();
}
handler->Unlink();
remove = true;
oldact_handler = handler->oldact();
any_removed = true;
} else {
any_remaining = true;
}
}
SignalInfo* next = handler->next();
if (remove) {
delete handler;
}
handler = next;
}
if (any_removed && !any_remaining) {
struct sigaction act = {};
act.sa_handler = oldact_handler;
VOID_NO_RETRY_EXPECTED(sigaction(signal, &act, NULL));
}
}
void Process::ClearSignalHandlerByFd(intptr_t fd, Dart_Port port) {
ThreadSignalBlocker blocker(kSignalsCount, kSignals);
MutexLocker lock(signal_mutex);
SignalInfo* handler = signal_handlers;
sa_handler_t oldact_handler = SIG_DFL;
bool any_remaining = false;
intptr_t signal = -1;
while (handler != NULL) {
bool remove = false;
if (handler->fd() == fd) {
if ((port == ILLEGAL_PORT) || (handler->port() == port)) {
if (signal_handlers == handler) {
signal_handlers = handler->next();
}
handler->Unlink();
remove = true;
signal = handler->signal();
} else {
any_remaining = true;
}
}
SignalInfo* next = handler->next();
if (remove) {
delete handler;
}
handler = next;
}
if ((signal != -1) && !any_remaining) {
struct sigaction act = {};
act.sa_handler = oldact_handler;
VOID_NO_RETRY_EXPECTED(sigaction(signal, &act, NULL));
}
}
void ProcessInfoList::Init() {
active_processes_ = NULL;
ASSERT(ProcessInfoList::mutex_ == nullptr);
ProcessInfoList::mutex_ = new Mutex();
}
void ProcessInfoList::Cleanup() {
ASSERT(ProcessInfoList::mutex_ != nullptr);
delete ProcessInfoList::mutex_;
ProcessInfoList::mutex_ = nullptr;
}
void ExitCodeHandler::Init() {
running_ = false;
process_count_ = 0;
terminate_done_ = false;
ASSERT(ExitCodeHandler::monitor_ == nullptr);
ExitCodeHandler::monitor_ = new Monitor();
}
void ExitCodeHandler::Cleanup() {
ASSERT(ExitCodeHandler::monitor_ != nullptr);
delete ExitCodeHandler::monitor_;
ExitCodeHandler::monitor_ = nullptr;
}
void Process::Init() {
ExitCodeHandler::Init();
ProcessInfoList::Init();
ASSERT(signal_mutex == nullptr);
signal_mutex = new Mutex();
signal_handlers = NULL;
ASSERT(Process::global_exit_code_mutex_ == nullptr);
Process::global_exit_code_mutex_ = new Mutex();
}
void Process::Cleanup() {
ClearAllSignalHandlers();
ASSERT(signal_mutex != nullptr);
delete signal_mutex;
signal_mutex = nullptr;
ASSERT(Process::global_exit_code_mutex_ != nullptr);
delete Process::global_exit_code_mutex_;
Process::global_exit_code_mutex_ = nullptr;
ProcessInfoList::Cleanup();
ExitCodeHandler::Cleanup();
}
} // namespace bin
} // namespace dart
#endif // defined(DART_HOST_OS_MACOS)