4#error "Process.hpp is currently UNIX only, and does not support Windows. Feel free to open a PR to change this"
46 ssize_t
writeToFd(
const std::string& data,
int fd) {
48 throw std::runtime_error(
"Illegal write on closed or invalid fd");
50 ssize_t bytes = write(fd, data.data(), data.size());
57 ssize_t
readFromFd(std::array<char, 4096>& out,
int fd) {
68 while (poll(&pdfs, nfds, 10)) {
83 std::array<char, 4096> buff;
94 while (poll(&pdfs, nfds, 10)) {
101 out << std::string_view {
102 buff.begin(), buff.begin() + bytes
116 if (pipe(
fds.data()) != 0) {
117 throw std::runtime_error(
"Failed to open pipe");
162 if (openpty(&
master, &
slave,
nullptr,
nullptr,
nullptr) == -1) {
163 throw std::runtime_error(
"Failed to open PTY");
212 return std::make_shared<Pipe>();
228 throw std::runtime_error(
"Must open stdin to write to stdin");
264 return std::make_shared<PTY>();
268 std::map<std::string, std::string>
env = {};
281 for (
auto& [k, v] :
env) {
282 if (k.find(
'=') != std::string::npos) {
283 throw std::runtime_error(
"Illegal key: " + k);
288 throw std::runtime_error(
290 "Working directory set to {}, which does not exist or isn't a directory",
309 std::stringstream
ss = {};
320 std::string d =
ss.str();
338 thread_local std::array<char, 4096> buff;
345 auto written = write(
fd, buff.data(), bytes);
348 std::cerr <<
"Writing failed: " << strerror(errno) << std::endl;
349 throw std::runtime_error(
"Failed to write to output buffer");
361 .stderrHandler = separateStderr ? std::make_shared<InMemoryReadHandler>() :
nullptr,
367 .
stdoutHandler = std::make_shared<FdRedirectInputHandler>(STDOUT_FILENO),
368 .stderrHandler = separateStderr ? std::make_shared<FdRedirectInputHandler>(STDERR_FILENO) :
nullptr,
375 std::optional<
decltype(fork())>
pid = std::nullopt;
378 std::variant<Pipes, std::shared_ptr<PTY>>
393 if (!
pid.has_value()) {
394 std::cerr <<
"waitPid called, but pid has no value. Something has gone very wrong" << std::endl;
397 if (waitpid(*
pid, &wstatus, opts) > 0) {
398 if (WIFEXITED(wstatus)) {
401 }
else if (WIFSIGNALED(wstatus)) {
404 }
else if (WIFSTOPPED(wstatus)) {
409 <<
"WARNING: stc::Unix::Process got an unknown status: " << wstatus
412 this->running =
false;
427 const std::optional<Environment>& env
429 if (env == std::nullopt) {
434 for (
char **env = environ; *env !=
nullptr; env++) {
442 std::vector<char*>* data =
new std::vector<char*>;
443 if (env->extendEnviron && size > 0) {
445 environ, environ + size
457 for (
const auto& [k, v] : env->env) {
458 if (env->extendEnviron) {
466 [&](
const auto& v) ->
bool {
467 return strncmp(v, k.data(), k.size()) == 0;
473 std::string combined = std::format(
476 auto* newStr = strdup(combined.c_str());
477 if (newStr ==
nullptr) {
478 std::cerr <<
"Failed to copy string to env" << std::endl;
481 data->push_back(newStr);
483 data->push_back(
nullptr);
489 const std::vector<std::string>& command,
490 const std::function<
void()>& readImpl,
491 const std::function<
void()>& prepDuping,
492 const std::optional<Environment>& env
494 if (command.size() == 0) {
495 throw std::runtime_error(
"Cannot run null command");
497 std::vector<const char*> convertedCommand;
504 std::cout <<
"Exec: ";
506 convertedCommand.reserve(command.size() + 1);
507 for (
auto& str : command) {
509 std::cout << std::quoted(str);
513 if (convertedCommand.size() != command.size() - 1) {
517 convertedCommand.push_back(str.c_str());
522 convertedCommand.push_back(
nullptr);
526 throw std::runtime_error(
"Failed to fork");
527 }
else if (
pid == 0) {
529 if (prepDuping !=
nullptr) {
535 std::visit([](
auto& resolved) {
536 using T = std::decay_t<
decltype(resolved)>;
537 if constexpr (std::is_same_v<T, std::shared_ptr<PTY>>) {
545 if (env.has_value()) {
546 if (env->workingDirectory.has_value()) {
549 std::filesystem::current_path(
550 env->workingDirectory.value()
556 convertedCommand.at(0),
557 (
char**) convertedCommand.data(),
562 if (readImpl !=
nullptr) {
563 this->inputCollector = std::thread(
570 void run(
const std::function<
void()>& readImpl) {
579 [[nodiscard(
"Discarding immediately terminates the process. You probably don't want this")]]
581 const std::vector<std::string>& command,
582 const std::optional<Environment>& env = std::nullopt,
588 [[nodiscard(
"Discarding immediately terminates the process. You probably don't want this")]]
590 const std::vector<std::string>& command,
592 const std::optional<Environment>& env = std::nullopt,
599 auto& pipes = std::get<Pipes>(this->
interface.value());
600 if (pipes.
stdoutPipe !=
nullptr && this->readHandlers.stdoutHandler !=
nullptr) {
601 std::lock_guard l(
lock);
604 if (pipes.
stderrPipe !=
nullptr && this->readHandlers.stderrHandler !=
nullptr) {
605 std::lock_guard l(
lock);
610 dup2(pipes.
stdinPipe->readFd(), STDIN_FILENO);
613 dup2(pipes.
stdoutPipe->writeFd(), STDOUT_FILENO);
616 dup2(pipes.
stderrPipe->writeFd(), STDERR_FILENO);
618 std::get<Pipes>(*interface).die();
622 [[nodiscard(
"Discarding immediately terminates the process. You probably don't want this")]]
624 const std::vector<std::string>& command,
625 const std::shared_ptr<PTY>& pty,
626 const std::optional<Environment>& env = std::nullopt,
630 if (pty ==
nullptr) {
631 throw std::runtime_error(
632 "pty cannot be null. If you don't want to attach anything, use the non-pipe/non-PTY constructor instead"
637 auto pty = std::get<std::shared_ptr<PTY>>(this->
interface.value());
640 std::lock_guard l(
lock);
652 dup2(pty->slave, STDIN_FILENO);
653 dup2(pty->slave, STDOUT_FILENO);
654 dup2(pty->slave, STDERR_FILENO);
655 std::get<std::shared_ptr<PTY>>(*interface)->die();
676 std::lock_guard g(
lock);
677 if (
auto ptr = std::dynamic_pointer_cast<InMemoryReadHandler>(this->readHandlers.
stdoutHandler);
680 return ptr->getStream(reset);
697 std::lock_guard g(
lock);
698 if (
auto ptr = std::dynamic_pointer_cast<InMemoryReadHandler>(this->readHandlers.
stderrHandler);
701 return ptr->getStream(reset);
713 std::lock_guard g(
lock);
714 if (
auto ptr = std::dynamic_pointer_cast<InMemoryReadHandler>(this->readHandlers.
stdoutHandler);
719 throw std::runtime_error(
"stdout handler is null or otherwise not an InMemoryReadHandler");
721 if (
auto ptr = std::dynamic_pointer_cast<InMemoryReadHandler>(this->readHandlers.
stderrHandler);
726 throw std::runtime_error(
"stderr handler is null or otherwise not an InMemoryReadHandler");
738 if (!this->running) {
739 throw std::runtime_error(
"Cannot write to closed proc");
742 return std::visit([&](
auto& resolved) -> ssize_t {
743 using T = std::decay_t<
decltype(resolved)>;
744 if constexpr (std::is_same_v<T, std::shared_ptr<PTY>>) {
745 return resolved->writeToStdin(data);
747 return resolved.writeToStdin(data);
751 throw std::runtime_error(
"Must use pty or pipe mode to write to stdin");
762 if (inputCollector.joinable()) {
774 if (
pid.has_value() && *
pid > 0) {
797 throw std::runtime_error(
"Must use pipe or pty mode to use this function");
800 if (std::holds_alternative<Pipes>(*this->
interface)) {
801 auto& ptr = std::get<Pipes>(*this->
interface).stdinPipe;
Definition Process.hpp:373
char *const * createEnviron(const std::optional< Environment > &env)
Definition Process.hpp:426
void sigkill()
Definition Process.hpp:791
std::string getStderrBuffer(bool reset=false)
Definition Process.hpp:696
ssize_t writeToStdin(const std::string &data)
Definition Process.hpp:737
void run(const std::function< void()> &readImpl)
Definition Process.hpp:570
std::mutex lock
Definition Process.hpp:382
std::optional< decltype(fork())> pid
Definition Process.hpp:375
Config config
Definition Process.hpp:389
void closeStdin()
Definition Process.hpp:795
std::optional< int > getExitCode()
Definition Process.hpp:817
ReadHandlers readHandlers
Definition Process.hpp:381
std::thread inputCollector
Definition Process.hpp:384
void stop()
Definition Process.hpp:783
int block()
Definition Process.hpp:760
virtual ~Process()
Definition Process.hpp:659
bool running
Definition Process.hpp:387
bool waitPid(int opts=0)
Definition Process.hpp:391
Process(const std::vector< std::string > &command, const std::shared_ptr< PTY > &pty, const std::optional< Environment > &env=std::nullopt, const Config &config={}, const ReadHandlers &readHandlers=ReadHandlers::inMemory())
Definition Process.hpp:623
void resetBuffers()
Definition Process.hpp:712
std::optional< std::variant< Pipes, std::shared_ptr< PTY > > > interface
Definition Process.hpp:379
void signal(int sig)
Definition Process.hpp:772
void doSpawnCommand(const std::vector< std::string > &command, const std::function< void()> &readImpl, const std::function< void()> &prepDuping, const std::optional< Environment > &env)
Definition Process.hpp:488
Process(const std::vector< std::string > &command, const std::optional< Environment > &env=std::nullopt, const Config &config={})
Definition Process.hpp:580
std::optional< bool > hasExitedNormally()
Definition Process.hpp:813
std::string getStdoutBuffer(bool reset=false)
Definition Process.hpp:675
std::atomic< std::optional< bool > > exitedNormally
Definition Process.hpp:386
std::atomic< int > statusCode
Definition Process.hpp:385
Process(const std::vector< std::string > &command, const Pipes &pipes, const std::optional< Environment > &env=std::nullopt, const Config &config={}, const ReadHandlers &readHandlers=ReadHandlers::inMemory())
Definition Process.hpp:589
Definition Process.hpp:43
std::shared_ptr< PTY > createPTY()
Definition Process.hpp:263
std::shared_ptr< Pipe > createPipe()
Definition Process.hpp:211
Definition Process.hpp:298
bool verboseUserOutput
Definition Process.hpp:299
Definition Process.hpp:267
std::map< std::string, std::string > env
Definition Process.hpp:268
bool extendEnviron
Definition Process.hpp:272
void validate() const
Definition Process.hpp:280
std::optional< std::string > workingDirectory
Definition Process.hpp:278
Definition Process.hpp:308
std::string getStream(bool reset=false)
Definition Process.hpp:319
virtual void read(LowLevelWrapper *primitive) override
Definition Process.hpp:310
std::stringstream ss
Definition Process.hpp:309
Definition Process.hpp:45
ssize_t writeToFd(const std::string &data, int fd)
Definition Process.hpp:46
ssize_t readFromFd(std::stringstream &out, int fd)
Definition Process.hpp:82
ssize_t readFromFd(std::array< char, 4096 > &out, int fd)
Definition Process.hpp:57
Definition Process.hpp:155
~PTY()
Definition Process.hpp:166
int master
Definition Process.hpp:156
ssize_t writeToStdin(const std::string &data)
Definition Process.hpp:199
PTY()
Definition Process.hpp:158
void closeSlaveChannel()
Definition Process.hpp:188
ssize_t readData(std::stringstream &out)
Definition Process.hpp:203
virtual int readFd() override
Definition Process.hpp:195
void closeMasterChannel()
Definition Process.hpp:182
int slave
Definition Process.hpp:156
void die()
Definition Process.hpp:177
Definition Process.hpp:113
~Pipe()
Definition Process.hpp:121
int readFd() override
Definition Process.hpp:143
Pipe()
Definition Process.hpp:115
void closeRead()
Definition Process.hpp:129
ssize_t readData(std::stringstream &out)
Definition Process.hpp:150
std::array< int, 2 > fds
Definition Process.hpp:114
void closeWrite()
Definition Process.hpp:135
void die()
Definition Process.hpp:125
int writeFd()
Definition Process.hpp:146
Definition Process.hpp:215
std::shared_ptr< Pipe > stderrPipe
Definition Process.hpp:217
ssize_t writeToStdin(const std::string &data)
Definition Process.hpp:226
std::shared_ptr< Pipe > stdoutPipe
Definition Process.hpp:216
std::shared_ptr< Pipe > stdinPipe
Definition Process.hpp:218
static Pipes separate(bool withStdin=true)
Definition Process.hpp:237
static Pipes shared(bool withStdin=true)
Definition Process.hpp:248
void die()
Definition Process.hpp:220
Definition Process.hpp:302
virtual void read(LowLevelWrapper *primitive)=0
Definition Process.hpp:355
std::shared_ptr< ReadHandler > stderrHandler
Definition Process.hpp:356
std::shared_ptr< ReadHandler > stdoutHandler
Definition Process.hpp:356
static ReadHandlers inMemory(bool separateStderr=true)
Definition Process.hpp:358
static ReadHandlers stdStreamRedirect(bool separateStderr=true)
Definition Process.hpp:365