summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Hatch <jhatch@multitech.com>2020-04-13 15:12:05 -0500
committerJeff Hatch <jhatch@multitech.com>2020-04-13 15:12:05 -0500
commitcbbfbb27fd2b4e7d53f614f626090e88000453de (patch)
treed9d6c5a7b7e27994805bbcd8272bdbadc5813990
parentb864bd066393e7a46f42eeffce3e7d3692e7c140 (diff)
parent43b59eb055df073a9a74173d79f5d6fe41322504 (diff)
downloadlibmts-0.7.tar.gz
libmts-0.7.tar.bz2
libmts-0.7.zip
Merge branch 'sk/cmd-no-shell' into 'master' 0.7
[MTX-3311] Execute system commands without invoking system shell See merge request !1
-rw-r--r--include/mts/MTS_System.h84
-rw-r--r--src/MTS_System.cpp200
2 files changed, 284 insertions, 0 deletions
diff --git a/include/mts/MTS_System.h b/include/mts/MTS_System.h
index bd5fa53..d51a5ea 100644
--- a/include/mts/MTS_System.h
+++ b/include/mts/MTS_System.h
@@ -31,6 +31,7 @@
#include <mts/MTS_NonConstructable.h>
#include <mts/MTS_Stdint.h>
#include <string>
+#include <vector>
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,78 @@ 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 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& cmd, const std::vector<std::string>& 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 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<std::string>& argv, PipeType type, ChildHandle& child);
+
+ //! Close the pipe and wait for a child to finish execution
+ /*!
+ * \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);
+
+ private:
+
+ //! Spawn a process and open pipe with the specified type (READ or WRITE)
+ 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<char*> castArgVector(const std::string& cmd, const std::vector<std::string>& 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..31976e1 100644
--- a/src/MTS_System.cpp
+++ b/src/MTS_System.cpp
@@ -22,6 +22,9 @@
#include <fstream>
#include <sstream>
#include <cassert>
+#include <spawn.h>
+#include <sys/wait.h>
+#include <unistd.h>
#ifdef WIN32
#include <windows.h>
@@ -169,3 +172,200 @@ int32_t System::readFile(const std::string& path, std::string& result) {
return 0;
}
+int32_t System::execute(const std::string& cmd, const std::vector<std::string>& 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(cmd, 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& cmd, const std::vector<std::string>& argv, System::PipeType type, System::ChildHandle& child) {
+ std::vector<char*> arguments = castArgVector(cmd, argv);
+ return executeBackground(cmd, 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& cmd, 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, cmd.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<char*>(arg.c_str());
+}
+
+std::vector<char*> System::castArgVector(const std::string& cmd, const std::vector<std::string>& argv) {
+ std::vector<char*> result;
+
+ // Append application name as argument zero
+ result.push_back(castArgument(cmd));
+
+ // 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;
+}