read and write pipes
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
91
tests/system/process_test.cpp
Normal file
91
tests/system/process_test.cpp
Normal 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());
|
||||
}
|
||||
Reference in New Issue
Block a user