c7d2f8276d
Review-Url: https://codereview.chromium.org/2628213002 .
853 lines
27 KiB
C++
853 lines
27 KiB
C++
// Copyright (c) 2016, 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.
|
|
|
|
#if !defined(DART_IO_DISABLED)
|
|
|
|
#include "platform/globals.h"
|
|
#if defined(TARGET_OS_FUCHSIA)
|
|
|
|
#include "bin/process.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <launchpad/launchpad.h>
|
|
#include <launchpad/vmo.h>
|
|
#include <magenta/status.h>
|
|
#include <magenta/syscalls.h>
|
|
#include <magenta/syscalls/object.h>
|
|
#include <mxio/util.h>
|
|
#include <pthread.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/epoll.h>
|
|
#include <unistd.h>
|
|
|
|
#include "bin/dartutils.h"
|
|
#include "bin/fdutils.h"
|
|
#include "bin/lockers.h"
|
|
#include "bin/log.h"
|
|
#include "platform/signal_blocker.h"
|
|
#include "platform/utils.h"
|
|
|
|
// #define PROCESS_LOGGING 1
|
|
#if defined(PROCESS_LOGGING)
|
|
#define LOG_ERR(msg, ...) Log::PrintErr("Dart Process: " msg, ##__VA_ARGS__)
|
|
#define LOG_INFO(msg, ...) Log::Print("Dart Process: " msg, ##__VA_ARGS__)
|
|
#else
|
|
#define LOG_ERR(msg, ...)
|
|
#define LOG_INFO(msg, ...)
|
|
#endif // defined(PROCESS_LOGGING)
|
|
|
|
namespace dart {
|
|
namespace bin {
|
|
|
|
int Process::global_exit_code_ = 0;
|
|
Mutex* Process::global_exit_code_mutex_ = new Mutex();
|
|
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(mx_handle_t process, intptr_t fd)
|
|
: process_(process), exit_pipe_fd_(fd) {}
|
|
~ProcessInfo() {
|
|
int closed = NO_RETRY_EXPECTED(close(exit_pipe_fd_));
|
|
if (closed != 0) {
|
|
FATAL("Failed to close process exit code pipe");
|
|
}
|
|
mx_handle_close(process_);
|
|
}
|
|
mx_handle_t process() const { return process_; }
|
|
intptr_t exit_pipe_fd() const { return exit_pipe_fd_; }
|
|
ProcessInfo* next() const { return next_; }
|
|
void set_next(ProcessInfo* info) { next_ = info; }
|
|
|
|
private:
|
|
mx_handle_t process_;
|
|
intptr_t exit_pipe_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 AddProcess(mx_handle_t process, intptr_t fd) {
|
|
MutexLocker locker(mutex_);
|
|
ProcessInfo* info = new ProcessInfo(process, fd);
|
|
info->set_next(active_processes_);
|
|
active_processes_ = info;
|
|
}
|
|
|
|
static intptr_t LookupProcessExitFd(mx_handle_t process) {
|
|
MutexLocker locker(mutex_);
|
|
ProcessInfo* current = active_processes_;
|
|
while (current != NULL) {
|
|
if (current->process() == process) {
|
|
return current->exit_pipe_fd();
|
|
}
|
|
current = current->next();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static bool Exists(mx_handle_t process) {
|
|
return LookupProcessExitFd(process) != 0;
|
|
}
|
|
|
|
static void RemoveProcess(mx_handle_t process) {
|
|
MutexLocker locker(mutex_);
|
|
ProcessInfo* prev = NULL;
|
|
ProcessInfo* current = active_processes_;
|
|
while (current != NULL) {
|
|
if (current->process() == process) {
|
|
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_ = new Mutex();
|
|
|
|
// 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:
|
|
// Notify the ExitCodeHandler that another process exists.
|
|
static void Start() {
|
|
// Multiple isolates could be starting processes at the same
|
|
// time. Make sure that only one ExitCodeHandler thread exists.
|
|
MonitorLocker locker(monitor_);
|
|
if (running_) {
|
|
return;
|
|
}
|
|
|
|
LOG_INFO("ExitCodeHandler Starting\n");
|
|
|
|
mx_status_t status = mx_socket_create(0, &interrupt_in_, &interrupt_out_);
|
|
if (status < 0) {
|
|
FATAL1("Failed to create exit code handler interrupt socket: %s\n",
|
|
mx_status_get_string(status));
|
|
}
|
|
|
|
// Start thread that handles process exits when wait returns.
|
|
intptr_t result =
|
|
Thread::Start(ExitCodeHandlerEntry, static_cast<uword>(interrupt_out_));
|
|
if (result != 0) {
|
|
FATAL1("Failed to start exit code handler worker thread %ld", result);
|
|
}
|
|
|
|
running_ = true;
|
|
}
|
|
|
|
static void Add(mx_handle_t process) {
|
|
MonitorLocker locker(monitor_);
|
|
LOG_INFO("ExitCodeHandler Adding Process: %ld\n", process);
|
|
SendMessage(Message::kAdd, process);
|
|
}
|
|
|
|
static void Terminate() {
|
|
MonitorLocker locker(monitor_);
|
|
if (!running_) {
|
|
return;
|
|
}
|
|
running_ = false;
|
|
|
|
LOG_INFO("ExitCodeHandler Terminating\n");
|
|
SendMessage(Message::kShutdown, MX_HANDLE_INVALID);
|
|
|
|
while (!terminate_done_) {
|
|
monitor_->Wait(Monitor::kNoTimeout);
|
|
}
|
|
mx_handle_close(interrupt_in_);
|
|
LOG_INFO("ExitCodeHandler Terminated\n");
|
|
}
|
|
|
|
private:
|
|
class Message {
|
|
public:
|
|
enum Command {
|
|
kAdd,
|
|
kShutdown,
|
|
};
|
|
Command command;
|
|
mx_handle_t handle;
|
|
};
|
|
|
|
static void SendMessage(Message::Command command, mx_handle_t handle) {
|
|
Message msg;
|
|
msg.command = command;
|
|
msg.handle = handle;
|
|
size_t actual;
|
|
mx_status_t status =
|
|
mx_socket_write(interrupt_in_, 0, &msg, sizeof(msg), &actual);
|
|
if (status < 0) {
|
|
FATAL1("Write to exit handler interrupt handle failed: %s\n",
|
|
mx_status_get_string(status));
|
|
}
|
|
ASSERT(actual == sizeof(msg));
|
|
}
|
|
|
|
// Entry point for the separate exit code handler thread started by
|
|
// the ExitCodeHandler.
|
|
static void ExitCodeHandlerEntry(uword param) {
|
|
LOG_INFO("ExitCodeHandler Entering ExitCodeHandler thread\n");
|
|
item_capacity_ = 16;
|
|
items_ = reinterpret_cast<mx_wait_item_t*>(
|
|
malloc(item_capacity_ * sizeof(*items_)));
|
|
items_to_remove_ = reinterpret_cast<intptr_t*>(
|
|
malloc(item_capacity_ * sizeof(*items_to_remove_)));
|
|
|
|
// The interrupt handle is fixed to the first entry.
|
|
items_[0].handle = interrupt_out_;
|
|
items_[0].waitfor = MX_SOCKET_READABLE | MX_SOCKET_PEER_CLOSED;
|
|
items_[0].pending = MX_SIGNAL_NONE;
|
|
item_count_ = 1;
|
|
|
|
while (!do_shutdown_) {
|
|
LOG_INFO("ExitCodeHandler Calling mx_handle_wait_many: %ld items\n",
|
|
item_count_);
|
|
mx_status_t status =
|
|
mx_handle_wait_many(items_, item_count_, MX_TIME_INFINITE);
|
|
if (status < 0) {
|
|
FATAL1("Exit code handler handle wait failed: %s\n",
|
|
mx_status_get_string(status));
|
|
}
|
|
LOG_INFO("ExitCodeHandler mx_handle_wait_many returned\n");
|
|
|
|
bool have_interrupt = false;
|
|
intptr_t remove_count = 0;
|
|
for (intptr_t i = 0; i < item_count_; i++) {
|
|
if (items_[i].pending == MX_SIGNAL_NONE) {
|
|
continue;
|
|
}
|
|
if (i == 0) {
|
|
LOG_INFO("ExitCodeHandler thread saw interrupt\n");
|
|
have_interrupt = true;
|
|
continue;
|
|
}
|
|
ASSERT(items_[i].waitfor == MX_TASK_TERMINATED);
|
|
ASSERT((items_[i].pending & MX_TASK_TERMINATED) != 0);
|
|
LOG_INFO("ExitCodeHandler signal for %ld\n", items_[i].handle);
|
|
SendProcessStatus(items_[i].handle);
|
|
items_to_remove_[remove_count++] = i;
|
|
}
|
|
for (intptr_t i = 0; i < remove_count; i++) {
|
|
RemoveItem(items_to_remove_[i]);
|
|
}
|
|
if (have_interrupt) {
|
|
HandleInterruptMsg();
|
|
}
|
|
}
|
|
|
|
LOG_INFO("ExitCodeHandler thread shutting down\n");
|
|
mx_handle_close(interrupt_out_);
|
|
free(items_);
|
|
items_ = NULL;
|
|
free(items_to_remove_);
|
|
items_to_remove_ = NULL;
|
|
item_count_ = 0;
|
|
item_capacity_ = 0;
|
|
|
|
terminate_done_ = true;
|
|
monitor_->Notify();
|
|
}
|
|
|
|
static void SendProcessStatus(mx_handle_t process) {
|
|
LOG_INFO("ExitCodeHandler thread getting process status: %ld\n", process);
|
|
mx_info_process_t proc_info;
|
|
mx_status_t status = mx_object_get_info(
|
|
process, MX_INFO_PROCESS, &proc_info, sizeof(proc_info), NULL, NULL);
|
|
if (status < 0) {
|
|
FATAL1("mx_object_get_info failed on process handle: %s\n",
|
|
mx_status_get_string(status));
|
|
}
|
|
|
|
const int return_code = proc_info.return_code;
|
|
status = mx_handle_close(process);
|
|
if (status < 0) {
|
|
FATAL1("Failed to close process handle: %s\n",
|
|
mx_status_get_string(status));
|
|
}
|
|
LOG_INFO("ExitCodeHandler thread process %ld exited with %d\n", process,
|
|
return_code);
|
|
|
|
const intptr_t exit_code_fd = ProcessInfoList::LookupProcessExitFd(process);
|
|
LOG_INFO("ExitCodeHandler thread sending %ld code %d on fd %ld\n", process,
|
|
return_code, exit_code_fd);
|
|
if (exit_code_fd != 0) {
|
|
int exit_message[2];
|
|
exit_message[0] = abs(return_code);
|
|
exit_message[1] = return_code >= 0 ? 0 : 1;
|
|
intptr_t result = FDUtils::WriteToBlocking(exit_code_fd, &exit_message,
|
|
sizeof(exit_message));
|
|
ASSERT((result == -1) || (result == sizeof(exit_code_fd)));
|
|
if ((result == -1) && (errno != EPIPE)) {
|
|
int err = errno;
|
|
FATAL1("Failed to write exit code to pipe: %d\n", err);
|
|
}
|
|
LOG_INFO("ExitCodeHandler thread wrote %ld bytes to fd %ld\n", result,
|
|
exit_code_fd);
|
|
LOG_INFO("ExitCodeHandler thread removing process %ld from list\n",
|
|
process);
|
|
ProcessInfoList::RemoveProcess(process);
|
|
}
|
|
}
|
|
|
|
static void HandleInterruptMsg() {
|
|
ASSERT(items_[0].handle == interrupt_out_);
|
|
ASSERT(items_[0].waitfor == MX_SOCKET_READABLE);
|
|
ASSERT((items_[0].pending & MX_SOCKET_READABLE) != 0);
|
|
while (true) {
|
|
Message msg;
|
|
size_t actual = 0;
|
|
LOG_INFO("ExitCodeHandler thread reading interrupt message\n");
|
|
mx_status_t status =
|
|
mx_socket_read(interrupt_out_, 0, &msg, sizeof(msg), &actual);
|
|
if (status == ERR_SHOULD_WAIT) {
|
|
LOG_INFO("ExitCodeHandler thread done reading interrupt messages\n");
|
|
return;
|
|
}
|
|
if (status < 0) {
|
|
FATAL1("Failed to read exit handler interrupt handle: %s\n",
|
|
mx_status_get_string(status));
|
|
}
|
|
if (actual < sizeof(msg)) {
|
|
FATAL1("Short read from exit handler interrupt handle: %ld\n", actual);
|
|
}
|
|
switch (msg.command) {
|
|
case Message::kShutdown:
|
|
LOG_INFO("ExitCodeHandler thread got shutdown message\n");
|
|
do_shutdown_ = true;
|
|
break;
|
|
case Message::kAdd:
|
|
LOG_INFO("ExitCodeHandler thread got add message: %ld\n", msg.handle);
|
|
AddItem(msg.handle);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void AddItem(mx_handle_t h) {
|
|
if (item_count_ == item_capacity_) {
|
|
item_capacity_ = item_capacity_ + (item_capacity_ >> 1);
|
|
items_ =
|
|
reinterpret_cast<mx_wait_item_t*>(realloc(items_, item_capacity_));
|
|
items_to_remove_ = reinterpret_cast<intptr_t*>(
|
|
realloc(items_to_remove_, item_capacity_));
|
|
}
|
|
LOG_INFO("ExitCodeHandler thread adding item %ld at %ld\n", h, item_count_);
|
|
items_[item_count_].handle = h;
|
|
items_[item_count_].waitfor = MX_TASK_TERMINATED;
|
|
items_[item_count_].pending = MX_SIGNAL_NONE;
|
|
item_count_++;
|
|
}
|
|
|
|
static void RemoveItem(intptr_t idx) {
|
|
LOG_INFO("ExitCodeHandler thread removing item %ld at %ld\n",
|
|
items_[idx].handle, idx);
|
|
ASSERT(idx != 0);
|
|
const intptr_t last = item_count_ - 1;
|
|
items_[idx].handle = MX_HANDLE_INVALID;
|
|
items_[idx].waitfor = MX_SIGNAL_NONE;
|
|
items_[idx].pending = MX_SIGNAL_NONE;
|
|
if (idx != last) {
|
|
items_[idx] = items_[last];
|
|
}
|
|
item_count_--;
|
|
}
|
|
|
|
// Interrupt message pipe.
|
|
static mx_handle_t interrupt_in_;
|
|
static mx_handle_t interrupt_out_;
|
|
|
|
// Accessed only by the ExitCodeHandler thread.
|
|
static mx_wait_item_t* items_;
|
|
static intptr_t* items_to_remove_;
|
|
static intptr_t item_count_;
|
|
static intptr_t item_capacity_;
|
|
|
|
// Protected by monitor_.
|
|
static bool do_shutdown_;
|
|
static bool terminate_done_;
|
|
static bool running_;
|
|
static Monitor* monitor_;
|
|
|
|
DISALLOW_ALLOCATION();
|
|
DISALLOW_IMPLICIT_CONSTRUCTORS(ExitCodeHandler);
|
|
};
|
|
|
|
mx_handle_t ExitCodeHandler::interrupt_in_ = MX_HANDLE_INVALID;
|
|
mx_handle_t ExitCodeHandler::interrupt_out_ = MX_HANDLE_INVALID;
|
|
mx_wait_item_t* ExitCodeHandler::items_ = NULL;
|
|
intptr_t* ExitCodeHandler::items_to_remove_ = NULL;
|
|
intptr_t ExitCodeHandler::item_count_ = 0;
|
|
intptr_t ExitCodeHandler::item_capacity_ = 0;
|
|
|
|
bool ExitCodeHandler::do_shutdown_ = false;
|
|
bool ExitCodeHandler::running_ = false;
|
|
bool ExitCodeHandler::terminate_done_ = false;
|
|
Monitor* ExitCodeHandler::monitor_ = new Monitor();
|
|
|
|
void Process::TerminateExitCodeHandler() {
|
|
ExitCodeHandler::Terminate();
|
|
}
|
|
|
|
|
|
intptr_t Process::CurrentProcessId() {
|
|
return static_cast<intptr_t>(getpid());
|
|
}
|
|
|
|
|
|
static bool ProcessWaitCleanup(intptr_t out,
|
|
intptr_t err,
|
|
intptr_t exit_event,
|
|
intptr_t epoll_fd) {
|
|
int e = errno;
|
|
VOID_NO_RETRY_EXPECTED(close(out));
|
|
VOID_NO_RETRY_EXPECTED(close(err));
|
|
VOID_NO_RETRY_EXPECTED(close(exit_event));
|
|
VOID_NO_RETRY_EXPECTED(close(epoll_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) {
|
|
VOID_NO_RETRY_EXPECTED(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;
|
|
|
|
// The initial size passed to epoll_create is ignore on newer (>=
|
|
// 2.6.8) Linux versions
|
|
static const int kEpollInitialSize = 64;
|
|
int epoll_fd = NO_RETRY_EXPECTED(epoll_create(kEpollInitialSize));
|
|
if (epoll_fd == -1) {
|
|
return ProcessWaitCleanup(out, err, exit_event, epoll_fd);
|
|
}
|
|
if (!FDUtils::SetCloseOnExec(epoll_fd)) {
|
|
return ProcessWaitCleanup(out, err, exit_event, epoll_fd);
|
|
}
|
|
|
|
struct epoll_event event;
|
|
event.events = EPOLLRDHUP | EPOLLIN;
|
|
event.data.fd = out;
|
|
int status = NO_RETRY_EXPECTED(
|
|
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, out, &event));
|
|
if (status == -1) {
|
|
return ProcessWaitCleanup(out, err, exit_event, epoll_fd);
|
|
}
|
|
event.data.fd = err;
|
|
status = NO_RETRY_EXPECTED(
|
|
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, err, &event));
|
|
if (status == -1) {
|
|
return ProcessWaitCleanup(out, err, exit_event, epoll_fd);
|
|
}
|
|
event.data.fd = exit_event;
|
|
status = NO_RETRY_EXPECTED(
|
|
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, exit_event, &event));
|
|
if (status == -1) {
|
|
return ProcessWaitCleanup(out, err, exit_event, epoll_fd);
|
|
}
|
|
intptr_t active = 3;
|
|
|
|
static const intptr_t kMaxEvents = 16;
|
|
struct epoll_event events[kMaxEvents];
|
|
while (active > 0) {
|
|
// TODO(US-109): When the epoll implementation is properly edge-triggered,
|
|
// remove this sleep, which prevents the message queue from being
|
|
// overwhelmed and leading to memory exhaustion.
|
|
usleep(5000);
|
|
intptr_t result = NO_RETRY_EXPECTED(
|
|
epoll_wait(epoll_fd, events, kMaxEvents, -1));
|
|
if ((result < 0) && (errno != EWOULDBLOCK)) {
|
|
return ProcessWaitCleanup(out, err, exit_event, epoll_fd);
|
|
}
|
|
for (intptr_t i = 0; i < result; i++) {
|
|
if ((events[i].events & EPOLLIN) != 0) {
|
|
const intptr_t avail = FDUtils::AvailableBytes(events[i].data.fd);
|
|
if (events[i].data.fd == out) {
|
|
if (!out_data.Read(out, avail)) {
|
|
return ProcessWaitCleanup(out, err, exit_event, epoll_fd);
|
|
}
|
|
} else if (events[i].data.fd == err) {
|
|
if (!err_data.Read(err, avail)) {
|
|
return ProcessWaitCleanup(out, err, exit_event, epoll_fd);
|
|
}
|
|
} else if (events[i].data.fd == exit_event) {
|
|
if (avail == 8) {
|
|
intptr_t b =
|
|
NO_RETRY_EXPECTED(read(exit_event, exit_code_data.bytes, 8));
|
|
if (b != 8) {
|
|
return ProcessWaitCleanup(out, err, exit_event, epoll_fd);
|
|
}
|
|
}
|
|
} else {
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
if ((events[i].events & (EPOLLHUP | EPOLLRDHUP)) != 0) {
|
|
NO_RETRY_EXPECTED(close(events[i].data.fd));
|
|
active--;
|
|
VOID_NO_RETRY_EXPECTED(
|
|
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL));
|
|
}
|
|
}
|
|
}
|
|
VOID_NO_RETRY_EXPECTED(close(epoll_fd));
|
|
|
|
// 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);
|
|
|
|
// Close the process handle.
|
|
mx_handle_t process = static_cast<mx_handle_t>(pid);
|
|
mx_handle_close(process);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Process::Kill(intptr_t id, int signal) {
|
|
LOG_INFO("Sending signal %d to process with id %ld\n", signal, id);
|
|
// mx_task_kill is definitely going to kill the process.
|
|
if ((signal != SIGTERM) && (signal != SIGKILL)) {
|
|
LOG_ERR("Signal %d not supported\n", signal);
|
|
errno = ENOSYS;
|
|
return false;
|
|
}
|
|
// We can only use mx_task_kill if we know id is a process handle, and we only
|
|
// know that for sure if it's in our list.
|
|
mx_handle_t process = static_cast<mx_handle_t>(id);
|
|
if (!ProcessInfoList::Exists(process)) {
|
|
LOG_ERR("Process %ld wasn't in the ProcessInfoList\n", id);
|
|
errno = ESRCH; // No such process.
|
|
return false;
|
|
}
|
|
mx_status_t status = mx_task_kill(process);
|
|
if (status != NO_ERROR) {
|
|
LOG_ERR("mx_task_kill failed: %s\n", mx_status_get_string(status));
|
|
errno = EPERM; // TODO(zra): Figure out what it really should be.
|
|
return false;
|
|
}
|
|
LOG_INFO("Signal %d sent successfully to process %ld\n", signal, id);
|
|
return true;
|
|
}
|
|
|
|
|
|
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) {
|
|
LOG_INFO("ProcessStarter: ctor %s with %ld args, mode = %d\n", path,
|
|
arguments_length, mode);
|
|
|
|
read_in_ = -1;
|
|
read_err_ = -1;
|
|
write_out_ = -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_arguments_count_ = arguments_length + 1;
|
|
|
|
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;
|
|
}
|
|
|
|
binary_vmo_ = MX_HANDLE_INVALID;
|
|
launchpad_ = NULL;
|
|
}
|
|
|
|
~ProcessStarter() {
|
|
if (binary_vmo_ != MX_HANDLE_INVALID) {
|
|
mx_handle_close(binary_vmo_);
|
|
}
|
|
if (launchpad_ != NULL) {
|
|
launchpad_destroy(launchpad_);
|
|
}
|
|
if (read_in_ != -1) {
|
|
close(read_in_);
|
|
}
|
|
if (read_err_ != -1) {
|
|
close(read_err_);
|
|
}
|
|
if (write_out_ != -1) {
|
|
close(write_out_);
|
|
}
|
|
}
|
|
|
|
int Start() {
|
|
LOG_INFO("ProcessStarter: Start()\n");
|
|
int exit_pipe_fds[2];
|
|
intptr_t result = NO_RETRY_EXPECTED(pipe(exit_pipe_fds));
|
|
if (result != 0) {
|
|
*os_error_message_ = DartUtils::ScopedCopyCString(
|
|
"Failed to create exit code pipe for process start.");
|
|
return result;
|
|
}
|
|
LOG_INFO("ProcessStarter: Start() set up exit_pipe_fds (%d, %d)\n",
|
|
exit_pipe_fds[0], exit_pipe_fds[1]);
|
|
|
|
mx_status_t status = SetupLaunchpad();
|
|
if (status != NO_ERROR) {
|
|
close(exit_pipe_fds[0]);
|
|
close(exit_pipe_fds[1]);
|
|
return status;
|
|
}
|
|
|
|
LOG_INFO("ProcessStarter: Start() Calling launchpad_start\n");
|
|
mx_handle_t process = launchpad_start(launchpad_);
|
|
launchpad_destroy(launchpad_);
|
|
launchpad_ = NULL;
|
|
if (process < 0) {
|
|
LOG_INFO("ProcessStarter: Start() launchpad_start failed\n");
|
|
const intptr_t kMaxMessageSize = 256;
|
|
close(exit_pipe_fds[0]);
|
|
close(exit_pipe_fds[1]);
|
|
char* message = DartUtils::ScopedCString(kMaxMessageSize);
|
|
snprintf(message, kMaxMessageSize, "%s:%d: launchpad_start failed: %s\n",
|
|
__FILE__, __LINE__, mx_status_get_string(process));
|
|
*os_error_message_ = message;
|
|
return process;
|
|
}
|
|
|
|
LOG_INFO("ProcessStarter: Start() adding %ld to list with exit_pipe %d\n",
|
|
process, exit_pipe_fds[1]);
|
|
ProcessInfoList::AddProcess(process, exit_pipe_fds[1]);
|
|
ExitCodeHandler::Start();
|
|
ExitCodeHandler::Add(process);
|
|
|
|
*id_ = process;
|
|
FDUtils::SetNonBlocking(read_in_);
|
|
*in_ = read_in_;
|
|
read_in_ = -1;
|
|
FDUtils::SetNonBlocking(read_err_);
|
|
*err_ = read_err_;
|
|
read_err_ = -1;
|
|
FDUtils::SetNonBlocking(write_out_);
|
|
*out_ = write_out_;
|
|
write_out_ = -1;
|
|
FDUtils::SetNonBlocking(exit_pipe_fds[0]);
|
|
*exit_event_ = exit_pipe_fds[0];
|
|
return 0;
|
|
}
|
|
|
|
private:
|
|
#define CHECK_FOR_ERROR(status, msg) \
|
|
if (status < 0) { \
|
|
const intptr_t kMaxMessageSize = 256; \
|
|
char* message = DartUtils::ScopedCString(kMaxMessageSize); \
|
|
snprintf(message, kMaxMessageSize, "%s:%d: %s: %s\n", __FILE__, __LINE__, \
|
|
msg, mx_status_get_string(status)); \
|
|
*os_error_message_ = message; \
|
|
return status; \
|
|
}
|
|
|
|
mx_status_t SetupLaunchpad() {
|
|
mx_handle_t binary_vmo = launchpad_vmo_from_file(path_);
|
|
CHECK_FOR_ERROR(binary_vmo, "launchpad_vmo_from_file");
|
|
binary_vmo_ = binary_vmo;
|
|
|
|
launchpad_t* lp;
|
|
mx_status_t status;
|
|
|
|
mx_handle_t job = MX_HANDLE_INVALID;
|
|
status = mx_handle_duplicate(mx_job_default(), MX_RIGHT_SAME_RIGHTS, &job);
|
|
CHECK_FOR_ERROR(status, "mx_handle_duplicate");
|
|
|
|
status = launchpad_create(job, program_arguments_[0], &lp);
|
|
CHECK_FOR_ERROR(status, "launchpad_create");
|
|
launchpad_ = lp;
|
|
|
|
status =
|
|
launchpad_arguments(lp, program_arguments_count_, program_arguments_);
|
|
CHECK_FOR_ERROR(status, "launchpad_arguments");
|
|
|
|
status = launchpad_environ(lp, program_environment_);
|
|
CHECK_FOR_ERROR(status, "launchpad_environ");
|
|
|
|
// TODO(zra): Use the supplied working directory when launchpad adds an
|
|
// API to set it.
|
|
|
|
status = launchpad_clone_mxio_root(lp);
|
|
CHECK_FOR_ERROR(status, "launchpad_clone_mxio_root");
|
|
|
|
status = launchpad_add_pipe(lp, &write_out_, 0);
|
|
CHECK_FOR_ERROR(status, "launchpad_add_pipe");
|
|
|
|
status = launchpad_add_pipe(lp, &read_in_, 1);
|
|
CHECK_FOR_ERROR(status, "launchpad_add_pipe");
|
|
|
|
status = launchpad_add_pipe(lp, &read_err_, 2);
|
|
CHECK_FOR_ERROR(status, "launchpad_add_pipe");
|
|
|
|
status = launchpad_add_vdso_vmo(lp);
|
|
CHECK_FOR_ERROR(status, "launchpad_add_vdso_vmo");
|
|
|
|
status = launchpad_elf_load(lp, binary_vmo);
|
|
CHECK_FOR_ERROR(status, "launchpad_elf_load");
|
|
binary_vmo_ = MX_HANDLE_INVALID; // launchpad_elf_load consumes the handle.
|
|
|
|
status = launchpad_load_vdso(lp, MX_HANDLE_INVALID);
|
|
CHECK_FOR_ERROR(status, "launchpad_load_vdso");
|
|
|
|
status = launchpad_clone_mxio_cwd(lp);
|
|
CHECK_FOR_ERROR(status, "launchpad_clone_mxio_cwd");
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
#undef CHECK_FOR_ERROR
|
|
|
|
int read_in_; // Pipe for stdout to child process.
|
|
int read_err_; // Pipe for stderr to child process.
|
|
int write_out_; // Pipe for stdin to child process.
|
|
|
|
char** program_arguments_;
|
|
intptr_t program_arguments_count_;
|
|
char** program_environment_;
|
|
|
|
mx_handle_t binary_vmo_;
|
|
launchpad_t* launchpad_;
|
|
|
|
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(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) {
|
|
if (mode != kNormal) {
|
|
*os_error_message = DartUtils::ScopedCopyCString(
|
|
"Only ProcessStartMode.NORMAL is supported on this platform");
|
|
return -1;
|
|
}
|
|
ProcessStarter starter(path, arguments, arguments_length, working_directory,
|
|
environment, environment_length, mode, in, out, err,
|
|
id, exit_event, os_error_message);
|
|
return starter.Start();
|
|
}
|
|
|
|
|
|
intptr_t Process::SetSignalHandler(intptr_t signal) {
|
|
UNIMPLEMENTED();
|
|
return -1;
|
|
}
|
|
|
|
|
|
void Process::ClearSignalHandler(intptr_t signal) {
|
|
UNIMPLEMENTED();
|
|
}
|
|
|
|
} // namespace bin
|
|
} // namespace dart
|
|
|
|
#endif // defined(TARGET_OS_FUCHSIA)
|
|
|
|
#endif // !defined(DART_IO_DISABLED)
|