diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 525f4cab0..6a5e06bc3 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -45,6 +45,7 @@ #include "servers/rendering/rendering_server_raster.h" #include "servers/rendering/rendering_server_wrap_mt.h" #include "windows_terminal_logger.h" +#include "sub_process_windows.h" #include #include @@ -197,6 +198,8 @@ void OS_Windows::initialize_core() { DirAccess::make_default(DirAccess::ACCESS_USERDATA); DirAccess::make_default(DirAccess::ACCESS_FILESYSTEM); + SubProcessWindows::make_default(); + NetSocketPosix::make_default(); // We need to know how often the clock is updated diff --git a/platform/windows/sub_process_windows.cpp b/platform/windows/sub_process_windows.cpp new file mode 100644 index 000000000..f15d9a54f --- /dev/null +++ b/platform/windows/sub_process_windows.cpp @@ -0,0 +1,330 @@ +/*************************************************************************/ +/* dir_access_unix.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "sub_process_windows.h" + +#include + +Error SubProcessWindows::start() { + if (_executable_path.empty()) { + return ERR_FILE_BAD_PATH; + } + + if (is_process_running()) { + return ERR_BUSY; + } + + String path = p_path.replace("/", "\\"); + + String cmdline = _quote_command_line_argument(path); + const List::Element *I = p_arguments.front(); + while (I) { + cmdline += " " + _quote_command_line_argument(I->get()); + I = I->next(); + } + + ZeroMemory(&_process_info.si, sizeof(_process_info.si)); + _process_info.si.cb = sizeof(_process_info.si); + ZeroMemory(&_process_info.pi, sizeof(_process_info.pi)); + LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&_process_info.si; + + Char16String modstr = cmdline.utf16(); // Windows wants to change this no idea why. + + bool inherit_handles = false; + + if (_read_output) { + // Create pipe for StdOut and StdErr. + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = true; + sa.lpSecurityDescriptor = NULL; + + ERR_FAIL_COND_V(!CreatePipe(&_pipe_handles[0], &_pipe_handles[1], &sa, 0), ERR_CANT_FORK); + ERR_FAIL_COND_V(!SetHandleInformation(_pipe_handles[0], HANDLE_FLAG_INHERIT, 0), ERR_CANT_FORK); // Read handle is for host process only and should not be inherited. + + _process_info.si.dwFlags |= STARTF_USESTDHANDLES; + _process_info.si.hStdOutput = _pipe_handles[1]; + if (_read_std_err) { + _process_info.si.hStdError = _pipe_handles[1]; + } + inherit_handles = true; + } + + DWORD creaton_flags = NORMAL_PRIORITY_CLASS; + if (_open_console) { + creaton_flags |= CREATE_NEW_CONSOLE; + } else { + creaton_flags |= CREATE_NO_WINDOW; + } + + int ret = CreateProcessW(nullptr, (LPWSTR)(modstr.ptrw()), nullptr, nullptr, inherit_handles, creaton_flags, nullptr, nullptr, si_w, &_process_info.pi); + if (!ret && _read_output) { + CloseHandle(_pipe_handles[0]); // Cleanup pipe handles. + CloseHandle(_pipe_handles[1]); + + _pipe_handles[0] = NULL; + _pipe_handles[1] = NULL; + } + + ERR_FAIL_COND_V(ret == 0, ERR_CANT_FORK); + + if (_blocking) { + if (_read_output) { + CloseHandle(_pipe_handles[1]); // Close pipe write handle (only child process is writing). + + int bytes_in_buffer = 0; + + const int CHUNK_SIZE = 4096; + DWORD read = 0; + for (;;) { // Read StdOut and StdErr from pipe. + _bytes.resize(bytes_in_buffer + CHUNK_SIZE); + const bool success = ReadFile(_pipe_handles[0], _bytes.ptr() + bytes_in_buffer, CHUNK_SIZE, &read, NULL); + if (!success || read == 0) { + break; + } + // Assume that all possible encodings are ASCII-compatible. + // Break at newline to allow receiving long output in portions. + int newline_index = -1; + for (int i = read - 1; i >= 0; i--) { + if (_bytes[bytes_in_buffer + i] == '\n') { + newline_index = i; + break; + } + } + + if (newline_index == -1) { + bytes_in_buffer += read; + continue; + } + + const int bytes_to_convert = bytes_in_buffer + (newline_index + 1); + _append_to_pipe(_bytes.ptr(), bytes_to_convert); + + bytes_in_buffer = read - (newline_index + 1); + memmove(_bytes.ptr(), _bytes.ptr() + bytes_to_convert, bytes_in_buffer); + } + + if (bytes_in_buffer > 0) { + _append_to_pipe(_bytes.ptr(), bytes_in_buffer); + } + + CloseHandle(_pipe_handles[0]); // Close pipe read handle. + } + + WaitForSingleObject(_process_info.pi.hProcess, INFINITE); + + if (r_exitcode) { + DWORD ret2; + GetExitCodeProcess(_process_info.pi.hProcess, &ret2); + _exitcode = ret2; + } + + CloseHandle(_process_info.pi.hProcess); + CloseHandle(_process_info.pi.hThread); + } else { + if (_read_output) { + //eventually we will need to keep this + CloseHandle(_pipe_handles[1]); // Close pipe write handle (only child process is writing). + _pipe_handles[1] = NULL; + } + + _process_started = true; + + ProcessID pid = _process_info.pi.dwProcessId; + _process_id = pid; + } + + return OK; +} + +Error SubProcessWindows::stop() { + if (!_process_started) { + return OK; + } + + if (_pipe_handles[0]) { + CloseHandle(_pipe_handles[0]); // Cleanup pipe handles. + _pipe_handles[0] = NULL; + } + + if (_pipe_handles[1]) { + CloseHandle(_pipe_handles[1]); + _pipe_handles[1] = NULL; + } + + const int ret = TerminateProcess(_process_info.pi.hProcess, 0); + + CloseHandle(_process_info.pi.hProcess); + CloseHandle(_process_info.pi.hThread); + + ZeroMemory(&_process_info.si, sizeof(_process_info.si)); + _process_info.si.cb = sizeof(_process_info.si); + ZeroMemory(&_process_info.pi, sizeof(_process_info.pi)); + + _process_started = false; + + return ret != 0 ? OK : FAILED; +} + +Error SubProcessWindows::poll() { + if (!_process_started) { + return FAILED; + } + + if (!_pipe_handles[0]) { + return FAILED; + } + + _pipe.clear(); + + int bytes_in_buffer = 0; + + const int CHUNK_SIZE = 4096; + DWORD read = 0; + + _bytes.resize(bytes_in_buffer + CHUNK_SIZE); + const bool success = ReadFile(_pipe_handles[0], _bytes.ptr() + bytes_in_buffer, CHUNK_SIZE, &read, NULL); + + if (!success) { + stop(); + return ERR_FILE_EOF; + } + + if (read == 0) { + return OK; + } + + // Assume that all possible encodings are ASCII-compatible. + // Break at newline to allow receiving long output in portions. + int newline_index = -1; + for (int i = read - 1; i >= 0; i--) { + if (_bytes[bytes_in_buffer + i] == '\n') { + newline_index = i; + break; + } + } + + if (newline_index == -1) { + bytes_in_buffer += read; + return OK; + } + + const int bytes_to_convert = bytes_in_buffer + (newline_index + 1); + _append_to_pipe(_bytes.ptr(), bytes_to_convert); + + bytes_in_buffer = read - (newline_index + 1); + memmove(_bytes.ptr(), _bytes.ptr() + bytes_to_convert, bytes_in_buffer); + + if (bytes_in_buffer > 0) { + _append_to_pipe(_bytes.ptr(), bytes_in_buffer); + } + + return OK; +} + +Error SubProcessWindows::send_signal(const int p_signal) { + //Not Yet Impl + ERR_FAIL_V(ERR_BUG); +} + +Error SubProcessWindows::send_data(const String &p_data) { + //Not Yet Impl + ERR_FAIL_V(ERR_BUG); +} + +bool SubProcessWindows::is_process_running() const { + if (_process_id == 0) { + return false; + } + + if (!_process_started) { + return false; + } + + DWORD dw_exit_code = 0; + if (!GetExitCodeProcess(_process_info.pi.hProcess, &dw_exit_code)) { + return false; + } + + if (dw_exit_code != STILL_ACTIVE) { + return false; + } + + return true; +} + +String SubProcessWindows::_quote_command_line_argument(const String &p_text) const { + for (int i = 0; i < p_text.size(); i++) { + CharType c = p_text[i]; + if (c == ' ' || c == '&' || c == '(' || c == ')' || c == '[' || c == ']' || c == '{' || c == '}' || c == '^' || c == '=' || c == ';' || c == '!' || c == '\'' || c == '+' || c == ',' || c == '`' || c == '~') { + return "\"" + p_text + "\""; + } + } + return p_text; +} + +void SubProcessWindows::_append_to_pipe(char *p_bytes, int p_size) { + // Try to convert from default ANSI code page to Unicode. + LocalVector wchars; + int total_wchars = MultiByteToWideChar(CP_ACP, 0, p_bytes, p_size, nullptr, 0); + if (total_wchars > 0) { + wchars.resize(total_wchars); + if (MultiByteToWideChar(CP_ACP, 0, p_bytes, p_size, wchars.ptr(), total_wchars) == 0) { + wchars.clear(); + } + } + + if (_pipe_mutex) { + _pipe_mutex->lock(); + } + if (wchars.empty()) { + // Let's hope it's compatible with UTF-8. + _pipe += String::utf8(p_bytes, p_size); + } else { + _pipe += String(wchars.ptr(), total_wchars); + } + if (_pipe_mutex) { + _pipe_mutex->unlock(); + } +} + +SubProcessWindows::SubProcessWindows() : + SubProcess() { + _pipe_handles = { NULL, NULL }; + + _process_started = false; + + ZeroMemory(&_process_info.si, sizeof(_process_info.si)); + _process_info.si.cb = sizeof(_process_info.si); + ZeroMemory(&_process_info.pi, sizeof(_process_info.pi)); +} +SubProcessWindows::~SubProcessWindows() { + stop(); +} diff --git a/platform/windows/sub_process_windows.h b/platform/windows/sub_process_windows.h new file mode 100644 index 000000000..7bcf5d23b --- /dev/null +++ b/platform/windows/sub_process_windows.h @@ -0,0 +1,67 @@ +#ifndef SUB_PROCESS_WINDOWS_H +#define SUB_PROCESS_WINDOWS_H + +/*************************************************************************/ +/* dir_access_unix.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "core/os/sub_process.h" + +#define WIN32_LEAN_AND_MEAN +#include +#include + +class SubProcessWindows : public SubProcess { +public: + virtual Error start(); + virtual Error stop(); + virtual Error poll(); + virtual Error send_signal(const int p_signal); + virtual Error send_data(const String &p_data); + virtual bool is_process_running() const; + + SubProcessWindows(); + ~SubProcessWindows(); + +protected: + String _quote_command_line_argument(const String &p_text) const; + void _append_to_pipe(char *p_bytes, int p_size); + + struct ProcessInfo { + STARTUPINFO si; + PROCESS_INFORMATION pi; + }; + + bool _process_started; + HANDLE _pipe_handles[2]; + ProcessInfo _process_info; + LocalVector _bytes; +}; + +#endif