stc
Loading...
Searching...
No Matches
Process.hpp
Go to the documentation of this file.
1#pragma once
2
3#ifdef _WIN32
4#error "Process.hpp is currently UNIX only, and does not support Windows. Feel free to open a PR to change this"
5#endif
6
17#include <array>
18#include <atomic>
19#include <cstdlib>
20#include <cstring>
21#include <fcntl.h>
22#include <filesystem>
23#include <format>
24#include <functional>
25#include <iostream>
26#include <map>
27#include <memory>
28#include <mutex>
29#include <optional>
30#include <pty.h>
31#include <sstream>
32#include <string>
33#include <string_view>
34#include <sys/poll.h>
35#include <sys/wait.h>
36#include <thread>
37#include <unistd.h>
38#include <variant>
39#include <vector>
40
41// TODO: this cannot be "unix", or the build inexplicably dies ("unexpected { before numeric constant")
42// It's probably a macro
43namespace stc::Unix {
44
46 ssize_t writeToFd(const std::string& data, int fd) {
47 if (fd < 0) {
48 throw std::runtime_error("Illegal write on closed or invalid fd");
49 }
50 ssize_t bytes = write(fd, data.data(), data.size());
51 if (bytes > 0) {
52 return bytes;
53 }
54 return 0;
55 }
56
57 ssize_t readFromFd(std::array<char, 4096>& out, int fd) {
58 ssize_t sum = 0;
59
60 nfds_t nfds = 1;
61 pollfd pdfs = {
62 .fd = fd,
63 .events = POLLIN,
64 .revents = 0
65 };
66
67 // we need a small timeout here to prevent race conditions
68 while (poll(&pdfs, nfds, 10)) {
69 ssize_t bytes = read(
70 fd,
71 out.data(),
72 out.size()
73 );
74 if (bytes > 0) {
75 sum += bytes;
76 break;
77 }
78 }
79 return sum;
80 }
81
82 ssize_t readFromFd(std::stringstream& out, int fd) {
83 std::array<char, 4096> buff;
84 ssize_t sum = 0;
85
86 nfds_t nfds = 1;
87 pollfd pdfs = {
88 .fd = fd,
89 .events = POLLIN,
90 .revents = 0
91 };
92
93 // we need a small timeout here to prevent race conditions
94 while (poll(&pdfs, nfds, 10)) {
95 ssize_t bytes = read(
96 fd,
97 buff.data(),
98 buff.size()
99 );
100 if (bytes > 0) {
101 out << std::string_view {
102 buff.begin(), buff.begin() + bytes
103 };
104 sum += bytes;
105 }
106 }
107 return sum;
108 }
109
110 virtual int readFd() = 0;
111};
112
113struct Pipe : public LowLevelWrapper {
114 std::array<int, 2> fds;
116 if (pipe(fds.data()) != 0) {
117 throw std::runtime_error("Failed to open pipe");
118 }
119 }
120
122 die();
123 }
124
125 void die() {
126 closeRead();
127 closeWrite();
128 }
129 void closeRead() {
130 if (fds[0] != -1) {
131 close(fds[0]);
132 }
133 fds[0] = -1;
134 }
135 void closeWrite() {
136 if (fds[1] != -1) {
137 close(fds[1]);
138 }
139
140 fds[1] = -1;
141 }
142
143 int readFd() override {
144 return fds[0];
145 }
146 int writeFd() {
147 return fds[1];
148 }
149
150 ssize_t readData(std::stringstream& out) {
151 return readFromFd(out, readFd());
152 }
153};
154
155struct PTY : public LowLevelWrapper {
157
158 PTY() {
159 // TODO: figure out if it makes sense to:
160 // 1. store the name
161 // 2. Allow customising whatever the last two parameters are
162 if (openpty(&master, &slave, nullptr, nullptr, nullptr) == -1) {
163 throw std::runtime_error("Failed to open PTY");
164 }
165 }
167 die();
168 }
169
177 void die() {
180 }
181
183 if (master >= 0) {
184 close(master);
185 }
186 master = -1;
187 }
189 if (slave >= 0) {
190 close(slave);
191 }
192 slave = -1;
193 }
194
195 virtual int readFd() override {
196 return master;
197 }
198
199 ssize_t writeToStdin(const std::string& data) {
200 return writeToFd(data, master);
201 }
202
203 ssize_t readData(std::stringstream& out) {
204 return readFromFd(out, master);
205 }
206};
207
211inline std::shared_ptr<Pipe> createPipe() {
212 return std::make_shared<Pipe>();
213}
214
215struct Pipes {
216 std::shared_ptr<Pipe> stdoutPipe = nullptr;
217 std::shared_ptr<Pipe> stderrPipe = nullptr;
218 std::shared_ptr<Pipe> stdinPipe = nullptr;
219
220 void die() {
221 if (stdoutPipe) stdoutPipe->die();
222 if (stderrPipe) stderrPipe->die();
223 if (stdinPipe) stdinPipe->die();
224 }
225
226 ssize_t writeToStdin(const std::string& data) {
227 if (stdinPipe == nullptr) {
228 throw std::runtime_error("Must open stdin to write to stdin");
229 }
230 return stdinPipe->writeToFd(data, stdinPipe->writeFd());
231 }
232
237 static Pipes separate(bool withStdin = true) {
238 return Pipes {
239 createPipe(),
240 createPipe(),
241 withStdin ? createPipe() : nullptr
242 };
243 }
248 static Pipes shared(bool withStdin = true) {
249 auto outPipe = createPipe();
250 return Pipes {
251 outPipe,
252 outPipe,
253 withStdin ? createPipe() : nullptr
254 };
255
256 }
257};
258
259
263inline std::shared_ptr<PTY> createPTY() {
264 return std::make_shared<PTY>();
265}
266
268 std::map<std::string, std::string> env = {};
272 bool extendEnviron = true;
273
278 std::optional<std::string> workingDirectory = std::nullopt;
279
280 void validate() const {
281 for (auto& [k, v] : env) {
282 if (k.find('=') != std::string::npos) {
283 throw std::runtime_error("Illegal key: " + k);
284 }
285 }
286
287 if (workingDirectory.has_value() && !std::filesystem::is_directory(*workingDirectory)) {
288 throw std::runtime_error(
289 std::format(
290 "Working directory set to {}, which does not exist or isn't a directory",
292 )
293 );
294 }
295 }
296};
297
298struct Config {
299 bool verboseUserOutput = false;
300};
301
303 virtual void read(
304 LowLevelWrapper* primitive
305 ) = 0;
306};
307
309 std::stringstream ss = {};
310 virtual void read(
311 LowLevelWrapper* primitive
312 ) override {
313 primitive->readFromFd(
314 ss,
315 primitive->readFd()
316 );
317 }
318
319 std::string getStream(bool reset = false) {
320 std::string d = ss.str();
321
322 if (reset) {
323 ss = {};
324 }
325
326 return d;
327 }
328};
329
331 int fd;
332
334
335 virtual void read(
336 LowLevelWrapper* primitive
337 ) override {
338 thread_local std::array<char, 4096> buff;
339 ssize_t bytes = primitive->readFromFd(
340 buff,
341 primitive->readFd()
342 );
343
344 if (bytes > 0) {
345 auto written = write(fd, buff.data(), bytes);
346
347 if (written <= 0) {
348 std::cerr << "Writing failed: " << strerror(errno) << std::endl;
349 throw std::runtime_error("Failed to write to output buffer");
350 }
351 }
352 }
353};
354
356 std::shared_ptr<ReadHandler> stdoutHandler, stderrHandler;
357
358 static ReadHandlers inMemory(bool separateStderr = true) {
359 return {
360 .stdoutHandler = std::make_shared<InMemoryReadHandler>(),
361 .stderrHandler = separateStderr ? std::make_shared<InMemoryReadHandler>() : nullptr,
362 };
363 }
364
365 static ReadHandlers stdStreamRedirect(bool separateStderr = true) {
366 return {
367 .stdoutHandler = std::make_shared<FdRedirectInputHandler>(STDOUT_FILENO),
368 .stderrHandler = separateStderr ? std::make_shared<FdRedirectInputHandler>(STDERR_FILENO) : nullptr,
369 };
370 }
371};
372
373class Process {
374protected:
375 std::optional<decltype(fork())> pid = std::nullopt;
376
377 std::optional<
378 std::variant<Pipes, std::shared_ptr<PTY>>
380 // std::stringstream stdoutBuff, stderrBuff;
382 std::mutex lock;
383
384 std::thread inputCollector;
385 std::atomic<int> statusCode = -1;
386 std::atomic<std::optional<bool>> exitedNormally;
387 bool running = true;
388
390
391 bool waitPid(int opts = 0) {
392 int wstatus;
393 if (!pid.has_value()) {
394 std::cerr << "waitPid called, but pid has no value. Something has gone very wrong" << std::endl;
395 exit(70);
396 }
397 if (waitpid(*pid, &wstatus, opts) > 0) {
398 if (WIFEXITED(wstatus)) {
399 statusCode = WEXITSTATUS(wstatus);
400 exitedNormally = true;
401 } else if (WIFSIGNALED(wstatus)) {
402 statusCode = WTERMSIG(wstatus);
403 exitedNormally = false;
404 } else if (WIFSTOPPED(wstatus)) {
405 statusCode = WSTOPSIG(wstatus);
406 exitedNormally = false;
407 } else {
408 std::cerr
409 << "WARNING: stc::Unix::Process got an unknown status: " << wstatus
410 << std::endl;
411 }
412 this->running = false;
413 return true;
414 }
415 return false;
416 }
417
426 char* const* createEnviron(
427 const std::optional<Environment>& env
428 ) {
429 if (env == std::nullopt) {
430 return environ;
431 }
432
433 size_t size = 0;
434 for (char **env = environ; *env != nullptr; env++) {
435 ++size;
436 }
437
438 // This is disgusting, but it beats fucking around with the real raw types. This will technically leak a vector,
439 // but it does not matter because it's disappeared once exec is called:
440 // https://stackoverflow.com/a/3617385
441 // Would prefer to do this better, but I just don't want to
442 std::vector<char*>* data = new std::vector<char*>;
443 if (env->extendEnviron && size > 0) {
444 data->assign(
445 environ, environ + size
446 );
447 }
448
449 data->reserve(
450 // Existing data or 0
451 data->size()
452 // nullptr
453 + 1
454 // Extra envs
455 + env->env.size()
456 );
457 for (const auto& [k, v] : env->env) {
458 if (env->extendEnviron) {
459 // If we're extending environ, keys here can conflict with environ. They won't conflict internally,
460 // because env is a non-multimap, and we enforce keys not containing '=', so no weird injection shit
461 // resulting in identical strings.
462 data->erase(
463 std::remove_if(
464 data->begin(),
465 data->end(),
466 [&](const auto& v) -> bool {
467 return strncmp(v, k.data(), k.size()) == 0;
468 }
469 ), data->end()
470 );
471 }
472
473 std::string combined = std::format(
474 "{}={}", k, v
475 );
476 auto* newStr = strdup(combined.c_str());
477 if (newStr == nullptr) {
478 std::cerr << "Failed to copy string to env" << std::endl;
479 exit(69);
480 }
481 data->push_back(newStr);
482 }
483 data->push_back(nullptr);
484
485 return data->data();
486 }
487
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
493 ) {
494 if (command.size() == 0) {
495 throw std::runtime_error("Cannot run null command");
496 }
497 std::vector<const char*> convertedCommand;
498
499 if (env) {
500 env->validate();
501 }
502
504 std::cout << "Exec: ";
505 }
506 convertedCommand.reserve(command.size() + 1);
507 for (auto& str : command) {
509 std::cout << std::quoted(str);
510 // This isn't strictly speaking necessary, but avoids a situation where the tests need a .starts_with(),
511 // or it'll require the full string to contain a load-bearing linebreak AND a load-bearing trailing
512 // space.
513 if (convertedCommand.size() != command.size() - 1) {
514 std::cout << " ";
515 }
516 }
517 convertedCommand.push_back(str.c_str());
518 }
520 std::cout << "\n";
521 }
522 convertedCommand.push_back(nullptr);
523
524 pid = fork();
525 if (pid < 0) {
526 throw std::runtime_error("Failed to fork");
527 } else if (pid == 0) {
528 // Child process
529 if (prepDuping != nullptr) {
530 prepDuping();
531 }
532
533 // Close handles if they're opened
534 if (interface) {
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>>) {
538 resolved->die();
539 } else {
540 resolved.die();
541 }
542 }, interface.value());
543 }
544
545 if (env.has_value()) {
546 if (env->workingDirectory.has_value()) {
547 // C++17 <3
548 // Avoids chdir() from <unistd.h> so there's one less thing to do if this file can be made portable.
549 std::filesystem::current_path(
550 env->workingDirectory.value()
551 );
552 }
553 }
554
555 execve(
556 convertedCommand.at(0),
557 (char**) convertedCommand.data(),
558 createEnviron(env)
559 );
560 } else {
561 // Parent process
562 if (readImpl != nullptr) {
563 this->inputCollector = std::thread(
564 std::bind(&Process::run, this, readImpl)
565 );
566 }
567 }
568 }
569
570 void run(const std::function<void()>& readImpl) {
571 // TODO: readImpl should bake in some timeout here, but is that enough? Is this thread going to be too busy?
572 do {
573 readImpl();
574 } while (!waitPid(WNOHANG));
575 // Read anything left in the buffer at exit time
576 readImpl();
577 }
578public:
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,
583 const Config& config = {}
584 ): config(config) {
585 doSpawnCommand(command, nullptr, nullptr, env);
586 }
587
588 [[nodiscard("Discarding immediately terminates the process. You probably don't want this")]]
590 const std::vector<std::string>& command,
591 const Pipes& pipes,
592 const std::optional<Environment>& env = std::nullopt,
593 const Config& config = {},
596 interface = pipes;
597
598 doSpawnCommand(command, [this]() {
599 auto& pipes = std::get<Pipes>(this->interface.value());
600 if (pipes.stdoutPipe != nullptr && this->readHandlers.stdoutHandler != nullptr) {
601 std::lock_guard l(lock);
602 this->readHandlers.stdoutHandler->read(pipes.stdoutPipe.get());
603 }
604 if (pipes.stderrPipe != nullptr && this->readHandlers.stderrHandler != nullptr) {
605 std::lock_guard l(lock);
606 this->readHandlers.stderrHandler->read(pipes.stderrPipe.get());
607 }
608 }, [&]() {
609 if (pipes.stdinPipe != nullptr) {
610 dup2(pipes.stdinPipe->readFd(), STDIN_FILENO);
611 }
612 if (pipes.stdoutPipe != nullptr) {
613 dup2(pipes.stdoutPipe->writeFd(), STDOUT_FILENO);
614 }
615 if (pipes.stderrPipe != nullptr) {
616 dup2(pipes.stderrPipe->writeFd(), STDERR_FILENO);
617 }
618 std::get<Pipes>(*interface).die();
619 }, env);
620 }
621
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,
627 const Config& config = {},
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"
633 );
634 }
635 interface = pty;
636 doSpawnCommand(command, [this]() {
637 auto pty = std::get<std::shared_ptr<PTY>>(this->interface.value());
638
639 {
640 std::lock_guard l(lock);
641 if (this->readHandlers.stdoutHandler != nullptr) {
642 this->readHandlers.stdoutHandler->read(
643 pty.get()
644 );
645 }
646 }
647 // TODO: a random python-related question I stumbled into suggested using two PTYs so the output and input
648 // can be handled separately. This was in relation to closing stdin. In theory, three separate PTYs could be
649 // used to achieve the same system as pipes. I imagine this is what some terminals use to highlight error
650 // output separately from standard output?
651 }, [&]() {
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();
656 }, env);
657 }
658
659 virtual ~Process() {
660 this->sigkill();
661 this->block();
662 }
663
675 std::string getStdoutBuffer(bool reset = false) {
676 std::lock_guard g(lock);
677 if (auto ptr = std::dynamic_pointer_cast<InMemoryReadHandler>(this->readHandlers.stdoutHandler);
678 ptr != nullptr
679 ) {
680 return ptr->getStream(reset);
681 } else {
682 return "";
683 }
684 }
685
696 std::string getStderrBuffer(bool reset = false) {
697 std::lock_guard g(lock);
698 if (auto ptr = std::dynamic_pointer_cast<InMemoryReadHandler>(this->readHandlers.stderrHandler);
699 ptr != nullptr
700 ) {
701 return ptr->getStream(reset);
702 } else {
703 return "";
704 }
705 }
706
713 std::lock_guard g(lock);
714 if (auto ptr = std::dynamic_pointer_cast<InMemoryReadHandler>(this->readHandlers.stdoutHandler);
715 ptr != nullptr
716 ) {
717 ptr->ss = {};
718 } else {
719 throw std::runtime_error("stdout handler is null or otherwise not an InMemoryReadHandler");
720 }
721 if (auto ptr = std::dynamic_pointer_cast<InMemoryReadHandler>(this->readHandlers.stderrHandler);
722 ptr != nullptr
723 ) {
724 ptr->ss = {};
725 } else {
726 throw std::runtime_error("stderr handler is null or otherwise not an InMemoryReadHandler");
727 }
728 }
729
737 ssize_t writeToStdin(const std::string& data) {
738 if (!this->running) {
739 throw std::runtime_error("Cannot write to closed proc");
740 }
741 if (interface) {
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);
746 } else {
747 return resolved.writeToStdin(data);
748 }
749 }, interface.value());
750 } else {
751 throw std::runtime_error("Must use pty or pipe mode to write to stdin");
752 }
753 }
754
760 int block() {
761 if (this->interface) {
762 if (inputCollector.joinable()) {
763 inputCollector.join();
764 }
765 return statusCode;
766 } else {
767 waitPid();
768 return statusCode;
769 }
770 }
771
772 void signal(int sig) {
773 if (statusCode == -1) {
774 if (pid.has_value() && *pid > 0) {
775 kill(*pid, sig);
776 }
777 }
778 }
779
783 void stop() {
784 signal(SIGTERM);
785 }
786
791 void sigkill() {
792 signal(SIGKILL);
793 }
794
795 void closeStdin() {
796 if (!this->interface.has_value()) {
797 throw std::runtime_error("Must use pipe or pty mode to use this function");
798 }
799
800 if (std::holds_alternative<Pipes>(*this->interface)) {
801 auto& ptr = std::get<Pipes>(*this->interface).stdinPipe;
802 if (ptr) {
803 ptr->closeWrite();
804 }
805 }
806 }
807
813 std::optional<bool> hasExitedNormally() {
814 return exitedNormally;
815 }
816
817 std::optional<int> getExitCode() {
818 if (statusCode != -1) {
819 return statusCode;
820 }
821 return std::nullopt;
822 }
823
824};
825
826}
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:330
virtual void read(LowLevelWrapper *primitive) override
Definition Process.hpp:335
FdRedirectInputHandler(int fd)
Definition Process.hpp:333
int fd
Definition Process.hpp:331
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