| // 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_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 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] = return_code; |
| exit_message[1] = 0; // Do not negate return_code. |
| 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); |
| |
| return true; |
| } |
| |
| |
| bool Process::Kill(intptr_t id, int signal) { |
| errno = ENOSYS; |
| return false; |
| } |
| |
| |
| 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; |
| |
| status = launchpad_create(0, 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) |