| // 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. |
| |
| #include "platform/globals.h" |
| #if defined(DART_HOST_OS_FUCHSIA) |
| |
| #include "bin/process.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <fuchsia/io/cpp/fidl.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/io.h> |
| #include <lib/fdio/namespace.h> |
| #include <lib/fdio/spawn.h> |
| #include <poll.h> |
| #include <pthread.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/object.h> |
| #include <zircon/types.h> |
| |
| #include "bin/dartutils.h" |
| #include "bin/eventhandler.h" |
| #include "bin/fdutils.h" |
| #include "bin/file.h" |
| #include "bin/lockers.h" |
| #include "bin/namespace.h" |
| #include "bin/namespace_fuchsia.h" |
| #include "platform/signal_blocker.h" |
| #include "platform/syslog.h" |
| #include "platform/utils.h" |
| |
| // #define PROCESS_LOGGING 1 |
| #if defined(PROCESS_LOGGING) |
| #define LOG_ERR(msg, ...) Syslog::PrintErr("Dart Process: " msg, ##__VA_ARGS__) |
| #define LOG_INFO(msg, ...) Syslog::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_ = nullptr; |
| Process::ExitHook Process::exit_hook_ = nullptr; |
| |
| // 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(zx_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) { |
| LOG_ERR("Failed to close process exit code pipe"); |
| } |
| zx_handle_close(process_); |
| } |
| zx_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: |
| zx_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 Init(); |
| static void Cleanup(); |
| |
| static void AddProcess(zx_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(zx_handle_t process) { |
| MutexLocker locker(mutex_); |
| ProcessInfo* current = active_processes_; |
| while (current != nullptr) { |
| if (current->process() == process) { |
| return current->exit_pipe_fd(); |
| } |
| current = current->next(); |
| } |
| return 0; |
| } |
| |
| static bool Exists(zx_handle_t process) { |
| return LookupProcessExitFd(process) != 0; |
| } |
| |
| static void RemoveProcess(zx_handle_t process) { |
| MutexLocker locker(mutex_); |
| ProcessInfo* prev = nullptr; |
| ProcessInfo* current = active_processes_; |
| while (current != nullptr) { |
| if (current->process() == process) { |
| if (prev == nullptr) { |
| 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_ = nullptr; |
| 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 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"); |
| |
| zx_status_t status = zx_port_create(0, &port_); |
| if (status != ZX_OK) { |
| FATAL("ExitCodeHandler: zx_port_create failed: %s\n", |
| zx_status_get_string(status)); |
| return; |
| } |
| |
| // Start thread that handles process exits when wait returns. |
| Thread::Start("dart:io Process.start", ExitCodeHandlerEntry, 0); |
| |
| running_ = true; |
| } |
| |
| static zx_status_t Add(zx_handle_t process) { |
| MonitorLocker locker(monitor_); |
| LOG_INFO("ExitCodeHandler Adding Process: %u\n", process); |
| return zx_object_wait_async(process, port_, static_cast<uint64_t>(process), |
| ZX_TASK_TERMINATED, ZX_WAIT_ASYNC_ONCE); |
| } |
| |
| static void Terminate() { |
| MonitorLocker locker(monitor_); |
| if (!running_) { |
| return; |
| } |
| running_ = false; |
| |
| LOG_INFO("ExitCodeHandler Terminating\n"); |
| SendShutdownMessage(); |
| |
| while (!terminate_done_) { |
| monitor_->Wait(Monitor::kNoTimeout); |
| } |
| zx_handle_close(port_); |
| LOG_INFO("ExitCodeHandler Terminated\n"); |
| } |
| |
| private: |
| static constexpr uint64_t kShutdownPacketKey = 1; |
| |
| static void SendShutdownMessage() { |
| zx_port_packet_t pkt; |
| pkt.key = kShutdownPacketKey; |
| zx_status_t status = zx_port_queue(port_, &pkt); |
| if (status != ZX_OK) { |
| Syslog::PrintErr("ExitCodeHandler: zx_port_queue failed: %s\n", |
| zx_status_get_string(status)); |
| } |
| } |
| |
| // 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"); |
| |
| zx_port_packet_t pkt; |
| while (true) { |
| zx_status_t status = zx_port_wait(port_, ZX_TIME_INFINITE, &pkt); |
| if (status != ZX_OK) { |
| FATAL("ExitCodeHandler: zx_port_wait failed: %s\n", |
| zx_status_get_string(status)); |
| } |
| if (pkt.type == ZX_PKT_TYPE_USER) { |
| ASSERT(pkt.key == kShutdownPacketKey); |
| break; |
| } |
| zx_handle_t process = static_cast<zx_handle_t>(pkt.key); |
| zx_signals_t observed = pkt.signal.observed; |
| if ((observed & ZX_TASK_TERMINATED) == ZX_SIGNAL_NONE) { |
| LOG_ERR("ExitCodeHandler: Unexpected signals, process %u: %ux\n", |
| process, observed); |
| } |
| SendProcessStatus(process); |
| } |
| |
| LOG_INFO("ExitCodeHandler thread shutting down\n"); |
| terminate_done_ = true; |
| monitor_->Notify(); |
| } |
| |
| static void SendProcessStatus(zx_handle_t process) { |
| LOG_INFO("ExitCodeHandler thread getting process status: %u\n", process); |
| int return_code = -1; |
| zx_info_process_t proc_info; |
| zx_status_t status = |
| zx_object_get_info(process, ZX_INFO_PROCESS, &proc_info, |
| sizeof(proc_info), nullptr, nullptr); |
| if (status != ZX_OK) { |
| Syslog::PrintErr("ExitCodeHandler: zx_object_get_info failed: %s\n", |
| zx_status_get_string(status)); |
| } else { |
| return_code = proc_info.return_code; |
| } |
| zx_handle_close(process); |
| LOG_INFO("ExitCodeHandler thread process %u exited with %d\n", process, |
| return_code); |
| |
| const intptr_t exit_code_fd = ProcessInfoList::LookupProcessExitFd(process); |
| LOG_INFO("ExitCodeHandler thread sending %u 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; |
| Syslog::PrintErr("Failed to write exit code for process %d: errno=%d\n", |
| process, err); |
| } |
| LOG_INFO("ExitCodeHandler thread wrote %ld bytes to fd %ld\n", result, |
| exit_code_fd); |
| LOG_INFO("ExitCodeHandler thread removing process %u from list\n", |
| process); |
| ProcessInfoList::RemoveProcess(process); |
| } else { |
| LOG_ERR("ExitCodeHandler: Process %u not found\n", process); |
| } |
| } |
| |
| static zx_handle_t port_; |
| |
| // Protected by monitor_. |
| static bool terminate_done_; |
| static bool running_; |
| static Monitor* monitor_; |
| |
| DISALLOW_ALLOCATION(); |
| DISALLOW_IMPLICIT_CONSTRUCTORS(ExitCodeHandler); |
| }; |
| |
| zx_handle_t ExitCodeHandler::port_ = ZX_HANDLE_INVALID; |
| bool ExitCodeHandler::running_ = false; |
| bool ExitCodeHandler::terminate_done_ = false; |
| Monitor* ExitCodeHandler::monitor_ = nullptr; |
| |
| void Process::TerminateExitCodeHandler() { |
| ExitCodeHandler::Terminate(); |
| } |
| |
| intptr_t Process::CurrentProcessId() { |
| return static_cast<intptr_t>(getpid()); |
| } |
| |
| int64_t Process::CurrentRSS() { |
| zx_info_task_stats_t task_stats; |
| zx_handle_t process = zx_process_self(); |
| zx_status_t status = |
| zx_object_get_info(process, ZX_INFO_TASK_STATS, &task_stats, |
| sizeof(task_stats), nullptr, nullptr); |
| if (status != ZX_OK) { |
| // TODO(zra): Translate this to a Unix errno. |
| errno = status; |
| return -1; |
| } |
| return task_stats.mem_private_bytes + task_stats.mem_shared_bytes; |
| } |
| |
| int64_t Process::MaxRSS() { |
| // There is currently no way to get the high watermark value on Fuchsia, so |
| // just return the current RSS value. |
| return CurrentRSS(); |
| } |
| |
| class IOHandleScope { |
| public: |
| explicit IOHandleScope(IOHandle* io_handle) : io_handle_(io_handle) {} |
| ~IOHandleScope() { |
| io_handle_->Close(); |
| io_handle_->Release(); |
| } |
| |
| private: |
| IOHandle* io_handle_; |
| |
| DISALLOW_ALLOCATION(); |
| DISALLOW_COPY_AND_ASSIGN(IOHandleScope); |
| }; |
| |
| bool Process::Wait(intptr_t pid, |
| intptr_t in, |
| intptr_t out, |
| intptr_t err, |
| intptr_t exit_event, |
| ProcessResult* result) { |
| IOHandle* out_iohandle = reinterpret_cast<IOHandle*>(out); |
| IOHandle* err_iohandle = reinterpret_cast<IOHandle*>(err); |
| IOHandle* exit_iohandle = reinterpret_cast<IOHandle*>(exit_event); |
| |
| // 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; |
| |
| // Create a port, which is like an epoll() fd on Linux. |
| zx_handle_t port; |
| zx_status_t status = zx_port_create(0, &port); |
| if (status != ZX_OK) { |
| Syslog::PrintErr("Process::Wait: zx_port_create failed: %s\n", |
| zx_status_get_string(status)); |
| return false; |
| } |
| |
| IOHandle* out_tmp = out_iohandle; |
| IOHandle* err_tmp = err_iohandle; |
| IOHandle* exit_tmp = exit_iohandle; |
| const uint64_t out_key = reinterpret_cast<uint64_t>(out_tmp); |
| const uint64_t err_key = reinterpret_cast<uint64_t>(err_tmp); |
| const uint64_t exit_key = reinterpret_cast<uint64_t>(exit_tmp); |
| const uint32_t events = POLLRDHUP | POLLIN; |
| if (!out_tmp->AsyncWait(port, events, out_key)) { |
| return false; |
| } |
| if (!err_tmp->AsyncWait(port, events, err_key)) { |
| return false; |
| } |
| if (!exit_tmp->AsyncWait(port, events, exit_key)) { |
| return false; |
| } |
| while ((out_tmp != nullptr) || (err_tmp != nullptr) || |
| (exit_tmp != nullptr)) { |
| zx_port_packet_t pkt; |
| status = zx_port_wait(port, ZX_TIME_INFINITE, &pkt); |
| if (status != ZX_OK) { |
| Syslog::PrintErr("Process::Wait: zx_port_wait failed: %s\n", |
| zx_status_get_string(status)); |
| return false; |
| } |
| IOHandle* event_handle = reinterpret_cast<IOHandle*>(pkt.key); |
| const intptr_t event_mask = event_handle->WaitEnd(pkt.signal.observed); |
| if (event_handle == out_tmp) { |
| if ((event_mask & POLLIN) != 0) { |
| const intptr_t avail = FDUtils::AvailableBytes(out_tmp->fd()); |
| if (!out_data.Read(out_tmp->fd(), avail)) { |
| return false; |
| } |
| } |
| if ((event_mask & POLLRDHUP) != 0) { |
| out_tmp->CancelWait(port, out_key); |
| out_tmp = nullptr; |
| } |
| } else if (event_handle == err_tmp) { |
| if ((event_mask & POLLIN) != 0) { |
| const intptr_t avail = FDUtils::AvailableBytes(err_tmp->fd()); |
| if (!err_data.Read(err_tmp->fd(), avail)) { |
| return false; |
| } |
| } |
| if ((event_mask & POLLRDHUP) != 0) { |
| err_tmp->CancelWait(port, err_key); |
| err_tmp = nullptr; |
| } |
| } else if (event_handle == exit_tmp) { |
| if ((event_mask & POLLIN) != 0) { |
| const intptr_t avail = FDUtils::AvailableBytes(exit_tmp->fd()); |
| if (avail == 8) { |
| intptr_t b = |
| NO_RETRY_EXPECTED(read(exit_tmp->fd(), exit_code_data.bytes, 8)); |
| if (b != 8) { |
| return false; |
| } |
| } |
| } |
| if ((event_mask & POLLRDHUP) != 0) { |
| exit_tmp->CancelWait(port, exit_key); |
| exit_tmp = nullptr; |
| } |
| } else { |
| Syslog::PrintErr("Process::Wait: Unexpected wait key: %p\n", |
| event_handle); |
| } |
| if (out_tmp != nullptr) { |
| if (!out_tmp->AsyncWait(port, events, out_key)) { |
| return false; |
| } |
| } |
| if (err_tmp != nullptr) { |
| if (!err_tmp->AsyncWait(port, events, err_key)) { |
| return false; |
| } |
| } |
| if (exit_tmp != nullptr) { |
| if (!exit_tmp->AsyncWait(port, events, exit_key)) { |
| return false; |
| } |
| } |
| } |
| |
| // 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. |
| zx_handle_t process = static_cast<zx_handle_t>(pid); |
| zx_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); |
| // zx_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 zx_task_kill if we know id is a process handle, and we only |
| // know that for sure if it's in our list. |
| zx_handle_t process = static_cast<zx_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; |
| } |
| zx_status_t status = zx_task_kill(process); |
| if (status != ZX_OK) { |
| LOG_ERR("zx_task_kill failed: %s\n", zx_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(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) |
| : namespc_(namespc), |
| 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] = nullptr; |
| |
| program_environment_ = nullptr; |
| if (environment != nullptr) { |
| 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] = nullptr; |
| } |
| } |
| |
| ~ProcessStarter() { |
| 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]); |
| |
| NamespaceScope ns(namespc_, path_); |
| int pathfd = -1; |
| zx_status_t status; |
| if (ns.fd() == AT_FDCWD) { |
| status = fdio_open_fd( |
| ns.path(), |
| static_cast<uint32_t>(fuchsia::io::OpenFlags::RIGHT_READABLE | |
| fuchsia::io::OpenFlags::RIGHT_EXECUTABLE), |
| &pathfd); |
| } else { |
| status = fdio_open_fd_at( |
| ns.fd(), ns.path(), |
| static_cast<uint32_t>(fuchsia::io::OpenFlags::RIGHT_READABLE | |
| fuchsia::io::OpenFlags::RIGHT_EXECUTABLE), |
| &pathfd); |
| } |
| if (status != ZX_OK) { |
| close(exit_pipe_fds[0]); |
| close(exit_pipe_fds[1]); |
| ReportStartError( |
| "Failed to load executable for process start (fdio_open_fd_at %s).", |
| zx_status_get_string(status)); |
| return status; |
| } |
| zx_handle_t vmo = ZX_HANDLE_INVALID; |
| status = fdio_get_vmo_exec(pathfd, &vmo); |
| close(pathfd); |
| if (status != ZX_OK) { |
| close(exit_pipe_fds[0]); |
| close(exit_pipe_fds[1]); |
| ReportStartError( |
| "Failed to load executable for process start (fdio_get_vmo_exec %s).", |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| fdio_spawn_action_t* actions; |
| const intptr_t actions_count = |
| BuildSpawnActions(namespc_->namespc()->fdio_ns(), &actions); |
| if (actions_count < 0) { |
| zx_handle_close(vmo); |
| close(exit_pipe_fds[0]); |
| close(exit_pipe_fds[1]); |
| *os_error_message_ = |
| DartUtils::ScopedCopyCString("Failed to build spawn actions array."); |
| return ZX_ERR_IO; |
| } |
| |
| // TODO(zra): Use the supplied working directory when fdio_spawn_vmo adds an |
| // API to set it. |
| |
| LOG_INFO("ProcessStarter: Start() Calling fdio_spawn_vmo\n"); |
| zx_handle_t process = ZX_HANDLE_INVALID; |
| char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; |
| uint32_t flags = FDIO_SPAWN_CLONE_JOB | FDIO_SPAWN_DEFAULT_LDSVC | |
| FDIO_SPAWN_CLONE_UTC_CLOCK; |
| status = fdio_spawn_vmo(ZX_HANDLE_INVALID, flags, vmo, program_arguments_, |
| program_environment_, actions_count, actions, |
| &process, err_msg); |
| // Handles are consumed by fdio_spawn_vmo even if it fails. |
| delete[] actions; |
| if (status != ZX_OK) { |
| LOG_ERR("ProcessStarter: Start() fdio_spawn_vmo failed\n"); |
| close(exit_pipe_fds[0]); |
| close(exit_pipe_fds[1]); |
| ReportStartError("Process start failed: %s\n", err_msg); |
| return status; |
| } |
| |
| LOG_INFO("ProcessStarter: Start() adding %u to list with exit_pipe %d\n", |
| process, exit_pipe_fds[1]); |
| ProcessInfoList::AddProcess(process, exit_pipe_fds[1]); |
| ExitCodeHandler::Start(); |
| status = ExitCodeHandler::Add(process); |
| if (status != ZX_OK) { |
| LOG_ERR("ProcessStarter: ExitCodeHandler: Add failed: %s\n", |
| zx_status_get_string(status)); |
| close(exit_pipe_fds[0]); |
| close(exit_pipe_fds[1]); |
| zx_task_kill(process); |
| ProcessInfoList::RemoveProcess(process); |
| ReportStartError("Process start failed: %s\n", |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| // The IOHandles allocated below are returned to Dart code. The Dart code |
| // calls into the runtime again to allocate a C++ Socket object, which |
| // becomes the native field of a Dart _NativeSocket object. The C++ Socket |
| // object and the EventHandler manage the lifetime of these IOHandles. |
| *id_ = process; |
| FDUtils::SetNonBlocking(read_in_); |
| *in_ = reinterpret_cast<intptr_t>(new IOHandle(read_in_)); |
| read_in_ = -1; |
| FDUtils::SetNonBlocking(read_err_); |
| *err_ = reinterpret_cast<intptr_t>(new IOHandle(read_err_)); |
| read_err_ = -1; |
| FDUtils::SetNonBlocking(write_out_); |
| *out_ = reinterpret_cast<intptr_t>(new IOHandle(write_out_)); |
| write_out_ = -1; |
| FDUtils::SetNonBlocking(exit_pipe_fds[0]); |
| *exit_event_ = reinterpret_cast<intptr_t>(new IOHandle(exit_pipe_fds[0])); |
| return 0; |
| } |
| |
| private: |
| void ReportStartError(const char* format, ...) PRINTF_ATTRIBUTE(2, 3) { |
| const intptr_t kMaxMessageSize = 256; |
| char* message = DartUtils::ScopedCString(kMaxMessageSize); |
| va_list args; |
| va_start(args, format); |
| vsnprintf(message, kMaxMessageSize, format, args); |
| va_end(args); |
| *os_error_message_ = message; |
| } |
| |
| zx_status_t AddPipe(int target_fd, |
| int* local_fd, |
| fdio_spawn_action_t* action) { |
| zx_status_t status = fdio_pipe_half(local_fd, &action->h.handle); |
| if (status != ZX_OK) return status; |
| action->action = FDIO_SPAWN_ACTION_ADD_HANDLE; |
| action->h.id = PA_HND(PA_HND_TYPE(PA_FD), target_fd); |
| return ZX_OK; |
| } |
| |
| // Fills in 'actions_out' and returns action count. |
| intptr_t BuildSpawnActions(fdio_ns_t* ns, fdio_spawn_action_t** actions_out) { |
| const intptr_t fixed_actions_cnt = 4; |
| intptr_t ns_cnt = 0; |
| zx_status_t status; |
| |
| // First, figure out how many namespace actions are needed. |
| fdio_flat_namespace_t* flat_ns = nullptr; |
| if (ns != nullptr) { |
| status = fdio_ns_export(ns, &flat_ns); |
| if (status != ZX_OK) { |
| LOG_ERR("ProcessStarter: BuildSpawnActions: fdio_ns_export: %s\n", |
| zx_status_get_string(status)); |
| return -1; |
| } |
| ns_cnt = flat_ns->count; |
| } |
| |
| // Allocate the actions array. |
| const intptr_t actions_cnt = ns_cnt + fixed_actions_cnt; |
| fdio_spawn_action_t* actions = new fdio_spawn_action_t[actions_cnt]; |
| |
| // Fill in the entries for passing stdin/out/err handles, and the program |
| // name. |
| status = AddPipe(0, &write_out_, &actions[0]); |
| if (status != ZX_OK) { |
| LOG_ERR("ProcessStarter: BuildSpawnActions: stdout AddPipe failed: %s\n", |
| zx_status_get_string(status)); |
| if (flat_ns != nullptr) { |
| fdio_ns_free_flat_ns(flat_ns); |
| } |
| return -1; |
| } |
| status = AddPipe(1, &read_in_, &actions[1]); |
| if (status != ZX_OK) { |
| LOG_ERR("ProcessStarter: BuildSpawnActions: stdin AddPipe failed: %s\n", |
| zx_status_get_string(status)); |
| if (flat_ns != nullptr) { |
| fdio_ns_free_flat_ns(flat_ns); |
| } |
| return -1; |
| } |
| status = AddPipe(2, &read_err_, &actions[2]); |
| if (status != ZX_OK) { |
| LOG_ERR("ProcessStarter: BuildSpawnActions: stderr AddPipe failed: %s\n", |
| zx_status_get_string(status)); |
| if (flat_ns != nullptr) { |
| fdio_ns_free_flat_ns(flat_ns); |
| } |
| return -1; |
| } |
| // clang-format off |
| actions[3] = { |
| .action = FDIO_SPAWN_ACTION_SET_NAME, |
| .name = { |
| .data = program_arguments_[0], |
| }, |
| }; |
| // clang-format on |
| |
| // Then fill in the namespace actions. |
| if (ns != nullptr) { |
| for (size_t i = 0; i < flat_ns->count; i++) { |
| // clang-format off |
| actions[fixed_actions_cnt + i] = { |
| .action = FDIO_SPAWN_ACTION_ADD_NS_ENTRY, |
| .ns = { |
| .prefix = DartUtils::ScopedCopyCString(flat_ns->path[i]), |
| .handle = flat_ns->handle[i], |
| }, |
| }; |
| // clang-format on |
| flat_ns->handle[i] = ZX_HANDLE_INVALID; |
| } |
| fdio_ns_free_flat_ns(flat_ns); |
| flat_ns = nullptr; |
| } |
| |
| *actions_out = actions; |
| return actions_cnt; |
| } |
| |
| 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_; |
| char** program_environment_; |
| |
| Namespace* namespc_; |
| 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) { |
| if (mode != kNormal) { |
| *os_error_message = DartUtils::ScopedCopyCString( |
| "Only ProcessStartMode.NORMAL is supported on this platform"); |
| return -1; |
| } |
| ProcessStarter starter(namespc, 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) { |
| errno = ENOSYS; |
| return -1; |
| } |
| |
| void Process::ClearSignalHandler(intptr_t signal, Dart_Port port) {} |
| |
| void Process::ClearSignalHandlerByFd(intptr_t fd, Dart_Port port) {} |
| |
| void ProcessInfoList::Init() { |
| active_processes_ = nullptr; |
| ASSERT(ProcessInfoList::mutex_ == nullptr); |
| ProcessInfoList::mutex_ = new Mutex(); |
| } |
| |
| void ProcessInfoList::Cleanup() { |
| ASSERT(ProcessInfoList::mutex_ != nullptr); |
| delete ProcessInfoList::mutex_; |
| ProcessInfoList::mutex_ = nullptr; |
| } |
| |
| void ExitCodeHandler::Init() { |
| port_ = ZX_HANDLE_INVALID; |
| running_ = false; |
| 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(Process::global_exit_code_mutex_ == nullptr); |
| Process::global_exit_code_mutex_ = new Mutex(); |
| } |
| |
| void Process::Cleanup() { |
| 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_FUCHSIA) |