diff --git a/common/include/villas/popen.hpp b/common/include/villas/popen.hpp index ba1c8ba63..dd90a598d 100644 --- a/common/include/villas/popen.hpp +++ b/common/include/villas/popen.hpp @@ -27,6 +27,7 @@ #include +#include #include #include #include @@ -38,10 +39,17 @@ namespace utils { class Popen { public: + using arg_list = std::vector; + using env_map = std::map; + using char_type = char; using stdio_buf = __gnu_cxx::stdio_filebuf; - Popen(const std::string &command); + Popen(const std::string &cmd, + const arg_list &args = arg_list(), + const env_map &env = env_map(), + const std::string &wd = std::string(), + bool shell = false); ~Popen(); void open(); @@ -70,6 +78,10 @@ public: protected: std::string command; + std::string working_dir; + arg_list arguments; + env_map environment; + bool shell; pid_t pid; struct { @@ -81,7 +93,6 @@ protected: std::unique_ptr stream; std::unique_ptr buffer; } output; - }; } /* namespace utils */ diff --git a/common/lib/popen.cpp b/common/lib/popen.cpp index 3979866de..d2811f9ad 100644 --- a/common/lib/popen.cpp +++ b/common/lib/popen.cpp @@ -20,26 +20,40 @@ * along with this program. If not, see . *********************************************************************************/ -#include #include #include #include #include #include -#include -#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) +Popen::Popen(const std::string &cmd, + const arg_list &args, + const env_map &env, + const std::string &wd, + bool sh) : + command(cmd), + working_dir(wd), + arguments(args), + environment(env), + shell(sh) { open(); } @@ -56,13 +70,14 @@ void Popen::kill(int signal) void Popen::open() { + std::vector argv, envp; + const int READ = 0; const int WRITE = 1; int ret; int inpipe[2]; int outpipe[2]; - char *argv[4]; ret = pipe(inpipe); if (ret) @@ -83,6 +98,35 @@ void Popen::open() goto clean_outpipe_out; if (pid == 0) { + /* Prepare arguments */ + if (shell) { + argv.push_back((char *) "sh"); + argv.push_back((char *) "-c"); + argv.push_back((char *) command.c_str()); + } + else { + for (auto arg: arguments) { + auto s = strdup(arg.c_str()); + + argv.push_back(s); + } + } + + /* Prepare environment */ + for (char **p = environ; *p; p++) + envp.push_back(*p); + + for (auto env: environment) { + auto e = fmt::format("{}={}", env.first, env.second); + auto s = strdup(e.c_str()); + + envp.push_back(s); + } + + argv.push_back(nullptr); + envp.push_back(nullptr); + + /* Redirect IO */ ::close(outpipe[READ]); ::close(inpipe[WRITE]); @@ -96,12 +140,11 @@ void Popen::open() ::close(outpipe[WRITE]); } - argv[0] = (char *) "sh"; - argv[1] = (char *) "-c"; - argv[2] = (char *) command.c_str(); - argv[3] = nullptr; + /* Change working directory */ + if (!working_dir.empty()) + chdir(working_dir.c_str()); - execv(_PATH_BSHELL, (char * const *) argv); + execvpe(shell ? _PATH_BSHELL : command.c_str(), (char * const *) argv.data(), (char * const *) envp.data()); exit(127); } diff --git a/common/tests/unit/popen.cpp b/common/tests/unit/popen.cpp index 22d41c85b..d9402f4e9 100644 --- a/common/tests/unit/popen.cpp +++ b/common/tests/unit/popen.cpp @@ -29,9 +29,9 @@ using namespace villas::utils; TestSuite(popen, .description = "Bi-directional popen"); -Test(popen, cat) +Test(popen, no_shell) { - Popen proc("cat"); + Popen proc("/usr/bin/tee", {"tee", "test"}); proc.cout() << "Hello World" << std::endl; proc.cout().flush(); @@ -40,9 +40,54 @@ Test(popen, cat) proc.cin() >> str >> str2; + std::cout << str << str2 << std::endl; + + cr_assert_eq(str, "Hello"); + cr_assert_eq(str2, "World"); + + proc.kill(); + proc.close(); +} + +Test(popen, shell) +{ + Popen proc("echo \"Hello World\"", {}, {}, std::string(), true); + + std::string str, str2; + + proc.cin() >> str >> str2; + cr_assert_eq(str, "Hello"); cr_assert_eq(str2, "World"); proc.kill(); proc.close(); } + +Test(popen, wd) +{ + Popen proc("/usr/bin/pwd", {"pwd"}, {}, "/usr/lib"); + + std::string wd; + + proc.cin() >> wd; + + cr_assert_eq(wd, "/usr/lib"); + + proc.kill(); + proc.close(); +} + +Test(popen, env) +{ + Popen proc("echo $MYVAR", {}, {{"MYVAR", "TESTVAL"}}, std::string(), true); + + std::string var; + + proc.cin() >> var; + + cr_assert_eq(var, "TESTVAL"); + + proc.kill(); + proc.close(); +}