Răsfoiți Sursa

Add a class to execute other processes. Only for Linux ATM

Panagiotis Christopoulos Charitos 5 ani în urmă
părinte
comite
419a7726e0

+ 1 - 1
LICENSE

@@ -1,5 +1,5 @@
 AnKi 3D Engine
 AnKi 3D Engine
-Copyright (c) 2009 - 2017 Panagiotis Christopoulos Charitos.
+Copyright (c) 2009 - 2020 Panagiotis Christopoulos Charitos.
 All rights reserved.
 All rights reserved.
 
 
 Redistribution and use in source and binary forms, with or without
 Redistribution and use in source and binary forms, with or without

+ 1 - 1
src/anki/renderer/ClusterBin.cpp

@@ -803,7 +803,7 @@ void ClusterBin::writeTypedObjectsToGpuBuffers(BinCtx& ctx) const
 			out.m_aabbMin = in.m_aabbMin;
 			out.m_aabbMin = in.m_aabbMin;
 			out.m_aabbMax = in.m_aabbMax;
 			out.m_aabbMax = in.m_aabbMax;
 			out.m_textureIndex = U32(&in - &rqueue.m_giProbes.getFront());
 			out.m_textureIndex = U32(&in - &rqueue.m_giProbes.getFront());
-			out.m_halfTexelSizeU = 1.0f / F32(in.m_cellCounts.x() * 6.0f) / 2.0f;
+			out.m_halfTexelSizeU = 1.0f / F32(F32(in.m_cellCounts.x()) * 6.0f) / 2.0f;
 			out.m_fadeDistance = in.m_fadeDistance;
 			out.m_fadeDistance = in.m_fadeDistance;
 		}
 		}
 	}
 	}

+ 0 - 5
src/anki/util/Array.h

@@ -147,11 +147,6 @@ public:
 	ANKI_ARRAY_SIZE_METHOD(U64, N > MAX_U32)
 	ANKI_ARRAY_SIZE_METHOD(U64, N > MAX_U32)
 #undef ANKI_ARRAY_SIZE_METHOD
 #undef ANKI_ARRAY_SIZE_METHOD
 
 
-	static constexpr PtrSize getSize()
-	{
-		return N;
-	}
-
 	/// Make it compatible with STL
 	/// Make it compatible with STL
 	static constexpr size_t size()
 	static constexpr size_t size()
 	{
 	{

+ 2 - 2
src/anki/util/CMakeLists.txt

@@ -2,9 +2,9 @@ set(SOURCES Assert.cpp Functions.cpp File.cpp Filesystem.cpp Memory.cpp System.c
 	ThreadHive.cpp Hash.cpp Logger.cpp String.cpp StringList.cpp Tracer.cpp Serializer.cpp Xml.cpp)
 	ThreadHive.cpp Hash.cpp Logger.cpp String.cpp StringList.cpp Tracer.cpp Serializer.cpp Xml.cpp)
 
 
 if(LINUX OR ANDROID OR MACOS)
 if(LINUX OR ANDROID OR MACOS)
-	set(SOURCES ${SOURCES} HighRezTimerPosix.cpp FilesystemPosix.cpp ThreadPosix.cpp)
+	set(SOURCES ${SOURCES} HighRezTimerPosix.cpp FilesystemPosix.cpp ThreadPosix.cpp ProcessPosix.cpp)
 else()
 else()
-	set(SOURCES ${SOURCES} HighRezTimerWindows.cpp FilesystemWindows.cpp ThreadWindows.cpp)
+	set(SOURCES ${SOURCES} HighRezTimerWindows.cpp FilesystemWindows.cpp ThreadWindows.cpp ProcessWindows.cpp)
 endif()
 endif()
 
 
 if(LINUX)
 if(LINUX)

+ 95 - 0
src/anki/util/Process.h

@@ -0,0 +1,95 @@
+// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/util/Array.h>
+#include <anki/util/String.h>
+#include <anki/util/WeakArray.h>
+
+namespace anki
+{
+
+/// @addtogroup util_system
+/// @{
+
+/// @memberof Process
+enum class ProcessStatus : U8
+{
+	RUNNING,
+	NOT_RUNNING,
+	NORMAL_EXIT,
+	CRASH_EXIT
+};
+
+/// @memberof Process
+enum class ProcessKillSignal : U8
+{
+	NORMAL,
+	FORCE
+};
+
+/// Executes external processes.
+class Process
+{
+public:
+	Process() = default;
+
+	~Process();
+
+	Process(const Process&) = delete;
+
+	Process& operator=(const Process&) = delete;
+
+	/// Start a process.
+	/// @param executable The executable to start.
+	/// @param arguments The command line arguments.
+	/// @param environment The environment variables.
+	ANKI_USE_RESULT Error start(
+		CString executable, ConstWeakArray<CString> arguments, ConstWeakArray<CString> environment);
+
+	/// Wait for the process to finish.
+	/// @param timeout The time to wait. If 0.0 wait forever.
+	/// @param[out] status The exit status
+	/// @param[out] exitCode The exit code if the process has finished.
+	ANKI_USE_RESULT Error wait(Second timeout = 0.0, ProcessStatus* status = nullptr, I32* exitCode = nullptr);
+
+	/// Get the status.
+	ANKI_USE_RESULT Error getStatus(ProcessStatus& status);
+
+	/// Kill the process.
+	ANKI_USE_RESULT Error kill(ProcessKillSignal k);
+
+	/// Read from stdout.
+	ANKI_USE_RESULT Error readFromStdout(StringAuto& text);
+
+	/// Read from stderr.
+	ANKI_USE_RESULT Error readFromStderr(StringAuto& text);
+
+private:
+#if ANKI_POSIX
+	static constexpr int DEFAULT_EXIT_CODE = -1;
+
+	int m_pid = -1;
+	int m_exitCode = DEFAULT_EXIT_CODE;
+	ProcessStatus m_status = ProcessStatus::NOT_RUNNING;
+	Array<int, 2> m_stdoutPipe = {{-1, -1}};
+	Array<int, 2> m_stderrPipe = {{-1, -1}};
+
+	void destroyPipes();
+
+	ANKI_USE_RESULT Error createPipes();
+
+	ANKI_USE_RESULT Error readFromFd(int fd, StringAuto& text) const;
+
+	/// Update some members.
+	ANKI_USE_RESULT Error refresh(int waitpidOptions);
+#else
+	// TODO
+#endif
+};
+/// @}
+
+} // end namespace anki

+ 304 - 0
src/anki/util/ProcessPosix.cpp

@@ -0,0 +1,304 @@
+// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/util/Process.h>
+#include <anki/util/Functions.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+
+namespace anki
+{
+
+Process::~Process()
+{
+	ProcessStatus s;
+	const Error err = getStatus(s);
+	if(err)
+	{
+		ANKI_UTIL_LOGE("Failed to get the status. Expect cleanup errors");
+		return;
+	}
+
+	if(s == ProcessStatus::RUNNING)
+	{
+		ANKI_UTIL_LOGE("Destroying while child process is running");
+		return;
+	}
+
+	destroyPipes();
+}
+
+void Process::destroyPipes()
+{
+	for(U32 i = 0; i < 2; ++i)
+	{
+		close(m_stdoutPipe[i]);
+		close(m_stderrPipe[i]);
+		m_stdoutPipe[i] = m_stderrPipe[i] = -1;
+	}
+}
+
+Error Process::createPipes()
+{
+	destroyPipes();
+
+	if(pipe(m_stdoutPipe.getBegin()) || pipe(m_stderrPipe.getBegin()))
+	{
+		ANKI_UTIL_LOGE("pipe() failed: %s", strerror(errno));
+		return Error::FUNCTION_FAILED;
+	}
+
+	fcntl(m_stdoutPipe[0], F_SETFL, O_NONBLOCK);
+	fcntl(m_stderrPipe[0], F_SETFL, O_NONBLOCK);
+
+	return Error::NONE;
+}
+
+Error Process::start(CString executable, ConstWeakArray<CString> arguments, ConstWeakArray<CString> environment)
+{
+	ANKI_CHECK(refresh(0));
+	if(m_status == ProcessStatus::RUNNING)
+	{
+		ANKI_UTIL_LOGE("Process already running");
+		return Error::USER_DATA;
+	}
+
+	// Create the pipes
+	ANKI_CHECK(createPipes());
+
+	// Fork
+	m_pid = fork();
+	if(m_pid < 0)
+	{
+		ANKI_UTIL_LOGE("fork() failed: %s", strerror(errno));
+	}
+
+	if(m_pid == 0)
+	{
+		// Child
+
+		dup2(m_stdoutPipe[1], 1);
+		close(m_stdoutPipe[0]);
+		m_stdoutPipe[0] = -1;
+
+		dup2(m_stderrPipe[1], 2);
+		close(m_stderrPipe[0]);
+		m_stderrPipe[0] = -1;
+
+		// Set the args
+		char** cargs = static_cast<char**>(malloc((arguments.getSize() + 2) * sizeof(char*)));
+		cargs[0] = static_cast<char*>(malloc(executable.getLength() + 1));
+		strcpy(cargs[0], executable.cstr());
+		U32 i = 0;
+		for(; i < arguments.getSize(); ++i)
+		{
+			cargs[i + 1] = static_cast<char*>(malloc(arguments[i].getLength() + 1));
+			strcpy(cargs[i + 1], arguments[i].cstr());
+		}
+		cargs[i + 1] = nullptr;
+
+		// Set the env
+		char** newenv = static_cast<char**>(malloc((environment.getSize() + 1) * sizeof(char*)));
+		i = 0;
+		for(; i < environment.getSize(); ++i)
+		{
+			newenv[i] = static_cast<char*>(malloc(environment[i].getLength() + 1));
+			strcpy(newenv[i], environment[i].cstr());
+		}
+		newenv[i] = nullptr;
+
+		// Execute file
+		const int execerror = execvpe(executable.cstr(), cargs, newenv);
+		if(execerror)
+		{
+			printf("execvpe() failed: %s", strerror(errno));
+			exit(1);
+		}
+	}
+	else
+	{
+		close(m_stdoutPipe[1]);
+		m_stdoutPipe[1] = -1;
+		close(m_stderrPipe[1]);
+		m_stderrPipe[1] = -1;
+	}
+
+	return Error::NONE;
+}
+
+Error Process::kill(ProcessKillSignal k)
+{
+	if(m_pid < 1)
+	{
+		return Error::NONE;
+	}
+
+	int sig;
+	switch(k)
+	{
+	case ProcessKillSignal::NORMAL:
+		sig = SIGTERM;
+		break;
+	case ProcessKillSignal::FORCE:
+		sig = SIGKILL;
+		break;
+	default:
+		ANKI_ASSERT(0);
+	};
+
+	const pid_t p = ::kill(m_pid, sig);
+	if(p != 0)
+	{
+		ANKI_UTIL_LOGE("kill() failed: %s", strerror(errno));
+		return Error::FUNCTION_FAILED;
+	}
+
+	return Error::NONE;
+}
+
+Error Process::readFromFd(int fd, StringAuto& text) const
+{
+	fd_set readfds;
+	FD_ZERO(&readfds);
+	FD_SET(fd, &readfds);
+
+	timeval timeout;
+	timeout.tv_sec = 0; // Seconds
+	timeout.tv_usec = 10; // Microseconds
+
+	// Limit the iterations in case the process writes to FD constantly
+	U32 maxIterations = 16;
+	while(maxIterations--)
+	{
+		// Check if there are data
+		const int sel = select(1 + fd, &readfds, nullptr, nullptr, &timeout);
+		if(sel == 0)
+		{
+			// Timeout expired
+			break;
+		}
+		else if(sel == -1)
+		{
+			ANKI_UTIL_LOGE("select() failed: %s", strerror(errno));
+			return Error::FUNCTION_FAILED;
+		}
+
+		// Read the data
+		Array<char, 1024> buff;
+		const ssize_t bytesCount = read(fd, buff.getBegin(), buff.getSize() - 1);
+
+		if(bytesCount < 0 && errno != EAGAIN)
+		{
+			// NOTE errno == EINTR if a non-fatal signal arrived
+			ANKI_UTIL_LOGE("read() failed: %s", strerror(errno));
+			return Error::FUNCTION_FAILED;
+		}
+		else if(bytesCount == 0)
+		{
+			break;
+		}
+		else
+		{
+			buff[min<U32>(U32(bytesCount), buff.getSize() - 1)] = '\0';
+			text.append(&buff[0]);
+		}
+	}
+
+	return Error::NONE;
+}
+
+Error Process::readFromStdout(StringAuto& text)
+{
+	return readFromFd(m_stdoutPipe[0], text);
+}
+
+Error Process::readFromStderr(StringAuto& text)
+{
+	return readFromFd(m_stderrPipe[0], text);
+}
+
+Error Process::getStatus(ProcessStatus& status)
+{
+	ANKI_CHECK(refresh(WNOHANG));
+	status = m_status;
+	return Error::NONE;
+}
+
+Error Process::refresh(int waitpidOptions)
+{
+	Error err = Error::NONE;
+	m_status = ProcessStatus::NOT_RUNNING;
+	m_exitCode = DEFAULT_EXIT_CODE;
+
+	if(m_pid == -1)
+	{
+		m_status = ProcessStatus::NOT_RUNNING;
+		m_exitCode = DEFAULT_EXIT_CODE;
+	}
+	else
+	{
+		int status;
+		const pid_t p = waitpid(m_pid, &status, waitpidOptions);
+
+		if(p == -1)
+		{
+			m_status = ProcessStatus::NOT_RUNNING;
+			m_exitCode = DEFAULT_EXIT_CODE;
+			m_pid = -1;
+			ANKI_UTIL_LOGE("waitpid() failed: %s", strerror(errno));
+			err = Error::FUNCTION_FAILED;
+		}
+		else if(p == 0)
+		{
+			m_status = ProcessStatus::RUNNING;
+			m_exitCode = DEFAULT_EXIT_CODE;
+		}
+		else if(WIFEXITED(status))
+		{
+			m_status = ProcessStatus::NORMAL_EXIT;
+			m_exitCode = WEXITSTATUS(status);
+			m_pid = -1;
+		}
+		else if(WIFSIGNALED(status))
+		{
+			m_status = ProcessStatus::CRASH_EXIT;
+			m_exitCode = WTERMSIG(status);
+			m_pid = -1;
+		}
+		else if(WIFSTOPPED(status))
+		{
+			// NOTE child may resume later (and become a zombie)
+			m_status = ProcessStatus::CRASH_EXIT;
+			m_exitCode = WSTOPSIG(status);
+		}
+		else
+		{
+			ANKI_ASSERT(0);
+		}
+	}
+
+	return err;
+}
+
+Error Process::wait(Second timeout, ProcessStatus* status, I32* exitCode)
+{
+	ANKI_CHECK(refresh(0));
+
+	if(status)
+	{
+		*status = m_status;
+	}
+
+	if(exitCode)
+	{
+		*exitCode = m_exitCode;
+	}
+
+	return Error::NONE;
+}
+
+} // end namespace anki

+ 52 - 0
src/anki/util/ProcessWindows.cpp

@@ -0,0 +1,52 @@
+// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/util/Process.h>
+
+namespace anki
+{
+
+Process::~Process()
+{
+	// TODO
+}
+
+Error Process::start(CString executable, ConstWeakArray<CString> arguments, ConstWeakArray<CString> environment)
+{
+	// TODO
+	return Error::NONE;
+}
+
+Error Process::kill(ProcessKillSignal k)
+{
+	// TODO
+	return Error::NONE;
+}
+
+Error Process::readFromStdout(StringAuto& text)
+{
+	// TODO
+	return Error::NONE;
+}
+
+Error Process::readFromStderr(StringAuto& text)
+{
+	// TODO
+	return Error::NONE;
+}
+
+Error Process::getStatus(ProcessStatus& status)
+{
+	// TODO
+	return Error::NONE;
+}
+
+Error Process::wait(Second timeout, ProcessStatus* status, I32* exitCode)
+{
+	// TODO
+	return Error::NONE;
+}
+
+} // end namespace anki

+ 1 - 1
src/anki/util/String.h

@@ -489,7 +489,7 @@ public:
 	{
 	{
 		auto size = m_data.getSize();
 		auto size = m_data.getSize();
 		auto out = (size != 0) ? (size - 1) : 0;
 		auto out = (size != 0) ? (size - 1) : 0;
-		ANKI_ASSERT(std::strlen(&m_data[0]) == out);
+		ANKI_ASSERT(size == 0 || std::strlen(&m_data[0]) == out);
 		return out;
 		return out;
 	}
 	}
 
 

+ 74 - 0
tests/util/Process.cpp

@@ -0,0 +1,74 @@
+// Copyright (C) 2009-2020, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <tests/framework/Framework.h>
+#include <anki/util/Process.h>
+#include <anki/util/File.h>
+#include <anki/util/HighRezTimer.h>
+
+ANKI_TEST(Util, Process)
+{
+	// Simple test
+	if(1)
+	{
+		Process proc;
+		ANKI_TEST_EXPECT_NO_ERR(proc.start("ls", Array<CString, 1>{{"-lt"}}, {}));
+		ANKI_TEST_EXPECT_NO_ERR(proc.wait());
+		HighRezTimer::sleep(1.0);
+
+		HeapAllocator<U8> alloc(allocAligned, nullptr);
+		StringAuto stdout(alloc);
+		ANKI_TEST_EXPECT_NO_ERR(proc.readFromStdout(stdout));
+		ANKI_TEST_LOGI(stdout.cstr());
+	}
+
+	// Stderr and stdout
+	if(1)
+	{
+		File file;
+		ANKI_TEST_EXPECT_NO_ERR(file.open("process_test.sh", FileOpenFlag::WRITE));
+		ANKI_TEST_EXPECT_NO_ERR(file.writeText(R"(#!/bin/bash
+x=1
+while [ $x -le 10 ]
+do
+	echo "Stdout message no $x"
+	echo "Stderr message no $x" 1>&2
+	sleep 1
+	x=$(( $x + 1 ))
+done
+)"));
+		file.close();
+
+		Process proc;
+		ANKI_TEST_EXPECT_NO_ERR(proc.start("bash", Array<CString, 1>{{"process_test.sh"}}, {}));
+		ProcessStatus status;
+
+		while(true)
+		{
+			ANKI_TEST_EXPECT_NO_ERR(proc.getStatus(status));
+			if(status == ProcessStatus::NORMAL_EXIT)
+			{
+				break;
+			}
+
+			HeapAllocator<U8> alloc(allocAligned, nullptr);
+			StringAuto stdout(alloc);
+			ANKI_TEST_EXPECT_NO_ERR(proc.readFromStdout(stdout));
+			if(stdout.getLength())
+			{
+				ANKI_TEST_LOGI(stdout.cstr());
+			}
+
+			StringAuto stderrStr(alloc);
+			ANKI_TEST_EXPECT_NO_ERR(proc.readFromStderr(stderrStr));
+			if(stderrStr.getLength())
+			{
+				ANKI_TEST_LOGI(stderrStr.cstr());
+			}
+		}
+
+		ANKI_TEST_EXPECT_NO_ERR(proc.wait(0.0, &status));
+	}
+}