read and write pipes

This commit is contained in:
2025-08-13 19:18:02 +03:00
parent d4254121b8
commit 154cbd0160
4 changed files with 244 additions and 145 deletions

View File

@@ -22,9 +22,11 @@
# include "piincludes_p.h"
# include "piprocess.h"
# include "piliterals_bytes.h"
# ifndef WINDOWS
# include <csignal>
# include <sys/wait.h>
# include <fcntl.h>
# endif
# ifdef MAC_OS
# include <crt_externs.h>
@@ -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<void(PipeHandleType &)> 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<int>(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<StdFile>(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);
@@ -235,46 +297,54 @@ void PIProcess::startProc(bool detached) {
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) {
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;
}

View File

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

View File

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

View File

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