| // Copyright (c) 2013, 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 "bin/process.h" | |
| #include "bin/dartutils.h" | |
| #include "bin/io_buffer.h" | |
| #include "bin/namespace.h" | |
| #include "bin/platform.h" | |
| #include "bin/socket.h" | |
| #include "bin/utils.h" | |
| #include "platform/syslog.h" | |
| #include "include/dart_api.h" | |
| namespace dart { | |
| namespace bin { | |
| static constexpr int kProcessIdNativeField = 0; | |
| // Extract an array of C strings from a list of Dart strings. | |
| static char** ExtractCStringList(Dart_Handle strings, | |
| Dart_Handle status_handle, | |
| const char* error_msg, | |
| intptr_t* length) { | |
| static constexpr intptr_t kMaxArgumentListLength = 1024 * 1024; | |
| ASSERT(Dart_IsList(strings)); | |
| intptr_t len = 0; | |
| Dart_Handle result = Dart_ListLength(strings, &len); | |
| ThrowIfError(result); | |
| // Protect against user-defined list implementations that can have | |
| // arbitrary length. | |
| if ((len < 0) || (len > kMaxArgumentListLength)) { | |
| result = DartUtils::SetIntegerField(status_handle, "_errorCode", 0); | |
| ThrowIfError(result); | |
| result = DartUtils::SetStringField(status_handle, "_errorMessage", | |
| "Max argument list length exceeded"); | |
| ThrowIfError(result); | |
| return nullptr; | |
| } | |
| *length = len; | |
| char** string_args; | |
| string_args = | |
| reinterpret_cast<char**>(Dart_ScopeAllocate(len * sizeof(*string_args))); | |
| for (int i = 0; i < len; i++) { | |
| Dart_Handle arg = Dart_ListGetAt(strings, i); | |
| ThrowIfError(arg); | |
| if (!Dart_IsString(arg)) { | |
| result = DartUtils::SetIntegerField(status_handle, "_errorCode", 0); | |
| ThrowIfError(result); | |
| result = | |
| DartUtils::SetStringField(status_handle, "_errorMessage", error_msg); | |
| ThrowIfError(result); | |
| return nullptr; | |
| } | |
| string_args[i] = const_cast<char*>(DartUtils::GetStringValue(arg)); | |
| } | |
| return string_args; | |
| } | |
| bool Process::ModeIsAttached(ProcessStartMode mode) { | |
| return (mode == kNormal) || (mode == kInheritStdio); | |
| } | |
| bool Process::ModeHasStdio(ProcessStartMode mode) { | |
| return (mode == kNormal) || (mode == kDetachedWithStdio); | |
| } | |
| void Process::ClearAllSignalHandlers() { | |
| for (intptr_t i = 1; i <= kLastSignal; i++) { | |
| ClearSignalHandler(i, ILLEGAL_PORT); | |
| } | |
| } | |
| void FUNCTION_NAME(Process_Start)(Dart_NativeArguments args) { | |
| Dart_Handle process = Dart_GetNativeArgument(args, 0); | |
| intptr_t process_stdin; | |
| intptr_t process_stdout; | |
| intptr_t process_stderr; | |
| intptr_t exit_event; | |
| Namespace* namespc = Namespace::GetNamespace(args, 1); | |
| Dart_Handle status_handle = Dart_GetNativeArgument(args, 11); | |
| Dart_Handle path_handle = Dart_GetNativeArgument(args, 2); | |
| Dart_Handle result; | |
| #if DART_HOST_OS_IOS | |
| // Do the iOS check here because the return value of Process::Start is | |
| // interpreted as a error with 0 meaning success while `ProcessException` | |
| // (which will be constructed with `_errorCode`) interprets 0 to mean that | |
| // no OS error code was available. | |
| result = DartUtils::SetIntegerField(status_handle, "_errorCode", 0); | |
| ThrowIfError(result); | |
| result = DartUtils::SetStringField( | |
| status_handle, "_errorMessage", | |
| "Starting new processes is not supported on iOS"); | |
| ThrowIfError(result); | |
| Dart_SetBooleanReturnValue(args, false); | |
| return; | |
| #endif | |
| // The Dart code verifies that the path implements the String | |
| // interface. However, only builtin Strings are handled by | |
| // GetStringValue. | |
| if (!Dart_IsString(path_handle)) { | |
| result = DartUtils::SetIntegerField(status_handle, "_errorCode", 0); | |
| ThrowIfError(result); | |
| result = DartUtils::SetStringField(status_handle, "_errorMessage", | |
| "Path must be a builtin string"); | |
| ThrowIfError(result); | |
| Dart_SetBooleanReturnValue(args, false); | |
| return; | |
| } | |
| const char* path = DartUtils::GetStringValue(path_handle); | |
| Dart_Handle arguments = Dart_GetNativeArgument(args, 3); | |
| intptr_t args_length = 0; | |
| const char** string_args = const_cast<const char**>( | |
| ExtractCStringList(arguments, status_handle, | |
| "Arguments must be builtin strings", &args_length)); | |
| if (string_args == nullptr) { | |
| Dart_SetBooleanReturnValue(args, false); | |
| return; | |
| } | |
| Dart_Handle working_directory_handle = Dart_GetNativeArgument(args, 4); | |
| // Defaults to the current working directory. | |
| const char* working_directory = nullptr; | |
| if (Dart_IsString(working_directory_handle)) { | |
| working_directory = DartUtils::GetStringValue(working_directory_handle); | |
| } else if (!Dart_IsNull(working_directory_handle)) { | |
| result = DartUtils::SetIntegerField(status_handle, "_errorCode", 0); | |
| ThrowIfError(result); | |
| result = | |
| DartUtils::SetStringField(status_handle, "_errorMessage", | |
| "WorkingDirectory must be a builtin string"); | |
| ThrowIfError(result); | |
| Dart_SetBooleanReturnValue(args, false); | |
| return; | |
| } | |
| Dart_Handle environment = Dart_GetNativeArgument(args, 5); | |
| intptr_t environment_length = 0; | |
| char** string_environment = nullptr; | |
| if (!Dart_IsNull(environment)) { | |
| string_environment = ExtractCStringList( | |
| environment, status_handle, | |
| "Environment values must be builtin strings", &environment_length); | |
| if (string_environment == nullptr) { | |
| Dart_SetBooleanReturnValue(args, false); | |
| return; | |
| } | |
| } | |
| int64_t mode = | |
| DartUtils::GetInt64ValueCheckRange(Dart_GetNativeArgument(args, 6), 0, 3); | |
| Dart_Handle stdin_handle = Dart_GetNativeArgument(args, 7); | |
| Dart_Handle stdout_handle = Dart_GetNativeArgument(args, 8); | |
| Dart_Handle stderr_handle = Dart_GetNativeArgument(args, 9); | |
| Dart_Handle exit_handle = Dart_GetNativeArgument(args, 10); | |
| intptr_t pid = -1; | |
| char* os_error_message = nullptr; | |
| int error_code = Process::Start( | |
| namespc, path, string_args, args_length, working_directory, | |
| string_environment, environment_length, | |
| static_cast<ProcessStartMode>(mode), &process_stdout, &process_stdin, | |
| &process_stderr, &pid, &exit_event, &os_error_message); | |
| if (error_code == 0) { | |
| if (Process::ModeHasStdio(static_cast<ProcessStartMode>(mode))) { | |
| Socket::SetSocketIdNativeField(stdin_handle, process_stdin, | |
| Socket::kFinalizerNormal); | |
| Socket::SetSocketIdNativeField(stdout_handle, process_stdout, | |
| Socket::kFinalizerNormal); | |
| Socket::SetSocketIdNativeField(stderr_handle, process_stderr, | |
| Socket::kFinalizerNormal); | |
| } | |
| if (Process::ModeIsAttached(static_cast<ProcessStartMode>(mode))) { | |
| Socket::SetSocketIdNativeField(exit_handle, exit_event, | |
| Socket::kFinalizerNormal); | |
| } | |
| Process::SetProcessIdNativeField(process, pid); | |
| } else { | |
| result = | |
| DartUtils::SetIntegerField(status_handle, "_errorCode", error_code); | |
| ThrowIfError(result); | |
| const char* error_message = (os_error_message != nullptr) | |
| ? os_error_message | |
| : "Failed to get error message"; | |
| Dart_Handle val = DartUtils::NewString(error_message); | |
| if (Dart_IsError(val)) { | |
| // Try to clean the message from non-ASCII characters. | |
| const intptr_t len = strlen(error_message); | |
| char* ascii_message = | |
| reinterpret_cast<char*>(Dart_ScopeAllocate(len + 1)); | |
| for (intptr_t i = 0; i < len; i++) { | |
| if (static_cast<uint8_t>(error_message[i]) < 0x80) { | |
| ascii_message[i] = error_message[i]; | |
| } else { | |
| ascii_message[i] = '?'; | |
| } | |
| } | |
| ascii_message[len] = '\0'; | |
| val = DartUtils::NewStringFormatted( | |
| "Failed to start %s. OS returned an error (code %d) which can't be " | |
| "fully converted to Dart string (%s): %s", | |
| path, error_code, Dart_GetError(val), ascii_message); | |
| } | |
| result = Dart_SetField(status_handle, DartUtils::NewString("_errorMessage"), | |
| val); | |
| ThrowIfError(result); | |
| } | |
| Dart_SetBooleanReturnValue(args, error_code == 0); | |
| } | |
| void FUNCTION_NAME(Process_Wait)(Dart_NativeArguments args) { | |
| Dart_Handle process = Dart_GetNativeArgument(args, 0); | |
| Socket* process_stdin = | |
| Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 1)); | |
| Socket* process_stdout = | |
| Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 2)); | |
| Socket* process_stderr = | |
| Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 3)); | |
| Socket* exit_event = | |
| Socket::GetSocketIdNativeField(Dart_GetNativeArgument(args, 4)); | |
| ProcessResult result; | |
| intptr_t pid; | |
| Process::GetProcessIdNativeField(process, &pid); | |
| bool success = Process::Wait(pid, process_stdin->fd(), process_stdout->fd(), | |
| process_stderr->fd(), exit_event->fd(), &result); | |
| // Process::Wait() closes the file handles, so blow away the fds in the | |
| // Sockets so that they don't get picked up by the finalizer on _NativeSocket. | |
| process_stdin->CloseFd(); | |
| process_stdout->CloseFd(); | |
| process_stderr->CloseFd(); | |
| exit_event->CloseFd(); | |
| if (success) { | |
| Dart_Handle out = result.stdout_data(); | |
| ThrowIfError(out); | |
| Dart_Handle err = result.stderr_data(); | |
| ThrowIfError(err); | |
| Dart_Handle list = Dart_NewList(4); | |
| Dart_ListSetAt(list, 0, Dart_NewInteger(pid)); | |
| Dart_ListSetAt(list, 1, Dart_NewInteger(result.exit_code())); | |
| Dart_ListSetAt(list, 2, out); | |
| Dart_ListSetAt(list, 3, err); | |
| Dart_SetReturnValue(args, list); | |
| } else { | |
| Dart_Handle error = DartUtils::NewDartOSError(); | |
| Process::Kill(pid, 9); | |
| Dart_ThrowException(error); | |
| } | |
| } | |
| void FUNCTION_NAME(Process_KillPid)(Dart_NativeArguments args) { | |
| intptr_t pid = DartUtils::GetIntptrValue(Dart_GetNativeArgument(args, 0)); | |
| intptr_t signal = DartUtils::GetIntptrValue(Dart_GetNativeArgument(args, 1)); | |
| bool success = Process::Kill(pid, signal); | |
| Dart_SetBooleanReturnValue(args, success); | |
| } | |
| void FUNCTION_NAME(Process_Exit)(Dart_NativeArguments args) { | |
| int64_t status = 0; | |
| // Ignore result if passing invalid argument and just exit 0. | |
| DartUtils::GetInt64Value(Dart_GetNativeArgument(args, 0), &status); | |
| Process::RunExitHook(status); | |
| Dart_ExitIsolate(); | |
| // We're not doing a full VM shutdown with Dart_Cleanup, which might block, | |
| // and other VM threads may be accessing state with global destructors, so | |
| // we skip global destructors by using _exit instead of exit. | |
| Platform::_Exit(static_cast<int>(status)); | |
| } | |
| void FUNCTION_NAME(Process_SetExitCode)(Dart_NativeArguments args) { | |
| int64_t status = 0; | |
| // Ignore result if passing invalid argument and just set exit code to 0. | |
| DartUtils::GetInt64Value(Dart_GetNativeArgument(args, 0), &status); | |
| Process::SetGlobalExitCode(status); | |
| } | |
| void FUNCTION_NAME(Process_GetExitCode)(Dart_NativeArguments args) { | |
| Dart_SetIntegerReturnValue(args, Process::GlobalExitCode()); | |
| } | |
| void FUNCTION_NAME(Process_Sleep)(Dart_NativeArguments args) { | |
| ScopedBlockingCall blocker; | |
| int64_t milliseconds = 0; | |
| // Ignore result if passing invalid argument and just set exit code to 0. | |
| DartUtils::GetInt64Value(Dart_GetNativeArgument(args, 0), &milliseconds); | |
| TimerUtils::Sleep(milliseconds); | |
| } | |
| void FUNCTION_NAME(Process_Pid)(Dart_NativeArguments args) { | |
| // Ignore result if passing invalid argument and just set exit code to 0. | |
| intptr_t pid = -1; | |
| Dart_Handle process = Dart_GetNativeArgument(args, 0); | |
| if (Dart_IsNull(process)) { | |
| pid = Process::CurrentProcessId(); | |
| } else { | |
| Process::GetProcessIdNativeField(process, &pid); | |
| } | |
| Dart_SetIntegerReturnValue(args, pid); | |
| } | |
| void FUNCTION_NAME(Process_SetSignalHandler)(Dart_NativeArguments args) { | |
| intptr_t signal = DartUtils::GetIntptrValue(Dart_GetNativeArgument(args, 0)); | |
| intptr_t id = Process::SetSignalHandler(signal); | |
| if (id == -1) { | |
| Dart_SetReturnValue(args, DartUtils::NewDartOSError()); | |
| } else { | |
| Dart_SetIntegerReturnValue(args, id); | |
| } | |
| } | |
| void FUNCTION_NAME(Process_ClearSignalHandler)(Dart_NativeArguments args) { | |
| intptr_t signal = DartUtils::GetIntptrValue(Dart_GetNativeArgument(args, 0)); | |
| Process::ClearSignalHandler(signal, Dart_GetMainPortId()); | |
| } | |
| Dart_Handle Process::GetProcessIdNativeField(Dart_Handle process, | |
| intptr_t* pid) { | |
| return Dart_GetNativeInstanceField(process, kProcessIdNativeField, pid); | |
| } | |
| Dart_Handle Process::SetProcessIdNativeField(Dart_Handle process, | |
| intptr_t pid) { | |
| return Dart_SetNativeInstanceField(process, kProcessIdNativeField, pid); | |
| } | |
| void FUNCTION_NAME(SystemEncodingToString)(Dart_NativeArguments args) { | |
| Dart_Handle bytes = Dart_GetNativeArgument(args, 0); | |
| intptr_t bytes_length = 0; | |
| Dart_Handle result = Dart_ListLength(bytes, &bytes_length); | |
| ThrowIfError(result); | |
| uint8_t* buffer = Dart_ScopeAllocate(bytes_length + 1); | |
| result = Dart_ListGetAsBytes(bytes, 0, buffer, bytes_length); | |
| buffer[bytes_length] = '\0'; | |
| ThrowIfError(result); | |
| intptr_t len; | |
| char* str = StringUtils::ConsoleStringToUtf8(reinterpret_cast<char*>(buffer), | |
| bytes_length, &len); | |
| if (str == nullptr) { | |
| Dart_ThrowException(DartUtils::NewDartUnsupportedError( | |
| "SystemEncodingToString not supported on this operating system")); | |
| } | |
| result = Dart_NewStringFromUTF8(reinterpret_cast<const uint8_t*>(str), len); | |
| ThrowIfError(result); | |
| Dart_SetReturnValue(args, result); | |
| } | |
| void FUNCTION_NAME(StringToSystemEncoding)(Dart_NativeArguments args) { | |
| Dart_Handle str = Dart_GetNativeArgument(args, 0); | |
| char* utf8; | |
| intptr_t utf8_len; | |
| Dart_Handle result = | |
| Dart_StringToUTF8(str, reinterpret_cast<uint8_t**>(&utf8), &utf8_len); | |
| ThrowIfError(result); | |
| intptr_t system_len; | |
| const char* system_string = | |
| StringUtils::Utf8ToConsoleString(utf8, utf8_len, &system_len); | |
| if (system_string == nullptr) { | |
| Dart_ThrowException(DartUtils::NewDartUnsupportedError( | |
| "StringToSystemEncoding not supported on this operating system")); | |
| } | |
| uint8_t* buffer = nullptr; | |
| Dart_Handle external_array = IOBuffer::Allocate(system_len, &buffer); | |
| if (Dart_IsNull(external_array)) { | |
| Dart_SetReturnValue(args, DartUtils::NewDartOSError()); | |
| return; | |
| } | |
| if (!Dart_IsError(external_array)) { | |
| memmove(buffer, system_string, system_len); | |
| } | |
| Dart_SetReturnValue(args, external_array); | |
| } | |
| void FUNCTION_NAME(ProcessInfo_CurrentRSS)(Dart_NativeArguments args) { | |
| int64_t current_rss = Process::CurrentRSS(); | |
| if (current_rss < 0) { | |
| Dart_SetReturnValue(args, DartUtils::NewDartOSError()); | |
| return; | |
| } | |
| Dart_SetIntegerReturnValue(args, current_rss); | |
| } | |
| void FUNCTION_NAME(ProcessInfo_MaxRSS)(Dart_NativeArguments args) { | |
| int64_t max_rss = Process::MaxRSS(); | |
| if (max_rss < 0) { | |
| Dart_SetReturnValue(args, DartUtils::NewDartOSError()); | |
| return; | |
| } | |
| Dart_SetIntegerReturnValue(args, max_rss); | |
| } | |
| void Process::GetRSSInformation(int64_t* max_rss, int64_t* current_rss) { | |
| ASSERT(max_rss != nullptr); | |
| ASSERT(current_rss != nullptr); | |
| // Max RSS should be queried after current RSS to produce | |
| // consistent values as current RSS can grow beyond max RSS which | |
| // was queried before. | |
| *current_rss = Process::CurrentRSS(); | |
| *max_rss = Process::MaxRSS(); | |
| } | |
| } // namespace bin | |
| } // namespace dart |