| // 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. | |
| #include "platform/globals.h" | |
| #if defined(DART_HOST_OS_WINDOWS) | |
| #include "bin/process.h" | |
| #include <process.h> // NOLINT | |
| #include <psapi.h> // NOLINT | |
| #include <source_location> | |
| #include <vector> | |
| #include "bin/builtin.h" | |
| #include "bin/dartutils.h" | |
| #include "bin/eventhandler.h" | |
| #include "bin/lockers.h" | |
| #include "bin/socket.h" | |
| #include "bin/thread.h" | |
| #include "bin/utils.h" | |
| #include "bin/utils_win.h" | |
| #include "platform/syslog.h" | |
| #include "platform/text_buffer.h" | |
| namespace dart { | |
| namespace bin { | |
| static constexpr int kReadHandle = 0; | |
| static constexpr int kWriteHandle = 1; | |
| 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 process handle, | |
| // wait handle for registered exit code event and 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(DWORD process_id, | |
| HANDLE process_handle, | |
| HANDLE wait_handle, | |
| HANDLE exit_pipe) | |
| : process_id_(process_id), | |
| process_handle_(process_handle), | |
| wait_handle_(wait_handle), | |
| exit_pipe_(exit_pipe) {} | |
| ~ProcessInfo() { | |
| BOOL success = CloseHandle(process_handle_); | |
| if (!success) { | |
| FATAL("Failed to close process handle"); | |
| } | |
| success = CloseHandle(exit_pipe_); | |
| if (!success) { | |
| FATAL("Failed to close process exit code pipe"); | |
| } | |
| } | |
| DWORD pid() { return process_id_; } | |
| HANDLE process_handle() { return process_handle_; } | |
| HANDLE wait_handle() { return wait_handle_; } | |
| HANDLE exit_pipe() { return exit_pipe_; } | |
| ProcessInfo* next() { return next_; } | |
| void set_next(ProcessInfo* next) { next_ = next; } | |
| private: | |
| // Process id. | |
| DWORD process_id_; | |
| // Process handle. | |
| HANDLE process_handle_; | |
| // Wait handle identifying the exit-code wait operation registered | |
| // with RegisterWaitForSingleObject. | |
| HANDLE wait_handle_; | |
| // File descriptor for pipe to report exit code. | |
| HANDLE exit_pipe_; | |
| // Link to next ProcessInfo object in the singly-linked list. | |
| 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(DWORD pid, HANDLE handle, HANDLE pipe) { | |
| // Register a callback to extract the exit code, when the process | |
| // is signaled. The callback runs in a independent thread from the OS pool. | |
| // Because the callback depends on the process list containing | |
| // the process, lock the mutex until the process is added to the list. | |
| MutexLocker locker(mutex_); | |
| HANDLE wait_handle = INVALID_HANDLE_VALUE; | |
| BOOL success = RegisterWaitForSingleObject( | |
| &wait_handle, handle, &ExitCodeCallback, reinterpret_cast<PVOID>(pid), | |
| INFINITE, WT_EXECUTEONLYONCE); | |
| if (!success) { | |
| FATAL("Failed to register exit code wait operation."); | |
| } | |
| ProcessInfo* info = new ProcessInfo(pid, handle, wait_handle, pipe); | |
| // Mutate the process list under the mutex. | |
| info->set_next(active_processes_); | |
| active_processes_ = info; | |
| } | |
| static bool LookupProcess(DWORD pid, | |
| HANDLE* handle, | |
| HANDLE* wait_handle, | |
| HANDLE* pipe) { | |
| MutexLocker locker(mutex_); | |
| ProcessInfo* current = active_processes_; | |
| while (current != nullptr) { | |
| if (current->pid() == pid) { | |
| *handle = current->process_handle(); | |
| *wait_handle = current->wait_handle(); | |
| *pipe = current->exit_pipe(); | |
| return true; | |
| } | |
| current = current->next(); | |
| } | |
| return false; | |
| } | |
| static void RemoveProcess(DWORD pid) { | |
| MutexLocker locker(mutex_); | |
| ProcessInfo* prev = nullptr; | |
| ProcessInfo* current = active_processes_; | |
| while (current != nullptr) { | |
| if (current->pid() == pid) { | |
| if (prev == nullptr) { | |
| active_processes_ = current->next(); | |
| } else { | |
| prev->set_next(current->next()); | |
| } | |
| delete current; | |
| return; | |
| } | |
| prev = current; | |
| current = current->next(); | |
| } | |
| } | |
| private: | |
| // Callback called when an exit code is available from one of the | |
| // processes in the list. | |
| static void CALLBACK ExitCodeCallback(PVOID data, BOOLEAN timed_out) { | |
| if (timed_out) { | |
| return; | |
| } | |
| DWORD pid = reinterpret_cast<UINT_PTR>(data); | |
| HANDLE handle; | |
| HANDLE wait_handle; | |
| HANDLE exit_pipe; | |
| bool success = LookupProcess(pid, &handle, &wait_handle, &exit_pipe); | |
| if (!success) { | |
| // Failed to lookup process in list of active processes. This might happen | |
| // if a child exits after dart:io shutdown. | |
| return; | |
| } | |
| // Unregister the event in a non-blocking way. | |
| BOOL ok = UnregisterWait(wait_handle); | |
| if (!ok && (GetLastError() != ERROR_IO_PENDING)) { | |
| FATAL("Failed unregistering wait operation"); | |
| } | |
| // Get and report the exit code to Dart. | |
| int exit_code; | |
| ok = GetExitCodeProcess(handle, reinterpret_cast<DWORD*>(&exit_code)); | |
| if (!ok) { | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| FATAL("GetExitCodeProcess failed %d (%s)\n", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| } | |
| int negative = 0; | |
| if (exit_code < 0) { | |
| exit_code = abs(exit_code); | |
| negative = 1; | |
| } | |
| int message[2] = {exit_code, negative}; | |
| DWORD written; | |
| ok = ::WriteFile(exit_pipe, message, sizeof(message), &written, nullptr); | |
| // If the process has been closed, the read end of the exit | |
| // pipe has been closed. It is therefore not a problem that | |
| // WriteFile fails with a closed pipe error | |
| // (ERROR_NO_DATA). Other errors should not happen. | |
| if (ok && (written != sizeof(message))) { | |
| FATAL("Failed to write entire process exit message"); | |
| } else if (!ok && (GetLastError() != ERROR_NO_DATA)) { | |
| FATAL("Failed to write exit code: %d", GetLastError()); | |
| } | |
| // Remove the process from the list of active processes. | |
| RemoveProcess(pid); | |
| } | |
| // 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; | |
| // Types of pipes to create. | |
| enum NamedPipeType { kInheritRead, kInheritWrite, kInheritNone }; | |
| // Create a pipe for communicating with a new process. The handles array | |
| // will contain the read and write ends of the pipe. Based on the type | |
| // one of the handles will be inheritable. | |
| // NOTE: If this function returns false the handles might have been allocated | |
| // and the caller should make sure to close them in case of an error. | |
| static bool CreateProcessPipe(HANDLE handles[2], | |
| wchar_t* pipe_name, | |
| NamedPipeType type) { | |
| // Security attributes describing an inheritable handle. | |
| SECURITY_ATTRIBUTES inherit_handle; | |
| inherit_handle.nLength = sizeof(SECURITY_ATTRIBUTES); | |
| inherit_handle.bInheritHandle = TRUE; | |
| inherit_handle.lpSecurityDescriptor = nullptr; | |
| if (type == kInheritRead) { | |
| handles[kWriteHandle] = | |
| CreateNamedPipeW(pipe_name, PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED, | |
| PIPE_TYPE_BYTE | PIPE_WAIT, | |
| 1, // Number of pipes | |
| 1024, // Out buffer size | |
| 1024, // In buffer size | |
| 0, // Timeout in ms | |
| nullptr); | |
| if (handles[kWriteHandle] == INVALID_HANDLE_VALUE) { | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| Syslog::PrintErr("CreateNamedPipe failed %d (%s)\n", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| return false; | |
| } | |
| handles[kReadHandle] = | |
| CreateFileW(pipe_name, GENERIC_READ, 0, &inherit_handle, OPEN_EXISTING, | |
| FILE_READ_ATTRIBUTES | FILE_FLAG_OVERLAPPED, nullptr); | |
| if (handles[kReadHandle] == INVALID_HANDLE_VALUE) { | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| Syslog::PrintErr("CreateFile failed %d (%s)\n", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| return false; | |
| } | |
| } else { | |
| ASSERT((type == kInheritWrite) || (type == kInheritNone)); | |
| handles[kReadHandle] = | |
| CreateNamedPipeW(pipe_name, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, | |
| PIPE_TYPE_BYTE | PIPE_WAIT, | |
| 1, // Number of pipes | |
| 1024, // Out buffer size | |
| 1024, // In buffer size | |
| 0, // Timeout in ms | |
| nullptr); | |
| if (handles[kReadHandle] == INVALID_HANDLE_VALUE) { | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| Syslog::PrintErr("CreateNamedPipe failed %d (%s)\n", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| return false; | |
| } | |
| handles[kWriteHandle] = CreateFileW( | |
| pipe_name, GENERIC_WRITE, 0, | |
| (type == kInheritWrite) ? &inherit_handle : nullptr, OPEN_EXISTING, | |
| FILE_WRITE_ATTRIBUTES | FILE_FLAG_OVERLAPPED, nullptr); | |
| if (handles[kWriteHandle] == INVALID_HANDLE_VALUE) { | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| Syslog::PrintErr("CreateFile failed %d (%s)\n", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| static void CloseProcessPipe(HANDLE handles[2]) { | |
| for (int i = kReadHandle; i < kWriteHandle; i++) { | |
| if (handles[i] != INVALID_HANDLE_VALUE) { | |
| if (!CloseHandle(handles[i])) { | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| Syslog::PrintErr("CloseHandle failed %d (%s)\n", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| } | |
| handles[i] = INVALID_HANDLE_VALUE; | |
| } | |
| } | |
| } | |
| static void CloseProcessPipes(HANDLE handles1[2], | |
| HANDLE handles2[2], | |
| HANDLE handles3[2], | |
| HANDLE handles4[2]) { | |
| CloseProcessPipe(handles1); | |
| CloseProcessPipe(handles2); | |
| CloseProcessPipe(handles3); | |
| CloseProcessPipe(handles4); | |
| } | |
| static int SetOsErrorMessage( | |
| CStringUniquePtr* os_error_message, | |
| std::source_location location = std::source_location::current()) { | |
| int error_code = GetLastError(); | |
| const int kMaxMessageLength = 256; | |
| wchar_t message[kMaxMessageLength]; | |
| FormatMessageIntoBuffer(error_code, message, kMaxMessageLength); | |
| WideToUtf8Scope utf8_message(message); | |
| os_error_message->reset(Utils::SCreate("%s (at %s:%d)", utf8_message.utf8(), | |
| location.file_name(), | |
| location.line())); | |
| return error_code; | |
| } | |
| // Open an inheritable handle to NUL. | |
| static HANDLE OpenNul() { | |
| SECURITY_ATTRIBUTES inherit_handle; | |
| inherit_handle.nLength = sizeof(SECURITY_ATTRIBUTES); | |
| inherit_handle.bInheritHandle = TRUE; | |
| inherit_handle.lpSecurityDescriptor = nullptr; | |
| HANDLE nul = CreateFile(L"NUL", GENERIC_READ | GENERIC_WRITE, 0, | |
| &inherit_handle, OPEN_EXISTING, 0, nullptr); | |
| if (nul == INVALID_HANDLE_VALUE) { | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| Syslog::PrintErr("CloseHandle failed %d (%s)\n", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| } | |
| return nul; | |
| } | |
| const int kMaxPipeNameSize = 80; | |
| template <int Count> | |
| static int GenerateNames(wchar_t pipe_names[Count][kMaxPipeNameSize]) { | |
| UUID uuid; | |
| RPC_STATUS status = UuidCreateSequential(&uuid); | |
| if ((status != RPC_S_OK) && (status != RPC_S_UUID_LOCAL_ONLY)) { | |
| return status; | |
| } | |
| RPC_WSTR uuid_string; | |
| status = UuidToStringW(&uuid, &uuid_string); | |
| if (status != RPC_S_OK) { | |
| return status; | |
| } | |
| for (int i = 0; i < Count; i++) { | |
| static const wchar_t* prefix = L"\\\\.\\Pipe\\dart"; | |
| _snwprintf(pipe_names[i], kMaxPipeNameSize, L"%s_%s_%d", prefix, | |
| uuid_string, i + 1); | |
| } | |
| status = RpcStringFreeW(&uuid_string); | |
| if (status != RPC_S_OK) { | |
| return status; | |
| } | |
| return 0; | |
| } | |
| class ProcessStarter { | |
| public: | |
| ProcessStarter(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, | |
| CStringUniquePtr* os_error_message) | |
| : path_(path), | |
| working_directory_(working_directory), | |
| mode_(mode), | |
| in_(in), | |
| out_(out), | |
| err_(err), | |
| id_(id), | |
| exit_handler_(exit_handler), | |
| os_error_message_(os_error_message) { | |
| stdin_handles_[kReadHandle] = INVALID_HANDLE_VALUE; | |
| stdin_handles_[kWriteHandle] = INVALID_HANDLE_VALUE; | |
| stdout_handles_[kReadHandle] = INVALID_HANDLE_VALUE; | |
| stdout_handles_[kWriteHandle] = INVALID_HANDLE_VALUE; | |
| stderr_handles_[kReadHandle] = INVALID_HANDLE_VALUE; | |
| stderr_handles_[kWriteHandle] = INVALID_HANDLE_VALUE; | |
| exit_handles_[kReadHandle] = INVALID_HANDLE_VALUE; | |
| exit_handles_[kWriteHandle] = INVALID_HANDLE_VALUE; | |
| child_process_handle_ = INVALID_HANDLE_VALUE; | |
| // Transform input strings to system format. | |
| wchar_t* system_path = nullptr; | |
| StringUtilsWin::Utf8ToWide(path_, &system_path); | |
| wchar_t** system_arguments; | |
| system_arguments = reinterpret_cast<wchar_t**>( | |
| malloc(arguments_length * sizeof(*system_arguments))); | |
| for (int i = 0; i < arguments_length; i++) { | |
| StringUtilsWin::Utf8ToWide(arguments[i], &(system_arguments[i])); | |
| } | |
| // Compute command-line length. | |
| int command_line_length = wcslen(system_path); | |
| for (int i = 0; i < arguments_length; i++) { | |
| command_line_length += wcslen(system_arguments[i]); | |
| } | |
| // Account for null termination and one space per argument. | |
| command_line_length += arguments_length + 1; | |
| // Put together command-line string. | |
| command_line_ = reinterpret_cast<wchar_t*>( | |
| malloc(command_line_length * sizeof(*command_line_))); | |
| int len = 0; | |
| int remaining = command_line_length; | |
| int written = | |
| _snwprintf(command_line_ + len, remaining, L"%s", system_path); | |
| len += written; | |
| remaining -= written; | |
| ASSERT(remaining >= 0); | |
| for (int i = 0; i < arguments_length; i++) { | |
| written = _snwprintf(command_line_ + len, remaining, L" %s", | |
| system_arguments[i]); | |
| len += written; | |
| remaining -= written; | |
| ASSERT(remaining >= 0); | |
| } | |
| for (int i = 0; i < arguments_length; i++) { | |
| free(system_arguments[i]); | |
| } | |
| free(system_arguments); | |
| free(system_path); | |
| // Create environment block if an environment is supplied. | |
| environment_block_ = nullptr; | |
| if (environment != nullptr) { | |
| wchar_t** system_environment; | |
| system_environment = reinterpret_cast<wchar_t**>( | |
| malloc(environment_length * sizeof(*system_environment))); | |
| // Convert environment strings to system strings. | |
| for (intptr_t i = 0; i < environment_length; i++) { | |
| StringUtilsWin::Utf8ToWide(environment[i], &(system_environment[i])); | |
| } | |
| // An environment block is a sequence of zero-terminated strings | |
| // followed by a block-terminating zero char. | |
| intptr_t block_size = 1; | |
| for (intptr_t i = 0; i < environment_length; i++) { | |
| block_size += wcslen(system_environment[i]) + 1; | |
| } | |
| environment_block_ = reinterpret_cast<wchar_t*>( | |
| malloc(block_size * sizeof(*environment_block_))); | |
| intptr_t block_index = 0; | |
| for (intptr_t i = 0; i < environment_length; i++) { | |
| intptr_t len = wcslen(system_environment[i]); | |
| intptr_t result = _snwprintf(environment_block_ + block_index, len, | |
| L"%s", system_environment[i]); | |
| ASSERT(result == len); | |
| block_index += len; | |
| environment_block_[block_index++] = '\0'; | |
| } | |
| // Block-terminating zero char. | |
| environment_block_[block_index++] = '\0'; | |
| ASSERT(block_index == block_size); | |
| for (intptr_t i = 0; i < environment_length; i++) { | |
| free(system_environment[i]); | |
| } | |
| free(system_environment); | |
| } | |
| system_working_directory_ = nullptr; | |
| if (working_directory_ != nullptr) { | |
| StringUtilsWin::Utf8ToWide(working_directory_, | |
| &system_working_directory_); | |
| } | |
| attribute_list_ = nullptr; | |
| } | |
| ~ProcessStarter() { | |
| if (attribute_list_ != nullptr) { | |
| DeleteProcThreadAttributeList(attribute_list_); | |
| free(attribute_list_); | |
| } | |
| free(command_line_); | |
| free(environment_block_); | |
| free(system_working_directory_); | |
| } | |
| int Start() { | |
| // Create pipes required. | |
| int err = CreatePipes(); | |
| if (err != 0) { | |
| return err; | |
| } | |
| // Setup info structures. | |
| STARTUPINFOEXW startup_info; | |
| ZeroMemory(&startup_info, sizeof(startup_info)); | |
| startup_info.StartupInfo.cb = sizeof(startup_info); | |
| HANDLE stdin_handle; | |
| HANDLE stdout_handle; | |
| HANDLE stderr_handle; | |
| if (mode_ == kInheritStdio) { | |
| stdin_handle = GetStdHandle(STD_INPUT_HANDLE); | |
| stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); | |
| stderr_handle = GetStdHandle(STD_ERROR_HANDLE); | |
| } else { | |
| stdin_handle = stdin_handles_[kReadHandle]; | |
| stdout_handle = stdout_handles_[kWriteHandle]; | |
| stderr_handle = stderr_handles_[kWriteHandle]; | |
| } | |
| startup_info.StartupInfo.hStdInput = stdin_handle; | |
| startup_info.StartupInfo.hStdOutput = stdout_handle; | |
| startup_info.StartupInfo.hStdError = stderr_handle; | |
| startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES; | |
| // Setup the handles to inherit. We only want to inherit the three | |
| // handles for stdin, stdout and stderr. | |
| SIZE_T size = 0; | |
| // The call to determine the size of an attribute list always fails with | |
| // ERROR_INSUFFICIENT_BUFFER and that error should be ignored. | |
| if (!InitializeProcThreadAttributeList(nullptr, 1, 0, &size) && | |
| (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { | |
| return CleanupAndReturnError(); | |
| } | |
| attribute_list_ = | |
| reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(size)); | |
| ZeroMemory(attribute_list_, size); | |
| if (!InitializeProcThreadAttributeList(attribute_list_, 1, 0, &size)) { | |
| return CleanupAndReturnError(); | |
| } | |
| inherited_handles_ = {stdin_handle}; | |
| if (stdout_handle != stdin_handle) { | |
| inherited_handles_.push_back(stdout_handle); | |
| } | |
| if (stderr_handle != stdout_handle && stderr_handle != stdin_handle) { | |
| inherited_handles_.push_back(stderr_handle); | |
| } | |
| if (!UpdateProcThreadAttribute( | |
| attribute_list_, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, | |
| inherited_handles_.data(), | |
| inherited_handles_.size() * sizeof(HANDLE), nullptr, nullptr)) { | |
| return CleanupAndReturnError(); | |
| } | |
| startup_info.lpAttributeList = attribute_list_; | |
| PROCESS_INFORMATION process_info; | |
| ZeroMemory(&process_info, sizeof(process_info)); | |
| // Create process. | |
| DWORD creation_flags = | |
| EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT; | |
| if (!Process::ModeIsAttached(mode_)) { | |
| creation_flags |= DETACHED_PROCESS; | |
| } else { | |
| // Unless we are inheriting stdio which means there is some console | |
| // associated with the app, we want to ensure no console window pops | |
| // up for the spawned child. | |
| if (mode_ != kInheritStdio) { | |
| // Normally stdout for console dart application is associated with a | |
| // console that is launched from, but for gui applications(flutter on | |
| // windows) console might be absent, will be created by CreateProcessW | |
| // below. When that happens we ensure that console window doesn't | |
| // pop up. | |
| creation_flags |= CREATE_NO_WINDOW; | |
| } | |
| } | |
| BOOL result = CreateProcessW( | |
| nullptr, // ApplicationName | |
| command_line_, | |
| nullptr, // ProcessAttributes | |
| nullptr, // ThreadAttributes | |
| TRUE, // InheritHandles | |
| creation_flags, environment_block_, system_working_directory_, | |
| reinterpret_cast<STARTUPINFOW*>(&startup_info), &process_info); | |
| if (result == 0) { | |
| return CleanupAndReturnError(); | |
| } | |
| if (mode_ != kInheritStdio) { | |
| CloseHandle(stdin_handles_[kReadHandle]); | |
| CloseHandle(stdout_handles_[kWriteHandle]); | |
| CloseHandle(stderr_handles_[kWriteHandle]); | |
| } | |
| if (Process::ModeIsAttached(mode_)) { | |
| ProcessInfoList::AddProcess(process_info.dwProcessId, | |
| process_info.hProcess, | |
| exit_handles_[kWriteHandle]); | |
| } | |
| if (mode_ != kDetached) { | |
| // Connect the three stdio streams. | |
| if (Process::ModeHasStdio(mode_)) { | |
| FileHandle* stdin_handle = new FileHandle(stdin_handles_[kWriteHandle]); | |
| FileHandle* stdout_handle = | |
| new FileHandle(stdout_handles_[kReadHandle]); | |
| FileHandle* stderr_handle = | |
| new FileHandle(stderr_handles_[kReadHandle]); | |
| *in_ = reinterpret_cast<intptr_t>(stdout_handle); | |
| *out_ = reinterpret_cast<intptr_t>(stdin_handle); | |
| *err_ = reinterpret_cast<intptr_t>(stderr_handle); | |
| } | |
| if (Process::ModeIsAttached(mode_)) { | |
| FileHandle* exit_handle = new FileHandle(exit_handles_[kReadHandle]); | |
| *exit_handler_ = reinterpret_cast<intptr_t>(exit_handle); | |
| } | |
| } | |
| child_process_handle_ = process_info.hProcess; | |
| CloseHandle(process_info.hThread); | |
| // Return process id. | |
| *id_ = process_info.dwProcessId; | |
| return 0; | |
| } | |
| // Check if the current process is attached to console by opening | |
| // CONOUT$. | |
| // | |
| // Checking STD_OUTPUT_HANDLE does not actually say anything because output | |
| // might be redirected, but the console will still exist and can be written | |
| // to. | |
| // | |
| // See https://learn.microsoft.com/en-us/windows/console/console-handles. | |
| static bool IsCurrentProcessAttachedToConsole() { | |
| HANDLE h = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE, | |
| FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); | |
| if (h != INVALID_HANDLE_VALUE) { | |
| CloseHandle(h); | |
| return true; | |
| } | |
| return false; | |
| } | |
| int StartForExec(HANDLE hjob) { | |
| ASSERT(mode_ == kInheritStdio); | |
| ASSERT(Process::ModeIsAttached(mode_)); | |
| ASSERT(!Process::ModeHasStdio(mode_)); | |
| // Setup info | |
| STARTUPINFOEXW startup_info; | |
| ZeroMemory(&startup_info, sizeof(startup_info)); | |
| startup_info.StartupInfo.cb = sizeof(startup_info); | |
| // Setup the handles to inherit. We only want to inherit the three | |
| // handles for stdin, stdout and stderr. | |
| HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE); | |
| HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); | |
| HANDLE stderr_handle = GetStdHandle(STD_ERROR_HANDLE); | |
| startup_info.StartupInfo.hStdInput = stdin_handle; | |
| startup_info.StartupInfo.hStdOutput = stdout_handle; | |
| startup_info.StartupInfo.hStdError = stderr_handle; | |
| startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES; | |
| SIZE_T size = 0; | |
| // The call to determine the size of an attribute list always fails with | |
| // ERROR_INSUFFICIENT_BUFFER and that error should be ignored. | |
| if (!InitializeProcThreadAttributeList(nullptr, 1, 0, &size) && | |
| (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) { | |
| return CleanupAndReturnError(); | |
| } | |
| attribute_list_ = | |
| reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(size)); | |
| ZeroMemory(attribute_list_, size); | |
| if (!InitializeProcThreadAttributeList(attribute_list_, 1, 0, &size)) { | |
| return CleanupAndReturnError(); | |
| } | |
| inherited_handles_ = {stdin_handle}; | |
| if (stdout_handle != stdin_handle) { | |
| inherited_handles_.push_back(stdout_handle); | |
| } | |
| if (stderr_handle != stdout_handle && stderr_handle != stdin_handle) { | |
| inherited_handles_.push_back(stderr_handle); | |
| } | |
| if (!UpdateProcThreadAttribute( | |
| attribute_list_, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, | |
| inherited_handles_.data(), | |
| inherited_handles_.size() * sizeof(HANDLE), nullptr, nullptr)) { | |
| return CleanupAndReturnError(); | |
| } | |
| startup_info.lpAttributeList = attribute_list_; | |
| PROCESS_INFORMATION process_info; | |
| ZeroMemory(&process_info, sizeof(process_info)); | |
| // Create process. | |
| DWORD creation_flags = | |
| EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT; | |
| if (!IsCurrentProcessAttachedToConsole()) { | |
| // Prevent child process from creating a console if the current | |
| // process does not have a console. This makes sure that if | |
| // dart.exe is spawned without a console then dartvm.exe | |
| // will not create its own console. | |
| creation_flags |= DETACHED_PROCESS; | |
| } | |
| BOOL result = CreateProcessW( | |
| nullptr, // ApplicationName | |
| command_line_, | |
| nullptr, // ProcessAttributes | |
| nullptr, // ThreadAttributes | |
| TRUE, // InheritHandles | |
| creation_flags, environment_block_, system_working_directory_, | |
| reinterpret_cast<STARTUPINFOW*>(&startup_info), &process_info); | |
| if (result == 0) { | |
| return CleanupAndReturnError(); | |
| } | |
| child_process_handle_ = process_info.hProcess; | |
| CloseHandle(process_info.hThread); | |
| CloseHandle(stdin_handle); | |
| CloseHandle(stdout_handle); | |
| CloseHandle(stderr_handle); | |
| // Put this new process into the job object of the parent so that it | |
| // is killed when the parent is killed. | |
| if (!AssignProcessToJobObject(hjob, child_process_handle_)) { | |
| return CleanupAndReturnError(); | |
| } | |
| // Return process id. | |
| *id_ = process_info.dwProcessId; | |
| return 0; | |
| } | |
| int CreatePipes() { | |
| // Generate unique pipe names for the four named pipes needed. | |
| wchar_t pipe_names[4][kMaxPipeNameSize]; | |
| int status = GenerateNames<4>(pipe_names); | |
| if (status != 0) { | |
| SetOsErrorMessage(os_error_message_); | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| Syslog::PrintErr("UuidCreateSequential failed %d (%s)\n", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| return status; | |
| } | |
| if (mode_ != kDetached) { | |
| // Open pipes for stdin, stdout, stderr and for communicating the exit | |
| // code. | |
| if (Process::ModeHasStdio(mode_)) { | |
| if (!CreateProcessPipe(stdin_handles_, pipe_names[0], kInheritRead) || | |
| !CreateProcessPipe(stdout_handles_, pipe_names[1], kInheritWrite) || | |
| !CreateProcessPipe(stderr_handles_, pipe_names[2], kInheritWrite)) { | |
| return CleanupAndReturnError(); | |
| } | |
| } | |
| // Only open exit code pipe for non detached processes. | |
| if (Process::ModeIsAttached(mode_)) { | |
| if (!CreateProcessPipe(exit_handles_, pipe_names[3], kInheritNone)) { | |
| return CleanupAndReturnError(); | |
| } | |
| } | |
| } else { | |
| // Open NUL for stdin, stdout, and stderr. | |
| stdin_handles_[kReadHandle] = OpenNul(); | |
| if (stdin_handles_[kReadHandle] == INVALID_HANDLE_VALUE) { | |
| return CleanupAndReturnError(); | |
| } | |
| stdout_handles_[kWriteHandle] = OpenNul(); | |
| if (stdout_handles_[kWriteHandle] == INVALID_HANDLE_VALUE) { | |
| return CleanupAndReturnError(); | |
| } | |
| stderr_handles_[kWriteHandle] = OpenNul(); | |
| if (stderr_handles_[kWriteHandle] == INVALID_HANDLE_VALUE) { | |
| return CleanupAndReturnError(); | |
| } | |
| } | |
| return 0; | |
| } | |
| int CleanupAndReturnError( | |
| std::source_location location = std::source_location::current()) { | |
| int error_code = SetOsErrorMessage(os_error_message_, location); | |
| CloseProcessPipes(stdin_handles_, stdout_handles_, stderr_handles_, | |
| exit_handles_); | |
| return error_code; | |
| } | |
| HANDLE stdin_handles_[2]; | |
| HANDLE stdout_handles_[2]; | |
| HANDLE stderr_handles_[2]; | |
| HANDLE exit_handles_[2]; | |
| HANDLE child_process_handle_; | |
| wchar_t* system_working_directory_; | |
| wchar_t* command_line_; | |
| wchar_t* environment_block_; | |
| std::vector<HANDLE> inherited_handles_; | |
| LPPROC_THREAD_ATTRIBUTE_LIST attribute_list_; | |
| const char* path_; | |
| const char* working_directory_; | |
| ProcessStartMode mode_; | |
| intptr_t* in_; | |
| intptr_t* out_; | |
| intptr_t* err_; | |
| intptr_t* id_; | |
| intptr_t* exit_handler_; | |
| CStringUniquePtr* os_error_message_; | |
| private: | |
| 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_handler, | |
| char** os_error_message) { | |
| CStringUniquePtr error; | |
| ProcessStarter starter(path, arguments, arguments_length, working_directory, | |
| environment, environment_length, mode, in, out, err, | |
| id, exit_handler, &error); | |
| const auto result = starter.Start(); | |
| if (result != 0 && error != nullptr) { | |
| *os_error_message = DartUtils::ScopedCopyCString(error.get()); | |
| } | |
| return result; | |
| } | |
| class BufferList : public BufferListBase { | |
| public: | |
| BufferList() : read_pending_(true) {} | |
| // Indicate that data has been read into the buffer provided to | |
| // overlapped read. | |
| void DataIsRead(intptr_t size) { | |
| ASSERT(read_pending_ == true); | |
| set_data_size(data_size() + size); | |
| set_free_size(free_size() - size); | |
| ASSERT(free_size() >= 0); | |
| read_pending_ = false; | |
| } | |
| // The access to the read buffer for overlapped read. | |
| bool GetReadBuffer(uint8_t** buffer, intptr_t* size) { | |
| ASSERT(!read_pending_); | |
| if (free_size() == 0) { | |
| if (!Allocate()) { | |
| return false; | |
| } | |
| } | |
| ASSERT(free_size() > 0); | |
| ASSERT(free_size() <= kBufferSize); | |
| *buffer = FreeSpaceAddress(); | |
| *size = free_size(); | |
| read_pending_ = true; | |
| return true; | |
| } | |
| intptr_t GetDataSize() { return data_size(); } | |
| uint8_t* GetFirstDataBuffer() { | |
| ASSERT(head() != nullptr); | |
| ASSERT(head() == tail()); | |
| ASSERT(data_size() <= kBufferSize); | |
| return head()->data(); | |
| } | |
| void FreeDataBuffer() { Free(); } | |
| private: | |
| bool read_pending_; | |
| DISALLOW_COPY_AND_ASSIGN(BufferList); | |
| }; | |
| class OverlappedHandle { | |
| public: | |
| OverlappedHandle() {} | |
| void Init(HANDLE handle, HANDLE event) { | |
| handle_ = handle; | |
| event_ = event; | |
| ClearOverlapped(); | |
| } | |
| bool HasEvent(HANDLE event) { return (event_ == event); } | |
| bool Read() { | |
| // Get the data read as a result of a completed overlapped operation. | |
| if (overlapped_.InternalHigh > 0) { | |
| buffer_.DataIsRead(overlapped_.InternalHigh); | |
| } else { | |
| buffer_.DataIsRead(0); | |
| } | |
| // Keep reading until error or pending operation. | |
| while (true) { | |
| ClearOverlapped(); | |
| uint8_t* buffer; | |
| intptr_t buffer_size; | |
| if (!buffer_.GetReadBuffer(&buffer, &buffer_size)) { | |
| return false; | |
| } | |
| BOOL ok = ::ReadFile(handle_, buffer, buffer_size, nullptr, &overlapped_); | |
| if (!ok) { | |
| return (GetLastError() == ERROR_IO_PENDING); | |
| } | |
| buffer_.DataIsRead(overlapped_.InternalHigh); | |
| } | |
| } | |
| Dart_Handle GetData() { return buffer_.GetData(); } | |
| intptr_t GetDataSize() { return buffer_.GetDataSize(); } | |
| uint8_t* GetFirstDataBuffer() { return buffer_.GetFirstDataBuffer(); } | |
| void FreeDataBuffer() { return buffer_.FreeDataBuffer(); } | |
| #if defined(DEBUG) | |
| bool IsEmpty() const { return buffer_.IsEmpty(); } | |
| #endif | |
| void Close() { | |
| CloseHandle(handle_); | |
| CloseHandle(event_); | |
| handle_ = INVALID_HANDLE_VALUE; | |
| overlapped_.hEvent = INVALID_HANDLE_VALUE; | |
| } | |
| private: | |
| void ClearOverlapped() { | |
| memset(&overlapped_, 0, sizeof(overlapped_)); | |
| // |FileHandle| constructor eagerly associates the given handle with | |
| // |EventHandler|'s completion port. However we don't want to notify | |
| // that completion port when |ReadFile| operation completes because | |
| // we are manually draining the pipe here instead of using |EventHandler|. | |
| // Setting LSB of |hEvent| to 1 prevents completion packets from being | |
| // enqueued. See documentation for |GetQueuedCompletionStatus| (specifically | |
| // notes for |lpOverlapped| argument). | |
| overlapped_.hEvent = | |
| reinterpret_cast<HANDLE>(reinterpret_cast<uintptr_t>(event_) | 0x1); | |
| } | |
| OVERLAPPED overlapped_; | |
| HANDLE handle_; | |
| HANDLE event_; | |
| BufferList buffer_; | |
| DISALLOW_ALLOCATION(); | |
| DISALLOW_COPY_AND_ASSIGN(OverlappedHandle); | |
| }; | |
| bool Process::Wait(intptr_t pid, | |
| intptr_t in, | |
| intptr_t out, | |
| intptr_t err, | |
| intptr_t exit_event, | |
| ProcessResult* result) { | |
| // Close input to the process right away. | |
| reinterpret_cast<FileHandle*>(in)->Close(); | |
| // All pipes created to the sub-process support overlapped IO. | |
| FileHandle* stdout_handle = reinterpret_cast<FileHandle*>(out); | |
| ASSERT(stdout_handle->supports_overlapped_io()); | |
| FileHandle* stderr_handle = reinterpret_cast<FileHandle*>(err); | |
| ASSERT(stderr_handle->supports_overlapped_io()); | |
| FileHandle* exit_handle = reinterpret_cast<FileHandle*>(exit_event); | |
| ASSERT(exit_handle->supports_overlapped_io()); | |
| // Create three events for overlapped IO. These are created as already | |
| // signalled to ensure they have read called at least once. | |
| const int kHandles = 3; | |
| HANDLE events[kHandles]; | |
| for (int i = 0; i < kHandles; i++) { | |
| events[i] = CreateEvent(nullptr, FALSE, TRUE, nullptr); | |
| } | |
| // Setup the structure for handling overlapped IO. | |
| OverlappedHandle oh[kHandles]; | |
| oh[0].Init(stdout_handle->handle(), events[0]); | |
| oh[1].Init(stderr_handle->handle(), events[1]); | |
| oh[2].Init(exit_handle->handle(), events[2]); | |
| // Continue until all handles are closed. | |
| int alive = kHandles; | |
| while (alive > 0) { | |
| // Blocking call waiting for events from the child process. | |
| DWORD wait_result = WaitForMultipleObjects(alive, events, FALSE, INFINITE); | |
| // Find the handle signalled. | |
| int index = wait_result - WAIT_OBJECT_0; | |
| for (int i = 0; i < kHandles; i++) { | |
| if (oh[i].HasEvent(events[index])) { | |
| bool ok = oh[i].Read(); | |
| if (!ok) { | |
| if (GetLastError() == ERROR_BROKEN_PIPE) { | |
| oh[i].Close(); | |
| alive--; | |
| if (index < alive) { | |
| events[index] = events[alive]; | |
| } | |
| } else if (err != ERROR_IO_PENDING) { | |
| DWORD e = GetLastError(); | |
| oh[0].Close(); | |
| oh[1].Close(); | |
| oh[2].Close(); | |
| SetLastError(e); | |
| return false; | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| // All handles closed and all data read. | |
| result->set_stdout_data(oh[0].GetData()); | |
| result->set_stderr_data(oh[1].GetData()); | |
| DEBUG_ASSERT(oh[0].IsEmpty()); | |
| DEBUG_ASSERT(oh[1].IsEmpty()); | |
| // Calculate the exit code. | |
| ASSERT(oh[2].GetDataSize() == 8); | |
| uint32_t exit_codes[2]; | |
| memmove(&exit_codes, oh[2].GetFirstDataBuffer(), sizeof(exit_codes)); | |
| oh[2].FreeDataBuffer(); | |
| intptr_t exit_code = exit_codes[0]; | |
| intptr_t negative = exit_codes[1]; | |
| if (negative != 0) { | |
| exit_code = -exit_code; | |
| } | |
| result->set_exit_code(exit_code); | |
| return true; | |
| } | |
| 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) { | |
| // Create a Job object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | |
| HANDLE hjob = CreateJobObject(nullptr, nullptr); | |
| if (hjob == nullptr) { | |
| BufferFormatter f(errmsg, errmsg_len); | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| f.Printf("Process::Exec - CreateJobObject failed %d %(s)", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| return -1; | |
| } | |
| JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; | |
| DWORD qresult; | |
| memset(&info, 0, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); | |
| if (!QueryInformationJobObject(hjob, JobObjectExtendedLimitInformation, &info, | |
| sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION), | |
| &qresult)) { | |
| BufferFormatter f(errmsg, errmsg_len); | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| f.Printf("Process::Exec - QueryInformationJobObject failed %d (%s)", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| return -1; | |
| } | |
| // Ensure that a child process that adds itself to this job object will | |
| // be killed when the parent dies and child processes that do not add | |
| // themselves to this job object will not get killed when the parent | |
| // dies. | |
| info.BasicLimitInformation.LimitFlags |= | |
| (JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | | |
| JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK); | |
| if (!SetInformationJobObject(hjob, JobObjectExtendedLimitInformation, &info, | |
| sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION))) { | |
| BufferFormatter f(errmsg, errmsg_len); | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| f.Printf("Process::Exec - SetInformationJobObject failed %d (%s)", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| return -1; | |
| } | |
| // Put the current process into the job object (there is a race here | |
| // as the process can crash before it is in the Job object, but since | |
| // we haven't spawned any children yet this race is harmless) | |
| if (!AssignProcessToJobObject(hjob, GetCurrentProcess())) { | |
| BufferFormatter f(errmsg, errmsg_len); | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| f.Printf("Process::Exec - AssignProcessToJobObject failed %d (%s)", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| return -1; | |
| } | |
| // Spawn the new child process (this child will automatically get | |
| // added to the Job object). | |
| // If the parent process is killed or it crashes the Job object | |
| // will get destroyed and all the child processes will also get killed. | |
| // arguments includes the name of the executable to run which is the same | |
| // as the value passed in 'path', we strip that off when starting the | |
| // process. | |
| intptr_t pid = -1; | |
| CStringUniquePtr os_error_message; | |
| ProcessStarter starter(path, &(arguments[1]), (arguments_length - 1), | |
| working_directory, nullptr, 0, kInheritStdio, nullptr, | |
| nullptr, nullptr, &pid, nullptr, &os_error_message); | |
| int result = starter.StartForExec(hjob); | |
| if (result != 0) { | |
| BufferFormatter f(errmsg, errmsg_len); | |
| f.Printf("ProcessStarter::StartForExec failed: %s", os_error_message.get()); | |
| return -1; | |
| } | |
| // Now wait for this child process to terminate (normal exit or crash). | |
| HANDLE child_process = starter.child_process_handle_; | |
| ASSERT(child_process != INVALID_HANDLE_VALUE); | |
| // Don't attempt to handle ctrl-c events, let spawned child handle them. | |
| SetConsoleCtrlHandler(NULL, TRUE); | |
| DWORD wait_result = WaitForSingleObject(child_process, INFINITE); | |
| if (wait_result != WAIT_OBJECT_0) { | |
| BufferFormatter f(errmsg, errmsg_len); | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| f.Printf("Process::Exec - WaitForSingleObject failed %d (%s)", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| CloseHandle(child_process); | |
| return -1; | |
| } | |
| int retval; | |
| if (!GetExitCodeProcess(child_process, reinterpret_cast<DWORD*>(&retval))) { | |
| BufferFormatter f(errmsg, errmsg_len); | |
| int error = GetLastError(); | |
| char buffer[1024]; | |
| f.Printf("Process::Exec - GetExitCodeProcess failed %d (%s)", error, | |
| Utils::StrError(error, buffer, sizeof(buffer))); | |
| CloseHandle(child_process); | |
| return -1; | |
| } | |
| CloseHandle(child_process); | |
| return retval; | |
| } | |
| bool Process::Kill(intptr_t id, int signal) { | |
| USE(signal); // signal is not used on Windows. | |
| HANDLE process_handle; | |
| HANDLE wait_handle; | |
| HANDLE exit_pipe; | |
| // First check the process info list for the process to get a handle to it. | |
| bool success = ProcessInfoList::LookupProcess(id, &process_handle, | |
| &wait_handle, &exit_pipe); | |
| // For detached processes we don't have the process registered in the | |
| // process info list. Try to look it up through the OS. | |
| if (!success) { | |
| process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, id); | |
| // The process is already dead. | |
| if (process_handle == INVALID_HANDLE_VALUE) { | |
| return false; | |
| } | |
| } | |
| BOOL result = TerminateProcess(process_handle, -1); | |
| return result ? true : false; | |
| } | |
| void Process::TerminateExitCodeHandler() { | |
| // Nothing needs to be done on Windows. | |
| } | |
| intptr_t Process::CurrentProcessId() { | |
| return static_cast<intptr_t>(GetCurrentProcessId()); | |
| } | |
| int64_t Process::CurrentRSS() { | |
| // Although the documentation at | |
| // https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getprocessmemoryinfo | |
| // claims that GetProcessMemoryInfo is UWP compatible, it is actually not | |
| // hence this function cannot work when compiled in UWP mode. | |
| #ifdef DART_TARGET_OS_WINDOWS_UWP | |
| return -1; | |
| #else | |
| PROCESS_MEMORY_COUNTERS pmc; | |
| if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { | |
| return -1; | |
| } | |
| return pmc.WorkingSetSize; | |
| #endif | |
| } | |
| int64_t Process::MaxRSS() { | |
| #ifdef DART_TARGET_OS_WINDOWS_UWP | |
| return -1; | |
| #else | |
| PROCESS_MEMORY_COUNTERS pmc; | |
| if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { | |
| return -1; | |
| } | |
| return pmc.PeakWorkingSetSize; | |
| #endif | |
| } | |
| static SignalInfo* signal_handlers = nullptr; | |
| static Mutex* signal_mutex = nullptr; | |
| SignalInfo::~SignalInfo() { | |
| FileHandle* file_handle = reinterpret_cast<FileHandle*>(fd_); | |
| file_handle->Close(); | |
| file_handle->Release(); | |
| } | |
| BOOL WINAPI SignalHandler(DWORD signal) { | |
| MutexLocker lock(signal_mutex); | |
| const SignalInfo* handler = signal_handlers; | |
| bool handled = false; | |
| while (handler != nullptr) { | |
| if (handler->signal() == signal) { | |
| int value = 0; | |
| SocketBase::Write(handler->fd(), &value, 1, SocketBase::kAsync); | |
| handled = true; | |
| } | |
| handler = handler->next(); | |
| } | |
| return handled; | |
| } | |
| intptr_t GetWinSignal(intptr_t signal) { | |
| switch (signal) { | |
| case kSighup: | |
| return CTRL_CLOSE_EVENT; | |
| case kSigint: | |
| return CTRL_C_EVENT; | |
| default: | |
| return -1; | |
| } | |
| } | |
| intptr_t Process::SetSignalHandler(intptr_t signal) { | |
| signal = GetWinSignal(signal); | |
| if (signal == -1) { | |
| SetLastError(ERROR_NOT_SUPPORTED); | |
| return -1; | |
| } | |
| // Generate a unique pipe name for the named pipe. | |
| wchar_t pipe_name[kMaxPipeNameSize]; | |
| int status = GenerateNames<1>(&pipe_name); | |
| if (status != 0) { | |
| return status; | |
| } | |
| HANDLE fds[2]; | |
| if (!CreateProcessPipe(fds, pipe_name, kInheritNone)) { | |
| int error_code = GetLastError(); | |
| CloseProcessPipe(fds); | |
| SetLastError(error_code); | |
| return -1; | |
| } | |
| MutexLocker lock(signal_mutex); | |
| FileHandle* write_handle = new FileHandle(fds[kWriteHandle]); | |
| intptr_t write_fd = reinterpret_cast<intptr_t>(write_handle); | |
| if (signal_handlers == nullptr) { | |
| if (SetConsoleCtrlHandler(SignalHandler, true) == 0) { | |
| int error_code = GetLastError(); | |
| // Since SetConsoleCtrlHandler failed, there will be no subsequent IO | |
| // operation on this handle. Release() it. | |
| write_handle->Release(); | |
| CloseProcessPipe(fds); | |
| SetLastError(error_code); | |
| return -1; | |
| } | |
| } | |
| signal_handlers = | |
| new SignalInfo(write_fd, signal, /*oldact=*/nullptr, signal_handlers); | |
| return reinterpret_cast<intptr_t>(new FileHandle(fds[kReadHandle])); | |
| } | |
| void Process::ClearSignalHandler(intptr_t signal, Dart_Port port) { | |
| signal = GetWinSignal(signal); | |
| if (signal == -1) { | |
| return; | |
| } | |
| MutexLocker lock(signal_mutex); | |
| SignalInfo* handler = signal_handlers; | |
| while (handler != nullptr) { | |
| bool remove = false; | |
| if (handler->signal() == signal) { | |
| if ((port == ILLEGAL_PORT) || (handler->port() == port)) { | |
| if (signal_handlers == handler) { | |
| signal_handlers = handler->next(); | |
| } | |
| handler->Unlink(); | |
| remove = true; | |
| } | |
| } | |
| SignalInfo* next = handler->next(); | |
| if (remove) { | |
| delete handler; | |
| } | |
| handler = next; | |
| } | |
| if (signal_handlers == nullptr) { | |
| USE(SetConsoleCtrlHandler(SignalHandler, false)); | |
| } | |
| } | |
| void Process::ClearSignalHandlerByFd(intptr_t fd, Dart_Port port) { | |
| MutexLocker lock(signal_mutex); | |
| SignalInfo* handler = signal_handlers; | |
| while (handler != nullptr) { | |
| bool remove = false; | |
| if (handler->fd() == fd) { | |
| if ((port == ILLEGAL_PORT) || (handler->port() == port)) { | |
| if (signal_handlers == handler) { | |
| signal_handlers = handler->next(); | |
| } | |
| handler->Unlink(); | |
| FileHandle* file_handle = reinterpret_cast<FileHandle*>(handler->fd()); | |
| file_handle->Release(); | |
| remove = true; | |
| } | |
| } | |
| SignalInfo* next = handler->next(); | |
| if (remove) { | |
| delete handler; | |
| } | |
| handler = next; | |
| } | |
| if (signal_handlers == nullptr) { | |
| USE(SetConsoleCtrlHandler(SignalHandler, false)); | |
| } | |
| } | |
| void ProcessInfoList::Init() { | |
| if (mutex_ == nullptr) { | |
| mutex_ = new Mutex(); | |
| } | |
| } | |
| void ProcessInfoList::Cleanup() { | |
| // Do not delete mutex_. A child process might exit during/after dart:io | |
| // shutdown. | |
| } | |
| void Process::Init() { | |
| ProcessInfoList::Init(); | |
| signal_handlers = nullptr; | |
| ASSERT(signal_mutex == nullptr); | |
| signal_mutex = new Mutex(); | |
| ASSERT(Process::global_exit_code_mutex_ == nullptr); | |
| Process::global_exit_code_mutex_ = new Mutex(); | |
| } | |
| void Process::Cleanup() { | |
| ClearAllSignalHandlers(); | |
| ASSERT(signal_mutex != nullptr); | |
| delete signal_mutex; | |
| signal_mutex = nullptr; | |
| ASSERT(Process::global_exit_code_mutex_ != nullptr); | |
| delete Process::global_exit_code_mutex_; | |
| Process::global_exit_code_mutex_ = nullptr; | |
| ProcessInfoList::Cleanup(); | |
| } | |
| } // namespace bin | |
| } // namespace dart | |
| #endif // defined(DART_HOST_OS_WINDOWS) |