I've been struggling with this one for a while. I am trying to write a C++ class around running a subprocess. I am using fork()
, pipe()
, dup2()
, and execv()
to launch a subprocess and redirect its stdout and stderr to the parent. As far as I can tell, everything works fine until dup2()
is called and it fails with EINVAL
(on macOS, I don't think that is an allowed error type in Linux). If I remove all the logic around pipes, the class works as expected.
The class declaration:
class PosixSubprocess : public Subprocess {
std::string _cmd = {};
std::string _stdout = "";
std::string _stderr = "";
std::vector<std::string> _args = {};
long _pid = -1;
int _status = -1;
public:
void cmd(std::string val) override;
void addArg(std::string val) override;
void run() override;
int status() const override;
std::string out() const override;
std::string err() const override;
};
The definition of run()
:
void PosixSubprocess::run() {
std::array<int, 2> stdoutPipe = {};
std::array<int, 2> stderrPipe = {};
if (pipe(stdoutPipe.data()) < 0)
{
throw std::runtime_error("Subprocess: failed to create pipes. Errno: " + std::to_string(errno));
}
if (pipe(stderrPipe.data()) < 0)
{
throw std::runtime_error("Subprocess: failed to create pipes. Errno: " + std::to_string(errno));
}
_pid = fork();
if (_pid == 0) {
if (dup2(stderrPipe[1], STDERR_FILENO)) {
std::cout << "Subprocess: failed to redirect stderr. Errno " << errno << '\n';
exit(errno);
}
if (dup2(stdoutPipe[1], STDOUT_FILENO)) {
std::cout << "Subprocess: failed to redirect stdout. Errno " << errno << '\n';
exit(errno);
}
close(stdoutPipe[0]);
close(stdoutPipe[1]);
close(stderrPipe[0]);
close(stderrPipe[1]);
auto argv = std::make_unique<char*[]>(_args.size() + 1);
argv[_args.size()] = nullptr;
for (size_t i = 0; i < _args.size(); ++i) {
argv[i] = &(_args[i].front());
}
if(execvp(_cmd.c_str(), argv.get())) {
std::cerr << "Subprocess: failed to launch. Errno " << errno << '\n';
exit(errno);
}
} else if (_pid > 0) {
close(stdoutPipe[1]);
close(stderrPipe[1]);
std::array<char, 1024> buf;
auto appendPipe = [&buf](int fd, std::string& str) {
ssize_t nBytes = 0;
do {
nBytes = read(fd, buf.data(), buf.size());
str.append(buf.data(), nBytes);
if (nBytes) std::cout << nBytes << '\n';
} while (nBytes > 0);
};
while(!waitpid(_pid, &_status, WNOHANG)) {
appendPipe(stdoutPipe[0], _stdout);
appendPipe(stdoutPipe[0], _stderr);
}
} else {
close(stdoutPipe[0]);
close(stdoutPipe[1]);
close(stderrPipe[0]);
close(stderrPipe[1]);
throw std::runtime_error("Subprocess: fork failed.");
}
}
Sorry about the wall of code here, there isn't much I can say for sure isn't related to the issue.
From dup2
's manual page:
RETURN VALUE
Upon successful completion a non-negative integer, namely the file descriptor, shall be returned; otherwise, -1 shall be returned and errno set to indicate the error.
Since, obviously, dup2()
returns the file descriptor the original was dup-ed to, and it is non-zero, the shown code thinks this is an error.
An error condition is indicated by a negative return value.