shellLinux.c 16.8 KB
Newer Older
H
hzcheng 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
 *
 * This program is free software: you can use, redistribute, and/or modify
 * it under the terms of the GNU Affero General Public License, version 3
 * or later ("AGPL"), as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#define __USE_XOPEN
17
#include "os.h"
S
slguan 已提交
18
#include "tglobal.h"
H
hzcheng 已提交
19 20 21
#include "shell.h"
#include "shellCommand.h"
#include "tkey.h"
S
Shengliang Guan 已提交
22 23 24 25 26
#include "ulog.h"

#include <wordexp.h>
#include <argp.h>
#include <termio.h>
H
hzcheng 已提交
27 28 29 30 31 32

#define OPT_ABORT 1 /* �Cabort */

int indicator = 1;
struct termios oldtio;

H
Hongze Cheng 已提交
33
extern int wcwidth(wchar_t c);
S
slguan 已提交
34
void insertChar(Command *cmd, char *c, int size);
H
hzcheng 已提交
35 36 37 38 39
const char *argp_program_version = version;
const char *argp_program_bug_address = "<support@taosdata.com>";
static char doc[] = "";
static char args_doc[] = "";
static struct argp_option options[] = {
H
Hui Li 已提交
40
  {"host",       'h', "HOST",       0,                   "TDengine server FQDN to connect. The default host is localhost."},
41
  {"password",   'p', 0,   0,                   "The password to use when connecting to the server."},
H
hzcheng 已提交
42
  {"port",       'P', "PORT",       0,                   "The TCP/IP port number to use for the connection."},
43
  {"user",       'u', "USER",       0,                   "The user name to use when connecting to the server."},
44
  {"auth",       'A', "Auth",       0,                   "The auth string to use when connecting to the server."},
H
hzcheng 已提交
45
  {"config-dir", 'c', "CONFIG_DIR", 0,                   "Configuration directory."},
46
  {"dump-config", 'C', 0,           0,                   "Dump configuration."},
H
hzcheng 已提交
47
  {"commands",   's', "COMMANDS",   0,                   "Commands to run without enter the shell."},
S
slguan 已提交
48
  {"raw-time",   'r', 0,            0,                   "Output time as uint64_t."},
H
hzcheng 已提交
49
  {"file",       'f', "FILE",       0,                   "Script to run without enter the shell."},
S
#928  
slguan 已提交
50 51
  {"directory",  'D', "DIRECTORY",  0,                   "Use multi-thread to import all SQL files in the directory separately."},
  {"thread",     'T', "THREADNUM",  0,                   "Number of threads when using multi-thread to import data."},
S
TD-3309  
Shengliang Guan 已提交
52
  {"check",      'k', "CHECK",      0,                   "Check tables."},
H
hzcheng 已提交
53
  {"database",   'd', "DATABASE",   0,                   "Database to use when connecting to the server."},
54 55
  {"timezone",   'z', "TIMEZONE",   0,                   "Time zone of the shell, default is local."},
  {"netrole",    'n', "NETROLE",    0,                   "Net role when network connectivity test, default is startup, options: client|server|rpc|startup|sync|speen|fqdn."},
H
Hui Li 已提交
56
  {"pktlen",     'l', "PKTLEN",     0,                   "Packet length used for net test, default is 1000 bytes."},
57 58
  {"pktnum",     'N', "PKTNUM",     0,                   "Packet numbers used for net test, default is 100."},
  {"pkttype",    'S', "PKTTYPE",    0,                   "Packet type used for net test, default is TCP."},
H
hzcheng 已提交
59 60 61 62 63
  {0}};

static error_t parse_opt(int key, char *arg, struct argp_state *state) {
  /* Get the input argument from argp_parse, which we
  know is a pointer to our arguments structure. */
64
  SShellArguments *arguments = state->input;
H
hzcheng 已提交
65 66 67 68 69 70 71 72 73
  wordexp_t full_path;

  switch (key) {
    case 'h':
      arguments->host = arg;
      break;
    case 'p':
      break;
    case 'P':
74
      if (arg) {
H
Hui Li 已提交
75
        arguments->port  = atoi(arg);
76 77 78 79 80
      } else {
        fprintf(stderr, "Invalid port\n");
        return -1;
      }

H
hzcheng 已提交
81
      break;
82
    case 'z':
H
hzcheng 已提交
83 84 85 86 87
      arguments->timezone = arg;
      break;
    case 'u':
      arguments->user = arg;
      break;
88 89 90
    case 'A':
      arguments->auth = arg;
      break;
H
hzcheng 已提交
91 92 93
    case 'c':
      if (wordexp(arg, &full_path, 0) != 0) {
        fprintf(stderr, "Invalid path %s\n", arg);
H
Hui Li 已提交
94
        return -1;
95
      }
B
Bomin Zhang 已提交
96
      if (strlen(full_path.we_wordv[0]) >= TSDB_FILENAME_LEN) {
H
Hui Li 已提交
97 98
        fprintf(stderr, "config file path: %s overflow max len %d\n", full_path.we_wordv[0], TSDB_FILENAME_LEN - 1);
        wordfree(&full_path);
H
hzcheng 已提交
99 100
        return -1;
      }
H
Hui Li 已提交
101
      tstrncpy(configDir, full_path.we_wordv[0], TSDB_FILENAME_LEN);
H
hzcheng 已提交
102 103
      wordfree(&full_path);
      break;
104 105 106
    case 'C':
      arguments->dump_config = true;
      break;
H
hzcheng 已提交
107 108 109 110 111 112 113
    case 's':
      arguments->commands = arg;
      break;
    case 'r':
      arguments->is_raw_time = true;
      break;
    case 'f':
114
      if ((0 == strlen(arg)) || (wordexp(arg, &full_path, 0) != 0)) {
H
hzcheng 已提交
115 116 117
        fprintf(stderr, "Invalid path %s\n", arg);
        return -1;
      }
H
Hui Li 已提交
118
      tstrncpy(arguments->file, full_path.we_wordv[0], TSDB_FILENAME_LEN);
H
hzcheng 已提交
119 120
      wordfree(&full_path);
      break;
S
#928  
slguan 已提交
121 122 123 124 125
    case 'D':
      if (wordexp(arg, &full_path, 0) != 0) {
        fprintf(stderr, "Invalid path %s\n", arg);
        return -1;
      }
H
Hui Li 已提交
126
      tstrncpy(arguments->dir, full_path.we_wordv[0], TSDB_FILENAME_LEN);
S
#928  
slguan 已提交
127 128 129
      wordfree(&full_path);
      break;
    case 'T':
130 131 132 133 134 135
      if (arg) {
        arguments->threadNum = atoi(arg);
      } else {
        fprintf(stderr, "Invalid number of threads\n");
        return -1;
      }
S
#928  
slguan 已提交
136
      break;
S
TD-3309  
Shengliang Guan 已提交
137 138 139
    case 'k':
      arguments->check = atoi(arg);
      break;
H
hzcheng 已提交
140 141 142
    case 'd':
      arguments->database = arg;
      break;
H
Hui Li 已提交
143 144 145 146 147 148 149 150 151 152 153
    case 'n':
      arguments->netTestRole = arg;
      break;
    case 'l':
      if (arg) {
        arguments->pktLen = atoi(arg);
      } else {
        fprintf(stderr, "Invalid packet length\n");
        return -1;
      }
      break;
154 155 156 157 158 159 160 161 162 163 164
    case 'N':
      if (arg) {
        arguments->pktNum = atoi(arg);
      } else {
        fprintf(stderr, "Invalid packet number\n");
        return -1;
      }
      break;
    case 'S':
      arguments->pktType = arg;
      break;
H
hzcheng 已提交
165 166 167 168 169 170 171 172 173 174 175 176
    case OPT_ABORT:
      arguments->abort = 1;
      break;
    default:
      return ARGP_ERR_UNKNOWN;
  }
  return 0;
}

/* Our argp parser. */
static struct argp argp = {options, parse_opt, args_doc, doc};

177 178
char      LINUXCLIENT_VERSION[] = "Welcome to the TDengine shell from %s, Client Version:%s\n"
                             "Copyright (c) 2020 by TAOS Data, Inc. All rights reserved.\n\n";
179
char g_password[SHELL_MAX_PASSWORD_LEN];
180

181
static void parse_args(
182 183
        int argc, char *argv[], SShellArguments *arguments) {
    for (int i = 1; i < argc; i++) {
184 185
        if ((strncmp(argv[i], "-p", 2) == 0)
              || (strncmp(argv[i], "--password", 10) == 0)) {
186 187
            strcpy(tsOsName, "Linux");
            printf(LINUXCLIENT_VERSION, tsOsName, taos_get_client_info());
188 189
            if ((strlen(argv[i]) == 2)
                  || (strncmp(argv[i], "--password", 10) == 0)) {
190
                printf("Enter password: ");
191
                taosSetConsoleEcho(false);
192 193 194
                if (scanf("%20s", g_password) > 1) {
                    fprintf(stderr, "password reading error\n");
                }
195
                taosSetConsoleEcho(true);
196 197 198
                if (EOF == getchar()) {
                    fprintf(stderr, "getchar() return EOF\n");
                }
199
            } else {
200 201
                tstrncpy(g_password, (char *)(argv[i] + 2), SHELL_MAX_PASSWORD_LEN);
                strcpy(argv[i], "-p");
202 203 204 205 206 207 208
            }
            arguments->password = g_password;
            arguments->is_use_passwd = true;
        }
    }
}

209
void shellParseArgument(int argc, char *argv[], SShellArguments *arguments) {
S
slguan 已提交
210 211 212
  static char verType[32] = {0};
  sprintf(verType, "version: %s\n", version);

L
lihui 已提交
213
  argp_program_version = verType;
214 215

  if (argc > 1) {
216
    parse_args(argc, argv, arguments);
217 218
  }

H
hzcheng 已提交
219 220
  argp_parse(&argp, argc, argv, 0, 0, arguments);
  if (arguments->abort) {
S
#1022  
slguan 已提交
221
    #ifndef _ALPINE
F
Frozen 已提交
222 223 224 225
      error(10, 0, "ABORTED");
    #else
      abort();
    #endif
H
hzcheng 已提交
226 227 228
  }
}

D
fix bug  
dapan1121 已提交
229
int32_t shellReadCommand(TAOS *con, char *command) {
H
hzcheng 已提交
230 231 232 233 234 235 236 237 238 239 240
  unsigned hist_counter = history.hend;
  char utf8_array[10] = "\0";
  Command cmd;
  memset(&cmd, 0, sizeof(cmd));
  cmd.buffer = (char *)calloc(1, MAX_COMMAND_SIZE);
  cmd.command = (char *)calloc(1, MAX_COMMAND_SIZE);
  showOnScreen(&cmd);

  // Read input.
  char c;
  while (1) {
H
Hui Li 已提交
241
    c = (char)getchar(); // getchar() return an 'int' value
H
hzcheng 已提交
242

D
fix bug  
dapan1121 已提交
243 244 245 246
    if (c == EOF) {
      return c;
    }

H
hzcheng 已提交
247 248 249 250
    if (c < 0) {  // For UTF-8
      int count = countPrefixOnes(c);
      utf8_array[0] = c;
      for (int k = 1; k < count; k++) {
H
Hui Li 已提交
251
        c = (char)getchar();
H
hzcheng 已提交
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
        utf8_array[k] = c;
      }
      insertChar(&cmd, utf8_array, count);
    } else if (c < '\033') {
      // Ctrl keys.  TODO: Implement ctrl combinations
      switch (c) {
        case 1:  // ctrl A
          positionCursorHome(&cmd);
          break;
        case 3:
          printf("\n");
          resetCommand(&cmd, "");
          kill(0, SIGINT);
          break;
        case 4:  // EOF or Ctrl+D
          printf("\n");
          taos_close(con);
          // write the history
          write_history();
          exitShell();
          break;
        case 5:  // ctrl E
          positionCursorEnd(&cmd);
          break;
        case 8:
          backspaceChar(&cmd);
          break;
        case '\n':
        case '\r':
          printf("\n");
          if (isReadyGo(&cmd)) {
            sprintf(command, "%s%s", cmd.buffer, cmd.command);
S
TD-1848  
Shengliang Guan 已提交
284 285
            tfree(cmd.buffer);
            tfree(cmd.command);
D
fix bug  
dapan1121 已提交
286
            return 0;
H
hzcheng 已提交
287 288 289 290
          } else {
            updateBuffer(&cmd);
          }
          break;
291 292 293
        case 11:  // Ctrl + K;
          clearLineAfter(&cmd);
          break;
H
hzcheng 已提交
294 295 296 297
        case 12:  // Ctrl + L;
          system("clear");
          showOnScreen(&cmd);
          break;
298 299 300
        case 21:  // Ctrl + U;
          clearLineBefore(&cmd);
          break;
H
hzcheng 已提交
301 302
      }
    } else if (c == '\033') {
H
Hui Li 已提交
303
      c = (char)getchar();
H
hzcheng 已提交
304 305
      switch (c) {
        case '[':
H
Hui Li 已提交
306
          c = (char)getchar();
H
hzcheng 已提交
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
          switch (c) {
            case 'A':  // Up arrow
              if (hist_counter != history.hstart) {
                hist_counter = (hist_counter + MAX_HISTORY_SIZE - 1) % MAX_HISTORY_SIZE;
                resetCommand(&cmd, (history.hist[hist_counter] == NULL) ? "" : history.hist[hist_counter]);
              }
              break;
            case 'B':  // Down arrow
              if (hist_counter != history.hend) {
                int next_hist = (hist_counter + 1) % MAX_HISTORY_SIZE;

                if (next_hist != history.hend) {
                  resetCommand(&cmd, (history.hist[next_hist] == NULL) ? "" : history.hist[next_hist]);
                } else {
                  resetCommand(&cmd, "");
                }
                hist_counter = next_hist;
              }
              break;
            case 'C':  // Right arrow
              moveCursorRight(&cmd);
              break;
            case 'D':  // Left arrow
              moveCursorLeft(&cmd);
              break;
            case '1':
H
Hui Li 已提交
333
              if ((c = (char)getchar()) == '~') {
H
hzcheng 已提交
334 335 336 337 338
                // Home key
                positionCursorHome(&cmd);
              }
              break;
            case '2':
H
Hui Li 已提交
339
              if ((c = (char)getchar()) == '~') {
H
hzcheng 已提交
340 341 342 343
                // Insert key
              }
              break;
            case '3':
H
Hui Li 已提交
344
              if ((c = (char)getchar()) == '~') {
H
hzcheng 已提交
345 346 347 348 349
                // Delete key
                deleteChar(&cmd);
              }
              break;
            case '4':
H
Hui Li 已提交
350
              if ((c = (char)getchar()) == '~') {
H
hzcheng 已提交
351 352 353 354 355
                // End key
                positionCursorEnd(&cmd);
              }
              break;
            case '5':
H
Hui Li 已提交
356
              if ((c = (char)getchar()) == '~') {
H
hzcheng 已提交
357 358 359 360
                // Page up key
              }
              break;
            case '6':
H
Hui Li 已提交
361
              if ((c = (char)getchar()) == '~') {
H
hzcheng 已提交
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
                // Page down key
              }
              break;
            case 72:
              // Home key
              positionCursorHome(&cmd);
              break;
            case 70:
              // End key
              positionCursorEnd(&cmd);
              break;
          }
          break;
      }
    } else if (c == 0x7f) {
      // press delete key
      backspaceChar(&cmd);
    } else {
      insertChar(&cmd, &c, 1);
    }
  }
D
fix bug  
dapan1121 已提交
383 384

  return 0;
H
hzcheng 已提交
385 386 387 388 389 390 391 392 393 394
}

void *shellLoopQuery(void *arg) {
  if (indicator) {
    get_old_terminal_mode(&oldtio);
    indicator = 0;
  }

  TAOS *con = (TAOS *)arg;

395 396
  setThreadName("shellLoopQuery");

H
hzcheng 已提交
397 398 399
  pthread_cleanup_push(cleanup_handler, NULL);

  char *command = malloc(MAX_COMMAND_SIZE);
400
  if (command == NULL){
S
slguan 已提交
401
    uError("failed to malloc command");
402 403
    return NULL;
  }
D
fix bug  
dapan1121 已提交
404 405

  int32_t err = 0;
406
  
407
  do {
H
hzcheng 已提交
408 409 410
    // Read command from shell.
    memset(command, 0, MAX_COMMAND_SIZE);
    set_terminal_mode();
D
fix bug  
dapan1121 已提交
411 412 413 414
    err = shellReadCommand(con, command);
    if (err) {
      break;
    }
H
hzcheng 已提交
415
    reset_terminal_mode();
416
  } while (shellRunCommand(con, command) == 0);
417
  
S
TD-1848  
Shengliang Guan 已提交
418
  tfree(command);
419
  exitShell();
H
hzcheng 已提交
420 421

  pthread_cleanup_pop(1);
422
  
H
hzcheng 已提交
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
  return NULL;
}

int get_old_terminal_mode(struct termios *tio) {
  /* Make sure stdin is a terminal. */
  if (!isatty(STDIN_FILENO)) {
    return -1;
  }

  // Get the parameter of current terminal
  if (tcgetattr(0, &oldtio) != 0) {
    return -1;
  }

  return 1;
}

void reset_terminal_mode() {
  if (tcsetattr(0, TCSANOW, &oldtio) != 0) {
    fprintf(stderr, "Fail to reset the terminal properties!\n");
    exit(EXIT_FAILURE);
  }
}

void set_terminal_mode() {
  struct termios newtio;

  /* if (atexit(reset_terminal_mode) != 0) { */
  /*     fprintf(stderr, "Error register exit function!\n"); */
  /*     exit(EXIT_FAILURE); */
  /* } */

  memcpy(&newtio, &oldtio, sizeof(oldtio));

  // Set new terminal attributes.
  newtio.c_iflag &= ~(IXON | IXOFF | ICRNL | INLCR | IGNCR | IMAXBEL | ISTRIP);
  newtio.c_iflag |= IGNBRK;

  // newtio.c_oflag &= ~(OPOST|ONLCR|OCRNL|ONLRET);
  newtio.c_oflag |= OPOST;
  newtio.c_oflag |= ONLCR;
  newtio.c_oflag &= ~(OCRNL | ONLRET);

  newtio.c_lflag &= ~(IEXTEN | ICANON | ECHO | ECHOE | ECHONL | ECHOCTL | ECHOPRT | ECHOKE | ISIG);
  newtio.c_cc[VMIN] = 1;
  newtio.c_cc[VTIME] = 0;

  if (tcsetattr(0, TCSANOW, &newtio) != 0) {
    fprintf(stderr, "Fail to set terminal properties!\n");
    exit(EXIT_FAILURE);
  }
}

476
void get_history_path(char *_history) { snprintf(_history, TSDB_FILENAME_LEN, "%s/%s", getenv("HOME"), HISTORY_FILE); }
H
hzcheng 已提交
477 478 479

void clearScreen(int ecmd_pos, int cursor_pos) {
  struct winsize w;
480 481 482 483 484
  if (ioctl(0, TIOCGWINSZ, &w) < 0 || w.ws_col == 0 || w.ws_row == 0) {
    //fprintf(stderr, "No stream device, and use default value(col 120, row 30)\n");
    w.ws_col = 120;
    w.ws_row = 30;
  }
H
hzcheng 已提交
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501

  int cursor_x = cursor_pos / w.ws_col;
  int cursor_y = cursor_pos % w.ws_col;
  int command_x = ecmd_pos / w.ws_col;
  positionCursor(cursor_y, LEFT);
  positionCursor(command_x - cursor_x, DOWN);
  fprintf(stdout, "\033[2K");
  for (int i = 0; i < command_x; i++) {
    positionCursor(1, UP);
    fprintf(stdout, "\033[2K");
  }
  fflush(stdout);
}

void showOnScreen(Command *cmd) {
  struct winsize w;
  if (ioctl(0, TIOCGWINSZ, &w) < 0 || w.ws_col == 0 || w.ws_row == 0) {
502 503 504
    //fprintf(stderr, "No stream device\n");
    w.ws_col = 120;
    w.ws_row = 30;
H
hzcheng 已提交
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
  }

  wchar_t wc;
  int size = 0;

  // Print out the command.
  char *total_string = malloc(MAX_COMMAND_SIZE);
  memset(total_string, '\0', MAX_COMMAND_SIZE);
  if (strcmp(cmd->buffer, "") == 0) {
    sprintf(total_string, "%s%s", PROMPT_HEADER, cmd->command);
  } else {
    sprintf(total_string, "%s%s", CONTINUE_PROMPT, cmd->command);
  }

  int remain_column = w.ws_col;
  /* size = cmd->commandSize + prompt_size; */
  for (char *str = total_string; size < cmd->commandSize + prompt_size;) {
    int ret = mbtowc(&wc, str, MB_CUR_MAX);
    if (ret < 0) break;
    size += ret;
    /* assert(size >= 0); */
    int width = wcwidth(wc);
    if (remain_column > width) {
      printf("%lc", wc);
      remain_column -= width;
    } else {
      if (remain_column == width) {
        printf("%lc\n\r", wc);
        remain_column = w.ws_col;
      } else {
        printf("\n\r%lc", wc);
        remain_column = w.ws_col - width;
      }
    }

    str = total_string + size;
  }

  free(total_string);
  /* for (int i = 0; i < size; i++){ */
  /*     char c = total_string[i]; */
  /*     if (k % w.ws_col == 0) { */
  /*         printf("%c\n\r", c); */
  /*     } */
  /*     else { */
  /*         printf("%c", c); */
  /*     } */
  /*     k += 1; */
  /* } */

  // Position the cursor
  int cursor_pos = cmd->screenOffset + prompt_size;
  int ecmd_pos = cmd->endOffset + prompt_size;

  int cursor_x = cursor_pos / w.ws_col;
  int cursor_y = cursor_pos % w.ws_col;
  // int cursor_y = cursor % w.ws_col;
  int command_x = ecmd_pos / w.ws_col;
  int command_y = ecmd_pos % w.ws_col;
  // int command_y = (command.size() + prompt_size) % w.ws_col;
  positionCursor(command_y, LEFT);
  positionCursor(command_x, UP);
  positionCursor(cursor_x, DOWN);
  positionCursor(cursor_y, RIGHT);
  fflush(stdout);
}

void cleanup_handler(void *arg) { tcsetattr(0, TCSANOW, &oldtio); }

void exitShell() {
575 576
  /*int32_t ret =*/ tcsetattr(STDIN_FILENO, TCSANOW, &oldtio);
  taos_cleanup();
H
hzcheng 已提交
577 578
  exit(EXIT_SUCCESS);
}