Browse Source

Implement `OS.execute_with_pipe` method to run process with redirected stdio.
Implement `pipe://*` path handling for creation of named pipes.

bruvzg 1 year ago
parent
commit
082b420c0a

+ 15 - 6
core/core_bind.cpp

@@ -283,8 +283,8 @@ String OS::read_string_from_stdin() {
 
 
 int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r_output, bool p_read_stderr, bool p_open_console) {
 int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r_output, bool p_read_stderr, bool p_open_console) {
 	List<String> args;
 	List<String> args;
-	for (int i = 0; i < p_arguments.size(); i++) {
-		args.push_back(p_arguments[i]);
+	for (const String &arg : p_arguments) {
+		args.push_back(arg);
 	}
 	}
 	String pipe;
 	String pipe;
 	int exitcode = 0;
 	int exitcode = 0;
@@ -296,10 +296,18 @@ int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r
 	return exitcode;
 	return exitcode;
 }
 }
 
 
+Dictionary OS::execute_with_pipe(const String &p_path, const Vector<String> &p_arguments) {
+	List<String> args;
+	for (const String &arg : p_arguments) {
+		args.push_back(arg);
+	}
+	return ::OS::get_singleton()->execute_with_pipe(p_path, args);
+}
+
 int OS::create_instance(const Vector<String> &p_arguments) {
 int OS::create_instance(const Vector<String> &p_arguments) {
 	List<String> args;
 	List<String> args;
-	for (int i = 0; i < p_arguments.size(); i++) {
-		args.push_back(p_arguments[i]);
+	for (const String &arg : p_arguments) {
+		args.push_back(arg);
 	}
 	}
 	::OS::ProcessID pid = 0;
 	::OS::ProcessID pid = 0;
 	Error err = ::OS::get_singleton()->create_instance(args, &pid);
 	Error err = ::OS::get_singleton()->create_instance(args, &pid);
@@ -311,8 +319,8 @@ int OS::create_instance(const Vector<String> &p_arguments) {
 
 
 int OS::create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console) {
 int OS::create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console) {
 	List<String> args;
 	List<String> args;
-	for (int i = 0; i < p_arguments.size(); i++) {
-		args.push_back(p_arguments[i]);
+	for (const String &arg : p_arguments) {
+		args.push_back(arg);
 	}
 	}
 	::OS::ProcessID pid = 0;
 	::OS::ProcessID pid = 0;
 	Error err = ::OS::get_singleton()->create_process(p_path, args, &pid, p_open_console);
 	Error err = ::OS::get_singleton()->create_process(p_path, args, &pid, p_open_console);
@@ -587,6 +595,7 @@ void OS::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path);
 	ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path);
 	ClassDB::bind_method(D_METHOD("read_string_from_stdin"), &OS::read_string_from_stdin);
 	ClassDB::bind_method(D_METHOD("read_string_from_stdin"), &OS::read_string_from_stdin);
 	ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL(Array()), DEFVAL(false), DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL(Array()), DEFVAL(false), DEFVAL(false));
+	ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::execute_with_pipe);
 	ClassDB::bind_method(D_METHOD("create_process", "path", "arguments", "open_console"), &OS::create_process, DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("create_process", "path", "arguments", "open_console"), &OS::create_process, DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance);
 	ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance);
 	ClassDB::bind_method(D_METHOD("kill", "pid"), &OS::kill);
 	ClassDB::bind_method(D_METHOD("kill", "pid"), &OS::kill);

+ 1 - 0
core/core_bind.h

@@ -156,6 +156,7 @@ public:
 	String get_executable_path() const;
 	String get_executable_path() const;
 	String read_string_from_stdin();
 	String read_string_from_stdin();
 	int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false, bool p_open_console = false);
 	int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false, bool p_open_console = false);
+	Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments);
 	int create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console = false);
 	int create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console = false);
 	int create_instance(const Vector<String> &p_arguments);
 	int create_instance(const Vector<String> &p_arguments);
 	Error kill(int p_pid);
 	Error kill(int p_pid);

+ 6 - 1
core/io/file_access.cpp

@@ -47,6 +47,7 @@ thread_local Error FileAccess::last_file_open_error = OK;
 
 
 Ref<FileAccess> FileAccess::create(AccessType p_access) {
 Ref<FileAccess> FileAccess::create(AccessType p_access) {
 	ERR_FAIL_INDEX_V(p_access, ACCESS_MAX, nullptr);
 	ERR_FAIL_INDEX_V(p_access, ACCESS_MAX, nullptr);
+	ERR_FAIL_NULL_V(create_func[p_access], nullptr);
 
 
 	Ref<FileAccess> ret = create_func[p_access]();
 	Ref<FileAccess> ret = create_func[p_access]();
 	ret->_set_access_type(p_access);
 	ret->_set_access_type(p_access);
@@ -75,7 +76,8 @@ Ref<FileAccess> FileAccess::create_for_path(const String &p_path) {
 		ret = create(ACCESS_RESOURCES);
 		ret = create(ACCESS_RESOURCES);
 	} else if (p_path.begins_with("user://")) {
 	} else if (p_path.begins_with("user://")) {
 		ret = create(ACCESS_USERDATA);
 		ret = create(ACCESS_USERDATA);
-
+	} else if (p_path.begins_with("pipe://")) {
+		ret = create(ACCESS_PIPE);
 	} else {
 	} else {
 		ret = create(ACCESS_FILESYSTEM);
 		ret = create(ACCESS_FILESYSTEM);
 	}
 	}
@@ -209,6 +211,9 @@ String FileAccess::fix_path(const String &p_path) const {
 			}
 			}
 
 
 		} break;
 		} break;
+		case ACCESS_PIPE: {
+			return r_path;
+		} break;
 		case ACCESS_FILESYSTEM: {
 		case ACCESS_FILESYSTEM: {
 			return r_path;
 			return r_path;
 		} break;
 		} break;

+ 1 - 0
core/io/file_access.h

@@ -50,6 +50,7 @@ public:
 		ACCESS_RESOURCES,
 		ACCESS_RESOURCES,
 		ACCESS_USERDATA,
 		ACCESS_USERDATA,
 		ACCESS_FILESYSTEM,
 		ACCESS_FILESYSTEM,
+		ACCESS_PIPE,
 		ACCESS_MAX
 		ACCESS_MAX
 	};
 	};
 
 

+ 1 - 0
core/os/os.h

@@ -170,6 +170,7 @@ public:
 	virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); };
 	virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); };
 	virtual String get_executable_path() const;
 	virtual String get_executable_path() const;
 	virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) = 0;
 	virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) = 0;
+	virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) { return Dictionary(); }
 	virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) = 0;
 	virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) = 0;
 	virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) { return create_process(get_executable_path(), p_arguments, r_child_id); };
 	virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) { return create_process(get_executable_path(), p_arguments, r_child_id); };
 	virtual Error kill(const ProcessID &p_pid) = 0;
 	virtual Error kill(const ProcessID &p_pid) = 0;

+ 21 - 4
doc/classes/OS.xml

@@ -50,9 +50,9 @@
 			<param index="1" name="arguments" type="PackedStringArray" />
 			<param index="1" name="arguments" type="PackedStringArray" />
 			<param index="2" name="open_console" type="bool" default="false" />
 			<param index="2" name="open_console" type="bool" default="false" />
 			<description>
 			<description>
-				Creates a new process that runs independently of Godot. It will not terminate when Godot terminates. The path specified in [param path] must exist and be executable file or macOS .app bundle. Platform path resolution will be used. The [param arguments] are used in the given order and separated by a space.
+				Creates a new process that runs independently of Godot. It will not terminate when Godot terminates. The path specified in [param path] must exist and be an executable file or macOS [code].app[/code] bundle. The path is resolved based on the current platform. The [param arguments] are used in the given order and separated by a space.
 				On Windows, if [param open_console] is [code]true[/code] and the process is a console app, a new terminal window will be opened.
 				On Windows, if [param open_console] is [code]true[/code] and the process is a console app, a new terminal window will be opened.
-				If the process is successfully created, this method returns its process ID, which you can use to monitor the process (and potentially terminate it with [method kill]). Otherwise this method returns [code]-1[/code].
+				If the process is successfully created, this method returns its process ID, which you can use to monitor the process (and potentially terminate it with [method kill]). Otherwise, this method returns [code]-1[/code].
 				For example, running another instance of the project:
 				For example, running another instance of the project:
 				[codeblocks]
 				[codeblocks]
 				[gdscript]
 				[gdscript]
@@ -63,7 +63,7 @@
 				[/csharp]
 				[/csharp]
 				[/codeblocks]
 				[/codeblocks]
 				See [method execute] if you wish to run an external command and retrieve the results.
 				See [method execute] if you wish to run an external command and retrieve the results.
-				[b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows.
+				[b]Note:[/b] This method is implemented on Android, Linux, macOS, and Windows.
 				[b]Note:[/b] On macOS, sandboxed applications are limited to run only embedded helper executables, specified during export or system .app bundle, system .app bundles will ignore arguments.
 				[b]Note:[/b] On macOS, sandboxed applications are limited to run only embedded helper executables, specified during export or system .app bundle, system .app bundles will ignore arguments.
 			</description>
 			</description>
 		</method>
 		</method>
@@ -120,7 +120,7 @@
 				OS.Execute("CMD.exe", new string[] {"/C", "cd %TEMP% &amp;&amp; dir"}, output);
 				OS.Execute("CMD.exe", new string[] {"/C", "cd %TEMP% &amp;&amp; dir"}, output);
 				[/csharp]
 				[/csharp]
 				[/codeblocks]
 				[/codeblocks]
-				[b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows.
+				[b]Note:[/b] This method is implemented on Android, Linux, macOS, and Windows.
 				[b]Note:[/b] To execute a Windows command interpreter built-in command, specify [code]cmd.exe[/code] in [param path], [code]/c[/code] as the first argument, and the desired command as the second argument.
 				[b]Note:[/b] To execute a Windows command interpreter built-in command, specify [code]cmd.exe[/code] in [param path], [code]/c[/code] as the first argument, and the desired command as the second argument.
 				[b]Note:[/b] To execute a PowerShell built-in command, specify [code]powershell.exe[/code] in [param path], [code]-Command[/code] as the first argument, and the desired command as the second argument.
 				[b]Note:[/b] To execute a PowerShell built-in command, specify [code]powershell.exe[/code] in [param path], [code]-Command[/code] as the first argument, and the desired command as the second argument.
 				[b]Note:[/b] To execute a Unix shell built-in command, specify shell executable name in [param path], [code]-c[/code] as the first argument, and the desired command as the second argument.
 				[b]Note:[/b] To execute a Unix shell built-in command, specify shell executable name in [param path], [code]-c[/code] as the first argument, and the desired command as the second argument.
@@ -128,6 +128,23 @@
 				[b]Note:[/b] On Android, system commands such as [code]dumpsys[/code] can only be run on a rooted device.
 				[b]Note:[/b] On Android, system commands such as [code]dumpsys[/code] can only be run on a rooted device.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="execute_with_pipe">
+			<return type="Dictionary" />
+			<param index="0" name="path" type="String" />
+			<param index="1" name="arguments" type="PackedStringArray" />
+			<description>
+				Creates a new process that runs independently of Godot with redirected IO. It will not terminate when Godot terminates. The path specified in [param path] must exist and be an executable file or macOS [code].app[/code] bundle. The path is resolved based on the current platform. The [param arguments] are used in the given order and separated by a space.
+				If the process cannot be created, this method returns an empty [Dictionary]. Otherwise, this method returns a [Dictionary] with the following keys:
+				- [code]"stdio"[/code] - [FileAccess] to access the process stdin and stdout pipes (read/write).
+				- [code]"stderr"[/code] - [FileAccess] to access the process stderr pipe (read only).
+				- [code]"pid"[/code] - Process ID as an [int], which you can use to monitor the process (and potentially terminate it with [method kill]).
+				[b]Note:[/b] This method is implemented on Android, Linux, macOS, and Windows.
+				[b]Note:[/b] To execute a Windows command interpreter built-in command, specify [code]cmd.exe[/code] in [param path], [code]/c[/code] as the first argument, and the desired command as the second argument.
+				[b]Note:[/b] To execute a PowerShell built-in command, specify [code]powershell.exe[/code] in [param path], [code]-Command[/code] as the first argument, and the desired command as the second argument.
+				[b]Note:[/b] To execute a Unix shell built-in command, specify shell executable name in [param path], [code]-c[/code] as the first argument, and the desired command as the second argument.
+				[b]Note:[/b] On macOS, sandboxed applications are limited to run only embedded helper executables, specified during export or system .app bundle, system .app bundles will ignore arguments.
+			</description>
+		</method>
 		<method name="find_keycode_from_string" qualifiers="const">
 		<method name="find_keycode_from_string" qualifiers="const">
 			<return type="int" enum="Key" />
 			<return type="int" enum="Key" />
 			<param index="0" name="string" type="String" />
 			<param index="0" name="string" type="String" />

+ 185 - 0
drivers/unix/file_access_unix_pipe.cpp

@@ -0,0 +1,185 @@
+/**************************************************************************/
+/*  file_access_unix_pipe.cpp                                             */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* 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 "file_access_unix_pipe.h"
+
+#if defined(UNIX_ENABLED)
+
+#include "core/os/os.h"
+#include "core/string/print_string.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd) {
+	// Open pipe using handles created by pipe(fd) call in the OS.execute_with_pipe.
+	_close();
+
+	path_src = String();
+	unlink_on_close = false;
+	ERR_FAIL_COND_V_MSG(fd[0] >= 0 || fd[1] >= 0, ERR_ALREADY_IN_USE, "Pipe is already in use.");
+	fd[0] = p_rfd;
+	fd[1] = p_wfd;
+
+	last_error = OK;
+	return OK;
+}
+
+Error FileAccessUnixPipe::open_internal(const String &p_path, int p_mode_flags) {
+	_close();
+
+	path_src = p_path;
+	ERR_FAIL_COND_V_MSG(fd[0] >= 0 || fd[1] >= 0, ERR_ALREADY_IN_USE, "Pipe is already in use.");
+
+	path = String("/tmp/") + p_path.replace("pipe://", "").replace("/", "_");
+	struct stat st = {};
+	int err = stat(path.utf8().get_data(), &st);
+	if (err) {
+		if (mkfifo(path.utf8().get_data(), 0666) != 0) {
+			last_error = ERR_FILE_CANT_OPEN;
+			return last_error;
+		}
+		unlink_on_close = true;
+	} else {
+		ERR_FAIL_COND_V_MSG(!S_ISFIFO(st.st_mode), ERR_ALREADY_IN_USE, "Pipe name is already used by file.");
+	}
+
+	int f = ::open(path.utf8().get_data(), O_RDWR | O_CLOEXEC);
+	if (f < 0) {
+		switch (errno) {
+			case ENOENT: {
+				last_error = ERR_FILE_NOT_FOUND;
+			} break;
+			default: {
+				last_error = ERR_FILE_CANT_OPEN;
+			} break;
+		}
+		return last_error;
+	}
+
+	// Set close on exec to avoid leaking it to subprocesses.
+	fd[0] = f;
+	fd[1] = f;
+
+	last_error = OK;
+	return OK;
+}
+
+void FileAccessUnixPipe::_close() {
+	if (fd[0] < 0) {
+		return;
+	}
+
+	if (fd[1] != fd[0]) {
+		::close(fd[1]);
+	}
+	::close(fd[0]);
+	fd[0] = -1;
+	fd[1] = -1;
+
+	if (unlink_on_close) {
+		::unlink(path.utf8().ptr());
+	}
+	unlink_on_close = false;
+}
+
+bool FileAccessUnixPipe::is_open() const {
+	return (fd[0] >= 0 || fd[1] >= 0);
+}
+
+String FileAccessUnixPipe::get_path() const {
+	return path_src;
+}
+
+String FileAccessUnixPipe::get_path_absolute() const {
+	return path_src;
+}
+
+uint8_t FileAccessUnixPipe::get_8() const {
+	ERR_FAIL_COND_V_MSG(fd[0] < 0, 0, "Pipe must be opened before use.");
+
+	uint8_t b;
+	if (::read(fd[0], &b, 1) == 0) {
+		last_error = ERR_FILE_CANT_READ;
+		b = '\0';
+	} else {
+		last_error = OK;
+	}
+	return b;
+}
+
+uint64_t FileAccessUnixPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
+	ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
+	ERR_FAIL_COND_V_MSG(fd[0] < 0, -1, "Pipe must be opened before use.");
+
+	uint64_t read = ::read(fd[0], p_dst, p_length);
+	if (read == p_length) {
+		last_error = ERR_FILE_CANT_READ;
+	} else {
+		last_error = OK;
+	}
+	return read;
+}
+
+Error FileAccessUnixPipe::get_error() const {
+	return last_error;
+}
+
+void FileAccessUnixPipe::store_8(uint8_t p_src) {
+	ERR_FAIL_COND_MSG(fd[1] < 0, "Pipe must be opened before use.");
+	if (::write(fd[1], &p_src, 1) != 1) {
+		last_error = ERR_FILE_CANT_WRITE;
+	} else {
+		last_error = OK;
+	}
+}
+
+void FileAccessUnixPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) {
+	ERR_FAIL_COND_MSG(fd[1] < 0, "Pipe must be opened before use.");
+	ERR_FAIL_COND(!p_src && p_length > 0);
+	if (::write(fd[1], p_src, p_length) != (ssize_t)p_length) {
+		last_error = ERR_FILE_CANT_WRITE;
+	} else {
+		last_error = OK;
+	}
+}
+
+void FileAccessUnixPipe::close() {
+	_close();
+}
+
+FileAccessUnixPipe::~FileAccessUnixPipe() {
+	_close();
+}
+
+#endif // UNIX_ENABLED

+ 96 - 0
drivers/unix/file_access_unix_pipe.h

@@ -0,0 +1,96 @@
+/**************************************************************************/
+/*  file_access_unix_pipe.h                                               */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* 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.                 */
+/**************************************************************************/
+
+#ifndef FILE_ACCESS_UNIX_PIPE_H
+#define FILE_ACCESS_UNIX_PIPE_H
+
+#include "core/io/file_access.h"
+#include "core/os/memory.h"
+
+#include <stdio.h>
+
+#if defined(UNIX_ENABLED)
+
+class FileAccessUnixPipe : public FileAccess {
+	bool unlink_on_close = false;
+
+	int fd[2] = { -1, -1 };
+
+	mutable Error last_error = OK;
+	String path;
+	String path_src;
+
+	void _close();
+
+public:
+	Error open_existing(int p_rfd, int p_wfd);
+	virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
+
+	virtual bool is_open() const override; ///< true when file is open
+
+	virtual String get_path() const override; /// returns the path for the current open file
+	virtual String get_path_absolute() const override; /// returns the absolute path for the current open file
+
+	virtual void seek(uint64_t p_position) override {}
+	virtual void seek_end(int64_t p_position = 0) override {}
+	virtual uint64_t get_position() const override { return 0; }
+	virtual uint64_t get_length() const override { return 0; }
+
+	virtual bool eof_reached() const override { return false; }
+
+	virtual uint8_t get_8() const override; ///< get a byte
+	virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
+
+	virtual Error get_error() const override; ///< get last error
+
+	virtual void flush() override {}
+	virtual void store_8(uint8_t p_src) override; ///< store a byte
+	virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
+
+	virtual bool file_exists(const String &p_path) override { return false; }
+
+	virtual uint64_t _get_modified_time(const String &p_file) override { return 0; }
+	virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }
+	virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return ERR_UNAVAILABLE; }
+
+	virtual bool _get_hidden_attribute(const String &p_file) override { return false; }
+	virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; }
+	virtual bool _get_read_only_attribute(const String &p_file) override { return false; }
+	virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override { return ERR_UNAVAILABLE; }
+
+	virtual void close() override;
+
+	FileAccessUnixPipe() {}
+	virtual ~FileAccessUnixPipe();
+};
+
+#endif // UNIX_ENABLED
+
+#endif // FILE_ACCESS_UNIX_PIPE_H

+ 102 - 0
drivers/unix/os_unix.cpp

@@ -37,6 +37,7 @@
 #include "core/debugger/script_debugger.h"
 #include "core/debugger/script_debugger.h"
 #include "drivers/unix/dir_access_unix.h"
 #include "drivers/unix/dir_access_unix.h"
 #include "drivers/unix/file_access_unix.h"
 #include "drivers/unix/file_access_unix.h"
+#include "drivers/unix/file_access_unix_pipe.h"
 #include "drivers/unix/net_socket_posix.h"
 #include "drivers/unix/net_socket_posix.h"
 #include "drivers/unix/thread_posix.h"
 #include "drivers/unix/thread_posix.h"
 #include "servers/rendering_server.h"
 #include "servers/rendering_server.h"
@@ -160,6 +161,7 @@ void OS_Unix::initialize_core() {
 	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
 	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
 	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA);
 	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA);
 	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM);
 	FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM);
+	FileAccess::make_default<FileAccessUnixPipe>(FileAccess::ACCESS_PIPE);
 	DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES);
 	DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES);
 	DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA);
 	DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA);
 	DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM);
 	DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM);
@@ -489,6 +491,106 @@ Dictionary OS_Unix::get_memory_info() const {
 	return meminfo;
 	return meminfo;
 }
 }
 
 
+Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &p_arguments) {
+#define CLEAN_PIPES           \
+	if (pipe_in[0] >= 0) {    \
+		::close(pipe_in[0]);  \
+	}                         \
+	if (pipe_in[1] >= 0) {    \
+		::close(pipe_in[1]);  \
+	}                         \
+	if (pipe_out[0] >= 0) {   \
+		::close(pipe_out[0]); \
+	}                         \
+	if (pipe_out[1] >= 0) {   \
+		::close(pipe_out[1]); \
+	}                         \
+	if (pipe_err[0] >= 0) {   \
+		::close(pipe_err[0]); \
+	}                         \
+	if (pipe_err[1] >= 0) {   \
+		::close(pipe_err[1]); \
+	}
+
+	Dictionary ret;
+#ifdef __EMSCRIPTEN__
+	// Don't compile this code at all to avoid undefined references.
+	// Actual virtual call goes to OS_Web.
+	ERR_FAIL_V(ret);
+#else
+	// Create pipes.
+	int pipe_in[2] = { -1, -1 };
+	int pipe_out[2] = { -1, -1 };
+	int pipe_err[2] = { -1, -1 };
+
+	ERR_FAIL_COND_V(pipe(pipe_in) != 0, ret);
+	if (pipe(pipe_out) != 0) {
+		CLEAN_PIPES
+		ERR_FAIL_V(ret);
+	}
+	if (pipe(pipe_err) != 0) {
+		CLEAN_PIPES
+		ERR_FAIL_V(ret);
+	}
+
+	// Create process.
+	pid_t pid = fork();
+	if (pid < 0) {
+		CLEAN_PIPES
+		ERR_FAIL_V(ret);
+	}
+
+	if (pid == 0) {
+		// The child process.
+		Vector<CharString> cs;
+		cs.push_back(p_path.utf8());
+		for (int i = 0; i < p_arguments.size(); i++) {
+			cs.push_back(p_arguments[i].utf8());
+		}
+
+		Vector<char *> args;
+		for (int i = 0; i < cs.size(); i++) {
+			args.push_back((char *)cs[i].get_data());
+		}
+		args.push_back(0);
+
+		::close(STDIN_FILENO);
+		::dup2(pipe_in[0], STDIN_FILENO);
+
+		::close(STDOUT_FILENO);
+		::dup2(pipe_out[1], STDOUT_FILENO);
+
+		::close(STDERR_FILENO);
+		::dup2(pipe_err[1], STDERR_FILENO);
+
+		CLEAN_PIPES
+
+		execvp(p_path.utf8().get_data(), &args[0]);
+		// The execvp() function only returns if an error occurs.
+		ERR_PRINT("Could not create child process: " + p_path);
+		raise(SIGKILL);
+	}
+	::close(pipe_in[0]);
+	::close(pipe_out[1]);
+	::close(pipe_err[1]);
+
+	Ref<FileAccessUnixPipe> main_pipe;
+	main_pipe.instantiate();
+	main_pipe->open_existing(pipe_out[0], pipe_in[1]);
+
+	Ref<FileAccessUnixPipe> err_pipe;
+	err_pipe.instantiate();
+	err_pipe->open_existing(pipe_err[0], 0);
+
+	ret["stdio"] = main_pipe;
+	ret["stderr"] = err_pipe;
+	ret["pid"] = pid;
+
+#undef CLEAN_PIPES
+	return ret;
+#endif
+}
+
 Error OS_Unix::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) {
 Error OS_Unix::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) {
 #ifdef __EMSCRIPTEN__
 #ifdef __EMSCRIPTEN__
 	// Don't compile this code at all to avoid undefined references.
 	// Don't compile this code at all to avoid undefined references.

+ 1 - 0
drivers/unix/os_unix.h

@@ -76,6 +76,7 @@ public:
 	virtual Dictionary get_memory_info() const override;
 	virtual Dictionary get_memory_info() const override;
 
 
 	virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
 	virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
+	virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override;
 	virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
 	virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
 	virtual Error kill(const ProcessID &p_pid) override;
 	virtual Error kill(const ProcessID &p_pid) override;
 	virtual int get_process_id() const override;
 	virtual int get_process_id() const override;

+ 159 - 0
drivers/windows/file_access_windows_pipe.cpp

@@ -0,0 +1,159 @@
+/**************************************************************************/
+/*  file_access_windows_pipe.cpp                                          */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* 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.                 */
+/**************************************************************************/
+
+#ifdef WINDOWS_ENABLED
+
+#include "file_access_windows_pipe.h"
+
+#include "core/os/os.h"
+#include "core/string/print_string.h"
+
+Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd) {
+	// Open pipe using handles created by CreatePipe(rfd, wfd, NULL, 4096) call in the OS.execute_with_pipe.
+	_close();
+
+	path_src = String();
+	ERR_FAIL_COND_V_MSG(fd[0] != 0 || fd[1] != 0, ERR_ALREADY_IN_USE, "Pipe is already in use.");
+	fd[0] = p_rfd;
+	fd[1] = p_wfd;
+
+	last_error = OK;
+	return OK;
+}
+
+Error FileAccessWindowsPipe::open_internal(const String &p_path, int p_mode_flags) {
+	_close();
+
+	path_src = p_path;
+	ERR_FAIL_COND_V_MSG(fd[0] != 0 || fd[1] != 0, ERR_ALREADY_IN_USE, "Pipe is already in use.");
+
+	path = String("\\\\.\\pipe\\LOCAL\\") + p_path.replace("pipe://", "").replace("/", "_");
+
+	HANDLE h = CreateFileW((LPCWSTR)path.utf16().get_data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+	if (h == INVALID_HANDLE_VALUE) {
+		h = CreateNamedPipeW((LPCWSTR)path.utf16().get_data(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, nullptr);
+		if (h == INVALID_HANDLE_VALUE) {
+			last_error = ERR_FILE_CANT_OPEN;
+			return last_error;
+		}
+		ConnectNamedPipe(h, NULL);
+	}
+	fd[0] = h;
+	fd[1] = h;
+
+	last_error = OK;
+	return OK;
+}
+
+void FileAccessWindowsPipe::_close() {
+	if (fd[0] == 0) {
+		return;
+	}
+	if (fd[1] != fd[0]) {
+		CloseHandle(fd[1]);
+	}
+	CloseHandle(fd[0]);
+	fd[0] = 0;
+	fd[1] = 0;
+}
+
+bool FileAccessWindowsPipe::is_open() const {
+	return (fd[0] != 0 || fd[1] != 0);
+}
+
+String FileAccessWindowsPipe::get_path() const {
+	return path_src;
+}
+
+String FileAccessWindowsPipe::get_path_absolute() const {
+	return path_src;
+}
+
+uint8_t FileAccessWindowsPipe::get_8() const {
+	ERR_FAIL_COND_V_MSG(fd[0] == 0, 0, "Pipe must be opened before use.");
+
+	uint8_t b;
+	if (!ReadFile(fd[0], &b, 1, nullptr, nullptr)) {
+		last_error = ERR_FILE_CANT_READ;
+		b = '\0';
+	} else {
+		last_error = OK;
+	}
+	return b;
+}
+
+uint64_t FileAccessWindowsPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
+	ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
+	ERR_FAIL_COND_V_MSG(fd[0] == 0, -1, "Pipe must be opened before use.");
+
+	DWORD read = -1;
+	if (!ReadFile(fd[0], p_dst, p_length, &read, nullptr) || read != p_length) {
+		last_error = ERR_FILE_CANT_READ;
+	} else {
+		last_error = OK;
+	}
+	return read;
+}
+
+Error FileAccessWindowsPipe::get_error() const {
+	return last_error;
+}
+
+void FileAccessWindowsPipe::store_8(uint8_t p_src) {
+	ERR_FAIL_COND_MSG(fd[1] == 0, "Pipe must be opened before use.");
+	if (!WriteFile(fd[1], &p_src, 1, nullptr, nullptr)) {
+		last_error = ERR_FILE_CANT_WRITE;
+	} else {
+		last_error = OK;
+	}
+}
+
+void FileAccessWindowsPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) {
+	ERR_FAIL_COND_MSG(fd[1] == 0, "Pipe must be opened before use.");
+	ERR_FAIL_COND(!p_src && p_length > 0);
+
+	DWORD read = -1;
+	bool ok = WriteFile(fd[1], p_src, p_length, &read, nullptr);
+	if (!ok || read != p_length) {
+		last_error = ERR_FILE_CANT_WRITE;
+	} else {
+		last_error = OK;
+	}
+}
+
+void FileAccessWindowsPipe::close() {
+	_close();
+}
+
+FileAccessWindowsPipe::~FileAccessWindowsPipe() {
+	_close();
+}
+
+#endif // WINDOWS_ENABLED

+ 95 - 0
drivers/windows/file_access_windows_pipe.h

@@ -0,0 +1,95 @@
+/**************************************************************************/
+/*  file_access_windows_pipe.h                                            */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* 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.                 */
+/**************************************************************************/
+
+#ifndef FILE_ACCESS_WINDOWS_PIPE_H
+#define FILE_ACCESS_WINDOWS_PIPE_H
+
+#ifdef WINDOWS_ENABLED
+
+#include "core/io/file_access.h"
+#include "core/os/memory.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+class FileAccessWindowsPipe : public FileAccess {
+	HANDLE fd[2] = { 0, 0 };
+
+	mutable Error last_error = OK;
+
+	String path;
+	String path_src;
+
+	void _close();
+
+public:
+	Error open_existing(HANDLE p_rfd, HANDLE p_wfd);
+
+	virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
+	virtual bool is_open() const override; ///< true when file is open
+
+	virtual String get_path() const override; /// returns the path for the current open file
+	virtual String get_path_absolute() const override; /// returns the absolute path for the current open file
+
+	virtual void seek(uint64_t p_position) override {}
+	virtual void seek_end(int64_t p_position = 0) override {}
+	virtual uint64_t get_position() const override { return 0; }
+	virtual uint64_t get_length() const override { return 0; }
+
+	virtual bool eof_reached() const override { return false; }
+
+	virtual uint8_t get_8() const override; ///< get a byte
+	virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
+
+	virtual Error get_error() const override; ///< get last error
+
+	virtual void flush() override {}
+	virtual void store_8(uint8_t p_src) override; ///< store a byte
+	virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes
+
+	virtual bool file_exists(const String &p_name) override { return false; }
+
+	uint64_t _get_modified_time(const String &p_file) override { return 0; }
+	virtual BitField<FileAccess::UnixPermissionFlags> _get_unix_permissions(const String &p_file) override { return 0; }
+	virtual Error _set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) override { return ERR_UNAVAILABLE; }
+
+	virtual bool _get_hidden_attribute(const String &p_file) override { return false; }
+	virtual Error _set_hidden_attribute(const String &p_file, bool p_hidden) override { return ERR_UNAVAILABLE; }
+	virtual bool _get_read_only_attribute(const String &p_file) override { return false; }
+	virtual Error _set_read_only_attribute(const String &p_file, bool p_ro) override { return ERR_UNAVAILABLE; }
+
+	virtual void close() override;
+
+	FileAccessWindowsPipe() {}
+	virtual ~FileAccessWindowsPipe();
+};
+
+#endif // WINDOWS_ENABLED
+
+#endif // FILE_ACCESS_WINDOWS_PIPE_H

+ 4 - 0
platform/web/os_web.cpp

@@ -105,6 +105,10 @@ Error OS_Web::execute(const String &p_path, const List<String> &p_arguments, Str
 	return create_process(p_path, p_arguments);
 	return create_process(p_path, p_arguments);
 }
 }
 
 
+Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments) {
+	ERR_FAIL_V_MSG(Dictionary(), "OS::execute_with_pipe is not available on the Web platform.");
+}
+
 Error OS_Web::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
 Error OS_Web::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
 	Array args;
 	Array args;
 	for (const String &E : p_arguments) {
 	for (const String &E : p_arguments) {

+ 1 - 0
platform/web/os_web.h

@@ -80,6 +80,7 @@ public:
 	bool main_loop_iterate();
 	bool main_loop_iterate();
 
 
 	Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
 	Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
+	Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override;
 	Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
 	Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
 	Error kill(const ProcessID &p_pid) override;
 	Error kill(const ProcessID &p_pid) override;
 	int get_process_id() const override;
 	int get_process_id() const override;

+ 101 - 0
platform/windows/os_windows.cpp

@@ -42,6 +42,7 @@
 #include "drivers/unix/net_socket_posix.h"
 #include "drivers/unix/net_socket_posix.h"
 #include "drivers/windows/dir_access_windows.h"
 #include "drivers/windows/dir_access_windows.h"
 #include "drivers/windows/file_access_windows.h"
 #include "drivers/windows/file_access_windows.h"
+#include "drivers/windows/file_access_windows_pipe.h"
 #include "main/main.h"
 #include "main/main.h"
 #include "servers/audio_server.h"
 #include "servers/audio_server.h"
 #include "servers/rendering/rendering_server_default.h"
 #include "servers/rendering/rendering_server_default.h"
@@ -178,6 +179,7 @@ void OS_Windows::initialize() {
 	FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES);
 	FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_RESOURCES);
 	FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_USERDATA);
 	FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_USERDATA);
 	FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_FILESYSTEM);
 	FileAccess::make_default<FileAccessWindows>(FileAccess::ACCESS_FILESYSTEM);
+	FileAccess::make_default<FileAccessWindowsPipe>(FileAccess::ACCESS_PIPE);
 	DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_RESOURCES);
 	DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_RESOURCES);
 	DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_USERDATA);
 	DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_USERDATA);
 	DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_FILESYSTEM);
 	DirAccess::make_default<DirAccessWindows>(DirAccess::ACCESS_FILESYSTEM);
@@ -727,6 +729,105 @@ Dictionary OS_Windows::get_memory_info() const {
 	return meminfo;
 	return meminfo;
 }
 }
 
 
+Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String> &p_arguments) {
+#define CLEAN_PIPES               \
+	if (pipe_in[0] != 0) {        \
+		CloseHandle(pipe_in[0]);  \
+	}                             \
+	if (pipe_in[1] != 0) {        \
+		CloseHandle(pipe_in[1]);  \
+	}                             \
+	if (pipe_out[0] != 0) {       \
+		CloseHandle(pipe_out[0]); \
+	}                             \
+	if (pipe_out[1] != 0) {       \
+		CloseHandle(pipe_out[1]); \
+	}                             \
+	if (pipe_err[0] != 0) {       \
+		CloseHandle(pipe_err[0]); \
+	}                             \
+	if (pipe_err[1] != 0) {       \
+		CloseHandle(pipe_err[1]); \
+	}
+
+	Dictionary ret;
+
+	String path = p_path.replace("/", "\\");
+	String command = _quote_command_line_argument(path);
+	for (const String &E : p_arguments) {
+		command += " " + _quote_command_line_argument(E);
+	}
+
+	// Create pipes.
+	HANDLE pipe_in[2] = { 0, 0 };
+	HANDLE pipe_out[2] = { 0, 0 };
+	HANDLE pipe_err[2] = { 0, 0 };
+
+	SECURITY_ATTRIBUTES sa;
+	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+	sa.bInheritHandle = true;
+	sa.lpSecurityDescriptor = nullptr;
+
+	ERR_FAIL_COND_V(!CreatePipe(&pipe_in[0], &pipe_in[1], &sa, 0), ret);
+	if (!SetHandleInformation(pipe_in[1], HANDLE_FLAG_INHERIT, 0)) {
+		CLEAN_PIPES
+		ERR_FAIL_V(ret);
+	}
+	if (!CreatePipe(&pipe_out[0], &pipe_out[1], &sa, 0)) {
+		CLEAN_PIPES
+		ERR_FAIL_V(ret);
+	}
+	if (!SetHandleInformation(pipe_out[0], HANDLE_FLAG_INHERIT, 0)) {
+		CLEAN_PIPES
+		ERR_FAIL_V(ret);
+	}
+	if (!CreatePipe(&pipe_err[0], &pipe_err[1], &sa, 0)) {
+		CLEAN_PIPES
+		ERR_FAIL_V(ret);
+	}
+	ERR_FAIL_COND_V(!SetHandleInformation(pipe_err[0], HANDLE_FLAG_INHERIT, 0), ret);
+
+	// Create process.
+	ProcessInfo pi;
+	ZeroMemory(&pi.si, sizeof(pi.si));
+	pi.si.cb = sizeof(pi.si);
+	ZeroMemory(&pi.pi, sizeof(pi.pi));
+	LPSTARTUPINFOW si_w = (LPSTARTUPINFOW)&pi.si;
+
+	pi.si.dwFlags |= STARTF_USESTDHANDLES;
+	pi.si.hStdInput = pipe_in[0];
+	pi.si.hStdOutput = pipe_out[1];
+	pi.si.hStdError = pipe_err[1];
+
+	DWORD creation_flags = NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW;
+
+	if (!CreateProcessW(nullptr, (LPWSTR)(command.utf16().ptrw()), nullptr, nullptr, true, creation_flags, nullptr, nullptr, si_w, &pi.pi)) {
+		CLEAN_PIPES
+		ERR_FAIL_V_MSG(ret, "Could not create child process: " + command);
+	}
+	CloseHandle(pipe_in[0]);
+	CloseHandle(pipe_out[1]);
+	CloseHandle(pipe_err[1]);
+
+	ProcessID pid = pi.pi.dwProcessId;
+	process_map->insert(pid, pi);
+
+	Ref<FileAccessWindowsPipe> main_pipe;
+	main_pipe.instantiate();
+	main_pipe->open_existing(pipe_out[0], pipe_in[1]);
+
+	Ref<FileAccessWindowsPipe> err_pipe;
+	err_pipe.instantiate();
+	err_pipe->open_existing(pipe_err[0], 0);
+
+	ret["stdio"] = main_pipe;
+	ret["stderr"] = err_pipe;
+	ret["pid"] = pid;
+
+#undef CLEAN_PIPES
+	return ret;
+}
+
 Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) {
 Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) {
 	String path = p_path.replace("/", "\\");
 	String path = p_path.replace("/", "\\");
 	String command = _quote_command_line_argument(path);
 	String command = _quote_command_line_argument(path);

+ 1 - 0
platform/windows/os_windows.h

@@ -181,6 +181,7 @@ public:
 	virtual Dictionary get_memory_info() const override;
 	virtual Dictionary get_memory_info() const override;
 
 
 	virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
 	virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
+	virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override;
 	virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
 	virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
 	virtual Error kill(const ProcessID &p_pid) override;
 	virtual Error kill(const ProcessID &p_pid) override;
 	virtual int get_process_id() const override;
 	virtual int get_process_id() const override;