From e8481260d763f8827e0188e5cd53e613aa4bd305 Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Tue, 7 Apr 2020 18:32:02 +0300 Subject: Execute system commands without invoking system shell Added several new methods to the MTS::System class: - ```executeBackground``` - popen alternative, creates a pipe with the specified mode and spawns a new process with specified arguments; - ```closeBackground``` - pclose alternative, closes parent side of the pipe and waits for the child process to finish its execution; - ```execute``` - MTS::System::cmd alternative that allows to spawn child processes directly without invoking system shell. --- include/mts/MTS_System.h | 85 ++++++++++++++++++++ src/MTS_System.cpp | 200 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 285 insertions(+) diff --git a/include/mts/MTS_System.h b/include/mts/MTS_System.h index bd5fa53..6bca21b 100644 --- a/include/mts/MTS_System.h +++ b/include/mts/MTS_System.h @@ -31,6 +31,7 @@ #include #include #include +#include namespace MTS { @@ -38,6 +39,17 @@ namespace MTS { public: + //! Type of the pipe to be opened in executeBackground + enum class PipeType { + READ, WRITE + }; + + //! Utility structure with information about the child process + struct ChildHandle { + pid_t pid; + FILE* stream; + }; + //UTC (Coordinated Universal Time) : This is the standard international time or the Greenwich Mean Time. //EPOCH : number of seconds elapsed since 00:00:00 on January 1, 1970, Coordinated Universal Time. @@ -53,6 +65,79 @@ namespace MTS { static int32_t cmd(const std::string& cmd, std::string& result); static int32_t readFile(const std::string& path, std::string& result); + + //! Execute application with specified arguments. + /*! + * A safer alternative to System::cmd that allows to execute applications with controlled + * list of arguments bypasing the system shell. + * + * \param app name of the application to be executed + * \param argv vector of arguments (excluding argument zero) + * \param result stdout output from the application is stored here + * \return result code as for std::system and UNIX pclose + */ + static int32_t execute(const std::string& app, const std::vector& argv, std::string& result); + + //! Spawn a process and open pipe with the specified type (READ or WRITE) + /*! + * A safer alternative to POSIX popen that allows to execute applications with controlled + * list of arguments bypasing the system shell. If succeded, information about the spawned + * process is written to the \p child structure. + * + * \param app name of the application to be executed + * \param argv vector of arguments (excluding argument zero) + * \param type open pipe for read from child or write to child + * \return true on success, false on failure + */ + static bool executeBackground(const std::string& app, const std::vector& argv, PipeType type, ChildHandle& child); + + //! Close the pipe and wait for a child to finish execution + /*! + * \param child name of the application to be executed + * \param argv vector of arguments (excluding argument zero) + * \param result stdout output from the application is stored here + * \return result code as for std::system and UNIX pclose + */ + static int closeBackground(ChildHandle& child); + + private: + + //! Spawn a process and open pipe with the specified type (READ or WRITE) + static bool executeBackground(const std::string& app, char* const argv[], PipeType type, ChildHandle& child); + + //! Utility method. Cast argument to a C pointer required by some C-style functions. + static inline char* castArgument(const std::string& arg); + + //! Utility method. Convert argument vector to a format required by execve and posix_spawn. + static std::vector castArgVector(const std::string& app, const std::vector& argv); + + //! Lightweight C++ wrapper with auto-close for file descriptors + class FdWrapper { + + public: + static const int FD_NOT_READY = -1; + + explicit FdWrapper(int fd = FD_NOT_READY); + ~FdWrapper(); + + inline int get() const; + int release(); + void reset(int fd = FD_NOT_READY); + + private: + int m_fd; + + }; + + //! Utility structure, used by popen wrapper + struct Pipe { + FdWrapper parent; + FdWrapper child; + }; + + //! Initialize UNIX pipe + static int initPipe(Pipe& pipe, PipeType type); + }; } diff --git a/src/MTS_System.cpp b/src/MTS_System.cpp index abed817..6acf065 100644 --- a/src/MTS_System.cpp +++ b/src/MTS_System.cpp @@ -22,6 +22,9 @@ #include #include #include +#include +#include +#include #ifdef WIN32 #include @@ -169,3 +172,200 @@ int32_t System::readFile(const std::string& path, std::string& result) { return 0; } +int32_t System::execute(const std::string& app, const std::vector& argv, std::string& result) { + // Ported directly from System::cmd + std::string output; + const int max_buffer = 256; + char buffer[max_buffer]; + int32_t code = -1; + ChildHandle child; + + if (executeBackground(app, argv, PipeType::READ, child)) { + while (!feof(child.stream)) { + if (fgets(buffer, max_buffer, child.stream) != NULL) { + output.append(buffer); + } + } + code = closeBackground(child); + } + + result = output; + + return code; +} + +bool System::executeBackground(const std::string& app, const std::vector& argv, System::PipeType type, System::ChildHandle& child) { + std::vector arguments = castArgVector(app, argv); + return executeBackground(app, arguments.data(), type, child); +} + +int System::closeBackground(System::ChildHandle& child) { + int pstat; + pid_t pid; + + fclose(child.stream); + + do { + pid = waitpid(child.pid, &pstat, 0); + } while (pid == -1 && errno == EINTR); + + return pstat; +} + +bool System::executeBackground(const std::string& app, char* const argv[], System::PipeType type, ChildHandle& child) { + pid_t childPid; + FILE* stream = NULL; + posix_spawn_file_actions_t actions; + int childTargetFd; + const char* parentMode = nullptr; + bool bActionsInitialized = false; + bool bSuccess = false; + + do { + + Pipe pipe; + if (initPipe(pipe, type) != 0) { + // failed to create pipe + break; + } + + if (type == PipeType::READ) { + childTargetFd = STDOUT_FILENO; + parentMode = "r"; + } else if (type == PipeType::WRITE) { + childTargetFd = STDIN_FILENO; + parentMode = "w"; + } else { + break; // unreachable, unknown mode + } + + // Define how child process should handle pipes + if (posix_spawn_file_actions_init(&actions) != 0) { + // failed to initialize file actions + break; + } + + bActionsInitialized = true; + + if (posix_spawn_file_actions_addclose(&actions, pipe.parent.get()) != 0) { + // failed to add action to close parent pipe fd + break; + } + + if (posix_spawn_file_actions_adddup2(&actions, pipe.child.get(), childTargetFd) != 0) { + // failed to add an action to use child pipe fd for output + break; + } + + if (posix_spawn_file_actions_addclose(&actions, pipe.child.get()) != 0) { + // failed to add an action to close dupped child pipe fd + break; + } + + if (posix_spawnp(&childPid, app.c_str(), &actions, NULL, argv, NULL) != 0) { + // failed to spawn the process + break; + } + + // Close child-specific file descriptor + pipe.child.reset(); + + // Convert parent fd to FILE pointer + stream = fdopen(pipe.parent.release(), parentMode); + + // Save data about child + child.pid = childPid; + child.stream = stream; + + bSuccess = true; + + } while (false); + + if (bActionsInitialized) { + posix_spawn_file_actions_destroy(&actions); + } + + return bSuccess; +} + +char* System::castArgument(const std::string& arg) { + // Casting const away is required only to be compatible with some C-style system functions. + // Arguments are not actually modified in such functions. + return const_cast(arg.c_str()); +} + +std::vector System::castArgVector(const std::string& app, const std::vector& argv) { + std::vector result; + + // Append application name as argument zero + result.push_back(castArgument(app)); + + // Append all other arguments + for (const std::string& arg : argv) { + result.push_back(castArgument(arg)); + } + + // Terminate with NULL + result.push_back(NULL); + return result; +} + +int System::initPipe(System::Pipe& pipe, System::PipeType type) { + const int READ_END_IDX = 0; + const int WRITE_END_IDX = 1; + + int holder[2]; + int parentIndex; + int childIndex; + int ret = -1; + + do { + + if (type == PipeType::READ) { + parentIndex = READ_END_IDX; + childIndex = WRITE_END_IDX; + } else if (type == PipeType::WRITE) { + parentIndex = WRITE_END_IDX; + childIndex = READ_END_IDX; + } else { + // unreachable, unknown mode + break; // error + } + + ret = ::pipe(holder); + if (ret != 0) { + break; + } + + pipe.parent.reset(holder[parentIndex]); + pipe.child.reset(holder[childIndex]); + + } while (false); + + return ret; +} + +System::FdWrapper::FdWrapper(int fd) + : m_fd(fd) +{} + +System::FdWrapper::~FdWrapper() { + reset(); +} + +int System::FdWrapper::get() const { + return m_fd; +} + +int System::FdWrapper::release() { + int fd = m_fd; + m_fd = FD_NOT_READY; + return fd; +} + +void System::FdWrapper::reset(int fd) { + if (FD_NOT_READY != m_fd) { + ::close(m_fd); + } + m_fd = fd; +} -- cgit v1.2.3 From 6a2ef269f37820db40b0d1cd19d7fdb7696c0d2a Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Thu, 9 Apr 2020 16:43:06 +0300 Subject: Execute system commands without invoking system shell Changes after a code review: - fixed documentation for the closeBackground function - renamed ```app``` parameter to ```cmd``` --- include/mts/MTS_System.h | 16 +++++++--------- src/MTS_System.cpp | 18 +++++++++--------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/include/mts/MTS_System.h b/include/mts/MTS_System.h index 6bca21b..cd9b9dd 100644 --- a/include/mts/MTS_System.h +++ b/include/mts/MTS_System.h @@ -71,12 +71,12 @@ namespace MTS { * A safer alternative to System::cmd that allows to execute applications with controlled * list of arguments bypasing the system shell. * - * \param app name of the application to be executed + * \param cmd name of the application to be executed * \param argv vector of arguments (excluding argument zero) * \param result stdout output from the application is stored here * \return result code as for std::system and UNIX pclose */ - static int32_t execute(const std::string& app, const std::vector& argv, std::string& result); + static int32_t execute(const std::string& cmd, const std::vector& argv, std::string& result); //! Spawn a process and open pipe with the specified type (READ or WRITE) /*! @@ -84,18 +84,16 @@ namespace MTS { * list of arguments bypasing the system shell. If succeded, information about the spawned * process is written to the \p child structure. * - * \param app name of the application to be executed + * \param cmd name of the application to be executed * \param argv vector of arguments (excluding argument zero) * \param type open pipe for read from child or write to child * \return true on success, false on failure */ - static bool executeBackground(const std::string& app, const std::vector& argv, PipeType type, ChildHandle& child); + static bool executeBackground(const std::string& cmd, const std::vector& argv, PipeType type, ChildHandle& child); //! Close the pipe and wait for a child to finish execution /*! - * \param child name of the application to be executed - * \param argv vector of arguments (excluding argument zero) - * \param result stdout output from the application is stored here + * \param child structure with child process information as populated by executeBackground * \return result code as for std::system and UNIX pclose */ static int closeBackground(ChildHandle& child); @@ -103,13 +101,13 @@ namespace MTS { private: //! Spawn a process and open pipe with the specified type (READ or WRITE) - static bool executeBackground(const std::string& app, char* const argv[], PipeType type, ChildHandle& child); + static bool executeBackground(const std::string& cmd, char* const argv[], PipeType type, ChildHandle& child); //! Utility method. Cast argument to a C pointer required by some C-style functions. static inline char* castArgument(const std::string& arg); //! Utility method. Convert argument vector to a format required by execve and posix_spawn. - static std::vector castArgVector(const std::string& app, const std::vector& argv); + static std::vector castArgVector(const std::string& cmd, const std::vector& argv); //! Lightweight C++ wrapper with auto-close for file descriptors class FdWrapper { diff --git a/src/MTS_System.cpp b/src/MTS_System.cpp index 6acf065..31976e1 100644 --- a/src/MTS_System.cpp +++ b/src/MTS_System.cpp @@ -172,7 +172,7 @@ int32_t System::readFile(const std::string& path, std::string& result) { return 0; } -int32_t System::execute(const std::string& app, const std::vector& argv, std::string& result) { +int32_t System::execute(const std::string& cmd, const std::vector& argv, std::string& result) { // Ported directly from System::cmd std::string output; const int max_buffer = 256; @@ -180,7 +180,7 @@ int32_t System::execute(const std::string& app, const std::vector& int32_t code = -1; ChildHandle child; - if (executeBackground(app, argv, PipeType::READ, child)) { + if (executeBackground(cmd, argv, PipeType::READ, child)) { while (!feof(child.stream)) { if (fgets(buffer, max_buffer, child.stream) != NULL) { output.append(buffer); @@ -194,9 +194,9 @@ int32_t System::execute(const std::string& app, const std::vector& return code; } -bool System::executeBackground(const std::string& app, const std::vector& argv, System::PipeType type, System::ChildHandle& child) { - std::vector arguments = castArgVector(app, argv); - return executeBackground(app, arguments.data(), type, child); +bool System::executeBackground(const std::string& cmd, const std::vector& argv, System::PipeType type, System::ChildHandle& child) { + std::vector arguments = castArgVector(cmd, argv); + return executeBackground(cmd, arguments.data(), type, child); } int System::closeBackground(System::ChildHandle& child) { @@ -212,7 +212,7 @@ int System::closeBackground(System::ChildHandle& child) { return pstat; } -bool System::executeBackground(const std::string& app, char* const argv[], System::PipeType type, ChildHandle& child) { +bool System::executeBackground(const std::string& cmd, char* const argv[], System::PipeType type, ChildHandle& child) { pid_t childPid; FILE* stream = NULL; posix_spawn_file_actions_t actions; @@ -262,7 +262,7 @@ bool System::executeBackground(const std::string& app, char* const argv[], Syste break; } - if (posix_spawnp(&childPid, app.c_str(), &actions, NULL, argv, NULL) != 0) { + if (posix_spawnp(&childPid, cmd.c_str(), &actions, NULL, argv, NULL) != 0) { // failed to spawn the process break; } @@ -294,11 +294,11 @@ char* System::castArgument(const std::string& arg) { return const_cast(arg.c_str()); } -std::vector System::castArgVector(const std::string& app, const std::vector& argv) { +std::vector System::castArgVector(const std::string& cmd, const std::vector& argv) { std::vector result; // Append application name as argument zero - result.push_back(castArgument(app)); + result.push_back(castArgument(cmd)); // Append all other arguments for (const std::string& arg : argv) { -- cgit v1.2.3 From 43b59eb055df073a9a74173d79f5d6fe41322504 Mon Sep 17 00:00:00 2001 From: Serhii Kostiuk Date: Thu, 9 Apr 2020 17:03:34 +0300 Subject: Execute system commands without invoking system shell Added a missed ```child``` parameter to the list of ```executeBackground``` params in docs --- include/mts/MTS_System.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/mts/MTS_System.h b/include/mts/MTS_System.h index cd9b9dd..d51a5ea 100644 --- a/include/mts/MTS_System.h +++ b/include/mts/MTS_System.h @@ -84,10 +84,11 @@ namespace MTS { * list of arguments bypasing the system shell. If succeded, information about the spawned * process is written to the \p child structure. * - * \param cmd name of the application to be executed - * \param argv vector of arguments (excluding argument zero) - * \param type open pipe for read from child or write to child - * \return true on success, false on failure + * \param cmd name of the application to be executed + * \param argv vector of arguments (excluding argument zero) + * \param type open pipe for read from child or write to child + * \param child on success is populated with child process information, on failure is not defined + * \return true on success, false on failure */ static bool executeBackground(const std::string& cmd, const std::vector& argv, PipeType type, ChildHandle& child); -- cgit v1.2.3