BitShares-Core  4.0.0
BitShares blockchain implementation and command-line interface 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 /****
123  * @brief loop through list of commands, attempting to find a match
124  * @param token what the user typed
125  * @param match sets to 1 if only 1 match was found
126  * @returns the remaining letters of the name of the command or NULL if 1 match not found
127  */
128 static char *my_rl_complete(char *token, int *match)
129 {
130  const auto& cmds = cli_commands();
131  const size_t partlen = strlen (token); /* Part of token */
132 
133  std::vector<std::reference_wrapper<const std::string>> matched_cmds;
134  for( const std::string& it : cmds )
135  {
136  if( it.compare(0, partlen, token) == 0 )
137  {
138  matched_cmds.push_back( it );
139  }
140  }
141 
142  if( matched_cmds.size() == 0 )
143  return NULL;
144 
145  const std::string& first_matched_cmd = matched_cmds[0];
146  if( matched_cmds.size() == 1 )
147  {
148  *match = 1;
149  std::string matched_cmd = first_matched_cmd + " ";
150  return strdup( matched_cmd.c_str() + partlen );
151  }
152 
153  size_t first_cmd_len = first_matched_cmd.size();
154  size_t matched_len = partlen;
155  for( ; matched_len < first_cmd_len; ++matched_len )
156  {
157  char next_char = first_matched_cmd[matched_len];
158  bool end = false;
159  for( const std::string& s : matched_cmds )
160  {
161  if( s.size() <= matched_len || s[matched_len] != next_char )
162  {
163  end = true;
164  break;
165  }
166  }
167  if( end )
168  break;
169  }
170 
171  if( matched_len == partlen )
172  return NULL;
173 
174  std::string matched_cmd_part = first_matched_cmd.substr( partlen, matched_len - partlen );
175  return strdup( matched_cmd_part.c_str() );
176 }
177 
178 /***
179  * @brief return an array of matching commands
180  * @param token the incoming text
181  * @param array the resultant array of possible matches
182  * @returns the number of matches
183  */
184 static int cli_completion(char *token, char ***array)
185 {
186  auto& cmd = cli_commands();
187  int num_commands = cmd.size();
188 
189  char **copy = (char **) malloc (num_commands * sizeof(char *));
190  if (copy == NULL)
191  {
192  // possible out of memory
193  return 0;
194  }
195  int total_matches = 0;
196 
197  const size_t partlen = strlen(token);
198 
199  for (const std::string& it : cmd)
200  {
201  if ( it.compare(0, partlen, token) == 0)
202  {
203  copy[total_matches] = strdup ( it.c_str() );
204  ++total_matches;
205  }
206  }
207  *array = copy;
208 
209  return total_matches;
210 }
211 
212 /***
213  * @brief regex match for secret information
214  * @param source the incoming text source
215  * @returns integer 1 in event of regex match for secret information, otherwise 0
216  */
217 static int cli_check_secret(const char *source)
218 {
219  if (!cli_regex_secret().empty() && boost::regex_match(source, cli_regex_secret()))
220  return 1;
221 
222  return 0;
223 }
224 
225 /***
226  * Indicates whether CLI is quitting after got a SIGINT signal.
227  * In order to be used by editline which is C-style, this is a global variable.
228  */
229 static int cli_quitting = false;
230 
231 #ifndef WIN32
232 
235 static int interruptible_getc(void)
236 {
237  if( cli_quitting )
238  return EOF;
239 
240  int r;
241  char c;
242 
243  r = read(0, &c, 1); // read from stdin, will return -1 on SIGINT
244 
245  if( r == -1 && errno == EINTR )
246  cli_quitting = true;
247 
248  return r == 1 && !cli_quitting ? c : EOF;
249 }
250 #endif
251 
253 {
254 
255 #ifdef HAVE_EDITLINE
256  el_hist_size = 256;
257 
258  rl_set_complete_func(my_rl_complete);
259  rl_set_list_possib_func(cli_completion);
260  //rl_set_check_secret_func(cli_check_secret);
261  rl_set_getc_func(interruptible_getc);
262 
263  static fc::thread getline_thread("getline");
264  _getline_thread = &getline_thread;
265 
266  cli_quitting = false;
267 
268  cli_commands() = get_method_names(0);
269 #endif
270 
271  _run_complete = fc::async( [this](){ run(); } );
272 }
273 
275 {
276  _run_complete.cancel();
277 #ifdef HAVE_EDITLINE
278  cli_quitting = true;
279  if( _getline_thread )
280  {
281  _getline_thread->signal(SIGINT);
282  _getline_thread = nullptr;
283  }
284 #endif
285 }
286 
287 void cli::stop()
288 {
289  cancel();
290  _run_complete.wait();
291 }
292 
293 void cli::wait()
294 {
295  _run_complete.wait();
296 }
297 
298 /***
299  * @brief Read input from the user
300  * @param prompt the prompt to display
301  * @param line what the user typed
302  */
303 void cli::getline( const std::string& prompt, std::string& line)
304 {
305  // getting file descriptor for C++ streams is near impossible
306  // so we just assume it's the same as the C stream...
307 #ifdef HAVE_EDITLINE
308 #ifndef WIN32
309  if( isatty( fileno( stdin ) ) )
310 #else
311  // it's implied by
312  // https://msdn.microsoft.com/en-us/library/f4s0ddew.aspx
313  // that this is the proper way to do this on Windows, but I have
314  // no access to a Windows compiler and thus,
315  // no idea if this actually works
316  if( _isatty( _fileno( stdin ) ) )
317 #endif
318  {
319  if( _getline_thread )
320  {
321  _getline_thread->async( [&prompt,&line](){
322  char* line_read = nullptr;
323  std::cout.flush(); //readline doesn't use cin, so we must manually flush _out
324  line_read = readline(prompt.c_str());
325  if( line_read == nullptr )
326  FC_THROW_EXCEPTION( fc::eof_exception, "" );
327  line = line_read;
328  // we don't need here to add line in editline's history, cause it will be doubled
329  if (cli_check_secret(line_read)) {
330  free(line_read);
331  el_no_echo = 1;
332  line_read = readline("Enter password: ");
333  el_no_echo = 0;
334  if( line_read == nullptr )
335  FC_THROW_EXCEPTION( fc::eof_exception, "" );
336  line = line + ' ' + line_read;
337  }
338  free(line_read);
339  }).wait();
340  }
341  }
342  else
343 #endif
344  {
345  std::cout << prompt;
346  // sync_call( cin_thread, [&](){ std::getline( *input_stream, line ); }, "getline");
347  fc::getline( fc::cin, line );
348  }
349 }
350 
351 } } // namespace fc::rpc
void set_prompt(const string &prompt)
Definition: cli.cpp:62
bool valid() const
Definition: future.hpp:311
auto async(Functor &&f, const char *desc FC_TASK_NAME_DEFAULT_ARG, priority prio=priority()) -> fc::future< decltype(f())>
Definition: thread.hpp:227
void signal(int)
Definition: thread.cpp:167
bool canceled() const
Definition: future.hpp:312
variant receive_call(api_id_type api_id, const string &method_name, const variants &args=variants()) const
void stop()
Definition: cli.cpp:287
Used to generate a useful error report when an exception is thrown.At each level in the stack where t...
Definition: exception.hpp:56
std::string to_detail_string(log_level ll=log_level::all) const
Definition: exception.cpp:183
std::vector< variant > variants
Definition: variant.hpp:170
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
size_t read(AsyncReadStream &s, const MutableBufferSequence &buf)
wraps boost::asio::async_read
Definition: asio.hpp:104
virtual void send_notice(uint64_t callback_id, variants args=variants())
Definition: cli.cpp:52
void start()
Definition: cli.cpp:252
void set_regex_secret(const string &expr)
Definition: cli.cpp:67
cout_t & cout
Definition: iostream.cpp:175
const auto source
void wait(const microseconds &timeout=microseconds::maximum())
Definition: future.hpp:299
auto async(Functor &&f, const char *desc FC_TASK_NAME_DEFAULT_ARG, priority prio=priority()) -> fc::future< decltype(f())>
Definition: thread.hpp:87
int64_t code() const
Definition: exception.cpp:141
fc::istream & getline(fc::istream &, std::string &, char delim= '\n')
Definition: iostream.cpp:78
void format_result(const string &method, std::function< string(variant, const variants &)> formatter)
Definition: cli.cpp:57
virtual variant send_call(api_id_type api_id, string method_name, variants args=variants())
Definition: cli.cpp:42
void cancel(const char *reason FC_CANCELATION_REASON_DEFAULT_ARG) const
Definition: future.hpp:332
void cancel()
Definition: cli.cpp:274
void wait()
Definition: cli.cpp:293
#define FC_ASSERT(TEST,...)
Checks a condition and throws an assert_exception if the test is FALSE.
Definition: exception.hpp:345
stores null, int64, uint64, double, bool, string, std::vector<variant>, and variant_object&#39;s.
Definition: variant.hpp:198
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:736
#define FC_THROW_EXCEPTION(EXCEPTION, FORMAT,...)
Definition: exception.hpp:378
cin_t & cin
Definition: iostream.cpp:177
virtual void flush()
Definition: iostream.cpp:95
uint32_t api_id_type
Definition: api.hpp:122
Definition: api.hpp:15
virtual variant send_callback(uint64_t callback_id, variants args=variants())
Definition: cli.cpp:47
void copy(const path &from, const path &to)
Definition: filesystem.cpp:241
virtual void getline(const std::string &prompt, std::string &line)
Definition: cli.cpp:303
std::vector< std::string > get_method_names(api_id_type local_api_id=0) const