1 /* Redis CLI (command line interface) 2 * 3 * Copyright (c) 2009-2012, 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 <time.h> 39 #include <ctype.h> 40 #include <errno.h> 41 #include <sys/stat.h> 42 #include <sys/time.h> 43 #include <assert.h> 44 #include <fcntl.h> 45 46 #include "hiredis.h" 47 #include "sds.h" 48 #include "zmalloc.h" 49 #include "linenoise.h" 50 #include "help.h" 51 #include "anet.h" 52 #include "ae.h" 53 54 #define REDIS_NOTUSED(V) ((void) V) 55 56 #define OUTPUT_STANDARD 0 57 #define OUTPUT_RAW 1 58 #define OUTPUT_CSV 2 59 60 static redisContext *context; 61 static struct config { 62 char *hostip; 63 int hostport; 64 char *hostsocket; 65 long repeat; 66 long interval; 67 int dbnum; 68 int interactive; 69 int shutdown; 70 int monitor_mode; 71 int pubsub_mode; 72 int latency_mode; 73 int cluster_mode; 74 int cluster_reissue_command; 75 int slave_mode; 76 int pipe_mode; 77 int getrdb_mode; 78 char *rdb_filename; 79 int bigkeys; 80 int stdinarg; /* get last arg from stdin. (-x option) */ 81 char *auth; 82 int output; /* output mode, see OUTPUT_* defines */ 83 sds mb_delim; 84 char prompt[128]; 85 char *eval; 86 } config; 87 88 static void usage(); 89 char *redisGitSHA1(void); 90 char *redisGitDirty(void); 91 92 /*------------------------------------------------------------------------------ 93 * Utility functions 94 *--------------------------------------------------------------------------- */ 95 96 static long long mstime(void) { 97 struct timeval tv; 98 long long mst; 99 100 gettimeofday(&tv, NULL); 101 mst = ((long long)tv.tv_sec)*1000; 102 mst += tv.tv_usec/1000; 103 return mst; 104 } 105 106 static void cliRefreshPrompt(void) { 107 int len; 108 109 if (config.hostsocket != NULL) 110 len = snprintf(config.prompt,sizeof(config.prompt),"redis %s", 111 config.hostsocket); 112 else 113 len = snprintf(config.prompt,sizeof(config.prompt),"redis %s:%d", 114 config.hostip, config.hostport); 115 /* Add [dbnum] if needed */ 116 if (config.dbnum != 0) 117 len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]", 118 config.dbnum); 119 snprintf(config.prompt+len,sizeof(config.prompt)-len,"> "); 120 } 121 122 /*------------------------------------------------------------------------------ 123 * Help functions 124 *--------------------------------------------------------------------------- */ 125 126 #define CLI_HELP_COMMAND 1 127 #define CLI_HELP_GROUP 2 128 129 typedef struct { 130 int type; 131 int argc; 132 sds *argv; 133 sds full; 134 135 /* Only used for help on commands */ 136 struct commandHelp *org; 137 } helpEntry; 138 139 static helpEntry *helpEntries; 140 static int helpEntriesLen; 141 142 static sds cliVersion() { 143 sds version; 144 version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION); 145 146 /* Add git commit and working tree status when available */ 147 if (strtoll(redisGitSHA1(),NULL,16)) { 148 version = sdscatprintf(version, " (git:%s", redisGitSHA1()); 149 if (strtoll(redisGitDirty(),NULL,10)) 150 version = sdscatprintf(version, "-dirty"); 151 version = sdscat(version, ")"); 152 } 153 return version; 154 } 155 156 static void cliInitHelp() { 157 int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp); 158 int groupslen = sizeof(commandGroups)/sizeof(char*); 159 int i, len, pos = 0; 160 helpEntry tmp; 161 162 helpEntriesLen = len = commandslen+groupslen; 163 helpEntries = malloc(sizeof(helpEntry)*len); 164 165 for (i = 0; i < groupslen; i++) { 166 tmp.argc = 1; 167 tmp.argv = malloc(sizeof(sds)); 168 tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]); 169 tmp.full = tmp.argv[0]; 170 tmp.type = CLI_HELP_GROUP; 171 tmp.org = NULL; 172 helpEntries[pos++] = tmp; 173 } 174 175 for (i = 0; i < commandslen; i++) { 176 tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc); 177 tmp.full = sdsnew(commandHelp[i].name); 178 tmp.type = CLI_HELP_COMMAND; 179 tmp.org = &commandHelp[i]; 180 helpEntries[pos++] = tmp; 181 } 182 } 183 184 /* Output command help to stdout. */ 185 static void cliOutputCommandHelp(struct commandHelp *help, int group) { 186 printf("\r\n \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params); 187 printf(" \x1b[33msummary:\x1b[0m %s\r\n", help->summary); 188 printf(" \x1b[33msince:\x1b[0m %s\r\n", help->since); 189 if (group) { 190 printf(" \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]); 191 } 192 } 193 194 /* Print generic help. */ 195 static void cliOutputGenericHelp() { 196 sds version = cliVersion(); 197 printf( 198 "redis-cli %s\r\n" 199 "Type: \"help @<group>\" to get a list of commands in <group>\r\n" 200 " \"help <command>\" for help on <command>\r\n" 201 " \"help <tab>\" to get a list of possible help topics\r\n" 202 " \"quit\" to exit\r\n", 203 version 204 ); 205 sdsfree(version); 206 } 207 208 /* Output all command help, filtering by group or command name. */ 209 static void cliOutputHelp(int argc, char **argv) { 210 int i, j, len; 211 int group = -1; 212 helpEntry *entry; 213 struct commandHelp *help; 214 215 if (argc == 0) { 216 cliOutputGenericHelp(); 217 return; 218 } else if (argc > 0 && argv[0][0] == '@') { 219 len = sizeof(commandGroups)/sizeof(char*); 220 for (i = 0; i < len; i++) { 221 if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) { 222 group = i; 223 break; 224 } 225 } 226 } 227 228 assert(argc > 0); 229 for (i = 0; i < helpEntriesLen; i++) { 230 entry = &helpEntries[i]; 231 if (entry->type != CLI_HELP_COMMAND) continue; 232 233 help = entry->org; 234 if (group == -1) { 235 /* Compare all arguments */ 236 if (argc == entry->argc) { 237 for (j = 0; j < argc; j++) { 238 if (strcasecmp(argv[j],entry->argv[j]) != 0) break; 239 } 240 if (j == argc) { 241 cliOutputCommandHelp(help,1); 242 } 243 } 244 } else { 245 if (group == help->group) { 246 cliOutputCommandHelp(help,0); 247 } 248 } 249 } 250 printf("\r\n"); 251 } 252 253 static void completionCallback(const char *buf, linenoiseCompletions *lc) { 254 size_t startpos = 0; 255 int mask; 256 int i; 257 size_t matchlen; 258 sds tmp; 259 260 if (strncasecmp(buf,"help ",5) == 0) { 261 startpos = 5; 262 while (isspace(buf[startpos])) startpos++; 263 mask = CLI_HELP_COMMAND | CLI_HELP_GROUP; 264 } else { 265 mask = CLI_HELP_COMMAND; 266 } 267 268 for (i = 0; i < helpEntriesLen; i++) { 269 if (!(helpEntries[i].type & mask)) continue; 270 271 matchlen = strlen(buf+startpos); 272 if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) { 273 tmp = sdsnewlen(buf,startpos); 274 tmp = sdscat(tmp,helpEntries[i].full); 275 linenoiseAddCompletion(lc,tmp); 276 sdsfree(tmp); 277 } 278 } 279 } 280 281 /*------------------------------------------------------------------------------ 282 * Networking / parsing 283 *--------------------------------------------------------------------------- */ 284 285 /* Send AUTH command to the server */ 286 static int cliAuth() { 287 redisReply *reply; 288 if (config.auth == NULL) return REDIS_OK; 289 290 reply = redisCommand(context,"AUTH %s",config.auth); 291 if (reply != NULL) { 292 freeReplyObject(reply); 293 return REDIS_OK; 294 } 295 return REDIS_ERR; 296 } 297 298 /* Send SELECT dbnum to the server */ 299 static int cliSelect() { 300 redisReply *reply; 301 if (config.dbnum == 0) return REDIS_OK; 302 303 reply = redisCommand(context,"SELECT %d",config.dbnum); 304 if (reply != NULL) { 305 freeReplyObject(reply); 306 return REDIS_OK; 307 } 308 return REDIS_ERR; 309 } 310 311 /* Connect to the server. If force is not zero the connection is performed 312 * even if there is already a connected socket. */ 313 static int cliConnect(int force) { 314 if (context == NULL || force) { 315 if (context != NULL) 316 redisFree(context); 317 318 if (config.hostsocket == NULL) { 319 context = redisConnect(config.hostip,config.hostport); 320 } else { 321 context = redisConnectUnix(config.hostsocket); 322 } 323 324 if (context->err) { 325 fprintf(stderr,"Could not connect to Redis at "); 326 if (config.hostsocket == NULL) 327 fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr); 328 else 329 fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr); 330 redisFree(context); 331 context = NULL; 332 return REDIS_ERR; 333 } 334 335 /* Do AUTH and select the right DB. */ 336 if (cliAuth() != REDIS_OK) 337 return REDIS_ERR; 338 if (cliSelect() != REDIS_OK) 339 return REDIS_ERR; 340 } 341 return REDIS_OK; 342 } 343 344 static void cliPrintContextError() { 345 if (context == NULL) return; 346 fprintf(stderr,"Error: %s\n",context->errstr); 347 } 348 349 static sds cliFormatReplyTTY(redisReply *r, char *prefix) { 350 sds out = sdsempty(); 351 switch (r->type) { 352 case REDIS_REPLY_ERROR: 353 out = sdscatprintf(out,"(error) %s\n", r->str); 354 break; 355 case REDIS_REPLY_STATUS: 356 out = sdscat(out,r->str); 357 out = sdscat(out,"\n"); 358 break; 359 case REDIS_REPLY_INTEGER: 360 out = sdscatprintf(out,"(integer) %lld\n",r->integer); 361 break; 362 case REDIS_REPLY_STRING: 363 /* If you are producing output for the standard output we want 364 * a more interesting output with quoted characters and so forth */ 365 out = sdscatrepr(out,r->str,r->len); 366 out = sdscat(out,"\n"); 367 break; 368 case REDIS_REPLY_NIL: 369 out = sdscat(out,"(nil)\n"); 370 break; 371 case REDIS_REPLY_ARRAY: 372 if (r->elements == 0) { 373 out = sdscat(out,"(empty list or set)\n"); 374 } else { 375 unsigned int i, idxlen = 0; 376 char _prefixlen[16]; 377 char _prefixfmt[16]; 378 sds _prefix; 379 sds tmp; 380 381 /* Calculate chars needed to represent the largest index */ 382 i = r->elements; 383 do { 384 idxlen++; 385 i /= 10; 386 } while(i); 387 388 /* Prefix for nested multi bulks should grow with idxlen+2 spaces */ 389 memset(_prefixlen,' ',idxlen+2); 390 _prefixlen[idxlen+2] = '\0'; 391 _prefix = sdscat(sdsnew(prefix),_prefixlen); 392 393 /* Setup prefix format for every entry */ 394 snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen); 395 396 for (i = 0; i < r->elements; i++) { 397 /* Don't use the prefix for the first element, as the parent 398 * caller already prepended the index number. */ 399 out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1); 400 401 /* Format the multi bulk entry */ 402 tmp = cliFormatReplyTTY(r->element[i],_prefix); 403 out = sdscatlen(out,tmp,sdslen(tmp)); 404 sdsfree(tmp); 405 } 406 sdsfree(_prefix); 407 } 408 break; 409 default: 410 fprintf(stderr,"Unknown reply type: %d\n", r->type); 411 exit(1); 412 } 413 return out; 414 } 415 416 static sds cliFormatReplyRaw(redisReply *r) { 417 sds out = sdsempty(), tmp; 418 size_t i; 419 420 switch (r->type) { 421 case REDIS_REPLY_NIL: 422 /* Nothing... */ 423 break; 424 case REDIS_REPLY_ERROR: 425 out = sdscatlen(out,r->str,r->len); 426 out = sdscatlen(out,"\n",1); 427 break; 428 case REDIS_REPLY_STATUS: 429 case REDIS_REPLY_STRING: 430 out = sdscatlen(out,r->str,r->len); 431 break; 432 case REDIS_REPLY_INTEGER: 433 out = sdscatprintf(out,"%lld",r->integer); 434 break; 435 case REDIS_REPLY_ARRAY: 436 for (i = 0; i < r->elements; i++) { 437 if (i > 0) out = sdscat(out,config.mb_delim); 438 tmp = cliFormatReplyRaw(r->element[i]); 439 out = sdscatlen(out,tmp,sdslen(tmp)); 440 sdsfree(tmp); 441 } 442 break; 443 default: 444 fprintf(stderr,"Unknown reply type: %d\n", r->type); 445 exit(1); 446 } 447 return out; 448 } 449 450 static sds cliFormatReplyCSV(redisReply *r) { 451 unsigned int i; 452 453 sds out = sdsempty(); 454 switch (r->type) { 455 case REDIS_REPLY_ERROR: 456 out = sdscat(out,"ERROR,"); 457 out = sdscatrepr(out,r->str,strlen(r->str)); 458 break; 459 case REDIS_REPLY_STATUS: 460 out = sdscatrepr(out,r->str,r->len); 461 break; 462 case REDIS_REPLY_INTEGER: 463 out = sdscatprintf(out,"%lld",r->integer); 464 break; 465 case REDIS_REPLY_STRING: 466 out = sdscatrepr(out,r->str,r->len); 467 break; 468 case REDIS_REPLY_NIL: 469 out = sdscat(out,"NIL\n"); 470 break; 471 case REDIS_REPLY_ARRAY: 472 for (i = 0; i < r->elements; i++) { 473 sds tmp = cliFormatReplyCSV(r->element[i]); 474 out = sdscatlen(out,tmp,sdslen(tmp)); 475 if (i != r->elements-1) out = sdscat(out,","); 476 sdsfree(tmp); 477 } 478 break; 479 default: 480 fprintf(stderr,"Unknown reply type: %d\n", r->type); 481 exit(1); 482 } 483 return out; 484 } 485 486 static int cliReadReply(int output_raw_strings) { 487 void *_reply; 488 redisReply *reply; 489 sds out = NULL; 490 int output = 1; 491 492 if (redisGetReply(context,&_reply) != REDIS_OK) { 493 if (config.shutdown) 494 return REDIS_OK; 495 if (config.interactive) { 496 /* Filter cases where we should reconnect */ 497 if (context->err == REDIS_ERR_IO && errno == ECONNRESET) 498 return REDIS_ERR; 499 if (context->err == REDIS_ERR_EOF) 500 return REDIS_ERR; 501 } 502 cliPrintContextError(); 503 exit(1); 504 return REDIS_ERR; /* avoid compiler warning */ 505 } 506 507 reply = (redisReply*)_reply; 508 509 /* Check if we need to connect to a different node and reissue the 510 * request. */ 511 if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR && 512 (!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK"))) 513 { 514 char *p = reply->str, *s; 515 int slot; 516 517 output = 0; 518 /* Comments show the position of the pointer as: 519 * 520 * [S] for pointer 's' 521 * [P] for pointer 'p' 522 */ 523 s = strchr(p,' '); /* MOVED[S]3999 127.0.0.1:6381 */ 524 p = strchr(s+1,' '); /* MOVED[S]3999[P]127.0.0.1:6381 */ 525 *p = '\0'; 526 slot = atoi(s+1); 527 s = strchr(p+1,':'); /* MOVED 3999[P]127.0.0.1[S]6381 */ 528 *s = '\0'; 529 sdsfree(config.hostip); 530 config.hostip = sdsnew(p+1); 531 config.hostport = atoi(s+1); 532 if (config.interactive) 533 printf("-> Redirected to slot [%d] located at %s:%d\n", 534 slot, config.hostip, config.hostport); 535 config.cluster_reissue_command = 1; 536 } 537 538 if (output) { 539 if (output_raw_strings) { 540 out = cliFormatReplyRaw(reply); 541 } else { 542 if (config.output == OUTPUT_RAW) { 543 out = cliFormatReplyRaw(reply); 544 out = sdscat(out,"\n"); 545 } else if (config.output == OUTPUT_STANDARD) { 546 out = cliFormatReplyTTY(reply,""); 547 } else if (config.output == OUTPUT_CSV) { 548 out = cliFormatReplyCSV(reply); 549 out = sdscat(out,"\n"); 550 } 551 } 552 fwrite(out,sdslen(out),1,stdout); 553 sdsfree(out); 554 } 555 freeReplyObject(reply); 556 return REDIS_OK; 557 } 558 559 static int cliSendCommand(int argc, char **argv, int repeat) { 560 char *command = argv[0]; 561 size_t *argvlen; 562 int j, output_raw; 563 564 if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) { 565 cliOutputHelp(--argc, ++argv); 566 return REDIS_OK; 567 } 568 569 if (context == NULL) return REDIS_ERR; 570 571 output_raw = 0; 572 if (!strcasecmp(command,"info") || 573 (argc == 2 && !strcasecmp(command,"cluster") && 574 (!strcasecmp(argv[1],"nodes") || 575 !strcasecmp(argv[1],"info"))) || 576 (argc == 2 && !strcasecmp(command,"client") && 577 !strcasecmp(argv[1],"list"))) 578 579 { 580 output_raw = 1; 581 } 582 583 if (!strcasecmp(command,"shutdown")) config.shutdown = 1; 584 if (!strcasecmp(command,"monitor")) config.monitor_mode = 1; 585 if (!strcasecmp(command,"subscribe") || 586 !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1; 587 588 /* Setup argument length */ 589 argvlen = malloc(argc*sizeof(size_t)); 590 for (j = 0; j < argc; j++) 591 argvlen[j] = sdslen(argv[j]); 592 593 while(repeat--) { 594 redisAppendCommandArgv(context,argc,(const char**)argv,argvlen); 595 while (config.monitor_mode) { 596 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 597 fflush(stdout); 598 } 599 600 if (config.pubsub_mode) { 601 if (config.output != OUTPUT_RAW) 602 printf("Reading messages... (press Ctrl-C to quit)\n"); 603 while (1) { 604 if (cliReadReply(output_raw) != REDIS_OK) exit(1); 605 } 606 } 607 608 if (cliReadReply(output_raw) != REDIS_OK) { 609 free(argvlen); 610 return REDIS_ERR; 611 } else { 612 /* Store database number when SELECT was successfully executed. */ 613 if (!strcasecmp(command,"select") && argc == 2) { 614 config.dbnum = atoi(argv[1]); 615 cliRefreshPrompt(); 616 } 617 } 618 if (config.interval) usleep(config.interval); 619 fflush(stdout); /* Make it grep friendly */ 620 } 621 622 free(argvlen); 623 return REDIS_OK; 624 } 625 626 /*------------------------------------------------------------------------------ 627 * User interface 628 *--------------------------------------------------------------------------- */ 629 630 static int parseOptions(int argc, char **argv) { 631 int i; 632 633 for (i = 1; i < argc; i++) { 634 int lastarg = i==argc-1; 635 636 if (!strcmp(argv[i],"-h") && !lastarg) { 637 sdsfree(config.hostip); 638 config.hostip = sdsnew(argv[++i]); 639 } else if (!strcmp(argv[i],"-h") && lastarg) { 640 usage(); 641 } else if (!strcmp(argv[i],"--help")) { 642 usage(); 643 } else if (!strcmp(argv[i],"-x")) { 644 config.stdinarg = 1; 645 } else if (!strcmp(argv[i],"-p") && !lastarg) { 646 config.hostport = atoi(argv[++i]); 647 } else if (!strcmp(argv[i],"-s") && !lastarg) { 648 config.hostsocket = argv[++i]; 649 } else if (!strcmp(argv[i],"-r") && !lastarg) { 650 config.repeat = strtoll(argv[++i],NULL,10); 651 } else if (!strcmp(argv[i],"-i") && !lastarg) { 652 double seconds = atof(argv[++i]); 653 config.interval = seconds*1000000; 654 } else if (!strcmp(argv[i],"-n") && !lastarg) { 655 config.dbnum = atoi(argv[++i]); 656 } else if (!strcmp(argv[i],"-a") && !lastarg) { 657 config.auth = argv[++i]; 658 } else if (!strcmp(argv[i],"--raw")) { 659 config.output = OUTPUT_RAW; 660 } else if (!strcmp(argv[i],"--csv")) { 661 config.output = OUTPUT_CSV; 662 } else if (!strcmp(argv[i],"--latency")) { 663 config.latency_mode = 1; 664 } else if (!strcmp(argv[i],"--slave")) { 665 config.slave_mode = 1; 666 } else if (!strcmp(argv[i],"--rdb") && !lastarg) { 667 config.getrdb_mode = 1; 668 config.rdb_filename = argv[++i]; 669 } else if (!strcmp(argv[i],"--pipe")) { 670 config.pipe_mode = 1; 671 } else if (!strcmp(argv[i],"--bigkeys")) { 672 config.bigkeys = 1; 673 } else if (!strcmp(argv[i],"--eval") && !lastarg) { 674 config.eval = argv[++i]; 675 } else if (!strcmp(argv[i],"-c")) { 676 config.cluster_mode = 1; 677 } else if (!strcmp(argv[i],"-d") && !lastarg) { 678 sdsfree(config.mb_delim); 679 config.mb_delim = sdsnew(argv[++i]); 680 } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) { 681 sds version = cliVersion(); 682 printf("redis-cli %s\n", version); 683 sdsfree(version); 684 exit(0); 685 } else { 686 break; 687 } 688 } 689 return i; 690 } 691 692 static sds readArgFromStdin(void) { 693 char buf[1024]; 694 sds arg = sdsempty(); 695 696 while(1) { 697 int nread = read(fileno(stdin),buf,1024); 698 699 if (nread == 0) break; 700 else if (nread == -1) { 701 perror("Reading from standard input"); 702 exit(1); 703 } 704 arg = sdscatlen(arg,buf,nread); 705 } 706 return arg; 707 } 708 709 static void usage() { 710 sds version = cliVersion(); 711 fprintf(stderr, 712 "redis-cli %s\n" 713 "\n" 714 "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n" 715 " -h <hostname> Server hostname (default: 127.0.0.1)\n" 716 " -p <port> Server port (default: 6379)\n" 717 " -s <socket> Server socket (overrides hostname and port)\n" 718 " -a <password> Password to use when connecting to the server\n" 719 " -r <repeat> Execute specified command N times\n" 720 " -i <interval> When -r is used, waits <interval> seconds per command.\n" 721 " It is possible to specify sub-second times like -i 0.1\n" 722 " -n <db> Database number\n" 723 " -x Read last argument from STDIN\n" 724 " -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n)\n" 725 " -c Enable cluster mode (follow -ASK and -MOVED redirections)\n" 726 " --raw Use raw formatting for replies (default when STDOUT is not a tty)\n" 727 " --latency Enter a special mode continuously sampling latency\n" 728 " --slave Simulate a slave showing commands received from the master\n" 729 " --rdb <filename> Transfer an RDB dump from remote server to local file.\n" 730 " --pipe Transfer raw Redis protocol from stdin to server\n" 731 " --bigkeys Sample Redis keys looking for big keys\n" 732 " --eval <file> Send an EVAL command using the Lua script at <file>\n" 733 " --help Output this help and exit\n" 734 " --version Output version and exit\n" 735 "\n" 736 "Examples:\n" 737 " cat /etc/passwd | redis-cli -x set mypasswd\n" 738 " redis-cli get mypasswd\n" 739 " redis-cli -r 100 lpush mylist x\n" 740 " redis-cli -r 100 -i 1 info | grep used_memory_human:\n" 741 " redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n" 742 " (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n" 743 "\n" 744 "When no command is given, redis-cli starts in interactive mode.\n" 745 "Type \"help\" in interactive mode for information on available commands.\n" 746 "\n", 747 version); 748 sdsfree(version); 749 exit(1); 750 } 751 752 /* Turn the plain C strings into Sds strings */ 753 static char **convertToSds(int count, char** args) { 754 int j; 755 char **sds = zmalloc(sizeof(char*)*count); 756 757 for(j = 0; j < count; j++) 758 sds[j] = sdsnew(args[j]); 759 760 return sds; 761 } 762 763 #define LINE_BUFLEN 4096 764 static void repl() { 765 sds historyfile = NULL; 766 int history = 0; 767 char *line; 768 int argc; 769 sds *argv; 770 771 config.interactive = 1; 772 linenoiseSetCompletionCallback(completionCallback); 773 774 /* Only use history when stdin is a tty. */ 775 if (isatty(fileno(stdin))) { 776 history = 1; 777 778 if (getenv("HOME") != NULL) { 779 historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME")); 780 linenoiseHistoryLoad(historyfile); 781 } 782 } 783 784 cliRefreshPrompt(); 785 while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) { 786 if (line[0] != '\0') { 787 argv = sdssplitargs(line,&argc); 788 if (history) linenoiseHistoryAdd(line); 789 if (historyfile) linenoiseHistorySave(historyfile); 790 791 if (argv == NULL) { 792 printf("Invalid argument(s)\n"); 793 free(line); 794 continue; 795 } else if (argc > 0) { 796 if (strcasecmp(argv[0],"quit") == 0 || 797 strcasecmp(argv[0],"exit") == 0) 798 { 799 exit(0); 800 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) { 801 sdsfree(config.hostip); 802 config.hostip = sdsnew(argv[1]); 803 config.hostport = atoi(argv[2]); 804 cliRefreshPrompt(); 805 cliConnect(1); 806 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) { 807 linenoiseClearScreen(); 808 } else { 809 long long start_time = mstime(), elapsed; 810 int repeat, skipargs = 0; 811 812 repeat = atoi(argv[0]); 813 if (argc > 1 && repeat) { 814 skipargs = 1; 815 } else { 816 repeat = 1; 817 } 818 819 while (1) { 820 config.cluster_reissue_command = 0; 821 if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) 822 != REDIS_OK) 823 { 824 cliConnect(1); 825 826 /* If we still cannot send the command print error. 827 * We'll try to reconnect the next time. */ 828 if (cliSendCommand(argc-skipargs,argv+skipargs,repeat) 829 != REDIS_OK) 830 cliPrintContextError(); 831 } 832 /* Issue the command again if we got redirected in cluster mode */ 833 if (config.cluster_mode && config.cluster_reissue_command) { 834 cliConnect(1); 835 } else { 836 break; 837 } 838 } 839 elapsed = mstime()-start_time; 840 if (elapsed >= 500) { 841 printf("(%.2fs)\n",(double)elapsed/1000); 842 } 843 } 844 } 845 /* Free the argument vector */ 846 while(argc--) sdsfree(argv[argc]); 847 zfree(argv); 848 } 849 /* linenoise() returns malloc-ed lines like readline() */ 850 free(line); 851 } 852 exit(0); 853 } 854 855 static int noninteractive(int argc, char **argv) { 856 int retval = 0; 857 if (config.stdinarg) { 858 argv = zrealloc(argv, (argc+1)*sizeof(char*)); 859 argv[argc] = readArgFromStdin(); 860 retval = cliSendCommand(argc+1, argv, config.repeat); 861 } else { 862 /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */ 863 retval = cliSendCommand(argc, argv, config.repeat); 864 } 865 return retval; 866 } 867 868 static int evalMode(int argc, char **argv) { 869 sds script = sdsempty(); 870 FILE *fp; 871 char buf[1024]; 872 size_t nread; 873 char **argv2; 874 int j, got_comma = 0, keys = 0; 875 876 /* Load the script from the file, as an sds string. */ 877 fp = fopen(config.eval,"r"); 878 if (!fp) { 879 fprintf(stderr, 880 "Can't open file '%s': %s\n", config.eval, strerror(errno)); 881 exit(1); 882 } 883 while((nread = fread(buf,1,sizeof(buf),fp)) != 0) { 884 script = sdscatlen(script,buf,nread); 885 } 886 fclose(fp); 887 888 /* Create our argument vector */ 889 argv2 = zmalloc(sizeof(sds)*(argc+3)); 890 argv2[0] = sdsnew("EVAL"); 891 argv2[1] = script; 892 for (j = 0; j < argc; j++) { 893 if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) { 894 got_comma = 1; 895 continue; 896 } 897 argv2[j+3-got_comma] = sdsnew(argv[j]); 898 if (!got_comma) keys++; 899 } 900 argv2[2] = sdscatprintf(sdsempty(),"%d",keys); 901 902 /* Call it */ 903 return cliSendCommand(argc+3-got_comma, argv2, config.repeat); 904 } 905 906 static void latencyMode(void) { 907 redisReply *reply; 908 long long start, latency, min = 0, max = 0, tot = 0, count = 0; 909 double avg; 910 911 if (!context) exit(1); 912 while(1) { 913 start = mstime(); 914 reply = redisCommand(context,"PING"); 915 if (reply == NULL) { 916 fprintf(stderr,"\nI/O error\n"); 917 exit(1); 918 } 919 latency = mstime()-start; 920 freeReplyObject(reply); 921 count++; 922 if (count == 1) { 923 min = max = tot = latency; 924 avg = (double) latency; 925 } else { 926 if (latency < min) min = latency; 927 if (latency > max) max = latency; 928 tot += latency; 929 avg = (double) tot/count; 930 } 931 printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)", 932 min, max, avg, count); 933 fflush(stdout); 934 usleep(10000); 935 } 936 } 937 938 /* Sends SYNC and reads the number of bytes in the payload. Used both by 939 * slaveMode() and getRDB(). */ 940 unsigned long long sendSync(int fd) { 941 /* To start we need to send the SYNC command and return the payload. 942 * The hiredis client lib does not understand this part of the protocol 943 * and we don't want to mess with its buffers, so everything is performed 944 * using direct low-level I/O. */ 945 char buf[4096], *p; 946 ssize_t nread; 947 948 /* Send the SYNC command. */ 949 if (write(fd,"SYNC\r\n",6) != 6) { 950 fprintf(stderr,"Error writing to master\n"); 951 exit(1); 952 } 953 954 /* Read $<payload>\r\n, making sure to read just up to "\n" */ 955 p = buf; 956 while(1) { 957 nread = read(fd,p,1); 958 if (nread <= 0) { 959 fprintf(stderr,"Error reading bulk length while SYNCing\n"); 960 exit(1); 961 } 962 if (*p == '\n' && p != buf) break; 963 if (*p != '\n') p++; 964 } 965 *p = '\0'; 966 if (buf[0] == '-') { 967 printf("SYNC with master failed: %s\n", buf); 968 exit(1); 969 } 970 return strtoull(buf+1,NULL,10); 971 } 972 973 static void slaveMode(void) { 974 int fd = context->fd; 975 unsigned long long payload = sendSync(fd); 976 char buf[1024]; 977 978 fprintf(stderr,"SYNC with master, discarding %llu " 979 "bytes of bulk transfer...\n", payload); 980 981 /* Discard the payload. */ 982 while(payload) { 983 ssize_t nread; 984 985 nread = read(fd,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); 986 if (nread <= 0) { 987 fprintf(stderr,"Error reading RDB payload while SYNCing\n"); 988 exit(1); 989 } 990 payload -= nread; 991 } 992 fprintf(stderr,"SYNC done. Logging commands from master.\n"); 993 994 /* Now we can use hiredis to read the incoming protocol. */ 995 config.output = OUTPUT_CSV; 996 while (cliReadReply(0) == REDIS_OK); 997 } 998 999 /* This function implements --rdb, so it uses the replication protocol in order 1000 * to fetch the RDB file from a remote server. */ 1001 static void getRDB(void) { 1002 int s = context->fd; 1003 int fd; 1004 unsigned long long payload = sendSync(s); 1005 char buf[4096]; 1006 1007 fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n", 1008 payload, config.rdb_filename); 1009 1010 /* Write to file. */ 1011 if (!strcmp(config.rdb_filename,"-")) { 1012 fd = STDOUT_FILENO; 1013 } else { 1014 fd = open(config.rdb_filename, O_CREAT|O_WRONLY, 0644); 1015 if (fd == -1) { 1016 fprintf(stderr, "Error opening '%s': %s\n", config.rdb_filename, 1017 strerror(errno)); 1018 exit(1); 1019 } 1020 } 1021 1022 while(payload) { 1023 ssize_t nread, nwritten; 1024 1025 nread = read(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload); 1026 if (nread <= 0) { 1027 fprintf(stderr,"I/O Error reading RDB payload from socket\n"); 1028 exit(1); 1029 } 1030 nwritten = write(fd, buf, nread); 1031 if (nwritten != nread) { 1032 fprintf(stderr,"Error writing data to file: %s\n", 1033 strerror(errno)); 1034 exit(1); 1035 } 1036 payload -= nread; 1037 } 1038 close(s); /* Close the file descriptor ASAP as fsync() may take time. */ 1039 fsync(fd); 1040 fprintf(stderr,"Transfer finished with success.\n"); 1041 exit(0); 1042 } 1043 1044 static void pipeMode(void) { 1045 int fd = context->fd; 1046 long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0; 1047 char ibuf[1024*16], obuf[1024*16]; /* Input and output buffers */ 1048 char aneterr[ANET_ERR_LEN]; 1049 redisReader *reader = redisReaderCreate(); 1050 redisReply *reply; 1051 int eof = 0; /* True once we consumed all the standard input. */ 1052 int done = 0; 1053 char magic[20]; /* Special reply we recognize. */ 1054 1055 srand(time(NULL)); 1056 1057 /* Use non blocking I/O. */ 1058 if (anetNonBlock(aneterr,fd) == ANET_ERR) { 1059 fprintf(stderr, "Can't set the socket in non blocking mode: %s\n", 1060 aneterr); 1061 exit(1); 1062 } 1063 1064 /* Transfer raw protocol and read replies from the server at the same 1065 * time. */ 1066 while(!done) { 1067 int mask = AE_READABLE; 1068 1069 if (!eof || obuf_len != 0) mask |= AE_WRITABLE; 1070 mask = aeWait(fd,mask,1000); 1071 1072 /* Handle the readable state: we can read replies from the server. */ 1073 if (mask & AE_READABLE) { 1074 ssize_t nread; 1075 1076 /* Read from socket and feed the hiredis reader. */ 1077 do { 1078 nread = read(fd,ibuf,sizeof(ibuf)); 1079 if (nread == -1 && errno != EAGAIN && errno != EINTR) { 1080 fprintf(stderr, "Error reading from the server: %s\n", 1081 strerror(errno)); 1082 exit(1); 1083 } 1084 if (nread > 0) redisReaderFeed(reader,ibuf,nread); 1085 } while(nread > 0); 1086 1087 /* Consume replies. */ 1088 do { 1089 if (redisReaderGetReply(reader,(void**)&reply) == REDIS_ERR) { 1090 fprintf(stderr, "Error reading replies from server\n"); 1091 exit(1); 1092 } 1093 if (reply) { 1094 if (reply->type == REDIS_REPLY_ERROR) { 1095 fprintf(stderr,"%s\n", reply->str); 1096 errors++; 1097 } else if (eof && reply->type == REDIS_REPLY_STRING && 1098 reply->len == 20) { 1099 /* Check if this is the reply to our final ECHO 1100 * command. If so everything was received 1101 * from the server. */ 1102 if (memcmp(reply->str,magic,20) == 0) { 1103 printf("Last reply received from server.\n"); 1104 done = 1; 1105 replies--; 1106 } 1107 } 1108 replies++; 1109 freeReplyObject(reply); 1110 } 1111 } while(reply); 1112 } 1113 1114 /* Handle the writable state: we can send protocol to the server. */ 1115 if (mask & AE_WRITABLE) { 1116 while(1) { 1117 /* Transfer current buffer to server. */ 1118 if (obuf_len != 0) { 1119 ssize_t nwritten = write(fd,obuf+obuf_pos,obuf_len); 1120 1121 if (nwritten == -1) { 1122 if (errno != EAGAIN && errno != EINTR) { 1123 fprintf(stderr, "Error writing to the server: %s\n", 1124 strerror(errno)); 1125 exit(1); 1126 } else { 1127 nwritten = 0; 1128 } 1129 } 1130 obuf_len -= nwritten; 1131 obuf_pos += nwritten; 1132 if (obuf_len != 0) break; /* Can't accept more data. */ 1133 } 1134 /* If buffer is empty, load from stdin. */ 1135 if (obuf_len == 0 && !eof) { 1136 ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf)); 1137 1138 if (nread == 0) { 1139 char echo[] = 1140 "*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n"; 1141 int j; 1142 1143 eof = 1; 1144 /* Everything transferred, so we queue a special 1145 * ECHO command that we can match in the replies 1146 * to make sure everything was read from the server. */ 1147 for (j = 0; j < 20; j++) 1148 magic[j] = rand() & 0xff; 1149 memcpy(echo+19,magic,20); 1150 memcpy(obuf,echo,sizeof(echo)-1); 1151 obuf_len = sizeof(echo)-1; 1152 obuf_pos = 0; 1153 printf("All data transferred. Waiting for the last reply...\n"); 1154 } else if (nread == -1) { 1155 fprintf(stderr, "Error reading from stdin: %s\n", 1156 strerror(errno)); 1157 exit(1); 1158 } else { 1159 obuf_len = nread; 1160 obuf_pos = 0; 1161 } 1162 } 1163 if (obuf_len == 0 && eof) break; 1164 } 1165 } 1166 } 1167 redisReaderFree(reader); 1168 printf("errors: %lld, replies: %lld\n", errors, replies); 1169 if (errors) 1170 exit(1); 1171 else 1172 exit(0); 1173 } 1174 1175 #define TYPE_STRING 0 1176 #define TYPE_LIST 1 1177 #define TYPE_SET 2 1178 #define TYPE_HASH 3 1179 #define TYPE_ZSET 4 1180 1181 static void findBigKeys(void) { 1182 unsigned long long biggest[5] = {0,0,0,0,0}; 1183 unsigned long long samples = 0; 1184 redisReply *reply1, *reply2, *reply3 = NULL; 1185 char *sizecmd, *typename[] = {"string","list","set","hash","zset"}; 1186 char *typeunit[] = {"bytes","items","members","fields","members"}; 1187 int type; 1188 1189 printf("\n# Press ctrl+c when you have had enough of it... :)\n"); 1190 printf("# You can use -i 0.1 to sleep 0.1 sec every 100 sampled keys\n"); 1191 printf("# in order to reduce server load (usually not needed).\n\n"); 1192 while(1) { 1193 /* Sample with RANDOMKEY */ 1194 reply1 = redisCommand(context,"RANDOMKEY"); 1195 if (reply1 == NULL) { 1196 fprintf(stderr,"\nI/O error\n"); 1197 exit(1); 1198 } else if (reply1->type == REDIS_REPLY_ERROR) { 1199 fprintf(stderr, "RANDOMKEY error: %s\n", 1200 reply1->str); 1201 exit(1); 1202 } 1203 /* Get the key type */ 1204 reply2 = redisCommand(context,"TYPE %s",reply1->str); 1205 assert(reply2 && reply2->type == REDIS_REPLY_STATUS); 1206 samples++; 1207 1208 /* Get the key "size" */ 1209 if (!strcmp(reply2->str,"string")) { 1210 sizecmd = "STRLEN"; 1211 type = TYPE_STRING; 1212 } else if (!strcmp(reply2->str,"list")) { 1213 sizecmd = "LLEN"; 1214 type = TYPE_LIST; 1215 } else if (!strcmp(reply2->str,"set")) { 1216 sizecmd = "SCARD"; 1217 type = TYPE_SET; 1218 } else if (!strcmp(reply2->str,"hash")) { 1219 sizecmd = "HLEN"; 1220 type = TYPE_HASH; 1221 } else if (!strcmp(reply2->str,"zset")) { 1222 sizecmd = "ZCARD"; 1223 type = TYPE_ZSET; 1224 } else if (!strcmp(reply2->str,"none")) { 1225 freeReplyObject(reply1); 1226 freeReplyObject(reply2); 1227 freeReplyObject(reply3); 1228 continue; 1229 } else { 1230 fprintf(stderr, "Unknown key type '%s' for key '%s'\n", 1231 reply2->str, reply1->str); 1232 exit(1); 1233 } 1234 1235 reply3 = redisCommand(context,"%s %s", sizecmd, reply1->str); 1236 if (reply3 && reply3->type == REDIS_REPLY_INTEGER) { 1237 if (biggest[type] < reply3->integer) { 1238 printf("Biggest %-6s found so far '%s' with %llu %s.\n", 1239 typename[type], reply1->str, 1240 (unsigned long long) reply3->integer, 1241 typeunit[type]); 1242 biggest[type] = reply3->integer; 1243 } 1244 } 1245 1246 if ((samples % 1000000) == 0) 1247 printf("(%llu keys sampled)\n", samples); 1248 1249 if ((samples % 100) == 0 && config.interval) 1250 usleep(config.interval); 1251 1252 freeReplyObject(reply1); 1253 freeReplyObject(reply2); 1254 if (reply3) freeReplyObject(reply3); 1255 } 1256 } 1257 1258 int main(int argc, char **argv) { 1259 int firstarg; 1260 1261 config.hostip = sdsnew("127.0.0.1"); 1262 config.hostport = 6379; 1263 config.hostsocket = NULL; 1264 config.repeat = 1; 1265 config.interval = 0; 1266 config.dbnum = 0; 1267 config.interactive = 0; 1268 config.shutdown = 0; 1269 config.monitor_mode = 0; 1270 config.pubsub_mode = 0; 1271 config.latency_mode = 0; 1272 config.cluster_mode = 0; 1273 config.slave_mode = 0; 1274 config.getrdb_mode = 0; 1275 config.rdb_filename = NULL; 1276 config.pipe_mode = 0; 1277 config.bigkeys = 0; 1278 config.stdinarg = 0; 1279 config.auth = NULL; 1280 config.eval = NULL; 1281 if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL)) 1282 config.output = OUTPUT_RAW; 1283 else 1284 config.output = OUTPUT_STANDARD; 1285 config.mb_delim = sdsnew("\n"); 1286 cliInitHelp(); 1287 1288 firstarg = parseOptions(argc,argv); 1289 argc -= firstarg; 1290 argv += firstarg; 1291 1292 /* Latency mode */ 1293 if (config.latency_mode) { 1294 if (cliConnect(0) == REDIS_ERR) exit(1); 1295 latencyMode(); 1296 } 1297 1298 /* Slave mode */ 1299 if (config.slave_mode) { 1300 if (cliConnect(0) == REDIS_ERR) exit(1); 1301 slaveMode(); 1302 } 1303 1304 /* Get RDB mode. */ 1305 if (config.getrdb_mode) { 1306 if (cliConnect(0) == REDIS_ERR) exit(1); 1307 getRDB(); 1308 } 1309 1310 /* Pipe mode */ 1311 if (config.pipe_mode) { 1312 if (cliConnect(0) == REDIS_ERR) exit(1); 1313 pipeMode(); 1314 } 1315 1316 /* Find big keys */ 1317 if (config.bigkeys) { 1318 if (cliConnect(0) == REDIS_ERR) exit(1); 1319 findBigKeys(); 1320 } 1321 1322 /* Start interactive mode when no command is provided */ 1323 if (argc == 0 && !config.eval) { 1324 /* Note that in repl mode we don't abort on connection error. 1325 * A new attempt will be performed for every command send. */ 1326 cliConnect(0); 1327 repl(); 1328 } 1329 1330 /* Otherwise, we have some arguments to execute */ 1331 if (cliConnect(0) != REDIS_OK) exit(1); 1332 if (config.eval) { 1333 return evalMode(argc,argv); 1334 } else { 1335 return noninteractive(argc,convertToSds(argc,argv)); 1336 } 1337 } 1338