Просмотр исходного кода

core: pipe(), fork() and exec() instead of popen()

Daniele Bartolini 6 лет назад
Родитель
Сommit
6604ceae3d

+ 134 - 22
src/core/process.cpp

@@ -8,7 +8,9 @@
 #include "core/strings/string_stream.h"
 
 #if CROWN_PLATFORM_POSIX
-	#include <unistd.h>   // fork, execv
+	#include <unistd.h>   // fork, execvp
+	#include <sys/wait.h> // waitpid
+	#include <errno.h>
 #elif CROWN_PLATFORM_WINDOWS
 	#include <windows.h>
 #endif
@@ -19,6 +21,7 @@ struct Private
 {
 #if CROWN_PLATFORM_POSIX
 	FILE* file;
+	pid_t pid;
 #elif CROWN_PLATFORM_WINDOWS
 	PROCESS_INFORMATION process;
 #endif
@@ -29,7 +32,7 @@ namespace process_internal
 	bool is_open(Private* priv)
 	{
 #if CROWN_PLATFORM_POSIX
-		return priv->file != NULL;
+		return priv->pid != -1;
 #elif CROWN_PLATFORM_WINDOWS
 		return priv->process.hProcess != 0;
 #endif
@@ -42,7 +45,7 @@ Process::Process()
 	Private* priv = (Private*)_data;
 
 #if CROWN_PLATFORM_POSIX
-	priv->file = NULL;
+	priv->pid = -1;
 #elif CROWN_PLATFORM_WINDOWS
 	memset(&priv->process, 0, sizeof(priv->process));
 #endif
@@ -54,20 +57,85 @@ Process::~Process()
 	CE_ENSURE(process_internal::is_open(priv) == false);
 }
 
-s32 Process::spawn(const char* const* argv)
+s32 Process::spawn(const char* const* argv, u32 flags)
 {
 	Private* priv = (Private*)_data;
 	CE_ENSURE(process_internal::is_open(priv) == false);
 
+#if CROWN_PLATFORM_POSIX
+	// https://opensource.apple.com/source/Libc/Libc-167/gen.subproj/popen.c.auto.html
+	int fildes[2];
+	pid_t pid;
+
+	if (flags & ProcessFlags::STDIN_PIPE || flags & ProcessFlags::STDOUT_PIPE)
+	{
+		if (pipe(fildes) < 0)
+			return -1;
+	}
+
+	pid = fork();
+	if (pid == -1) // Error, cleanup and return
+	{
+		close(fildes[0]);
+		close(fildes[1]);
+		return -1;
+	}
+	else if (pid == 0) // Child
+	{
+		if (flags & ProcessFlags::STDOUT_PIPE)
+		{
+			if (fildes[1] != STDOUT_FILENO)
+			{
+				dup2(fildes[1], STDOUT_FILENO);
+				close(fildes[1]);
+				fildes[1] = STDOUT_FILENO;
+			}
+			close(fildes[0]);
+
+			if (flags & ProcessFlags::STDERR_MERGE)
+			{
+				dup2(fildes[1], 2);
+			}
+		}
+		else if (flags & ProcessFlags::STDIN_PIPE)
+		{
+			if (fildes[0] != STDIN_FILENO)
+			{
+				dup2(fildes[0], STDIN_FILENO);
+				close(fildes[0]);
+				fildes[0] = STDIN_FILENO;
+			}
+			close(fildes[1]);
+		}
+
+		execvp(argv[0], (char* const*)argv);
+		// exec returned error
+		return -1;
+	}
+
+	// Parent
+	if (flags & ProcessFlags::STDOUT_PIPE)
+	{
+		priv->file = fdopen(fildes[0], "r");
+		close(fildes[1]);
+	}
+	else if (flags & ProcessFlags::STDIN_PIPE)
+	{
+		priv->file = fdopen(fildes[1], "w");
+		close(fildes[0]);
+	}
+	else
+	{
+		priv->file = NULL;
+	}
+
+	priv->pid = pid;
+	return 0;
+#elif CROWN_PLATFORM_WINDOWS
 	TempAllocator512 ta;
 	StringStream path(ta);
 
-	path << argv[0];
-	path << ' ';
-#if CROWN_PLATFORM_POSIX
-	path << "2>&1 ";
-#endif
-	for (s32 i = 1; argv[i] != NULL; ++i)
+	for (s32 i = 0; argv[i] != NULL; ++i)
 	{
 		const char* arg = argv[i];
 		for (; *arg; ++arg)
@@ -78,11 +146,7 @@ s32 Process::spawn(const char* const* argv)
 		}
 		path << ' ';
 	}
-#if CROWN_PLATFORM_POSIX
-	priv->file = popen(string_stream::c_str(path), "r");
 
-	return priv->file != NULL ? 0 : -1;
-#elif CROWN_PLATFORM_WINDOWS
 	STARTUPINFO info;
 	memset(&info, 0, sizeof(info));
 	info.cb = sizeof(info);
@@ -102,22 +166,49 @@ s32 Process::spawn(const char* const* argv)
 #endif
 }
 
-s32 Process::wait(StringStream* output)
+bool Process::spawned()
+{
+	Private* priv = (Private*)_data;
+#if CROWN_PLATFORM_POSIX
+	return priv->pid != -1;
+#elif CROWN_PLATFORM_WINDOWS
+	return priv->process.hProcess != 0;
+#endif
+}
+
+void Process::force_exit()
+{
+	Private* priv = (Private*)_data;
+	CE_ENSURE(process_internal::is_open(priv) == true);
+#if CROWN_PLATFORM_POSIX
+	kill(priv->pid, SIGKILL);
+#elif CROWN_PLATFORM_WINDOWS
+#endif
+}
+
+s32 Process::wait()
 {
 	Private* priv = (Private*)_data;
 	CE_ENSURE(process_internal::is_open(priv) == true);
 
 #if CROWN_PLATFORM_POSIX
-	if (output != NULL)
+	pid_t pid;
+	int wstatus;
+
+	if (priv->file != NULL)
 	{
-		char buf[1024];
-		while (fgets(buf, sizeof(buf), priv->file) != NULL)
-			*output << buf;
+		fclose(priv->file);
+		priv->file = NULL;
 	}
 
-	s32 exitcode = pclose(priv->file);
-	priv->file = NULL;
-	return exitcode;
+	do
+	{
+		pid = waitpid(priv->pid, &wstatus, 0);
+	}
+	while (pid == -1 && errno == EINTR);
+
+	priv->pid = -1;
+	return WIFEXITED(wstatus) ? (s32)WEXITSTATUS(wstatus) : -1;
 #elif CROWN_PLATFORM_WINDOWS
 	DWORD exitcode = 1;
 	::WaitForSingleObject(priv->process.hProcess, INFINITE);
@@ -129,4 +220,25 @@ s32 Process::wait(StringStream* output)
 #endif
 }
 
+char* Process::fgets(char* data, u32 len)
+{
+	Private* priv = (Private*)_data;
+	CE_ENSURE(process_internal::is_open(priv) == true);
+#if CROWN_PLATFORM_POSIX
+	CE_ENSURE(priv->file != NULL);
+	char* ret = ::fgets(data, len, priv->file);
+	if (ret == NULL)
+	{
+#if 0
+		if (feof(priv->file))
+			printf("EOF\n");
+		else
+			printf("Error\n");
+#endif
+	}
+	return ret;
+#elif CROWN_PLATFORM_WINDOWS
+#endif
+}
+
 } // namespace crown

+ 28 - 6
src/core/process.h

@@ -9,6 +9,19 @@
 
 namespace crown
 {
+/// ProcessFlags
+///
+/// @ingroup Core
+struct ProcessFlags
+{
+	enum Enum
+	{
+		STDIN_PIPE   = 1 << 0, ///< Create stdin pipe.
+		STDOUT_PIPE  = 1 << 1, ///< Create stdout pipe.
+		STDERR_MERGE = 1 << 2  ///< Merge stderr with stdout.
+	};
+};
+
 /// Process.
 ///
 /// @ingroup Core
@@ -22,14 +35,23 @@ struct Process
 	///
 	~Process();
 
-	/// Spawns the process described by @a argv.
+	/// Spawns the process described by @a argv with the given @a flags.
 	/// Returns 0 on success, non-zero otherwise.
-	s32 spawn(const char* const* argv);
+	s32 spawn(const char* const* argv, u32 flags = 0);
 
-	/// Waits for the process to terminate and returns its exit code.
-	/// If @a output is not NULL it reads stdout and stderr into
-	/// the StringStream.
-	s32 wait(StringStream* output);
+	/// Returns whether the process has been spawned
+	/// due to a previous successful call to spawn().
+	bool spawned();
+
+	/// Focefully terminates the process.
+	/// On Linux, this function sends SIGKILL.
+	void force_exit();
+
+	/// Waits synchronously for the process to terminate and returns its exit code.
+	s32 wait();
+
+	///
+	char* fgets(char* data, u32 len);
 };
 
 } // namespace crown

+ 29 - 0
src/core/unit_tests.cpp

@@ -29,6 +29,7 @@
 #include "core/memory/memory.h"
 #include "core/memory/temp_allocator.h"
 #include "core/murmur.h"
+#include "core/process.h"
 #include "core/strings/dynamic_string.h"
 #include "core/strings/string.h"
 #include "core/strings/string_id.h"
@@ -1319,6 +1320,33 @@ static void test_thread()
 	ENSURE(thread.exit_code() == -1);
 }
 
+static void test_process()
+{
+#if CROWN_PLATFORM_POSIX
+	{
+		char buf[128] = {0};
+		const char* argv[] = {"printf", "Hello,\\nworld.\\n", NULL};
+
+		Process pr;
+		if (pr.spawn(argv, ProcessFlags::STDOUT_PIPE) == 0)
+		{
+			pr.fgets(buf, sizeof(buf));
+			ENSURE(strcmp(buf, "Hello,\n") == 0);
+			pr.fgets(buf, sizeof(buf));
+			ENSURE(strcmp(buf, "world.\n") == 0);
+			pr.wait();
+		}
+	}
+	{
+		const char* argv[] = {"false", NULL};
+
+		Process pr;
+		if (pr.spawn(argv) == 0)
+			ENSURE(pr.wait() == 1);
+	}
+#endif
+}
+
 #define RUN_TEST(name)      \
 	do {                    \
 		printf(#name "\n"); \
@@ -1350,6 +1378,7 @@ int main_unit_tests()
 	RUN_TEST(test_path);
 	RUN_TEST(test_command_line);
 	RUN_TEST(test_thread);
+	RUN_TEST(test_process);
 
 	return EXIT_SUCCESS;
 }

+ 8 - 2
src/resource/lua_resource.cpp

@@ -40,14 +40,20 @@ namespace lua_resource_internal
 			NULL
 		};
 		Process pr;
-		s32 sc = pr.spawn(argv);
+		s32 sc = pr.spawn(argv, ProcessFlags::STDOUT_PIPE | ProcessFlags::STDERR_MERGE);
 		DATA_COMPILER_ASSERT(sc == 0
 			, opts
 			, "Failed to spawn `%s`"
 			, argv[0]
 			);
 		StringStream output(ta);
-		s32 ec = pr.wait(&output);
+		// Read error messages if any
+		{
+			char err[512];
+			while (pr.fgets(err, sizeof(err)) != NULL)
+				output << err;
+		}
+		s32 ec = pr.wait();
 		DATA_COMPILER_ASSERT(ec == 0
 			, opts
 			, "Failed to compile lua:\n%s"

+ 15 - 3
src/resource/shader_resource.cpp

@@ -456,7 +456,7 @@ namespace shader_resource_internal
 			argv[12] = ((strcmp(type, "vertex") == 0) ? "vs_4_0" : "ps_4_0");
 		}
 
-		return pr.spawn(argv);
+		return pr.spawn(argv, ProcessFlags::STDOUT_PIPE | ProcessFlags::STDERR_MERGE);
 	}
 
 	struct RenderState
@@ -1235,7 +1235,13 @@ namespace shader_resource_internal
 			StringStream output_vert(ta);
 			StringStream output_frag(ta);
 
-			ec = pr_vert.wait(&output_vert);
+			// Read error messages if any
+			{
+				char err[512];
+				while (pr_vert.fgets(err, sizeof(err)) != NULL)
+					output_vert << err;
+			}
+			ec = pr_vert.wait();
 			if (ec != 0)
 			{
 				delete_temp_files();
@@ -1246,7 +1252,13 @@ namespace shader_resource_internal
 					);
 			}
 
-			ec = pr_frag.wait(&output_frag);
+			// Read error messages if any
+			{
+				char err[512];
+				while (pr_frag.fgets(err, sizeof(err)) != NULL)
+					output_vert << err;
+			}
+			ec = pr_frag.wait();
 			if (ec != 0)
 			{
 				delete_temp_files();

+ 8 - 2
src/resource/texture_resource.cpp

@@ -69,14 +69,20 @@ namespace texture_resource_internal
 			NULL
 		};
 		Process pr;
-		s32 sc = pr.spawn(argv);
+		s32 sc = pr.spawn(argv, ProcessFlags::STDOUT_PIPE | ProcessFlags::STDERR_MERGE);
 		DATA_COMPILER_ASSERT(sc == 0
 			, opts
 			, "Failed to spawn `%s`"
 			, argv[0]
 			);
 		StringStream output(ta);
-		s32 ec = pr.wait(&output);
+		// Read error messages if any
+		{
+			char err[512];
+			while (pr.fgets(err, sizeof(err)) != NULL)
+				output << err;
+		}
+		s32 ec = pr.wait();
 		DATA_COMPILER_ASSERT(ec == 0
 			, opts
 			, "Failed to compile texture:\n%s"