/*
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