| // 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 | POLLRDHUP)) != 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 | POLLRDHUP)) != 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 | POLLRDHUP)) != 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, | |
| const 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<const 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; | |
| constexpr fuchsia::io::Flags kFlags = | |
| fuchsia::io::PERM_READABLE | fuchsia::io::PERM_EXECUTABLE; | |
| if (ns.fd() == AT_FDCWD) { | |
| status = fdio_open3_fd(ns.path(), uint64_t{kFlags}, &pathfd); | |
| } else { | |
| status = fdio_open3_fd_at(ns.fd(), ns.path(), uint64_t{kFlags}, &pathfd); | |
| } | |
| if (status != ZX_OK) { | |
| close(exit_pipe_fds[0]); | |
| close(exit_pipe_fds[1]); | |
| ReportStartError( | |
| "Failed to load executable for process start (fdio_open3_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() != nullptr ? namespc_->namespc()->fdio_ns() | |
| : nullptr, | |
| &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. | |
| const 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, | |
| const 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(); | |
| } | |
| // The command line dart utility does not run on Fuchsia, this functionality | |
| // is not supported on that platform. | |
| int Process::Exec(Namespace* namespc, | |
| const char* path, | |
| const char** arguments, | |
| intptr_t arguments_length, | |
| const char* working_directory, | |
| char* errmsg, | |
| intptr_t errmsg_len) { | |
| snprintf(errmsg, errmsg_len, | |
| "Process::Exec is not supported on this platform"); | |
| return -1; | |
| } | |
| 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) |