xref: /redis-3.2.3/src/redis-cli.c (revision 40f822da)
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