diff options
Diffstat (limited to 'src/redis-cli.c')
-rw-r--r-- | src/redis-cli.c | 228 |
1 files changed, 160 insertions, 68 deletions
diff --git a/src/redis-cli.c b/src/redis-cli.c index bbbe6d6ec..1cd1ad85c 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -70,6 +70,7 @@ #define OUTPUT_RAW 1 #define OUTPUT_CSV 2 #define OUTPUT_JSON 3 +#define OUTPUT_QUOTED_JSON 4 #define REDIS_CLI_KEEPALIVE_INTERVAL 15 /* seconds */ #define REDIS_CLI_DEFAULT_PIPE_TIMEOUT 30 /* seconds */ #define REDIS_CLI_HISTFILE_ENV "REDISCLI_HISTFILE" @@ -155,6 +156,9 @@ #define CC_FORCE (1<<0) /* Re-connect if already connected. */ #define CC_QUIET (1<<1) /* Don't log connecting errors. */ +/* DNS lookup */ +#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46 */ + /* --latency-dist palettes. */ int spectrum_palette_color_size = 19; int spectrum_palette_color[] = {0,233,234,235,237,239,241,243,245,247,144,143,142,184,226,214,208,202,196}; @@ -281,7 +285,7 @@ static void usage(int err); static void slaveMode(void); char *redisGitSHA1(void); char *redisGitDirty(void); -static int cliConnect(int force); +static int cliConnect(int flags); static char *getInfoField(char *info, char *field); static long getLongInfoField(char *info, char *field); @@ -799,7 +803,12 @@ static void cliInitHelp(void) { redisReply *commandTable; dict *groups; - if (cliConnect(CC_QUIET) == REDIS_ERR) return; + if (cliConnect(CC_QUIET) == REDIS_ERR) { + /* Can not connect to the server, but we still want to provide + * help, generate it only from the old help.h data instead. */ + cliOldInitHelp(); + return; + } commandTable = redisCommand(context, "COMMAND DOCS"); if (commandTable == NULL || commandTable->type == REDIS_REPLY_ERROR) { /* New COMMAND DOCS subcommand not supported - generate help from old help.h data instead. */ @@ -809,7 +818,7 @@ static void cliInitHelp(void) { return; }; if (commandTable->type != REDIS_REPLY_MAP && commandTable->type != REDIS_REPLY_ARRAY) return; - + /* Scan the array reported by COMMAND DOCS and fill in the entries */ helpEntriesLen = cliCountCommands(commandTable); helpEntries = zmalloc(sizeof(helpEntry)*helpEntriesLen); @@ -869,6 +878,12 @@ static void cliOutputHelp(int argc, char **argv) { group = argv[0]+1; } + if (helpEntries == NULL) { + /* Initialize the help using the results of the COMMAND command. + * In case we are using redis-cli help XXX, we need to init it. */ + cliInitHelp(); + } + assert(argc > 0); for (i = 0; i < helpEntriesLen; i++) { entry = &helpEntries[i]; @@ -1486,16 +1501,39 @@ static sds cliFormatReplyCSV(redisReply *r) { return out; } -static sds cliFormatReplyJson(sds out, redisReply *r) { +/* Append specified buffer to out and return it, using required JSON output + * mode. */ +static sds jsonStringOutput(sds out, const char *p, int len, int mode) { + if (mode == OUTPUT_JSON) { + return escapeJsonString(out, p, len); + } else if (mode == OUTPUT_QUOTED_JSON) { + /* Need to double-quote backslashes */ + sds tmp = sdscatrepr(sdsempty(), p, len); + int tmplen = sdslen(tmp); + char *n = tmp; + while (tmplen--) { + if (*n == '\\') out = sdscatlen(out, "\\\\", 2); + else out = sdscatlen(out, n, 1); + n++; + } + + sdsfree(tmp); + return out; + } else { + assert(0); + } +} + +static sds cliFormatReplyJson(sds out, redisReply *r, int mode) { unsigned int i; switch (r->type) { case REDIS_REPLY_ERROR: out = sdscat(out,"error:"); - out = sdscatrepr(out,r->str,strlen(r->str)); + out = jsonStringOutput(out,r->str,strlen(r->str),mode); break; case REDIS_REPLY_STATUS: - out = sdscatrepr(out,r->str,r->len); + out = jsonStringOutput(out,r->str,r->len,mode); break; case REDIS_REPLY_INTEGER: out = sdscatprintf(out,"%lld",r->integer); @@ -1505,7 +1543,7 @@ static sds cliFormatReplyJson(sds out, redisReply *r) { break; case REDIS_REPLY_STRING: case REDIS_REPLY_VERB: - out = sdscatrepr(out,r->str,r->len); + out = jsonStringOutput(out,r->str,r->len,mode); break; case REDIS_REPLY_NIL: out = sdscat(out,"null"); @@ -1518,7 +1556,7 @@ static sds cliFormatReplyJson(sds out, redisReply *r) { case REDIS_REPLY_PUSH: out = sdscat(out,"["); for (i = 0; i < r->elements; i++ ) { - out = cliFormatReplyJson(out, r->element[i]); + out = cliFormatReplyJson(out,r->element[i],mode); if (i != r->elements-1) out = sdscat(out,","); } out = sdscat(out,"]"); @@ -1527,20 +1565,25 @@ static sds cliFormatReplyJson(sds out, redisReply *r) { out = sdscat(out,"{"); for (i = 0; i < r->elements; i += 2) { redisReply *key = r->element[i]; - if (key->type == REDIS_REPLY_STATUS || + if (key->type == REDIS_REPLY_ERROR || + key->type == REDIS_REPLY_STATUS || key->type == REDIS_REPLY_STRING || - key->type == REDIS_REPLY_VERB) { - out = cliFormatReplyJson(out, key); + key->type == REDIS_REPLY_VERB) + { + out = cliFormatReplyJson(out,key,mode); } else { - /* According to JSON spec, JSON map keys must be strings, */ - /* and in RESP3, they can be other types. */ - sds tmp = cliFormatReplyJson(sdsempty(), key); - out = sdscatrepr(out,tmp,sdslen(tmp)); - sdsfree(tmp); + /* According to JSON spec, JSON map keys must be strings, + * and in RESP3, they can be other types. + * The first one(cliFormatReplyJson) is to convert non string type to string + * The Second one(escapeJsonString) is to escape the converted string */ + sds keystr = cliFormatReplyJson(sdsempty(),key,mode); + if (keystr[0] == '"') out = sdscatsds(out,keystr); + else out = sdscatfmt(out,"\"%S\"",keystr); + sdsfree(keystr); } out = sdscat(out,":"); - out = cliFormatReplyJson(out, r->element[i+1]); + out = cliFormatReplyJson(out,r->element[i+1],mode); if (i != r->elements-2) out = sdscat(out,","); } out = sdscat(out,"}"); @@ -1566,8 +1609,8 @@ static sds cliFormatReply(redisReply *reply, int mode, int verbatim) { } else if (mode == OUTPUT_CSV) { out = cliFormatReplyCSV(reply); out = sdscatlen(out, "\n", 1); - } else if (mode == OUTPUT_JSON) { - out = cliFormatReplyJson(sdsempty(), reply); + } else if (mode == OUTPUT_JSON || mode == OUTPUT_QUOTED_JSON) { + out = cliFormatReplyJson(sdsempty(), reply, mode); out = sdscatlen(out, "\n", 1); } else { fprintf(stderr, "Error: Unknown output encoding %d\n", mode); @@ -1684,12 +1727,6 @@ static int cliSendCommand(int argc, char **argv, long repeat) { size_t *argvlen; int j, output_raw; - if (!config.eval_ldb && /* In debugging mode, let's pass "help" to Redis. */ - (!strcasecmp(command,"help") || !strcasecmp(command,"?"))) { - cliOutputHelp(--argc, ++argv); - return REDIS_OK; - } - if (context == NULL) return REDIS_ERR; output_raw = 0; @@ -1953,11 +1990,17 @@ static int parseOptions(int argc, char **argv) { } else if (!strcmp(argv[i],"--csv")) { config.output = OUTPUT_CSV; } else if (!strcmp(argv[i],"--json")) { - /* Not overwrite explicit value by -3*/ + /* Not overwrite explicit value by -3 */ if (config.resp3 == 0) { config.resp3 = 2; } config.output = OUTPUT_JSON; + } else if (!strcmp(argv[i],"--quoted-json")) { + /* Not overwrite explicit value by -3*/ + if (config.resp3 == 0) { + config.resp3 = 2; + } + config.output = OUTPUT_QUOTED_JSON; } else if (!strcmp(argv[i],"--latency")) { config.latency_mode = 1; } else if (!strcmp(argv[i],"--latency-dist")) { @@ -2289,6 +2332,7 @@ static void usage(int err) { " --quoted-input Force input to be handled as quoted strings.\n" " --csv Output in CSV format.\n" " --json Output in JSON format (default RESP3, use -2 if you want to use with RESP2).\n" +" --quoted-json Same as --json, but produce ASCII-safe quoted strings, not Unicode.\n" " --show-pushes <yn> Whether to print RESP3 PUSH messages. Enabled by default when\n" " STDOUT is a tty but can be overridden with --show-pushes no.\n" " --stat Print rolling stats about server: mem, clients, ...\n",version); @@ -2384,6 +2428,17 @@ static int confirmWithYes(char *msg, int ignore_force) { } static int issueCommandRepeat(int argc, char **argv, long repeat) { + /* In Lua debugging mode, we want to pass the "help" to Redis to get + * it's own HELP message, rather than handle it by the CLI, see ldbRepl. + * + * For the normal Redis HELP, we can process it without a connection. */ + if (!config.eval_ldb && + (!strcasecmp(argv[0],"help") || !strcasecmp(argv[0],"?"))) + { + cliOutputHelp(--argc, ++argv); + return REDIS_OK; + } + while (1) { if (config.cluster_reissue_command || context == NULL || context->err == REDIS_ERR_IO || context->err == REDIS_ERR_EOF) @@ -2403,6 +2458,8 @@ static int issueCommandRepeat(int argc, char **argv, long repeat) { } if (cliSendCommand(argc,argv,repeat) != REDIS_OK) { cliPrintContextError(); + redisFree(context); + context = NULL; return REDIS_ERR; } @@ -2540,8 +2597,13 @@ static void repl(void) { int argc; sds *argv; - /* Initialize the help using the results of the COMMAND command. */ - cliInitHelp(); + /* There is no need to initialize redis HELP when we are in lua debugger mode. + * It has its own HELP and commands (COMMAND or COMMAND DOCS will fail and got nothing). + * We will initialize the redis HELP after the Lua debugging session ended.*/ + if (!config.eval_ldb) { + /* Initialize the help using the results of the COMMAND command. */ + cliInitHelp(); + } config.interactive = 1; linenoiseSetMultiLine(1); @@ -2643,6 +2705,7 @@ static void repl(void) { printf("\n(Lua debugging session ended%s)\n\n", config.eval_ldb_sync ? "" : " -- dataset changes rolled back"); + cliInitHelp(); } elapsed = mstime()-start_time; @@ -2695,7 +2758,7 @@ static int noninteractive(int argc, char **argv) { retval = issueCommand(argc, sds_args); sdsfreesplitres(sds_args, argc); - return retval; + return retval == REDIS_OK ? 0 : 1; } /*------------------------------------------------------------------------------ @@ -2782,7 +2845,7 @@ static int evalMode(int argc, char **argv) { break; /* Return to the caller. */ } } - return retval; + return retval == REDIS_OK ? 0 : 1; } /*------------------------------------------------------------------------------ @@ -3915,7 +3978,10 @@ static int clusterManagerSetSlot(clusterManagerNode *node1, slot, status, (char *) node2->name); if (err != NULL) *err = NULL; - if (!reply) return 0; + if (!reply) { + if (err) *err = zstrdup("CLUSTER SETSLOT failed to run"); + return 0; + } int success = 1; if (reply->type == REDIS_REPLY_ERROR) { success = 0; @@ -4365,33 +4431,41 @@ static int clusterManagerMoveSlot(clusterManagerNode *source, pipeline, print_dots, err); if (!(opts & CLUSTER_MANAGER_OPT_QUIET)) printf("\n"); if (!success) return 0; - /* Set the new node as the owner of the slot in all the known nodes. */ if (!option_cold) { + /* Set the new node as the owner of the slot in all the known nodes. + * + * We inform the target node first. It will propagate the information to + * the rest of the cluster. + * + * If we inform any other node first, it can happen that the target node + * crashes before it is set as the new owner and then the slot is left + * without an owner which results in redirect loops. See issue #7116. */ + success = clusterManagerSetSlot(target, target, slot, "node", err); + if (!success) return 0; + + /* Inform the source node. If the source node has just lost its last + * slot and the target node has already informed the source node, the + * source node has turned itself into a replica. This is not an error in + * this scenario so we ignore it. See issue #9223. */ + success = clusterManagerSetSlot(source, target, slot, "node", err); + const char *acceptable = "ERR Please use SETSLOT only with masters."; + if (!success && err && !strncmp(*err, acceptable, strlen(acceptable))) { + zfree(*err); + *err = NULL; + } else if (!success && err) { + return 0; + } + + /* We also inform the other nodes to avoid redirects in case the target + * node is slow to propagate the change to the entire cluster. */ listIter li; listNode *ln; listRewind(cluster_manager.nodes, &li); while ((ln = listNext(&li)) != NULL) { clusterManagerNode *n = ln->value; + if (n == target || n == source) continue; /* already done */ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue; - redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER " - "SETSLOT %d %s %s", - slot, "node", - target->name); - success = (r != NULL); - if (!success) { - if (err) *err = zstrdup("CLUSTER SETSLOT failed to run"); - return 0; - } - if (r->type == REDIS_REPLY_ERROR) { - success = 0; - if (err != NULL) { - *err = zmalloc((r->len + 1) * sizeof(char)); - strcpy(*err, r->str); - } else { - CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, r->str); - } - } - freeReplyObject(r); + success = clusterManagerSetSlot(n, target, slot, "node", err); if (!success) return 0; } } @@ -6235,16 +6309,26 @@ assign_replicas: clusterManagerLogInfo(">>> Sending CLUSTER MEET messages to join " "the cluster\n"); clusterManagerNode *first = NULL; + char first_ip[NET_IP_STR_LEN]; /* first->ip may be a hostname */ listRewind(cluster_manager.nodes, &li); while ((ln = listNext(&li)) != NULL) { clusterManagerNode *node = ln->value; if (first == NULL) { first = node; + /* Although hiredis supports connecting to a hostname, CLUSTER + * MEET requires an IP address, so we do a DNS lookup here. */ + if (anetResolve(NULL, first->ip, first_ip, sizeof(first_ip), ANET_NONE) + == ANET_ERR) + { + fprintf(stderr, "Invalid IP address or hostname specified: %s\n", first->ip); + success = 0; + goto cleanup; + } continue; } redisReply *reply = NULL; reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d", - first->ip, first->port); + first_ip, first->port); int is_err = 0; if (reply != NULL) { if ((is_err = reply->type == REDIS_REPLY_ERROR)) @@ -6416,8 +6500,15 @@ static int clusterManagerCommandAddNode(int argc, char **argv) { // Send CLUSTER MEET command to the new node clusterManagerLogInfo(">>> Send CLUSTER MEET to node %s:%d to make it " "join the cluster.\n", ip, port); + /* CLUSTER MEET requires an IP address, so we do a DNS lookup here. */ + char first_ip[NET_IP_STR_LEN]; + if (anetResolve(NULL, first->ip, first_ip, sizeof(first_ip), ANET_NONE) == ANET_ERR) { + fprintf(stderr, "Invalid IP address or hostname specified: %s\n", first->ip); + success = 0; + goto cleanup; + } reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER MEET %s %d", - first->ip, first->port); + first_ip, first->port); if (!(success = clusterManagerCheckRedisReply(new_node, reply, NULL))) goto cleanup; @@ -7252,14 +7343,14 @@ static int clusterManagerCommandHelp(int argc, char **argv) { int commands_count = sizeof(clusterManagerCommands) / sizeof(clusterManagerCommandDef); int i = 0, j; - fprintf(stderr, "Cluster Manager Commands:\n"); + fprintf(stdout, "Cluster Manager Commands:\n"); int padding = 15; for (; i < commands_count; i++) { clusterManagerCommandDef *def = &(clusterManagerCommands[i]); int namelen = strlen(def->name), padlen = padding - namelen; - fprintf(stderr, " %s", def->name); - for (j = 0; j < padlen; j++) fprintf(stderr, " "); - fprintf(stderr, "%s\n", (def->args ? def->args : "")); + fprintf(stdout, " %s", def->name); + for (j = 0; j < padlen; j++) fprintf(stdout, " "); + fprintf(stdout, "%s\n", (def->args ? def->args : "")); if (def->options != NULL) { int optslen = strlen(def->options); char *p = def->options, *eos = p + optslen; @@ -7269,18 +7360,18 @@ static int clusterManagerCommandHelp(int argc, char **argv) { char buf[255]; memcpy(buf, p, deflen); buf[deflen] = '\0'; - for (j = 0; j < padding; j++) fprintf(stderr, " "); - fprintf(stderr, " --cluster-%s\n", buf); + for (j = 0; j < padding; j++) fprintf(stdout, " "); + fprintf(stdout, " --cluster-%s\n", buf); p = comma + 1; if (p >= eos) break; } if (p < eos) { - for (j = 0; j < padding; j++) fprintf(stderr, " "); - fprintf(stderr, " --cluster-%s\n", p); + for (j = 0; j < padding; j++) fprintf(stdout, " "); + fprintf(stdout, " --cluster-%s\n", p); } } } - fprintf(stderr, "\nFor check, fix, reshard, del-node, set-timeout, " + fprintf(stdout, "\nFor check, fix, reshard, del-node, set-timeout, " "info, rebalance, call, import, backup you " "can specify the host and port of any working node in " "the cluster.\n"); @@ -7288,16 +7379,16 @@ static int clusterManagerCommandHelp(int argc, char **argv) { int options_count = sizeof(clusterManagerOptions) / sizeof(clusterManagerOptionDef); i = 0; - fprintf(stderr, "\nCluster Manager Options:\n"); + fprintf(stdout, "\nCluster Manager Options:\n"); for (; i < options_count; i++) { clusterManagerOptionDef *def = &(clusterManagerOptions[i]); int namelen = strlen(def->name), padlen = padding - namelen; - fprintf(stderr, " %s", def->name); - for (j = 0; j < padlen; j++) fprintf(stderr, " "); - fprintf(stderr, "%s\n", def->desc); + fprintf(stdout, " %s", def->name); + for (j = 0; j < padlen; j++) fprintf(stdout, " "); + fprintf(stdout, "%s\n", def->desc); } - fprintf(stderr, "\n"); + fprintf(stdout, "\n"); return 0; } @@ -8969,10 +9060,11 @@ int main(int argc, char **argv) { } /* Otherwise, we have some arguments to execute */ - if (cliConnect(0) != REDIS_OK) exit(1); if (config.eval) { + if (cliConnect(0) != REDIS_OK) exit(1); return evalMode(argc,argv); } else { + cliConnect(CC_QUIET); return noninteractive(argc,argv); } } |