/* PIP - Platform Independent Primitives Process Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include "pitime.h" #ifndef MICRO_PIP # include "piincludes_p.h" # include "piprocess.h" # ifndef WINDOWS # include # include # endif # ifdef MAC_OS # include # endif //! \class PIProcess piprocess.h //! \~\details //! \~english //! This class able to start external executables, watch for them, //! grab output and exit code. //! //! \~russian //! //! //! \~english \section PIProcess_sec0 Synopsis //! \~russian \section PIProcess_sec0 Краткий обзор //! \~english //! External executable can be started with control or fully independent //! from application.\n //! Start with control allow you to wait for finish, grab output //! and terminate it at any time.\n //! You can change working directory and environment of executable.\n //! //! \~russian //! //! //! \~english \section PIProcess_sec1 Usage //! \~russian \section PIProcess_sec1 Использование //! \~english //! //! //! \~russian //! namespace { enum class PipeDirection { Read, Write, Last = Write }; enum class StdFile { In, Out, Err, Last = Err }; constexpr int PipesDirections = static_cast(PipeDirection::Last) + 1; constexpr int StdFileCount = static_cast(StdFile::Last) + 1; #ifdef WINDOWS using PipeHandleType = HANDLE; #else using PipeHandleType = int; #endif } PRIVATE_DEFINITION_START(PIProcess) # ifdef WINDOWS PROCESS_INFORMATION pi; # else pid_t pid; # endif PipeHandleType pipes[StdFileCount][PipesDirections]; bool grab[StdFileCount]; void forEachPipe(std::function func) { for (int i = 0; i < StdFileCount; ++i) { for (int j = 0; j < PipesDirections; ++j) { func(pipes[i][j]); } } } void initGrab() { for (int i = 0; i < StdFileCount; ++i) { grab[i] = false; } } bool createPipe(StdFile pipe_type) { const int pt = static_cast(pipe_type); #ifdef WINDOWS SECURITY_ATTRIBUTES saAttr; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; if (!CreatePipe(&(pipes[pt][PipeDirection::Read]), &(pipes[pt][PipeDirection::Write]), &saAttr, 0)) { piCout << "CreatePipe failed: " << GetLastError() << std::endl; return false; } return true; #else int ret = pipe(pipes[pt]); return ret != -1; #endif return false; } PRIVATE_DEFINITION_END(PIProcess) PIProcess::PIProcess(): PIThread() { exit_code = -1; # ifdef WINDOWS PRIVATE->pi.dwProcessId = 0; # else PRIVATE->pid = 0; # endif is_exec = false; PRIVATE->forEachPipe([](PipeHandleType & pipe) { pipe = 0;}); PRIVATE->initGrab(); env = PIProcess::currentEnvironment(); } PIProcess::~PIProcess() { PIThread::stopAndWait(); } void PIProcess::exec_() { is_exec = false; startOnce(); // cout << "exec wait" << endl; while (!is_exec) piMinSleep(); // cout << "exec end" << endl; } void PIProcess::startProc(bool detached) { // cout << "run" << endl; PIString str; /// arguments convertion int as = 0; const char * argscc[args.size() + 1]; int argsl[args.size()]; for (int i = 0; i < args.size_s(); ++i) { argscc[i] = args[i].data(); argsl[i] = strlen(argscc[i]); as += argsl[i] + 3; } argscc[args.size()] = 0; # ifdef WINDOWS char * a = new char[as]; memset(a, ' ', as - 1); as = 0; for (int i = 0; i < args.size_s(); ++i) { str = args[i]; a[as] = '"'; memcpy(&(a[as + 1]), argscc[i], argsl[i]); a[as + argsl[i] + 1] = '"'; as += argsl[i] + 3; } a[as - 1] = 0; // piCout << a; # endif # ifndef WINDOWS /// environment convertion const char * envcc[env.size() + 1]; envcc[env.size_s()] = 0; for (int i = 0; i < env.size_s(); ++i) { envcc[i] = env[i].data(); } # endif /// files for stdin/out/err str = args.front(); is_exec = true; if (!detached) execStarted(str); # ifndef WINDOWS int pid_ = fork(); if (!detached) PRIVATE->pid = pid_; if (pid_ == 0) { # endif // PRIVATE->tf_in = PRIVATE->tf_out = PRIVATE->tf_err = 0; // // cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl; // // cout << f_out.path() << endl; // if (g_in) PRIVATE->tf_in = freopen(f_in.path().data(), "r", stdin); // if (g_out) PRIVATE->tf_out = freopen(f_out.path().data(), "w", stdout); // if (g_err) PRIVATE->tf_err = freopen(f_err.path().data(), "w", stderr); # ifdef WINDOWS STARTUPINFOA si; piZeroMemory(pi); si.cb = sizeof(STARTUPINFOA); if (PRIVATE->grab[StdFile::In]) si.hStdInput = PRIVATE->pipes[StdFile::In][PipeDirection::Read]; if (PRIVATE->grab[StdFile::Out]) si.hStdOutput = PRIVATE->pipes[StdFile::Out][PipeDirection::Write]; if (PRIVATE->grab[StdFile::Err]) si.hStdError = PRIVATE->pipes[StdFile::Err][PipeDirection::Write]; si.dwFlags |= STARTF_USESTDHANDLES; if (CreateProcessA(0, // No module name (use command line) a, // Command line 0, // Process handle not inheritable 0, // Thread handle not inheritable false, // Set handle inheritance to FALSE detached ? DETACHED_PROCESS /*CREATE_NEW_CONSOLE*/ : 0, // Creation flags 0, // envcc, // Use environment wd.isEmpty() ? 0 : wd.data(), // Use working directory &si, // Pointer to STARTUPINFO structure &(PRIVATE->pi))) // Pointer to PROCESS_INFORMATION structure { if (!detached) { WaitForSingleObject(PRIVATE->pi.hProcess, INFINITE); DWORD code = -1; if (GetExitCodeProcess(PRIVATE->pi.hProcess, &code) != 0) exit_code = code; } CloseHandle(PRIVATE->pi.hThread); CloseHandle(PRIVATE->pi.hProcess); } else { piCoutObj << "\"CreateProcess\" error: %1"_tr("PIProcess").arg(errorString()); } # endif # ifndef WINDOWS if (!wd.isEmpty()) { if (!chdir(wd.data())) piCoutObj << "Error while set working directory"; } // cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl; if (execve(str.data(), (char * const *)argscc, (char * const *)envcc) < 0) { piCoutObj << "\"execve" << str << args << "\" error :" << errorString(); } } else { piMinSleep(); // cout << "wait" << endl; if (!detached) { waitpid(pid_, &exit_code, 0); pid_ = 0; if (!detached) PRIVATE->pid = pid_; // cout << "wait done" << endl; } } # endif if (!detached) execFinished(str, exit_code); is_exec = false; # ifdef WINDOWS delete[] a; # endif } // PIByteArray PIProcess::readFile(PIFile & f, bool clear) // { // f.open(PIIODevice::ReadOnly); // const auto ret = f.readAll(); // if (clear) { // f.clear(); // } // return ret; // } void PIProcess::terminate() { # ifdef WINDOWS if (is_exec) { if (!TerminateProcess(PRIVATE->pi.hProcess, 0)) return; } PRIVATE->pi.dwProcessId = 0; # else if (is_exec) kill(PRIVATE->pid, SIGKILL); PRIVATE->pid = 0; # endif } void PIProcess::execIndependent(const PIString & program, const PIStringList & args_) { PIProcess p; p.args << program << args_; p.startProc(true); } int PIProcess::pID() const { # ifdef WINDOWS return PRIVATE->pi.dwProcessId; # else return PRIVATE->pid; # endif } PIByteArray PIProcess::readOutput(bool clear) { return {}; //readFile(f_out, clear); } PIByteArray PIProcess::readError(bool clear) { return {}; //readFile(f_err, clear); } int PIProcess::currentPID() { # ifdef WINDOWS return GetCurrentProcessId(); # else return getpid(); # endif } PIStringList PIProcess::currentEnvironment() { PIStringList l; int i = 0; while (environ[i] != 0) { l << environ[i]; ++i; } return l; } void PIProcess::run() { startProc(false); } void PIProcess::removeEnvironmentVariable(const PIString & variable) { PIString s; for (int i = 0; i < env.size_s(); ++i) { s = env[i]; if (s.left(s.find("=")).trimmed() == variable) { env.remove(i); --i; } } } void PIProcess::setEnvironmentVariable(const PIString & variable, const PIString & value) { PIString s, v; for (int i = 0; i < env.size_s(); ++i) { s = env[i]; v = s.left(s.find("=")).trimmed(); if (v == variable) { env[i] = v + "=" + value; return; } } env << variable + "=" + value; } PIString PIProcess::getEnvironmentVariable(const PIString & variable) { PIStringList env_ = currentEnvironment(); PIString s, v; for (int i = 0; i < env_.size_s(); ++i) { s = env_[i]; v = s.left(s.find("=")).trimmed(); if (v == variable) { return s.right(s.size() - 1 - s.find("=")).trimmed(); } } return PIString(); } #endif // MICRO_PIP