1
0
Fork 0
mirror of https://gitlab.com/niansa/discord_llama.git synced 2025-03-06 20:48:25 +01:00
discord_llama/ProcPipe.hpp
2023-03-19 21:09:15 +01:00

174 lines
4.5 KiB
C++

#ifndef __WIN32
#ifndef _PROCPIPE_HPP
#define _PROCPIPE_HPP
#include <vector>
#include <tuple>
#include <string>
#include <string_view>
#include <stdexcept>
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <csignal>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
template<bool redir_stdin, bool redir_stdout, bool redir_stderr>
class ProcPipe {
struct Pipe {
int readFd = -1, writeFd = -1;
auto make() {
return pipe(reinterpret_cast<int*>(this));
}
~Pipe() {
close(readFd); close(writeFd);
}
};
struct Redirect {
Pipe *pipe;
int fd;
bool output;
int fdbak = -1;
};
constexpr static int errExit = 48;
pid_t pid = 0;
Pipe stdin,
stdout,
stderr;
template<unsigned size>
auto recvFrom(int fd) {
static_assert (size != 0, "Can't read zero bytes");
std::vector<char> fres(size);
ssize_t bytes_read;
if ((bytes_read = read(fd, fres.data(), fres.size())) < 0) {
throw FdError("Failed to read() from stdout");
}
fres.resize(bytes_read);
return fres;
}
public:
struct ExecutionError : public std::runtime_error {
using std::runtime_error::runtime_error;
};
struct AlreadyRunning : public std::runtime_error {
using std::runtime_error::runtime_error;
};
struct FdError : public std::runtime_error {
using std::runtime_error::runtime_error;
};
ProcPipe() {}
template<typename... Args>
ProcPipe(Args&&... args) {
start(args...);
}
~ProcPipe() {
terminate();
}
void send(std::string_view str) {
static_assert (redir_stdin, "Can't write to stdin if not redirected");
if (write(stdin.writeFd, str.data(), str.size()) < 0) {
throw FdError("Failed to write() to stdin");
}
}
template<unsigned size>
auto recvStd() {
static_assert (redir_stdout, "Can't read from stdout if not redirected");
return recvFrom<size>(stdout.readFd);
}
template<unsigned size>
auto recvErr() {
static_assert (redir_stderr, "Can't read from stdout if not redirected");
return recvFrom<size>(stderr.readFd);
}
auto makeRedirs() {
constexpr int redirs_size = redir_stdin + redir_stdout + redir_stderr;
std::array<Redirect, redirs_size> redirs = {};
{
int idx = 0;
if constexpr(redir_stdin) {
redirs[idx++] = {&stdin, STDIN_FILENO, true};
}
if constexpr(redir_stdout) {
redirs[idx++] = {&stdout, STDOUT_FILENO, false};
}
if constexpr(redir_stderr) {
redirs[idx++] = {&stderr, STDERR_FILENO, false};
}
}
return redirs;
}
template<typename... Args>
void start(Args&&... args) {
if (pid) {
throw AlreadyRunning("Tried to run process in an instance where it is already running");
} else {
// Make redirects
auto redirs = makeRedirs();
// Redirect fds
for (auto& io : redirs) {
// Backup fd
io.fdbak = dup(io.fd);
// Create new pipe
io.pipe->make();
dup2((io.output ? io.pipe->readFd : io.pipe->writeFd), io.fd);
}
// Run process
pid = fork();
if (pid == 0) {
const auto executable = std::get<0>(std::tuple{args...});
execlp(executable, args..., nullptr);
perror((std::string("Failed to launch ")+executable).c_str());
exit(errExit);
}
// Restore fds
for (const auto& io : redirs) {
// Restore
dup2(io.fdbak, io.fd);
}
}
}
auto waitExit() noexcept {
if (pid) {
int status = 0;
waitpid(pid, &status, 0);
pid = 0;
return status;
} else {
return -1;
}
}
auto terminate() noexcept {
if (pid) {
::kill(pid, SIGTERM);
return waitExit();
} else {
return -1;
}
}
auto kill() noexcept {
::kill(pid, SIGKILL);
}
auto isRunning() noexcept {
return !(::kill(pid, 0) < 0);
}
};
#endif
#endif