Merge pull request 'piprocess' (#189) from piprocess into master
Reviewed-on: #189
This commit was merged in pull request #189.
This commit is contained in:
@@ -615,7 +615,7 @@ if(NOT PIP_FREERTOS)
|
||||
add_subdirectory("utils/translator")
|
||||
add_subdirectory("utils/value_tree_translator")
|
||||
if(PIP_UTILS AND (NOT CROSSTOOLS))
|
||||
add_subdirectory("utils/system_test")
|
||||
add_subdirectory("utils/system_calib")
|
||||
add_subdirectory("utils/udp_file_transfer")
|
||||
if(sodium_FOUND)
|
||||
add_subdirectory("utils/system_daemon")
|
||||
|
||||
@@ -17,16 +17,17 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "piliterals_time.h"
|
||||
#include "pitime.h"
|
||||
#include "pitranslator.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>
|
||||
@@ -63,14 +64,189 @@
|
||||
//!
|
||||
|
||||
|
||||
PRIVATE_DEFINITION_START(PIProcess)
|
||||
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
|
||||
STARTUPINFOA si;
|
||||
PROCESS_INFORMATION pi;
|
||||
# else
|
||||
pid_t pid;
|
||||
# endif
|
||||
FILE *tf_in, *tf_out, *tf_err;
|
||||
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;
|
||||
bytes_read = 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)
|
||||
|
||||
|
||||
@@ -80,177 +256,129 @@ PIProcess::PIProcess(): PIThread() {
|
||||
PRIVATE->pi.dwProcessId = 0;
|
||||
# else
|
||||
PRIVATE->pid = 0;
|
||||
PRIVATE->forEachPipe([](PipeHandleType & pipe) { pipe = -1; });
|
||||
# endif
|
||||
is_exec = false;
|
||||
g_in = g_out = g_err = false;
|
||||
t_in = t_out = t_err = false;
|
||||
PRIVATE->tf_in = PRIVATE->tf_out = PRIVATE->tf_err = 0;
|
||||
env = PIProcess::currentEnvironment();
|
||||
exec_start = false;
|
||||
exec_finished = false;
|
||||
PRIVATE->initGrab();
|
||||
env = PIProcess::currentEnvironment();
|
||||
}
|
||||
|
||||
|
||||
PIProcess::~PIProcess() {
|
||||
PIThread::stopAndWait();
|
||||
if (t_in) f_in.remove();
|
||||
if (t_out) f_out.remove();
|
||||
if (t_err) f_err.remove();
|
||||
PRIVATE->closeAllPipes();
|
||||
}
|
||||
|
||||
|
||||
void PIProcess::exec_() {
|
||||
is_exec = false;
|
||||
exec_finished = false;
|
||||
exec_start = false;
|
||||
PRIVATE->closeAllPipes();
|
||||
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
|
||||
t_in = t_out = t_err = false;
|
||||
if (f_in.path().isEmpty()) {
|
||||
f_in.openTemporary(PIIODevice::ReadWrite);
|
||||
t_in = true;
|
||||
}
|
||||
if (f_out.path().isEmpty()) {
|
||||
f_out.openTemporary(PIIODevice::ReadWrite);
|
||||
t_out = true;
|
||||
}
|
||||
if (f_err.path().isEmpty()) {
|
||||
f_err.openTemporary(PIIODevice::ReadWrite);
|
||||
t_err = true;
|
||||
}
|
||||
|
||||
str = args.front();
|
||||
is_exec = true;
|
||||
const PIString & str = args.front();
|
||||
if (!detached) execStarted(str);
|
||||
# ifndef WINDOWS
|
||||
int pid_ = fork();
|
||||
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) {
|
||||
# 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
|
||||
GetStartupInfoA(&(PRIVATE->si));
|
||||
piZeroMemory(PRIVATE->pi);
|
||||
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
|
||||
&(PRIVATE->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();
|
||||
}
|
||||
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 {
|
||||
piMinSleep();
|
||||
// cout << "wait" << endl;
|
||||
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);
|
||||
pid_ = 0;
|
||||
if (WIFEXITED(exit_code)) {
|
||||
exec_finished = WEXITSTATUS(exit_code) != 127;
|
||||
}
|
||||
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;
|
||||
# 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;
|
||||
exec_start = false;
|
||||
}
|
||||
|
||||
|
||||
void PIProcess::terminate() {
|
||||
# ifdef WINDOWS
|
||||
if (is_exec)
|
||||
if (exec_start) {
|
||||
if (!TerminateProcess(PRIVATE->pi.hProcess, 0)) return;
|
||||
}
|
||||
PRIVATE->pi.dwProcessId = 0;
|
||||
# else
|
||||
if (is_exec) kill(PRIVATE->pid, SIGKILL);
|
||||
if (exec_start) kill(PRIVATE->pid, SIGKILL);
|
||||
PRIVATE->pid = 0;
|
||||
# endif
|
||||
}
|
||||
|
||||
|
||||
bool PIProcess::waitForFinish() {
|
||||
return PIThread::waitForFinish(1_m);
|
||||
}
|
||||
|
||||
|
||||
void PIProcess::execIndependent(const PIString & program, const PIStringList & args_) {
|
||||
PIProcess p;
|
||||
p.args << program << args_;
|
||||
@@ -266,12 +394,45 @@ int PIProcess::pID() const {
|
||||
# endif
|
||||
}
|
||||
|
||||
PIByteArray PIProcess::readOutput(bool clear) {
|
||||
return readFile(f_out, clear);
|
||||
|
||||
PIByteArray PIProcess::readOutput() {
|
||||
return PRIVATE->readPipe(StdOut);
|
||||
}
|
||||
|
||||
PIByteArray PIProcess::readError(bool clear) {
|
||||
return readFile(f_err, clear);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,18 +9,18 @@
|
||||
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 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.
|
||||
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/>.
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#ifndef PIPROCESS_H
|
||||
@@ -28,14 +28,45 @@
|
||||
|
||||
#ifndef MICRO_PIP
|
||||
|
||||
# include "pifile.h"
|
||||
# include "pithread.h"
|
||||
|
||||
|
||||
//! \class PIProcess
|
||||
//! \ingroup System
|
||||
//! \~\brief
|
||||
//! \~english External process.
|
||||
//! \~russian Внешний процесс.
|
||||
//! \~english
|
||||
//! \brief Class for managing external processes
|
||||
//! \details
|
||||
//! The PIProcess class provides functionality to create, control and interact with external processes.
|
||||
//! It allows both attached execution (with full control over input/output streams) and detached execution.
|
||||
//!
|
||||
//! Key features:
|
||||
//! - Start processes with arguments and environment variables
|
||||
//! - Monitor process state (running/finished)
|
||||
//! - Read from stdout/stderr streams
|
||||
//! - Write to stdin stream
|
||||
//! - Set custom working directory
|
||||
//! - Modify environment variables
|
||||
//! - Wait for process completion
|
||||
//! - Terminate processes
|
||||
//! - Retrieve exit codes and process IDs
|
||||
//!
|
||||
//! This class inherits from PIThread and provides event-based notifications for process lifecycle events.
|
||||
//! \~russian
|
||||
//! \brief Класс для управления внешними процессами
|
||||
//! \details
|
||||
//! Класс PIProcess предоставляет функциональность для создания, управления и взаимодействия с внешними процессами.
|
||||
//! Поддерживает как присоединенное выполнение (с полным контролем потоков ввода/вывода), так и независимое выполнение.
|
||||
//!
|
||||
//! Основные возможности:
|
||||
//! - Запуск процессов с аргументами и переменными окружения
|
||||
//! - Мониторинг состояния процесса (запущен/завершен)
|
||||
//! - Чтение из потоков stdout/stderr
|
||||
//! - Запись в поток stdin
|
||||
//! - Установка рабочей директории
|
||||
//! - Изменение переменных окружения
|
||||
//! - Ожидание завершения процесса
|
||||
//! - Завершение процессов
|
||||
//! - Получение кодов завершения и идентификаторов процессов
|
||||
//!
|
||||
class PIP_EXPORT PIProcess: public PIThread {
|
||||
PIOBJECT_SUBCLASS(PIProcess, PIThread);
|
||||
|
||||
@@ -55,74 +86,64 @@ public:
|
||||
//! \~russian Возвращает ID процесса текущего выполнения
|
||||
int pID() const;
|
||||
|
||||
void setGrabInput(bool yes) { g_in = yes; }
|
||||
|
||||
//! \~english Set attached execution grab output stream enabled
|
||||
//! \~russian
|
||||
void setGrabOutput(bool yes) { g_out = yes; }
|
||||
|
||||
//! \~english Set attached execution grab error stream enabled
|
||||
//! \~russian
|
||||
void setGrabError(bool yes) { g_err = yes; }
|
||||
|
||||
void setInputFile(const PIString & path) { f_in.setPath(path); }
|
||||
|
||||
//! \~english Set attached execution grab output stream file
|
||||
//! \~russian
|
||||
void setOutputFile(const PIString & path) { f_out.setPath(path); }
|
||||
|
||||
//! \~english Set attached execution grab error stream file
|
||||
//! \~russian
|
||||
void setErrorFile(const PIString & path) { f_err.setPath(path); }
|
||||
|
||||
void unsetInputFile() { f_in.setPath(""); }
|
||||
|
||||
//! \~english Reset attached execution grab output stream file
|
||||
//! \~russian
|
||||
void unsetOutputFile() { f_out.setPath(""); }
|
||||
|
||||
//! \~english Reset attached execution grab error stream file
|
||||
//! \~russian
|
||||
void unsetErrorFile() { f_err.setPath(""); }
|
||||
|
||||
//! \~english Returns current attached execution working directory or empty string if it wasn`t set
|
||||
//! \~russian
|
||||
//! \~russian Возвращает рабочую директорию выполнения или пустую строку, если не установлена
|
||||
PIString workingDirectory() const { return wd; }
|
||||
|
||||
//! \~english Set attached execution working directory
|
||||
//! \~russian
|
||||
//! \~russian Устанавливает рабочую директорию для выполнения
|
||||
void setWorkingDirectory(const PIString & path) { wd = path; }
|
||||
|
||||
//! \~english Rseet attached execution working directory, application working dir will be used
|
||||
//! \~russian
|
||||
//! \~russian Сбрасывает рабочую директорию, будет использоваться директория приложения
|
||||
void resetWorkingDirectory() { wd.clear(); }
|
||||
|
||||
//! \~english Returns all attached execution output stream
|
||||
//! \~russian
|
||||
PIByteArray readOutput(bool clear = false);
|
||||
//! \~russian Возвращает весь вывод из стандартного потока вывода (stdout)
|
||||
PIByteArray readOutput();
|
||||
|
||||
//! \~english Returns all attached execution error stream
|
||||
//! \~russian
|
||||
PIByteArray readError(bool clear = false);
|
||||
//! \~russian Возвращает весь вывод из потока ошибок (stderr)
|
||||
PIByteArray readError();
|
||||
|
||||
//! \~english Write data to attached execution input stream
|
||||
//! \~russian Записывает данные в стандартный поток ввода (stdin)
|
||||
bool writeInput(const PIByteArray & data);
|
||||
|
||||
//! \~english Close attached execution input stream and send EOF
|
||||
//! \~russian Закрывает поток ввода (stdin) и отправляет EOF
|
||||
void closeInput();
|
||||
|
||||
//! \~english Enable or disable writing to process stdin
|
||||
//! \~russian Включает или отключает запись в стандартный поток ввода (stdin) процесса
|
||||
void enableWriteStdIn(bool on = true);
|
||||
|
||||
//! \~english Enable or disable reading from process stdout
|
||||
//! \~russian Включает или отключает чтение из стандартного потока вывода (stdout) процесса
|
||||
void enableReadStdOut(bool on = true);
|
||||
|
||||
//! \~english Enable or disable reading from process stderr
|
||||
//! \~russian Включает или отключает чтение из потока ошибок (stderr) процесса
|
||||
void enableReadStdErr(bool on = true);
|
||||
|
||||
//! \~english Returns current attached execution environment
|
||||
//! \~russian
|
||||
//! \~russian Возвращает текущее окружение выполнения
|
||||
PIStringList environment() { return env; }
|
||||
|
||||
//! \~english Clear current attached execution environment. Call before \a exec()
|
||||
//! \~russian
|
||||
//! \~russian Очищает окружение выполнения. Вызывать перед \a exec()
|
||||
void clearEnvironment() { env.clear(); }
|
||||
|
||||
//! \~english Remove variable "variable" from current attached execution environment. Call before \a exec()
|
||||
//! \~russian
|
||||
//! \~russian Удаляет переменную "variable" из окружения выполнения. Вызывать перед \a exec()
|
||||
void removeEnvironmentVariable(const PIString & variable);
|
||||
|
||||
//! \~english Set variable "variable" to "value" in current attached execution environment. Call before \a exec()
|
||||
//! \~russian
|
||||
//! \~russian Устанавливает значение "value" для переменной "variable" в окружении выполнения. Вызывать перед \a exec()
|
||||
void setEnvironmentVariable(const PIString & variable, const PIString & value);
|
||||
|
||||
//! \~english Start attached execution "program" with one argument "arg"
|
||||
//! \~russian
|
||||
//! \~russian Запускает выполнение "program" с одним аргументом "arg"
|
||||
void exec(const PIString & program, const PIString & arg) {
|
||||
args.clear();
|
||||
args << program << arg;
|
||||
@@ -140,36 +161,43 @@ public:
|
||||
exec_();
|
||||
}
|
||||
EVENT_HANDLER(void, terminate);
|
||||
EVENT_HANDLER(bool, waitForFinish);
|
||||
EVENT_HANDLER(bool, waitForFinish) { return PIThread::waitForFinish(); }
|
||||
EVENT_HANDLER1(bool, waitForFinish, PISystemTime, timeout) { return PIThread::waitForFinish(timeout); }
|
||||
|
||||
EVENT1(execStarted, PIString, program);
|
||||
EVENT2(execFinished, PIString, program, int, exit_code);
|
||||
|
||||
//! \~english Check if attached execution has finished
|
||||
//! \~russian Проверяет, завершилось ли выполнение процесса
|
||||
bool isExecFinished() const { return exec_finished; }
|
||||
|
||||
//! \~english Check if attached execution has started
|
||||
//! \~russian Проверяет, запущен ли процесс выполнения
|
||||
bool isExecStarted() const { return exec_start; }
|
||||
|
||||
//! \~english Start detached execution "program" without arguments
|
||||
//! \~russian
|
||||
//! \~russian Запускает независимое выполнение "program" без аргументов
|
||||
static void execIndependent(const PIString & program) { execIndependent(program, PIStringList()); }
|
||||
|
||||
//! \~english Start detached execution "program" with one argument "arg"
|
||||
//! \~russian
|
||||
//! \~russian Запускает независимое выполнение "program" с одним аргументом "arg"
|
||||
static void execIndependent(const PIString & program, const PIString & arg) { execIndependent(program, PIStringList() << arg); }
|
||||
|
||||
//! \~english Start detached execution "program" with arguments "args"
|
||||
//! \~russian
|
||||
//! \~russian Запускает независимое выполнение "program" с аргументами "args"
|
||||
static void execIndependent(const PIString & program, const PIStringList & args);
|
||||
|
||||
|
||||
//! \~english Returns application environment
|
||||
//! \~russian
|
||||
//! \~russian Возвращает окружение текущего приложения
|
||||
static PIStringList currentEnvironment();
|
||||
|
||||
//! \~english Returns application process ID
|
||||
//! \~russian
|
||||
//! \~russian Возвращает ID процесса текущего приложения
|
||||
static int currentPID();
|
||||
|
||||
//! \~english Returns variable "variable" value from application environment
|
||||
//! \~russian
|
||||
//! \~russian Возвращает значение переменной "variable" из окружения приложения
|
||||
static PIString getEnvironmentVariable(const PIString & variable);
|
||||
|
||||
//! \handlers
|
||||
@@ -178,27 +206,27 @@ public:
|
||||
//! \fn void exec(const PIString & program)
|
||||
//! \brief
|
||||
//! \~english Start attached execution "program" without arguments
|
||||
//! \~russian
|
||||
//! \~russian Запускает выполнение "program" без аргументов
|
||||
|
||||
//! \fn void exec(const PIString & program, const PIStringList & args)
|
||||
//! \brief
|
||||
//! \~english Start attached execution "program" with arguments "args"
|
||||
//! \~russian
|
||||
//! \~russian Запускает выполнение "program" с аргументами "args"
|
||||
|
||||
//! \fn void terminate()
|
||||
//! \brief
|
||||
//! \~english Immediately terminate attached execution
|
||||
//! \~russian
|
||||
//! \~russian Немедленно завершает выполнение
|
||||
|
||||
//! \fn bool waitForFinish()
|
||||
//! \brief
|
||||
//! \~english Wait for attached execution finish maximum for 60 seconds
|
||||
//! \~russian
|
||||
//! \~russian Ожидает завершения выполнения (максимум 60 секунд)
|
||||
|
||||
//! \fn bool waitForFinish(PISystemTime timeout)
|
||||
//! \brief
|
||||
//! \~english Wait for attached execution finish maximum for "timeout_"
|
||||
//! \~russian
|
||||
//! \~russian Ожидает завершения выполнения в течение "timeout_"
|
||||
|
||||
//! \}
|
||||
//! \events
|
||||
@@ -207,12 +235,12 @@ public:
|
||||
//! \fn void execStarted(PIString program)
|
||||
//! \brief
|
||||
//! \~english Raise on attached execution start
|
||||
//! \~russian
|
||||
//! \~russian Генерируется при запуске выполнения
|
||||
|
||||
//! \fn void execFinished(PIString program)
|
||||
//! \brief
|
||||
//! \~english Raise on attached execution finish
|
||||
//! \~russian
|
||||
//! \~russian Генерируется при завершении выполнения
|
||||
|
||||
//! \}
|
||||
|
||||
@@ -220,16 +248,14 @@ private:
|
||||
void run() override;
|
||||
void exec_();
|
||||
void startProc(bool detached);
|
||||
PIByteArray readFile(PIFile & f, bool clear);
|
||||
|
||||
private:
|
||||
PRIVATE_DECLARATION(PIP_EXPORT)
|
||||
PIStringList args, env;
|
||||
PIString wd;
|
||||
PIByteArray out;
|
||||
PIFile f_in, f_out, f_err;
|
||||
bool g_in, g_out, g_err, t_in, t_out, t_err;
|
||||
int exit_code;
|
||||
bool is_exec;
|
||||
std::atomic_bool exec_start;
|
||||
std::atomic_bool exec_finished;
|
||||
};
|
||||
|
||||
#endif // MICRO_PIP
|
||||
|
||||
@@ -36,3 +36,4 @@ pip_test(core)
|
||||
pip_test(piobject)
|
||||
pip_test(client_server pip_client_server)
|
||||
pip_test(io)
|
||||
pip_test(system)
|
||||
|
||||
98
tests/system/process_test.cpp
Normal file
98
tests/system/process_test.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "piprocess.h"
|
||||
#include "pitime.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
|
||||
class ProcessTest: public ::testing::Test {
|
||||
protected:
|
||||
PIProcess launcher;
|
||||
const PIString command =
|
||||
#ifdef _WIN32
|
||||
"C:/Windows/System32/cmd.exe";
|
||||
#else
|
||||
"/bin/sh";
|
||||
#endif
|
||||
|
||||
void SetUp() override {
|
||||
launcher.enableWriteStdIn(false);
|
||||
launcher.enableReadStdOut();
|
||||
launcher.enableReadStdErr();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
if (launcher.isRunning()) {
|
||||
launcher.waitForFinish();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TEST_F(ProcessTest, Output) {
|
||||
#ifdef _WIN32
|
||||
const PIStringList args = {"/c", "echo Hello from stdout && echo Hello from stderr 1>&2"};
|
||||
#else
|
||||
const PIStringList args = {"-c", "echo Hello from stdout; echo Hello from stderr 1>&2"};
|
||||
#endif
|
||||
|
||||
launcher.exec(command, args);
|
||||
ASSERT_TRUE(launcher.isRunning());
|
||||
|
||||
ASSERT_TRUE(launcher.waitForFinish());
|
||||
ASSERT_TRUE(launcher.isExecFinished());
|
||||
|
||||
const auto out = PIString::fromAscii(launcher.readOutput());
|
||||
const auto err = PIString::fromAscii(launcher.readError());
|
||||
|
||||
EXPECT_TRUE(out.contains("Hello from stdout"));
|
||||
EXPECT_TRUE(err.contains("Hello from stderr"));
|
||||
|
||||
const int exit_code = launcher.exitCode();
|
||||
EXPECT_FALSE(launcher.isRunning());
|
||||
|
||||
#ifdef _WIN32
|
||||
EXPECT_EQ(exit_code, 0);
|
||||
#else
|
||||
EXPECT_TRUE(WIFEXITED(exit_code));
|
||||
EXPECT_EQ(WEXITSTATUS(exit_code), 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
TEST_F(ProcessTest, Input) {
|
||||
#ifdef _WIN32
|
||||
const PIStringList args = {"/c", "more"};
|
||||
#else
|
||||
const PIStringList args = {"-c", "read input; echo $input"};
|
||||
#endif
|
||||
|
||||
launcher.enableWriteStdIn();
|
||||
launcher.exec(command, args);
|
||||
ASSERT_TRUE(launcher.isRunning());
|
||||
piMSleep(100);
|
||||
EXPECT_TRUE(launcher.isExecStarted());
|
||||
EXPECT_TRUE(!launcher.isExecFinished());
|
||||
|
||||
PIString test_input = "Test input string\n";
|
||||
EXPECT_TRUE(launcher.writeInput(test_input.toAscii()));
|
||||
launcher.closeInput();
|
||||
|
||||
ASSERT_TRUE(launcher.waitForFinish());
|
||||
EXPECT_TRUE(launcher.isExecFinished());
|
||||
|
||||
const auto out = PIString::fromAscii(launcher.readOutput());
|
||||
EXPECT_TRUE(out.contains("Test input string"));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(ProcessTest, NonexistentCommand) {
|
||||
const PIString command = {"nonexistent_command_12345"};
|
||||
|
||||
launcher.enableWriteStdIn(false);
|
||||
launcher.enableReadStdOut(false);
|
||||
launcher.enableReadStdErr(false);
|
||||
launcher.exec(command);
|
||||
ASSERT_TRUE(launcher.isRunning());
|
||||
ASSERT_TRUE(launcher.waitForFinish());
|
||||
EXPECT_FALSE(launcher.isExecFinished());
|
||||
}
|
||||
7
utils/system_calib/CMakeLists.txt
Executable file
7
utils/system_calib/CMakeLists.txt
Executable file
@@ -0,0 +1,7 @@
|
||||
list(APPEND PIP_UTILS_LIST "pip_system_calib")
|
||||
set(PIP_UTILS_LIST ${PIP_UTILS_LIST} PARENT_SCOPE)
|
||||
add_executable(pip_system_calib "main.cpp")
|
||||
target_link_libraries(pip_system_calib pip)
|
||||
if (DEFINED LIB)
|
||||
install(TARGETS pip_system_calib DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
|
||||
endif ()
|
||||
@@ -1,7 +0,0 @@
|
||||
list(APPEND PIP_UTILS_LIST "pip_system_test")
|
||||
set(PIP_UTILS_LIST ${PIP_UTILS_LIST} PARENT_SCOPE)
|
||||
add_executable(pip_system_test "main.cpp")
|
||||
target_link_libraries(pip_system_test pip)
|
||||
if (DEFINED LIB)
|
||||
install(TARGETS pip_system_test DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
|
||||
endif ()
|
||||
Reference in New Issue
Block a user