Skip to content

Instantly share code, notes, and snippets.

@konstantint
Created November 14, 2014 17:28
Show Gist options
  • Save konstantint/d49ab683b978b3d74172 to your computer and use it in GitHub Desktop.
Save konstantint/d49ab683b978b3d74172 to your computer and use it in GitHub Desktop.
Example of communication with a subprocess via stdin/stdout
//
// Example of communication with a subprocess via stdin/stdout
// Author: Konstantin Tretyakov
// License: MIT
//
#include <ext/stdio_filebuf.h> // NB: Specific to libstdc++
#include <sys/wait.h>
#include <unistd.h>
#include <iostream>
#include <memory>
#include <exception>
// Wrapping pipe in a class makes sure they are closed when we leave scope
class cpipe {
private:
int fd[2];
public:
const inline int read_fd() const { return fd[0]; }
const inline int write_fd() const { return fd[1]; }
cpipe() { if (pipe(fd)) throw std::runtime_error("Failed to create pipe"); }
void close() { ::close(fd[0]); ::close(fd[1]); }
~cpipe() { close(); }
};
//
// Usage:
// spawn s(argv)
// s.stdin << ...
// s.stdout >> ...
// s.send_eol()
// s.wait()
//
class spawn {
private:
cpipe write_pipe;
cpipe read_pipe;
public:
int child_pid = -1;
std::unique_ptr<__gnu_cxx::stdio_filebuf<char> > write_buf = NULL;
std::unique_ptr<__gnu_cxx::stdio_filebuf<char> > read_buf = NULL;
std::ostream stdin;
std::istream stdout;
spawn(const char* const argv[], bool with_path = false, const char* const envp[] = 0): stdin(NULL), stdout(NULL) {
child_pid = fork();
if (child_pid == -1) throw std::runtime_error("Failed to start child process");
if (child_pid == 0) { // In child process
dup2(write_pipe.read_fd(), STDIN_FILENO);
dup2(read_pipe.write_fd(), STDOUT_FILENO);
write_pipe.close(); read_pipe.close();
int result;
if (with_path) {
if (envp != 0) result = execvpe(argv[0], const_cast<char* const*>(argv), const_cast<char* const*>(envp));
else result = execvp(argv[0], const_cast<char* const*>(argv));
}
else {
if (envp != 0) result = execve(argv[0], const_cast<char* const*>(argv), const_cast<char* const*>(envp));
else result = execv(argv[0], const_cast<char* const*>(argv));
}
if (result == -1) {
// Note: no point writing to stdout here, it has been redirected
std::cerr << "Error: Failed to launch program" << std::endl;
exit(1);
}
}
else {
close(write_pipe.read_fd());
close(read_pipe.write_fd());
write_buf = std::unique_ptr<__gnu_cxx::stdio_filebuf<char> >(new __gnu_cxx::stdio_filebuf<char>(write_pipe.write_fd(), std::ios::out));
read_buf = std::unique_ptr<__gnu_cxx::stdio_filebuf<char> >(new __gnu_cxx::stdio_filebuf<char>(read_pipe.read_fd(), std::ios::in));
stdin.rdbuf(write_buf.get());
stdout.rdbuf(read_buf.get());
}
}
void send_eof() { write_buf->close(); }
int wait() {
int status;
waitpid(child_pid, &status, 0);
return status;
}
};
// ---------------- Usage example -------------------- //
#include <string>
using std::string;
using std::getline;
using std::cout;
using std::endl;
int main() {
const char* const argv[] = {"/bin/cat", (const char*)0};
spawn cat(argv);
cat.stdin << "Hello" << std::endl;
string s;
getline(cat.stdout, s);
cout << "Read from program: '" << s << "'" << endl;
cat.send_eof();
cout << "Waiting to terminate..." << endl;
cout << "Status: " << cat.wait() << endl;
return 0;
}
@braindigitalis
Copy link

braindigitalis commented Oct 25, 2023

This has a tiny issue. waitpid is always going to set the exit status of the child process to 0, because it is returning -1 and setting errno to 10, or ECHILD.

To fix this the parent process needs to properly handle SIGCHLD, an empty handler, anything other than the default SIG_IGN is enough.

Add a new method to the class to handle the signal, and instantiate it whenever the spawn class is constructed:

private:
static void sig_chld(int sig)
{
	signal(SIGCHLD, spawn::sig_chld);
}

// ...

spawn(const char* const argv[], bool with_path = false, const char* const envp[] = 0): stdin(NULL), stdout(NULL) {
    spawn::sig_chld(SIGCHLD);
    child_pid = fork();
    ...

You should probably also check the error status of waitpid too:

    int wait() {
        int status;
        if (waitpid(child_pid, &status, 0) == -1) {
            return status;
        } else {
            return -1; // or whatever you want here to indicate waitpid failure
        }
    }

Hope this helps someone, as i spent a good hour debugging this! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment