Files
pip/libs/main/system/piprocess.cpp

503 lines
12 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
*/
#include "pitime.h"
#ifndef MICRO_PIP
# include "piincludes_p.h"
# include "piliterals_bytes.h"
# include "piprocess.h"
# include "pitranslator.h"
# ifndef WINDOWS
# include <csignal>
# include <sys/wait.h>
# include <fcntl.h>
# endif
# ifdef MAC_OS
# include <crt_externs.h>
# 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<char *>(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<void(PipeHandleType &)> 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<StdFile>(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) {
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