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