commit
This commit is contained in:
commit
5659d5fdaf
11
CMakeLists.txt
Normal file
11
CMakeLists.txt
Normal 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)
|
||||
|
51
include/cppshell/shell.hpp
Normal file
51
include/cppshell/shell.hpp
Normal 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
|
9
include/cppshell/utils/signals.hpp
Normal file
9
include/cppshell/utils/signals.hpp
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
struct SigpipeState {
|
||||
bool sigpipe_unblock = false;
|
||||
};
|
||||
|
||||
bool IgnoreSigpipe();
|
||||
void UnignoreSigpipe();
|
||||
|
90
src/shell.cpp
Normal file
90
src/shell.cpp
Normal 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
50
src/utils/signals.cpp
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue