Created
November 14, 2014 17:28
-
-
Save konstantint/d49ab683b978b3d74172 to your computer and use it in GitHub Desktop.
Example of communication with a subprocess via stdin/stdout
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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 settingerrno
to 10, orECHILD
.To fix this the parent process needs to properly handle
SIGCHLD
, an empty handler, anything other than the defaultSIG_IGN
is enough.Add a new method to the class to handle the signal, and instantiate it whenever the spawn class is constructed:
You should probably also check the error status of
waitpid
too:Hope this helps someone, as i spent a good hour debugging this! :)