Remove unused ssh code
This commit is contained in:
parent
7f10088db8
commit
2b26a51b6c
6 changed files with 0 additions and 1607 deletions
|
|
@ -1,193 +0,0 @@
|
|||
#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
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
#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
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
#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
|
||||
|
|
@ -1,717 +0,0 @@
|
|||
#define NOMINMAX // prevent windows from defining min and max macros
|
||||
#include <libssh2.h>
|
||||
#include <libssh2_sftp.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <fc/filesystem.hpp>
|
||||
#include <fc/ssh/client.hpp>
|
||||
#include <fc/ssh/process.hpp>
|
||||
#include <fc/time.hpp>
|
||||
#include <fc/io/iostream.hpp>
|
||||
#include <fc/thread/thread.hpp>
|
||||
#include <fc/vector.hpp>
|
||||
#include <fc/interprocess/file_mapping.hpp>
|
||||
#include <fc/thread/unique_lock.hpp>
|
||||
#include <fc/asio.hpp>
|
||||
|
||||
#include "client_impl.hpp"
|
||||
|
||||
namespace fc { namespace ssh {
|
||||
|
||||
namespace detail {
|
||||
static int ssh_init = libssh2_init(0);
|
||||
}
|
||||
|
||||
client::client():my( new detail::client_impl() ){ (void)detail::ssh_init; /* fix unused warning...*/ }
|
||||
client::~client() { my->close(); }
|
||||
|
||||
void client::set_trace_level( int bitmask ) { my->_trace_level = bitmask; }
|
||||
int client::get_trace_level()const { return my->_trace_level; }
|
||||
const logger& client::get_logger()const { return my->logr; }
|
||||
void client::set_logger( const logger& l ) { my->logr = l; }
|
||||
|
||||
void client::connect( const fc::string& user, const fc::string& host, uint16_t port ) {
|
||||
my->hostname = host;
|
||||
my->uname = user;
|
||||
my->port = port;
|
||||
my->connect();
|
||||
}
|
||||
void client::connect( const fc::string& user, const fc::string& pass,
|
||||
const fc::string& host, uint16_t port ) {
|
||||
my->hostname = host;
|
||||
my->uname = user;
|
||||
my->upass = pass;
|
||||
my->port = port;
|
||||
|
||||
my->connect();
|
||||
}
|
||||
|
||||
void client::close() { my->close(); }
|
||||
|
||||
|
||||
// ssh::process client::exec( const fc::string& cmd, const fc::string& pty_type ) {
|
||||
// return ssh::process( *this, cmd, pty_type );
|
||||
// }
|
||||
|
||||
/**
|
||||
* @todo implement progress reporting.
|
||||
*/
|
||||
void client::scp_send_dir( const fc::path& local_dir, const fc::path& remote_path,
|
||||
std::function<bool(uint64_t,uint64_t)> progress )
|
||||
{
|
||||
fc::path remote_dir = remote_path;
|
||||
if( remote_dir.filename() == fc::path(".") )
|
||||
remote_dir /= local_dir.filename();
|
||||
|
||||
fc_dlog( my->logr, "scp -r ${local} ${remote}", ("local",local_dir)("remote",remote_dir) );
|
||||
create_directories( remote_dir );
|
||||
|
||||
directory_iterator ditr(local_dir);
|
||||
directory_iterator dend;
|
||||
|
||||
while( ditr != dend ) {
|
||||
if( (*ditr).filename() == "." ||
|
||||
(*ditr).filename() == ".." )
|
||||
{ }
|
||||
else if( fc::is_directory(*ditr) )
|
||||
{
|
||||
scp_send_dir( (*ditr), remote_dir / (*ditr).filename() );
|
||||
} else if( fc::is_regular_file(*ditr) ) {
|
||||
scp_send( *ditr, remote_dir / (*ditr).filename() );
|
||||
} else {
|
||||
fc_wlog( my->logr, "Skipping '${path}", ("path",fc::canonical(*ditr)) );
|
||||
}
|
||||
++ditr;
|
||||
}
|
||||
}
|
||||
|
||||
void client::scp_send( const fc::path& local_path, const fc::path& remote_path,
|
||||
std::function<bool(uint64_t,uint64_t)> progress ) {
|
||||
fc_wlog( my->logr, "scp ${local} ${remote}", ("local",local_path)("remote",remote_path ) );
|
||||
if( !fc::exists(local_path) ) {
|
||||
FC_THROW( "Source file '${file}' does not exist", ("file",local_path) ) ;
|
||||
}
|
||||
if( is_directory( local_path ) ) {
|
||||
FC_THROW( "Source file '${file}' is a directory, expected a file", ("file",local_path) ) ;
|
||||
}
|
||||
|
||||
// memory map the file
|
||||
uint64_t fsize = file_size(local_path);
|
||||
if( fsize == 0 ) {
|
||||
elog( "file size ${file_size}", ("file_size", fsize) );
|
||||
// TODO: handle empty file case
|
||||
if( progress ) progress(0,0);
|
||||
return;
|
||||
}
|
||||
file_mapping fmap( local_path.string().c_str(), read_only );
|
||||
|
||||
LIBSSH2_CHANNEL* chan = 0;
|
||||
time_t now;
|
||||
memset( &now, 0, sizeof(now) );
|
||||
|
||||
// TODO: preserve creation / modification date
|
||||
// TODO: perserve permissions / exec bit?
|
||||
try {
|
||||
// libssh2_scp_send64 stores state data in the session object, and it calls channel_open which
|
||||
// stores its own state data, so lock both.
|
||||
fc::scoped_lock<fc::mutex> channel_open_lock(my->channel_open_mutex);
|
||||
fc::scoped_lock<fc::mutex> scp_send_lock(my->scp_send_mutex);
|
||||
chan = my->call_ssh2_ptr_function_throw<LIBSSH2_CHANNEL*>(boost::bind(libssh2_scp_send64, my->session, remote_path.generic_string().c_str(), 0700, fsize, now, now ));
|
||||
} catch (fc::exception& er) {
|
||||
FC_RETHROW_EXCEPTION(er, error, "scp ${local_file} to ${remote_file} failed", ("local_file", local_path)("remote_file",remote_path));
|
||||
}
|
||||
uint64_t total_bytes_written = 0;
|
||||
try {
|
||||
const size_t max_mapping_size = 1024*1024*1024; // 1GB
|
||||
for (uint64_t current_offset = 0; current_offset < fsize; current_offset += max_mapping_size) {
|
||||
uint64_t total_bytes_left_to_send = fsize - current_offset;
|
||||
size_t bytes_to_send_this_iteration = (size_t)std::min<uint64_t>(total_bytes_left_to_send, max_mapping_size);
|
||||
mapped_region mr( fmap, fc::read_only, current_offset, bytes_to_send_this_iteration);
|
||||
size_t bytes_written_this_iteration = 0;
|
||||
char* pos = reinterpret_cast<char*>(mr.get_address());
|
||||
while( progress(total_bytes_written, fsize) && bytes_written_this_iteration < bytes_to_send_this_iteration) {
|
||||
int r = my->call_ssh2_function_throw(boost::bind(libssh2_channel_write_ex, chan, 0, pos,
|
||||
bytes_to_send_this_iteration - bytes_written_this_iteration),
|
||||
"scp failed ${code} - ${message}");
|
||||
bytes_written_this_iteration += r;
|
||||
total_bytes_written += r;
|
||||
pos += r;
|
||||
// fc_wlog( my->logr, "wrote ${bytes} bytes", ("bytes",r) );
|
||||
}
|
||||
}
|
||||
my->call_ssh2_function(boost::bind(libssh2_channel_send_eof, chan));
|
||||
my->call_ssh2_function(boost::bind(libssh2_channel_wait_eof, chan));
|
||||
my->call_ssh2_function(boost::bind(libssh2_channel_close, chan));
|
||||
} catch ( fc::exception& er ) {
|
||||
// clean up chan
|
||||
my->call_ssh2_function(boost::bind(libssh2_channel_free, chan));
|
||||
throw er;
|
||||
}
|
||||
my->call_ssh2_function_throw(boost::bind(libssh2_channel_free, chan),
|
||||
"scp failed ${code} - ${message}");
|
||||
}
|
||||
|
||||
|
||||
void client::rm( const fc::path& remote_path ) {
|
||||
try {
|
||||
auto s = stat(remote_path);
|
||||
if( s.is_directory() ) {
|
||||
FC_THROW( "sftp cannot remove directory ${path}", ("path",remote_path) );
|
||||
}
|
||||
else if( !s.exists() ) {
|
||||
return; // nothing to do
|
||||
}
|
||||
|
||||
{
|
||||
fc::scoped_lock<fc::mutex> scp_unlink_lock(my->scp_unlink_mutex);
|
||||
my->call_ssh2_function_throw(boost::bind(libssh2_sftp_unlink_ex, my->sftp, remote_path.generic_string().c_str(), remote_path.generic_string().size()),
|
||||
"sftp rm failed ${code}");
|
||||
}
|
||||
} catch ( fc::exception& er ) {
|
||||
FC_RETHROW_EXCEPTION( er, error, "sftp remove '${remote_path}' failed", ("remote_path",remote_path) );
|
||||
}
|
||||
}
|
||||
|
||||
void client::rmdir( const fc::path& remote_path ) {
|
||||
try {
|
||||
auto s = stat(remote_path);
|
||||
if( !s.is_directory() )
|
||||
FC_THROW( "sftp cannot rmdir non-directory ${path}", ("path",remote_path) );
|
||||
else if( !s.exists() )
|
||||
return; // nothing to do
|
||||
|
||||
{
|
||||
fc::scoped_lock<fc::mutex> scp_rmdir_lock(my->scp_rmdir_mutex);
|
||||
my->call_ssh2_function_throw(boost::bind(libssh2_sftp_rmdir_ex, my->sftp, remote_path.generic_string().c_str(), remote_path.generic_string().size()),
|
||||
"sftp rmdir failed ${code}");
|
||||
}
|
||||
} catch ( fc::exception& er ) {
|
||||
FC_RETHROW_EXCEPTION( er, error, "sftp rmdir '${remote_path}' failed", ("remote_path",remote_path) );
|
||||
}
|
||||
}
|
||||
|
||||
void client::rmdir_recursive( const fc::path& remote_path ) {
|
||||
try {
|
||||
auto s = stat(remote_path);
|
||||
if( !s.is_directory() )
|
||||
FC_THROW( "sftp cannot rmdir non-directory ${path}", ("path",remote_path) );
|
||||
else if( !s.exists() )
|
||||
return; // nothing to do
|
||||
|
||||
LIBSSH2_SFTP_HANDLE *dir_handle;
|
||||
{
|
||||
fc::scoped_lock<fc::mutex> scp_open_lock(my->scp_open_mutex);
|
||||
dir_handle =
|
||||
my->call_ssh2_ptr_function_throw<LIBSSH2_SFTP_HANDLE*>(boost::bind(libssh2_sftp_open_ex, my->sftp, remote_path.generic_string().c_str(), remote_path.generic_string().size(), 0, 0, LIBSSH2_SFTP_OPENDIR),
|
||||
"sftp libssh2_sftp_opendir failed ${code}");
|
||||
}
|
||||
do {
|
||||
char mem[512];
|
||||
LIBSSH2_SFTP_ATTRIBUTES attrs;
|
||||
|
||||
int rc;
|
||||
{
|
||||
fc::scoped_lock<fc::mutex> scp_readdir_lock(my->scp_readdir_mutex);
|
||||
rc = my->call_ssh2_function_throw(boost::bind(libssh2_sftp_readdir_ex, dir_handle, mem, sizeof(mem), (char*)NULL, 0, &attrs),
|
||||
"sftp readdir failed ${code}");
|
||||
}
|
||||
if (rc > 0) {
|
||||
fc::string file_or_dir_name(mem, rc);
|
||||
if (file_or_dir_name == "." || file_or_dir_name == "..")
|
||||
continue;
|
||||
fc::path full_remote_path = remote_path / file_or_dir_name;
|
||||
if (LIBSSH2_SFTP_S_ISDIR(attrs.permissions))
|
||||
rmdir_recursive(full_remote_path);
|
||||
else if (LIBSSH2_SFTP_S_ISREG(attrs.permissions)) {
|
||||
fc::scoped_lock<fc::mutex> scp_unlink_lock(my->scp_unlink_mutex);
|
||||
my->call_ssh2_function_throw(boost::bind(libssh2_sftp_unlink_ex, my->sftp, full_remote_path.generic_string().c_str(), full_remote_path.generic_string().size()),
|
||||
"sftp rm failed ${code}");
|
||||
}
|
||||
} else
|
||||
break;
|
||||
} while (1);
|
||||
|
||||
{
|
||||
fc::scoped_lock<fc::mutex> scp_close_lock(my->scp_close_mutex);
|
||||
my->call_ssh2_function_throw(boost::bind(libssh2_sftp_close_handle, dir_handle), "sftp libssh2_sftp_closedir failed ${code}");
|
||||
}
|
||||
{
|
||||
fc::scoped_lock<fc::mutex> scp_rmdir_lock(my->scp_rmdir_mutex);
|
||||
my->call_ssh2_function_throw(boost::bind(libssh2_sftp_rmdir_ex, my->sftp, remote_path.generic_string().c_str(), remote_path.generic_string().size()),
|
||||
"sftp rmdir failed ${code}");
|
||||
}
|
||||
} catch ( fc::exception& er ) {
|
||||
FC_RETHROW_EXCEPTION( er, error, "sftp rmdir recursive '${remote_path}' failed", ("remote_path",remote_path) );
|
||||
}
|
||||
}
|
||||
|
||||
file_attrib client::stat( const fc::path& remote_path ){
|
||||
my->init_sftp();
|
||||
LIBSSH2_SFTP_ATTRIBUTES att;
|
||||
int ec;
|
||||
{
|
||||
fc::scoped_lock<fc::mutex> scp_stat_lock(my->scp_stat_mutex);
|
||||
ec = my->call_ssh2_function(boost::bind(libssh2_sftp_stat_ex, my->sftp,
|
||||
remote_path.generic_string().c_str(), remote_path.generic_string().size(),
|
||||
LIBSSH2_SFTP_STAT, &att));
|
||||
}
|
||||
if( ec )
|
||||
return file_attrib();
|
||||
file_attrib ft;
|
||||
ft.size = att.filesize;
|
||||
ft.permissions = att.permissions;
|
||||
return ft;
|
||||
}
|
||||
void client::create_directories( const fc::path& rdir, int mode ) {
|
||||
boost::filesystem::path dir = rdir;
|
||||
boost::filesystem::path p;
|
||||
auto pitr = dir.begin();
|
||||
while( pitr != dir.end() ) {
|
||||
p /= *pitr;
|
||||
if( !stat( p ).exists() ) {
|
||||
mkdir(p,mode);
|
||||
}
|
||||
++pitr;
|
||||
}
|
||||
}
|
||||
|
||||
void client::mkdir( const fc::path& rdir, int mode ) {
|
||||
try {
|
||||
auto s = stat(rdir);
|
||||
if( s.is_directory() )
|
||||
return;
|
||||
else if( s.exists() )
|
||||
FC_THROW( "File already exists at path ${path}", ("path",rdir) );
|
||||
|
||||
{
|
||||
fc::scoped_lock<fc::mutex> scp_mkdir_lock(my->scp_mkdir_mutex);
|
||||
my->call_ssh2_function_throw(boost::bind(libssh2_sftp_mkdir_ex, my->sftp,
|
||||
rdir.generic_string().c_str(), rdir.generic_string().size(), mode),
|
||||
"sftp mkdir error");
|
||||
}
|
||||
} catch ( fc::exception& er ) {
|
||||
FC_RETHROW_EXCEPTION( er, error, "sftp failed to create directory '${directory}'", ( "directory", rdir ) );
|
||||
}
|
||||
}
|
||||
|
||||
void client::set_remote_system_is_windows(bool is_windows /* = true */) {
|
||||
my->remote_system_is_windows = is_windows;
|
||||
}
|
||||
|
||||
|
||||
file_attrib::file_attrib()
|
||||
:size(0),uid(0),gid(0),permissions(0),atime(0),mtime(0)
|
||||
{ }
|
||||
|
||||
bool file_attrib::is_directory() {
|
||||
return LIBSSH2_SFTP_S_ISDIR(permissions);
|
||||
}
|
||||
bool file_attrib::is_file() {
|
||||
return LIBSSH2_SFTP_S_ISREG(permissions);
|
||||
}
|
||||
bool file_attrib::exists() {
|
||||
return 0 != permissions;
|
||||
}
|
||||
|
||||
detail::client_impl::client_impl() :
|
||||
session(nullptr),
|
||||
knownhosts(nullptr),
|
||||
sftp(nullptr),
|
||||
agent(nullptr),
|
||||
_trace_level(0), // was LIBSSH2_TRACE_ERROR
|
||||
logr(fc::logger::get( "fc::ssh::client" )),
|
||||
remote_system_is_windows(false) {
|
||||
logr.set_parent( fc::logger::get( "default" ) );
|
||||
}
|
||||
|
||||
detail::client_impl::~client_impl() {
|
||||
close();
|
||||
}
|
||||
|
||||
/* static */
|
||||
void detail::client_impl::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) {
|
||||
detail::client_impl* self = (client_impl*)*abstract;
|
||||
|
||||
|
||||
for (int i = 0; i < num_prompts; i++) {
|
||||
fwrite(prompts[i].text, 1, prompts[i].length, stdout);
|
||||
|
||||
if( self->upass.size() == 0 ) {
|
||||
/** TODO: add keyboard callback here...
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
n = strlen(buf);
|
||||
while (n > 0 && strchr("\r\n", buf[n - 1]))
|
||||
n--;
|
||||
buf[n] = 0;
|
||||
|
||||
#ifdef WIN32 // fix warning
|
||||
# define strdup _strdup
|
||||
#endif
|
||||
responses[i].text = strdup(buf);
|
||||
responses[i].length = n;
|
||||
*/
|
||||
responses[i].text = nullptr;
|
||||
responses[i].length = 0;
|
||||
} else {
|
||||
responses[i].text = strdup(self->upass.c_str());
|
||||
responses[i].length = self->upass.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void detail::client_impl::connect() {
|
||||
try {
|
||||
if( libssh2_init(0) < 0 )
|
||||
FC_THROW( "Unable to init libssh2" );
|
||||
|
||||
auto eps = fc::asio::tcp::resolve( hostname, boost::lexical_cast<std::string>(port) );
|
||||
if( eps.size() == 0 )
|
||||
FC_THROW( "Unable to resolve host '${host}'", ("host",hostname) );
|
||||
|
||||
sock.reset( new boost::asio::ip::tcp::socket( fc::asio::default_io_service() ) );
|
||||
|
||||
bool resolved = false;
|
||||
for( uint32_t i = 0; i < eps.size(); ++i ) {
|
||||
std::stringstream ss; ss << eps[i];
|
||||
try {
|
||||
boost::system::error_code ec;
|
||||
fc_ilog( logr, "Attempting to connect to ${endpoint}", ("endpoint",ss.str().c_str()) );
|
||||
fc::asio::tcp::connect( *sock, eps[i] );
|
||||
endpt = eps[i];
|
||||
resolved = true;
|
||||
break;
|
||||
} catch ( fc::exception& er ) {
|
||||
fc_ilog( logr, "Failed to connect to ${endpoint}\n${error_reprot}",
|
||||
("endpoint",ss.str().c_str())("error_report", er.to_detail_string()) );
|
||||
sock->close();
|
||||
}
|
||||
}
|
||||
if( !resolved )
|
||||
FC_THROW( "Unable to connect to any resolved endpoint for ${host}:${port}",
|
||||
("host", hostname).set("port",port) );
|
||||
|
||||
session = libssh2_session_init();
|
||||
libssh2_trace( session, _trace_level );
|
||||
libssh2_trace_sethandler( session, this, client_impl::handle_trace );
|
||||
|
||||
*libssh2_session_abstract(session) = this;
|
||||
|
||||
libssh2_session_set_blocking( session, 0 );
|
||||
try {
|
||||
call_ssh2_function_throw(boost::bind(libssh2_session_handshake, session, sock->native()),
|
||||
"SSH Handshake error: ${code} - ${message}");
|
||||
} catch (fc::exception& er) {
|
||||
FC_RETHROW_EXCEPTION( er, error, "Error during SSH handshake" );;
|
||||
}
|
||||
//const char* fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_SHA1);
|
||||
//slog( "fingerprint: %s", fingerprint );
|
||||
|
||||
// try to authenticate, throw on error.
|
||||
try {
|
||||
authenticate();
|
||||
} catch (fc::exception& er) {
|
||||
FC_RETHROW_EXCEPTION( er, error, "Error during SSH authentication" );;
|
||||
}
|
||||
//slog(".");
|
||||
} catch ( fc::exception& er ) {
|
||||
elog( "Unable to connect to ssh server: ${detail}", ("detail", er.to_detail_string().c_str()) );
|
||||
close();
|
||||
FC_RETHROW_EXCEPTION( er, error, "Unable to connect to ssh server" );;
|
||||
} catch ( ... ) {
|
||||
close();
|
||||
FC_THROW( "Unable to connect to ssh server", ("exception", fc::except_str() ) );
|
||||
}
|
||||
}
|
||||
|
||||
/* static */
|
||||
void detail::client_impl::handle_trace( LIBSSH2_SESSION* session, void* context, const char* data, size_t length ) {
|
||||
client_impl* my = (client_impl*)context;
|
||||
fc::string str(data,length);
|
||||
fc_wlog( my->logr, "${message}", ("message",str) );
|
||||
}
|
||||
|
||||
void detail::client_impl::close() {
|
||||
if( session ) {
|
||||
if( sftp ) {
|
||||
try {
|
||||
call_ssh2_function(boost::bind(libssh2_sftp_shutdown, sftp));
|
||||
}catch(...){
|
||||
fc_wlog( logr, "caught closing sftp session" );
|
||||
}
|
||||
sftp = 0;
|
||||
}
|
||||
|
||||
if (agent) {
|
||||
libssh2_agent_disconnect(agent);
|
||||
libssh2_agent_free(agent);
|
||||
agent = nullptr;
|
||||
}
|
||||
|
||||
try {
|
||||
call_ssh2_function(boost::bind(libssh2_session_disconnect_ex, session, SSH_DISCONNECT_BY_APPLICATION, "exit cleanly", ""));
|
||||
call_ssh2_function(boost::bind(libssh2_session_free, session), false);
|
||||
} catch ( ... ){
|
||||
fc_wlog( logr, "caught freeing session" );
|
||||
}
|
||||
session = 0;
|
||||
try {
|
||||
if( sock )
|
||||
sock->close();
|
||||
} catch ( ... ){
|
||||
fc_wlog( logr, "caught error closing socket" );
|
||||
}
|
||||
sock.reset(0);
|
||||
try {
|
||||
if( read_prom )
|
||||
read_prom->wait();
|
||||
} catch ( ... ){
|
||||
fc_wlog( logr, "caught error waiting on read" );
|
||||
}
|
||||
try {
|
||||
if( write_prom )
|
||||
write_prom->wait();
|
||||
} catch ( ... ){
|
||||
fc_wlog( logr, "caught error waiting on write" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void detail::client_impl::authenticate() {
|
||||
try {
|
||||
char * alist = NULL;
|
||||
// libssh2_userauth_list has strange enough behavior that we can't use the
|
||||
// call_blocking_libssh2_function-type functions to wait and retry, so we must
|
||||
// explicitly lock around the libssh2_userauth_list calls.
|
||||
// hence, this anonymous scope:
|
||||
{
|
||||
fc::unique_lock<fc::mutex> lock(ssh_session_mutex);
|
||||
|
||||
alist = libssh2_userauth_list(session, uname.c_str(),uname.size());
|
||||
|
||||
if(alist==NULL) {
|
||||
char * msg = 0;
|
||||
int ec = 0;
|
||||
if(libssh2_userauth_authenticated(session))
|
||||
return; // CONNECTED!
|
||||
ec = libssh2_session_last_error(session,&msg,NULL,0);
|
||||
|
||||
while( !alist && (ec == LIBSSH2_ERROR_EAGAIN) ) {
|
||||
wait_on_socket();
|
||||
alist = libssh2_userauth_list(session, uname.c_str(), uname.size());
|
||||
ec = libssh2_session_last_error(session,&msg,NULL,0);
|
||||
}
|
||||
if( !alist ) {
|
||||
FC_THROW( "Error getting authorization list: ${code} - ${message}",
|
||||
("code",ec).set("message",msg));
|
||||
}
|
||||
}
|
||||
} // end anonymous scope
|
||||
|
||||
std::vector<std::string> split_alist;
|
||||
bool pubkey = false;
|
||||
bool pass = false;
|
||||
bool keybd = false;
|
||||
boost::split( split_alist, alist, boost::is_any_of(",") );
|
||||
std::for_each( split_alist.begin(), split_alist.end(), [&](const std::string& s){
|
||||
if( s == "publickey" )
|
||||
pubkey = true;
|
||||
else if( s == "password" )
|
||||
pass = true;
|
||||
else if( s == "keyboard-interactive" )
|
||||
keybd = true;
|
||||
else
|
||||
fc_dlog( logr, "Unknown/unsupported authentication type '${auth_type}'", ("auth_type",s.c_str()));
|
||||
});
|
||||
|
||||
if( pubkey && try_pub_key() )
|
||||
return;
|
||||
if( pass && try_pass() )
|
||||
return;
|
||||
if( keybd && try_keyboard() )
|
||||
return;
|
||||
} catch ( fc::exception& er ) {
|
||||
FC_RETHROW_EXCEPTION( er, error, "Unable to authenticate ssh connection" );
|
||||
}
|
||||
FC_THROW( "Unable to authenticate ssh connection" );
|
||||
} // authenticate()
|
||||
|
||||
bool detail::client_impl::try_pass() {
|
||||
return !call_ssh2_function(boost::bind(libssh2_userauth_password_ex, session, uname.c_str(), uname.size(),
|
||||
upass.c_str(), upass.size(), (LIBSSH2_PASSWD_CHANGEREQ_FUNC((*)))NULL));
|
||||
}
|
||||
bool detail::client_impl::try_keyboard() {
|
||||
return !call_ssh2_function(boost::bind(libssh2_userauth_keyboard_interactive_ex, session,
|
||||
uname.c_str(), uname.size(), &client_impl::kbd_callback));
|
||||
}
|
||||
bool detail::client_impl::try_pub_key() {
|
||||
if (privkey.size()) {
|
||||
if (!call_ssh2_function(boost::bind(libssh2_userauth_publickey_fromfile_ex,
|
||||
session,
|
||||
uname.c_str(), uname.size(),
|
||||
pubkey.c_str(),
|
||||
privkey.c_str(),
|
||||
passphrase.c_str())))
|
||||
return true; // successful authentication from file
|
||||
fc_ilog( logr, "failed to authenticate with private key from file '${privkey_filename}'", ("privkey_filename",privkey));
|
||||
} else
|
||||
fc_ilog( logr, "no private key file set, skiping pubkey authorization from file");
|
||||
|
||||
agent = libssh2_agent_init(session);
|
||||
if (!agent) {
|
||||
fc_wlog( logr, "failed to initialize ssh-agent support");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (call_ssh2_function(boost::bind(libssh2_agent_connect, agent))) {
|
||||
fc_ilog( logr, "failed to connect to ssh-agent");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (call_ssh2_function(boost::bind(libssh2_agent_list_identities, agent))) {
|
||||
fc_ilog( logr, "failed requesting identities from ssh-agent");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct libssh2_agent_publickey *prev_identity = NULL;
|
||||
while (1) {
|
||||
struct libssh2_agent_publickey *identity;
|
||||
int ec = call_ssh2_function(boost::bind(libssh2_agent_get_identity, agent, &identity, prev_identity));
|
||||
if (ec == 1)
|
||||
break; // done iterating over keys
|
||||
if (ec < 0) {
|
||||
fc_ilog( logr, "failed obtaining identity from ssh-agent");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (call_ssh2_function(boost::bind(libssh2_agent_userauth, agent, uname.c_str(), identity)))
|
||||
fc_ilog( logr, "unable to authenticate with public key '${key_comment}'", ("key_comment",identity->comment));
|
||||
else {
|
||||
fc_ilog( logr, "authenticated with public key '${key_comment}'", ("key_comment",identity->comment));
|
||||
return true;
|
||||
}
|
||||
prev_identity = identity;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void detail::client_impl::wait_on_socket(int additionalDirections /* = 0 */) {
|
||||
int dir = libssh2_session_block_directions(session);
|
||||
dir |= additionalDirections;
|
||||
if( !dir )
|
||||
return;
|
||||
|
||||
fc::promise<boost::system::error_code>::ptr rprom, wprom;
|
||||
if( dir & LIBSSH2_SESSION_BLOCK_INBOUND ) {
|
||||
fc::scoped_lock<fc::spin_lock> lock(this->_spin_lock);
|
||||
if( !read_prom ) {
|
||||
read_prom.reset( new fc::promise<boost::system::error_code>("read_prom") );
|
||||
sock->async_read_some( boost::asio::null_buffers(),
|
||||
[=]( const boost::system::error_code& e, size_t ) {
|
||||
fc::scoped_lock<fc::spin_lock> lock(this->_spin_lock);
|
||||
this->read_prom->set_value(e);
|
||||
this->read_prom.reset(nullptr);
|
||||
} );
|
||||
}
|
||||
rprom = read_prom;
|
||||
}
|
||||
|
||||
if( dir & LIBSSH2_SESSION_BLOCK_OUTBOUND ) {
|
||||
fc::scoped_lock<fc::spin_lock> lock(this->_spin_lock);
|
||||
if( !write_prom ) {
|
||||
write_prom.reset( new fc::promise<boost::system::error_code>("write_prom") );
|
||||
sock->async_write_some( boost::asio::null_buffers(),
|
||||
[=]( const boost::system::error_code& e, size_t ) {
|
||||
fc::scoped_lock<fc::spin_lock> lock(this->_spin_lock);
|
||||
this->write_prom->set_value(e);
|
||||
this->write_prom.reset(0);
|
||||
} );
|
||||
}
|
||||
wprom = write_prom;
|
||||
}
|
||||
|
||||
boost::system::error_code ec;
|
||||
if( rprom.get() && wprom.get() ) {
|
||||
typedef fc::future<boost::system::error_code> fprom;
|
||||
fprom fw(wprom);
|
||||
fprom fr(rprom);
|
||||
#if 0
|
||||
// EMF: at present there are known bugs in fc::wait_any, and it will fail to wake up
|
||||
// when one of the futures is ready.
|
||||
int r = fc::wait_any( fw, fr, fc::seconds(1) );
|
||||
#else
|
||||
int r;
|
||||
while (1) {
|
||||
if (fw.ready()) {
|
||||
r = 0; break;
|
||||
}
|
||||
if (fr.ready()) {
|
||||
r = 1; break;
|
||||
}
|
||||
fc::usleep(fc::microseconds(5000));
|
||||
}
|
||||
#endif
|
||||
switch( r ) {
|
||||
case 0:
|
||||
if( wprom->wait() ) {
|
||||
FC_THROW( "Socket Error ${message}",
|
||||
( "message", boost::system::system_error(rprom->wait() ).what() ) );
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if( rprom->wait() ) {
|
||||
FC_THROW( "Socket Error ${message}",
|
||||
( "message", boost::system::system_error(rprom->wait() ).what() ) );
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if( rprom ) {
|
||||
if( rprom->wait() ) {
|
||||
FC_THROW( "Socket Error ${message}",
|
||||
( "message", boost::system::system_error(rprom->wait() ).what() ) );
|
||||
}
|
||||
} else if( wprom ) {
|
||||
if( wprom->wait() ) {
|
||||
FC_THROW( "Socket Error ${message}",
|
||||
( "message", boost::system::system_error(wprom->wait() ).what() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
void detail::client_impl::init_sftp() {
|
||||
if( !sftp )
|
||||
sftp = call_ssh2_ptr_function_throw<LIBSSH2_SFTP*>(boost::bind(libssh2_sftp_init,session),
|
||||
"init sftp error ${code} - ${message}");
|
||||
}
|
||||
|
||||
|
||||
LIBSSH2_CHANNEL* detail::client_impl::open_channel( const fc::string& pty_type ) {
|
||||
LIBSSH2_CHANNEL* chan = 0;
|
||||
/* anonymous scope */ {
|
||||
fc::scoped_lock<fc::mutex> channel_open_lock(channel_open_mutex);
|
||||
|
||||
chan = call_ssh2_ptr_function_throw<LIBSSH2_CHANNEL*>(boost::bind(libssh2_channel_open_ex, session,
|
||||
"session", sizeof("session") - 1,
|
||||
LIBSSH2_CHANNEL_WINDOW_DEFAULT,
|
||||
LIBSSH2_CHANNEL_PACKET_DEFAULT,
|
||||
(const char*)NULL, 0),
|
||||
"libssh2_channel_open_session failed: ${message}");
|
||||
}
|
||||
|
||||
if( pty_type.size() )
|
||||
call_ssh2_function_throw(boost::bind(libssh2_channel_request_pty_ex, chan, pty_type.c_str(), pty_type.size(),
|
||||
(char *)NULL, 0, LIBSSH2_TERM_WIDTH, LIBSSH2_TERM_HEIGHT,
|
||||
LIBSSH2_TERM_WIDTH_PX, LIBSSH2_TERM_HEIGHT_PX),
|
||||
"libssh2_channel_req_pty failed: ${message}");
|
||||
return chan;
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
@ -1,280 +0,0 @@
|
|||
#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
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
@ -1,334 +0,0 @@
|
|||
#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");
|
||||
}
|
||||
}
|
||||
|
||||
} }
|
||||
Loading…
Reference in a new issue