xref: /redis-3.2.3/src/redis-cli.c (revision bbf93108)
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 <signal.h>
38 #include <unistd.h>
39 #include <time.h>
40 #include <ctype.h>
41 #include <errno.h>
42 #include <sys/stat.h>
43 #include <sys/time.h>
44 #include <assert.h>
45 #include <fcntl.h>
46 #include <limits.h>
47 #include <math.h>
48 
49 #include <hiredis.h>
50 #include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
51 #include "zmalloc.h"
52 #include "linenoise.h"
53 #include "help.h"
54 #include "anet.h"
55 #include "ae.h"
56 
57 #define UNUSED(V) ((void) V)
58 
59 #define OUTPUT_STANDARD 0
60 #define OUTPUT_RAW 1
61 #define OUTPUT_CSV 2
62 #define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
63 #define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
64 #define REDIS_CLI_HISTFILE_ENV "REDISCLI_HISTFILE"
65 #define REDIS_CLI_HISTFILE_DEFAULT ".rediscli_history"
66 #define REDIS_CLI_RCFILE_ENV "REDISCLI_RCFILE"
67 #define REDIS_CLI_RCFILE_DEFAULT ".redisclirc"
68 
69 /* --latency-dist palettes. */
70 int spectrum_palette_color_size = 19;
71 int spectrum_palette_color[] = {0,233,234,235,237,239,241,243,245,247,144,143,142,184,226,214,208,202,196};
72 
73 int spectrum_palette_mono_size = 13;
74 int spectrum_palette_mono[] = {0,233,234,235,237,239,241,243,245,247,249,251,253};
75 
76 /* The actual palette in use. */
77 int *spectrum_palette;
78 int spectrum_palette_size;
79 
80 static redisContext *context;
81 static struct config {
82     char *hostip;
83     int hostport;
84     char *hostsocket;
85     long repeat;
86     long interval;
87     int dbnum;
88     int interactive;
89     int shutdown;
90     int monitor_mode;
91     int pubsub_mode;
92     int latency_mode;
93     int latency_dist_mode;
94     int latency_history;
95     int lru_test_mode;
96     long long lru_test_sample_size;
97     int cluster_mode;
98     int cluster_reissue_command;
99     int slave_mode;
100     int pipe_mode;
101     int pipe_timeout;
102     int getrdb_mode;
103     int stat_mode;
104     int scan_mode;
105     int intrinsic_latency_mode;
106     int intrinsic_latency_duration;
107     char *pattern;
108     char *rdb_filename;
109     int bigkeys;
110     int stdinarg; /* get last arg from stdin. (-x option) */
111     char *auth;
112     int output; /* output mode, see OUTPUT_* defines */
113     sds mb_delim;
114     char prompt[128];
115     char *eval;
116     int eval_ldb;
117     int eval_ldb_sync;  /* Ask for synchronous mode of the Lua debugger. */
118     int eval_ldb_end;   /* Lua debugging session ended. */
119     int enable_ldb_on_eval; /* Handle manual SCRIPT DEBUG + EVAL commands. */
120     int last_cmd_type;
121 } config;
122 
123 /* User preferences. */
124 static struct pref {
125     int hints;
126 } pref;
127 
128 static volatile sig_atomic_t force_cancel_loop = 0;
129 static void usage(void);
130 static void slaveMode(void);
131 char *redisGitSHA1(void);
132 char *redisGitDirty(void);
133 
134 /*------------------------------------------------------------------------------
135  * Utility functions
136  *--------------------------------------------------------------------------- */
137 
138 static long long ustime(void) {
139     struct timeval tv;
140     long long ust;
141 
142     gettimeofday(&tv, NULL);
143     ust = ((long long)tv.tv_sec)*1000000;
144     ust += tv.tv_usec;
145     return ust;
146 }
147 
148 static long long mstime(void) {
149     return ustime()/1000;
150 }
151 
152 static void cliRefreshPrompt(void) {
153     int len;
154 
155     if (config.eval_ldb) return;
156     if (config.hostsocket != NULL)
157         len = snprintf(config.prompt,sizeof(config.prompt),"redis %s",
158                        config.hostsocket);
159     else
160         len = anetFormatAddr(config.prompt, sizeof(config.prompt),
161                            config.hostip, config.hostport);
162     /* Add [dbnum] if needed */
163     if (config.dbnum != 0 && config.last_cmd_type != REDIS_REPLY_ERROR)
164         len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]",
165             config.dbnum);
166     snprintf(config.prompt+len,sizeof(config.prompt)-len,"> ");
167 }
168 
169 /* Return the name of the dotfile for the specified 'dotfilename'.
170  * Normally it just concatenates user $HOME to the file specified
171  * in 'dotfilename'. However if the environment varialbe 'envoverride'
172  * is set, its value is taken as the path.
173  *
174  * The function returns NULL (if the file is /dev/null or cannot be
175  * obtained for some error), or an SDS string that must be freed by
176  * the user. */
177 static sds getDotfilePath(char *envoverride, char *dotfilename) {
178     char *path = NULL;
179     sds dotPath = NULL;
180 
181     /* Check the env for a dotfile override. */
182     path = getenv(envoverride);
183     if (path != NULL && *path != '\0') {
184         if (!strcmp("/dev/null", path)) {
185             return NULL;
186         }
187 
188         /* If the env is set, return it. */
189         dotPath = sdsnew(path);
190     } else {
191         char *home = getenv("HOME");
192         if (home != NULL && *home != '\0') {
193             /* If no override is set use $HOME/<dotfilename>. */
194             dotPath = sdscatprintf(sdsempty(), "%s/%s", home, dotfilename);
195         }
196     }
197     return dotPath;
198 }
199 
200 /*------------------------------------------------------------------------------
201  * Help functions
202  *--------------------------------------------------------------------------- */
203 
204 #define CLI_HELP_COMMAND 1
205 #define CLI_HELP_GROUP 2
206 
207 typedef struct {
208     int type;
209     int argc;
210     sds *argv;
211     sds full;
212 
213     /* Only used for help on commands */
214     struct commandHelp *org;
215 } helpEntry;
216 
217 static helpEntry *helpEntries;
218 static int helpEntriesLen;
219 
220 static sds cliVersion(void) {
221     sds version;
222     version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);
223 
224     /* Add git commit and working tree status when available */
225     if (strtoll(redisGitSHA1(),NULL,16)) {
226         version = sdscatprintf(version, " (git:%s", redisGitSHA1());
227         if (strtoll(redisGitDirty(),NULL,10))
228             version = sdscatprintf(version, "-dirty");
229         version = sdscat(version, ")");
230     }
231     return version;
232 }
233 
234 static void cliInitHelp(void) {
235     int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
236     int groupslen = sizeof(commandGroups)/sizeof(char*);
237     int i, len, pos = 0;
238     helpEntry tmp;
239 
240     helpEntriesLen = len = commandslen+groupslen;
241     helpEntries = malloc(sizeof(helpEntry)*len);
242 
243     for (i = 0; i < groupslen; i++) {
244         tmp.argc = 1;
245         tmp.argv = malloc(sizeof(sds));
246         tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]);
247         tmp.full = tmp.argv[0];
248         tmp.type = CLI_HELP_GROUP;
249         tmp.org = NULL;
250         helpEntries[pos++] = tmp;
251     }
252 
253     for (i = 0; i < commandslen; i++) {
254         tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc);
255         tmp.full = sdsnew(commandHelp[i].name);
256         tmp.type = CLI_HELP_COMMAND;
257         tmp.org = &commandHelp[i];
258         helpEntries[pos++] = tmp;
259     }
260 }
261 
262 /* Output command help to stdout. */
263 static void cliOutputCommandHelp(struct commandHelp *help, int group) {
264     printf("\r\n  \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params);
265     printf("  \x1b[33msummary:\x1b[0m %s\r\n", help->summary);
266     printf("  \x1b[33msince:\x1b[0m %s\r\n", help->since);
267     if (group) {
268         printf("  \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]);
269     }
270 }
271 
272 /* Print generic help. */
273 static void cliOutputGenericHelp(void) {
274     sds version = cliVersion();
275     printf(
276         "redis-cli %s\n"
277         "To get help about Redis commands type:\n"
278         "      \"help @<group>\" to get a list of commands in <group>\n"
279         "      \"help <command>\" for help on <command>\n"
280         "      \"help <tab>\" to get a list of possible help topics\n"
281         "      \"quit\" to exit\n"
282         "\n"
283         "To set redis-cli perferences:\n"
284         "      \":set hints\" enable online hints\n"
285         "      \":set nohints\" disable online hints\n"
286         "Set your preferences in ~/.redisclirc\n",
287         version
288     );
289     sdsfree(version);
290 }
291 
292 /* Output all command help, filtering by group or command name. */
293 static void cliOutputHelp(int argc, char **argv) {
294     int i, j, len;
295     int group = -1;
296     helpEntry *entry;
297     struct commandHelp *help;
298 
299     if (argc == 0) {
300         cliOutputGenericHelp();
301         return;
302     } else if (argc > 0 && argv[0][0] == '@') {
303         len = sizeof(commandGroups)/sizeof(char*);
304         for (i = 0; i < len; i++) {
305             if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) {
306                 group = i;
307                 break;
308             }
309         }
310     }
311 
312     assert(argc > 0);
313     for (i = 0; i < helpEntriesLen; i++) {
314         entry = &helpEntries[i];
315         if (entry->type != CLI_HELP_COMMAND) continue;
316 
317         help = entry->org;
318         if (group == -1) {
319             /* Compare all arguments */
320             if (argc == entry->argc) {
321                 for (j = 0; j < argc; j++) {
322                     if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
323                 }
324                 if (j == argc) {
325                     cliOutputCommandHelp(help,1);
326                 }
327             }
328         } else {
329             if (group == help->group) {
330                 cliOutputCommandHelp(help,0);
331             }
332         }
333     }
334     printf("\r\n");
335 }
336 
337 /* Linenoise completion callback. */
338 static void completionCallback(const char *buf, linenoiseCompletions *lc) {
339     size_t startpos = 0;
340     int mask;
341     int i;
342     size_t matchlen;
343     sds tmp;
344 
345     if (strncasecmp(buf,"help ",5) == 0) {
346         startpos = 5;
347         while (isspace(buf[startpos])) startpos++;
348         mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
349     } else {
350         mask = CLI_HELP_COMMAND;
351     }
352 
353     for (i = 0; i < helpEntriesLen; i++) {
354         if (!(helpEntries[i].type & mask)) continue;
355 
356         matchlen = strlen(buf+startpos);
357         if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) {
358             tmp = sdsnewlen(buf,startpos);
359             tmp = sdscat(tmp,helpEntries[i].full);
360             linenoiseAddCompletion(lc,tmp);
361             sdsfree(tmp);
362         }
363     }
364 }
365 
366 /* Linenoise hints callback. */
367 static char *hintsCallback(const char *buf, int *color, int *bold) {
368     if (!pref.hints) return NULL;
369 
370     int i, argc, buflen = strlen(buf);
371     sds *argv = sdssplitargs(buf,&argc);
372     int endspace = buflen && isspace(buf[buflen-1]);
373 
374     /* Check if the argument list is empty and return ASAP. */
375     if (argc == 0) {
376         sdsfreesplitres(argv,argc);
377         return NULL;
378     }
379 
380     for (i = 0; i < helpEntriesLen; i++) {
381         if (!(helpEntries[i].type & CLI_HELP_COMMAND)) continue;
382 
383         if (strcasecmp(argv[0],helpEntries[i].full) == 0)
384         {
385             *color = 90;
386             *bold = 0;
387             sds hint = sdsnew(helpEntries[i].org->params);
388 
389             /* Remove arguments from the returned hint to show only the
390              * ones the user did not yet typed. */
391             int toremove = argc-1;
392             while(toremove > 0 && sdslen(hint)) {
393                 if (hint[0] == '[') break;
394                 if (hint[0] == ' ') toremove--;
395                 sdsrange(hint,1,-1);
396             }
397 
398             /* Add an initial space if needed. */
399             if (!endspace) {
400                 sds newhint = sdsnewlen(" ",1);
401                 newhint = sdscatsds(newhint,hint);
402                 sdsfree(hint);
403                 hint = newhint;
404             }
405 
406             sdsfreesplitres(argv,argc);
407             return hint;
408         }
409     }
410     sdsfreesplitres(argv,argc);
411     return NULL;
412 }
413 
414 static void freeHintsCallback(void *ptr) {
415     sdsfree(ptr);
416 }
417 
418 /*------------------------------------------------------------------------------
419  * Networking / parsing
420  *--------------------------------------------------------------------------- */
421 
422 /* Send AUTH command to the server */
423 static int cliAuth(void) {
424     redisReply *reply;
425     if (config.auth == NULL) return REDIS_OK;
426 
427     reply = redisCommand(context,"AUTH %s",config.auth);
428     if (reply != NULL) {
429         freeReplyObject(reply);
430         return REDIS_OK;
431     }
432     return REDIS_ERR;
433 }
434 
435 /* Send SELECT dbnum to the server */
436 static int cliSelect(void) {
437     redisReply *reply;
438     if (config.dbnum == 0) return REDIS_OK;
439 
440     reply = redisCommand(context,"SELECT %d",config.dbnum);
441     if (reply != NULL) {
442         int result = REDIS_OK;
443         if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
444         freeReplyObject(reply);
445         return result;
446     }
447     return REDIS_ERR;
448 }
449 
450 /* Connect to the server. If force is not zero the connection is performed
451  * even if there is already a connected socket. */
452 static int cliConnect(int force) {
453     if (context == NULL || force) {
454         if (context != NULL) {
455             redisFree(context);
456         }
457 
458         if (config.hostsocket == NULL) {
459             context = redisConnect(config.hostip,config.hostport);
460         } else {
461             context = redisConnectUnix(config.hostsocket);
462         }
463 
464         if (context->err) {
465             fprintf(stderr,"Could not connect to Redis at ");
466             if (config.hostsocket == NULL)
467                 fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr);
468             else
469                 fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr);
470             redisFree(context);
471             context = NULL;
472             return REDIS_ERR;
473         }
474 
475         /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
476          * in order to prevent timeouts caused by the execution of long
477          * commands. At the same time this improves the detection of real
478          * errors. */
479         anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
480 
481         /* Do AUTH and select the right DB. */
482         if (cliAuth() != REDIS_OK)
483             return REDIS_ERR;
484         if (cliSelect() != REDIS_OK)
485             return REDIS_ERR;
486     }
487     return REDIS_OK;
488 }
489 
490 static void cliPrintContextError(void) {
491     if (context == NULL) return;
492     fprintf(stderr,"Error: %s\n",context->errstr);
493 }
494 
495 static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
496     sds out = sdsempty();
497     switch (r->type) {
498     case REDIS_REPLY_ERROR:
499         out = sdscatprintf(out,"(error) %s\n", r->str);
500     break;
501     case REDIS_REPLY_STATUS:
502         out = sdscat(out,r->str);
503         out = sdscat(out,"\n");
504     break;
505     case REDIS_REPLY_INTEGER:
506         out = sdscatprintf(out,"(integer) %lld\n",r->integer);
507     break;
508     case REDIS_REPLY_STRING:
509         /* If you are producing output for the standard output we want
510         * a more interesting output with quoted characters and so forth */
511         out = sdscatrepr(out,r->str,r->len);
512         out = sdscat(out,"\n");
513     break;
514     case REDIS_REPLY_NIL:
515         out = sdscat(out,"(nil)\n");
516     break;
517     case REDIS_REPLY_ARRAY:
518         if (r->elements == 0) {
519             out = sdscat(out,"(empty list or set)\n");
520         } else {
521             unsigned int i, idxlen = 0;
522             char _prefixlen[16];
523             char _prefixfmt[16];
524             sds _prefix;
525             sds tmp;
526 
527             /* Calculate chars needed to represent the largest index */
528             i = r->elements;
529             do {
530                 idxlen++;
531                 i /= 10;
532             } while(i);
533 
534             /* Prefix for nested multi bulks should grow with idxlen+2 spaces */
535             memset(_prefixlen,' ',idxlen+2);
536             _prefixlen[idxlen+2] = '\0';
537             _prefix = sdscat(sdsnew(prefix),_prefixlen);
538 
539             /* Setup prefix format for every entry */
540             snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud) ",idxlen);
541 
542             for (i = 0; i < r->elements; i++) {
543                 /* Don't use the prefix for the first element, as the parent
544                  * caller already prepended the index number. */
545                 out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);
546 
547                 /* Format the multi bulk entry */
548                 tmp = cliFormatReplyTTY(r->element[i],_prefix);
549                 out = sdscatlen(out,tmp,sdslen(tmp));
550                 sdsfree(tmp);
551             }
552             sdsfree(_prefix);
553         }
554     break;
555     default:
556         fprintf(stderr,"Unknown reply type: %d\n", r->type);
557         exit(1);
558     }
559     return out;
560 }
561 
562 int isColorTerm(void) {
563     char *t = getenv("TERM");
564     return t != NULL && strstr(t,"xterm") != NULL;
565 }
566 
567 /* Helpe  function for sdsCatColorizedLdbReply() appending colorize strings
568  * to an SDS string. */
569 sds sdscatcolor(sds o, char *s, size_t len, char *color) {
570     if (!isColorTerm()) return sdscatlen(o,s,len);
571 
572     int bold = strstr(color,"bold") != NULL;
573     int ccode = 37; /* Defaults to white. */
574     if (strstr(color,"red")) ccode = 31;
575     else if (strstr(color,"red")) ccode = 31;
576     else if (strstr(color,"green")) ccode = 32;
577     else if (strstr(color,"yellow")) ccode = 33;
578     else if (strstr(color,"blue")) ccode = 34;
579     else if (strstr(color,"magenta")) ccode = 35;
580     else if (strstr(color,"cyan")) ccode = 36;
581     else if (strstr(color,"white")) ccode = 37;
582 
583     o = sdscatfmt(o,"\033[%i;%i;49m",bold,ccode);
584     o = sdscatlen(o,s,len);
585     o = sdscat(o,"\033[0m");
586     return o;
587 }
588 
589 /* Colorize Lua debugger status replies according to the prefix they
590  * have. */
591 sds sdsCatColorizedLdbReply(sds o, char *s, size_t len) {
592     char *color = "white";
593 
594     if (strstr(s,"<debug>")) color = "bold";
595     if (strstr(s,"<redis>")) color = "green";
596     if (strstr(s,"<reply>")) color = "cyan";
597     if (strstr(s,"<error>")) color = "red";
598     if (strstr(s,"<hint>")) color = "bold";
599     if (strstr(s,"<value>") || strstr(s,"<retval>")) color = "magenta";
600     if (len > 4 && isdigit(s[3])) {
601         if (s[1] == '>') color = "yellow"; /* Current line. */
602         else if (s[2] == '#') color = "bold"; /* Break point. */
603     }
604     return sdscatcolor(o,s,len,color);
605 }
606 
607 static sds cliFormatReplyRaw(redisReply *r) {
608     sds out = sdsempty(), tmp;
609     size_t i;
610 
611     switch (r->type) {
612     case REDIS_REPLY_NIL:
613         /* Nothing... */
614         break;
615     case REDIS_REPLY_ERROR:
616         out = sdscatlen(out,r->str,r->len);
617         out = sdscatlen(out,"\n",1);
618         break;
619     case REDIS_REPLY_STATUS:
620     case REDIS_REPLY_STRING:
621         if (r->type == REDIS_REPLY_STATUS && config.eval_ldb) {
622             /* The Lua debugger replies with arrays of simple (status)
623              * strings. We colorize the output for more fun if this
624              * is a debugging session. */
625 
626             /* Detect the end of a debugging session. */
627             if (strstr(r->str,"<endsession>") == r->str) {
628                 config.enable_ldb_on_eval = 0;
629                 config.eval_ldb = 0;
630                 config.eval_ldb_end = 1; /* Signal the caller session ended. */
631                 config.output = OUTPUT_STANDARD;
632                 cliRefreshPrompt();
633             } else {
634                 out = sdsCatColorizedLdbReply(out,r->str,r->len);
635             }
636         } else {
637             out = sdscatlen(out,r->str,r->len);
638         }
639         break;
640     case REDIS_REPLY_INTEGER:
641         out = sdscatprintf(out,"%lld",r->integer);
642         break;
643     case REDIS_REPLY_ARRAY:
644         for (i = 0; i < r->elements; i++) {
645             if (i > 0) out = sdscat(out,config.mb_delim);
646             tmp = cliFormatReplyRaw(r->element[i]);
647             out = sdscatlen(out,tmp,sdslen(tmp));
648             sdsfree(tmp);
649         }
650         break;
651     default:
652         fprintf(stderr,"Unknown reply type: %d\n", r->type);
653         exit(1);
654     }
655     return out;
656 }
657 
658 static sds cliFormatReplyCSV(redisReply *r) {
659     unsigned int i;
660 
661     sds out = sdsempty();
662     switch (r->type) {
663     case REDIS_REPLY_ERROR:
664         out = sdscat(out,"ERROR,");
665         out = sdscatrepr(out,r->str,strlen(r->str));
666     break;
667     case REDIS_REPLY_STATUS:
668         out = sdscatrepr(out,r->str,r->len);
669     break;
670     case REDIS_REPLY_INTEGER:
671         out = sdscatprintf(out,"%lld",r->integer);
672     break;
673     case REDIS_REPLY_STRING:
674         out = sdscatrepr(out,r->str,r->len);
675     break;
676     case REDIS_REPLY_NIL:
677         out = sdscat(out,"NIL");
678     break;
679     case REDIS_REPLY_ARRAY:
680         for (i = 0; i < r->elements; i++) {
681             sds tmp = cliFormatReplyCSV(r->element[i]);
682             out = sdscatlen(out,tmp,sdslen(tmp));
683             if (i != r->elements-1) out = sdscat(out,",");
684             sdsfree(tmp);
685         }
686     break;
687     default:
688         fprintf(stderr,"Unknown reply type: %d\n", r->type);
689         exit(1);
690     }
691     return out;
692 }
693 
694 static int cliReadReply(int output_raw_strings) {
695     void *_reply;
696     redisReply *reply;
697     sds out = NULL;
698     int output = 1;
699 
700     if (redisGetReply(context,&_reply) != REDIS_OK) {
701         if (config.shutdown) {
702             redisFree(context);
703             context = NULL;
704             return REDIS_OK;
705         }
706         if (config.interactive) {
707             /* Filter cases where we should reconnect */
708             if (context->err == REDIS_ERR_IO &&
709                 (errno == ECONNRESET || errno == EPIPE))
710                 return REDIS_ERR;
711             if (context->err == REDIS_ERR_EOF)
712                 return REDIS_ERR;
713         }
714         cliPrintContextError();
715         exit(1);
716         return REDIS_ERR; /* avoid compiler warning */
717     }
718 
719     reply = (redisReply*)_reply;
720 
721     config.last_cmd_type = reply->type;
722 
723     /* Check if we need to connect to a different node and reissue the
724      * request. */
725     if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR &&
726         (!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK")))
727     {
728         char *p = reply->str, *s;
729         int slot;
730 
731         output = 0;
732         /* Comments show the position of the pointer as:
733          *
734          * [S] for pointer 's'
735          * [P] for pointer 'p'
736          */
737         s = strchr(p,' ');      /* MOVED[S]3999 127.0.0.1:6381 */
738         p = strchr(s+1,' ');    /* MOVED[S]3999[P]127.0.0.1:6381 */
739         *p = '\0';
740         slot = atoi(s+1);
741         s = strchr(p+1,':');    /* MOVED 3999[P]127.0.0.1[S]6381 */
742         *s = '\0';
743         sdsfree(config.hostip);
744         config.hostip = sdsnew(p+1);
745         config.hostport = atoi(s+1);
746         if (config.interactive)
747             printf("-> Redirected to slot [%d] located at %s:%d\n",
748                 slot, config.hostip, config.hostport);
749         config.cluster_reissue_command = 1;
750         cliRefreshPrompt();
751     }
752 
753     if (output) {
754         if (output_raw_strings) {
755             out = cliFormatReplyRaw(reply);
756         } else {
757             if (config.output == OUTPUT_RAW) {
758                 out = cliFormatReplyRaw(reply);
759                 out = sdscat(out,"\n");
760             } else if (config.output == OUTPUT_STANDARD) {
761                 out = cliFormatReplyTTY(reply,"");
762             } else if (config.output == OUTPUT_CSV) {
763                 out = cliFormatReplyCSV(reply);
764                 out = sdscat(out,"\n");
765             }
766         }
767         fwrite(out,sdslen(out),1,stdout);
768         sdsfree(out);
769     }
770     freeReplyObject(reply);
771     return REDIS_OK;
772 }
773 
774 static int cliSendCommand(int argc, char **argv, int repeat) {
775     char *command = argv[0];
776     size_t *argvlen;
777     int j, output_raw;
778 
779     if (!config.eval_ldb && /* In debugging mode, let's pass "help" to Redis. */
780         (!strcasecmp(command,"help") || !strcasecmp(command,"?"))) {
781         cliOutputHelp(--argc, ++argv);
782         return REDIS_OK;
783     }
784 
785     if (context == NULL) return REDIS_ERR;
786 
787     output_raw = 0;
788     if (!strcasecmp(command,"info") ||
789         (argc >= 2 && !strcasecmp(command,"debug") &&
790                       (!strcasecmp(argv[1],"jemalloc") ||
791                        !strcasecmp(argv[1],"htstats"))) ||
792         (argc == 2 && !strcasecmp(command,"cluster") &&
793                       (!strcasecmp(argv[1],"nodes") ||
794                        !strcasecmp(argv[1],"info"))) ||
795         (argc == 2 && !strcasecmp(command,"client") &&
796                        !strcasecmp(argv[1],"list")) ||
797         (argc == 3 && !strcasecmp(command,"latency") &&
798                        !strcasecmp(argv[1],"graph")) ||
799         (argc == 2 && !strcasecmp(command,"latency") &&
800                        !strcasecmp(argv[1],"doctor")))
801     {
802         output_raw = 1;
803     }
804 
805     if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
806     if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
807     if (!strcasecmp(command,"subscribe") ||
808         !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
809     if (!strcasecmp(command,"sync") ||
810         !strcasecmp(command,"psync")) config.slave_mode = 1;
811 
812     /* When the user manually calls SCRIPT DEBUG, setup the activation of
813      * debugging mode on the next eval if needed. */
814     if (argc == 3 && !strcasecmp(argv[0],"script") &&
815                      !strcasecmp(argv[1],"debug"))
816     {
817         if (!strcasecmp(argv[2],"yes") || !strcasecmp(argv[2],"sync")) {
818             config.enable_ldb_on_eval = 1;
819         } else {
820             config.enable_ldb_on_eval = 0;
821         }
822     }
823 
824     /* Actually activate LDB on EVAL if needed. */
825     if (!strcasecmp(command,"eval") && config.enable_ldb_on_eval) {
826         config.eval_ldb = 1;
827         config.output = OUTPUT_RAW;
828     }
829 
830     /* Setup argument length */
831     argvlen = malloc(argc*sizeof(size_t));
832     for (j = 0; j < argc; j++)
833         argvlen[j] = sdslen(argv[j]);
834 
835     while(repeat--) {
836         redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
837         while (config.monitor_mode) {
838             if (cliReadReply(output_raw) != REDIS_OK) exit(1);
839             fflush(stdout);
840         }
841 
842         if (config.pubsub_mode) {
843             if (config.output != OUTPUT_RAW)
844                 printf("Reading messages... (press Ctrl-C to quit)\n");
845             while (1) {
846                 if (cliReadReply(output_raw) != REDIS_OK) exit(1);
847             }
848         }
849 
850         if (config.slave_mode) {
851             printf("Entering slave output mode...  (press Ctrl-C to quit)\n");
852             slaveMode();
853             config.slave_mode = 0;
854             free(argvlen);
855             return REDIS_ERR;  /* Error = slaveMode lost connection to master */
856         }
857 
858         if (cliReadReply(output_raw) != REDIS_OK) {
859             free(argvlen);
860             return REDIS_ERR;
861         } else {
862             /* Store database number when SELECT was successfully executed. */
863             if (!strcasecmp(command,"select") && argc == 2) {
864                 config.dbnum = atoi(argv[1]);
865                 cliRefreshPrompt();
866             } else if (!strcasecmp(command,"auth") && argc == 2) {
867                 cliSelect();
868             }
869         }
870         if (config.interval) usleep(config.interval);
871         fflush(stdout); /* Make it grep friendly */
872     }
873 
874     free(argvlen);
875     return REDIS_OK;
876 }
877 
878 /* Send a command reconnecting the link if needed. */
879 static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ...) {
880     redisReply *reply = NULL;
881     int tries = 0;
882     va_list ap;
883 
884     assert(!c->err);
885     while(reply == NULL) {
886         while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
887             printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
888             printf("Reconnecting... %d\r", ++tries);
889             fflush(stdout);
890 
891             redisFree(c);
892             c = redisConnect(config.hostip,config.hostport);
893             usleep(1000000);
894         }
895 
896         va_start(ap,fmt);
897         reply = redisvCommand(c,fmt,ap);
898         va_end(ap);
899 
900         if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
901             fprintf(stderr, "Error: %s\n", c->errstr);
902             exit(1);
903         } else if (tries > 0) {
904             printf("\r\x1b[0K"); /* Cursor to left edge + clear line. */
905         }
906     }
907 
908     context = c;
909     return reply;
910 }
911 
912 /*------------------------------------------------------------------------------
913  * User interface
914  *--------------------------------------------------------------------------- */
915 
916 static int parseOptions(int argc, char **argv) {
917     int i;
918 
919     for (i = 1; i < argc; i++) {
920         int lastarg = i==argc-1;
921 
922         if (!strcmp(argv[i],"-h") && !lastarg) {
923             sdsfree(config.hostip);
924             config.hostip = sdsnew(argv[++i]);
925         } else if (!strcmp(argv[i],"-h") && lastarg) {
926             usage();
927         } else if (!strcmp(argv[i],"--help")) {
928             usage();
929         } else if (!strcmp(argv[i],"-x")) {
930             config.stdinarg = 1;
931         } else if (!strcmp(argv[i],"-p") && !lastarg) {
932             config.hostport = atoi(argv[++i]);
933         } else if (!strcmp(argv[i],"-s") && !lastarg) {
934             config.hostsocket = argv[++i];
935         } else if (!strcmp(argv[i],"-r") && !lastarg) {
936             config.repeat = strtoll(argv[++i],NULL,10);
937         } else if (!strcmp(argv[i],"-i") && !lastarg) {
938             double seconds = atof(argv[++i]);
939             config.interval = seconds*1000000;
940         } else if (!strcmp(argv[i],"-n") && !lastarg) {
941             config.dbnum = atoi(argv[++i]);
942         } else if (!strcmp(argv[i],"-a") && !lastarg) {
943             config.auth = argv[++i];
944         } else if (!strcmp(argv[i],"--raw")) {
945             config.output = OUTPUT_RAW;
946         } else if (!strcmp(argv[i],"--no-raw")) {
947             config.output = OUTPUT_STANDARD;
948         } else if (!strcmp(argv[i],"--csv")) {
949             config.output = OUTPUT_CSV;
950         } else if (!strcmp(argv[i],"--latency")) {
951             config.latency_mode = 1;
952         } else if (!strcmp(argv[i],"--latency-dist")) {
953             config.latency_dist_mode = 1;
954         } else if (!strcmp(argv[i],"--mono")) {
955             spectrum_palette = spectrum_palette_mono;
956             spectrum_palette_size = spectrum_palette_mono_size;
957         } else if (!strcmp(argv[i],"--latency-history")) {
958             config.latency_mode = 1;
959             config.latency_history = 1;
960         } else if (!strcmp(argv[i],"--lru-test") && !lastarg) {
961             config.lru_test_mode = 1;
962             config.lru_test_sample_size = strtoll(argv[++i],NULL,10);
963         } else if (!strcmp(argv[i],"--slave")) {
964             config.slave_mode = 1;
965         } else if (!strcmp(argv[i],"--stat")) {
966             config.stat_mode = 1;
967         } else if (!strcmp(argv[i],"--scan")) {
968             config.scan_mode = 1;
969         } else if (!strcmp(argv[i],"--pattern") && !lastarg) {
970             config.pattern = argv[++i];
971         } else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) {
972             config.intrinsic_latency_mode = 1;
973             config.intrinsic_latency_duration = atoi(argv[++i]);
974         } else if (!strcmp(argv[i],"--rdb") && !lastarg) {
975             config.getrdb_mode = 1;
976             config.rdb_filename = argv[++i];
977         } else if (!strcmp(argv[i],"--pipe")) {
978             config.pipe_mode = 1;
979         } else if (!strcmp(argv[i],"--pipe-timeout") && !lastarg) {
980             config.pipe_timeout = atoi(argv[++i]);
981         } else if (!strcmp(argv[i],"--bigkeys")) {
982             config.bigkeys = 1;
983         } else if (!strcmp(argv[i],"--eval") && !lastarg) {
984             config.eval = argv[++i];
985         } else if (!strcmp(argv[i],"--ldb")) {
986             config.eval_ldb = 1;
987             config.output = OUTPUT_RAW;
988         } else if (!strcmp(argv[i],"--ldb-sync-mode")) {
989             config.eval_ldb = 1;
990             config.eval_ldb_sync = 1;
991             config.output = OUTPUT_RAW;
992         } else if (!strcmp(argv[i],"-c")) {
993             config.cluster_mode = 1;
994         } else if (!strcmp(argv[i],"-d") && !lastarg) {
995             sdsfree(config.mb_delim);
996             config.mb_delim = sdsnew(argv[++i]);
997         } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
998             sds version = cliVersion();
999             printf("redis-cli %s\n", version);
1000             sdsfree(version);
1001             exit(0);
1002         } else {
1003             if (argv[i][0] == '-') {
1004                 fprintf(stderr,
1005                     "Unrecognized option or bad number of args for: '%s'\n",
1006                     argv[i]);
1007                 exit(1);
1008             } else {
1009                 /* Likely the command name, stop here. */
1010                 break;
1011             }
1012         }
1013     }
1014 
1015     /* --ldb requires --eval. */
1016     if (config.eval_ldb && config.eval == NULL) {
1017         fprintf(stderr,"Options --ldb and --ldb-sync-mode require --eval.\n");
1018         fprintf(stderr,"Try %s --help for more information.\n", argv[0]);
1019         exit(1);
1020     }
1021     return i;
1022 }
1023 
1024 static sds readArgFromStdin(void) {
1025     char buf[1024];
1026     sds arg = sdsempty();
1027 
1028     while(1) {
1029         int nread = read(fileno(stdin),buf,1024);
1030 
1031         if (nread == 0) break;
1032         else if (nread == -1) {
1033             perror("Reading from standard input");
1034             exit(1);
1035         }
1036         arg = sdscatlen(arg,buf,nread);
1037     }
1038     return arg;
1039 }
1040 
1041 static void usage(void) {
1042     sds version = cliVersion();
1043     fprintf(stderr,
1044 "redis-cli %s\n"
1045 "\n"
1046 "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
1047 "  -h <hostname>      Server hostname (default: 127.0.0.1).\n"
1048 "  -p <port>          Server port (default: 6379).\n"
1049 "  -s <socket>        Server socket (overrides hostname and port).\n"
1050 "  -a <password>      Password to use when connecting to the server.\n"
1051 "  -r <repeat>        Execute specified command N times.\n"
1052 "  -i <interval>      When -r is used, waits <interval> seconds per command.\n"
1053 "                     It is possible to specify sub-second times like -i 0.1.\n"
1054 "  -n <db>            Database number.\n"
1055 "  -x                 Read last argument from STDIN.\n"
1056 "  -d <delimiter>     Multi-bulk delimiter in for raw formatting (default: \\n).\n"
1057 "  -c                 Enable cluster mode (follow -ASK and -MOVED redirections).\n"
1058 "  --raw              Use raw formatting for replies (default when STDOUT is\n"
1059 "                     not a tty).\n"
1060 "  --no-raw           Force formatted output even when STDOUT is not a tty.\n"
1061 "  --csv              Output in CSV format.\n"
1062 "  --stat             Print rolling stats about server: mem, clients, ...\n"
1063 "  --latency          Enter a special mode continuously sampling latency.\n"
1064 "  --latency-history  Like --latency but tracking latency changes over time.\n"
1065 "                     Default time interval is 15 sec. Change it using -i.\n"
1066 "  --latency-dist     Shows latency as a spectrum, requires xterm 256 colors.\n"
1067 "                     Default time interval is 1 sec. Change it using -i.\n"
1068 "  --lru-test <keys>  Simulate a cache workload with an 80-20 distribution.\n"
1069 "  --slave            Simulate a slave showing commands received from the master.\n"
1070 "  --rdb <filename>   Transfer an RDB dump from remote server to local file.\n"
1071 "  --pipe             Transfer raw Redis protocol from stdin to server.\n"
1072 "  --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
1073 "                     no reply is received within <n> seconds.\n"
1074 "                     Default timeout: %d. Use 0 to wait forever.\n"
1075 "  --bigkeys          Sample Redis keys looking for big keys.\n"
1076 "  --scan             List all keys using the SCAN command.\n"
1077 "  --pattern <pat>    Useful with --scan to specify a SCAN pattern.\n"
1078 "  --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
1079 "                     The test will run for the specified amount of seconds.\n"
1080 "  --eval <file>      Send an EVAL command using the Lua script at <file>.\n"
1081 "  --ldb              Used with --eval enable the Redis Lua debugger.\n"
1082 "  --ldb-sync-mode    Like --ldb but uses the synchronous Lua debugger, in\n"
1083 "                     this mode the server is blocked and script changes are\n"
1084 "                     are not rolled back from the server memory.\n"
1085 "  --help             Output this help and exit.\n"
1086 "  --version          Output version and exit.\n"
1087 "\n"
1088 "Examples:\n"
1089 "  cat /etc/passwd | redis-cli -x set mypasswd\n"
1090 "  redis-cli get mypasswd\n"
1091 "  redis-cli -r 100 lpush mylist x\n"
1092 "  redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
1093 "  redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n"
1094 "  redis-cli --scan --pattern '*:12345*'\n"
1095 "\n"
1096 "  (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n"
1097 "\n"
1098 "When no command is given, redis-cli starts in interactive mode.\n"
1099 "Type \"help\" in interactive mode for information on available commands.\n"
1100 "\n",
1101         version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
1102     sdsfree(version);
1103     exit(1);
1104 }
1105 
1106 /* Turn the plain C strings into Sds strings */
1107 static char **convertToSds(int count, char** args) {
1108   int j;
1109   char **sds = zmalloc(sizeof(char*)*count);
1110 
1111   for(j = 0; j < count; j++)
1112     sds[j] = sdsnew(args[j]);
1113 
1114   return sds;
1115 }
1116 
1117 static int issueCommandRepeat(int argc, char **argv, long repeat) {
1118     while (1) {
1119         config.cluster_reissue_command = 0;
1120         if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
1121             cliConnect(1);
1122 
1123             /* If we still cannot send the command print error.
1124              * We'll try to reconnect the next time. */
1125             if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
1126                 cliPrintContextError();
1127                 return REDIS_ERR;
1128             }
1129          }
1130          /* Issue the command again if we got redirected in cluster mode */
1131          if (config.cluster_mode && config.cluster_reissue_command) {
1132             cliConnect(1);
1133          } else {
1134              break;
1135         }
1136     }
1137     return REDIS_OK;
1138 }
1139 
1140 static int issueCommand(int argc, char **argv) {
1141     return issueCommandRepeat(argc, argv, config.repeat);
1142 }
1143 
1144 /* Split the user provided command into multiple SDS arguments.
1145  * This function normally uses sdssplitargs() from sds.c which is able
1146  * to understand "quoted strings", escapes and so forth. However when
1147  * we are in Lua debugging mode and the "eval" command is used, we want
1148  * the remaining Lua script (after "e " or "eval ") to be passed verbatim
1149  * as a single big argument. */
1150 static sds *cliSplitArgs(char *line, int *argc) {
1151     if (config.eval_ldb && (strstr(line,"eval ") == line ||
1152                             strstr(line,"e ") == line))
1153     {
1154         sds *argv = sds_malloc(sizeof(sds)*2);
1155         *argc = 2;
1156         int len = strlen(line);
1157         int elen = line[1] == ' ' ? 2 : 5; /* "e " or "eval "? */
1158         argv[0] = sdsnewlen(line,elen-1);
1159         argv[1] = sdsnewlen(line+elen,len-elen);
1160         return argv;
1161     } else {
1162         return sdssplitargs(line,argc);
1163     }
1164 }
1165 
1166 /* Set the CLI perferences. This function is invoked when an interactive
1167  * ":command" is called, or when reading ~/.redisclirc file, in order to
1168  * set user preferences. */
1169 void cliSetPreferences(char **argv, int argc, int interactive) {
1170     if (!strcasecmp(argv[0],":set") && argc >= 2) {
1171         if (!strcasecmp(argv[1],"hints")) pref.hints = 1;
1172         else if (!strcasecmp(argv[1],"nohints")) pref.hints = 0;
1173         else {
1174             printf("%sunknown redis-cli preference '%s'\n",
1175                 interactive ? "" : ".redisclirc: ",
1176                 argv[1]);
1177         }
1178     } else {
1179         printf("%sunknown redis-cli internal command '%s'\n",
1180             interactive ? "" : ".redisclirc: ",
1181             argv[0]);
1182     }
1183 }
1184 
1185 /* Load the ~/.redisclirc file if any. */
1186 void cliLoadPreferences(void) {
1187     sds rcfile = getDotfilePath(REDIS_CLI_RCFILE_ENV,REDIS_CLI_RCFILE_DEFAULT);
1188     if (rcfile == NULL) return;
1189     FILE *fp = fopen(rcfile,"r");
1190     char buf[1024];
1191 
1192     if (fp) {
1193         while(fgets(buf,sizeof(buf),fp) != NULL) {
1194             sds *argv;
1195             int argc;
1196 
1197             argv = sdssplitargs(buf,&argc);
1198             if (argc > 0) cliSetPreferences(argv,argc,0);
1199             sdsfreesplitres(argv,argc);
1200         }
1201     }
1202     sdsfree(rcfile);
1203 }
1204 
1205 static void repl(void) {
1206     sds historyfile = NULL;
1207     int history = 0;
1208     char *line;
1209     int argc;
1210     sds *argv;
1211 
1212     config.interactive = 1;
1213     linenoiseSetMultiLine(1);
1214     linenoiseSetCompletionCallback(completionCallback);
1215     linenoiseSetHintsCallback(hintsCallback);
1216     linenoiseSetFreeHintsCallback(freeHintsCallback);
1217 
1218     /* Only use history and load the rc file when stdin is a tty. */
1219     if (isatty(fileno(stdin))) {
1220         historyfile = getDotfilePath(REDIS_CLI_HISTFILE_ENV,REDIS_CLI_HISTFILE_DEFAULT);
1221         if (historyfile != NULL) {
1222             history = 1;
1223             linenoiseHistoryLoad(historyfile);
1224             sdsfree(historyfile);
1225         }
1226         cliLoadPreferences();
1227     }
1228 
1229     cliRefreshPrompt();
1230     while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
1231         if (line[0] != '\0') {
1232             argv = cliSplitArgs(line,&argc);
1233             if (history) linenoiseHistoryAdd(line);
1234             if (historyfile) linenoiseHistorySave(historyfile);
1235 
1236             if (argv == NULL) {
1237                 printf("Invalid argument(s)\n");
1238                 free(line);
1239                 continue;
1240             } else if (argc > 0) {
1241                 if (strcasecmp(argv[0],"quit") == 0 ||
1242                     strcasecmp(argv[0],"exit") == 0)
1243                 {
1244                     exit(0);
1245                 } else if (argv[0][0] == ':') {
1246                     cliSetPreferences(argv,argc,1);
1247                     continue;
1248                 } else if (strcasecmp(argv[0],"restart") == 0) {
1249                     if (config.eval) {
1250                         config.eval_ldb = 1;
1251                         config.output = OUTPUT_RAW;
1252                         return; /* Return to evalMode to restart the session. */
1253                     } else {
1254                         printf("Use 'restart' only in Lua debugging mode.");
1255                     }
1256                 } else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
1257                     sdsfree(config.hostip);
1258                     config.hostip = sdsnew(argv[1]);
1259                     config.hostport = atoi(argv[2]);
1260                     cliRefreshPrompt();
1261                     cliConnect(1);
1262                 } else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
1263                     linenoiseClearScreen();
1264                 } else {
1265                     long long start_time = mstime(), elapsed;
1266                     int repeat, skipargs = 0;
1267 
1268                     repeat = atoi(argv[0]);
1269                     if (argc > 1 && repeat) {
1270                         skipargs = 1;
1271                     } else {
1272                         repeat = 1;
1273                     }
1274 
1275                     issueCommandRepeat(argc-skipargs, argv+skipargs, repeat);
1276 
1277                     /* If our debugging session ended, show the EVAL final
1278                      * reply. */
1279                     if (config.eval_ldb_end) {
1280                         config.eval_ldb_end = 0;
1281                         cliReadReply(0);
1282                         printf("\n(Lua debugging session ended%s)\n\n",
1283                             config.eval_ldb_sync ? "" :
1284                             " -- dataset changes rolled back");
1285                     }
1286 
1287                     elapsed = mstime()-start_time;
1288                     if (elapsed >= 500) {
1289                         printf("(%.2fs)\n",(double)elapsed/1000);
1290                     }
1291                 }
1292             }
1293             /* Free the argument vector */
1294             sdsfreesplitres(argv,argc);
1295         }
1296         /* linenoise() returns malloc-ed lines like readline() */
1297         free(line);
1298     }
1299     exit(0);
1300 }
1301 
1302 static int noninteractive(int argc, char **argv) {
1303     int retval = 0;
1304     if (config.stdinarg) {
1305         argv = zrealloc(argv, (argc+1)*sizeof(char*));
1306         argv[argc] = readArgFromStdin();
1307         retval = issueCommand(argc+1, argv);
1308     } else {
1309         retval = issueCommand(argc, argv);
1310     }
1311     return retval;
1312 }
1313 
1314 /*------------------------------------------------------------------------------
1315  * Eval mode
1316  *--------------------------------------------------------------------------- */
1317 
1318 static int evalMode(int argc, char **argv) {
1319     sds script = NULL;
1320     FILE *fp;
1321     char buf[1024];
1322     size_t nread;
1323     char **argv2;
1324     int j, got_comma, keys;
1325     int retval = REDIS_OK;
1326 
1327     while(1) {
1328         if (config.eval_ldb) {
1329             printf(
1330             "Lua debugging session started, please use:\n"
1331             "quit    -- End the session.\n"
1332             "restart -- Restart the script in debug mode again.\n"
1333             "help    -- Show Lua script debugging commands.\n\n"
1334             );
1335         }
1336 
1337         sdsfree(script);
1338         script = sdsempty();
1339         got_comma = 0;
1340         keys = 0;
1341 
1342         /* Load the script from the file, as an sds string. */
1343         fp = fopen(config.eval,"r");
1344         if (!fp) {
1345             fprintf(stderr,
1346                 "Can't open file '%s': %s\n", config.eval, strerror(errno));
1347             exit(1);
1348         }
1349         while((nread = fread(buf,1,sizeof(buf),fp)) != 0) {
1350             script = sdscatlen(script,buf,nread);
1351         }
1352         fclose(fp);
1353 
1354         /* If we are debugging a script, enable the Lua debugger. */
1355         if (config.eval_ldb) {
1356             redisReply *reply = redisCommand(context,
1357                     config.eval_ldb_sync ?
1358                     "SCRIPT DEBUG sync": "SCRIPT DEBUG yes");
1359             if (reply) freeReplyObject(reply);
1360         }
1361 
1362         /* Create our argument vector */
1363         argv2 = zmalloc(sizeof(sds)*(argc+3));
1364         argv2[0] = sdsnew("EVAL");
1365         argv2[1] = script;
1366         for (j = 0; j < argc; j++) {
1367             if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) {
1368                 got_comma = 1;
1369                 continue;
1370             }
1371             argv2[j+3-got_comma] = sdsnew(argv[j]);
1372             if (!got_comma) keys++;
1373         }
1374         argv2[2] = sdscatprintf(sdsempty(),"%d",keys);
1375 
1376         /* Call it */
1377         int eval_ldb = config.eval_ldb; /* Save it, may be reverteed. */
1378         retval = issueCommand(argc+3-got_comma, argv2);
1379         if (eval_ldb) {
1380             if (!config.eval_ldb) {
1381                 /* If the debugging session ended immediately, there was an
1382                  * error compiling the script. Show it and don't enter
1383                  * the REPL at all. */
1384                 printf("Eval debugging session can't start:\n");
1385                 cliReadReply(0);
1386                 break; /* Return to the caller. */
1387             } else {
1388                 strncpy(config.prompt,"lua debugger> ",sizeof(config.prompt));
1389                 repl();
1390                 /* Restart the session if repl() returned. */
1391                 cliConnect(1);
1392                 printf("\n");
1393             }
1394         } else {
1395             break; /* Return to the caller. */
1396         }
1397     }
1398     return retval;
1399 }
1400 
1401 /*------------------------------------------------------------------------------
1402  * Latency and latency history modes
1403  *--------------------------------------------------------------------------- */
1404 
1405 #define LATENCY_SAMPLE_RATE 10 /* milliseconds. */
1406 #define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */
1407 static void latencyMode(void) {
1408     redisReply *reply;
1409     long long start, latency, min = 0, max = 0, tot = 0, count = 0;
1410     long long history_interval =
1411         config.interval ? config.interval/1000 :
1412                           LATENCY_HISTORY_DEFAULT_INTERVAL;
1413     double avg;
1414     long long history_start = mstime();
1415 
1416     if (!context) exit(1);
1417     while(1) {
1418         start = mstime();
1419         reply = reconnectingRedisCommand(context,"PING");
1420         if (reply == NULL) {
1421             fprintf(stderr,"\nI/O error\n");
1422             exit(1);
1423         }
1424         latency = mstime()-start;
1425         freeReplyObject(reply);
1426         count++;
1427         if (count == 1) {
1428             min = max = tot = latency;
1429             avg = (double) latency;
1430         } else {
1431             if (latency < min) min = latency;
1432             if (latency > max) max = latency;
1433             tot += latency;
1434             avg = (double) tot/count;
1435         }
1436         printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)",
1437             min, max, avg, count);
1438         fflush(stdout);
1439         if (config.latency_history && mstime()-history_start > history_interval)
1440         {
1441             printf(" -- %.2f seconds range\n", (float)(mstime()-history_start)/1000);
1442             history_start = mstime();
1443             min = max = tot = count = 0;
1444         }
1445         usleep(LATENCY_SAMPLE_RATE * 1000);
1446     }
1447 }
1448 
1449 /*------------------------------------------------------------------------------
1450  * Latency distribution mode -- requires 256 colors xterm
1451  *--------------------------------------------------------------------------- */
1452 
1453 #define LATENCY_DIST_DEFAULT_INTERVAL 1000 /* milliseconds. */
1454 
1455 /* Structure to store samples distribution. */
1456 struct distsamples {
1457     long long max;   /* Max latency to fit into this interval (usec). */
1458     long long count; /* Number of samples in this interval. */
1459     int character;   /* Associated character in visualization. */
1460 };
1461 
1462 /* Helper function for latencyDistMode(). Performs the spectrum visualization
1463  * of the collected samples targeting an xterm 256 terminal.
1464  *
1465  * Takes an array of distsamples structures, ordered from smaller to bigger
1466  * 'max' value. Last sample max must be 0, to mean that it olds all the
1467  * samples greater than the previous one, and is also the stop sentinel.
1468  *
1469  * "tot' is the total number of samples in the different buckets, so it
1470  * is the SUM(samples[i].conut) for i to 0 up to the max sample.
1471  *
1472  * As a side effect the function sets all the buckets count to 0. */
1473 void showLatencyDistSamples(struct distsamples *samples, long long tot) {
1474     int j;
1475 
1476      /* We convert samples into a index inside the palette
1477      * proportional to the percentage a given bucket represents.
1478      * This way intensity of the different parts of the spectrum
1479      * don't change relative to the number of requests, which avoids to
1480      * pollute the visualization with non-latency related info. */
1481     printf("\033[38;5;0m"); /* Set foreground color to black. */
1482     for (j = 0; ; j++) {
1483         int coloridx =
1484             ceil((float) samples[j].count / tot * (spectrum_palette_size-1));
1485         int color = spectrum_palette[coloridx];
1486         printf("\033[48;5;%dm%c", (int)color, samples[j].character);
1487         samples[j].count = 0;
1488         if (samples[j].max == 0) break; /* Last sample. */
1489     }
1490     printf("\033[0m\n");
1491     fflush(stdout);
1492 }
1493 
1494 /* Show the legend: different buckets values and colors meaning, so
1495  * that the spectrum is more easily readable. */
1496 void showLatencyDistLegend(void) {
1497     int j;
1498 
1499     printf("---------------------------------------------\n");
1500     printf(". - * #          .01 .125 .25 .5 milliseconds\n");
1501     printf("1,2,3,...,9      from 1 to 9     milliseconds\n");
1502     printf("A,B,C,D,E        10,20,30,40,50  milliseconds\n");
1503     printf("F,G,H,I,J        .1,.2,.3,.4,.5       seconds\n");
1504     printf("K,L,M,N,O,P,Q,?  1,2,4,8,16,30,60,>60 seconds\n");
1505     printf("From 0 to 100%%: ");
1506     for (j = 0; j < spectrum_palette_size; j++) {
1507         printf("\033[48;5;%dm ", spectrum_palette[j]);
1508     }
1509     printf("\033[0m\n");
1510     printf("---------------------------------------------\n");
1511 }
1512 
1513 static void latencyDistMode(void) {
1514     redisReply *reply;
1515     long long start, latency, count = 0;
1516     long long history_interval =
1517         config.interval ? config.interval/1000 :
1518                           LATENCY_DIST_DEFAULT_INTERVAL;
1519     long long history_start = ustime();
1520     int j, outputs = 0;
1521 
1522     struct distsamples samples[] = {
1523         /* We use a mostly logarithmic scale, with certain linear intervals
1524          * which are more interesting than others, like 1-10 milliseconds
1525          * range. */
1526         {10,0,'.'},         /* 0.01 ms */
1527         {125,0,'-'},        /* 0.125 ms */
1528         {250,0,'*'},        /* 0.25 ms */
1529         {500,0,'#'},        /* 0.5 ms */
1530         {1000,0,'1'},       /* 1 ms */
1531         {2000,0,'2'},       /* 2 ms */
1532         {3000,0,'3'},       /* 3 ms */
1533         {4000,0,'4'},       /* 4 ms */
1534         {5000,0,'5'},       /* 5 ms */
1535         {6000,0,'6'},       /* 6 ms */
1536         {7000,0,'7'},       /* 7 ms */
1537         {8000,0,'8'},       /* 8 ms */
1538         {9000,0,'9'},       /* 9 ms */
1539         {10000,0,'A'},      /* 10 ms */
1540         {20000,0,'B'},      /* 20 ms */
1541         {30000,0,'C'},      /* 30 ms */
1542         {40000,0,'D'},      /* 40 ms */
1543         {50000,0,'E'},      /* 50 ms */
1544         {100000,0,'F'},     /* 0.1 s */
1545         {200000,0,'G'},     /* 0.2 s */
1546         {300000,0,'H'},     /* 0.3 s */
1547         {400000,0,'I'},     /* 0.4 s */
1548         {500000,0,'J'},     /* 0.5 s */
1549         {1000000,0,'K'},    /* 1 s */
1550         {2000000,0,'L'},    /* 2 s */
1551         {4000000,0,'M'},    /* 4 s */
1552         {8000000,0,'N'},    /* 8 s */
1553         {16000000,0,'O'},   /* 16 s */
1554         {30000000,0,'P'},   /* 30 s */
1555         {60000000,0,'Q'},   /* 1 minute */
1556         {0,0,'?'},          /* > 1 minute */
1557     };
1558 
1559     if (!context) exit(1);
1560     while(1) {
1561         start = ustime();
1562         reply = reconnectingRedisCommand(context,"PING");
1563         if (reply == NULL) {
1564             fprintf(stderr,"\nI/O error\n");
1565             exit(1);
1566         }
1567         latency = ustime()-start;
1568         freeReplyObject(reply);
1569         count++;
1570 
1571         /* Populate the relevant bucket. */
1572         for (j = 0; ; j++) {
1573             if (samples[j].max == 0 || latency <= samples[j].max) {
1574                 samples[j].count++;
1575                 break;
1576             }
1577         }
1578 
1579         /* From time to time show the spectrum. */
1580         if (count && (ustime()-history_start)/1000 > history_interval) {
1581             if ((outputs++ % 20) == 0)
1582                 showLatencyDistLegend();
1583             showLatencyDistSamples(samples,count);
1584             history_start = ustime();
1585             count = 0;
1586         }
1587         usleep(LATENCY_SAMPLE_RATE * 1000);
1588     }
1589 }
1590 
1591 /*------------------------------------------------------------------------------
1592  * Slave mode
1593  *--------------------------------------------------------------------------- */
1594 
1595 /* Sends SYNC and reads the number of bytes in the payload. Used both by
1596  * slaveMode() and getRDB(). */
1597 unsigned long long sendSync(int fd) {
1598     /* To start we need to send the SYNC command and return the payload.
1599      * The hiredis client lib does not understand this part of the protocol
1600      * and we don't want to mess with its buffers, so everything is performed
1601      * using direct low-level I/O. */
1602     char buf[4096], *p;
1603     ssize_t nread;
1604 
1605     /* Send the SYNC command. */
1606     if (write(fd,"SYNC\r\n",6) != 6) {
1607         fprintf(stderr,"Error writing to master\n");
1608         exit(1);
1609     }
1610 
1611     /* Read $<payload>\r\n, making sure to read just up to "\n" */
1612     p = buf;
1613     while(1) {
1614         nread = read(fd,p,1);
1615         if (nread <= 0) {
1616             fprintf(stderr,"Error reading bulk length while SYNCing\n");
1617             exit(1);
1618         }
1619         if (*p == '\n' && p != buf) break;
1620         if (*p != '\n') p++;
1621     }
1622     *p = '\0';
1623     if (buf[0] == '-') {
1624         printf("SYNC with master failed: %s\n", buf);
1625         exit(1);
1626     }
1627     return strtoull(buf+1,NULL,10);
1628 }
1629 
1630 static void slaveMode(void) {
1631     int fd = context->fd;
1632     unsigned long long payload = sendSync(fd);
1633     char buf[1024];
1634     int original_output = config.output;
1635 
1636     fprintf(stderr,"SYNC with master, discarding %llu "
1637                    "bytes of bulk transfer...\n", payload);
1638 
1639     /* Discard the payload. */
1640     while(payload) {
1641         ssize_t nread;
1642 
1643         nread = read(fd,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
1644         if (nread <= 0) {
1645             fprintf(stderr,"Error reading RDB payload while SYNCing\n");
1646             exit(1);
1647         }
1648         payload -= nread;
1649     }
1650     fprintf(stderr,"SYNC done. Logging commands from master.\n");
1651 
1652     /* Now we can use hiredis to read the incoming protocol. */
1653     config.output = OUTPUT_CSV;
1654     while (cliReadReply(0) == REDIS_OK);
1655     config.output = original_output;
1656 }
1657 
1658 /*------------------------------------------------------------------------------
1659  * RDB transfer mode
1660  *--------------------------------------------------------------------------- */
1661 
1662 /* This function implements --rdb, so it uses the replication protocol in order
1663  * to fetch the RDB file from a remote server. */
1664 static void getRDB(void) {
1665     int s = context->fd;
1666     int fd;
1667     unsigned long long payload = sendSync(s);
1668     char buf[4096];
1669 
1670     fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n",
1671         payload, config.rdb_filename);
1672 
1673     /* Write to file. */
1674     if (!strcmp(config.rdb_filename,"-")) {
1675         fd = STDOUT_FILENO;
1676     } else {
1677         fd = open(config.rdb_filename, O_CREAT|O_WRONLY, 0644);
1678         if (fd == -1) {
1679             fprintf(stderr, "Error opening '%s': %s\n", config.rdb_filename,
1680                 strerror(errno));
1681             exit(1);
1682         }
1683     }
1684 
1685     while(payload) {
1686         ssize_t nread, nwritten;
1687 
1688         nread = read(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
1689         if (nread <= 0) {
1690             fprintf(stderr,"I/O Error reading RDB payload from socket\n");
1691             exit(1);
1692         }
1693         nwritten = write(fd, buf, nread);
1694         if (nwritten != nread) {
1695             fprintf(stderr,"Error writing data to file: %s\n",
1696                 strerror(errno));
1697             exit(1);
1698         }
1699         payload -= nread;
1700     }
1701     close(s); /* Close the file descriptor ASAP as fsync() may take time. */
1702     fsync(fd);
1703     fprintf(stderr,"Transfer finished with success.\n");
1704     exit(0);
1705 }
1706 
1707 /*------------------------------------------------------------------------------
1708  * Bulk import (pipe) mode
1709  *--------------------------------------------------------------------------- */
1710 
1711 #define PIPEMODE_WRITE_LOOP_MAX_BYTES (128*1024)
1712 static void pipeMode(void) {
1713     int fd = context->fd;
1714     long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0;
1715     char ibuf[1024*16], obuf[1024*16]; /* Input and output buffers */
1716     char aneterr[ANET_ERR_LEN];
1717     redisReader *reader = redisReaderCreate();
1718     redisReply *reply;
1719     int eof = 0; /* True once we consumed all the standard input. */
1720     int done = 0;
1721     char magic[20]; /* Special reply we recognize. */
1722     time_t last_read_time = time(NULL);
1723 
1724     srand(time(NULL));
1725 
1726     /* Use non blocking I/O. */
1727     if (anetNonBlock(aneterr,fd) == ANET_ERR) {
1728         fprintf(stderr, "Can't set the socket in non blocking mode: %s\n",
1729             aneterr);
1730         exit(1);
1731     }
1732 
1733     /* Transfer raw protocol and read replies from the server at the same
1734      * time. */
1735     while(!done) {
1736         int mask = AE_READABLE;
1737 
1738         if (!eof || obuf_len != 0) mask |= AE_WRITABLE;
1739         mask = aeWait(fd,mask,1000);
1740 
1741         /* Handle the readable state: we can read replies from the server. */
1742         if (mask & AE_READABLE) {
1743             ssize_t nread;
1744 
1745             /* Read from socket and feed the hiredis reader. */
1746             do {
1747                 nread = read(fd,ibuf,sizeof(ibuf));
1748                 if (nread == -1 && errno != EAGAIN && errno != EINTR) {
1749                     fprintf(stderr, "Error reading from the server: %s\n",
1750                         strerror(errno));
1751                     exit(1);
1752                 }
1753                 if (nread > 0) {
1754                     redisReaderFeed(reader,ibuf,nread);
1755                     last_read_time = time(NULL);
1756                 }
1757             } while(nread > 0);
1758 
1759             /* Consume replies. */
1760             do {
1761                 if (redisReaderGetReply(reader,(void**)&reply) == REDIS_ERR) {
1762                     fprintf(stderr, "Error reading replies from server\n");
1763                     exit(1);
1764                 }
1765                 if (reply) {
1766                     if (reply->type == REDIS_REPLY_ERROR) {
1767                         fprintf(stderr,"%s\n", reply->str);
1768                         errors++;
1769                     } else if (eof && reply->type == REDIS_REPLY_STRING &&
1770                                       reply->len == 20) {
1771                         /* Check if this is the reply to our final ECHO
1772                          * command. If so everything was received
1773                          * from the server. */
1774                         if (memcmp(reply->str,magic,20) == 0) {
1775                             printf("Last reply received from server.\n");
1776                             done = 1;
1777                             replies--;
1778                         }
1779                     }
1780                     replies++;
1781                     freeReplyObject(reply);
1782                 }
1783             } while(reply);
1784         }
1785 
1786         /* Handle the writable state: we can send protocol to the server. */
1787         if (mask & AE_WRITABLE) {
1788             ssize_t loop_nwritten = 0;
1789 
1790             while(1) {
1791                 /* Transfer current buffer to server. */
1792                 if (obuf_len != 0) {
1793                     ssize_t nwritten = write(fd,obuf+obuf_pos,obuf_len);
1794 
1795                     if (nwritten == -1) {
1796                         if (errno != EAGAIN && errno != EINTR) {
1797                             fprintf(stderr, "Error writing to the server: %s\n",
1798                                 strerror(errno));
1799                             exit(1);
1800                         } else {
1801                             nwritten = 0;
1802                         }
1803                     }
1804                     obuf_len -= nwritten;
1805                     obuf_pos += nwritten;
1806                     loop_nwritten += nwritten;
1807                     if (obuf_len != 0) break; /* Can't accept more data. */
1808                 }
1809                 /* If buffer is empty, load from stdin. */
1810                 if (obuf_len == 0 && !eof) {
1811                     ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf));
1812 
1813                     if (nread == 0) {
1814                         /* The ECHO sequence starts with a "\r\n" so that if there
1815                          * is garbage in the protocol we read from stdin, the ECHO
1816                          * will likely still be properly formatted.
1817                          * CRLF is ignored by Redis, so it has no effects. */
1818                         char echo[] =
1819                         "\r\n*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n";
1820                         int j;
1821 
1822                         eof = 1;
1823                         /* Everything transferred, so we queue a special
1824                          * ECHO command that we can match in the replies
1825                          * to make sure everything was read from the server. */
1826                         for (j = 0; j < 20; j++)
1827                             magic[j] = rand() & 0xff;
1828                         memcpy(echo+21,magic,20);
1829                         memcpy(obuf,echo,sizeof(echo)-1);
1830                         obuf_len = sizeof(echo)-1;
1831                         obuf_pos = 0;
1832                         printf("All data transferred. Waiting for the last reply...\n");
1833                     } else if (nread == -1) {
1834                         fprintf(stderr, "Error reading from stdin: %s\n",
1835                             strerror(errno));
1836                         exit(1);
1837                     } else {
1838                         obuf_len = nread;
1839                         obuf_pos = 0;
1840                     }
1841                 }
1842                 if ((obuf_len == 0 && eof) ||
1843                     loop_nwritten > PIPEMODE_WRITE_LOOP_MAX_BYTES) break;
1844             }
1845         }
1846 
1847         /* Handle timeout, that is, we reached EOF, and we are not getting
1848          * replies from the server for a few seconds, nor the final ECHO is
1849          * received. */
1850         if (eof && config.pipe_timeout > 0 &&
1851             time(NULL)-last_read_time > config.pipe_timeout)
1852         {
1853             fprintf(stderr,"No replies for %d seconds: exiting.\n",
1854                 config.pipe_timeout);
1855             errors++;
1856             break;
1857         }
1858     }
1859     redisReaderFree(reader);
1860     printf("errors: %lld, replies: %lld\n", errors, replies);
1861     if (errors)
1862         exit(1);
1863     else
1864         exit(0);
1865 }
1866 
1867 /*------------------------------------------------------------------------------
1868  * Find big keys
1869  *--------------------------------------------------------------------------- */
1870 
1871 #define TYPE_STRING 0
1872 #define TYPE_LIST   1
1873 #define TYPE_SET    2
1874 #define TYPE_HASH   3
1875 #define TYPE_ZSET   4
1876 #define TYPE_NONE   5
1877 
1878 static redisReply *sendScan(unsigned long long *it) {
1879     redisReply *reply = redisCommand(context, "SCAN %llu", *it);
1880 
1881     /* Handle any error conditions */
1882     if(reply == NULL) {
1883         fprintf(stderr, "\nI/O error\n");
1884         exit(1);
1885     } else if(reply->type == REDIS_REPLY_ERROR) {
1886         fprintf(stderr, "SCAN error: %s\n", reply->str);
1887         exit(1);
1888     } else if(reply->type != REDIS_REPLY_ARRAY) {
1889         fprintf(stderr, "Non ARRAY response from SCAN!\n");
1890         exit(1);
1891     } else if(reply->elements != 2) {
1892         fprintf(stderr, "Invalid element count from SCAN!\n");
1893         exit(1);
1894     }
1895 
1896     /* Validate our types are correct */
1897     assert(reply->element[0]->type == REDIS_REPLY_STRING);
1898     assert(reply->element[1]->type == REDIS_REPLY_ARRAY);
1899 
1900     /* Update iterator */
1901     *it = strtoull(reply->element[0]->str, NULL, 10);
1902 
1903     return reply;
1904 }
1905 
1906 static int getDbSize(void) {
1907     redisReply *reply;
1908     int size;
1909 
1910     reply = redisCommand(context, "DBSIZE");
1911 
1912     if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) {
1913         fprintf(stderr, "Couldn't determine DBSIZE!\n");
1914         exit(1);
1915     }
1916 
1917     /* Grab the number of keys and free our reply */
1918     size = reply->integer;
1919     freeReplyObject(reply);
1920 
1921     return size;
1922 }
1923 
1924 static int toIntType(char *key, char *type) {
1925     if(!strcmp(type, "string")) {
1926         return TYPE_STRING;
1927     } else if(!strcmp(type, "list")) {
1928         return TYPE_LIST;
1929     } else if(!strcmp(type, "set")) {
1930         return TYPE_SET;
1931     } else if(!strcmp(type, "hash")) {
1932         return TYPE_HASH;
1933     } else if(!strcmp(type, "zset")) {
1934         return TYPE_ZSET;
1935     } else if(!strcmp(type, "none")) {
1936         return TYPE_NONE;
1937     } else {
1938         fprintf(stderr, "Unknown type '%s' for key '%s'\n", type, key);
1939         exit(1);
1940     }
1941 }
1942 
1943 static void getKeyTypes(redisReply *keys, int *types) {
1944     redisReply *reply;
1945     unsigned int i;
1946 
1947     /* Pipeline TYPE commands */
1948     for(i=0;i<keys->elements;i++) {
1949         redisAppendCommand(context, "TYPE %s", keys->element[i]->str);
1950     }
1951 
1952     /* Retrieve types */
1953     for(i=0;i<keys->elements;i++) {
1954         if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
1955             fprintf(stderr, "Error getting type for key '%s' (%d: %s)\n",
1956                 keys->element[i]->str, context->err, context->errstr);
1957             exit(1);
1958         } else if(reply->type != REDIS_REPLY_STATUS) {
1959             fprintf(stderr, "Invalid reply type (%d) for TYPE on key '%s'!\n",
1960                 reply->type, keys->element[i]->str);
1961             exit(1);
1962         }
1963 
1964         types[i] = toIntType(keys->element[i]->str, reply->str);
1965         freeReplyObject(reply);
1966     }
1967 }
1968 
1969 static void getKeySizes(redisReply *keys, int *types,
1970                         unsigned long long *sizes)
1971 {
1972     redisReply *reply;
1973     char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"};
1974     unsigned int i;
1975 
1976     /* Pipeline size commands */
1977     for(i=0;i<keys->elements;i++) {
1978         /* Skip keys that were deleted */
1979         if(types[i]==TYPE_NONE)
1980             continue;
1981 
1982         redisAppendCommand(context, "%s %s", sizecmds[types[i]],
1983             keys->element[i]->str);
1984     }
1985 
1986     /* Retreive sizes */
1987     for(i=0;i<keys->elements;i++) {
1988         /* Skip keys that dissapeared between SCAN and TYPE */
1989         if(types[i] == TYPE_NONE) {
1990             sizes[i] = 0;
1991             continue;
1992         }
1993 
1994         /* Retreive size */
1995         if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
1996             fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n",
1997                 keys->element[i]->str, context->err, context->errstr);
1998             exit(1);
1999         } else if(reply->type != REDIS_REPLY_INTEGER) {
2000             /* Theoretically the key could have been removed and
2001              * added as a different type between TYPE and SIZE */
2002             fprintf(stderr,
2003                 "Warning:  %s on '%s' failed (may have changed type)\n",
2004                  sizecmds[types[i]], keys->element[i]->str);
2005             sizes[i] = 0;
2006         } else {
2007             sizes[i] = reply->integer;
2008         }
2009 
2010         freeReplyObject(reply);
2011     }
2012 }
2013 
2014 static void findBigKeys(void) {
2015     unsigned long long biggest[5] = {0}, counts[5] = {0}, totalsize[5] = {0};
2016     unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0;
2017     sds maxkeys[5] = {0};
2018     char *typename[] = {"string","list","set","hash","zset"};
2019     char *typeunit[] = {"bytes","items","members","fields","members"};
2020     redisReply *reply, *keys;
2021     unsigned int arrsize=0, i;
2022     int type, *types=NULL;
2023     double pct;
2024 
2025     /* Total keys pre scanning */
2026     total_keys = getDbSize();
2027 
2028     /* Status message */
2029     printf("\n# Scanning the entire keyspace to find biggest keys as well as\n");
2030     printf("# average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec\n");
2031     printf("# per 100 SCAN commands (not usually needed).\n\n");
2032 
2033     /* New up sds strings to keep track of overall biggest per type */
2034     for(i=0;i<TYPE_NONE; i++) {
2035         maxkeys[i] = sdsempty();
2036         if(!maxkeys[i]) {
2037             fprintf(stderr, "Failed to allocate memory for largest key names!\n");
2038             exit(1);
2039         }
2040     }
2041 
2042     /* SCAN loop */
2043     do {
2044         /* Calculate approximate percentage completion */
2045         pct = 100 * (double)sampled/total_keys;
2046 
2047         /* Grab some keys and point to the keys array */
2048         reply = sendScan(&it);
2049         keys  = reply->element[1];
2050 
2051         /* Reallocate our type and size array if we need to */
2052         if(keys->elements > arrsize) {
2053             types = zrealloc(types, sizeof(int)*keys->elements);
2054             sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements);
2055 
2056             if(!types || !sizes) {
2057                 fprintf(stderr, "Failed to allocate storage for keys!\n");
2058                 exit(1);
2059             }
2060 
2061             arrsize = keys->elements;
2062         }
2063 
2064         /* Retreive types and then sizes */
2065         getKeyTypes(keys, types);
2066         getKeySizes(keys, types, sizes);
2067 
2068         /* Now update our stats */
2069         for(i=0;i<keys->elements;i++) {
2070             if((type = types[i]) == TYPE_NONE)
2071                 continue;
2072 
2073             totalsize[type] += sizes[i];
2074             counts[type]++;
2075             totlen += keys->element[i]->len;
2076             sampled++;
2077 
2078             if(biggest[type]<sizes[i]) {
2079                 printf(
2080                    "[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
2081                    pct, typename[type], keys->element[i]->str, sizes[i],
2082                    typeunit[type]);
2083 
2084                 /* Keep track of biggest key name for this type */
2085                 maxkeys[type] = sdscpy(maxkeys[type], keys->element[i]->str);
2086                 if(!maxkeys[type]) {
2087                     fprintf(stderr, "Failed to allocate memory for key!\n");
2088                     exit(1);
2089                 }
2090 
2091                 /* Keep track of the biggest size for this type */
2092                 biggest[type] = sizes[i];
2093             }
2094 
2095             /* Update overall progress */
2096             if(sampled % 1000000 == 0) {
2097                 printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
2098             }
2099         }
2100 
2101         /* Sleep if we've been directed to do so */
2102         if(sampled && (sampled %100) == 0 && config.interval) {
2103             usleep(config.interval);
2104         }
2105 
2106         freeReplyObject(reply);
2107     } while(it != 0);
2108 
2109     if(types) zfree(types);
2110     if(sizes) zfree(sizes);
2111 
2112     /* We're done */
2113     printf("\n-------- summary -------\n\n");
2114 
2115     printf("Sampled %llu keys in the keyspace!\n", sampled);
2116     printf("Total key length in bytes is %llu (avg len %.2f)\n\n",
2117        totlen, totlen ? (double)totlen/sampled : 0);
2118 
2119     /* Output the biggest keys we found, for types we did find */
2120     for(i=0;i<TYPE_NONE;i++) {
2121         if(sdslen(maxkeys[i])>0) {
2122             printf("Biggest %6s found '%s' has %llu %s\n", typename[i], maxkeys[i],
2123                biggest[i], typeunit[i]);
2124         }
2125     }
2126 
2127     printf("\n");
2128 
2129     for(i=0;i<TYPE_NONE;i++) {
2130         printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n",
2131            counts[i], typename[i], totalsize[i], typeunit[i],
2132            sampled ? 100 * (double)counts[i]/sampled : 0,
2133            counts[i] ? (double)totalsize[i]/counts[i] : 0);
2134     }
2135 
2136     /* Free sds strings containing max keys */
2137     for(i=0;i<TYPE_NONE;i++) {
2138         sdsfree(maxkeys[i]);
2139     }
2140 
2141     /* Success! */
2142     exit(0);
2143 }
2144 
2145 /*------------------------------------------------------------------------------
2146  * Stats mode
2147  *--------------------------------------------------------------------------- */
2148 
2149 /* Return the specified INFO field from the INFO command output "info".
2150  * A new buffer is allocated for the result, that needs to be free'd.
2151  * If the field is not found NULL is returned. */
2152 static char *getInfoField(char *info, char *field) {
2153     char *p = strstr(info,field);
2154     char *n1, *n2;
2155     char *result;
2156 
2157     if (!p) return NULL;
2158     p += strlen(field)+1;
2159     n1 = strchr(p,'\r');
2160     n2 = strchr(p,',');
2161     if (n2 && n2 < n1) n1 = n2;
2162     result = malloc(sizeof(char)*(n1-p)+1);
2163     memcpy(result,p,(n1-p));
2164     result[n1-p] = '\0';
2165     return result;
2166 }
2167 
2168 /* Like the above function but automatically convert the result into
2169  * a long. On error (missing field) LONG_MIN is returned. */
2170 static long getLongInfoField(char *info, char *field) {
2171     char *value = getInfoField(info,field);
2172     long l;
2173 
2174     if (!value) return LONG_MIN;
2175     l = strtol(value,NULL,10);
2176     free(value);
2177     return l;
2178 }
2179 
2180 /* Convert number of bytes into a human readable string of the form:
2181  * 100B, 2G, 100M, 4K, and so forth. */
2182 void bytesToHuman(char *s, long long n) {
2183     double d;
2184 
2185     if (n < 0) {
2186         *s = '-';
2187         s++;
2188         n = -n;
2189     }
2190     if (n < 1024) {
2191         /* Bytes */
2192         sprintf(s,"%lldB",n);
2193         return;
2194     } else if (n < (1024*1024)) {
2195         d = (double)n/(1024);
2196         sprintf(s,"%.2fK",d);
2197     } else if (n < (1024LL*1024*1024)) {
2198         d = (double)n/(1024*1024);
2199         sprintf(s,"%.2fM",d);
2200     } else if (n < (1024LL*1024*1024*1024)) {
2201         d = (double)n/(1024LL*1024*1024);
2202         sprintf(s,"%.2fG",d);
2203     }
2204 }
2205 
2206 static void statMode(void) {
2207     redisReply *reply;
2208     long aux, requests = 0;
2209     int i = 0;
2210 
2211     while(1) {
2212         char buf[64];
2213         int j;
2214 
2215         reply = reconnectingRedisCommand(context,"INFO");
2216         if (reply->type == REDIS_REPLY_ERROR) {
2217             printf("ERROR: %s\n", reply->str);
2218             exit(1);
2219         }
2220 
2221         if ((i++ % 20) == 0) {
2222             printf(
2223 "------- data ------ --------------------- load -------------------- - child -\n"
2224 "keys       mem      clients blocked requests            connections          \n");
2225         }
2226 
2227         /* Keys */
2228         aux = 0;
2229         for (j = 0; j < 20; j++) {
2230             long k;
2231 
2232             sprintf(buf,"db%d:keys",j);
2233             k = getLongInfoField(reply->str,buf);
2234             if (k == LONG_MIN) continue;
2235             aux += k;
2236         }
2237         sprintf(buf,"%ld",aux);
2238         printf("%-11s",buf);
2239 
2240         /* Used memory */
2241         aux = getLongInfoField(reply->str,"used_memory");
2242         bytesToHuman(buf,aux);
2243         printf("%-8s",buf);
2244 
2245         /* Clients */
2246         aux = getLongInfoField(reply->str,"connected_clients");
2247         sprintf(buf,"%ld",aux);
2248         printf(" %-8s",buf);
2249 
2250         /* Blocked (BLPOPPING) Clients */
2251         aux = getLongInfoField(reply->str,"blocked_clients");
2252         sprintf(buf,"%ld",aux);
2253         printf("%-8s",buf);
2254 
2255         /* Requets */
2256         aux = getLongInfoField(reply->str,"total_commands_processed");
2257         sprintf(buf,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests);
2258         printf("%-19s",buf);
2259         requests = aux;
2260 
2261         /* Connections */
2262         aux = getLongInfoField(reply->str,"total_connections_received");
2263         sprintf(buf,"%ld",aux);
2264         printf(" %-12s",buf);
2265 
2266         /* Children */
2267         aux = getLongInfoField(reply->str,"bgsave_in_progress");
2268         aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1;
2269         aux |= getLongInfoField(reply->str,"loading") << 2;
2270         switch(aux) {
2271         case 0: break;
2272         case 1:
2273             printf("SAVE");
2274             break;
2275         case 2:
2276             printf("AOF");
2277             break;
2278         case 3:
2279             printf("SAVE+AOF");
2280             break;
2281         case 4:
2282             printf("LOAD");
2283             break;
2284         }
2285 
2286         printf("\n");
2287         freeReplyObject(reply);
2288         usleep(config.interval);
2289     }
2290 }
2291 
2292 /*------------------------------------------------------------------------------
2293  * Scan mode
2294  *--------------------------------------------------------------------------- */
2295 
2296 static void scanMode(void) {
2297     redisReply *reply;
2298     unsigned long long cur = 0;
2299 
2300     do {
2301         if (config.pattern)
2302             reply = redisCommand(context,"SCAN %llu MATCH %s",
2303                 cur,config.pattern);
2304         else
2305             reply = redisCommand(context,"SCAN %llu",cur);
2306         if (reply == NULL) {
2307             printf("I/O error\n");
2308             exit(1);
2309         } else if (reply->type == REDIS_REPLY_ERROR) {
2310             printf("ERROR: %s\n", reply->str);
2311             exit(1);
2312         } else {
2313             unsigned int j;
2314 
2315             cur = strtoull(reply->element[0]->str,NULL,10);
2316             for (j = 0; j < reply->element[1]->elements; j++)
2317                 printf("%s\n", reply->element[1]->element[j]->str);
2318         }
2319         freeReplyObject(reply);
2320     } while(cur != 0);
2321 
2322     exit(0);
2323 }
2324 
2325 /*------------------------------------------------------------------------------
2326  * LRU test mode
2327  *--------------------------------------------------------------------------- */
2328 
2329 /* Return an integer from min to max (both inclusive) using a power-law
2330  * distribution, depending on the value of alpha: the greater the alpha
2331  * the more bias towards lower values.
2332  *
2333  * With alpha = 6.2 the output follows the 80-20 rule where 20% of
2334  * the returned numbers will account for 80% of the frequency. */
2335 long long powerLawRand(long long min, long long max, double alpha) {
2336     double pl, r;
2337 
2338     max += 1;
2339     r = ((double)rand()) / RAND_MAX;
2340     pl = pow(
2341         ((pow(max,alpha+1) - pow(min,alpha+1))*r + pow(min,alpha+1)),
2342         (1.0/(alpha+1)));
2343     return (max-1-(long long)pl)+min;
2344 }
2345 
2346 /* Generates a key name among a set of lru_test_sample_size keys, using
2347  * an 80-20 distribution. */
2348 void LRUTestGenKey(char *buf, size_t buflen) {
2349     snprintf(buf, buflen, "lru:%lld\n",
2350         powerLawRand(1, config.lru_test_sample_size, 6.2));
2351 }
2352 
2353 #define LRU_CYCLE_PERIOD 1000 /* 1000 milliseconds. */
2354 #define LRU_CYCLE_PIPELINE_SIZE 250
2355 static void LRUTestMode(void) {
2356     redisReply *reply;
2357     char key[128];
2358     long long start_cycle;
2359     int j;
2360 
2361     srand(time(NULL)^getpid());
2362     while(1) {
2363         /* Perform cycles of 1 second with 50% writes and 50% reads.
2364          * We use pipelining batching writes / reads N times per cycle in order
2365          * to fill the target instance easily. */
2366         start_cycle = mstime();
2367         long long hits = 0, misses = 0;
2368         while(mstime() - start_cycle < 1000) {
2369             /* Write cycle. */
2370             for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
2371                 LRUTestGenKey(key,sizeof(key));
2372                 redisAppendCommand(context, "SET %s val",key);
2373             }
2374             for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++)
2375                 redisGetReply(context, (void**)&reply);
2376 
2377             /* Read cycle. */
2378             for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
2379                 LRUTestGenKey(key,sizeof(key));
2380                 redisAppendCommand(context, "GET %s",key);
2381             }
2382             for (j = 0; j < LRU_CYCLE_PIPELINE_SIZE; j++) {
2383                 if (redisGetReply(context, (void**)&reply) == REDIS_OK) {
2384                     switch(reply->type) {
2385                         case REDIS_REPLY_ERROR:
2386                             printf("%s\n", reply->str);
2387                             break;
2388                         case REDIS_REPLY_NIL:
2389                             misses++;
2390                             break;
2391                         default:
2392                             hits++;
2393                             break;
2394                     }
2395                 }
2396             }
2397 
2398             if (context->err) {
2399                 fprintf(stderr,"I/O error during LRU test\n");
2400                 exit(1);
2401             }
2402         }
2403         /* Print stats. */
2404         printf(
2405             "%lld Gets/sec | Hits: %lld (%.2f%%) | Misses: %lld (%.2f%%)\n",
2406             hits+misses,
2407             hits, (double)hits/(hits+misses)*100,
2408             misses, (double)misses/(hits+misses)*100);
2409     }
2410     exit(0);
2411 }
2412 
2413 /*------------------------------------------------------------------------------
2414  * Intrisic latency mode.
2415  *
2416  * Measure max latency of a running process that does not result from
2417  * syscalls. Basically this software should provide an hint about how much
2418  * time the kernel leaves the process without a chance to run.
2419  *--------------------------------------------------------------------------- */
2420 
2421 /* This is just some computation the compiler can't optimize out.
2422  * Should run in less than 100-200 microseconds even using very
2423  * slow hardware. Runs in less than 10 microseconds in modern HW. */
2424 unsigned long compute_something_fast(void) {
2425     unsigned char s[256], i, j, t;
2426     int count = 1000, k;
2427     unsigned long output = 0;
2428 
2429     for (k = 0; k < 256; k++) s[k] = k;
2430 
2431     i = 0;
2432     j = 0;
2433     while(count--) {
2434         i++;
2435         j = j + s[i];
2436         t = s[i];
2437         s[i] = s[j];
2438         s[j] = t;
2439         output += s[(s[i]+s[j])&255];
2440     }
2441     return output;
2442 }
2443 
2444 static void intrinsicLatencyModeStop(int s) {
2445     UNUSED(s);
2446     force_cancel_loop = 1;
2447 }
2448 
2449 static void intrinsicLatencyMode(void) {
2450     long long test_end, run_time, max_latency = 0, runs = 0;
2451 
2452     run_time = config.intrinsic_latency_duration*1000000;
2453     test_end = ustime() + run_time;
2454     signal(SIGINT, intrinsicLatencyModeStop);
2455 
2456     while(1) {
2457         long long start, end, latency;
2458 
2459         start = ustime();
2460         compute_something_fast();
2461         end = ustime();
2462         latency = end-start;
2463         runs++;
2464         if (latency <= 0) continue;
2465 
2466         /* Reporting */
2467         if (latency > max_latency) {
2468             max_latency = latency;
2469             printf("Max latency so far: %lld microseconds.\n", max_latency);
2470         }
2471 
2472         double avg_us = (double)run_time/runs;
2473         double avg_ns = avg_us * 10e3;
2474         if (force_cancel_loop || end > test_end) {
2475             printf("\n%lld total runs "
2476                 "(avg latency: "
2477                 "%.4f microseconds / %.2f nanoseconds per run).\n",
2478                 runs, avg_us, avg_ns);
2479             printf("Worst run took %.0fx longer than the average latency.\n",
2480                 max_latency / avg_us);
2481             exit(0);
2482         }
2483     }
2484 }
2485 
2486 /*------------------------------------------------------------------------------
2487  * Program main()
2488  *--------------------------------------------------------------------------- */
2489 
2490 int main(int argc, char **argv) {
2491     int firstarg;
2492 
2493     config.hostip = sdsnew("127.0.0.1");
2494     config.hostport = 6379;
2495     config.hostsocket = NULL;
2496     config.repeat = 1;
2497     config.interval = 0;
2498     config.dbnum = 0;
2499     config.interactive = 0;
2500     config.shutdown = 0;
2501     config.monitor_mode = 0;
2502     config.pubsub_mode = 0;
2503     config.latency_mode = 0;
2504     config.latency_dist_mode = 0;
2505     config.latency_history = 0;
2506     config.lru_test_mode = 0;
2507     config.lru_test_sample_size = 0;
2508     config.cluster_mode = 0;
2509     config.slave_mode = 0;
2510     config.getrdb_mode = 0;
2511     config.stat_mode = 0;
2512     config.scan_mode = 0;
2513     config.intrinsic_latency_mode = 0;
2514     config.pattern = NULL;
2515     config.rdb_filename = NULL;
2516     config.pipe_mode = 0;
2517     config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
2518     config.bigkeys = 0;
2519     config.stdinarg = 0;
2520     config.auth = NULL;
2521     config.eval = NULL;
2522     config.eval_ldb = 0;
2523     config.eval_ldb_end = 0;
2524     config.eval_ldb_sync = 0;
2525     config.enable_ldb_on_eval = 0;
2526     config.last_cmd_type = -1;
2527 
2528     pref.hints = 1;
2529 
2530     spectrum_palette = spectrum_palette_color;
2531     spectrum_palette_size = spectrum_palette_color_size;
2532 
2533     if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL))
2534         config.output = OUTPUT_RAW;
2535     else
2536         config.output = OUTPUT_STANDARD;
2537     config.mb_delim = sdsnew("\n");
2538     cliInitHelp();
2539 
2540     firstarg = parseOptions(argc,argv);
2541     argc -= firstarg;
2542     argv += firstarg;
2543 
2544     /* Latency mode */
2545     if (config.latency_mode) {
2546         if (cliConnect(0) == REDIS_ERR) exit(1);
2547         latencyMode();
2548     }
2549 
2550     /* Latency distribution mode */
2551     if (config.latency_dist_mode) {
2552         if (cliConnect(0) == REDIS_ERR) exit(1);
2553         latencyDistMode();
2554     }
2555 
2556     /* Slave mode */
2557     if (config.slave_mode) {
2558         if (cliConnect(0) == REDIS_ERR) exit(1);
2559         slaveMode();
2560     }
2561 
2562     /* Get RDB mode. */
2563     if (config.getrdb_mode) {
2564         if (cliConnect(0) == REDIS_ERR) exit(1);
2565         getRDB();
2566     }
2567 
2568     /* Pipe mode */
2569     if (config.pipe_mode) {
2570         if (cliConnect(0) == REDIS_ERR) exit(1);
2571         pipeMode();
2572     }
2573 
2574     /* Find big keys */
2575     if (config.bigkeys) {
2576         if (cliConnect(0) == REDIS_ERR) exit(1);
2577         findBigKeys();
2578     }
2579 
2580     /* Stat mode */
2581     if (config.stat_mode) {
2582         if (cliConnect(0) == REDIS_ERR) exit(1);
2583         if (config.interval == 0) config.interval = 1000000;
2584         statMode();
2585     }
2586 
2587     /* Scan mode */
2588     if (config.scan_mode) {
2589         if (cliConnect(0) == REDIS_ERR) exit(1);
2590         scanMode();
2591     }
2592 
2593     /* LRU test mode */
2594     if (config.lru_test_mode) {
2595         if (cliConnect(0) == REDIS_ERR) exit(1);
2596         LRUTestMode();
2597     }
2598 
2599     /* Intrinsic latency mode */
2600     if (config.intrinsic_latency_mode) intrinsicLatencyMode();
2601 
2602     /* Start interactive mode when no command is provided */
2603     if (argc == 0 && !config.eval) {
2604         /* Ignore SIGPIPE in interactive mode to force a reconnect */
2605         signal(SIGPIPE, SIG_IGN);
2606 
2607         /* Note that in repl mode we don't abort on connection error.
2608          * A new attempt will be performed for every command send. */
2609         cliConnect(0);
2610         repl();
2611     }
2612 
2613     /* Otherwise, we have some arguments to execute */
2614     if (cliConnect(0) != REDIS_OK) exit(1);
2615     if (config.eval) {
2616         return evalMode(argc,argv);
2617     } else {
2618         return noninteractive(argc,convertToSds(argc,argv));
2619     }
2620 }
2621