From 6ffbbbe63615ec6c34252525e0d7b9641c0372ec Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Tue, 12 Aug 2025 21:44:50 +0300 Subject: [PATCH 01/12] starting pipes --- libs/main/system/piprocess.cpp | 134 ++++++++++++++++++++++----------- libs/main/system/piprocess.h | 35 +-------- 2 files changed, 93 insertions(+), 76 deletions(-) diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp index cedd417e..129063dd 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -63,14 +63,72 @@ //! +namespace { +enum class PipeDirection { + Read, + Write, + Last = Write +}; + +enum class StdFile { + In, + Out, + Err, + Last = Err +}; + +constexpr int PipesDirections = static_cast(PipeDirection::Last) + 1; +constexpr int StdFileCount = static_cast(StdFile::Last) + 1; + +#ifdef WINDOWS +using PipeHandleType = HANDLE; +#else +using PipeHandleType = int; +#endif +} + + PRIVATE_DEFINITION_START(PIProcess) # ifdef WINDOWS - 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 = static_cast(pipe_type); +#ifdef WINDOWS + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + if (!CreatePipe(&(pipes[pt][PipeDirection::Read]), &(pipes[pt][PipeDirection::Write]), &saAttr, 0)) { + piCout << "CreatePipe failed: " << GetLastError() << std::endl; + return false; + } + return true; +#else + int ret = pipe(pipes[pt]); + return ret != -1; +#endif + return false; + } PRIVATE_DEFINITION_END(PIProcess) @@ -82,18 +140,14 @@ PIProcess::PIProcess(): PIThread() { PRIVATE->pid = 0; # 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(); + PRIVATE->forEachPipe([](PipeHandleType & pipe) { pipe = 0;}); + 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(); } @@ -143,19 +197,6 @@ void PIProcess::startProc(bool detached) { } # 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; @@ -165,16 +206,21 @@ void PIProcess::startProc(bool detached) { 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); + // 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); + STARTUPINFOA si; + piZeroMemory(pi); + si.cb = sizeof(STARTUPINFOA); + if (PRIVATE->grab[StdFile::In]) si.hStdInput = PRIVATE->pipes[StdFile::In][PipeDirection::Read]; + if (PRIVATE->grab[StdFile::Out]) si.hStdOutput = PRIVATE->pipes[StdFile::Out][PipeDirection::Write]; + if (PRIVATE->grab[StdFile::Err]) si.hStdError = PRIVATE->pipes[StdFile::Err][PipeDirection::Write]; + si.dwFlags |= STARTF_USESTDHANDLES; if (CreateProcessA(0, // No module name (use command line) a, // Command line 0, // Process handle not inheritable @@ -183,7 +229,7 @@ void PIProcess::startProc(bool detached) { 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 + &si, // Pointer to STARTUPINFO structure &(PRIVATE->pi))) // Pointer to PROCESS_INFORMATION structure { if (!detached) { @@ -223,21 +269,23 @@ void PIProcess::startProc(bool detached) { # endif } -PIByteArray PIProcess::readFile(PIFile & f, bool clear) -{ - f.open(PIIODevice::ReadOnly); - const auto ret = f.readAll(); - if (clear) { - f.clear(); - } - return ret; -} +// PIByteArray PIProcess::readFile(PIFile & f, bool clear) +// { +// f.open(PIIODevice::ReadOnly); +// const auto ret = f.readAll(); +// if (clear) { +// f.clear(); +// } +// return ret; +// } void PIProcess::terminate() { # ifdef WINDOWS if (is_exec) + { if (!TerminateProcess(PRIVATE->pi.hProcess, 0)) return; + } PRIVATE->pi.dwProcessId = 0; # else if (is_exec) kill(PRIVATE->pid, SIGKILL); @@ -266,12 +314,14 @@ int PIProcess::pID() const { # endif } + PIByteArray PIProcess::readOutput(bool clear) { - return readFile(f_out, clear); + return {}; //readFile(f_out, clear); } + PIByteArray PIProcess::readError(bool clear) { - return readFile(f_err, clear); + return {}; //readFile(f_err, clear); } diff --git a/libs/main/system/piprocess.h b/libs/main/system/piprocess.h index 419776ee..e82b03d5 100644 --- a/libs/main/system/piprocess.h +++ b/libs/main/system/piprocess.h @@ -55,36 +55,6 @@ 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 PIString workingDirectory() const { return wd; } @@ -220,14 +190,11 @@ 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; }; -- 2.43.0 From 30c4f215a2986d5c2d73824a806b3d37e919ac52 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 13 Aug 2025 10:32:42 +0300 Subject: [PATCH 02/12] remove unused includes --- libs/main/system/piprocess.cpp | 7 ------- libs/main/system/piprocess.h | 3 +-- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp index 129063dd..7d4a84b1 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -17,9 +17,7 @@ along with this program. If not, see . */ -#include "piliterals_time.h" #include "pitime.h" -#include "pitranslator.h" #ifndef MICRO_PIP # include "piincludes_p.h" @@ -294,11 +292,6 @@ void PIProcess::terminate() { } -bool PIProcess::waitForFinish() { - return PIThread::waitForFinish(1_m); -} - - void PIProcess::execIndependent(const PIString & program, const PIStringList & args_) { PIProcess p; p.args << program << args_; diff --git a/libs/main/system/piprocess.h b/libs/main/system/piprocess.h index e82b03d5..26977d46 100644 --- a/libs/main/system/piprocess.h +++ b/libs/main/system/piprocess.h @@ -28,7 +28,6 @@ #ifndef MICRO_PIP -# include "pifile.h" # include "pithread.h" @@ -110,7 +109,7 @@ 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); -- 2.43.0 From da30ae558f94c130b311bd577890f28f86cd461c Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 13 Aug 2025 14:09:18 +0300 Subject: [PATCH 03/12] add process tests --- CMakeLists.txt | 2 +- libs/main/system/piprocess.cpp | 199 ++++++++++--------- libs/main/system/piprocess.h | 12 +- tests/CMakeLists.txt | 1 + tests/system/process.cpp | 84 ++++++++ utils/system_calib/CMakeLists.txt | 7 + utils/{system_test => system_calib}/main.cpp | 0 utils/system_test/CMakeLists.txt | 7 - 8 files changed, 204 insertions(+), 108 deletions(-) create mode 100644 tests/system/process.cpp create mode 100755 utils/system_calib/CMakeLists.txt rename utils/{system_test => system_calib}/main.cpp (100%) delete mode 100755 utils/system_test/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index cf4ea85d..3b5d2f40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -604,7 +604,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 7d4a84b1..8f79b914 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -62,28 +62,49 @@ namespace { -enum class PipeDirection { - Read, - Write, - Last = Write +enum PipeDirection { + PipeRead, + PipeWrite, + PipeLast = PipeWrite }; -enum class StdFile { - In, - Out, - Err, - Last = Err +enum StdFile { + StdIn, + StdOut, + StdErr, + StdLast = StdErr }; -constexpr int PipesDirections = static_cast(PipeDirection::Last) + 1; -constexpr int StdFileCount = static_cast(StdFile::Last) + 1; +constexpr int PipesDirections = PipeLast + 1; +constexpr int StdFileCount = StdLast + 1; -#ifdef WINDOWS +# ifdef WINDOWS using PipeHandleType = HANDLE; -#else +# else using PipeHandleType = int; -#endif +# endif + +# ifdef WINDOWS +const char * convertWindowsCmd(PIStringList sl) { + for (int i = 0; i < sl.size_s(); ++i) { + sl[i].push_front('"'); + sl[i].push_back('"'); + } + PIString s = sl.join(' '); + return s.data(); } +# else +char * const * convertToCharArrays(const PIStringList & sl) { + char ** cc = new char *[sl.size() + 1]; + for (int i = 0; i < sl.size_s(); ++i) { + cc[i] = const_cast(sl[i].data()); + } + cc[sl.size()] = 0; + return cc; +} +# endif + +} // namespace PRIVATE_DEFINITION_START(PIProcess) @@ -111,20 +132,20 @@ PRIVATE_DEFINITION_START(PIProcess) bool createPipe(StdFile pipe_type) { const int pt = static_cast(pipe_type); -#ifdef WINDOWS +# ifdef WINDOWS SECURITY_ATTRIBUTES saAttr; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; if (!CreatePipe(&(pipes[pt][PipeDirection::Read]), &(pipes[pt][PipeDirection::Write]), &saAttr, 0)) { piCout << "CreatePipe failed: " << GetLastError() << std::endl; return false; } return true; -#else +# else int ret = pipe(pipes[pt]); return ret != -1; -#endif +# endif return false; } PRIVATE_DEFINITION_END(PIProcess) @@ -136,9 +157,9 @@ PIProcess::PIProcess(): PIThread() { PRIVATE->pi.dwProcessId = 0; # else PRIVATE->pid = 0; + PRIVATE->forEachPipe([](PipeHandleType & pipe) { pipe = -1; }); # endif is_exec = false; - PRIVATE->forEachPipe([](PipeHandleType & pipe) { pipe = 0;}); PRIVATE->initGrab(); env = PIProcess::currentEnvironment(); } @@ -161,49 +182,14 @@ void PIProcess::exec_() { void PIProcess::startProc(bool detached) { // cout << "run" << endl; - PIString str; + const PIString & str = args.front(); /// arguments convertion - int as = 0; - const char * argscc[args.size() + 1]; - int argsl[args.size()]; - for (int i = 0; i < args.size_s(); ++i) { - argscc[i] = args[i].data(); - argsl[i] = strlen(argscc[i]); - as += argsl[i] + 3; - } - argscc[args.size()] = 0; -# ifdef WINDOWS - char * a = new char[as]; - memset(a, ' ', as - 1); - as = 0; - for (int i = 0; i < args.size_s(); ++i) { - str = args[i]; - a[as] = '"'; - memcpy(&(a[as + 1]), argscc[i], argsl[i]); - a[as + argsl[i] + 1] = '"'; - as += argsl[i] + 3; - } - a[as - 1] = 0; - // piCout << a; -# endif -# ifndef WINDOWS - /// environment convertion - const char * envcc[env.size() + 1]; - envcc[env.size_s()] = 0; - for (int i = 0; i < env.size_s(); ++i) { - envcc[i] = env[i].data(); - } -# endif + /// files for stdin/out/err - str = args.front(); - is_exec = true; + is_exec = true; if (!detached) execStarted(str); -# ifndef WINDOWS - int pid_ = fork(); - if (!detached) PRIVATE->pid = pid_; - if (pid_ == 0) { -# endif + // PRIVATE->tf_in = PRIVATE->tf_out = PRIVATE->tf_err = 0; // // cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl; // // cout << f_out.path() << endl; @@ -212,41 +198,45 @@ void PIProcess::startProc(bool detached) { // if (g_err) PRIVATE->tf_err = freopen(f_err.path().data(), "w", stderr); # ifdef WINDOWS - STARTUPINFOA si; - piZeroMemory(pi); - si.cb = sizeof(STARTUPINFOA); - if (PRIVATE->grab[StdFile::In]) si.hStdInput = PRIVATE->pipes[StdFile::In][PipeDirection::Read]; - if (PRIVATE->grab[StdFile::Out]) si.hStdOutput = PRIVATE->pipes[StdFile::Out][PipeDirection::Write]; - if (PRIVATE->grab[StdFile::Err]) si.hStdError = PRIVATE->pipes[StdFile::Err][PipeDirection::Write]; - si.dwFlags |= STARTF_USESTDHANDLES; - if (CreateProcessA(0, // No module name (use command line) - a, // Command line - 0, // Process handle not inheritable - 0, // Thread handle not inheritable - false, // Set handle inheritance to FALSE - detached ? DETACHED_PROCESS /*CREATE_NEW_CONSOLE*/ : 0, // Creation flags - 0, // envcc, // Use environment - wd.isEmpty() ? 0 : wd.data(), // Use working directory - &si, // Pointer to STARTUPINFO structure - &(PRIVATE->pi))) // Pointer to PROCESS_INFORMATION structure - { - if (!detached) { - WaitForSingleObject(PRIVATE->pi.hProcess, INFINITE); - DWORD code = -1; - if (GetExitCodeProcess(PRIVATE->pi.hProcess, &code) != 0) exit_code = code; - } - CloseHandle(PRIVATE->pi.hThread); - CloseHandle(PRIVATE->pi.hProcess); - } else { - piCoutObj << "\"CreateProcess\" error: %1"_tr("PIProcess").arg(errorString()); + STARTUPINFOA si; + piZeroMemory(pi); + si.cb = sizeof(STARTUPINFOA); + if (PRIVATE->grab[StdFile::In]) si.hStdInput = PRIVATE->pipes[StdFile::In][PipeDirection::Read]; + if (PRIVATE->grab[StdFile::Out]) si.hStdOutput = PRIVATE->pipes[StdFile::Out][PipeDirection::Write]; + if (PRIVATE->grab[StdFile::Err]) si.hStdError = PRIVATE->pipes[StdFile::Err][PipeDirection::Write]; + si.dwFlags |= STARTF_USESTDHANDLES; + if (CreateProcessA(0, // No module name (use command line) + convertWindowsCmd(args), // Command line + 0, // Process handle not inheritable + 0, // Thread handle not inheritable + false, // Set handle inheritance to FALSE + detached ? DETACHED_PROCESS /*CREATE_NEW_CONSOLE*/ : 0, // Creation flags + 0, // Use environment + wd.isEmpty() ? 0 : wd.data(), // Use working directory + &si, // Pointer to STARTUPINFO structure + &(PRIVATE->pi))) // Pointer to PROCESS_INFORMATION structure + { + if (!detached) { + WaitForSingleObject(PRIVATE->pi.hProcess, INFINITE); + DWORD code = -1; + if (GetExitCodeProcess(PRIVATE->pi.hProcess, &code) != 0) exit_code = code; } -# endif -# ifndef WINDOWS + CloseHandle(PRIVATE->pi.hThread); + CloseHandle(PRIVATE->pi.hProcess); + } else { + piCoutObj << "\"CreateProcess\" error: %1"_tr("PIProcess").arg(errorString()); + } +# else + auto largs = convertToCharArrays(args); + auto lenv = convertToCharArrays(env); + int pid_ = fork(); + if (!detached) PRIVATE->pid = pid_; + if (pid_ == 0) { if (!wd.isEmpty()) { if (!chdir(wd.data())) piCoutObj << "Error while set working directory"; } // cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl; - if (execve(str.data(), (char * const *)argscc, (char * const *)envcc) < 0) { + if (execve(str.data(), largs, lenv) < 0) { piCoutObj << "\"execve" << str << args << "\" error :" << errorString(); } } else { @@ -259,12 +249,11 @@ void PIProcess::startProc(bool detached) { // 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) @@ -280,8 +269,7 @@ void PIProcess::startProc(bool detached) { void PIProcess::terminate() { # ifdef WINDOWS - if (is_exec) - { + if (is_exec) { if (!TerminateProcess(PRIVATE->pi.hProcess, 0)) return; } PRIVATE->pi.dwProcessId = 0; @@ -308,13 +296,28 @@ int PIProcess::pID() const { } -PIByteArray PIProcess::readOutput(bool clear) { - return {}; //readFile(f_out, clear); +PIByteArray PIProcess::readOutput() { + return {}; // readFile(f_out, clear); } -PIByteArray PIProcess::readError(bool clear) { - return {}; //readFile(f_err, clear); +PIByteArray PIProcess::readError() { + return {}; // readFile(f_err, clear); +} + +bool PIProcess::writeInput(const PIByteArray &data) +{ + if (PRIVATE->grab[StdIn]) { + + } + return false; +} + +void PIProcess::closeInput() +{ + if (PRIVATE->grab[StdIn]) { + + } } diff --git a/libs/main/system/piprocess.h b/libs/main/system/piprocess.h index 26977d46..fb8b168f 100644 --- a/libs/main/system/piprocess.h +++ b/libs/main/system/piprocess.h @@ -68,11 +68,19 @@ public: //! \~english Returns all attached execution output stream //! \~russian - PIByteArray readOutput(bool clear = false); + PIByteArray readOutput(); //! \~english Returns all attached execution error stream //! \~russian - PIByteArray readError(bool clear = false); + PIByteArray readError(); + + //! \~english Write data to attached execution input stream + //! \~russian + bool writeInput(const PIByteArray & data); + + //! \~english Close attached execution input stream and send EOF + //! \~russian + void closeInput(); //! \~english Returns current attached execution environment //! \~russian 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.cpp b/tests/system/process.cpp new file mode 100644 index 00000000..018f0b6b --- /dev/null +++ b/tests/system/process.cpp @@ -0,0 +1,84 @@ +#include "piprocess.h" +#include "pitime.h" + +#include "gtest/gtest.h" + + +class ProcessLauncherTest: public ::testing::Test { +protected: + PIProcess launcher; + PIString command = +#ifdef _WIN32 + "cmd.exe"; +#else + "/bin/sh"; +#endif + + void SetUp() override {} + + void TearDown() override { + if (launcher.isRunning()) { + launcher.waitForFinish(); + } + } +}; + + +TEST_F(ProcessLauncherTest, Output) { +#ifdef _WIN32 + PIStringList args = {"/c", "echo Hello from stdout && echo Hello from stderr 1>&2"}; +#else + PIStringList args = {"-c", "echo Hello from stdout; echo Hello from stderr 1>&2"}; +#endif + + launcher.exec(command, args); + ASSERT_TRUE(launcher.isRunning()); + + piMSleep(100); + + auto out = PIString::fromConsole(launcher.readOutput()); + auto err = PIString::fromConsole(launcher.readError()); + + EXPECT_TRUE(out.contains("Hello from stdout")); + EXPECT_TRUE(err.contains("Hello from stderr")); + + ASSERT_TRUE(launcher.waitForFinish()); + 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(ProcessLauncherTest, Input) { +#ifdef _WIN32 + PIStringList args = {"/c", "set /p input= && echo %input%"}; +#else + PIStringList args = {"-c", "read input; echo $input"}; +#endif + + launcher.exec(command, args); + ASSERT_TRUE(launcher.isRunning()); + + PIString test_input = "Test input string\n"; + EXPECT_TRUE(launcher.writeInput(test_input.toSystem())); + launcher.closeInput(); + + piMSleep(100); + + auto out = PIString::fromConsole(launcher.readOutput()); + EXPECT_TRUE(out.contains("Test input string")); +} + + +TEST_F(ProcessLauncherTest, DISABLED_NonexistentCommand) { + PIString command = {"nonexistent_command_12345"}; + + launcher.exec(command); + EXPECT_FALSE(launcher.isRunning()); +} 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 () -- 2.43.0 From 154cbd01600a68585b71f98492ecee413370fd92 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 13 Aug 2025 19:18:02 +0300 Subject: [PATCH 04/12] read and write pipes --- libs/main/system/piprocess.cpp | 204 +++++++++++++++++++++++---------- libs/main/system/piprocess.h | 10 +- tests/system/process.cpp | 84 -------------- tests/system/process_test.cpp | 91 +++++++++++++++ 4 files changed, 244 insertions(+), 145 deletions(-) delete mode 100644 tests/system/process.cpp create mode 100644 tests/system/process_test.cpp diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp index 8f79b914..ec84d317 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -22,9 +22,11 @@ # include "piincludes_p.h" # include "piprocess.h" +# include "piliterals_bytes.h" # ifndef WINDOWS # include # include +# include # endif # ifdef MAC_OS # include @@ -80,7 +82,9 @@ constexpr int StdFileCount = StdLast + 1; # ifdef WINDOWS using PipeHandleType = HANDLE; +using SizeType = DWORD; # else +using SizeType = ssize_t; using PipeHandleType = int; # endif @@ -108,6 +112,7 @@ char * const * convertToCharArrays(const PIStringList & sl) { PRIVATE_DEFINITION_START(PIProcess) + # ifdef WINDOWS PROCESS_INFORMATION pi; # else @@ -116,6 +121,7 @@ PRIVATE_DEFINITION_START(PIProcess) 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) { @@ -124,23 +130,22 @@ PRIVATE_DEFINITION_START(PIProcess) } } + void initGrab() { for (int i = 0; i < StdFileCount; ++i) { grab[i] = false; } } + bool createPipe(StdFile pipe_type) { - const int pt = static_cast(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][PipeDirection::Read]), &(pipes[pt][PipeDirection::Write]), &saAttr, 0)) { - piCout << "CreatePipe failed: " << GetLastError() << std::endl; - return false; - } + satrueAttr.lpSecurityDescriptor = NULL; + if (!CreatePipe(&(pipes[pt][PipeRead]), &(pipes[pt][PipeWrite]), &saAttr, 0)) return false; return true; # else int ret = pipe(pipes[pt]); @@ -148,6 +153,76 @@ PRIVATE_DEFINITION_START(PIProcess) # 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; + } + } + } + return true; + } + + + void closePipe(StdFile pipe_type, PipeDirection direction) { + if (grab[pipe_type]) { +# ifdef WINDOWS + CloseHandle(pipes[pipe_type][direction]); + pipes[pipe_type][direction] = 0; +# else + ::close(pipes[pipe_type][direction]); + pipes[pipe_type][direction] = -1; +# endif + } + } + + + 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 + BOOL ok = ReadFile(pipes[pipe_type][PipeRead], read_buffer.data(offset), read_buffer.size() - offset, &bytes_read, NULL); + if (!ok) bytes_read = 0; +#else + bytes_read = ::read(pipes[pipe_type][PipeRead], read_buffer.data(offset), read_buffer.size() - offset); +#endif + piCout << "readed" << bytes_read; + if (bytes_read > 0) { + offset += bytes_read; + read_buffer.resize(offset + read_buffer_size); + } else { + read_buffer.resize(offset); + break; + } + } + piCout << "readPipe" << read_buffer.toHex(); + 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) @@ -159,7 +234,8 @@ PIProcess::PIProcess(): PIThread() { PRIVATE->pid = 0; PRIVATE->forEachPipe([](PipeHandleType & pipe) { pipe = -1; }); # endif - is_exec = false; + exec_start = false; + exec_finished = false; PRIVATE->initGrab(); env = PIProcess::currentEnvironment(); } @@ -171,39 +247,23 @@ PIProcess::~PIProcess() { void PIProcess::exec_() { - is_exec = false; + exec_finished = false; + exec_start = false; startOnce(); - // cout << "exec wait" << endl; - while (!is_exec) - piMinSleep(); - // cout << "exec end" << endl; } void PIProcess::startProc(bool detached) { - // cout << "run" << endl; const PIString & str = args.front(); - /// arguments convertion - - /// files for stdin/out/err - - is_exec = true; if (!detached) execStarted(str); - - // PRIVATE->tf_in = PRIVATE->tf_out = PRIVATE->tf_err = 0; - // // cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl; - // // cout << f_out.path() << endl; - // if (g_in) PRIVATE->tf_in = freopen(f_in.path().data(), "r", stdin); - // if (g_out) PRIVATE->tf_out = freopen(f_out.path().data(), "w", stdout); - // if (g_err) PRIVATE->tf_err = freopen(f_err.path().data(), "w", stderr); - + if (!PRIVATE->createPipes()) return; # ifdef WINDOWS STARTUPINFOA si; piZeroMemory(pi); si.cb = sizeof(STARTUPINFOA); - if (PRIVATE->grab[StdFile::In]) si.hStdInput = PRIVATE->pipes[StdFile::In][PipeDirection::Read]; - if (PRIVATE->grab[StdFile::Out]) si.hStdOutput = PRIVATE->pipes[StdFile::Out][PipeDirection::Write]; - if (PRIVATE->grab[StdFile::Err]) si.hStdError = PRIVATE->pipes[StdFile::Err][PipeDirection::Write]; + 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; if (CreateProcessA(0, // No module name (use command line) convertWindowsCmd(args), // Command line @@ -216,10 +276,12 @@ void PIProcess::startProc(bool detached) { &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); @@ -228,53 +290,61 @@ void PIProcess::startProc(bool detached) { } # else auto largs = convertToCharArrays(args); - auto lenv = convertToCharArrays(env); - int pid_ = fork(); + auto lenv = convertToCharArrays(env); + int pid_ = fork(); if (!detached) PRIVATE->pid = pid_; if (pid_ == 0) { if (!wd.isEmpty()) { if (!chdir(wd.data())) piCoutObj << "Error while set working directory"; } - // cout << "exec " << tf_in << ", " << tf_out << ", " << tf_err << endl; - if (execve(str.data(), largs, lenv) < 0) { - piCoutObj << "\"execve" << str << args << "\" error :" << errorString(); - } + 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(); } else { + 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; piMinSleep(); - // cout << "wait" << endl; if (!detached) { waitpid(pid_, &exit_code, 0); + exec_finished = true; 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; + exec_start = false; } -// PIByteArray PIProcess::readFile(PIFile & f, bool clear) -// { -// f.open(PIIODevice::ReadOnly); -// const auto ret = f.readAll(); -// if (clear) { -// f.clear(); -// } -// return ret; -// } - void PIProcess::terminate() { # ifdef WINDOWS - if (is_exec) { + if (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 } @@ -297,27 +367,41 @@ int PIProcess::pID() const { PIByteArray PIProcess::readOutput() { - return {}; // readFile(f_out, clear); + return PRIVATE->readPipe(StdOut); } PIByteArray PIProcess::readError() { - return {}; // readFile(f_err, clear); + return PRIVATE->readPipe(StdErr); } -bool PIProcess::writeInput(const PIByteArray &data) -{ - if (PRIVATE->grab[StdIn]) { - } +bool PIProcess::writeInput(const PIByteArray & data) { + if (PRIVATE->grab[StdIn]) return PRIVATE->writePipe(data); return false; } -void PIProcess::closeInput() -{ - if (PRIVATE->grab[StdIn]) { - } +void PIProcess::closeInput() { + if (PRIVATE->grab[StdIn]) 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 fb8b168f..9e77b579 100644 --- a/libs/main/system/piprocess.h +++ b/libs/main/system/piprocess.h @@ -82,6 +82,10 @@ public: //! \~russian void closeInput(); + void enableWriteStdIn(bool on = true); + void enableReadStdOut(bool on = true); + void enableReadStdErr(bool on = true); + //! \~english Returns current attached execution environment //! \~russian PIStringList environment() { return env; } @@ -123,6 +127,9 @@ public: EVENT1(execStarted, PIString, program); EVENT2(execFinished, PIString, program, int, exit_code); + bool isExecFinished() const {return exec_finished;} + bool isExecStarted() const {return exec_start;} + //! \~english Start detached execution "program" without arguments //! \~russian @@ -203,7 +210,8 @@ private: PIStringList args, env; PIString wd; int exit_code; - bool is_exec; + std::atomic_bool exec_start; + std::atomic_bool exec_finished; }; #endif // MICRO_PIP diff --git a/tests/system/process.cpp b/tests/system/process.cpp deleted file mode 100644 index 018f0b6b..00000000 --- a/tests/system/process.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "piprocess.h" -#include "pitime.h" - -#include "gtest/gtest.h" - - -class ProcessLauncherTest: public ::testing::Test { -protected: - PIProcess launcher; - PIString command = -#ifdef _WIN32 - "cmd.exe"; -#else - "/bin/sh"; -#endif - - void SetUp() override {} - - void TearDown() override { - if (launcher.isRunning()) { - launcher.waitForFinish(); - } - } -}; - - -TEST_F(ProcessLauncherTest, Output) { -#ifdef _WIN32 - PIStringList args = {"/c", "echo Hello from stdout && echo Hello from stderr 1>&2"}; -#else - PIStringList args = {"-c", "echo Hello from stdout; echo Hello from stderr 1>&2"}; -#endif - - launcher.exec(command, args); - ASSERT_TRUE(launcher.isRunning()); - - piMSleep(100); - - auto out = PIString::fromConsole(launcher.readOutput()); - auto err = PIString::fromConsole(launcher.readError()); - - EXPECT_TRUE(out.contains("Hello from stdout")); - EXPECT_TRUE(err.contains("Hello from stderr")); - - ASSERT_TRUE(launcher.waitForFinish()); - 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(ProcessLauncherTest, Input) { -#ifdef _WIN32 - PIStringList args = {"/c", "set /p input= && echo %input%"}; -#else - PIStringList args = {"-c", "read input; echo $input"}; -#endif - - launcher.exec(command, args); - ASSERT_TRUE(launcher.isRunning()); - - PIString test_input = "Test input string\n"; - EXPECT_TRUE(launcher.writeInput(test_input.toSystem())); - launcher.closeInput(); - - piMSleep(100); - - auto out = PIString::fromConsole(launcher.readOutput()); - EXPECT_TRUE(out.contains("Test input string")); -} - - -TEST_F(ProcessLauncherTest, DISABLED_NonexistentCommand) { - PIString command = {"nonexistent_command_12345"}; - - launcher.exec(command); - EXPECT_FALSE(launcher.isRunning()); -} diff --git a/tests/system/process_test.cpp b/tests/system/process_test.cpp new file mode 100644 index 00000000..55f884fe --- /dev/null +++ b/tests/system/process_test.cpp @@ -0,0 +1,91 @@ +#include "piprocess.h" +#include "pitime.h" + +#include "gtest/gtest.h" + + +class ProcessTest: public ::testing::Test { +protected: + PIProcess launcher; + const PIString command = +#ifdef _WIN32 + "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()); + + piMSleep(100); + + ASSERT_TRUE(launcher.isExecFinished()); + + const auto out = PIString::fromConsole(launcher.readOutput()); + const auto err = PIString::fromConsole(launcher.readError()); + + EXPECT_TRUE(out.contains("Hello from stdout")); + EXPECT_TRUE(err.contains("Hello from stderr")); + + ASSERT_TRUE(launcher.waitForFinish()); + 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", "set /p input= && echo %input%"}; +#else + const PIStringList args = {"-c", "read input; echo $input"}; +#endif + + launcher.enableWriteStdIn(); + launcher.exec(command, args); + ASSERT_TRUE(launcher.isRunning()); + + const PIString test_input = "Test input string\n"; + EXPECT_TRUE(launcher.writeInput(test_input.toSystem())); + launcher.closeInput(); + + piMSleep(100); + + const auto out = PIString::fromConsole(launcher.readOutput()); + EXPECT_TRUE(out.contains("Test input string")); +} + + +TEST_F(ProcessTest, DISABLED_NonexistentCommand) { + const PIString command = {"nonexistent_command_12345"}; + + launcher.exec(command); + EXPECT_FALSE(launcher.isRunning()); +} -- 2.43.0 From 3625afa7830f83c48413cc1b16889ec6c20cf4ed Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 13 Aug 2025 19:50:47 +0300 Subject: [PATCH 05/12] fix tests --- libs/main/system/piprocess.cpp | 67 ++++++++++++++++++++-------------- tests/system/process_test.cpp | 24 +++++++----- 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp index ec84d317..6af89a70 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -21,8 +21,8 @@ #ifndef MICRO_PIP # include "piincludes_p.h" -# include "piprocess.h" # include "piliterals_bytes.h" +# include "piprocess.h" # ifndef WINDOWS # include # include @@ -82,9 +82,9 @@ constexpr int StdFileCount = StdLast + 1; # ifdef WINDOWS using PipeHandleType = HANDLE; -using SizeType = DWORD; +using SizeType = DWORD; # else -using SizeType = ssize_t; +using SizeType = ssize_t; using PipeHandleType = int; # endif @@ -142,8 +142,8 @@ PRIVATE_DEFINITION_START(PIProcess) const int pt = pipe_type; # ifdef WINDOWS SECURITY_ATTRIBUTES saAttr; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; satrueAttr.lpSecurityDescriptor = NULL; if (!CreatePipe(&(pipes[pt][PipeRead]), &(pipes[pt][PipeWrite]), &saAttr, 0)) return false; return true; @@ -172,15 +172,27 @@ PRIVATE_DEFINITION_START(PIProcess) void closePipe(StdFile pipe_type, PipeDirection direction) { - if (grab[pipe_type]) { + closePipe(pipes[pipe_type][direction]); + } + + + void closePipe(PipeHandleType & hpipe) { # ifdef WINDOWS - CloseHandle(pipes[pipe_type][direction]); - pipes[pipe_type][direction] = 0; -# else - ::close(pipes[pipe_type][direction]); - pipes[pipe_type][direction] = -1; -# endif + if (hpipe] != 0) { + CloseHandle(hpipe); + hpipe = 0; } +# else + if (hpipe != -1) { + ::close(hpipe); + hpipe = -1; + } +# endif + } + + + void closeAllPipes() { + forEachPipe([this](PipeHandleType & hpipe) { closePipe(hpipe); }); } @@ -190,14 +202,14 @@ PRIVATE_DEFINITION_START(PIProcess) PIByteArray read_buffer; read_buffer.resize(read_buffer_size); SizeType bytes_read = 0; - size_t offset = 0; + size_t offset = 0; while (1) { # ifdef WINDOWS BOOL ok = ReadFile(pipes[pipe_type][PipeRead], read_buffer.data(offset), read_buffer.size() - offset, &bytes_read, NULL); if (!ok) bytes_read = 0; -#else +# else bytes_read = ::read(pipes[pipe_type][PipeRead], read_buffer.data(offset), read_buffer.size() - offset); -#endif +# endif piCout << "readed" << bytes_read; if (bytes_read > 0) { offset += bytes_read; @@ -214,12 +226,13 @@ PRIVATE_DEFINITION_START(PIProcess) bool writePipe(const PIByteArray & data) { SizeType sz = 0; - # ifdef WINDOWS +# ifdef WINDOWS BOOL ok = WriteFile(pipes[StdIn][PipeWrite], data.data(), data.size(), &sz, NULL); if (!ok) sz = 0; - #else +# else sz = ::write(pipes[StdIn][PipeWrite], data.data(), data.size()); -# endif +# endif + piCout << "writePipe" << sz; return sz == data.size_s(); } @@ -234,7 +247,7 @@ PIProcess::PIProcess(): PIThread() { PRIVATE->pid = 0; PRIVATE->forEachPipe([](PipeHandleType & pipe) { pipe = -1; }); # endif - exec_start = false; + exec_start = false; exec_finished = false; PRIVATE->initGrab(); env = PIProcess::currentEnvironment(); @@ -243,12 +256,14 @@ PIProcess::PIProcess(): PIThread() { PIProcess::~PIProcess() { PIThread::stopAndWait(); + PRIVATE->closeAllPipes(); } void PIProcess::exec_() { exec_finished = false; - exec_start = false; + exec_start = false; + PRIVATE->closeAllPipes(); startOnce(); } @@ -321,11 +336,10 @@ void PIProcess::startProc(bool detached) { if (PRIVATE->grab[StdErr]) fcntl(PRIVATE->pipes[StdErr][PipeRead], F_SETFL, O_NONBLOCK); exec_start = true; - piMinSleep(); if (!detached) { waitpid(pid_, &exit_code, 0); exec_finished = true; - pid_ = 0; + pid_ = 0; if (!detached) PRIVATE->pid = pid_; } } @@ -387,20 +401,17 @@ void PIProcess::closeInput() { } -void PIProcess::enableWriteStdIn(bool on) -{ +void PIProcess::enableWriteStdIn(bool on) { PRIVATE->grab[StdIn] = on; } -void PIProcess::enableReadStdOut(bool on) -{ +void PIProcess::enableReadStdOut(bool on) { PRIVATE->grab[StdOut] = on; } -void PIProcess::enableReadStdErr(bool on) -{ +void PIProcess::enableReadStdErr(bool on) { PRIVATE->grab[StdErr] = on; } diff --git a/tests/system/process_test.cpp b/tests/system/process_test.cpp index 55f884fe..db678929 100644 --- a/tests/system/process_test.cpp +++ b/tests/system/process_test.cpp @@ -38,17 +38,15 @@ TEST_F(ProcessTest, Output) { launcher.exec(command, args); ASSERT_TRUE(launcher.isRunning()); - piMSleep(100); - + ASSERT_TRUE(launcher.waitForFinish()); ASSERT_TRUE(launcher.isExecFinished()); - const auto out = PIString::fromConsole(launcher.readOutput()); - const auto err = PIString::fromConsole(launcher.readError()); + 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")); - ASSERT_TRUE(launcher.waitForFinish()); const int exit_code = launcher.exitCode(); EXPECT_FALSE(launcher.isRunning()); @@ -71,21 +69,27 @@ TEST_F(ProcessTest, Input) { launcher.enableWriteStdIn(); launcher.exec(command, args); ASSERT_TRUE(launcher.isRunning()); + piMSleep(100); + EXPECT_TRUE(launcher.isExecStarted()); + EXPECT_TRUE(!launcher.isExecFinished()); const PIString test_input = "Test input string\n"; - EXPECT_TRUE(launcher.writeInput(test_input.toSystem())); + EXPECT_TRUE(launcher.writeInput(test_input.toAscii())); launcher.closeInput(); - piMSleep(100); + ASSERT_TRUE(launcher.waitForFinish()); + EXPECT_TRUE(launcher.isExecFinished()); - const auto out = PIString::fromConsole(launcher.readOutput()); + const auto out = PIString::fromAscii(launcher.readOutput()); EXPECT_TRUE(out.contains("Test input string")); } -TEST_F(ProcessTest, DISABLED_NonexistentCommand) { +TEST_F(ProcessTest, NonexistentCommand) { const PIString command = {"nonexistent_command_12345"}; launcher.exec(command); - EXPECT_FALSE(launcher.isRunning()); + ASSERT_TRUE(launcher.isRunning()); + ASSERT_TRUE(launcher.waitForFinish()); + EXPECT_FALSE(!launcher.isExecFinished()); } -- 2.43.0 From d6a0ae6106851390edcbb61d9124ffbd2c0e57c9 Mon Sep 17 00:00:00 2001 From: peri4 Date: Wed, 13 Aug 2025 21:36:13 +0300 Subject: [PATCH 06/12] PIProcess windows works --- libs/main/system/piprocess.cpp | 61 ++++++++++++++++++++++++---------- tests/system/process_test.cpp | 11 +++--- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp index 6af89a70..19684090 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -23,6 +23,7 @@ # include "piincludes_p.h" # include "piliterals_bytes.h" # include "piprocess.h" +# include "pitranslator.h" # ifndef WINDOWS # include # include @@ -89,13 +90,12 @@ using PipeHandleType = int; # endif # ifdef WINDOWS -const char * convertWindowsCmd(PIStringList sl) { - for (int i = 0; i < sl.size_s(); ++i) { - sl[i].push_front('"'); - sl[i].push_back('"'); +PIString convertWindowsCmd(PIStringList sl) { + if (sl.isNotEmpty()) { + sl[0].replaceAll('/', '\\'); + sl[0].quote(); } - PIString s = sl.join(' '); - return s.data(); + return sl.join(' '); } # else char * const * convertToCharArrays(const PIStringList & sl) { @@ -142,9 +142,9 @@ PRIVATE_DEFINITION_START(PIProcess) const int pt = pipe_type; # ifdef WINDOWS SECURITY_ATTRIBUTES saAttr; - saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); - saAttr.bInheritHandle = TRUE; - satrueAttr.lpSecurityDescriptor = NULL; + 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 @@ -167,6 +167,11 @@ PRIVATE_DEFINITION_START(PIProcess) } } } +# 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; } @@ -178,7 +183,7 @@ PRIVATE_DEFINITION_START(PIProcess) void closePipe(PipeHandleType & hpipe) { # ifdef WINDOWS - if (hpipe] != 0) { + if (hpipe != 0) { CloseHandle(hpipe); hpipe = 0; } @@ -205,12 +210,24 @@ PRIVATE_DEFINITION_START(PIProcess) size_t offset = 0; while (1) { # ifdef WINDOWS - BOOL ok = ReadFile(pipes[pipe_type][PipeRead], read_buffer.data(offset), read_buffer.size() - offset, &bytes_read, NULL); + DWORD available = 0; + BOOL ok = PeekNamedPipe(pipes[pipe_type][PipeRead], nullptr, 0, nullptr, &available, nullptr); + // piCout << "ReadFile" << available; + if (available == 0) { + read_buffer.resize(offset); + break; + } + ok = ReadFile(pipes[pipe_type][PipeRead], + read_buffer.data(offset), + piMini(available, read_buffer.size() - offset), + &bytes_read, + nullptr); + // piCout << "ReadFile" << ok; if (!ok) bytes_read = 0; # else bytes_read = ::read(pipes[pipe_type][PipeRead], read_buffer.data(offset), read_buffer.size() - offset); # endif - piCout << "readed" << bytes_read; + // piCout << "readed" << bytes_read; if (bytes_read > 0) { offset += bytes_read; read_buffer.resize(offset + read_buffer_size); @@ -219,7 +236,7 @@ PRIVATE_DEFINITION_START(PIProcess) break; } } - piCout << "readPipe" << read_buffer.toHex(); + // piCout << "readPipe" << PIString::fromConsole(read_buffer); return read_buffer; } @@ -232,7 +249,7 @@ PRIVATE_DEFINITION_START(PIProcess) # else sz = ::write(pipes[StdIn][PipeWrite], data.data(), data.size()); # endif - piCout << "writePipe" << sz; + // piCout << "writePipe" << sz; return sz == data.size_s(); } @@ -274,23 +291,26 @@ void PIProcess::startProc(bool detached) { if (!PRIVATE->createPipes()) return; # ifdef WINDOWS STARTUPINFOA si; - piZeroMemory(pi); + 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); + // piCout << cmd; if (CreateProcessA(0, // No module name (use command line) - convertWindowsCmd(args), // Command line + (LPSTR)cmd.data(), // Command line 0, // Process handle not inheritable 0, // Thread handle not inheritable - false, // Set handle inheritance to FALSE + 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 { + // piCout << "started"; exec_start = true; if (!detached) { WaitForSingleObject(PRIVATE->pi.hProcess, INFINITE); @@ -397,7 +417,12 @@ bool PIProcess::writeInput(const PIByteArray & data) { void PIProcess::closeInput() { - if (PRIVATE->grab[StdIn]) PRIVATE->closePipe(StdIn, PipeWrite); + if (PRIVATE->grab[StdIn]) { +# ifdef WINDOWS + // PRIVATE->writePipe({0x1A}); +# endif + PRIVATE->closePipe(StdIn, PipeWrite); + } } diff --git a/tests/system/process_test.cpp b/tests/system/process_test.cpp index db678929..3b2878e7 100644 --- a/tests/system/process_test.cpp +++ b/tests/system/process_test.cpp @@ -9,7 +9,7 @@ protected: PIProcess launcher; const PIString command = #ifdef _WIN32 - "cmd.exe"; + "C:/Windows/System32/cmd.exe"; #else "/bin/sh"; #endif @@ -61,7 +61,7 @@ TEST_F(ProcessTest, Output) { TEST_F(ProcessTest, Input) { #ifdef _WIN32 - const PIStringList args = {"/c", "set /p input= && echo %input%"}; + const PIStringList args = {"/c", "more"}; #else const PIStringList args = {"-c", "read input; echo $input"}; #endif @@ -73,7 +73,10 @@ TEST_F(ProcessTest, Input) { EXPECT_TRUE(launcher.isExecStarted()); EXPECT_TRUE(!launcher.isExecFinished()); - const PIString test_input = "Test input string\n"; + PIString test_input = "Test input string\n"; +#ifdef WINDOWS + test_input += (char)0x1A; +#endif EXPECT_TRUE(launcher.writeInput(test_input.toAscii())); launcher.closeInput(); @@ -91,5 +94,5 @@ TEST_F(ProcessTest, NonexistentCommand) { launcher.exec(command); ASSERT_TRUE(launcher.isRunning()); ASSERT_TRUE(launcher.waitForFinish()); - EXPECT_FALSE(!launcher.isExecFinished()); + EXPECT_FALSE(launcher.isExecFinished()); } -- 2.43.0 From 0ac7ea30960cc2e09ccc011adfbbca84218903e5 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 13 Aug 2025 22:12:14 +0300 Subject: [PATCH 07/12] fix linux exit finished --- libs/main/system/piprocess.cpp | 32 +++++++++++++------------------- tests/system/process_test.cpp | 6 +++--- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp index 19684090..c551b48c 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -211,23 +211,18 @@ PRIVATE_DEFINITION_START(PIProcess) while (1) { # ifdef WINDOWS DWORD available = 0; - BOOL ok = PeekNamedPipe(pipes[pipe_type][PipeRead], nullptr, 0, nullptr, &available, nullptr); - // piCout << "ReadFile" << available; - if (available == 0) { - read_buffer.resize(offset); - break; + 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; } - ok = ReadFile(pipes[pipe_type][PipeRead], - read_buffer.data(offset), - piMini(available, read_buffer.size() - offset), - &bytes_read, - nullptr); - // piCout << "ReadFile" << ok; - if (!ok) bytes_read = 0; # else bytes_read = ::read(pipes[pipe_type][PipeRead], read_buffer.data(offset), read_buffer.size() - offset); # endif - // piCout << "readed" << bytes_read; if (bytes_read > 0) { offset += bytes_read; read_buffer.resize(offset + read_buffer_size); @@ -236,7 +231,6 @@ PRIVATE_DEFINITION_START(PIProcess) break; } } - // piCout << "readPipe" << PIString::fromConsole(read_buffer); return read_buffer; } @@ -249,7 +243,6 @@ PRIVATE_DEFINITION_START(PIProcess) # else sz = ::write(pipes[StdIn][PipeWrite], data.data(), data.size()); # endif - // piCout << "writePipe" << sz; return sz == data.size_s(); } @@ -298,7 +291,6 @@ void PIProcess::startProc(bool detached) { if (PRIVATE->grab[StdErr]) si.hStdError = PRIVATE->pipes[StdErr][PipeWrite]; si.dwFlags |= STARTF_USESTDHANDLES; const auto cmd = convertWindowsCmd(args); - // piCout << cmd; if (CreateProcessA(0, // No module name (use command line) (LPSTR)cmd.data(), // Command line 0, // Process handle not inheritable @@ -310,7 +302,6 @@ void PIProcess::startProc(bool detached) { &si, // Pointer to STARTUPINFO structure &(PRIVATE->pi))) // Pointer to PROCESS_INFORMATION structure { - // piCout << "started"; exec_start = true; if (!detached) { WaitForSingleObject(PRIVATE->pi.hProcess, INFINITE); @@ -347,6 +338,7 @@ void PIProcess::startProc(bool detached) { execve(str.data(), largs, lenv); // normaly execve can't return, if it returns - error occured piCoutObj << "\"execve" << str << args << "\" error :" << errorString(); + exit(127); } else { PRIVATE->closePipe(StdIn, PipeRead); PRIVATE->closePipe(StdOut, PipeWrite); @@ -358,7 +350,9 @@ void PIProcess::startProc(bool detached) { exec_start = true; if (!detached) { waitpid(pid_, &exit_code, 0); - exec_finished = true; + if (WIFEXITED(exit_code)) { + exec_finished = WEXITSTATUS(exit_code) != 127; + } pid_ = 0; if (!detached) PRIVATE->pid = pid_; } @@ -419,7 +413,7 @@ bool PIProcess::writeInput(const PIByteArray & data) { void PIProcess::closeInput() { if (PRIVATE->grab[StdIn]) { # ifdef WINDOWS - // PRIVATE->writePipe({0x1A}); + PRIVATE->writePipe({0x1A}); # endif PRIVATE->closePipe(StdIn, PipeWrite); } diff --git a/tests/system/process_test.cpp b/tests/system/process_test.cpp index 3b2878e7..3a04b53d 100644 --- a/tests/system/process_test.cpp +++ b/tests/system/process_test.cpp @@ -74,9 +74,6 @@ TEST_F(ProcessTest, Input) { EXPECT_TRUE(!launcher.isExecFinished()); PIString test_input = "Test input string\n"; -#ifdef WINDOWS - test_input += (char)0x1A; -#endif EXPECT_TRUE(launcher.writeInput(test_input.toAscii())); launcher.closeInput(); @@ -91,6 +88,9 @@ TEST_F(ProcessTest, Input) { 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()); -- 2.43.0 From 781b430c3336c1cbf8203ab5bf6f631c0420e417 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 13 Aug 2025 22:15:12 +0300 Subject: [PATCH 08/12] revert --- libs/main/system/piprocess.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp index c551b48c..a21b6930 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -212,14 +212,16 @@ PRIVATE_DEFINITION_START(PIProcess) # ifdef WINDOWS DWORD available = 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; + if (available == 0) { + read_buffer.resize(offset); + break; } + 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 -- 2.43.0 From 3bcb778628218eb11ff116195b8902554ff23feb Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 13 Aug 2025 22:17:04 +0300 Subject: [PATCH 09/12] try 2 --- libs/main/system/piprocess.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp index a21b6930..adfcfac3 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -210,18 +210,15 @@ PRIVATE_DEFINITION_START(PIProcess) size_t offset = 0; while (1) { # ifdef WINDOWS - DWORD available = 0; - PeekNamedPipe(pipes[pipe_type][PipeRead], nullptr, 0, nullptr, &available, nullptr); - if (available == 0) { - read_buffer.resize(offset); - break; + PeekNamedPipe(pipes[pipe_type][PipeRead], nullptr, 0, nullptr, &bytes_read, nullptr); + if (bytes_read > 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; } - 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 -- 2.43.0 From 39266f8c3cde881d4f8d468e80ad912d1bfed743 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 13 Aug 2025 22:20:13 +0300 Subject: [PATCH 10/12] try 3 --- libs/main/system/piprocess.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/main/system/piprocess.cpp b/libs/main/system/piprocess.cpp index adfcfac3..74730ec4 100644 --- a/libs/main/system/piprocess.cpp +++ b/libs/main/system/piprocess.cpp @@ -210,8 +210,10 @@ PRIVATE_DEFINITION_START(PIProcess) size_t offset = 0; while (1) { # ifdef WINDOWS - PeekNamedPipe(pipes[pipe_type][PipeRead], nullptr, 0, nullptr, &bytes_read, nullptr); - if (bytes_read > 0) { + 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), -- 2.43.0 From a41379a40e2c17c5193978c5b2f72dc8ea836493 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 13 Aug 2025 22:29:44 +0300 Subject: [PATCH 11/12] doc --- libs/main/system/piprocess.h | 86 ++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/libs/main/system/piprocess.h b/libs/main/system/piprocess.h index 9e77b579..c6b14ac3 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 @@ -55,55 +55,63 @@ public: int pID() const; //! \~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 + //! \~russian Возвращает весь вывод из стандартного потока вывода (stdout) PIByteArray readOutput(); //! \~english Returns all attached execution error stream - //! \~russian + //! \~russian Возвращает весь вывод из потока ошибок (stderr) PIByteArray readError(); //! \~english Write data to attached execution input stream - //! \~russian + //! \~russian Записывает данные в стандартный поток ввода (stdin) bool writeInput(const PIByteArray & data); //! \~english Close attached execution input stream and send EOF - //! \~russian + //! \~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; @@ -127,33 +135,37 @@ public: EVENT1(execStarted, PIString, program); EVENT2(execFinished, PIString, program, int, exit_code); - bool isExecFinished() const {return exec_finished;} - bool isExecStarted() const {return exec_start;} + //! \~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 @@ -162,27 +174,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 @@ -191,12 +203,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 Генерируется при завершении выполнения //! \} -- 2.43.0 From 3426a3064eaeeb3671654c60e86b21ef1ad39d02 Mon Sep 17 00:00:00 2001 From: "andrey.bychkov" Date: Wed, 13 Aug 2025 22:35:43 +0300 Subject: [PATCH 12/12] main doc --- libs/main/system/piprocess.h | 40 ++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/libs/main/system/piprocess.h b/libs/main/system/piprocess.h index c6b14ac3..e5841a64 100644 --- a/libs/main/system/piprocess.h +++ b/libs/main/system/piprocess.h @@ -30,11 +30,43 @@ # 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); -- 2.43.0