diff --git a/src/util/system.cpp b/src/util/system.cpp new file mode 100644 index 0000000..839038c --- /dev/null +++ b/src/util/system.cpp @@ -0,0 +1,159 @@ +#include // errno, EAGAIN, EINTR +#include // size_t +#include // perror +#include // exit, WEXITSTATUS +#include // strcpy, strtok +#include // function +#include +#include +#include // waitpid +#include // close, dup2, execvp, fork, pipe, read +#include + +#include "util/system.h" + +namespace Util { + +void System::operator()(const char* command) +{ + // Split modifies the string, so two copies are needed + char charCommand[strlen(command)]; + strcpy(charCommand, command); + char charCommand2[strlen(command)]; + strcpy(charCommand2, command); + + size_t index = split(charCommand, " "); + + // Preallocation is roughly ~10% faster even with just a single space. + std::vector arguments(index + 1, 0); + + split(charCommand2, " ", [&arguments](int index, char* pointer) { + arguments[index] = pointer; + }); + + operator()(arguments); +} + +void System::operator()(std::string command) +{ + operator()(command.c_str()); +} + +void System::operator()(std::string_view command) +{ + operator()(command.data()); +} + +void System::operator()(const std::vector& arguments) +{ + std::vector nonConstArguments(arguments.size() + 1, 0); + for (size_t i = 0; i < arguments.size(); ++i) { + nonConstArguments[i] = const_cast(arguments[i]); + } + operator()(nonConstArguments); +} + +void System::operator()(const std::vector& arguments) +{ + std::vector nonConstArguments(arguments.size() + 1, 0); + for (size_t i = 0; i < arguments.size(); ++i) { + nonConstArguments[i] = const_cast(arguments[i].c_str()); + } + + operator()(nonConstArguments); +} + +void System::operator()(const std::vector& arguments) +{ + std::vector nonConstArguments(arguments.size() + 1, 0); + for (size_t i = 0; i < arguments.size(); ++i) { + nonConstArguments[i] = const_cast(arguments[i].data()); + } + operator()(nonConstArguments); +} + +// ----------------------------------------- + +void System::operator()(const std::vector& arguments) +{ + int stdoutFd[2]; + int stderrFd[2]; + if (pipe(stdoutFd) < 0) { + perror("\033[31;1mError\033[0m"); + } + if (pipe(stderrFd) < 0) { + perror("\033[31;1mError\033[0m"); + } + + pid_t pid = fork(); + switch (pid) { + // Failed + case -1: + perror("\033[31;1mError\033[0m"); + break; + // Child + case 0: { + close(stdoutFd[ReadFileDescriptor]); + dup2(stdoutFd[WriteFileDescriptor], fileno(stdout)); + close(stdoutFd[WriteFileDescriptor]); + + close(stderrFd[ReadFileDescriptor]); + dup2(stderrFd[WriteFileDescriptor], fileno(stderr)); + close(stderrFd[WriteFileDescriptor]); + + execvp(arguments[0], &arguments[0]); + exit(0); + } + // Parent + default: + break; + } + + readFromFileDescriptor(stdoutFd, m_output); + readFromFileDescriptor(stderrFd, m_error); + + int result; + do { + result = waitpid(pid, &m_status, 0); + } while (result == -1 && errno == EINTR); + m_status = WEXITSTATUS(m_status); +} + +void System::readFromFileDescriptor(int fileDescriptor[2], std::string& output) +{ + close(fileDescriptor[WriteFileDescriptor]); + + constexpr int bufferSize = 4096; + char buffer[bufferSize]; + + do { + const ssize_t result = read(fileDescriptor[ReadFileDescriptor], buffer, bufferSize); + // FIXME: also handle failure cases + if (result > 0) { + output.append(buffer, result); + } + } while (errno == EAGAIN || errno == EINTR); + + if (!output.empty() && output.find_last_of('\n') == output.size() - 1) { + output.pop_back(); + } + + close(fileDescriptor[ReadFileDescriptor]); +} + +size_t System::split(char* split, const char* delimiters, SplitCallback callback) +{ + size_t index = 0; + char* pointer = strtok(split, delimiters); + while (pointer != nullptr) { + if (callback) { + callback(index, pointer); + } + index++; + pointer = strtok(nullptr, delimiters); + } + + return index; +} + +} // namespace Util diff --git a/src/util/system.h b/src/util/system.h new file mode 100644 index 0000000..362a4f0 --- /dev/null +++ b/src/util/system.h @@ -0,0 +1,47 @@ +#ifndef SYSTEM_H +#define SYSTEM_H + +#include // size_t +#include // function +#include +#include +#include + +namespace Util { + +using SplitCallback = std::function; + +class System { +public: + System() {} + virtual ~System() {} + + enum FileDescriptor { + ReadFileDescriptor, + WriteFileDescriptor, + }; + + void operator()(const char* command); + void operator()(std::string command); + void operator()(std::string_view command); + void operator()(const std::vector& arguments); + void operator()(const std::vector& arguments); + void operator()(const std::vector& arguments); + + std::string output() const { return m_output; } + std::string error() const { return m_error; } + int status() const { return m_status; } + +private: + void operator()(const std::vector& arguments); + void readFromFileDescriptor(int fileDescriptor[2], std::string& output); + size_t split(char* split, const char* delimiters, SplitCallback callback = {}); + + std::string m_output; + std::string m_error; + int m_status { 0 }; +}; + +} // namespace Util + +#endif // SYSTEM_H