diff options
author | Serhii Kostiuk <serhii.o.kostiuk@globallogic.com> | 2020-04-07 18:32:02 +0300 |
---|---|---|
committer | Serhii Kostiuk <serhii.o.kostiuk@globallogic.com> | 2020-04-07 18:32:02 +0300 |
commit | e8481260d763f8827e0188e5cd53e613aa4bd305 (patch) | |
tree | 07ab35e586aafe56f688aa938f8af6143635e525 /src | |
parent | b864bd066393e7a46f42eeffce3e7d3692e7c140 (diff) | |
download | libmts-e8481260d763f8827e0188e5cd53e613aa4bd305.tar.gz libmts-e8481260d763f8827e0188e5cd53e613aa4bd305.tar.bz2 libmts-e8481260d763f8827e0188e5cd53e613aa4bd305.zip |
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.
Diffstat (limited to 'src')
-rw-r--r-- | src/MTS_System.cpp | 200 |
1 files changed, 200 insertions, 0 deletions
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 <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& app, 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(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<std::string>& argv, System::PipeType type, System::ChildHandle& child) { + std::vector<char*> 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<char*>(arg.c_str()); +} + +std::vector<char*> System::castArgVector(const std::string& app, const std::vector<std::string>& argv) { + std::vector<char*> 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; +} |