BitShares-Core  7.0.2
BitShares blockchain node software and command-line wallet software
cli.cpp
Go to the documentation of this file.
1 #include <fc/rpc/cli.hpp>
2 #include <fc/thread/thread.hpp>
3 
4 #include <iostream>
5 
6 #ifndef WIN32
7 #include <unistd.h>
8 #endif
9 
10 #ifdef HAVE_EDITLINE
11 # include "editline.h"
12 # include <signal.h>
13 # ifdef WIN32
14 # include <io.h>
15 # endif
16 #endif
17 
18 #include <boost/regex.hpp>
19 
20 namespace fc { namespace rpc {
21 
22 static boost::regex& cli_regex_secret()
23 {
24  static boost::regex regex_expr;
25  return regex_expr;
26 }
27 
28 static std::vector<std::string>& cli_commands()
29 {
30  static std::vector<std::string>* cmds = new std::vector<std::string>();
31  return *cmds;
32 }
33 
35 {
36  if( _run_complete.valid() )
37  {
38  stop();
39  }
40 }
41 
42 variant cli::send_call( api_id_type api_id, string method_name, variants args /* = variants() */ )
43 {
44  FC_ASSERT(false);
45 }
46 
47 variant cli::send_callback( uint64_t callback_id, variants args /* = variants() */ )
48 {
49  FC_ASSERT(false);
50 }
51 
52 void cli::send_notice( uint64_t callback_id, variants args /* = variants() */ )
53 {
54  FC_ASSERT(false);
55 }
56 
57 void cli::format_result( const string& method, std::function<string(variant,const variants&)> formatter)
58 {
59  _result_formatters[method] = formatter;
60 }
61 
62 void cli::set_prompt( const string& prompt )
63 {
64  _prompt = prompt;
65 }
66 
67 void cli::set_regex_secret( const string& expr )
68 {
69  cli_regex_secret() = expr;
70 }
71 
72 void cli::run()
73 {
74  while( !_run_complete.canceled() )
75  {
76  try
77  {
78  std::string line;
79  try
80  {
81  getline( _prompt.c_str(), line );
82  }
83  catch ( const fc::eof_exception& e )
84  {
85  _getline_thread = nullptr;
86  break;
87  }
88  catch ( const fc::canceled_exception& e )
89  {
90  _getline_thread = nullptr;
91  break;
92  }
93 
94  line += char(EOF);
96  if( args.size() == 0 )
97  continue;
98 
99  const string& method = args[0].get_string();
100 
101  auto result = receive_call( 0, method, variants( args.begin()+1,args.end() ) );
102  auto itr = _result_formatters.find( method );
103  if( itr == _result_formatters.end() )
104  {
105  std::cout << fc::json::to_pretty_string( result ) << "\n";
106  }
107  else
108  std::cout << itr->second( result, args ) << "\n";
109  }
110  catch ( const fc::exception& e )
111  {
113  {
114  _getline_thread = nullptr;
115  break;
116  }
117  std::cout << e.to_detail_string() << "\n";
118  }
119  }
120 }
121 
122 #ifdef HAVE_EDITLINE
123 
124 /****
125  * @brief loop through list of commands, attempting to find a match
126  * @param token what the user typed
127  * @param match sets to 1 if only 1 match was found
128  * @returns the remaining letters of the name of the command or NULL if 1 match not found
129  */
130 static char *my_rl_complete(char *token, int *match)
131 {
132  const auto& cmds = cli_commands();
133  const size_t partlen = strlen (token); /* Part of token */
134 
135  std::vector<std::reference_wrapper<const std::string>> matched_cmds;
136  for( const std::string& it : cmds )
137  {
138  if( it.compare(0, partlen, token) == 0 )
139  {
140  matched_cmds.push_back( it );
141  }
142  }
143 
144  if( matched_cmds.size() == 0 )
145  return NULL;
146 
147  const std::string& first_matched_cmd = matched_cmds[0];
148  if( matched_cmds.size() == 1 )
149  {
150  *match = 1;
151  std::string matched_cmd = first_matched_cmd + " ";
152  return strdup( matched_cmd.c_str() + partlen );
153  }
154 
155  size_t first_cmd_len = first_matched_cmd.size();
156  size_t matched_len = partlen;
157  for( ; matched_len < first_cmd_len; ++matched_len )
158  {
159  char next_char = first_matched_cmd[matched_len];
160  bool end = false;
161  for( const std::string& s : matched_cmds )
162  {
163  if( s.size() <= matched_len || s[matched_len] != next_char )
164  {
165  end = true;
166  break;
167  }
168  }
169  if( end )
170  break;
171  }
172 
173  if( matched_len == partlen )
174  return NULL;
175 
176  std::string matched_cmd_part = first_matched_cmd.substr( partlen, matched_len - partlen );
177  return strdup( matched_cmd_part.c_str() );
178 }
179 
180 /***
181  * @brief return an array of matching commands
182  * @param token the incoming text
183  * @param array the resultant array of possible matches
184  * @returns the number of matches
185  */
186 static int cli_completion(char *token, char ***array)
187 {
188  auto& cmd = cli_commands();
189  int num_commands = cmd.size();
190 
191  char **copy = (char **) malloc (num_commands * sizeof(char *));
192  if (copy == NULL)
193  {
194  // possible out of memory
195  return 0;
196  }
197  int total_matches = 0;
198 
199  const size_t partlen = strlen(token);
200 
201  for (const std::string& it : cmd)
202  {
203  if ( it.compare(0, partlen, token) == 0)
204  {
205  copy[total_matches] = strdup ( it.c_str() );
206  ++total_matches;
207  }
208  }
209  *array = copy;
210 
211  return total_matches;
212 }
213 
214 /***
215  * @brief regex match for secret information
216  * @param source the incoming text source
217  * @returns integer 1 in event of regex match for secret information, otherwise 0
218  */
219 static int cli_check_secret(const char *source)
220 {
221  if (!cli_regex_secret().empty() && boost::regex_match(source, cli_regex_secret()))
222  return 1;
223 
224  return 0;
225 }
226 
227 /***
228  * Indicates whether CLI is quitting after got a SIGINT signal.
229  * In order to be used by editline which is C-style, this is a global variable.
230  */
231 static int cli_quitting = false;
232 
236 static int interruptible_getc(void)
237 {
238  if( cli_quitting )
239  return EOF;
240 
241  int r;
242  char c;
243 
244  r = read(0, &c, 1); // read from stdin, will return -1 on SIGINT
245 
246  if( r == -1 && errno == EINTR )
247  cli_quitting = true;
248 
249  return r == 1 && !cli_quitting ? c : EOF;
250 }
251 
252 #endif //HAVE_EDITLINE
253 
255 {
256 
257 #ifdef HAVE_EDITLINE
258  el_hist_size = 256;
259 
260  rl_set_complete_func(my_rl_complete);
261  rl_set_list_possib_func(cli_completion);
262  //rl_set_check_secret_func(cli_check_secret);
263  rl_set_getc_func(interruptible_getc);
264 
265  static fc::thread getline_thread("getline");
266  _getline_thread = &getline_thread;
267 
268  cli_quitting = false;
269 
270  cli_commands() = get_method_names(0);
271 #endif
272 
273  _run_complete = fc::async( [this](){ run(); } );
274 }
275 
277 {
278  _run_complete.cancel();
279 #ifdef HAVE_EDITLINE
280  cli_quitting = true;
281  if( _getline_thread )
282  {
283  _getline_thread->signal(SIGINT);
284  _getline_thread = nullptr;
285  }
286 #endif
287 }
288 
289 void cli::stop()
290 {
291  cancel();
292  _run_complete.wait();
293 }
294 
295 void cli::wait()
296 {
297  _run_complete.wait();
298 }
299 
300 /***
301  * @brief Read input from the user
302  * @param prompt the prompt to display
303  * @param line what the user typed
304  */
305 void cli::getline( const std::string& prompt, std::string& line)
306 {
307  // getting file descriptor for C++ streams is near impossible
308  // so we just assume it's the same as the C stream...
309 #ifdef HAVE_EDITLINE
310 #ifndef WIN32
311  if( isatty( fileno( stdin ) ) )
312 #else
313  // it's implied by
314  // https://msdn.microsoft.com/en-us/library/f4s0ddew.aspx
315  // that this is the proper way to do this on Windows, but I have
316  // no access to a Windows compiler and thus,
317  // no idea if this actually works
318  if( _isatty( _fileno( stdin ) ) )
319 #endif
320  {
321  if( _getline_thread )
322  {
323  _getline_thread->async( [&prompt,&line](){
324  char* line_read = nullptr;
325  std::cout.flush(); //readline doesn't use cin, so we must manually flush _out
326  line_read = readline(prompt.c_str());
327  if( line_read == nullptr )
328  FC_THROW_EXCEPTION( fc::eof_exception, "" );
329  line = line_read;
330  // we don't need here to add line in editline's history, cause it will be doubled
331  if (cli_check_secret(line_read)) {
332  free(line_read);
333  el_no_echo = 1;
334  line_read = readline("Enter password: ");
335  el_no_echo = 0;
336  if( line_read == nullptr )
337  FC_THROW_EXCEPTION( fc::eof_exception, "" );
338  line = line + ' ' + line_read;
339  }
340  free(line_read);
341  }).wait();
342  }
343  }
344  else
345 #endif
346  {
347  std::cout << prompt;
348  // sync_call( cin_thread, [&](){ std::getline( *input_stream, line ); }, "getline");
349  fc::getline( fc::cin, line );
350  }
351 }
352 
353 } } // namespace fc::rpc
fc::rpc::cli::~cli
~cli()
Definition: cli.cpp:34
fc::copy
void copy(const path &from, const path &to)
Definition: filesystem.cpp:241
fc::rpc::cli::stop
void stop()
Definition: cli.cpp:289
fc::rpc::cli::set_regex_secret
void set_regex_secret(const string &expr)
Definition: cli.cpp:67
fc::exception
Used to generate a useful error report when an exception is thrown.
Definition: exception.hpp:56
fc::rpc::cli::wait
void wait()
Definition: cli.cpp:295
fc::exception::code
int64_t code() const
Definition: exception.cpp:141
fc
Definition: api.hpp:15
fc::api_id_type
uint32_t api_id_type
Definition: api.hpp:122
fc::json::to_pretty_string
static string to_pretty_string(const variant &v, output_formatting format=stringify_large_ints_and_doubles, uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
Definition: json.cpp:748
cli.hpp
fc::cin
cin_t & cin
Definition: iostream.cpp:177
fc::future< void >::canceled
bool canceled() const
Definition: future.hpp:312
fc::rpc::cli::cancel
void cancel()
Definition: cli.cpp:276
fc::rpc::cli::getline
virtual void getline(const std::string &prompt, std::string &line)
Definition: cli.cpp:305
fc::rpc::cli::send_notice
virtual void send_notice(uint64_t callback_id, variants args=variants())
Definition: cli.cpp:52
fc::cout
cout_t & cout
Definition: iostream.cpp:175
fc::asio::read
size_t read(AsyncReadStream &s, const MutableBufferSequence &buf)
wraps boost::asio::async_read
Definition: asio.hpp:103
fc::future< void >::wait
void wait(const microseconds &timeout=microseconds::maximum())
Definition: future.hpp:299
fc::variants
std::vector< variant > variants
Definition: variant.hpp:170
fc::thread
Definition: thread.hpp:39
thread.hpp
fc::async
auto async(Functor &&f, const char *desc FC_TASK_NAME_DEFAULT_ARG, priority prio=priority()) -> fc::future< decltype(f())>
Definition: thread.hpp:227
fc::rpc::cli::send_callback
virtual variant send_callback(uint64_t callback_id, variants args=variants())
Definition: cli.cpp:47
fc::api_connection::get_method_names
std::vector< std::string > get_method_names(api_id_type local_api_id=0) const
Definition: api_connection.hpp:295
fc::exception::to_detail_string
std::string to_detail_string(log_level ll=log_level::all) const
Definition: exception.cpp:183
fc::future< void >::valid
bool valid() const
Definition: future.hpp:311
fc::rpc::cli::send_call
virtual variant send_call(api_id_type api_id, string method_name, variants args=variants())
Definition: cli.cpp:42
fc::rpc::cli::format_result
void format_result(const string &method, std::function< string(variant, const variants &)> formatter)
Definition: cli.cpp:57
FC_ASSERT
#define FC_ASSERT(TEST,...)
Checks a condition and throws an assert_exception if the test is FALSE.
Definition: exception.hpp:345
fc::cout_t::flush
virtual void flush()
Definition: iostream.cpp:95
fc::variant
stores null, int64, uint64, double, bool, string, std::vector<variant>, and variant_object's.
Definition: variant.hpp:198
fc::api_connection::receive_call
variant receive_call(api_id_type api_id, const string &method_name, const variants &args=variants()) const
Definition: api_connection.hpp:260
fc::canceled_exception_code
@ canceled_exception_code
Definition: exception.hpp:28
fc::rpc::cli::start
void start()
Definition: cli.cpp:254
fc::future< void >::cancel
void cancel(const char *reason FC_CANCELATION_REASON_DEFAULT_ARG) const
Definition: future.hpp:332
fc::thread::async
auto async(Functor &&f, const char *desc FC_TASK_NAME_DEFAULT_ARG, priority prio=priority()) -> fc::future< decltype(f())>
Definition: thread.hpp:87
FC_THROW_EXCEPTION
#define FC_THROW_EXCEPTION(EXCEPTION, FORMAT,...)
Definition: exception.hpp:379
fc::rpc::cli::set_prompt
void set_prompt(const string &prompt)
Definition: cli.cpp:62
fc::thread::signal
void signal(int)
Definition: thread.cpp:167
fc::json::variants_from_string
static variants variants_from_string(const string &utf8_str, parse_type ptype=legacy_parser, uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
Definition: json.cpp:465
fc::getline
fc::istream & getline(fc::istream &, std::string &, char delim='\n')
Definition: iostream.cpp:78