diff --git a/pkg/dartdev/lib/src/commands/compile.dart b/pkg/dartdev/lib/src/commands/compile.dart index 54ad4543dcf..2ec72764c14 100644 --- a/pkg/dartdev/lib/src/commands/compile.dart +++ b/pkg/dartdev/lib/src/commands/compile.dart @@ -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} '; diff --git a/pkg/dartdev/lib/src/vm_interop_handler.dart b/pkg/dartdev/lib/src/vm_interop_handler.dart index 37eed1dc3b4..79f705e5af0 100644 --- a/pkg/dartdev/lib/src/vm_interop_handler.dart +++ b/pkg/dartdev/lib/src/vm_interop_handler.dart @@ -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 args, { @@ -27,16 +32,33 @@ abstract class VmInteropHandler { // // See https://github.com/dart-lang/sdk/issues/53576 bool markMainIsolateAsSystemIsolate = false, + bool useExecProcess = false, }) { + List 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 = [ - _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; } diff --git a/runtime/bin/dartdev_isolate.cc b/runtime/bin/dartdev_isolate.cc index c7aab45c115..329f6046998 100644 --- a/runtime/bin/dartdev_isolate.cc +++ b/runtime/bin/dartdev_isolate.cc @@ -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(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(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(); } diff --git a/runtime/bin/dartdev_isolate.h b/runtime/bin/dartdev_isolate.h index 79566c658f3..dc0397f7c49 100644 --- a/runtime/bin/dartdev_isolate.h +++ b/runtime/bin/dartdev_isolate.h @@ -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 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); diff --git a/runtime/bin/main_impl.cc b/runtime/bin/main_impl.cc index f0075f10904..40f5db8087d 100644 --- a/runtime/bin/main_impl.cc +++ b/runtime/bin/main_impl.cc @@ -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 = diff --git a/runtime/bin/process.cc b/runtime/bin/process.cc index f990e80e3ac..dfb0b77e403 100644 --- a/runtime/bin/process.cc +++ b/runtime/bin/process.cc @@ -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( 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; diff --git a/runtime/bin/process.h b/runtime/bin/process.h index 9bd547c802b..2aa40cdcb85 100644 --- a/runtime/bin/process.h +++ b/runtime/bin/process.h @@ -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); diff --git a/runtime/bin/process_fuchsia.cc b/runtime/bin/process_fuchsia.cc index eed2af0494d..4c6a1688ea1 100644 --- a/runtime/bin/process_fuchsia.cc +++ b/runtime/bin/process_fuchsia.cc @@ -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(Dart_ScopeAllocate( + program_arguments_ = reinterpret_cast(Dart_ScopeAllocate( (arguments_length + 2) * sizeof(*program_arguments_))); program_arguments_[0] = const_cast(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; diff --git a/runtime/bin/process_linux.cc b/runtime/bin/process_linux.cc index 64e74fcfab9..da95e5a1ecd 100644 --- a/runtime/bin/process_linux.cc +++ b/runtime/bin/process_linux.cc @@ -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(Dart_ScopeAllocate( + program_arguments_ = reinterpret_cast(Dart_ScopeAllocate( (arguments_length + 2) * sizeof(*program_arguments_))); program_arguments_[0] = const_cast(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(realpath), + const_cast(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); } diff --git a/runtime/bin/process_macos.cc b/runtime/bin/process_macos.cc index d7eb45e4ae0..94568e3e176 100644 --- a/runtime/bin/process_macos.cc +++ b/runtime/bin/process_macos.cc @@ -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(Dart_ScopeAllocate( + program_arguments_ = reinterpret_cast(Dart_ScopeAllocate( (arguments_length + 2) * sizeof(*program_arguments_))); program_arguments_[0] = const_cast(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(path), const_cast(arguments)); + Utils::StrError(errno, errmsg, errmsg_len); + return -1; +} + static int SignalMap(intptr_t id) { switch (static_cast(id)) { case kSighup: diff --git a/runtime/bin/process_win.cc b/runtime/bin/process_win.cc index e9a353b36c3..b9949c9c52a 100644 --- a/runtime/bin/process_win.cc +++ b/runtime/bin/process_win.cc @@ -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( + const wchar_t** system_arguments; + system_arguments = reinterpret_cast( 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(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(&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(&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;