text: /* Redis CLI (command line interface)
   *
   * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   *   * Redistributions of source code must retain the above copyright notice,
   *     this list of conditions and the following disclaimer.
   *   * Redistributions in binary form must reproduce the above copyright
   *     notice, this list of conditions and the following disclaimer in the
   *     documentation and/or other materials provided with the distribution.
   *   * Neither the name of Redis nor the names of its contributors may be used
   *     to endorse or promote products derived from this software without
   *     specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   * POSSIBILITY OF SUCH DAMAGE.
   */
  
  #include "fmacros.h"
  #include "version.h"
  
  #include <stdio.h>
  #include <string.h>
  #include <stdlib.h>
  #include <signal.h>
  #include <unistd.h>
  #include <time.h>
  #include <ctype.h>
  #include <errno.h>
  #include <sys/stat.h>
  #include <sys/time.h>
  #include <assert.h>
  #include <fcntl.h>
  #include <limits.h>
  
  #include "hiredis.h"
  #include "sds.h"
  #include "zmalloc.h"
  #include "linenoise.h"
  #include "help.h"
  #include "anet.h"
  #include "ae.h"
  
  #define REDIS_NOTUSED(V) ((void) V)
  
  #define OUTPUT_STANDARD 0
  #define OUTPUT_RAW 1
  #define OUTPUT_CSV 2
  #define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */
  #define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */
  
  static redisContext *context;
  static struct config {
      char *hostip;
      int hostport;
      char *hostsocket;
      long repeat;
      long interval;
      int dbnum;
      int interactive;
      int shutdown;
      int monitor_mode;
      int pubsub_mode;
      int latency_mode;
      int latency_history;
      int cluster_mode;
      int cluster_reissue_command;
      int slave_mode;
      int pipe_mode;
      int pipe_timeout;
      int getrdb_mode;
      int stat_mode;
      int scan_mode;
      int intrinsic_latency_mode;
      int intrinsic_latency_duration;
      char *pattern;
      char *rdb_filename;
      int bigkeys;
      int stdinarg; /* get last arg from stdin. (-x option) */
      char *auth;
      int output; /* output mode, see OUTPUT_* defines */
      sds mb_delim;
      char prompt[128];
      char *eval;
      int last_cmd_type;
  } config;
  
  static volatile sig_atomic_t force_cancel_loop = 0;
  static void usage(void);
  static void slaveMode(void);
  char *redisGitSHA1(void);
  char *redisGitDirty(void);
  
  /*------------------------------------------------------------------------------
   * Utility functions
   *--------------------------------------------------------------------------- */
  
  static long long ustime(void) {
      struct timeval tv;
      long long ust;
  
      gettimeofday(&tv, NULL);
      ust = ((long long)tv.tv_sec)*1000000;
      ust += tv.tv_usec;
      return ust;
  }
  
  static long long mstime(void) {
      return ustime()/1000;
  }
  
  static void cliRefreshPrompt(void) {
      int len;
  
      if (config.hostsocket != NULL)
          len = snprintf(config.prompt,sizeof(config.prompt),"redis %s",
                         config.hostsocket);
      else
          len = snprintf(config.prompt,sizeof(config.prompt),
                         strchr(config.hostip,':') ? "[%s]:%d" : "%s:%d",
                         config.hostip, config.hostport);
      /* Add [dbnum] if needed */
      if (config.dbnum != 0 && config.last_cmd_type != REDIS_REPLY_ERROR)
          len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]",
              config.dbnum);
      snprintf(config.prompt+len,sizeof(config.prompt)-len,"> ");
  }
  
  /*------------------------------------------------------------------------------
   * Help functions
   *--------------------------------------------------------------------------- */
  
  #define CLI_HELP_COMMAND 1
  #define CLI_HELP_GROUP 2
  
  typedef struct {
      int type;
      int argc;
      sds *argv;
      sds full;
  
      /* Only used for help on commands */
      struct commandHelp *org;
  } helpEntry;
  
  static helpEntry *helpEntries;
  static int helpEntriesLen;
  
  static sds cliVersion(void) {
      sds version;
      version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);
  
      /* Add git commit and working tree status when available */
      if (strtoll(redisGitSHA1(),NULL,16)) {
          version = sdscatprintf(version, " (git:%s", redisGitSHA1());
          if (strtoll(redisGitDirty(),NULL,10))
              version = sdscatprintf(version, "-dirty");
          version = sdscat(version, ")");
      }
      return version;
  }
  
  static void cliInitHelp(void) {
      int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
      int groupslen = sizeof(commandGroups)/sizeof(char*);
      int i, len, pos = 0;
      helpEntry tmp;
  
      helpEntriesLen = len = commandslen+groupslen;
      helpEntries = malloc(sizeof(helpEntry)*len);
  
      for (i = 0; i < groupslen; i++) {
          tmp.argc = 1;
          tmp.argv = malloc(sizeof(sds));
          tmp.argv[0] = sdscatprintf(sdsempty(),"@%s",commandGroups[i]);
          tmp.full = tmp.argv[0];
          tmp.type = CLI_HELP_GROUP;
          tmp.org = NULL;
          helpEntries[pos++] = tmp;
      }
  
      for (i = 0; i < commandslen; i++) {
          tmp.argv = sdssplitargs(commandHelp[i].name,&tmp.argc);
          tmp.full = sdsnew(commandHelp[i].name);
          tmp.type = CLI_HELP_COMMAND;
          tmp.org = &commandHelp[i];
          helpEntries[pos++] = tmp;
      }
  }
  
  /* Output command help to stdout. */
  static void cliOutputCommandHelp(struct commandHelp *help, int group) {
      printf("\r\n  \x1b[1m%s\x1b[0m \x1b[90m%s\x1b[0m\r\n", help->name, help->params);
      printf("  \x1b[33msummary:\x1b[0m %s\r\n", help->summary);
      printf("  \x1b[33msince:\x1b[0m %s\r\n", help->since);
      if (group) {
          printf("  \x1b[33mgroup:\x1b[0m %s\r\n", commandGroups[help->group]);
      }
  }
  
  /* Print generic help. */
  static void cliOutputGenericHelp(void) {
      sds version = cliVersion();
      printf(
          "redis-cli %s\r\n"
          "Type: \"help @<group>\" to get a list of commands in <group>\r\n"
          "      \"help <command>\" for help on <command>\r\n"
          "      \"help <tab>\" to get a list of possible help topics\r\n"
          "      \"quit\" to exit\r\n",
          version
      );
      sdsfree(version);
  }
  
  /* Output all command help, filtering by group or command name. */
  static void cliOutputHelp(int argc, char **argv) {
      int i, j, len;
      int group = -1;
      helpEntry *entry;
      struct commandHelp *help;
  
      if (argc == 0) {
          cliOutputGenericHelp();
          return;
      } else if (argc > 0 && argv[0][0] == '@') {
          len = sizeof(commandGroups)/sizeof(char*);
          for (i = 0; i < len; i++) {
              if (strcasecmp(argv[0]+1,commandGroups[i]) == 0) {
                  group = i;
                  break;
              }
          }
      }
  
      assert(argc > 0);
      for (i = 0; i < helpEntriesLen; i++) {
          entry = &helpEntries[i];
          if (entry->type != CLI_HELP_COMMAND) continue;
  
          help = entry->org;
          if (group == -1) {
              /* Compare all arguments */
              if (argc == entry->argc) {
                  for (j = 0; j < argc; j++) {
                      if (strcasecmp(argv[j],entry->argv[j]) != 0) break;
                  }
                  if (j == argc) {
                      cliOutputCommandHelp(help,1);
                  }
              }
          } else {
              if (group == help->group) {
                  cliOutputCommandHelp(help,0);
              }
          }
      }
      printf("\r\n");
  }
  
  static void completionCallback(const char *buf, linenoiseCompletions *lc) {
      size_t startpos = 0;
      int mask;
      int i;
      size_t matchlen;
      sds tmp;
  
      if (strncasecmp(buf,"help ",5) == 0) {
          startpos = 5;
          while (isspace(buf[startpos])) startpos++;
          mask = CLI_HELP_COMMAND | CLI_HELP_GROUP;
      } else {
          mask = CLI_HELP_COMMAND;
      }
  
      for (i = 0; i < helpEntriesLen; i++) {
          if (!(helpEntries[i].type & mask)) continue;
  
          matchlen = strlen(buf+startpos);
          if (strncasecmp(buf+startpos,helpEntries[i].full,matchlen) == 0) {
              tmp = sdsnewlen(buf,startpos);
              tmp = sdscat(tmp,helpEntries[i].full);
              linenoiseAddCompletion(lc,tmp);
              sdsfree(tmp);
          }
      }
  }
  
  /*------------------------------------------------------------------------------
   * Networking / parsing
   *--------------------------------------------------------------------------- */
  
  /* Send AUTH command to the server */
  static int cliAuth() {
      redisReply *reply;
      if (config.auth == NULL) return REDIS_OK;
  
      reply = redisCommand(context,"AUTH %s",config.auth);
      if (reply != NULL) {
          freeReplyObject(reply);
          return REDIS_OK;
      }
      return REDIS_ERR;
  }
  
  /* Send SELECT dbnum to the server */
  static int cliSelect() {
      redisReply *reply;
      if (config.dbnum == 0) return REDIS_OK;
  
      reply = redisCommand(context,"SELECT %d",config.dbnum);
      if (reply != NULL) {
          int result = REDIS_OK;
          if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
          freeReplyObject(reply);
          return result;
      }
      return REDIS_ERR;
  }
  
  /* Connect to the server. If force is not zero the connection is performed
   * even if there is already a connected socket. */
  static int cliConnect(int force) {
      if (context == NULL || force) {
          if (context != NULL)
              redisFree(context);
  
          if (config.hostsocket == NULL) {
              context = redisConnect(config.hostip,config.hostport);
          } else {
              context = redisConnectUnix(config.hostsocket);
          }
  
          if (context->err) {
              fprintf(stderr,"Could not connect to Redis at ");
              if (config.hostsocket == NULL)
                  fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr);
              else
                  fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr);
              redisFree(context);
              context = NULL;
              return REDIS_ERR;
          }
  
          /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
           * in order to prevent timeouts caused by the execution of long
           * commands. At the same time this improves the detection of real
           * errors. */
          anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
  
          /* Do AUTH and select the right DB. */
          if (cliAuth() != REDIS_OK)
              return REDIS_ERR;
          if (cliSelect() != REDIS_OK)
              return REDIS_ERR;
      }
      return REDIS_OK;
  }
  
  static void cliPrintContextError(void) {
      if (context == NULL) return;
      fprintf(stderr,"Error: %s\n",context->errstr);
  }
  
  static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
      sds out = sdsempty();
      switch (r->type) {
      case REDIS_REPLY_ERROR:
          out = sdscatprintf(out,"(error) %s\n", r->str);
      break;
      case REDIS_REPLY_STATUS:
          out = sdscat(out,r->str);
          out = sdscat(out,"\n");
      break;
      case REDIS_REPLY_INTEGER:
          out = sdscatprintf(out,"(integer) %lld\n",r->integer);
      break;
      case REDIS_REPLY_STRING:
          /* If you are producing output for the standard output we want
          * a more interesting output with quoted characters and so forth */
          out = sdscatrepr(out,r->str,r->len);
          out = sdscat(out,"\n");
      break;
      case REDIS_REPLY_NIL:
          out = sdscat(out,"(nil)\n");
      break;
      case REDIS_REPLY_ARRAY:
          if (r->elements == 0) {
              out = sdscat(out,"(empty list or set)\n");
          } else {
              unsigned int i, idxlen = 0;
              char _prefixlen[16];
              char _prefixfmt[16];
              sds _prefix;
              sds tmp;
  
              /* Calculate chars needed to represent the largest index */
              i = r->elements;
              do {
                  idxlen++;
                  i /= 10;
              } while(i);
  
              /* Prefix for nested multi bulks should grow with idxlen+2 spaces */
              memset(_prefixlen,' ',idxlen+2);
              _prefixlen[idxlen+2] = '\0';
              _prefix = sdscat(sdsnew(prefix),_prefixlen);
  
              /* Setup prefix format for every entry */
              snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%dd) ",idxlen);
  
              for (i = 0; i < r->elements; i++) {
                  /* Don't use the prefix for the first element, as the parent
                   * caller already prepended the index number. */
                  out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);
  
                  /* Format the multi bulk entry */
                  tmp = cliFormatReplyTTY(r->element[i],_prefix);
                  out = sdscatlen(out,tmp,sdslen(tmp));
                  sdsfree(tmp);
              }
              sdsfree(_prefix);
          }
      break;
      default:
          fprintf(stderr,"Unknown reply type: %d\n", r->type);
          exit(1);
      }
      return out;
  }
  
  static sds cliFormatReplyRaw(redisReply *r) {
      sds out = sdsempty(), tmp;
      size_t i;
  
      switch (r->type) {
      case REDIS_REPLY_NIL:
          /* Nothing... */
          break;
      case REDIS_REPLY_ERROR:
          out = sdscatlen(out,r->str,r->len);
          out = sdscatlen(out,"\n",1);
          break;
      case REDIS_REPLY_STATUS:
      case REDIS_REPLY_STRING:
          out = sdscatlen(out,r->str,r->len);
          break;
      case REDIS_REPLY_INTEGER:
          out = sdscatprintf(out,"%lld",r->integer);
          break;
      case REDIS_REPLY_ARRAY:
          for (i = 0; i < r->elements; i++) {
              if (i > 0) out = sdscat(out,config.mb_delim);
              tmp = cliFormatReplyRaw(r->element[i]);
              out = sdscatlen(out,tmp,sdslen(tmp));
              sdsfree(tmp);
          }
          break;
      default:
          fprintf(stderr,"Unknown reply type: %d\n", r->type);
          exit(1);
      }
      return out;
  }
  
  static sds cliFormatReplyCSV(redisReply *r) {
      unsigned int i;
  
      sds out = sdsempty();
      switch (r->type) {
      case REDIS_REPLY_ERROR:
          out = sdscat(out,"ERROR,");
          out = sdscatrepr(out,r->str,strlen(r->str));
      break;
      case REDIS_REPLY_STATUS:
          out = sdscatrepr(out,r->str,r->len);
      break;
      case REDIS_REPLY_INTEGER:
          out = sdscatprintf(out,"%lld",r->integer);
      break;
      case REDIS_REPLY_STRING:
          out = sdscatrepr(out,r->str,r->len);
      break;
      case REDIS_REPLY_NIL:
          out = sdscat(out,"NIL\n");
      break;
      case REDIS_REPLY_ARRAY:
          for (i = 0; i < r->elements; i++) {
              sds tmp = cliFormatReplyCSV(r->element[i]);
              out = sdscatlen(out,tmp,sdslen(tmp));
              if (i != r->elements-1) out = sdscat(out,",");
              sdsfree(tmp);
          }
      break;
      default:
          fprintf(stderr,"Unknown reply type: %d\n", r->type);
          exit(1);
      }
      return out;
  }
  
  static int cliReadReply(int output_raw_strings) {
      void *_reply;
      redisReply *reply;
      sds out = NULL;
      int output = 1;
  
      if (redisGetReply(context,&_reply) != REDIS_OK) {
          if (config.shutdown) {
              redisFree(context);
              context = NULL;
              return REDIS_OK;
          }
          if (config.interactive) {
              /* Filter cases where we should reconnect */
              if (context->err == REDIS_ERR_IO && errno == ECONNRESET)
                  return REDIS_ERR;
              if (context->err == REDIS_ERR_EOF)
                  return REDIS_ERR;
          }
          cliPrintContextError();
          exit(1);
          return REDIS_ERR; /* avoid compiler warning */
      }
  
      reply = (redisReply*)_reply;
  
      config.last_cmd_type = reply->type;
  
      /* Check if we need to connect to a different node and reissue the
       * request. */
      if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR &&
          (!strncmp(reply->str,"MOVED",5) || !strcmp(reply->str,"ASK")))
      {
          char *p = reply->str, *s;
          int slot;
  
          output = 0;
          /* Comments show the position of the pointer as:
           *
           * [S] for pointer 's'
           * [P] for pointer 'p'
           */
          s = strchr(p,' ');      /* MOVED[S]3999 127.0.0.1:6381 */
          p = strchr(s+1,' ');    /* MOVED[S]3999[P]127.0.0.1:6381 */
          *p = '\0';
          slot = atoi(s+1);
          s = strchr(p+1,':');    /* MOVED 3999[P]127.0.0.1[S]6381 */
          *s = '\0';
          sdsfree(config.hostip);
          config.hostip = sdsnew(p+1);
          config.hostport = atoi(s+1);
          if (config.interactive)
              printf("-> Redirected to slot [%d] located at %s:%d\n",
                  slot, config.hostip, config.hostport);
          config.cluster_reissue_command = 1;
          cliRefreshPrompt();
      }
  
      if (output) {
          if (output_raw_strings) {
              out = cliFormatReplyRaw(reply);
          } else {
              if (config.output == OUTPUT_RAW) {
                  out = cliFormatReplyRaw(reply);
                  out = sdscat(out,"\n");
              } else if (config.output == OUTPUT_STANDARD) {
                  out = cliFormatReplyTTY(reply,"");
              } else if (config.output == OUTPUT_CSV) {
                  out = cliFormatReplyCSV(reply);
                  out = sdscat(out,"\n");
              }
          }
          fwrite(out,sdslen(out),1,stdout);
          sdsfree(out);
      }
      freeReplyObject(reply);
      return REDIS_OK;
  }
  
  static int cliSendCommand(int argc, char **argv, int repeat) {
      char *command = argv[0];
      size_t *argvlen;
      int j, output_raw;
  
      if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
          cliOutputHelp(--argc, ++argv);
          return REDIS_OK;
      }
  
      if (context == NULL) return REDIS_ERR;
  
      output_raw = 0;
      if (!strcasecmp(command,"info") ||
          (argc == 2 && !strcasecmp(command,"cluster") &&
                        (!strcasecmp(argv[1],"nodes") ||
                         !strcasecmp(argv[1],"info"))) ||
          (argc == 2 && !strcasecmp(command,"client") &&
                         !strcasecmp(argv[1],"list")) ||
          (argc == 3 && !strcasecmp(command,"latency") &&
                         !strcasecmp(argv[1],"graph")) ||
          (argc == 2 && !strcasecmp(command,"latency") &&
                         !strcasecmp(argv[1],"doctor")))
      {
          output_raw = 1;
      }
  
      if (!strcasecmp(command,"shutdown")) config.shutdown = 1;
      if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
      if (!strcasecmp(command,"subscribe") ||
          !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
      if (!strcasecmp(command,"sync") ||
          !strcasecmp(command,"psync")) config.slave_mode = 1;
  
      /* Setup argument length */
      argvlen = malloc(argc*sizeof(size_t));
      for (j = 0; j < argc; j++)
          argvlen[j] = sdslen(argv[j]);
  
      while(repeat--) {
          redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
          while (config.monitor_mode) {
              if (cliReadReply(output_raw) != REDIS_OK) exit(1);
              fflush(stdout);
          }
  
          if (config.pubsub_mode) {
              if (config.output != OUTPUT_RAW)
                  printf("Reading messages... (press Ctrl-C to quit)\n");
              while (1) {
                  if (cliReadReply(output_raw) != REDIS_OK) exit(1);
              }
          }
  
          if (config.slave_mode) {
              printf("Entering slave output mode...  (press Ctrl-C to quit)\n");
              slaveMode();
              config.slave_mode = 0;
              free(argvlen);
              return REDIS_ERR;  /* Error = slaveMode lost connection to master */
          }
  
          if (cliReadReply(output_raw) != REDIS_OK) {
              free(argvlen);
              return REDIS_ERR;
          } else {
              /* Store database number when SELECT was successfully executed. */
              if (!strcasecmp(command,"select") && argc == 2) {
                  config.dbnum = atoi(argv[1]);
                  cliRefreshPrompt();
              } else if (!strcasecmp(command,"auth") && argc == 2) {
                  cliSelect();
              }
          }
          if (config.interval) usleep(config.interval);
          fflush(stdout); /* Make it grep friendly */
      }
  
      free(argvlen);
      return REDIS_OK;
  }
  
  /* Send the INFO command, reconnecting the link if needed. */
  static redisReply *reconnectingInfo(void) {
      redisContext *c = context;
      redisReply *reply = NULL;
      int tries = 0;
  
      assert(!c->err);
      while(reply == NULL) {
          while (c->err & (REDIS_ERR_IO | REDIS_ERR_EOF)) {
              printf("Reconnecting (%d)...\r", ++tries);
              fflush(stdout);
  
              redisFree(c);
              c = redisConnect(config.hostip,config.hostport);
              usleep(1000000);
          }
  
          reply = redisCommand(c,"INFO");
          if (c->err && !(c->err & (REDIS_ERR_IO | REDIS_ERR_EOF))) {
              fprintf(stderr, "Error: %s\n", c->errstr);
              exit(1);
          } else if (tries > 0) {
              printf("\n");
          }
      }
  
      context = c;
      return reply;
  }
  
  /*------------------------------------------------------------------------------
   * User interface
   *--------------------------------------------------------------------------- */
  
  static int parseOptions(int argc, char **argv) {
      int i;
  
      for (i = 1; i < argc; i++) {
          int lastarg = i==argc-1;
  
          if (!strcmp(argv[i],"-h") && !lastarg) {
              sdsfree(config.hostip);
              config.hostip = sdsnew(argv[++i]);
          } else if (!strcmp(argv[i],"-h") && lastarg) {
              usage();
          } else if (!strcmp(argv[i],"--help")) {
              usage();
          } else if (!strcmp(argv[i],"-x")) {
              config.stdinarg = 1;
          } else if (!strcmp(argv[i],"-p") && !lastarg) {
              config.hostport = atoi(argv[++i]);
          } else if (!strcmp(argv[i],"-s") && !lastarg) {
              config.hostsocket = argv[++i];
          } else if (!strcmp(argv[i],"-r") && !lastarg) {
              config.repeat = strtoll(argv[++i],NULL,10);
          } else if (!strcmp(argv[i],"-i") && !lastarg) {
              double seconds = atof(argv[++i]);
              config.interval = seconds*1000000;
          } else if (!strcmp(argv[i],"-n") && !lastarg) {
              config.dbnum = atoi(argv[++i]);
          } else if (!strcmp(argv[i],"-a") && !lastarg) {
              config.auth = argv[++i];
          } else if (!strcmp(argv[i],"--raw")) {
              config.output = OUTPUT_RAW;
          } else if (!strcmp(argv[i],"--no-raw")) {
              config.output = OUTPUT_STANDARD;
          } else if (!strcmp(argv[i],"--csv")) {
              config.output = OUTPUT_CSV;
          } else if (!strcmp(argv[i],"--latency")) {
              config.latency_mode = 1;
          } else if (!strcmp(argv[i],"--latency-history")) {
              config.latency_mode = 1;
              config.latency_history = 1;
          } else if (!strcmp(argv[i],"--slave")) {
              config.slave_mode = 1;
          } else if (!strcmp(argv[i],"--stat")) {
              config.stat_mode = 1;
          } else if (!strcmp(argv[i],"--scan")) {
              config.scan_mode = 1;
          } else if (!strcmp(argv[i],"--pattern") && !lastarg) {
              config.pattern = argv[++i];
          } else if (!strcmp(argv[i],"--intrinsic-latency") && !lastarg) {
              config.intrinsic_latency_mode = 1;
              config.intrinsic_latency_duration = atoi(argv[++i]);
          } else if (!strcmp(argv[i],"--rdb") && !lastarg) {
              config.getrdb_mode = 1;
              config.rdb_filename = argv[++i];
          } else if (!strcmp(argv[i],"--pipe")) {
              config.pipe_mode = 1;
          } else if (!strcmp(argv[i],"--pipe-timeout") && !lastarg) {
              config.pipe_timeout = atoi(argv[++i]);
          } else if (!strcmp(argv[i],"--bigkeys")) {
              config.bigkeys = 1;
          } else if (!strcmp(argv[i],"--eval") && !lastarg) {
              config.eval = argv[++i];
          } else if (!strcmp(argv[i],"-c")) {
              config.cluster_mode = 1;
          } else if (!strcmp(argv[i],"-d") && !lastarg) {
              sdsfree(config.mb_delim);
              config.mb_delim = sdsnew(argv[++i]);
          } else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
              sds version = cliVersion();
              printf("redis-cli %s\n", version);
              sdsfree(version);
              exit(0);
          } else {
              if (argv[i][0] == '-') {
                  fprintf(stderr,
                      "Unrecognized option or bad number of args for: '%s'\n",
                      argv[i]);
                  exit(1);
              } else {
                  /* Likely the command name, stop here. */
                  break;
              }
          }
      }
      return i;
  }
  
  static sds readArgFromStdin(void) {
      char buf[1024];
      sds arg = sdsempty();
  
      while(1) {
          int nread = read(fileno(stdin),buf,1024);
  
          if (nread == 0) break;
          else if (nread == -1) {
              perror("Reading from standard input");
              exit(1);
          }
          arg = sdscatlen(arg,buf,nread);
      }
      return arg;
  }
  
  static void usage(void) {
      sds version = cliVersion();
      fprintf(stderr,
  "redis-cli %s\n"
  "\n"
  "Usage: redis-cli [OPTIONS] [cmd [arg [arg ...]]]\n"
  "  -h <hostname>      Server hostname (default: 127.0.0.1).\n"
  "  -p <port>          Server port (default: 6379).\n"
  "  -s <socket>        Server socket (overrides hostname and port).\n"
  "  -a <password>      Password to use when connecting to the server.\n"
  "  -r <repeat>        Execute specified command N times.\n"
  "  -i <interval>      When -r is used, waits <interval> seconds per command.\n"
  "                     It is possible to specify sub-second times like -i 0.1.\n"
  "  -n <db>            Database number.\n"
  "  -x                 Read last argument from STDIN.\n"
  "  -d <delimiter>     Multi-bulk delimiter in for raw formatting (default: \\n).\n"
  "  -c                 Enable cluster mode (follow -ASK and -MOVED redirections).\n"
  "  --raw              Use raw formatting for replies (default when STDOUT is\n"
  "                     not a tty).\n"
  "  --no-raw           Force formatted output even when STDOUT is not a tty.\n"
  "  --csv              Output in CSV format.\n"
  "  --latency          Enter a special mode continuously sampling latency.\n"
  "  --latency-history  Like --latency but tracking latency changes over time.\n"
  "                     Default time interval is 15 sec. Change it using -i.\n"
  "  --slave            Simulate a slave showing commands received from the master.\n"
  "  --rdb <filename>   Transfer an RDB dump from remote server to local file.\n"
  "  --pipe             Transfer raw Redis protocol from stdin to server.\n"
  "  --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
  "                     no reply is received within <n> seconds.\n"
  "                     Default timeout: %d. Use 0 to wait forever.\n"
  "  --bigkeys          Sample Redis keys looking for big keys.\n"
  "  --scan             List all keys using the SCAN command.\n"
  "  --pattern <pat>    Useful with --scan to specify a SCAN pattern.\n"
  "  --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
  "                     The test will run for the specified amount of seconds.\n"
  "  --eval <file>      Send an EVAL command using the Lua script at <file>.\n"
  "  --help             Output this help and exit.\n"
  "  --version          Output version and exit.\n"
  "\n"
  "Examples:\n"
  "  cat /etc/passwd | redis-cli -x set mypasswd\n"
  "  redis-cli get mypasswd\n"
  "  redis-cli -r 100 lpush mylist x\n"
  "  redis-cli -r 100 -i 1 info | grep used_memory_human:\n"
  "  redis-cli --eval myscript.lua key1 key2 , arg1 arg2 arg3\n"
  "  redis-cli --scan --pattern '*:12345*'\n"
  "\n"
  "  (Note: when using --eval the comma separates KEYS[] from ARGV[] items)\n"
  "\n"
  "When no command is given, redis-cli starts in interactive mode.\n"
  "Type \"help\" in interactive mode for information on available commands.\n"
  "\n",
          version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
      sdsfree(version);
      exit(1);
  }
  
  /* Turn the plain C strings into Sds strings */
  static char **convertToSds(int count, char** args) {
    int j;
    char **sds = zmalloc(sizeof(char*)*count);
  
    for(j = 0; j < count; j++)
      sds[j] = sdsnew(args[j]);
  
    return sds;
  }
  
  static void repl(void) {
      sds historyfile = NULL;
      int history = 0;
      char *line;
      int argc;
      sds *argv;
  
      config.interactive = 1;
      linenoiseSetMultiLine(1);
      linenoiseSetCompletionCallback(completionCallback);
  
      /* Only use history when stdin is a tty. */
      if (isatty(fileno(stdin))) {
          history = 1;
  
          if (getenv("HOME") != NULL) {
              historyfile = sdscatprintf(sdsempty(),"%s/.rediscli_history",getenv("HOME"));
              linenoiseHistoryLoad(historyfile);
          }
      }
  
      cliRefreshPrompt();
      while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
          if (line[0] != '\0') {
              argv = sdssplitargs(line,&argc);
              if (history) linenoiseHistoryAdd(line);
              if (historyfile) linenoiseHistorySave(historyfile);
  
              if (argv == NULL) {
                  printf("Invalid argument(s)\n");
                  free(line);
                  continue;
              } else if (argc > 0) {
                  if (strcasecmp(argv[0],"quit") == 0 ||
                      strcasecmp(argv[0],"exit") == 0)
                  {
                      exit(0);
                  } else if (argc == 3 && !strcasecmp(argv[0],"connect")) {
                      sdsfree(config.hostip);
                      config.hostip = sdsnew(argv[1]);
                      config.hostport = atoi(argv[2]);
                      cliRefreshPrompt();
                      cliConnect(1);
                  } else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
                      linenoiseClearScreen();
                  } else {
                      long long start_time = mstime(), elapsed;
                      int repeat, skipargs = 0;
  
                      repeat = atoi(argv[0]);
                      if (argc > 1 && repeat) {
                          skipargs = 1;
                      } else {
                          repeat = 1;
                      }
  
                      while (1) {
                          config.cluster_reissue_command = 0;
                          if (cliSendCommand(argc-skipargs,argv+skipargs,repeat)
                              != REDIS_OK)
                          {
                              cliConnect(1);
  
                              /* If we still cannot send the command print error.
                               * We'll try to reconnect the next time. */
                              if (cliSendCommand(argc-skipargs,argv+skipargs,repeat)
                                  != REDIS_OK)
                                  cliPrintContextError();
                          }
                          /* Issue the command again if we got redirected in cluster mode */
                          if (config.cluster_mode && config.cluster_reissue_command) {
                              cliConnect(1);
                          } else {
                              break;
                          }
                      }
                      elapsed = mstime()-start_time;
                      if (elapsed >= 500) {
                          printf("(%.2fs)\n",(double)elapsed/1000);
                      }
                  }
              }
              /* Free the argument vector */
              sdsfreesplitres(argv,argc);
          }
          /* linenoise() returns malloc-ed lines like readline() */
          free(line);
      }
      exit(0);
  }
  
  static int noninteractive(int argc, char **argv) {
      int retval = 0;
      if (config.stdinarg) {
          argv = zrealloc(argv, (argc+1)*sizeof(char*));
          argv[argc] = readArgFromStdin();
          retval = cliSendCommand(argc+1, argv, config.repeat);
      } else {
          /* stdin is probably a tty, can be tested with S_ISCHR(s.st_mode) */
          retval = cliSendCommand(argc, argv, config.repeat);
      }
      return retval;
  }
  
  /*------------------------------------------------------------------------------
   * Eval mode
   *--------------------------------------------------------------------------- */
  
  static int evalMode(int argc, char **argv) {
      sds script = sdsempty();
      FILE *fp;
      char buf[1024];
      size_t nread;
      char **argv2;
      int j, got_comma = 0, keys = 0;
  
      /* Load the script from the file, as an sds string. */
      fp = fopen(config.eval,"r");
      if (!fp) {
          fprintf(stderr,
              "Can't open file '%s': %s\n", config.eval, strerror(errno));
          exit(1);
      }
      while((nread = fread(buf,1,sizeof(buf),fp)) != 0) {
          script = sdscatlen(script,buf,nread);
      }
      fclose(fp);
  
      /* Create our argument vector */
      argv2 = zmalloc(sizeof(sds)*(argc+3));
      argv2[0] = sdsnew("EVAL");
      argv2[1] = script;
      for (j = 0; j < argc; j++) {
          if (!got_comma && argv[j][0] == ',' && argv[j][1] == 0) {
              got_comma = 1;
              continue;
          }
          argv2[j+3-got_comma] = sdsnew(argv[j]);
          if (!got_comma) keys++;
      }
      argv2[2] = sdscatprintf(sdsempty(),"%d",keys);
  
      /* Call it */
      return cliSendCommand(argc+3-got_comma, argv2, config.repeat);
  }
  
  /*------------------------------------------------------------------------------
   * Latency and latency history modes
   *--------------------------------------------------------------------------- */
  
  #define LATENCY_SAMPLE_RATE 10 /* milliseconds. */
  #define LATENCY_HISTORY_DEFAULT_INTERVAL 15000 /* milliseconds. */
  static void latencyMode(void) {
      redisReply *reply;
      long long start, latency, min = 0, max = 0, tot = 0, count = 0;
      long long history_interval =
          config.interval ? config.interval/1000 :
                            LATENCY_HISTORY_DEFAULT_INTERVAL;
      double avg;
      long long history_start = mstime();
  
      if (!context) exit(1);
      while(1) {
          start = mstime();
          reply = redisCommand(context,"PING");
          if (reply == NULL) {
              fprintf(stderr,"\nI/O error\n");
              exit(1);
          }
          latency = mstime()-start;
          freeReplyObject(reply);
          count++;
          if (count == 1) {
              min = max = tot = latency;
              avg = (double) latency;
          } else {
              if (latency < min) min = latency;
              if (latency > max) max = latency;
              tot += latency;
              avg = (double) tot/count;
          }
          printf("\x1b[0G\x1b[2Kmin: %lld, max: %lld, avg: %.2f (%lld samples)",
              min, max, avg, count);
          fflush(stdout);
          if (config.latency_history && mstime()-history_start > history_interval)
          {
              printf(" -- %.2f seconds range\n", (float)(mstime()-history_start)/1000);
              history_start = mstime();
              min = max = tot = count = 0;
          }
          usleep(LATENCY_SAMPLE_RATE * 1000);
      }
  }
  
  /*------------------------------------------------------------------------------
   * Slave mode
   *--------------------------------------------------------------------------- */
  
  /* Sends SYNC and reads the number of bytes in the payload. Used both by
   * slaveMode() and getRDB(). */
  unsigned long long sendSync(int fd) {
      /* To start we need to send the SYNC command and return the payload.
       * The hiredis client lib does not understand this part of the protocol
       * and we don't want to mess with its buffers, so everything is performed
       * using direct low-level I/O. */
      char buf[4096], *p;
      ssize_t nread;
  
      /* Send the SYNC command. */
      if (write(fd,"SYNC\r\n",6) != 6) {
          fprintf(stderr,"Error writing to master\n");
          exit(1);
      }
  
      /* Read $<payload>\r\n, making sure to read just up to "\n" */
      p = buf;
      while(1) {
          nread = read(fd,p,1);
          if (nread <= 0) {
              fprintf(stderr,"Error reading bulk length while SYNCing\n");
              exit(1);
          }
          if (*p == '\n' && p != buf) break;
          if (*p != '\n') p++;
      }
      *p = '\0';
      if (buf[0] == '-') {
          printf("SYNC with master failed: %s\n", buf);
          exit(1);
      }
      return strtoull(buf+1,NULL,10);
  }
  
  static void slaveMode(void) {
      int fd = context->fd;
      unsigned long long payload = sendSync(fd);
      char buf[1024];
      int original_output = config.output;
  
      fprintf(stderr,"SYNC with master, discarding %llu "
                     "bytes of bulk transfer...\n", payload);
  
      /* Discard the payload. */
      while(payload) {
          ssize_t nread;
  
          nread = read(fd,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
          if (nread <= 0) {
              fprintf(stderr,"Error reading RDB payload while SYNCing\n");
              exit(1);
          }
          payload -= nread;
      }
      fprintf(stderr,"SYNC done. Logging commands from master.\n");
  
      /* Now we can use hiredis to read the incoming protocol. */
      config.output = OUTPUT_CSV;
      while (cliReadReply(0) == REDIS_OK);
      config.output = original_output;
  }
  
  /*------------------------------------------------------------------------------
   * RDB transfer mode
   *--------------------------------------------------------------------------- */
  
  /* This function implements --rdb, so it uses the replication protocol in order
   * to fetch the RDB file from a remote server. */
  static void getRDB(void) {
      int s = context->fd;
      int fd;
      unsigned long long payload = sendSync(s);
      char buf[4096];
  
      fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n",
          payload, config.rdb_filename);
  
      /* Write to file. */
      if (!strcmp(config.rdb_filename,"-")) {
          fd = STDOUT_FILENO;
      } else {
          fd = open(config.rdb_filename, O_CREAT|O_WRONLY, 0644);
          if (fd == -1) {
              fprintf(stderr, "Error opening '%s': %s\n", config.rdb_filename,
                  strerror(errno));
              exit(1);
          }
      }
  
      while(payload) {
          ssize_t nread, nwritten;
  
          nread = read(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
          if (nread <= 0) {
              fprintf(stderr,"I/O Error reading RDB payload from socket\n");
              exit(1);
          }
          nwritten = write(fd, buf, nread);
          if (nwritten != nread) {
              fprintf(stderr,"Error writing data to file: %s\n",
                  strerror(errno));
              exit(1);
          }
          payload -= nread;
      }
      close(s); /* Close the file descriptor ASAP as fsync() may take time. */
      fsync(fd);
      fprintf(stderr,"Transfer finished with success.\n");
      exit(0);
  }
  
  /*------------------------------------------------------------------------------
   * Bulk import (pipe) mode
   *--------------------------------------------------------------------------- */
  
  static void pipeMode(void) {
      int fd = context->fd;
      long long errors = 0, replies = 0, obuf_len = 0, obuf_pos = 0;
      char ibuf[1024*16], obuf[1024*16]; /* Input and output buffers */
      char aneterr[ANET_ERR_LEN];
      redisReader *reader = redisReaderCreate();
      redisReply *reply;
      int eof = 0; /* True once we consumed all the standard input. */
      int done = 0;
      char magic[20]; /* Special reply we recognize. */
      time_t last_read_time = time(NULL);
  
      srand(time(NULL));
  
      /* Use non blocking I/O. */
      if (anetNonBlock(aneterr,fd) == ANET_ERR) {
          fprintf(stderr, "Can't set the socket in non blocking mode: %s\n",
              aneterr);
          exit(1);
      }
  
      /* Transfer raw protocol and read replies from the server at the same
       * time. */
      while(!done) {
          int mask = AE_READABLE;
  
          if (!eof || obuf_len != 0) mask |= AE_WRITABLE;
          mask = aeWait(fd,mask,1000);
  
          /* Handle the readable state: we can read replies from the server. */
          if (mask & AE_READABLE) {
              ssize_t nread;
  
              /* Read from socket and feed the hiredis reader. */
              do {
                  nread = read(fd,ibuf,sizeof(ibuf));
                  if (nread == -1 && errno != EAGAIN && errno != EINTR) {
                      fprintf(stderr, "Error reading from the server: %s\n",
                          strerror(errno));
                      exit(1);
                  }
                  if (nread > 0) {
                      redisReaderFeed(reader,ibuf,nread);
                      last_read_time = time(NULL);
                  }
              } while(nread > 0);
  
              /* Consume replies. */
              do {
                  if (redisReaderGetReply(reader,(void**)&reply) == REDIS_ERR) {
                      fprintf(stderr, "Error reading replies from server\n");
                      exit(1);
                  }
                  if (reply) {
                      if (reply->type == REDIS_REPLY_ERROR) {
                          fprintf(stderr,"%s\n", reply->str);
                          errors++;
                      } else if (eof && reply->type == REDIS_REPLY_STRING &&
                                        reply->len == 20) {
                          /* Check if this is the reply to our final ECHO
                           * command. If so everything was received
                           * from the server. */
                          if (memcmp(reply->str,magic,20) == 0) {
                              printf("Last reply received from server.\n");
                              done = 1;
                              replies--;
                          }
                      }
                      replies++;
                      freeReplyObject(reply);
                  }
              } while(reply);
          }
  
          /* Handle the writable state: we can send protocol to the server. */
          if (mask & AE_WRITABLE) {
              while(1) {
                  /* Transfer current buffer to server. */
                  if (obuf_len != 0) {
                      ssize_t nwritten = write(fd,obuf+obuf_pos,obuf_len);
  
                      if (nwritten == -1) {
                          if (errno != EAGAIN && errno != EINTR) {
                              fprintf(stderr, "Error writing to the server: %s\n",
                                  strerror(errno));
                              exit(1);
                          } else {
                              nwritten = 0;
                          }
                      }
                      obuf_len -= nwritten;
                      obuf_pos += nwritten;
                      if (obuf_len != 0) break; /* Can't accept more data. */
                  }
                  /* If buffer is empty, load from stdin. */
                  if (obuf_len == 0 && !eof) {
                      ssize_t nread = read(STDIN_FILENO,obuf,sizeof(obuf));
  
                      if (nread == 0) {
                          /* The ECHO sequence starts with a "\r\n" so that if there
                           * is garbage in the protocol we read from stdin, the ECHO
                           * will likely still be properly formatted.
                           * CRLF is ignored by Redis, so it has no effects. */
                          char echo[] =
                          "\r\n*2\r\n$4\r\nECHO\r\n$20\r\n01234567890123456789\r\n";
                          int j;
  
                          eof = 1;
                          /* Everything transferred, so we queue a special
                           * ECHO command that we can match in the replies
                           * to make sure everything was read from the server. */
                          for (j = 0; j < 20; j++)
                              magic[j] = rand() & 0xff;
                          memcpy(echo+21,magic,20);
                          memcpy(obuf,echo,sizeof(echo)-1);
                          obuf_len = sizeof(echo)-1;
                          obuf_pos = 0;
                          printf("All data transferred. Waiting for the last reply...\n");
                      } else if (nread == -1) {
                          fprintf(stderr, "Error reading from stdin: %s\n",
                              strerror(errno));
                          exit(1);
                      } else {
                          obuf_len = nread;
                          obuf_pos = 0;
                      }
                  }
                  if (obuf_len == 0 && eof) break;
              }
          }
  
          /* Handle timeout, that is, we reached EOF, and we are not getting
           * replies from the server for a few seconds, nor the final ECHO is
           * received. */
          if (eof && config.pipe_timeout > 0 &&
              time(NULL)-last_read_time > config.pipe_timeout)
          {
              fprintf(stderr,"No replies for %d seconds: exiting.\n",
                  config.pipe_timeout);
              errors++;
              break;
          }
      }
      redisReaderFree(reader);
      printf("errors: %lld, replies: %lld\n", errors, replies);
      if (errors)
          exit(1);
      else
          exit(0);
  }
  
  /*------------------------------------------------------------------------------
   * Find big keys
   *--------------------------------------------------------------------------- */
  
  #define TYPE_STRING 0
  #define TYPE_LIST   1
  #define TYPE_SET    2
  #define TYPE_HASH   3
  #define TYPE_ZSET   4
  #define TYPE_NONE   5
  
  static redisReply *sendScan(unsigned long long *it) {
      redisReply *reply = redisCommand(context, "SCAN %llu", *it);
  
      /* Handle any error conditions */
      if(reply == NULL) {
          fprintf(stderr, "\nI/O error\n");
          exit(1);
      } else if(reply->type == REDIS_REPLY_ERROR) {
          fprintf(stderr, "SCAN error: %s\n", reply->str);
          exit(1);
      } else if(reply->type != REDIS_REPLY_ARRAY) {
          fprintf(stderr, "Non ARRAY response from SCAN!\n");
          exit(1);
      } else if(reply->elements != 2) {
          fprintf(stderr, "Invalid element count from SCAN!\n");
          exit(1);
      }
  
      /* Validate our types are correct */
      assert(reply->element[0]->type == REDIS_REPLY_STRING);
      assert(reply->element[1]->type == REDIS_REPLY_ARRAY);
  
      /* Update iterator */
      *it = atoi(reply->element[0]->str);
  
      return reply;
  }
  
  static int getDbSize(void) {
      redisReply *reply;
      int size;
  
      reply = redisCommand(context, "DBSIZE");
  
      if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) {
          fprintf(stderr, "Couldn't determine DBSIZE!\n");
          exit(1);
      }
  
      /* Grab the number of keys and free our reply */
      size = reply->integer;
      freeReplyObject(reply);
  
      return size;
  }
  
  static int toIntType(char *key, char *type) {
      if(!strcmp(type, "string")) {
          return TYPE_STRING;
      } else if(!strcmp(type, "list")) {
          return TYPE_LIST;
      } else if(!strcmp(type, "set")) {
          return TYPE_SET;
      } else if(!strcmp(type, "hash")) {
          return TYPE_HASH;
      } else if(!strcmp(type, "zset")) {
          return TYPE_ZSET;
      } else if(!strcmp(type, "none")) {
          return TYPE_NONE;
      } else {
          fprintf(stderr, "Unknown type '%s' for key '%s'\n", type, key);
          exit(1);
      }
  }
  
  static void getKeyTypes(redisReply *keys, int *types) {
      redisReply *reply;
      unsigned int i;
  
      /* Pipeline TYPE commands */
      for(i=0;i<keys->elements;i++) {
          redisAppendCommand(context, "TYPE %s", keys->element[i]->str);
      }
  
      /* Retrieve types */
      for(i=0;i<keys->elements;i++) {
          if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
              fprintf(stderr, "Error getting type for key '%s' (%d: %s)\n",
                  keys->element[i]->str, context->err, context->errstr);
              exit(1);
          } else if(reply->type != REDIS_REPLY_STATUS) {
              fprintf(stderr, "Invalid reply type (%d) for TYPE on key '%s'!\n",
                  reply->type, keys->element[i]->str);
              exit(1);
          }
  
          types[i] = toIntType(keys->element[i]->str, reply->str);
          freeReplyObject(reply);
      }
  }
  
  static void getKeySizes(redisReply *keys, int *types,
                          unsigned long long *sizes)
  {
      redisReply *reply;
      char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"};
      unsigned int i;
  
      /* Pipeline size commands */
      for(i=0;i<keys->elements;i++) {
          /* Skip keys that were deleted */
          if(types[i]==TYPE_NONE)
              continue;
  
          redisAppendCommand(context, "%s %s", sizecmds[types[i]],
              keys->element[i]->str);
      }
  
      /* Retreive sizes */
      for(i=0;i<keys->elements;i++) {
          /* Skip keys that dissapeared between SCAN and TYPE */
          if(types[i] == TYPE_NONE) {
              sizes[i] = 0;
              continue;
          }
  
          /* Retreive size */
          if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
              fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n",
                  keys->element[i]->str, context->err, context->errstr);
              exit(1);
          } else if(reply->type != REDIS_REPLY_INTEGER) {
              /* Theoretically the key could have been removed and
               * added as a different type between TYPE and SIZE */
              fprintf(stderr,
                  "Warning:  %s on '%s' failed (may have changed type)\n",
                   sizecmds[types[i]], keys->element[i]->str);
              sizes[i] = 0;
          } else {
              sizes[i] = reply->integer;
          }
  
          freeReplyObject(reply);
      }
  }
  
  static void findBigKeys(void) {
      unsigned long long biggest[5] = {0}, counts[5] = {0}, totalsize[5] = {0};
      unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0;
      sds maxkeys[5] = {0};
      char *typename[] = {"string","list","set","hash","zset"};
      char *typeunit[] = {"bytes","items","members","fields","members"};
      redisReply *reply, *keys;
      unsigned int arrsize=0, i;
      int type, *types=NULL;
      double pct;
  
      /* Total keys pre scanning */
      total_keys = getDbSize();
  
      /* Status message */
      printf("\n# Scanning the entire keyspace to find biggest keys as well as\n");
      printf("# average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec\n");
      printf("# per 100 SCAN commands (not usually needed).\n\n");
  
      /* New up sds strings to keep track of overall biggest per type */
      for(i=0;i<TYPE_NONE; i++) {
          maxkeys[i] = sdsempty();
          if(!maxkeys[i]) {
              fprintf(stderr, "Failed to allocate memory for largest key names!");
              exit(1);
          }
      }
  
      /* SCAN loop */
      do {
          /* Calculate approximate percentage completion */
          pct = 100 * (double)sampled/total_keys;
  
          /* Grab some keys and point to the keys array */
          reply = sendScan(&it);
          keys  = reply->element[1];
  
          /* Reallocate our type and size array if we need to */
          if(keys->elements > arrsize) {
              types = zrealloc(types, sizeof(int)*keys->elements);
              sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements);
  
              if(!types || !sizes) {
                  fprintf(stderr, "Failed to allocate storage for keys!\n");
                  exit(1);
              }
  
              arrsize = keys->elements;
          }
  
          /* Retreive types and then sizes */
          getKeyTypes(keys, types);
          getKeySizes(keys, types, sizes);
  
          /* Now update our stats */
          for(i=0;i<keys->elements;i++) {
              if((type = types[i]) == TYPE_NONE)
                  continue;
  
              totalsize[type] += sizes[i];
              counts[type]++;
              totlen += keys->element[i]->len;
              sampled++;
  
              if(biggest[type]<sizes[i]) {
                  printf(
                     "[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
                     pct, typename[type], keys->element[i]->str, sizes[i],
                     typeunit[type]);
  
                  /* Keep track of biggest key name for this type */
                  maxkeys[type] = sdscpy(maxkeys[type], keys->element[i]->str);
                  if(!maxkeys[type]) {
                      fprintf(stderr, "Failed to allocate memory for key!\n");
                      exit(1);
                  }
  
                  /* Keep track of the biggest size for this type */
                  biggest[type] = sizes[i];
              }
  
              /* Update overall progress */
              if(sampled % 1000000 == 0) {
                  printf("[%05.2f%%] Sampled %llu keys so far\n", pct, sampled);
              }
          }
  
          /* Sleep if we've been directed to do so */
          if(sampled && (sampled %100) == 0 && config.interval) {
              usleep(config.interval);
          }
  
          freeReplyObject(reply);
      } while(it != 0);
  
      if(types) zfree(types);
      if(sizes) zfree(sizes);
  
      /* We're done */
      printf("\n-------- summary -------\n\n");
  
      printf("Sampled %llu keys in the keyspace!\n", sampled);
      printf("Total key length in bytes is %llu (avg len %.2f)\n\n",
         totlen, totlen ? (double)totlen/sampled : 0);
  
      /* Output the biggest keys we found, for types we did find */
      for(i=0;i<TYPE_NONE;i++) {
          if(sdslen(maxkeys[i])>0) {
              printf("Biggest %6s found '%s' has %llu %s\n", typename[i], maxkeys[i],
                 biggest[i], typeunit[i]);
          }
      }
  
      printf("\n");
  
      for(i=0;i<TYPE_NONE;i++) {
          printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n",
             counts[i], typename[i], totalsize[i], typeunit[i],
             sampled ? 100 * (double)counts[i]/sampled : 0,
             counts[i] ? (double)totalsize[i]/counts[i] : 0);
      }
  
      /* Free sds strings containing max keys */
      for(i=0;i<TYPE_NONE;i++) {
          sdsfree(maxkeys[i]);
      }
  
      /* Success! */
      exit(0);
  }
  
  /*------------------------------------------------------------------------------
   * Stats mode
   *--------------------------------------------------------------------------- */
  
  /* Return the specified INFO field from the INFO command output "info".
   * A new buffer is allocated for the result, that needs to be free'd.
   * If the field is not found NULL is returned. */
  static char *getInfoField(char *info, char *field) {
      char *p = strstr(info,field);
      char *n1, *n2;
      char *result;
  
      if (!p) return NULL;
      p += strlen(field)+1;
      n1 = strchr(p,'\r');
      n2 = strchr(p,',');
      if (n2 && n2 < n1) n1 = n2;
      result = malloc(sizeof(char)*(n1-p)+1);
      memcpy(result,p,(n1-p));
      result[n1-p] = '\0';
      return result;
  }
  
  /* Like the above function but automatically convert the result into
   * a long. On error (missing field) LONG_MIN is returned. */
  static long getLongInfoField(char *info, char *field) {
      char *value = getInfoField(info,field);
      long l;
  
      if (!value) return LONG_MIN;
      l = strtol(value,NULL,10);
      free(value);
      return l;
  }
  
  /* Convert number of bytes into a human readable string of the form:
   * 100B, 2G, 100M, 4K, and so forth. */
  void bytesToHuman(char *s, long long n) {
      double d;
  
      if (n < 0) {
          *s = '-';
          s++;
          n = -n;
      }
      if (n < 1024) {
          /* Bytes */
          sprintf(s,"%lluB",n);
          return;
      } else if (n < (1024*1024)) {
          d = (double)n/(1024);
          sprintf(s,"%.2fK",d);
      } else if (n < (1024LL*1024*1024)) {
          d = (double)n/(1024*1024);
          sprintf(s,"%.2fM",d);
      } else if (n < (1024LL*1024*1024*1024)) {
          d = (double)n/(1024LL*1024*1024);
          sprintf(s,"%.2fG",d);
      }
  }
  
  static void statMode(void) {
      redisReply *reply;
      long aux, requests = 0;
      int i = 0;
  
      while(1) {
          char buf[64];
          int j;
  
          reply = reconnectingInfo();
          if (reply->type == REDIS_REPLY_ERROR) {
              printf("ERROR: %s\n", reply->str);
              exit(1);
          }
  
          if ((i++ % 20) == 0) {
              printf(
  "------- data ------ --------------------- load -------------------- - child -\n"
  "keys       mem      clients blocked requests            connections          \n");
          }
  
          /* Keys */
          aux = 0;
          for (j = 0; j < 20; j++) {
              long k;
  
              sprintf(buf,"db%d:keys",j);
              k = getLongInfoField(reply->str,buf);
              if (k == LONG_MIN) continue;
              aux += k;
          }
          sprintf(buf,"%ld",aux);
          printf("%-11s",buf);
  
          /* Used memory */
          aux = getLongInfoField(reply->str,"used_memory");
          bytesToHuman(buf,aux);
          printf("%-8s",buf);
  
          /* Clients */
          aux = getLongInfoField(reply->str,"connected_clients");
          sprintf(buf,"%ld",aux);
          printf(" %-8s",buf);
  
          /* Blocked (BLPOPPING) Clients */
          aux = getLongInfoField(reply->str,"blocked_clients");
          sprintf(buf,"%ld",aux);
          printf("%-8s",buf);
  
          /* Requets */
          aux = getLongInfoField(reply->str,"total_commands_processed");
          sprintf(buf,"%ld (+%ld)",aux,requests == 0 ? 0 : aux-requests);
          printf("%-19s",buf);
          requests = aux;
  
          /* Connections */
          aux = getLongInfoField(reply->str,"total_connections_received");
          sprintf(buf,"%ld",aux);
          printf(" %-12s",buf);
  
          /* Children */
          aux = getLongInfoField(reply->str,"bgsave_in_progress");
          aux |= getLongInfoField(reply->str,"aof_rewrite_in_progress") << 1;
          switch(aux) {
          case 0: break;
          case 1:
              printf("SAVE");
              break;
          case 2:
              printf("AOF");
              break;
          case 3:
              printf("SAVE+AOF");
              break;
          }
  
          printf("\n");
          freeReplyObject(reply);
          usleep(config.interval);
      }
  }
  
  /*------------------------------------------------------------------------------
   * Scan mode
   *--------------------------------------------------------------------------- */
  
  static void scanMode(void) {
      redisReply *reply;
      unsigned long long cur = 0;
  
      do {
          if (config.pattern)
              reply = redisCommand(context,"SCAN %llu MATCH %s",
                  cur,config.pattern);
          else
              reply = redisCommand(context,"SCAN %llu",cur);
          if (reply == NULL) {
              printf("I/O error\n");
              exit(1);
          } else if (reply->type == REDIS_REPLY_ERROR) {
              printf("ERROR: %s\n", reply->str);
              exit(1);
          } else {
              unsigned int j;
  
              cur = strtoull(reply->element[0]->str,NULL,10);
              for (j = 0; j < reply->element[1]->elements; j++)
                  printf("%s\n", reply->element[1]->element[j]->str);
          }
          freeReplyObject(reply);
      } while(cur != 0);
  
      exit(0);
  }
  
  /*------------------------------------------------------------------------------
   * Intrisic latency mode.
   *
   * Measure max latency of a running process that does not result from
   * syscalls. Basically this software should provide an hint about how much
   * time the kernel leaves the process without a chance to run.
   *--------------------------------------------------------------------------- */
  
  /* This is just some computation the compiler can't optimize out.
   * Should run in less than 100-200 microseconds even using very
   * slow hardware. Runs in less than 10 microseconds in modern HW. */
  unsigned long compute_something_fast(void) {
      unsigned char s[256], i, j, t;
      int count = 1000, k;
      unsigned long output = 0;
  
      for (k = 0; k < 256; k++) s[k] = k;
  
      i = 0;
      j = 0;
      while(count--) {
          i++;
          j = j + s[i];
          t = s[i];
          s[i] = s[j];
          s[j] = t;
          output += s[(s[i]+s[j])&255];
      }
      return output;
  }
  
  static void intrinsicLatencyModeStop(int s) {
      REDIS_NOTUSED(s);
      force_cancel_loop = 1;
  }
  
  static void intrinsicLatencyMode(void) {
      long long test_end, run_time, max_latency = 0, runs = 0;
  
      run_time = config.intrinsic_latency_duration*1000000;
      test_end = ustime() + run_time;
      signal(SIGINT, intrinsicLatencyModeStop);
  
      while(1) {
          long long start, end, latency;
  
          start = ustime();
          compute_something_fast();
          end = ustime();
          latency = end-start;
          runs++;
          if (latency <= 0) continue;
  
          /* Reporting */
          if (latency > max_latency) {
              max_latency = latency;
              printf("Max latency so far: %lld microseconds.\n", max_latency);
          }
  
          double avg_us = (double)run_time/runs;
          double avg_ns = avg_us * 10e3;
          if (force_cancel_loop || end > test_end) {
              printf("\n%lld total runs "
                  "(avg latency: "
                  "%.4f microseconds / %.2f nanoseconds per run).\n",
                  runs, avg_us, avg_ns);
              printf("Worst run took %.0fx longer than the average latency.\n",
                  max_latency / avg_us);
              exit(0);
          }
      }
  }
  
  /*------------------------------------------------------------------------------
   * Program main()
   *--------------------------------------------------------------------------- */
  
  int main(int argc, char **argv) {
      int firstarg;
  
      config.hostip = sdsnew("127.0.0.1");
      config.hostport = 6379;
      config.hostsocket = NULL;
      config.repeat = 1;
      config.interval = 0;
      config.dbnum = 0;
      config.interactive = 0;
      config.shutdown = 0;
      config.monitor_mode = 0;
      config.pubsub_mode = 0;
      config.latency_mode = 0;
      config.latency_history = 0;
      config.cluster_mode = 0;
      config.slave_mode = 0;
      config.getrdb_mode = 0;
      config.stat_mode = 0;
      config.scan_mode = 0;
      config.intrinsic_latency_mode = 0;
      config.pattern = NULL;
      config.rdb_filename = NULL;
      config.pipe_mode = 0;
      config.pipe_timeout = REDIS_CLI_DEFAULT_PIPE_TIMEOUT;
      config.bigkeys = 0;
      config.stdinarg = 0;
      config.auth = NULL;
      config.eval = NULL;
      config.last_cmd_type = -1;
  
      if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL))
          config.output = OUTPUT_RAW;
      else
          config.output = OUTPUT_STANDARD;
      config.mb_delim = sdsnew("\n");
      cliInitHelp();
  
      firstarg = parseOptions(argc,argv);
      argc -= firstarg;
      argv += firstarg;
  
      /* Latency mode */
      if (config.latency_mode) {
          if (cliConnect(0) == REDIS_ERR) exit(1);
          latencyMode();
      }
  
      /* Slave mode */
      if (config.slave_mode) {
          if (cliConnect(0) == REDIS_ERR) exit(1);
          slaveMode();
      }
  
      /* Get RDB mode. */
      if (config.getrdb_mode) {
          if (cliConnect(0) == REDIS_ERR) exit(1);
          getRDB();
      }
  
      /* Pipe mode */
      if (config.pipe_mode) {
          if (cliConnect(0) == REDIS_ERR) exit(1);
          pipeMode();
      }
  
      /* Find big keys */
      if (config.bigkeys) {
          if (cliConnect(0) == REDIS_ERR) exit(1);
          findBigKeys();
      }
  
      /* Stat mode */
      if (config.stat_mode) {
          if (cliConnect(0) == REDIS_ERR) exit(1);
          if (config.interval == 0) config.interval = 1000000;
          statMode();
      }
  
      /* Scan mode */
      if (config.scan_mode) {
          if (cliConnect(0) == REDIS_ERR) exit(1);
          scanMode();
      }
  
      /* Intrinsic latency mode */
      if (config.intrinsic_latency_mode) intrinsicLatencyMode();
  
      /* Start interactive mode when no command is provided */
      if (argc == 0 && !config.eval) {
          /* Note that in repl mode we don't abort on connection error.
           * A new attempt will be performed for every command send. */
          cliConnect(0);
          repl();
      }
  
      /* Otherwise, we have some arguments to execute */
      if (cliConnect(0) != REDIS_OK) exit(1);
      if (config.eval) {
          return evalMode(argc,argv);
      } else {
          return noninteractive(argc,convertToSds(argc,argv));
      }
  }
  