/* 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 "piliterals_bytes.h" # include "piprocess.h" # include "pitranslator.h" # ifndef WINDOWS # include # 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 PipeDirection { PipeRead, PipeWrite, PipeLast = PipeWrite }; enum StdFile { StdIn, StdOut, StdErr, StdLast = StdErr }; constexpr int PipesDirections = PipeLast + 1; constexpr int StdFileCount = StdLast + 1; # ifdef WINDOWS using PipeHandleType = HANDLE; using SizeType = DWORD; # else using SizeType = ssize_t; using PipeHandleType = int; # endif # ifdef WINDOWS PIString convertWindowsCmd(PIStringList sl) { if (sl.isNotEmpty()) { sl[0].replaceAll('/', '\\'); sl[0].quote(); } return sl.join(' '); } # else char * const * convertToCharArrays(const PIStringList & sl) { char ** cc = new char *[sl.size() + 1]; for (int i = 0; i < sl.size_s(); ++i) { cc[i] = const_cast(sl[i].data()); } cc[sl.size()] = 0; return cc; } # endif } // namespace 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 = pipe_type; # ifdef WINDOWS SECURITY_ATTRIBUTES saAttr; saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; if (!CreatePipe(&(pipes[pt][PipeRead]), &(pipes[pt][PipeWrite]), &saAttr, 0)) return false; return true; # else int ret = pipe(pipes[pt]); return ret != -1; # endif return false; } bool createPipes() { for (int i = 0; i < StdFileCount; ++i) { if (grab[i]) { if (!createPipe(static_cast(i))) { piCout << "CreatePipe failed"; # ifdef WINDOWS piCout << GetLastError(); # endif return false; } } } # ifdef WINDOWS if (grab[StdIn]) SetHandleInformation(pipes[StdIn][PipeWrite], HANDLE_FLAG_INHERIT, 0); if (grab[StdOut]) SetHandleInformation(pipes[StdOut][PipeRead], HANDLE_FLAG_INHERIT, 0); if (grab[StdErr]) SetHandleInformation(pipes[StdErr][PipeRead], HANDLE_FLAG_INHERIT, 0); # endif return true; } void closePipe(StdFile pipe_type, PipeDirection direction) { closePipe(pipes[pipe_type][direction]); } void closePipe(PipeHandleType & hpipe) { # ifdef WINDOWS if (hpipe != 0) { CloseHandle(hpipe); hpipe = 0; } # else if (hpipe != -1) { ::close(hpipe); hpipe = -1; } # endif } void closeAllPipes() { forEachPipe([this](PipeHandleType & hpipe) { closePipe(hpipe); }); } PIByteArray readPipe(StdFile pipe_type) { if (!grab[pipe_type]) return {}; constexpr size_t read_buffer_size = 4_KiB; PIByteArray read_buffer; read_buffer.resize(read_buffer_size); SizeType bytes_read = 0; size_t offset = 0; while (1) { # ifdef WINDOWS DWORD available = 0; PeekNamedPipe(pipes[pipe_type][PipeRead], nullptr, 0, nullptr, &available, nullptr); if (available == 0) { read_buffer.resize(offset); break; } BOOL ok = ReadFile(pipes[pipe_type][PipeRead], read_buffer.data(offset), piMini(available, read_buffer.size() - offset), &bytes_read, nullptr); if (!ok) bytes_read = 0; # else bytes_read = ::read(pipes[pipe_type][PipeRead], read_buffer.data(offset), read_buffer.size() - offset); # endif if (bytes_read > 0) { offset += bytes_read; read_buffer.resize(offset + read_buffer_size); } else { read_buffer.resize(offset); break; } } return read_buffer; } bool writePipe(const PIByteArray & data) { SizeType sz = 0; # ifdef WINDOWS BOOL ok = WriteFile(pipes[StdIn][PipeWrite], data.data(), data.size(), &sz, NULL); if (!ok) sz = 0; # else sz = ::write(pipes[StdIn][PipeWrite], data.data(), data.size()); # endif return sz == data.size_s(); } PRIVATE_DEFINITION_END(PIProcess) PIProcess::PIProcess(): PIThread() { exit_code = -1; # ifdef WINDOWS PRIVATE->pi.dwProcessId = 0; # else PRIVATE->pid = 0; PRIVATE->forEachPipe([](PipeHandleType & pipe) { pipe = -1; }); # endif exec_start = false; exec_finished = false; PRIVATE->initGrab(); env = PIProcess::currentEnvironment(); } PIProcess::~PIProcess() { PIThread::stopAndWait(); PRIVATE->closeAllPipes(); } void PIProcess::exec_() { exec_finished = false; exec_start = false; PRIVATE->closeAllPipes(); startOnce(); } void PIProcess::startProc(bool detached) { const PIString & str = args.front(); if (!detached) execStarted(str); if (!PRIVATE->createPipes()) return; # ifdef WINDOWS STARTUPINFOA si; piZeroMemory(si); si.cb = sizeof(STARTUPINFOA); if (PRIVATE->grab[StdIn]) si.hStdInput = PRIVATE->pipes[StdIn][PipeRead]; if (PRIVATE->grab[StdOut]) si.hStdOutput = PRIVATE->pipes[StdOut][PipeWrite]; if (PRIVATE->grab[StdErr]) si.hStdError = PRIVATE->pipes[StdErr][PipeWrite]; si.dwFlags |= STARTF_USESTDHANDLES; const auto cmd = convertWindowsCmd(args); if (CreateProcessA(0, // No module name (use command line) (LPSTR)cmd.data(), // Command line 0, // Process handle not inheritable 0, // Thread handle not inheritable true, // Set handle inheritance to FALSE detached ? DETACHED_PROCESS /*CREATE_NEW_CONSOLE*/ : 0, // Creation flags 0, // Use environment wd.isEmpty() ? 0 : wd.data(), // Use working directory &si, // Pointer to STARTUPINFO structure &(PRIVATE->pi))) // Pointer to PROCESS_INFORMATION structure { exec_start = true; if (!detached) { WaitForSingleObject(PRIVATE->pi.hProcess, INFINITE); DWORD code = -1; if (GetExitCodeProcess(PRIVATE->pi.hProcess, &code) != 0) exit_code = code; exec_finished = true; } CloseHandle(PRIVATE->pi.hThread); CloseHandle(PRIVATE->pi.hProcess); } else { piCoutObj << "\"CreateProcess\" error: %1"_tr("PIProcess").arg(errorString()); } # else auto largs = convertToCharArrays(args); auto lenv = convertToCharArrays(env); int pid_ = fork(); if (!detached) PRIVATE->pid = pid_; if (pid_ == 0) { if (!wd.isEmpty()) { if (!chdir(wd.data())) piCoutObj << "Error while set working directory"; } PRIVATE->closePipe(StdIn, PipeWrite); PRIVATE->closePipe(StdOut, PipeRead); PRIVATE->closePipe(StdErr, PipeRead); if (PRIVATE->grab[StdIn]) dup2(PRIVATE->pipes[StdIn][PipeRead], STDIN_FILENO); if (PRIVATE->grab[StdOut]) dup2(PRIVATE->pipes[StdOut][PipeWrite], STDOUT_FILENO); if (PRIVATE->grab[StdErr]) dup2(PRIVATE->pipes[StdErr][PipeWrite], STDERR_FILENO); PRIVATE->closePipe(StdIn, PipeRead); PRIVATE->closePipe(StdOut, PipeWrite); PRIVATE->closePipe(StdErr, PipeWrite); execve(str.data(), largs, lenv); // normaly execve can't return, if it returns - error occured piCoutObj << "\"execve" << str << args << "\" error :" << errorString(); exit(127); } else { PRIVATE->closePipe(StdIn, PipeRead); PRIVATE->closePipe(StdOut, PipeWrite); PRIVATE->closePipe(StdErr, PipeWrite); if (PRIVATE->grab[StdOut]) fcntl(PRIVATE->pipes[StdOut][PipeRead], F_SETFL, O_NONBLOCK); if (PRIVATE->grab[StdErr]) fcntl(PRIVATE->pipes[StdErr][PipeRead], F_SETFL, O_NONBLOCK); exec_start = true; if (!detached) { waitpid(pid_, &exit_code, 0); if (WIFEXITED(exit_code)) { exec_finished = WEXITSTATUS(exit_code) != 127; } pid_ = 0; if (!detached) PRIVATE->pid = pid_; } } delete[] largs; delete[] lenv; # endif if (!detached) execFinished(str, exit_code); exec_start = false; } void PIProcess::terminate() { # ifdef WINDOWS if (exec_start) { if (!TerminateProcess(PRIVATE->pi.hProcess, 0)) return; } PRIVATE->pi.dwProcessId = 0; # else if (exec_start) 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() { return PRIVATE->readPipe(StdOut); } PIByteArray PIProcess::readError() { return PRIVATE->readPipe(StdErr); } bool PIProcess::writeInput(const PIByteArray & data) { if (PRIVATE->grab[StdIn]) return PRIVATE->writePipe(data); return false; } void PIProcess::closeInput() { if (PRIVATE->grab[StdIn]) { # ifdef WINDOWS PRIVATE->writePipe({0x1A}); # endif PRIVATE->closePipe(StdIn, PipeWrite); } } void PIProcess::enableWriteStdIn(bool on) { PRIVATE->grab[StdIn] = on; } void PIProcess::enableReadStdOut(bool on) { PRIVATE->grab[StdOut] = on; } void PIProcess::enableReadStdErr(bool on) { PRIVATE->grab[StdErr] = on; } 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