This commit is contained in:
Timofey 2022-08-10 09:17:11 +03:00
commit 5659d5fdaf
5 changed files with 211 additions and 0 deletions

11
CMakeLists.txt Normal file
View file

@ -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)

View file

@ -0,0 +1,51 @@
#pragma once
#include <string>
#include <fstream>
#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

View file

@ -0,0 +1,9 @@
#pragma once
struct SigpipeState {
bool sigpipe_unblock = false;
};
bool IgnoreSigpipe();
void UnignoreSigpipe();

90
src/shell.cpp Normal file
View file

@ -0,0 +1,90 @@
#include <cppshell/shell.hpp>
#include <cppshell/utils/signals.hpp>
#include <sys/stat.h>
#include <wait.h>
#include <cstdio>
#include <stdexcept>
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<char*>(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

50
src/utils/signals.cpp Normal file
View file

@ -0,0 +1,50 @@
#include <cppshell/utils/signals.hpp>
#include <csignal>
#include <cerrno>
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);
}
}