diff --git a/CMakeLists.txt b/CMakeLists.txt
index 04764845..f44a16e3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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")
diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp
index cedd417e..74730ec4 100644
--- a/libs/main/system/piprocess.cpp
+++ b/libs/main/system/piprocess.cpp
@@ -17,16 +17,17 @@
along with this program. If not, see .
*/
-#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
# include
+# include
# endif
# ifdef MAC_OS
# include
@@ -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(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 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(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;
}
diff --git a/libs/main/system/piprocess.h b/libs/main/system/piprocess.h
index 419776ee..e5841a64 100644
--- a/libs/main/system/piprocess.h
+++ b/libs/main/system/piprocess.h
@@ -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 .
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see .
*/
#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
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index c5dae9c3..a5ba75e2 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -36,3 +36,4 @@ pip_test(core)
pip_test(piobject)
pip_test(client_server pip_client_server)
pip_test(io)
+pip_test(system)
diff --git a/tests/system/process_test.cpp b/tests/system/process_test.cpp
new file mode 100644
index 00000000..3a04b53d
--- /dev/null
+++ b/tests/system/process_test.cpp
@@ -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());
+}
diff --git a/utils/system_calib/CMakeLists.txt b/utils/system_calib/CMakeLists.txt
new file mode 100755
index 00000000..1cc42fce
--- /dev/null
+++ b/utils/system_calib/CMakeLists.txt
@@ -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 ()
diff --git a/utils/system_test/main.cpp b/utils/system_calib/main.cpp
similarity index 100%
rename from utils/system_test/main.cpp
rename to utils/system_calib/main.cpp
diff --git a/utils/system_test/CMakeLists.txt b/utils/system_test/CMakeLists.txt
deleted file mode 100755
index e93f6be7..00000000
--- a/utils/system_test/CMakeLists.txt
+++ /dev/null
@@ -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 ()