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