0.9.8.10
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
CommandShell.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2007-2015 Hypertable, Inc.
3  *
4  * This file is part of Hypertable.
5  *
6  * Hypertable is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; version 3 of the
9  * License, or any later version.
10  *
11  * Hypertable is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  * 02110-1301, USA.
20  */
21 
22 #include <Common/Compat.h>
23 
24 #include "CommandShell.h"
25 
26 #include <Common/Error.h>
27 #include <Common/FileUtils.h>
28 #include <Common/System.h>
29 #include <Common/Usage.h>
30 #include <Common/Logger.h>
31 
32 #include <boost/algorithm/string.hpp>
33 #include <boost/algorithm/string/classification.hpp>
34 #include <boost/thread/exceptions.hpp>
35 
36 #include <chrono>
37 #include <cstdlib>
38 #include <cstring>
39 #include <iostream>
40 #include <queue>
41 #include <thread>
42 
43 extern "C" {
44 #include <dirent.h>
45 #include <editline/readline.h>
46 #include <errno.h>
47 #include <limits.h>
48 #include <signal.h>
49 }
50 
51 using namespace Hypertable;
52 using namespace std;
53 
55 
56 namespace {
57  const char *help_text =
58  "\n" \
59  "Interpreter Meta Commands\n" \
60  "-------------------------\n" \
61  "? (\\?) Synonym for `help'.\n" \
62  "clear (\\c) Clear command.\n" \
63  "exit [rc] (\\q) Exit program with optional return code rc.\n" \
64  "print (\\p) Print current command.\n" \
65  "quit (\\q) Quit program.\n" \
66  "source <f> (.) Execute commands in file <f>.\n" \
67  "system (\\!) Execute a system shell command.\n" \
68  "\n";
69 
70  char *find_char(const char *s, int c) {
71  bool in_quotes = false;
72  char quote_char = 0;
73 
74  for (const char *ptr = s; *ptr; ptr++) {
75  if (in_quotes) {
76  if (*ptr == quote_char && *(ptr - 1) != '\\')
77  in_quotes = false;
78  }
79  else {
80  if (*ptr == (char )c)
81  return (char *)ptr;
82  else if (*ptr == '\'' || *ptr == '"') {
83  in_quotes = true;
84  quote_char = *ptr;
85  }
86  }
87  }
88  return 0;
89  }
90 
91  const string longest_common_prefix(vector<string> &completions) {
92  if (completions.empty())
93  return "";
94  else if (completions.size() == 1)
95  return completions[0];
96 
97  char ch, memo;
98  size_t idx = 0;
99  while (true) {
100  memo = '\0';
101  size_t i;
102  for (i=0; i<completions.size(); i++) {
103  if (completions[i].length() == idx)
104  break;
105  ch = completions[i].at(idx);
106  if (memo == '\0')
107  memo = ch;
108  else if (ch != memo)
109  break;
110  }
111  if (i < completions.size())
112  return completions[0].substr(0, idx);
113  idx++;
114  }
115  return "";
116  }
117 
118  unsigned char complete(EditLine *el, int ch) {
119  struct dirent *dp;
120  const wchar_t *ptr;
121  char *buf, *bptr;
122  const LineInfoW *lf = el_wline(el);
123  int len, mblen, i;
124  unsigned char res = 0;
125  wchar_t dir[1024];
126 
127  /* Find the last word */
128  for (ptr = lf->cursor -1; !iswspace(*ptr) && ptr > lf->buffer; --ptr)
129  continue;
130  if (ptr > lf->buffer)
131  ptr++;
132  len = lf->cursor - ptr;
133 
134  /* Convert last word to multibyte encoding, so we can compare to it */
135  wctomb(NULL, 0); /* Reset shift state */
136  mblen = MB_LEN_MAX * len + 1;
137  buf = bptr = (char *)malloc(mblen);
138  for (i = 0; i < len; ++i) {
139  /* Note: really should test for -1 return from wctomb */
140  bptr += wctomb(bptr, ptr[i]);
141  }
142  *bptr = 0; /* Terminate multibyte string */
143  mblen = bptr - buf;
144 
145  string directory;
146  string prefix;
147 
148  bptr = strrchr(buf, '/');
149  if (bptr == nullptr) {
150  directory.append(".");
151  prefix.append(buf);
152  }
153  else if (bptr == buf) {
154  directory.append("/");
155  prefix.append(buf+1);
156  }
157  else if (buf[0] == '~') {
158  directory.append(buf, bptr-buf);
159  FileUtils::expand_tilde(directory);
160  prefix.append(bptr+1);
161  }
162  else {
163  if (buf[0] != '/')
164  directory.append("./");
165  directory.append(buf, bptr-buf);
166  prefix.append(bptr+1);
167  }
168 
169  vector<string> completions;
170 
171  DIR *dd = opendir(directory.c_str());
172  if (dd) {
173  string completion;
174  for (dp = readdir(dd); dp != NULL; dp = readdir(dd)) {
175  if (strncmp(dp->d_name, prefix.c_str(), prefix.length()) == 0) {
176  completion = string(&dp->d_name[prefix.length()]);
177  if (dp->d_type == DT_DIR)
178  completion.append("/");
179  completions.push_back(completion);
180  }
181  }
182  string longest_prefix = longest_common_prefix(completions);
183  if (!longest_prefix.empty()) {
184  mbstowcs(dir, longest_prefix.c_str(), sizeof(dir) / sizeof(*dir));
185  if (el_winsertstr(el, dir) == -1)
186  res = CC_ERROR;
187  else
188  res = CC_REFRESH;
189  }
190  closedir(dd);
191  }
192 
193  free(buf);
194  return res;
195  }
196 
197 }
198 
200 
203 CommandShell::CommandShell(const string &prompt_str, const string &service_name,
204  CommandInterpreterPtr &interp_ptr,
205  PropertiesPtr &props)
206  : m_interp_ptr(interp_ptr), m_props(props), m_prompt(prompt_str),
207  m_service_name(service_name) {
208 
209  const char *home = getenv("HOME");
210  if (home)
211  ms_history_file = (String)home + "/." + m_prompt + "_history";
212  else
213  ms_history_file = (String)"." + m_prompt + "_history";
214 
215  m_verbose = m_props->has("verbose") ? m_props->get_bool("verbose") : false;
216  m_batch_mode = m_props->has("batch");
217  m_silent = m_props->has("silent") ? m_props->get_bool("silent") : false;
218  m_test_mode = m_props->has("test-mode");
219  if (m_test_mode) {
221  m_batch_mode = true;
222  }
223  m_no_prompt = m_props->has("no-prompt");
224 
225  m_notify = m_props->has("notification-address");
226  if (m_notify) {
227  String notification_address = m_props->get_str("notification-address");
228  m_notifier_ptr = make_shared<Notifier>(notification_address.c_str());
229  }
230 
231  if (m_props->has("execute")) {
232  m_cmd_str = m_props->get_str("execute");
233  boost::trim(m_cmd_str);
234  if (!m_cmd_str.empty() && m_cmd_str[m_cmd_str.length()] != ';')
235  m_cmd_str.append(";");
236  m_has_cmd_exec = true;
237  m_batch_mode = true;
238  }
239  else if (m_props->has("command-file")) {
240  m_cmd_file = m_props->get_str("command-file");
241  m_has_cmd_file = true;
242  m_batch_mode = true;
243  }
244 
245  setlocale(LC_ALL, "");
246 
247  /* initialize libedit */
248  if (!m_batch_mode) {
249  ms_instance = this;
250  m_editline = el_init("hypertable", stdin, stdout, stderr);
251  m_history = history_winit();
252  history_w(m_history, &m_history_event, H_SETSIZE, 100);
253  history_w(m_history, &m_history_event, H_LOAD, ms_history_file.c_str());
254 
255  el_wset(m_editline, EL_HIST, history_w, m_history);
256  el_wset(m_editline, EL_PROMPT, prompt);
257  el_wset(m_editline, EL_SIGNAL, 1);
258  el_wset(m_editline, EL_EDITOR, L"emacs");
259 
260  /* Add a user-defined function */
261  el_wset(m_editline, EL_ADDFN, L"ed-complete", L"Complete argument", complete);
262 
263  /* Bind <tab> to it */
264  el_wset(m_editline, EL_BIND, L"^I", L"ed-complete", NULL);
265 
266  /* Source the user's defaults file. */
267  el_source(m_editline, NULL);
268  m_tokenizer = tok_winit(NULL);
269  }
270  else {
271  m_editline = 0;
272  m_history = 0;
273  m_tokenizer = 0;
274  }
275 
276  // Initialize prompt string
277  wchar_t buf[64] = {0};
278  const char *p = prompt_str.c_str();
279  mbsrtowcs(buf, &p, 63, 0);
280  m_wprompt = buf;
281  m_wprompt += L"> ";
282 
283  // Propagate mode flags to interpreter
284  m_interp_ptr->set_interactive_mode(!m_batch_mode);
285  m_interp_ptr->set_silent(m_silent);
286 }
287 
289  if (m_editline)
290  el_end(m_editline);
291  if (m_history)
292  history_wend(m_history);
293  if (m_tokenizer)
294  tok_wend(m_tokenizer);
295 }
296 
300  if (m_line_read) {
301  free(m_line_read);
302  m_line_read = (char *)NULL;
303  }
304 
305  /* Execute commands from command line string/file */
307  static bool done = false;
308 
309  if (done)
310  return 0;
311 
312  if (m_has_cmd_exec) {
313  m_line_read = (char *)malloc(m_cmd_str.size() + 1);
314  strcpy(m_line_read, m_cmd_str.c_str());
315  }
316  else {
317  off_t len;
318  char *tmp;
319  // copy bcos readline uses malloc, FileUtils::file_to_buffer uses new
321  m_line_read = (char *)malloc(len+1);
322  memcpy(m_line_read, tmp, len);
323  m_line_read[len] = 0;
324  delete[] tmp;
325  }
326 
327  done = true;
328  return m_line_read;
329  }
330 
331  /* Get a line from the user. */
332  if (m_batch_mode || m_no_prompt || m_silent) {
333  if (!getline(cin, m_input_str))
334  return 0;
335  boost::trim(m_input_str);
336  if (m_input_str.find("quit", 0) != 0 && m_test_mode)
337  cout << m_input_str << endl;
338  return (char *)m_input_str.c_str();
339  }
340  else {
341  const wchar_t *wline = 0;
342  int numc = 0;
343  while (true) {
344  wline = el_wgets(m_editline, &numc);
345  if (wline == 0 || numc == 0)
346  return (char *)"exit";
347 
348  if (!m_cont && numc == 1)
349  continue; /* Only got a linefeed */
350 
351  const LineInfoW *li = el_wline(m_editline);
352  int ac = 0, cc = 0, co = 0;
353  const wchar_t **av;
354  int ncont = tok_wline(m_tokenizer, li, &ac, &av, &cc, &co);
355  if (ncont < 0) {
356  // (void) fprintf(stderr, "Internal error\n");
357  m_cont = false;
358  continue;
359  }
360 
361  if (el_wparse(m_editline, ac, av) == -1)
362  break;
363  }
364 
365  char *buffer = (char *)malloc(1024 * 8);
366  size_t len = 1024 * 8;
367  while (1) {
368  const wchar_t *wp = &wline[0];
369  size_t l = wcsrtombs(buffer, &wp, len, 0);
370  if (l > len) {
371  buffer = (char *)realloc(buffer, l + 1);
372  len = l + 1;
373  }
374  else
375  break;
376  }
377  m_line_read = buffer;
378 
379  /* If the line has any text in it, save it on the history. */
381  history_w(m_history, &m_history_event,
382  m_cont ? H_APPEND : H_ENTER, wline);
383  }
384 
385  return m_line_read;
386 }
387 
389  desc.add_options()
390  ("batch", "Disable interactive behavior")
391  ("no-prompt", "Do not display an input prompt")
392  ("test-mode", "Don't display anything that might change from run to run "
393  "(e.g. timing statistics)")
394  ("timestamp-format", Property::str(), "Output format for timestamp. "
395  "Currently the only formats are 'default' and 'nanoseconds'")
396  ("notification-address", Property::str(), "[<host>:]<port> "
397  "Send notification datagram to this address after each command.")
398  ("execute,e", Property::str(), "Execute specified commands.")
399  ("command-file", Property::str(), "Execute commands from file.")
400  ;
401 }
402 
403 
405  const char *line;
406  std::queue<string> command_queue;
407  String command;
408  String timestamp_format;
409  String source_commands;
410  String use_ns;
411  const char *base, *ptr;
412  int exit_status = 0;
413 
414  if (m_props->has("timestamp-format"))
415  timestamp_format = m_props->get_str("timestamp-format");
416 
417  if (timestamp_format != "")
418  m_interp_ptr->set_timestamp_output_format(timestamp_format);
419 
420  if (!m_batch_mode && !m_silent && !m_batch_mode) {
421  read_history(ms_history_file.c_str());
422 
423  cout << endl;
424  cout << "Welcome to the " << m_prompt << " command interpreter."
425  << endl;
426  cout << "For information about Hypertable, visit http://hypertable.com"
427  << endl;
428  cout << endl;
429  cout << "Type 'help' for a list of commands, or 'help shell' for a" << endl;
430  cout << "list of shell meta commands." << endl;
431  cout << endl << flush;
432  }
433 
434  m_accum = "";
435  if (!m_batch_mode)
436  using_history();
437 
438  if (!m_prompt.compare("hypertable")) {
439  trim_if(m_namespace, boost::is_any_of(" \t\n\r;"));
440  if (m_namespace.empty())
441  m_namespace = "/";
442  use_ns = "USE \"" + m_namespace + "\";";
443  line = use_ns.c_str();
444  goto process_line;
445  }
446 
447  while ((line = rl_gets()) != 0) {
448 process_line:
449  try {
450  if (*line == 0)
451  continue;
452 
453  if (!strncasecmp(line, "help shell", 10)) {
454  cout << help_text;
455  continue;
456  }
457  else if (!strncasecmp(line, "help", 4)
458  || !strncmp(line, "\\h", 2) || *line == '?') {
459  command = line;
460  std::transform(command.begin(), command.end(), command.begin(),
461  ::tolower);
462  trim_if(command, boost::is_any_of(" \t\n\r;"));
463  m_interp_ptr->execute_line(command);
464  if (m_notify)
465  m_notifier_ptr->notify();
466  continue;
467  }
468  else if (!strncasecmp(line, "quit", 4) || !strcmp(line, "\\q")) {
469  if (!m_batch_mode)
470  history_w(m_history, &m_history_event, H_SAVE,
471  ms_history_file.c_str());
472  return exit_status;
473  }
474  else if (!strncasecmp(line, "exit", 4)) {
475  if (!m_batch_mode)
476  history_w(m_history, &m_history_event, H_SAVE,
477  ms_history_file.c_str());
478  const char *ptr = line + 4;
479  while (*ptr && isspace(*ptr))
480  ptr++;
481  return (*ptr) ? atoi(ptr) : exit_status;
482  }
483  else if (!strncasecmp(line, "print", 5) || !strcmp(line, "\\p")) {
484  cout << m_accum << endl;
485  continue;
486  }
487  else if (!strncasecmp(line, "clear", 5) || !strcmp(line, "\\c")) {
488  m_accum = "";
489  m_cont = false;
490  continue;
491  }
492  else if (!strncasecmp(line, "source", 6) || line[0] == '.') {
493  if ((base = strchr(line, ' ')) == 0) {
494  cout << "syntax error: source or '.' must be followed by a space "
495  "character" << endl;
496  continue;
497  }
498  String fname = base;
499  trim_if(fname, boost::is_any_of(" \t\n\r;"));
500  off_t flen;
501  if ((base = FileUtils::file_to_buffer(fname.c_str(), &flen)) == 0)
502  continue;
503  source_commands = "";
504  ptr = strtok((char *)base, "\n\r");
505  while (ptr != 0) {
506  command = ptr;
507  boost::trim(command);
508  if (command.find("#") != 0)
509  source_commands += command + " ";
510  ptr = strtok(0, "\n\r");
511  }
512  if (source_commands == "")
513  continue;
514  delete [] base;
515  line = source_commands.c_str();
516  }
517  else if (!strncasecmp(line, "system", 6) || !strncmp(line, "\\!", 2)) {
518  String command = line;
519  size_t offset = command.find_first_of(' ');
520  if (offset != String::npos) {
521  command = command.substr(offset+1);
522  trim_if(command, boost::is_any_of(" \t\n\r;"));
523  HT_EXPECT(system(command.c_str()) == 0, Error::EXTERNAL);
524  }
525  continue;
526  }
527 
531  if (m_line_command_mode) {
532  if (*line == 0 || *line == '#')
533  continue;
534  command_queue.push(line);
535  }
536  else {
537  base = line;
538  ptr = find_char(base, ';');
539  while (ptr) {
540  m_accum += string(base, ptr-base);
541  if (m_accum.size() > 0) {
542  boost::trim(m_accum);
543  if (m_accum.find("#") != 0)
544  command_queue.push(m_accum);
545  m_accum = "";
546  m_cont = false;
547  }
548  base = ptr+1;
549  ptr = find_char(base, ';');
550  }
551  command = string(base);
552  boost::trim(command);
553  if (command != "" && command.find("#") != 0) {
554  m_accum += command;
555  boost::trim(m_accum);
556  }
557  if (m_accum != "") {
558  m_cont = true;
559  m_accum += " ";
560  }
561  }
562 
563  while (!command_queue.empty()) {
564  if (command_queue.front() == "quit") {
565  if (!m_batch_mode)
566  history_w(m_history, &m_history_event, H_SAVE,
567  ms_history_file.c_str());
568  return exit_status;
569  }
570  else if (boost::algorithm::starts_with(command_queue.front(), "exit")) {
571  if (!m_batch_mode)
572  history_w(m_history, &m_history_event, H_SAVE,
573  ms_history_file.c_str());
574  const char *ptr = command_queue.front().c_str() + 4;
575  while (*ptr && isspace(*ptr))
576  ptr++;
577  return (*ptr) ? atoi(ptr) : exit_status;
578  }
579  command = command_queue.front();
580  command_queue.pop();
581  if (!strncmp(command.c_str(), "pause", 5)) {
582  String sec_str = command.substr(5);
583  boost::trim(sec_str);
584  char *endptr;
585  long secs = strtol(sec_str.c_str(), &endptr, 0);
586  if ((secs == 0 && errno == EINVAL) || *endptr != 0) {
587  cout << "error: invalid seconds specification" << endl;
588  if (m_batch_mode)
589  return 2;
590  }
591  else
592  this_thread::sleep_for(chrono::milliseconds(secs*1000));
593  }
594  else {
595  exit_status = m_interp_ptr->execute_line(command);
596  if(m_notify)
597  m_notifier_ptr->notify();
598  }
599  }
600  }
601  catch (Hypertable::Exception &e) {
602  if (e.code() == Error::BAD_NAMESPACE)
603  cerr << "ERROR: No namespace is open (see 'use' command)" << endl;
604  else {
605  if (m_verbose)
606  cerr << e << endl;
607  else if (!m_silent)
608  cout << m_service_name << " CRITICAL - " << Error::get_text(e.code())
609  << " (" << e.what() << ")" << endl;
610  }
611  if(m_notify)
612  m_notifier_ptr->notify();
613  exit_status = 2;
614  if (m_batch_mode && !m_test_mode)
615  return exit_status;
616  m_accum = "";
617  while (!command_queue.empty())
618  command_queue.pop();
619  m_cont = false;
620  }
621  }
622 
623  if (m_batch_mode) {
624  boost::trim(m_accum);
625  if (!m_accum.empty()) {
626  line = ";";
627  goto process_line;
628  }
629  }
630 
631  if (!m_batch_mode)
632  history_w(m_history, &m_history_event, H_SAVE, ms_history_file.c_str());
633 
634  return exit_status;
635 }
636 
637 const wchar_t *
638 CommandShell::prompt(EditLine *el) {
639  if (ms_instance->m_cont)
640  return L" -> ";
641  else
642  return ms_instance->m_wprompt.c_str();
643 }
Retrieves system information (hardware, installation directory, etc)
static CommandShell * ms_instance
Definition: CommandShell.h:58
static char * file_to_buffer(const String &fname, off_t *lenp)
Reads a full file into a new buffer; the buffer is allocated with operator new[], and the caller has ...
Definition: FileUtils.cc:282
static const wchar_t * prompt(EditLine *el)
std::string String
A String is simply a typedef to std::string.
Definition: String.h:44
Helper class for printing usage banners on the command line.
Po::typed_value< String > * str(String *v=0)
Definition: Properties.h:166
STL namespace.
CommandShell(const std::string &prompt_str, const std::string &service_name, CommandInterpreterPtr &, PropertiesPtr &)
CommandInterpreterPtr m_interp_ptr
Definition: CommandShell.h:61
std::string m_service_name
Definition: CommandShell.h:80
void set_test_mode(int fd=-1)
The test mode disables line numbers and timestamps and can redirect the output to a separate file des...
Definition: Logger.h:100
#define HT_EXPECT(_e_, _code_)
Definition: Logger.h:388
Directory container class.
Definition: directory.h:343
File system utility functions.
const char * get_text(int error)
Returns a descriptive error message.
Definition: Error.cc:330
std::shared_ptr< Properties > PropertiesPtr
Definition: Properties.h:447
Logging routines and macros.
Compatibility Macros for C/C++.
Po::options_description PropertiesDesc
Definition: Properties.h:200
TokenizerW * m_tokenizer
Definition: CommandShell.h:88
static void add_options(PropertiesDesc &)
NotifierPtr m_notifier_ptr
Definition: CommandShell.h:63
static String ms_history_file
Definition: CommandShell.h:54
static bool expand_tilde(String &fname)
Expands a leading tilde character in a filename.
Definition: FileUtils.cc:472
std::shared_ptr< CommandInterpreter > CommandInterpreterPtr
Hypertable definitions
LogWriter * get()
Accessor for the LogWriter singleton instance.
Definition: Logger.cc:49
This is a generic exception class for Hypertable.
Definition: Error.h:314
Error codes, Exception handling, error logging.
int code() const
Returns the error code.
Definition: Error.h:391