5#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());
58 std::array<char, 4096> buff;
69 while (poll(&pdfs, nfds, 10)) {
76 out << std::string_view {
77 buff.begin(), buff.begin() + bytes
87 std::array<int, 2>
fds;
89 if (pipe(
fds.data()) != 0) {
90 throw std::runtime_error(
"Failed to open pipe");
135 if (openpty(&
master, &
slave,
nullptr,
nullptr,
nullptr) == -1) {
136 throw std::runtime_error(
"Failed to open PTY");
185 return std::make_shared<Pipe>();
201 throw std::runtime_error(
"Must open stdin to write to stdin");
237 return std::make_shared<PTY>();
241 std::map<std::string, std::string>
env = {};
254 for (
auto& [k, v] :
env) {
255 if (k.find(
'=') != std::string::npos) {
256 throw std::runtime_error(
"Illegal key: " + k);
261 throw std::runtime_error(
263 "Working directory set to {}, which does not exist or isn't a directory",
277 std::optional<
decltype(fork())>
pid = std::nullopt;
280 std::variant<Pipes, std::shared_ptr<PTY>>
294 if (!
pid.has_value()) {
295 std::cerr <<
"waitPid called, but pid has no value. Something has gone very wrong" << std::endl;
298 if (waitpid(*
pid, &wstatus, opts) > 0) {
299 if (WIFEXITED(wstatus)) {
302 }
else if (WIFSIGNALED(wstatus)) {
305 }
else if (WIFSTOPPED(wstatus)) {
310 <<
"WARNING: stc::Unix::Process got an unknown status: " << wstatus
313 this->running =
false;
328 const std::optional<Environment>& env
330 if (env == std::nullopt) {
335 for (
char **env = environ; *env !=
nullptr; env++) {
343 std::vector<char*>* data =
new std::vector<char*>;
344 if (env->extendEnviron && size > 0) {
346 environ, environ + size
358 for (
const auto& [k, v] : env->env) {
359 if (env->extendEnviron) {
367 [&](
const auto& v) ->
bool {
368 return strncmp(v, k.data(), k.size()) == 0;
374 std::string combined = std::format(
377 auto* newStr = strdup(combined.c_str());
378 if (newStr ==
nullptr) {
379 std::cerr <<
"Failed to copy string to env" << std::endl;
382 data->push_back(newStr);
384 data->push_back(
nullptr);
391 const std::vector<std::string>& command,
392 const std::function<
void()>& readImpl,
393 const std::function<
void()>& prepDuping,
394 const std::optional<Environment>& env
396 if (command.size() == 0) {
397 throw std::runtime_error(
"Cannot run null command");
399 std::vector<const char*> convertedCommand;
406 std::cout <<
"Exec: ";
408 convertedCommand.reserve(command.size() + 1);
409 for (
auto& str : command) {
411 std::cout << std::quoted(str);
415 if (convertedCommand.size() != command.size() - 1) {
419 convertedCommand.push_back(str.c_str());
424 convertedCommand.push_back(
nullptr);
428 throw std::runtime_error(
"Failed to fork");
429 }
else if (
pid == 0) {
431 if (prepDuping !=
nullptr) {
437 std::visit([](
auto& resolved) {
438 using T = std::decay_t<
decltype(resolved)>;
439 if constexpr (std::is_same_v<T, std::shared_ptr<PTY>>) {
447 if (env.has_value()) {
448 if (env->workingDirectory.has_value()) {
451 std::filesystem::current_path(
452 env->workingDirectory.value()
458 convertedCommand.at(0),
459 (
char**) convertedCommand.data(),
464 if (readImpl !=
nullptr) {
465 this->inputCollector = std::thread(
472 void run(
const std::function<
void()>& readImpl) {
481 [[nodiscard(
"Discarding immediately terminates the process. You probably don't want this")]]
483 const std::vector<std::string>& command,
484 const std::optional<Environment>& env = std::nullopt,
490 [[nodiscard(
"Discarding immediately terminates the process. You probably don't want this")]]
492 const std::vector<std::string>& command,
494 const std::optional<Environment>& env = std::nullopt,
500 auto& pipes = std::get<Pipes>(this->
interface.value());
502 std::lock_guard l(
lock);
508 std::lock_guard l(
lock);
515 dup2(pipes.
stdinPipe->readFd(), STDIN_FILENO);
518 dup2(pipes.
stdoutPipe->writeFd(), STDOUT_FILENO);
521 dup2(pipes.
stderrPipe->writeFd(), STDERR_FILENO);
523 std::get<Pipes>(*interface).die();
527 [[nodiscard(
"Discarding immediately terminates the process. You probably don't want this")]]
529 const std::vector<std::string>& command,
530 const std::shared_ptr<PTY>& pty,
531 const std::optional<Environment>& env = std::nullopt,
534 if (pty ==
nullptr) {
535 throw std::runtime_error(
536 "pty cannot be null. If you don't want to attach anything, use the non-pipe/non-PTY constructor instead"
541 auto pty = std::get<std::shared_ptr<PTY>>(this->
interface.value());
544 std::lock_guard l(
lock);
554 dup2(pty->slave, STDIN_FILENO);
555 dup2(pty->slave, STDOUT_FILENO);
556 dup2(pty->slave, STDERR_FILENO);
557 std::get<std::shared_ptr<PTY>>(*interface)->die();
580 std::lock_guard g(
lock);
599 std::lock_guard g(
lock);
612 std::lock_guard g(
lock);
625 if (!this->running) {
626 throw std::runtime_error(
"Cannot write to closed proc");
629 return std::visit([&](
auto& resolved) -> ssize_t {
630 using T = std::decay_t<
decltype(resolved)>;
631 if constexpr (std::is_same_v<T, std::shared_ptr<PTY>>) {
632 return resolved->writeToStdin(data);
634 return resolved.writeToStdin(data);
638 throw std::runtime_error(
"Must use pty or pipe mode to write to stdin");
649 if (inputCollector.joinable()) {
661 if (
pid.has_value() && *
pid > 0) {
684 throw std::runtime_error(
"Must use pipe or pty mode to use this function");
687 if (std::holds_alternative<Pipes>(*this->
interface)) {
688 auto& ptr = std::get<Pipes>(*this->
interface).stdinPipe;
Definition Process.hpp:275
char *const * createEnviron(const std::optional< Environment > &env)
Definition Process.hpp:327
void sigkill()
Definition Process.hpp:678
std::string getStderrBuffer(bool reset=false)
Definition Process.hpp:598
ssize_t writeToStdin(const std::string &data)
Definition Process.hpp:624
void run(const std::function< void()> &readImpl)
Definition Process.hpp:472
std::mutex lock
Definition Process.hpp:283
std::optional< decltype(fork())> pid
Definition Process.hpp:277
std::stringstream stderrBuff
Definition Process.hpp:282
Config config
Definition Process.hpp:290
void closeStdin()
Definition Process.hpp:682
std::optional< int > getExitCode()
Definition Process.hpp:704
std::thread inputCollector
Definition Process.hpp:285
std::stringstream stdoutBuff
Definition Process.hpp:282
void stop()
Definition Process.hpp:670
int block()
Definition Process.hpp:647
virtual ~Process()
Definition Process.hpp:563
bool running
Definition Process.hpp:288
bool waitPid(int opts=0)
Definition Process.hpp:292
void resetBuffers()
Definition Process.hpp:611
std::optional< std::variant< Pipes, std::shared_ptr< PTY > > > interface
Definition Process.hpp:281
void signal(int sig)
Definition Process.hpp:659
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:390
Process(const std::vector< std::string > &command, const std::shared_ptr< PTY > &pty, const std::optional< Environment > &env=std::nullopt, const Config &config={})
Definition Process.hpp:528
Process(const std::vector< std::string > &command, const std::optional< Environment > &env=std::nullopt, const Config &config={})
Definition Process.hpp:482
std::optional< bool > hasExitedNormally()
Definition Process.hpp:700
std::string getStdoutBuffer(bool reset=false)
Definition Process.hpp:579
Process(const std::vector< std::string > &command, const Pipes &pipes, const std::optional< Environment > &env=std::nullopt, const Config &config={})
Definition Process.hpp:491
std::atomic< std::optional< bool > > exitedNormally
Definition Process.hpp:287
std::atomic< int > statusCode
Definition Process.hpp:286
Definition Process.hpp:43
std::shared_ptr< PTY > createPTY()
Definition Process.hpp:236
std::shared_ptr< Pipe > createPipe()
Definition Process.hpp:184
Definition Process.hpp:271
bool verboseUserOutput
Definition Process.hpp:272
Definition Process.hpp:240
std::map< std::string, std::string > env
Definition Process.hpp:241
bool extendEnviron
Definition Process.hpp:245
void validate() const
Definition Process.hpp:253
std::optional< std::string > workingDirectory
Definition Process.hpp:251
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:57
Definition Process.hpp:128
~PTY()
Definition Process.hpp:139
int master
Definition Process.hpp:129
ssize_t writeToStdin(const std::string &data)
Definition Process.hpp:172
PTY()
Definition Process.hpp:131
void closeSlaveChannel()
Definition Process.hpp:161
ssize_t readData(std::stringstream &out)
Definition Process.hpp:176
void closeMasterChannel()
Definition Process.hpp:155
int slave
Definition Process.hpp:129
void die()
Definition Process.hpp:150
Definition Process.hpp:86
int readFd()
Definition Process.hpp:116
~Pipe()
Definition Process.hpp:94
Pipe()
Definition Process.hpp:88
void closeRead()
Definition Process.hpp:102
ssize_t readData(std::stringstream &out)
Definition Process.hpp:123
std::array< int, 2 > fds
Definition Process.hpp:87
void closeWrite()
Definition Process.hpp:108
void die()
Definition Process.hpp:98
int writeFd()
Definition Process.hpp:119
Definition Process.hpp:188
std::shared_ptr< Pipe > stderrPipe
Definition Process.hpp:190
ssize_t writeToStdin(const std::string &data)
Definition Process.hpp:199
std::shared_ptr< Pipe > stdoutPipe
Definition Process.hpp:189
std::shared_ptr< Pipe > stdinPipe
Definition Process.hpp:191
static Pipes separate(bool withStdin=true)
Definition Process.hpp:210
static Pipes shared(bool withStdin=true)
Definition Process.hpp:221
void die()
Definition Process.hpp:193