[dartdev] Use VmInteropHandler for invoking sub commands

Use VmInteropHandler for invoking sub commands instead of running them
in an isolate. Running sub commands in an isolate causes an increased footprint.
Changing this to use VmInteropHandler avoids the additional memory footprint.

Commands that need to use an AOT runtime for execution now exec the AOT
runtime and run the command.

TEST=ci

Change-Id: Ic96845b19951170effea3dd3619f798e2c72968a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/402781
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Siva Annamalai <asiva@google.com>
This commit is contained in:
asiva
2025-01-16 09:15:01 -08:00
committed by Commit Queue
parent 6880ea8495
commit 7b25ce88d3
11 changed files with 442 additions and 57 deletions
+17 -6
View File
@@ -17,6 +17,7 @@ import '../experiments.dart';
import '../native_assets.dart';
import '../sdk.dart';
import '../utils.dart';
import '../vm_interop_handler.dart';
const int genericErrorExitCode = 255;
const int compileErrorExitCode = 254;
@@ -93,17 +94,18 @@ class CompileJSCommand extends CompileSubcommandCommand {
final args = argResults!;
var snapshot = sdk.dart2jsAotSnapshot;
var runtime = sdk.dartAotRuntime;
var useExecProcess = true;
if (!Sdk.checkArtifactExists(snapshot, logError: false)) {
// AOT snapshots cannot be generated on IA32, so we need this fallback
// branch until support for IA32 is dropped (https://dartbug.com/49969).
snapshot = sdk.dart2jsSnapshot;
runtime = sdk.dart;
if (!Sdk.checkArtifactExists(snapshot)) {
return genericErrorExitCode;
}
runtime = sdk.dart;
useExecProcess = false;
}
final dart2jsCommand = [
runtime,
snapshot,
'--libraries-spec=${sdk.librariesJson}',
'--cfe-invocation-modes=compile',
@@ -112,8 +114,13 @@ class CompileJSCommand extends CompileSubcommandCommand {
if (args.rest.isNotEmpty) ...args.rest.sublist(0),
];
try {
final exitCode = await runProcessInheritStdio(dart2jsCommand);
return exitCode;
VmInteropHandler.run(
runtime,
dart2jsCommand,
packageConfigOverride: null,
useExecProcess: useExecProcess,
);
return 0;
} catch (e, st) {
log.stderr('Error: JS compilation failed');
log.stderr(e.toString());
@@ -135,8 +142,12 @@ class CompileDDCCommand extends CompileSubcommandCommand {
// This command is an internal developer command used by tools and is
// hidden in the help message.
CompileDDCCommand({bool verbose = false})
: super(cmdName, 'Compile Dart to JavaScript using ddc.', verbose,
hidden:true,);
: super(
cmdName,
'Compile Dart to JavaScript using ddc.',
verbose,
hidden: true,
);
@override
String get invocation => '${super.invocation} <dart entry point>';
+82 -4
View File
@@ -2,6 +2,7 @@
// 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.
import 'dart:io';
import 'dart:isolate';
/// Contains methods used to communicate DartDev results back to the VM.
@@ -18,6 +19,10 @@ abstract class VmInteropHandler {
///
/// If [markMainIsolateAsSystemIsolate] is given and set to true, the spawned
/// isolate will run with `--mark-main-isolate-as-system-isolate` enabled.
///
/// If [useExecProcess] is given and set to true, the script is executed by
/// execing it (On Linux/Mac the exec call is used, on Windows a new child
/// process is started).
static void run(
String script,
List<String> args, {
@@ -27,16 +32,33 @@ abstract class VmInteropHandler {
//
// See https://github.com/dart-lang/sdk/issues/53576
bool markMainIsolateAsSystemIsolate = false,
bool useExecProcess = false,
}) {
List<String> argsList;
if (useExecProcess && Platform.isWindows) {
// On Windows if a new process is used to execute the script we
// need to escape the script path and all the arguments as we
// construct a single command line string to be passed to the
// Windows process create call.
if (script.contains(' ') && !script.contains('"')) {
// Escape paths that may contain spaces
script = '"$script"';
}
argsList = [
for (int i = 0; i < args.length; i++) _windowsArgumentEscape(args[i]),
];
} else {
// Copy the list so it doesn't get GC'd underneath us.
argsList = args.toList();
}
final port = _port;
if (port == null) return;
final message = <dynamic>[
_kResultRun,
useExecProcess? _kResultRunExec : _kResultRun,
script,
packageConfigOverride,
markMainIsolateAsSystemIsolate,
// Copy the list so it doesn't get GC'd underneath us.
args.toList()
argsList
];
port.send(message);
}
@@ -50,9 +72,65 @@ abstract class VmInteropHandler {
port.send(message);
}
/// This code is identical to the one in process_patch.dart, please ensure
/// changes made here are also done in process_patch.dart.
/// TODO : figure out if this functionality can be abstracted out to a
/// common place.
static String _windowsArgumentEscape(String argument) {
if (argument.isEmpty) {
return '""';
}
var result = argument;
if (argument.contains('\t') ||
argument.contains(' ') ||
argument.contains('"')) {
// Produce something that the C runtime on Windows will parse
// back as this string.
// Replace any number of '\' followed by '"' with
// twice as many '\' followed by '\"'.
var backslash = '\\'.codeUnitAt(0);
var sb = StringBuffer();
var nextPos = 0;
var quotePos = argument.indexOf('"', nextPos);
while (quotePos != -1) {
var numBackslash = 0;
var pos = quotePos - 1;
while (pos >= 0 && argument.codeUnitAt(pos) == backslash) {
numBackslash++;
pos--;
}
sb.write(argument.substring(nextPos, quotePos - numBackslash));
for (var i = 0; i < numBackslash; i++) {
sb.write(r'\\');
}
sb.write(r'\"');
nextPos = quotePos + 1;
quotePos = argument.indexOf('"', nextPos);
}
sb.write(argument.substring(nextPos, argument.length));
result = sb.toString();
// Add '"' at the beginning and end and replace all '\' at
// the end with two '\'.
sb = StringBuffer('"');
sb.write(result);
nextPos = argument.length - 1;
while (argument.codeUnitAt(nextPos) == backslash) {
sb.write('\\');
nextPos--;
}
sb.write('"');
result = sb.toString();
}
return result;
}
// Note: keep in sync with runtime/bin/dartdev_isolate.h
static const int _kResultRun = 1;
static const int _kResultExit = 2;
static const int _kResultRunExec = 2;
static const int _kResultExit = 3;
static SendPort? _port;
}
+83
View File
@@ -38,6 +38,7 @@ DartDevIsolate::DartDevRunner DartDevIsolate::runner_ =
DartDevIsolate::DartDevRunner();
bool DartDevIsolate::should_run_dart_dev_ = false;
bool DartDevIsolate::print_usage_error_ = false;
CommandLineOptions* DartDevIsolate::vm_options_ = nullptr;
Monitor* DartDevIsolate::DartDevRunner::monitor_ = new Monitor();
DartDevIsolate::DartDev_Result DartDevIsolate::DartDevRunner::result_ =
DartDevIsolate::DartDev_Result_Unknown;
@@ -204,6 +205,86 @@ void DartDevIsolate::DartDevRunner::DartDevResultCallback(
}
break;
}
case DartDevIsolate::DartDev_Result_RunExec: {
result_ = DartDevIsolate::DartDev_Result_RunExec;
ASSERT(GetArrayItem(message, 1)->type == Dart_CObject_kString);
auto item2 = GetArrayItem(message, 2);
ASSERT(item2->type == Dart_CObject_kString ||
item2->type == Dart_CObject_kNull);
auto item3 = GetArrayItem(message, 3);
ASSERT(item3->type == Dart_CObject_kBool);
const bool mark_main_isolate_as_system_isolate = item3->value.as_bool;
if (mark_main_isolate_as_system_isolate) {
Options::set_mark_main_isolate_as_system_isolate(true);
}
if (*script_ != nullptr) {
free(*script_);
}
if (*package_config_override_ != nullptr) {
free(*package_config_override_);
*package_config_override_ = nullptr;
}
*script_ = Utils::StrDup(GetArrayItem(message, 1)->value.as_string);
if (item2->type == Dart_CObject_kString) {
*package_config_override_ = Utils::StrDup(item2->value.as_string);
}
intptr_t num_vm_options = 0;
const char** vm_options = nullptr;
ASSERT(GetArrayItem(message, 4)->type == Dart_CObject_kArray);
Dart_CObject* args = GetArrayItem(message, 4);
intptr_t argc = args->value.as_array.length;
Dart_CObject** dart_args = args->value.as_array.values;
if (vm_options_ != nullptr) {
num_vm_options = vm_options_->count();
vm_options = vm_options_->arguments();
}
auto deleter = [](char** args) {
for (intptr_t i = 0; i < argc_; ++i) {
free(args[i]);
}
delete[] args;
};
// Total count of arguments to be passed to the script being execed.
argc_ = argc + num_vm_options + 1;
// Array of arguments to be passed to the script being execed.
argv_ = std::unique_ptr<char*[], void (*)(char**)>(new char*[argc_ + 1],
deleter);
intptr_t idx = 0;
// Copy in name of the script to run (dartaotruntime).
argv_[0] = Utils::StrDup(GetArrayItem(message, 1)->value.as_string);
idx += 1;
// Copy in any vm options that need to be passed to the execed process.
for (intptr_t i = 0; i < num_vm_options; ++i) {
argv_[i + idx] = Utils::StrDup(vm_options[i]);
}
idx += num_vm_options;
// Copy in the dart options that need to be passed to the command.
for (intptr_t i = 0; i < argc; ++i) {
argv_[i + idx] = Utils::StrDup(dart_args[i]->value.as_string);
}
// Null terminate the argv array.
argv_[argc + idx] = nullptr;
// Exec the script to be run and pass the arguments.
char err_msg[256];
err_msg[0] = '\0';
int ret = Process::Exec(nullptr, *script_,
const_cast<const char**>(argv_.get()), argc_,
nullptr, err_msg, sizeof(err_msg));
if (ret != 0) {
ProcessError(err_msg, kErrorExitCode);
}
break;
}
case DartDevIsolate::DartDev_Result_Exit: {
ASSERT(GetArrayItem(message, 1)->type == Dart_CObject_kInt32);
int32_t dartdev_exit_code = GetArrayItem(message, 1)->value.as_int32;
@@ -314,7 +395,9 @@ DartDevIsolate::DartDev_Result DartDevIsolate::RunDartDev(
Dart_IsolateGroupCreateCallback create_isolate,
char** packages_file,
char** script,
CommandLineOptions* vm_options,
CommandLineOptions* dart_options) {
vm_options_ = vm_options;
runner_.Run(create_isolate, packages_file, script, dart_options);
return runner_.result();
}
+5 -2
View File
@@ -28,7 +28,8 @@ class DartDevIsolate {
typedef enum {
DartDev_Result_Unknown = -1,
DartDev_Result_Run = 1,
DartDev_Result_Exit = 2,
DartDev_Result_RunExec = 2,
DartDev_Result_Exit = 3,
} DartDev_Result;
// Returns true if there does not exist a file at |script_uri| or the URI is
@@ -58,6 +59,7 @@ class DartDevIsolate {
Dart_IsolateGroupCreateCallback create_isolate,
char** packages_file,
char** script,
CommandLineOptions* vm_options,
CommandLineOptions* dart_options);
protected:
@@ -83,11 +85,11 @@ class DartDevIsolate {
static char** package_config_override_;
static std::unique_ptr<char*[], void (*)(char**)> argv_;
static intptr_t argc_;
static Monitor* monitor_;
Dart_IsolateGroupCreateCallback create_isolate_;
CommandLineOptions* dart_options_;
const char* packages_file_;
static Monitor* monitor_;
DISALLOW_ALLOCATION();
};
@@ -98,6 +100,7 @@ class DartDevIsolate {
static DartDevRunner runner_;
static bool should_run_dart_dev_;
static bool print_usage_error_;
static CommandLineOptions* vm_options_;
DISALLOW_ALLOCATION();
DISALLOW_IMPLICIT_CONSTRUCTORS(DartDevIsolate);
+1 -1
View File
@@ -1433,7 +1433,7 @@ void main(int argc, char** argv) {
Options::gen_snapshot_kind() == SnapshotKind::kNone) {
DartDevIsolate::DartDev_Result dartdev_result = DartDevIsolate::RunDartDev(
CreateIsolateGroupAndSetup, &package_config_override, &script_name,
&dart_options);
&vm_options, &dart_options);
ASSERT(dartdev_result != DartDevIsolate::DartDev_Result_Unknown);
ran_dart_dev = true;
should_run_user_program =
+2 -2
View File
@@ -114,9 +114,9 @@ void FUNCTION_NAME(Process_Start)(Dart_NativeArguments args) {
const char* path = DartUtils::GetStringValue(path_handle);
Dart_Handle arguments = Dart_GetNativeArgument(args, 3);
intptr_t args_length = 0;
char** string_args =
const char** string_args = const_cast<const char**>(
ExtractCStringList(arguments, status_handle,
"Arguments must be builtin strings", &args_length);
"Arguments must be builtin strings", &args_length));
if (string_args == nullptr) {
Dart_SetBooleanReturnValue(args, false);
return;
+18 -1
View File
@@ -94,7 +94,7 @@ class Process {
// process exit streams.
static int Start(Namespace* namespc,
const char* path,
char* arguments[],
const char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
@@ -114,6 +114,23 @@ class Process {
intptr_t exit_handler,
ProcessResult* result);
// Exec process.
// On systems that support 'exec' it will use it to replace
// the current process image with the image corresponding to 'path'
// On systems that do not support it (Windows) it will start in a
// child process in the same group as the parent so that when the parent
// is killed the child also dies.
// Returns 0 if the process could be execed successfully
// Returns -1 if the exec could not be done successfully and 'errmsg'
// points to the error message
static int Exec(Namespace* namespc,
const char* path,
const char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* errmsg,
intptr_t errmsg_len);
// Kill a process with a given pid.
static bool Kill(intptr_t id, int signal);
+18 -4
View File
@@ -511,7 +511,7 @@ class ProcessStarter {
public:
ProcessStarter(Namespace* namespc,
const char* path,
char* arguments[],
const char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
@@ -540,7 +540,7 @@ class ProcessStarter {
read_err_ = -1;
write_out_ = -1;
program_arguments_ = reinterpret_cast<char**>(Dart_ScopeAllocate(
program_arguments_ = reinterpret_cast<const char**>(Dart_ScopeAllocate(
(arguments_length + 2) * sizeof(*program_arguments_)));
program_arguments_[0] = const_cast<char*>(path_);
for (int i = 0; i < arguments_length; i++) {
@@ -795,7 +795,7 @@ class ProcessStarter {
int read_err_; // Pipe for stderr to child process.
int write_out_; // Pipe for stdin to child process.
char** program_arguments_;
const char** program_arguments_;
char** program_environment_;
Namespace* namespc_;
@@ -815,7 +815,7 @@ class ProcessStarter {
int Process::Start(Namespace* namespc,
const char* path,
char* arguments[],
const char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
@@ -838,6 +838,20 @@ int Process::Start(Namespace* namespc,
return starter.Start();
}
// The command line dart utility does not run on Fuchsia, this functionality
// is not supported on that platform.
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) {
snprintf(errmsg, errmsg_len,
"Process::Exec is not supported on this platform");
return -1;
}
intptr_t Process::SetSignalHandler(intptr_t signal) {
errno = ENOSYS;
return -1;
+66 -29
View File
@@ -246,11 +246,47 @@ int ExitCodeHandler::process_count_ = 0;
bool ExitCodeHandler::terminate_done_ = false;
Monitor* ExitCodeHandler::monitor_ = nullptr;
// Tries to find path relative to the current namespace unless it should be
// searched in the PATH environment variable.
// The path that should be passed to exec is returned in realpath.
// Returns true on success, and false if there was an error that should
// be reported to the parent.
static bool PathInNamespace(char* realpath,
intptr_t realpath_size,
Namespace* namespc,
const char* path) {
// Perform a PATH search if there's no slash in the path.
if (Namespace::IsDefault(namespc) || strchr(path, '/') == nullptr) {
// TODO(zra): If there is a non-default namespace, the entries in PATH
// should be treated as relative to the namespace.
strncpy(realpath, path, realpath_size);
realpath[realpath_size - 1] = '\0';
return true;
}
NamespaceScope ns(namespc, path);
const int fd =
TEMP_FAILURE_RETRY(openat64(ns.fd(), ns.path(), O_RDONLY | O_CLOEXEC));
if (fd == -1) {
return false;
}
char procpath[PATH_MAX];
snprintf(procpath, PATH_MAX, "/proc/self/fd/%d", fd);
const intptr_t length =
TEMP_FAILURE_RETRY(readlink(procpath, realpath, realpath_size));
if (length < 0) {
FDUtils::SaveErrorAndClose(fd);
return false;
}
realpath[length] = '\0';
FDUtils::SaveErrorAndClose(fd);
return true;
}
class ProcessStarter {
public:
ProcessStarter(Namespace* namespc,
const char* path,
char* arguments[],
const char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
@@ -281,7 +317,7 @@ class ProcessStarter {
exec_control_[0] = -1;
exec_control_[1] = -1;
program_arguments_ = reinterpret_cast<char**>(Dart_ScopeAllocate(
program_arguments_ = reinterpret_cast<const char**>(Dart_ScopeAllocate(
(arguments_length + 2) * sizeof(*program_arguments_)));
program_arguments_[0] = const_cast<char*>(path_);
for (int i = 0; i < arguments_length; i++) {
@@ -447,31 +483,7 @@ class ProcessStarter {
// Returns true on success, and false if there was an error that should
// be reported to the parent.
bool FindPathInNamespace(char* realpath, intptr_t realpath_size) {
// Perform a PATH search if there's no slash in the path.
if (Namespace::IsDefault(namespc_) || strchr(path_, '/') == nullptr) {
// TODO(zra): If there is a non-default namespace, the entries in PATH
// should be treated as relative to the namespace.
strncpy(realpath, path_, realpath_size);
realpath[realpath_size - 1] = '\0';
return true;
}
NamespaceScope ns(namespc_, path_);
const int fd =
TEMP_FAILURE_RETRY(openat64(ns.fd(), ns.path(), O_RDONLY | O_CLOEXEC));
if (fd == -1) {
return false;
}
char procpath[PATH_MAX];
snprintf(procpath, PATH_MAX, "/proc/self/fd/%d", fd);
const intptr_t length =
TEMP_FAILURE_RETRY(readlink(procpath, realpath, realpath_size));
if (length < 0) {
FDUtils::SaveErrorAndClose(fd);
return false;
}
realpath[length] = '\0';
FDUtils::SaveErrorAndClose(fd);
return true;
return PathInNamespace(realpath, realpath_size, namespc_, path_);
}
void ExecProcess() {
@@ -772,7 +784,7 @@ class ProcessStarter {
int write_out_[2]; // Pipe for stdin to child process.
int exec_control_[2]; // Pipe to get the result from exec.
char** program_arguments_;
const char** program_arguments_;
char** program_environment_;
Namespace* namespc_;
@@ -792,7 +804,7 @@ class ProcessStarter {
int Process::Start(Namespace* namespc,
const char* path,
char* arguments[],
const char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
@@ -914,6 +926,31 @@ bool Process::Wait(intptr_t pid,
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) {
if (working_directory != nullptr &&
!Directory::SetCurrent(namespc, working_directory)) {
Utils::StrError(errno, errmsg, errmsg_len);
return -1;
}
char realpath[PATH_MAX];
if (!PathInNamespace(realpath, PATH_MAX, namespc, path)) {
Utils::StrError(errno, errmsg, errmsg_len);
return -1;
}
// TODO(dart:io) Test for the existence of execveat, and use it instead.
execvp(const_cast<const char*>(realpath),
const_cast<char* const*>(arguments));
Utils::StrError(errno, errmsg, errmsg_len);
return -1;
}
bool Process::Kill(intptr_t id, int signal) {
return (TEMP_FAILURE_RETRY(kill(id, signal)) != -1);
}
+22 -4
View File
@@ -249,7 +249,7 @@ Monitor* ExitCodeHandler::monitor_ = nullptr;
class ProcessStarter {
public:
ProcessStarter(const char* path,
char* arguments[],
const char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
@@ -279,7 +279,7 @@ class ProcessStarter {
exec_control_[0] = -1;
exec_control_[1] = -1;
program_arguments_ = reinterpret_cast<char**>(Dart_ScopeAllocate(
program_arguments_ = reinterpret_cast<const char**>(Dart_ScopeAllocate(
(arguments_length + 2) * sizeof(*program_arguments_)));
program_arguments_[0] = const_cast<char*>(path_);
for (int i = 0; i < arguments_length; i++) {
@@ -740,7 +740,7 @@ class ProcessStarter {
int write_out_[2]; // Pipe for stdin to child process.
int exec_control_[2]; // Pipe to get the result from exec.
char** program_arguments_;
const char** program_arguments_;
char** program_environment_;
const char* path_;
@@ -760,7 +760,7 @@ class ProcessStarter {
int Process::Start(Namespace* namespc,
const char* path,
char* arguments[],
const char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
@@ -898,6 +898,24 @@ bool Process::Wait(intptr_t pid,
#endif // defined(DART_HOST_OS_IOS)
}
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) {
if (working_directory != nullptr &&
TEMP_FAILURE_RETRY(chdir(working_directory)) == -1) {
Utils::StrError(errno, errmsg, errmsg_len);
return -1;
}
execvp(const_cast<const char*>(path), const_cast<char* const*>(arguments));
Utils::StrError(errno, errmsg, errmsg_len);
return -1;
}
static int SignalMap(intptr_t id) {
switch (static_cast<ProcessSignals>(id)) {
case kSighup:
+128 -4
View File
@@ -20,6 +20,7 @@
#include "bin/utils.h"
#include "bin/utils_win.h"
#include "platform/syslog.h"
#include "platform/text_buffer.h"
namespace dart {
namespace bin {
@@ -344,7 +345,7 @@ static int GenerateNames(wchar_t pipe_names[Count][kMaxPipeNameSize]) {
class ProcessStarter {
public:
ProcessStarter(const char* path,
char* arguments[],
const char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
@@ -373,11 +374,12 @@ class ProcessStarter {
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.
const wchar_t* system_path = StringUtilsWin::Utf8ToWide(path_);
wchar_t** system_arguments;
system_arguments = reinterpret_cast<wchar_t**>(
const wchar_t** system_arguments;
system_arguments = reinterpret_cast<const wchar_t**>(
Dart_ScopeAllocate(arguments_length * sizeof(*system_arguments)));
for (int i = 0; i < arguments_length; i++) {
system_arguments[i] = StringUtilsWin::Utf8ToWide(arguments[i]);
@@ -562,7 +564,42 @@ class ProcessStarter {
*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;
}
int StartForExec() {
// Setup info
STARTUPINFOEXW startup_info;
ZeroMemory(&startup_info, sizeof(startup_info));
startup_info.StartupInfo.cb = sizeof(startup_info);
ASSERT(mode_ == kInheritStdio);
ASSERT(Process::ModeIsAttached(mode_));
ASSERT(!Process::ModeHasStdio(mode_));
PROCESS_INFORMATION process_info;
ZeroMemory(&process_info, sizeof(process_info));
// Create process.
DWORD creation_flags =
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT;
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 SetOsErrorMessage(os_error_message_);
}
child_process_handle_ = process_info.hProcess;
CloseHandle(process_info.hThread);
// Return process id.
@@ -627,6 +664,7 @@ class ProcessStarter {
HANDLE stdout_handles_[2];
HANDLE stderr_handles_[2];
HANDLE exit_handles_[2];
HANDLE child_process_handle_;
const wchar_t* system_working_directory_;
wchar_t* command_line_;
@@ -651,7 +689,7 @@ class ProcessStarter {
int Process::Start(Namespace* namespc,
const char* path,
char* arguments[],
const char* arguments[],
intptr_t arguments_length,
const char* working_directory,
char* environment[],
@@ -880,6 +918,92 @@ bool Process::Wait(intptr_t pid,
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);
f.Printf("Process::Exec - CreateJobObject failed %d\n", GetLastError());
return -1;
}
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info;
DWORD qresult;
if (!QueryInformationJobObject(hjob, JobObjectExtendedLimitInformation, &info,
sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION),
&qresult)) {
BufferFormatter f(errmsg, errmsg_len);
f.Printf("Process::Exec - QueryInformationJobObject failed %d\n",
GetLastError());
return -1;
}
info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (!SetInformationJobObject(hjob, JobObjectExtendedLimitInformation, &info,
sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION))) {
BufferFormatter f(errmsg, errmsg_len);
f.Printf("Process::Exec - SetInformationJobObject failed %d\n",
GetLastError());
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);
f.Printf("Process::Exec - AssignProcessToJobObject failed %d\n",
GetLastError());
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;
char* os_error_message = nullptr; // Scope allocated by Process::Start.
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();
if (result != 0) {
BufferFormatter f(errmsg, errmsg_len);
f.Printf("Process::Exec - %s\n", os_error_message);
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);
DWORD wait_result = WaitForSingleObject(child_process, INFINITE);
if (wait_result != WAIT_OBJECT_0) {
BufferFormatter f(errmsg, errmsg_len);
f.Printf("Process::Exec - WaitForSingleObject failed %d\n", GetLastError());
CloseHandle(child_process);
return -1;
}
int retval;
if (!GetExitCodeProcess(child_process, reinterpret_cast<DWORD*>(&retval))) {
BufferFormatter f(errmsg, errmsg_len);
f.Printf("Process::Exec - GetExitCodeProcess failed %d\n", GetLastError());
CloseHandle(child_process);
return -1;
}
CloseHandle(child_process);
// We exit the process here to simulate the same behaviour as exec on systems
// that support it.
ExitProcess(retval);
return 0;
}
bool Process::Kill(intptr_t id, int signal) {
USE(signal); // signal is not used on Windows.
HANDLE process_handle;