I am trying to create a helper class to execute a system command and get response back with piping support. For the cases where I need to get the response only (no STDIN to consume for the command) it is working as expected, for pipe support, I am getting garbled STDIN and I can not find out the root cause.
The main function which handles this mechanism is (please ignore the minor error check issues)
the minimal working example
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdarg.h>
struct exec_cmd_t {
exec_cmd_t(std::vector<std::string> args) : args(args), has_executed(false), cpid(-1) { }
exec_cmd_t(const exec_cmd_t &) = delete;
exec_cmd_t(exec_cmd_t &&) = delete;
exec_cmd_t & operator=(const exec_cmd_t &) = delete;
exec_cmd_t & operator=(exec_cmd_t &&) = delete;
std::string operator()();
std::string pipe_cmd(const std::string & input);
std::string pipe_cmd();
~exec_cmd_t();
private:
std::vector<std::string> args;
bool has_executed;
int cpid;
std::stringstream in_stream;
std::stringstream out_stream;
friend std::string operator | (exec_cmd_t & first, exec_cmd_t & second);
friend std::string operator | (exec_cmd_t && first, exec_cmd_t && second);
friend std::string operator | (std::string, exec_cmd_t & second);
friend std::string operator | (std::string, exec_cmd_t && second);
};
std::string exec_cmd_t::pipe_cmd(const std::string & input) {
this->has_executed = true;
const int read_end = 0;
const int write_end = 1;
int read_pipe[2];
int write_pipe[2];
if (pipe(read_pipe) < 0 || pipe(write_pipe) < 0) {
this->has_executed = false;
return std::string{};
}
this->in_stream << input;
std::string line;
while(getline(this->in_stream, line)) {
if (line.size() == 0) {
continue;
}
int wr_sz = write(write_pipe[write_end], line.c_str(), line.size());
if (wr_sz <= 0) {
break;
}
write(write_pipe[write_end], "\n", 1);
}
close(write_pipe[write_end]);
this->cpid = fork();
if (this->cpid == 0) {
dup2(write_pipe[read_end], STDIN_FILENO);
dup2(read_pipe[write_end], STDOUT_FILENO);
close(read_pipe[read_end]);
close(write_pipe[write_end]);
close(read_pipe[write_end]);
close(write_pipe[read_end]);
prctl(PR_SET_PDEATHSIG, SIGTERM);
char * params[args.size()];
const char * image_path = args[0].c_str();
for(int i = 1; i < args.size(); i++) {
params[i-1] = const_cast<char *>(args[i].c_str());
}
params[args.size()] = nullptr;
execv(image_path, params);
exit(1);
}
close(read_pipe[write_end]);
close(write_pipe[read_end]);
char buff[256];
int rd_sz = -1;
int flags = fcntl(read_pipe[0], F_GETFL, 0);
fcntl(read_pipe[read_end], F_SETFL, flags | O_NONBLOCK);
int status = 0;
waitpid(this->cpid, &status, 0);
this->has_executed = false;
int error_code = 0;
while((rd_sz = read(read_pipe[read_end], buff, sizeof(buff))) > 0) {
buff[rd_sz] = '\0';
this->out_stream << std::string{buff};
}
close(read_pipe[read_end]);
return this->out_stream.str();
}
std::string exec_cmd_t::pipe_cmd() {
static std::string empty_str{};
return pipe_cmd(empty_str);
}
std::string exec_cmd_t::operator()() {
return pipe_cmd();
}
exec_cmd_t::~exec_cmd_t() {
if (this->has_executed) {
int status;
waitpid(this->cpid, &status, WNOHANG);
if (!WIFEXITED(status)) {
kill(this->cpid, SIGKILL);
waitpid(this->cpid, &status, 0);
}
}
}
std::string operator | (exec_cmd_t & first, exec_cmd_t & second) {
return second.pipe_cmd(first());
}
std::string operator | (exec_cmd_t && first, exec_cmd_t && second) {
return second.pipe_cmd(first());
}
std::string operator | (std::string output, exec_cmd_t & second) {
return second.pipe_cmd(output);
}
std::string operator | (std::string output, exec_cmd_t && second) {
return second.pipe_cmd(output);
}
int main() {
auto str = exec_cmd_t{ {"/bin/echo", "echo", "hello\nworld\nor\nnot"} } | exec_cmd_t{ {"/bin/grep", "grep", "world", "-"} };
std::cout << str << std::endl;
return 0;
}
gives me
grep: =V: No such file or directory
(standard input):world
It seems like grep is executing twice, one failing with no such file or directory and another one is succeeding. Any suggestion would be very helpful :-) . Thanks in advance.