1 /* Redis CLI (command line interface) 2 * 3 * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * * Redistributions of source code must retain the above copyright notice, 10 * this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * * Neither the name of Redis nor the names of its contributors may be used 15 * to endorse or promote products derived from this software without 16 * specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "fmacros.h" 32 #include "version.h" 33 34 #include <stdio.h> 35 #include <string.h> 36 #include <stdlib.h> 37 #include <unistd.h> 38 #include <ctype.h> 39 #include <errno.h> 40 #include <sys/stat.h> 41 #include <sys/time.h> 42 #include <assert.h> 43 44 #include "hiredis.h" 45 #include "sds.h" 46 #include "zmalloc.h" 47 #include "linenoise.h" 48 #include "help.h" 49 50 #define REDIS_NOTUSED(V) ((void) V) 51 52 static redisContext *context; 53 static struct config { 54 char *hostip; 55 int hostport; 56 char *hostsocket; 57 long repeat; 58 long interval; 59 int dbnum; 60 int interactive; 61 int shutdown; 62 int monitor_mode; 63 int pubsub_mode; 64 int latency_mode; 65 int cluster_mode; 66 int cluster_reissue_command; 67 int stdinarg; /* get last arg from stdin. (-x option) */ 68 char *auth; 69 int raw_output; /* output mode per command */ 70 sds mb_delim; 71 char prompt[32]; 72 char *eval; 73 } config; 74 75 static void usage(); 76 char *redisGitSHA1(void); 77 char *redisGitDirty(void); 78 79 /*------------------------------------------------------------------------------ 80 * Utility functions 81 *--------------------------------------------------------------------------- */ 82 83 static long long mstime(void) { 84 struct timeval tv; 85 long long mst; 86 87 gettimeofday(&tv, NULL); 88 mst = ((long)tv.tv_sec)*1000; 89 mst += tv.tv_usec/1000; 90 return mst; 91 } 92 93 static void cliRefreshPrompt(void) { 94 if (config.dbnum == 0) 95 snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d> ", 96 config.hostip, config.hostport); 97 else 98 snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d[%d]> ", 99 config.hostip, config.hostport, config.dbnum); 100 } 101 102 /*------------------------------------------------------------------------------ 103 * Help functions 104 *--------------------------------------------------------------------------- */ 105 106 #define CLI_HELP_COMMAND 1 107 #define CLI_HELP_GROUP 2 108 109 typedef struct { 110 int type; 111 int argc; 112 sds *argv; 113 sds full; 114 115 /* Only used for help on commands */ 116 struct commandHelp *org; 117 } helpEntry; 118 119 static helpEntry *helpEntries; 120 static int helpEntriesLen; 121 122 static sds cliVersion() { 123 sds version; 124 version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION); 125 126 /* Add git commit and working tree status when available */ 127 if (strtoll(redisGitSHA1(),NULL,16)) { 128 version = sdscatprintf(version, " (git:%s", redisGitSHA1()); 129 if (strtoll(redisGitDirty(),NULL,10)) 130 version = sdscatprintf(version, "-dirty"); 131 version = sdscat(version, ")"); 132 } 133 return version; 134 } 135 136 static void cliInitHelp() { 137 int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp); 138 int groupslen = sizeof(commandGroups)/sizeof(char*); 139 int i, len, pos = 0; 140 helpEntry tmp; 141 142 helpEntriesLen = len = commandslen+groupslen; 143 helpEntries = malloc(sizeof(helpEntry)*len); 144 145 for (i = 0; i < groupslen; i++) { 146 tmp.argc = 1; 147 tmp.argv = malloc(sizeof(sds)); 148 tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]); 149 tmp.full = tmp.argv[0]; 150 tmp.type = CLI_HELP_GROUP; 151 tmp.org = NULL; 152 helpEntries[pos++] = tmp; 153 } 154 155 for (i = 0; i < commandslen; i++) { 156 tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc); 157 tmp.full = sdsnew(commandHelp[i].name); 158 tmp.type = CLI_HELP_COMMAND; 159 tmp.org = &commandHelp[i]; 160 helpEntries[pos++] = tmp; 161 } 162 } 163 164 /* Output command help to stdout. */ 165 static void cliOutputCommandHelp(struct commandHelp *help, int group) { 166 printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params); 167 printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary); 168 printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since); 169 if (group) { 170 printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]); 171 } 172 } 173 174 /* Print generic help. */ 175 static void cliOutputGenericHelp() { 176 sds version = cliVersion(); 177 printf( 178 "redis-cli %s\r\n" 179 "Type: \"help @<group>\" to get a list of commands in <group>\r\n" 180 " \"help <command>\" for help on <command>\r\n" 181 " \"help <tab>\" to get a list of possible help topics\r\n" 182 " \"quit\" to exit\r\n", 183 version 184 ); 185 sdsfree(version); 186 } 187 188 /* Output all command help, filtering by group or command name. */ 189 static void cliOutputHelp(int argc, char **argv) { 190 int i, j, len; 191 int group = -1; 192 helpEntry *entry; 193 struct commandHelp *help; 194 195 if (argc == 0) { 196 cliOutputGenericHelp(); 197 return; 198 } else if (argc > 0 && argv[0][0] == '@') { 199 len = sizeof(commandGroups)/sizeof(char*); 200 for (i = 0; i < len; i++) { 201 if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) { 202 group = i; 203 break; 204 } 205 } 206 } 207 208 assert(argc > 0); 209 for (i = 0; i < helpEntriesLen; i++) { 210 entry = &helpEntries[i]; 211 if (entry->type != CLI_HELP_COMMAND) continue; 212 213 help = entry->org; 214 if (group == -1) { 215 /* Compare all arguments */ 216 if (argc == entry->argc) { 217 for (j = 0; j < argc; j++) { 218 if (strcasecmp(argv[j],entry->argv[j]) != 0) break; 219 } 220 if (j == argc) { 221 cliOutputCommandHelp(help,1); 222 } 223 } 224 } else { 225 if (group == help->group) { 226 cliOutputCommandHelp(help,0); 227 } 228 } 229 } 230 printf("\r\n"); 231 } 232 233 static void completionCallback(const char *buf, linenoiseCompletions *lc) { 234 size_t startpos = 0; 235 int mask; 236 int i; 237 size_t matchlen; 238 sds tmp; 239 240 if (strncasecmp(buf,"help ",5) == 0) { 241 startpos = 5; 242 while (isspace(buf[startpos])) startpos++; 243 mask = CLI_HELP_COMMAND | CLI_HELP_GROUP; 244 } else { 245 mask = CLI_HELP_COMMAND; 246 } 247 248 for (i = 0; i < helpEntriesLen; i++) { 249 if (!(helpEntries[i].type & mask)) continue; 250 251 matchlen = strlen(buf+startpos); 252 if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) { 253 tmp = sdsnewlen(buf,startpos); 254 tmp = sdscat(tmp,helpEntries[i].full); 255 linenoiseAddCompletion(lc,tmp); 256 sdsfree(tmp); 257 } 258 } 259 } 260 261 /*------------------------------------------------------------------------------ 262 * Networking / parsing 263 *--------------------------------------------------------------------------- */ 264 265 /* Send AUTH command to the server */ 266 static int cliAuth() { 267 redisReply *reply; 268 if (config.auth == NULL) return REDIS_OK; 269 270 reply = redisCommand(context,"AUTH %s",config.auth); 271 if (reply != NULL) { 272 freeReplyObject(reply); 273 return REDIS_OK; 274 } 275 return REDIS_ERR; 276 } 277 278 /* Send SELECT dbnum to the server */ 279 static int cliSelect() { 280 redisReply *reply; 281 if (config.dbnum == 0) return REDIS_OK; 282 283 reply = redisCommand(context,"SELECT %d",config.dbnum); 284 if (reply != NULL) { 285 freeReplyObject(reply); 286 return REDIS_OK; 287 } 288 return REDIS_ERR; 289 } 290 291 /* Connect to the client. If force is not zero the connection is performed 292 * even if there is already a connected socket. */ 293 static int cliConnect(int force) { 294 if (context == NULL || force) { 295 if (context != NULL) 296 redisFree(context); 297 298 if (config.hostsocket == NULL) { 299 context = redisConnect(config.hostip,config.hostport); 300 } else { 301 context = redisConnectUnix(config.hostsocket); 302 } 303 304 if (context->err) { 305 fprintf(stderr,"Could not connect to Redis at "); 306 if (config.hostsocket == NULL) 307 fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr); 308 else 309 fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr); 310 redisFree(context); 311 context = NULL; 312 return REDIS_ERR; 313 } 314 315 /* Do AUTH and select the right DB. */ 316 if (cliAuth() != REDIS_OK) 317 return REDIS_ERR; 318 if (cliSelect() != REDIS_OK) 319 return REDIS_ERR; 320 } 321 return REDIS_OK; 322 } 323 324 static void cliPrintContextError() { 325 if (context == NULL) return; 326 fprintf(stderr,"Error: %s\n",context->errstr); 327 } 328 329 static sds cliFormatReplyTTY(redisReply *r, char *prefix) { 330 sds out = sdsempty(); 331 switch (r->type) { 332 case REDIS_REPLY_ERROR: 333 out = sdscatprintf(out,"(error) %s\n", r->str); 334 break; 335 case REDIS_REPLY_STATUS: 336 out = sdscat(out,r->str); 337 out = sdscat(out,"\n"); 338 break; 339 case REDIS_REPLY_INTEGER: 340 out = sdscatprintf(out,"(integer) %lld\n",r->integer); 341 break; 342 case REDIS_REPLY_STRING: 343 /* If you are producing output for the standard output we want 344 * a more interesting output with quoted characters and so forth */ 345 out = sdscatrepr(out,r->str,r->len); 346 out = sdscat(out,"\n"); 347 break; 348 case REDIS_REPLY_NIL: 349 out = sdscat(out,"(nil)\n"); 350 break; 351 case REDIS_REPLY_ARRAY: 352 if (r->elements == 0) { 353 out = sdscat(out,"(empty list or set)\n"); 354 } else { 355 unsigned int i, idxlen = 0; 356 char _prefixlen[16]; 357 char _prefixfmt[16]; 358 sds _prefix; 359 sds tmp; 360 361 /* Calculate chars needed to represent the largest index */ 362 i = r->elements; 363 do { 364 idxlen++; 365 i /= 10; 366 } while(i); 367 368 /* Prefix for nested multi bulks should grow with idxlen+2 spaces */ 369 memset(_prefixlen,' ',idxlen+2); 370 _prefixlen[idxlen+2] = '\0'; 371 _prefix = sdscat(sdsnew(prefix),_prefixlen); 372 373 /* Setup prefix format for every entry */ 374 snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen); 375 376 for (i = 0; i < r->elements; i++) { 377 /* Don't use the prefix for the first element, as the parent 378 * caller already prepended the index number. */ 379 out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1); 380 381 /* Format the multi bulk entry */ 382 tmp = cliFormatReplyTTY(r->element[i],_prefix); 383 out = sdscatlen(out,tmp,sdslen(tmp)); 384 sdsfree(tmp); 385 } 386 sdsfree(_prefix); 387 } 388 break; 389 default: 390 fprintf(stderr,"Unknown reply type: %d\n", r->type); 391 exit(1); 392 } 393 return out; 394 } 395 396 static sds cliFormatReplyRaw(redisReply *r) { 397 sds out = sdsempty(), tmp; 398 size_t i; 399 400 switch (r->type) { 401 case REDIS_REPLY_NIL: 402 /* Nothing... */ 403 break; 404 case REDIS_REPLY_ERROR: 405 out = sdscatlen(out,r->str,r->len); 406 out = sdscatlen(out,"\n",1); 407 break; 408 case REDIS_REPLY_STATUS: 409 case REDIS_REPLY_STRING: 410 out = sdscatlen(out,r->str,r->len); 411 break; 412 case REDIS_REPLY_INTEGER: 413 out = sdscatprintf(out,"%lld",r->integer); 414 break; 415 case REDIS_REPLY_ARRAY: 416 for (i = 0; i < r->elements; i++) { 417 if (i > 0) out = sdscat(out,config.mb_delim); 418 tmp = cliFormatReplyRaw(r->element[i]); 419 out = sdscatlen(out,tmp,sdslen(tmp)); 420 sdsfree(tmp); 421 } 422 break; 423 default: 424 fprintf(stderr,"Unknown reply type: %d\n", r->type); 425 exit(1); 426 } 427 return out; 428 } 429 430 static int cliReadReply(int output_raw_strings) { 431 void *_reply; 432 redisReply *reply; 433 sds out; 434 int output = 1; 435 436 if (redisGetReply(context,&_reply) != REDIS_OK) { 437 if (config.shutdown) 438 return REDIS_OK; 439 if (config.interactive) { 440 /* Filter cases where we should reconnect */ 441 if (context->err == REDIS_ERR_IO && errno == ECONNRESET) 442 return REDIS_ERR; 443 if (context->err == REDIS_ERR_EOF) 444 return REDIS_ERR; 445 } 446 cliPrintContextError(); 447 exit(1); 448 return REDIS_ERR; /* avoid compiler warning */ 449 } 450 451 reply = (redisReply*)_reply; 452 453 /* Check if we need to connect to a different node and reissue the request. */ 454 if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR && 455 (!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK"))) 456 { 457 char *p = reply->str, *s; 458 int slot; 459 460 output = 0; 461 /* Comments show the position of the pointer as: 462 * 463 * [S] for pointer 's' 464 * [P] for pointer 'p' 465 */ 466 s = strchr(p,' '); /* MOVED[S]3999 127.0.0.1:6381 */ 467 p = strchr(s+1,' '); /* MOVED[S]3999[P]127.0.0.1:6381 */ 468 *p = '\0'; 469 slot = atoi(s+1); 470 s = strchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */ 471 *s = '\0'; 472 sdsfree(config.hostip); 473 config.hostip = sdsnew(p+1); 474 config.hostport = atoi(s+1); 475 if (config.interactive) 476 printf("-> Redirected to slot [%d] located at %s:%d\n", 477 slot, config.hostip, config.hostport); 478 config.cluster_reissue_command = 1; 479 } 480 481 if (output) { 482 if (output_raw_strings) { 483 out = cliFormatReplyRaw(reply); 484 } else { 485 if (config.raw_output) { 486 out = cliFormatReplyRaw(reply); 487 out = sdscat(out,"\n"); 488 } else { 489 out = cliFormatReplyTTY(reply,""); 490 } 491 } 492 fwrite(out,sdslen(out),1,stdout); 493 sdsfree(out); 494 } 495 freeReplyObject(reply); 496 return REDIS_OK; 497 } 498 499 static int cliSendCommand(int argc, char **argv, int repeat) { 500 char *command = argv[0]; 501 size_t *argvlen; 502 int j, output_raw; 503 504 if (context == NULL) return REDIS_ERR; 505 506 output_raw = 0; 507 if (!strcasecmp(command,"info") || 508 (argc == 2 && !strcasecmp(command,"cluster") && 509 (!strcasecmp(argv[1],"nodes") || 510 !strcasecmp(argv[1],"info"))) || 511 (argc == 2 && !strcasecmp(command,"client") && 512 !strcasecmp(argv[1],"list"))) 513 514 { 515 output_raw = 1; 516 } 517 518 if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) { 519 cliOutputHelp(--argc, ++argv); 520 return REDIS_OK; 521 } 522 if (!strcasecmp(command,"shutdown")) config.shutdown = 1; 523 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; 524 if (!strcasecmp(command,"subscribe") || 525 !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1; 526 527 /* Setup argument length */ 528 argvlen = malloc(argc*sizeof(size_t)); 529 for (j = 0; j < argc; j++) 530 argvlen[j] = sdslen(argv[j]); 531 532 while(repeat--) { 533 redisAppendCommandArgv(context,argc,(const char**)argv,argvlen); 534 while (config.monitor_mode) { 535 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 536 fflush(stdout); 537 } 538 539 if (config.pubsub_mode) { 540 if (!config.raw_output) 541 printf("Reading messages... (press Ctrl-C to quit)\n"); 542 while (1) { 543 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 544 } 545 } 546 547 if (cliReadReply(output_raw) != REDIS_OK) { 548 free(argvlen); 549 return REDIS_ERR; 550 } else { 551 /* Store database number when SELECT was successfully executed. */ 552 if (!strcasecmp(command,"select") && argc == 2) { 553 config.dbnum = atoi(argv[1]); 554 cliRefreshPrompt(); 555 } 556 } 557 if (config.interval) usleep(config.interval); 558 fflush(stdout); /* Make it grep friendly */ 559 } 560 561 free(argvlen); 562 return REDIS_OK; 563 } 564 565 /*------------------------------------------------------------------------------ 566 * User interface 567 *--------------------------------------------------------------------------- */ 568 569 static int parseOptions(int argc, char **argv) { 570 int i; 571 572 for (i = 1; i < argc; i++) { 573 int lastarg = i==argc-1; 574 575 if (!strcmp(argv[i],"-h") && !lastarg) { 576 sdsfree(config.hostip); 577 config.hostip = sdsnew(argv[++i]); 578 } else if (!strcmp(argv[i],"-h") && lastarg) { 579 usage(); 580 } else if (!strcmp(argv[i],"--help")) { 581 usage(); 582 } else if (!strcmp(argv[i],"-x")) { 583 config.stdinarg = 1; 584 } else if (!strcmp(argv[i],"-p") && !lastarg) { 585 config.hostport = atoi(argv[++i]); 586 } else if (!strcmp(argv[i],"-s") && !lastarg) { 587 config.hostsocket = argv[++i]; 588 } else if (!strcmp(argv[i],"-r") && !lastarg) { 589 config.repeat = strtoll(argv[++i],NULL,10); 590 } else if (!strcmp(argv[i],"-i") && !lastarg) { 591 double seconds = atof(argv[++i]); 592 config.interval = seconds*1000000; 593 } else if (!strcmp(argv[i],"-n") && !lastarg) { 594 config.dbnum = atoi(argv[++i]); 595 } else if (!strcmp(argv[i],"-a") && !lastarg) { 596 config.auth = argv[++i]; 597 } else if (!strcmp(argv[i],"--raw")) { 598 config.raw_output = 1; 599 } else if (!strcmp(argv[i],"--latency")) { 600 config.latency_mode = 1; 601 } else if (!strcmp(argv[i],"--eval") && !lastarg) { 602 config.eval = argv[++i]; 603 } else if (!strcmp(argv[i],"-c")) { 604 config.cluster_mode = 1; 605 } else if (!strcmp(argv[i],"-d") && !lastarg) { 606 sdsfree(config.mb_delim); 607 config.mb_delim = sdsnew(argv[++i]); 608 } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) { 609 sds version = cliVersion(); 610 printf("redis-cli %s\n", version); 611 sdsfree(version); 612 exit(0); 613 } else { 614 break; 615 } 616 } 617 return i; 618 } 619 620 static sds readArgFromStdin(void) { 621 char buf[1024]; 622 sds arg = sdsempty(); 623 624 while(1) { 625 int nread = read(fileno(stdin),buf,1024); 626 627 if (nread == 0) break; 628 else if (nread == -1) { 629 perror("Reading from standard input"); 630 exit(1); 631 } 632 arg = sdscatlen(arg,buf,nread); 633 } 634 return arg; 635 } 636 637 static void usage() { 638 sds version = cliVersion(); 639 fprintf(stderr, 640 "redis-cli %s\n" 641 "\n" 642 "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n" 643 " -h <hostname> Server hostname (default: 127.0.0.1)\n" 644 " -p <port> Server port (default: 6379)\n" 645 " -s <socket> Server socket (overrides hostname and port)\n" 646 " -a <password> Password to use when connecting to the server\n" 647 " -r <repeat> Execute specified command N times\n" 648 " -i <interval> When -r is used, waits <interval> seconds per command.\n" 649 " It is possible to specify sub-second times like -i 0.1.\n" 650 " -n <db> Database number\n" 651 " -x Read last argument from STDIN\n" 652 " -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n)\n" 653 " -c Enable cluster mode (follow -ASK and -MOVED redirections)\n" 654 " --raw Use raw formatting for replies (default when STDOUT is not a tty)\n" 655 " --latency Enter a special mode continuously sampling latency.\n" 656 " --eval <file> Send an EVAL command using the Lua script at <file>.\n" 657 " --help Output this help and exit\n" 658 " --version Output version and exit\n" 659 "\n" 660 "Examples:\n" 661 " cat /etc/passwd | redis-cli -x set mypasswd\n" 662 " redis-cli get mypasswd\n" 663 " redis-cli -r 100 lpush mylist x\n" 664 " redis-cli -r 100 -i 1 info | grep used_memory_human:\n" 665 " redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n" 666 " (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n" 667 "\n" 668 "When no command is given, redis-cli starts in interactive mode.\n" 669 "Type \"help\" in interactive mode for information on available commands.\n" 670 "\n", 671 version); 672 sdsfree(version); 673 exit(1); 674 } 675 676 /* Turn the plain C strings into Sds strings */ 677 static char **convertToSds(int count, char** args) { 678 int j; 679 char **sds = zmalloc(sizeof(char*)*count); 680 681 for(j = 0; j < count; j++) 682 sds[j] = sdsnew(args[j]); 683 684 return sds; 685 } 686 687 #define LINE_BUFLEN 4096 688 static void repl() { 689 sds historyfile = NULL; 690 int history = 0; 691 char *line; 692 int argc; 693 sds *argv; 694 695 config.interactive = 1; 696 linenoiseSetCompletionCallback(completionCallback); 697 698 /* Only use history when stdin is a tty. */ 699 if (isatty(fileno(stdin))) { 700 history = 1; 701 702 if (getenv("HOME") != NULL) { 703 historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME")); 704 linenoiseHistoryLoad(historyfile); 705 } 706 } 707 708 cliRefreshPrompt(); 709 while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) { 710 if (line[0] != '\0') { 711 argv = sdssplitargs(line,&argc); 712 if (history) linenoiseHistoryAdd(line); 713 if (historyfile) linenoiseHistorySave(historyfile); 714 715 if (argv == NULL) { 716 printf("Invalid argument(s)\n"); 717 free(line); 718 continue; 719 } else if (argc > 0) { 720 if (strcasecmp(argv[0],"quit") == 0 || 721 strcasecmp(argv[0],"exit") == 0) 722 { 723 exit(0); 724 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) { 725 sdsfree(config.hostip); 726 config.hostip = sdsnew(argv[1]); 727 config.hostport = atoi(argv[2]); 728 cliConnect(1); 729 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) { 730 linenoiseClearScreen(); 731 } else { 732 long long start_time = mstime(), elapsed; 733 int repeat, skipargs = 0; 734 735 repeat = atoi(argv[0]); 736 if (argc > 1 && repeat) { 737 skipargs = 1; 738 } else { 739 repeat = 1; 740 } 741 742 while (1) { 743 config.cluster_reissue_command = 0; 744 if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) 745 != REDIS_OK) 746 { 747 cliConnect(1); 748 749 /* If we still cannot send the command print error. 750 * We'll try to reconnect the next time. */ 751 if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) 752 != REDIS_OK) 753 cliPrintContextError(); 754 } 755 /* Issue the command again if we got redirected in cluster mode */ 756 if (config.cluster_mode && config.cluster_reissue_command) { 757 cliConnect(1); 758 } else { 759 break; 760 } 761 } 762 elapsed = mstime()-start_time; 763 if (elapsed >= 500) { 764 printf("(%.2fs)\n",(double)elapsed/1000); 765 } 766 } 767 } 768 /* Free the argument vector */ 769 while(argc--) sdsfree(argv[argc]); 770 zfree(argv); 771 } 772 /* linenoise() returns malloc-ed lines like readline() */ 773 free(line); 774 } 775 exit(0); 776 } 777 778 static int noninteractive(int argc, char **argv) { 779 int retval = 0; 780 if (config.stdinarg) { 781 argv = zrealloc(argv, (argc+1)*sizeof(char*)); 782 argv[argc] = readArgFromStdin(); 783 retval = cliSendCommand(argc+1, argv, config.repeat); 784 } else { 785 /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */ 786 retval = cliSendCommand(argc, argv, config.repeat); 787 } 788 return retval; 789 } 790 791 static int evalMode(int argc, char **argv) { 792 sds script = sdsempty(); 793 FILE *fp; 794 char buf[1024]; 795 size_t nread; 796 char **argv2; 797 int j, got_comma = 0, keys = 0; 798 799 /* Load the script from the file, as an sds string. */ 800 fp = fopen(config.eval,"r"); 801 if (!fp) { 802 fprintf(stderr, 803 "Can't open file '%s': %s\n", config.eval, strerror(errno)); 804 exit(1); 805 } 806 while((nread = fread(buf,1,sizeof(buf),fp)) != 0) { 807 script = sdscatlen(script,buf,nread); 808 } 809 fclose(fp); 810 811 /* Create our argument vector */ 812 argv2 = zmalloc(sizeof(sds)*(argc+3)); 813 argv2[0] = sdsnew("EVAL"); 814 argv2[1] = script; 815 for (j = 0; j < argc; j++) { 816 if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) { 817 got_comma = 1; 818 continue; 819 } 820 argv2[j+3-got_comma] = sdsnew(argv[j]); 821 if (!got_comma) keys++; 822 } 823 argv2[2] = sdscatprintf(sdsempty(),"%d",keys); 824 825 /* Call it */ 826 return cliSendCommand(argc+3-got_comma, argv2, config.repeat); 827 } 828 829 static void latencyMode(void) { 830 redisReply *reply; 831 long long start, latency, min, max, tot, count = 0; 832 double avg; 833 834 if (!context) exit(1); 835 while(1) { 836 start = mstime(); 837 reply = redisCommand(context,"PING"); 838 if (reply == NULL) { 839 fprintf(stderr,"\nI/O error\n"); 840 exit(1); 841 } 842 latency = mstime()-start; 843 freeReplyObject(reply); 844 count++; 845 if (count == 1) { 846 min = max = tot = latency; 847 avg = (double) latency; 848 } else { 849 if (latency < min) min = latency; 850 if (latency > max) max = latency; 851 tot += latency; 852 avg = (double) tot/count; 853 } 854 printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)", 855 min, max, avg, count); 856 fflush(stdout); 857 usleep(10000); 858 } 859 } 860 861 int main(int argc, char **argv) { 862 int firstarg; 863 864 config.hostip = sdsnew("127.0.0.1"); 865 config.hostport = 6379; 866 config.hostsocket = NULL; 867 config.repeat = 1; 868 config.interval = 0; 869 config.dbnum = 0; 870 config.interactive = 0; 871 config.shutdown = 0; 872 config.monitor_mode = 0; 873 config.pubsub_mode = 0; 874 config.latency_mode = 0; 875 config.cluster_mode = 0; 876 config.stdinarg = 0; 877 config.auth = NULL; 878 config.eval = NULL; 879 config.raw_output = !isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL); 880 config.mb_delim = sdsnew("\n"); 881 cliInitHelp(); 882 883 firstarg = parseOptions(argc,argv); 884 argc -= firstarg; 885 argv += firstarg; 886 887 /* Start in latency mode if appropriate */ 888 if (config.latency_mode) { 889 cliConnect(0); 890 latencyMode(); 891 } 892 893 /* Start interactive mode when no command is provided */ 894 if (argc == 0 && !config.eval) { 895 /* Note that in repl mode we don't abort on connection error. 896 * A new attempt will be performed for every command send. */ 897 cliConnect(0); 898 repl(); 899 } 900 901 /* Otherwise, we have some arguments to execute */ 902 if (cliConnect(0) != REDIS_OK) exit(1); 903 if (config.eval) { 904 return evalMode(argc,argv); 905 } else { 906 return noninteractive(argc,convertToSds(argc,argv)); 907 } 908 } 909