389 lines
9.4 KiB
C++
389 lines
9.4 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 "piprocess.h"
|
|
# ifndef WINDOWS
|
|
# include <csignal>
|
|
# include <sys/wait.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;
|
|
# else
|
|
using PipeHandleType = int;
|
|
# endif
|
|
|
|
# ifdef WINDOWS
|
|
const char * convertWindowsCmd(PIStringList sl) {
|
|
for (int i = 0; i < sl.size_s(); ++i) {
|
|
sl[i].push_front('"');
|
|
sl[i].push_back('"');
|
|
}
|
|
PIString s = sl.join(' ');
|
|
return s.data();
|
|
}
|
|
# 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 = static_cast<int>(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;
|
|
PRIVATE->forEachPipe([](PipeHandleType & pipe) { pipe = -1; });
|
|
# endif
|
|
is_exec = false;
|
|
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;
|
|
const PIString & str = args.front();
|
|
/// arguments convertion
|
|
|
|
/// files for stdin/out/err
|
|
|
|
is_exec = true;
|
|
if (!detached) execStarted(str);
|
|
|
|
// 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)
|
|
convertWindowsCmd(args), // 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, // 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());
|
|
}
|
|
# 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";
|
|
}
|
|
// cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl;
|
|
if (execve(str.data(), largs, lenv) < 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;
|
|
}
|
|
}
|
|
delete[] largs;
|
|
delete[] lenv;
|
|
# endif
|
|
if (!detached) execFinished(str, exit_code);
|
|
is_exec = false;
|
|
}
|
|
|
|
// 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() {
|
|
return {}; // readFile(f_out, clear);
|
|
}
|
|
|
|
|
|
PIByteArray PIProcess::readError() {
|
|
return {}; // readFile(f_err, clear);
|
|
}
|
|
|
|
bool PIProcess::writeInput(const PIByteArray &data)
|
|
{
|
|
if (PRIVATE->grab[StdIn]) {
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void PIProcess::closeInput()
|
|
{
|
|
if (PRIVATE->grab[StdIn]) {
|
|
|
|
}
|
|
}
|
|
|
|
|
|
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
|