diff options
Diffstat (limited to 'Build/source/texk/dvisvgm/dvisvgm-src/src/Process.cpp')
-rw-r--r-- | Build/source/texk/dvisvgm/dvisvgm-src/src/Process.cpp | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/Build/source/texk/dvisvgm/dvisvgm-src/src/Process.cpp b/Build/source/texk/dvisvgm/dvisvgm-src/src/Process.cpp new file mode 100644 index 00000000000..2010b05db93 --- /dev/null +++ b/Build/source/texk/dvisvgm/dvisvgm-src/src/Process.cpp @@ -0,0 +1,320 @@ +/************************************************************************* +** Process.cpp ** +** ** +** This file is part of dvisvgm -- a fast DVI to SVG converter ** +** Copyright (C) 2005-2021 Martin Gieseking <martin.gieseking@uos.de> ** +** ** +** This program is free software; you can redistribute it and/or ** +** modify it under the terms of the GNU General Public License as ** +** published by the Free Software Foundation; either version 3 of ** +** the License, or (at your option) any later version. ** +** ** +** This program is distributed in the hope that it will be useful, but ** +** WITHOUT ANY WARRANTY; without even the implied warranty of ** +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** +** GNU General Public License for more details. ** +** ** +** You should have received a copy of the GNU General Public License ** +** along with this program; if not, see <http://www.gnu.org/licenses/>. ** +*************************************************************************/ + +#ifdef _WIN32 + #include "windows.hpp" +#else + #include <csignal> + #include <cstring> + #include <fcntl.h> + #include <sys/wait.h> + #include <unistd.h> +#endif + +#include <cstdlib> +#include "FileSystem.hpp" +#include "Process.hpp" +#include "SignalHandler.hpp" + +using namespace std; + + +/** Helper class that encapsulates the system-specific parts of + * running a subprocess and retrieving its terminal output. */ +class Subprocess { + public: + enum class State {RUNNING, FINISHED, FAILED}; + + public: + Subprocess () =default; + Subprocess (const Subprocess&) =delete; + Subprocess (Subprocess&&) =delete; + ~Subprocess (); + bool run (const string &cmd, string params); + bool readFromPipe (string &out); + State state (); + + private: +#ifdef _WIN32 + HANDLE _pipeReadHandle=NULL; ///< handle of read end of pipe + HANDLE _childProcHandle=NULL; ///< handle of child process +#else + int _readfd=-1; ///< file descriptor of read end of pipe + pid_t _pid=-1; ///< PID of the subprocess +#endif +}; + + +Process::Process (string cmd, string paramstr) + : _cmd(std::move(cmd)), _paramstr(std::move(paramstr)) +{ +} + + +/** Runs the process and waits until it's finished. + * @param[out] out takes the output written to stdout by the executed subprocess + * @return true if process terminated properly + * @throw SignalException if CTRL-C was pressed during execution */ +bool Process::run (string *out) { + Subprocess subprocess; + if (!subprocess.run(_cmd, _paramstr)) + return false; + for (;;) { + if (out) { + out->clear(); + subprocess.readFromPipe(*out); + } + Subprocess::State state = subprocess.state(); + if (state != Subprocess::State::RUNNING) + return state == Subprocess::State::FINISHED; + SignalHandler::instance().check(); + } +} + + +/** Runs the process in the given working directory and waits until it's finished. + * @param[in] dir working directory + * @param[out] out takes the output written to stdout by the executed process + * @return true if process terminated properly + * @throw SignalException if CTRL-C was pressed during execution */ +bool Process::run (const string &dir, string *out) { + bool ret = false; + string cwd = FileSystem::getcwd(); + if (FileSystem::chdir(dir)) { + ret = run(out); + ret &= FileSystem::chdir(cwd); + } + return ret; +} + +// system-specific stuff + +#ifdef _WIN32 + +static inline void close_and_zero_handle (HANDLE &handle) { + CloseHandle(handle); + handle = NULL; +} + + +Subprocess::~Subprocess () { + if (_pipeReadHandle != NULL) + CloseHandle(_pipeReadHandle); + if (_childProcHandle != NULL) { + TerminateProcess(_childProcHandle, 1); + CloseHandle(_childProcHandle); + } +} + + +/** Retrieves output generated by child process. + * @param[out] out read output is appended to this string + * @returns false on errors */ +bool Subprocess::readFromPipe (string &out) { + if (!_pipeReadHandle) + return false; + + bool success=false; + bool processExited=false; + DWORD len=0; + while (PeekNamedPipe(_pipeReadHandle, NULL, 0, NULL, &len, NULL)) { // prevent blocking + if (len == 0) { + if (processExited) + break; + // process still busy + processExited = (!_childProcHandle || WaitForSingleObject(_childProcHandle, 100) != WAIT_TIMEOUT); + } + else { + char buf[4096]; + success = ReadFile(_pipeReadHandle, buf, sizeof(buf), &len, NULL); + if (!success || len == 0) + break; + out.append(buf, len); + } + } + return success; +} + + +/** Starts a child process. + * @param[in] cmd name of command to execute + * @param[in] paramstr parameters required by command + * @returns true if child process started properly */ +bool Subprocess::run (const string &cmd, string paramstr) { + SECURITY_ATTRIBUTES securityAttribs; + ZeroMemory(&securityAttribs, sizeof(SECURITY_ATTRIBUTES)); + securityAttribs.nLength = sizeof(SECURITY_ATTRIBUTES); + securityAttribs.bInheritHandle = true; + + HANDLE pipeWriteHandle; // write end of pipe + if (CreatePipe(&_pipeReadHandle, &pipeWriteHandle, &securityAttribs, 0) == ERROR_INVALID_HANDLE) + return false; + + SetHandleInformation(_pipeReadHandle, HANDLE_FLAG_INHERIT, 0); + HANDLE nullFile = CreateFile("NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &securityAttribs, OPEN_EXISTING, 0, NULL); + bool success = false; + if (nullFile != INVALID_HANDLE_VALUE) { + STARTUPINFO startupInfo; + ZeroMemory(&startupInfo, sizeof(STARTUPINFO)); + startupInfo.cb = sizeof(STARTUPINFO); + startupInfo.dwFlags = STARTF_USESTDHANDLES; + startupInfo.hStdInput = nullFile; + startupInfo.hStdOutput = pipeWriteHandle; + startupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + PROCESS_INFORMATION processInfo; + ZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION)); + + string cmdline = cmd + " " + paramstr; + // put subprocess in separate process group to prevent its termination in case of CTRL-C + success = CreateProcess(NULL, (LPSTR)cmdline.c_str(), NULL, NULL, true, CREATE_NEW_PROCESS_GROUP, NULL, NULL, &startupInfo, &processInfo); + if (success) { + _childProcHandle = processInfo.hProcess; + CloseHandle(processInfo.hThread); + } + CloseHandle(nullFile); + } + CloseHandle(pipeWriteHandle); // must be closed before reading from pipe to prevent blocking + if (!success) + close_and_zero_handle(_pipeReadHandle); + return success; +} + + +/** Returns the current state of the child process. */ +Subprocess::State Subprocess::state () { + DWORD status; + if (!GetExitCodeProcess(_childProcHandle, &status)) + return State::FAILED; + if (status == STILL_ACTIVE) + return State::RUNNING; + close_and_zero_handle(_childProcHandle); + return status == 0 ? State::FINISHED : State::FAILED; +} + +#else // !_WIN32 + +Subprocess::~Subprocess () { + if (_readfd >= 0) + close(_readfd); + if (_pid > 0) + kill(_pid, SIGKILL); +} + + +/** Retrieves output generated by child process. + * @param[out] out read output is appended to this string + * @returns false on errors */ +bool Subprocess::readFromPipe (string &out) { + if (_readfd < 0 || _pid < 0) + return false; + + char buf[1024]; + ssize_t len; + while ((len = read(_readfd, buf, sizeof(buf))) > 0) + out.append(buf, len); + if (len < 0) { + close(_readfd); + _readfd = -1; + } + return len >= 0; +} + + +/** Extracts whitespace-separated parameters from a string. + * @param[in,out] paramstr the parameter string + * @param[out] params vector holding pointers to the extracted parameters */ +static void split_paramstr (string ¶mstr, vector<const char*> ¶ms) { + size_t left=0, right=0; // index of first and last character of current parameter + char quote=0; // current quote character, 0=none + const size_t len = paramstr.length(); + while (left <= right && right < len) { + while (left < len && isspace(paramstr[left])) + ++left; + if (left < len && (paramstr[left] == '"' || paramstr[left] == '\'')) + quote = paramstr[left++]; + right = left; + while (right < len && (quote || !isspace(paramstr[right]))) { + if (quote && paramstr[right] == quote) { + quote=0; + break; + } + else + ++right; + } + if (right < len) + paramstr[right]=0; + if (left < len) + params.push_back(¶mstr[left]); + left = ++right; + } +} + + +/** Starts a child process. + * @param[in] cmd name of command to execute or absolute path to executable + * @param[in] paramstr parameters required by the command + * @returns true if child process started properly */ +bool Subprocess::run (const string &cmd, string paramstr) { + int pipefd[2]; + if (cmd.empty() || pipe(pipefd) < 0) + return false; + + _pid = fork(); + if (_pid < 0) { + close(pipefd[0]); + close(pipefd[1]); + return false; + } + if (_pid == 0) { // child process + dup2(pipefd[1], STDOUT_FILENO); // redirect stdout to the pipe + dup2(pipefd[1], STDERR_FILENO); // redirect stderr to the pipe + close(pipefd[0]); + close(pipefd[1]); + + vector<const char*> params; + params.push_back(cmd.c_str()); + split_paramstr(paramstr, params); + params.push_back(nullptr); // trailing null pointer marks end of parameter list + signal(SIGINT, SIG_IGN); // child process is supposed to ignore ctrl-c events + if (params[0][0] == '/') // absolute path to executable? + params[0] = strrchr(params[0], '/')+1; // filename of executable + execvp(cmd.c_str(), const_cast<char* const*>(params.data())); + exit(1); + } + _readfd = pipefd[0]; + close(pipefd[1]); // close write end of pipe + return true; +} + + +/** Returns the current state of the child process. */ +Subprocess::State Subprocess::state () { + int status; + pid_t wpid = waitpid(_pid, &status, WNOHANG); + if (wpid == 0) + return State::RUNNING; // still running + _pid = -1; + if (wpid > 0 && WIFEXITED(status) && WEXITSTATUS(status) == 0) + return State::FINISHED; + return State::FAILED; +} + +#endif // !_WIN32 |