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:
2025-08-14 10:05:23 +03:00
8 changed files with 508 additions and 222 deletions

View File

@@ -615,7 +615,7 @@ if(NOT PIP_FREERTOS)
add_subdirectory("utils/translator") add_subdirectory("utils/translator")
add_subdirectory("utils/value_tree_translator") add_subdirectory("utils/value_tree_translator")
if(PIP_UTILS AND (NOT CROSSTOOLS)) if(PIP_UTILS AND (NOT CROSSTOOLS))
add_subdirectory("utils/system_test") add_subdirectory("utils/system_calib")
add_subdirectory("utils/udp_file_transfer") add_subdirectory("utils/udp_file_transfer")
if(sodium_FOUND) if(sodium_FOUND)
add_subdirectory("utils/system_daemon") add_subdirectory("utils/system_daemon")

View File

@@ -17,16 +17,17 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "piliterals_time.h"
#include "pitime.h" #include "pitime.h"
#include "pitranslator.h"
#ifndef MICRO_PIP #ifndef MICRO_PIP
# include "piincludes_p.h" # include "piincludes_p.h"
# include "piliterals_bytes.h"
# include "piprocess.h" # include "piprocess.h"
# include "pitranslator.h"
# ifndef WINDOWS # ifndef WINDOWS
# include <csignal> # include <csignal>
# include <sys/wait.h> # include <sys/wait.h>
# include <fcntl.h>
# endif # endif
# ifdef MAC_OS # ifdef MAC_OS
# include <crt_externs.h> # 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 # ifdef WINDOWS
STARTUPINFOA si;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
# else # else
pid_t pid; pid_t pid;
# endif # 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) PRIVATE_DEFINITION_END(PIProcess)
@@ -80,177 +256,129 @@ PIProcess::PIProcess(): PIThread() {
PRIVATE->pi.dwProcessId = 0; PRIVATE->pi.dwProcessId = 0;
# else # else
PRIVATE->pid = 0; PRIVATE->pid = 0;
PRIVATE->forEachPipe([](PipeHandleType & pipe) { pipe = -1; });
# endif # endif
is_exec = false; exec_start = false;
g_in = g_out = g_err = false; exec_finished = false;
t_in = t_out = t_err = false; PRIVATE->initGrab();
PRIVATE->tf_in = PRIVATE->tf_out = PRIVATE->tf_err = 0; env = PIProcess::currentEnvironment();
env = PIProcess::currentEnvironment();
} }
PIProcess::~PIProcess() { PIProcess::~PIProcess() {
PIThread::stopAndWait(); PIThread::stopAndWait();
if (t_in) f_in.remove(); PRIVATE->closeAllPipes();
if (t_out) f_out.remove();
if (t_err) f_err.remove();
} }
void PIProcess::exec_() { void PIProcess::exec_() {
is_exec = false; exec_finished = false;
exec_start = false;
PRIVATE->closeAllPipes();
startOnce(); startOnce();
// cout << "exec wait" << endl;
while (!is_exec)
piMinSleep();
// cout << "exec end" << endl;
} }
void PIProcess::startProc(bool detached) { void PIProcess::startProc(bool detached) {
// cout << "run" << endl; const PIString & str = args.front();
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;
if (!detached) execStarted(str); if (!detached) execStarted(str);
# ifndef WINDOWS if (!PRIVATE->createPipes()) return;
int pid_ = fork(); # 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 (!detached) PRIVATE->pid = pid_;
if (pid_ == 0) { 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 (!wd.isEmpty()) {
if (!chdir(wd.data())) piCoutObj << "Error while set working directory"; if (!chdir(wd.data())) piCoutObj << "Error while set working directory";
} }
// cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl; PRIVATE->closePipe(StdIn, PipeWrite);
if (execve(str.data(), (char * const *)argscc, (char * const *)envcc) < 0) { PRIVATE->closePipe(StdOut, PipeRead);
piCoutObj << "\"execve" << str << args << "\" error :" << errorString(); 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 { } else {
piMinSleep(); PRIVATE->closePipe(StdIn, PipeRead);
// cout << "wait" << endl; 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) { if (!detached) {
waitpid(pid_, &exit_code, 0); waitpid(pid_, &exit_code, 0);
pid_ = 0; if (WIFEXITED(exit_code)) {
exec_finished = WEXITSTATUS(exit_code) != 127;
}
pid_ = 0;
if (!detached) PRIVATE->pid = pid_; if (!detached) PRIVATE->pid = pid_;
// cout << "wait done" << endl;
} }
} }
delete[] largs;
delete[] lenv;
# endif # endif
if (!detached) execFinished(str, exit_code); if (!detached) execFinished(str, exit_code);
is_exec = false; exec_start = 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() { void PIProcess::terminate() {
# ifdef WINDOWS # ifdef WINDOWS
if (is_exec) if (exec_start) {
if (!TerminateProcess(PRIVATE->pi.hProcess, 0)) return; if (!TerminateProcess(PRIVATE->pi.hProcess, 0)) return;
}
PRIVATE->pi.dwProcessId = 0; PRIVATE->pi.dwProcessId = 0;
# else # else
if (is_exec) kill(PRIVATE->pid, SIGKILL); if (exec_start) kill(PRIVATE->pid, SIGKILL);
PRIVATE->pid = 0; PRIVATE->pid = 0;
# endif # endif
} }
bool PIProcess::waitForFinish() {
return PIThread::waitForFinish(1_m);
}
void PIProcess::execIndependent(const PIString & program, const PIStringList & args_) { void PIProcess::execIndependent(const PIString & program, const PIStringList & args_) {
PIProcess p; PIProcess p;
p.args << program << args_; p.args << program << args_;
@@ -266,12 +394,45 @@ int PIProcess::pID() const {
# endif # 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;
} }

View File

@@ -9,18 +9,18 @@
Process Process
Ivan Pelipenko peri4ko@yandex.ru Ivan Pelipenko peri4ko@yandex.ru
This program is free software: you can redistribute it and/or modify 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 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 the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details. GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License 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/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef PIPROCESS_H #ifndef PIPROCESS_H
@@ -28,14 +28,45 @@
#ifndef MICRO_PIP #ifndef MICRO_PIP
# include "pifile.h"
# include "pithread.h" # include "pithread.h"
//! \class PIProcess
//! \ingroup System //! \ingroup System
//! \~\brief //! \~english
//! \~english External process. //! \brief Class for managing external processes
//! \~russian Внешний процесс. //! \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 { class PIP_EXPORT PIProcess: public PIThread {
PIOBJECT_SUBCLASS(PIProcess, PIThread); PIOBJECT_SUBCLASS(PIProcess, PIThread);
@@ -55,74 +86,64 @@ public:
//! \~russian Возвращает ID процесса текущего выполнения //! \~russian Возвращает ID процесса текущего выполнения
int pID() const; 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 //! \~english Returns current attached execution working directory or empty string if it wasn`t set
//! \~russian //! \~russian Возвращает рабочую директорию выполнения или пустую строку, если не установлена
PIString workingDirectory() const { return wd; } PIString workingDirectory() const { return wd; }
//! \~english Set attached execution working directory //! \~english Set attached execution working directory
//! \~russian //! \~russian Устанавливает рабочую директорию для выполнения
void setWorkingDirectory(const PIString & path) { wd = path; } void setWorkingDirectory(const PIString & path) { wd = path; }
//! \~english Rseet attached execution working directory, application working dir will be used //! \~english Rseet attached execution working directory, application working dir will be used
//! \~russian //! \~russian Сбрасывает рабочую директорию, будет использоваться директория приложения
void resetWorkingDirectory() { wd.clear(); } void resetWorkingDirectory() { wd.clear(); }
//! \~english Returns all attached execution output stream //! \~english Returns all attached execution output stream
//! \~russian //! \~russian Возвращает весь вывод из стандартного потока вывода (stdout)
PIByteArray readOutput(bool clear = false); PIByteArray readOutput();
//! \~english Returns all attached execution error stream //! \~english Returns all attached execution error stream
//! \~russian //! \~russian Возвращает весь вывод из потока ошибок (stderr)
PIByteArray readError(bool clear = false); 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 //! \~english Returns current attached execution environment
//! \~russian //! \~russian Возвращает текущее окружение выполнения
PIStringList environment() { return env; } PIStringList environment() { return env; }
//! \~english Clear current attached execution environment. Call before \a exec() //! \~english Clear current attached execution environment. Call before \a exec()
//! \~russian //! \~russian Очищает окружение выполнения. Вызывать перед \a exec()
void clearEnvironment() { env.clear(); } void clearEnvironment() { env.clear(); }
//! \~english Remove variable "variable" from current attached execution environment. Call before \a exec() //! \~english Remove variable "variable" from current attached execution environment. Call before \a exec()
//! \~russian //! \~russian Удаляет переменную "variable" из окружения выполнения. Вызывать перед \a exec()
void removeEnvironmentVariable(const PIString & variable); void removeEnvironmentVariable(const PIString & variable);
//! \~english Set variable "variable" to "value" in current attached execution environment. Call before \a exec() //! \~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); void setEnvironmentVariable(const PIString & variable, const PIString & value);
//! \~english Start attached execution "program" with one argument "arg" //! \~english Start attached execution "program" with one argument "arg"
//! \~russian //! \~russian Запускает выполнение "program" с одним аргументом "arg"
void exec(const PIString & program, const PIString & arg) { void exec(const PIString & program, const PIString & arg) {
args.clear(); args.clear();
args << program << arg; args << program << arg;
@@ -140,36 +161,43 @@ public:
exec_(); exec_();
} }
EVENT_HANDLER(void, terminate); 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); } EVENT_HANDLER1(bool, waitForFinish, PISystemTime, timeout) { return PIThread::waitForFinish(timeout); }
EVENT1(execStarted, PIString, program); EVENT1(execStarted, PIString, program);
EVENT2(execFinished, PIString, program, int, exit_code); 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 //! \~english Start detached execution "program" without arguments
//! \~russian //! \~russian Запускает независимое выполнение "program" без аргументов
static void execIndependent(const PIString & program) { execIndependent(program, PIStringList()); } static void execIndependent(const PIString & program) { execIndependent(program, PIStringList()); }
//! \~english Start detached execution "program" with one argument "arg" //! \~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); } static void execIndependent(const PIString & program, const PIString & arg) { execIndependent(program, PIStringList() << arg); }
//! \~english Start detached execution "program" with arguments "args" //! \~english Start detached execution "program" with arguments "args"
//! \~russian //! \~russian Запускает независимое выполнение "program" с аргументами "args"
static void execIndependent(const PIString & program, const PIStringList & args); static void execIndependent(const PIString & program, const PIStringList & args);
//! \~english Returns application environment //! \~english Returns application environment
//! \~russian //! \~russian Возвращает окружение текущего приложения
static PIStringList currentEnvironment(); static PIStringList currentEnvironment();
//! \~english Returns application process ID //! \~english Returns application process ID
//! \~russian //! \~russian Возвращает ID процесса текущего приложения
static int currentPID(); static int currentPID();
//! \~english Returns variable "variable" value from application environment //! \~english Returns variable "variable" value from application environment
//! \~russian //! \~russian Возвращает значение переменной "variable" из окружения приложения
static PIString getEnvironmentVariable(const PIString & variable); static PIString getEnvironmentVariable(const PIString & variable);
//! \handlers //! \handlers
@@ -178,27 +206,27 @@ public:
//! \fn void exec(const PIString & program) //! \fn void exec(const PIString & program)
//! \brief //! \brief
//! \~english Start attached execution "program" without arguments //! \~english Start attached execution "program" without arguments
//! \~russian //! \~russian Запускает выполнение "program" без аргументов
//! \fn void exec(const PIString & program, const PIStringList & args) //! \fn void exec(const PIString & program, const PIStringList & args)
//! \brief //! \brief
//! \~english Start attached execution "program" with arguments "args" //! \~english Start attached execution "program" with arguments "args"
//! \~russian //! \~russian Запускает выполнение "program" с аргументами "args"
//! \fn void terminate() //! \fn void terminate()
//! \brief //! \brief
//! \~english Immediately terminate attached execution //! \~english Immediately terminate attached execution
//! \~russian //! \~russian Немедленно завершает выполнение
//! \fn bool waitForFinish() //! \fn bool waitForFinish()
//! \brief //! \brief
//! \~english Wait for attached execution finish maximum for 60 seconds //! \~english Wait for attached execution finish maximum for 60 seconds
//! \~russian //! \~russian Ожидает завершения выполнения (максимум 60 секунд)
//! \fn bool waitForFinish(PISystemTime timeout) //! \fn bool waitForFinish(PISystemTime timeout)
//! \brief //! \brief
//! \~english Wait for attached execution finish maximum for "timeout_" //! \~english Wait for attached execution finish maximum for "timeout_"
//! \~russian //! \~russian Ожидает завершения выполнения в течение "timeout_"
//! \} //! \}
//! \events //! \events
@@ -207,12 +235,12 @@ public:
//! \fn void execStarted(PIString program) //! \fn void execStarted(PIString program)
//! \brief //! \brief
//! \~english Raise on attached execution start //! \~english Raise on attached execution start
//! \~russian //! \~russian Генерируется при запуске выполнения
//! \fn void execFinished(PIString program) //! \fn void execFinished(PIString program)
//! \brief //! \brief
//! \~english Raise on attached execution finish //! \~english Raise on attached execution finish
//! \~russian //! \~russian Генерируется при завершении выполнения
//! \} //! \}
@@ -220,16 +248,14 @@ private:
void run() override; void run() override;
void exec_(); void exec_();
void startProc(bool detached); void startProc(bool detached);
PIByteArray readFile(PIFile & f, bool clear);
private:
PRIVATE_DECLARATION(PIP_EXPORT) PRIVATE_DECLARATION(PIP_EXPORT)
PIStringList args, env; PIStringList args, env;
PIString wd; 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; int exit_code;
bool is_exec; std::atomic_bool exec_start;
std::atomic_bool exec_finished;
}; };
#endif // MICRO_PIP #endif // MICRO_PIP

View File

@@ -36,3 +36,4 @@ pip_test(core)
pip_test(piobject) pip_test(piobject)
pip_test(client_server pip_client_server) pip_test(client_server pip_client_server)
pip_test(io) pip_test(io)
pip_test(system)

View 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());
}

View 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 ()

View File

@@ -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 ()