diff --git a/common/include/villas/popen.hpp b/common/include/villas/popen.hpp new file mode 100644 index 000000000..a6e0ecc57 --- /dev/null +++ b/common/include/villas/popen.hpp @@ -0,0 +1,84 @@ +/** Bi-directional popen + * + * @file + * @author Steffen Vogel + * @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLAScommon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#include + +#include + +#include +#include +#include +#include + +namespace villas { +namespace utils { + +class Popen { + +public: + using char_type = char; + using stdio_buf = __gnu_cxx::stdio_filebuf; + + Popen(const std::string &command); + ~Popen(); + + void open(); + int close(); + void kill(int signal = SIGINT); + + std::istream &in() + { + return *(input.stream); + } + + std::ostream &out() + { + return *(output.stream); + } + +protected: + std::string command; + pid_t pid; + + struct { + std::unique_ptr stream; + std::unique_ptr buffer; + } input; + + struct { + std::unique_ptr stream; + std::unique_ptr buffer; + } output; + +}; + +} /* namespace utils */ +} /* namespace villas */ + +template +std::istream& operator>>(villas::utils::Popen& po, T& value) +{ + return *(po.input.stream) >> value; +} diff --git a/common/lib/CMakeLists.txt b/common/lib/CMakeLists.txt index edbc0a366..ca60a4d7c 100644 --- a/common/lib/CMakeLists.txt +++ b/common/lib/CMakeLists.txt @@ -46,6 +46,7 @@ add_library(villas-common SHARED common.cpp tool.cpp base64.cpp + popen.cpp ) execute_process( diff --git a/common/lib/popen.cpp b/common/lib/popen.cpp new file mode 100644 index 000000000..3979866de --- /dev/null +++ b/common/lib/popen.cpp @@ -0,0 +1,145 @@ +/** Bi-directional popen + * + * @author Steffen Vogel + * @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLAScommon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace villas { +namespace utils { + +Popen::Popen(const std::string &cmd) : + command(cmd) +{ + open(); +} + +Popen::~Popen() +{ + close(); +} + +void Popen::kill(int signal) +{ + ::kill(pid, signal); +} + +void Popen::open() +{ + const int READ = 0; + const int WRITE = 1; + + int ret; + int inpipe[2]; + int outpipe[2]; + char *argv[4]; + + ret = pipe(inpipe); + if (ret) + goto error_out; + + ret = pipe(outpipe); + if (ret) + goto clean_inpipe_out; + + input.buffer = std::make_unique(outpipe[READ], std::ios_base::in); + output.buffer = std::make_unique(inpipe[WRITE], std::ios_base::out); + + input.stream = std::make_unique(input.buffer.get()); + output.stream = std::make_unique(output.buffer.get()); + + pid = fork(); + if (pid == -1) + goto clean_outpipe_out; + + if (pid == 0) { + ::close(outpipe[READ]); + ::close(inpipe[WRITE]); + + if (inpipe[READ] != STDIN_FILENO) { + dup2(inpipe[READ], STDIN_FILENO); + ::close(inpipe[READ]); + } + + if (outpipe[WRITE] != STDOUT_FILENO) { + dup2(outpipe[WRITE], STDOUT_FILENO); + ::close(outpipe[WRITE]); + } + + argv[0] = (char *) "sh"; + argv[1] = (char *) "-c"; + argv[2] = (char *) command.c_str(); + argv[3] = nullptr; + + execv(_PATH_BSHELL, (char * const *) argv); + exit(127); + } + + ::close(outpipe[WRITE]); + ::close(inpipe[READ]); + + return; + +clean_outpipe_out: + ::close(outpipe[READ]); + ::close(outpipe[WRITE]); + +clean_inpipe_out: + ::close(inpipe[READ]); + ::close(inpipe[WRITE]); + +error_out: + throw SystemError("Failed to start subprocess"); +} + +int Popen::close() +{ + int pstat; + + if (pid != -1) { + do { + pid = waitpid(pid, &pstat, 0); + } while (pid == -1 && errno == EINTR); + } + + input.stream.reset(); + output.stream.reset(); + + input.buffer.reset(); + output.buffer.reset(); + + return pid == -1 ? -1 : pstat; +} + +} /* namespace utils */ +} /* namespace villas */ diff --git a/common/tests/unit/CMakeLists.txt b/common/tests/unit/CMakeLists.txt index 0e49a970c..20f41715d 100644 --- a/common/tests/unit/CMakeLists.txt +++ b/common/tests/unit/CMakeLists.txt @@ -31,6 +31,7 @@ add_executable(unit-tests-common timing.cpp utils.cpp base64.cpp + popen.cpp ) if(ARCH STREQUAL "x86_64") diff --git a/common/tests/unit/popen.cpp b/common/tests/unit/popen.cpp new file mode 100644 index 000000000..d3ef7d30e --- /dev/null +++ b/common/tests/unit/popen.cpp @@ -0,0 +1,48 @@ +/** Unit tests for bi-directional popen + * + * @author Steffen Vogel + * @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLAScommon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include + +#include + +using namespace villas::utils; + +TestSuite(popen, .description = "Bi-directional popen"); + +Test(popen, cat) +{ + Popen proc("cat"); + + proc.out() << "Hello World" << std::endl; + proc.out().flush(); + + std::string str, str2; + + proc.in() >> str >> str2; + + cr_assert_eq(str, "Hello"); + cr_assert_eq(str2, "World"); + + proc.kill(); + proc.close(); +}