adding fc::process
This commit is contained in:
parent
ff226f9df4
commit
413f741ac8
2 changed files with 241 additions and 0 deletions
56
include/fc/process.hpp
Normal file
56
include/fc/process.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
#include <fc/shared_impl.hpp>
|
||||
#include <fc/future.hpp>
|
||||
|
||||
namespace fc {
|
||||
class istream;
|
||||
class ostream;
|
||||
class path;
|
||||
class string;
|
||||
template<typename> class vector;
|
||||
|
||||
/**
|
||||
* @brief start and manage an external process
|
||||
*
|
||||
* @note this class implements reference semantics.
|
||||
*/
|
||||
class process {
|
||||
public:
|
||||
enum exec_opts {
|
||||
open_none = 0,
|
||||
open_stdin = 0x01,
|
||||
open_stdout = 0x02,
|
||||
open_stderr = 0x04,
|
||||
open_all = open_stdin|open_stdout|open_stderr,
|
||||
};
|
||||
/**
|
||||
* Return a new process executing the specified exe with the specified args.
|
||||
*/
|
||||
fc::future<int> exec( const fc::path& exe, int opt = open_all );
|
||||
fc::future<int> exec( const fc::path& exe, const fc::path& wd, int opt = open_all );
|
||||
fc::future<int> exec( const fc::path& exe, fc::vector<fc::string>&& args , int opt = open_all );
|
||||
fc::future<int> exec( const fc::path& exe, fc::vector<fc::string>&& args, const fc::path& wd, int opt = open_all );
|
||||
|
||||
/**
|
||||
* Forcefully kills the process.
|
||||
*/
|
||||
void kill();
|
||||
|
||||
/**
|
||||
* @brief returns a stream that writes to the process' stdin
|
||||
*/
|
||||
fc::ostream& in_stream();
|
||||
|
||||
/**
|
||||
* @brief returns a stream that reads from the process' stdout
|
||||
*/
|
||||
fc::istream& out_stream();
|
||||
/**
|
||||
* @brief returns a stream that reads from the process' stderr
|
||||
*/
|
||||
fc::istream& err_stream();
|
||||
|
||||
FC_REFERENCE_TYPE(process)
|
||||
};
|
||||
|
||||
} // namespace fc
|
||||
185
src/process.cpp
Normal file
185
src/process.cpp
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
#include <fc/process.hpp>
|
||||
#include <fc/iostream.hpp>
|
||||
#include <fc/iostream_wrapper.hpp>
|
||||
#include <fc/asio.hpp>
|
||||
#include <fc/filesystem.hpp>
|
||||
#include <fc/vector.hpp>
|
||||
#include <boost/process.hpp>
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
|
||||
namespace fc {
|
||||
|
||||
namespace bp = boost::process;
|
||||
namespace io = boost::iostreams;
|
||||
|
||||
class process_sink : public io::sink {
|
||||
public:
|
||||
struct category : io::sink::category, io::flushable_tag {};
|
||||
typedef char type;
|
||||
|
||||
process_sink( std::shared_ptr<bp::pipe>& p ):m_in(p){}
|
||||
|
||||
std::streamsize write( const char* s, std::streamsize n ) {
|
||||
if( !m_in ) return -1;
|
||||
return static_cast<std::streamsize>(fc::asio::write( *m_in,
|
||||
boost::asio::const_buffers_1( s, static_cast<size_t>(n) ) ));
|
||||
}
|
||||
void close() { if(m_in) m_in->close(); }
|
||||
bool flush() { return true; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<bp::pipe>& m_in;
|
||||
};
|
||||
|
||||
class process_source : public io::source {
|
||||
public:
|
||||
typedef char type;
|
||||
|
||||
process_source( std::shared_ptr<bp::pipe>& pi )
|
||||
:m_pi(pi){}
|
||||
|
||||
std::streamsize read( char* s, std::streamsize n ) {
|
||||
if( !m_pi ) return -1;
|
||||
try {
|
||||
return static_cast<std::streamsize>(fc::asio::read_some( *m_pi, boost::asio::buffer( s, static_cast<size_t>(n) ) ));
|
||||
} catch ( const boost::system::system_error& e ) {
|
||||
if( e.code() == boost::asio::error::eof )
|
||||
return -1;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
private:
|
||||
std::shared_ptr<bp::pipe>& m_pi;
|
||||
};
|
||||
} // namespace fc
|
||||
|
||||
FC_START_SHARED_IMPL( fc::process )
|
||||
impl()
|
||||
:stat( fc::asio::default_io_service() ),
|
||||
std_out(process_source(outp)),
|
||||
std_err(process_source(errp)),
|
||||
std_in(process_sink(inp)),
|
||||
_ins(std_in),
|
||||
_outs(std_out),
|
||||
_errs(std_err){}
|
||||
|
||||
std::shared_ptr<bp::child> child;
|
||||
std::shared_ptr<bp::pipe> outp;
|
||||
std::shared_ptr<bp::pipe> errp;
|
||||
std::shared_ptr<bp::pipe> inp;
|
||||
|
||||
bp::status stat;
|
||||
bp::context pctx;
|
||||
|
||||
// provide useful buffering
|
||||
io::stream<fc::process_source> std_out;
|
||||
io::stream<fc::process_source> std_err;
|
||||
io::stream<fc::process_sink> std_in;
|
||||
|
||||
// adapt to ostream and istream interfaces
|
||||
fc::ostream_wrapper _ins;
|
||||
fc::istream_wrapper _outs;
|
||||
fc::istream_wrapper _errs;
|
||||
FC_END_SHARED_IMPL
|
||||
#include <fc/shared_impl.cpp>
|
||||
|
||||
namespace fc {
|
||||
|
||||
FC_REFERENCE_TYPE_IMPL( process )
|
||||
|
||||
|
||||
fc::future<int> process::exec( const fc::path& exe, fc::vector<fc::string>&& args,
|
||||
const fc::path& work_dir, int opt ) {
|
||||
|
||||
my->pctx.work_dir = work_dir.string();
|
||||
|
||||
if( opt&open_stdout)
|
||||
my->pctx.streams[boost::process::stdout_id] = bp::behavior::async_pipe();
|
||||
else
|
||||
my->pctx.streams[boost::process::stdout_id] = bp::behavior::null();
|
||||
|
||||
|
||||
if( opt& open_stderr )
|
||||
my->pctx.streams[boost::process::stderr_id] = bp::behavior::async_pipe();
|
||||
else
|
||||
my->pctx.streams[boost::process::stderr_id] = bp::behavior::null();
|
||||
|
||||
if( opt& open_stdout )
|
||||
my->pctx.streams[boost::process::stdin_id] = bp::behavior::async_pipe();
|
||||
else
|
||||
my->pctx.streams[boost::process::stdin_id] = bp::behavior::close();
|
||||
|
||||
std::vector<std::string> a;
|
||||
a.reserve(args.size());
|
||||
for( uint32_t i = 0; i < args.size(); ++i ) {
|
||||
a.push_back( args[i] );
|
||||
}
|
||||
my->child.reset( new bp::child( bp::create_child( exe.string(), fc::move(a), my->pctx ) ) );
|
||||
|
||||
if( opt & open_stdout ) {
|
||||
bp::handle outh = my->child->get_handle( bp::stdout_id );
|
||||
my->outp.reset( new bp::pipe( fc::asio::default_io_service(), outh.release() ) );
|
||||
}
|
||||
if( opt & open_stderr ) {
|
||||
bp::handle errh = my->child->get_handle( bp::stderr_id );
|
||||
my->errp.reset( new bp::pipe( fc::asio::default_io_service(), errh.release() ) );
|
||||
}
|
||||
if( opt & open_stdin ) {
|
||||
bp::handle inh = my->child->get_handle( bp::stdin_id );
|
||||
my->inp.reset( new bp::pipe( fc::asio::default_io_service(), inh.release() ) );
|
||||
}
|
||||
|
||||
|
||||
promise<int>::ptr p(new promise<int>("process"));
|
||||
my->stat.async_wait( my->child->get_id(), [=]( const boost::system::error_code& ec, int exit_code )
|
||||
{
|
||||
slog( "process::result %1%", exit_code );
|
||||
if( !ec ) {
|
||||
#ifdef BOOST_POSIX_API
|
||||
try {
|
||||
if( WIFEXITED(exit_code) )
|
||||
p->set_value( WEXITSTATUS(exit_code) );
|
||||
else {
|
||||
FC_THROW_MSG( "process exited with: %s ", strsignal(WTERMSIG(exit_code)) );
|
||||
}
|
||||
} catch ( ... ) {
|
||||
p->set_exception( fc::current_exception() );
|
||||
}
|
||||
#else
|
||||
p->set_value(exit_code);
|
||||
#endif
|
||||
}
|
||||
else p->set_exception( fc::copy_exception( boost::system::system_error(ec) ) );
|
||||
});
|
||||
return p;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcefully kills the process.
|
||||
*/
|
||||
void process::kill() {
|
||||
my->child->terminate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief returns a stream that writes to the process' stdin
|
||||
*/
|
||||
fc::ostream& process::in_stream() {
|
||||
return my->_ins;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief returns a stream that reads from the process' stdout
|
||||
*/
|
||||
fc::istream& process::out_stream() {
|
||||
return my->_outs;
|
||||
}
|
||||
/**
|
||||
* @brief returns a stream that reads from the process' stderr
|
||||
*/
|
||||
fc::istream& process::err_stream() {
|
||||
return my->_errs;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue