// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(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(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(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(out); IOHandle* err_iohandle = reinterpret_cast(err); IOHandle* exit_iohandle = reinterpret_cast(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(out_tmp); const uint64_t err_key = reinterpret_cast(err_tmp); const uint64_t exit_key = reinterpret_cast(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(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(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(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(Dart_ScopeAllocate( (arguments_length + 2) * sizeof(*program_arguments_))); program_arguments_[0] = const_cast(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(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(fuchsia::io::OpenFlags::RIGHT_READABLE | fuchsia::io::OpenFlags::RIGHT_EXECUTABLE), &pathfd); } else { status = fdio_open_fd_at( ns.fd(), ns.path(), static_cast(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(new IOHandle(read_in_)); read_in_ = -1; FDUtils::SetNonBlocking(read_err_); *err_ = reinterpret_cast(new IOHandle(read_err_)); read_err_ = -1; FDUtils::SetNonBlocking(write_out_); *out_ = reinterpret_cast(new IOHandle(write_out_)); write_out_ = -1; FDUtils::SetNonBlocking(exit_pipe_fds[0]); *exit_event_ = reinterpret_cast(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)