1dc1b015eb
The exit callback might fire during or after dart:io shutdown. TEST=ci (flaky) Bug: https://github.com/dart-lang/sdk/issues/60499 Change-Id: I950ef4a6ba1d99a8eafb55cb051a60f0cb097e71 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/493485 Reviewed-by: Alexander Aprelev <aam@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
1377 lines
46 KiB
C++
1377 lines
46 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 <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);
|
|
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;
|
|
|
|
// 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_handles_[kReadHandle],
|
|
stdout_handles_[kWriteHandle],
|
|
stderr_handles_[kWriteHandle]};
|
|
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)
|