|
|
@@ -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
|