From 5659d5fdaf1d51c4c037ab7ecd9bac01c09970bd Mon Sep 17 00:00:00 2001 From: Timofey Date: Wed, 10 Aug 2022 09:17:11 +0300 Subject: [PATCH] commit --- CMakeLists.txt | 11 ++++ include/cppshell/shell.hpp | 51 +++++++++++++++++ include/cppshell/utils/signals.hpp | 9 +++ src/shell.cpp | 90 ++++++++++++++++++++++++++++++ src/utils/signals.cpp | 50 +++++++++++++++++ 5 files changed, 211 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 include/cppshell/shell.hpp create mode 100644 include/cppshell/utils/signals.hpp create mode 100644 src/shell.cpp create mode 100644 src/utils/signals.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ffaf0d1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.14) + +project(cppshell) + +set(CMAKE_CXX_STANDARD 17) + +file(GLOB_RECURSE SOURCES_FILES src/*) + +add_library(cppshell ${SOURCES_FILES}) +target_include_directories(cppshell PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + diff --git a/include/cppshell/shell.hpp b/include/cppshell/shell.hpp new file mode 100644 index 0000000..07b07fd --- /dev/null +++ b/include/cppshell/shell.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#if !defined(__linux__) +#error Only linux supported +#endif + +namespace cppshell { +class Shell { + public: + Shell(const std::string& shell = "bash"); + + void Execute(const std::string& command) { +#if defined(SIGPIPE_ALWAYS_IGNORE) + ExecuteImpl(command, false); +#else + ExecuteImpl(command, true); +#endif + } + + int GetExitCodeLastCommand(); + + ~Shell() { +#if defined(SIGPIPE_ALWAYS_IGNORE) + Destroy(false); +#else + Destroy(true); +#endif + } + + private: + void ExecuteImpl(const std::string& command, bool need_ignore_sigpipe); + + void Destroy(bool need_ignore_sigpipe); + + void MakeUniqueDirectory(); + void MakeFifoFile(); + + private: + std::string unique_directory_; + std::string command_transmission_file_; + + pid_t pid_shell_process_; + int fd_exit_codes_transmission_; + + std::ofstream command_transmission_; + FILE* exit_codes_receiving_; +}; +} // namespace cppshell diff --git a/include/cppshell/utils/signals.hpp b/include/cppshell/utils/signals.hpp new file mode 100644 index 0000000..4f19306 --- /dev/null +++ b/include/cppshell/utils/signals.hpp @@ -0,0 +1,9 @@ +#pragma once + +struct SigpipeState { + bool sigpipe_unblock = false; +}; + +bool IgnoreSigpipe(); +void UnignoreSigpipe(); + diff --git a/src/shell.cpp b/src/shell.cpp new file mode 100644 index 0000000..013778d --- /dev/null +++ b/src/shell.cpp @@ -0,0 +1,90 @@ +#include +#include + +#include +#include + +#include +#include + +namespace cppshell { +Shell::Shell(const std::string& shell) { + MakeUniqueDirectory(); + MakeFifoFile(); + + int fds_exit_codes[2]; + pipe(fds_exit_codes); + + pid_shell_process_ = fork(); + if (pid_shell_process_ == -1) { + throw std::logic_error("fork failed"); + } + + if (pid_shell_process_ == 0) { // child + close(fds_exit_codes[0]); + + execlp("bash", "bash", command_transmission_file_.data(), NULL); + } else { // parent + fd_exit_codes_transmission_ = fds_exit_codes[1]; + + close(fds_exit_codes[1]); + exit_codes_receiving_ = fdopen(fds_exit_codes[0], "r"); + + command_transmission_.open(command_transmission_file_); + } +} + +void Shell::MakeUniqueDirectory() { + unique_directory_ = "/tmp/cppshell_XXXXXX"; + if (mkdtemp(const_cast(unique_directory_.data())) == nullptr) { + throw std::logic_error("make unique directory failed"); + } +} + +void Shell::MakeFifoFile() { + command_transmission_file_ = unique_directory_ + "/commands"; + if (mkfifo(command_transmission_file_.data(), 0600) == -1) { + throw std::logic_error("make fifo failed"); + } +} + +void Shell::ExecuteImpl(const std::string& command, bool need_ignore_sigpipe) { + if (need_ignore_sigpipe) { + IgnoreSigpipe(); + } + + command_transmission_ << command << std::endl; + + if (need_ignore_sigpipe) { + UnignoreSigpipe(); + } +} + +int Shell::GetExitCodeLastCommand() { + Execute("echo $?>&" + std::to_string(fd_exit_codes_transmission_)); + + int result; + int x = fscanf(exit_codes_receiving_, "%d", &result); + + return result; +} + +void Shell::Destroy(bool need_ignore_sigpipe) { + Execute("exit"); + + if (need_ignore_sigpipe) { + IgnoreSigpipe(); + } + + command_transmission_.close(); + + if (need_ignore_sigpipe) { + UnignoreSigpipe(); + } + + waitpid(pid_shell_process_, 0, 0); + + unlink(command_transmission_file_.data()); + rmdir(unique_directory_.data()); +} +} // namespace cppshell diff --git a/src/utils/signals.cpp b/src/utils/signals.cpp new file mode 100644 index 0000000..a41559d --- /dev/null +++ b/src/utils/signals.cpp @@ -0,0 +1,50 @@ +#include + +#include +#include + +thread_local SigpipeState global_state; + +bool IgnoreSigpipe() { + sigset_t pending; + sigemptyset(&pending); + sigpending(&pending); + bool sigpipe_pending = sigismember(&pending, SIGPIPE); + + if (!sigpipe_pending) { + sigset_t sigpipe_mask; + sigemptyset(&sigpipe_mask); + sigaddset(&sigpipe_mask, SIGPIPE); + + sigset_t blocked; + sigemptyset(&blocked); + pthread_sigmask(SIG_BLOCK, &sigpipe_mask, &blocked); + + global_state.sigpipe_unblock = !sigismember(&blocked, SIGPIPE); + + return true; + } + return false; +} + +void UnignoreSigpipe() { + sigset_t pending; + sigemptyset(&pending); + sigpending(&pending); + bool sigpipe_pending = sigismember(&pending, SIGPIPE); + + sigset_t sigpipe_mask; + sigemptyset(&sigpipe_mask); + sigaddset(&sigpipe_mask, SIGPIPE); + + if (sigpipe_pending) { + const struct timespec nowait = {0, 0}; + + while (sigtimedwait(&sigpipe_mask, NULL, &nowait) == -1 && errno == EINTR) { + } + } + + if (global_state.sigpipe_unblock) { + pthread_sigmask(SIG_UNBLOCK, &sigpipe_mask, NULL); + } +}