To create a boost::process with output redirection, you should do:
bp::ipstream out;
bp::child c(\"c++filt\", std_out > out);
Now, what
I've been there.
Indeed, launcher functions (not exactly factories, but composable procedural wrappers) were what I used.
We had a CommandRunner with a legacy implementation that I rewrote to use Boost. I'm skipping the public interface:
class CommandRunner {
public: struct IRunnerImpl;
};
The implementation was Pimpl-ed and worked with the base implementation storing mostly simple implementation-independent parameters:
struct CommandRunner::IRunnerImpl {
virtual ~IRunnerImpl() = default;
virtual void run() = 0;
virtual void ensure_completed() = 0;
virtual std::string to_string() const = 0;
friend class CommandRunner;
protected:
std::string _working_directory;
mylibrary::optional _time_constraint;
std::string _stdin_data;
int _redirected_output_fd = -1;
std::string _redirected_output_fname;
bool _discard_output = false;
int _exit_code;
std::string _stdout_str;
std::string _stderr_str;
bool _command_timed_out = false;
bool _sensitive_args = false;
string_map_t _env;
};
The core of ensure_completed was composed using helper lambdas like this:
try {
mylibrary::threads::safe_io_service safe_ios;
boost::asio::io_service& ios = safe_ios;
mylibrary::io::time::timer deadline(ios);
bp::group process_group;
bp::async_pipe input(ios);
std::future output, error;
if (_working_directory.empty())
_working_directory = ".";
auto on_exit = [this, &deadline](int exit, std::error_code ec) {
if (!_command_timed_out) {
_exit_code = exit;
}
deadline.cancel();
if (ec) s_logger.log(LOG_WARNING) << "Child process returned " << ec.message();
else s_logger.log(LOG_DEBUG) << "Child process returned";
};
auto launcher = [](auto&&... args) { return bp::child(std::forward(args).../*, bp::posix::fd.restrict_inherit()*/); };
auto redirect_out = [&](auto f) {
return [&,f](auto&&... args) {
if (_discard_output) {
if (_redirected_output_fd != -1 && !_redirected_output_fname.empty()) {
s_logger.log(LOG_ERR) << "Ignoring output redirection with set_discard_output. This is a bug.";
}
return f(std::forward(args)..., bp::std_out > bp::null, bp::std_err > bp::null);
}
if (_redirected_output_fd != -1 && !_redirected_output_fname.empty()) {
s_logger.log(LOG_WARNING) << "Conflicting output redirection, ignoring filename with descriptor";
}
if (_redirected_output_fd != -1) {
return f(std::forward(args)..., bp::posix::fd.bind(1, _redirected_output_fd), bp::std_err > error);
}
return _redirected_output_fname.empty()
? f(std::forward(args)..., bp::std_out > output, bp::std_err > error)
: f(std::forward(args)..., bp::std_out > _redirected_output_fname, bp::std_err > error);
};
};
bp::environment bp_env = boost::this_process::environment();
for (auto& p : _env)
bp_env[p.first] = p.second;
auto c = redirect_out(launcher)(_command_path, _args,
process_group,
bp::std_in < input,
bp::start_dir(_working_directory),
bp_env,
ios, bp::on_exit(on_exit)
);
if (_time_constraint) {
deadline.expires_from_now(*_time_constraint);
deadline.async_wait([&](boost::system::error_code ec) {
if (ec != boost::asio::error::operation_aborted) {
if (ec) {
s_logger.log(LOG_WARNING) << "Unexpected condition in CommandRunner deadline: " << ec.message();
}
_command_timed_out = true;
_exit_code = 1;
::killpg(process_group.native_handle(), SIGTERM);
deadline.expires_from_now(3s); // grace time
deadline.async_wait([&](boost::system::error_code ec) { if (!ec) process_group.terminate(); }); // timed out
}
});
}
boost::asio::async_write(input, bp::buffer(_stdin_data), [&input](auto ec, auto bytes_written){
if (ec) {
s_logger.log(LOG_WARNING) << "Standard input rejected: " << ec.message() << " after " << bytes_written << " bytes written";
}
may_fail([&] { input.close(); });
});
ios.run();
if (output.valid()) _stdout_str = output.get();
if (error.valid()) _stderr_str = error.get();
// make sure no grand children survive
if (process_group && process_group.joinable() && !process_group.wait_for(1s))
process_group.terminate();
// Note: even kills get the child reaped; 'on_exit' handler is
// actually the 'on wait_pid() complete'). No need for c.wait()
// in this scenario
//// may_fail([&] { if (c.running()) c.wait(); }); // to avoid zombies
} catch(bp::process_error const& e) {
if (e.code().value() != static_cast(std::errc::no_child_process)) throw;
}
This compiles, but doesn't have any public interface. Merely for expositional purposes.
Note:
safe_io_service (that guarantees fork synchronization and notifications on all active io_services)this does not include our patch that adds restricted FD inheritance:
auto launcher = [](auto&&... args) { return
bp::child(std::forward(args)...,
bp::posix::fd.restrict_inherit()); };
other crucial things like on-fork handlers for global (library) state have not been shown here (they used pthread_atfork and similar)
Compiling On Coliru
#include
#include
#include
#include
#include
#include
#include // ::killpg
#include
#include