I am using boost::process to call an external program - the external program reads input via stdin, and writes to stdout and stderr. The external program is as follows (expects a single argument - the path to a file for debugging)
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
int main(int argc, char** argv)
{
try
{
if (argc != 2)
{
throw std::logic_error("Expected two arguments");
}
std::ofstream ofs(argv[1]);
std::vector<std::string> someTestInput;
ofs << "Starting program..." << std::endl;
// Read from cin
{
ofs << "Reading from cin..." << std::endl;
std::string input;
while (std::getline(std::cin, input))
{
ofs << "Received from cin: " << input << std::endl;
someTestInput.emplace_back(input);
}
ofs << "Finished receiving from cin..." << std::endl;
}
// Error if nothing has been input
if (someTestInput.empty())
{
throw std::logic_error("Expected some input and received nothing...");
}
ofs << "Writing to cout..." << std::endl;
// Write to cout
for (const auto& output : someTestInput)
{
std::cout << output << '\n';
}
ofs << "Finished!\n";
}
catch (std::exception& e)
{
std::cerr << "Error caught: " << e.what() << '\n';
return 1;
}
return 0;
}
The caller expects 2+ arguments, one of which is the path to the external program, and the rest are passed on as arguments to the external program.
It hangs while waiting for the process to exit, and it seems like the external program is waiting for an EOF from stdin.
#include <memory>
#include <vector>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/process.hpp>
int main(int argc, char** argv)
{
try
{
if (argc < 2)
{
throw std::logic_error("Expecting at least 2 arguments...");
}
std::vector<std::string> args;
for (int i = 1; i < argc; ++i)
{
args.emplace_back(argv[i]);
}
std::cout << "Creating stdout, stderr pipes...\n";
// Create pipes for stdout, stderr
boost::process::pipe pstdout = boost::process::create_pipe();
boost::process::pipe pstderr = boost::process::create_pipe();
std::cout << "Mapping pipes to sources...\n";
// Map pipe source from stdout and stderr to sources
boost::iostreams::file_descriptor_source sourcestdout(pstdout.source, boost::iostreams::close_handle);
boost::iostreams::file_descriptor_source sourcestderr(pstderr.source, boost::iostreams::close_handle);
std::cout << "Setting up streams for the sources...\n";
// And set up streams for the sources
boost::iostreams::stream<boost::iostreams::file_descriptor_source> istdout(sourcestdout);
boost::iostreams::stream<boost::iostreams::file_descriptor_source> istderr(sourcestderr);
std::unique_ptr<boost::process::child> p;
// Want to check for process result, but also need to ensure stdin handle is closed properly,
// so place everything in separate scope
{
std::cout << "Mapping pipes to sinks...\n";
// Map pipe sink from stdout and stderr to sinks
boost::iostreams::file_descriptor_sink sinkstdout(pstdout.sink, boost::iostreams::close_handle);
boost::iostreams::file_descriptor_sink sinkstderr(pstderr.sink, boost::iostreams::close_handle);
std::cout << "Creating stdin pipe, mapping to source and sink...\n";
boost::process::pipe pstdin = boost::process::create_pipe();
// For stdin, map pipe to source and sink as before - want it to close on exiting this scope
boost::iostreams::file_descriptor_sink sinkstdin(pstdin.sink, boost::iostreams::close_handle);
boost::iostreams::file_descriptor_source sourcestdin(pstdin.source, boost::iostreams::close_handle);
boost::iostreams::stream<boost::iostreams::file_descriptor_sink> ostdin(sinkstdin);
std::cout << "Calling process... \n";
// Call process
p = std::unique_ptr<boost::process::child>(new boost::process::child(boost::process::execute(
boost::process::initializers::set_args(args),
boost::process::initializers::throw_on_error(),
boost::process::initializers::bind_stdout(sinkstdout),
boost::process::initializers::bind_stderr(sinkstderr),
boost::process::initializers::bind_stdin(sourcestdin)
)));
std::cout << "Sending test data...\n";
// Send some test data to cin - comment out the below to test for error case
ostdin << "Test Input 1\n";
ostdin << "Some\n";
ostdin << "Useful\n";
ostdin << "Data\n";
std::cout << "Test data sent, exiting scope...\n";
}
std::cout << "Check if process has exited...\n";
// Check if process has exited OK - if not, report errors
if (boost::process::wait_for_exit(*p))
{
std::cout << "Has not exited OK, reporting problems...\n";
// Gather output from stderr
std::string error;
while (std::getline(istderr, error))
{
std::cout << "Error: " << error << '\n';
}
throw std::logic_error("Problem executing TestProgram...");
}
std::cout << "Exited OK, here is output from the callee...\n";
// Gather the output
std::string output;
while (std::getline(istdout, output))
{
std::cout << output << '\n';
}
}
catch (std::exception& e)
{
std::cerr << "Error: " << e.what() << '\n';
return 1;
}
}
I was under the impression that placing my stdin pipe and related sources/sinks within a scope will guarantee they're closed, and therefore send the EOF.
The same code works perfectly under Windows (VS2013, boost_1_53).
I am using boost_1_53, boost-process 0.5, gcc 4.8.2.
That does not happen, because there's still a pipe handle open in the child process; that is only closed on posix if you set it explicitly (on windows it is done automatically). So you'd need to add something like that:
#if defined (BOOST_POSIX_API)
fcntl(pstdout.sink, F_SETFD, FD_CLOEXEC);
fcntl(pstderr.sink, F_SETFD, FD_CLOEXEC);
#endif
I would however recommend to use boost.asio and wait asynchronously for the exit of the subprocess and close the pipes there.
Just FYI: I've worked on boost-process 0.6 which has a different interface but makes the asio stuff much easier. This will hopefully be in review in October/November, so it might become an official boost library soon. It's currently in beta so you might want to check that one out.
Related
I'm new to the boost::asio, and boost::process libraries and I've come across a problem which I'm struggling to find a solution for...
Consider that I have a small toy program that does the following:
Firstly, fork()s itself into a parent-branch and a child-branch.
The child-branch then uses the boost::process::child class to invoke the unix command ls in an asynchronous context.
The child-branch supplies the boost::process::child class with a boost::process::async_pipe to direct std_out to.
The parent-branch wishes to read what has been written to the pipe, line by line, and process it further.
Currently, my implementation of this works up to a point. However, the read_loop() call in the parent-branch does not terminate. It is almost as if it never reaches EOF, or is blocked. Why is this?
Here is my MWE:
#include <boost/process.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <string>
#include <unistd.h>
void read_loop(boost::process::async_pipe& pipe)
{
static boost::asio::streambuf buffer;
boost::asio::async_read_until(
pipe,
buffer,
'\n',
[&](boost::system::error_code error_code, std::size_t bytes) {
if (!error_code) {
std::istream is(&buffer);
if (std::string line; std::getline(is, line)) {
std::cout << "Read Line: " << line << "\n";
}
read_loop(pipe);
}
else {
std::cout << "Error in read_loop()!\n";
pipe.close();
}
}
);
}
int main(int argc, char* argv[])
{
boost::asio::io_context io_context{};
boost::process::async_pipe pipe{ io_context };
io_context.notify_fork(boost::asio::io_context::fork_prepare);
pid_t pid{ fork() };
if (pid == 0) {
io_context.notify_fork(boost::asio::io_context::fork_child);
boost::process::child child(
boost::process::args({ "/usr/bin/ls", "/etc/" }),
boost::process::std_out > pipe,
boost::process::on_exit([&](int exit, std::error_code error_code) { std::cout << "[Exited with code " << exit << " (" << error_code.message() << ")]\n"; }),
io_context
);
io_context.run();
}
else {
io_context.notify_fork(boost::asio::io_context::fork_parent);
read_loop(pipe);
io_context.run();
}
return 0;
}
Which will successfully give the (abridged) output, as expected:
Read Line: adduser.conf
...
[Exited with code 0 (Success)]
...
Read Line: zsh_command_not_found
but will then just hang until it is forcibly killed.
Which leaves the main question, why does my read_loop() function end up blocking/not exiting correctly?
Thanks in advance!
Chasing The Symptom
The process not "seeing" EOF makes me think you have to close either end of the pipe. This is somewhat hacky, but works:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/process.hpp>
#include <iostream>
namespace bp = boost::process;
void read_loop(bp::async_pipe& pipe) {
static boost::asio::streambuf buffer;
using boost::system::error_code;
async_read_until( //
pipe, buffer, '\n', [&](error_code ec, [[maybe_unused]] size_t bytes) {
// std::cout << "Handler " << ec.message() << " bytes:" << bytes << " (" <<
// buffer.size() << ")" << std::endl;
if (!ec) {
std::istream is(&buffer);
if (std::string line; std::getline(is, line)) {
std::cout << "Read Line: " << line << "\n";
}
read_loop(pipe);
} else {
std::cout << "Loop exit (" << ec.message() << ")" << std::endl;
pipe.close();
}
});
}
int main() {
boost::asio::io_context ioc{};
bp::async_pipe pipe{ioc};
ioc.notify_fork(boost::asio::io_context::fork_prepare);
pid_t pid{fork()};
if (pid == 0) {
ioc.notify_fork(boost::asio::io_context::fork_child);
bp::child child( //
bp::args({"/usr/bin/ls", "/etc/"}), bp::std_out > pipe, bp::std_in.close(),
bp::on_exit([&](int exit, std::error_code ec) {
std::cout << "[Exited with code " << exit << " (" << ec.message() << ")]\n";
pipe.close();
}),
ioc);
ioc.run();
} else {
ioc.notify_fork(boost::asio::io_context::fork_parent);
std::move(pipe).sink().close();
read_loop(pipe);
ioc.run();
}
}
Side note: I guess it would be nice to have a more unhacky way to specify this, like (bp::std_in < pipe).close() or so.
Fixing The Root Cause
When using Boost Process, the fork is completely redundant. Boost Process literally does the fork for you, complete with correct service notification and file descriptor handling.
You'll find the code becomes a lot simpler and also handles the closing correctly (likely because some assumptions within Boost Process implementation details):
Live On Coliru
#include <boost/asio.hpp>
#include <boost/process.hpp>
#include <iostream>
namespace bp = boost::process;
void read_loop(bp::async_pipe& pipe) {
static boost::asio::streambuf buffer;
static std::string line; // re-used because we can
async_read_until( //
pipe, buffer, '\n',
[&](boost::system::error_code ec, size_t /*bytes*/) {
if (ec) {
std::cout << "Loop exit (" << ec.message() << ")" << std::endl;
return;
}
if (getline(std::istream(&buffer), line))
std::cout << "Read Line: " << line << "\n";
read_loop(pipe);
});
}
int main() {
boost::asio::io_context ioc{};
bp::async_pipe pipe{ioc};
bp::child child( //
bp::args({"/bin/ls", "/etc/"}), bp::std_out > pipe,
bp::on_exit([&](int exit, std::error_code ec) {
std::cout << "[Exited with " << exit << " (" << ec.message()
<< ")]\n";
}));
read_loop(pipe);
ioc.run();
}
I am writing an application that needs to open another process and get its output. Everywhere I've read online says I have to use popen and read from the file.
But I can't read from it. The output of the command gets outputted into the console window of the calling application. Below is the code I am using. I added some prints to debug.
#include <string>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <array>
int main()
{
// some command that fails to execute properly.
std::string command("ls afskfksakfafkas");
std::array<char, 128> buffer;
std::string result;
std::cout << "Opening reading pipe" << std::endl;
FILE* pipe = popen(command.c_str(), "r");
if (!pipe)
{
std::cerr << "Couldn't start command." << std::endl;
return 0;
}
while (fgets(buffer.data(), 128, pipe) != NULL) {
std::cout << "Reading..." << std::endl;
result += buffer.data();
}
auto returnCode = pclose(pipe);
std::cout << result << std::endl;
std::cout << returnCode << std::endl;
return 0;
}
Reading is never actually printed to my cout and result is an empty string. I clearly see the output of the command in my terminal. If the command exits gracefully the behaviour is as expected. But I only capture the output for error cases.
Popen doesn't capture stderr only stdout. Redirecting stderr to stdout fixes the issue.
#include <string>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <array>
int main()
{
std::string command("ls afskfksakfafkas 2>&1");
std::array<char, 128> buffer;
std::string result;
std::cout << "Opening reading pipe" << std::endl;
FILE* pipe = popen(command.c_str(), "r");
if (!pipe)
{
std::cerr << "Couldn't start command." << std::endl;
return 0;
}
while (fgets(buffer.data(), 128, pipe) != NULL) {
std::cout << "Reading..." << std::endl;
result += buffer.data();
}
auto returnCode = pclose(pipe);
std::cout << result << std::endl;
std::cout << returnCode << std::endl;
return 0;
}
You have to add "2>&1" at the end of command string
command.append(" 2>&1");
there is a full example https://www.jeremymorgan.com/tutorials/c-programming/how-to-capture-the-output-of-a-linux-command-in-c/
I have two threads that start up a child process each. The first application is a binary that runs quite long. The second exits quite quickly.
There is a race condition that sometimes causes this to fail. Below I have a minimum viable code example.
It uses Boost Process 0.5, which uses the standard fork / execve / dup2 system. There are some hacks regarding how Boost Process works, but in general it works quite well.
The parent process starts up a lot more processes, and in general it works.
I can't readily boot processes one at a time, especially since I don't know which parts can't interleave.
Any ideas on why this would hang?
Expected output:
/etc/init.d/led restart: Creating child
Creating child1
Reading STDOUT
/etc/init.d/led restart: Waiting for it to exit
Reading std_err_pipe
wait_for_exit(pullapp);
Reading std_out_pipe
< file list>
Done
However, often, but not always, it stops at std_err_pipe.
#include <iostream>
#include <string>
#include <vector>
#include <boost/iostreams/stream.hpp>
#include <boost/process.hpp>
#include <boost/thread.hpp>
void run_sleep()
{
int exit_code;
std::string str;
std::vector< std::string > args;
boost::shared_ptr<boost::process::child> child;
args.push_back(boost::process::search_path("sleep"));
args.push_back("20");
boost::iostreams::stream< boost::iostreams::file_descriptor_source >
out_stream;
boost::process::pipe out_pipe = boost::process::create_pipe();
{
//MUST BE IN SEPARATE SCOPE SO SINK AND SOURCE ARE DESTROYED
// See http://stackoverflow.com/a/12469478/5151127
boost::iostreams::file_descriptor_sink out_sink
(out_pipe.sink, boost::iostreams::close_handle);
boost::iostreams::file_descriptor_source out_source
(out_pipe.source, boost::iostreams::close_handle);
std::cout << "Creating child1" << std::endl;
child.reset(new boost::process::child(
boost::process::execute(
boost::process::initializers::run_exe(args[0]),
boost::process::initializers::set_args(args),
boost::process::initializers::bind_stdout(out_sink),
boost::process::initializers::bind_stderr(out_sink)
)
));
out_stream.open(out_source);
}
std::cout << "Reading STDOUT" << std::endl;
while( out_stream ) {
std::string line;
std::getline(out_stream, line);
std::cout << line << std::endl;
}
std::cout << "wait_for_exit(pullapp);" << std::endl;
exit_code = wait_for_exit(*child);
child.reset();
return;
}
void run_ls()
{
int exit_code;
std::string str;
std::vector< std::string > args ;
args.push_back(boost::process::search_path("ls"));
args.push_back("/lib");
boost::process::pipe std_out_pipe = boost::process::create_pipe();
boost::process::pipe std_err_pipe = boost::process::create_pipe();
std::cout << "/etc/init.d/led restart: Creating child" << std::endl;
{
boost::process::child child = boost::process::execute(
boost::process::initializers::set_args(args),
boost::process::initializers::bind_stdout(
boost::iostreams::file_descriptor_sink(
std_out_pipe.sink,
boost::iostreams::close_handle
)
),
boost::process::initializers::bind_stderr(
boost::iostreams::file_descriptor_sink(
std_err_pipe.sink,
boost::iostreams::close_handle
)
),
boost::process::initializers::throw_on_error()
);
std::cout << "/etc/init.d/led restart: Waiting for it to exit" << std::endl;
exit_code = wait_for_exit(child);
}
{ //with std_err_stream, istream
boost::iostreams::stream< boost::iostreams::file_descriptor_source >
std_err_stream(
boost::iostreams::file_descriptor_source(
std_err_pipe.source, boost::iostreams::close_handle
)
);
std::cout << "Reading std_err_pipe" << std::endl;
std::istream istream(std_err_stream.rdbuf());
while( istream ) {
getline(istream, str);
std::cout << str << std::endl;
}
}
{ //with std_out_stream, istream
boost::iostreams::stream< boost::iostreams::file_descriptor_source >
std_out_stream(
boost::iostreams::file_descriptor_source(
std_out_pipe.source, boost::iostreams::close_handle
)
);
std::cout << "Reading std_out_pipe" << std::endl;
std::istream istream(std_out_stream.rdbuf());
while( istream ) {
getline(istream, str);
std::cout << str << std::endl;
}
}
std::cout << "Done" << std::endl;
}
int main()
{
boost::thread run_sleep_tr(run_sleep);
boost::thread run_ls_tr(run_ls);
run_sleep_tr.join();
run_ls_tr.join();
return 0;
}
(Save as process-test.cpp and compile with g++ process-test.cpp -o process-test -lboost_iostreams -lboost_filesystem -lboost_thread -lboost_system)
Apparently this is because file handles end up in multiple processes. Those processes don't close those handles, so the parent remains waiting.
For Linux the fix is relatively easy; the pipe should be created with O_CLOEXEC in create_pipe. The dup2 call in the bind_* methods clears this flag, which is enough for the pipe to work properly.
On Windows, I haven't really found a solution yet. You have to mark the handle as inheritable. It may be possible to do this in the executor() method, but maybe this requires a global mutex. I haven't had the time to properly look into it yet.
I'm not sure if "use boost.process 0.6" counts as an answer, but that does that for you. After a few bug-reports that is.
On windows closing the sink in the father process should suffice.
I have a multi-threaded C++03 application that presently uses popen() to invoke itself (same binary) and ssh (different binary) again in a new process and reads the output, however, when porting to Android NDK this is posing some issues such as not not having permissions to access ssh, so I'm linking in Dropbear ssh to my application to try and avoid that issue. Further, my current popen solution requires that stdout and stderr be merged together into a single FD which is a bit messy and I'd like to stop doing that.
I would think the pipe code could be simplified by using fork() instead but wonder how to drop all of the parent's stack/memory which is not needed in the child of the fork? Here is a snippet of the old working code:
#include <iostream>
#include <stdio.h>
#include <string>
#include <errno.h>
using std::endl;
using std::cerr;
using std::cout;
using std::string;
void
doPipe()
{
// Redirect stderr to stdout with '2>&1' so that we see any error messages
// in the pipe output.
const string selfCmd = "/path/to/self/binary arg1 arg2 arg3 2>&1";
FILE *fPtr = ::popen(selfCmd.c_str(), "r");
const int bufSize = 4096;
char buf[bufSize + 1];
if (fPtr == NULL) {
cerr << "Failed attempt to popen '" << selfCmd << "'." << endl;
} else {
cout << "Result of: '" << selfCmd << "':\n";
while (true) {
if (::fgets(buf, bufSize, fPtr) == NULL) {
if (!::feof(fPtr)) {
cerr << "Failed attempt to fgets '" << selfCmd << "'." << endl;
}
break;
} else {
cout << buf;
}
}
if (pclose(fPtr) == -1) {
if (errno != 10) {
cerr << "Failed attempt to pclose '" << selfCmd << "'." << endl;
}
}
cout << "\n";
}
}
So far, this is loosely what I have done to convert to fork(), but fork needlessly duplicates the entire parent process memory space. Further, it does not quite work, because the parent never sees EOF on the outFD it is reading from the pipe(). Where else do I need to close the FDs for this to work? How can I do something like execlp() without supplying a binary path (not easily available on Android) but instead start over with the same binary and a blank image with new args?
#include <iostream>
#include <stdio.h>
#include <string>
#include <errno.h>
using std::endl;
using std::cerr;
using std::cout;
using std::string;
int
selfAction(int argc, char *argv[], int &outFD, int &errFD)
{
pid_t childPid; // Process id used for current process.
// fd[0] is the read end of the pipe and fd[1] is the write end of the pipe.
int fd[2]; // Pipe for normal communication between parent/child.
int fdErr[2]; // Pipe for error communication between parent/child.
// Create a pipe for IPC between child and parent.
const int pipeResult = pipe(fd);
if (pipeResult) {
cerr << "selfAction normal pipe failed: " << errno << ".\n";
return -1;
}
const int errorPipeResult = pipe(fdErr);
if (errorPipeResult) {
cerr << "selfAction error pipe failed: " << errno << ".\n";
return -1;
}
// Fork - error.
if ((childPid = fork()) < 0) {
cerr << "selfAction fork failed: " << errno << ".\n";
return -1;
} else if (childPid == 0) { // Fork -> child.
// Close read end of pipe.
::close(fd[0]);
::close(fdErr[0]);
// Close stdout and set fd[1] to it, this way any stdout of the child is
// piped to the parent.
::dup2(fd[1], STDOUT_FILENO);
::dup2(fdErr[1], STDERR_FILENO);
// Close write end of pipe.
::close(fd[1]);
::close(fdErr[1]);
// Exit child process.
exit(main(argc, argv));
} else { // Fork -> parent.
// Close write end of pipe.
::close(fd[1]);
::close(fdErr[1]);
// Provide fd's to our caller for stdout and stderr:
outFD = fd[0];
errFD = fdErr[0];
return 0;
}
}
void
doFork()
{
int argc = 4;
char *argv[4] = { "/path/to/self/binary", "arg1", "arg2", "arg3" };
int outFD = -1;
int errFD = -1;
int result = selfAction(argc, argv, outFD, errFD);
if (result) {
cerr << "Failed to execute selfAction." << endl;
return;
}
FILE *outFile = fdopen(outFD, "r");
FILE *errFile = fdopen(errFD, "r");
const int bufSize = 4096;
char buf[bufSize + 1];
if (outFile == NULL) {
cerr << "Failed attempt to open fork file." << endl;
return;
} else {
cout << "Result:\n";
while (true) {
if (::fgets(buf, bufSize, outFile) == NULL) {
if (!::feof(outFile)) {
cerr << "Failed attempt to fgets." << endl;
}
break;
} else {
cout << buf;
}
}
if (::close(outFD) == -1) {
if (errno != 10) {
cerr << "Failed attempt to close." << endl;
}
}
cout << "\n";
}
if (errFile == NULL) {
cerr << "Failed attempt to open fork file err." << endl;
return;
} else {
cerr << "Error result:\n";
while (true) {
if (::fgets(buf, bufSize, errFile) == NULL) {
if (!::feof(errFile)) {
cerr << "Failed attempt to fgets err." << endl;
}
break;
} else {
cerr << buf;
}
}
if (::close(errFD) == -1) {
if (errno != 10) {
cerr << "Failed attempt to close err." << endl;
}
}
cerr << "\n";
}
}
There are two kinds of child processes created in this fashion with different tasks in my application:
SSH to another machine and invoke a server that will communicate back to the parent that is acting as a client.
Compute a signature, delta, or merge file using rsync.
First of all, popen is a very thin wrapper on top of fork() followed by exec() [and some call to pipe and dup and so on to manage the ends of a pipe] .
Second, the memory is only duplicated in form of "copy-on-write" memory - meaning that unless one of the processes writes to some page, the actual physical memory is shared between the two processes.
It does mean, of course, the OS has to create a memory map with 4-8 bytes per 4KB [in typical cases] (probably plus some internal OS data to track how many copies there are of that page and stuff - but as long as the page remains the same one as the parent process, the child page uses the parent processes internal data). Compared to everything else involved in creating a new process and loading an executable file into the new process, it's a pretty small part of the time. Since you are almost immediately doing exec, not much of the parent process' memory will be touched, so very little will happen there.
My advice would be that if popen works, keep using popen. If popen doesn't quite do what you want for some reason, then use fork + exec - but make sure you know what the reason for doing so is.
I have a C++ program that uses boost log, and I load a user-provided dynamic link library. I'd like to redirect stderr to the boost log, so that anytime the user's library does:
std::cerr << "Some stuff";
It produces the same result** as:
BOOST_LOG_SEV(log,info) << "Some stuff";
Is this possible, and if so then how do I do it?
(Also, I'm not sure what to do about the severity... since cerr << doesn't privide severity information. I'm open to suggestions on that as well...)
** By "same result" I mean that it gets logged to the same log file as the rest of the log messages, and the same log formatter is applied to those lines.
Here's my C++11 implementation. This class can be used for anything (not just boost) to capture stdout/stderr on a line-by-line basis and calling a user function (could be a lambda) to process it.
Warning: if you redirect stderr and stdout and are using Boost, then redirect stderr first, then stdout. Otherwise, Boost will write loopback the stderr message back to stdout, and you'll get a boost log entry inside another boost log entry.
Usage example
cout << "testing out before 1 2 3 " << endl;
cerr << "testing err before 1 2 3 " << endl;
{
StdErrHandler err([](const char* line){
BOOST_LOG_TRIVIAL(error) << "ERROR:" << strlen(line) << " " << line;
});
StdOutHandler out([](const char* line){
BOOST_LOG_TRIVIAL(info) << "OUT:" << strlen(line) << " " << line;
});
cout << "cout testing 1 2 3 " << endl;
cerr << "cerr testing 1 2 3 " << endl;
}
cout << "testing out after 1 2 3 " << endl;
cerr << "testing err after 1 2 3 " << endl;
Example output
pa-poca$ ./test
testing out before 1 2 3
testing err before 1 2 3
[2014-08-01 12:24:56.468335] [0x000007f89d8990d4] [error] ERROR:19 cerr testing 1 2 3
[2014-08-01 12:24:56.468360] [0x000007f89d8990d4] [info] OUT:19 cout testing 1 2 3
testing out after 1 2 3
testing err after 1 2 3
Code
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
class StdioHandler
{
private:
pid_t pid = 0;
int origfd;
int streamid;
int pipefd[2];
public:
enum class Stream
{
stdout = STDOUT_FILENO,
stderr = STDERR_FILENO
};
StdioHandler(Stream stream, std::function<void(const char*)> callback)
:streamid(static_cast<int>(stream))
{
origfd = dup(streamid);
pipe(pipefd); // create pipe
pid = fork(); //spawn a child process to handle output of pipe
if (pid == 0)
{
char line[256];
FILE* output;
close(pipefd[1]);
output = fdopen(pipefd[0], "r");
if (output)
{
while(fgets(line, sizeof(line), output))
{
int n = strlen(line);
if (n > 0)
if (line[n-1] == '\n') line[n-1] = 0;
callback(line);
}
fclose(output);
}
abort();
} else {
// connect input of pipe to
close(pipefd[0]);
dup2(pipefd[1], streamid);
}
}
~StdioHandler()
{
int status;
usleep(10000);
close(pipefd[1]);
kill(pid,SIGINT);
waitpid(pid, &status, 0);
dup2(origfd, streamid);
}
};
class StdOutHandler : public StdioHandler
{
public:
StdOutHandler(std::function<void(const char*)> callback) :
StdioHandler(Stream::stdout, callback)
{
}
};
class StdErrHandler : public StdioHandler
{
public:
StdErrHandler(std::function<void(const char*)> callback) :
StdioHandler(Stream::stderr, callback)
{
}
};
I am guessing , You can redirect File descriptor STDERR to your stream file descriptor [you have to get the file desc of your stream] using dup/dup2 api [its a posix api]