dart-sdk/runtime/bin/process_win.cc
Ryan Macnak 63e6041ca9 [vm] Update to variadic FATAL.
TEST=ci
Change-Id: Ic6bc784605e10760bb28ea6df34242336a33b4d0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/286947
Reviewed-by: Alexander Aprelev <aam@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
2023-03-06 22:06:59 +00:00

1158 lines
37 KiB
C++

// 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 <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"
namespace dart {
namespace bin {
static const int kReadHandle = 0;
static const int kWriteHandle = 1;
int Process::global_exit_code_ = 0;
Mutex* Process::global_exit_code_mutex_ = nullptr;
Process::ExitHook Process::exit_hook_ = NULL;
// 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 != NULL) {
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 = NULL;
ProcessInfo* current = active_processes_;
while (current != NULL) {
if (current->pid() == pid) {
if (prev == NULL) {
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) {
FATAL("Failed to lookup process in list of active processes");
}
// 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) {
FATAL("GetExitCodeProcess failed %d\n", GetLastError());
}
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, NULL);
// 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_ = NULL;
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 = NULL;
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
NULL);
if (handles[kWriteHandle] == INVALID_HANDLE_VALUE) {
Syslog::PrintErr("CreateNamedPipe failed %d\n", GetLastError());
return false;
}
handles[kReadHandle] =
CreateFileW(pipe_name, GENERIC_READ, 0, &inherit_handle, OPEN_EXISTING,
FILE_READ_ATTRIBUTES | FILE_FLAG_OVERLAPPED, NULL);
if (handles[kReadHandle] == INVALID_HANDLE_VALUE) {
Syslog::PrintErr("CreateFile failed %d\n", GetLastError());
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
NULL);
if (handles[kReadHandle] == INVALID_HANDLE_VALUE) {
Syslog::PrintErr("CreateNamedPipe failed %d\n", GetLastError());
return false;
}
handles[kWriteHandle] = CreateFileW(
pipe_name, GENERIC_WRITE, 0,
(type == kInheritWrite) ? &inherit_handle : NULL, OPEN_EXISTING,
FILE_WRITE_ATTRIBUTES | FILE_FLAG_OVERLAPPED, NULL);
if (handles[kWriteHandle] == INVALID_HANDLE_VALUE) {
Syslog::PrintErr("CreateFile failed %d\n", GetLastError());
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])) {
Syslog::PrintErr("CloseHandle failed %d\n", GetLastError());
}
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(char** os_error_message) {
int error_code = GetLastError();
const int kMaxMessageLength = 256;
wchar_t message[kMaxMessageLength];
FormatMessageIntoBuffer(error_code, message, kMaxMessageLength);
*os_error_message = StringUtilsWin::WideToUtf8(message);
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 = NULL;
HANDLE nul = CreateFile(L"NUL", GENERIC_READ | GENERIC_WRITE, 0,
&inherit_handle, OPEN_EXISTING, 0, NULL);
if (nul == INVALID_HANDLE_VALUE) {
Syslog::PrintErr("CloseHandle failed %d\n", GetLastError());
}
return nul;
}
typedef BOOL(WINAPI* InitProcThreadAttrListFn)(LPPROC_THREAD_ATTRIBUTE_LIST,
DWORD,
DWORD,
PSIZE_T);
typedef BOOL(WINAPI* UpdateProcThreadAttrFn)(LPPROC_THREAD_ATTRIBUTE_LIST,
DWORD,
DWORD_PTR,
PVOID,
SIZE_T,
PVOID,
PSIZE_T);
typedef VOID(WINAPI* DeleteProcThreadAttrListFn)(LPPROC_THREAD_ATTRIBUTE_LIST);
static InitProcThreadAttrListFn init_proc_thread_attr_list = NULL;
static UpdateProcThreadAttrFn update_proc_thread_attr = NULL;
static DeleteProcThreadAttrListFn delete_proc_thread_attr_list = NULL;
static Mutex* initialized_mutex = nullptr;
static bool load_attempted = false;
static bool EnsureInitialized() {
HMODULE kernel32_module = GetModuleHandleW(L"kernel32.dll");
if (!load_attempted) {
MutexLocker locker(initialized_mutex);
if (load_attempted) {
return (delete_proc_thread_attr_list != NULL);
}
init_proc_thread_attr_list = reinterpret_cast<InitProcThreadAttrListFn>(
GetProcAddress(kernel32_module, "InitializeProcThreadAttributeList"));
update_proc_thread_attr = reinterpret_cast<UpdateProcThreadAttrFn>(
GetProcAddress(kernel32_module, "UpdateProcThreadAttribute"));
delete_proc_thread_attr_list = reinterpret_cast<DeleteProcThreadAttrListFn>(
GetProcAddress(kernel32_module, "DeleteProcThreadAttributeList"));
load_attempted = true;
return (delete_proc_thread_attr_list != NULL);
}
return (delete_proc_thread_attr_list != NULL);
}
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,
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)
: 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;
// Transform input strings to system format.
const wchar_t* system_path = StringUtilsWin::Utf8ToWide(path_);
wchar_t** system_arguments;
system_arguments = reinterpret_cast<wchar_t**>(
Dart_ScopeAllocate(arguments_length * sizeof(*system_arguments)));
for (int i = 0; i < arguments_length; i++) {
system_arguments[i] = StringUtilsWin::Utf8ToWide(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*>(
Dart_ScopeAllocate(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);
}
// Create environment block if an environment is supplied.
environment_block_ = NULL;
if (environment != NULL) {
wchar_t** system_environment;
system_environment = reinterpret_cast<wchar_t**>(
Dart_ScopeAllocate(environment_length * sizeof(*system_environment)));
// Convert environment strings to system strings.
for (intptr_t i = 0; i < environment_length; i++) {
system_environment[i] = StringUtilsWin::Utf8ToWide(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*>(
Dart_ScopeAllocate(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);
}
system_working_directory_ = NULL;
if (working_directory_ != NULL) {
system_working_directory_ =
StringUtilsWin::Utf8ToWide(working_directory_);
}
attribute_list_ = NULL;
}
~ProcessStarter() {
if (attribute_list_ != NULL) {
delete_proc_thread_attr_list(attribute_list_);
}
}
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);
if (mode_ != kInheritStdio) {
startup_info.StartupInfo.hStdInput = stdin_handles_[kReadHandle];
startup_info.StartupInfo.hStdOutput = stdout_handles_[kWriteHandle];
startup_info.StartupInfo.hStdError = stderr_handles_[kWriteHandle];
startup_info.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
bool supports_proc_thread_attr_lists = EnsureInitialized();
if (supports_proc_thread_attr_lists) {
// 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 (!init_proc_thread_attr_list(NULL, 1, 0, &size) &&
(GetLastError() != ERROR_INSUFFICIENT_BUFFER)) {
return CleanupAndReturnError();
}
attribute_list_ = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(
Dart_ScopeAllocate(size));
ZeroMemory(attribute_list_, size);
if (!init_proc_thread_attr_list(attribute_list_, 1, 0, &size)) {
return CleanupAndReturnError();
}
inherited_handles_ = {stdin_handles_[kReadHandle],
stdout_handles_[kWriteHandle],
stderr_handles_[kWriteHandle]};
if (!update_proc_thread_attr(
attribute_list_, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
inherited_handles_.data(),
inherited_handles_.size() * sizeof(HANDLE), NULL, NULL)) {
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(
NULL, // ApplicationName
command_line_,
NULL, // ProcessAttributes
NULL, // 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);
}
}
CloseHandle(process_info.hThread);
// 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_);
Syslog::PrintErr("UuidCreateSequential failed %d\n", status);
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() {
int error_code = SetOsErrorMessage(os_error_message_);
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];
const 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_;
char** os_error_message_;
private:
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_handler,
char** os_error_message) {
ProcessStarter starter(path, arguments, arguments_length, working_directory,
environment, environment_length, mode, in, out, err,
id, exit_handler, os_error_message);
return starter.Start();
}
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() != NULL);
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, NULL, &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_));
overlapped_.hEvent = event_;
}
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->SupportsOverlappedIO());
FileHandle* stderr_handle = reinterpret_cast<FileHandle*>(err);
ASSERT(stderr_handle->SupportsOverlappedIO());
FileHandle* exit_handle = reinterpret_cast<FileHandle*>(exit_event);
ASSERT(exit_handle->SupportsOverlappedIO());
// Create three events for overlapped IO. These are created as already
// signalled to ensure they have read called at least once.
static const int kHandles = 3;
HANDLE events[kHandles];
for (int i = 0; i < kHandles; i++) {
events[i] = CreateEvent(NULL, FALSE, TRUE, NULL);
}
// 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;
}
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 = NULL;
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 != NULL) {
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]);
write_handle->EnsureInitialized(EventHandler::delegate());
intptr_t write_fd = reinterpret_cast<intptr_t>(write_handle);
if (signal_handlers == NULL) {
if (SetConsoleCtrlHandler(SignalHandler, true) == 0) {
int error_code = GetLastError();
// Since SetConsoleCtrlHandler failed, the IO completion port will
// never receive an event for this handle, and will therefore never
// release the reference Retained by EnsureInitialized(). So, we
// have to do a second Release() here.
write_handle->Release();
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 != NULL) {
bool remove = false;
if (handler->signal() == signal) {
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 == NULL) {
USE(SetConsoleCtrlHandler(SignalHandler, false));
}
}
void Process::ClearSignalHandlerByFd(intptr_t fd, Dart_Port port) {
MutexLocker lock(signal_mutex);
SignalInfo* handler = signal_handlers;
while (handler != NULL) {
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 == NULL) {
USE(SetConsoleCtrlHandler(SignalHandler, false));
}
}
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 Process::Init() {
ProcessInfoList::Init();
signal_handlers = NULL;
ASSERT(signal_mutex == nullptr);
signal_mutex = new Mutex();
ASSERT(initialized_mutex == nullptr);
initialized_mutex = new Mutex();
load_attempted = false;
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(initialized_mutex != nullptr);
delete initialized_mutex;
initialized_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)