I am trying to execute a Bash command, get the pid, read the (error) output and optionally pass input.
I am still quite new at C++ and I would like to know if this is a correct & safe implementation for use in real-world applications?
The target operating systems are MacOS 12.5 & Ubuntu (server) 20.04.
The target C++ version is C++20 (aka C++2a).
#include <iostream>
#include <unistd.h>
#include <array>
#include <string>
int main() {
// Vars.
const char* command = "echo Hello World!";
const char* input = "";
const int READ_END = 0;
const int WRITE_END = 1;
int infd[2] = {0, 0};
int outfd[2] = {0, 0};
int errfd[2] = {0, 0};
// Cleanup func.
auto cleanup = [&]() {
::close(infd[READ_END]);
::close(infd[WRITE_END]);
::close(outfd[READ_END]);
::close(outfd[WRITE_END]);
::close(errfd[READ_END]);
::close(errfd[WRITE_END]);
};
// Pipes.
auto rc = ::pipe(infd);
if(rc < 0) {
throw std::runtime_error(std::strerror(errno));
}
rc = ::pipe(outfd);
if(rc < 0) {
::close(infd[READ_END]);
::close(infd[WRITE_END]);
throw std::runtime_error(std::strerror(errno));
}
rc = ::pipe(errfd);
if(rc < 0) {
::close(infd[READ_END]);
::close(infd[WRITE_END]);
::close(outfd[READ_END]);
::close(outfd[WRITE_END]);
throw std::runtime_error(std::strerror(errno));
}
// Parent.
auto pid = fork();
if(pid > 0) {
::close(infd[READ_END]); // Parent does not read from stdin
::close(outfd[WRITE_END]); // Parent does not write to stdout
::close(errfd[WRITE_END]); // Parent does not write to stderr
if(::write(infd[WRITE_END], input, strlen(input)) < 0) {
throw std::runtime_error(std::strerror(errno));
}
::close(infd[WRITE_END]);
}
// Child.
else if(pid == 0) {
::dup2(infd[READ_END], STDIN_FILENO);
::dup2(outfd[WRITE_END], STDOUT_FILENO);
::dup2(errfd[WRITE_END], STDERR_FILENO);
::close(infd[WRITE_END]); // Child does not write to stdin
::close(outfd[READ_END]); // Child does not read from stdout
::close(errfd[READ_END]); // Child does not read from stderr
::execl("/bin/bash", "bash", "-c", command, nullptr);
::exit(EXIT_SUCCESS);
}
// Error.
if(pid < 0) {
cleanup();
throw std::runtime_error("Failed to fork");
}
// Wait at pid.
int status = 0;
::waitpid(pid, &status, 0);
// Read output.
std::string output;
std::string error;
std::array<char, 256> buffer;
ssize_t bytes = 0;
while ((bytes = ::read(outfd[READ_END], buffer.data(), buffer.size())) > 0) {
output.append(buffer.data(), bytes);
}
while ((bytes = ::read(errfd[READ_END], buffer.data(), buffer.size())) > 0) {
error.append(buffer.data(), bytes);
}
// Get exit status.
if(WIFEXITED(status)) {
status = WEXITSTATUS(status);
}
// Cleanup.
cleanup();
std::cout << "PID: " << pid << "\n";
std::cout << "STATUS: " << status << "\n";
std::cout << "OUTPUT: " << output << "\n";
std::cout << "ERROR: " << error << "\n";
}