summaryrefslogtreecommitdiff
path: root/src/redis-cli.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/redis-cli.c')
-rw-r--r--src/redis-cli.c228
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);
}
}