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