提交 f5b384f5 编写于 作者: D Daniel Larimer

removing ssh from fc

上级 c2f9a372
#pragma once
#include <memory>
#include <functional>
#include <fc/filesystem.hpp>
namespace fc {
class path;
class logger;
namespace ssh {
namespace detail {
class client_impl;
class process_impl;
};
enum sftp_file_type {
named_pipe = 0010000,
directory = 0040000,
regular = 0100000,
symlink = 0120000
};
enum sftp_file_mode {
owner_mask = 0000700, /* RWX mask for owner */
owner_read = 0000400, /* R for owner */
owner_write = 0000200, /* W for owner */
owner_exec = 0000100, /* X for owner */
group_mask = 0000070, /* RWX mask for group */
group_read = 0000040, /* R for group */
group_write = 0000020, /* W for group */
group_exec = 0000010, /* X for group */
other_mask = 0000007, /* RWX mask for other */
other_read = 0000004, /* R for other */
other_write = 0000002, /* W for other */
other_exec = 0000001 /* X for other */
};
struct file_attrib {
file_attrib();
uint64_t size;
uint32_t uid;
uint32_t gid;
uint32_t permissions;
uint32_t atime;
uint32_t mtime;
bool exists();
bool is_file();
bool is_directory();
};
/**
* @brief Enables communication over ssh using libssh2.
*
* Because the client creates other resources that depend upon
* it, it can only be created as a std::shared_ptr<client> (aka client::ptr)
* via client::create();
*
*/
class client
{
public:
enum trace_level {
TRACE_NONE = 0,
TRACE_TRANS = (1<<1),
TRACE_KEX = (1<<2),
TRACE_AUTH = (1<<3),
TRACE_CONN = (1<<4),
TRACE_SCP = (1<<5),
TRACE_SFTP = (1<<6),
TRACE_ERROR = (1<<7),
TRACE_PUBLICKEY = (1<<8),
TRACE_SOCKET = (1<<9)
};
/**
* Everything but TRACE_ERROR will be logged at fc::log_level::debug, while
* TRACE_ERROR will be logged at fc::log_level::error
*
* @param bitmask comprised of values from trace_level
**/
void set_trace_level( int bitmask );
int get_trace_level()const;
/**
* Override the default logger used by fc::ssh::client
*/
void set_logger( const logger& lgr );
const logger& get_logger()const;
/**
* Connect, with no password specified. Authentication will try public key,
* (via agent or explicitly-set key), empty password, then keyboard-interactive
*/
void connect( const fc::string& user, const fc::string& host, uint16_t port = 22);
/**
* Connect, specifying a password to be used for password authentication
*/
void connect( const fc::string& user, const fc::string& pass, const fc::string& host, uint16_t port = 22);
/**
* @note THIS METHOD IS DEPRECATED and should be replace with:
*
* ssh::client_ptr sshc = std::make_shared<ssh::client>();
* sshc->connect( ... )
* ssh::process_ptr proc = std::make_shared<ssh::process>( sshc );
* proc->exec( ... )
*
*
* @brief execute command on remote machine
* @param pty_type - whether or not to request a PTY when executing this process, this is necessary
* for interactive (non-buffered) IO with the remote process, if left empty no pty will be
* requested
*
* @note Processes launched in this manner will fully buffer stdin and stdout regardless of whether
* the process calls flush(). If you need unbuffered (streaming, realtime) access to standard
* out then you must launch the process via a shell.
ssh::process exec( const fc::string& cmd, const fc::string& pty_type = "" );
*/
/**
* @brief upload a file to remote host
* @param progress a callback to report / cancel upload.
* The callback takes two parameters, bytes sent and file size. To continue the
* transfer, the callback should return true. To cancel the callback should return false.
*/
void scp_send( const fc::path& local_path, const fc::path& remote_path,
std::function<bool(uint64_t,uint64_t)> progress = [](uint64_t,uint64_t){return true;} );
/**
* @brief recursively sends the contents of local_dir to the remote_path
*
* If remote_path ends in '/' then a new directory at <code>remote_path/local_dir.filename()</code> will
* be created, otherwise <code>local_dir / *</code> will be copied to <code>remote_path / *</code>
*
* Progress will be reported as total bytes transferred for all files.
*/
void scp_send_dir( const fc::path& local_dir, const fc::path& remote_path,
std::function<bool(uint64_t,uint64_t)> progress = [](uint64_t,uint64_t){return true;} );
/**
* @pre remote_path is not a directory
* @post remote file is removed from the remote filesystem
*/
void rm( const fc::path& remote_path );
/**
* @pre remote_path is a directory
* @post remote directory is removed from the remote filesystem
*/
void rmdir( const fc::path& remote_path );
void rmdir_recursive( const fc::path& remote_path );
file_attrib stat( const fc::path& remote_path );
/**
* @pre all parent directories already exist.
* @pre remote_dir is not exist or is already a directory
* @post remote_dir exists.
*/
void mkdir( const fc::path& remote_dir, int mode = owner_read|owner_write|owner_exec );
/**
* Create all parent directories for remote_dir if they do not exist.
*
* @post remote_dir exists.
*/
void create_directories( const fc::path& remote_dir, int mode = owner_read|owner_write|owner_exec );
/**
* Sets whether the remote system is believed to be a Windows box (by default, it's
* assumed to be running UNIX. This alters how command-line arguments are quoted
* and possibly how filenames are altered when copying files
*/
void set_remote_system_is_windows(bool is_windows = true);
void close();
client();
~client();
private:
friend class process;
friend class detail::process_impl;
std::unique_ptr<detail::client_impl> my;
};
typedef std::shared_ptr<client> client_ptr;
} } // namespace fc::ssh
#ifndef MACE_SSH_ERROR_HPP
#define MACE_SSH_ERROR_HPP
#include <boost/exception/all.hpp>
#include <boost/format.hpp>
namespace mace { namespace ssh {
typedef boost::error_info<struct err_msg_,std::string> err_msg;
struct exception : public virtual boost::exception, public virtual std::exception {
const char* what()const throw() { return "exception"; }
virtual void rethrow()const { BOOST_THROW_EXCEPTION(*this); }
const std::string& message()const { return *boost::get_error_info<mace::ssh::err_msg>(*this); }
};
} } // mace::ssh
/**
* Helper macro for throwing exceptions with a message: THROW( "Hello World %1%, %2%", %"Hello" %"World" )
*/
#define MACE_SSH_THROW( MSG, ... ) \
do { \
BOOST_THROW_EXCEPTION( mace::ssh::exception() << mace::ssh::err_msg( (boost::format( MSG ) __VA_ARGS__ ).str() ) );\
} while(0)
#endif
#pragma once
#include <fc/interprocess/iprocess.hpp>
namespace fc { namespace ssh
{
class client;
namespace detail {
class process_impl;
};
/**
* Enables communication with a process executed via
* client::exec().
*
* Process can only be created by mace::ssh::client.
*/
class process : public iprocess
{
public:
virtual iprocess& exec( const fc::path& exe, std::vector<std::string> args,
const fc::path& work_dir = fc::path(), exec_opts opts = open_all );
/**
* Blocks until the result code of the process has been returned.
*/
virtual int result();
/**
* Not supported. libssh2 does not support sending signals to remote processes.
* closing in_stream() is the best you can do
*/
virtual void kill();
/**
* @brief returns a stream that writes to the process' stdin
*/
virtual fc::buffered_ostream_ptr in_stream();
/**
* @brief returns a stream that reads from the process' stdout
*/
virtual fc::buffered_istream_ptr out_stream();
/**
* @brief returns a stream that reads from the process' stderr
*/
virtual fc::buffered_istream_ptr err_stream();
process( fc::ssh::client_ptr c );
~process();
private:
std::unique_ptr<detail::process_impl> my;
};
} } // fc::ssh
此差异已折叠。
#define NOMINMAX
#include <libssh2.h>
#include <libssh2_sftp.h>
#include <boost/asio.hpp>
#include <fc/ssh/client.hpp>
#include <fc/ssh/process.hpp>
#include <fc/thread/mutex.hpp>
#include <fc/thread/spin_lock.hpp>
#include <fc/thread/scoped_lock.hpp>
#include <fc/log/logger.hpp>
#include <fc/asio.hpp>
// include this to get acess to the details of the LIBSSH2_SESSION structure, so
// we can verify that all data has really been sent when libssh2 says it has.
#include <../src/libssh2_priv.h>
namespace fc { namespace ssh {
namespace detail {
class client_impl {
public:
client_impl();
~client_impl();
LIBSSH2_SESSION* session;
LIBSSH2_KNOWNHOSTS* knownhosts;
LIBSSH2_SFTP* sftp;
LIBSSH2_AGENT* agent;
std::unique_ptr<boost::asio::ip::tcp::socket> sock;
boost::asio::ip::tcp::endpoint endpt;
fc::mutex ssh_session_mutex;
fc::mutex channel_open_mutex;
fc::mutex process_startup_mutex;
fc::mutex scp_send_mutex;
fc::mutex scp_stat_mutex;
fc::mutex scp_mkdir_mutex;
fc::mutex scp_rmdir_mutex;
fc::mutex scp_unlink_mutex;
fc::mutex scp_close_mutex;
fc::mutex scp_readdir_mutex;
fc::mutex scp_open_mutex;
fc::string uname;
fc::string upass;
fc::string pubkey;
fc::string privkey;
fc::string passphrase;
fc::string hostname;
uint16_t port;
bool session_connected;
fc::promise<boost::system::error_code>::ptr read_prom;
fc::promise<boost::system::error_code>::ptr write_prom;
fc::spin_lock _spin_lock;
int _trace_level;
logger logr;
bool remote_system_is_windows; // true if windows, false if unix, used for command-line quoting and maybe filename translation
LIBSSH2_CHANNEL* open_channel( const fc::string& pty_type );
static void kbd_callback(const char *name, int name_len,
const char *instruction, int instruction_len, int num_prompts,
const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts,
LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses,
void **abstract);
void connect();
static void handle_trace( LIBSSH2_SESSION* session, void* context, const char* data, size_t length );
void close();
void authenticate();
bool try_pass();
bool try_keyboard();
bool try_pub_key();
// don't call this "unlocked" version directly
template <typename T>
int call_ssh2_function_unlocked(const T& lambda, bool check_for_errors = true);
// calls into libssh2, waits and retries the function if we get LIBSSH2_ERROR_EAGAIN
template <typename T>
int call_ssh2_function(const T& lambda, bool check_for_errors = true);
// calls into libssh2, waits and retries the function if we get LIBSSH2_ERROR_EAGAIN
// if libssh2 returns an error, get extended info and throw a message with ${code} and ${message}
// set appropriately.
template <typename T>
int call_ssh2_function_throw(const T& lambda, const char* message = "libssh2 call failed ${code} - ${message}", bool check_for_errors = true);
// this version is a little different, it handles functions like libssh2_sftp_init which return
// a pointer instead of an int. These retry automatically if the result is NULL and the error
// is LIBSSH2_ERROR_EAGAIN
template <typename return_type>
return_type call_ssh2_ptr_function_throw(std::function<return_type()> lambda, const char* message = "libssh2 call failed ${code} - ${message}", bool check_for_errors = true);
void wait_on_socket(int additionalDirections = 0);
void init_sftp();
};
// #define OLD_BLOCKING,
// the OLD_BLOCKING version of these functions will ensure that if a libssh2 function returns
// LIBSSH2_ERROR_EAGAIN, no other libssh2 functions will be called until that function has been
// called again and returned some other value.
//
// if you don't define this and use the new version of this, we will release the lock and let
// other libssh2 functions be called *unless* it appears that there was unwritten data.
//
// the OLD_BLOCKING version is too conservative -- if you try to read on a channel that doesn't
// have any data, you're likely to deadlock. The new version is not heavily tested and may be
// too lax, time will tell.
#ifdef OLD_BLOCKING
// don't call this "unlocked" version directly
template <typename T>
int client_impl::call_ssh2_function_unlocked(const T& lambda, bool check_for_errors /* = true */) {
int ec = lambda();
while (ec == LIBSSH2_ERROR_EAGAIN ) {
wait_on_socket();
ec = lambda();
}
// this assert catches bugs in libssh2 if libssh2 returns ec != LIBSSH2_ERROR_EAGAIN
// but the internal session data indicates a data write is still in progress
// set check_for_errors to false when closing the connection
assert(!check_for_errors || !session->packet.olen);
return ec;
}
// calls into libssh2, waits and retries the function if we get LIBSSH2_ERROR_EAGAIN
template <typename T>
int client_impl::call_ssh2_function(const T& lambda, bool check_for_errors /* = true */) {
fc::scoped_lock<fc::mutex> lock(ssh_session_mutex);
return call_ssh2_function_unlocked(lambda, check_for_errors);
}
#else
// calls into libssh2, waits and retries the function if we get LIBSSH2_ERROR_EAGAIN
template <typename T>
int client_impl::call_ssh2_function(const T& lambda, bool check_for_errors /* = true */) {
fc::unique_lock<fc::mutex> lock(ssh_session_mutex);
int ec = lambda();
while (ec == LIBSSH2_ERROR_EAGAIN) {
bool unlock_to_wait = !session->packet.olen;
if (unlock_to_wait)
lock.unlock();
wait_on_socket();
if (unlock_to_wait)
lock.lock();
ec = lambda();
}
// this assert catches bugs in libssh2 if libssh2 returns ec != LIBSSH2_ERROR_EAGAIN
// but the internal session data indicates a data write is still in progress
// set check_for_errors to false when closing the connection
assert(!check_for_errors || !session->packet.olen);
return ec;
}
#endif
#ifdef OLD_BLOCKING
// calls into libssh2, waits and retries the function if we get LIBSSH2_ERROR_EAGAIN
// if libssh2 returns an error, get extended info and throw a message with ${code} and ${message}
// set appropriately.
template <typename T>
int client_impl::call_ssh2_function_throw(const T& lambda, const char* message /* = "libssh2 call failed ${code} - ${message}" */, bool check_for_errors /* = true */) {
fc::scoped_lock<fc::mutex> lock(ssh_session_mutex);
int ec = call_ssh2_function_unlocked(lambda, check_for_errors);
if (ec == LIBSSH2_ERROR_SFTP_PROTOCOL && sftp) {
ec = libssh2_sftp_last_error(sftp);
FC_THROW(message, ("code", ec).set("message", "SFTP protocol error"));
} else if( ec < 0 ) {
char* msg = 0;
ec = libssh2_session_last_error( session, &msg, 0, 0 );
FC_THROW(message, ("code",ec).set("message",msg));
}
return ec;
}
#else
// calls into libssh2, waits and retries the function if we get LIBSSH2_ERROR_EAGAIN
// if libssh2 returns an error, get extended info and throw a message with ${code} and ${message}
// set appropriately.
template <typename T>
int client_impl::call_ssh2_function_throw(const T& lambda, const char* message /* = "libssh2 call failed ${code} - ${message}" */, bool check_for_errors /* = true */) {
fc::unique_lock<fc::mutex> lock(ssh_session_mutex);
int ec = lambda();
while (ec == LIBSSH2_ERROR_EAGAIN) {
bool unlock_to_wait = !session->packet.olen;
if (unlock_to_wait)
lock.unlock();
wait_on_socket();
if (unlock_to_wait)
lock.lock();
ec = lambda();
}
// this assert catches bugs in libssh2 if libssh2 returns ec != LIBSSH2_ERROR_EAGAIN
// but the internal session data indicates a data write is still in progress
// set check_for_errors to false when closing the connection
assert(!check_for_errors || !session->packet.olen);
if (ec == LIBSSH2_ERROR_SFTP_PROTOCOL && sftp) {
ec = libssh2_sftp_last_error(sftp);
FC_THROW(message, ("code", ec).set("message", "SFTP protocol error"));
} else if( ec < 0 ) {
char* msg = 0;
ec = libssh2_session_last_error( session, &msg, 0, 0 );
FC_THROW(message, ("code",ec).set("message",msg));
}
return ec;
}
#endif
#ifdef OLD_BLOCKING
// this version is a little different, it handles functions like libssh2_sftp_init which return
// a pointer instead of an int. These retry automatically if the result is NULL and the error
// is LIBSSH2_ERROR_EAGAIN
template <typename return_type>
return_type client_impl::call_ssh2_ptr_function_throw(std::function<return_type()> lambda, const char* message /* = "libssh2 call failed ${code} - ${message}" */, bool check_for_errors /* = true */) {
fc::scoped_lock<fc::mutex> lock(ssh_session_mutex);
return_type ret = lambda();
while (!ret) {
char* msg = 0;
int ec = libssh2_session_last_error(session,&msg,NULL,0);
if ( ec == LIBSSH2_ERROR_EAGAIN ) {
wait_on_socket();
ret = lambda();
} else if (ec == LIBSSH2_ERROR_SFTP_PROTOCOL && sftp) {
ec = libssh2_sftp_last_error(sftp);
FC_THROW(message, ("code", ec).set("message", "SFTP protocol error"));
} else {
ec = libssh2_session_last_error( session, &msg, 0, 0 );
FC_THROW(message, ("code",ec).set("message",msg));
}
}
assert(!check_for_errors || !session->packet.olen);
return ret;
}
#else
// this version is a little different, it handles functions like libssh2_sftp_init which return
// a pointer instead of an int. These retry automatically if the result is NULL and the error
// is LIBSSH2_ERROR_EAGAIN
template <typename return_type>
return_type client_impl::call_ssh2_ptr_function_throw(std::function<return_type()> lambda, const char* message /* = "libssh2 call failed ${code} - ${message}" */, bool check_for_errors /* = true */) {
fc::unique_lock<fc::mutex> lock(ssh_session_mutex);
return_type ret = lambda();
while (!ret) {
char* msg = 0;
int ec = libssh2_session_last_error(session,&msg,NULL,0);
if ( ec == LIBSSH2_ERROR_EAGAIN ) {
bool unlock_to_wait = !session->packet.olen;
if (unlock_to_wait)
lock.unlock();
wait_on_socket();
if (unlock_to_wait)
lock.lock();
ret = lambda();
} else if (ec == LIBSSH2_ERROR_SFTP_PROTOCOL && sftp) {
ec = libssh2_sftp_last_error(sftp);
FC_THROW(message, ("code", ec).set("message", "SFTP protocol error"));
} else {
ec = libssh2_session_last_error( session, &msg, 0, 0 );
FC_THROW(message, ("code",ec).set("message",msg));
}
}
assert(!check_for_errors || !session->packet.olen);
return ret;
}
#endif
}
} }
#define NOMINMAX // prevent windows from defining min and max macros
#include <libssh2.h>
#include <libssh2_sftp.h>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <fc/ssh/client.hpp>
#include <fc/ssh/process.hpp>
#include <fc/io/sstream.hpp>
#include <fc/vector.hpp>
#include <fc/thread/unique_lock.hpp>
#include "client_impl.hpp"
#if defined (_MSC_VER)
#pragma warning (disable : 4355)
#endif
namespace fc { namespace ssh {
namespace detail {
class process_impl;
class process_istream : public fc::istream {
public:
process_istream( process_impl& p, int c )
:proc(p),chan(c){}
virtual size_t readsome( char* buf, size_t len );
virtual bool eof() const;
process_impl& proc;
int chan;
};
class process_ostream : public fc::ostream {
public:
process_ostream( process_impl& p )
:proc(p){}
virtual size_t writesome( const char* buf, size_t len );
virtual void close();
virtual void flush();
process_impl& proc;
};
class process_impl {
public:
process_impl( client_ptr c );
~process_impl();
//process_impl( const client& c, const fc::string& cmd, const fc::string& pty_type );
void exec(const fc::path& exe, vector<string> args,
const fc::path& work_dir /* = fc::path() */, fc::iprocess::exec_opts opts /* = open_all */);
int read_some( char* data, size_t len, int stream_id );
int write_some( const char* data, size_t len, int stream_id );
void flush();
void send_eof();
LIBSSH2_CHANNEL* chan;
client_ptr sshc;
buffered_ostream_ptr buffered_std_in;
buffered_istream_ptr buffered_std_out;
buffered_istream_ptr buffered_std_err;
fc::string command;
fc::promise<int>::ptr result;
fc::optional<int> return_code;
fc::ostring return_signal;
fc::ostring return_signal_message;
private:
static fc::string windows_shell_escape(const fc::string& str);
static fc::string unix_shell_escape(const fc::string& str);
static fc::string windows_shell_escape_command(const fc::path& exe, const vector<string>& args);
static fc::string unix_shell_escape_command(const fc::path& exe, const vector<string>& args);
};
} // end namespace detail
process::process(client_ptr c) :
my(new detail::process_impl(c))
{}
process::~process()
{}
iprocess& process::exec( const fc::path& exe, vector<string> args,
const fc::path& work_dir /* = fc::path() */, exec_opts opts /* = open_all */ ) {
my->exec(exe, args, work_dir, opts);
return *this;
}
/**
* Blocks until the result code of the process has been returned.
*/
int process::result() {
if (!my->return_code && !my->return_signal) {
// we don't have any cached exit status, so wait and obtain the values now
my->sshc->my->call_ssh2_function(boost::bind(libssh2_channel_wait_eof, my->chan));
my->sshc->my->call_ssh2_function_throw(boost::bind(libssh2_channel_wait_closed, my->chan),
"Error waiting on socket to close: ${message}");
char* exit_signal;
char* error_message;
libssh2_channel_get_exit_signal(my->chan, &exit_signal, NULL, &error_message, NULL, NULL, NULL);
if (exit_signal) {
// process terminated with a signal
my->return_signal = exit_signal;
libssh2_free(my->chan->session, exit_signal);
if (error_message) {
my->return_signal_message = error_message;
libssh2_free(my->chan->session, error_message);
}
} else
my->return_code = libssh2_channel_get_exit_status(my->chan);
}
if (my->return_signal)
FC_THROW("process terminated with signal ${signal}: ${signal_message}", ("signal", *my->return_signal)
("signal_message", my->return_signal_message ? *my->return_signal_message : ""));
else
return *my->return_code;
}
void process::kill() {
elog("error: fc::ssh::process::kill() not supported");
}
/**
* @brief returns a stream that writes to the procss' stdin
*/
fc::buffered_ostream_ptr process::in_stream() {
return my->buffered_std_in;
}
/**
* @brief returns a stream that reads from the process' stdout
*/
fc::buffered_istream_ptr process::out_stream() {
return my->buffered_std_out;
}
/**
* @brief returns a stream that reads from the process' stderr
*/
fc::buffered_istream_ptr process::err_stream() {
return my->buffered_std_err;
}
void detail::process_impl::flush() {
if( !chan ) return;
/* channel_flush deleates input buffer, and does not ensure writes go out
*
int ec = libssh2_channel_flush_ex( chan, LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA);
while( ec == LIBSSH2_ERROR_EAGAIN ) {
sshc.my->wait_on_socket();
ec = libssh2_channel_flush_ex( chan, LIBSSH2_CHANNEL_FLUSH_EXTENDED_DATA );
}
ec = libssh2_channel_flush( chan );
while( ec == LIBSSH2_ERROR_EAGAIN ) {
sshc.my->wait_on_socket();
ec = libssh2_channel_flush( chan );
}
if( ec < 0 ) {
FC_THROW( "ssh flush failed", ( "channel_error", ec) );
}
*/
}
int detail::process_impl::read_some( char* data, size_t len, int stream_id ){
if( !sshc->my->session ) { FC_THROW( "Session closed" ); }
int rc;
char* buf = data;
size_t buflen = len;
do {
rc = sshc->my->call_ssh2_function_throw(boost::bind(libssh2_channel_read_ex, chan, stream_id, buf, buflen),
"read failed: ${message}");
if( rc > 0 ) {
buf += rc;
buflen -= rc;
return buf-data;
} else if( rc == 0 ) {
if( libssh2_channel_eof( chan ) )
return -1; // eof
sshc->my->wait_on_socket();
}
} while( rc >= 0 && buflen);
return buf-data;
}
int detail::process_impl::write_some( const char* data, size_t len, int stream_id ) {
if( !sshc->my->session ) { FC_THROW( "Session closed" ); }
int rc;
const char* buf = data;
size_t buflen = len;
do {
rc = sshc->my->call_ssh2_function_throw(boost::bind(libssh2_channel_write_ex, chan, stream_id, buf, buflen),
"write failed: ${message}");
if( rc > 0 ) {
buf += rc;
buflen -= rc;
return buf-data;
} else if( rc == 0 ) {
if( libssh2_channel_eof( chan ) ) {
FC_THROW( "EOF" );
//return -1; // eof
}
}
} while( rc >= 0 && buflen);
return buf-data;
}
void detail::process_impl::send_eof() {
if( sshc->my->session )
sshc->my->call_ssh2_function_throw(boost::bind(libssh2_channel_send_eof, chan),
"send eof failed: ${message}");
}
size_t detail::process_istream::readsome( char* buf, size_t len ) {
int bytesRead = proc.read_some(buf, len, chan);
if (bytesRead < 0)
FC_THROW("EOF");
else
return bytesRead;
}
bool detail::process_istream::eof()const {
return 0 != libssh2_channel_eof( proc.chan );
}
size_t detail::process_ostream::writesome( const char* buf, size_t len ) {
return proc.write_some(buf, len, 0);
}
void detail::process_ostream::close(){
proc.send_eof();
}
void detail::process_ostream::flush(){
proc.flush();
}
detail::process_impl::process_impl( client_ptr c )
:chan(nullptr),
sshc(c),
buffered_std_in(new buffered_ostream(ostream_ptr(new process_ostream(*this)))),
buffered_std_out(new buffered_istream(istream_ptr(new process_istream(*this, 0)))),
buffered_std_err(new buffered_istream(istream_ptr(new process_istream(*this, SSH_EXTENDED_DATA_STDERR))))
{
}
detail::process_impl::~process_impl() {
if (chan) {
sshc->my->call_ssh2_function(boost::bind(libssh2_channel_free, chan));
chan = NULL;
}
}
// these rules work pretty well for a standard bash shell on unix
fc::string detail::process_impl::unix_shell_escape(const fc::string& str) {
if (str.find_first_of(" ;&|><*?`$(){}[]!#'\"") == fc::string::npos)
return str;
fc::string escaped_quotes(str);
for (size_t start = escaped_quotes.find("'");
start != fc::string::npos;
start = escaped_quotes.find("'", start + 5))
escaped_quotes.replace(start, 1, "'\"'\"'");
fc::string escaped_str("\'");
escaped_str += escaped_quotes;
escaped_str += "\'";
return escaped_str;
}
fc::string detail::process_impl::unix_shell_escape_command(const fc::path& exe, const vector<string>& args) {
fc::stringstream command_line;
command_line << unix_shell_escape(exe.string());
for (unsigned i = 0; i < args.size(); ++i)
command_line << " " << unix_shell_escape(args[i]);
return command_line.str();
}
// windows command-line escaping rules are a disaster, partly because how the command-line is
// parsed depends on what program you're running. In windows, the command line is passed in
// as a single string, and the process is left to interpret it as it sees fit. The standard
// C runtime uses one set of rules, the function CommandLineToArgvW usually used by
// GUI-mode programs uses a different set.
// Here we try to find a common denominator that works well for simple cases
// it's only minimally tested right now due to time constraints.
fc::string detail::process_impl::windows_shell_escape(const fc::string& str) {
if (str.find_first_of(" \"") == fc::string::npos)
return str;
fc::string escaped_quotes(str);
for (size_t start = escaped_quotes.find("\"");
start != fc::string::npos;
start = escaped_quotes.find("\"", start + 2))
escaped_quotes.replace(start, 1, "\\\"");
fc::string escaped_str("\"");
escaped_str += escaped_quotes;
escaped_str += "\"";
return escaped_str;
}
fc::string detail::process_impl::windows_shell_escape_command(const fc::path& exe, const vector<string>& args) {
fc::stringstream command_line;
command_line << windows_shell_escape(exe.string());
for (unsigned i = 0; i < args.size(); ++i)
command_line << " " << windows_shell_escape(args[i]);
return command_line.str();
}
void detail::process_impl::exec(const fc::path& exe, vector<string> args,
const fc::path& work_dir /* = fc::path() */,
fc::iprocess::exec_opts opts /* = open_all */) {
chan = sshc->my->open_channel("");
sshc->my->call_ssh2_function(boost::bind(libssh2_channel_handle_extended_data2, chan, LIBSSH2_CHANNEL_EXTENDED_DATA_NORMAL));
try {
fc::scoped_lock<fc::mutex> process_startup_lock(sshc->my->process_startup_mutex);
fc::string command_line = sshc->my->remote_system_is_windows ? windows_shell_escape_command(exe, args) : unix_shell_escape_command(exe, args);
sshc->my->call_ssh2_function_throw(boost::bind(libssh2_channel_process_startup, chan, "exec", sizeof("exec") - 1, command_line.c_str(), command_line.size()),
"exec failed: ${message}"); // equiv to libssh2_channel_exec(chan, cmd) macro
} catch (fc::exception& er) {
elog( "error starting process" );
FC_RETHROW_EXCEPTION(er, error, "error starting process");
}
}
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册