]> git.saurik.com Git - redis.git/blobdiff - src/redis-cli.c
Refactor reply buildup for speed on large multi bulk replies
[redis.git] / src / redis-cli.c
index b4a108904962578f7f6c40decb971feec223c858..8b7d0777dc5f7b4f4c7f8a9819d3d53f632dbf29 100644 (file)
@@ -36,6 +36,8 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
 
 #include "anet.h"
 #include "sds.h"
@@ -54,12 +56,13 @@ static struct config {
     int hostport;
     long repeat;
     int dbnum;
-    int argn_from_stdin;
     int interactive;
     int shutdown;
     int monitor_mode;
     int pubsub_mode;
-    int raw_output;
+    int raw_output; /* output mode per command */
+    int tty; /* flag for default output format */
+    char mb_sep;
     char *auth;
     char *historyfile;
 } config;
@@ -67,11 +70,14 @@ static struct config {
 static int cliReadReply(int fd);
 static void usage();
 
-static int cliConnect(void) {
+/* Connect to the client. If force is not zero the connection is performed
+ * even if there is already a connected socket. */
+static int cliConnect(int force) {
     char err[ANET_ERR_LEN];
     static int fd = ANET_ERR;
 
-    if (fd == ANET_ERR) {
+    if (fd == ANET_ERR || force) {
+        if (force) close(fd);
         fd = anetTcpConnect(err,config.hostip,config.hostport);
         if (fd == ANET_ERR) {
             fprintf(stderr, "Could not connect to Redis at %s:%d: %s", config.hostip, config.hostport, err);
@@ -107,7 +113,7 @@ static int cliReadSingleLineReply(int fd, int quiet) {
 
     if (reply == NULL) return 1;
     if (!quiet)
-        printf("%s\n", reply);
+        printf("%s", reply);
     sdsfree(reply);
     return 0;
 }
@@ -134,7 +140,7 @@ static void printStringRepr(char *s, int len) {
         }
         s++;
     }
-    printf("\"\n");
+    printf("\"");
 }
 
 static int cliReadBulkReply(int fd) {
@@ -152,7 +158,7 @@ static int cliReadBulkReply(int fd) {
     reply = zmalloc(bulklen);
     anetRead(fd,reply,bulklen);
     anetRead(fd,crlf,2);
-    if (config.raw_output || !isatty(fileno(stdout))) {
+    if (config.raw_output || !config.tty) {
         if (bulklen && fwrite(reply,bulklen,1,stdout) == 0) {
             zfree(reply);
             return 1;
@@ -169,6 +175,7 @@ static int cliReadBulkReply(int fd) {
 static int cliReadMultiBulkReply(int fd) {
     sds replylen = cliReadLine(fd);
     int elements, c = 1;
+    int retval = 0;
 
     if (replylen == NULL) return 1;
     elements = atoi(replylen);
@@ -181,36 +188,45 @@ static int cliReadMultiBulkReply(int fd) {
         printf("(empty list or set)\n");
     }
     while(elements--) {
-        printf("%d. ", c);
-        if (cliReadReply(fd)) return 1;
+        if (config.tty) printf("%d. ", c);
+        if (cliReadReply(fd)) retval = 1;
+        if (elements) printf("%c",config.mb_sep);
         c++;
     }
-    return 0;
+    return retval;
 }
 
 static int cliReadReply(int fd) {
     char type;
+    int nread;
 
-    if (anetRead(fd,&type,1) <= 0) {
+    if ((nread = anetRead(fd,&type,1)) <= 0) {
         if (config.shutdown) return 0;
-        exit(1);
+        if (config.interactive &&
+            (nread == 0 || (nread == -1 && errno == ECONNRESET)))
+        {
+            return ECONNRESET;
+        } else {
+            printf("I/O error while reading from socket: %s",strerror(errno));
+            exit(1);
+        }
     }
     switch(type) {
     case '-':
-        printf("(error) ");
+        if (config.tty) printf("(error) ");
         cliReadSingleLineReply(fd,0);
         return 1;
     case '+':
         return cliReadSingleLineReply(fd,0);
     case ':':
-        printf("(integer) ");
+        if (config.tty) printf("(integer) ");
         return cliReadSingleLineReply(fd,0);
     case '$':
         return cliReadBulkReply(fd);
     case '*':
         return cliReadMultiBulkReply(fd);
     default:
-        printf("protocol error, got '%c' as reply type byte\n", type);
+        printf("protocol error, got '%c' as reply type byte", type);
         return 1;
     }
 }
@@ -245,7 +261,7 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
     if (!strcasecmp(command,"monitor")) config.monitor_mode = 1;
     if (!strcasecmp(command,"subscribe") ||
         !strcasecmp(command,"psubscribe")) config.pubsub_mode = 1;
-    if ((fd = cliConnect()) == -1) return 1;
+    if ((fd = cliConnect(0)) == -1) return 1;
 
     /* Select db number */
     retval = selectDb(fd);
@@ -273,14 +289,13 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
             printf("Reading messages... (press Ctrl-c to quit)\n");
             while (1) {
                 cliReadReply(fd);
-                printf("\n");
+                printf("\n\n");
             }
         }
 
         retval = cliReadReply(fd);
-        if (retval) {
-            return retval;
-        }
+        if (!config.raw_output && config.tty) printf("\n");
+        if (retval) return retval;
     }
     return 0;
 }
@@ -314,9 +329,16 @@ static int parseOptions(int argc, char **argv) {
             config.auth = argv[i+1];
             i++;
         } else if (!strcmp(argv[i],"-i")) {
-            config.interactive = 1;
+            fprintf(stderr,
+"Starting interactive mode using -i is deprecated. Interactive mode is started\n"
+"by default when redis-cli is executed without a command to execute.\n"
+            );
         } else if (!strcmp(argv[i],"-c")) {
-            config.argn_from_stdin = 1;
+            fprintf(stderr,
+"Reading last argument from standard input using -c is deprecated.\n"
+"When standard input is connected to a pipe or regular file, it is\n"
+"automatically used as last argument.\n"
+            );
         } else if (!strcmp(argv[i],"-v")) {
             printf("redis-cli shipped with Redis verison %s\n", REDIS_VERSION);
             exit(0);
@@ -346,7 +368,7 @@ static sds readArgFromStdin(void) {
 
 static void usage() {
     fprintf(stderr, "usage: redis-cli [-iv] [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 arg3 ... argN\n");
-    fprintf(stderr, "usage: echo \"argN\" | redis-cli -c [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n");
+    fprintf(stderr, "usage: echo \"argN\" | redis-cli [-h host] [-p port] [-a authpw] [-r repeat_times] [-n db_num] cmd arg1 arg2 ... arg(N-1)\n");
     fprintf(stderr, "\nIf a pipe from standard input is detected this data is used as last argument.\n\n");
     fprintf(stderr, "example: cat /etc/passwd | redis-cli set my_passwd\n");
     fprintf(stderr, "example: redis-cli get my_passwd\n");
@@ -372,17 +394,33 @@ static void repl() {
     char *line;
     sds *argv;
 
+    config.interactive = 1;
     while((line = linenoise("redis> ")) != NULL) {
         if (line[0] != '\0') {
             argv = sdssplitargs(line,&argc);
             linenoiseHistoryAdd(line);
             if (config.historyfile) linenoiseHistorySave(config.historyfile);
-            if (argc > 0) {
+            if (argv == NULL) {
+                printf("Invalid argument(s)\n");
+                continue;
+            } else if (argc > 0) {
                 if (strcasecmp(argv[0],"quit") == 0 ||
                     strcasecmp(argv[0],"exit") == 0)
-                        exit(0);
-                else
-                    cliSendCommand(argc, argv, 1);
+                {
+                    exit(0);
+                } else {
+                    int err;
+
+                    if ((err = cliSendCommand(argc, argv, 1)) != 0) {
+                        if (err == ECONNRESET) {
+                            printf("Reconnecting... ");
+                            fflush(stdout);
+                            if (cliConnect(1) == -1) exit(1);
+                            printf("OK\n");
+                            cliSendCommand(argc,argv,1);
+                        }
+                    }
+                }
             }
             /* Free the argument vector */
             for (j = 0; j < argc; j++)
@@ -395,22 +433,37 @@ static void repl() {
     exit(0);
 }
 
+static int noninteractive(int argc, char **argv) {
+    int retval = 0;
+    struct stat s;
+    fstat(fileno(stdin), &s);
+    if (S_ISFIFO(s.st_mode) || S_ISREG(s.st_mode)) { /* pipe, regular file */
+        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;
+}
+
 int main(int argc, char **argv) {
     int firstarg;
-    char **argvcopy;
 
     config.hostip = "127.0.0.1";
     config.hostport = 6379;
     config.repeat = 1;
     config.dbnum = 0;
-    config.argn_from_stdin = 0;
-    config.shutdown = 0;
     config.interactive = 0;
+    config.shutdown = 0;
     config.monitor_mode = 0;
     config.pubsub_mode = 0;
     config.raw_output = 0;
     config.auth = NULL;
     config.historyfile = NULL;
+    config.tty = isatty(fileno(stdout)) || (getenv("FAKETTY") != NULL);
+    config.mb_sep = '\n';
 
     if (getenv("HOME") != NULL) {
         config.historyfile = malloc(256);
@@ -430,13 +483,8 @@ int main(int argc, char **argv) {
         cliSendCommand(2, convertToSds(2, authargv), 1);
     }
 
-    if (argc == 0 || config.interactive == 1) repl();
-
-    argvcopy = convertToSds(argc+1, argv);
-    if (config.argn_from_stdin) {
-        sds lastarg = readArgFromStdin();
-        argvcopy[argc] = lastarg;
-        argc++;
-    }
-    return cliSendCommand(argc, argvcopy, config.repeat);
+    /* Start interactive mode when no command is provided */
+    if (argc == 0) repl();
+    /* Otherwise, we have some arguments to execute */
+    return noninteractive(argc,convertToSds(argc,argv));
 }