// Copyright (c) 2012, 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. #ifndef RUNTIME_BIN_PROCESS_H_ #define RUNTIME_BIN_PROCESS_H_ #include #include "bin/builtin.h" #include "bin/io_buffer.h" #include "bin/lockers.h" #include "bin/namespace.h" #include "bin/thread.h" #include "platform/globals.h" #if !defined(DART_HOST_OS_WINDOWS) #include "platform/signal_blocker.h" #endif #include "platform/utils.h" namespace dart { namespace bin { class ProcessResult { public: ProcessResult() : exit_code_(0) {} void set_stdout_data(Dart_Handle stdout_data) { stdout_data_ = stdout_data; } void set_stderr_data(Dart_Handle stderr_data) { stderr_data_ = stderr_data; } void set_exit_code(intptr_t exit_code) { exit_code_ = exit_code; } Dart_Handle stdout_data() { return stdout_data_; } Dart_Handle stderr_data() { return stderr_data_; } intptr_t exit_code() { return exit_code_; } private: Dart_Handle stdout_data_; Dart_Handle stderr_data_; intptr_t exit_code_; DISALLOW_ALLOCATION(); }; // To be kept in sync with ProcessSignal consts in sdk/lib/io/process.dart // Note that this map is as on Linux. enum ProcessSignals { kSighup = 1, kSigint = 2, kSigquit = 3, kSigill = 4, kSigtrap = 5, kSigabrt = 6, kSigbus = 7, kSigfpe = 8, kSigkill = 9, kSigusr1 = 10, kSigsegv = 11, kSigusr2 = 12, kSigpipe = 13, kSigalrm = 14, kSigterm = 15, kSigchld = 17, kSigcont = 18, kSigstop = 19, kSigtstp = 20, kSigttin = 21, kSigttou = 22, kSigurg = 23, kSigxcpu = 24, kSigxfsz = 25, kSigvtalrm = 26, kSigprof = 27, kSigwinch = 28, kSigpoll = 29, kSigsys = 31, kLastSignal = kSigsys, }; // To be kept in sync with ProcessStartMode consts in sdk/lib/io/process.dart. enum ProcessStartMode { kNormal = 0, kInheritStdio = 1, kDetached = 2, kDetachedWithStdio = 3, }; class Process { public: static void Init(); static void Cleanup(); // Start a new process providing access to stdin, stdout, stderr and // process exit streams. static int 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_handler, char** os_error_message); static bool Wait(intptr_t id, intptr_t in, intptr_t out, intptr_t err, intptr_t exit_handler, ProcessResult* result); // Exec process. // On systems that support 'exec' it will use it to replace // the current process image with the image corresponding to 'path' // On systems that do not support it (Windows) it will start in a // child process in the same group as the parent so that when the parent // is killed the child also dies. // Returns 0 if the process could be execed successfully // Returns -1 if the exec could not be done successfully and 'errmsg' // points to the error message static int Exec(Namespace* namespc, const char* path, const char* arguments[], intptr_t arguments_length, const char* working_directory, char* errmsg, intptr_t errmsg_len); // Kill a process with a given pid. static bool Kill(intptr_t id, int signal); // Terminate the exit code handler thread. Does not return before // the thread has terminated. static void TerminateExitCodeHandler(); static int GlobalExitCode() { MutexLocker ml(global_exit_code_mutex_); return global_exit_code_; } static void SetGlobalExitCode(int exit_code) { MutexLocker ml(global_exit_code_mutex_); global_exit_code_ = exit_code; } typedef void (*ExitHook)(int64_t exit_code); static void SetExitHook(ExitHook hook) { exit_hook_ = hook; } static void RunExitHook(int64_t exit_code) { if (exit_hook_ != nullptr) { exit_hook_(exit_code); } } static intptr_t CurrentProcessId(); static intptr_t SetSignalHandler(intptr_t signal); // When there is a current Isolate and the 'port' argument is // Dart_GetMainPortId(), this clears the signal handler for the current // isolate. When 'port' is ILLEGAL_PORT, this clears all signal handlers for // 'signal' for all Isolates. static void ClearSignalHandler(intptr_t signal, Dart_Port port); static void ClearSignalHandlerByFd(intptr_t fd, Dart_Port port); static void ClearAllSignalHandlers(); static Dart_Handle GetProcessIdNativeField(Dart_Handle process, intptr_t* pid); static Dart_Handle SetProcessIdNativeField(Dart_Handle process, intptr_t pid); static int64_t CurrentRSS(); static int64_t MaxRSS(); static void GetRSSInformation(int64_t* max_rss, int64_t* current_rss); static bool ModeIsAttached(ProcessStartMode mode); static bool ModeHasStdio(ProcessStartMode mode); private: static int global_exit_code_; static Mutex* global_exit_code_mutex_; static ExitHook exit_hook_; DISALLOW_ALLOCATION(); DISALLOW_IMPLICIT_CONSTRUCTORS(Process); }; typedef void (*sa_handler_t)(int); class SignalInfo { public: SignalInfo(intptr_t fd, intptr_t signal, sa_handler_t oldact, SignalInfo* next) : fd_(fd), signal_(signal), oldact_(oldact), // SignalInfo is expected to be created when in a isolate. port_(Dart_GetMainPortId()), next_(next), prev_(nullptr) { if (next_ != nullptr) { next_->prev_ = this; } } ~SignalInfo(); void Unlink() { if (prev_ != nullptr) { prev_->next_ = next_; } if (next_ != nullptr) { next_->prev_ = prev_; } } intptr_t fd() const { return fd_; } intptr_t signal() const { return signal_; } sa_handler_t oldact() const { return oldact_; } Dart_Port port() const { return port_; } SignalInfo* next() const { return next_; } private: intptr_t fd_; intptr_t signal_; sa_handler_t oldact_; // The port_ is used to identify what isolate the signal-info belongs to. Dart_Port port_; SignalInfo* next_; SignalInfo* prev_; DISALLOW_COPY_AND_ASSIGN(SignalInfo); }; // Utility class for collecting the output when running a process // synchronously by using Process::Wait. This class is sub-classed in // the platform specific files to implement reading into the buffers // allocated. class BufferListBase { protected: static constexpr intptr_t kBufferSize = 16 * 1024; class BufferListNode { public: explicit BufferListNode(intptr_t size) { data_ = new uint8_t[size]; // We check for a failed allocation below in Allocate() next_ = nullptr; } ~BufferListNode() { delete[] data_; } bool Valid() const { return data_ != nullptr; } uint8_t* data() const { return data_; } BufferListNode* next() const { return next_; } void set_next(BufferListNode* n) { next_ = n; } private: uint8_t* data_; BufferListNode* next_; DISALLOW_IMPLICIT_CONSTRUCTORS(BufferListNode); }; public: BufferListBase() : head_(nullptr), tail_(nullptr), data_size_(0), free_size_(0) {} ~BufferListBase() { Free(); DEBUG_ASSERT(IsEmpty()); } // Returns the collected data as a Uint8List. If an error occurs an // error handle is returned. Dart_Handle GetData() { uint8_t* buffer; intptr_t buffer_position = 0; Dart_Handle result = IOBuffer::Allocate(data_size_, &buffer); if (Dart_IsNull(result)) { return DartUtils::NewDartOSError(); } if (Dart_IsError(result)) { Free(); return result; } for (BufferListNode* current = head_; current != nullptr; current = current->next()) { intptr_t to_copy = dart::Utils::Minimum(data_size_, kBufferSize); memmove(buffer + buffer_position, current->data(), to_copy); buffer_position += to_copy; data_size_ -= to_copy; } ASSERT(data_size_ == 0); Free(); return result; } #if defined(DEBUG) bool IsEmpty() const { return (head_ == nullptr) && (tail_ == nullptr); } #endif protected: bool Allocate() { ASSERT(free_size_ == 0); BufferListNode* node = new BufferListNode(kBufferSize); if ((node == nullptr) || !node->Valid()) { // Failed to allocate a buffer for the node. delete node; return false; } if (head_ == nullptr) { head_ = node; tail_ = node; } else { ASSERT(tail_->next() == nullptr); tail_->set_next(node); tail_ = node; } free_size_ = kBufferSize; return true; } void Free() { BufferListNode* current = head_; while (current != nullptr) { BufferListNode* tmp = current; current = current->next(); delete tmp; } head_ = nullptr; tail_ = nullptr; data_size_ = 0; free_size_ = 0; } // Returns the address of the first byte in the free space. uint8_t* FreeSpaceAddress() { return tail_->data() + (kBufferSize - free_size_); } intptr_t data_size() const { return data_size_; } void set_data_size(intptr_t size) { data_size_ = size; } intptr_t free_size() const { return free_size_; } void set_free_size(intptr_t size) { free_size_ = size; } BufferListNode* head() const { return head_; } BufferListNode* tail() const { return tail_; } private: // Linked list for data collected. BufferListNode* head_; BufferListNode* tail_; // Number of bytes of data collected in the linked list. intptr_t data_size_; // Number of free bytes in the last node in the list. intptr_t free_size_; DISALLOW_COPY_AND_ASSIGN(BufferListBase); }; #if defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_FUCHSIA) || \ defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS) class BufferList : public BufferListBase { public: BufferList() {} bool Read(int fd, intptr_t available) { // Read all available bytes. while (available > 0) { if (free_size() == 0) { if (!Allocate()) { errno = ENOMEM; return false; } } ASSERT(free_size() > 0); ASSERT(free_size() <= kBufferSize); intptr_t block_size = dart::Utils::Minimum(free_size(), available); #if defined(DART_HOST_OS_FUCHSIA) intptr_t bytes = NO_RETRY_EXPECTED( read(fd, reinterpret_cast(FreeSpaceAddress()), block_size)); #else intptr_t bytes = TEMP_FAILURE_RETRY( read(fd, reinterpret_cast(FreeSpaceAddress()), block_size)); #endif // defined(DART_HOST_OS_FUCHSIA) if (bytes < 0) { return false; } set_data_size(data_size() + bytes); set_free_size(free_size() - bytes); available -= bytes; } return true; } private: DISALLOW_COPY_AND_ASSIGN(BufferList); }; #endif // defined(DART_HOST_OS_ANDROID) ... } // namespace bin } // namespace dart #endif // RUNTIME_BIN_PROCESS_H_